[
  {
    "path": ".devcontainer/.zshrc",
    "content": "export ZSH=$HOME/.oh-my-zsh\n\nZSH_THEME=\"cloud\"\n\nplugins=(\n    git\n    zsh-autosuggestions\n)\n\nsource $ZSH/oh-my-zsh.sh\n\nsource /usr/share/doc/fzf/examples/key-bindings.zsh\nsource /usr/share/doc/fzf/examples/completion.zsh\n\nalias ll='ls -alF'\nalias hammer-clean='go clean -testcache'\nalias hammer-test-n-cover='gotest -coverpkg=./... -coverprofile=coverage.out ./... && go tool cover -func coverage.out'\n\nexport PATH=\"$PATH:/go/bin\"\n"
  },
  {
    "path": ".devcontainer/Dockerfile.dev",
    "content": "FROM golang:1.18.1\n\nWORKDIR /workspace\n\nCOPY go.mod ./\nCOPY go.sum ./\n \nENV GOPATH /go\nENV GOBIN /go/bin\n\nENV LC_ALL=C.UTF-8\nENV LANG=C.UTF-8\nENV SHELL /bin/zsh\n\nRUN apt update && apt install -y git zsh vim fzf locales gcc musl-dev curl iputils-ping telnet graphviz bc jq && rm -rf /var/lib/apt/lists/*\nRUN sh -c \"$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)\"\nRUN git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting\nRUN git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions\nRUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.45.2\nCOPY .devcontainer/.zshrc /root/.zshrc\n\nRUN go install -v golang.org/x/tools/gopls@v0.8.3\nRUN go install -v github.com/rogpeppe/godef@v1.1.2\nRUN go install -v github.com/rakyll/gotest@v0.0.6\nRUN go install -v github.com/ramya-rao-a/go-outline@1.0.0\nRUN go install -v github.com/go-delve/delve/cmd/dlv@v1.8.1\nRUN go install -v golang.org/x/perf/cmd/benchstat@v0.0.0-20221222172245-91a04616dc65\nRUN go mod download\n\nCMD [ \"zsh\" ]"
  },
  {
    "path": ".devcontainer/devcontainer.json",
    "content": "{\n\t\"name\": \"Ddosify Open Source\",\n\t\"build\": {\n\t\t\"dockerfile\": \"Dockerfile.dev\",\n\t\t\"context\": \"../\"\n\t},\n\t\"runArgs\": [\n\t\t\"-v\",\n\t\t\"${env:HOME}${env:USERPROFILE}/.ssh:/root/.ssh-localhost:ro\",\n\t\t\"--cap-add=SYS_PTRACE\",\n\t\t\"--security-opt\",\n\t\t\"seccomp=unconfined\",\n\t\t\"--ipc\",\n\t\t\"host\",\n\t\t\"--hostname\",\n\t\t\"ddosify\"\n\t],\n\t\"customizations\": {\n\t\t\"vscode\": {\n\t\t\t\"settings\": {\n\t\t\t\t\"terminal.integrated.defaultProfile.linux\": \"zsh\",\n\t\t\t\t\"go.useLanguageServer\": true,\n\t\t\t\t\"go.gopath\": \"/go\",\n\t\t\t\t\"go.goroot\": \"/usr/local/go\",\n\t\t\t\t\"go.toolsGopath\": \"/go\",\n\t\t\t\t\"go.lintTool\": \"golangci-lint\",\n\t\t\t\t\"go.lintFlags\": [\n\t\t\t\t\t\"--config=${workspaceFolder}/.golangci.yml\",\n\t\t\t\t\t\"--fast\"\n\t\t\t\t],\n\t\t\t\t\"files.eol\": \"\\n\"\n\t\t\t},\n\t\t\t\"extensions\": [\n\t\t\t\t\"golang.Go\",\n\t\t\t\t\"eamodio.gitlens\",\n\t\t\t\t\"premparihar.gotestexplorer\",\n\t\t\t\t\"GitHub.copilot\",\n\t\t\t\t\"GitHub.copilot-labs\"\n\t\t\t]\n\t\t}\n\t},\n\t\"postCreateCommand\": \"mkdir -p ~/.ssh && cp -r ~/.ssh-localhost/* ~/.ssh && chmod 700 ~/.ssh && chmod 600 ~/.ssh/*\",\n\t\"mounts\": [\n\t\t// \"source=${env:HOME}/.zsh_history,target=/root/.zsh_history,type=bind,consistency=cached\"\n\t\t// \"source=${env:HOME}/.bash_history,target=/root/.zsh_history,type=bind,consistency=cached\"\n\t]\n}"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: ''\n---\n### Describe the bug\n\n<!-- A clear and concise description of what the bug is. -->\n\n### To Reproduce\n\nSteps to reproduce the behavior:\n\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n### Expected behavior\n\n<!-- A clear and concise description of what you expected to happen. -->\n\n### Screenshots\n\n<!-- If applicable, add screenshots to help explain your problem. -->\n\n### System (please complete the following information):\n\n- OS: [e.g. MacOS]\n- Anteon Version [e.g. v0.15.1]\n\n### Additional context\n\n<!-- Add any other context about the problem here. -->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: enhancement\nassignees: ''\n---\n**Is your feature request related to a problem? Please describe.**\n\n<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->\n\n**Describe the solution you'd like**\n\n<!-- A clear and concise description of what you want to happen. -->\n\n**Describe alternatives you've considered**\n\n<!-- A clear and concise description of any alternative solutions or features you've considered. -->\n\n**Additional context**\n\n<!-- Add any other context or screenshots about the feature request here. -->\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where the package manifests are located.\n# Please see the documentation for all configuration options:\n# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file\n\nversion: 2\nupdates:\n  - package-ecosystem: \"gomod\" # See documentation for possible values\n    directory: \"/\" # Location of package manifests\n    schedule:\n      interval: \"daily\"\n  - package-ecosystem: \"docker\" # See documentation for possible values\n    directory: \"/\" # Location of package manifests\n    schedule:\n      interval: \"daily\"\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "## Description\n\n<!-- Please provide a brief and concise description of the changes in this pull request, including the problem being solved or the feature being added. -->\n\n<!-- Please list any related issues or reference them. -->\n\n## Screenshots\n\n<!-- If applicable, please provide screenshots, video or GIF to help demonstrate the changes. -->\n\n## Type of Changes\n\n<!-- Please check the relevant boxes by putting an \"x\" in the appropriate box. -->\n\n- [ ] Bug fix (non-breaking change which fixes an issue)\n- [ ] New feature (non-breaking change which adds functionality)\n- [ ] Breaking change (fix or feature that would cause existing functionality to change)\n- [ ] This change requires a documentation update\n\n## Checklist\n\n<!-- Please go through this checklist and make sure all applicable tasks have been done. -->\n\n- [ ] I have read the [CONTRIBUTING.md](../CONTRIBUTING.md) document.\n- [ ] My code follows the code style of this project.\n- [ ] I have added tests to cover my changes.\n- [ ] All new and existing tests passed.\n- [ ] I have updated the [README.md](../README.md) as necessary if there are changes.\n- [ ] I have tested the changes on my local machine before submitting the PR.\n"
  },
  {
    "path": ".github/workflows/coverage.yml",
    "content": "name: Coverage\n\non:\n  push:\n    branches:\n      - master\n      - develop\n  pull_request:\n    branches:\n      - master\n      - develop\n\njobs:\n\n  coverage:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v2\n\n    - name: Set up Go\n      uses: actions/setup-go@v2\n      with:\n        go-version: 1.18.x\n\n    - name: Test\n      run: cd ddosify_engine && go test -coverpkg=./... -coverprofile=coverage.txt -parallel 1 -covermode=atomic -short ./... && go tool cover -func coverage.txt\n  \n    - name: Upload reports to codecov\n      run: |\n        curl -Os https://uploader.codecov.io/latest/linux/codecov\n        chmod +x codecov\n        ./codecov -t ${CODECOV_TOKEN} -f coverage.txt\n      env:\n        CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/docs.yml",
    "content": "name: Documentation\n\non:\n  push:\n    branches:\n      - master\n      - develop\n  pull_request:\n    branches:\n      - master\n      - develop\n\njobs:\n  link-checker:\n    name: Check links\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout the repository\n        uses: actions/checkout@v4\n\n      - name: Check the links\n        uses: lycheeverse/lychee-action@v1\n        with:\n          args: --max-concurrency 1 -v *.md **/*.md\n          fail: true\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release Ddosify\n\non:\n  push:\n    tags:\n      - '*'\n\npermissions:\n  contents: write\n\njobs:\n  release:\n    runs-on: ubuntu-latest\n    env:\n      DOCKER_CLI_EXPERIMENTAL: \"enabled\"\n    steps:\n      -\n        name: Checkout\n        uses: actions/checkout@v2\n        with:\n          fetch-depth: 0\n      -\n        name: QEMU\n        uses: docker/setup-qemu-action@v1\n      -\n        name: Docker Buildx\n        uses: docker/setup-buildx-action@v1\n      -\n        name: Set up Go\n        uses: actions/setup-go@v2\n        with:\n          go-version: 1.18\n      -\n        name: Docker Hub Login\n        uses: docker/login-action@v1\n        with:\n          username: ${{ secrets.DOCKER_USERNAME }}\n          password: ${{ secrets.DOCKER_PASSWORD }}\n      -\n        name: Run GoReleaser\n        uses: goreleaser/goreleaser-action@v2\n        with:\n          distribution: goreleaser\n          version: latest\n          args: release --clean\n        env:\n          GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Test\n\non:\n  push:\n    branches:\n      - master\n      - develop\n  pull_request:\n    branches:\n      - master\n      - develop\n\njobs:\n\n  test:\n    strategy:\n      matrix:\n        go-version: [1.18.x, 1.19.x]\n        os: [ubuntu-latest, macos-latest, windows-latest]\n    runs-on: ${{ matrix.os }}\n    steps:\n    - uses: actions/checkout@v2\n\n    - name: Set up Go\n      uses: actions/setup-go@v2\n      with:\n        go-version: ${{ matrix.go-version }}\n\n    - name: Build\n      run: cd ddosify_engine && go build -race ./...\n\n    - name: Test\n      run: cd ddosify_engine && go test -parallel 1 -short ./..."
  },
  {
    "path": ".gitignore",
    "content": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\n\n# Test binary, built with `go test -c`\n*.test\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n\n# Dependency directories (remove the comment below to include it)\n# vendor/\n__debug*\ncoverage.html\nmain\n\ndist/\nddosify\n.vscode\n.idea/\n.DS_Store\n\n"
  },
  {
    "path": ".lycheeignore",
    "content": "https://getanteon.com/endpoint_1\nhttps://getanteon.com/endpoint_2\nhttp://localhost:8014/\nhttps://gurubase.io/"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, religion, or sexual identity\nand orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n- Demonstrating empathy and kindness toward other people\n- Being respectful of differing opinions, viewpoints, and experiences\n- Giving and gracefully accepting constructive feedback\n- Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n- Focusing on what is best not just for us as individuals, but for the\n  overall community\n\nExamples of unacceptable behavior include:\n\n- The use of sexualized language or imagery, and sexual attention or\n  advances of any kind\n- Trolling, insulting or derogatory comments, and personal or political attacks\n- Public or private harassment\n- Publishing others' private information, such as a physical or email\n  address, without their explicit permission\n- Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\ninfo@getanteon.com.\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series\nof actions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or\npermanent ban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior, harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within\nthe community.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.0, available at\nhttps://www.contributor-covenant.org/version/2/0/code_of_conduct.html.\n\nCommunity Impact Guidelines were inspired by [Mozilla's code of conduct\nenforcement ladder](https://github.com/mozilla/diversity).\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see the FAQ at\nhttps://www.contributor-covenant.org/faq. Translations are available at\nhttps://www.contributor-covenant.org/translations.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to Anteon 🐝\n\nThank you for your interest in contributing to [Anteon](https://github.com/getanteon/anteon)!\n\nIn this guide, we'll provide you with the necessary information and guidelines to help you get started.\n\n## 🚀 Getting Started\n\n1. Fork [Anteon](https://github.com/getanteon/anteon) on GitHub.\n2. Clone your fork to your local machine:\n\n```bash\ngit clone git@github.com:<YOUR_USERNAME>/anteon.git\n```\n\n3. Add the Anteon repository as an upstream remote:\n\n```bash\ngit remote add upstream https://github.com/getanteon/anteon\n```\n\n4. We follow Gitflow branching model. Create a feature branch from the `develop` branch:\n\n```bash\ngit checkout -b feature/FEATURE_NAME develop\n```\n\n5. Set up your development environment.\n\n- Go programming language (`Version >= 1.18`) is required to build and run Anteon. You can find the installation instructions [here](https://go.dev/doc/install).\n\n- We also provide [Dockerfile](./.devcontainer/Dockerfile.dev) and Visual Studio Code (VS Code) [remote container configuration](./.devcontainer/devcontainer.json) for development. More information about VS Code remote container can be found [here](https://code.visualstudio.com/docs/devcontainers/containers).\n\n6. Run the `main.go` file:\n\n```bash\ngo run main.go\n```\n\n## 💻 Submitting Changes\n\nBefore submitting a [pull request (PR)](https://github.com/getanteon/anteon/pulls) with your changes, please make sure you follow these guidelines:\n\n1. Ensure your code is well-formatted and follows the established coding style for this project (e.g., proper indentation, naming conventions, etc.).\n2. Write unit tests for any new functionality or bug fixes. Ensure that all tests pass before submitting your PR.\n3. Update the [README.md](./README.md) file according to your changes.\n\n4. Keep your PRs focused and as small as possible. If you have multiple unrelated changes, create separate PRs for them.\n\n5. Add a descriptive title and detailed description to your PR, explaining the purpose and rationale behind your changes.\n\n6. Rebase your branch with the latest upstream changes before submitting your PR:\n\n```bash\ngit pull --rebase upstream master\n```\n\n7. Create a pull request (PR) against the `develop` branch.\n\nAfter submitting your PR, our team will review your changes. We may ask for revisions or provide feedback before merging your changes into the master branch. Your patience and cooperation are greatly appreciated.\n\n## 🐛 Bug Reports\n\nWhen submitting a [bug report](https://github.com/getanteon/anteon/issues), please include:\n\n- A clear and descriptive title.\n- A detailed description of the issue, including the steps to reproduce the bug.\n- Any relevant information about your environment, such as the OS, Go version, and configuration.\n- If possible, attach a minimal code sample or test case that demonstrates the issue.\n- If possible, attach a screenshot or animated GIF that demonstrates the issue.\n\n## ✨ Feature Requests\n\nWhen submitting a [feature request](https://github.com/getanteon/anteon/issues), please include:\n\n- A clear and descriptive title.\n- A detailed description of the proposed feature or enhancement, including the rationale behind it and any potential use cases.\n- If possible, provide examples or mockups to help illustrate your proposal.\n\n## 💬 Community\n\nJoin our [Discord Server](https://discord.com/invite/9KdnrSUZQg) for issues, feature requests, feedbacks or anything else. We're happy to help you out!\n\n## 📜 Code of Conduct\n\nBy participating in this project, you agree to abide by our [Code of Conduct](./CODE_OF_CONDUCT.md). Please read it carefully and ensure that your contributions and interactions with the community adhere to its principles.\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU AFFERO GENERAL PUBLIC LICENSE\n                       Version 3, 19 November 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU Affero General Public License is a free, copyleft license for\nsoftware and other kinds of works, specifically designed to ensure\ncooperation with the community in the case of network server software.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nour General Public Licenses are intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  Developers that use our General Public Licenses protect your rights\nwith two steps: (1) assert copyright on the software, and (2) offer\nyou this License which gives you legal permission to copy, distribute\nand/or modify the software.\n\n  A secondary benefit of defending all users' freedom is that\nimprovements made in alternate versions of the program, if they\nreceive widespread use, become available for other developers to\nincorporate.  Many developers of free software are heartened and\nencouraged by the resulting cooperation.  However, in the case of\nsoftware used on network servers, this result may fail to come about.\nThe GNU General Public License permits making a modified version and\nletting the public access it on a server without ever releasing its\nsource code to the public.\n\n  The GNU Affero General Public License is designed specifically to\nensure that, in such cases, the modified source code becomes available\nto the community.  It requires the operator of a network server to\nprovide the source code of the modified version running there to the\nusers of that server.  Therefore, public use of a modified version, on\na publicly accessible server, gives the public access to the source\ncode of the modified version.\n\n  An older license, called the Affero General Public License and\npublished by Affero, was designed to accomplish similar goals.  This is\na different license, not a version of the Affero GPL, but Affero has\nreleased a new version of the Affero GPL which permits relicensing under\nthis license.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU Affero General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Remote Network Interaction; Use with the GNU General Public License.\n\n  Notwithstanding any other provision of this License, if you modify the\nProgram, your modified version must prominently offer all users\ninteracting with it remotely through a computer network (if your version\nsupports such interaction) an opportunity to receive the Corresponding\nSource of your version by providing access to the Corresponding Source\nfrom a network server at no charge, through some standard or customary\nmeans of facilitating copying of software.  This Corresponding Source\nshall include the Corresponding Source for any work covered by version 3\nof the GNU General Public License that is incorporated pursuant to the\nfollowing paragraph.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the work with which it is combined will remain governed by version\n3 of the GNU General Public License.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU Affero General Public License from time to time.  Such new versions\nwill be similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU Affero General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU Affero General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU Affero General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    Ddosify - Load testing tool for any web system.\n    Copyright (C) 2021  Ddosify\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU Affero General Public License as published\n    by the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU Affero General Public License for more details.\n\n    You should have received a copy of the GNU Affero General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If your software can interact with users remotely through a computer\nnetwork, you should also make sure that it provides a way for users to\nget its source.  For example, if your program is a web application, its\ninterface could display a \"Source\" link that leads users to an archive\nof the code.  There are many ways you could offer source, and different\nsolutions will be better for different programs; see section 13 for the\nspecific requirements.\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU AGPL, see\n<https://www.gnu.org/licenses/>.\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n    <img src=\"https://raw.githubusercontent.com/getanteon/anteon/master/assets/anteon-logo-db.svg#gh-dark-mode-only\" alt=\"Anteon logo dark\" width=\"336px\" /><br />\n    <img src=\"https://raw.githubusercontent.com/getanteon/anteon/master/assets/anteon-logo-wb.svg#gh-light-mode-only\" alt=\"Anteon logo light\" width=\"336px\" /><br />\n</div>\n\n<h2 align=\"center\">eBPF-powered Kubernetes Monitoring and Performance Testing</h2>\n\n<p align=\"center\">\n    <img src=\"https://raw.githubusercontent.com/getanteon/anteon/master/assets/anteon_service_map.png\" alt=\"Anteon Kubernetes Monitoring Service Map\" />\n    <p align=\"center\">\n        <a href=\"https://github.com/getanteon/anteon/releases\" target=\"_blank\"><img src=\"https://img.shields.io/github/v/release/getanteon/anteon?style=for-the-badge&logo=github&color=orange\" alt=\"anteon latest version\" /></a>&nbsp;\n        <a href=\"https://github.com/getanteon/anteon/blob/master/LICENSE\" target=\"_blank\"><img src=\"https://img.shields.io/badge/LICENSE-AGPL--3.0-orange?style=for-the-badge&logo=none\" alt=\"Anteon license\" /></a>\n        <a href=\"https://discord.com/invite/9KdnrSUZQg\" target=\"_blank\"><img src=\"https://img.shields.io/discord/898523141788287017?style=for-the-badge&logo=discord&label=DISCORD\" alt=\"Anteon discord server\" /></a>\n        <a href=\"https://landscape.cncf.io/?item=observability-and-analysis--observability--anteon\" target=\"_blank\"><img src=\"https://img.shields.io/badge/CNCF%20Landscape-5699C6?style=for-the-badge&logo=cncf&label=cncf\" alt=\"cncf landscape\" /></a>\n        <a href=\"https://gurubase.io/g/anteon\" target=\"_blank\"><img alt=\"Anteon Guru\" src=\"https://img.shields.io/badge/Anteon%20Guru-F40003?style=for-the-badge&label=Gurubase&color=%23006BFF\">\n</a>\n    </p>\n    <i>Anteon automatically generates Service Map of your K8s cluster without code instrumentation or sidecars. So you can easily find the bottlenecks in your system. Red lines indicate the high latency between services.</i>\n</p>\n\n<h2 align=\"center\">\n    <a href=\"https://demo.getanteon.com/\" target=\"_blank\">Live Demo</a> •\n    <a href=\"https://getanteon.com/docs\" target=\"_blank\">Documentation</a> •\n    <a href=\"https://discord.com/invite/9KdnrSUZQg\" target=\"_blank\">Discord</a>\n</h2>\n\n## 🐝 What is Anteon?\n\n**Anteon** (formerly Ddosify) is an [open-source](https://github.com/getanteon/anteon), eBPF-based **Kubernetes Monitoring** and **Performance Testing** platform.\n\n### 🔎 Kubernetes Monitoring\n\n- **Automatic Service Map Creation:** Anteon automatically creates a **service map** of your cluster without code instrumentation or sidecars. So you can easily [find the bottlenecks](https://getanteon.com/docs/kubernetes-monitoring/#finding-bottlenecks) in your system.\n- **Performance Insights:** It helps you spot issues like services taking too long to respond or slow SQL queries.\n- **Real-Time Metrics:** The platform tracks and displays live data on your cluster instances CPU, memory, disk, and network usage.\n- **Ease of Use:** You don't need to change any code, restart services, or add extra components (like sidecars) to get these insights, thanks to the [eBPF based agent (Alaz)](https://github.com/getanteon/alaz).\n- **Alerts for Anomalies:** If something unusual, like a sudden increase in CPU usage, happens in your Kubernetes (K8s) cluster, Anteon immediately sends alerts to your Slack.\n- **Seamless Integration with Performance Testing:** Performance testing is natively integrated with Kubernetes monitoring for a unified experience.\n\n<p align=\"center\">\n<img src=\"https://raw.githubusercontent.com/getanteon/anteon/master/assets/anteon_metrics.png\" alt=\"Anteon Kubernetes Monitoring Metrics\" />\n<i>Anteon tracks and displays live data on your cluster instances CPU, memory, disk, and network usage.</i>\n</p>\n\n### 🔨 Performance Testing\n\n- **Multi-Location Based:** Generate load/performance tests from over 25 countries worldwide.\n- **Easy Scenario Builder:** Create test scenarios easily without writing any code.\n- **Seamless Integration with Kubernetes Monitoring:** Performance testing is natively integrated with Kubernetes monitoring for a unified experience.\n- **Postman Integration:** Import tests directly from Postman, making it convenient for those already using Postman for API development and testing.\n\n<p align=\"center\">\n<img src=\"https://raw.githubusercontent.com/getanteon/anteon/master/assets/anteon_performance_testing.png\" alt=\"Anteon Kubernetes Monitoring Metrics\" />\n<i>Anteon Performance Testing generates load from worldwide with no-code scenario builder.</i>\n</p>\n\n## 📚 Documentation\n\n- [🐝 Anteon Stack](https://getanteon.com/docs/stack/)\n- [🚀 Getting Started](https://getanteon.com/docs/getting-started/)\n- [🔎 Kubernetes Monitoring](https://getanteon.com/docs/kubernetes-monitoring/)\n- [🔨 Performance Testing](https://getanteon.com/docs/performance-testing/)\n\n## ✨ Ask Anteon Guru\nIf you don’t want to get lost in the documentation, you can ask [Anteon Guru](https://gurubase.io/g/anteon) directly. It's an Anteon-focused AI that uses information from the Anteon Website and Anteon GitHub Repository to answer your questions.\n\n<a href=\"https://gurubase.io/g/anteon\" target=\"_blank\"><img alt=\"Anteon Guru\" src=\"https://img.shields.io/badge/ASK%20ANTEON%20GURU-F40003?color=%23006BFF&style=for-the-badge\"></a>\n\n## ℹ️ About This Repository\n\nThis repository includes the source code for the Anteon Load Engine (Ddosify). You can access Docker Images for the Anteon Engine and Self Hosted on <a href=\"https://hub.docker.com/u/ddosify\" target=\"_blank\">Docker Hub</a>. Since Anteon is a Verified Publisher on Docker Hub, there isn't any pull limits.\n\n- [Ddosify documentation](https://github.com/getanteon/anteon/tree/master/ddosify_engine) provides information on the installation, usage, and features of the Anteon Load Engine.\n- The [Self-Hosted](https://github.com/getanteon/anteon/tree/master/selfhosted) folder contains installation instructions for the Self-Hosted version.\n- [Anteon eBPF agent (Alaz)](https://github.com/getanteon/alaz) has its own repository.\n\nSee the [Anteon website](https://getanteon.com/) for more information.\n\n## 🛠️ Contributing\n\nSee our [Contribution Guide](./CONTRIBUTING.md) and please follow the [Code of Conduct](./CODE_OF_CONDUCT.md) in all your interactions with the project.\n\nThanks goes to these wonderful people!\n\n<a href=\"https://github.com/getanteon/anteon/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=getanteon/anteon\" />\n</a>\n\nMade with [contrib.rocks](https://contrib.rocks).\n\n### 📨 Communication\n\nYou can join our [Discord Server](https://discord.com/invite/9KdnrSUZQg) for issues, feature requests, feedbacks or anything else.\n\n### ⚠️ Disclaimer\n\nAnteon is created for testing the performance of web applications. Users must be the owner of the target system. Using it for harmful purposes is extremely forbidden. Anteon team & company is not responsible for its’ usages and consequences.\n\n## 📜 License\n\nLicensed under the [AGPLv3](LICENSE)\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Anteon Security Policy 🐝\n\nWe are committed to maintaining the security and integrity of [Anteon](https://github.com/getanteon/anteon), and we appreciate your help in identifying and addressing potential vulnerabilities.\n\nThis document outlines the process for reporting security issues, as well as our commitment to addressing them in a timely manner.\n\n## Supported Versions\n\nWe provide security updates for the following versions:\n\n| Version    | Supported |\n| ---------- | --------- |\n| > `0.15.x` | ✅        |\n\n⚠️ Please note that older versions may not receive security updates. We encourage you to use the [latest version](https://github.com/getanteon/anteon/releases) of the software to benefit from the most recent security enhancements.\n\n## Reporting a Vulnerability\n\nIf you believe you have discovered a security vulnerability, please follow these steps to report it:\n\n1. Do not create a public issue on GitHub. Disclosing security vulnerabilities publicly can put users at risk.\n2. Send an email to our security team at security@getanteon.com with a detailed description of the vulnerability, including the steps to reproduce it, and any relevant information about your environment (e.g., OS, Go version, etc.).\n3. If possible, provide a minimal code sample or test case that demonstrates the vulnerability.\n4. Allow us a reasonable amount of time to investigate and address the issue before publicly disclosing it. We will make every effort to resolve the issue as soon as possible.\n\nWe keep you informed of our progress in addressing the issue.\n\n## Our Commitment\n\nWe take security issues very seriously and are committed to working with you to address any vulnerabilities that you report. We will:\n\n- Investigate and validate reported vulnerabilities.\n- Work on a fix or mitigation for the issue.\n- Provide regular updates on our progress in resolving the issue.\n- Notify you when the issue has been resolved and provide details on the changes made.\n- We appreciate your assistance in maintaining the security of Anteon, and we thank you for your responsible disclosure.\n"
  },
  {
    "path": "assets/ddosify.profile",
    "content": "export TERM=xterm-256color\nNC='\\033[0m'\nprintf \"\\e[38;5;172m\\n\"\ncat<<ddosify\n       __     __              _  ____      \n  ____/ /____/ /____   _____ (_)/ __/__  __\n / __  // __  // __ \\ / ___// // /_ / / / /\n/ /_/ // /_/ // /_/ /(__  )/ // __// /_/ / \n\\__,_/ \\__,_/ \\____//____//_//_/   \\__, /  \n                                  /____/   \n\nddosify\n\nprintf \"Simple usage:${NC} ddosify -t https://getanteon.com\\n\\n\"\n"
  },
  {
    "path": "ddosify_engine/.dockerignore",
    "content": "dist/\n*.yml\n*.out\nJenkinsfile\nREADME.md\n"
  },
  {
    "path": "ddosify_engine/.golangci.yml",
    "content": "linters:\n  enable:\n    - lll\n    - golint\n    - misspell\nlinters-settings:\n  lll:\n    # max line length, lines longer will be reported. Default is 120.\n    # '\\t' is counted as 1 character by default, and can be changed with the tab-width option\n    line-length: 120\n    # tab width in spaces. Default to 1.\n    tab-width: 1\n  golint:\n    min-confidence: 0.85\n  misspell:\n    locale: US"
  },
  {
    "path": "ddosify_engine/.goreleaser.yml",
    "content": "project_name: ddosify\nbefore:\n  hooks:\n    - go mod tidy\nbuilds:\n  - env:\n      - CGO_ENABLED=0\n    goos:\n      - linux\n      - windows\n      - darwin\n    goarch:\n      - 386\n      - amd64\n      - arm\n      - arm64\n    goarm:\n      - 6\n    ldflags:\n      - -s -w -X main.GitVersion={{ .Tag }} -X main.GitCommit={{ .ShortCommit }}  -X main.BuildDate={{ .CommitDate }}\n    ignore:\n      - goos: darwin\n        goarch: 386\n      - goos: darwin\n        goarch: arm\n        goarm: 7\n      - goos: darwin\n        goarch: arm\n        goarm: 6\n      - goos: darwin\n        goarch: arm\n        goarm: 5\narchives:\n  - format_overrides:\n      - goos: windows\n        format: zip\n    files:\n      - README.md\n      - LICENSE*\n\nuniversal_binaries:\n- replace: true\n\nchecksum:\n  name_template: 'checksums.txt'\nsnapshot:\n  name_template: \"{{ incpatch .Tag }}-next\"\nchangelog:\n  sort: asc\n  use: github\n  filters:\n    exclude:\n      - '^docs:'\n      - '^test:'\n      - Merge pull request\n      - Merge branch\n      - go mod tidy\n\nbrews:\n  - tap:\n      owner: ddosify\n      name: homebrew-tap\n    folder: Formula\n    homepage:  https://ddosify.com\n    description: High-performance load testing tool, written in Golang.\n    license: AGPL-3.0-only\n    skip_upload: false\n    test: |\n      system \"#{bin}/ddosify --help\"\n    dependencies:\n    - name: go\n      type: optional\n    install: |-\n      bin.install \"ddosify\"\n    commit_author:\n      name: ddosifyadmin\n      email: admin@ddosify.com\n\ndockers:\n- image_templates:\n  - 'ddosify/ddosify:{{ .Tag }}-amd64'\n  dockerfile: Dockerfile.release\n  use: buildx\n  build_flag_templates:\n  - \"--pull\"\n  - \"--label=org.opencontainers.image.created={{.Date}}\"\n  - \"--label=org.opencontainers.image.name={{.ProjectName}}\"\n  - \"--label=org.opencontainers.image.revision={{.FullCommit}}\"\n  - \"--label=org.opencontainers.image.version={{.Tag}}\"\n  - \"--label=org.opencontainers.image.source={{.GitURL}}\"\n  - \"--platform=linux/amd64\"\n  extra_files:\n  - assets/ddosify.profile\n- image_templates:\n  - 'ddosify/ddosify:{{ .Tag }}-arm64'\n  dockerfile: Dockerfile.release\n  use: buildx\n  build_flag_templates:\n  - \"--pull\"\n  - \"--label=org.opencontainers.image.created={{.Date}}\"\n  - \"--label=org.opencontainers.image.name={{.ProjectName}}\"\n  - \"--label=org.opencontainers.image.revision={{.FullCommit}}\"\n  - \"--label=org.opencontainers.image.version={{.Tag}}\"\n  - \"--label=org.opencontainers.image.source={{.GitURL}}\"\n  - \"--platform=linux/arm64\"\n  extra_files:\n  - assets/ddosify.profile\n  goarch: arm64\n\ndocker_manifests:\n- name_template: 'ddosify/ddosify:{{ .Tag }}'\n  image_templates:\n  - 'ddosify/ddosify:{{ .Tag }}-amd64'\n  - 'ddosify/ddosify:{{ .Tag }}-arm64'\n- name_template: 'ddosify/ddosify:latest'\n  image_templates:\n  - 'ddosify/ddosify:{{ .Tag }}-amd64'\n  - 'ddosify/ddosify:{{ .Tag }}-arm64'\n\nnfpms:\n  - file_name_template: '{{ .ProjectName }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'\n    id: packages\n    homepage:  https://ddosify.com\n    description: High-performance load testing tool, written in Golang.\n    maintainer: Ddosify <admin@ddosify.com>\n    license: AGPL-3.0-only\n    vendor: Ddosify\n    formats:\n    - apk\n    - deb\n    - rpm\n\nrelease:\n  footer: |\n    ## More? 🚀\n\n    - Join our [Discord server](https://discord.com/invite/9KdnrSUZQg)\n    - Follow us on [Twitter](https://twitter.com/getanteon)\n"
  },
  {
    "path": "ddosify_engine/Dockerfile",
    "content": "FROM golang:1.18.1-alpine as builder\nWORKDIR /app\nCOPY . ./\nRUN go mod download\nRUN CGO_ENABLED=0 GOOS=linux go build -o /app/ddosify main.go\n\n\nFROM alpine:3.15.4\nENV ENV=\"/root/.ashrc\"\nWORKDIR /root\nRUN apk --no-cache add ca-certificates\n\nCOPY --from=builder /app/ddosify /bin/\n\nCOPY assets/ddosify.profile /tmp/profile\nRUN cat /tmp/profile >> \"$ENV\"\n"
  },
  {
    "path": "ddosify_engine/Dockerfile.dev",
    "content": "FROM golang:1.18.1\n\nWORKDIR /workspace\n\nCOPY go.mod ./\nCOPY go.sum ./\n \nENV GOPATH /go\nENV GOBIN /go/bin\n\nENV LC_ALL=C.UTF-8\nENV LANG=C.UTF-8\nENV SHELL /bin/zsh\n\nRUN apt update && apt install -y git zsh vim fzf locales gcc musl-dev curl iputils-ping telnet graphviz bc jq && rm -rf /var/lib/apt/lists/*\nRUN sh -c \"$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)\"\nRUN git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting\nRUN git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions\nRUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.45.2\nCOPY .devcontainer/.zshrc /root/.zshrc\n\nRUN go install -v golang.org/x/tools/gopls@v0.8.3\nRUN go install -v github.com/rogpeppe/godef@v1.1.2\nRUN go install -v github.com/rakyll/gotest@v0.0.6\nRUN go install -v github.com/ramya-rao-a/go-outline@1.0.0\nRUN go install -v github.com/go-delve/delve/cmd/dlv@v1.8.1\nRUN go install -v golang.org/x/perf/cmd/benchstat@v0.0.0-20221222172245-91a04616dc65\nRUN go mod download\n\nCMD [ \"zsh\" ]"
  },
  {
    "path": "ddosify_engine/Dockerfile.release",
    "content": "FROM alpine:3.15.4\nENV ENV=\"/root/.ashrc\"\nWORKDIR /root\nRUN apk --no-cache add ca-certificates\nCOPY ddosify /bin/\n\nCOPY assets/ddosify.profile /tmp/profile\nRUN cat /tmp/profile >> \"$ENV\"\n"
  },
  {
    "path": "ddosify_engine/Jenkinsfile",
    "content": "pipeline {\n  agent {\n    dockerfile {\n      filename '.devcontainer/Dockerfile.dev'\n    }\n  }\n  environment {\n    PROXY_TEST_USERNAME     = credentials('proxy-test-username')\n    PROXY_TEST_PASSWORD = credentials('proxy-test-password')\n  }\n\n  options { \n    disableConcurrentBuilds() \n  }\n\n  stages {\n    stage('Unit Test') {\n      steps {\n        sh 'go test -coverpkg=./... -coverprofile=coverage.out ./... -timeout 100s -parallel 4'\n      }\n    }\n\n    stage('Coverage') {\n      steps {\n        sh 'go tool cover -html=coverage.out -o coverage.html'\n        archiveArtifacts '*.html'\n        sh 'echo \"Coverage Report: ${BUILD_URL}artifact/coverage.html\"'\n        sh '''t=$(go tool cover -func coverage.out | grep total | tail -1 | awk \\'{print substr($3, 1, length($3)-1)}\\')\nif [ \"${t%.*}\" -lt 80 ]; then \n    echo \"Coverage failed ${t}/80\"\n    exit 1\nfi'''\n      }\n    }\n\n    stage('Main Race Condition') {\n      steps {\n        lock('multi_branch_server') {\n          sh 'go run --race main.go -t https://servdown.com/ -d 1 -n 1500'\n          sh 'go run --race main.go -config config/config_testdata/race_configs/step_assertions_stdout.json'\n          sh 'go run --race main.go -config config/config_testdata/race_configs/step_assertions_stdout_json.json'\n          sh 'go run --race main.go -config config/config_testdata/race_configs/capture_envs.json'\n          sh 'go run --race main.go -config config/config_testdata/race_configs/global_envs.json'\n          sh 'go test -race -run ^TestDynamicVariableRace$ go.ddosify.com/ddosify/core/scenario/scripting/injection'\n        }\n      }\n    }\n\n  }\n  post {\n    unstable {\n      slackSend(channel: '#jenkins', color: 'danger', message: \"${currentBuild.currentResult}: ${currentBuild.fullDisplayName} - ${BUILD_URL}\")\n    }\n\n    failure {\n      slackSend(channel: '#jenkins', color: 'danger', message: \"${currentBuild.currentResult}: ${currentBuild.fullDisplayName} - ${BUILD_URL}\")\n    }\n\n  }\n}\n"
  },
  {
    "path": "ddosify_engine/Jenkinsfile_benchmark",
    "content": "pipeline {\n  agent {\n    dockerfile {\n      label 'performance-test'\n      filename '.devcontainer/Dockerfile.dev'\n    }\n  }\n\n  options { \n    disableConcurrentBuilds() \n  }\n\n  stages {\n    stage('Performance Test') {\n      steps {\n        lock('multi_branch_server_benchmark') {\n          sh 'set -o pipefail && GOCACHE=/tmp/ go test -benchmem -timeout 60m -benchtime=1x -cpuprof=cpu.out -memprof=mem.out -tracef=trace.out -run=^$ -bench ^BenchmarkEngines/$ -count 1 -runN=10 | tee gobench_branch.txt'\n        }\n      }\n    }\n    stage('Performance Test Develop') {\n      when {\n        // Run on only PR\n        allOf {\n          expression { env.CHANGE_ID != null }\n          expression { env.CHANGE_TARGET != null }\n          expression { env.CHANGE_BRANCH != 'develop' }\n        }\n      }\n      steps {\n        lock('multi_branch_server_benchmark') {\n          sh 'git fetch origin develop:develop ||  git checkout develop && git pull && set -o pipefail && GOCACHE=/tmp/ go test -benchmem -timeout 60m -benchtime=1x -cpuprof=cpu_develop.out -memprof=mem_develop.out -tracef=trace_develop.out -run=^$ -bench ^BenchmarkEngines/$ -count 1 -runN=10 | tee gobench_develop.txt'\n          sh \"git checkout ${BRANCH_NAME}\"\n          sh \"benchstat -alpha 1.01 --sort delta gobench_develop.txt gobench_branch.txt | tee gobench_branch_result.txt\"\n          sh \"benchstat -alpha 1.01 --sort delta --html gobench_develop.txt gobench_branch.txt > 00_gobench_result.html && echo ${BUILD_URL}artifact/00_gobench_result.html\"\n          \n          withCredentials([usernamePassword(credentialsId: 'ddosifyadmin_comment_github_access', passwordVariable: 'GITHUB_TOKEN', usernameVariable: 'GITHUB_USERNAME')]) {\n            sh \"./scripts/testing/benchstat.sh ${GITHUB_TOKEN} ${env.CHANGE_ID}\"\n          }\n        }\n      }\n    }\n  }\n  post {\n    always {\n      archiveArtifacts artifacts: '*.out', fingerprint: true\n      archiveArtifacts artifacts: 'gobench_*.txt', fingerprint: true\n      archiveArtifacts artifacts: '00_gobench_result.html', fingerprint: true, allowEmptyArchive: true\n    }\n    unstable {\n      slackSend(channel: '#jenkins', color: 'danger', message: \"${currentBuild.currentResult}: ${currentBuild.fullDisplayName} - ${BUILD_URL}\")\n    }\n\n    failure {\n      slackSend(channel: '#jenkins', color: 'danger', message: \"${currentBuild.currentResult}: ${currentBuild.fullDisplayName} - ${BUILD_URL}\")\n    }\n  }\n}\n"
  },
  {
    "path": "ddosify_engine/README.md",
    "content": "<div align=\"center\">\n    <img src=\"https://raw.githubusercontent.com/getanteon/anteon/master/assets/anteon-logo-db.svg#gh-dark-mode-only\" alt=\"Anteon logo dark\" width=\"336px\" /><br />\n    <img src=\"https://raw.githubusercontent.com/getanteon/anteon/master/assets/anteon-logo-wb.svg#gh-light-mode-only\" alt=\"Anteon logo light\" width=\"336px\" /><br />\n</div>\n\n<h1 align=\"center\">Ddosify: A high-performance load testing tool</h1>\n\n<p align=\"center\">\n    <a href=\"https://app.codecov.io/gh/ddosify/ddosify\" target=\"_blank\"><img src=\"https://img.shields.io/codecov/c/github/ddosify/ddosify?style=for-the-badge&logo=codecov\" alt=\"go coverage\" /></a>&nbsp;\n    <a href=\"https://goreportcard.com/report/github.com/getanteon/ddosify\" target=\"_blank\"><img src=\"https://goreportcard.com/badge/github.com/getanteon/ddosify?style=for-the-badge&logo=go\" alt=\"go report\" /></a>&nbsp;\n    <a href=\"https://github.com/getanteon/anteon/blob/master/LICENSE\" target=\"_blank\"><img src=\"https://img.shields.io/badge/LICENSE-AGPL--3.0-orange?style=for-the-badge&logo=none\" alt=\"ddosify license\" /></a>\n    <a href=\"https://discord.com/invite/9KdnrSUZQg\" target=\"_blank\"><img src=\"https://img.shields.io/discord/898523141788287017?style=for-the-badge&logo=discord&label=DISCORD\" alt=\"ddosify discord server\" /></a>\n    <a href=\"https://hub.docker.com/r/ddosify/ddosify\" target=\"_blank\"><img src=\"https://img.shields.io/docker/v/ddosify/ddosify?style=for-the-badge&logo=docker&label=docker&sort=semver\" alt=\"ddosify docker image\" /></a>\n</p>\n\n<p align=\"center\">\n<img src=\"https://raw.githubusercontent.com/getanteon/anteon/master/assets/ddosify-quick-start.gif\" alt=\"Ddosify - High-performance load testing tool quick start\" />\n</p>\n\n<details>\n  <summary>Table of Contents</summary>\n\n<!-- vim-markdown-toc GFM -->\n\n- [Features](#features)\n- [Tutorials / Blog Posts](#tutorials--blog-posts)\n- [Installation](#installation)\n  - [Docker](#docker)\n  - [Docker Extension](#docker-extension)\n  - [Homebrew Tap (macOS and Linux)](#homebrew-tap-macos-and-linux)\n  - [Linux](#linux)\n    - [Redhat (Fedora, CentOS, RHEL, etc.)](#redhat-fedora-centos-rhel-etc)\n    - [Debian (Ubuntu, Linux Mint, etc.)](#debian-ubuntu-linux-mint-etc)\n    - [Alpine](#alpine)\n  - [FreeBSD](#freebsd)\n  - [Windows Executable](#windows-executable)\n  - [Using the convenience script (macOS and Linux)](#using-the-convenience-script-macos-and-linux)\n  - [Go install from source (macOS, FreeBSD, Linux, Windows)](#go-install-from-source-macos-freebsd-linux-windows)\n- [Quick Start](#quick-start)\n- [Advanced Usage](#advanced-usage)\n  - [CLI Flags](#cli-flags)\n  - [Load Types](#load-types)\n    - [Linear](#linear)\n    - [Incremental](#incremental)\n    - [Waved](#waved)\n  - [Configuration](#configuration)\n- [Parameterization (Dynamic Variables)](#parameterization-dynamic-variables)\n  - [Parameterization on URL](#parameterization-on-url)\n  - [Parameterization on Headers](#parameterization-on-headers)\n  - [Parameterization on Payload (Body)](#parameterization-on-payload-body)\n  - [Parameterization on Basic Authentication](#parameterization-on-basic-authentication)\n  - [Parameterization on Config File](#parameterization-on-config-file)\n  - [Environment Variables](#environment-variables)\n- [Assertion](#assertion)\n  - [Keywords](#keywords)\n  - [Functions](#functions)\n  - [Operators](#operators)\n  - [Assertion Examples](#assertion-examples)\n- [Success Criteria (Pass / Fail)](#success-criteria-pass--fail)\n- [Difference Between Success Criteria and Step Assertions](#difference-between-success-criteria-and-step-assertions)\n  - [Keywords](#keywords-1)\n  - [Functions](#functions-1)\n  - [Examples](#examples)\n- [Correlation](#correlation)\n  - [Capture with json_path](#capture-with-json_path)\n  - [Capture with XPath on XML](#capture-with-xpath-on-xml)\n  - [Capture with XPath on HTML](#capture-with-xpath-on-html)\n  - [Capture with Regular Expressions](#capture-with-regular-expressions)\n  - [Capture Header Value](#capture-header-value)\n  - [Scenario-Scoped Variables](#scenario-scoped-variables)\n  - [Overall Config and Injection](#overall-config-and-injection)\n- [Test Data Set](#test-data-set)\n- [Cookies](#cookies)\n  - [Initial / Custom Cookies](#initial--custom-cookies)\n  - [Cookie Capture](#cookie-capture)\n  - [Cookie Assertion](#cookie-assertion)\n- [Common Issues](#common-issues)\n  - [macOS Security Issue](#macos-security-issue)\n  - [OS Limit - Too Many Open Files](#os-limit---too-many-open-files)\n- [Contributing](#contributing)\n- [Communication](#communication)\n- [More](#more)\n- [Disclaimer](#disclaimer)\n- [License](#license)\n\n<!-- vim-markdown-toc -->\n\n</details>\n\n- [📥 Installation](https://getanteon.com/docs/ddosify/installation/)\n- [🚀 Quickstart Guide](https://getanteon.com/docs/ddosify/quickstart/)\n- [⚙️ Configuration](https://getanteon.com/docs/ddosify/configuration/)\n- [✨ Examples](https://getanteon.com/docs/ddosify/examples/)\n\n- ✅ **[Scenario-Based](#config-file)** - Create your flow in a JSON file. Without a line of code!\n\n- ✅ **[Different Load Types](#load-types)** - Test your system's limits across different load types.\n\n- ✅ **[Parameterization](#parameterization-dynamic-variables)** - Use dynamic variables just like on Postman.\n\n- ✅ **[Correlation](#correlation)** - Extract variables from earlier phases and pass them on to the following ones.\n\n- ✅ **[Test Data](#test-data-set)** - Import test data from CSV and use it in the scenario.\n\n- ✅ **[Assertion](#assertion)** - Verify that the response matches your expectations.\n\n- ✅ **[Success Criteria](#success-criteria-pass--fail)** - Set the success criteria for your test.\n\n- ✅ **[Cookies](#cookies)** - Pass cookies through steps and set initial cookies if you want.\n\n- ✅ **Widely Used Protocols** - Currently supporting _HTTP, HTTPS, HTTP/2_. Other protocols are on the way.\n\n## Tutorials / Blog Posts\n\n- [Testing the Performance of User Authentication Flow](https://getanteon.com/blog/testing-the-performance-of-user-authentication-flow)\n- [Load Testing a Fintech API with CSV Test Data Import](https://getanteon.com/blog/load-testing-a-fintech-exchange-api-with-csv-test-data-import)\n\n## Installation\n\n`ddosify` is available via [Docker](https://hub.docker.com/r/ddosify/ddosify), [Docker Extension](https://hub.docker.com/extensions/ddosify/ddosify-docker-extension), [Homebrew Tap](#homebrew-tap-macos-and-linux), and downloadable as pre-compiled binaries from the [releases page](https://github.com/getanteon/anteon/releases/tag/v1.0.6) for macOS, Linux and Windows.\n\nFor shell auto completions, see [Ddosify Completions](https://github.com/getanteon/anteon/tree/master/ddosify_engine/completions).\n\n### Docker\n\n```bash\ndocker run -it --rm ddosify/ddosify\n```\n\n### Docker Extension\n\nRun Ddosify on Docker Desktop with Ddosify Docker extension. More details [here](https://hub.docker.com/extensions/ddosify/ddosify-docker-extension).\n\n### Homebrew Tap (macOS and Linux)\n\n```bash\nbrew install ddosify/tap/ddosify\n```\n\n### Linux\n\n- For ARM architectures change `ddosify_amd64` to `ddosify_arm64` or `ddosify_armv6`.\n- Superuser privilege is required.\n\n#### Redhat (Fedora, CentOS, RHEL, etc.)\n\n```bash\nrpm -i https://github.com/ddosify/ddosify/releases/download/v1.0.6/ddosify_amd64.rpm\n```\n\n#### Debian (Ubuntu, Linux Mint, etc.)\n\n```bash\nwget https://github.com/ddosify/ddosify/releases/download/v1.0.6/ddosify_amd64.deb\ndpkg -i ddosify_amd64.deb\n```\n\n#### Alpine\n\n```bash\nwget https://github.com/ddosify/ddosify/releases/download/v1.0.6/ddosify_amd64.apk\napk add --allow-untrusted ddosify_amd64.apk\n```\n\n### FreeBSD\n\n```bash\npkg install ddosify\n```\n\n### Windows Executable\n\n- Download zip file for your architecture from the [releases page](https://github.com/ddosify/ddosify/releases/tag/v1.0.6).\n  - For example, download ddosify version `vx.x.x` with amd64 architecture: `ddosify_x.x.x.zip_windows_amd64`\n- Unzip `ddosify_x.x.x_windows_amd64.zip`\n- Open Powershell or CMD (Command Prompt) and change directory to unzipped folder: `ddosify_x.x.x_windows_amd64`\n- Run ddosify:\n\n```bash\n.\\ddosify.exe -t https://getanteon.com\n```\n\n### Using the convenience script (macOS and Linux)\n\n- The script requires root or sudo privileges to move ddosify binary to `/usr/local/bin`.\n- The script attempts to detect your operating system (macOS or Linux) and architecture (arm64, x86, amd64) to download the appropriate binary from the [releases page](https://github.com/getanteon/anteon/tree/master/ddosify_engine/completions).\n- By default, the script installs the latest version of `ddosify`.\n- If you have problems, check [common issues](#common-issues).\n- Required packages: `curl` and `sudo`\n\n```bash\ncurl -sSfL https://raw.githubusercontent.com/getanteon/anteon/master/scripts/install.sh | sh\n```\n\n### Go install from source (macOS, FreeBSD, Linux, Windows)\n\n_Minimum supported Go version is 1.18_\n\n```bash\ngo install -v go.ddosify.com/ddosify@latest\n```\n\n## Quick Start\n\nThis section aims to show you how to use Ddosify easily without deep dive into its details.\n\n1.  ### Simple load test\n\n    `ddosify -t https://getanteon.com`\n\n    The above command runs a load test with the default value that is 100 requests in 10 seconds.\n\n2.  ### Using some of the features\n\n    `ddosify -t https://getanteon.com -n 1000 -d 20 -m PUT -T 7 -P http://proxy_server.com:80`\n\n    Ddosify sends a total of _1000_ _PUT_ requests to *https://getanteon.com* over proxy _http://proxy_server.com:80_ in _20_ seconds with a timeout of _7_ seconds per request.\n\n3.  ### Usage for CI/CD pipelines (JSON output)\n\n    `ddosify -t https://getanteon.com -o stdout-json | jq .avg_duration`\n\n    Ddosify outputs the result in JSON format. Then `jq` (or any other command-line JSON processor) fetches the `avg_duration`. The rest depends on your CI/CD flow logic.\n\n4.  ### Scenario based load test\n\n    `ddosify -config config_examples/config.json`\n\n    Ddosify first sends _HTTP/2 POST_ request to *https://getanteon.com/endpoint_1* using basic auth credentials _test_user:12345_ over proxy _http://proxy_host.com:proxy_port_ and with a timeout of _3_ seconds. Once the response is received, HTTPS GET request will be sent to *https://getanteon.com/endpoint_2* along with the payload included in _config_examples/payload.txt_ file with a timeout of 2 seconds. This flow will be repeated _20_ times in _5_ seconds and response will be written to _stdout_.\n\n5.  ### Load test with Dynamic Variables (Parameterization)\n\n    `ddosify -t https://getanteon.com/{{_randomInt}} -d 10 -n 100 -h 'User-Agent: {{_randomUserAgent}}' -b '{\"city\": \"{{_randomCity}}\"}'`\n\n    Ddosify sends a total of _100_ _GET_ requests to *https://getanteon.com/{{_randomInt}}* in _10_ seconds. `{{_randomInt}}` path generates random integers between 1 and 1000 in every request. Dynamic variables can be used in _URL_, _headers_, _payload (body)_ and _basic authentication_. In this example, Ddosify generates a random user agent in the header and a random city in the body. The full list of the dynamic variables can be found in the [docs](https://getanteon.com/docs/performance-testing/dynamic-variables-parametrization/).\n\n6.  ### Correlation (Captured Variables)\n\n    `ddosify -config ddosify_config_correlation.json`\n\n    Ddosify allows you to specify variables at the global level and use them throughout the scenario, as well as extract variables from previous steps and inject them to the next steps in each iteration individually. You can inject those variables in requests _url_, _headers_ and _payload(body)_. The example config can be found in [correlation-config-example](#Correlation).\n\n7.  ### Test Data\n\n    `ddosify -config ddosify_data_csv.json`\n\n    Ddosify allows you to load test data from a file, tag specific columns for later use. You can inject those variables in requests _url_, _headers_ and _payload (body)_. The example config can be found in [test-data-example](#test-data-set).\n\n## Advanced Usage\n\nYou can configure your load test by the CLI options or a config file. Config file supports more features than the CLI. For example, you can't create a scenario-based load test with CLI options.\n\n### CLI Flags\n\n```bash\nddosify [FLAG]\n```\n\n| Flag                                                        | Description                                                                                                       | Type     | Default  | Required |\n| ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | -------- | -------- | -------- |\n| `-t`                                                        | Target website URL. Example: https://getanteon.com                                                                | `string` | -        | Yes      |\n| `-n`                                                        | Total iteration count                                                                                             | `int`    | `100`    | No       |\n| `-d`                                                        | Test duration in seconds.                                                                                         | `int`    | `10`     | No       |\n| `-m`                                                        | Request method. Available methods for HTTP(s) are _GET, POST, PUT, DELETE, HEAD, PATCH, OPTIONS_                  | `string` | `GET`    | No       |\n| `-b`                                                        | The payload of the network packet. AKA body for the HTTP.                                                         | `string` | -        | No       |\n| `-a`                                                        | Basic authentication. Usage: `-a username:password`                                                               | `string` | -        | No       |\n| `-h`                                                        | Headers of the request. You can provide multiple headers with multiple `-h` flag. Usage: `-h 'Accept: text/html'` | `string` | -        | No       |\n| `-T`                                                        | Timeout of the request in seconds.                                                                                | `int`    | `5`      | No       |\n| `-P`                                                        | Proxy address as host:port. `-P 'http://user:pass@proxy_host.com:port'`                                           | `string` | -        | No       |\n| `-o`                                                        | Test result output destination. Supported outputs are [*stdout, stdout-json*] Other output types will be added.   | `string` | `stdout` | No       |\n| `-l`                                                        | [Type](#load-types) of the load test. Ddosify supports 3 load types.                                              | `string` | `linear` | No       |\n| <span style=\"white-space: nowrap;\">`--config`</span>        | [Config File](#config-file) of the load test.                                                                     | `string` | -        | No       |\n| <span style=\"white-space: nowrap;\">`--version`</span>       | Prints version, git commit, built date (utc), go information and quit                                             | -        | -        | No       |\n| <span style=\"white-space: nowrap;\">`--cert_path`</span>     | A path to a certificate file (usually called 'cert.pem')                                                          | -        | -        | No       |\n| <span style=\"white-space: nowrap;\">`--cert_key_path`</span> | A path to a certificate key file (usually called 'key.pem')                                                       | -        | -        | No       |\n| <span style=\"white-space: nowrap;\">`--debug`</span>         | Iterates the scenario once and prints curl-like verbose result. Note that this flag overrides json config.        | `bool`   | `false`  | No       |\n\n### Load Types\n\n#### Linear\n\n```bash\nddosify -t https://getanteon.com -l linear\n```\n\nResult:\n\n![linear load](https://raw.githubusercontent.com/getanteon/anteon/master/assets/linear.gif)\n\n_Note:_ If the iteration count is too low for the given duration, the test might be finished earlier than you expect.\n\n#### Incremental\n\n```bash\nddosify -t https://getanteon.com -l incremental\n```\n\nResult:\n\n![incremental load](https://raw.githubusercontent.com/getanteon/anteon/master/assets/incremental.gif)\n\n#### Waved\n\n```bash\nddosify -t https://getanteon.com -l waved\n```\n\nResult:\n\n![waved load](https://raw.githubusercontent.com/getanteon/anteon/master/assets/waved.gif)\n\n### Configuration\n\nConfiguration file lets you use all the capabilities of Ddosify.\n\nThe features you can use by config file:\n\n- Scenario creation\n- Environment variables\n- Correlation\n- Assertions\n- Cookies\n- Custom load type creation\n- Payload from a file\n- Multipart/form-data payload\n- Extra connection configuration\n- HTTP2 support\n\nUsage:\n\n```bash\nddosify -config <json_config_path>\n```\n\nThere is an example config file at [config_examples/config.json](https://github.com/getanteon/anteon/blob/master/ddosify_engine/config_examples/config.json). This file contains all of the parameters you can use. Details of each parameter;\n\n- `iteration_count` (_optional_)\n\n  This is the equivalent of the `-n` flag. The difference is that if you have multiple steps in your scenario, this value represents the iteration count of the steps.\n\n- `load_type` (_optional_)\n\n  This is the equivalent of the `-l` flag.\n\n- `duration` (_optional_)\n\n  This is the equivalent of the `-d` flag.\n\n- `manual_load` (_optional_)\n\n  If you are looking for creating your own custom load type, you can use this feature. The example below says that Ddosify will run the scenario 5 times, 10 times, and 20 times, respectively along with the provided durations. `iteration_count` and `duration` will be auto-filled by Ddosify according to `manual_load` configuration. In this example, `iteration_count` will be 35 and the `duration` will be 18 seconds.\n  Also `manual_load` overrides `load_type` if you provide both of them. As a result, you don't need to provide these 3 parameters when using `manual_load`.\n\n  ```json\n  \"manual_load\": [\n      {\"duration\": 5, \"count\": 5},\n      {\"duration\": 6, \"count\": 10},\n      {\"duration\": 7, \"count\": 20}\n  ]\n  ```\n\n- `proxy` (_optional_)\n\n  This is the equivalent of the `-P` flag.\n\n- `output` (_optional_)\n\n  This is the equivalent of the `-o` flag.\n\n- `engine_mode` (_optional_)\n\n  Can be one of `distinct-user`, `repeated-user`, or default mode `ddosify`.\n\n  - `distinct-user` mode simulates a new user for every iteration.\n  - `repeated-user` mode can use pre-used user in subsequent iterations.\n  - `ddosify` mode is default mode of the engine. In this mode engine runs in its max capacity, and does not show user simulation behaviour.\n\n- `env` (_optional_)\n\n  Scenario-scoped global variables. Note that dynamic variables changes every iteration.\n\n  ```json\n  \"env\": {\n          \"COMPANY_NAME\" :\"Ddosify\",\n          \"randomCountry\" : \"{{_randomCountry}}\"\n  }\n  ```\n\n- `data` (_optional_)\n\n  Config for loading test data from a CSV file.\n  [CSV data](https://github.com/getanteon/anteon/blob/master/ddosify_engine/config/config_testdata/test.csv) used in below config.\n\n  ```json\n  \"data\":{\n      \"info\": {\n          \"path\" : \"config/config_testdata/test.csv\",\n          \"delimiter\": \";\",\n          \"vars\": {\n                  \"0\":{\"tag\":\"name\"},\n                  \"1\":{\"tag\":\"city\"},\n                  \"2\":{\"tag\":\"team\"},\n                  \"3\":{\"tag\":\"payload\", \"type\":\"json\"},\n                  \"4\":{\"tag\":\"age\", \"type\":\"int\"}\n                  },\n          \"allow_quota\" : true,\n          \"order\": \"sequential\",\n          \"skip_first_line\" : true,\n          \"skip_empty_line\" : true\n      }\n  }\n  ```\n\n  | Field             | Description                                                                                                                                                                               | Type     | Default  | Required? |\n  | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -------- | --------- |\n  | `path`            | Local path or remote url for your CSV file                                                                                                                                                | `string` | -        | Yes       |\n  | `delimiter`       | Delimiter for reading CSV                                                                                                                                                                 | `string` | `,`      | No        |\n  | `vars`            | Tag columns using column index as key, use `type` field if you want to cast a column to a specific type, default is `string`, can be one of the following: `json`, `int`, `float`,`bool`. | `map`    | -        | Yes       |\n  | `allow_quota`     | If set to true, a quote may appear in an unquoted field and a non-doubled quote may appear in a quoted field                                                                              | `bool`   | `false`  | No        |\n  | `order`           | Order of reading records from CSV. Can be `random` or `sequential`                                                                                                                        | `string` | `random` | No        |\n  | `skip_first_line` | Skips first line while reading records from CSV.                                                                                                                                          | `bool`   | `false`  | No        |\n  | `skip_empty_line` | Skips empty lines while reading records from CSV.                                                                                                                                         | `bool`   | `true`   | No        |\n\n- `success_criterias` (_optional_)\n\n  Config for pass fail logic for the test. _abort_ and _delay_ fields can be used to adjust the abort behaviour in case of failure. If abort is true for a rule and rules fails at certain point, engine will decide to abort test immediately if delay is 0 or not given. If delay is given, it will wait for delay seconds and reassert the rule.\n\n  **Example:** Check _90th percentile_ and _fail_count_;\n\n  ```json\n  {\n   \"duration\": 10,\n   <other_global_configurations>,\n   \"success_criterias\": [\n    {\n      \"rule\" : \"p90(iteration_duration) < 220\",\n      \"abort\" : false\n    },\n    {\n      \"rule\" : \"fail_count_perc < 0.1\",\n      \"abort\" : true,\n      \"delay\" : 1\n    },\n    {\n      \"rule\" : \"fail_count < 100\",\n      \"abort\" : true,\n      \"delay\" : 0\n    }\n  ],\n   \"steps\": [....]\n  }\n  ```\n\n- `steps` (_required_)\n\n  This parameter lets you create your scenario. Ddosify runs the provided steps, respectively. For the given example file step id: 2 will be executed immediately after the response of step id: 1 is received. The order of the execution is the same as the order of the steps in the config file.\n\n  **Details of each parameter for a step;**\n\n  - `id` (_required_)\n\n    Each step must have a unique integer id.\n\n  - `url` (_required_)\n\n    This is the equivalent of the `-t` flag.\n\n  - `name` (_optional_) <a name=\"#step-name\"></a>\n\n    Name of the step.\n\n  - `method` (_optional_)\n\n    This is the equivalent of the `-m` flag.\n\n  - `headers` (_optional_)\n\n    List of headers with key:value format.\n\n  - `payload` (_optional_)\n\n    Body or payload. This is the equivalent of the `-b` flag.\n\n    _Note:_ If you want to use `x-www-form-urlencoded`, set Content-Type header to `application/x-www-form-urlencoded`.\n\n    **Example:** send `x-www-form-urlencoded` data;\n\n    ```json\n    {\n      \"headers\": {\n        \"Content-Type\": \"application/x-www-form-urlencoded\"\n      },\n      \"payload\": \"key1=value1&key2=value2\"\n    }\n    ```\n\n  - `payload_file` (_optional_)\n\n    If you need a long payload, we suggest using this parameter instead of `payload`.\n\n  - `payload_multipart` (_optional_) <a name=\"#payload_multipart\"></a>\n\n    Use this for `multipart/form-data` Content-Type.\n\n    Accepts list of `form-field` objects, structured as below;\n\n    ```json\n    {\n        \"name\": [field-name],\n        \"value\": [field-value|file-path|url],\n        \"type\": <text|file>,    // Default \"text\"\n        \"src\": <local|remote>   // Default \"local\"\n    }\n    ```\n\n    **Example:** Sending form name-value pairs;\n\n    ```json\n    \"payload_multipart\": [\n        {\n            \"name\": \"[field-name]\",\n            \"value\": \"[field-value]\"\n        }\n    ]\n    ```\n\n    **Example:** Sending form name-value pairs and a local file;\n\n    ```json\n    \"payload_multipart\": [\n        {\n            \"name\": \"[field-name]\",\n            \"value\": \"[field-value]\",\n        },\n        {\n            \"name\": \"[field-name]\",\n            \"value\": \"./test.png\",\n            \"type\": \"file\"\n        }\n    ]\n    ```\n\n    **Example:** Sending form name-value pairs and a local file and a remote file;\n\n    ```json\n    \"payload_multipart\": [\n        {\n            \"name\": \"[field-name]\",\n            \"value\": \"[field-value]\",\n        },\n        {\n            \"name\": \"[field-name]\",\n            \"value\": \"./test.png\",\n            \"type\": \"file\"\n        },\n        {\n            \"name\": \"[field-name]\",\n            \"value\": \"http://getanteon.com/test.png\",\n            \"type\": \"file\",\n            \"src\": \"remote\"\n        }\n    ]\n    ```\n\n    _Note:_ Ddosify adds `Content-Type: multipart/form-data; boundary=[generated-boundary-value]` header to the request when using `payload_multipart`.\n\n  - `timeout` (_optional_)\n\n    This is the equivalent of the `-T` flag.\n\n  - `capture_env` (_optional_)\n\n    Config for extraction of variables to use them in next steps.\n    **Example:** Capture _NUM_ variable from steps response body;\n\n    ```json\n    \"steps\": [\n        {\n            \"id\": 1,\n            \"url\": \"http://getanteon.com/endpoint1\",\n            \"capture_env\": {\n                 \"NUM\" :{\"from\":\"body\",\"json_path\":\"num\"},\n            }\n        },\n    ]\n    ```\n\n  - `assertion` (_optional_)\n\n    The response from this step will be subject to the assertion rules. If one of the provided rules fails, step is considered as failure.\n    **Example:** Check _status code_ and _content-length_ header values;\n\n    ```json\n    \"steps\": [\n        {\n            \"id\": 1,\n            \"url\": \"http://getanteon.com/endpoint1\",\n            \"assertion\": [\n                \"equals(status_code,200)\",\n                \"in(headers.content-length,[2000,3000])\"\n            ]\n        },\n    ]\n    ```\n\n  - `sleep` (_optional_) <a name=\"#sleep\"></a>\n\n    Sleep duration(ms) before executing the next step. Can be an exact duration or a range.\n\n    **Example:** Sleep 1000ms after step-1;\n\n    ```json\n    \"steps\": [\n        {\n            \"id\": 1,\n            \"url\": \"http://getanteon.com/endpoint1\",\n            \"sleep\": \"1000\"\n        },\n        {\n            \"id\": 2,\n            \"url\": \"http://getanteon.com/endpoint2\",\n        }\n    ]\n    ```\n\n    **Example:** Sleep between 300ms-500ms after step-1;\n\n    ```json\n    \"steps\": [\n        {\n            \"id\": 1,\n            \"url\": \"http://getanteon.com/endpoint1\",\n            \"sleep\": \"300-500\"\n        },\n        {\n            \"id\": 2,\n            \"url\": \"http://getanteon.com/endpoint2\",\n        }\n    ]\n    ```\n\n  - `auth` (_optional_)\n\n    Basic authentication.\n\n    ```json\n    \"auth\": {\n        \"username\": \"test_user\",\n        \"password\": \"12345\"\n    }\n    ```\n\n  - `others` (_optional_)\n\n    This parameter accepts dynamic _key: value_ pairs to configure connection details of the protocol in use.\n\n    ```json\n    \"others\": {\n        \"disable-compression\": false,    // Default true\n        \"h2\": true,                      // Enables HTTP/2. Default false.\n        \"disable-redirect\": true         // Default false\n    }\n    ```\n\n## Parameterization (Dynamic Variables)\n\nJust like the Postman, Ddosify supports parameterization (dynamic variables) on _URL_, _headers_, _payload (body)_ and _basic authentication_. Actually, we support all the random methods Postman supports. If you use `{{$randomVariable}}` on Postman you can use it as `{{_randomVariable}}` on Ddosify. Just change `$` to `_` and you will be fine. To simulate a realistic load test on your system, Ddosify can send every request with dynamic variables.\n\nThe full list of dynamic variables can be found in the [documentation](https://getanteon.com/docs/performance-testing/dynamic-variables-parametrization/).\n\n### Parameterization on URL\n\nDdosify sends _100_ GET requests in _10_ seconds with random string `key` parameter. This approach can be also used in cache bypass.\n\n```bash\nddosify -t https://getanteon.com/?key={{_randomString}} -d 10 -n 100\n```\n\n### Parameterization on Headers\n\nDdosify sends _100_ GET requests in _10_ seconds with random `Transaction-Type` and `Country` headers.\n\n```bash\nddosify -t https://getanteon.com -d 10 -n 100 -h 'Transaction-Type: {{_randomTransactionType}}' -h 'Country: {{_randomCountry}}'\n```\n\n### Parameterization on Payload (Body)\n\nDdosify sends _100_ GET requests in _10_ seconds with random `latitude` and `longitude` values in body.\n\n```bash\nddosify -t https://getanteon.com -d 10 -n 100 -b '{\"latitude\": \"{{_randomLatitude}}\", \"longitude\": \"{{_randomLongitude}}\"}'\n```\n\n### Parameterization on Basic Authentication\n\nDdosify sends _100_ GET requests in _10_ seconds with random `username` and `password` with basic authentication.\n\n```bash\nddosify -t https://getanteon.com -d 10 -n 100 -a '{{_randomUserName}}:{{_randomPassword}}'\n```\n\n### Parameterization on Config File\n\nDynamic variables can be used on config file as well. Ddosify sends _100_ GET requests in _10_ seconds with random string `key` parameter in URL and random `User-Key` header.\n\n```bash\nddosify -config ddosify_config_dynamic.json\n```\n\n```json\n{\n  \"iteration_count\": 100,\n  \"load_type\": \"linear\",\n  \"duration\": 10,\n  \"steps\": [\n    {\n      \"id\": 1,\n      \"url\": \"https://getanteon.com/?key={{_randomString}}\",\n      \"method\": \"POST\",\n      \"headers\": {\n        \"User-Key\": \"{{_randomInt}}\"\n      }\n    }\n  ]\n}\n```\n\n### Environment Variables\n\nIn addition, you can also use operating system environment variables. To access these variables, simply add the `$` prefix followed by the variable name wrapped in double curly braces. The syntax for this is `{{$OS_ENV_VARIABLE}}` within the **config file**.\n\nFor instance, to use the `USER` environment variable from your operating system, simply input `{{$USER}}`. You can use operating system environment variables in `URL`, `Headers`, `Body (Payload)`, and `Basic Authentication`.\n\nHere is an example of using operating system environment variables in the config file. `TARGET_SITE` operating system environment variable is used in `URL` and `USER` environment variable is used in `Headers`.\n\n```bash\nexport TARGET_SITE=\"https://getanteon.com\"\nddosify -config ddosify_config_os_env.json\n```\n\n```json\n{\n  \"iteration_count\": 100,\n  \"load_type\": \"linear\",\n  \"duration\": 10,\n  \"steps\": [\n    {\n      \"id\": 1,\n      \"url\": \"{{$TARGET_SITE}}\",\n      \"method\": \"POST\",\n      \"headers\": {\n        \"os-env-user\": \"{{$USER}}\"\n      }\n    }\n  ]\n}\n```\n\n## Assertion\n\nBy default, Ddosify marks a step result as successful if it sends the request and receives the response without any network errors. Status code or body type (or content) does not affect the success/failure criteria. However, this may not provide a good test result for your use case, and you may want to create your own success/fail logic. That's where Assertions come in.\n\nDdosify supports assertions on `status code`, `response body`, `response size`, `response time`, `headers`, and `variables`. You can use the `assertion` parameter in the config file to check if the response matches the given condition per step. If the condition is not met, Ddosify will fail the step. Check the [example config](https://github.com/getanteon/anteon/blob/master/ddosify_engine/config_examples/config.json) to see how it looks.\n\nAs shown in the related table, the first five keywords store different data related to the response. The last keyword, `variables`, stores the current state of environment variables for the step. You can use [Functions](#functions) or [Operators](#operators) to build conditional expressions based on these keywords.\n\nYou can write multiple assertions for a step. If any assertion fails, the step is marked as failed.\n\nIf Ddosify can't receive the response for a request, that step is marked as failed without processing the assertions. You will see a **Server Error** as the failure reason in the test result instead of an **Assertion Error**.\n\n### Keywords\n\n| Keyword         | Description                   | Usage              |\n| --------------- | ----------------------------- | ------------------ |\n| `status_code`   | Status code                   | -                  |\n| `body`          | Response body                 | -                  |\n| `response_size` | Response size in bytes        | -                  |\n| `response_time` | Response time in ms           | -                  |\n| `headers`       | Response headers              | headers.header-key |\n| `variables`     | Global and captured variables | variables.VarName  |\n\n### Functions\n\n| Function         | Parameters                                      | Description                                                                     |\n| ---------------- | ----------------------------------------------- | ------------------------------------------------------------------------------- |\n| `less_than`      | ( param `int`, limit `int` )                    | checks if param is less than limit                                              |\n| `greater_than`   | ( param `int`, limit `int` )                    | checks if param is greater than limit                                           |\n| `exists`         | ( param `any` )                                 | checks if variable exists                                                       |\n| `equals`         | ( param1 `any`, param2 `any` )                  | checks if given parameters are equal                                            |\n| `equals_on_file` | ( param `any`, file_path `string` )             | reads from given file path and checks if it equals to given parameter           |\n| `in`             | ( param `any`, array_param `array` )            | checks if expression is in given array                                          |\n| `contains`       | ( param1 `any`, param2 `any` )                  | makes substring with param1 inside param2                                       |\n| `not`            | ( param `bool` )                                | returns converse of given param                                                 |\n| `range`          | ( param `int`, low `int`,high `int` )           | returns param is in range of [low,high): low is included, high is not included. |\n| `json_path`      | ( json_path `string`)                           | extracts from response body using given json path                               |\n| `xpath`          | ( xpath `string` )                              | extracts from response body using given xml path                                |\n| `html_path`      | ( html `string` )                               | extracts from response body using given html path                               |\n| `regexp`         | ( param `any`, regexp `string`, matchNo `int` ) | extracts from given value in the first parameter using given regular expression |\n\n### Operators\n\n| Operator | Description  |\n| -------- | ------------ |\n| `==`     | equals       |\n| `!=`     | not equals   |\n| `>`      | greater than |\n| `<`      | less than    |\n| `!`      | not          |\n| `&&`     | and          |\n| `\\|\\|`   | or           |\n\n### Assertion Examples\n\n| Expression                                         | Description                                                                     |\n| -------------------------------------------------- | ------------------------------------------------------------------------------- |\n| `less_than(status_code,201)`                       | checks if status code is less than 201                                          |\n| `equals(status_code,200)`                          | checks if status code equals to 200                                             |\n| `status_code == 200`                               | same as preceding one                                                           |\n| `not(status_code == 500)`                          | checks if status code not equals to 500                                         |\n| `status_code != 500`                               | same as preceding one                                                           |\n| `equals(json_path(\\\"employees.0.name\\\"),\\\"Name\\\")` | checks if json extracted value is equal to \"Name\"                               |\n| `equals(xpath(\\\"//item/title\\\"),\\\"ABC\\\")`          | checks if xml extracted value is equal to \"ABC\"                                 |\n| `equals(html_path(\\\"//body/h1\\\"),\\\"ABC\\\")`         | checks if html extracted value is equal to \"ABC\"                                |\n| `equals(variables.x,100)`                          | checks if `x` variable coming from global or captured variables is equal to 100 |\n| `equals(variables.x,variables.y)`                  | checks if variables `x` and `y` are equal to each other                         |\n| `equals_on_file(body,\\\"file.json\\\")`               | reads from file.json and compares response body with read file                  |\n| `exists(headers.Content-Type)`                     | checks if content-type header exists in response headers                        |\n| `contains(body,\\\"xyz\\\")`                           | checks if body contains \"xyz\" in it                                             |\n| `range(headers.content-length,100,300)`            | checks if content-length header is in range [100,300)                           |\n| `in(status_code,[200,201])`                        | checks if status code equal to 200 or 201                                       |\n| `(status_code == 200) \\|\\| (status_code == 201)`   | same as preceding one                                                           |\n| `regexp(body,\\\"[a-z]+_[0-9]+\\\",0) == \\\"messi_10\\\"` | checks if matched result from regex is equal to \"messi_10\"                      |\n\n## Success Criteria (Pass / Fail)\n\nDdosify supports success criteria, allowing users to verify the success of their load tests based on response times and failure counts of iterations. With this feature, users can assert the percentile of response times and the failure counts of all iterations in a test.\n\nUsers can specify the required percentile of response times and failure counts in the configuration file, and the engine will compare the actual response times and failure counts to these values throughout the test continuously. According to the user's configuration, the test can be aborted or continue running until the end. Check the [example config](https://github.com/getanteon/anteon/blob/master/ddosify_engine/config_examples/config.json) to see how the `success_criterias` keyword looks.\n\nNote that the functions and operators mentioned in the [Step Assertion](#assertion) section can also be utilized for the Success Criteria keywords listed below.\n\nYou can see a success criteria example in the [EXAMPLES](https://github.com/getanteon/anteon/blob/master/ddosify_engine/EXAMPLES.md#example-2-success-criteria) file.\n\n## Difference Between Success Criteria and Step Assertions\n\nUnlike assertions focused on individual steps, which determine the success or failure of a step according to its response, Success Criteria create an abort/continue logic for the entire test, which is based on the accumulated data from all iterations.\n\n### Keywords\n\n| Keyword              | Description                           | Usage                                                             |\n| -------------------- | ------------------------------------- | ----------------------------------------------------------------- |\n| `fail_count`         | Failure count of iterations           | Used for aborting when test exceeds certain fail_count            |\n| `iteration_duration` | Response times of iterations in ms    | Used for percentile functions                                     |\n| `fail_count_perc`    | Fail count percentage, in range [0,1] | Used for aborting when test exceeds certain fail count percentage |\n\n### Functions\n\n| Function | Parameters          | Description                                       |\n| -------- | ------------------- | ------------------------------------------------- |\n| `p99`    | ( arr `int array` ) | 99th percentile, use as `p99(iteration_duration)` |\n| `p98`    | ( arr `int array` ) | 98th percentile, use as `p98(iteration_duration)` |\n| `p95`    | ( arr `int array`)  | 95th percentile, use as `p95(iteration_duration)` |\n| `p90`    | ( arr `int array`)  | 90th percentile, use as `p90(iteration_duration)` |\n| `p80`    | ( arr `int array`)  | 80th percentile, use as `p80(iteration_duration)` |\n| `min`    | ( arr `int array`)  | returns minimum element                           |\n| `max`    | ( arr `int array`)  | returns maximum element                           |\n| `avg`    | ( arr `int array`)  | calculates and returns average                    |\n\n### Examples\n\n| Expression                        | Description                                  |\n| --------------------------------- | -------------------------------------------- |\n| `p95(iteration_duration) < 100`   | 95th percentile should be less than 100 ms   |\n| `less_than(fail_count,120)`       | Total fail count should be less than 120     |\n| `less_than(fail_count_perc,0.05)` | Fail count percentage should be less than 5% |\n\n## Correlation\n\nDdosify enables you to capture variables from steps using **json_path**, **xpath**, **xpath_html**, or **regular expressions**. Later, in the subsequent steps, you can inject both the captured variables and the scenario-scoped global variables.\n\n> **:warning: Points to keep in mind**\n>\n> - You must specify **'header_key'** when capturing from header.\n> - For json_path syntax, please take a look at [gjson syntax](https://github.com/tidwall/gjson/blob/master/SYNTAX.md) doc.\n> - Regular expression are expected in **'Golang'** style regex. For converting your existing regular expressions, you can use [regex101](https://regex101.com).\n> - You can extract values from **headers**, **body**, and **cookies**.\n\nYou can use **debug** parameter to validate your config.\n\n```bash\nddosify -config ddosify_config_correlation.json -debug\n```\n\n### Capture with json_path\n\n```json\n{\n  \"steps\": [\n    {\n      \"capture_env\": {\n        \"NUM\": { \"from\": \"body\", \"json_path\": \"num\" },\n        \"NAME\": { \"from\": \"body\", \"json_path\": \"name\" },\n        \"SQUAD\": { \"from\": \"body\", \"json_path\": \"squad\" },\n        \"PLAYERS\": { \"from\": \"body\", \"json_path\": \"squad.players\" },\n        \"MESSI\": { \"from\": \"body\", \"json_path\": \"squad.players.0\" }\n      }\n    }\n  ]\n}\n```\n\n### Capture with XPath on XML\n\n```json\n{\n  \"steps\": [\n    {\n      \"capture_env\": {\n        \"TITLE\": { \"from\": \"body\", \"xpath\": \"//item/title\" }\n      }\n    }\n  ]\n}\n```\n\n### Capture with XPath on HTML\n\n```json\n{\n  \"steps\": [\n    {\n      \"capture_env\": {\n        \"TITLE\": { \"from\": \"body\", \"xpath_html\": \"//body/h1\" }\n      }\n    }\n  ]\n}\n```\n\n### Capture with Regular Expressions\n\n```json\n{\n  \"steps\": [\n    {\n      \"capture_env\": {\n        \"CONTENT_TYPE\": {\n          \"from\": \"header\",\n          \"header_key\": \"Content-Type\",\n          \"regexp\": { \"exp\": \"application/(\\\\w)+\", \"matchNo\": 0 }\n        },\n        \"REGEX_MATCH_ENV\": {\n          \"from\": \"body\",\n          \"regexp\": { \"exp\": \"[a-z]+_[0-9]+\", \"matchNo\": 1 }\n        }\n      }\n    }\n  ]\n}\n```\n\n### Capture Header Value\n\n```json\n{\n  \"steps\": [\n    {\n      \"capture_env\": {\n        \"TOKEN\": { \"from\": \"header\", \"header_key\": \"Authorization\" }\n      }\n    }\n  ]\n}\n```\n\n### Scenario-Scoped Variables\n\n```json\n{\n  \"env\": {\n    \"TARGET_URL\": \"http://localhost:8084/hello\",\n    \"USER_KEY\": \"ABC\",\n    \"COMPANY_NAME\": \"Ddosify\",\n    \"RANDOM_COUNTRY\": \"{{_randomCountry}}\",\n    \"NUMBERS\": [22, 33, 10, 52]\n  }\n}\n```\n\n### Overall Config and Injection\n\nOn array-like captured variables or environment vars, the **rand( )** function can be utilized.\n\n```json\n// ddosify_config_correlation.json\n{\n  \"iteration_count\": 100,\n  \"load_type\": \"linear\",\n  \"duration\": 10,\n  \"steps\": [\n    {\n      \"id\": 1,\n      \"url\": \"{{TARGET_URL}}\",\n      \"method\": \"POST\",\n      \"headers\": {\n        \"User-Key\": \"{{USER_KEY}}\",\n        \"Rand-Selected-Num\": \"{{rand(NUMBERS)}}\"\n      },\n      \"payload\": \"{{COMPANY_NAME}}\",\n      \"capture_env\": {\n        \"NUM\": { \"from\": \"body\", \"json_path\": \"num\" },\n        \"NAME\": { \"from\": \"body\", \"json_path\": \"name\" },\n        \"SQUAD\": { \"from\": \"body\", \"json_path\": \"squad\" },\n        \"PLAYERS\": { \"from\": \"body\", \"json_path\": \"squad.players\" },\n        \"MESSI\": { \"from\": \"body\", \"json_path\": \"squad.players.0\" },\n        \"TOKEN\": { \"from\": \"header\", \"header_key\": \"Authorization\" },\n        \"CONTENT_TYPE\": {\n          \"from\": \"header\",\n          \"header_key\": \"Content-Type\",\n          \"regexp\": { \"exp\": \"application/(\\\\w)+\", \"matchNo\": 0 }\n        }\n      }\n    },\n    {\n      \"id\": 2,\n      \"url\": \"{{TARGET_URL}}\",\n      \"method\": \"POST\",\n      \"headers\": {\n        \"User-Key\": \"{{USER_KEY}}\",\n        \"Authorization\": \"{{TOKEN}}\",\n        \"Content-Type\": \"{{CONTENT_TYPE}}\"\n      },\n      \"payload_file\": \"payload.json\",\n      \"capture_env\": {\n        \"TITLE\": { \"from\": \"body\", \"xpath\": \"//item/title\" },\n        \"REGEX_MATCH_ENV\": {\n          \"from\": \"body\",\n          \"regexp\": { \"exp\": \"[a-z]+_[0-9]+\", \"matchNo\": 1 }\n        }\n      }\n    }\n  ],\n  \"env\": {\n    \"TARGET_URL\": \"http://localhost:8084/hello\",\n    \"USER_KEY\": \"ABC\",\n    \"COMPANY_NAME\": \"Ddosify\",\n    \"RANDOM_COUNTRY\": \"{{_randomCountry}}\",\n    \"NUMBERS\": [22, 33, 10, 52]\n  }\n}\n```\n\n```json\n// payload.json\n{\n  \"boolField\": \"{{_randomBoolean}}\",\n  \"numField\": \"{{NUM}}\",\n  \"strField\": \"{{NAME}}\",\n  \"numArrayField\": [\"{{NUM}}\", 34],\n  \"strArrayField\": [\"{{NAME}}\", \"hello\"],\n  \"mixedArrayField\": [\"{{NUM}}\", 34, \"{{NAME}}\", \"{{SQUAD}}\"],\n  \"{{NAME}}\": \"messi\",\n  \"obj\": {\n    \"numField\": \"{{NUM}}\",\n    \"objectField\": \"{{SQUAD}}\",\n    \"arrayField\": \"{{PLAYERS}}\"\n  }\n}\n```\n\n## Test Data Set\n\nDdosify enables you to load test data from **CSV** files. Later, in your scenario, you can inject variables that you tagged.\n\nWe are using this [CSV data](https://github.com/getanteon/anteon/tree/master/ddosify_engine/config/config_testdata/test.csv) in config below.\n\n```json\n// config_data_csv.json\n\"data\":{\n      \"csv_test\": {\n          \"path\" : \"config/config_testdata/test.csv\",\n          \"delimiter\": \";\",\n          \"vars\": {\n                  \"0\":{\"tag\":\"name\"},\n                  \"1\":{\"tag\":\"city\"},\n                  \"2\":{\"tag\":\"team\"},\n                  \"3\":{\"tag\":\"payload\", \"type\":\"json\"},\n                  \"4\":{\"tag\":\"age\", \"type\":\"int\"}\n                },\n          \"allow_quota\" : true,\n          \"order\": \"random\",\n          \"skip_first_line\" : true\n      }\n    }\n```\n\nYou can refer to tagged variables in your request like below.\n\n```json\n// payload.json\n{\n  \"name\": \"{{data.csv_test.name}}\",\n  \"team\": \"{{data.csv_test.team}}\",\n  \"city\": \"{{data.csv_test.city}}\",\n  \"payload\": \"{{data.csv_test.payload}}\",\n  \"age\": \"{{data.csv_test.age}}\"\n}\n```\n\n## Cookies\n\nDdosify supports cookies in the following engine modes: `distinct-user` and `repeated-user`. Cookies are not supported in the default `ddosify` mode.\n\nIn `repeated-user` mode, Ddosify uses the same cookie jar for all iterations executed by the same user. It sets cookies returned at the first successful iteration and does not change them afterward. This way, the same cookies are passed through steps in all iterations executed by the same user.\n\nIn `distinct-user` mode, Ddosify uses a different cookie jar for each iteration, so cookies are passed through steps in one iteration only.\n\nYou can see a cookie example in the [EXAMPLES](https://github.com/getanteon/anteon/blob/master/ddosify_engine/EXAMPLES.md#example-1-cookie-support) file.\n\n### Initial / Custom Cookies\n\nYou can set initial/custom cookies for your test scenario using `cookie_jar` field in the config file. You can enable/disable custom cookies with `enabled` key. Check the [example config](https://github.com/getanteon/anteon/blob/master/ddosify_engine/config/config_testdata/config_init_cookies.json).\n\n| Key         | Description                                                                                                     | Example                                                           |\n| ----------- | --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |\n| `name`      | The name of the cookie. This field is used to identify the cookie.                                              | `platform`                                                        |\n| `value`     | The value of the cookie. This field contains the data that the cookie stores.                                   | `web`                                                             |\n| `domain`    | Domain or subdomain that can access the cookie.                                                                 | `app.getanteon.com`                                               |\n| `path`      | Path within the domain that can access the cookie.                                                              | `/`                                                               |\n| `expires`   | When the cookie should expire. The date format should be rfc2616.                                               | `Thu, 16 Mar 2023 09:24:02 GMT`                                   |\n| `max_age`   | Number of seconds until the cookie expires.                                                                     | `5`                                                               |\n| `http_only` | Whether the cookie should only be accessible through HTTP or HTTPS headers, and not through client-side scripts | `true`                                                            |\n| `secure`    | Whether the cookie should only be sent over a secure (HTTPS) connection                                         | `false`                                                           |\n| `raw`       | The raw format of the cookie. If it is used, the other keys are discarded.                                      | `myCookie=myValue; Expires=Wed, 21 Oct 2026 07:28:00 GMT; Path=/` |\n\n### Cookie Capture\n\nYou can capture values from cookies from its name just like you do for headers and body and use them in your test scenario.\n\n```json\n{\n    \"iteration_count\": 100,\n    \"load_type\": \"linear\",\n    \"duration\": 10,\n    \"steps\": [\n        {\n          ...\n          \"capture_env\": {\n            \"TEST\" :{\"from\":\"cookies\",\"cookie_name\":\"test\"}\n          }\n        }\n    ]\n}\n```\n\n### Cookie Assertion\n\nYou can refer to cookie values as `cookies.cookie_name` while you write assertions for your steps.\n\nFollowing fields are available for cookie assertion:\n\n- `name`: Name of the cookie\n- `domain`: Domain of the cookie\n- `path`: Path of the cookie\n- `value`: Value of the cookie\n- `expires`: Expiration date of the cookie\n- `maxAge`: Max age of the cookie\n- `secure`: Secure flag of the cookie\n- `httpOnly`: Http only flag of the cookie\n- `rawExpires`: Raw expiration date of the cookie\n\n**Examples:**\n\n- `cookies.test.expires < time(\\\"Thu, 01 Jan 1990 00:00:00 GMT\\\")` is a valid assertion expression. It checks if the cookie named `test` has an expiration date before `Thu, 01 Jan 1990 00:00:00 GMT`.\n- `cookies.test.path == \\\"/login\\\"` is another valid assertion expression. It checks if the cookie named `test` has a path value equal to `/login`.\n\n## Common Issues\n\n### macOS Security Issue\n\n```\n\"ddosify\" can’t be opened because Apple cannot check it for malicious software.\n```\n\n- Open `/usr/local/bin`\n- Right click `ddosify` and select Open\n- Select Open\n- Close the opened terminal\n\n### OS Limit - Too Many Open Files\n\nIf you create large load tests, you may encounter the following errors:\n\n```\nServer Error Distribution (Count:Reason):\n  199      :Get \"https://getanteon.com\": dial tcp 188.114.96.3:443: socket: too many open files\n  159      :Get \"https://getanteon.com\": dial tcp 188.114.97.3:443: socket: too many open files\n```\n\nThis is because the OS limits the number of open files. You can check the current limit by running `ulimit -n` command. You can increase this limit to 50000 by running the following command on both Linux and macOS.\n\n```bash\nulimit -n 50000\n```\n\nBut this will only increase the limit for the current session. To increase the limit permanently, you can change the shell configuration file. For example, if you are using bash, you can add the following lines to `~/.bashrc` file. If you are using zsh, you can add the following lines to `~/.zshrc` file.\n\n```bash\n# For .bashrc\necho \"ulimit -n 50000\" >> ~/.bashrc\n\n# For .zshrc\necho \"ulimit -n 50000\" >> ~/.zshrc\n```\n\n## Contributing\n\nSee our [Contribution Guide](../CONTRIBUTING.md) and please follow the [Code of Conduct](../CODE_OF_CONDUCT.md) in all your interactions with the project.\n\n## Communication\n\nYou can join our [Discord Server](https://discord.com/invite/9KdnrSUZQg) for issues, feature requests, feedbacks or anything else.\n\n## Disclaimer\n\nDdosify is created for testing the performance of web applications. Users must be the owner of the target system. Using it for harmful purposes is extremely forbidden. Ddosify team & company is not responsible for its’ usages and consequences.\n\n## License\n\nLicensed under the [AGPLv3](../LICENSE)\n"
  },
  {
    "path": "ddosify_engine/completions/README.md",
    "content": "# Shell completions\n\n## Zsh\n\n`completions/_ddosify` provides a basic auto-completions. You can apply one of the steps to get an auto-completion successfully. \n\nYou can locate the file in any directory referenced by `$fpath`. You can use the following command to list directories in `$fpath`.\n\n```bash\necho $fpath | tr ' ' '\\n'\n```\n\nFor example, if you are using [oh-my-zsh](https://ohmyz.sh/) you can add it as a plugin after locating file under plugin related directory appeared in `$fpath`. You can create a directory named `ddosify` under `~/.oh-my-zsh/plugins` and copy `_ddosify` file to it.\n\n```bash\nmkdir -p ~/.oh-my-zsh/plugins/ddosify\ncp completions/_ddosify ~/.oh-my-zsh/plugins/ddosify\n```\n\nThen, you can add `ddosify` to your plugins list in `~/.zshrc` file.\n\n```\n# ~/.zshrc\n\nplugins=(\n  ...\n  ddosify\n)\n```\n\nIf you don't have an appropriate directory, you can create one and add it to `$fpath`.\n  \n```\nmkdir -p ${ZDOTDIR:-~}/.zsh_functions\necho 'fpath+=${ZDOTDIR:-~}/.zsh_functions' >> ${ZDOTDIR:-~}/.zshrc\n```\n\nThen, you can copy `_ddosify` file to the directory you created.\n\n```\ncp completions/_ddosify ${ZDOTDIR:-~}/.zsh_functions/_ddosify\n```\n"
  },
  {
    "path": "ddosify_engine/completions/_ddosify",
    "content": "#compdef ddosify _ddosify\n\ntypeset -A opt_args\n\n_ddosify() {\n    local curcontext=\"$curcontext\" state line\n    local -a opts\n\n    opts+=(\n        \"-t[Target URL.]\"\n        \"-P[Proxy address as protocol\\://username\\:password@host\\:port. Supported proxies \\[http(s), socks\\].]\"\n        \"-T[Request timeout in seconds (default 5).]\" \n        \"-a[Basic authentication, username\\:password.]\"\n        \"-b[Payload of the network packet (body).]\"\n        \"-cert_key_path[A path to a certificate key file (usually called 'key.pem').]:filename:_files\"\n        \"-cert_path[A path to a certificate file (usually called 'cert.pem'.)]:filename:_files\"\n        \"-config[Json config file path. If a config file is provided, other flag values will be ignored.]:filename:_files\"\n        \"-d[Test duration in seconds (default 10).]\"\n        \"-debug[Iterates the scenario once and prints curl-like verbose result.]\"\n        \"-h[Request Headers. Ex\\: -h 'Accept\\: text/html' -h 'Content-Type\\: application/xml'.]\"\n        \"-l[Type of the load test \\['linear', 'incremental', 'waved'\\] (default 'linear').]\"\n        \"-m[Request Method Type. For Http(s)\\:\\['GET', 'POST', 'PUT', 'DELETE', 'UPDATE', 'PATCH'\\] (default 'GET').]\"\n        \"-n[Total iteration count (default 100).]\"\n        \"-o[Output destination (default 'stdout').]\"\n        \"-version[Prints version, git commit, built date (utc), go information and quit.]\"\n    )\n\n    _arguments -s -w : $opts && return 0\n\n    return 1\n}\n\n"
  },
  {
    "path": "ddosify_engine/config/base.go",
    "content": "/*\n*\n*\tDdosify - Load testing tool for any web system.\n*   Copyright (C) 2021  Ddosify (https://ddosify.com)\n*\n*   This program is free software: you can redistribute it and/or modify\n*   it under the terms of the GNU Affero General Public License as published\n*   by the Free Software Foundation, either version 3 of the License, or\n*   (at your option) any later version.\n*\n*   This program is distributed in the hope that it will be useful,\n*   but WITHOUT ANY WARRANTY; without even the implied warranty of\n*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n*   GNU Affero General Public License for more details.\n*\n*   You should have received a copy of the GNU Affero General Public License\n*   along with this program.  If not, see <https://www.gnu.org/licenses/>.\n*\n */\n\npackage config\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\n\t\"go.ddosify.com/ddosify/core/types\"\n)\n\nvar AvailableConfigReader = make(map[string]ConfigReader)\n\n// ConfigReader is the interface that abstracts different config reader implementations.\ntype ConfigReader interface {\n\tInit([]byte) error\n\tCreateHammer() (types.Hammer, error)\n}\n\n// NewConfigReader is the factory method of the ConfigReader.\nfunc NewConfigReader(config []byte, configType string) (reader ConfigReader, err error) {\n\tif val, ok := AvailableConfigReader[configType]; ok {\n\t\t// Create a new object from the service type\n\t\treader = reflect.New(reflect.TypeOf(val).Elem()).Interface().(ConfigReader)\n\t\terr = reader.Init(config)\n\t} else {\n\t\terr = fmt.Errorf(\"unsupported config reader type: %s\", configType)\n\t}\n\treturn\n}\n"
  },
  {
    "path": "ddosify_engine/config/base_test.go",
    "content": "/*\n*\n*\tDdosify - Load testing tool for any web system.\n*   Copyright (C) 2021  Ddosify (https://ddosify.com)\n*\n*   This program is free software: you can redistribute it and/or modify\n*   it under the terms of the GNU Affero General Public License as published\n*   by the Free Software Foundation, either version 3 of the License, or\n*   (at your option) any later version.\n*\n*   This program is distributed in the hope that it will be useful,\n*   but WITHOUT ANY WARRANTY; without even the implied warranty of\n*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n*   GNU Affero General Public License for more details.\n*\n*   You should have received a copy of the GNU Affero General Public License\n*   along with this program.  If not, see <https://www.gnu.org/licenses/>.\n*\n */\n\npackage config\n\nimport (\n\t\"io/ioutil\"\n\t\"os\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc readConfigFile(path string) []byte {\n\tf, _ := os.Open(path)\n\n\tbyteValue, _ := ioutil.ReadAll(f)\n\treturn byteValue\n}\n\nfunc TestNewConfigReader(t *testing.T) {\n\tt.Parallel()\n\tconfigPath := \"config_testdata/config.json\"\n\treader, err := NewConfigReader(readConfigFile(configPath), ConfigTypeJson)\n\n\tif err != nil {\n\t\tt.Errorf(\"TestNewConfigReader errored: %v\", err)\n\t}\n\n\tif reflect.TypeOf(reader) != reflect.TypeOf(&JsonReader{}) {\n\t\tt.Errorf(\"Expected jsonReader found: %v\", reflect.TypeOf(reader))\n\t}\n}\n\nfunc TestNewConfigReaderInvalidConfigType(t *testing.T) {\n\tt.Parallel()\n\tconfigPath := \"config_testdata/config.json\"\n\t_, err := NewConfigReader(readConfigFile(configPath), \"invalidConfigType\")\n\n\tif err == nil {\n\t\tt.Errorf(\"TestNewConfigReaderInvalidConfigType errored\")\n\t}\n}\n\nfunc TestNewConfigReaderIncorrectJsonFile(t *testing.T) {\n\tt.Parallel()\n\tconfigPath := \"config_testdata/config_incorrect.json\"\n\t_, err := NewConfigReader(readConfigFile(configPath), ConfigTypeJson)\n\n\tif err == nil {\n\t\tt.Errorf(\"TestNewConfigReaderInvalidFilePath errored\")\n\t}\n}\n"
  },
  {
    "path": "ddosify_engine/config/config_testdata/benchmark/config_correlation_load_1.json",
    "content": "{\n    \"iteration_count\": 100,\n    \"engine_mode\": \"ddosify\",\n    \"load_type\": \"waved\",\n    \"duration\": 10,\n    \"steps\": [\n            {\n              \"id\": 1,\n              \"url\": \"{{HTTPBIN}}/json\",\n              \"name\": \"JSON\",\n              \"method\": \"GET\",\n              \"others\": {\n                \"h2\": false,\n                \"disable-redirect\": true,\n                \"disable-compression\": false\n              },\n              \"headers\": {\n                \n              },\n              \"payload\": \"\",\n              \"timeout\": 3,\n              \"capture_env\": {\n                \"NUM\" :{ \"from\":\"body\",\"json_path\":\"quoteResponse.result.0.askSize\"},\n                \"STR\" :{ \"from\":\"body\",\"json_path\":\"quoteResponse.result.0.currency\"},\n                \"BOOL\": {\"from\":\"body\",\"json_path\":\"quoteResponse.result.0.cryptoTradeable\"},\n                \"FLOAT\" : {\"from\":\"body\",\"json_path\":\"quoteResponse.result.0.epsForward\"},\n                \"ALL_RESULT\" :{\"from\":\"body\",\"json_path\":\"quoteResponse.result.0\"},\n                \"CONTENT_LENGTH\" :{\"from\":\"header\", \"header_key\":\"Content-Length\"},\n                \"CONTENT_TYPE\" :{\"from\":\"header\", \"header_key\":\"Content-Type\" ,\"regexp\":{\"exp\":\"application\\/(\\\\w)+\",\"matchNo\":0}  }             \n            }         \n            },\n            {\n              \"id\": 2,\n              \"url\": \"{{HTTPBIN}}/xml\",\n              \"name\": \"XML\",\n              \"method\": \"GET\",\n              \"others\": {\n                \"h2\": false,\n                \"disable-redirect\": true,\n                \"disable-compression\": false\n              },\n              \"headers\": {\n                \"num\": \"{{NUM}}\",\n                \"currency\": \"{{STR}}\",\n                \"yahoo\" : \"{{CONTENT_LENGTH}}\"\n              },\n              \"payload\": \"\",\n              \"timeout\": 10\n            },\n            {\n              \"id\": 3,\n              \"url\": \"https://servdown.com\",\n              \"name\": \"HTML\",\n              \"method\": \"GET\",\n              \"others\": {\n                \"h2\": false,\n                \"disable-redirect\": true,\n                \"disable-compression\": false\n              },\n              \"headers\": {\n                \"num\": \"{{NUM}}\"\n              },\n              \"payload_file\": \"config/config_testdata/benchmark/json_payload.json\",\n              \"timeout\": 10\n            }\n    ],\n    \"output\": \"stdout\",\n    \"env\":{\n        \"HTTPBIN\" : \"https://httpbin.ddosify.com\",\n        \"LOCAL\" : \"http://localhost:8084\"\n    },\n    \"debug\" : false\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/benchmark/config_correlation_load_2.json",
    "content": "{\n    \"iteration_count\": 1000,\n    \"load_type\": \"waved\",\n    \"engine_mode\": \"ddosify\",\n    \"duration\": 10,\n    \"steps\": [\n            {\n              \"id\": 1,\n              \"url\": \"{{HTTPBIN}}/json\",\n              \"name\": \"JSON\",\n              \"method\": \"GET\",\n              \"others\": {\n                \"h2\": false,\n                \"disable-redirect\": true,\n                \"disable-compression\": false\n              },\n              \"headers\": {\n                \n              },\n              \"payload\": \"\",\n              \"timeout\": 3,\n              \"capture_env\": {\n                \"NUM\" :{ \"from\":\"body\",\"json_path\":\"quoteResponse.result.0.askSize\"},\n                \"STR\" :{ \"from\":\"body\",\"json_path\":\"quoteResponse.result.0.currency\"},\n                \"BOOL\": {\"from\":\"body\",\"json_path\":\"quoteResponse.result.0.cryptoTradeable\"},\n                \"FLOAT\" : {\"from\":\"body\",\"json_path\":\"quoteResponse.result.0.epsForward\"},\n                \"ALL_RESULT\" :{\"from\":\"body\",\"json_path\":\"quoteResponse.result.0\"},\n                \"CONTENT_LENGTH\" :{\"from\":\"header\", \"header_key\":\"Content-Length\"},\n                \"CONTENT_TYPE\" :{\"from\":\"header\", \"header_key\":\"Content-Type\" ,\"regexp\":{\"exp\":\"application\\/(\\\\w)+\",\"matchNo\":0}  }             \n            }         \n            },\n            {\n              \"id\": 2,\n              \"url\": \"{{HTTPBIN}}/xml\",\n              \"name\": \"XML\",\n              \"method\": \"GET\",\n              \"others\": {\n                \"h2\": false,\n                \"disable-redirect\": true,\n                \"disable-compression\": false\n              },\n              \"headers\": {\n                \"num\": \"{{NUM}}\",\n                \"currency\": \"{{STR}}\",\n                \"yahoo\" : \"{{CONTENT_LENGTH}}\"\n              },\n              \"payload\": \"\",\n              \"timeout\": 10\n            },\n            {\n              \"id\": 3,\n              \"url\": \"https://servdown.com\",\n              \"name\": \"HTML\",\n              \"method\": \"GET\",\n              \"others\": {\n                \"h2\": false,\n                \"disable-redirect\": true,\n                \"disable-compression\": false\n              },\n              \"headers\": {\n                \"num\": \"{{NUM}}\"\n              },\n              \"payload_file\": \"config/config_testdata/benchmark/json_payload.json\",\n              \"timeout\": 10\n            }\n    ],\n    \"output\": \"stdout\",\n    \"env\":{\n        \"HTTPBIN\" : \"https://httpbin.ddosify.com\",\n        \"LOCAL\" : \"http://localhost:8084\"\n    },\n    \"debug\" : false\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/benchmark/config_correlation_load_3.json",
    "content": "{\n    \"iteration_count\": 5000,\n    \"load_type\": \"waved\",\n    \"engine_mode\": \"ddosify\",\n    \"duration\": 10,\n    \"steps\": [\n            {\n              \"id\": 1,\n              \"url\": \"{{HTTPBIN}}/json\",\n              \"name\": \"JSON\",\n              \"method\": \"GET\",\n              \"others\": {\n                \"h2\": false,\n                \"disable-redirect\": true,\n                \"disable-compression\": false\n              },\n              \"headers\": {\n                \n              },\n              \"payload\": \"\",\n              \"timeout\": 3,\n              \"capture_env\": {\n                \"NUM\" :{ \"from\":\"body\",\"json_path\":\"quoteResponse.result.0.askSize\"},\n                \"STR\" :{ \"from\":\"body\",\"json_path\":\"quoteResponse.result.0.currency\"},\n                \"BOOL\": {\"from\":\"body\",\"json_path\":\"quoteResponse.result.0.cryptoTradeable\"},\n                \"FLOAT\" : {\"from\":\"body\",\"json_path\":\"quoteResponse.result.0.epsForward\"},\n                \"ALL_RESULT\" :{\"from\":\"body\",\"json_path\":\"quoteResponse.result.0\"},\n                \"CONTENT_LENGTH\" :{\"from\":\"header\", \"header_key\":\"Content-Length\"},\n                \"CONTENT_TYPE\" :{\"from\":\"header\", \"header_key\":\"Content-Type\" ,\"regexp\":{\"exp\":\"application\\/(\\\\w)+\",\"matchNo\":0}  }             \n            }         \n            },\n            {\n              \"id\": 2,\n              \"url\": \"{{HTTPBIN}}/xml\",\n              \"name\": \"XML\",\n              \"method\": \"GET\",\n              \"others\": {\n                \"h2\": false,\n                \"disable-redirect\": true,\n                \"disable-compression\": false\n              },\n              \"headers\": {\n                \"num\": \"{{NUM}}\",\n                \"currency\": \"{{STR}}\",\n                \"yahoo\" : \"{{CONTENT_LENGTH}}\"\n              },\n              \"payload\": \"\",\n              \"timeout\": 10\n            },\n            {\n              \"id\": 3,\n              \"url\": \"https://servdown.com\",\n              \"name\": \"HTML\",\n              \"method\": \"GET\",\n              \"others\": {\n                \"h2\": false,\n                \"disable-redirect\": true,\n                \"disable-compression\": false\n              },\n              \"headers\": {\n                \"num\": \"{{NUM}}\"\n              },\n              \"payload_file\": \"config/config_testdata/benchmark/json_payload.json\",\n              \"timeout\": 10\n            }\n    ],\n    \"output\": \"stdout\",\n    \"env\":{\n        \"HTTPBIN\" : \"https://httpbin.ddosify.com\",\n        \"LOCAL\" : \"http://localhost:8084\"\n    },\n    \"debug\" : false\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/benchmark/config_correlation_load_4.json",
    "content": "{\n    \"iteration_count\": 10000,\n    \"load_type\": \"waved\",\n    \"engine_mode\": \"ddosify\",\n\n    \"duration\": 10,\n    \"steps\": [\n            {\n              \"id\": 1,\n              \"url\": \"{{HTTPBIN}}/json\",\n              \"name\": \"JSON\",\n              \"method\": \"GET\",\n              \"others\": {\n                \"h2\": false,\n                \"disable-redirect\": true,\n                \"disable-compression\": false\n              },\n              \"headers\": {\n                \n              },\n              \"payload\": \"\",\n              \"timeout\": 3,\n              \"capture_env\": {\n                \"NUM\" :{ \"from\":\"body\",\"json_path\":\"quoteResponse.result.0.askSize\"},\n                \"STR\" :{ \"from\":\"body\",\"json_path\":\"quoteResponse.result.0.currency\"},\n                \"BOOL\": {\"from\":\"body\",\"json_path\":\"quoteResponse.result.0.cryptoTradeable\"},\n                \"FLOAT\" : {\"from\":\"body\",\"json_path\":\"quoteResponse.result.0.epsForward\"},\n                \"ALL_RESULT\" :{\"from\":\"body\",\"json_path\":\"quoteResponse.result.0\"},\n                \"CONTENT_LENGTH\" :{\"from\":\"header\", \"header_key\":\"Content-Length\"},\n                \"CONTENT_TYPE\" :{\"from\":\"header\", \"header_key\":\"Content-Type\" ,\"regexp\":{\"exp\":\"application\\/(\\\\w)+\",\"matchNo\":0}  }             \n            }         \n            },\n            {\n              \"id\": 2,\n              \"url\": \"{{HTTPBIN}}/xml\",\n              \"name\": \"XML\",\n              \"method\": \"GET\",\n              \"others\": {\n                \"h2\": false,\n                \"disable-redirect\": true,\n                \"disable-compression\": false\n              },\n              \"headers\": {\n                \"num\": \"{{NUM}}\",\n                \"currency\": \"{{STR}}\",\n                \"yahoo\" : \"{{CONTENT_LENGTH}}\"\n              },\n              \"payload\": \"\",\n              \"timeout\": 10\n            },\n            {\n              \"id\": 3,\n              \"url\": \"https://servdown.com\",\n              \"name\": \"HTML\",\n              \"method\": \"GET\",\n              \"others\": {\n                \"h2\": false,\n                \"disable-redirect\": true,\n                \"disable-compression\": false\n              },\n              \"headers\": {\n                \"num\": \"{{NUM}}\"\n              },\n              \"payload_file\": \"config/config_testdata/benchmark/json_payload.json\",\n              \"timeout\": 10\n            }\n    ],\n    \"output\": \"stdout\",\n    \"env\":{\n        \"HTTPBIN\" : \"https://httpbin.ddosify.com\",\n        \"LOCAL\" : \"http://localhost:8084\"\n    },\n    \"debug\" : false\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/benchmark/config_correlation_load_5.json",
    "content": "{\n    \"iteration_count\": 20000,\n    \"load_type\": \"waved\",\n    \"engine_mode\": \"ddosify\",\n\n    \"duration\": 10,\n    \"steps\": [\n            {\n              \"id\": 1,\n              \"url\": \"{{HTTPBIN}}/json\",\n              \"name\": \"JSON\",\n              \"method\": \"GET\",\n              \"others\": {\n                \"h2\": false,\n                \"disable-redirect\": true,\n                \"disable-compression\": false\n              },\n              \"headers\": {\n                \n              },\n              \"payload\": \"\",\n              \"timeout\": 3,\n              \"capture_env\": {\n                \"NUM\" :{ \"from\":\"body\",\"json_path\":\"quoteResponse.result.0.askSize\"},\n                \"STR\" :{ \"from\":\"body\",\"json_path\":\"quoteResponse.result.0.currency\"},\n                \"BOOL\": {\"from\":\"body\",\"json_path\":\"quoteResponse.result.0.cryptoTradeable\"},\n                \"FLOAT\" : {\"from\":\"body\",\"json_path\":\"quoteResponse.result.0.epsForward\"},\n                \"ALL_RESULT\" :{\"from\":\"body\",\"json_path\":\"quoteResponse.result.0\"},\n                \"CONTENT_LENGTH\" :{\"from\":\"header\", \"header_key\":\"Content-Length\"},\n                \"CONTENT_TYPE\" :{\"from\":\"header\", \"header_key\":\"Content-Type\" ,\"regexp\":{\"exp\":\"application\\/(\\\\w)+\",\"matchNo\":0}  }             \n            }         \n            },\n            {\n              \"id\": 2,\n              \"url\": \"{{HTTPBIN}}/xml\",\n              \"name\": \"XML\",\n              \"method\": \"GET\",\n              \"others\": {\n                \"h2\": false,\n                \"disable-redirect\": true,\n                \"disable-compression\": false\n              },\n              \"headers\": {\n                \"num\": \"{{NUM}}\",\n                \"currency\": \"{{STR}}\",\n                \"yahoo\" : \"{{CONTENT_LENGTH}}\"\n              },\n              \"payload\": \"\",\n              \"timeout\": 10\n            },\n            {\n              \"id\": 3,\n              \"url\": \"https://servdown.com\",\n              \"name\": \"HTML\",\n              \"method\": \"GET\",\n              \"others\": {\n                \"h2\": false,\n                \"disable-redirect\": true,\n                \"disable-compression\": false\n              },\n              \"headers\": {\n                \"num\": \"{{NUM}}\"\n              },\n              \"payload_file\": \"config/config_testdata/benchmark/json_payload.json\",\n              \"timeout\": 10\n            }\n    ],\n    \"output\": \"stdout\",\n    \"env\":{\n        \"HTTPBIN\" : \"https://httpbin.ddosify.com\",\n        \"LOCAL\" : \"http://localhost:8084\"\n    },\n    \"debug\" : false\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/benchmark/config_distinct_user.json",
    "content": "{\n    \"iteration_count\": 100,\n    \"engine_mode\": \"ddosify\",\n    \"load_type\": \"linear\",\n    \"duration\": 10,\n    \"steps\": [\n            {\n              \"id\": 1,\n              \"url\": \"{{HTTPBIN}}/json\",\n              \"name\": \"JSON\",\n              \"method\": \"GET\",\n              \"others\": {\n                \"h2\": false,\n                \"disable-redirect\": true,\n                \"disable-compression\": false\n              }         \n            }\n    ],\n    \"output\": \"stdout\",\n    \"env\":{\n        \"HTTPBIN\" : \"https://httpbin.ddosify.com\"\n    },\n    \"debug\" : false,\n    \"engine-mode\": \"distinct-user\"\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/benchmark/config_multipart_inject_100rps.json",
    "content": "{\n    \"steps\": [\n      {\n        \"id\": 1,\n        \"url\": \"https://testserver.ddosify.com/upload_image/\",\n        \"name\": \"\",\n        \"method\": \"POST\",\n        \"others\": {\n          \"h2\": false,\n          \"keep-alive\": true,\n          \"disable-redirect\": true,\n          \"disable-compression\": false\n        },\n        \"headers\": {\n          \"Content-Type\": \"multipart/form-data\"\n        },\n        \"timeout\": 60,\n        \"capture_env\": {},\n        \"payload_multipart\": [\n          {\n            \"src\": \"remote\",\n            \"name\": \"image\",\n            \"type\": \"file\",\n            \"value\": \"https://ddosify-backend-storage.s3.amazonaws.com/media/staging/multipart/tVGNdwRxwB-GvQth9mqKITzq28-suX6R3BzvrSH9rWo/random_image.png\"\n          },\n          {\n            \"name\": \"ballot_id\",\n            \"value\": \"{{sandikID}}\"\n          }\n        ]\n      }\n    ],\n    \"output\": \"stdout\",\n    \"env\":{\n      \"sandikID\" : \"mamak75yil\"\n    },\n    \"duration\": 10,\n    \"load_type\": \"linear\",\n    \"iteration_count\": 1000\n  }"
  },
  {
    "path": "ddosify_engine/config/config_testdata/benchmark/config_multipart_inject_10rps.json",
    "content": "{\n    \"steps\": [\n      {\n        \"id\": 1,\n        \"url\": \"https://testserver.ddosify.com/upload_image/\",\n        \"name\": \"\",\n        \"method\": \"POST\",\n        \"others\": {\n          \"h2\": false,\n          \"keep-alive\": true,\n          \"disable-redirect\": true,\n          \"disable-compression\": false\n        },\n        \"headers\": {\n          \"Content-Type\": \"multipart/form-data\"\n        },\n        \"timeout\": 10,\n        \"capture_env\": {},\n        \"payload_multipart\": [\n          {\n            \"src\": \"remote\",\n            \"name\": \"image\",\n            \"type\": \"file\",\n            \"value\": \"https://ddosify-backend-storage.s3.amazonaws.com/media/staging/multipart/tVGNdwRxwB-GvQth9mqKITzq28-suX6R3BzvrSH9rWo/random_image.png\"\n          },\n          {\n            \"name\": \"ballot_id\",\n            \"value\": \"{{_randomInt}}\"\n          }\n        ]\n      }\n    ],\n    \"output\": \"stdout\",\n    \"duration\": 10,\n    \"load_type\": \"linear\",\n    \"iteration_count\": 100\n  }"
  },
  {
    "path": "ddosify_engine/config/config_testdata/benchmark/config_multipart_inject_1krps.json",
    "content": "{\n    \"steps\": [\n      {\n        \"id\": 1,\n        \"url\": \"https://testserver.ddosify.com/upload_image/\",\n        \"name\": \"\",\n        \"method\": \"POST\",\n        \"others\": {\n          \"h2\": false,\n          \"keep-alive\": true,\n          \"disable-redirect\": true,\n          \"disable-compression\": false\n        },\n        \"headers\": {\n          \"Content-Type\": \"multipart/form-data\"\n        },\n        \"timeout\": 15,\n        \"capture_env\": {},\n        \"payload_multipart\": [\n          {\n            \"src\": \"remote\",\n            \"name\": \"image\",\n            \"type\": \"file\",\n            \"value\": \"https://ddosify-backend-storage.s3.amazonaws.com/media/staging/multipart/tVGNdwRxwB-GvQth9mqKITzq28-suX6R3BzvrSH9rWo/random_image.png\"\n          },\n          {\n            \"name\": \"ballot_id\",\n            \"value\": \"{{sandikID}}\"\n          }\n        ]\n      }\n    ],\n    \"output\": \"stdout\",\n    \"env\":{\n      \"sandikID\" : \"mamak75yil\"\n    },\n    \"duration\": 10,\n    \"load_type\": \"linear\",\n    \"iteration_count\": 10000\n  }"
  },
  {
    "path": "ddosify_engine/config/config_testdata/benchmark/config_multipart_inject_200rps.json",
    "content": "{\n    \"steps\": [\n      {\n        \"id\": 1,\n        \"url\": \"https://testserver.ddosify.com/upload_image/\",\n        \"name\": \"\",\n        \"method\": \"POST\",\n        \"others\": {\n          \"h2\": false,\n          \"keep-alive\": true,\n          \"disable-redirect\": true,\n          \"disable-compression\": false\n        },\n        \"headers\": {\n          \"Content-Type\": \"multipart/form-data\"\n        },\n        \"timeout\": 10,\n        \"capture_env\": {},\n        \"payload_multipart\": [\n          {\n            \"src\": \"remote\",\n            \"name\": \"image\",\n            \"type\": \"file\",\n            \"value\": \"https://ddosify-backend-storage.s3.amazonaws.com/media/staging/multipart/tVGNdwRxwB-GvQth9mqKITzq28-suX6R3BzvrSH9rWo/random_image.png\"\n          },\n          {\n            \"name\": \"ballot_id\",\n            \"value\": \"{{sandikID}}\"\n          }\n        ]\n      }\n    ],\n    \"output\": \"stdout\",\n    \"env\":{\n      \"sandikID\" : \"mamak75yil\"\n    },\n    \"duration\": 10,\n    \"load_type\": \"linear\",\n    \"iteration_count\": 2000\n  }"
  },
  {
    "path": "ddosify_engine/config/config_testdata/benchmark/config_multipart_inject_2krps.json",
    "content": "{\n    \"steps\": [\n      {\n        \"id\": 1,\n        \"url\": \"https://testserver.ddosify.com/upload_image/\",\n        \"name\": \"\",\n        \"method\": \"POST\",\n        \"others\": {\n          \"h2\": false,\n          \"keep-alive\": true,\n          \"disable-redirect\": true,\n          \"disable-compression\": false\n        },\n        \"headers\": {\n          \"Content-Type\": \"multipart/form-data\"\n        },\n        \"timeout\": 30,\n        \"capture_env\": {},\n        \"payload_multipart\": [\n          {\n            \"src\": \"remote\",\n            \"name\": \"image\",\n            \"type\": \"file\",\n            \"value\": \"https://ddosify-backend-storage.s3.amazonaws.com/media/staging/multipart/tVGNdwRxwB-GvQth9mqKITzq28-suX6R3BzvrSH9rWo/random_image.png\"\n          },\n          {\n            \"name\": \"ballot_id\",\n            \"value\": \"{{sandikID}}\"\n          }\n        ]\n      }\n    ],\n    \"output\": \"stdout\",\n    \"env\":{\n      \"sandikID\" : \"mamak75yil\"\n    },\n    \"duration\": 10,\n    \"load_type\": \"linear\",\n    \"iteration_count\": 20000\n  }"
  },
  {
    "path": "ddosify_engine/config/config_testdata/benchmark/config_multipart_inject_500rps.json",
    "content": "{\n    \"steps\": [\n      {\n        \"id\": 1,\n        \"url\": \"https://testserver.ddosify.com/upload_image/\",\n        \"name\": \"\",\n        \"method\": \"POST\",\n        \"others\": {\n          \"h2\": false,\n          \"keep-alive\": true,\n          \"disable-redirect\": true,\n          \"disable-compression\": false\n        },\n        \"headers\": {\n          \"Content-Type\": \"multipart/form-data\"\n        },\n        \"timeout\": 30,\n        \"capture_env\": {},\n        \"payload_multipart\": [\n          {\n            \"src\": \"remote\",\n            \"name\": \"image\",\n            \"type\": \"file\",\n            \"value\": \"https://ddosify-backend-storage.s3.amazonaws.com/media/staging/multipart/tVGNdwRxwB-GvQth9mqKITzq28-suX6R3BzvrSH9rWo/random_image.png\"\n          },\n          {\n            \"name\": \"ballot_id\",\n            \"value\": \"{{sandikID}}\"\n          }\n        ]\n      }\n    ],\n    \"output\": \"stdout\",\n    \"env\":{\n      \"sandikID\" : \"mamak75yil\"\n    },\n    \"duration\": 10,\n    \"load_type\": \"linear\",\n    \"iteration_count\": 5000\n  }"
  },
  {
    "path": "ddosify_engine/config/config_testdata/benchmark/config_repeated_user.json",
    "content": "{\n    \"iteration_count\": 100,\n    \"engine_mode\": \"ddosify\",\n    \"load_type\": \"linear\",\n    \"duration\": 10,\n    \"steps\": [\n            {\n              \"id\": 1,\n              \"url\": \"{{HTTPBIN}}/json\",\n              \"name\": \"JSON\",\n              \"method\": \"GET\",\n              \"others\": {\n                \"h2\": false,\n                \"disable-redirect\": true,\n                \"disable-compression\": false\n              }         \n            }\n    ],\n    \"output\": \"stdout\",\n    \"env\":{\n        \"HTTPBIN\" : \"https://httpbin.ddosify.com\"\n    },\n    \"debug\" : false,\n    \"engine-mode\": \"distinct-user\"\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/benchmark/json_payload.json",
    "content": "{\n    \"boolField\" : \"{{BOOL}}\",\n    \"numField\" : \"{{NUM}}\",\n    \"strField\" : \"{{STR}}\",\n    \"numArrayField\" : [\"{{NUM}}\",34],\n    \"strArrayField\" : [\"{{STR}}\",\"hello\"],\n    \"mixedArrayField\" : [\"{{NUM}}\",34,\"{{FLOAT}}\"],\n    \"{{STR}}\" : \"xxxx\",\n    \"obj\" :{\n        \"numField\" : \"{{CONTENT_LENGTH}}\",\n        \"objectField\" : \"{{ALL_RESULT}}\"\n    }\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/config.json",
    "content": "{\n    \"request_count\": 1555,\n    \"load_type\": \"waved\",\n    \"duration\": 21,\n    \"steps\": [\n        {\n            \"id\": 1,\n            \"name\": \"Example Name 1\",\n            \"url\": \"https://app.servdown.com/accounts/login/?next=/\",\n            \"method\": \"GET\",\n            \"payload\": \"payload str\",\n            \"timeout\": 3,\n            \"sleep\": \"1000\",\n            \"others\": {\n            }\n        },\n        {\n            \"id\": 2,\n            \"name\": \"Example Name 2\",\n            \"url\": \"http://test.com\",\n            \"method\": \"PUT\",\n            \"headers\": {\n                \"ContenType\": \"application/xml\",\n                \"X-ddosify-key\": \"ajkndalnasd\"\n            },\n            \"timeout\": 2,\n            \"sleep\": \" 300-500\"\n        }\n    ],\n    \"output\": \"stdout\",\n    \"proxy\": \"http://proxy_host:80\"\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/config_auth.json",
    "content": "{\n    \"steps\": [\n        {\n            \"id\": 1,\n            \"url\": \"https://app.servdown.com/accounts/login/?next=/\",\n            \"auth\": {\n                \"type\": \"basic\",\n                \"username\": \"kursat\",\n                \"password\": \"12345\"\n            }\n        },\n        {\n            \"id\": 2,\n            \"url\": \"https://app.servdown.com/accounts/login/?next=/&112f12f12f12f\"\n        }\n    ]\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/config_capture_environment.json",
    "content": "{\n    \"iteration_count\": 100,\n    \"load_type\": \"waved\",\n    \"duration\": 21,\n    \"steps\": [\n        {\n            \"id\": 1,\n            \"name\": \"Example Name 1\",\n            \"url\": \"http://localhost:8080/hello\",\n            \"method\": \"GET\",\n            \"capture_env\": {\n                \"NUM\" :{ \"from\":\"body\",\"json_path\":\"num\"},\n                \"X_COOKIE\" :{ \"from\":\"cookies\",\"cookie_name\":\"x\"}\n            }             \n        },\n        {\n            \"id\": 2,\n            \"name\": \"Example Name 2 Json Body\",\n            \"url\": \"http://localhost:8080/\",\n            \"method\": \"POST\",\n            \"headers\": {\n                \"Content-Type\": \"application/json\",\n                \"num\": \"{{NUM}}\"\n            },\n            \"capture_env\": {\n                \"REGEX_MATCH_ENV\" :{\"from\":\"body\",\"regexp\":{\"exp\" : \"[a-z]+_[0-9]+\", \"matchNo\": 1}}\n            }   \n        }\n    ],\n    \"debug\" : true\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/config_data_csv.json",
    "content": "{\n    \"iteration_count\": 4,\n    \"load_type\": \"waved\",\n    \"duration\": 1,\n    \"steps\": [\n            {\n              \"id\": 2,\n              \"url\": \"{{LOCAL}}/body\",\n              \"name\": \"JSON\",\n              \"method\": \"GET\",\n              \"others\": {\n                \"h2\": false,\n                \"disable-redirect\": true,\n                \"disable-compression\": false\n              },\n              \"payload_file\": \"../config/config_testdata/data_json_payload.json\",\n              \"timeout\": 10\n            }\n    ],\n    \"output\": \"stdout\",\n    \"env\":{\n        \"HTTPBIN\" : \"https://httpbin.ddosify.com\",\n        \"LOCAL\" : \"http://localhost:8084\",\n        \"RANDOM_NAMES\" : [\"kenan\",\"fatih\",\"kursat\",\"semih\",\"sertac\"] ,\n        \"RANDOM_INT\" : [52,99,60,33],\n        \"RANDOM_BOOL\" : [true,true,true,false] \n    },\n    \"data\":{\n      \"info\": {\n          \"path\" : \"../config/config_testdata/test.csv\",\n          \"src\" : \"local\",\n          \"delimiter\": \";\",\n          \"vars\": {\n                  \"0\":{\"tag\":\"name\"},\n                  \"1\":{\"tag\":\"city\"},\n                  \"2\":{\"tag\":\"team\"},\n                  \"3\":{\"tag\":\"payload\", \"type\":\"json\"},\n                  \"4\":{\"tag\":\"age\", \"type\":\"int\"}\n                },\n          \"allow_quota\" : true,\n          \"order\": \"random\",\n          \"skip_first_line\" : true\n      }\n    },\n    \"debug\" : false\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/config_debug_false.json",
    "content": "{\n    \"debug\": false,\n    \"iteration_count\": 1555,\n    \"load_type\": \"waved\",\n    \"duration\": 21,\n    \"steps\": [\n        {\n            \"id\": 1,\n            \"name\": \"Example Name 1\",\n            \"url\": \"https://app.servdown.com/accounts/login/?next=/\",\n            \"method\": \"GET\",\n            \"payload\": \"payload str\",\n            \"timeout\": 3,\n            \"sleep\": \"1000\",\n            \"others\": {\n            }\n        },\n        {\n            \"id\": 2,\n            \"name\": \"Example Name 2\",\n            \"url\": \"http://test.com\",\n            \"method\": \"PUT\",\n            \"headers\": {\n                \"ContenType\": \"application/xml\",\n                \"X-ddosify-key\": \"ajkndalnasd\"\n            },\n            \"timeout\": 2,\n            \"sleep\": \" 300-500\"\n        }\n    ],\n    \"output\": \"stdout\",\n    \"proxy\": \"http://proxy_host:80\"\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/config_debug_mode.json",
    "content": "{\n    \"debug\": true,\n    \"iteration_count\": 1555,\n    \"load_type\": \"waved\",\n    \"duration\": 21,\n    \"steps\": [\n        {\n            \"id\": 1,\n            \"name\": \"Example Name 1\",\n            \"url\": \"https://app.servdown.com/accounts/login/?next=/\",\n            \"method\": \"GET\",\n            \"payload\": \"payload str\",\n            \"timeout\": 3,\n            \"sleep\": \"1000\",\n            \"others\": {\n            }\n        },\n        {\n            \"id\": 2,\n            \"name\": \"Example Name 2\",\n            \"url\": \"http://test.com\",\n            \"method\": \"PUT\",\n            \"headers\": {\n                \"ContenType\": \"application/xml\",\n                \"X-ddosify-key\": \"ajkndalnasd\"\n            },\n            \"timeout\": 2,\n            \"sleep\": \" 300-500\"\n        }\n    ],\n    \"output\": \"stdout\",\n    \"proxy\": \"http://proxy_host:80\"\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/config_empty.json",
    "content": "{\n    \"steps\": [\n        {\n            \"id\": 1,\n            \"url\": \"test.com\"\n        }\n    ]\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/config_global_envs.json",
    "content": "{\n    \"steps\": [\n        {\n            \"id\": 1,\n            \"name\": \"Example Name 1\",\n            \"url\": \"{{LOCAL}}\",\n            \"method\": \"GET\"\n        },\n        {\n            \"id\": 2,\n            \"name\": \"Example Name 2 Json Body\",\n            \"url\": \"{{HTTPBIN}}\",\n            \"method\": \"GET\",\n            \"headers\": {\n                \"Content-Type\": \"application/json\"\n            }\n        }\n    ],\n    \"env\":{\n        \"HTTPBIN\" : \"https://httpbin.ddosify.com\",\n        \"LOCAL\" : \"http://localhost:8084/hello\"\n    }\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/config_incorrect.json",
    "content": "{\n    \"request_count\": 100,\n    \"load_type\": \"linear\",\n    \"duration\": 10,\n    \"steps\": [\n        {\n            \"id\": 1,\n            \"url\": \"https://app.servdown.com/accounts/login/?next=/\",\n            \"auth\": {\n                \"type\": \"basic\",\n                \"username\": \"kursat\",\n                \"password\": \"12345\"\n            },\n            \"method\": \"GET\",\n            \"headers\": {\n                \"ContenType\": \"application/xml\",\n                \"User-Agent\": \"chrome5\"\n            },\n            \"payload\": \"body yt kanl adnlandlandaln\",\n            \"timeout\": 1,\n            \"others\": {\n            }\n        },\n        {\n            \"id\": 2,\n            \"url\": \"https://app.servdown.com/accounts/login/?next=/&112f12f12f12f\",\n            \"method\": \"GET\",\n            \"headers\": {\n                \"ContenType\": \"application/xml\",\n                \"X-ddosify-key\": \"ajkndalnasd\"\n            },\n            \"payload_file\": \"config_examples/payload.txt\",\n            \"timeout\": 1,\n            \"others\": {\n            }\n        },\n    ],\n    \"proxy\": \"http://proxy_host:80\",\n    \"output\": \"stdout\"\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/config_init_cookies.json",
    "content": "{\n    \"iteration_count\": 1555,\n    \"load_type\": \"waved\",\n    \"duration\": 21,\n    \"steps\": [\n        {\n            \"id\": 1,\n            \"name\": \"Example Name 1\",\n            \"url\": \"https://app.servdown.com/accounts/login/?next=/\",\n\n            \"method\": \"GET\",\n            \"payload\": \"payload str\",\n            \"timeout\": 3,\n            \"sleep\": \"1000\",\n            \"others\": {\n            }\n        }\n    ],\n    \"output\": \"stdout\",\n    \"engine_mode\": \"distinct-user\",\n    \"cookie_jar\":{\n        \"enabled\" : true,\n        \"cookies\" :[\n            {\n                \"name\": \"platform\",\n                \"value\": \"web\",\n                \"domain\": \"httpbin.ddosify.com\",\n                \"path\": \"/\",\n                \"expires\": \"Thu, 16 Mar 2023 09:24:02 GMT\",\n                \"http_only\": true,\n                \"secure\": false\n            }\n        ]\n    } \n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/config_inject_json.json",
    "content": "{\n    \"iteration_count\": 100,\n    \"load_type\": \"waved\",\n    \"duration\": 21,\n    \"steps\": [\n        {\n            \"id\": 1,\n            \"name\": \"Example Name 1\",\n            \"url\": \"{{LOCAL}}\",\n            \"method\": \"GET\",\n            \"capture_env\": {\n                \"NUM\" :{ \"from\":\"body\",\"json_path\":\"num\"},\n                \"NAME\" :{ \"from\":\"body\",\"json_path\":\"name\"},\n                \"IS_CHAMPION\": {\"from\":\"body\",\"json_path\":\"isChampion\"},\n                \"MESSI\" : {\"from\":\"body\",\"json_path\":\"squad.players.0\"},\n                \"PLAYERS\" :{\"from\":\"body\",\"json_path\":\"squad.players\"},\n                \"SQUAD\" :{\"from\":\"body\",\"json_path\":\"squad\"},\n                \"ARGENTINA\" :{\"from\":\"header\", \"header_key\":\"Argentina\"},\n                \"m10\" :{\"from\":\"header\", \"header_key\":\"Argentina\" ,\"regexp\":{\"exp\":\"[a-z]+_[0-9]+\",\"matchNo\":1}  }             \n            }         \n        },\n        {\n            \"id\": 2,\n            \"name\": \"Example Name 2 Json Body\",\n            \"url\": \"{{LOCAL}}\",\n            \"method\": \"POST\",\n            \"headers\": {\n                \"Content-Type\": \"application/json\",\n                \"num\": \"{{NUM}}\",\n                \"bool\" : \"{{IS_CHAMPION}}\"\n            },\n            \"payload_file\" : \"../config/config_testdata/json_payload.json\",\n            \"capture_env\": {\n                \"REGEX_MATCH_ENV\" :{\"from\":\"body\",\"json_path\":\"num\"}\n            }   \n        }\n    ],\n    \"env\":{\n        \"HTTPBIN\" : \"https://httpbin.ddosify.com\",\n        \"LOCAL\" : \"http://localhost:8084/hello\"\n    },\n    \"debug\" : true\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/config_inject_json_dynamic.json",
    "content": "{\n    \"iteration_count\": 100,\n    \"load_type\": \"waved\",\n    \"duration\": 21,\n    \"steps\": [\n        {\n            \"id\": 1,\n            \"name\": \"Example Name 1\",\n            \"url\": \"{{LOCAL}}\",\n            \"method\": \"GET\",\n            \"capture_env\": {\n                \"NUM\" :{ \"from\":\"body\",\"json_path\":\"num\"},\n                \"NAME\" :{ \"from\":\"body\",\"json_path\":\"name\"},\n                \"IS_CHAMPION\": {\"from\":\"body\",\"json_path\":\"isChampion\"},\n                \"MESSI\" : {\"from\":\"body\",\"json_path\":\"squad.players.0\"},\n                \"PLAYERS\" :{\"from\":\"body\",\"json_path\":\"squad.players\"},\n                \"SQUAD\" :{\"from\":\"body\",\"json_path\":\"squad\"},\n                \"ARGENTINA\" :{\"from\":\"header\", \"header_key\":\"Argentina\"},\n                \"m10\" :{\"from\":\"header\", \"header_key\":\"Argentina\" ,\"regexp\":{\"exp\":\"[a-z]+_[0-9]+\",\"matchNo\":1}  }             \n            }         \n        },\n        {\n            \"id\": 2,\n            \"name\": \"Example Name 2 Json Body\",\n            \"url\": \"{{LOCAL}}\",\n            \"method\": \"POST\",\n            \"headers\": {\n                \"Content-Type\": \"application/json\",\n                \"num\": \"{{NUM}}\",\n                \"bool\" : \"{{IS_CHAMPION}}\"\n            },\n            \"payload_file\" : \"../config/config_testdata/json_payload_dynamic.json\",\n            \"capture_env\": {\n                \"REGEX_MATCH_ENV\" :{\"from\":\"body\",\"json_path\":\"num\"}\n            }   \n        }\n    ],\n    \"env\":{\n        \"HTTPBIN\" : \"https://httpbin.ddosify.com\",\n        \"LOCAL\" : \"http://localhost:8084/hello\"\n    },\n    \"debug\" : true\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/config_inject_xml.json",
    "content": "{\n    \"iteration_count\": 100,\n    \"load_type\": \"waved\",\n    \"duration\": 21,\n    \"steps\": [\n        {\n            \"id\": 1,\n            \"name\": \"Example Name 1\",\n            \"url\": \"{{LOCAL}}\",\n            \"method\": \"GET\",\n            \"payload_file\" :\"../config/config_testdata/xml_payload.xml\"\n        }\n    ],\n    \"env\":{\n        \"LOCAL\" : \"http://localhost:8084\",\n        \"HELLO\" : \"hello\"\n    },\n    \"debug\" : true\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/config_invalid_capture_env.json",
    "content": "{\n    \"iteration_count\": 100,\n    \"load_type\": \"waved\",\n    \"duration\": 21,\n    \"steps\": [\n        {\n            \"id\": 1,\n            \"name\": \"Example Name 1\",\n            \"url\": \"{{LOCAL}}\",\n            \"method\": \"GET\",\n            \"capture_env\": {\n                \"NUM\" :{ \"from\":\"body\",\"json_path\":\"num\"}\n            }             \n        },\n        {\n            \"id\": 2,\n            \"name\": \"Example Name 2 Json Body\",\n            \"url\": \"{{HTTPBIN}}\",\n            \"method\": \"POST\",\n            \"headers\": {\n                \"Content-Type\": \"application/json\",\n                \"num\": \"{{NUM}}\"\n            },\n            \"capture_env\": {\n                \"REGEX_MATCH_ENV\" :{\"from\":\"header\",\"regexp\":{\"exp\" : \"\", \"matchNo\": 1}}\n            }   \n        }\n    ],\n    \"debug\" : true\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/config_invalid_target.json",
    "content": "{\n    \"steps\": [\n        {\n            \"id\": 1,\n            \"url\": \"_invalid.com\"\n        }\n    ]\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/config_invalid_user_mode_for_cookies.json",
    "content": "{\n    \"iteration_count\": 1555,\n    \"load_type\": \"waved\",\n    \"duration\": 21,\n    \"steps\": [\n        {\n            \"id\": 1,\n            \"name\": \"Example Name 1\",\n            \"url\": \"https://app.servdown.com/accounts/login/?next=/\",\n\n            \"method\": \"GET\",\n            \"payload\": \"payload str\",\n            \"timeout\": 3,\n            \"sleep\": \"1000\",\n            \"others\": {\n            }\n        }\n    ],\n    \"output\": \"stdout\",\n    \"cookie_jar\":{\n        \"enabled\" : true,\n        \"cookies\" :[\n            {\n                \"name\": \"platform\",\n                \"value\": \"web\",\n                \"domain\": \"httpbin.ddosify.com\",\n                \"path\": \"/\",\n                \"expires\": \"Thu, 16 Mar 2023 09:24:02 GMT\",\n                \"http_only\": true,\n                \"secure\": false\n            }\n        ]\n      } \n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/config_iteration_count.json",
    "content": "{\n    \"iteration_count\": 1555,\n    \"load_type\": \"waved\",\n    \"duration\": 21,\n    \"steps\": [\n        {\n            \"id\": 1,\n            \"name\": \"Example Name 1\",\n            \"url\": \"https://app.servdown.com/accounts/login/?next=/\",\n\n            \"method\": \"GET\",\n            \"payload\": \"payload str\",\n            \"timeout\": 3,\n            \"sleep\": \"1000\",\n            \"others\": {\n            }\n        },\n        {\n            \"id\": 2,\n            \"name\": \"Example Name 2\",\n            \"url\": \"http://test.com\",\n\n            \"method\": \"PUT\",\n            \"headers\": {\n                \"ContenType\": \"application/xml\",\n                \"X-ddosify-key\": \"ajkndalnasd\"\n            },\n            \"timeout\": 2,\n            \"sleep\": \" 300-500\"\n        }\n    ],\n    \"output\": \"stdout\",\n    \"proxy\": \"http://proxy_host:80\"\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/config_iteration_count_over_req_count.json",
    "content": "{\n    \"iteration_count\": 333,\n    \"req_count\": 222,\n    \"load_type\": \"waved\",\n    \"duration\": 21,\n    \"steps\": [\n        {\n            \"id\": 1,\n            \"name\": \"Example Name 1\",\n            \"url\": \"https://app.servdown.com/accounts/login/?next=/\",\n            \"method\": \"GET\",\n            \"payload\": \"payload str\",\n            \"timeout\": 3,\n            \"sleep\": \"1000\",\n            \"others\": {\n            }\n        },\n        {\n            \"id\": 2,\n            \"name\": \"Example Name 2\",\n            \"url\": \"http://test.com\",\n            \"method\": \"PUT\",\n            \"headers\": {\n                \"ContenType\": \"application/xml\",\n                \"X-ddosify-key\": \"ajkndalnasd\"\n            },\n            \"timeout\": 2,\n            \"sleep\": \" 300-500\"\n        }\n    ],\n    \"output\": \"stdout\",\n    \"proxy\": \"http://proxy_host:80\"\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/config_manual_load.json",
    "content": "{\n    \"manual_load\": [\n        {\"duration\": 5, \"count\": 5},\n        {\"duration\": 6, \"count\": 10},\n        {\"duration\": 7, \"count\": 20}\n    ],\n    \"steps\": [\n        {\n            \"id\": 1,\n            \"url\": \"test.com\"\n        }\n    ]\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/config_manual_load_override.json",
    "content": "{\n    \"requests_count\": 100,\n    \"duration\": 22,\n    \"manual_load\": [\n        {\"duration\": 5, \"count\": 5},\n        {\"duration\": 6, \"count\": 10},\n        {\"duration\": 7, \"count\": 20}\n    ],\n    \"steps\": [\n        {\n            \"id\": 1,\n            \"url\": \"test.com\"\n        }\n    ]\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/config_multipart_err.json",
    "content": "{\n    \"steps\": [\n        {\n            \"id\": 1,\n            \"url\": \"https://app.servdown.com/accounts/login/?next=/\",\n            \"method\": \"GET\",\n            \"payload_multipart\": [\n                \n                {\n                    \"name\": \"example-name-5\",\n                    \"value\": \"https://uplo333ad.wikimedia.org/wikipedia/commons/b/bd/Test.svg\",\n                    \"type\": \"file\",\n                    \"src\": \"remote\"\n                }\n            ]\n        }\n    ]\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/config_multipart_payload.json",
    "content": "{\n    \"steps\": [\n        {\n            \"id\": 1,\n            \"url\": \"https://app.servdown.com/accounts/login/?next=/\",\n            \"method\": \"GET\",\n            \"payload_multipart\": [\n                {\n                    \"name\": \"example-name-1\",\n                    \"value\": \"config_testdata/test_img.svg\",\n                    \"type\": \"file\"\n                },\n                {\n                    \"name\": \"example-name-2\",\n                    \"value\": \"https://upload.wikimedia.org/wikipedia/commons/b/bd/Test.svg\",\n                    \"type\": \"file\",\n                    \"src\": \"remote\"\n                },\n                {\n                    \"name\": \"example-name-3\",\n                    \"value\": \"text-field-value\"\n                },\n                {\n                    \"name\": \"example-name-4\",\n                    \"value\": \"123123\",\n                    \"type\": \"text\"\n                }\n            ]\n        }\n    ]\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/config_payload.json",
    "content": "{\n    \"steps\": [\n        {\n            \"id\": 1,\n            \"url\": \"https://app.servdown.com/accounts/login/?next=/\",\n            \"method\": \"GET\",\n            \"payload\": \"payload from string\"\n        },\n        {\n            \"id\": 2,\n            \"url\": \"https://app.servdown.com/accounts/login/?next=/&112f12f12f12f\",\n            \"payload_file\": \"config_testdata/payload.txt\"\n        }\n    ]\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/config_protocol.json",
    "content": "{\n    \"steps\": [\n        {\n            \"id\": 1,\n            \"url\": \"https://app.servdown.com/accounts/login/?next=/\",\n            \"protocol\": \"http\"\n        },\n        {\n            \"id\": 2,\n            \"url\": \"http://app.servdown.com/accounts/login/?next=/&112f12f12f12f\"\n        },\n        {\n            \"id\": 3,\n            \"url\": \"app.servdown.com/accounts/login/?next=/&112f12f12f12f\"\n        },\n        {\n            \"id\": 4,\n            \"url\": \"app.servdown.com/accounts/login/?next=/&112f12f12f12f\",\n            \"protocol\": \"http\"\n        }\n    ]\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/config_test_assertion_fail.json",
    "content": "{\n    \"iteration_count\": 100,\n    \"load_type\": \"linear\",\n    \"duration\": 10,\n    \"debug\" : false,\n    \"success_criterias\": [\n      {\n        \"rule\" : \"false\", \n        \"abort\" : true,\n        \"delay\" : 1\n      }\n    ],\n    \"steps\": [\n            {\n              \"id\": 1,\n              \"url\": \"https://httpbin.ddosify.com/json2\",\n              \"name\": \"JSON\",\n              \"method\": \"GET\",\n              \"others\": {\n                \"h2\": false,\n                \"keep-alive\": true,\n                \"disable-redirect\": true,\n                \"disable-compression\": false\n              },\n              \"timeout\": 2\n            }\n    ]\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/data_json_payload.json",
    "content": "{\n    \"name\" : \"{{data.info.name}}\",\n    \"team\" : \"{{data.info.team}}\",\n    \"city\" : \"{{data.info.city}}\",\n    \"payload\" : \"{{rand(data.info.payload)}}\",\n    \"age\" : \"{{data.info.age}}\"\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/json_payload.json",
    "content": "{\n    \"boolField\" : \"{{IS_CHAMPION}}\",\n    \"numField\" : \"{{NUM}}\",\n    \"strField\" : \"{{NAME}}\",\n    \"numArrayField\" : [\"{{NUM}}\",34],\n    \"strArrayField\" : [\"{{NAME}}\",\"hello\"],\n    \"mixedArrayField\" : [\"{{NUM}}\",34,\"{{NAME}}\",\"{{SQUAD}}\"],\n    \"{{NAME}}\" : \"xxxx\",\n    \"obj\" :{\n        \"numField\" : \"{{NUM}}\",\n        \"objectField\" : \"{{SQUAD}}\",\n        \"arrayField\" : \"{{PLAYERS}}\"\n    }\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/json_payload_dynamic.json",
    "content": "{\n    \"name\" : \"{{_randomString}}\",\n    \"city\" : \"{{_randomCity}}\",\n    \"age\" : \"{{_randomInt}}\"\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/payload.txt",
    "content": "Payloaf from file."
  },
  {
    "path": "ddosify_engine/config/config_testdata/race_configs/capture_envs.json",
    "content": "{\n    \"iteration_count\": 10,\n    \"duration\": 2,\n    \"steps\": [\n        {\n            \"id\": 1,\n            \"name\": \"Example Name 2 Json Body\",\n            \"url\": \"{{HTTPBIN}}/json\",\n            \"method\": \"GET\",\n            \"headers\": {\n                \"Content-Type\": \"application/json\"\n            },\n            \"capture_env\": {\n                \"NUM\" :{ \"from\":\"body\",\"json_path\":\"quoteResponse.result.0.askSize\"}\n            }\n        },\n        {\n            \"id\": 2,\n            \"name\": \"Example Name 1\",\n            \"url\": \"{{LOCAL}}\",\n            \"method\": \"GET\"\n        }\n    ],\n    \"env\":{\n        \"HTTPBIN\" : \"https://httpbin.ddosify.com\",\n        \"LOCAL\" : \"http://localhost:8084/hello\"\n    }\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/race_configs/global_envs.json",
    "content": "{\n    \"iteration_count\": 10,\n    \"duration\": 2,\n    \"steps\": [\n        {\n            \"id\": 1,\n            \"name\": \"Example Name 1\",\n            \"url\": \"{{LOCAL}}\",\n            \"method\": \"GET\"\n        },\n        {\n            \"id\": 2,\n            \"name\": \"Example Name 2 Json Body\",\n            \"url\": \"{{HTTPBIN}}\",\n            \"method\": \"GET\",\n            \"headers\": {\n                \"Content-Type\": \"application/json\"\n            }\n        }\n    ],\n    \"env\":{\n        \"HTTPBIN\" : \"https://httpbin.ddosify.com\",\n        \"LOCAL\" : \"http://localhost:8084/hello\"\n    }\n}"
  },
  {
    "path": "ddosify_engine/config/config_testdata/race_configs/step_assertions_stdout.json",
    "content": "{\n    \"debug\": false,\n    \"steps\": [\n      {\n        \"id\": 1,\n        \"url\": \"https://testserver.ddosify.com/exchange/\",\n        \"name\": \"\",\n        \"method\": \"GET\",\n        \"others\": {\n          \"h2\": false,\n          \"keep-alive\": true,\n          \"disable-redirect\": true,\n          \"disable-compression\": false\n        },\n        \"timeout\": 10,\n        \"capture_env\": {},\n        \"assertion\": [\n          \"equals(status_code,203)\",\n          \"contains(body,\\\"afssafs\\\")\"\n        ]\n      },\n      {\n        \"id\": 2,\n        \"url\": \"https://testserver.ddosify.com/exchange/\",\n        \"name\": \"\",\n        \"method\": \"GET\",\n        \"others\": {\n          \"h2\": false,\n          \"keep-alive\": true,\n          \"disable-redirect\": true,\n          \"disable-compression\": false\n        },\n        \"timeout\": 10,\n        \"capture_env\": {},\n        \"assertion\": [\n          \"equals(status_code,401)\"\n        ]\n      },\n      {\n        \"id\": 3,\n        \"url\": \"https://teasgsagasgsastserver.ddosify.com/exchange/\",\n        \"name\": \"\",\n        \"method\": \"GET\",\n        \"others\": {\n          \"h2\": false,\n          \"keep-alive\": true,\n          \"disable-redirect\": true,\n          \"disable-compression\": false\n        },\n        \"timeout\": 10,\n        \"capture_env\": {},\n        \"assertion\": [\n          \"equals(status_code,401)\"\n        ]\n      }\n    ],\n    \"iteration_count\": 10,\n    \"duration\": 2,\n    \"output\": \"stdout\"\n  }"
  },
  {
    "path": "ddosify_engine/config/config_testdata/race_configs/step_assertions_stdout_json.json",
    "content": "{\n    \"debug\": false,\n    \"steps\": [\n      {\n        \"id\": 1,\n        \"url\": \"https://testserver.ddosify.com/exchange/\",\n        \"name\": \"\",\n        \"method\": \"GET\",\n        \"others\": {\n          \"h2\": false,\n          \"keep-alive\": true,\n          \"disable-redirect\": true,\n          \"disable-compression\": false\n        },\n        \"timeout\": 10,\n        \"capture_env\": {},\n        \"assertion\": [\n          \"equals(status_code,203)\",\n          \"contains(body,\\\"afssafs\\\")\"\n        ]\n      },\n      {\n        \"id\": 2,\n        \"url\": \"https://testserver.ddosify.com/exchange/\",\n        \"name\": \"\",\n        \"method\": \"GET\",\n        \"others\": {\n          \"h2\": false,\n          \"keep-alive\": true,\n          \"disable-redirect\": true,\n          \"disable-compression\": false\n        },\n        \"timeout\": 10,\n        \"capture_env\": {},\n        \"assertion\": [\n          \"equals(status_code,401)\"\n        ]\n      },\n      {\n        \"id\": 3,\n        \"url\": \"https://teasgsagasgsastserver.ddosify.com/exchange/\",\n        \"name\": \"\",\n        \"method\": \"GET\",\n        \"others\": {\n          \"h2\": false,\n          \"keep-alive\": true,\n          \"disable-redirect\": true,\n          \"disable-compression\": false\n        },\n        \"timeout\": 10,\n        \"capture_env\": {},\n        \"assertion\": [\n          \"equals(status_code,401)\"\n        ]\n      }\n    ],\n    \"iteration_count\": 10,\n    \"duration\": 2,\n    \"output\": \"stdout-json\"\n  }"
  },
  {
    "path": "ddosify_engine/config/config_testdata/test.csv",
    "content": "Username;City;Team;Payload;Age;Percent;BoolField;;;\nKenan;Tokat;Galatasaray;{\"data\":{\"profile\":{\"name\":\"Kenan\"}}};25;22.3;true;;;\nFatih;Bolu;Galatasaray;[5,6,7];29;44.3;false;;;\nKursat;Samsun;Besiktas;{\"a\":\"b\"};28;12.54;True;;;\nSemih;Duzce;Besiktas;{\"a\":\"b\"};27;663.67;False;;;\n;;;;;;;;;\n;;;;;;;;;"
  },
  {
    "path": "ddosify_engine/config/config_testdata/xml_payload.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n\t<rss version=\"2.0\">\n\t<channel>\n\t  <item>\n\t\t<title>{{HELLO}}</title>\n\t  </item>\n\t</channel>\n\t</rss>"
  },
  {
    "path": "ddosify_engine/config/json.go",
    "content": "/*\n*\n*\tDdosify - Load testing tool for any web system.\n*   Copyright (C) 2021  Ddosify (https://ddosify.com)\n*\n*   This program is free software: you can redistribute it and/or modify\n*   it under the terms of the GNU Affero General Public License as published\n*   by the Free Software Foundation, either version 3 of the License, or\n*   (at your option) any later version.\n*\n*   This program is distributed in the hope that it will be useful,\n*   but WITHOUT ANY WARRANTY; without even the implied warranty of\n*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n*   GNU Affero General Public License for more details.\n*\n*   You should have received a copy of the GNU Affero General Public License\n*   along with this program.  If not, see <https://www.gnu.org/licenses/>.\n*\n */\n\npackage config\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"mime/multipart\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"unsafe\"\n\n\t\"go.ddosify.com/ddosify/core/proxy\"\n\t\"go.ddosify.com/ddosify/core/types\"\n)\n\nconst ConfigTypeJson = \"jsonReader\"\n\nfunc init() {\n\tAvailableConfigReader[ConfigTypeJson] = &JsonReader{}\n}\n\ntype timeRunCount []struct {\n\tDuration int `json:\"duration\"`\n\tCount    int `json:\"count\"`\n}\n\ntype auth struct {\n\tType     string `json:\"type\"`\n\tUsername string `json:\"username\"`\n\tPassword string `json:\"password\"`\n}\n\ntype multipartFormData struct {\n\tName  string `json:\"name\"`\n\tValue string `json:\"value\"`\n\tType  string `json:\"type\"`\n\tSrc   string `json:\"src\"`\n}\n\ntype RegexCaptureConf struct {\n\tExp *string `json:\"exp\"`\n\tNo  int     `json:\"matchNo\"`\n}\ntype capturePath struct {\n\tJsonPath   *string           `json:\"json_path\"`\n\tXPath      *string           `json:\"xpath\"`\n\tXpathHtml  *string           `json:\"xpath_html\"`\n\tRegExp     *RegexCaptureConf `json:\"regexp\"`\n\tFrom       string            `json:\"from\"` // body,header,cookie\n\tCookieName *string           `json:\"cookie_name\"`\n\tHeaderKey  *string           `json:\"header_key\"` // header key\n}\n\ntype step struct {\n\tId               uint16                 `json:\"id\"`\n\tName             string                 `json:\"name\"`\n\tUrl              string                 `json:\"url\"`\n\tAuth             auth                   `json:\"auth\"`\n\tMethod           string                 `json:\"method\"`\n\tHeaders          map[string]string      `json:\"headers\"`\n\tPayload          string                 `json:\"payload\"`\n\tPayloadFile      string                 `json:\"payload_file\"`\n\tPayloadMultipart []multipartFormData    `json:\"payload_multipart\"`\n\tTimeout          int                    `json:\"timeout\"`\n\tSleep            string                 `json:\"sleep\"`\n\tOthers           map[string]interface{} `json:\"others\"`\n\tCertPath         string                 `json:\"cert_path\"`\n\tCertKeyPath      string                 `json:\"cert_key_path\"`\n\tCaptureEnv       map[string]capturePath `json:\"capture_env\"`\n\tAssertions       []string               `json:\"assertion\"`\n}\n\nfunc (s *step) UnmarshalJSON(data []byte) error {\n\ttype stepAlias step\n\tdefaultFields := &stepAlias{\n\t\tMethod:  types.DefaultMethod,\n\t\tTimeout: types.DefaultTimeout,\n\t}\n\n\terr := json.Unmarshal(data, defaultFields)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t*s = step(*defaultFields)\n\treturn nil\n}\n\ntype Tag struct {\n\tTag  string `json:\"tag\"`\n\tType string `json:\"type\"`\n}\n\nfunc (t *Tag) UnmarshalJSON(data []byte) error {\n\t// default values\n\tt.Type = \"string\"\n\ttype tempTag Tag\n\treturn json.Unmarshal(data, (*tempTag)(t))\n}\n\ntype CsvConf struct {\n\tPath          string         `json:\"path\"`\n\tDelimiter     string         `json:\"delimiter\"`\n\tSkipFirstLine bool           `json:\"skip_first_line\"`\n\tVars          map[string]Tag `json:\"vars\"` // \"0\":\"name\", \"1\":\"city\",\"2\":\"team\"\n\tSkipEmptyLine bool           `json:\"skip_empty_line\"`\n\tAllowQuota    bool           `json:\"allow_quota\"`\n\tOrder         string         `json:\"order\"`\n}\n\nfunc (c *CsvConf) UnmarshalJSON(data []byte) error {\n\t// default values\n\tc.SkipEmptyLine = true\n\tc.SkipFirstLine = false\n\tc.AllowQuota = false\n\tc.Delimiter = \",\"\n\tc.Order = \"random\"\n\n\ttype tempCsv CsvConf\n\treturn json.Unmarshal(data, (*tempCsv)(c))\n}\n\ntype JsonReader struct {\n\tReqCount     *int                   `json:\"request_count\"`\n\tIterCount    *int                   `json:\"iteration_count\"`\n\tLoadType     string                 `json:\"load_type\"`\n\tDuration     int                    `json:\"duration\"`\n\tAssertions   []TestAssertion        `json:\"success_criterias\"`\n\tTimeRunCount timeRunCount           `json:\"manual_load\"`\n\tSteps        []step                 `json:\"steps\"`\n\tOutput       string                 `json:\"output\"`\n\tProxy        string                 `json:\"proxy\"`\n\tEnvs         map[string]interface{} `json:\"env\"`\n\tData         map[string]CsvConf     `json:\"data\"`\n\tDebug        bool                   `json:\"debug\"`\n\tSamplingRate *int                   `json:\"sampling_rate\"`\n\tEngineMode   string                 `json:\"engine_mode\"`\n\tCookies      CookieConf             `json:\"cookie_jar\"`\n}\n\ntype CookieConf struct {\n\tCookies []CustomCookie `json:\"cookies\"`\n\tEnabled bool           `json:\"enabled\"`\n}\n\ntype CustomCookie struct {\n\tName     string `json:\"name\"`\n\tValue    string `json:\"value\"`\n\tDomain   string `json:\"domain\"`\n\tPath     string `json:\"path\"`\n\tExpires  string `json:\"expires\"`\n\tMaxAge   int    `json:\"max_age\"`\n\tHttpOnly bool   `json:\"http_only\"`\n\tSecure   bool   `json:\"secure\"`\n\tRaw      string `json:\"raw\"`\n}\n\ntype TestAssertion struct {\n\tRule  string `json:\"rule\"`\n\tAbort bool   `json:\"abort\"`\n\tDelay int    `json:\"delay\"`\n}\n\nfunc (j *JsonReader) UnmarshalJSON(data []byte) error {\n\ttype jsonReaderAlias JsonReader\n\tdefaultFields := &jsonReaderAlias{\n\t\tLoadType:   types.DefaultLoadType,\n\t\tDuration:   types.DefaultDuration,\n\t\tOutput:     types.DefaultOutputType,\n\t\tEngineMode: types.EngineModeDdosify,\n\t}\n\n\terr := json.Unmarshal(data, defaultFields)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t*j = JsonReader(*defaultFields)\n\treturn nil\n}\n\nfunc (j *JsonReader) Init(jsonByte []byte) (err error) {\n\tif !json.Valid(jsonByte) {\n\t\terr = fmt.Errorf(\"provided json is invalid\")\n\t\treturn\n\t}\n\n\terr = json.Unmarshal(jsonByte, &j)\n\tif err != nil {\n\t\treturn\n\t}\n\treturn\n}\n\nfunc (j *JsonReader) CreateHammer() (h types.Hammer, err error) {\n\t// Scenario\n\ts := types.Scenario{\n\t\tEnvs: j.Envs,\n\t}\n\tvar si types.ScenarioStep\n\tfor _, step := range j.Steps {\n\t\tsi, err = stepToScenarioStep(step)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\ts.Steps = append(s.Steps, si)\n\t}\n\n\t// Proxy\n\tvar proxyURL *url.URL\n\tif j.Proxy != \"\" {\n\t\tproxyURL, err = url.Parse(j.Proxy)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\tp := proxy.Proxy{\n\t\tStrategy: proxy.ProxyTypeSingle,\n\t\tAddr:     proxyURL,\n\t}\n\n\t// for backwards compatibility\n\tvar iterationCount int\n\tif j.IterCount != nil {\n\t\titerationCount = *j.IterCount\n\t} else if j.ReqCount != nil {\n\t\titerationCount = *j.ReqCount\n\t} else {\n\t\titerationCount = types.DefaultIterCount\n\t}\n\tj.IterCount = &iterationCount\n\n\t// TimeRunCount\n\tif len(j.TimeRunCount) > 0 {\n\t\t*j.IterCount, j.Duration = 0, 0\n\t\tfor _, t := range j.TimeRunCount {\n\t\t\t*j.IterCount += t.Count\n\t\t\tj.Duration += t.Duration\n\t\t}\n\t}\n\n\tvar samplingRate int\n\tif j.SamplingRate != nil {\n\t\tsamplingRate = *j.SamplingRate\n\t} else {\n\t\tsamplingRate = types.DefaultSamplingCount\n\t}\n\n\ttestDataConf := make(map[string]types.CsvConf)\n\tfor key, val := range j.Data {\n\t\tvars := make(map[string]types.Tag)\n\t\tfor k, v := range val.Vars {\n\t\t\tvars[k] = types.Tag{\n\t\t\t\tTag:  v.Tag,\n\t\t\t\tType: v.Type,\n\t\t\t}\n\t\t}\n\t\ttestDataConf[key] = types.CsvConf{\n\t\t\tPath:          val.Path,\n\t\t\tDelimiter:     val.Delimiter,\n\t\t\tSkipFirstLine: val.SkipFirstLine,\n\t\t\tVars:          vars,\n\t\t\tSkipEmptyLine: val.SkipEmptyLine,\n\t\t\tAllowQuota:    val.AllowQuota,\n\t\t\tOrder:         val.Order,\n\t\t}\n\t}\n\n\tif j.Cookies.Enabled && j.EngineMode == types.EngineModeDdosify {\n\t\treturn h, fmt.Errorf(\"cookies are not supported in ddosify engine mode, please use distinct-user or repeated-user mode\")\n\t}\n\n\tvar testAssertions map[string]types.TestAssertionOpt\n\tif len(j.Assertions) > 0 {\n\t\ttestAssertions = make(map[string]types.TestAssertionOpt, 0)\n\t}\n\tfor _, as := range j.Assertions {\n\t\ttestAssertions[as.Rule] = types.TestAssertionOpt{\n\t\t\tAbort: as.Abort,\n\t\t\tDelay: as.Delay,\n\t\t}\n\t}\n\n\t// Hammer\n\th = types.Hammer{\n\t\tIterationCount:    *j.IterCount,\n\t\tLoadType:          strings.ToLower(j.LoadType),\n\t\tTestDuration:      j.Duration,\n\t\tTimeRunCountMap:   types.TimeRunCount(j.TimeRunCount),\n\t\tScenario:          s,\n\t\tProxy:             p,\n\t\tReportDestination: j.Output,\n\t\tDebug:             j.Debug,\n\t\tSamplingRate:      samplingRate,\n\t\tEngineMode:        j.EngineMode,\n\t\tTestDataConf:      testDataConf,\n\t\tCookies:           *(*[]types.CustomCookie)(unsafe.Pointer(&j.Cookies.Cookies)),\n\t\tCookiesEnabled:    j.Cookies.Enabled,\n\t\tAssertions:        testAssertions,\n\t\tSingleMode:        types.DefaultSingleMode,\n\t}\n\treturn\n}\n\nfunc stepToScenarioStep(s step) (types.ScenarioStep, error) {\n\tvar payload string\n\tvar err error\n\tif len(s.PayloadMultipart) > 0 {\n\t\tif s.Headers == nil {\n\t\t\ts.Headers = make(map[string]string)\n\t\t}\n\n\t\tpayload, s.Headers[\"Content-Type\"], err = prepareMultipartPayload(s.PayloadMultipart)\n\t\tif err != nil {\n\t\t\treturn types.ScenarioStep{}, err\n\t\t}\n\t} else if s.PayloadFile != \"\" {\n\t\tvar pUrl *url.URL\n\t\tif pUrl, err = url.ParseRequestURI(s.PayloadFile); err == nil && pUrl.IsAbs() { // url\n\t\t\tpayload, err = preparePayloadFile(s.PayloadFile)\n\t\t\tif err != nil {\n\t\t\t\treturn types.ScenarioStep{}, err\n\t\t\t}\n\t\t} else if _, err = os.Stat(s.PayloadFile); err == nil { // local file path\n\t\t\tbuf, err := ioutil.ReadFile(s.PayloadFile)\n\t\t\tif err != nil {\n\t\t\t\treturn types.ScenarioStep{}, err\n\t\t\t}\n\t\t\tpayload = string(buf)\n\t\t} else {\n\t\t\treturn types.ScenarioStep{}, fmt.Errorf(\"payload file %s not found\", s.PayloadFile)\n\t\t}\n\t} else {\n\t\tpayload = s.Payload\n\t}\n\n\t// Set default Auth type if not set\n\tif s.Auth != (auth{}) && s.Auth.Type == \"\" {\n\t\ts.Auth.Type = types.AuthHttpBasic\n\t}\n\n\terr = types.IsTargetValid(s.Url)\n\tif err != nil {\n\t\treturn types.ScenarioStep{}, err\n\t}\n\n\tvar capturedEnvs []types.EnvCaptureConf\n\tfor name, path := range s.CaptureEnv {\n\t\tcapConf := types.EnvCaptureConf{\n\t\t\tJsonPath:   path.JsonPath,\n\t\t\tXpath:      path.XPath,\n\t\t\tXpathHtml:  path.XpathHtml,\n\t\t\tName:       name,\n\t\t\tFrom:       types.SourceType(path.From),\n\t\t\tKey:        path.HeaderKey,\n\t\t\tCookieName: path.CookieName,\n\t\t}\n\n\t\tif path.RegExp != nil {\n\t\t\tcapConf.RegExp = &types.RegexCaptureConf{\n\t\t\t\tExp: path.RegExp.Exp,\n\t\t\t\tNo:  path.RegExp.No,\n\t\t\t}\n\t\t}\n\n\t\tcapturedEnvs = append(capturedEnvs, capConf)\n\t}\n\n\titem := types.ScenarioStep{\n\t\tID:            s.Id,\n\t\tName:          s.Name,\n\t\tURL:           s.Url,\n\t\tAuth:          types.Auth(s.Auth),\n\t\tMethod:        strings.ToUpper(s.Method),\n\t\tHeaders:       s.Headers,\n\t\tPayload:       payload,\n\t\tTimeout:       s.Timeout,\n\t\tSleep:         strings.ReplaceAll(s.Sleep, \" \", \"\"),\n\t\tCustom:        s.Others,\n\t\tEnvsToCapture: capturedEnvs,\n\t\tAssertions:    s.Assertions,\n\t}\n\n\tif s.CertPath != \"\" && s.CertKeyPath != \"\" {\n\t\tcert, pool, err := types.ParseTLS(s.CertPath, s.CertKeyPath)\n\t\tif err != nil {\n\t\t\treturn item, err\n\t\t}\n\n\t\titem.Cert = cert\n\t\titem.CertPool = pool\n\t}\n\n\treturn item, nil\n}\n\nfunc prepareMultipartPayload(parts []multipartFormData) (body string, contentType string, err error) {\n\tbyteBody := &bytes.Buffer{}\n\twriter := multipart.NewWriter(byteBody)\n\n\tfor _, part := range parts {\n\t\tvar multipartError RemoteMultipartError\n\t\tif strings.EqualFold(part.Type, \"file\") {\n\t\t\tif strings.EqualFold(part.Src, \"remote\") {\n\t\t\t\tresponse, err := http.Get(part.Value)\n\t\t\t\tif err != nil {\n\t\t\t\t\tmultipartError.wrappedErr = err\n\t\t\t\t\tmultipartError.msg = \"Error while getting remote file\"\n\t\t\t\t\treturn \"\", \"\", multipartError\n\t\t\t\t}\n\t\t\t\tdefer response.Body.Close()\n\n\t\t\t\tu, _ := url.Parse(part.Value)\n\t\t\t\tformPart, err := writer.CreateFormFile(part.Name, path.Base(u.Path))\n\t\t\t\tif err != nil {\n\t\t\t\t\tmultipartError.wrappedErr = err\n\t\t\t\t\tmultipartError.msg = \"Error while creating form file\"\n\t\t\t\t\treturn \"\", \"\", err\n\t\t\t\t}\n\n\t\t\t\tif !(response.StatusCode >= 200 && response.StatusCode <= 299) {\n\t\t\t\t\tmultipartError.wrappedErr = fmt.Errorf(\"Multipart: request to remote url (%s) failed. Status code: %d\", part.Value, response.StatusCode)\n\t\t\t\t\tmultipartError.msg = \"Error while getting remote multipart file\"\n\t\t\t\t\treturn \"\", \"\", multipartError\n\t\t\t\t}\n\n\t\t\t\t_, err = io.Copy(formPart, response.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tmultipartError.wrappedErr = err\n\t\t\t\t\tmultipartError.msg = \"Error while copying response body\"\n\t\t\t\t\treturn \"\", \"\", multipartError\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfile, err := os.Open(part.Value)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn \"\", \"\", err\n\t\t\t\t}\n\t\t\t\tdefer file.Close()\n\n\t\t\t\tformPart, err := writer.CreateFormFile(part.Name, filepath.Base(file.Name()))\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn \"\", \"\", err\n\t\t\t\t}\n\n\t\t\t\t_, err = io.Copy(formPart, file)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn \"\", \"\", err\n\t\t\t\t}\n\t\t\t}\n\n\t\t} else {\n\t\t\t// If we have to specify Content-Type in Content-Disposition, we should use writer.CreatePart directly.\n\t\t\terr = writer.WriteField(part.Name, part.Value)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", \"\", err\n\t\t\t}\n\t\t}\n\t}\n\n\twriter.Close()\n\treturn byteBody.String(), writer.FormDataContentType(), err\n}\n\nfunc preparePayloadFile(url string) (body string, err error) {\n\treq, err := http.NewRequest(http.MethodGet, url, nil)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif !(resp.StatusCode >= 200 && resp.StatusCode <= 299) {\n\t\treturn \"\", fmt.Errorf(\"Payload File: request to remote url (%s) failed. Status Code: %d\", url, resp.StatusCode)\n\t}\n\tdefer resp.Body.Close()\n\tby, _ := io.ReadAll(resp.Body)\n\treturn string(by), nil\n}\n\ntype RemoteMultipartError struct { // UnWrappable\n\tmsg        string\n\twrappedErr error\n}\n\nfunc (nf RemoteMultipartError) Error() string {\n\tif nf.wrappedErr != nil {\n\t\treturn fmt.Sprintf(\"%s, %s\", nf.msg, nf.wrappedErr.Error())\n\t}\n\treturn nf.msg\n}\n\nfunc (nf RemoteMultipartError) Unwrap() error {\n\treturn nf.wrappedErr\n}\n"
  },
  {
    "path": "ddosify_engine/config/json_test.go",
    "content": "/*\n*\n*\tDdosify - Load testing tool for any web system.\n*   Copyright (C) 2021  Ddosify (https://ddosify.com)\n*\n*   This program is free software: you can redistribute it and/or modify\n*   it under the terms of the GNU Affero General Public License as published\n*   by the Free Software Foundation, either version 3 of the License, or\n*   (at your option) any later version.\n*\n*   This program is distributed in the hope that it will be useful,\n*   but WITHOUT ANY WARRANTY; without even the implied warranty of\n*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n*   GNU Affero General Public License for more details.\n*\n*   You should have received a copy of the GNU Affero General Public License\n*   along with this program.  If not, see <https://www.gnu.org/licenses/>.\n*\n */\n\npackage config\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"go.ddosify.com/ddosify/core/proxy\"\n\t\"go.ddosify.com/ddosify/core/report\"\n\t\"go.ddosify.com/ddosify/core/types\"\n)\n\nfunc TestCreateHammerDefaultValues(t *testing.T) {\n\tt.Parallel()\n\tjsonReader, _ := NewConfigReader(readConfigFile(\"config_testdata/config_empty.json\"), ConfigTypeJson)\n\texpectedHammer := types.Hammer{\n\t\tIterationCount:    types.DefaultIterCount,\n\t\tLoadType:          types.DefaultLoadType,\n\t\tTestDuration:      types.DefaultDuration,\n\t\tReportDestination: types.DefaultOutputType,\n\t\tScenario: types.Scenario{\n\t\t\tSteps: []types.ScenarioStep{{\n\t\t\t\tID:      1,\n\t\t\t\tURL:     \"test.com\",\n\t\t\t\tMethod:  types.DefaultMethod,\n\t\t\t\tTimeout: types.DefaultTimeout,\n\t\t\t}},\n\t\t},\n\t\tProxy: proxy.Proxy{\n\t\t\tStrategy: proxy.ProxyTypeSingle,\n\t\t},\n\t\tSamplingRate: types.DefaultSamplingCount,\n\t\tEngineMode:   types.EngineModeDdosify,\n\t\tTestDataConf: make(map[string]types.CsvConf),\n\t\tSingleMode:   true,\n\t}\n\n\th, err := jsonReader.CreateHammer()\n\n\tif err != nil {\n\t\tt.Errorf(\"TestCreateHammerDefaultValues error occurred: %v\", err)\n\t}\n\n\tif !reflect.DeepEqual(expectedHammer, h) {\n\t\tt.Errorf(\"\\nExpected: %#v, \\nFound: %#v\", expectedHammer, h)\n\t}\n}\n\nfunc TestCreateHammer(t *testing.T) {\n\tt.Parallel()\n\tjsonReader, _ := NewConfigReader(readConfigFile(\"config_testdata/config.json\"), ConfigTypeJson)\n\taddr, _ := url.Parse(\"http://proxy_host:80\")\n\texpectedHammer := types.Hammer{\n\t\tIterationCount:    1555,\n\t\tLoadType:          types.LoadTypeWaved,\n\t\tTestDuration:      21,\n\t\tReportDestination: report.OutputTypeStdout,\n\t\tScenario: types.Scenario{\n\t\t\tSteps: []types.ScenarioStep{\n\t\t\t\t{\n\t\t\t\t\tID:      1,\n\t\t\t\t\tName:    \"Example Name 1\",\n\t\t\t\t\tURL:     \"https://app.servdown.com/accounts/login/?next=/\",\n\t\t\t\t\tMethod:  http.MethodGet,\n\t\t\t\t\tTimeout: 3,\n\t\t\t\t\tSleep:   \"1000\",\n\t\t\t\t\tPayload: \"payload str\",\n\t\t\t\t\tCustom:  map[string]interface{}{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tID:      2,\n\t\t\t\t\tName:    \"Example Name 2\",\n\t\t\t\t\tURL:     \"http://test.com\",\n\t\t\t\t\tMethod:  http.MethodPut,\n\t\t\t\t\tTimeout: 2,\n\t\t\t\t\tSleep:   \"300-500\",\n\t\t\t\t\tHeaders: map[string]string{\n\t\t\t\t\t\t\"ContenType\":    \"application/xml\",\n\t\t\t\t\t\t\"X-ddosify-key\": \"ajkndalnasd\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tProxy: proxy.Proxy{\n\t\t\tStrategy: \"single\",\n\t\t\tAddr:     addr,\n\t\t},\n\t\tSamplingRate: types.DefaultSamplingCount,\n\t\tEngineMode:   types.EngineModeDdosify,\n\t\tTestDataConf: make(map[string]types.CsvConf),\n\t\tSingleMode:   true,\n\t}\n\n\th, err := jsonReader.CreateHammer()\n\n\tif err != nil {\n\t\tt.Errorf(\"TestCreateHammer error occurred: %v\", err)\n\t}\n\n\tif !reflect.DeepEqual(expectedHammer, h) {\n\t\tt.Errorf(\"\\nExpected: %v,\\n Found: %v\", expectedHammer, h)\n\t}\n}\n\nfunc TestCreateHammerWithIterationCountInsteadOfReqCount(t *testing.T) {\n\tt.Parallel()\n\tjsonReader, _ := NewConfigReader(readConfigFile(\"config_testdata/config_iteration_count.json\"), ConfigTypeJson)\n\taddr, _ := url.Parse(\"http://proxy_host:80\")\n\texpectedHammer := types.Hammer{\n\t\tIterationCount:    1555,\n\t\tLoadType:          types.LoadTypeWaved,\n\t\tTestDuration:      21,\n\t\tReportDestination: report.OutputTypeStdout,\n\t\tScenario: types.Scenario{\n\t\t\tSteps: []types.ScenarioStep{\n\t\t\t\t{\n\t\t\t\t\tID:      1,\n\t\t\t\t\tName:    \"Example Name 1\",\n\t\t\t\t\tURL:     \"https://app.servdown.com/accounts/login/?next=/\",\n\t\t\t\t\tMethod:  http.MethodGet,\n\t\t\t\t\tTimeout: 3,\n\t\t\t\t\tSleep:   \"1000\",\n\t\t\t\t\tPayload: \"payload str\",\n\t\t\t\t\tCustom:  map[string]interface{}{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tID:      2,\n\t\t\t\t\tName:    \"Example Name 2\",\n\t\t\t\t\tURL:     \"http://test.com\",\n\t\t\t\t\tMethod:  http.MethodPut,\n\t\t\t\t\tTimeout: 2,\n\t\t\t\t\tSleep:   \"300-500\",\n\t\t\t\t\tHeaders: map[string]string{\n\t\t\t\t\t\t\"ContenType\":    \"application/xml\",\n\t\t\t\t\t\t\"X-ddosify-key\": \"ajkndalnasd\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tProxy: proxy.Proxy{\n\t\t\tStrategy: \"single\",\n\t\t\tAddr:     addr,\n\t\t},\n\t\tSamplingRate: types.DefaultSamplingCount,\n\t\tEngineMode:   types.EngineModeDdosify,\n\t\tTestDataConf: make(map[string]types.CsvConf),\n\t\tSingleMode:   true,\n\t}\n\n\th, err := jsonReader.CreateHammer()\n\n\tif err != nil {\n\t\tt.Errorf(\"TestCreateHammer error occurred: %v\", err)\n\t}\n\n\tif !reflect.DeepEqual(expectedHammer, h) {\n\t\tt.Errorf(\"\\nExpected: %v,\\n Found: %v\", expectedHammer, h)\n\t}\n}\n\nfunc TestCreateHammerWithIterationCountOverridesReqCount(t *testing.T) {\n\tt.Parallel()\n\tjsonReader, _ := NewConfigReader(readConfigFile(\"config_testdata/config_iteration_count_over_req_count.json\"),\n\t\tConfigTypeJson)\n\taddr, _ := url.Parse(\"http://proxy_host:80\")\n\texpectedHammer := types.Hammer{\n\t\tIterationCount:    333,\n\t\tLoadType:          types.LoadTypeWaved,\n\t\tTestDuration:      21,\n\t\tReportDestination: report.OutputTypeStdout,\n\t\tScenario: types.Scenario{\n\t\t\tSteps: []types.ScenarioStep{\n\t\t\t\t{\n\t\t\t\t\tID:   1,\n\t\t\t\t\tName: \"Example Name 1\",\n\t\t\t\t\tURL:  \"https://app.servdown.com/accounts/login/?next=/\",\n\t\t\t\t\t// Protocol: types.ProtocolHTTPS,\n\t\t\t\t\tMethod:  http.MethodGet,\n\t\t\t\t\tTimeout: 3,\n\t\t\t\t\tSleep:   \"1000\",\n\t\t\t\t\tPayload: \"payload str\",\n\t\t\t\t\tCustom:  map[string]interface{}{},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tID:   2,\n\t\t\t\t\tName: \"Example Name 2\",\n\t\t\t\t\tURL:  \"http://test.com\",\n\t\t\t\t\t// Protocol: types.ProtocolHTTP,\n\t\t\t\t\tMethod:  http.MethodPut,\n\t\t\t\t\tTimeout: 2,\n\t\t\t\t\tSleep:   \"300-500\",\n\t\t\t\t\tHeaders: map[string]string{\n\t\t\t\t\t\t\"ContenType\":    \"application/xml\",\n\t\t\t\t\t\t\"X-ddosify-key\": \"ajkndalnasd\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tProxy: proxy.Proxy{\n\t\t\tStrategy: \"single\",\n\t\t\tAddr:     addr,\n\t\t},\n\t\tSamplingRate: types.DefaultSamplingCount,\n\t\tEngineMode:   types.EngineModeDdosify,\n\t\tTestDataConf: make(map[string]types.CsvConf),\n\t\tSingleMode:   true,\n\t}\n\n\th, err := jsonReader.CreateHammer()\n\n\tif err != nil {\n\t\tt.Errorf(\"TestCreateHammer error occurred: %v\", err)\n\t}\n\n\tif !reflect.DeepEqual(expectedHammer, h) {\n\t\tt.Errorf(\"\\nExpected: %v,\\n Found: %v\", expectedHammer, h)\n\t}\n}\n\nfunc TestCreateHammerManualLoad(t *testing.T) {\n\tt.Parallel()\n\n\tjsonReader, _ := NewConfigReader(readConfigFile(\"config_testdata/config_manual_load.json\"), ConfigTypeJson)\n\texpectedHammer := types.Hammer{\n\t\tIterationCount:    35,\n\t\tLoadType:          types.DefaultLoadType,\n\t\tTestDuration:      18,\n\t\tTimeRunCountMap:   types.TimeRunCount{{Duration: 5, Count: 5}, {Duration: 6, Count: 10}, {Duration: 7, Count: 20}},\n\t\tReportDestination: types.DefaultOutputType,\n\t\tScenario: types.Scenario{\n\t\t\tSteps: []types.ScenarioStep{{\n\t\t\t\tID:      1,\n\t\t\t\tURL:     \"test.com\",\n\t\t\t\tMethod:  types.DefaultMethod,\n\t\t\t\tTimeout: types.DefaultTimeout,\n\t\t\t}},\n\t\t},\n\t\tProxy: proxy.Proxy{\n\t\t\tStrategy: proxy.ProxyTypeSingle,\n\t\t},\n\t\tSamplingRate: types.DefaultSamplingCount,\n\t\tEngineMode:   types.EngineModeDdosify,\n\t\tTestDataConf: make(map[string]types.CsvConf),\n\t\tSingleMode:   true,\n\t}\n\n\th, err := jsonReader.CreateHammer()\n\n\tif err != nil {\n\t\tt.Errorf(\"TestCreateHammerManualLoad error occurred: %v\", err)\n\t}\n\n\tif !reflect.DeepEqual(expectedHammer, h) {\n\t\tt.Errorf(\"Expected: %v, Found: %v\", expectedHammer, h)\n\t}\n}\n\nfunc TestCreateHammerManualLoadOverrideOthers(t *testing.T) {\n\tt.Parallel()\n\n\tjsonReader, _ := NewConfigReader(readConfigFile(\"config_testdata/config_manual_load_override.json\"), ConfigTypeJson)\n\texpectedHammer := types.Hammer{\n\t\tIterationCount:    35,\n\t\tLoadType:          types.DefaultLoadType,\n\t\tTestDuration:      18,\n\t\tTimeRunCountMap:   types.TimeRunCount{{Duration: 5, Count: 5}, {Duration: 6, Count: 10}, {Duration: 7, Count: 20}},\n\t\tReportDestination: types.DefaultOutputType,\n\t\tScenario: types.Scenario{\n\t\t\tSteps: []types.ScenarioStep{{\n\t\t\t\tID:      1,\n\t\t\t\tURL:     \"test.com\",\n\t\t\t\tMethod:  types.DefaultMethod,\n\t\t\t\tTimeout: types.DefaultTimeout,\n\t\t\t}},\n\t\t},\n\t\tProxy: proxy.Proxy{\n\t\t\tStrategy: proxy.ProxyTypeSingle,\n\t\t},\n\t\tSamplingRate: types.DefaultSamplingCount,\n\t\tEngineMode:   types.EngineModeDdosify,\n\t\tTestDataConf: make(map[string]types.CsvConf),\n\t\tSingleMode:   true,\n\t}\n\n\th, err := jsonReader.CreateHammer()\n\n\tif err != nil {\n\t\tt.Errorf(\"TestCreateHammerManualLoad error occurred: %v\", err)\n\t}\n\n\tif !reflect.DeepEqual(expectedHammer, h) {\n\t\tt.Errorf(\"Expected: %v, Found: %v\", expectedHammer, h)\n\t}\n}\n\nfunc TestCreateHammerPayload(t *testing.T) {\n\tt.Parallel()\n\tjsonReader, _ := NewConfigReader(readConfigFile(\"config_testdata/config_payload.json\"), ConfigTypeJson)\n\texpectedPayloads := []string{\"payload from string\", \"Payloaf from file.\"}\n\th, err := jsonReader.CreateHammer()\n\n\tif err != nil {\n\t\tt.Errorf(\"TestCreateHammerPayload error occurred: %v\", err)\n\t}\n\n\tsteps := h.Scenario.Steps\n\n\tif steps[0].Payload != expectedPayloads[0] {\n\t\tt.Errorf(\"Expected: %v, Found: %v\", expectedPayloads[0], steps[0].Payload)\n\t}\n\n\tif steps[1].Payload != expectedPayloads[1] {\n\t\tt.Errorf(\"Expected: %v, Found: %v\", expectedPayloads[1], steps[1].Payload)\n\t}\n}\n\nfunc TestCreateHammerMultipartPayload(t *testing.T) {\n\tt.Parallel()\n\tjsonReader, _ := NewConfigReader(readConfigFile(\"config_testdata/config_multipart_payload.json\"), ConfigTypeJson)\n\n\th, err := jsonReader.CreateHammer()\n\tif err != nil {\n\t\tt.Errorf(\"TestCreateHammerMultipartPayload error occurred: %v\", err)\n\t}\n\tsteps := h.Scenario.Steps\n\n\t// Content-Type Header Check\n\tval, ok := steps[0].Headers[\"Content-Type\"]\n\tif !ok {\n\t\tt.Error(\"Content-Type header should be exist\")\n\t}\n\n\trgx := \"multipart/form-data; boundary=.*\"\n\tr, _ := regexp.Compile(rgx)\n\tif !r.MatchString(val) {\n\t\tt.Errorf(\"Expected: %v, Found: %v\", rgx, val)\n\t}\n\n\t// Payload Check - Ensure that payload contains 4 form field.\n\tif c := strings.Count(steps[0].Payload, \"Content-Disposition: form-data;\"); c != 4 {\n\t\tt.Errorf(\"Expected: %v, Found: %v\", 4, c)\n\t}\n}\n\nfunc TestCreateHammerMultipartPayload_RemoteErr(t *testing.T) {\n\tt.Parallel()\n\tjsonReader, _ := NewConfigReader(readConfigFile(\"config_testdata/config_multipart_err.json\"), ConfigTypeJson)\n\n\t_, err := jsonReader.CreateHammer()\n\tif err == nil {\n\t\tt.Error(\"TestCreateHammerMultipartPayload_RemoteErr should return error\")\n\t}\n\n\tvar multipartErr RemoteMultipartError\n\tif !errors.As(err, &multipartErr) {\n\t\tt.Errorf(\"Expected: %v, Found: %v\", multipartErr, err)\n\t}\n}\n\nfunc TestCreateHammerAuth(t *testing.T) {\n\tt.Parallel()\n\tjsonReader, _ := NewConfigReader(readConfigFile(\"config_testdata/config_auth.json\"), ConfigTypeJson)\n\texpectedAuths := []types.Auth{\n\t\t{\n\t\t\tType:     types.AuthHttpBasic,\n\t\t\tUsername: \"kursat\",\n\t\t\tPassword: \"12345\",\n\t\t},\n\t\t{}}\n\n\th, err := jsonReader.CreateHammer()\n\tif err != nil {\n\t\tt.Errorf(\"TestCreateHammerAuth error occurred: %v\", err)\n\t}\n\n\tsteps := h.Scenario.Steps\n\tif steps[0].Auth != expectedAuths[0] {\n\t\tt.Errorf(\"Expected: %v, Found: %v\", expectedAuths[0], steps[0].Auth)\n\t}\n\n\tif steps[1].Auth != expectedAuths[1] {\n\t\tt.Errorf(\"Expected: %v, Found: %v\", expectedAuths[1], steps[1].Auth)\n\t}\n}\n\nfunc TestCreateHammerGlobalEnvs(t *testing.T) {\n\tt.Parallel()\n\tjsonReader, _ := NewConfigReader(readConfigFile(\"config_testdata/config_global_envs.json\"), ConfigTypeJson)\n\texpectedGlobalEnvs := map[string]interface{}{\n\t\t\"HTTPBIN\": \"https://httpbin.ddosify.com\",\n\t\t\"LOCAL\":   \"http://localhost:8084/hello\",\n\t}\n\n\th, err := jsonReader.CreateHammer()\n\tif err != nil {\n\t\tt.Errorf(\"TestCreateHammerGlobalEnvs error occurred: %v\", err)\n\t}\n\n\tglobalEnvs := h.Scenario.Envs\n\n\tif !reflect.DeepEqual(globalEnvs, expectedGlobalEnvs) {\n\t\tt.Errorf(\"TestCreateHammerGlobalEnvs global envs got: %#v expected: %#v\", globalEnvs, expectedGlobalEnvs)\n\t}\n}\n\nfunc TestCreateHammerCaptureEnvs(t *testing.T) {\n\tt.Parallel()\n\tjsonReader, _ := NewConfigReader(readConfigFile(\"config_testdata/config_capture_environment.json\"), ConfigTypeJson)\n\tjson_path := \"num\"\n\tcookie_name := \"x\"\n\texpectedEnvsToCaptureFirstStep := []types.EnvCaptureConf{\n\t\t{\n\t\t\tName:     \"NUM\",\n\t\t\tFrom:     types.Body,\n\t\t\tJsonPath: &json_path,\n\t\t},\n\t\t{\n\t\t\tName:       \"X_COOKIE\",\n\t\t\tFrom:       types.Cookie,\n\t\t\tCookieName: &cookie_name,\n\t\t}}\n\n\tregex := \"[a-z]+_[0-9]+\"\n\texpectedEnvsToCaptureSecondStep := []types.EnvCaptureConf{{\n\t\tName: \"REGEX_MATCH_ENV\",\n\t\tFrom: types.Body,\n\t\tRegExp: &types.RegexCaptureConf{\n\t\t\tExp: &regex,\n\t\t\tNo:  1,\n\t\t},\n\t}}\n\n\th, err := jsonReader.CreateHammer()\n\tif err != nil {\n\t\tt.Errorf(\"TestCreateHammerCaptureEnvs error occurred: %v\", err)\n\t}\n\n\tenvsToCapture0 := h.Scenario.Steps[0].EnvsToCapture\n\n\tvar numCapturedEnv, xcookieCaptureEnv types.EnvCaptureConf\n\tfor i := range envsToCapture0 {\n\t\tif envsToCapture0[i].Name == \"NUM\" {\n\t\t\tnumCapturedEnv = envsToCapture0[i]\n\t\t} else if envsToCapture0[i].Name == \"X_COOKIE\" {\n\t\t\txcookieCaptureEnv = envsToCapture0[i]\n\t\t}\n\t}\n\n\tif numCapturedEnv.Name != \"NUM\" || numCapturedEnv.From != types.Body || *numCapturedEnv.JsonPath != \"num\" {\n\t\tt.Errorf(\"TestCreateHammerCaptureEnvs global envs got: %#v expected: %#v\", numCapturedEnv, expectedEnvsToCaptureFirstStep[0])\n\t}\n\n\tif xcookieCaptureEnv.Name != \"X_COOKIE\" || xcookieCaptureEnv.From != types.Cookie || *xcookieCaptureEnv.CookieName != \"x\" {\n\t\tt.Errorf(\"TestCreateHammerCaptureEnvs global envs got: %#v expected: %#v\", xcookieCaptureEnv, expectedEnvsToCaptureFirstStep[1])\n\t}\n\n\tenvsToCaptureSecondStep := h.Scenario.Steps[1].EnvsToCapture\n\tvar regexMatchEnv types.EnvCaptureConf\n\tfor i := range envsToCaptureSecondStep {\n\t\tif envsToCaptureSecondStep[i].Name == \"REGEX_MATCH_ENV\" {\n\t\t\tregexMatchEnv = envsToCaptureSecondStep[i]\n\t\t}\n\t}\n\n\tif regexMatchEnv.Name != \"REGEX_MATCH_ENV\" || regexMatchEnv.From != types.Body || *regexMatchEnv.RegExp.Exp != \"[a-z]+_[0-9]+\" || regexMatchEnv.RegExp.No != 1 {\n\t\tt.Errorf(\"TestCreateHammerCaptureEnvs global envs got: %#v expected: %#v\", regexMatchEnv, expectedEnvsToCaptureSecondStep[0])\n\t}\n}\n\nfunc TestCreateHammerInvalidTarget(t *testing.T) {\n\tt.Parallel()\n\tjsonReader, _ := NewConfigReader(readConfigFile(\"config_testdata/config_invalid_target.json\"), ConfigTypeJson)\n\n\t_, err := jsonReader.CreateHammer()\n\tif err == nil {\n\t\tt.Errorf(\"TestCreateHammerProtocol error occurred\")\n\t}\n}\n\nfunc TestCreateHammerCookiesEnabledValidOnlyOnUserModes(t *testing.T) {\n\tt.Parallel()\n\tjsonReader, _ := NewConfigReader(readConfigFile(\"config_testdata/config_invalid_user_mode_for_cookies.json\"), ConfigTypeJson)\n\n\t_, err := jsonReader.CreateHammer()\n\tif err == nil {\n\t\tt.Errorf(\"TestCreateHammerCookiesEnabledValidOnlyOnUserModes expected error but got nil, cookies enabled only on user modes\")\n\t}\n}\n\nfunc TestCreateHammerTLS(t *testing.T) {\n\tt.Parallel()\n\n\t// prepare TLS files\n\tcert, certKey := generateCerts()\n\tcertFile, keyFile, err := createCertPairFiles(cert, certKey)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to prepare certs %v\", err)\n\t}\n\tdefer os.Remove(certFile.Name())\n\tdefer os.Remove(keyFile.Name())\n\n\tconfig := buildJSONTLSConfig(certFile.Name(), keyFile.Name())\n\n\tjsonReader, _ := NewConfigReader(config, ConfigTypeJson)\n\n\th, err := jsonReader.CreateHammer()\n\n\tif err != nil {\n\t\tt.Errorf(\"TestCreateHammerDefaultValues error occurred: %v\", err)\n\t}\n\n\tcertVal, _, err := types.ParseTLS(certFile.Name(), keyFile.Name())\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to gen certs %v\", err)\n\t}\n\n\t// We compare only Certificte because CertPool has pointers inside and it's hard to compare it\n\tif !reflect.DeepEqual(certVal, h.Scenario.Steps[0].Cert) {\n\t\tt.Errorf(\"\\nExpected: %#v, \\nFound: %#v\", certVal, h.Scenario.Steps[0].Cert)\n\t}\n}\n\nfunc TestCreateHammerTLSWithOnlyCertPath(t *testing.T) {\n\tt.Parallel()\n\n\t// prepare TLS files\n\tcert, certKey := generateCerts()\n\tcertFile, keyFile, err := createCertPairFiles(cert, certKey)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to prepare certs %v\", err)\n\t}\n\tdefer os.Remove(certFile.Name())\n\tdefer os.Remove(keyFile.Name())\n\n\tconfig := buildJSONTLSConfig(certFile.Name(), \"\")\n\n\tjsonReader, _ := NewConfigReader(config, ConfigTypeJson)\n\texpectedHammer := types.Hammer{\n\t\tIterationCount:    types.DefaultIterCount,\n\t\tLoadType:          types.DefaultLoadType,\n\t\tTestDuration:      types.DefaultDuration,\n\t\tReportDestination: types.DefaultOutputType,\n\t\tScenario: types.Scenario{\n\t\t\tSteps: []types.ScenarioStep{{\n\t\t\t\tID:      1,\n\t\t\t\tURL:     \"test.com\",\n\t\t\t\tMethod:  types.DefaultMethod,\n\t\t\t\tTimeout: types.DefaultTimeout,\n\t\t\t}},\n\t\t},\n\t\tProxy: proxy.Proxy{\n\t\t\tStrategy: proxy.ProxyTypeSingle,\n\t\t},\n\t\tSamplingRate: types.DefaultSamplingCount,\n\t\tEngineMode:   types.EngineModeDdosify,\n\t\tTestDataConf: make(map[string]types.CsvConf),\n\t\tSingleMode:   true,\n\t}\n\n\th, err := jsonReader.CreateHammer()\n\n\tif err != nil {\n\t\tt.Errorf(\"TestCreateHammerDefaultValues error occurred: %v\", err)\n\t}\n\n\tif !reflect.DeepEqual(expectedHammer, h) {\n\t\tt.Errorf(\"\\nExpected: %#v, \\nFound: %#v\", expectedHammer, h)\n\t}\n}\n\nfunc TestCreateHammerTLSWithOnlyKeyPath(t *testing.T) {\n\tt.Parallel()\n\n\t// prepare TLS files\n\tcert, certKey := generateCerts()\n\tcertFile, keyFile, err := createCertPairFiles(cert, certKey)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to prepare certs %v\", err)\n\t}\n\tdefer os.Remove(certFile.Name())\n\tdefer os.Remove(keyFile.Name())\n\n\tconfig := buildJSONTLSConfig(\"\", keyFile.Name())\n\n\tjsonReader, _ := NewConfigReader(config, ConfigTypeJson)\n\texpectedHammer := types.Hammer{\n\t\tIterationCount:    types.DefaultIterCount,\n\t\tLoadType:          types.DefaultLoadType,\n\t\tTestDuration:      types.DefaultDuration,\n\t\tReportDestination: types.DefaultOutputType,\n\t\tScenario: types.Scenario{\n\t\t\tSteps: []types.ScenarioStep{{\n\t\t\t\tID:      1,\n\t\t\t\tURL:     \"test.com\",\n\t\t\t\tMethod:  types.DefaultMethod,\n\t\t\t\tTimeout: types.DefaultTimeout,\n\t\t\t}},\n\t\t},\n\t\tProxy: proxy.Proxy{\n\t\t\tStrategy: proxy.ProxyTypeSingle,\n\t\t},\n\t\tSamplingRate: types.DefaultSamplingCount,\n\t\tEngineMode:   types.EngineModeDdosify,\n\t\tTestDataConf: make(map[string]types.CsvConf),\n\t\tSingleMode:   true,\n\t}\n\n\th, err := jsonReader.CreateHammer()\n\n\tif err != nil {\n\t\tt.Errorf(\"TestCreateHammerDefaultValues error occurred: %v\", err)\n\t}\n\n\tif !reflect.DeepEqual(expectedHammer, h) {\n\t\tt.Errorf(\"\\nExpected: %#v, \\nFound: %#v\", expectedHammer, h)\n\t}\n}\n\nfunc TestCreateHammerTLSWithWithEmptyPath(t *testing.T) {\n\tt.Parallel()\n\n\tconfig := buildJSONTLSConfig(\"\", \"\")\n\n\tjsonReader, _ := NewConfigReader(config, ConfigTypeJson)\n\texpectedHammer := types.Hammer{\n\t\tIterationCount:    types.DefaultIterCount,\n\t\tLoadType:          types.DefaultLoadType,\n\t\tTestDuration:      types.DefaultDuration,\n\t\tReportDestination: types.DefaultOutputType,\n\t\tScenario: types.Scenario{\n\t\t\tSteps: []types.ScenarioStep{{\n\t\t\t\tID:      1,\n\t\t\t\tURL:     \"test.com\",\n\t\t\t\tMethod:  types.DefaultMethod,\n\t\t\t\tTimeout: types.DefaultTimeout,\n\t\t\t}},\n\t\t},\n\t\tProxy: proxy.Proxy{\n\t\t\tStrategy: proxy.ProxyTypeSingle,\n\t\t},\n\t\tSamplingRate: types.DefaultSamplingCount,\n\t\tEngineMode:   types.EngineModeDdosify,\n\t\tTestDataConf: make(map[string]types.CsvConf),\n\t\tSingleMode:   true,\n\t}\n\n\th, err := jsonReader.CreateHammer()\n\n\tif err != nil {\n\t\tt.Errorf(\"TestCreateHammerDefaultValues error occurred: %v\", err)\n\t}\n\n\tif !reflect.DeepEqual(expectedHammer, h) {\n\t\tt.Errorf(\"\\nExpected: %#v, \\nFound: %#v\", expectedHammer, h)\n\t}\n}\n\nfunc buildJSONTLSConfig(certPath, keyPath string) []byte {\n\tformat := `\n\t{\n\t\t\"steps\": [\n\t\t\t{\n\t\t\t\t\"id\": 1,\n\t\t\t\t\"url\": \"test.com\",\n\t\t\t\t\"cert_path\": %q,\n\t\t\t\t\"cert_key_path\": %q\n\t\t\t}\n\t\t]\n\t}`\n\n\tconfig := fmt.Sprintf(format, certPath, keyPath)\n\n\tfmt.Println(config)\n\n\treturn []byte(config)\n}\n\nfunc createCertPairFiles(cert string, certKey string) (*os.File, *os.File, error) {\n\tcertFile, err := os.CreateTemp(\"\", \".pem\")\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\t_, err = io.WriteString(certFile, cert)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tkeyFile, err := os.CreateTemp(\"\", \".pem\")\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\t_, err = io.WriteString(keyFile, certKey)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\treturn certFile, keyFile, nil\n}\n\nfunc generateCerts() (string, string) {\n\tcert := `-----BEGIN CERTIFICATE-----\nMIIDazCCAlOgAwIBAgIUS4UhTks8aRCQ1k9IGn437ZyP3MgwDQYJKoZIhvcNAQEL\nBQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMjEwMDUyMjM5MDVaFw0zMjEw\nMDIyMjM5MDVaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB\nAQUAA4IBDwAwggEKAoIBAQDMbZctKXBx8v63TXIhM/OB7S6VfPqpzfHufhs6kAHu\njfC2ooCUqzqdg0T8bM1bjahYuAbQA1cWKYBsqfd01Po1ltWmbMf7ZvmSB6VN7kC2\nY670zee91dGDQ2yzmorJuIZAtOBVZesYLg8UHSGzSC/smJOrjYidtlbvzOcX0pv3\nRCIUrNMed60EpSch/rzAJLzJmwNSQZ4vJHNlNetSkvTi7cxMWfwpcM/rN1hEmP1X\nJ43hJp/TNRZVnEsvs/yggP/FwUjG74mU3KfnWiv91AkkarNTNquEMJ+f4OFqMcnF\np0wqg47JTqcAAT0n1B0VB+z0hGXEFMN+IJXsHETZNG+JAgMBAAGjUzBRMB0GA1Ud\nDgQWBBSIw+qUKQJjXWti5x/Cnn2GueuX5zAfBgNVHSMEGDAWgBSIw+qUKQJjXWti\n5x/Cnn2GueuX5zAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAA\nDXzf8VXi4s2GScNfHf0BzMjpyrtRZ0Wbp2Vfh7OwVR6xcx+pqXNjlydM/vu2LvOK\nhh7Jbo+JS+o7O24UJ9lLFkCRsZVF+NFqJf+2rdHCaOiZSdZmtjBU0dFuAGS7+lU3\nM8P7WCNOm6NAKbs7VZHVcZPzp81SCPQgQIS19xRf4Irbvsijv4YdyL4Qv7aWcclb\nMdZX9AH9Fx8tJq4VKvUYsCXAD0kuywMLjh+yj5O/2hMvs5rvaQvm2daQNRDNp884\nuTLrNF7W7QaKEL06ZpXJoBqdKsiwn577XTDKvzN0XxQrT+xV9VHO7OXblF+Od3/Y\nSzBR+QiQKy3x+LkOxhkk\n-----END CERTIFICATE-----`\n\n\tcertKey := `-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDMbZctKXBx8v63\nTXIhM/OB7S6VfPqpzfHufhs6kAHujfC2ooCUqzqdg0T8bM1bjahYuAbQA1cWKYBs\nqfd01Po1ltWmbMf7ZvmSB6VN7kC2Y670zee91dGDQ2yzmorJuIZAtOBVZesYLg8U\nHSGzSC/smJOrjYidtlbvzOcX0pv3RCIUrNMed60EpSch/rzAJLzJmwNSQZ4vJHNl\nNetSkvTi7cxMWfwpcM/rN1hEmP1XJ43hJp/TNRZVnEsvs/yggP/FwUjG74mU3Kfn\nWiv91AkkarNTNquEMJ+f4OFqMcnFp0wqg47JTqcAAT0n1B0VB+z0hGXEFMN+IJXs\nHETZNG+JAgMBAAECggEAM+U6NHfJmNPD/8qER5OFpJ0Ob1qL06F5Yj7XMLWwF9wm\nmGaGV7dkKOpTD/Wa6Dv82ZDWAeZnLDQa6vr228zZO9Nvp1EEL3kDsCOKvk7WVLbX\nikPfKZznE/iA1tNLmkvioPiJ3oQB+2Bt6YA/tuCDcf+FtU43uTm5tiSBIdYQS+Om\nxN9OEXihk1svxHXQKa/a3nKPVLvdp3P90hDJ0PcRslXSy1V8az+A94JFEnCvnKsK\nnF2rItCcXkInL0lYHZKgLHQMXGWkNl8e3PA1GZk3yF6LPNtPI1T5Ek9GwkHNw4JZ\nBL/xEWLKB1qR2Z4I3UbWGVyi418kANv1eISb+49egQKBgQDraSRWB8nM5O3Zl9kT\n8S5K924o1oXrO17eqQlVtQVmtUdoVvIBc6uHQZOmV1eHYpr6c95h8apNLexI22AY\nSWkq9smpCnxLUsdkplwzie0F4bAzD6MCR8WIJxapUSPlyCA+8st1hquYBchKGQhd\n6mMY1gzMDacYV/WhtG4E5d0nMQKBgQDeTr793n00VtpKuquFJe6Stu7Ujf64dL0s\n3opLovyI0TmtMz5oCqIezwrjqc0Vy0UksWXaz0AboinDP+5n60cTEIt/6H0kryDc\ndxfSHEA9BBDoQtxOFi3QGcxXbwu0i9QSoexrKY7FhA2xPji6bCcPycthhIrCpUiZ\ns5gVkjHn2QKBgQCGklxLMbiSgGvXb46Qb9be1AMNJVT427+n2UmUzR6BUC+53boK\nSm1LrJkTBerrYdrmQUZnBxcrd40TORT9zTlpbhppn6zeAjwptVAPxlDQg+uNxOqS\nayToaC/0KoYy3OxSD8lvLcT56pRMh3LY/RwZHoPCQiu7Js0r21DpS93YgQKBgAuc\nc09RMprsOmSS0WiX7ZkOIvVJIVfDCSpxySlgLu56dxe7yHOosoUHbVsswEB2KHtd\nJKPEFWYcFzBSg4I8AK9XOuIIY5jp6L57Hexke1p0fumSrG0LrYLkBg8/Bo58iywZ\n9v414nYgipKKXG4oPfYOJShHwvOdrGgSwEvIIgEpAoGAZz0yC9+x+JaoTnyUIRyI\n+Aj5a4KhYjFtsZhcn/yCZHDqzJNDz6gAu579ey+J2CVOhjtgB5lowsDrHu32Hqnn\nSEfyTru/ynQ8obwaRzdDYml+On86YWOw+brpMXkN+KB6bs2okE2N68v0qGPakxjt\nOLDW6kKz5pI4T8lQJhdqjCU=\n-----END PRIVATE KEY-----`\n\n\treturn cert, certKey\n}\n"
  },
  {
    "path": "ddosify_engine/config_examples/assertion/expected_body.json",
    "content": "[\n    \"AED\",\n    \"ARS\",\n    \"AUD\",\n    \"BGN\",\n    \"BHD\",\n    \"BRL\",\n    \"CAD\",\n    \"CHF\",\n    \"CNY\",\n    \"DKK\",\n    \"DZD\",\n    \"EUR\",\n    \"FKP\",\n    \"INR\",\n    \"JEP\",\n    \"JPY\",\n    \"KES\",\n    \"KWD\",\n    \"KZT\",\n    \"MXN\",\n    \"NZD\",\n    \"RUB\",\n    \"SEK\",\n    \"SGD\",\n    \"TRY\",\n    \"USD\"\n]"
  },
  {
    "path": "ddosify_engine/config_examples/config.json",
    "content": "// This file contains full features of Ddosify as a reference. Don't use it directly.\n{\n    \"request_count\": 30, // This field will be deprecated, please use iteration_count instead.\n    \"iteration_count\": 30,\n    \"debug\" : false, // use this field for debugging, see verbose result\n    \"load_type\": \"linear\",\n    \"engine_mode\": \"distinct-user\", // could be one of  \"distinct-user\",\"repeated-user\", or default mode \"ddosify\"\n    \"duration\": 5,\n    \"manual_load\": [\n        {\"duration\": 5, \"count\": 5},\n        {\"duration\": 6, \"count\": 10},\n        {\"duration\": 7, \"count\": 20}\n    ],\n    \"env\" : {\n        \"HTTPBIN\" : \"https://httpbin.ddosify.com\",\n        \"LOCAL\" : \"http://localhost:8084\",\n        \"NAMES\" : [\"kenan\",\"fatih\",\"kursat\",\"semih\",\"sertac\"] ,\n        \"NUMBERS\" : [52,99,60,33],\n        \"BOOLS\" : [true,true,true,false],\n        \"randomIntPerIteration\": \"{{_randomInt}}\"\n    },\n    \"data\":{\n        \"info\": {\n            \"path\" : \"config/config_testdata/test.csv\",\n            \"delimiter\": \";\",\n            \"vars\": {\n                    \"0\":{\"tag\":\"name\"},\n                    \"1\":{\"tag\":\"city\"},\n                    \"2\":{\"tag\":\"team\"},\n                    \"3\":{\"tag\":\"payload\", \"type\":\"json\"},\n                    \"4\":{\"tag\":\"age\", \"type\":\"int\"}\n                  },\n            \"allow_quota\" : true,\n            \"order\": \"random\",\n            \"skip_first_line\" : true,\n            \"skip_empty_line\" : true\n        }\n    },\n    \"success_criterias\": [\n        {\n          \"rule\" : \"p90(iteration_duration) < 220\", \n          \"abort\" : false\n        },\n        {\n          \"rule\" : \"fail_count_perc < 0.1\", \n          \"abort\" : true,\n          \"delay\" : 1\n        },\n        {\n          \"rule\" : \"fail_count < 100\", \n          \"abort\" : true,\n          \"delay\" : 0\n        }\n    ],\n    \"proxy\": \"http://proxy_host.com:proxy_port\",\n    \"output\": \"stdout\",\n    \"steps\": [\n        {\n            \"id\": 1,\n            \"url\": \"https://getanteon.com/endpoint_1\",\n            \"method\": \"POST\",\n            \"headers\": {\n                \"Content-Type\": \"application/xml\",\n                \"header1\": \"header2\"\n            },\n            \"payload\": \"Body content 1\",\n            \"timeout\": 3,\n            \"sleep\": \"300-500\",\n            \"auth\": {\n                \"username\": \"test_user\",\n                \"password\": \"12345\"\n            },\n            \"others\": {\n                \"disable-compression\": false,\n                \"h2\": true,\n                \"disable-redirect\": true\n            },\n            \"capture_env\": {\n                \"NUM\" :{ \"from\":\"body\",\"json_path\":\"num\"}\n            },\n            \"assertion\":[\n                \"equals(status_code,200)\",\n                \"in(variables.num,[10,20])\"\n            ]\n        },\n        {\n            \"id\": 2,\n            \"url\": \"{{LOCAL}}\",\n            \"method\": \"GET\",\n            \"payload_file\": \"config_examples/payload.txt\",\n            \"timeout\": 2,\n            \"sleep\": \"1000\",\n            \"headers\":{\n                \"num\": \"{{NUM}}\",\n                \"randNum\": \"{{rand(NUMBERS)}}\",\n                \"randInt\" : \"{{randomIntPerIteration}}\"\n            },\n            \"assertion\":[\n                \"contains(body,\\\"xyz\\\")\",\n                \"range(headers.content-length,1000,10000)\"\n            ]\n        },\n        {\n            \"id\": 3,\n            \"url\": \"https://getanteon.com/endpoint_3\",\n            \"method\": \"POST\",\n            \"payload_multipart\": [\n                {\n                    \"name\": \"[field-name]\",\n                    \"value\": \"[field-value]\"\n                },\n                {\n                    \"name\": \"[field-name]\",\n                    \"value\": \"./test.png\",\n                    \"type\": \"file\"\n                },\n                {\n                    \"name\": \"[field-name]\",\n                    \"value\": \"http://test.com/test.png\",\n                    \"type\": \"file\",\n                    \"src\": \"remote\"\n                }\n            ],\n            \"timeout\": 2\n        }\n    ]\n}\n"
  },
  {
    "path": "ddosify_engine/config_examples/payload.txt",
    "content": "body file 1111111111\nbody file 22222222222"
  },
  {
    "path": "ddosify_engine/core/assertion/base.go",
    "content": "package assertion\n\nimport (\n\t\"go.ddosify.com/ddosify/core/types\"\n)\n\ntype Aborter interface {\n\tAbortChan() <-chan struct{}\n}\n\ntype ResultListener interface {\n\tStart(input <-chan *types.ScenarioResult)\n\tDoneChan() <-chan struct{} // indicates processing of results are done\n}\n\ntype Asserter interface {\n\tResultChan() <-chan TestAssertionResult\n}\n"
  },
  {
    "path": "ddosify_engine/core/assertion/service.go",
    "content": "package assertion\n\nimport (\n\t\"sort\"\n\t\"sync\"\n\t\"time\"\n\n\t\"go.ddosify.com/ddosify/core/scenario/scripting/assertion\"\n\t\"go.ddosify.com/ddosify/core/scenario/scripting/assertion/evaluator\"\n\t\"go.ddosify.com/ddosify/core/types\"\n\t\"golang.org/x/exp/slices\"\n)\n\nvar tickerInterval = 100 // interval in millisecond\n\ntype DefaultAssertionService struct {\n\tassertions map[string]types.TestAssertionOpt // Rule -> Opts\n\tabortChan  chan struct{}\n\tdoneChan   chan struct{}\n\tresChan    chan TestAssertionResult\n\tassertEnv  *evaluator.AssertEnv\n\tabortTick  map[string]int // rule -> tickIndex\n\titerCount  int\n\tmu         sync.Mutex\n}\n\ntype TestAssertionResult struct {\n\tFail        bool         `json:\"fail\"`\n\tAborted     bool         `json:\"aborted\"`\n\tFailedRules []FailedRule `json:\"failed_rules\"`\n}\n\ntype FailedRule struct {\n\tRule        string                 `json:\"rule\"`\n\tReceivedMap map[string]interface{} `json:\"received\"`\n}\n\nfunc NewDefaultAssertionService() (service *DefaultAssertionService) {\n\treturn &DefaultAssertionService{}\n}\n\nfunc (as *DefaultAssertionService) Init(assertions map[string]types.TestAssertionOpt) chan struct{} {\n\tas.assertions = assertions\n\tas.abortChan = make(chan struct{})\n\tas.doneChan = make(chan struct{})\n\tas.resChan = make(chan TestAssertionResult, 1)\n\ttotalTime := make([]int64, 0)\n\tas.assertEnv = &evaluator.AssertEnv{TotalTime: totalTime}\n\tas.abortTick = make(map[string]int)\n\tas.mu = sync.Mutex{}\n\treturn as.abortChan\n}\n\nfunc (as *DefaultAssertionService) GetTotalTimes() []int64 {\n\treturn as.assertEnv.TotalTime\n}\nfunc (as *DefaultAssertionService) GetFailCount() int {\n\treturn as.assertEnv.FailCount\n}\n\nfunc (as *DefaultAssertionService) Start(input <-chan *types.ScenarioResult) {\n\t// get iteration results, add store them cumulatively\n\tfirstResult := true\n\tfor r := range input {\n\t\tas.mu.Lock()\n\t\tas.aggregate(r)\n\t\tas.mu.Unlock()\n\n\t\t// after first result start checking assertions\n\t\tif firstResult {\n\t\t\tgo as.applyAssertions()\n\t\t\tfirstResult = false\n\t\t}\n\t}\n\tas.resChan <- as.giveFinalResult()\n\tas.doneChan <- struct{}{}\n}\n\nfunc (as *DefaultAssertionService) aggregate(r *types.ScenarioResult) {\n\tvar iterationTime int64\n\tvar iterFailed bool\n\tas.iterCount++\n\tfor _, sr := range r.StepResults {\n\t\titerationTime += sr.Duration.Milliseconds()\n\t\tif sr.Err.Type != \"\" || len(sr.FailedAssertions) > 0 {\n\t\t\titerFailed = true\n\t\t}\n\t}\n\tif iterFailed {\n\t\tas.assertEnv.FailCount++\n\t}\n\n\t// keep totalTime array sorted\n\tas.insertSorted(iterationTime)\n\n\tas.assertEnv.FailCountPerc = float64(as.assertEnv.FailCount) / float64(as.iterCount)\n}\n\nfunc (as *DefaultAssertionService) applyAssertions() {\n\tticker := time.NewTicker(time.Duration(tickerInterval) * time.Millisecond)\n\ttickIndex := 1\n\t// apply assertions on the fly for only abort:true ones\n\tassertionsWithAbort := make(map[string]types.TestAssertionOpt)\n\tfor rule, opts := range as.assertions {\n\t\tif opts.Abort {\n\t\t\tassertionsWithAbort[rule] = opts\n\t\t}\n\t}\n\tfor range ticker.C {\n\t\tas.mu.Lock()\n\t\tvar totalTime []int64\n\t\ttotalTime = append(totalTime, as.assertEnv.TotalTime...)\n\t\tassertEnv := evaluator.AssertEnv{\n\t\t\tTotalTime: totalTime,\n\t\t\tFailCount: as.assertEnv.FailCount,\n\t\t}\n\t\tas.mu.Unlock()\n\n\t\t// apply assertions\n\t\tfor rule, opts := range assertionsWithAbort {\n\t\t\tres, _ := assertion.Assert(rule, &assertEnv)\n\t\t\tif res == false && opts.Abort {\n\t\t\t\t// if delay is zero, immediately abort\n\t\t\t\tif opts.Delay == 0 || as.abortTick[rule] == tickIndex {\n\t\t\t\t\tas.abortChan <- struct{}{}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif _, ok := as.abortTick[rule]; !ok {\n\t\t\t\t\t// schedule check at\n\t\t\t\t\tdelayTick := (time.Duration(opts.Delay) * time.Second) / (time.Duration(tickerInterval) * time.Millisecond)\n\t\t\t\t\tas.abortTick[rule] = tickIndex + int(delayTick) - 1\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\ttickIndex++\n\t}\n}\n\nfunc (as *DefaultAssertionService) giveFinalResult() TestAssertionResult {\n\t// return final result\n\tresult := TestAssertionResult{\n\t\tFail: false,\n\t}\n\tfailedRules := []FailedRule{}\n\tfor rule, _ := range as.assertions {\n\t\tres, err := assertion.Assert(rule, as.assertEnv)\n\t\tif res == false {\n\t\t\tfailedRules = append(failedRules, FailedRule{\n\t\t\t\tRule:        rule,\n\t\t\t\tReceivedMap: err.(assertion.AssertionError).Received(),\n\t\t\t})\n\t\t}\n\t}\n\n\tif len(failedRules) > 0 {\n\t\tresult.Fail = true\n\t\tresult.FailedRules = failedRules\n\t}\n\n\treturn result\n}\n\nfunc (as *DefaultAssertionService) ResultChan() <-chan TestAssertionResult {\n\treturn as.resChan\n}\n\nfunc (as *DefaultAssertionService) AbortChan() <-chan struct{} {\n\treturn as.abortChan\n}\n\nfunc (as *DefaultAssertionService) DoneChan() <-chan struct{} {\n\treturn as.doneChan\n}\n\nfunc (as *DefaultAssertionService) insertSorted(v int64) {\n\tindex := sort.Search(len(as.assertEnv.TotalTime), func(i int) bool { return as.assertEnv.TotalTime[i] >= v })\n\tas.assertEnv.TotalTime = slices.Insert(as.assertEnv.TotalTime, index, v)\n}\n"
  },
  {
    "path": "ddosify_engine/core/assertion/service_test.go",
    "content": "package assertion\n\nimport (\n\t\"reflect\"\n\t\"sort\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"go.ddosify.com/ddosify/core/types\"\n)\n\nfunc TestApplyAssertionsAbortsCorrectly(t *testing.T) {\n\tservice := NewDefaultAssertionService()\n\tassertions := make(map[string]types.TestAssertionOpt)\n\trule := \"false\"\n\tdelay := 3\n\tassertions[rule] = types.TestAssertionOpt{\n\t\tAbort: true,\n\t\tDelay: delay,\n\t}\n\tabortChan := service.Init(assertions)\n\n\tinputChan := make(chan *types.ScenarioResult)\n\tgo service.Start(inputChan)\n\n\twg := sync.WaitGroup{}\n\twg.Add(1)\n\tgo func() {\n\t\t<-abortChan\n\t\twg.Done()\n\t}()\n\n\tinputChan <- &types.ScenarioResult{}\n\tstart := time.Now()\n\n\twg.Wait()\n\ttimePassed := time.Since(start).Seconds()\n\tif int(timePassed) != delay {\n\t\tt.Errorf(\"Delay, got %f, expected %d\", timePassed, delay)\n\t}\n}\n\nfunc TestServiceKeepsIterationTimes(t *testing.T) {\n\tservice := NewDefaultAssertionService()\n\tassertions := make(map[string]types.TestAssertionOpt)\n\trule := \"false\"\n\tdelay := 3\n\tassertions[rule] = types.TestAssertionOpt{\n\t\tAbort: false,\n\t\tDelay: delay,\n\t}\n\t_ = service.Init(assertions)\n\n\tinputChan := make(chan *types.ScenarioResult)\n\tgo service.Start(inputChan)\n\n\twg := sync.WaitGroup{}\n\twg.Add(1)\n\tgo func() {\n\t\t<-service.ResultChan()\n\t\twg.Done()\n\t}()\n\n\texpectedIterationTimes := SortableInt64Slice{}\n\tfor i := 0; i < 10; i++ {\n\t\titerTime := time.Duration(((i * 5) % 4) * int(time.Millisecond))\n\t\texpectedIterationTimes = append(expectedIterationTimes, iterTime.Milliseconds())\n\t\tinputChan <- &types.ScenarioResult{\n\t\t\tStepResults: []*types.ScenarioStepResult{\n\t\t\t\t{\n\t\t\t\t\tStepID:   1,\n\t\t\t\t\tDuration: iterTime,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t}\n\tsort.Sort(expectedIterationTimes)\n\tclose(inputChan)\n\n\twg.Wait()\n\n\titerationTimes := service.GetTotalTimes()\n\n\tif !reflect.DeepEqual(iterationTimes, []int64(expectedIterationTimes)) {\n\t\tt.Errorf(\"TestServiceKeepsIterationTimes, cumulative data store failed\")\n\t}\n}\n\nfunc TestServiceKeepsFailCount(t *testing.T) {\n\tservice := NewDefaultAssertionService()\n\tassertions := make(map[string]types.TestAssertionOpt)\n\t_ = service.Init(assertions)\n\n\tinputChan := make(chan *types.ScenarioResult)\n\tgo service.Start(inputChan)\n\n\twg := sync.WaitGroup{}\n\twg.Add(1)\n\tgo func() {\n\t\t<-service.ResultChan()\n\t\twg.Done()\n\t}()\n\n\tN := 10\n\t// 2*N times failed iteration result\n\tfor i := 0; i < N; i++ {\n\t\tinputChan <- &types.ScenarioResult{\n\t\t\tStepResults: []*types.ScenarioStepResult{\n\t\t\t\t{\n\t\t\t\t\tStepID: 1,\n\t\t\t\t\tFailedAssertions: []types.FailedAssertion{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tRule:     \"failed assertion expression\",\n\t\t\t\t\t\t\tReceived: map[string]interface{}{},\n\t\t\t\t\t\t\tReason:   \"\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tinputChan <- &types.ScenarioResult{\n\t\t\tStepResults: []*types.ScenarioStepResult{\n\t\t\t\t{\n\t\t\t\t\tStepID: 1,\n\t\t\t\t\tErr: types.RequestError{\n\t\t\t\t\t\tType:   \"server error type\",\n\t\t\t\t\t\tReason: \"\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t}\n\tclose(inputChan)\n\n\twg.Wait()\n\n\tfailCount := service.GetFailCount()\n\n\tif failCount != 2*N {\n\t\tt.Errorf(\"TestServiceKeepsFailCount, expected : %d, got : %d\", 2*N, failCount)\n\t}\n}\n\ntype SortableInt64Slice []int64\n\nfunc (a SortableInt64Slice) Len() int           { return len(a) }\nfunc (a SortableInt64Slice) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }\nfunc (a SortableInt64Slice) Less(i, j int) bool { return a[i] < a[j] }\n"
  },
  {
    "path": "ddosify_engine/core/engine.go",
    "content": "/*\n*\n*\tDdosify - Load testing tool for any web system.\n*   Copyright (C) 2021  Ddosify (https://ddosify.com)\n*\n*   This program is free software: you can redistribute it and/or modify\n*   it under the terms of the GNU Affero General Public License as published\n*   by the Free Software Foundation, either version 3 of the License, or\n*   (at your option) any later version.\n*\n*   This program is distributed in the hope that it will be useful,\n*   but WITHOUT ANY WARRANTY; without even the implied warranty of\n*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n*   GNU Affero General Public License for more details.\n*\n*   You should have received a copy of the GNU Affero General Public License\n*   along with this program.  If not, see <https://www.gnu.org/licenses/>.\n*\n */\n\npackage core\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"math\"\n\t\"net/http\"\n\t\"reflect\"\n\t\"sync\"\n\t\"time\"\n\n\t\"go.ddosify.com/ddosify/core/assertion\"\n\n\t\"go.ddosify.com/ddosify/core/proxy\"\n\t\"go.ddosify.com/ddosify/core/report\"\n\t\"go.ddosify.com/ddosify/core/scenario\"\n\t\"go.ddosify.com/ddosify/core/scenario/data\"\n\t\"go.ddosify.com/ddosify/core/types\"\n)\n\nconst (\n\t// interval in millisecond\n\ttickerInterval = 100\n\n\t// test result status\n\tresultDone    = \"done\"\n\tresultStopped = \"stopped\"\n\tresultAborted = \"aborted\"\n)\n\ntype engine struct {\n\thammer types.Hammer\n\n\tproxyService    proxy.ProxyService\n\treportService   report.ReportService\n\tscenarioService *scenario.ScenarioService\n\n\t// for assertion\n\taborter     assertion.Aborter\n\tasserter    assertion.Asserter\n\tresListener assertion.ResultListener\n\n\ttickCounter int\n\treqCountArr []int\n\twg          sync.WaitGroup\n\n\tresultReportChan chan *types.ScenarioResult\n\tresultAssertChan chan *types.ScenarioResult\n\n\tabortChan   <-chan struct{}\n\ttestSuccess bool\n\tctx         context.Context\n}\n\ntype EngineServices struct {\n\tAborter     assertion.Aborter\n\tAsserter    assertion.Asserter\n\tResListener assertion.ResultListener\n\n\tProxyServ  proxy.ProxyService\n\tReportServ report.ReportService\n}\n\nvar InitEngineServices = func(h types.Hammer) (*EngineServices, error) {\n\t// Initialize things here and pass interfaces to NewEngine which it depends ?\n\t// this piece can change between implementations\n\tas := assertion.NewDefaultAssertionService()\n\tas.Init(h.Assertions)\n\n\t// TODO: remove reflection ?\n\tps, err := proxy.NewProxyService(h.Proxy.Strategy)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = ps.Init(h.Proxy)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// TODO: remove reflection ?\n\trs, err := report.NewReportService(h.ReportDestination)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err = rs.Init(h.Debug, h.SamplingRate); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &EngineServices{\n\t\t// defaultAssertionService as implements all\n\t\tAborter:     as,\n\t\tAsserter:    as,\n\t\tResListener: as,\n\n\t\tProxyServ:  ps,\n\t\tReportServ: rs,\n\t}, nil\n}\n\n// NewEngine is the constructor of the engine.\n// Hammer is used for initializing the engine itself and its' external services.\n// Engine can be stopped by canceling the given ctx.\nfunc NewEngine(ctx context.Context, h types.Hammer,\n\tservices *EngineServices) (e *engine, err error) {\n\tss := scenario.NewScenarioService()\n\n\te = &engine{\n\t\thammer:          h,\n\t\tctx:             ctx,\n\t\tproxyService:    services.ProxyServ,\n\t\tscenarioService: ss,\n\t\treportService:   services.ReportServ,\n\n\t\t// for assertion\n\t\taborter:     services.Aborter,\n\t\tresListener: services.ResListener,\n\t\tasserter:    services.Asserter,\n\t}\n\n\treturn\n}\n\nfunc (e *engine) IsTestFailed() bool {\n\treturn !e.testSuccess\n}\n\nfunc (e *engine) Init() (err error) {\n\t// read test data\n\treadData, err := readTestData(e.hammer.TestDataConf)\n\tif err != nil {\n\t\treturn err\n\t}\n\te.hammer.Scenario.Data = readData\n\n\te.initReqCountArr()\n\n\tvar initialCookies []*http.Cookie\n\tif e.hammer.CookiesEnabled && len(e.hammer.Cookies) > 0 {\n\t\tinitialCookies, err = createInitialCookies(e.hammer.Cookies)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif err = e.scenarioService.Init(e.ctx, e.hammer.Scenario, e.proxyService.GetAll(), scenario.ScenarioOpts{\n\t\tDebug:                  e.hammer.Debug,\n\t\tIterationCount:         e.hammer.IterationCount,\n\t\tMaxConcurrentIterCount: e.getMaxConcurrentIterCount(),\n\t\tEngineMode:             e.hammer.EngineMode,\n\t\tInitialCookies:         initialCookies,\n\t}); err != nil {\n\t\treturn\n\t}\n\n\te.abortChan = e.aborter.AbortChan()\n\n\treturn\n}\n\nfunc (e *engine) Start() string {\n\tticker := time.NewTicker(time.Duration(tickerInterval) * time.Millisecond)\n\te.resultReportChan = make(chan *types.ScenarioResult, e.hammer.IterationCount)\n\te.resultAssertChan = make(chan *types.ScenarioResult, e.hammer.IterationCount)\n\n\tvar testResultChan <-chan assertion.TestAssertionResult\n\tif e.runAssertionsInEngine() {\n\t\t// run test wide assertions in parallel\n\t\ttestResultChan = e.asserter.ResultChan()\n\t}\n\n\tif len(e.hammer.Assertions) > 0 { // test-wide assertions given\n\t\tgo e.resListener.Start(e.resultAssertChan)\n\t}\n\n\tgo e.reportService.Start(e.resultReportChan, testResultChan)\n\n\tdefer func() {\n\t\tticker.Stop()\n\t\te.stop()\n\t}()\n\n\te.tickCounter = 0\n\te.wg = sync.WaitGroup{}\n\tvar mutex = &sync.Mutex{}\n\tfor range ticker.C {\n\t\tif e.tickCounter >= len(e.reqCountArr) {\n\t\t\treturn resultDone\n\t\t}\n\n\t\tselect {\n\t\tcase <-e.ctx.Done():\n\t\t\treturn resultStopped\n\t\tcase <-e.abortChan:\n\t\t\te.testSuccess = false\n\t\t\treturn resultAborted\n\t\tdefault:\n\t\t\tmutex.Lock()\n\t\t\te.wg.Add(e.reqCountArr[e.tickCounter])\n\t\t\tgo e.runWorkers(e.tickCounter)\n\t\t\te.tickCounter++\n\t\t\tmutex.Unlock()\n\t\t}\n\t}\n\treturn resultDone\n}\n\nfunc (e *engine) runWorkers(c int) {\n\tfor i := 1; i <= e.reqCountArr[c]; i++ {\n\t\tscenarioStartTime := time.Now()\n\t\tgo func(t time.Time) {\n\t\t\te.runWorker(t)\n\t\t\te.wg.Done()\n\t\t}(scenarioStartTime)\n\t}\n}\n\nfunc (e *engine) runWorker(scenarioStartTime time.Time) {\n\tvar res *types.ScenarioResult\n\tvar err *types.RequestError\n\n\tp := e.proxyService.GetProxy()\n\tretryCount := 3\n\tfor i := 1; i <= retryCount; i++ {\n\t\tres, err = e.scenarioService.Do(p, scenarioStartTime)\n\n\t\tif err != nil && err.Type == types.ErrorProxy {\n\t\t\tp = e.proxyService.ReportProxy(p, err.Reason)\n\t\t\tcontinue\n\t\t}\n\n\t\tif err != nil && err.Type == types.ErrorIntented {\n\t\t\t// Don't report intentionally created errors. Like canceled requests.\n\t\t\treturn\n\t\t}\n\t\tbreak\n\t}\n\n\tres.Others = make(map[string]interface{})\n\tres.Others[\"hammerOthers\"] = e.hammer.Others\n\tres.Others[\"proxyCountry\"] = e.proxyService.GetProxyCountry(p)\n\te.resultReportChan <- res\n\n\tif len(e.hammer.Assertions) > 0 {\n\t\te.resultAssertChan <- res\n\t}\n}\n\nfunc (e *engine) runAssertionsInEngine() bool {\n\treturn e.hammer.SingleMode && len(e.hammer.Assertions) > 0\n}\n\nfunc (e *engine) stop() {\n\te.wg.Wait()\n\tclose(e.resultReportChan)\n\tclose(e.resultAssertChan)\n\te.proxyService.Done()\n\te.scenarioService.Done()\n\n\tif len(e.hammer.Assertions) > 0 { // if results are listened, wait\n\t\t<-e.resListener.DoneChan()\n\t}\n\n\te.testSuccess = <-e.reportService.DoneChan()\n\n}\n\nfunc (e *engine) getMaxConcurrentIterCount() int {\n\tmax := 0\n\tfor _, v := range e.reqCountArr {\n\t\tif v > max {\n\t\t\tmax = v\n\t\t}\n\t}\n\treturn max\n}\n\nfunc (e *engine) initReqCountArr() {\n\tif e.hammer.Debug {\n\t\te.reqCountArr = []int{1}\n\t\treturn\n\t}\n\tlength := int(e.hammer.TestDuration * int(time.Second/(tickerInterval*time.Millisecond)))\n\te.reqCountArr = make([]int, length)\n\n\tif e.hammer.TimeRunCountMap != nil {\n\t\te.createManualReqCountArr()\n\t} else {\n\t\tswitch e.hammer.LoadType {\n\t\tcase types.LoadTypeLinear:\n\t\t\te.createLinearReqCountArr()\n\t\tcase types.LoadTypeIncremental:\n\t\t\te.createIncrementalReqCountArr()\n\t\tcase types.LoadTypeWaved:\n\t\t\te.createWavedReqCountArr()\n\t\t}\n\t}\n}\n\nfunc (e *engine) createManualReqCountArr() {\n\ttickPerSecond := int(time.Second / (tickerInterval * time.Millisecond))\n\tstepStartIndex := 0\n\tfor _, t := range e.hammer.TimeRunCountMap {\n\t\tsteps := make([]int, t.Duration)\n\t\tcreateLinearDistArr(t.Count, steps)\n\n\t\tfor i := range steps {\n\t\t\ttickArrStartIndex := (i * tickPerSecond) + stepStartIndex\n\t\t\ttickArrEndIndex := tickArrStartIndex + tickPerSecond\n\t\t\tsegment := e.reqCountArr[tickArrStartIndex:tickArrEndIndex]\n\t\t\tcreateLinearDistArr(steps[i], segment)\n\t\t}\n\t\tstepStartIndex += len(steps) * tickPerSecond\n\t}\n}\n\nfunc (e *engine) createLinearReqCountArr() {\n\tsteps := make([]int, e.hammer.TestDuration)\n\tcreateLinearDistArr(e.hammer.IterationCount, steps)\n\ttickPerSecond := int(time.Second / (tickerInterval * time.Millisecond))\n\tfor i := range steps {\n\t\ttickArrStartIndex := i * tickPerSecond\n\t\ttickArrEndIndex := tickArrStartIndex + tickPerSecond\n\t\tsegment := e.reqCountArr[tickArrStartIndex:tickArrEndIndex]\n\t\tcreateLinearDistArr(steps[i], segment)\n\t}\n}\n\nfunc (e *engine) createIncrementalReqCountArr() {\n\tsteps := createIncrementalDistArr(e.hammer.IterationCount, e.hammer.TestDuration)\n\ttickPerSecond := int(time.Second / (tickerInterval * time.Millisecond))\n\tfor i := range steps {\n\t\ttickArrStartIndex := i * tickPerSecond\n\t\ttickArrEndIndex := tickArrStartIndex + tickPerSecond\n\t\tsegment := e.reqCountArr[tickArrStartIndex:tickArrEndIndex]\n\t\tcreateLinearDistArr(steps[i], segment)\n\t}\n}\n\nfunc (e *engine) createWavedReqCountArr() {\n\ttickPerSecond := int(time.Second / (tickerInterval * time.Millisecond))\n\tquarterWaveCount := int((math.Log2(float64(e.hammer.TestDuration))))\n\tif quarterWaveCount == 0 {\n\t\tquarterWaveCount = 1\n\t}\n\tqWaveDuration := int(e.hammer.TestDuration / quarterWaveCount)\n\treqCountPerQWave := int(e.hammer.IterationCount / quarterWaveCount)\n\ttickArrStartIndex := 0\n\n\tfor i := 0; i < quarterWaveCount; i++ {\n\t\tif i == quarterWaveCount-1 {\n\t\t\t// Add remaining req count to the last wave\n\t\t\treqCountPerQWave += e.hammer.IterationCount - (reqCountPerQWave * quarterWaveCount)\n\t\t}\n\n\t\tsteps := createIncrementalDistArr(reqCountPerQWave, qWaveDuration)\n\t\tif i%2 == 1 {\n\t\t\treverse(steps)\n\t\t}\n\n\t\tfor j := range steps {\n\t\t\ttickArrEndIndex := tickArrStartIndex + tickPerSecond\n\t\t\tsegment := e.reqCountArr[tickArrStartIndex:tickArrEndIndex]\n\t\t\tcreateLinearDistArr(steps[j], segment)\n\t\t\ttickArrStartIndex += tickPerSecond\n\t\t}\n\t}\n}\n\nfunc createLinearDistArr(count int, arr []int) {\n\tarrLen := len(arr)\n\tminReqCount := int(count / arrLen)\n\tremaining := count - minReqCount*arrLen\n\tfor i := range arr {\n\t\tplusOne := 0\n\t\tif i < remaining {\n\t\t\tplusOne = 1\n\t\t}\n\t\treqCount := minReqCount + plusOne\n\t\tarr[i] = reqCount\n\t}\n}\n\nfunc createIncrementalDistArr(count int, len int) []int {\n\tsteps := make([]int, len)\n\tsum := (len * (len + 1)) / 2\n\tincrementStep := int(math.Ceil(float64(sum) / float64(count)))\n\tval := 0\n\tfor i := range steps {\n\t\tif i > 0 {\n\t\t\tval = steps[i-1]\n\t\t}\n\n\t\tif i%incrementStep == 0 {\n\t\t\tsteps[i] = val + 1\n\t\t} else {\n\t\t\tsteps[i] = val\n\t\t}\n\t}\n\n\tsum = arraySum(steps)\n\n\tfactor := count / sum\n\tremaining := count - (sum * factor)\n\tplus := remaining / len\n\tlastRemaining := remaining - (plus * len)\n\tfor i := range steps {\n\t\tsteps[i] = steps[i]*factor + plus\n\t\tif len-i-1 < lastRemaining {\n\t\t\tsteps[i]++\n\t\t}\n\t}\n\treturn steps\n}\n\nfunc arraySum(steps []int) int {\n\tsum := 0\n\tfor i := range steps {\n\t\tsum += steps[i]\n\t}\n\treturn sum\n}\n\nfunc reverse(s interface{}) {\n\tn := reflect.ValueOf(s).Len()\n\tswap := reflect.Swapper(s)\n\tfor i, j := 0, n-1; i < j; i, j = i+1, j-1 {\n\t\tswap(i, j)\n\t}\n}\n\nvar readTestData = func(testDataConf map[string]types.CsvConf) (map[string]types.CsvData, error) {\n\t// Read Data\n\tvar readData map[string]types.CsvData\n\tif len(testDataConf) > 0 {\n\t\treadData = make(map[string]types.CsvData, len(testDataConf))\n\t}\n\tfor k, conf := range testDataConf {\n\t\tvar rows []map[string]interface{}\n\t\tvar err error\n\t\trows, err = data.ReadCsv(conf)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tvar csvData types.CsvData\n\t\tcsvData.Rows = rows\n\n\t\tif conf.Order == \"random\" {\n\t\t\tcsvData.Random = true\n\t\t}\n\t\treadData[k] = csvData\n\t}\n\n\treturn readData, nil\n}\n\nfunc parseRawCookie(cookie string) []*http.Cookie {\n\theader := http.Header{}\n\theader.Add(\"Set-Cookie\", cookie)\n\treq := http.Response{Header: header}\n\treturn req.Cookies()\n}\n\nvar createInitialCookies = func(cookies []types.CustomCookie) ([]*http.Cookie, error) {\n\tinitialCookies := make([]*http.Cookie, 0, len(cookies))\n\tfor _, c := range cookies {\n\t\tvar ck *http.Cookie\n\t\tif c.Raw != \"\" {\n\t\t\tcookies := parseRawCookie(c.Raw)\n\t\t\tif len(cookies) == 0 {\n\t\t\t\treturn nil, fmt.Errorf(\"cookie could not be parsed, got : %s\", c.Raw)\n\t\t\t}\n\t\t\tck = cookies[0]\n\t\t} else {\n\t\t\tvar expires time.Time\n\t\t\tif c.Expires != \"\" {\n\t\t\t\tvar err error\n\t\t\t\texpires, err = time.Parse(time.RFC1123, c.Expires)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\"error parsing cookie expiry: %s\", err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tck = &http.Cookie{\n\t\t\t\tName:       c.Name,\n\t\t\t\tValue:      c.Value,\n\t\t\t\tPath:       c.Path,\n\t\t\t\tDomain:     c.Domain,\n\t\t\t\tExpires:    expires,\n\t\t\t\tRawExpires: c.Expires,\n\t\t\t\tMaxAge:     c.MaxAge,\n\t\t\t\tSecure:     c.Secure,\n\t\t\t\tHttpOnly:   c.HttpOnly,\n\t\t\t\tRaw:        c.Raw,\n\n\t\t\t\t// below fields not used\n\t\t\t\tSameSite: 0,\n\t\t\t\tUnparsed: []string{},\n\t\t\t}\n\t\t}\n\n\t\tinitialCookies = append(initialCookies, ck)\n\t}\n\n\treturn initialCookies, nil\n}\n"
  },
  {
    "path": "ddosify_engine/core/engine_test.go",
    "content": "/*\n*\n*\tDdosify - Load testing tool for any web system.\n*   Copyright (C) 2021  Ddosify (https://ddosify.com)\n*\n*   This program is free software: you can redistribute it and/or modify\n*   it under the terms of the GNU Affero General Public License as published\n*   by the Free Software Foundation, either version 3 of the License, or\n*   (at your option) any later version.\n*\n*   This program is distributed in the hope that it will be useful,\n*   but WITHOUT ANY WARRANTY; without even the implied warranty of\n*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n*   GNU Affero General Public License for more details.\n*\n*   You should have received a copy of the GNU Affero General Public License\n*   along with this program.  If not, see <https://www.gnu.org/licenses/>.\n*\n */\n\npackage core\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/ddosify/go-faker/faker\"\n\t\"go.ddosify.com/ddosify/config\"\n\t\"go.ddosify.com/ddosify/core/proxy\"\n\t\"go.ddosify.com/ddosify/core/report\"\n\t\"go.ddosify.com/ddosify/core/types\"\n)\n\n//TODO: Engine stop channel close order test\n\nfunc newDummyHammer() types.Hammer {\n\treturn types.Hammer{\n\t\tProxy:             proxy.Proxy{Strategy: proxy.ProxyTypeSingle},\n\t\tReportDestination: report.OutputTypeStdout,\n\t\tLoadType:          types.LoadTypeLinear,\n\t\tTestDuration:      1,\n\t\tIterationCount:    1,\n\t\tScenario: types.Scenario{\n\t\t\tSteps: []types.ScenarioStep{\n\t\t\t\t{\n\t\t\t\t\tID:     1,\n\t\t\t\t\tMethod: \"GET\",\n\t\t\t\t\tURL:    \"http://127.0.0.1\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tSingleMode: true,\n\t}\n}\n\nfunc TestCreateEngine(t *testing.T) {\n\tt.Parallel()\n\n\thInvalidProxy := newDummyHammer()\n\thInvalidProxy.Proxy = proxy.Proxy{Strategy: \"invalidProxy\"}\n\n\thInvalidReport := newDummyHammer()\n\thInvalidReport.ReportDestination = \"invalidReport\"\n\n\ttests := []struct {\n\t\tname      string\n\t\thammer    types.Hammer\n\t\tshouldErr bool\n\t}{\n\t\t{\"Normal\", newDummyHammer(), false},\n\t\t{\"InvalidProxy\", hInvalidProxy, true},\n\t\t{\"InvalidReport\", hInvalidReport, true},\n\t}\n\n\tfor _, tc := range tests {\n\t\ttest := tc\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\tes, err := InitEngineServices(test.hammer)\n\n\t\t\t// e, err := NewEngine(context.TODO(), test.hammer, es)\n\n\t\t\tif test.shouldErr {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Errorf(\"Should be errored\")\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"Error occurred %v\", err)\n\t\t\t\t}\n\n\t\t\t\tif es.ProxyServ == nil {\n\t\t\t\t\tt.Errorf(\"Proxy Service should be created\")\n\t\t\t\t}\n\n\t\t\t\t// TODOr: not an interface ?\n\t\t\t\t// if es.scenarioService == nil {\n\t\t\t\t// \tt.Errorf(\"Scenario Service should be created\")\n\t\t\t\t// }\n\n\t\t\t\tif es.ReportServ == nil {\n\t\t\t\t\tt.Errorf(\"Report Service should be created\")\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestReqCountArrDebugMode(t *testing.T) {\n\tt.Parallel()\n\n\thammer := newDummyHammer()\n\thammer.Debug = true\n\ttests := []struct {\n\t\tname   string\n\t\thammer types.Hammer\n\t}{\n\t\t{\"DebugMode\", hammer},\n\t}\n\n\tfor _, tc := range tests {\n\t\ttest := tc\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tes, err := InitEngineServices(test.hammer)\n\t\t\te, err := NewEngine(context.TODO(), test.hammer, es)\n\t\t\te.Init()\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Should have been nil, got %v\", err)\n\t\t\t}\n\n\t\t\t// one iteration one tick\n\t\t\tif !reflect.DeepEqual(e.reqCountArr, []int{1}) {\n\t\t\t\tt.Errorf(\"Debug mode reqCountArr should have only one iteration in one tick, got %v\", e.reqCountArr)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TODO: Add other load types as you implement\nfunc TestRequestCount(t *testing.T) {\n\tt.Parallel()\n\n\ttests := []struct {\n\t\tname           string\n\t\tloadType       string\n\t\tduration       int\n\t\treqCount       int\n\t\ttimeRunCount   types.TimeRunCount\n\t\texpectedReqArr []int\n\t\tdelta          int\n\t}{\n\t\t{\"Linear1\", types.LoadTypeLinear, 1, 100, nil, []int{10, 10, 10, 10, 10, 10, 10, 10, 10, 10}, 1},\n\t\t{\"Linear2\", types.LoadTypeLinear, 1, 5, nil, []int{1, 1, 1, 1, 1, 0, 0, 0, 0, 0}, 0},\n\t\t{\"Linear3\", types.LoadTypeLinear, 2, 4, nil,\n\t\t\t[]int{1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0}, 0},\n\t\t{\"Linear4\", types.LoadTypeLinear, 2, 23, nil,\n\t\t\t[]int{2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1}, 0},\n\t\t{\"Incremental1\", types.LoadTypeIncremental, 1, 5, nil,\n\t\t\t[]int{1, 1, 1, 1, 1, 0, 0, 0, 0, 0}, 2},\n\t\t{\"Incremental2\", types.LoadTypeIncremental, 3, 1022, nil,\n\t\t\t[]int{17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 35, 34, 34, 34,\n\t\t\t\t34, 34, 34, 34, 34, 34, 52, 51, 51, 51, 51, 51, 51, 51, 51, 51}, 2},\n\t\t{\"Incremental3\", types.LoadTypeIncremental, 5, 10, nil,\n\t\t\t[]int{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,\n\t\t\t\t0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0}, 0},\n\t\t{\"Incremental4\", types.LoadTypeIncremental, 4, 10, nil,\n\t\t\t[]int{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,\n\t\t\t\t0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0}, 0},\n\t\t{\"Waved1\", types.LoadTypeWaved, 1, 5, nil,\n\t\t\t[]int{1, 1, 1, 1, 1, 0, 0, 0, 0, 0}, 0},\n\t\t{\"Waved2\", types.LoadTypeWaved, 4, 32, nil,\n\t\t\t[]int{1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1,\n\t\t\t\t1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0}, 0},\n\t\t{\"Waved3\", types.LoadTypeWaved, 5, 10, nil,\n\t\t\t[]int{1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,\n\t\t\t\t0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 0},\n\t\t{\"Waved4\", types.LoadTypeWaved, 9, 1000, nil,\n\t\t\t[]int{6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 17, 17, 17, 17,\n\t\t\t\t17, 17, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 16, 16, 16, 16, 12, 11, 11, 11, 11, 11,\n\t\t\t\t11, 11, 11, 11, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 12, 11, 11,\n\t\t\t\t11, 11, 11, 11, 11, 11, 11, 17, 17, 17, 17, 17, 17, 17, 16, 16, 16}, 1},\n\t\t{\"TimeRunCount1\", \"\", 1, 100, types.TimeRunCount{{Duration: 1, Count: 100}},\n\t\t\t[]int{10, 10, 10, 10, 10, 10, 10, 10, 10, 10}, 1},\n\t\t{\"TimeRunCount2\", \"\", 1, 5, types.TimeRunCount{{Duration: 1, Count: 5}},\n\t\t\t[]int{1, 1, 1, 1, 1, 0, 0, 0, 0, 0}, 0},\n\t\t{\"TimeRunCount3\", \"\", 6, 55,\n\t\t\ttypes.TimeRunCount{{Duration: 1, Count: 20}, {Duration: 2, Count: 30}, {Duration: 3, Count: 5}},\n\t\t\t[]int{2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1,\n\t\t\t\t1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 0},\n\t\t{\"TimeRunCount4\", \"\", 5, 40,\n\t\t\ttypes.TimeRunCount{{Duration: 1, Count: 20}, {Duration: 2, Count: 0}, {Duration: 2, Count: 20}},\n\t\t\t[]int{2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t\t\t\t0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, 0},\n\t}\n\n\tfor _, tc := range tests {\n\t\ttest := tc\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\tvar timeReqMap map[int]int\n\t\t\tvar now time.Time\n\t\t\tvar m sync.Mutex\n\n\t\t\t// Test server\n\t\t\thandler := func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tm.Lock()\n\t\t\t\ti := time.Since(now).Milliseconds()/tickerInterval - 1\n\t\t\t\ttimeReqMap[int(i)]++\n\t\t\t\tm.Unlock()\n\t\t\t}\n\t\t\tserver := httptest.NewServer(http.HandlerFunc(handler))\n\t\t\tdefer server.Close()\n\n\t\t\t// Prepare\n\t\t\th := newDummyHammer()\n\t\t\th.LoadType = test.loadType\n\t\t\th.TestDuration = test.duration\n\t\t\th.TimeRunCountMap = test.timeRunCount\n\t\t\th.IterationCount = test.reqCount\n\t\t\th.Scenario.Steps[0].URL = server.URL\n\n\t\t\tnow = time.Now()\n\t\t\ttimeReqMap = make(map[int]int, 0)\n\t\t\tes, err := InitEngineServices(h)\n\t\t\te, err := NewEngine(context.TODO(), h, es)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"TestRequestCount error occurred %v\", err)\n\t\t\t}\n\n\t\t\t// Act\n\t\t\terr = e.Init()\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"TestRequestCount error occurred %v\", err)\n\t\t\t}\n\n\t\t\te.Start()\n\n\t\t\tm.Lock()\n\t\t\t// Assert create reqCountArr\n\t\t\tif !reflect.DeepEqual(e.reqCountArr, test.expectedReqArr) {\n\t\t\t\tt.Errorf(\"Expected: %v, Found: %v\", test.expectedReqArr, e.reqCountArr)\n\t\t\t}\n\n\t\t\t// Assert sent request count\n\t\t\tif testing.Short() {\n\t\t\t\t// Poor machine's test case assertions are special since they can't run the test fast.\n\t\t\t\ttotalRecieved := 0\n\t\t\t\tfor _, v := range timeReqMap {\n\t\t\t\t\ttotalRecieved += v\n\t\t\t\t}\n\t\t\t\texpected := arraySum(test.expectedReqArr)\n\t\t\t\tif totalRecieved != expected {\n\t\t\t\t\tt.Errorf(\"Poor Machine Expected: %v, Received: %v\", totalRecieved, expected)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor i, v := range test.expectedReqArr {\n\t\t\t\t\tif timeReqMap[i] > v+test.delta || timeReqMap[i] < v-test.delta {\n\t\t\t\t\t\tt.Errorf(\"Expected: %v, Received: %v, Tick: %v\", v, timeReqMap[i], i)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tm.Unlock()\n\t\t})\n\t}\n}\n\nfunc TestRequestData(t *testing.T) {\n\tt.Parallel()\n\n\tvar uri, header1, header2, body, protocol, method string\n\n\t// Test server\n\thandler := func(w http.ResponseWriter, r *http.Request) {\n\t\tprotocol = r.Proto\n\t\tmethod = r.Method\n\t\turi = r.RequestURI\n\t\theader1 = r.Header.Get(\"Test1\")\n\t\theader2 = r.Header.Get(\"Test2\")\n\n\t\tbodyByte, _ := ioutil.ReadAll(r.Body)\n\t\tbody = string(bodyByte)\n\t}\n\tserver := httptest.NewServer(http.HandlerFunc(handler))\n\tdefer server.Close()\n\n\t// Prepare\n\th := newDummyHammer()\n\th.Scenario.Steps[0] = types.ScenarioStep{\n\t\tID:      1,\n\t\tMethod:  \"GET\",\n\t\tURL:     server.URL + \"/get_test_data\",\n\t\tHeaders: map[string]string{\"Test1\": \"Test1Value\", \"Test2\": \"Test2Value\"},\n\t\tPayload: \"Body content\",\n\t}\n\n\t// Act\n\tes, err := InitEngineServices(h)\n\te, err := NewEngine(context.TODO(), h, es)\n\tif err != nil {\n\t\tt.Errorf(\"TestRequestData error occurred %v\", err)\n\t}\n\n\terr = e.Init()\n\tif err != nil {\n\t\tt.Errorf(\"TestRequestData error occurred %v\", err)\n\t}\n\n\te.Start()\n\n\t// Assert\n\tif uri != \"/get_test_data\" {\n\t\tt.Errorf(\"invalid uri received: %s\", uri)\n\t}\n\n\tif protocol != \"HTTP/1.1\" {\n\t\tt.Errorf(\"invalid protocol received: %v\", protocol)\n\t}\n\n\tif method != \"GET\" {\n\t\tt.Errorf(\"invalid method received: %v\", method)\n\t}\n\n\tif header1 != \"Test1Value\" {\n\t\tt.Errorf(\"invalid header1 received: %s\", header1)\n\t}\n\n\tif header2 != \"Test2Value\" {\n\t\tt.Errorf(\"invalid header2 received: %s\", header2)\n\t}\n\n\tif body != \"Body content\" {\n\t\tt.Errorf(\"invalid body received: %v\", body)\n\t}\n}\n\nfunc TestRequestDataForMultiScenarioStep(t *testing.T) {\n\tt.Parallel()\n\n\tvar uri, header, body, protocol, method []string\n\n\tvar m sync.Mutex\n\n\t// Test server\n\thandler := func(w http.ResponseWriter, r *http.Request) {\n\t\tm.Lock()\n\t\tprotocol = append(protocol, r.Proto)\n\t\tmethod = append(method, r.Method)\n\t\turi = append(uri, r.RequestURI)\n\t\theader = append(header, r.Header.Get(\"Test\"))\n\n\t\tbodyByte, _ := ioutil.ReadAll(r.Body)\n\t\tbody = append(body, string(bodyByte))\n\t\tm.Unlock()\n\t}\n\tserver := httptest.NewServer(http.HandlerFunc(handler))\n\tdefer server.Close()\n\n\t// Prepare\n\th := newDummyHammer()\n\th.Scenario = types.Scenario{\n\t\tSteps: []types.ScenarioStep{\n\t\t\t{\n\t\t\t\tID:      1,\n\t\t\t\tMethod:  \"GET\",\n\t\t\t\tURL:     server.URL + \"/api_get\",\n\t\t\t\tHeaders: map[string]string{\"Test\": \"h1\"},\n\t\t\t\tPayload: \"Body 1\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:      2,\n\t\t\t\tMethod:  \"POST\",\n\t\t\t\tURL:     server.URL + \"/api_post\",\n\t\t\t\tHeaders: map[string]string{\"Test\": \"h2\"},\n\t\t\t\tPayload: \"Body 2\",\n\t\t\t},\n\t\t}}\n\n\t// Act\n\tes, err := InitEngineServices(h)\n\te, err := NewEngine(context.TODO(), h, es)\n\tif err != nil {\n\t\tt.Errorf(\"TestRequestDataForMultiScenarioStep error occurred %v\", err)\n\t}\n\n\terr = e.Init()\n\tif err != nil {\n\t\tt.Errorf(\"TestRequestDataForMultiScenarioStep error occurred %v\", err)\n\t}\n\n\te.Start()\n\n\t// Assert\n\texpected := []string{\"/api_get\", \"/api_post\"}\n\tif !reflect.DeepEqual(uri, expected) {\n\t\tt.Logf(\"%#v - %#v\", uri, expected)\n\t\tt.Errorf(\"invalid uri received: %#v expected %#v\", uri, expected)\n\t}\n\n\texpected = []string{\"HTTP/1.1\", \"HTTP/1.1\"}\n\tif !reflect.DeepEqual(protocol, expected) {\n\t\tt.Errorf(\"invalid protocol received: %#v expected %#v\", protocol, expected)\n\t}\n\n\texpected = []string{\"GET\", \"POST\"}\n\tif !reflect.DeepEqual(method, expected) {\n\t\tt.Errorf(\"invalid method received: %#v expected %#v\", method, expected)\n\t}\n\n\texpected = []string{\"h1\", \"h2\"}\n\tif !reflect.DeepEqual(header, expected) {\n\t\tt.Errorf(\"invalid header received: %#v expected %#v\", header, expected)\n\t}\n\n\texpected = []string{\"Body 1\", \"Body 2\"}\n\tif !reflect.DeepEqual(body, expected) {\n\t\tt.Errorf(\"invalid body received: %#v expected %#v\", body, expected)\n\t}\n}\n\nfunc TestRequestTimeout(t *testing.T) {\n\tt.Parallel()\n\n\t// Prepare\n\ttests := []struct {\n\t\tname     string\n\t\ttimeout  int\n\t\texpected bool\n\t}{\n\t\t{\"Timeout\", 1, false},\n\t\t{\"NotTimeout\", 3, true},\n\t}\n\n\t// Act\n\tfor _, tc := range tests {\n\t\ttest := tc\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tresult := false\n\t\t\tvar m sync.Mutex\n\n\t\t\t// Test server\n\t\t\thandler := func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\ttime.Sleep(time.Duration(2) * time.Second)\n\n\t\t\t\tm.Lock()\n\t\t\t\tresult = true\n\t\t\t\tm.Unlock()\n\t\t\t}\n\t\t\tserver := httptest.NewServer(http.HandlerFunc(handler))\n\t\t\tdefer server.Close()\n\n\t\t\th := newDummyHammer()\n\t\t\th.Scenario.Steps[0].Timeout = test.timeout\n\t\t\th.Scenario.Steps[0].URL = server.URL\n\t\t\tes, err := InitEngineServices(h)\n\t\t\te, err := NewEngine(context.TODO(), h, es)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"TestRequestTimeout error occurred %v\", err)\n\t\t\t}\n\n\t\t\terr = e.Init()\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"TestRequestTimeout error occurred %v\", err)\n\t\t\t}\n\n\t\t\te.Start()\n\n\t\t\t// Assert\n\t\t\tm.Lock()\n\t\t\tif result != test.expected {\n\t\t\t\tt.Errorf(\"Expected %v, Found :%v\", test.expected, result)\n\t\t\t}\n\t\t\tm.Unlock()\n\t\t})\n\t}\n}\n\nfunc TestEngineResult(t *testing.T) {\n\tt.Parallel()\n\n\t// Prepare\n\ttests := []struct {\n\t\tname           string\n\t\tcancelCtx      bool\n\t\texpectedStatus string\n\t\ttestFailed     bool\n\t}{\n\t\t{\"CtxCancel\", true, \"stopped\", false},\n\t\t{\"Normal\", false, \"done\", false},\n\t\t{\"Abort\", false, \"aborted\", true},\n\t}\n\n\t// Act\n\tfor _, tc := range tests {\n\t\ttest := tc\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tvar m sync.Mutex\n\n\t\t\t// Test server\n\t\t\thandler := func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tserver := httptest.NewServer(http.HandlerFunc(handler))\n\t\t\tdefer server.Close()\n\n\t\t\th := newDummyHammer()\n\t\t\th.TestDuration = 2\n\t\t\th.Scenario.Steps[0].URL = server.URL\n\n\t\t\tctx, cancel := context.WithCancel(context.Background())\n\n\t\t\tif test.name == \"Abort\" {\n\t\t\t\th.Assertions = map[string]types.TestAssertionOpt{\n\t\t\t\t\t\"false\": { // rule evaluated to false\n\t\t\t\t\t\tAbort: true,\n\t\t\t\t\t\tDelay: 1,\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tes, err := InitEngineServices(h)\n\t\t\te, err := NewEngine(ctx, h, es)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"TestRequestTimeout error occurred %v\", err)\n\t\t\t}\n\n\t\t\terr = e.Init()\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"TestRequestTimeout error occurred %v\", err)\n\t\t\t}\n\n\t\t\tif test.cancelCtx {\n\t\t\t\ttime.AfterFunc(time.Duration(500)*time.Millisecond, func() {\n\t\t\t\t\tcancel()\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tres := e.Start()\n\t\t\tcancel()\n\n\t\t\t// Assert\n\t\t\tm.Lock()\n\t\t\tif res != test.expectedStatus {\n\t\t\t\tt.Errorf(\"Expected %v, Found %v\", test.expectedStatus, res)\n\t\t\t}\n\t\t\tif test.testFailed != e.IsTestFailed() {\n\t\t\t\tt.Errorf(\"Expected %v, Found %v\", test.testFailed, e.IsTestFailed())\n\n\t\t\t}\n\t\t\tm.Unlock()\n\t\t})\n\t}\n}\n\nfunc TestDynamicData(t *testing.T) {\n\tt.Parallel()\n\n\tvar headers http.Header\n\tvar body, uri string\n\n\t// Test server\n\thandler := func(w http.ResponseWriter, r *http.Request) {\n\t\theaders = r.Header\n\t\turi = r.RequestURI\n\t\tbodyByte, _ := ioutil.ReadAll(r.Body)\n\t\tbody = string(bodyByte)\n\t}\n\tserver := httptest.NewServer(http.HandlerFunc(handler))\n\tdefer server.Close()\n\n\t// Prepare\n\th := newDummyHammer()\n\th.Scenario.Steps[0] = types.ScenarioStep{\n\t\tID:     1,\n\t\tMethod: \"GET\",\n\t\tURL:    server.URL + \"/get_test_data/{{_randomInt}}\",\n\t\tHeaders: map[string]string{\n\t\t\t\"Test1\":            \"{{_randomInt}}\",\n\t\t\t\"{{_randomInt}}\":   \"Test2Value\",\n\t\t\t\"{{_randomColor}}\": \"{{_randomInt}}\",\n\t\t\t\"Test4\":            \"Test4Value\",\n\t\t},\n\t\tPayload: \"{{_randomJobArea}}\",\n\t\tAuth: types.Auth{\n\t\t\tType:     types.AuthHttpBasic,\n\t\t\tUsername: \"testuser\",\n\t\t\tPassword: \"{{_randomBankAccountBic}}\",\n\t\t},\n\t}\n\n\t// Act\n\tes, err := InitEngineServices(h)\n\te, err := NewEngine(context.TODO(), h, es)\n\tif err != nil {\n\t\tt.Errorf(\"TestRequestData error occurred %v\", err)\n\t}\n\n\terr = e.Init()\n\tif err != nil {\n\t\tt.Errorf(\"TestRequestData error occurred %v\", err)\n\t}\n\n\te.Start()\n\n\t// Assert\n\tif i, err := strconv.Atoi(headers.Get(\"Test1\")); err != nil {\n\t\tt.Errorf(\"invalid header received: %v\", i)\n\t}\n\n\tif headers.Get(\"Test4\") != \"Test4Value\" {\n\t\tt.Errorf(\"invalid header received: %v\", headers.Get(\"Test4\"))\n\t}\n\n\tfor k, v := range headers {\n\t\tvFirst := v[0]\n\t\tif vFirst == \"Test2Value\" {\n\t\t\tif i, err := strconv.Atoi(k); err != nil {\n\t\t\t\tt.Errorf(\"invalid header received: %v\", i)\n\t\t\t}\n\t\t}\n\t\tfmt.Println(k, v)\n\t}\n\n\t// body\n\tcontains := false\n\tfor _, v := range faker.JobAreas {\n\t\tif body == v {\n\t\t\tcontains = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif contains == false {\n\t\tt.Errorf(\"invalid body received: %v\", body)\n\t}\n\n\t// basic auth\n\tauthHeader := strings.ReplaceAll(headers.Get(\"Authorization\"), \"Basic \", \"\")\n\td, _ := base64.StdEncoding.DecodeString(authHeader)\n\tusernamePassword := string(d)\n\tusernamePasswordSlice := strings.Split(usernamePassword, \":\")\n\tusername := usernamePasswordSlice[0]\n\tpassword := usernamePasswordSlice[1]\n\n\tif username != \"testuser\" {\n\t\tt.Errorf(\"invalid username received: %v\", username)\n\t}\n\n\tcontains = false\n\tfor _, v := range faker.BankAccountBics {\n\t\tif password == v {\n\t\t\tcontains = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif contains == false {\n\t\tt.Errorf(\"invalid body received: %v\", body)\n\t}\n\n\t// uri\n\turiDynamicPart := strings.ReplaceAll(uri, \"/get_test_data/\", \"\")\n\tif i, err := strconv.Atoi(uriDynamicPart); err != nil {\n\t\tt.Errorf(\"invalid uri received: %v\", i)\n\t}\n}\n\nfunc TestGlobalEnvs(t *testing.T) {\n\tt.Parallel()\n\n\t// Test server\n\trequestCalled := false\n\theaderKey := \"HEADER_KEY\"\n\tvar gotHeaderVal string\n\n\thandler := func(w http.ResponseWriter, r *http.Request) {\n\t\trequestCalled = true\n\t\tgotHeaderVal = r.Header.Get(headerKey)\n\t}\n\n\tpath := \"/xxx\"\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(path, handler)\n\n\tserver := httptest.NewServer(mux)\n\tdefer server.Close()\n\n\t// Prepare\n\th := newDummyHammer()\n\th.Debug = true\n\th.Scenario.Envs = map[string]interface{}{\n\t\t\"URL_PATH\":   path,\n\t\t\"HEADER_VAL\": \"headerValToBeInjected\",\n\t}\n\th.Scenario.Steps[0] = types.ScenarioStep{\n\t\tID:     1,\n\t\tMethod: \"GET\",\n\t\tURL:    server.URL + \"{{URL_PATH}}\",\n\t\tHeaders: map[string]string{\n\t\t\t\"HEADER_KEY\": \"{{HEADER_VAL}}\",\n\t\t},\n\t\tPayload: \"{{_randomJobArea}}{{_randomInt}}{{_randomBoolean}}\",\n\t\tAuth: types.Auth{\n\t\t\tType:     types.AuthHttpBasic,\n\t\t\tUsername: \"testuser\",\n\t\t\tPassword: \"{{_randomBankAccountBic}}\",\n\t\t},\n\t}\n\n\t// Act\n\tes, err := InitEngineServices(h)\n\te, err := NewEngine(context.TODO(), h, es)\n\tif err != nil {\n\t\tt.Errorf(\"TestGlobalAndCapturedVars error occurred %v\", err)\n\t}\n\n\terr = e.Init()\n\tif err != nil {\n\t\tt.Errorf(\"TestGlobalAndCapturedVars error occurred %v\", err)\n\t}\n\n\te.Start()\n\n\tif !requestCalled {\n\t\tt.Errorf(\"TestGlobalAndCapturedVars test server has not been called, url path injection failed\")\n\t}\n\n\texpectedHeaderVal := h.Scenario.Envs[\"HEADER_VAL\"].(string)\n\tif !strings.EqualFold(gotHeaderVal, expectedHeaderVal) {\n\t\tt.Errorf(\"TestGlobalAndCapturedVars header val could not be set from envs, expected : %s, got: %s\", expectedHeaderVal, gotHeaderVal)\n\t}\n}\n\nfunc TestInjectEnvToBasicAuth(t *testing.T) {\n\tt.Parallel()\n\n\t// Test server\n\trequestCalled := false\n\theaderKey := \"Authorization\"\n\tvar gotHeaderVal string\n\n\thandler := func(w http.ResponseWriter, r *http.Request) {\n\t\trequestCalled = true\n\t\tgotHeaderVal = r.Header.Get(headerKey)\n\t}\n\n\tpath := \"/xxx\"\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(path, handler)\n\n\tserver := httptest.NewServer(mux)\n\tdefer server.Close()\n\n\t// Prepare\n\th := newDummyHammer()\n\th.Debug = true\n\th.Scenario.Envs = map[string]interface{}{\n\t\t\"URL_PATH\": path,\n\t}\n\th.Scenario.Steps[0] = types.ScenarioStep{\n\t\tID:      1,\n\t\tMethod:  \"GET\",\n\t\tURL:     server.URL + \"{{URL_PATH}}\",\n\t\tHeaders: map[string]string{},\n\t\tAuth: types.Auth{\n\t\t\tType:     types.AuthHttpBasic,\n\t\t\tUsername: \"kfc\",\n\t\t\tPassword: \"1234\",\n\t\t},\n\t}\n\n\t// Act\n\tes, err := InitEngineServices(h)\n\te, err := NewEngine(context.TODO(), h, es)\n\tif err != nil {\n\t\tt.Errorf(\"TestInjectEnvToBasicAuth error occurred %v\", err)\n\t}\n\n\terr = e.Init()\n\tif err != nil {\n\t\tt.Errorf(\"TestInjectEnvToBasicAuth error occurred %v\", err)\n\t}\n\n\te.Start()\n\n\tif !requestCalled {\n\t\tt.Errorf(\"TestInjectEnvToBasicAuth test server has not been called, url path injection failed\")\n\t}\n\n\t// base64 encoding of kfc:1234 -> a2ZjOjEyMzQ=\n\texpectedAuthzHeader := \"Basic a2ZjOjEyMzQ=\"\n\n\tif !strings.EqualFold(gotHeaderVal, expectedAuthzHeader) {\n\t\tt.Errorf(\"TestInjectEnvToBasicAuth header val could not be set from envs, expected : %s, got: %s\", expectedAuthzHeader, gotHeaderVal)\n\t}\n}\n\nfunc TestCapturedEnvsFromJsonBody(t *testing.T) {\n\tt.Parallel()\n\n\t// Test server\n\tfirstRequestCalled := false\n\tsecondRequestCalled := false\n\theaderKey := \"HEADER_KEY\"\n\tvar gotHeaderVal string\n\tsecondReqBody := make(map[string]interface{}, 0)\n\n\tfirstReqHandler := func(w http.ResponseWriter, r *http.Request) {\n\t\tfirstRequestCalled = true\n\t\tbody := struct {\n\t\t\tNum      int    `json:\"num\"`\n\t\t\tName     string `json:\"name\"`\n\t\t\tChampion bool   `json:\"isChampion\"`\n\t\t\tSquad    struct {\n\t\t\t\tResults map[string]string `json:\"results\"`\n\t\t\t\tPlayers []string          `json:\"players\"`\n\t\t\t} `json:\"squad\"`\n\t\t}{\n\t\t\tNum:      25,\n\t\t\tName:     \"Argentina\",\n\t\t\tChampion: true,\n\t\t\tSquad: struct {\n\t\t\t\tResults map[string]string `json:\"results\"`\n\t\t\t\tPlayers []string          \"json:\\\"players\\\"\"\n\t\t\t}{\n\t\t\t\tResults: map[string]string{\"SAR\": \"1-2\",\n\t\t\t\t\t\"MEX\": \"2-1\",\n\t\t\t\t\t\"POL\": \"2-0\",\n\t\t\t\t\t\"AUS\": \"2-0\",\n\t\t\t\t\t\"HOL\": \"4-2\",\n\t\t\t\t\t\"CRO\": \"2-0\",\n\t\t\t\t\t\"FRA\": \"CHAMPIONS\",\n\t\t\t\t},\n\t\t\t\tPlayers: []string{\"messi\", \"alvarez\", \"dimaria\", \"enzo\"},\n\t\t\t},\n\t\t}\n\n\t\tw.Header().Set(\"Argentina\", \"Messi\")\n\n\t\tbyteBody, _ := json.Marshal(body)\n\t\tw.Write(byteBody)\n\t}\n\n\tsecondReqHandler := func(w http.ResponseWriter, r *http.Request) {\n\t\tsecondRequestCalled = true\n\t\tgotHeaderVal = r.Header.Get(headerKey)\n\t\tbBody, _ := io.ReadAll(r.Body)\n\t\tjson.Unmarshal(bBody, &secondReqBody)\n\n\t}\n\tpathFirst := \"/json-body\"\n\tpathSecond := \"/passed-captured-vars\"\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(pathFirst, firstReqHandler)\n\tmux.HandleFunc(pathSecond, secondReqHandler)\n\n\tserver := httptest.NewServer(mux)\n\tdefer server.Close()\n\n\t// Prepare\n\th := newDummyHammer()\n\th.Scenario.Envs = map[string]interface{}{\n\t\t\"FIRST_REQ_URL_PATH\": pathFirst,\n\t\t\"HEADER_VAL\":         \"headerValToBeInjected\",\n\t}\n\n\th.Scenario.Steps = make([]types.ScenarioStep, 2)\n\tjsonPath := \"isChampion\"\n\th.Scenario.Steps[0] = types.ScenarioStep{\n\t\tID:      1,\n\t\tMethod:  \"GET\",\n\t\tURL:     server.URL + \"{{FIRST_REQ_URL_PATH}}\",\n\t\tPayload: \"{{_randomJobArea}}\",\n\t\tAuth: types.Auth{\n\t\t\tType:     types.AuthHttpBasic,\n\t\t\tUsername: \"testuser\",\n\t\t\tPassword: \"{{_randomBankAccountBic}}\",\n\t\t},\n\t\tEnvsToCapture: []types.EnvCaptureConf{\n\t\t\t{Name: \"CHAMPION\", From: \"body\", JsonPath: &jsonPath},\n\t\t},\n\t}\n\th.Scenario.Steps[1] = types.ScenarioStep{\n\t\tID:     2,\n\t\tMethod: \"GET\",\n\t\tURL:    server.URL + pathSecond,\n\t\tHeaders: map[string]string{\n\t\t\t\"HEADER_KEY\": \"{{HEADER_VAL}}\",\n\t\t},\n\t\tAuth: types.Auth{\n\t\t\tType:     types.AuthHttpBasic,\n\t\t\tUsername: \"testuser\",\n\t\t\tPassword: \"{{_randomBankAccountBic}}\",\n\t\t},\n\t\tPayload: \"{\\n    \\\"ARGENTINA\\\" : \\\"{{CHAMPION}}\\\"\\n}\", // json escaped string, use payload_file instead\n\t}\n\n\t// Act\n\tes, err := InitEngineServices(h)\n\te, err := NewEngine(context.TODO(), h, es)\n\tif err != nil {\n\t\tt.Errorf(\"TestCapturedEnvsFromJsonBody error occurred %v\", err)\n\t}\n\n\terr = e.Init()\n\tif err != nil {\n\t\tt.Errorf(\"TestCapturedEnvsFromJsonBody error occurred %v\", err)\n\t}\n\n\te.Start()\n\n\tif !firstRequestCalled || !secondRequestCalled {\n\t\tt.Errorf(\"TestCapturedEnvsFromJsonBody test server has not been called, url path injection failed\")\n\t}\n\n\texpectedHeaderVal := h.Scenario.Envs[\"HEADER_VAL\"].(string)\n\tif !strings.EqualFold(gotHeaderVal, expectedHeaderVal) {\n\t\tt.Errorf(\"TestCapturedEnvsFromJsonBody header val could not be set from envs, expected : %s, got: %s\",\n\t\t\texpectedHeaderVal, gotHeaderVal)\n\t}\n\n\texpectedReqPayloadOnSecondReq := true\n\tif secondReqBody[\"ARGENTINA\"].(bool) != expectedReqPayloadOnSecondReq {\n\t\tt.Errorf(\"TestCapturedEnvsFromJsonBody second req body could not be set from envs, expected : %t, got: %s\",\n\t\t\texpectedReqPayloadOnSecondReq, secondReqBody)\n\t}\n\n}\n\nfunc TestContinueTestOnCaptureError(t *testing.T) {\n\tt.Parallel()\n\n\t// Test server\n\tfirstRequestCalled := false\n\tsecondRequestCalled := false\n\tnotExistHeaderKey := \"NO_HEADER_KEY\"\n\tvar gotHeaderVal string\n\tsecondReqBody := make(map[string]interface{}, 0)\n\tsecondReqInjectedHeaderKey := \"INJECTED_HEADER\"\n\n\tfirstReqHandler := func(w http.ResponseWriter, r *http.Request) {\n\t\tfirstRequestCalled = true\n\t\tw.Header().Set(\"Argentina\", \"Messi\")\n\t}\n\n\tsecondReqHandler := func(w http.ResponseWriter, r *http.Request) {\n\t\tsecondRequestCalled = true\n\t\tgotHeaderVal = r.Header.Get(secondReqInjectedHeaderKey)\n\t\tbBody, _ := io.ReadAll(r.Body)\n\t\tjson.Unmarshal(bBody, &secondReqBody)\n\n\t}\n\tpathFirst := \"/header-capture\"\n\tpathSecond := \"/passed-captured-vars\"\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(pathFirst, firstReqHandler)\n\tmux.HandleFunc(pathSecond, secondReqHandler)\n\n\tserver := httptest.NewServer(mux)\n\tdefer server.Close()\n\n\t// Prepare\n\th := newDummyHammer()\n\th.Scenario.Envs = map[string]interface{}{\n\t\t\"FIRST_REQ_URL_PATH\": pathFirst,\n\t}\n\n\th.Scenario.Steps = make([]types.ScenarioStep, 2)\n\th.Scenario.Steps[0] = types.ScenarioStep{\n\t\tID:     1,\n\t\tMethod: \"GET\",\n\t\tURL:    server.URL + \"{{FIRST_REQ_URL_PATH}}\",\n\t\tEnvsToCapture: []types.EnvCaptureConf{\n\t\t\t{Name: \"HEADER_VAL\", From: \"header\", Key: &notExistHeaderKey},\n\t\t},\n\t}\n\th.Scenario.Steps[1] = types.ScenarioStep{\n\t\tID:     2,\n\t\tMethod: \"GET\",\n\t\tURL:    server.URL + pathSecond,\n\t\tHeaders: map[string]string{\n\t\t\t\"INJECTED_HEADER\": \"{{HEADER_VAL}}\",\n\t\t},\n\t}\n\n\t// Act\n\tes, err := InitEngineServices(h)\n\te, err := NewEngine(context.TODO(), h, es)\n\tif err != nil {\n\t\tt.Errorf(\"TestContinueTestOnCaptureError error occurred %v\", err)\n\t}\n\n\terr = e.Init()\n\tif err != nil {\n\t\tt.Errorf(\"TestContinueTestOnCaptureError error occurred %v\", err)\n\t}\n\n\te.Start()\n\n\tif !firstRequestCalled || !secondRequestCalled {\n\t\tt.Errorf(\"TestContinueTestOnCaptureError test server has not been called, url path injection failed\")\n\t}\n\n\texpectedHeaderVal := \"\"\n\tif !strings.EqualFold(gotHeaderVal, expectedHeaderVal) { // default value \"\"\n\t\tt.Errorf(\"TestContinueTestOnCaptureError header val could not be set from envs, must be default value, expected : %s, got: %s\",\n\t\t\texpectedHeaderVal, gotHeaderVal)\n\t}\n\n}\n\nfunc TestCaptureAndInjectEnvironmentsJsonPayload(t *testing.T) {\n\tt.Parallel()\n\tfirstRequestCalled := false\n\tsecondRequestCalled := false\n\tsecondReqBody := make(map[string]interface{}, 0)\n\n\tvar secondReqboolHeader string\n\tvar secondReqnumHeader string\n\n\tfirstReqHandler := func(w http.ResponseWriter, r *http.Request) {\n\t\tfirstRequestCalled = true\n\t\tbody := struct {\n\t\t\tNum      int    `json:\"num\"`\n\t\t\tName     string `json:\"name\"`\n\t\t\tChampion bool   `json:\"isChampion\"`\n\t\t\tSquad    struct {\n\t\t\t\tResults map[string]string `json:\"results\"`\n\t\t\t\tPlayers []string          `json:\"players\"`\n\t\t\t} `json:\"squad\"`\n\t\t}{\n\t\t\tNum:      25,\n\t\t\tName:     \"Argentina\",\n\t\t\tChampion: true,\n\t\t\tSquad: struct {\n\t\t\t\tResults map[string]string `json:\"results\"`\n\t\t\t\tPlayers []string          \"json:\\\"players\\\"\"\n\t\t\t}{\n\t\t\t\tResults: map[string]string{\"SAR\": \"1-2\",\n\t\t\t\t\t\"MEX\": \"2-1\",\n\t\t\t\t\t\"POL\": \"2-0\",\n\t\t\t\t\t\"AUS\": \"2-0\",\n\t\t\t\t\t\"HOL\": \"4-2\",\n\t\t\t\t\t\"CRO\": \"2-0\",\n\t\t\t\t\t\"FRA\": \"CHAMPIONS\",\n\t\t\t\t},\n\t\t\t\tPlayers: []string{\"messi\", \"alvarez\", \"dimaria\", \"enzo\"},\n\t\t\t},\n\t\t}\n\n\t\tw.Header().Set(\"Argentina\", \"Messi\")\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\n\t\tbyteBody, _ := json.Marshal(body)\n\t\tw.Write(byteBody)\n\t}\n\tsecondReqHandler := func(w http.ResponseWriter, r *http.Request) {\n\t\tsecondRequestCalled = true\n\t\tbBody, _ := io.ReadAll(r.Body)\n\t\tjson.Unmarshal(bBody, &secondReqBody)\n\t\tsecondReqnumHeader = r.Header.Get(\"num\")\n\t\tsecondReqboolHeader = r.Header.Get(\"bool\")\n\t}\n\tpathFirst := \"/header-capture\"\n\tpathSecond := \"/passed-captured-vars\"\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(pathFirst, firstReqHandler)\n\tmux.HandleFunc(pathSecond, secondReqHandler)\n\tserver := httptest.NewServer(mux)\n\tdefer server.Close()\n\n\t// read config, create hammer\n\tconfigPath := \"../config/config_testdata/config_inject_json.json\"\n\tf, err := os.Open(configPath)\n\tif err != nil {\n\t\tt.Errorf(\"could not open test config %v\", err)\n\t}\n\n\tbyteValue, err := ioutil.ReadAll(f)\n\tif err != nil {\n\t\tt.Errorf(\"could not read test config %v\", err)\n\t}\n\tc, err := config.NewConfigReader(byteValue, config.ConfigTypeJson)\n\tif err != nil {\n\t\tt.Errorf(\"could not create json config reader %v\", err)\n\t}\n\th, err := c.CreateHammer()\n\tif err != nil {\n\t\tt.Errorf(\"could not create hammer, %v\", err)\n\t}\n\n\t// set test servers paths\n\th.Scenario.Steps[0].URL = server.URL + pathFirst\n\th.Scenario.Steps[1].URL = server.URL + pathSecond\n\n\t// run engine\n\tes, err := InitEngineServices(h)\n\te, err := NewEngine(context.TODO(), h, es)\n\tif err != nil {\n\t\tt.Errorf(\"TestCaptureAndInjectEnvironmentsJsonPayload error occurred %v\", err)\n\t}\n\n\terr = e.Init()\n\tif err != nil {\n\t\tt.Errorf(\"TestCaptureAndInjectEnvironmentsJsonPayload error occurred %v\", err)\n\t}\n\n\te.Start()\n\n\t// assert\n\tif !firstRequestCalled || !secondRequestCalled {\n\t\tt.Errorf(\"TestCaptureAndInjectEnvironmentsJsonPayload test server has not been called, url path injection failed\")\n\t}\n\n\tif _, ok := secondReqBody[\"boolField\"].(bool); !ok {\n\t\tt.Errorf(\"TestCaptureAndInjectEnvironmentsJsonPayload bool field could not be injected to json payload\")\n\t}\n\tif _, ok := secondReqBody[\"numField\"].(float64); !ok {\n\t\tt.Errorf(\"TestCaptureAndInjectEnvironmentsJsonPayload num field could not be injected to json payload\")\n\t}\n\tif _, ok := secondReqBody[\"strField\"].(string); !ok {\n\t\tt.Errorf(\"TestCaptureAndInjectEnvironmentsJsonPayload string field could not be injected to json payload\")\n\t}\n\n\tfor _, v := range secondReqBody[\"numArrayField\"].([]interface{}) {\n\t\tif _, ok := v.(float64); !ok {\n\t\t\tt.Errorf(\"TestCaptureAndInjectEnvironmentsJsonPayload num array field could not be injected to json payload\")\n\t\t}\n\t}\n\n\tfor _, v := range secondReqBody[\"strArrayField\"].([]interface{}) {\n\t\tif _, ok := v.(string); !ok {\n\t\t\tt.Errorf(\"TestCaptureAndInjectEnvironmentsJsonPayload str array field could not be injected to json payload\")\n\t\t}\n\t}\n\n\tobj, _ := secondReqBody[\"obj\"].(map[string]interface{})\n\tif _, ok := obj[\"objectField\"].(map[string]interface{}); !ok {\n\t\tt.Errorf(\"TestCaptureAndInjectEnvironmentsJsonPayload object field could not be injected to json payload\")\n\t}\n\tif _, ok := obj[\"arrayField\"].([]interface{}); !ok {\n\t\tt.Errorf(\"TestCaptureAndInjectEnvironmentsJsonPayload array field could not be injected to json payload\")\n\t}\n\n\tif secondReqnumHeader != \"25\" {\n\t\tt.Errorf(\"TestCaptureAndInjectEnvironmentsJsonPayload num header could not be injected to json payload\")\n\t}\n\tif secondReqboolHeader != \"true\" {\n\t\tt.Errorf(\"TestCaptureAndInjectEnvironmentsJsonPayload bool header could not be injected to json payload\")\n\t}\n\n}\n\nfunc TestCaptureAndInjectEnvironmentsJsonPayloadDynamic(t *testing.T) {\n\tt.Parallel()\n\tfirstRequestCalled := false\n\tsecondRequestCalled := false\n\tsecondReqBody := make(map[string]interface{}, 0)\n\n\tvar secondReqboolHeader string\n\tvar secondReqnumHeader string\n\n\tfirstReqHandler := func(w http.ResponseWriter, r *http.Request) {\n\t\tfirstRequestCalled = true\n\t\tbody := struct {\n\t\t\tNum      int    `json:\"num\"`\n\t\t\tName     string `json:\"name\"`\n\t\t\tChampion bool   `json:\"isChampion\"`\n\t\t\tSquad    struct {\n\t\t\t\tResults map[string]string `json:\"results\"`\n\t\t\t\tPlayers []string          `json:\"players\"`\n\t\t\t} `json:\"squad\"`\n\t\t}{\n\t\t\tNum:      25,\n\t\t\tName:     \"Argentina\",\n\t\t\tChampion: true,\n\t\t\tSquad: struct {\n\t\t\t\tResults map[string]string `json:\"results\"`\n\t\t\t\tPlayers []string          \"json:\\\"players\\\"\"\n\t\t\t}{\n\t\t\t\tResults: map[string]string{\"SAR\": \"1-2\",\n\t\t\t\t\t\"MEX\": \"2-1\",\n\t\t\t\t\t\"POL\": \"2-0\",\n\t\t\t\t\t\"AUS\": \"2-0\",\n\t\t\t\t\t\"HOL\": \"4-2\",\n\t\t\t\t\t\"CRO\": \"2-0\",\n\t\t\t\t\t\"FRA\": \"CHAMPIONS\",\n\t\t\t\t},\n\t\t\t\tPlayers: []string{\"messi\", \"alvarez\", \"dimaria\", \"enzo\"},\n\t\t\t},\n\t\t}\n\n\t\tw.Header().Set(\"Argentina\", \"Messi\")\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\n\t\tbyteBody, _ := json.Marshal(body)\n\t\tw.Write(byteBody)\n\t}\n\tsecondReqHandler := func(w http.ResponseWriter, r *http.Request) {\n\t\tsecondRequestCalled = true\n\t\tbBody, _ := io.ReadAll(r.Body)\n\t\tjson.Unmarshal(bBody, &secondReqBody)\n\t\tsecondReqnumHeader = r.Header.Get(\"num\")\n\t\tsecondReqboolHeader = r.Header.Get(\"bool\")\n\t}\n\tpathFirst := \"/header-capture\"\n\tpathSecond := \"/passed-captured-vars\"\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(pathFirst, firstReqHandler)\n\tmux.HandleFunc(pathSecond, secondReqHandler)\n\tserver := httptest.NewServer(mux)\n\tdefer server.Close()\n\n\t// read config, create hammer\n\tconfigPath := \"../config/config_testdata/config_inject_json_dynamic.json\"\n\tf, err := os.Open(configPath)\n\tif err != nil {\n\t\tt.Errorf(\"could not open test config %v\", err)\n\t}\n\n\tbyteValue, err := ioutil.ReadAll(f)\n\tif err != nil {\n\t\tt.Errorf(\"could not read test config %v\", err)\n\t}\n\tc, err := config.NewConfigReader(byteValue, config.ConfigTypeJson)\n\tif err != nil {\n\t\tt.Errorf(\"could not create json config reader %v\", err)\n\t}\n\th, err := c.CreateHammer()\n\tif err != nil {\n\t\tt.Errorf(\"could not create hammer, %v\", err)\n\t}\n\n\t// set test servers paths\n\th.Scenario.Steps[0].URL = server.URL + pathFirst\n\th.Scenario.Steps[1].URL = server.URL + pathSecond\n\n\t// run engine\n\tes, err := InitEngineServices(h)\n\te, err := NewEngine(context.TODO(), h, es)\n\tif err != nil {\n\t\tt.Errorf(\"TestCaptureAndInjectEnvironmentsJsonPayloadDynamic error occurred %v\", err)\n\t}\n\n\terr = e.Init()\n\tif err != nil {\n\t\tt.Errorf(\"TestCaptureAndInjectEnvironmentsJsonPayloadDynamic error occurred %v\", err)\n\t}\n\n\te.Start()\n\n\t// assert\n\tif !firstRequestCalled || !secondRequestCalled {\n\t\tt.Errorf(\"TestCaptureAndInjectEnvironmentsJsonPayloadDynamic test server has not been called, url path injection failed\")\n\t}\n\n\tif _, ok := secondReqBody[\"name\"].(string); !ok {\n\t\tt.Errorf(\"TestCaptureAndInjectEnvironmentsJsonPayloadDynamic bool field could not be injected to json payload\")\n\t}\n\tif _, ok := secondReqBody[\"city\"].(string); !ok {\n\t\tt.Errorf(\"TestCaptureAndInjectEnvironmentsJsonPayloadDynamic num field could not be injected to json payload\")\n\t}\n\tif _, ok := secondReqBody[\"age\"].(float64); !ok {\n\t\tt.Errorf(\"TestCaptureAndInjectEnvironmentsJsonPayloadDynamic string field could not be injected to json payload\")\n\t}\n\n\tif secondReqnumHeader != \"25\" {\n\t\tt.Errorf(\"TestCaptureAndInjectEnvironmentsJsonPayloadDynamic num header could not be injected to json payload\")\n\t}\n\tif secondReqboolHeader != \"true\" {\n\t\tt.Errorf(\"TestCaptureAndInjectEnvironmentsJsonPayloadDynamic bool header could not be injected to json payload\")\n\t}\n\n}\n\nfunc TestEnvInjectToXmlPayload(t *testing.T) {\n\tt.Parallel()\n\trequestCalled := false\n\treadReqBody := make([]byte, 0)\n\tinjectedEnv := \"hello\"\n\texpectedReqBody := []byte(\n\t\tfmt.Sprintf(`<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n\t\t\t\t\t\t<rss version=\"2.0\">\n\t\t\t\t\t\t<channel>\n\t\t\t\t\t\t<item>\n\t\t\t\t\t\t\t<title>%s</title>\n\t\t\t\t\t\t</item>\n\t\t\t\t\t\t</channel>\n\t\t\t\t\t\t</rss>`, injectedEnv))\n\n\tfirstReqHandler := func(w http.ResponseWriter, r *http.Request) {\n\t\trequestCalled = true\n\t\treadReqBody, _ = io.ReadAll(r.Body)\n\t}\n\n\tpathFirst := \"/header-capture\"\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(pathFirst, firstReqHandler)\n\n\tserver := httptest.NewServer(mux)\n\tdefer server.Close()\n\n\t// read config, create hammer\n\tconfigPath := \"../config/config_testdata/config_inject_xml.json\"\n\tf, err := os.Open(configPath)\n\tif err != nil {\n\t\tt.Errorf(\"could not open test config %v\", err)\n\t}\n\n\tbyteValue, err := ioutil.ReadAll(f)\n\tif err != nil {\n\t\tt.Errorf(\"could not read test config %v\", err)\n\t}\n\tc, err := config.NewConfigReader(byteValue, config.ConfigTypeJson)\n\tif err != nil {\n\t\tt.Errorf(\"could not create json config reader %v\", err)\n\t}\n\th, err := c.CreateHammer()\n\tif err != nil {\n\t\tt.Errorf(\"could not create hammer, %v\", err)\n\t}\n\n\t// set test servers paths\n\th.Scenario.Steps[0].URL = server.URL + pathFirst\n\n\t// run engine\n\tes, err := InitEngineServices(h)\n\te, err := NewEngine(context.TODO(), h, es)\n\tif err != nil {\n\t\tt.Errorf(\"TestInjectXmlPayload error occurred %v\", err)\n\t}\n\n\terr = e.Init()\n\tif err != nil {\n\t\tt.Errorf(\"TestInjectXmlPayload error occurred %v\", err)\n\t}\n\n\te.Start()\n\n\t// assert\n\tif !requestCalled {\n\t\tt.Errorf(\"TestInjectXmlPayload test server has not been called, url path injection failed\")\n\t}\n\n\tif bytes.Equal(readReqBody, expectedReqBody) {\n\n\t}\n\n}\n\nfunc TestCaptureHeaderWithRegex(t *testing.T) {\n\tt.Parallel()\n\n\t// Test server\n\tfirstRequestCalled := false\n\tsecondRequestCalled := false\n\theaderKey := \"Argentina\"\n\tvar gotHeaderVal string\n\tsecondReqBody := make(map[string]interface{}, 0)\n\tsecondReqInjectedHeaderKey := \"BallondorWinner\"\n\n\tfirstReqHandler := func(w http.ResponseWriter, r *http.Request) {\n\t\tfirstRequestCalled = true\n\t\tw.Header().Set(headerKey, \"messi_10alvarez9\")\n\t}\n\n\tsecondReqHandler := func(w http.ResponseWriter, r *http.Request) {\n\t\tsecondRequestCalled = true\n\t\tgotHeaderVal = r.Header.Get(secondReqInjectedHeaderKey)\n\t\tbBody, _ := io.ReadAll(r.Body)\n\t\tjson.Unmarshal(bBody, &secondReqBody)\n\n\t}\n\tpathFirst := \"/header-capture\"\n\tpathSecond := \"/passed-captured-vars\"\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(pathFirst, firstReqHandler)\n\tmux.HandleFunc(pathSecond, secondReqHandler)\n\n\tserver := httptest.NewServer(mux)\n\tdefer server.Close()\n\n\t// Prepare\n\th := newDummyHammer()\n\th.Scenario.Envs = map[string]interface{}{\n\t\t\"FIRST_REQ_URL_PATH\": pathFirst,\n\t}\n\n\th.Scenario.Steps = make([]types.ScenarioStep, 2)\n\tregex := \"[a-z]+_[0-9]+\"\n\th.Scenario.Steps[0] = types.ScenarioStep{\n\t\tID:     1,\n\t\tMethod: \"GET\",\n\t\tURL:    server.URL + \"{{FIRST_REQ_URL_PATH}}\",\n\t\tEnvsToCapture: []types.EnvCaptureConf{\n\t\t\t{Name: \"GOAT\", From: \"header\", Key: &headerKey, RegExp: &types.RegexCaptureConf{Exp: &regex, No: 0}},\n\t\t},\n\t}\n\th.Scenario.Steps[1] = types.ScenarioStep{\n\t\tID:     2,\n\t\tMethod: \"GET\",\n\t\tURL:    server.URL + pathSecond,\n\t\tHeaders: map[string]string{\n\t\t\tsecondReqInjectedHeaderKey: \"{{GOAT}}\",\n\t\t},\n\t}\n\n\t// Act\n\tes, err := InitEngineServices(h)\n\te, err := NewEngine(context.TODO(), h, es)\n\tif err != nil {\n\t\tt.Errorf(\"TestCaptureHeaderWithRegex error occurred %v\", err)\n\t}\n\n\terr = e.Init()\n\tif err != nil {\n\t\tt.Errorf(\"TestCaptureHeaderWithRegex error occurred %v\", err)\n\t}\n\n\te.Start()\n\n\tif !firstRequestCalled || !secondRequestCalled {\n\t\tt.Errorf(\"TestCaptureHeaderWithRegex test server has not been called, url path injection failed\")\n\t}\n\n\texpectedHeaderVal := \"messi_10\"\n\tif !strings.EqualFold(gotHeaderVal, expectedHeaderVal) {\n\t\tt.Errorf(\n\t\t\t\"TestCaptureHeaderWithRegex header val could not be set from envs, must be default value, expected : %s, got: %s\",\n\t\t\texpectedHeaderVal, gotHeaderVal)\n\t}\n\n}\n\nfunc TestCaptureCookie(t *testing.T) {\n\tt.Parallel()\n\n\t// Test server\n\tfirstRequestCalled := false\n\tsecondRequestCalled := false\n\n\tcookieName := \"Argentina\"\n\tvar gotCookieVal string\n\tsecondReqBody := make(map[string]interface{}, 0)\n\tsecondReqInjectedHeaderKey := \"BallondorWinner\"\n\texpectedCookieValue := \"messi_10\"\n\n\tfirstReqHandler := func(w http.ResponseWriter, r *http.Request) {\n\t\tfirstRequestCalled = true\n\t\thttp.SetCookie(w, &http.Cookie{Name: cookieName, Value: expectedCookieValue})\n\t}\n\n\tsecondReqHandler := func(w http.ResponseWriter, r *http.Request) {\n\t\tsecondRequestCalled = true\n\t\tgotCookieVal = r.Header.Get(secondReqInjectedHeaderKey)\n\t\tbBody, _ := io.ReadAll(r.Body)\n\t\tjson.Unmarshal(bBody, &secondReqBody)\n\t}\n\tpathFirst := \"/header-capture\"\n\tpathSecond := \"/passed-captured-vars\"\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(pathFirst, firstReqHandler)\n\tmux.HandleFunc(pathSecond, secondReqHandler)\n\n\tserver := httptest.NewServer(mux)\n\tdefer server.Close()\n\n\t// Prepare\n\th := newDummyHammer()\n\th.Scenario.Envs = map[string]interface{}{\n\t\t\"FIRST_REQ_URL_PATH\": pathFirst,\n\t}\n\n\th.Scenario.Steps = make([]types.ScenarioStep, 2)\n\th.Scenario.Steps[0] = types.ScenarioStep{\n\t\tID:     1,\n\t\tMethod: \"GET\",\n\t\tURL:    server.URL + \"{{FIRST_REQ_URL_PATH}}\",\n\t\tEnvsToCapture: []types.EnvCaptureConf{\n\t\t\t{Name: \"GOAT\", From: \"cookies\", CookieName: &cookieName},\n\t\t},\n\t}\n\th.Scenario.Steps[1] = types.ScenarioStep{\n\t\tID:     2,\n\t\tMethod: \"GET\",\n\t\tURL:    server.URL + pathSecond,\n\t\tHeaders: map[string]string{\n\t\t\tsecondReqInjectedHeaderKey: \"{{GOAT}}\",\n\t\t},\n\t}\n\n\t// Act\n\tes, err := InitEngineServices(h)\n\te, err := NewEngine(context.TODO(), h, es)\n\tif err != nil {\n\t\tt.Errorf(\"TestCaptureCookie error occurred %v\", err)\n\t}\n\n\terr = e.Init()\n\tif err != nil {\n\t\tt.Errorf(\"TestCaptureCookie error occurred %v\", err)\n\t}\n\n\te.Start()\n\n\tif !firstRequestCalled || !secondRequestCalled {\n\t\tt.Errorf(\"TestCaptureCookie test server has not been called, url path injection failed\")\n\t}\n\n\tif !strings.EqualFold(gotCookieVal, expectedCookieValue) {\n\t\tt.Errorf(\n\t\t\t\"TestCaptureCookie, expected : %s, got: %s\",\n\t\t\texpectedCookieValue, gotCookieVal)\n\t}\n\n}\n\nfunc TestCaptureStringPayloadWithRegex(t *testing.T) {\n\tt.Parallel()\n\n\t// Test server\n\tfirstRequestCalled := false\n\tsecondRequestCalled := false\n\tvar secondReqBody []byte\n\n\tfirstReqHandler := func(w http.ResponseWriter, r *http.Request) {\n\t\tfirstRequestCalled = true\n\t\tw.Write([]byte(\"messi_10alvarez9\"))\n\t}\n\n\tsecondReqHandler := func(w http.ResponseWriter, r *http.Request) {\n\t\tsecondRequestCalled = true\n\t\tsecondReqBody, _ = io.ReadAll(r.Body)\n\t}\n\tpathFirst := \"/header-capture\"\n\tpathSecond := \"/passed-captured-vars\"\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(pathFirst, firstReqHandler)\n\tmux.HandleFunc(pathSecond, secondReqHandler)\n\n\tserver := httptest.NewServer(mux)\n\tdefer server.Close()\n\n\t// Prepare\n\th := newDummyHammer()\n\th.Scenario.Envs = map[string]interface{}{\n\t\t\"FIRST_REQ_URL_PATH\": pathFirst,\n\t}\n\n\th.Scenario.Steps = make([]types.ScenarioStep, 2)\n\tregex := \"[a-z]+_[0-9]+\"\n\th.Scenario.Steps[0] = types.ScenarioStep{\n\t\tID:     1,\n\t\tMethod: \"GET\",\n\t\tURL:    server.URL + \"{{FIRST_REQ_URL_PATH}}\",\n\t\tEnvsToCapture: []types.EnvCaptureConf{\n\t\t\t{Name: \"GOAT\", From: \"body\", RegExp: &types.RegexCaptureConf{Exp: &regex, No: 0}},\n\t\t},\n\t}\n\th.Scenario.Steps[1] = types.ScenarioStep{\n\t\tID:      2,\n\t\tMethod:  \"GET\",\n\t\tURL:     server.URL + pathSecond,\n\t\tPayload: \"{{GOAT}}\",\n\t}\n\n\t// Act\n\tes, err := InitEngineServices(h)\n\te, err := NewEngine(context.TODO(), h, es)\n\tif err != nil {\n\t\tt.Errorf(\"TestCaptureHeaderWithRegex error occurred %v\", err)\n\t}\n\n\terr = e.Init()\n\tif err != nil {\n\t\tt.Errorf(\"TestCaptureHeaderWithRegex error occurred %v\", err)\n\t}\n\n\te.Start()\n\n\tif !firstRequestCalled || !secondRequestCalled {\n\t\tt.Errorf(\"TestCaptureHeaderWithRegex test server has not been called, url path injection failed\")\n\t}\n\n\texpectedBodyVal := []byte(\"messi_10\")\n\tif !bytes.Equal(secondReqBody, expectedBodyVal) {\n\t\tt.Errorf(\n\t\t\t\"TestCaptureHeaderWithRegex header val could not be set from envs, must be default value, expected : %s, got: %s\",\n\t\t\texpectedBodyVal, secondReqBody)\n\t}\n\n}\n\nfunc TestBothDynamicVarAndEnvVar(t *testing.T) {\n\tt.Parallel()\n\n\t// Test server\n\trequestCalled := false\n\theaderKey := \"country\"\n\tvar gotHeaderVal string\n\n\thandler := func(w http.ResponseWriter, r *http.Request) {\n\t\trequestCalled = true\n\t\tgotHeaderVal = r.Header.Get(headerKey)\n\t}\n\n\tpath := \"/xxx\"\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(path, handler)\n\n\tserver := httptest.NewServer(mux)\n\tdefer server.Close()\n\n\t// Prepare\n\th := newDummyHammer()\n\th.Debug = true\n\th.Scenario.Envs = map[string]interface{}{\n\t\t\"URL_PATH\":           path,\n\t\t\"COUNTRY_HEADER_KEY\": headerKey,\n\t}\n\th.Scenario.Steps[0] = types.ScenarioStep{\n\t\tID:     1,\n\t\tMethod: \"GET\",\n\t\tURL:    server.URL + \"{{URL_PATH}}\",\n\t\tHeaders: map[string]string{\n\t\t\t\"{{COUNTRY_HEADER_KEY}}\": \"{{_randomCountry}}\",\n\t\t},\n\t\tPayload: \"{{_randomJobArea}}\",\n\t\tAuth: types.Auth{\n\t\t\tType:     types.AuthHttpBasic,\n\t\t\tUsername: \"testuser\",\n\t\t\tPassword: \"{{_randomBankAccountBic}}\",\n\t\t},\n\t}\n\n\t// Act\n\tes, err := InitEngineServices(h)\n\te, err := NewEngine(context.TODO(), h, es)\n\tif err != nil {\n\t\tt.Errorf(\"TestBothDynamicVarAndEnvVar error occurred %v\", err)\n\t}\n\n\terr = e.Init()\n\tif err != nil {\n\t\tt.Errorf(\"TestBothDynamicVarAndEnvVar error occurred %v\", err)\n\t}\n\n\te.Start()\n\n\tif !requestCalled {\n\t\tt.Errorf(\"TestBothDynamicVarAndEnvVar test server has not been called, url path injection failed\")\n\t}\n\n\tif strings.EqualFold(gotHeaderVal, \"\") {\n\t\tt.Errorf(\"TestBothDynamicVarAndEnvVar dynamic var could not be set, expected a country, got: %s\", \"\")\n\t}\n}\n\nfunc TestDynamicVarAndEnvVarInSameSection(t *testing.T) {\n\tt.Parallel()\n\n\t// Test server\n\trequestCalled := false\n\theaderKey := \"composite\"\n\tvar gotHeaderVal string\n\n\thandler := func(w http.ResponseWriter, r *http.Request) {\n\t\trequestCalled = true\n\t\tgotHeaderVal = r.Header.Get(headerKey)\n\t}\n\n\tpath := \"/xxx\"\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(path, handler)\n\n\tserver := httptest.NewServer(mux)\n\tdefer server.Close()\n\n\t// Prepare\n\th := newDummyHammer()\n\th.Debug = true\n\th.Scenario.Envs = map[string]interface{}{\n\t\t\"A\":             \"B\",\n\t\t\"URL_PATH\":      path,\n\t\t\"COMPOSITE_KEY\": headerKey,\n\t}\n\th.Scenario.Steps[0] = types.ScenarioStep{\n\t\tID:     1,\n\t\tMethod: \"GET\",\n\t\tURL:    server.URL + \"{{URL_PATH}}\",\n\t\tHeaders: map[string]string{\n\t\t\t\"{{COMPOSITE_KEY}}\": \"{{_randomBoolean}}-{{A}}\",\n\t\t},\n\t}\n\n\t// Act\n\tes, err := InitEngineServices(h)\n\te, err := NewEngine(context.TODO(), h, es)\n\tif err != nil {\n\t\tt.Errorf(\"TestDynamicVarAndEnvVarInSameSection error occurred %v\", err)\n\t}\n\n\terr = e.Init()\n\tif err != nil {\n\t\tt.Errorf(\"TestDynamicVarAndEnvVarInSameSection error occurred %v\", err)\n\t}\n\n\te.Start()\n\n\tif !requestCalled {\n\t\tt.Errorf(\"TestDynamicVarAndEnvVarInSameSection test server has not been called, url path injection failed\")\n\t}\n\n\tre := regexp.MustCompile(\"(true|false|)-B\")\n\tif !re.MatchString(gotHeaderVal) {\n\t\tt.Errorf(\"TestDynamicVarAndEnvVarInSameSection gotHeaderVal did not match expected regex, got: %s\", gotHeaderVal)\n\t}\n}\n\nfunc TestLoadRandomInfoFromData(t *testing.T) {\n\tt.Parallel()\n\n\t// Test server\n\trequestCalled := false\n\tkenan := \"Kenan\"\n\tfatih := \"Fatih\"\n\texpectedKenanAge := \"25\"\n\texpectedFatihAge := \"29\"\n\n\tageMap := map[string]string{kenan: \"\", fatih: \"\"}\n\thandler := func(w http.ResponseWriter, r *http.Request) {\n\t\trequestCalled = true\n\t\tkenanAge := r.Header.Get(kenan)\n\t\tfatihAge := r.Header.Get(fatih)\n\t\tif kenanAge != \"\" {\n\t\t\tageMap[kenan] = kenanAge\n\t\t}\n\n\t\tif fatihAge != \"\" {\n\t\t\tageMap[fatih] = fatihAge\n\t\t}\n\t}\n\n\tpath := \"/xxx\"\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(path, handler)\n\n\tserver := httptest.NewServer(mux)\n\tdefer server.Close()\n\n\t// Prepare\n\th := newDummyHammer()\n\tvar csvData types.CsvData\n\tcsvData.Random = false\n\tcsvData.Rows = []map[string]interface{}{{\n\t\t\"name\": kenan,\n\t\t\"age\":  expectedKenanAge,\n\t}, {\n\t\t\"name\": fatih,\n\t\t\"age\":  expectedFatihAge,\n\t}}\n\th.Scenario.Data = map[string]types.CsvData{\"info\": csvData}\n\th.Scenario.Envs = map[string]interface{}{\n\t\t\"A\":        \"B\",\n\t\t\"URL_PATH\": path,\n\t}\n\th.TestDataConf = map[string]types.CsvConf{\n\t\t\"info\": {\n\t\t\tPath:          path,\n\t\t\tDelimiter:     \"\",\n\t\t\tSkipFirstLine: false,\n\t\t\tVars: map[string]types.Tag{\n\t\t\t\t\"0\": {\n\t\t\t\t\tTag:  \"name\",\n\t\t\t\t\tType: \"string\",\n\t\t\t\t},\n\t\t\t\t\"1\": {\n\t\t\t\t\tTag:  \"age\",\n\t\t\t\t\tType: \"string\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tSkipEmptyLine: false,\n\t\t\tAllowQuota:    false,\n\t\t\tOrder:         \"\",\n\t\t},\n\t}\n\th.IterationCount = 2\n\th.Scenario.Steps[0] = types.ScenarioStep{\n\t\tID:     1,\n\t\tMethod: \"GET\",\n\t\tURL:    server.URL + \"{{URL_PATH}}\",\n\t\tHeaders: map[string]string{\n\t\t\t\"{{data.info.name}}\": \"{{data.info.age}}\",\n\t\t},\n\t}\n\n\t// Act\n\tes, err := InitEngineServices(h)\n\te, err := NewEngine(context.TODO(), h, es)\n\tif err != nil {\n\t\tt.Errorf(\"TestLoadRandomInfoFromData error occurred %v\", err)\n\t}\n\n\toriginalReadTestData := readTestData\n\treadTestData = func(testDataConf map[string]types.CsvConf) (map[string]types.CsvData, error) {\n\t\treturn map[string]types.CsvData{\"info\": csvData}, nil\n\t}\n\n\terr = e.Init()\n\tif err != nil {\n\t\tt.Errorf(\"TestLoadRandomInfoFromData error occurred %v\", err)\n\t}\n\n\te.Start()\n\n\treadTestData = originalReadTestData\n\tif !requestCalled {\n\t\tt.Errorf(\"TestLoadRandomInfoFromData test server has not been called, url path injection failed\")\n\t}\n\n\tif ageMap[kenan] != expectedKenanAge || ageMap[fatih] != expectedFatihAge {\n\t\tt.Errorf(\"TestLoadRandomInfoFromData did not match\")\n\t}\n}\n\nfunc TestDataCsv(t *testing.T) {\n\treadConfigFile := func(path string) []byte {\n\t\tf, _ := os.Open(path)\n\n\t\tbyteValue, _ := ioutil.ReadAll(f)\n\t\treturn byteValue\n\t}\n\n\tjsonReader, _ := config.NewConfigReader(readConfigFile(\"../config/config_testdata/config_data_csv.json\"), config.ConfigTypeJson)\n\n\texpectedRandom := true\n\n\th, _ := jsonReader.CreateHammer()\n\n\tdata, err := readTestData(h.TestDataConf)\n\n\tif err != nil {\n\t\tt.Errorf(\"TestDataCsv error occurred: %v\", err)\n\t}\n\n\tcsvData := data[\"info\"]\n\n\tif !reflect.DeepEqual(csvData.Random, expectedRandom) {\n\t\tt.Errorf(\"TestCreateHammerDataCsv got: %t expected: %t\", csvData.Random, expectedRandom)\n\t}\n\n\texpectedRow := map[string]interface{}{\n\t\t\"name\": \"Kenan\",\n\t\t\"city\": \"Tokat\",\n\t\t\"team\": \"Galatasaray\",\n\t\t\"payload\": map[string]interface{}{\n\t\t\t\"data\": map[string]interface{}{\n\t\t\t\t\"profile\": map[string]interface{}{\n\t\t\t\t\t\"name\": \"Kenan\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"age\": 25,\n\t}\n\n\tif !reflect.DeepEqual(expectedRow, csvData.Rows[0]) {\n\t\tt.Errorf(\"TestCreateHammerDataCsv got: %#v expected: %#v\", csvData.Rows[0], expectedRow)\n\t}\n\n}\n\nfunc TestInvalidCsvEnvs(t *testing.T) {\n\treadConfigFile := func(path string) []byte {\n\t\tf, _ := os.Open(path)\n\n\t\tbyteValue, _ := ioutil.ReadAll(f)\n\t\treturn byteValue\n\t}\n\n\tjsonReader, _ := config.NewConfigReader(readConfigFile(\"../config/config_testdata/config_invalid_csv_envs.json\"), config.ConfigTypeJson)\n\n\th, _ := jsonReader.CreateHammer()\n\n\terr := h.Validate()\n\n\tif err == nil {\n\t\tt.Errorf(\"TestInvalidCsvEnvs should be errored\")\n\t}\n}\n\nfunc TestCreateInitialCookiesReturnsErr(t *testing.T) {\n\tt.Parallel()\n\n\t// Prepare\n\th := newDummyHammer()\n\th.CookiesEnabled = true\n\th.Cookies = []types.CustomCookie{\n\t\t{Name: \"test\", Value: \"test\"},\n\t}\n\ttmpFunc := createInitialCookies\n\tcreateInitialCookies = func(cookies []types.CustomCookie) ([]*http.Cookie, error) {\n\t\treturn nil, errors.New(\"test error\")\n\t}\n\tdefer func() { createInitialCookies = tmpFunc }()\n\n\t// Act\n\tes, err := InitEngineServices(h)\n\te, err := NewEngine(context.TODO(), h, es)\n\tif err != nil {\n\t\tt.Errorf(\"TestCreateInitialCookiesReturnsErr error occurred %v\", err)\n\t}\n\n\terr = e.Init()\n\tif err == nil {\n\t\tt.Errorf(\"TestCreateInitialCookiesReturnsErr should be errored\")\n\t}\n}\n\nfunc TestCreateInitialCookies(t *testing.T) {\n\treadConfigFile := func(path string) []byte {\n\t\tf, _ := os.Open(path)\n\n\t\tbyteValue, _ := ioutil.ReadAll(f)\n\t\treturn byteValue\n\t}\n\n\tjsonReader, _ := config.NewConfigReader(readConfigFile(\"../config/config_testdata/config_init_cookies.json\"), config.ConfigTypeJson)\n\n\th, _ := jsonReader.CreateHammer()\n\tinitCookies, err := createInitialCookies(h.Cookies)\n\n\tif err != nil {\n\t\tt.Errorf(\"TestCreateInitialCookies error occurred: %v\", err)\n\t}\n\n\trawExpires := \"Thu, 16 Mar 2023 09:24:02 GMT\"\n\texpires, _ := time.Parse(time.RFC1123, rawExpires)\n\n\texpectedCookie := http.Cookie{\n\t\tName:       \"platform\",\n\t\tValue:      \"web\",\n\t\tPath:       \"/\",\n\t\tDomain:     \"httpbin.ddosify.com\",\n\t\tExpires:    expires,\n\t\tRawExpires: rawExpires,\n\t\tMaxAge:     0,\n\t\tSecure:     false,\n\t\tHttpOnly:   true,\n\n\t\tSameSite: 0,\n\t\tRaw:      \"\",\n\t\tUnparsed: []string{},\n\t}\n\n\tif !reflect.DeepEqual(expectedCookie, *initCookies[0]) {\n\t\tt.Errorf(\"TestCreateInitialCookies got: %v expected: %v\", initCookies[0], expectedCookie)\n\t}\n}\n\n// The test creates a web server with Certificate auth,\n// then it spawns an Engine and verifies that the auth was successfully passsed.\nfunc TestTLSMutualAuth(t *testing.T) {\n\tt.Parallel()\n\n\thandlerCalls := 0\n\n\t// Test server\n\thandler := func(w http.ResponseWriter, r *http.Request) {\n\t\thandlerCalls += 1\n\t}\n\tserver := httptest.NewUnstartedServer(http.HandlerFunc(handler))\n\tdefer server.Close()\n\n\t// prepare TLS files\n\tcert, certKey := generateCerts()\n\tcertFile, keyFile, err := createCertPairFiles(cert, certKey)\n\tif err != nil {\n\t\tt.Errorf(\"Failed to prepare certs %v\", err)\n\t}\n\tdefer os.Remove(certFile.Name())\n\tdefer os.Remove(keyFile.Name())\n\n\t// Prepare\n\th := newDummyHammer()\n\th.Scenario.Steps[0] = types.ScenarioStep{\n\t\tID:     1,\n\t\tMethod: \"GET\",\n\t\tURL:    \"\",\n\t}\n\n\tcertVal, poolVal, err := types.ParseTLS(certFile.Name(), keyFile.Name())\n\tif err != nil {\n\t\tt.Errorf(\"Failed to parse certs %v\", err)\n\t}\n\n\th.Scenario.Steps[0].Cert = certVal\n\th.Scenario.Steps[0].CertPool = poolVal\n\n\tserver.TLS = &tls.Config{\n\t\tClientAuth:   tls.RequireAndVerifyClientCert,\n\t\tClientCAs:    h.Scenario.Steps[0].CertPool,\n\t\tCertificates: []tls.Certificate{h.Scenario.Steps[0].Cert},\n\t}\n\n\tserver.StartTLS()\n\n\th.Scenario.Steps[0].URL = server.URL\n\n\t// Act\n\tes, err := InitEngineServices(h)\n\te, err := NewEngine(context.TODO(), h, es)\n\tif err != nil {\n\t\tt.Errorf(\"TestRequestData error occurred %v\", err)\n\t}\n\n\terr = e.Init()\n\tif err != nil {\n\t\tt.Errorf(\"TestRequestData error occurred %v\", err)\n\t}\n\n\te.Start()\n\n\t// Assert\n\tif handlerCalls == 0 {\n\t\tt.Errorf(\"handler was not called at all: %#v\", handlerCalls)\n\t}\n}\n\n// The test creates a web server with Certificate auth,\n// then it spawns an Engine, but the engine doesn't have a certificate therefore it's expected that no handler is called.\nfunc TestTLSMutualAuthButWeHaveNoCerts(t *testing.T) {\n\tt.Parallel()\n\n\thandlerCalls := 0\n\n\t// Test server\n\thandler := func(w http.ResponseWriter, r *http.Request) {\n\t\thandlerCalls += 1\n\t}\n\tserver := httptest.NewUnstartedServer(http.HandlerFunc(handler))\n\tdefer server.Close()\n\n\t// prepare TLS files\n\tcert, certKey := generateCerts()\n\tcertFile, keyFile, err := createCertPairFiles(cert, certKey)\n\tif err != nil {\n\t\tt.Errorf(\"Failed to prepare certs %v\", err)\n\t}\n\tdefer os.Remove(certFile.Name())\n\tdefer os.Remove(keyFile.Name())\n\n\t// Prepare\n\th := newDummyHammer()\n\th.Scenario.Steps[0] = types.ScenarioStep{\n\t\tID:     1,\n\t\tMethod: \"GET\",\n\t\tURL:    \"\",\n\t}\n\n\tcertVal, poolVal, err := types.ParseTLS(certFile.Name(), keyFile.Name())\n\tif err != nil {\n\t\tt.Errorf(\"Failed to parse certs %v\", err)\n\t}\n\n\th.Scenario.Steps[0].Cert = certVal\n\th.Scenario.Steps[0].CertPool = poolVal\n\n\tserver.TLS = &tls.Config{\n\t\tClientAuth:   tls.RequireAndVerifyClientCert,\n\t\tClientCAs:    h.Scenario.Steps[0].CertPool,\n\t\tCertificates: []tls.Certificate{h.Scenario.Steps[0].Cert},\n\t}\n\n\tserver.StartTLS()\n\n\th.Scenario.Steps[0].URL = server.URL\n\n\t// invalidate the certs\n\th.Scenario.Steps[0].CertPool = nil\n\th.Scenario.Steps[0].Cert = tls.Certificate{}\n\n\tes, err := InitEngineServices(h)\n\te, err := NewEngine(context.TODO(), h, es)\n\tif err != nil {\n\t\tt.Errorf(\"TestRequestData error occurred %v\", err)\n\t}\n\n\terr = e.Init()\n\tif err != nil {\n\t\tt.Errorf(\"TestRequestData error occurred %v\", err)\n\t}\n\n\te.Start()\n\n\tif handlerCalls != 0 {\n\t\tt.Errorf(\"handler was called unexpectedly: %#v\", handlerCalls)\n\t}\n}\n\n// The test creates a web server with Certificate auth,\n// then it spawns an Engine, but the engine have a different certificate therefore it's expected that no handler is called.\nfunc TestTLSMutualAuthButServerAndClientHasDifferentCerts(t *testing.T) {\n\tt.Parallel()\n\n\thandlerCalls := 0\n\n\t// Test server\n\thandler := func(w http.ResponseWriter, r *http.Request) {\n\t\thandlerCalls += 1\n\t}\n\tserver := httptest.NewUnstartedServer(http.HandlerFunc(handler))\n\tdefer server.Close()\n\n\t// prepare TLS files\n\tcert, certKey := generateCerts()\n\tcertFile, keyFile, err := createCertPairFiles(cert, certKey)\n\tif err != nil {\n\t\tt.Errorf(\"Failed to prepare certs %v\", err)\n\t}\n\tdefer os.Remove(certFile.Name())\n\tdefer os.Remove(keyFile.Name())\n\n\t// prepare server TLS files\n\tcert, certKey = generateCerts2()\n\tcertFile2, keyFile2, err := createCertPairFiles(cert, certKey)\n\tif err != nil {\n\t\tt.Errorf(\"Failed to prepare certs %v\", err)\n\t}\n\tdefer os.Remove(certFile2.Name())\n\tdefer os.Remove(keyFile2.Name())\n\n\t// Prepare\n\th := newDummyHammer()\n\th.Scenario.Steps[0] = types.ScenarioStep{ID: 1, Method: \"GET\", URL: \"\"}\n\n\t// here we use server certs first\n\tcertVal, poolVal, err := types.ParseTLS(certFile.Name(), keyFile.Name())\n\tif err != nil {\n\t\tt.Errorf(\"Failed to parse certs %v\", err)\n\t}\n\n\th.Scenario.Steps[0].Cert = certVal\n\th.Scenario.Steps[0].CertPool = poolVal\n\n\tserver.TLS = &tls.Config{\n\t\tClientAuth:   tls.RequireAndVerifyClientCert,\n\t\tClientCAs:    h.Scenario.Steps[0].CertPool,\n\t\tCertificates: []tls.Certificate{h.Scenario.Steps[0].Cert},\n\t}\n\n\tserver.StartTLS()\n\n\th.Scenario.Steps[0].URL = server.URL\n\n\t// here we use different certs\n\t// so the server and client has different pairs\n\tcertVal, poolVal, err = types.ParseTLS(certFile2.Name(), keyFile2.Name())\n\tif err != nil {\n\t\tt.Errorf(\"Failed to parse certs %v\", err)\n\t}\n\n\th.Scenario.Steps[0].Cert = certVal\n\th.Scenario.Steps[0].CertPool = poolVal\n\n\tes, err := InitEngineServices(h)\n\te, err := NewEngine(context.TODO(), h, es)\n\tif err != nil {\n\t\tt.Errorf(\"TestRequestData error occurred %v\", err)\n\t}\n\n\terr = e.Init()\n\tif err != nil {\n\t\tt.Errorf(\"TestRequestData error occurred %v\", err)\n\t}\n\n\te.Start()\n\n\tif handlerCalls != 0 {\n\t\tt.Errorf(\"handler was called unexpectedly: %#v\", handlerCalls)\n\t}\n}\n\nfunc TestEngineModeUserKeepAlive(t *testing.T) {\n\tt.Parallel()\n\t// For DistinctUser and RepeatedUser modes\n\n\t// Test server\n\tclientAddress1 := []string{}\n\tclientAddress2 := []string{}\n\tvar m1 sync.Mutex\n\tvar m2 sync.Mutex\n\n\tfirstReqHandler := func(w http.ResponseWriter, r *http.Request) {\n\t\tm1.Lock()\n\t\tdefer m1.Unlock()\n\n\t\tclientAddress1 = append(clientAddress1, r.RemoteAddr) // network address that sent the request\n\t}\n\n\tsecondReqHandler := func(w http.ResponseWriter, r *http.Request) {\n\t\tm2.Lock()\n\t\tdefer m2.Unlock()\n\n\t\tclientAddress2 = append(clientAddress2, r.RemoteAddr) // network address that sent the request\n\t}\n\n\tpathFirst := \"/first\"\n\tpathSecond := \"/second\"\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(pathFirst, firstReqHandler)\n\tmux.HandleFunc(pathSecond, secondReqHandler)\n\n\thost := httptest.NewServer(mux)\n\tdefer host.Close()\n\n\t// Prepare\n\th := newDummyHammer()\n\th.IterationCount = 2\n\th.Scenario.Steps = make([]types.ScenarioStep, 2)\n\th.Scenario.Steps[0] = types.ScenarioStep{\n\t\tID:     1,\n\t\tMethod: \"GET\",\n\t\tURL:    host.URL + pathFirst,\n\t}\n\th.Scenario.Steps[1] = types.ScenarioStep{\n\t\tID:     2,\n\t\tMethod: \"GET\",\n\t\tURL:    host.URL + pathSecond,\n\t}\n\n\t// Act\n\tes, err := InitEngineServices(h)\n\th.EngineMode = types.EngineModeRepeatedUser // could have been DistinctUser also\n\te, err := NewEngine(context.TODO(), h, es)\n\tif err != nil {\n\t\tt.Errorf(\"TestEngineModeDistinctUserKeepAlive error occurred %v\", err)\n\t}\n\n\terr = e.Init()\n\tif err != nil {\n\t\tt.Errorf(\"TestEngineModeDistinctUserKeepAlive error occurred %v\", err)\n\t}\n\n\te.Start()\n\n\t// same host\n\n\t// check first iter\n\tif clientAddress1[0] != clientAddress2[0] {\n\t\tt.Errorf(\"TestEngineModeDistinctUserKeepAlive, same hosts connection should be same throughout iteration\")\n\t}\n\t// check second iter\n\tif clientAddress1[1] != clientAddress2[1] {\n\t\tt.Errorf(\"TestEngineModeDistinctUserKeepAlive, same hosts connection should be same throughout iteration\")\n\t}\n\n}\n\nfunc TestEngineModeUserKeepAliveDifferentHosts(t *testing.T) {\n\tt.Parallel()\n\t// For DistinctUser and RepeatedUser modes\n\n\t// Test server\n\tclientAddress := make(map[string]struct{})\n\tvar m sync.Mutex\n\n\tfirstReqHandler := func(w http.ResponseWriter, r *http.Request) {\n\t\tm.Lock()\n\t\tdefer m.Unlock()\n\t\tclientAddress[r.RemoteAddr] = struct{}{} // network address that sent the request\n\t}\n\n\tpathFirst := \"/first\"\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(pathFirst, firstReqHandler)\n\n\thost1 := httptest.NewServer(mux)\n\thost2 := httptest.NewServer(mux)\n\n\tdefer host1.Close()\n\tdefer host2.Close()\n\n\t// Prepare\n\th := newDummyHammer()\n\th.IterationCount = 1\n\th.Scenario.Steps = make([]types.ScenarioStep, 4)\n\th.Scenario.Steps[0] = types.ScenarioStep{\n\t\tID:     1,\n\t\tMethod: \"GET\",\n\t\tURL:    host1.URL + pathFirst,\n\t}\n\th.Scenario.Steps[1] = types.ScenarioStep{\n\t\tID:     2,\n\t\tMethod: \"GET\",\n\t\tURL:    host1.URL + pathFirst,\n\t}\n\th.Scenario.Steps[2] = types.ScenarioStep{\n\t\tID:     3,\n\t\tMethod: \"GET\",\n\t\tURL:    host2.URL + pathFirst,\n\t}\n\th.Scenario.Steps[3] = types.ScenarioStep{\n\t\tID:     4,\n\t\tMethod: \"GET\",\n\t\tURL:    host2.URL + pathFirst,\n\t}\n\n\t// Act\n\tes, err := InitEngineServices(h)\n\th.EngineMode = types.EngineModeDistinctUser // could have been RepeatedUser also\n\te, err := NewEngine(context.TODO(), h, es)\n\tif err != nil {\n\t\tt.Errorf(\"TestEngineModeUserKeepAliveDifferentHosts error occurred %v\", err)\n\t}\n\n\terr = e.Init()\n\tif err != nil {\n\t\tt.Errorf(\"TestEngineModeUserKeepAliveDifferentHosts error occurred %v\", err)\n\t}\n\n\te.Start()\n\n\t// one iteration, two hosts, two connections expected\n\tif len(clientAddress) != 2 {\n\t\tt.Errorf(\"TestEngineModeUserKeepAliveDifferentHosts, expected 2 connections, got : %d\", len(clientAddress))\n\t}\n}\n\nfunc TestEngineModeUserKeepAlive_StepsKeepAliveFalse(t *testing.T) {\n\tt.Parallel()\n\t// For DistinctUser and RepeatedUser modes\n\t// Test server\n\tclientAddress := make(map[string]struct{})\n\tvar m sync.Mutex\n\n\tfirstReqHandler := func(w http.ResponseWriter, r *http.Request) {\n\t\tm.Lock()\n\t\tdefer m.Unlock()\n\t\tclientAddress[r.RemoteAddr] = struct{}{} // network address that sent the request\n\t}\n\n\tpathFirst := \"/first\"\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(pathFirst, firstReqHandler)\n\n\thost1 := httptest.NewServer(mux)\n\n\tdefer host1.Close()\n\n\t// Prepare\n\th := newDummyHammer()\n\th.IterationCount = 1\n\th.Scenario.Steps = make([]types.ScenarioStep, 4)\n\t// connection opened by 1 will not be reused\n\th.Scenario.Steps[0] = types.ScenarioStep{\n\t\tID:      1,\n\t\tMethod:  \"GET\",\n\t\tURL:     host1.URL + pathFirst,\n\t\tHeaders: map[string]string{\"Connection\": \"close\"},\n\t}\n\t// below will use the connection opened by 2\n\th.Scenario.Steps[1] = types.ScenarioStep{\n\t\tID:     2,\n\t\tMethod: \"GET\",\n\t\tURL:    host1.URL + pathFirst,\n\t}\n\th.Scenario.Steps[2] = types.ScenarioStep{\n\t\tID:     3,\n\t\tMethod: \"GET\",\n\t\tURL:    host1.URL + pathFirst,\n\t}\n\th.Scenario.Steps[3] = types.ScenarioStep{\n\t\tID:     4,\n\t\tMethod: \"GET\",\n\t\tURL:    host1.URL + pathFirst,\n\t}\n\n\t// Act\n\th.EngineMode = types.EngineModeDistinctUser\n\tes, err := InitEngineServices(h)\n\te, err := NewEngine(context.TODO(), h, es)\n\tif err != nil {\n\t\tt.Errorf(\"TestEngineModeUserKeepAliveDifferentHosts error occurred %v\", err)\n\t}\n\n\terr = e.Init()\n\tif err != nil {\n\t\tt.Errorf(\"TestEngineModeUserKeepAliveDifferentHosts error occurred %v\", err)\n\t}\n\n\te.Start()\n\n\t// one iteration, one host, 4 steps, one's keep-alive is false (Connection: close)\n\tif len(clientAddress) != 2 {\n\t\tt.Errorf(\"TestEngineModeUserKeepAliveDifferentHosts, expected 2 connections, got : %d\", len(clientAddress))\n\t}\n\n}\n\nfunc TestEngineModeDdosifyKeepAlive(t *testing.T) {\n\tt.Parallel()\n\n\t// Test server\n\tclientAddress1 := []string{}\n\tclientAddress2 := []string{}\n\tvar m1 sync.Mutex\n\tvar m2 sync.Mutex\n\n\tfirstReqHandler := func(w http.ResponseWriter, r *http.Request) {\n\t\tm1.Lock()\n\t\tdefer m1.Unlock()\n\n\t\tclientAddress1 = append(clientAddress1, r.RemoteAddr) // network address that sent the request\n\t}\n\n\tsecondReqHandler := func(w http.ResponseWriter, r *http.Request) {\n\t\tm2.Lock()\n\t\tdefer m2.Unlock()\n\n\t\tclientAddress2 = append(clientAddress2, r.RemoteAddr) // network address that sent the request\n\t}\n\n\tpathFirst := \"/first\"\n\tpathSecond := \"/second\"\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(pathFirst, firstReqHandler)\n\tmux.HandleFunc(pathSecond, secondReqHandler)\n\n\thost := httptest.NewServer(mux)\n\tdefer host.Close()\n\n\t// Prepare\n\th := newDummyHammer()\n\th.IterationCount = 2\n\th.Scenario.Steps = make([]types.ScenarioStep, 2)\n\th.Scenario.Steps[0] = types.ScenarioStep{\n\t\tID:     1,\n\t\tMethod: \"GET\",\n\t\tURL:    host.URL + pathFirst,\n\t}\n\th.Scenario.Steps[1] = types.ScenarioStep{\n\t\tID:     2,\n\t\tMethod: \"GET\",\n\t\tURL:    host.URL + pathSecond,\n\t}\n\n\t// Act\n\th.EngineMode = types.EngineModeDdosify\n\tes, err := InitEngineServices(h)\n\te, err := NewEngine(context.TODO(), h, es)\n\tif err != nil {\n\t\tt.Errorf(\"TestEngineModeDdosifyKeepAlive error occurred %v\", err)\n\t}\n\n\terr = e.Init()\n\tif err != nil {\n\t\tt.Errorf(\"TestEngineModeDdosifyKeepAlive error occurred %v\", err)\n\t}\n\n\te.Start()\n\n\t// same host\n\t// in ddosify mode every step has its own client, therefore connections should be different\n\t// check first iter\n\tif clientAddress1[0] == clientAddress2[0] {\n\t\tt.Errorf(\"TestEngineModeDistinctUserKeepAlive, \")\n\t}\n\t// check second iter\n\tif clientAddress1[1] == clientAddress2[1] {\n\t\tt.Errorf(\"TestEngineModeDistinctUserKeepAlive, \")\n\t}\n\n}\nfunc createCertPairFiles(cert string, certKey string) (*os.File, *os.File, error) {\n\tcertFile, err := os.CreateTemp(\"\", \".pem\")\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\t_, err = io.WriteString(certFile, cert)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tkeyFile, err := os.CreateTemp(\"\", \".pem\")\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\t_, err = io.WriteString(keyFile, certKey)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\treturn certFile, keyFile, nil\n}\n\nfunc generateCerts() (string, string) {\n\tcert := `-----BEGIN CERTIFICATE-----\nMIIDazCCAlOgAwIBAgIUS4UhTks8aRCQ1k9IGn437ZyP3MgwDQYJKoZIhvcNAQEL\nBQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMjEwMDUyMjM5MDVaFw0zMjEw\nMDIyMjM5MDVaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB\nAQUAA4IBDwAwggEKAoIBAQDMbZctKXBx8v63TXIhM/OB7S6VfPqpzfHufhs6kAHu\njfC2ooCUqzqdg0T8bM1bjahYuAbQA1cWKYBsqfd01Po1ltWmbMf7ZvmSB6VN7kC2\nY670zee91dGDQ2yzmorJuIZAtOBVZesYLg8UHSGzSC/smJOrjYidtlbvzOcX0pv3\nRCIUrNMed60EpSch/rzAJLzJmwNSQZ4vJHNlNetSkvTi7cxMWfwpcM/rN1hEmP1X\nJ43hJp/TNRZVnEsvs/yggP/FwUjG74mU3KfnWiv91AkkarNTNquEMJ+f4OFqMcnF\np0wqg47JTqcAAT0n1B0VB+z0hGXEFMN+IJXsHETZNG+JAgMBAAGjUzBRMB0GA1Ud\nDgQWBBSIw+qUKQJjXWti5x/Cnn2GueuX5zAfBgNVHSMEGDAWgBSIw+qUKQJjXWti\n5x/Cnn2GueuX5zAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAA\nDXzf8VXi4s2GScNfHf0BzMjpyrtRZ0Wbp2Vfh7OwVR6xcx+pqXNjlydM/vu2LvOK\nhh7Jbo+JS+o7O24UJ9lLFkCRsZVF+NFqJf+2rdHCaOiZSdZmtjBU0dFuAGS7+lU3\nM8P7WCNOm6NAKbs7VZHVcZPzp81SCPQgQIS19xRf4Irbvsijv4YdyL4Qv7aWcclb\nMdZX9AH9Fx8tJq4VKvUYsCXAD0kuywMLjh+yj5O/2hMvs5rvaQvm2daQNRDNp884\nuTLrNF7W7QaKEL06ZpXJoBqdKsiwn577XTDKvzN0XxQrT+xV9VHO7OXblF+Od3/Y\nSzBR+QiQKy3x+LkOxhkk\n-----END CERTIFICATE-----`\n\n\tcertKey := `-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDMbZctKXBx8v63\nTXIhM/OB7S6VfPqpzfHufhs6kAHujfC2ooCUqzqdg0T8bM1bjahYuAbQA1cWKYBs\nqfd01Po1ltWmbMf7ZvmSB6VN7kC2Y670zee91dGDQ2yzmorJuIZAtOBVZesYLg8U\nHSGzSC/smJOrjYidtlbvzOcX0pv3RCIUrNMed60EpSch/rzAJLzJmwNSQZ4vJHNl\nNetSkvTi7cxMWfwpcM/rN1hEmP1XJ43hJp/TNRZVnEsvs/yggP/FwUjG74mU3Kfn\nWiv91AkkarNTNquEMJ+f4OFqMcnFp0wqg47JTqcAAT0n1B0VB+z0hGXEFMN+IJXs\nHETZNG+JAgMBAAECggEAM+U6NHfJmNPD/8qER5OFpJ0Ob1qL06F5Yj7XMLWwF9wm\nmGaGV7dkKOpTD/Wa6Dv82ZDWAeZnLDQa6vr228zZO9Nvp1EEL3kDsCOKvk7WVLbX\nikPfKZznE/iA1tNLmkvioPiJ3oQB+2Bt6YA/tuCDcf+FtU43uTm5tiSBIdYQS+Om\nxN9OEXihk1svxHXQKa/a3nKPVLvdp3P90hDJ0PcRslXSy1V8az+A94JFEnCvnKsK\nnF2rItCcXkInL0lYHZKgLHQMXGWkNl8e3PA1GZk3yF6LPNtPI1T5Ek9GwkHNw4JZ\nBL/xEWLKB1qR2Z4I3UbWGVyi418kANv1eISb+49egQKBgQDraSRWB8nM5O3Zl9kT\n8S5K924o1oXrO17eqQlVtQVmtUdoVvIBc6uHQZOmV1eHYpr6c95h8apNLexI22AY\nSWkq9smpCnxLUsdkplwzie0F4bAzD6MCR8WIJxapUSPlyCA+8st1hquYBchKGQhd\n6mMY1gzMDacYV/WhtG4E5d0nMQKBgQDeTr793n00VtpKuquFJe6Stu7Ujf64dL0s\n3opLovyI0TmtMz5oCqIezwrjqc0Vy0UksWXaz0AboinDP+5n60cTEIt/6H0kryDc\ndxfSHEA9BBDoQtxOFi3QGcxXbwu0i9QSoexrKY7FhA2xPji6bCcPycthhIrCpUiZ\ns5gVkjHn2QKBgQCGklxLMbiSgGvXb46Qb9be1AMNJVT427+n2UmUzR6BUC+53boK\nSm1LrJkTBerrYdrmQUZnBxcrd40TORT9zTlpbhppn6zeAjwptVAPxlDQg+uNxOqS\nayToaC/0KoYy3OxSD8lvLcT56pRMh3LY/RwZHoPCQiu7Js0r21DpS93YgQKBgAuc\nc09RMprsOmSS0WiX7ZkOIvVJIVfDCSpxySlgLu56dxe7yHOosoUHbVsswEB2KHtd\nJKPEFWYcFzBSg4I8AK9XOuIIY5jp6L57Hexke1p0fumSrG0LrYLkBg8/Bo58iywZ\n9v414nYgipKKXG4oPfYOJShHwvOdrGgSwEvIIgEpAoGAZz0yC9+x+JaoTnyUIRyI\n+Aj5a4KhYjFtsZhcn/yCZHDqzJNDz6gAu579ey+J2CVOhjtgB5lowsDrHu32Hqnn\nSEfyTru/ynQ8obwaRzdDYml+On86YWOw+brpMXkN+KB6bs2okE2N68v0qGPakxjt\nOLDW6kKz5pI4T8lQJhdqjCU=\n-----END PRIVATE KEY-----`\n\n\treturn cert, certKey\n}\n\nfunc generateCerts2() (string, string) {\n\tcert := `-----BEGIN CERTIFICATE-----\nMIIDazCCAlOgAwIBAgIUSun8oI56ArKxfhqNLLfEmteRHRUwDQYJKoZIhvcNAQEL\nBQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMjEwMDYyMTE1NDdaFw0zMjEw\nMDMyMTE1NDdaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB\nAQUAA4IBDwAwggEKAoIBAQC63U6N03rm4I8yFmYK27DUlVdMUGSRQt6UIdPT5F2c\nfv5mBRLwEANoqscNenNajGHiIqBiFQ3pG+p7BIIq11d87Of24XSll4MoK+6R9SFF\n6lTdGt9HSzuCXQtMf5g6/MbgH240xrBXmwwJNkqpUzXVOeQBPzxplf1b/0ircf8n\nfE81wnCtWyiu8BtlWvs/yJBTvSiIQ6w2Tp+K5oFZLCUwgQZdUcqzXp5nbWZkdO+D\nhOGdiY7G+fC19GX7lVt+kw+xB/uAqmXw2WoR/Db/M8tJDzTw810ZbWp0tAw7Pga+\nybvIYN9mTFr4Tm052r2jVXAYejf8z4kdr4mCDKlSQTIlAgMBAAGjUzBRMB0GA1Ud\nDgQWBBRWchX65rXlT+/xlgxhKMTX5/FdtTAfBgNVHSMEGDAWgBRWchX65rXlT+/x\nlgxhKMTX5/FdtTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCo\nI3MOkAULOaa4Vr80lVn/kZi8HIwQ1NenqyoykqO/FDS7q5o5vaeNquqOgC4scTdb\nWJEgzBNpbIOxEM6ou5Q7IUlX6YZaTMK/Z0QbqjZuHA5ny8uaUERDLoDit318yNe+\n0TOY5m5n+pRkFPvjnqoNNxvYabUqQ7NpgKTv277eecfGdFPi971EiT9HSUM8n7tU\n1C1FNr7P1WGmng2EO1UCG3SQi1JpMGUYyFLSOP6F7wWhflO1JqdF57nmTtv8lKJ9\nO4ACJ5BuWUqUyDLYjMK+oHh/c6xLHxfQKs62HuLqfaobqUPyE0kS7LXN2G7adjrs\n2vBHv2U/QrjmLLF8CSdh\n-----END CERTIFICATE-----`\n\n\tcertKey := `-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC63U6N03rm4I8y\nFmYK27DUlVdMUGSRQt6UIdPT5F2cfv5mBRLwEANoqscNenNajGHiIqBiFQ3pG+p7\nBIIq11d87Of24XSll4MoK+6R9SFF6lTdGt9HSzuCXQtMf5g6/MbgH240xrBXmwwJ\nNkqpUzXVOeQBPzxplf1b/0ircf8nfE81wnCtWyiu8BtlWvs/yJBTvSiIQ6w2Tp+K\n5oFZLCUwgQZdUcqzXp5nbWZkdO+DhOGdiY7G+fC19GX7lVt+kw+xB/uAqmXw2WoR\n/Db/M8tJDzTw810ZbWp0tAw7Pga+ybvIYN9mTFr4Tm052r2jVXAYejf8z4kdr4mC\nDKlSQTIlAgMBAAECggEANaE4X1n3pvWCA3UMOkeM+6YU1PEpu8r+SHNg8SpUd4q3\nBp6kLcPaxppk4IhpPO6XVShs8VlrkaCSblX/6b29/Tuc420XZkMSwF/Da553uzIi\nwwZoWHTOEn8TtBPWo+9SQJaksX7os2vrS2WKjgg0pgqkVntIomEKwvGEcLgZ68Gy\naCYgrJfvzS38+XhOJB00YOoq6vgqHj8YnTGtYAwwW+nI7oHGJS7H09eQV51cmQ2j\nNSmc0SsGJ/IYrCMfJp0W8Ho9z66qRiFLb7vFS1050r5r3+slHCZPQwYXY6ovo2EJ\n2Y5mKdem70dP8JZx6siVlOCKh/2fHOFNnegcQ/ADgQKBgQDx1ueRb7w9a/lh0PPN\n8tLvclN/BJCqVoaF31f+Ah9Q7bfagkI7kmaQfYChWPLM5mXwr8YCPM1jysQOUTJp\nExBkGbngv/M0JeXSyt2Z9kbreFSll+ILnImAME+0KKjHTy1gDSvqX/a4NiZdDOaK\n44r4CZSeVrpH2YY4tq/huL68xQKBgQDFzlhPEYOxTnQytPuXWRTtB5is1WNs7cU0\nAKVGkqgNKj5++Jl+IT3/pDhcJXe06E1V9ldHFpwAorkbIvAEE45aqzp5ZrrlrAjJ\n06wmEEgP5tQxmBj+hx6jitzDoEmqHvyN5Dm8/Kxu2VF2n4yTGEeSX+ep1ojLCeAj\nheJuuO614QKBgGV+O1DeA7IDTnWuq6MS9VNoN4Jm+A+EoJAuW09OtLXSDga2A/Xc\nSw74nLMaEUvMpZuNKRxnSAtJXV5k1TMjvQ1FfqzD4d1QylLcsIOcx8aqiVu1kjgt\nScdyfwCsz6hVokVdQcDq5TAKCa+jal1/gSL3YlfRLfxZXesPQGEKl4HBAoGBALOw\nBMye7nDNAgVmHv6Xr8i6k9i9Z7p2LCRXScxYQUzkSS1yi4zmibmG5qPebWXreQVT\n6Gjtgv2Y1GpwTHSHh1OaJF5QEgu9QaaGIOXa+Htphu0ea+YbvJt385/KJeDikS4c\nWs7xAXsY80W9HigpcCrp8Dp6Zn17FR9v6ggG+uJBAoGAFGo7X1bpEA1bKAA04wJL\ngq6wwKgTUjqnvHSo1CqPqoWeX8MM0VU9Jw2n0bxfD5He/snYO4pQUatD90kcgQch\nBmvE1yTn4kzC0ZO3++qPulpXpAp4QJLIdKeAE9cPhKqe4lBboJRbJqoXCaoIxNeg\nz0xcfR+tEmGlvxaHqXlQg9o=\n-----END PRIVATE KEY-----`\n\n\treturn cert, certKey\n}\n"
  },
  {
    "path": "ddosify_engine/core/proxy/base.go",
    "content": "/*\n*\n*\tDdosify - Load testing tool for any web system.\n*   Copyright (C) 2021  Ddosify (https://ddosify.com)\n*\n*   This program is free software: you can redistribute it and/or modify\n*   it under the terms of the GNU Affero General Public License as published\n*   by the Free Software Foundation, either version 3 of the License, or\n*   (at your option) any later version.\n*\n*   This program is distributed in the hope that it will be useful,\n*   but WITHOUT ANY WARRANTY; without even the implied warranty of\n*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n*   GNU Affero General Public License for more details.\n*\n*   You should have received a copy of the GNU Affero General Public License\n*   along with this program.  If not, see <https://www.gnu.org/licenses/>.\n*\n */\n\npackage proxy\n\nimport (\n\t\"fmt\"\n\t\"net/url\"\n\t\"reflect\"\n)\n\nvar AvailableProxyServices = make(map[string]ProxyService)\n\n// Proxy struct is used for initializing the ProxyService implementations.\ntype Proxy struct {\n\t// Stragy of the proxy usage.\n\tStrategy string\n\n\t// Set this field if ProxyStrategy is single\n\tAddr *url.URL\n\n\t// Dynamic field for other proxy strategies.\n\tOthers map[string]interface{}\n}\n\n// ProvideService is the interface that abstracts different proxy implementations.\n// Strategy field in types.Proxy determines which implementation to use.\ntype ProxyService interface {\n\tInit(Proxy) error\n\tGetAll() []*url.URL\n\tGetProxy() *url.URL\n\tReportProxy(addr *url.URL, reason string) *url.URL\n\tGetProxyCountry(*url.URL) string\n\tDone() error\n}\n\n// NewProxyService is the factory method of the ProxyService.\nfunc NewProxyService(s string) (service ProxyService, err error) {\n\tif val, ok := AvailableProxyServices[s]; ok {\n\t\t// Create a new object from the service type\n\t\tservice = reflect.New(reflect.TypeOf(val).Elem()).Interface().(ProxyService)\n\t} else {\n\t\terr = fmt.Errorf(\"unsupported proxy strategy: %s\", s)\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "ddosify_engine/core/proxy/base_test.go",
    "content": "/*\n*\n*\tDdosify - Load testing tool for any web system.\n*   Copyright (C) 2021  Ddosify (https://ddosify.com)\n*\n*   This program is free software: you can redistribute it and/or modify\n*   it under the terms of the GNU Affero General Public License as published\n*   by the Free Software Foundation, either version 3 of the License, or\n*   (at your option) any later version.\n*\n*   This program is distributed in the hope that it will be useful,\n*   but WITHOUT ANY WARRANTY; without even the implied warranty of\n*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n*   GNU Affero General Public License for more details.\n*\n*   You should have received a copy of the GNU Affero General Public License\n*   along with this program.  If not, see <https://www.gnu.org/licenses/>.\n*\n */\n\npackage proxy\n\nimport (\n\t\"testing\"\n)\n\nfunc TestNewProxyService(t *testing.T) {\n\n\t// Valid proxy types\n\tfor k := range AvailableProxyServices {\n\t\t_, err := NewProxyService(k)\n\n\t\tif err != nil {\n\t\t\tt.Errorf(\"TestNewProxyService errored %v\", err)\n\t\t}\n\n\t}\n\n\t// Invalid proxy type\n\t_, err := NewProxyService(\"invalid_proxy_type\")\n\tif err == nil {\n\t\tt.Errorf(\"TestNewProxyService invalid proxy should errored\")\n\t}\n}\n"
  },
  {
    "path": "ddosify_engine/core/proxy/single.go",
    "content": "/*\n*\n*\tDdosify - Load testing tool for any web system.\n*   Copyright (C) 2021  Ddosify (https://ddosify.com)\n*\n*   This program is free software: you can redistribute it and/or modify\n*   it under the terms of the GNU Affero General Public License as published\n*   by the Free Software Foundation, either version 3 of the License, or\n*   (at your option) any later version.\n*\n*   This program is distributed in the hope that it will be useful,\n*   but WITHOUT ANY WARRANTY; without even the implied warranty of\n*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n*   GNU Affero General Public License for more details.\n*\n*   You should have received a copy of the GNU Affero General Public License\n*   along with this program.  If not, see <https://www.gnu.org/licenses/>.\n*\n */\n\npackage proxy\n\nimport (\n\t\"net/url\"\n)\n\nconst ProxyTypeSingle = \"single\"\n\nfunc init() {\n\tAvailableProxyServices[ProxyTypeSingle] = &singleProxyStrategy{}\n}\n\ntype singleProxyStrategy struct {\n\tproxyAddr *url.URL\n}\n\nfunc (sp *singleProxyStrategy) Init(p Proxy) error {\n\tsp.proxyAddr = p.Addr\n\treturn nil\n}\n\n// Since there is a 1 proxy, return that always\nfunc (sp *singleProxyStrategy) GetAll() []*url.URL {\n\treturn []*url.URL{sp.proxyAddr}\n}\n\n// Since there is a 1 proxy, return that always\nfunc (sp *singleProxyStrategy) GetProxy() *url.URL {\n\treturn sp.proxyAddr\n}\n\nfunc (sp *singleProxyStrategy) ReportProxy(addr *url.URL, reason string) *url.URL {\n\treturn sp.proxyAddr\n}\n\nfunc (sp *singleProxyStrategy) GetProxyCountry(addr *url.URL) string {\n\treturn \"unknown\"\n}\n\nfunc (sp *singleProxyStrategy) Done() error {\n\treturn nil\n}\n"
  },
  {
    "path": "ddosify_engine/core/report/aggregator.go",
    "content": "/*\n*\n*\tDdosify - Load testing tool for any web system.\n*   Copyright (C) 2021  Ddosify (https://ddosify.com)\n*\n*   This program is free software: you can redistribute it and/or modify\n*   it under the terms of the GNU Affero General Public License as published\n*   by the Free Software Foundation, either version 3 of the License, or\n*   (at your option) any later version.\n*\n*   This program is distributed in the hope that it will be useful,\n*   but WITHOUT ANY WARRANTY; without even the implied warranty of\n*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n*   GNU Affero General Public License for more details.\n*\n*   You should have received a copy of the GNU Affero General Public License\n*   along with this program.  If not, see <https://www.gnu.org/licenses/>.\n*\n */\n\npackage report\n\nimport (\n\t\"strings\"\n\t\"time\"\n\n\t\"go.ddosify.com/ddosify/core/assertion\"\n\t\"go.ddosify.com/ddosify/core/types\"\n)\n\nfunc aggregate(result *Result, scr *types.ScenarioResult, samplingCount map[uint16]map[string]int, samplingRate int) {\n\tvar scenarioDuration float32\n\terrOccured := false\n\tassertionFail := false\n\tfor _, sr := range scr.StepResults {\n\t\tscenarioDuration += float32(sr.Duration.Seconds())\n\n\t\tfv := FailVerbose{}\n\t\tfv.AssertionErrorDist.Conditions = make(map[string]*AssertInfo)\n\t\tfv.ServerErrorDist.Reasons = make(map[string]int)\n\n\t\tif _, ok := result.StepResults[sr.StepID]; !ok {\n\t\t\tresult.StepResults[sr.StepID] = &ScenarioStepResultSummary{\n\t\t\t\tName:           sr.StepName,\n\t\t\t\tStatusCodeDist: make(map[int]int, 0),\n\t\t\t\tFail:           fv,\n\t\t\t\tDurations:      map[string]float32{},\n\t\t\t\tSuccessCount:   0,\n\t\t\t}\n\t\t}\n\t\tstepResult := result.StepResults[sr.StepID]\n\n\t\tif len(sr.FailedAssertions) > 0 { // assertion error\n\t\t\terrOccured = true\n\t\t\tassertionFail = true\n\t\t\tstepResult.Fail.Count++\n\t\t\tstepResult.Fail.AssertionErrorDist.Count++\n\t\t\tstepResult.StatusCodeDist[sr.StatusCode]++\n\t\t\tfor _, fa := range sr.FailedAssertions {\n\t\t\t\tif aed, ok := stepResult.Fail.AssertionErrorDist.Conditions[fa.Rule]; !ok {\n\t\t\t\t\tsamplingCount[sr.StepID] = make(map[string]int)\n\t\t\t\t\tsamplingCount[sr.StepID][fa.Rule] = 1\n\t\t\t\t\tae := &AssertInfo{\n\t\t\t\t\t\tCount:    1,\n\t\t\t\t\t\tReceived: make(map[string][]interface{}),\n\t\t\t\t\t\tReason:   fa.Reason,\n\t\t\t\t\t}\n\n\t\t\t\t\tfor ident, value := range fa.Received {\n\t\t\t\t\t\tae.Received[ident] = []interface{}{value}\n\t\t\t\t\t}\n\n\t\t\t\t\tstepResult.Fail.AssertionErrorDist.Conditions[fa.Rule] = ae\n\t\t\t\t} else {\n\t\t\t\t\taed.Count++\n\t\t\t\t\tsamplingCount[sr.StepID][fa.Rule]++\n\t\t\t\t\tif samplingCount[sr.StepID][fa.Rule] <= samplingRate {\n\n\t\t\t\t\t\tfor ident, value := range fa.Received {\n\t\t\t\t\t\t\t// do not append if the value is already in the list\n\t\t\t\t\t\t\texists := false\n\t\t\t\t\t\t\tfor _, v := range aed.Received[ident] {\n\t\t\t\t\t\t\t\tif v == value {\n\t\t\t\t\t\t\t\t\texists = true\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif !exists {\n\t\t\t\t\t\t\t\taed.Received[ident] = append(aed.Received[ident], value)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\ttotalDur := float32(stepResult.SuccessCount+stepResult.Fail.Count-1)*stepResult.Durations[\"duration\"] + float32(sr.Duration.Seconds())\n\t\t\tstepResult.Durations[\"duration\"] = totalDur / float32(stepResult.SuccessCount+stepResult.Fail.Count)\n\t\t\tfor k, v := range sr.Custom {\n\t\t\t\tif strings.Contains(k, \"Duration\") {\n\t\t\t\t\ttotalDur := float32(stepResult.SuccessCount+stepResult.Fail.Count-1)*stepResult.Durations[k] + float32(v.(time.Duration).Seconds())\n\t\t\t\t\tstepResult.Durations[k] = float32(totalDur / float32(stepResult.SuccessCount+stepResult.Fail.Count))\n\t\t\t\t}\n\t\t\t}\n\t\t} else if sr.Err.Type != \"\" { // server error\n\t\t\terrOccured = true\n\t\t\tstepResult.Fail.Count++\n\t\t\tstepResult.Fail.ServerErrorDist.Count++\n\t\t\tstepResult.Fail.ServerErrorDist.Reasons[sr.Err.Reason]++\n\t\t} else { // success\n\t\t\tstepResult.StatusCodeDist[sr.StatusCode]++\n\t\t\tstepResult.SuccessCount++\n\n\t\t\ttotalDur := float32(stepResult.SuccessCount+stepResult.Fail.Count-1)*stepResult.Durations[\"duration\"] + float32(sr.Duration.Seconds())\n\t\t\tstepResult.Durations[\"duration\"] = totalDur / float32(stepResult.SuccessCount+stepResult.Fail.Count)\n\t\t\tfor k, v := range sr.Custom {\n\t\t\t\tif strings.Contains(k, \"Duration\") {\n\t\t\t\t\ttotalDur := float32(stepResult.SuccessCount-1)*stepResult.Durations[k] + float32(v.(time.Duration).Seconds())\n\t\t\t\t\tstepResult.Durations[k] = float32(totalDur / float32(stepResult.SuccessCount+stepResult.Fail.Count))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t}\n\n\t// Don't change avg duration if there is a error\n\tif !errOccured {\n\t\ttotalDuration := float32(result.SuccessCount)*result.AvgDuration + scenarioDuration\n\t\tresult.SuccessCount++\n\t\tresult.AvgDuration = totalDuration / float32(result.SuccessCount)\n\t} else if errOccured {\n\t\tif assertionFail { // if any step failed because of assertion, that iteration counts as assertion fail\n\t\t\tresult.AssertionFailCount++\n\t\t} else { // server error\n\t\t\tresult.ServerFailedCount++\n\t\t}\n\t}\n}\n\n// Total test result, all scenario iterations combined\ntype Result struct {\n\tTestStatus           string                                `json:\"test_status\"`\n\tTestFailedAssertions []assertion.FailedRule                `json:\"failed_criterias\"`\n\tSuccessCount         int64                                 `json:\"success_count\"`\n\tServerFailedCount    int64                                 `json:\"server_fail_count\"`\n\tAssertionFailCount   int64                                 `json:\"assertion_fail_count\"`\n\tAvgDuration          float32                               `json:\"avg_duration\"`\n\tStepResults          map[uint16]*ScenarioStepResultSummary `json:\"steps\"`\n}\n\nfunc (r *Result) successPercentage() int {\n\tif r.SuccessCount+r.ServerFailedCount+r.AssertionFailCount == 0 {\n\t\treturn 0\n\t}\n\tt := float32(r.SuccessCount) / float32(r.SuccessCount+r.ServerFailedCount+r.AssertionFailCount)\n\treturn int(t * 100)\n}\n\nfunc (r *Result) failedPercentage() int {\n\tif r.SuccessCount+r.ServerFailedCount+r.AssertionFailCount == 0 {\n\t\treturn 0\n\t}\n\treturn 100 - r.successPercentage()\n}\n\ntype AssertionErrVerbose struct {\n\tCount      int64                  `json:\"count\"`\n\tConditions map[string]*AssertInfo `json:\"conditions\"`\n}\n\ntype ServerErrVerbose struct {\n\tCount   int64          `json:\"count\"`\n\tReasons map[string]int `json:\"reasons\"`\n}\n\ntype FailVerbose struct {\n\tCount              int64               `json:\"count\"`\n\tAssertionErrorDist AssertionErrVerbose `json:\"assertions\"`\n\tServerErrorDist    ServerErrVerbose    `json:\"server\"`\n}\n\ntype ScenarioStepResultSummary struct {\n\tName           string             `json:\"name\"`\n\tStatusCodeDist map[int]int        `json:\"status_code_dist\"`\n\tFail           FailVerbose        `json:\"fail\"`\n\tDurations      map[string]float32 `json:\"durations\"`\n\tSuccessCount   int64              `json:\"success_count\"`\n}\n\nfunc (s *ScenarioStepResultSummary) successPercentage() int {\n\tif s.SuccessCount+s.Fail.Count == 0 {\n\t\treturn 0\n\t}\n\tt := float32(s.SuccessCount) / float32(s.SuccessCount+s.Fail.Count)\n\treturn int(t * 100)\n}\n\nfunc (s *ScenarioStepResultSummary) failedPercentage() int {\n\tif s.SuccessCount+s.Fail.Count == 0 {\n\t\treturn 0\n\t}\n\treturn 100 - s.successPercentage()\n}\n\ntype AssertInfo struct {\n\tCount    int\n\tReceived map[string][]interface{}\n\tReason   string\n}\n"
  },
  {
    "path": "ddosify_engine/core/report/aggregator_test.go",
    "content": "package report\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n\n\t\"go.ddosify.com/ddosify/core/types\"\n)\n\nfunc TestStart(t *testing.T) {\n\tresponses := []*types.ScenarioResult{\n\t\t{\n\t\t\tStartTime: time.Now(),\n\t\t\tStepResults: []*types.ScenarioStepResult{\n\t\t\t\t{\n\t\t\t\t\tStepID:      1,\n\t\t\t\t\tStatusCode:  200,\n\t\t\t\t\tRequestTime: time.Now().Add(1),\n\t\t\t\t\tDuration:    time.Duration(10) * time.Second,\n\t\t\t\t\tCustom: map[string]interface{}{\n\t\t\t\t\t\t\"dnsDuration\":  time.Duration(5) * time.Second,\n\t\t\t\t\t\t\"connDuration\": time.Duration(5) * time.Second,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tStepID:      2,\n\t\t\t\t\tRequestTime: time.Now().Add(2),\n\t\t\t\t\tDuration:    time.Duration(30) * time.Second,\n\t\t\t\t\tErr:         types.RequestError{Type: types.ErrorConn, Reason: types.ReasonConnTimeout},\n\t\t\t\t\tCustom: map[string]interface{}{\n\t\t\t\t\t\t\"dnsDuration\":  time.Duration(10) * time.Second,\n\t\t\t\t\t\t\"connDuration\": time.Duration(20) * time.Second,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tStepID:      3,\n\t\t\t\t\tStatusCode:  400,\n\t\t\t\t\tRequestTime: time.Now().Add(2),\n\t\t\t\t\tDuration:    time.Duration(30) * time.Second,\n\t\t\t\t\tCustom: map[string]interface{}{\n\t\t\t\t\t\t\"dnsDuration\":  time.Duration(10) * time.Second,\n\t\t\t\t\t\t\"connDuration\": time.Duration(20) * time.Second,\n\t\t\t\t\t},\n\t\t\t\t\tFailedAssertions: []types.FailedAssertion{{\n\t\t\t\t\t\tRule:     \"equals(status_code,200)\",\n\t\t\t\t\t\tReceived: map[string]interface{}{\"status_code\": 400},\n\t\t\t\t\t}},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tStartTime: time.Now().Add(10),\n\t\t\tStepResults: []*types.ScenarioStepResult{\n\t\t\t\t{\n\t\t\t\t\tStepID:      1,\n\t\t\t\t\tStatusCode:  200,\n\t\t\t\t\tRequestTime: time.Now().Add(11),\n\t\t\t\t\tDuration:    time.Duration(30) * time.Second,\n\t\t\t\t\tCustom: map[string]interface{}{\n\t\t\t\t\t\t\"dnsDuration\":  time.Duration(10) * time.Second,\n\t\t\t\t\t\t\"connDuration\": time.Duration(20) * time.Second,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tStepID:      2,\n\t\t\t\t\tStatusCode:  401,\n\t\t\t\t\tRequestTime: time.Now().Add(12),\n\t\t\t\t\tDuration:    time.Duration(60) * time.Second,\n\t\t\t\t\tCustom: map[string]interface{}{\n\t\t\t\t\t\t\"dnsDuration\":  time.Duration(20) * time.Second,\n\t\t\t\t\t\t\"connDuration\": time.Duration(40) * time.Second,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tStepID:      3,\n\t\t\t\t\tStatusCode:  200,\n\t\t\t\t\tRequestTime: time.Now().Add(2),\n\t\t\t\t\tDuration:    time.Duration(30) * time.Second,\n\t\t\t\t\tCustom: map[string]interface{}{\n\t\t\t\t\t\t\"dnsDuration\":  time.Duration(10) * time.Second,\n\t\t\t\t\t\t\"connDuration\": time.Duration(20) * time.Second,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfail1 := FailVerbose{}\n\tfail1.Count = 0\n\tfail1.ServerErrorDist.Count = 0\n\tfail1.ServerErrorDist.Reasons = make(map[string]int)\n\tfail1.AssertionErrorDist.Conditions = make(map[string]*AssertInfo)\n\titemReport1 := &ScenarioStepResultSummary{\n\t\tStatusCodeDist: map[int]int{200: 2},\n\t\tSuccessCount:   2,\n\t\tFail:           fail1,\n\t\tDurations: map[string]float32{\n\t\t\t\"dnsDuration\":  7.5,\n\t\t\t\"connDuration\": 12.5,\n\t\t\t\"duration\":     20,\n\t\t},\n\t}\n\n\tfail2 := FailVerbose{}\n\tfail2.Count = 1\n\tfail2.ServerErrorDist.Count = 1\n\tfail2.ServerErrorDist.Reasons = make(map[string]int)\n\tfail2.ServerErrorDist.Reasons[types.ReasonConnTimeout] = 1\n\tfail2.AssertionErrorDist.Conditions = make(map[string]*AssertInfo)\n\titemReport2 := &ScenarioStepResultSummary{\n\t\tStatusCodeDist: map[int]int{401: 1},\n\t\tSuccessCount:   1,\n\t\tFail:           fail2,\n\t\tDurations: map[string]float32{\n\t\t\t\"dnsDuration\":  20,\n\t\t\t\"connDuration\": 40,\n\t\t\t\"duration\":     60,\n\t\t},\n\t}\n\n\tfail3 := FailVerbose{}\n\tfail3.Count = 1\n\tfail3.AssertionErrorDist.Count = 1\n\tfail3.ServerErrorDist.Reasons = make(map[string]int)\n\tfail3.AssertionErrorDist.Conditions = make(map[string]*AssertInfo)\n\tfail3.AssertionErrorDist.Conditions[\"equals(status_code,200)\"] = &AssertInfo{\n\t\tCount: 1,\n\t\tReceived: map[string][]interface{}{\n\t\t\t\"status_code\": {400},\n\t\t},\n\t}\n\n\titemReport3 := &ScenarioStepResultSummary{\n\t\tStatusCodeDist: map[int]int{400: 1, 200: 1},\n\t\tSuccessCount:   1,\n\t\tFail:           fail3,\n\t\tDurations: map[string]float32{\n\t\t\t\"dnsDuration\":  20,\n\t\t\t\"connDuration\": 40,\n\t\t\t\"duration\":     60,\n\t\t},\n\t}\n\n\texpectedResult := Result{\n\t\tSuccessCount:       1,\n\t\tServerFailedCount:  0,\n\t\tAssertionFailCount: 1,\n\t\tAvgDuration:        120,\n\t\tStepResults: map[uint16]*ScenarioStepResultSummary{\n\t\t\tuint16(1): itemReport1,\n\t\t\tuint16(2): itemReport2,\n\t\t\tuint16(3): itemReport3,\n\t\t},\n\t}\n\n\ts := &stdout{}\n\tdebug := false\n\ts.Init(debug, 0)\n\n\tresponseChan := make(chan *types.ScenarioResult, len(responses))\n\tgo s.Start(responseChan, nil)\n\n\tgo func() {\n\t\tfor _, r := range responses {\n\t\t\tresponseChan <- r\n\t\t}\n\t\tclose(responseChan)\n\t}()\n\n\tdoneChanSignaled := false\n\tselect {\n\tcase <-s.doneChan:\n\t\tdoneChanSignaled = true\n\tcase <-time.After(time.Duration(1) * time.Second):\n\t}\n\n\tif !doneChanSignaled {\n\t\tt.Errorf(\"DoneChan is not signaled\")\n\t}\n\n\tif !compareResults(s.result, &expectedResult) {\n\t\tt.Errorf(\"Expected %#v, Found %#v\", s.result, expectedResult)\n\n\t}\n}\n\nfunc compareResults(r1, r2 *Result) bool {\n\n\tif r1.successPercentage() != r2.successPercentage() ||\n\t\tr1.failedPercentage() != r2.failedPercentage() ||\n\t\tr1.SuccessCount != r2.SuccessCount ||\n\t\tr1.AvgDuration != r2.AvgDuration ||\n\t\tr1.ServerFailedCount != r2.ServerFailedCount ||\n\t\tr1.AssertionFailCount != r2.AssertionFailCount {\n\t\treturn false\n\t}\n\n\tfor stepId, sr := range r1.StepResults {\n\t\tif !compareStepResults(sr, r2.StepResults[stepId]) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc compareStepResults(s1, s2 *ScenarioStepResultSummary) bool {\n\tif s1.successPercentage() != s2.successPercentage() ||\n\t\ts1.failedPercentage() != s2.failedPercentage() ||\n\t\ts1.SuccessCount != s2.SuccessCount ||\n\t\ts1.Name != s2.Name ||\n\t\ts1.Fail.Count != s2.Fail.Count ||\n\t\ts1.Fail.AssertionErrorDist.Count != s2.Fail.AssertionErrorDist.Count ||\n\t\ts1.Fail.ServerErrorDist.Count != s2.Fail.ServerErrorDist.Count ||\n\t\t!reflect.DeepEqual(s1.Fail.AssertionErrorDist.Conditions, s2.Fail.AssertionErrorDist.Conditions) ||\n\t\t!reflect.DeepEqual(s1.Fail.ServerErrorDist.Reasons, s2.Fail.ServerErrorDist.Reasons) {\n\t\treturn false\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "ddosify_engine/core/report/base.go",
    "content": "/*\n*\n*\tDdosify - Load testing tool for any web system.\n*   Copyright (C) 2021  Ddosify (https://ddosify.com)\n*\n*   This program is free software: you can redistribute it and/or modify\n*   it under the terms of the GNU Affero General Public License as published\n*   by the Free Software Foundation, either version 3 of the License, or\n*   (at your option) any later version.\n*\n*   This program is distributed in the hope that it will be useful,\n*   but WITHOUT ANY WARRANTY; without even the implied warranty of\n*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n*   GNU Affero General Public License for more details.\n*\n*   You should have received a copy of the GNU Affero General Public License\n*   along with this program.  If not, see <https://www.gnu.org/licenses/>.\n*\n */\n\npackage report\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\n\t\"go.ddosify.com/ddosify/core/assertion\"\n\t\"go.ddosify.com/ddosify/core/types\"\n)\n\nvar AvailableOutputServices = make(map[string]ReportService)\n\n// ReportService is the interface that abstracts different report implementations.\ntype ReportService interface {\n\tDoneChan() <-chan bool\n\tInit(debug bool, samplingRate int) error\n\tStart(input chan *types.ScenarioResult, assertionResultChan <-chan assertion.TestAssertionResult)\n}\n\n// NewReportService is the factory method of the ReportService.\nfunc NewReportService(s string) (service ReportService, err error) {\n\tif val, ok := AvailableOutputServices[s]; ok {\n\t\t// Create a new object from the service type\n\t\tservice = reflect.New(reflect.TypeOf(val).Elem()).Interface().(ReportService)\n\t} else {\n\t\terr = fmt.Errorf(\"unsupported output type: %s\", s)\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "ddosify_engine/core/report/base_test.go",
    "content": "/*\n*\n*\tDdosify - Load testing tool for any web system.\n*   Copyright (C) 2021  Ddosify (https://ddosify.com)\n*\n*   This program is free software: you can redistribute it and/or modify\n*   it under the terms of the GNU Affero General Public License as published\n*   by the Free Software Foundation, either version 3 of the License, or\n*   (at your option) any later version.\n*\n*   This program is distributed in the hope that it will be useful,\n*   but WITHOUT ANY WARRANTY; without even the implied warranty of\n*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n*   GNU Affero General Public License for more details.\n*\n*   You should have received a copy of the GNU Affero General Public License\n*   along with this program.  If not, see <https://www.gnu.org/licenses/>.\n*\n */\n\npackage report\n\nimport (\n\t\"testing\"\n)\n\nfunc TestNewReportService(t *testing.T) {\n\n\t// Valid output types\n\tfor k := range AvailableOutputServices {\n\t\t_, err := NewReportService(k)\n\n\t\tif err != nil {\n\t\t\tt.Errorf(\"TestNewReportService %v\", err)\n\t\t}\n\n\t}\n\n\t// Invalid output type\n\t_, err := NewReportService(\"invalid_output_type\")\n\tif err == nil {\n\t\tt.Errorf(\"TestNewReportService invalid output should errored\")\n\t}\n}\n"
  },
  {
    "path": "ddosify_engine/core/report/debug.go",
    "content": "package report\n\nimport (\n\t\"encoding/json\"\n\t\"html\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"go.ddosify.com/ddosify/core/types\"\n)\n\ntype verboseRequest struct {\n\tUrl     string            `json:\"url\"`\n\tMethod  string            `json:\"method\"`\n\tHeaders map[string]string `json:\"headers\"`\n\tBody    interface{}       `json:\"body\"`\n}\n\ntype verboseResponse struct {\n\tStatusCode   int               `json:\"status_code\"`\n\tHeaders      map[string]string `json:\"headers\"`\n\tBody         interface{}       `json:\"body\"`\n\tResponseTime int64             `json:\"response_time\"` // in milliseconds\n}\n\ntype verboseHttpRequestInfo struct {\n\tStepId           uint16                  `json:\"step_id\"`\n\tStepName         string                  `json:\"step_name\"`\n\tRequest          verboseRequest          `json:\"request\"`\n\tResponse         verboseResponse         `json:\"response\"`\n\tEnvs             map[string]interface{}  `json:\"envs\"`\n\tTestData         map[string]interface{}  `json:\"test_data\"`\n\tFailedCaptures   map[string]string       `json:\"failed_captures\"`\n\tFailedAssertions []types.FailedAssertion `json:\"failed_assertions\"`\n\tError            string                  `json:\"error\"`\n}\n\nfunc ScenarioStepResultToVerboseHttpRequestInfo(sr *types.ScenarioStepResult) verboseHttpRequestInfo {\n\tvar verboseInfo verboseHttpRequestInfo\n\n\tverboseInfo.StepId = sr.StepID\n\tverboseInfo.StepName = sr.StepName\n\n\tif sr.Err.Type == types.ErrorInvalidRequest {\n\t\t// could not prepare request at all\n\t\tverboseInfo.Error = sr.Err.Error()\n\t\treturn verboseInfo\n\t}\n\n\trequestHeaders, requestBody, _ := decode(sr.ReqHeaders,\n\t\tsr.ReqBody)\n\tverboseInfo.Request = struct {\n\t\tUrl     string            \"json:\\\"url\\\"\"\n\t\tMethod  string            \"json:\\\"method\\\"\"\n\t\tHeaders map[string]string \"json:\\\"headers\\\"\"\n\t\tBody    interface{}       \"json:\\\"body\\\"\"\n\t}{\n\t\tUrl:     sr.Url,\n\t\tMethod:  sr.Method,\n\t\tHeaders: requestHeaders,\n\t\tBody:    requestBody,\n\t}\n\n\tif sr.Err.Type != \"\" {\n\t\tverboseInfo.Error = sr.Err.Error()\n\t} else {\n\t\tresponseHeaders, responseBody, _ := decode(sr.RespHeaders,\n\t\t\tsr.RespBody)\n\t\t// TODO what to do with error\n\t\tverboseInfo.Response = verboseResponse{\n\t\t\tStatusCode:   sr.StatusCode,\n\t\t\tHeaders:      responseHeaders,\n\t\t\tBody:         responseBody,\n\t\t\tResponseTime: sr.Duration.Milliseconds(),\n\t\t}\n\t}\n\n\tenvs := make(map[string]interface{})\n\ttestData := make(map[string]interface{})\n\tfor key, val := range sr.UsableEnvs {\n\t\tif strings.HasPrefix(key, \"data.\") {\n\t\t\ttestData[key] = val\n\t\t} else {\n\t\t\tenvs[key] = val\n\t\t}\n\t}\n\n\tverboseInfo.Envs = envs\n\tverboseInfo.TestData = testData\n\tverboseInfo.FailedCaptures = sr.FailedCaptures\n\tverboseInfo.FailedAssertions = sr.FailedAssertions\n\n\treturn verboseInfo\n}\n\nfunc decode(headers http.Header, byteBody []byte) (map[string]string, interface{}, error) {\n\tcontentType := headers.Get(\"Content-Type\")\n\tvar reqBody interface{}\n\n\ths := make(map[string]string, 0)\n\tfor k, v := range headers {\n\t\tvalues := strings.Join(v, \",\")\n\t\ths[k] = values\n\t}\n\n\tif strings.Contains(contentType, \"text/html\") {\n\t\tunescapedHmtl := html.UnescapeString(string(byteBody))\n\t\treqBody = unescapedHmtl\n\t} else if strings.Contains(contentType, \"application/json\") {\n\t\terr := json.Unmarshal(byteBody, &reqBody)\n\t\tif err != nil {\n\t\t\treqBody = string(byteBody)\n\t\t}\n\t} else { // for remaining content-types return plain string\n\t\t// xml.Unmarshal() needs xml tags to decode encoded xml, we have no knowledge about the xml structure\n\t\treqBody = string(byteBody)\n\t}\n\n\treturn hs, reqBody, nil\n}\n\nfunc isVerboseInfoRequestEmpty(req verboseRequest) bool {\n\tif req.Url == \"\" && req.Method == \"\" && req.Headers == nil && req.Body == nil {\n\t\treturn true\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "ddosify_engine/core/report/debug_test.go",
    "content": "package report\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestDecode(t *testing.T) {\n\th := http.Header{}\n\th.Add(\"Content-Type\", \"application/json\")\n\n\ttype Temp struct {\n\t\tX float64 `json:\"x\"`\n\t}\n\n\tbody := Temp{\n\t\tX: 52.2,\n\t}\n\n\tbBody, _ := json.Marshal(body)\n\n\t_, bodyDecoded, err := decode(h, bBody)\n\n\tif err != nil {\n\t\tt.Errorf(\"%v\", err)\n\t}\n\n\texpected := reflect.ValueOf(map[string]interface{}{\"x\": 52.2})\n\tei := expected.Interface()\n\tif !reflect.DeepEqual(ei, bodyDecoded) {\n\t\tt.Errorf(\"TestDecode, expected:%s got:%s\", ei, bodyDecoded)\n\t}\n\n}\n"
  },
  {
    "path": "ddosify_engine/core/report/stdout.go",
    "content": "/*\n*\n*\tDdosify - Load testing tool for any web system.\n*   Copyright (C) 2021  Ddosify (https://ddosify.com)\n*\n*   This program is free software: you can redistribute it and/or modify\n*   it under the terms of the GNU Affero General Public License as published\n*   by the Free Software Foundation, either version 3 of the License, or\n*   (at your option) any later version.\n*\n*   This program is distributed in the hope that it will be useful,\n*   but WITHOUT ANY WARRANTY; without even the implied warranty of\n*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n*   GNU Affero General Public License for more details.\n*\n*   You should have received a copy of the GNU Affero General Public License\n*   along with this program.  If not, see <https://www.gnu.org/licenses/>.\n*\n */\n\npackage report\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\t\"text/tabwriter\"\n\t\"time\"\n\n\t\"github.com/enescakir/emoji\"\n\t\"github.com/fatih/color\"\n\t\"github.com/mattn/go-colorable\"\n\t\"go.ddosify.com/ddosify/core/assertion\"\n\t\"go.ddosify.com/ddosify/core/types\"\n\t\"go.ddosify.com/ddosify/core/util\"\n)\n\nconst OutputTypeStdout = \"stdout\"\n\nvar out = colorable.NewColorableStdout()\n\nfunc init() {\n\tAvailableOutputServices[OutputTypeStdout] = &stdout{}\n}\n\ntype stdout struct {\n\tdoneChan     chan bool\n\tresult       *Result\n\tprintTicker  *time.Ticker\n\tmu           sync.Mutex\n\tdebug        bool\n\tsamplingRate int\n}\n\nvar white = color.New(color.FgHiWhite).SprintFunc()\nvar blue = color.New(color.FgHiBlue).SprintFunc()\nvar green = color.New(color.FgHiGreen).SprintFunc()\nvar yellow = color.New(color.FgHiYellow).SprintFunc()\nvar red = color.New(color.FgHiRed).SprintFunc()\nvar realTimePrintInterval = time.Duration(1500) * time.Millisecond\n\nfunc (s *stdout) Init(debug bool, samplingRate int) (err error) {\n\ts.doneChan = make(chan bool, 1)\n\ts.result = &Result{\n\t\tStepResults: make(map[uint16]*ScenarioStepResultSummary),\n\t}\n\ts.debug = debug\n\ts.samplingRate = samplingRate\n\n\tcolor.Cyan(\"%s  Initializing... \\n\", emoji.Gear)\n\tif s.debug {\n\t\tcolor.Cyan(\"%s Running in debug mode, 1 iteration will be played... \\n\", emoji.Bug)\n\t}\n\treturn\n}\n\nfunc (s *stdout) Start(input chan *types.ScenarioResult, assertionResultChan <-chan assertion.TestAssertionResult) {\n\tif s.debug {\n\t\ts.result.TestStatus = \"success\"\n\t\tif assertionResultChan != nil {\n\t\t\tresult := <-assertionResultChan\n\t\t\tif result.Fail {\n\t\t\t\ts.result.TestStatus = \"failed\"\n\t\t\t\ts.result.TestFailedAssertions = result.FailedRules\n\t\t\t}\n\t\t}\n\t\ts.printInDebugMode(input)\n\t\tif s.result.TestStatus == \"success\" {\n\t\t\ts.doneChan <- true\n\t\t} else {\n\t\t\ts.doneChan <- false\n\t\t}\n\t\treturn\n\t}\n\tgo s.realTimePrintStart()\n\n\tstopSampling := make(chan struct{})\n\tsamplingCount := make(map[uint16]map[string]int)\n\tgo s.cleanSamplingCount(samplingCount, stopSampling, s.samplingRate)\n\n\tfor r := range input {\n\t\ts.mu.Lock() // avoid race around samplingCount\n\t\taggregate(s.result, r, samplingCount, s.samplingRate)\n\t\ts.mu.Unlock()\n\t}\n\n\t// listen for assertion result\n\ts.result.TestStatus = \"success\"\n\tif assertionResultChan != nil {\n\t\tresult := <-assertionResultChan\n\t\tif result.Fail {\n\t\t\ts.result.TestStatus = \"failed\"\n\t\t\ts.result.TestFailedAssertions = result.FailedRules\n\t\t}\n\t}\n\ts.realTimePrintStop()\n\ts.report()\n\tstopSampling <- struct{}{}\n\n\tif s.result.TestStatus == \"success\" {\n\t\ts.doneChan <- true\n\t} else {\n\t\ts.doneChan <- false\n\t}\n}\n\nfunc (s *stdout) cleanSamplingCount(samplingCount map[uint16]map[string]int, stopSampling chan struct{}, samplingRate int) {\n\tticker := time.NewTicker(1 * time.Second)\n\tfor {\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\t\ts.mu.Lock() // avoid race around samplingCount\n\t\t\tfor stepId, ruleMap := range samplingCount {\n\t\t\t\tfor rule, count := range ruleMap {\n\t\t\t\t\tif count >= samplingRate {\n\t\t\t\t\t\tsamplingCount[stepId][rule] = 0\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\ts.mu.Unlock()\n\t\tcase <-stopSampling:\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (s *stdout) report() {\n\ts.printDetails()\n}\n\nfunc (s *stdout) DoneChan() <-chan bool {\n\treturn s.doneChan\n}\n\nfunc (s *stdout) realTimePrintStart() {\n\tif util.IsSystemInTestMode() {\n\t\treturn\n\t}\n\n\ts.printTicker = time.NewTicker(realTimePrintInterval)\n\n\tcolor.Cyan(\"%s Engine fired. \\n\\n\", emoji.Fire)\n\tcolor.Cyan(\"%s CTRL+C to gracefully stop.\\n\", emoji.StopSign)\n\n\tfor range s.printTicker.C {\n\t\tgo func() {\n\t\t\ts.mu.Lock()\n\t\t\ts.liveResultPrint()\n\t\t\ts.mu.Unlock()\n\t\t}()\n\t}\n}\n\nfunc (s *stdout) liveResultPrint() {\n\tfmt.Fprintf(out, \"%s %s %s\\n\",\n\t\tgreen(fmt.Sprintf(\"%s  Successful Run: %-6d %3d%% %5s\",\n\t\t\temoji.CheckMark, s.result.SuccessCount, s.result.successPercentage(), \"\")),\n\t\tred(fmt.Sprintf(\"%s Failed Run: %-6d %3d%% %5s\",\n\t\t\temoji.CrossMark, s.result.ServerFailedCount+s.result.AssertionFailCount, s.result.failedPercentage(), \"\")),\n\t\tblue(fmt.Sprintf(\"%s  Avg. Duration: %.5fs\", emoji.Stopwatch, s.result.AvgDuration)))\n}\n\nfunc (s *stdout) realTimePrintStop() {\n\tif util.IsSystemInTestMode() {\n\t\treturn\n\t}\n\t// Last print.\n\ts.liveResultPrint()\n\ts.printTicker.Stop()\n}\n\nfunc (s *stdout) printInDebugMode(input chan *types.ScenarioResult) {\n\tcolor.Cyan(\"%s Engine fired. \\n\\n\", emoji.Fire)\n\tcolor.Cyan(\"%s CTRL+C to gracefully stop.\\n\", emoji.StopSign)\n\n\tfor r := range input { // only 1 ScenarioResult expected\n\t\tfor _, sr := range r.StepResults {\n\t\t\tverboseInfo := ScenarioStepResultToVerboseHttpRequestInfo(sr)\n\n\t\t\tb := strings.Builder{}\n\t\t\tw := tabwriter.NewWriter(&b, 0, 0, 4, ' ', 0)\n\t\t\tcolor.Cyan(\"\\n\\nSTEP (%d) %-5s\\n\", verboseInfo.StepId, verboseInfo.StepName)\n\t\t\tcolor.Cyan(\"-------------------------------------\")\n\t\t\tif len(verboseInfo.Envs) > 0 {\n\t\t\t\tfmt.Fprintf(w, \"%s\\n\", blue(\"- Environment Variables\"))\n\t\t\t\tfor eKey, eVal := range verboseInfo.Envs {\n\t\t\t\t\tswitch eVal.(type) {\n\t\t\t\t\tcase map[string]interface{}:\n\t\t\t\t\t\tvalPretty, _ := json.Marshal(eVal)\n\t\t\t\t\t\tfmt.Fprintf(w, \"\\t%s:\\t%-5s \\n\", fmt.Sprint(eKey), valPretty)\n\t\t\t\t\tcase []string:\n\t\t\t\t\t\tvalPretty, _ := json.Marshal(eVal)\n\t\t\t\t\t\tfmt.Fprintf(w, \"\\t%s:\\t%-5s \\n\", fmt.Sprint(eKey), valPretty)\n\t\t\t\t\tcase []float64:\n\t\t\t\t\t\tvalPretty, _ := json.Marshal(eVal)\n\t\t\t\t\t\tfmt.Fprintf(w, \"\\t%s:\\t%-5s \\n\", fmt.Sprint(eKey), valPretty)\n\t\t\t\t\tcase []bool:\n\t\t\t\t\t\tvalPretty, _ := json.Marshal(eVal)\n\t\t\t\t\t\tfmt.Fprintf(w, \"\\t%s:\\t%-5s \\n\", fmt.Sprint(eKey), valPretty)\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tfmt.Fprintf(w, \"\\t%s:\\t%-5s \\n\", fmt.Sprint(eKey), fmt.Sprint(eVal))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfmt.Fprintf(w, \"\\n\")\n\t\t\t}\n\t\t\tif len(verboseInfo.TestData) > 0 {\n\t\t\t\tfmt.Fprintf(w, \"%s\\n\", blue(\"- Test Data\"))\n\n\t\t\t\tfor eKey, eVal := range verboseInfo.TestData {\n\t\t\t\t\tswitch eVal.(type) {\n\t\t\t\t\tcase map[string]interface{}:\n\t\t\t\t\t\tvalPretty, _ := json.Marshal(eVal)\n\t\t\t\t\t\tfmt.Fprintf(w, \"\\t%s:\\t%-5s \\n\", fmt.Sprint(eKey), valPretty)\n\t\t\t\t\tcase []int:\n\t\t\t\t\t\tvalPretty, _ := json.Marshal(eVal)\n\t\t\t\t\t\tfmt.Fprintf(w, \"\\t%s:\\t%-5s \\n\", fmt.Sprint(eKey), valPretty)\n\t\t\t\t\tcase []string:\n\t\t\t\t\t\tvalPretty, _ := json.Marshal(eVal)\n\t\t\t\t\t\tfmt.Fprintf(w, \"\\t%s:\\t%-5s \\n\", fmt.Sprint(eKey), valPretty)\n\t\t\t\t\tcase []float64:\n\t\t\t\t\t\tvalPretty, _ := json.Marshal(eVal)\n\t\t\t\t\t\tfmt.Fprintf(w, \"\\t%s:\\t%-5s \\n\", fmt.Sprint(eKey), valPretty)\n\t\t\t\t\tcase []bool:\n\t\t\t\t\t\tvalPretty, _ := json.Marshal(eVal)\n\t\t\t\t\t\tfmt.Fprintf(w, \"\\t%s:\\t%-5s \\n\", fmt.Sprint(eKey), valPretty)\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tfmt.Fprintf(w, \"\\t%s:\\t%-5s \\n\", fmt.Sprint(eKey), fmt.Sprint(eVal))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfmt.Fprintf(w, \"\\n\")\n\t\t\t}\n\n\t\t\tif verboseInfo.Error != \"\" && isVerboseInfoRequestEmpty(verboseInfo.Request) {\n\t\t\t\tfmt.Fprintf(w, \"%s Error: \\t%-5s \\n\", emoji.SosButton, verboseInfo.Error)\n\t\t\t\tfmt.Fprintln(w)\n\t\t\t\tfmt.Fprint(out, b.String())\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tfmt.Fprintf(w, \"%s\\n\", blue(\"- Request\"))\n\t\t\tfmt.Fprintf(w, \"\\tTarget: \\t%s \\n\", verboseInfo.Request.Url)\n\t\t\tfmt.Fprintf(w, \"\\tMethod: \\t%s \\n\", verboseInfo.Request.Method)\n\n\t\t\tfmt.Fprintf(w, \"\\t%s\\n\", \"Headers: \")\n\t\t\tfor hKey, hVal := range verboseInfo.Request.Headers {\n\t\t\t\tfmt.Fprintf(w, \"\\t\\t%s:\\t%-5s \\n\", hKey, hVal)\n\t\t\t}\n\n\t\t\tcontentType := sr.ReqHeaders.Get(\"content-type\")\n\t\t\tfmt.Fprintf(w, \"\\t%s\", \"Body: \")\n\t\t\tprintBody(w, contentType, verboseInfo.Request.Body)\n\t\t\tfmt.Fprintf(w, \"\\n\")\n\n\t\t\tif verboseInfo.Error == \"\" {\n\t\t\t\t// response\n\t\t\t\tfmt.Fprintf(w, \"\\n%s\\n\", blue(\"- Response\"))\n\t\t\t\tfmt.Fprintf(w, \"\\tStatusCode:\\t%-5d \\n\", verboseInfo.Response.StatusCode)\n\t\t\t\tfmt.Fprintf(w, \"\\tResponseTime:\\t%-5d(ms) \\n\", verboseInfo.Response.ResponseTime)\n\t\t\t\tfmt.Fprintf(w, \"\\t%s\\n\", \"Headers: \")\n\t\t\t\tfor hKey, hVal := range verboseInfo.Response.Headers {\n\t\t\t\t\tfmt.Fprintf(w, \"\\t\\t%s:\\t%-5s \\n\", hKey, hVal)\n\t\t\t\t}\n\n\t\t\t\tcontentType := sr.RespHeaders.Get(\"content-type\")\n\t\t\t\tfmt.Fprintf(w, \"\\t%s\", \"Body: \")\n\t\t\t\tprintBody(w, contentType, verboseInfo.Response.Body)\n\t\t\t\tfmt.Fprintf(w, \"\\n\")\n\t\t\t}\n\n\t\t\tif len(verboseInfo.FailedCaptures) > 0 {\n\t\t\t\tfmt.Fprintf(w, \"%s\\n\", yellow(\"- Failed Captures\"))\n\t\t\t\tfor wKey, wVal := range verboseInfo.FailedCaptures {\n\t\t\t\t\tfmt.Fprintf(w, \"\\t\\t%s: \\t%s \\n\", wKey, wVal)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif len(verboseInfo.FailedAssertions) > 0 {\n\t\t\t\tfmt.Fprintf(w, \"%s\", yellow(\"- Failed Assertions\"))\n\t\t\t\tfor _, failAssertion := range verboseInfo.FailedAssertions {\n\t\t\t\t\tfmt.Fprintf(w, \"\\n\\t\\tRule: %s\\n\", failAssertion.Rule)\n\t\t\t\t\tprettyReceived, _ := json.MarshalIndent(failAssertion.Received, \"\\t\\t\", \"\\t\")\n\t\t\t\t\tfmt.Fprintf(w, \"\\t\\tReceived: %s\\n\", prettyReceived)\n\t\t\t\t\tfmt.Fprintf(w, \"\\t\\tReason: %s\\n\", failAssertion.Reason)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif verboseInfo.Error != \"\" { // server error\n\t\t\t\tfmt.Fprintf(w, \"\\n%s Error: \\t%-5s \\n\", emoji.SosButton, verboseInfo.Error)\n\t\t\t}\n\n\t\t\tfmt.Fprintln(w)\n\t\t\tfmt.Fprint(out, b.String())\n\t\t}\n\t}\n\n\tb := strings.Builder{}\n\tw := tabwriter.NewWriter(&b, 0, 0, 4, ' ', 0)\n\tif s.result.TestStatus == \"success\" {\n\t\tfmt.Fprintf(w, \"%s\", green(\"Test Status : Success\\n\"))\n\n\t} else if s.result.TestStatus == \"failed\" {\n\t\tfmt.Fprintf(w, \"\\n%s\", red(\"Test Status: Failed\\n\"))\n\t\tfor _, failedRule := range s.result.TestFailedAssertions {\n\t\t\tfmt.Fprintf(w, red(\"\\nRule: %s\\n\"), failedRule.Rule)\n\t\t\tfmt.Fprintf(w, red(\"Received: \\n\"))\n\n\t\t\tfor ident, values := range failedRule.ReceivedMap {\n\t\t\t\tfmt.Fprintf(w, red(\"\\t%s: %v\\n\"), ident, values)\n\t\t\t}\n\t\t}\n\t}\n\tfmt.Fprintln(w)\n\tfmt.Fprint(out, b.String())\n\n}\n\nfunc printBody(w io.Writer, contentType string, body interface{}) {\n\tif body == nil {\n\t\treturn\n\t}\n\tif strings.Contains(contentType, \"application/json\") {\n\t\tvalPretty, _ := json.MarshalIndent(body, \"\\t\\t\", \"\\t\")\n\t\tfmt.Fprintf(w, \"\\n\\t\\t%s\\n\", valPretty)\n\t} else {\n\t\t// html unescaped text\n\t\t// if xml came as decoded, we could pretty print it like json\n\t\tfmt.Fprintf(w, \"%+v\\n\", fmt.Sprintf(\"%s\", body))\n\t}\n}\n\n// TODO:REFACTOR use template\nfunc (s *stdout) printDetails() {\n\tcolor.Set(color.FgHiCyan)\n\tdefer color.Unset()\n\n\tb := strings.Builder{}\n\tw := tabwriter.NewWriter(&b, 0, 0, 4, ' ', 0)\n\n\tfmt.Fprintln(w, \"\\n\\nRESULT\")\n\tfmt.Fprintln(w, \"-------------------------------------\")\n\n\tkeys := make([]int, 0)\n\tfor k := range s.result.StepResults {\n\t\tkeys = append(keys, int(k))\n\t}\n\n\t// Since map is not a ordered data structure,\n\t// We should sort scenarioItemIDs to traverse itemReports\n\tsort.Ints(keys)\n\n\tfor _, k := range keys {\n\t\tv := s.result.StepResults[uint16(k)]\n\n\t\tif len(keys) > 1 {\n\t\t\tstepHeader := v.Name\n\t\t\tif v.Name == \"\" {\n\t\t\t\tstepHeader = fmt.Sprintf(\"Step %d\", k)\n\t\t\t}\n\t\t\tfmt.Fprintf(w, \"\\n%d. \"+stepHeader+\"\\n\", k)\n\t\t\tfmt.Fprintln(w, \"---------------------------------\")\n\t\t}\n\n\t\tfmt.Fprintf(w, \"Success Count:\\t%-5d (%d%%)\\n\", v.SuccessCount, v.successPercentage())\n\t\tfmt.Fprintf(w, \"Failed Count:\\t%-5d (%d%%)\\n\", v.Fail.Count, v.failedPercentage())\n\n\t\tfmt.Fprintln(w, \"\\nDurations (Avg):\")\n\t\tvar durationList = make([]duration, 0)\n\t\tfor d, s := range v.Durations {\n\t\t\tdur := keyToStr[d]\n\t\t\tdur.duration = s\n\t\t\tdurationList = append(durationList, dur)\n\t\t}\n\t\tsort.Slice(durationList, func(i, j int) bool {\n\t\t\treturn durationList[i].order < durationList[j].order\n\t\t})\n\t\tfor _, v := range durationList {\n\t\t\tfmt.Fprintf(w, \"  %s\\t:%.4fs\\n\", v.name, v.duration)\n\t\t}\n\n\t\tif len(v.StatusCodeDist) > 0 {\n\t\t\tfmt.Fprintln(w, \"\\nStatus Code (Message) :Count\")\n\t\t\tfor s, c := range v.StatusCodeDist {\n\t\t\t\tdesc := fmt.Sprintf(\"%3d (%s)\", s, http.StatusText(s))\n\t\t\t\tfmt.Fprintf(w, \"  %s\\t:%d\\n\", desc, c)\n\t\t\t}\n\t\t}\n\n\t\tif v.Fail.AssertionErrorDist.Count > 0 {\n\t\t\tfmt.Fprintln(w, \"\\nAssertion Error Distribution:\")\n\t\t\tfor e, c := range v.Fail.AssertionErrorDist.Conditions {\n\t\t\t\tfmt.Fprintf(w, \"\\tCondition: %s\\n\", e)\n\t\t\t\tfmt.Fprintf(w, \"\\t\\tFail Count: %d\\n\", c.Count)\n\t\t\t\tfmt.Fprintf(w, \"\\t\\tReceived: \\n\")\n\n\t\t\t\tfor ident, values := range c.Received {\n\t\t\t\t\t// deduplication\n\t\t\t\t\tvalues = deduplicate(values)\n\t\t\t\t\tfmt.Fprintf(w, \"\\t\\t\\t %s : %v\\n\", ident, values)\n\t\t\t\t}\n\n\t\t\t\tfmt.Fprintf(w, \"\\t\\tReason: %s \\n\\n\", c.Reason)\n\t\t\t}\n\t\t}\n\n\t\tif v.Fail.ServerErrorDist.Count > 0 {\n\t\t\tfmt.Fprintln(w, \"\\nServer Error Distribution (Count:Reason):\")\n\t\t\tfor e, c := range v.Fail.ServerErrorDist.Reasons {\n\t\t\t\tfmt.Fprintf(w, \"  %d\\t :%s\\n\", c, e)\n\t\t\t}\n\t\t}\n\t\tfmt.Fprintln(w)\n\t}\n\n\tif s.result.TestStatus == \"success\" {\n\t\tfmt.Fprintf(w, \"%s\", green(\"Test Status : Success\\n\"))\n\n\t} else if s.result.TestStatus == \"failed\" {\n\t\tfmt.Fprintf(w, \"\\n%s\", red(\"Test Status: Failed\\n\"))\n\t\tfor _, failedRule := range s.result.TestFailedAssertions {\n\t\t\tfmt.Fprintf(w, red(\"\\nRule: %s\\n\"), failedRule.Rule)\n\t\t\tfmt.Fprintf(w, red(\"Received: \\n\"))\n\n\t\t\tfor ident, values := range failedRule.ReceivedMap {\n\t\t\t\tfmt.Fprintf(w, red(\"\\t%s: %v\\n\"), ident, values)\n\t\t\t}\n\t\t}\n\t}\n\n\tw.Flush()\n\tfmt.Fprint(out, b.String())\n}\n\nfunc deduplicate(values []interface{}) []interface{} {\n\tseen := make(map[interface{}]bool)\n\tresult := make([]interface{}, 0)\n\n\tfor _, v := range values {\n\t\tif _, ok := seen[v]; !ok {\n\t\t\tseen[v] = true\n\t\t\tresult = append(result, v)\n\t\t}\n\t}\n\n\treturn result\n}\n\ntype duration struct {\n\tname     string\n\tduration float32\n\torder    int\n}\n\nvar keyToStr = map[string]duration{\n\t\"dnsDuration\":           {name: \"DNS\", order: 1},\n\t\"connDuration\":          {name: \"Connection\", order: 2},\n\t\"tlsDuration\":           {name: \"TLS\", order: 3},\n\t\"reqDuration\":           {name: \"Request Write\", order: 4},\n\t\"serverProcessDuration\": {name: \"Server Processing\", order: 5},\n\t\"resDuration\":           {name: \"Response Read\", order: 6},\n\t\"duration\":              {name: \"Total\", order: 7},\n}\n"
  },
  {
    "path": "ddosify_engine/core/report/stdoutJson.go",
    "content": "/*\n*\n*\tDdosify - Load testing tool for any web system.\n*   Copyright (C) 2021  Ddosify (https://ddosify.com)\n*\n*   This program is free software: you can redistribute it and/or modify\n*   it under the terms of the GNU Affero General Public License as published\n*   by the Free Software Foundation, either version 3 of the License, or\n*   (at your option) any later version.\n*\n*   This program is distributed in the hope that it will be useful,\n*   but WITHOUT ANY WARRANTY; without even the implied warranty of\n*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n*   GNU Affero General Public License for more details.\n*\n*   You should have received a copy of the GNU Affero General Public License\n*   along with this program.  If not, see <https://www.gnu.org/licenses/>.\n*\n */\n\npackage report\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"math\"\n\t\"sync\"\n\t\"time\"\n\n\t\"go.ddosify.com/ddosify/core/assertion\"\n\t\"go.ddosify.com/ddosify/core/types\"\n)\n\nconst OutputTypeStdoutJson = \"stdout-json\"\n\nfunc init() {\n\tAvailableOutputServices[OutputTypeStdoutJson] = &stdoutJson{}\n}\n\ntype stdoutJson struct {\n\tdoneChan     chan bool\n\tresult       *Result\n\tdebug        bool\n\tsamplingRate int\n\tmu           sync.Mutex\n}\n\nfunc (s *stdoutJson) Init(debug bool, samplingRate int) (err error) {\n\ts.doneChan = make(chan bool)\n\ts.result = &Result{\n\t\tStepResults: make(map[uint16]*ScenarioStepResultSummary),\n\t}\n\ts.debug = debug\n\ts.samplingRate = samplingRate\n\treturn\n}\n\nfunc (s *stdoutJson) Start(input chan *types.ScenarioResult, assertionResultChan <-chan assertion.TestAssertionResult) {\n\tif s.debug {\n\t\ts.result.TestStatus = \"success\"\n\t\tif assertionResultChan != nil {\n\t\t\tresult := <-assertionResultChan\n\t\t\tif result.Fail {\n\t\t\t\ts.result.TestStatus = \"failed\"\n\t\t\t\ts.result.TestFailedAssertions = result.FailedRules\n\t\t\t}\n\t\t}\n\t\ts.printInDebugMode(input)\n\t\tif s.result.TestStatus == \"success\" {\n\t\t\ts.doneChan <- true\n\t\t} else {\n\t\t\ts.doneChan <- false\n\t\t}\n\t\treturn\n\t}\n\ts.listenAndAggregate(input, assertionResultChan)\n\ts.report()\n\n\tif s.result.TestStatus == \"success\" {\n\t\ts.doneChan <- true\n\t} else {\n\t\ts.doneChan <- false\n\t}\n}\n\nfunc (s *stdoutJson) report() {\n\tp := 1e3\n\n\ts.result.AvgDuration = float32(math.Round(float64(s.result.AvgDuration)*p) / p)\n\n\tfor _, itemReport := range s.result.StepResults {\n\t\tdurations := make(map[string]float32)\n\t\tfor d, s := range itemReport.Durations {\n\t\t\t// Less precision for durations.\n\t\t\tt := math.Round(float64(s)*p) / p\n\t\t\tdurations[strKeyToJsonKey[d]] = float32(t)\n\t\t}\n\t\titemReport.Durations = durations\n\t}\n\n\tj, _ := json.Marshal(s.result)\n\tprintJson(j)\n}\n\nfunc (s *stdoutJson) DoneChan() <-chan bool {\n\treturn s.doneChan\n}\n\nfunc (s *stdoutJson) listenAndAggregate(input chan *types.ScenarioResult, assertionResultChan <-chan assertion.TestAssertionResult) {\n\tstopSampling := make(chan struct{})\n\tsamplingCount := make(map[uint16]map[string]int)\n\tgo s.cleanSamplingCount(samplingCount, stopSampling, s.samplingRate)\n\tfor r := range input {\n\t\ts.mu.Lock() // avoid race around samplingCount\n\t\taggregate(s.result, r, samplingCount, s.samplingRate)\n\t\ts.mu.Unlock()\n\t}\n\t// listen for assertion result, add to json\n\ts.result.TestStatus = \"success\"\n\tif assertionResultChan != nil {\n\t\tresult := <-assertionResultChan\n\t\tif result.Fail {\n\t\t\ts.result.TestStatus = \"failed\"\n\t\t\ts.result.TestFailedAssertions = result.FailedRules\n\t\t}\n\t}\n}\n\nfunc (s *stdoutJson) cleanSamplingCount(samplingCount map[uint16]map[string]int, stopSampling chan struct{}, samplingRate int) {\n\tticker := time.NewTicker(1 * time.Second)\n\tfor {\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\t\ts.mu.Lock() // avoid race around samplingCount\n\t\t\tfor stepId, ruleMap := range samplingCount {\n\t\t\t\tfor rule, count := range ruleMap {\n\t\t\t\t\tif count >= samplingRate {\n\t\t\t\t\t\tsamplingCount[stepId][rule] = 0\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\ts.mu.Unlock()\n\t\tcase <-stopSampling:\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (s *stdoutJson) printInDebugMode(input chan *types.ScenarioResult) {\n\tstepDebugResults := struct {\n\t\tDebugResults         map[uint16]verboseHttpRequestInfo \"json:\\\"steps\\\"\"\n\t\tTestStatus           string                            \"json:\\\"test_status\\\"\"\n\t\tTestFailedAssertions []assertion.FailedRule            \"json:\\\"failed_criterias,omitempty\\\"\"\n\t}{\n\t\tDebugResults: map[uint16]verboseHttpRequestInfo{},\n\t}\n\tfor r := range input { // only 1 sc ScenarioResult expected\n\t\tfor _, sr := range r.StepResults {\n\t\t\tverboseInfo := ScenarioStepResultToVerboseHttpRequestInfo(sr)\n\t\t\tstepDebugResults.DebugResults[verboseInfo.StepId] = verboseInfo\n\t\t}\n\t}\n\n\tif s.result.TestStatus == \"failed\" {\n\t\tstepDebugResults.TestStatus = \"failed\"\n\t\tstepDebugResults.TestFailedAssertions = s.result.TestFailedAssertions\n\t} else {\n\t\tstepDebugResults.TestStatus = \"success\"\n\t}\n\n\tprintPretty(out, stepDebugResults)\n}\n\nfunc printPretty(w io.Writer, info any) {\n\tvalPretty, _ := json.MarshalIndent(info, \"\", \"  \")\n\tfmt.Fprintf(out, \"%s \\n\",\n\t\twhite(fmt.Sprintf(\" %-6s\",\n\t\t\tvalPretty)))\n}\n\n// Report wraps Result to add success/fails percentage values\ntype Report Result\n\nfunc (r Result) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(struct {\n\t\tSuccesPerc int `json:\"success_perc\"`\n\t\tFailPerc   int `json:\"fail_perc\"`\n\t\tReport\n\t}{\n\t\tSuccesPerc: r.successPercentage(),\n\t\tFailPerc:   r.failedPercentage(),\n\t\tReport:     Report(r),\n\t})\n}\n\n// ItemReport wraps ScenarioStepReport to add success/fails percentage values\ntype ItemReport ScenarioStepResultSummary\n\nfunc (s ScenarioStepResultSummary) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(struct {\n\t\tItemReport\n\t\tSuccesPerc int `json:\"success_perc\"`\n\t\tFailPerc   int `json:\"fail_perc\"`\n\t}{\n\t\tItemReport: ItemReport(s),\n\t\tSuccesPerc: s.successPercentage(),\n\t\tFailPerc:   s.failedPercentage(),\n\t})\n}\n\nvar printJson = func(j []byte) {\n\tfmt.Println(string(j))\n}\n\nvar strKeyToJsonKey = map[string]string{\n\t\"dnsDuration\":           \"dns\",\n\t\"connDuration\":          \"connection\",\n\t\"tlsDuration\":           \"tls\",\n\t\"reqDuration\":           \"request_write\",\n\t\"serverProcessDuration\": \"server_processing\",\n\t\"resDuration\":           \"response_read\",\n\t\"duration\":              \"total\",\n}\n\nfunc (v verboseHttpRequestInfo) MarshalJSON() ([]byte, error) {\n\t// could not prepare req, correlation\n\tif v.Error != \"\" && isVerboseInfoRequestEmpty(v.Request) {\n\t\ttype alias struct {\n\t\t\tStepId           uint16                  `json:\"step_id\"`\n\t\t\tStepName         string                  `json:\"step_name\"`\n\t\t\tEnvs             map[string]interface{}  `json:\"envs\"`\n\t\t\tTestData         map[string]interface{}  `json:\"test_data\"`\n\t\t\tFailedCaptures   map[string]string       `json:\"failed_captures\"`\n\t\t\tFailedAssertions []types.FailedAssertion `json:\"failed_assertions\"`\n\t\t\tError            string                  `json:\"error\"`\n\t\t}\n\n\t\ta := alias{\n\t\t\tError:            v.Error,\n\t\t\tStepId:           v.StepId,\n\t\t\tStepName:         v.StepName,\n\t\t\tFailedCaptures:   v.FailedCaptures,\n\t\t\tFailedAssertions: v.FailedAssertions,\n\t\t\tEnvs:             v.Envs,\n\t\t\tTestData:         v.TestData,\n\t\t}\n\t\treturn json.Marshal(a)\n\t}\n\n\tif v.Error != \"\" { // server error no body\n\t\ttype alias struct {\n\t\t\tStepId           uint16                  `json:\"step_id\"`\n\t\t\tStepName         string                  `json:\"step_name\"`\n\t\t\tEnvs             map[string]interface{}  `json:\"envs\"`\n\t\t\tTestData         map[string]interface{}  `json:\"test_data\"`\n\t\t\tFailedCaptures   map[string]string       `json:\"failed_captures\"`\n\t\t\tFailedAssertions []types.FailedAssertion `json:\"failed_assertions\"`\n\t\t\tRequest          struct {\n\t\t\t\tUrl     string            `json:\"url\"`\n\t\t\t\tMethod  string            `json:\"method\"`\n\t\t\t\tHeaders map[string]string `json:\"headers\"`\n\t\t\t\tBody    interface{}       `json:\"body\"`\n\t\t\t} `json:\"request\"`\n\t\t\tError string `json:\"error\"`\n\t\t}\n\n\t\ta := alias{\n\t\t\tRequest:          v.Request,\n\t\t\tError:            v.Error,\n\t\t\tStepId:           v.StepId,\n\t\t\tStepName:         v.StepName,\n\t\t\tFailedCaptures:   v.FailedCaptures,\n\t\t\tFailedAssertions: v.FailedAssertions,\n\n\t\t\tEnvs:     v.Envs,\n\t\t\tTestData: v.TestData,\n\t\t}\n\t\treturn json.Marshal(a)\n\t}\n\n\ttype alias struct {\n\t\tStepId           uint16                  `json:\"step_id\"`\n\t\tStepName         string                  `json:\"step_name\"`\n\t\tEnvs             map[string]interface{}  `json:\"envs\"`\n\t\tTestData         map[string]interface{}  `json:\"test_data\"`\n\t\tFailedCaptures   map[string]string       `json:\"failed_captures\"`\n\t\tFailedAssertions []types.FailedAssertion `json:\"failed_assertions\"`\n\t\tRequest          struct {\n\t\t\tUrl     string            `json:\"url\"`\n\t\t\tMethod  string            `json:\"method\"`\n\t\t\tHeaders map[string]string `json:\"headers\"`\n\t\t\tBody    interface{}       `json:\"body\"`\n\t\t} `json:\"request\"`\n\t\tResponse verboseResponse `json:\"response\"`\n\t}\n\n\ta := alias{\n\t\tStepId:           v.StepId,\n\t\tStepName:         v.StepName,\n\t\tRequest:          v.Request,\n\t\tResponse:         v.Response,\n\t\tFailedCaptures:   v.FailedCaptures,\n\t\tFailedAssertions: v.FailedAssertions,\n\t\tEnvs:             v.Envs,\n\t\tTestData:         v.TestData,\n\t}\n\treturn json.Marshal(a)\n\n}\n"
  },
  {
    "path": "ddosify_engine/core/report/stdoutJson_test.go",
    "content": "/*\n*\n*\tDdosify - Load testing tool for any web system.\n*   Copyright (C) 2021  Ddosify (https://ddosify.com)\n*\n*   This program is free software: you can redistribute it and/or modify\n*   it under the terms of the GNU Affero General Public License as published\n*   by the Free Software Foundation, either version 3 of the License, or\n*   (at your option) any later version.\n*\n*   This program is distributed in the hope that it will be useful,\n*   but WITHOUT ANY WARRANTY; without even the implied warranty of\n*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n*   GNU Affero General Public License for more details.\n*\n*   You should have received a copy of the GNU Affero General Public License\n*   along with this program.  If not, see <https://www.gnu.org/licenses/>.\n*\n */\npackage report\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"go.ddosify.com/ddosify/core/assertion\"\n\t\"go.ddosify.com/ddosify/core/types\"\n)\n\nfunc TestInitStdoutJson(t *testing.T) {\n\tsj := &stdoutJson{}\n\tdebug := false\n\tsj.Init(debug, 0)\n\n\tif sj.doneChan == nil {\n\t\tt.Errorf(\"DoneChan should be initialized\")\n\t}\n\n\tif sj.result == nil {\n\t\tt.Errorf(\"Result map should be initialized\")\n\t}\n}\n\nfunc TestStdoutJsonAggregate(t *testing.T) {\n\tresponses := []*types.ScenarioResult{\n\t\t{\n\t\t\tStartTime: time.Now(),\n\t\t\tStepResults: []*types.ScenarioStepResult{\n\t\t\t\t{\n\t\t\t\t\tStepID:      1,\n\t\t\t\t\tStatusCode:  200,\n\t\t\t\t\tRequestTime: time.Now().Add(1),\n\t\t\t\t\tDuration:    time.Duration(10) * time.Second,\n\t\t\t\t\tCustom: map[string]interface{}{\n\t\t\t\t\t\t\"dnsDuration\":  time.Duration(5) * time.Second,\n\t\t\t\t\t\t\"connDuration\": time.Duration(5) * time.Second,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tStepID:      2,\n\t\t\t\t\tRequestTime: time.Now().Add(2),\n\t\t\t\t\tDuration:    time.Duration(30) * time.Second,\n\t\t\t\t\tErr:         types.RequestError{Type: types.ErrorConn, Reason: types.ReasonConnTimeout},\n\t\t\t\t\tCustom: map[string]interface{}{\n\t\t\t\t\t\t\"dnsDuration\":  time.Duration(10) * time.Second,\n\t\t\t\t\t\t\"connDuration\": time.Duration(20) * time.Second,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tStartTime: time.Now().Add(10),\n\t\t\tStepResults: []*types.ScenarioStepResult{\n\t\t\t\t{\n\t\t\t\t\tStepID:      1,\n\t\t\t\t\tStatusCode:  200,\n\t\t\t\t\tRequestTime: time.Now().Add(11),\n\t\t\t\t\tDuration:    time.Duration(30) * time.Second,\n\t\t\t\t\tCustom: map[string]interface{}{\n\t\t\t\t\t\t\"dnsDuration\":  time.Duration(10) * time.Second,\n\t\t\t\t\t\t\"connDuration\": time.Duration(20) * time.Second,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tStepID:      2,\n\t\t\t\t\tStatusCode:  401,\n\t\t\t\t\tRequestTime: time.Now().Add(12),\n\t\t\t\t\tDuration:    time.Duration(60) * time.Second,\n\t\t\t\t\tCustom: map[string]interface{}{\n\t\t\t\t\t\t\"dnsDuration\":  time.Duration(20) * time.Second,\n\t\t\t\t\t\t\"connDuration\": time.Duration(40) * time.Second,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfail1 := FailVerbose{}\n\tfail1.Count = 0\n\tfail1.ServerErrorDist.Count = 0\n\tfail1.ServerErrorDist.Reasons = make(map[string]int)\n\tfail1.AssertionErrorDist.Conditions = make(map[string]*AssertInfo)\n\titemReport1 := &ScenarioStepResultSummary{\n\t\tStatusCodeDist: map[int]int{200: 2},\n\t\tSuccessCount:   2,\n\t\tFail:           fail1,\n\t\tDurations: map[string]float32{\n\t\t\t\"dnsDuration\":  7.5,\n\t\t\t\"connDuration\": 12.5,\n\t\t\t\"duration\":     20,\n\t\t},\n\t}\n\n\tfail2 := FailVerbose{}\n\tfail2.Count = 1\n\tfail2.ServerErrorDist.Count = 1\n\tfail2.ServerErrorDist.Reasons = make(map[string]int)\n\tfail2.ServerErrorDist.Reasons[types.ReasonConnTimeout] = 1\n\tfail2.AssertionErrorDist.Conditions = make(map[string]*AssertInfo)\n\titemReport2 := &ScenarioStepResultSummary{\n\t\tStatusCodeDist: map[int]int{401: 1},\n\t\tSuccessCount:   1,\n\t\tFail:           fail2,\n\t\tDurations: map[string]float32{\n\t\t\t\"dnsDuration\":  20,\n\t\t\t\"connDuration\": 40,\n\t\t\t\"duration\":     60,\n\t\t},\n\t}\n\n\texpectedResult := Result{\n\t\tSuccessCount:      1,\n\t\tServerFailedCount: 1,\n\t\tAvgDuration:       90,\n\t\tStepResults: map[uint16]*ScenarioStepResultSummary{\n\t\t\tuint16(1): itemReport1,\n\t\t\tuint16(2): itemReport2,\n\t\t},\n\t}\n\n\ts := &stdoutJson{}\n\tdebug := false\n\ts.Init(debug, 0)\n\n\tfor _, r := range responses {\n\t\taggregate(s.result, r, make(map[uint16]map[string]int), 3)\n\t}\n\n\tif !compareResults(s.result, &expectedResult) {\n\t\tt.Errorf(\"Expected %#v, Found %#v\", expectedResult, *s.result)\n\t}\n}\n\nfunc TestStdoutJsonOutput(t *testing.T) {\n\t// Arrange\n\tfail1 := FailVerbose{}\n\tfail1.Count = 0\n\tfail1.ServerErrorDist.Count = 0\n\tfail1.ServerErrorDist.Reasons = make(map[string]int)\n\tfail1.AssertionErrorDist.Conditions = make(map[string]*AssertInfo)\n\titemReport1 := &ScenarioStepResultSummary{\n\t\tStatusCodeDist: map[int]int{200: 11},\n\t\tSuccessCount:   11,\n\t\tFail:           fail1,\n\t\tDurations: map[string]float32{\n\t\t\t\"dnsDuration\":  0.1897,\n\t\t\t\"connDuration\": 0.0003,\n\t\t\t\"duration\":     0.1900,\n\t\t},\n\t}\n\tfail2 := FailVerbose{}\n\tfail2.Count = 2\n\tfail2.ServerErrorDist.Count = 2\n\tfail2.ServerErrorDist.Reasons = make(map[string]int)\n\tfail2.ServerErrorDist.Reasons[types.ReasonConnTimeout] = 2\n\tfail2.AssertionErrorDist.Conditions = make(map[string]*AssertInfo)\n\titemReport2 := &ScenarioStepResultSummary{\n\t\tStatusCodeDist: map[int]int{401: 1, 200: 9},\n\t\tSuccessCount:   9,\n\t\tFail:           fail2,\n\t\tDurations: map[string]float32{\n\t\t\t\"dnsDuration\":  0.48000,\n\t\t\t\"connDuration\": 0.01356,\n\t\t\t\"duration\":     0.493566,\n\t\t},\n\t}\n\tresult := Result{\n\t\tSuccessCount:      9,\n\t\tServerFailedCount: 2,\n\t\tAvgDuration:       0.25637,\n\t\tStepResults: map[uint16]*ScenarioStepResultSummary{\n\t\t\tuint16(1): itemReport1,\n\t\t\tuint16(2): itemReport2,\n\t\t},\n\t\tTestStatus: \"success\",\n\t}\n\n\tvar output string\n\tprintJson = func(j []byte) {\n\t\toutput = string(j)\n\t}\n\n\texpectedOutputByte := []byte(`{\n\t\t\"success_perc\": 81,\n\t\t\"fail_perc\": 19,\n\t\t\"test_status\": \"success\",\n\t\t\"failed_criterias\":null,\n\t\t\"success_count\": 9,\n\t\t\"server_fail_count\":2,\n\t\t\"assertion_fail_count\":0,\n\t\t\"avg_duration\": 0.256,\n\t\t\"steps\": {\n\t\t\t\"1\": {\n\t\t\t\t\"name\": \"\",\n\t\t\t\t\"status_code_dist\": {\n\t\t\t\t\t\"200\": 11\n\t\t\t\t},\n\t\t\t\t\"fail\":{\n\t\t\t\t\t\"count\":0,\n\t\t\t\t\t\"assertions\":\n\t\t\t\t\t\t{\"count\":0,\"conditions\":{}},\n\t\t\t\t\t\"server\":\n\t\t\t\t\t\t{\"count\":0,\"reasons\":{}}\n\t\t\t\t},\n\t\t\t\t\"durations\": {\n\t\t\t\t\t\"connection\": 0,\n\t\t\t\t\t\"dns\": 0.19,\n\t\t\t\t\t\"total\": 0.19\n\t\t\t\t},\n\t\t\t\t\"success_count\": 11,\n\t\t\t\t\"success_perc\": 100,\n\t\t\t\t\"fail_perc\": 0\n\t\t\t},\n\t\t\t\"2\": {\n\t\t\t\t\"name\": \"\",\n\t\t\t\t\"status_code_dist\": {\n\t\t\t\t\t\"200\": 9,\n\t\t\t\t\t\"401\": 1\n\t\t\t\t},\n\t\t\t\t\"fail\":{\n\t\t\t\t\t\"count\":2,\n\t\t\t\t\t\"assertions\":\n\t\t\t\t\t\t{\"count\":0,\"conditions\":{}},\n\t\t\t\t\t\"server\":\n\t\t\t\t\t\t{\"count\":2,\"reasons\":{\"connection timeout\":2}}\n\t\t\t\t},\n\t\t\t\t\"durations\": {\n\t\t\t\t\t\"connection\": 0.014,\n\t\t\t\t\t\"dns\": 0.48,\n\t\t\t\t\t\"total\": 0.494\n\t\t\t\t},\n\t\t\t\t\"success_count\": 9,\n\t\t\t\t\"success_perc\": 81,\n\t\t\t\t\"fail_perc\": 19\n\t\t\t}\n\t\t}\n\t}`)\n\tbuffer := new(bytes.Buffer)\n\tjson.Compact(buffer, expectedOutputByte)\n\texpectedOutput := buffer.String()\n\n\t// Act\n\ts := &stdoutJson{result: &result}\n\ts.report()\n\n\t// Assert\n\tif output != expectedOutput {\n\t\tt.Errorf(\"Expected: %v, Found: %v\", expectedOutput, output)\n\t}\n}\n\nfunc TestStdoutJsonDebugModePrintsValidJson(t *testing.T) {\n\ts := &stdoutJson{}\n\ts.Init(true, 0)\n\ttestDoneChan := make(chan struct{}, 1)\n\n\trealOut := out\n\tr, w, _ := os.Pipe()\n\tout = w\n\tdefer func() {\n\t\tout = realOut\n\t}()\n\n\tinputChan := make(chan *types.ScenarioResult, 1)\n\tinputChan <- &types.ScenarioResult{}\n\tclose(inputChan)\n\n\tgo func() {\n\t\ts.Start(inputChan, nil)\n\t\tw.Close()\n\t}()\n\n\tgo func() {\n\t\t// wait for print and debug\n\t\t<-s.DoneChan()\n\n\t\tprintedOutput, _ := ioutil.ReadAll(r)\n\t\tif !json.Valid(printedOutput) {\n\t\t\tt.Errorf(\"Printed output is not valid json: %v\", string(printedOutput))\n\t\t}\n\t\ttestDoneChan <- struct{}{}\n\t}()\n\n\t<-testDoneChan\n\n}\n\nfunc TestVerboseHttpInfoMarshallingErrorCaseEmptyReq(t *testing.T) {\n\terrorStr := \"there is error\"\n\tvError := verboseHttpRequestInfo{\n\t\tStepId:   0,\n\t\tStepName: \"\",\n\t\tRequest: struct {\n\t\t\tUrl     string            \"json:\\\"url\\\"\"\n\t\t\tMethod  string            \"json:\\\"method\\\"\"\n\t\t\tHeaders map[string]string \"json:\\\"headers\\\"\"\n\t\t\tBody    interface{}       \"json:\\\"body\\\"\"\n\t\t}{},\n\t\tError: errorStr,\n\t}\n\n\tbytesWithErrorAndNoResponse, _ := vError.MarshalJSON()\n\n\tvar aliasStruct map[string]interface{}\n\tjson.Unmarshal(bytesWithErrorAndNoResponse, &aliasStruct)\n\n\tval, errExists := aliasStruct[\"error\"]\n\t_, respExists := aliasStruct[\"response\"]\n\t_, requestExists := aliasStruct[\"request\"]\n\n\tif !errExists {\n\t\tt.Errorf(\"Verbose Http Info should have error key\")\n\t} else if val != errorStr {\n\t\tt.Errorf(\"Verbose Http Info should have error value as : %s, found: %s\", errorStr, val)\n\t} else if respExists {\n\t\tt.Errorf(\"Verbose Http Info should not have response in case of error\")\n\t} else if requestExists {\n\t\tt.Errorf(\"Verbose Http Info should not have request in case of empty req and error\")\n\t}\n}\n\nfunc TestVerboseHttpInfoMarshallingErrorCase(t *testing.T) {\n\terrorStr := \"there is error\"\n\tvError := verboseHttpRequestInfo{\n\t\tStepId:   0,\n\t\tStepName: \"\",\n\t\tRequest: struct {\n\t\t\tUrl     string            \"json:\\\"url\\\"\"\n\t\t\tMethod  string            \"json:\\\"method\\\"\"\n\t\t\tHeaders map[string]string \"json:\\\"headers\\\"\"\n\t\t\tBody    interface{}       \"json:\\\"body\\\"\"\n\t\t}{\n\t\t\tUrl:     \"\",\n\t\t\tMethod:  \"GET\",\n\t\t\tHeaders: map[string]string{},\n\t\t\tBody:    \"some body\",\n\t\t},\n\t\tError: errorStr,\n\t}\n\n\tbytesWithErrorAndNoResponse, _ := vError.MarshalJSON()\n\n\tvar aliasStruct map[string]interface{}\n\tjson.Unmarshal(bytesWithErrorAndNoResponse, &aliasStruct)\n\n\tval, errExists := aliasStruct[\"error\"]\n\t_, respExists := aliasStruct[\"response\"]\n\n\tif !errExists {\n\t\tt.Errorf(\"Verbose Http Info should have error key\")\n\t} else if val != errorStr {\n\t\tt.Errorf(\"Verbose Http Info should have error value as : %s, found: %s\", errorStr, val)\n\t} else if respExists {\n\t\tt.Errorf(\"Verbose Http Info should not have response in case of error\")\n\t}\n}\n\nfunc TestVerboseHttpInfoMarshallingSuccessCase(t *testing.T) {\n\tnoErrorStr := \"\"\n\tvSuccess := verboseHttpRequestInfo{\n\t\tStepId:   0,\n\t\tStepName: \"\",\n\t\tRequest: struct {\n\t\t\tUrl     string            \"json:\\\"url\\\"\"\n\t\t\tMethod  string            \"json:\\\"method\\\"\"\n\t\t\tHeaders map[string]string \"json:\\\"headers\\\"\"\n\t\t\tBody    interface{}       \"json:\\\"body\\\"\"\n\t\t}{},\n\t\tResponse: verboseResponse{},\n\t\tError:    noErrorStr,\n\t}\n\n\tbytesWithResponseAndNoError, _ := vSuccess.MarshalJSON()\n\n\tvar aliasStruct map[string]interface{}\n\tjson.Unmarshal(bytesWithResponseAndNoError, &aliasStruct)\n\n\t_, errExists := aliasStruct[\"error\"]\n\t_, respExists := aliasStruct[\"response\"]\n\n\tif errExists {\n\t\tt.Errorf(\"Verbose Http Info should not have error key in success case\")\n\t} else if !respExists {\n\t\tt.Errorf(\"Verbose Http Info should have response in success case\")\n\t}\n}\n\nfunc TestStdoutJsonTestResultStatusShouldBeTrueWhenNoAssertion(t *testing.T) {\n\ts := &stdoutJson{}\n\ts.Init(false, 0)\n\n\tinputChan := make(chan *types.ScenarioResult, 1)\n\tinputChan <- &types.ScenarioResult{}\n\tclose(inputChan)\n\n\tgo func() {\n\t\ts.Start(inputChan, nil)\n\t}()\n\n\ttestStatus := <-s.DoneChan()\n\n\tif !testStatus {\n\t\tt.Errorf(\"Test status should be true\")\n\t}\n\n}\n\nfunc TestStdoutJsonTestResultStatusShouldBeFalseWhenAssertionsFail(t *testing.T) {\n\ts := &stdoutJson{}\n\ts.Init(false, 0)\n\n\tinputChan := make(chan *types.ScenarioResult, 1)\n\tinputChan <- &types.ScenarioResult{}\n\tclose(inputChan)\n\n\tassertionResultChan := make(chan assertion.TestAssertionResult, 1)\n\tassertionResultChan <- assertion.TestAssertionResult{\n\t\tFail: true,\n\t}\n\n\tgo func() {\n\t\ts.Start(inputChan, assertionResultChan)\n\t}()\n\n\ttestStatus := <-s.DoneChan()\n\n\tif testStatus {\n\t\tt.Errorf(\"Test status should be false\")\n\t}\n}\n"
  },
  {
    "path": "ddosify_engine/core/report/stdout_test.go",
    "content": "/*\n*\n*\tDdosify - Load testing tool for any web system.\n*   Copyright (C) 2021  Ddosify (https://ddosify.com)\n*\n*   This program is free software: you can redistribute it and/or modify\n*   it under the terms of the GNU Affero General Public License as published\n*   by the Free Software Foundation, either version 3 of the License, or\n*   (at your option) any later version.\n*\n*   This program is distributed in the hope that it will be useful,\n*   but WITHOUT ANY WARRANTY; without even the implied warranty of\n*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n*   GNU Affero General Public License for more details.\n*\n*   You should have received a copy of the GNU Affero General Public License\n*   along with this program.  If not, see <https://www.gnu.org/licenses/>.\n*\n */\npackage report\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"go.ddosify.com/ddosify/core/types\"\n)\n\n//TODO:move aggregator.go related tests cases to aggregator_test.go\n\nfunc TestScenarioStepReport(t *testing.T) {\n\ttests := []struct {\n\t\tname              string\n\t\ts                 ScenarioStepResultSummary\n\t\tsuccessPercentage int\n\t\tfailedPercentage  int\n\t}{\n\t\t{\"S:0-SF:0-AF:0\", ScenarioStepResultSummary{SuccessCount: 0, Fail: FailVerbose{Count: 0, ServerErrorDist: ServerErrVerbose{Count: 0}, AssertionErrorDist: AssertionErrVerbose{Count: 0}}}, 0, 0},\n\t\t{\"S:0-SF:1-AF:0\", ScenarioStepResultSummary{SuccessCount: 0, Fail: FailVerbose{Count: 1, ServerErrorDist: ServerErrVerbose{Count: 1}, AssertionErrorDist: AssertionErrVerbose{Count: 0}}}, 0, 100},\n\t\t{\"S:1-SF:0-AF:0\", ScenarioStepResultSummary{SuccessCount: 1, Fail: FailVerbose{Count: 0, ServerErrorDist: ServerErrVerbose{Count: 0}, AssertionErrorDist: AssertionErrVerbose{Count: 0}}}, 100, 0},\n\t\t{\"S:3-SF:9-AF:6\", ScenarioStepResultSummary{SuccessCount: 3, Fail: FailVerbose{Count: 9, ServerErrorDist: ServerErrVerbose{Count: 3}, AssertionErrorDist: AssertionErrVerbose{Count: 6}}}, 25, 75},\n\t\t{\"S:5-SF:2-AF:3\", ScenarioStepResultSummary{SuccessCount: 5, Fail: FailVerbose{Count: 5, ServerErrorDist: ServerErrVerbose{Count: 2}, AssertionErrorDist: AssertionErrVerbose{Count: 3}}}, 50, 50},\n\t}\n\n\tfor _, test := range tests {\n\t\ttf := func(t *testing.T) {\n\t\t\tsp := test.s.successPercentage()\n\t\t\tfp := test.s.failedPercentage()\n\n\t\t\tif test.successPercentage != sp {\n\t\t\t\tt.Errorf(\"SuccessPercentage Expected %d Found %d\", test.successPercentage, sp)\n\t\t\t}\n\n\t\t\tif test.failedPercentage != fp {\n\t\t\t\tt.Errorf(\"FailedPercentage Expected %d Found %d\", test.failedPercentage, fp)\n\t\t\t}\n\t\t}\n\t\tt.Run(test.name, tf)\n\t}\n}\n\nfunc TestResult(t *testing.T) {\n\ttests := []struct {\n\t\tname              string\n\t\tr                 Result\n\t\tsuccessPercentage int\n\t\tfailedPercentage  int\n\t}{\n\t\t{\"S:0-F:0\", Result{ServerFailedCount: 0, SuccessCount: 0}, 0, 0},\n\t\t{\"S:0-F:1\", Result{ServerFailedCount: 1, SuccessCount: 0}, 0, 100},\n\t\t{\"S:1-F:0\", Result{ServerFailedCount: 0, SuccessCount: 1}, 100, 0},\n\t\t{\"S:3-F:9\", Result{ServerFailedCount: 9, SuccessCount: 3}, 25, 75},\n\t}\n\n\tfor _, test := range tests {\n\t\ttf := func(t *testing.T) {\n\t\t\tsp := test.r.successPercentage()\n\t\t\tfp := test.r.failedPercentage()\n\n\t\t\tif test.successPercentage != sp {\n\t\t\t\tt.Errorf(\"SuccessPercentage Expected %d Found %d\", test.successPercentage, sp)\n\t\t\t}\n\n\t\t\tif test.failedPercentage != fp {\n\t\t\t\tt.Errorf(\"FailedPercentage Expected %d Found %d\", test.failedPercentage, fp)\n\t\t\t}\n\t\t}\n\t\tt.Run(test.name, tf)\n\t}\n}\n\nfunc TestInit(t *testing.T) {\n\ts := &stdout{}\n\tdebug := false\n\ts.Init(debug, 0)\n\n\tif s.doneChan == nil {\n\t\tt.Errorf(\"DoneChan should be initialized\")\n\t}\n\n\tif s.result == nil {\n\t\tt.Errorf(\"Result map should be initialized\")\n\t}\n}\n\nfunc TestPrintJsonBody(t *testing.T) {\n\tvar byteArr []byte\n\tbuffer := bytes.NewBuffer(byteArr)\n\n\tcontentTypeJson := \"application/json\"\n\tbody := map[string]interface{}{\"x\": \"y\"}\n\tprintBody(buffer, contentTypeJson, body)\n\n\tprintedBody := buffer.Bytes()\n\n\tif !json.Valid(printedBody) {\n\t\tt.Errorf(\"Printed body is not valid json: %v\", string(printedBody))\n\t}\n}\n\nfunc TestPrintBodyAsString(t *testing.T) {\n\tvar byteArr []byte\n\tbuffer := bytes.NewBuffer(byteArr)\n\n\tcontentTypeAny := \"any\"\n\tbody := \"argentina\"\n\tprintBody(buffer, contentTypeAny, body)\n\n\tprintedBody := buffer.Bytes()\n\n\tif !strings.Contains(string(printedBody), body) {\n\t\tt.Errorf(\"Printed body does not match expected: %s, found: %v\", body, string(printedBody))\n\t}\n}\n\nfunc TestStdoutPrintsHeadlinesInDebugMode(t *testing.T) {\n\ts := &stdout{}\n\ts.Init(true, 0)\n\ttestDoneChan := make(chan struct{}, 1)\n\n\t// listen to output\n\trealOut := out\n\tr, w, _ := os.Pipe()\n\tout = w\n\tdefer func() {\n\t\tout = realOut\n\t}()\n\n\tinputChan := make(chan *types.ScenarioResult, 1)\n\tinputChan <- &types.ScenarioResult{\n\t\tStepResults: []*types.ScenarioStepResult{\n\t\t\t{\n\t\t\t\tStepID:        0,\n\t\t\t\tStepName:      \"\",\n\t\t\t\tRequestID:     [16]byte{},\n\t\t\t\tStatusCode:    0,\n\t\t\t\tRequestTime:   time.Time{},\n\t\t\t\tDuration:      0,\n\t\t\t\tContentLength: 0,\n\t\t\t\tErr:           types.RequestError{},\n\t\t\t\tCustom:        map[string]interface{}{},\n\t\t\t},\n\t\t},\n\t}\n\tclose(inputChan)\n\n\tgo func() {\n\t\ts.Start(inputChan, nil)\n\t\tw.Close()\n\t}()\n\n\tgo func() {\n\t\t// wait for print and debug\n\t\t<-s.DoneChan()\n\n\t\tprintedOutput, err := ioutil.ReadAll(r)\n\t\tt.Log(err)\n\t\tt.Log(printedOutput)\n\n\t\toutStr := string(printedOutput)\n\t\tif !strings.Contains(outStr, \"- Request\") ||\n\t\t\t!strings.Contains(outStr, \"Headers:\") ||\n\t\t\t!strings.Contains(outStr, \"Body:\") ||\n\t\t\t!strings.Contains(outStr, \"- Response\") ||\n\t\t\t!strings.Contains(outStr, \"StatusCode:\") {\n\n\t\t\tt.Errorf(\"One or multiple headlines are missing in stdout debug mode\")\n\t\t}\n\n\t\ttestDoneChan <- struct{}{}\n\t}()\n\n\t<-testDoneChan\n\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/client_pool.go",
    "content": "package scenario\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"net/http/cookiejar\"\n\t\"net/url\"\n\n\t\"go.ddosify.com/ddosify/core/types\"\n\t\"go.ddosify.com/ddosify/core/util\"\n)\n\n// Factory is a function to create new connections.\ntype ClientFactoryMethod func() *http.Client\ntype ClientCloseMethod func(*http.Client)\n\n// NewClientPool returns a new pool based on buffered channels with an initial\n// capacity and maximum capacity. Factory is used when initial capacity is\n// greater than zero to fill the pool. A zero initialCap doesn't fill the Pool\n// until a new Get() is called. During a Get(), If there is no new client\n// available in the pool, a new client will be created via the Factory()\n// method.\nfunc NewClientPool(initialCap, maxCap int, engineMode string, factory ClientFactoryMethod, close ClientCloseMethod) (*util.Pool[*http.Client], error) {\n\tif initialCap < 0 || maxCap <= 0 || initialCap > maxCap {\n\t\treturn nil, errors.New(\"invalid capacity settings\")\n\t}\n\n\tpool := &util.Pool[*http.Client]{\n\t\tItems:   make(chan *http.Client, maxCap),\n\t\tFactory: factory,\n\t\tClose:   close,\n\t\tAfterPut: func(client *http.Client) {\n\t\t\t// if engine is in repeated mode, notify jar that cookies are already set\n\t\t\t// to avoid setting them again in the next iteration\n\t\t\tif engineMode == types.EngineModeRepeatedUser && client.Jar != nil && !client.Jar.(*cookieJarRepeated).firstIterPassed {\n\t\t\t\tclient.Jar.(*cookieJarRepeated).firstIterPassed = true\n\t\t\t}\n\t\t},\n\t}\n\n\t// create initial clients, if something goes wrong,\n\t// just close the pool error out.\n\tfor i := 0; i < initialCap; i++ {\n\t\tclient := pool.Factory()\n\t\tpool.Items <- client\n\t}\n\n\treturn pool, nil\n}\n\ntype cookieJarRepeated struct {\n\tdefaultCookieJar *cookiejar.Jar\n\tfirstIterPassed  bool\n}\n\nfunc NewCookieJarRepeated() (*cookieJarRepeated, error) {\n\tjar, err := cookiejar.New(nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &cookieJarRepeated{defaultCookieJar: jar}, nil\n}\n\n// SetCookies implements the http.CookieJar interface.\n// Only set cookies if they are not already set for repeated mode.\nfunc (c *cookieJarRepeated) SetCookies(u *url.URL, cookies []*http.Cookie) {\n\tif !c.firstIterPassed {\n\t\t// execute default behavior if no cookies are set\n\t\tc.defaultCookieJar.SetCookies(u, cookies)\n\t}\n}\n\n// Cookies implements the http.CookieJar interface.\nfunc (c *cookieJarRepeated) Cookies(u *url.URL) []*http.Cookie {\n\treturn c.defaultCookieJar.Cookies(u)\n}\n\nvar defaultFactory = func() *http.Client {\n\treturn &http.Client{}\n}\n\nvar defaultClose = func(c *http.Client) {\n\tc.CloseIdleConnections()\n}\n\n// createClientFactoryMethod returns a Factory function based on the engine mode.\nfunc createClientFactoryMethod(mode string, opts ...func(http.CookieJar)) ClientFactoryMethod {\n\tif mode == types.EngineModeRepeatedUser {\n\t\treturn func() *http.Client {\n\t\t\tjar, err := NewCookieJarRepeated()\n\t\t\tif err != nil {\n\t\t\t\treturn defaultFactory() // no cookie jar, use default factory\n\t\t\t}\n\n\t\t\tfor _, opt := range opts {\n\t\t\t\topt(jar)\n\t\t\t}\n\t\t\treturn &http.Client{Jar: jar}\n\t\t}\n\t}\n\n\t// distinct users mode\n\treturn func() *http.Client {\n\t\tjar, err := cookiejar.New(nil)\n\t\tif err != nil {\n\t\t\treturn defaultFactory() // no cookie jar, use default factory\n\t\t}\n\n\t\tfor _, opt := range opts {\n\t\t\topt(jar)\n\t\t}\n\t\treturn &http.Client{Jar: jar}\n\t}\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/client_pool_cookie_test.go",
    "content": "package scenario\n\nimport (\n\t\"net/http\"\n\t\"net/http/cookiejar\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"testing\"\n\n\t\"go.ddosify.com/ddosify/core/types\"\n)\n\n// If the user agent receives a new cookie with the same cookie-name,\n// domain-value, and path-value as a cookie that it has already stored,\n// the existing cookie is evicted and replaced with the new cookie\n\nfunc TestCookieManagerInRepeatedModeOnlySetInFirstIter(t *testing.T) {\n\tt.Parallel()\n\n\tcookieName := \"test\"\n\n\t// cookie value sent by server (first step)\n\tvalue1 := \"test1\"\n\tvalue2 := \"test2\" // login endpoint will set this value to cookie in second iteration, but we expect it to be ignored\n\n\t// cookies sent to second step\n\tvar cookieInFirstCall string\n\tvar cookieInSecondCall string\n\n\tloginCallCount := 0\n\torderCallCount := 0\n\n\tfirstReqHandler := func(w http.ResponseWriter, r *http.Request) {\n\t\t// set cookie, act as server\n\t\tvar val string\n\t\tif loginCallCount == 0 {\n\t\t\tval = value1\n\t\t} else {\n\t\t\tval = value2\n\t\t}\n\n\t\tcookie := http.Cookie{Name: cookieName, Value: val}\n\t\thttp.SetCookie(w, &cookie)\n\t\tloginCallCount++\n\t}\n\n\tsecondReqHandler := func(w http.ResponseWriter, r *http.Request) {\n\t\t// check cookie sent by client\n\t\tck, _ := r.Cookie(cookieName)\n\t\tif orderCallCount == 0 {\n\t\t\tcookieInFirstCall = ck.Value\n\t\t} else {\n\t\t\tcookieInSecondCall = ck.Value\n\t\t}\n\t\torderCallCount++\n\t}\n\n\tpathFirst := \"/login\"\n\tpathSecond := \"/order\"\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(pathFirst, firstReqHandler)\n\tmux.HandleFunc(pathSecond, secondReqHandler)\n\n\thost := httptest.NewServer(mux)\n\tdefer host.Close()\n\n\t// make sure we get the same client in second iteration, 1,1 means we have only one client\n\tpool, _ := NewClientPool(1, 1, types.EngineModeRepeatedUser, createClientFactoryMethod(types.EngineModeRepeatedUser), defaultClose)\n\n\tc := pool.Get()\n\t// using same client\n\n\t// first iteration\n\tc.Get(host.URL + pathFirst)\n\tc.Get(host.URL + pathSecond)\n\n\t// put client back to pool, so we can reuse it in second iteration\n\tpool.Put(c)\n\tc = pool.Get()\n\n\t// second iteration\n\tc.Get(host.URL + pathFirst)\n\tc.Get(host.URL + pathSecond)\n\n\tif cookieInFirstCall != cookieInSecondCall {\n\t\tt.Errorf(\"TestCookieManagerInRepeatedModeOnlySetInFirstIter, cookie should be same in second iteration\")\n\t}\n}\n\nfunc TestSetCookiesAppendToCurrentSliceOfCookies(t *testing.T) {\n\tjar, _ := cookiejar.New(nil)\n\tcookie1 := &http.Cookie{Name: \"test1\", Value: \"test1\"}\n\tcookie2 := &http.Cookie{Name: \"test2\", Value: \"test2\"}\n\n\t// set cookie\n\turl := url.URL{Scheme: \"http\", Host: \"test.com\"}\n\tjar.SetCookies(&url, []*http.Cookie{cookie1})\n\tjar.SetCookies(&url, []*http.Cookie{cookie2})\n\n\tcookies := jar.Cookies(&url)\n\n\tif len(cookies) != 2 {\n\t\tt.Errorf(\"TestCookieSetOverrides, expected 2 cookies, got %d\", len(cookies))\n\t}\n}\n\nfunc TestSetCookiesOverridesCookieWithSameName(t *testing.T) {\n\tjar, _ := cookiejar.New(nil)\n\tcookie1 := &http.Cookie{Name: \"test\", Value: \"test1\"}\n\tcookie2 := &http.Cookie{Name: \"test\", Value: \"test2\"}\n\n\t// set cookie\n\turl := url.URL{Scheme: \"http\", Host: \"test.com\"}\n\tjar.SetCookies(&url, []*http.Cookie{cookie1})\n\tjar.SetCookies(&url, []*http.Cookie{cookie2})\n\n\tcookies := jar.Cookies(&url)\n\n\tif len(cookies) != 1 || cookies[0].Value != \"test2\" {\n\t\tt.Errorf(\"TestSetCookiesOverridesCookieWithSameName, expected 1 cookie with value 'test2', got %d\", len(cookies))\n\t}\n}\n\nfunc TestSetCookiesDeletesIfUnnecessary(t *testing.T) {\n\tjar, _ := cookiejar.New(nil)\n\tcookie1 := &http.Cookie{Name: \"test\", Value: \"test1\", Secure: false}\n\tcookie2 := &http.Cookie{Name: \"test\", Value: \"test2\", Secure: true}\n\n\t// set cookie\n\turl := url.URL{Scheme: \"http\", Host: \"test.com\"}\n\tjar.SetCookies(&url, []*http.Cookie{cookie1})\n\tcookies := jar.Cookies(&url)\n\tif len(cookies) != 1 {\n\t\tt.Errorf(\"TestSetCookiesDeletesIfUnnecessary, expected 1 cookie with value 'test1', got %d\", len(cookies))\n\t}\n\tjar.SetCookies(&url, []*http.Cookie{cookie2})\n\tcookies = jar.Cookies(&url)\n\n\t// cookiejar deletes cookies with same name if url scheme is http and cookie is secure\n\tif len(cookies) != 0 {\n\t\tt.Errorf(\"TestSetCookiesDeletesIfUnnecessary, expected 1 cookie with value 'test2', got %d\", len(cookies))\n\t}\n}\n\nfunc TestSetCookiesUrlScheme(t *testing.T) {\n\tjar, _ := cookiejar.New(nil)\n\tcookie1 := &http.Cookie{Name: \"test1\", Value: \"test1\"}\n\tcookie2 := &http.Cookie{Name: \"test2\", Value: \"test2\"}\n\n\t// set cookie\n\turl := url.URL{Scheme: \"\", Host: \"test.com\"}\n\t// expect set cookies to be ignored since url scheme is empty\n\tjar.SetCookies(&url, []*http.Cookie{cookie1})\n\tjar.SetCookies(&url, []*http.Cookie{cookie2})\n\n\tcookies := jar.Cookies(&url)\n\n\tif len(cookies) != 0 {\n\t\tt.Errorf(\"TestSetCookiesUrlScheme, expected 0 cookies, got %d\", len(cookies))\n\t}\n}\n\nfunc TestSetCookiesSecure(t *testing.T) {\n\tt.Parallel()\n\n\tsecureCookieName := \"https-cookie\"\n\tsecureCookieVal := \"secure-cookie\"\n\n\tvar httpServerGotCookie *http.Cookie\n\treqHandler := func(w http.ResponseWriter, r *http.Request) {\n\t\thttpServerGotCookie, _ = r.Cookie(secureCookieName)\n\t}\n\n\tvar httpsServerGotCookie *http.Cookie\n\tsecureReqHandler := func(w http.ResponseWriter, r *http.Request) {\n\t\thttpsServerGotCookie, _ = r.Cookie(secureCookieName)\n\t}\n\n\tpath := \"/default\"\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(path, reqHandler)\n\n\thost := httptest.NewServer(mux)\n\tdefer host.Close()\n\n\tpathSecure := \"/secure\"\n\tmuxHttps := http.NewServeMux()\n\tmuxHttps.HandleFunc(pathSecure, secureReqHandler)\n\n\tsecureHost := httptest.NewTLSServer(muxHttps)\n\tdefer secureHost.Close()\n\n\tc := secureHost.Client()\n\tc.Jar, _ = cookiejar.New(nil)\n\n\tsecureCookie := http.Cookie{Name: secureCookieName, Value: secureCookieVal, Secure: true}\n\t// set cookies\n\turl, _ := url.Parse(secureHost.URL)\n\tc.Jar.SetCookies(url, []*http.Cookie{&secureCookie})\n\n\tc.Get(host.URL + path)\n\tc.Get(secureHost.URL + pathSecure)\n\n\t// expect secure cookie to be sent only to secure host\n\n\tif httpServerGotCookie != nil {\n\t\tt.Errorf(\"TestSetCookiesSecure, expected no cookie to be sent to http host, got %s\", httpServerGotCookie.Value)\n\t}\n\n\tif httpsServerGotCookie == nil || httpsServerGotCookie.Value != secureCookieVal {\n\t\tt.Errorf(\"TestSetCookiesSecure, expected cookie to be sent to https host, got %s\", httpsServerGotCookie.Value)\n\t}\n}\n\nfunc TestPutInitialCookiesInJarFactory(t *testing.T) {\n\tmode := types.EngineModeDistinctUser\n\tpool, _ := NewClientPool(1, 1, mode, putInitialCookiesInJarFactory(mode,\n\t\t[]*http.Cookie{\n\t\t\t{\n\t\t\t\tName:   \"test\",\n\t\t\t\tValue:  \"test\",\n\t\t\t\tDomain: \"ddosify.com\",\n\t\t\t\tSecure: false,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:   \"test\",\n\t\t\t\tValue:  \"test\",\n\t\t\t\tDomain: \"servdown.com\",\n\t\t\t\tSecure: true,\n\t\t\t}}), defaultClose)\n\n\tc := pool.Get()\n\n\tcookies := c.Jar.Cookies(&url.URL{Scheme: \"http\", Host: \"ddosify.com\"})\n\n\tif len(cookies) != 1 {\n\t\tt.Errorf(\"TestPutInitialCookiesInJarFactory, expected 1 cookie, got %d\", len(cookies))\n\t}\n\n\tif cookies[0].Value != \"test\" {\n\t\tt.Errorf(\"TestPutInitialCookiesInJarFactory, expected cookie value 'test', got %s\", cookies[0].Value)\n\t}\n\n\tcookies = c.Jar.Cookies(&url.URL{Scheme: \"https\", Host: \"servdown.com\"})\n\n\tif len(cookies) != 1 {\n\t\tt.Errorf(\"TestPutInitialCookiesInJarFactory, expected 1 cookie, got %d\", len(cookies))\n\t}\n\n\tif cookies[0].Value != \"test\" {\n\t\tt.Errorf(\"TestPutInitialCookiesInJarFactory, expected cookie value 'test', got %s\", cookies[0].Value)\n\t}\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/data/csv.go",
    "content": "package data\n\nimport (\n\t\"encoding/csv\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"strconv\"\n\n\t\"go.ddosify.com/ddosify/core/types\"\n)\n\nfunc validateConf(conf types.CsvConf) error {\n\tif !(conf.Order == \"random\" || conf.Order == \"sequential\") {\n\t\treturn fmt.Errorf(\"unsupported order %s, should be random|sequential\", conf.Order)\n\t}\n\treturn nil\n}\n\ntype RemoteCsvError struct { // UnWrappable\n\tmsg        string\n\twrappedErr error\n}\n\nfunc (nf RemoteCsvError) Error() string {\n\tif nf.wrappedErr != nil {\n\t\treturn fmt.Sprintf(\"%s,%s\", nf.msg, nf.wrappedErr.Error())\n\t}\n\treturn nf.msg\n}\n\nfunc (nf RemoteCsvError) Unwrap() error {\n\treturn nf.wrappedErr\n}\n\nfunc ReadCsv(conf types.CsvConf) ([]map[string]interface{}, error) {\n\terr := validateConf(conf)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar reader io.Reader\n\n\tvar pUrl *url.URL\n\tif pUrl, err = url.ParseRequestURI(conf.Path); err == nil && pUrl.IsAbs() { // url\n\t\treq, err := http.NewRequest(http.MethodGet, conf.Path, nil)\n\t\tif err != nil {\n\t\t\treturn nil, wrapAsCsvError(\"can not create request\", err)\n\t\t}\n\t\tresp, err := http.DefaultClient.Do(req)\n\t\tif err != nil {\n\t\t\treturn nil, wrapAsCsvError(\"can not get response\", err)\n\t\t}\n\n\t\tif !(resp.StatusCode >= 200 && resp.StatusCode <= 299) {\n\t\t\treturn nil, wrapAsCsvError(fmt.Sprintf(\"Test Data: request to remote url (%s) failed. Status Code: %d\", conf.Path, resp.StatusCode), nil)\n\t\t}\n\t\treader = resp.Body\n\t\tdefer resp.Body.Close()\n\t} else if _, err = os.Stat(conf.Path); err == nil { // local file path\n\t\tf, err := os.Open(conf.Path)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treader = f\n\t\tdefer f.Close()\n\t} else {\n\t\treturn nil, wrapAsCsvError(fmt.Sprintf(\"can not parse path: %s\", conf.Path), err)\n\t}\n\n\t// read csv values using csv.Reader\n\tcsvReader := csv.NewReader(reader)\n\tcsvReader.Comma = []rune(conf.Delimiter)[0]\n\tcsvReader.TrimLeadingSpace = true\n\tcsvReader.LazyQuotes = conf.AllowQuota\n\n\tdata, err := csvReader.ReadAll()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif conf.SkipFirstLine {\n\t\tdata = data[1:]\n\t}\n\n\trt := make([]map[string]interface{}, 0) // unclear how many empty line exist\n\n\tfor _, row := range data {\n\t\tif conf.SkipEmptyLine && emptyLine(row) {\n\t\t\tcontinue\n\t\t}\n\t\tx := map[string]interface{}{}\n\t\tfor index, tag := range conf.Vars {\n\t\t\ti, err := strconv.Atoi(index)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tif i >= len(row) {\n\t\t\t\treturn nil, fmt.Errorf(\"index number out of range, check your vars or delimiter\")\n\t\t\t}\n\n\t\t\t// convert\n\t\t\tvar val interface{}\n\t\t\tswitch tag.Type {\n\t\t\tcase \"json\":\n\t\t\t\terr := json.Unmarshal([]byte(row[i]), &val)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\"can not convert %s to json,%v\", row[i], err)\n\t\t\t\t}\n\t\t\tcase \"int\":\n\t\t\t\tvar err error\n\t\t\t\tval, err = strconv.Atoi(row[i])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\"can not convert %s to int,%v\", row[i], err)\n\t\t\t\t}\n\t\t\tcase \"float\":\n\t\t\t\tvar err error\n\t\t\t\tval, err = strconv.ParseFloat(row[i], 64)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\"can not convert %s to float,%v\", row[i], err)\n\t\t\t\t}\n\t\t\tcase \"bool\":\n\t\t\t\tvar err error\n\t\t\t\tval, err = strconv.ParseBool(row[i])\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\"can not convert %s to bool,%v\", row[i], err)\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\tval = row[i]\n\t\t\t}\n\t\t\tx[tag.Tag] = val\n\t\t}\n\t\trt = append(rt, x)\n\t}\n\n\treturn rt, nil\n}\n\nfunc emptyLine(row []string) bool {\n\tfor _, field := range row {\n\t\tif field != \"\" {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc wrapAsCsvError(msg string, err error) RemoteCsvError {\n\tvar csvReqError RemoteCsvError\n\tcsvReqError.msg = msg\n\tcsvReqError.wrappedErr = err\n\treturn csvReqError\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/data/csv_test.go",
    "content": "package data\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"go.ddosify.com/ddosify/core/types\"\n)\n\nfunc TestValidateCsvConf(t *testing.T) {\n\tt.Parallel()\n\tconf := types.CsvConf{\n\t\tPath:          \"\",\n\t\tDelimiter:     \"\",\n\t\tSkipFirstLine: false,\n\t\tVars:          map[string]types.Tag{},\n\t\tSkipEmptyLine: false,\n\t\tAllowQuota:    false,\n\t\tOrder:         \"\",\n\t}\n\n\tconf.Order = \"invalidOrder\"\n\terr := validateConf(conf)\n\n\tif err == nil {\n\t\tt.Errorf(\"TestValidateCsvConf should be errored\")\n\t}\n}\n\nfunc TestReadCsv_RemoteErr(t *testing.T) {\n\tt.Parallel()\n\tconf := types.CsvConf{\n\t\tPath:          \"https://invalidurl.com/csv\",\n\t\tDelimiter:     \";\",\n\t\tSkipFirstLine: true,\n\t\tVars: map[string]types.Tag{\n\t\t\t\"0\": {Tag: \"name\", Type: \"string\"},\n\t\t\t\"3\": {Tag: \"payload\", Type: \"json\"},\n\t\t\t\"4\": {Tag: \"age\", Type: \"int\"},\n\t\t\t\"5\": {Tag: \"percent\", Type: \"float\"},\n\t\t\t\"6\": {Tag: \"boolField\", Type: \"bool\"},\n\t\t},\n\t\tSkipEmptyLine: true,\n\t\tAllowQuota:    true,\n\t\tOrder:         \"sequential\",\n\t}\n\n\t_, err := ReadCsv(conf)\n\n\tif err == nil {\n\t\tt.Errorf(\"TestReadCsv_RemoteErr %v\", err)\n\t}\n\n\tvar remoteCsvErr RemoteCsvError\n\tif !errors.As(err, &remoteCsvErr) {\n\t\tt.Errorf(\"Expected: %v, Found: %v\", remoteCsvErr, err)\n\t}\n\tif remoteCsvErr.Unwrap() == nil {\n\t\tt.Errorf(\"Expected: %v, Found: %v\", \"not nil\", remoteCsvErr.Unwrap())\n\t}\n}\n\nfunc TestWrapAsRemoteCsvError(t *testing.T) {\n\tmsg := \"xxyy\"\n\tcsvErr := wrapAsCsvError(msg, fmt.Errorf(\"error\"))\n\n\tvar remoteCsvErr RemoteCsvError\n\tif !errors.As(csvErr, &remoteCsvErr) {\n\t\tt.Errorf(\"Expected: %v, Found: %v\", remoteCsvErr, csvErr)\n\t}\n\terrmsg := remoteCsvErr.Error()\n\tif errmsg != msg+\",error\" {\n\t\tt.Errorf(\"Expected: %v, Found: %v\", msg, remoteCsvErr.msg)\n\t}\n}\n\nfunc TestReadCsvFromRemote(t *testing.T) {\n\t// Test server\n\thandler := func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(http.StatusUnauthorized)\n\t}\n\n\tpath := \"/csv\"\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(path, handler)\n\n\tserver := httptest.NewServer(mux)\n\tdefer server.Close()\n\n\tconf := types.CsvConf{\n\t\tPath:          server.URL + path,\n\t\tDelimiter:     \";\",\n\t\tSkipFirstLine: true,\n\t\tVars: map[string]types.Tag{\n\t\t\t\"0\": {Tag: \"name\", Type: \"string\"},\n\t\t\t\"3\": {Tag: \"payload\", Type: \"json\"},\n\t\t\t\"4\": {Tag: \"age\", Type: \"int\"},\n\t\t\t\"5\": {Tag: \"percent\", Type: \"float\"},\n\t\t\t\"6\": {Tag: \"boolField\", Type: \"bool\"},\n\t\t},\n\t\tSkipEmptyLine: true,\n\t\tAllowQuota:    true,\n\t\tOrder:         \"sequential\",\n\t}\n\n\t_, err := ReadCsv(conf)\n\n\tif err == nil {\n\t\tt.Errorf(\"TestReadCsvFromRemote %v\", err)\n\t}\n\n\tvar remoteCsvErr RemoteCsvError\n\tif !errors.As(err, &remoteCsvErr) {\n\t\tt.Errorf(\"Expected: %v, Found: %v\", remoteCsvErr, err)\n\t}\n\n}\n\nfunc TestReadCsv(t *testing.T) {\n\tt.Parallel()\n\tconf := types.CsvConf{\n\t\tPath:          \"../../../config/config_testdata/test.csv\",\n\t\tDelimiter:     \";\",\n\t\tSkipFirstLine: true,\n\t\tVars: map[string]types.Tag{\n\t\t\t\"0\": {Tag: \"name\", Type: \"string\"},\n\t\t\t\"3\": {Tag: \"payload\", Type: \"json\"},\n\t\t\t\"4\": {Tag: \"age\", Type: \"int\"},\n\t\t\t\"5\": {Tag: \"percent\", Type: \"float\"},\n\t\t\t\"6\": {Tag: \"boolField\", Type: \"bool\"},\n\t\t},\n\t\tSkipEmptyLine: true,\n\t\tAllowQuota:    true,\n\t\tOrder:         \"sequential\",\n\t}\n\n\trows, err := ReadCsv(conf)\n\n\tif err != nil {\n\t\tt.Errorf(\"TestReadCsv %v\", err)\n\t}\n\n\tfirstName := rows[0][\"name\"].(string)\n\texpectedName := \"Kenan\"\n\tif !strings.EqualFold(firstName, expectedName) {\n\t\tt.Errorf(\"TestReadCsv found: %s , expected: %s\", firstName, expectedName)\n\t}\n\n\tfirstAge := rows[0][\"age\"].(int)\n\texpectedAge := 25\n\tif firstAge != expectedAge {\n\t\tt.Errorf(\"TestReadCsv found: %d , expected: %d\", firstAge, expectedAge)\n\t}\n\n\tfirstPercent := rows[0][\"percent\"].(float64)\n\texpectedPercent := 22.3\n\tif firstPercent != expectedPercent {\n\t\tt.Errorf(\"TestReadCsv found: %f , expected: %f\", firstPercent, expectedPercent)\n\t}\n\n\tfirstBool := rows[0][\"boolField\"].(bool)\n\texpectedBool := true\n\tif firstBool != expectedBool {\n\t\tt.Errorf(\"TestReadCsv found: %t , expected: %t\", firstBool, expectedBool)\n\t}\n\n\tfirstPayload := rows[0][\"payload\"].(map[string]interface{})\n\texpectedPayload := map[string]interface{}{\n\t\t\"data\": map[string]interface{}{\n\t\t\t\"profile\": map[string]interface{}{\n\t\t\t\t\"name\": \"Kenan\",\n\t\t\t},\n\t\t},\n\t}\n\tif !reflect.DeepEqual(firstPayload, expectedPayload) {\n\t\tt.Errorf(\"TestReadCsv found: %#v , expected: %#v\", firstPayload, expectedPayload)\n\t}\n\n\tsecondPayload := rows[1][\"payload\"].([]interface{})\n\texpectedPayload2 := []interface{}{5.0, 6.0, 7.0} // underlying type float64\n\tif !reflect.DeepEqual(secondPayload, expectedPayload2) {\n\t\tt.Errorf(\"TestReadCsv found: %#v , expected: %#v\", secondPayload, expectedPayload2)\n\t}\n}\n\nvar table = []struct {\n\tconf    types.CsvConf\n\tlatency float64\n}{\n\t{\n\t\tconf: types.CsvConf{\n\t\t\tPath:          \"config_testdata/test.csv\",\n\t\t\tDelimiter:     \";\",\n\t\t\tSkipFirstLine: true,\n\t\t\tVars: map[string]types.Tag{\n\t\t\t\t\"0\": {Tag: \"name\", Type: \"string\"},\n\t\t\t\t\"3\": {Tag: \"payload\", Type: \"json\"},\n\t\t\t\t\"4\": {Tag: \"age\", Type: \"int\"},\n\t\t\t\t\"5\": {Tag: \"percent\", Type: \"float\"},\n\t\t\t\t\"6\": {Tag: \"boolField\", Type: \"bool\"},\n\t\t\t},\n\t\t\tSkipEmptyLine: true,\n\t\t\tAllowQuota:    true,\n\t\t\tOrder:         \"sequential\",\n\t\t},\n\t},\n}\n\nfunc TestBenchmarkCsvRead(t *testing.T) {\n\tfor _, v := range table {\n\n\t\tres := testing.Benchmark(func(b *testing.B) {\n\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\tReadCsv(v.conf)\n\t\t\t}\n\t\t})\n\n\t\tfmt.Printf(\"ns:%d\", res.T.Nanoseconds())\n\t\tfmt.Printf(\"N:%d\", res.N)\n\t}\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/requester/base.go",
    "content": "/*\n*\n*\tDdosify - Load testing tool for any web system.\n*   Copyright (C) 2021  Ddosify (https://ddosify.com)\n*\n*   This program is free software: you can redistribute it and/or modify\n*   it under the terms of the GNU Affero General Public License as published\n*   by the Free Software Foundation, either version 3 of the License, or\n*   (at your option) any later version.\n*\n*   This program is distributed in the hope that it will be useful,\n*   but WITHOUT ANY WARRANTY; without even the implied warranty of\n*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n*   GNU Affero General Public License for more details.\n*\n*   You should have received a copy of the GNU Affero General Public License\n*   along with this program.  If not, see <https://www.gnu.org/licenses/>.\n*\n */\n\npackage requester\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"net/url\"\n\n\t\"go.ddosify.com/ddosify/core/scenario/scripting/injection\"\n\t\"go.ddosify.com/ddosify/core/types\"\n)\n\n// Requester is the interface that abstracts different protocols' request sending implementations.\n// Protocol field in the types.ScenarioStep determines which requester implementation to use.\ntype Requester interface {\n\tType() string\n\tDone()\n}\n\ntype HttpRequesterI interface {\n\tInit(ctx context.Context, ss types.ScenarioStep, url *url.URL, debug bool, ei *injection.EnvironmentInjector) error\n\tSend(client *http.Client, envs map[string]interface{}) *types.ScenarioStepResult // should use its own client if client is nil\n}\n\n// NewRequester is the factory method of the Requester.\nfunc NewRequester(s types.ScenarioStep) (requester Requester, err error) {\n\trequester = &HttpRequester{} // we have only HttpRequester type for now, add check for rpc in future\n\treturn\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/requester/base_test.go",
    "content": "/*\n*\n*\tDdosify - Load testing tool for any web system.\n*   Copyright (C) 2021  Ddosify (https://ddosify.com)\n*\n*   This program is free software: you can redistribute it and/or modify\n*   it under the terms of the GNU Affero General Public License as published\n*   by the Free Software Foundation, either version 3 of the License, or\n*   (at your option) any later version.\n*\n*   This program is distributed in the hope that it will be useful,\n*   but WITHOUT ANY WARRANTY; without even the implied warranty of\n*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n*   GNU Affero General Public License for more details.\n*\n*   You should have received a copy of the GNU Affero General Public License\n*   along with this program.  If not, see <https://www.gnu.org/licenses/>.\n*\n */\n\npackage requester\n\nimport (\n\t\"reflect\"\n\n\t\"go.ddosify.com/ddosify/core/types\"\n)\n\nvar protocolStrategiesStructMap = map[string]reflect.Type{\n\ttypes.ProtocolHTTP:  reflect.TypeOf(&HttpRequester{}),\n\ttypes.ProtocolHTTPS: reflect.TypeOf(&HttpRequester{}),\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/requester/http.go",
    "content": "/*\n*\n*\tDdosify - Load testing tool for any web system.\n*   Copyright (C) 2021  Ddosify (https://ddosify.com)\n*\n*   This program is free software: you can redistribute it and/or modify\n*   it under the terms of the GNU Affero General Public License as published\n*   by the Free Software Foundation, either version 3 of the License, or\n*   (at your option) any later version.\n*\n*   This program is distributed in the hope that it will be useful,\n*   but WITHOUT ANY WARRANTY; without even the implied warranty of\n*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n*   GNU Affero General Public License for more details.\n*\n*   You should have received a copy of the GNU Affero General Public License\n*   along with this program.  If not, see <https://www.gnu.org/licenses/>.\n*\n */\n\npackage requester\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptrace\"\n\t\"net/url\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n\t\"go.ddosify.com/ddosify/core/scenario/scripting/assertion\"\n\t\"go.ddosify.com/ddosify/core/scenario/scripting/assertion/evaluator\"\n\t\"go.ddosify.com/ddosify/core/scenario/scripting/extraction\"\n\t\"go.ddosify.com/ddosify/core/scenario/scripting/injection\"\n\t\"go.ddosify.com/ddosify/core/types\"\n\t\"go.ddosify.com/ddosify/core/types/regex\"\n\t\"golang.org/x/net/http2\"\n)\n\ntype HttpRequester struct {\n\tctx                  context.Context\n\tproxyAddr            *url.URL\n\tpacket               types.ScenarioStep\n\tclient               *http.Client\n\trequest              *http.Request\n\tei                   *injection.EnvironmentInjector\n\tcontainsDynamicField map[string]bool\n\tcontainsEnvVar       map[string]bool\n\tdebug                bool\n\tdynamicRgx           *regexp.Regexp\n\tenvRgx               *regexp.Regexp\n}\n\n// Init creates a client with the given scenarioItem. HttpRequester uses the same http.Client for all requests\nfunc (h *HttpRequester) Init(ctx context.Context, s types.ScenarioStep, proxyAddr *url.URL, debug bool,\n\tei *injection.EnvironmentInjector) (err error) {\n\th.ctx = ctx\n\th.packet = s\n\th.proxyAddr = proxyAddr\n\th.ei = ei\n\th.containsDynamicField = make(map[string]bool)\n\th.containsEnvVar = make(map[string]bool)\n\th.debug = debug\n\th.dynamicRgx = regexp.MustCompile(regex.DynamicVariableRegex)\n\th.envRgx = regexp.MustCompile(regex.EnvironmentVariableRegex)\n\n\t// Transport segment\n\ttr := h.initTransport()\n\ttr.MaxIdleConnsPerHost = 60000\n\ttr.MaxIdleConns = 0\n\n\t// http client\n\th.client = &http.Client{Transport: tr, Timeout: time.Duration(h.packet.Timeout) * time.Second}\n\tif val, ok := h.packet.Custom[\"disable-redirect\"]; ok {\n\t\tval := val.(bool)\n\t\tif val {\n\t\t\th.client.CheckRedirect = func(req *http.Request, via []*http.Request) error {\n\t\t\t\treturn http.ErrUseLastResponse\n\t\t\t}\n\t\t}\n\t}\n\n\t// Request instance\n\terr = h.initRequestInstance()\n\tif err != nil {\n\t\treturn\n\t}\n\n\t// body\n\tif h.dynamicRgx.MatchString(h.packet.Payload) {\n\t\t_, err = h.ei.InjectDynamic(h.packet.Payload)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\th.containsDynamicField[\"body\"] = true\n\t}\n\n\tif h.envRgx.MatchString(h.packet.Payload) {\n\t\th.containsEnvVar[\"body\"] = true\n\t}\n\n\t// url\n\tif h.dynamicRgx.MatchString(h.packet.URL) {\n\t\t_, err = h.ei.InjectDynamic(h.packet.URL)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\th.containsDynamicField[\"url\"] = true\n\t}\n\n\tif h.envRgx.MatchString(h.packet.URL) {\n\t\th.containsEnvVar[\"url\"] = true\n\t}\n\n\t// header\n\tfor k, values := range h.request.Header {\n\t\tfor _, v := range values {\n\t\t\tif h.dynamicRgx.MatchString(k) || h.dynamicRgx.MatchString(v) {\n\t\t\t\t_, err = h.ei.InjectDynamic(k)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t_, err = h.ei.InjectDynamic(v)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\th.containsDynamicField[\"header\"] = true\n\t\t\t}\n\t\t\tif h.envRgx.MatchString(k) || h.envRgx.MatchString(v) {\n\t\t\t\th.containsEnvVar[\"header\"] = true\n\t\t\t}\n\t\t}\n\t}\n\n\t// basicauth\n\tif h.dynamicRgx.MatchString(h.packet.Auth.Username) || h.dynamicRgx.MatchString(h.packet.Auth.Password) {\n\t\t_, err = h.ei.InjectDynamic(h.packet.Auth.Username)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\t_, err = h.ei.InjectDynamic(h.packet.Auth.Password)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\th.containsDynamicField[\"basicauth\"] = true\n\t}\n\n\tif h.envRgx.MatchString(h.packet.Auth.Username) || h.envRgx.MatchString(h.packet.Auth.Password) {\n\t\th.containsEnvVar[\"basicauth\"] = true\n\t}\n\n\treturn\n}\n\nfunc (h *HttpRequester) Done() {\n\t// MaxIdleConnsPerHost and MaxIdleConns at Transport layer configuration\n\t// let us reuse the connections when keep-alive enabled(default)\n\t// When the Job is finished, we have to Close idle connections to prevent sockets to lock in at the TIME_WAIT state.\n\t// Otherwise, the next job can't use these sockets because they are reserved for the current target host.\n\n\th.client.CloseIdleConnections()\n}\n\nfunc (h *HttpRequester) Send(client *http.Client, envs map[string]interface{}) (res *types.ScenarioStepResult) {\n\tvar statusCode int\n\tvar contentLength int64\n\tvar requestErr types.RequestError\n\tvar reqStartTime = time.Now()\n\n\t// for debug mode\n\tvar copiedReqBody []byte\n\tvar respBody []byte\n\tvar respHeaders http.Header\n\tvar bodyReadErr error\n\tvar extractedVars = make(map[string]interface{})\n\tvar failedCaptures = make(map[string]string, 0)\n\tvar failedAssertions = make([]types.FailedAssertion, 0)\n\n\tvar usableVars = make(map[string]interface{}, len(envs))\n\tfor k, v := range envs {\n\t\tusableVars[k] = v\n\t}\n\n\tif client == nil {\n\t\t// engine mode is 'ddosify'\n\t\t// if passed client is nil , use requesters client that is dedicated to one step, thereby one transport\n\t\tclient = h.client\n\t} else {\n\t\t// engine mode is 'distinct-user' or 'repeated-user'\n\t\t// passed client is used for multiple steps throughout an iteration, update transport\n\t\tif client.Transport == nil {\n\t\t\tclient.Transport = h.initTransport()\n\t\t\tclient.Transport.(*http.Transport).MaxConnsPerHost = 1 // use same connection per host throughout an iteration\n\t\t} else {\n\t\t\th.updateTransport(client.Transport.(*http.Transport))\n\t\t}\n\n\t\t// update client timeout\n\t\tclient.Timeout = time.Duration(h.packet.Timeout) * time.Second\n\t}\n\n\tdurations := &duration{\n\t\tserverProcessDurCh:   make(chan time.Duration, 1),\n\t\tserverProcessStartCh: make(chan time.Time, 1),\n\t\tresDurCh:             make(chan time.Duration, 1),\n\t\tresStartCh:           make(chan time.Time, 1),\n\t}\n\theadersAddedByClient := make(map[string][]string)\n\ttrace := newTrace(durations, h.proxyAddr, headersAddedByClient)\n\n\thttpReq, err := h.prepareReq(usableVars, trace)\n\n\tif err != nil { // could not prepare req\n\t\trequestErr.Type = types.ErrorInvalidRequest\n\t\trequestErr.Reason = fmt.Sprintf(\"Could not prepare req, %s\", err.Error())\n\t\tres = &types.ScenarioStepResult{\n\t\t\tStepID:    h.packet.ID,\n\t\t\tStepName:  h.packet.Name,\n\t\t\tRequestID: uuid.New(),\n\t\t\tErr:       requestErr,\n\t\t}\n\n\t\treturn res\n\t}\n\n\tif httpReq.Body != nil {\n\t\tif int64(len(h.packet.Payload)) > 300000 {\n\t\t\t// Don't store req bodies bigger than 300KB\n\t\t\tcopiedReqBody = []byte(\"too long body\")\n\t\t} else {\n\t\t\t// TODO: make copiedReqBody an io.Reader, and pass same underlying buffer to both httpReq and copiedReqBody\n\t\t\t// copy\n\t\t\tbuf := new(bytes.Buffer)\n\t\t\tio.Copy(buf, httpReq.Body)\n\t\t\tcopiedReqBody = buf.Bytes()\n\t\t\thttpReq.Body = io.NopCloser(bytes.NewBuffer(copiedReqBody)) // restore body\n\t\t}\n\t}\n\n\t// Action\n\thttpRes, err := client.Do(httpReq)\n\tif err != nil {\n\t\trequestErr = fetchErrType(err)\n\t\tfailedCaptures = h.captureEnvironmentVariables(nil, nil, nil, extractedVars)\n\t} else {\n\t\t// got response, no timeout or any other error, resStart should be set\n\t\tdurations.setResDur()\n\t}\n\n\t// From the DOC: If the Body is not both read to EOF and closed,\n\t// the Client's underlying RoundTripper (typically Transport)\n\t// may not be able to re-use a persistent TCP connection to the server for a subsequent \"keep-alive\" request.\n\tif httpRes != nil {\n\t\t// read resp body conditionally\n\t\tif h.debug || len(h.packet.EnvsToCapture) > 0 || len(h.packet.Assertions) > 0 {\n\t\t\trespBody, bodyReadErr = io.ReadAll(httpRes.Body)\n\t\t\tif bodyReadErr != nil {\n\t\t\t\trequestErr = fetchErrType(bodyReadErr)\n\t\t\t}\n\t\t} else {\n\t\t\t// do not write into memory, just read\n\t\t\t_, bodyReadErr = io.Copy(io.Discard, httpRes.Body)\n\t\t\tif bodyReadErr != nil {\n\t\t\t\trequestErr = fetchErrType(bodyReadErr)\n\t\t\t}\n\t\t}\n\n\t\thttpRes.Body.Close()\n\t\trespHeaders = httpRes.Header\n\t\tcontentLength = httpRes.ContentLength\n\t\tstatusCode = httpRes.StatusCode\n\t\tcookies := make(map[string]*http.Cookie, len(httpRes.Cookies()))\n\t\tfor _, cookie := range httpRes.Cookies() {\n\t\t\tcookies[cookie.Name] = &http.Cookie{\n\t\t\t\tName:     cookie.Name,\n\t\t\t\tValue:    cookie.Value,\n\t\t\t\tPath:     cookie.Path,\n\t\t\t\tDomain:   cookie.Domain,\n\t\t\t\tExpires:  cookie.Expires,\n\t\t\t\tSecure:   cookie.Secure,\n\t\t\t\tHttpOnly: cookie.HttpOnly,\n\t\t\t\tSameSite: cookie.SameSite,\n\t\t\t\tRaw:      cookie.Raw,\n\t\t\t\tUnparsed: cookie.Unparsed,\n\t\t\t}\n\t\t}\n\n\t\t// capture\n\t\tif len(h.packet.EnvsToCapture) > 0 {\n\t\t\tfailedCaptures = h.captureEnvironmentVariables(httpRes.Header, respBody, cookies, extractedVars)\n\t\t}\n\n\t\t// assert\n\t\tif len(h.packet.Assertions) > 0 {\n\t\t\t_, failedAssertions = h.applyAssertions(&evaluator.AssertEnv{\n\t\t\t\tStatusCode:   int64(httpRes.StatusCode),\n\t\t\t\tResponseSize: int64(len(respBody)),\n\t\t\t\tResponseTime: durations.totalDuration().Milliseconds(), // in ms\n\t\t\t\tBody:         string(respBody),\n\t\t\t\tHeaders:      httpRes.Header,\n\t\t\t\tVariables:    concatEnvs(envs, extractedVars),\n\t\t\t\tCookies:      cookies,\n\t\t\t})\n\t\t}\n\t}\n\n\tvar ddResTime time.Duration\n\tif httpRes != nil && httpRes.Header.Get(\"x-ddsfy-response-time\") != \"\" {\n\t\tresTime, _ := strconv.ParseFloat(httpRes.Header.Get(\"x-ddsfy-response-time\"), 8)\n\t\tddResTime = time.Duration(resTime*1000) * time.Millisecond\n\t}\n\n\t// close duration channels, so that if any goroutine is waiting on them, it can return\n\tgo time.AfterFunc(10*time.Millisecond, durationCloseFunc(durations))\n\n\t// Finalize\n\tres = &types.ScenarioStepResult{\n\t\tStepID:        h.packet.ID,\n\t\tStepName:      h.packet.Name,\n\t\tRequestID:     uuid.New(),\n\t\tStatusCode:    statusCode,\n\t\tRequestTime:   reqStartTime,\n\t\tDuration:      durations.totalDuration(),\n\t\tContentLength: contentLength,\n\t\tErr:           requestErr,\n\n\t\tUrl:         httpReq.URL.String(),\n\t\tMethod:      httpReq.Method,\n\t\tReqHeaders:  concatHeaders(httpReq.Header, headersAddedByClient),\n\t\tReqBody:     copiedReqBody,\n\t\tRespHeaders: respHeaders,\n\t\tRespBody:    respBody,\n\n\t\tCustom: map[string]interface{}{\n\t\t\t\"dnsDuration\":           durations.getDNSDur(),\n\t\t\t\"connDuration\":          durations.getConnDur(),\n\t\t\t\"reqDuration\":           durations.getReqDur(),\n\t\t\t\"resDuration\":           durations.getResDur(),\n\t\t\t\"serverProcessDuration\": durations.getServerProcessDur(),\n\t\t},\n\t\tExtractedEnvs:    extractedVars,\n\t\tUsableEnvs:       usableVars,\n\t\tFailedCaptures:   failedCaptures,\n\t\tFailedAssertions: failedAssertions,\n\t}\n\n\tif strings.EqualFold(h.request.URL.Scheme, types.ProtocolHTTPS) { // TODOcorr : check here, used URL.scheme instead TODOcorr\n\t\tres.Custom[\"tlsDuration\"] = durations.getTLSDur()\n\t}\n\n\tif ddResTime != 0 {\n\t\tres.Custom[\"ddResponseTime\"] = ddResTime\n\t}\n\n\treturn\n}\n\nvar durationCloseFunc = func(d *duration) func() {\n\treturn func() {\n\t\td.close()\n\t}\n}\n\nfunc concatEnvs(envs1, envs2 map[string]interface{}) map[string]interface{} {\n\ttotal := make(map[string]interface{})\n\n\tfor k, v := range envs1 {\n\t\ttotal[k] = v\n\t}\n\n\tfor k, v := range envs2 {\n\t\ttotal[k] = v\n\t}\n\n\treturn total\n}\n\nfunc concatHeaders(envs1, envs2 map[string][]string) map[string][]string {\n\ttotal := make(map[string][]string)\n\n\tfor k, v := range envs1 {\n\t\ttotal[k] = v\n\t}\n\n\tfor k, v := range envs2 {\n\t\ttotal[k] = v\n\t}\n\n\treturn total\n}\n\nfunc (h *HttpRequester) prepareReq(envs map[string]interface{}, trace *httptrace.ClientTrace) (*http.Request, error) {\n\tre := regexp.MustCompile(regex.DynamicVariableRegex)\n\thttpReq := h.request.Clone(h.ctx)\n\n\tbody := h.packet.Payload\n\tif h.containsDynamicField[\"body\"] || h.containsEnvVar[\"body\"] {\n\t\tpieces := h.ei.GenerateBodyPieces(body, envs)\n\t\tcustomReader := injection.DdosifyBodyReader{\n\t\t\tBody:   body,\n\t\t\tPieces: pieces,\n\t\t}\n\t\thttpReq.Body = &customReader\n\t\thttpReq.ContentLength = int64(injection.GetContentLength(pieces))\n\t} else {\n\t\t// if body is constant, we can just set it\n\t\thttpReq.Body = io.NopCloser(bytes.NewReader(injection.StringToBytes(body)))\n\t\thttpReq.ContentLength = int64(len(body))\n\t}\n\n\t// url\n\thostURL := h.packet.URL\n\tvar errURL error\n\n\tif h.containsDynamicField[\"url\"] {\n\t\thostURL, _ = h.ei.InjectDynamic(hostURL)\n\t}\n\tif h.containsEnvVar[\"url\"] {\n\t\thostURL, errURL = h.ei.InjectEnv(hostURL, envs)\n\t\tif errURL != nil {\n\t\t\treturn nil, errURL\n\t\t}\n\t}\n\n\thttpReq.URL, errURL = url.Parse(hostURL)\n\tif errURL != nil {\n\t\treturn nil, errURL\n\t}\n\n\t// If Host is not given in the header, set it from the original URL\n\t// Note that a temporary url used in initRequest\n\tif httpReq.Header.Get(\"Host\") == \"\" {\n\t\thttpReq.Host = httpReq.URL.Host\n\t}\n\n\t// header\n\tif h.containsDynamicField[\"header\"] {\n\t\tfor k, values := range httpReq.Header {\n\t\t\tfor _, v := range values {\n\t\t\t\tkk := k\n\t\t\t\tvv := v\n\t\t\t\tif re.MatchString(v) {\n\t\t\t\t\tvv, _ = h.ei.InjectDynamic(v)\n\t\t\t\t}\n\t\t\t\tif re.MatchString(k) {\n\t\t\t\t\tkk, _ = h.ei.InjectDynamic(k)\n\t\t\t\t\thttpReq.Header.Del(k)\n\t\t\t\t}\n\t\t\t\thttpReq.Header.Set(kk, vv)\n\t\t\t}\n\t\t}\n\t}\n\n\tif h.containsEnvVar[\"header\"] {\n\t\tfor k, v := range httpReq.Header {\n\t\t\t// check vals\n\t\t\tfor i, vv := range v {\n\t\t\t\tif h.envRgx.MatchString(vv) {\n\t\t\t\t\tvvv, err := h.ei.InjectEnv(vv, envs)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\t\t\t\t\tv[i] = vvv\n\t\t\t\t}\n\t\t\t}\n\t\t\thttpReq.Header.Set(k, strings.Join(v, \",\"))\n\n\t\t\t// check keys\n\t\t\tif h.envRgx.MatchString(k) {\n\t\t\t\tkk, err := h.ei.InjectEnv(k, envs)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\thttpReq.Header.Del(k)\n\t\t\t\thttpReq.Header.Set(kk, strings.Join(v, \",\"))\n\t\t\t}\n\t\t}\n\t}\n\n\tusername, password := h.packet.Auth.Username, h.packet.Auth.Password\n\tif h.containsDynamicField[\"basicauth\"] {\n\t\tusername, _ = h.ei.InjectDynamic(username)\n\t\tpassword, _ = h.ei.InjectDynamic(password)\n\t}\n\tif h.containsEnvVar[\"basicauth\"] {\n\t\tvar err error\n\t\tusername, err = h.ei.InjectEnv(username, envs)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tpassword, err = h.ei.InjectEnv(password, envs)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tif username != \"\" && password != \"\" {\n\t\thttpReq.SetBasicAuth(username, password)\n\t}\n\n\thttpReq = httpReq.WithContext(httptrace.WithClientTrace(httpReq.Context(), trace))\n\treturn httpReq, nil\n}\n\n// Currently we can't detect exact error type by returned err.\n// But we need to find an elegant way instead of this.\nfunc fetchErrType(err error) types.RequestError {\n\tvar requestErr types.RequestError = types.RequestError{\n\t\tType:   types.ErrorUnkown,\n\t\tReason: err.Error()}\n\n\tue, ok := err.(*url.Error)\n\tif ok {\n\t\terrString := ue.Error()\n\t\tif strings.Contains(errString, \"proxyconnect\") {\n\t\t\tif strings.Contains(errString, \"connection refused\") {\n\t\t\t\trequestErr = types.RequestError{Type: types.ErrorProxy, Reason: types.ReasonProxyFailed}\n\t\t\t} else if strings.Contains(errString, \"Client.Timeout\") {\n\t\t\t\trequestErr = types.RequestError{Type: types.ErrorProxy, Reason: types.ReasonProxyTimeout}\n\t\t\t} else {\n\t\t\t\trequestErr = types.RequestError{Type: types.ErrorProxy, Reason: errString}\n\t\t\t}\n\t\t} else if strings.Contains(errString, context.DeadlineExceeded.Error()) {\n\t\t\trequestErr = types.RequestError{Type: types.ErrorConn, Reason: types.ReasonConnTimeout}\n\t\t} else if strings.Contains(errString, \"Client.Timeout exceeded while awaiting headers\") {\n\t\t\trequestErr = types.RequestError{Type: types.ErrorConn, Reason: types.ReasonConnTimeout}\n\t\t} else if strings.Contains(errString, \"i/o timeout\") {\n\t\t\trequestErr = types.RequestError{Type: types.ErrorConn, Reason: types.ReasonReadTimeout}\n\t\t} else if strings.Contains(errString, \"connection refused\") {\n\t\t\trequestErr = types.RequestError{Type: types.ErrorConn, Reason: types.ReasonConnRefused}\n\t\t} else if strings.Contains(errString, context.Canceled.Error()) {\n\t\t\trequestErr = types.RequestError{Type: types.ErrorIntented, Reason: types.ReasonCtxCanceled}\n\t\t} else if strings.Contains(errString, \"connection reset by peer\") {\n\t\t\trequestErr = types.RequestError{Type: types.ErrorConn, Reason: \"connection reset by peer\"}\n\t\t} else {\n\t\t\trequestErr = types.RequestError{Type: types.ErrorConn, Reason: errString}\n\t\t}\n\t}\n\n\treturn requestErr\n}\n\nfunc (h *HttpRequester) initTransport() *http.Transport {\n\ttr := &http.Transport{\n\t\tTLSClientConfig: h.initTLSConfig(),\n\t\tProxy:           http.ProxyURL(h.proxyAddr),\n\t}\n\n\ttr.DisableKeepAlives = false\n\tif h.packet.Headers[\"Connection\"] == \"close\" {\n\t\ttr.DisableKeepAlives = true\n\t}\n\tif val, ok := h.packet.Custom[\"disable-compression\"]; ok {\n\t\ttr.DisableCompression = val.(bool)\n\t}\n\tif val, ok := h.packet.Custom[\"h2\"]; ok {\n\t\tval := val.(bool)\n\t\tif val {\n\t\t\thttp2.ConfigureTransport(tr)\n\t\t}\n\t}\n\treturn tr\n}\n\nfunc (h *HttpRequester) updateTransport(tr *http.Transport) {\n\ttr.TLSClientConfig = h.initTLSConfig()\n\ttr.Proxy = http.ProxyURL(h.proxyAddr)\n\n\ttr.DisableKeepAlives = false\n\tif h.packet.Headers[\"Connection\"] == \"close\" {\n\t\ttr.DisableKeepAlives = true\n\t}\n\tif val, ok := h.packet.Custom[\"disable-compression\"]; ok {\n\t\ttr.DisableCompression = val.(bool)\n\t}\n\tif val, ok := h.packet.Custom[\"h2\"]; ok {\n\t\tval := val.(bool)\n\t\tif val {\n\t\t\thttp2.ConfigureTransport(tr)\n\t\t}\n\t}\n}\n\nfunc (h *HttpRequester) initTLSConfig() *tls.Config {\n\ttlsConfig := &tls.Config{\n\t\tInsecureSkipVerify: true,\n\t}\n\n\tif h.packet.CertPool != nil && h.packet.Cert.Certificate != nil {\n\t\ttlsConfig.RootCAs = h.packet.CertPool\n\t\ttlsConfig.Certificates = []tls.Certificate{h.packet.Cert}\n\t}\n\n\tif val, ok := h.packet.Custom[\"hostname\"]; ok {\n\t\ttlsConfig.ServerName = val.(string)\n\t}\n\treturn tlsConfig\n}\n\nfunc (h *HttpRequester) initRequestInstance() (err error) {\n\t// TODOcorr: https://{{TARGET_URL}} or http://{{TARGET_URL}} could not be parsed, invalidHost\n\t// give a basic url for now here to avoid initiating request every time\n\t// override later on prepareReq\n\ttempValidUrl := \"app.ddosify.com\"\n\tif strings.HasPrefix(h.packet.URL, \"https://\") {\n\t\ttempValidUrl = \"https://\" + \"app.ddosify.com\"\n\t}\n\th.request, err = http.NewRequest(h.packet.Method, tempValidUrl, bytes.NewBufferString(h.packet.Payload))\n\tif err != nil {\n\t\treturn\n\t}\n\n\t// Headers\n\theader := make(http.Header)\n\tfor k, v := range h.packet.Headers {\n\t\theader.Set(k, v)\n\t\t// Since we use a temp url, we need to override the request.Host either\n\t\t// it will be app.ddosify.com\n\t\t// or it will be the host from the headers\n\t\t// later on prepareReq, we will override the host if it is set in the headers\n\t\tif strings.EqualFold(k, \"Host\") {\n\t\t\th.request.Host = v\n\t\t}\n\t}\n\n\th.request.Header = header\n\n\t// Auth should be set after header assignment.\n\tif h.packet.Auth != (types.Auth{}) {\n\t\th.request.SetBasicAuth(h.packet.Auth.Username, h.packet.Auth.Password)\n\t}\n\n\t// If keep-alive is false, prevent the reuse of the previous TCP connection at the request layer also.\n\th.request.Close = false\n\tif h.packet.Headers[\"Connection\"] == \"close\" {\n\t\th.request.Close = true\n\t}\n\treturn\n}\n\nfunc (h *HttpRequester) Type() string {\n\treturn \"HTTP\"\n}\n\nfunc newTrace(duration *duration, proxyAddr *url.URL, headersByClient map[string][]string) *httptrace.ClientTrace {\n\tvar dnsStart, connStart, tlsStart, reqStart time.Time\n\n\t// According to the doc in the trace.go;\n\t// Some of the hooks below can be triggered multiple times in case of retried connections, \"Happy Eyeballs\" etc..\n\t// Also, some of the hooks can be triggered after the TCP roundtrip if the request is not successfully finished.\n\t// To fetch the time only at the first trigger and prevent data race we need to use the mutex mechanism.\n\t// For start times, except resStart, this mutex is been using.\n\t// For duration calculations, \"duration\" struct internally uses another mutex.\n\tvar m sync.Mutex\n\n\treturn &httptrace.ClientTrace{\n\t\tDNSStart: func(info httptrace.DNSStartInfo) {\n\t\t\tm.Lock()\n\t\t\tif dnsStart.IsZero() {\n\t\t\t\tdnsStart = time.Now()\n\t\t\t}\n\t\t\tm.Unlock()\n\t\t},\n\t\tDNSDone: func(dnsInfo httptrace.DNSDoneInfo) {\n\t\t\tm.Lock()\n\t\t\t// no need to handle error in here. We can detect it at http.Client.Do return.\n\t\t\tif dnsInfo.Err == nil {\n\t\t\t\tduration.setDNSDur(time.Since(dnsStart))\n\t\t\t}\n\t\t\tm.Unlock()\n\t\t},\n\t\tConnectStart: func(network, addr string) {\n\t\t\tm.Lock()\n\t\t\tif connStart.IsZero() {\n\t\t\t\tconnStart = time.Now()\n\t\t\t}\n\t\t\tm.Unlock()\n\t\t},\n\t\tConnectDone: func(network, addr string, err error) {\n\t\t\tm.Lock()\n\t\t\t// no need to handle error in here. We can detect it at http.Client.Do return.\n\t\t\tif err == nil {\n\t\t\t\tduration.setConnDur(time.Since(connStart))\n\t\t\t}\n\t\t\tm.Unlock()\n\t\t},\n\t\tTLSHandshakeStart: func() {\n\t\t\tm.Lock()\n\t\t\t// This hook can be hit 2 times;\n\t\t\t// If both proxy and target are HTTPS\n\t\t\t//\tFirst hit is for proxy, second is for target.\n\t\t\t//  To catch the second TLS start time (for target), we can't perform tlsStart.IsZero() check here.\n\t\t\ttlsStart = time.Now()\n\t\t\tm.Unlock()\n\t\t},\n\t\tTLSHandshakeDone: func(cs tls.ConnectionState, e error) {\n\t\t\tm.Lock()\n\t\t\t// This hook can be hit 2 times;\n\t\t\t// If proxy: HTTPS, target: HTTPS\n\t\t\t//\tFirst hit is for proxy, second is for target TLS\n\t\t\t//  We need to calculate TLS duration if and only if the TLS handshake process is for the target.\n\n\t\t\tif e == nil {\n\t\t\t\tif proxyAddr == nil || proxyAddr.Hostname() != cs.ServerName {\n\t\t\t\t\tduration.setTLSDur(time.Since(tlsStart))\n\t\t\t\t}\n\t\t\t}\n\t\t\tm.Unlock()\n\t\t},\n\t\tGotConn: func(connInfo httptrace.GotConnInfo) {\n\t\t\tm.Lock()\n\t\t\tif reqStart.IsZero() {\n\t\t\t\treqStart = time.Now()\n\t\t\t}\n\t\t\tm.Unlock()\n\t\t},\n\t\tWroteRequest: func(w httptrace.WroteRequestInfo) {\n\t\t\t// no need to handle error in here. We can detect it at http.Client.Do return.\n\t\t\tif w.Err == nil {\n\t\t\t\tgo duration.setReqDur(time.Since(reqStart))\n\t\t\t\tgo duration.setServerProcessStart(time.Now())\n\t\t\t}\n\t\t},\n\t\tGotFirstResponseByte: func() {\n\t\t\tgo duration.setServerProcessDur()\n\t\t\tgo duration.setResStartTime(time.Now())\n\t\t},\n\t\tWroteHeaderField: func(key string, value []string) {\n\t\t\theadersByClient[key] = value\n\t\t},\n\t}\n}\n\nfunc (h *HttpRequester) applyAssertions(assertEnv *evaluator.AssertEnv) (bool, []types.FailedAssertion) {\n\t// result, failedAssertionIndex, assertionError\n\tassertions := h.packet.Assertions\n\tassertionsSuccess := true\n\tfailedAssertions := []types.FailedAssertion{}\n\tfor _, rule := range assertions {\n\t\tboolVal, err := assertion.Assert(rule, assertEnv)\n\n\t\tif err != nil {\n\t\t\tassertErr := err.(assertion.AssertionError)\n\t\t\tfailedAssertions = append(failedAssertions, types.FailedAssertion{\n\t\t\t\tRule:     assertErr.Rule(),\n\t\t\t\tReceived: assertErr.Received(),\n\t\t\t\tReason:   assertErr.Unwrap().Error(),\n\t\t\t})\n\t\t\tassertionsSuccess = false\n\t\t}\n\t\tif !boolVal {\n\t\t\tassertionsSuccess = false\n\t\t}\n\t}\n\n\tif assertionsSuccess {\n\t\treturn true, nil\n\t}\n\n\treturn false, failedAssertions\n\n}\n\nfunc (h *HttpRequester) captureEnvironmentVariables(header http.Header, respBody []byte,\n\tcookies map[string]*http.Cookie, extractedVars map[string]interface{}) map[string]string {\n\tvar err error\n\tfailedCaptures := make(map[string]string, 0)\n\tvar captureError extraction.ExtractionError\n\n\t// request failed, only set default value for later steps\n\tif header == nil && respBody == nil {\n\t\tfor _, ce := range h.packet.EnvsToCapture {\n\t\t\textractedVars[ce.Name] = \"\" // default value for not extracted envs\n\t\t\tfailedCaptures[ce.Name] = \"request failed\"\n\t\t}\n\t\treturn failedCaptures\n\t}\n\n\t// extract from response\n\tfor _, ce := range h.packet.EnvsToCapture {\n\t\tvar val interface{}\n\t\tswitch ce.From {\n\t\tcase types.Header:\n\t\t\tval, err = extraction.Extract(header, ce)\n\t\tcase types.Body:\n\t\t\tval, err = extraction.Extract(respBody, ce)\n\t\tcase types.Cookie:\n\t\t\tval, err = extraction.Extract(cookies, ce)\n\t\t}\n\t\tif err != nil && errors.As(err, &captureError) {\n\t\t\t// do not terminate in case of a capture error, continue capturing\n\t\t\textractedVars[ce.Name] = \"\" // default value for not extracted envs\n\t\t\tfailedCaptures[ce.Name] = captureError.Error()\n\t\t\tcontinue\n\t\t}\n\t\textractedVars[ce.Name] = val\n\t}\n\n\treturn failedCaptures\n}\n\ntype duration struct {\n\n\t// DNS lookup duration. If IP:Port porvided instead of domain, this will be 0\n\tdnsDur time.Duration\n\n\t// TCP connection setup duration\n\tconnDur time.Duration\n\n\t// TLS handshake duration. For HTTP this will be 0\n\ttlsDur time.Duration\n\n\t// Request write duration\n\treqDur time.Duration\n\n\t// Response read duration\n\tresDur time.Duration\n\n\t// Duration between full request write to first response. AKA Time To First Byte (TTFB)\n\tserverProcessDur time.Duration\n\n\t// Time at response reading start\n\tresStart         time.Time\n\tresStartCh       chan time.Time\n\tresStartChClosed bool\n\n\tserverProcessDurCh       chan time.Duration\n\tserverProcessDurChClosed bool\n\n\tserverProcessStartCh       chan time.Time\n\tserverProcessStartChClosed bool\n\n\tresDurCh       chan time.Duration\n\tresDurChClosed bool\n\n\tmu        sync.Mutex\n\tchMu      sync.Mutex\n\tgetChLock sync.Mutex\n}\n\nfunc (d *duration) setResStartTime(t time.Time) {\n\td.chMu.Lock()\n\tdefer d.chMu.Unlock()\n\n\tif !d.resStartChClosed {\n\t\td.resStartCh <- t\n\t\td.resStartChClosed = true\n\t\tclose(d.resStartCh)\n\t}\n}\n\n// this maybe called multiple times in case of retried requests by WroteRequest hook\nfunc (d *duration) setServerProcessStart(t time.Time) {\n\td.chMu.Lock()\n\tdefer d.chMu.Unlock()\n\n\tif !d.serverProcessStartChClosed {\n\t\td.serverProcessStartCh <- t\n\t\td.serverProcessStartChClosed = true\n\t\tclose(d.serverProcessStartCh)\n\t}\n}\n\nfunc (d *duration) setDNSDur(t time.Duration) {\n\td.mu.Lock()\n\tdefer d.mu.Unlock()\n\tif d.dnsDur == 0 {\n\t\td.dnsDur = t\n\t}\n}\n\nfunc (d *duration) getDNSDur() time.Duration {\n\td.mu.Lock()\n\tdefer d.mu.Unlock()\n\treturn d.dnsDur\n}\n\nfunc (d *duration) setTLSDur(t time.Duration) {\n\td.mu.Lock()\n\tdefer d.mu.Unlock()\n\tif d.tlsDur == 0 {\n\t\td.tlsDur = t\n\t}\n}\n\nfunc (d *duration) getTLSDur() time.Duration {\n\td.mu.Lock()\n\tdefer d.mu.Unlock()\n\treturn d.tlsDur\n}\n\nfunc (d *duration) setConnDur(t time.Duration) {\n\td.mu.Lock()\n\tdefer d.mu.Unlock()\n\tif d.connDur == 0 {\n\t\td.connDur = t\n\t}\n}\n\nfunc (d *duration) getConnDur() time.Duration {\n\td.mu.Lock()\n\tdefer d.mu.Unlock()\n\treturn d.connDur\n}\n\nfunc (d *duration) setReqDur(t time.Duration) {\n\td.mu.Lock()\n\tdefer d.mu.Unlock()\n\tif d.reqDur == 0 {\n\t\td.reqDur = t\n\t}\n}\n\nfunc (d *duration) getReqDur() time.Duration {\n\td.mu.Lock()\n\tdefer d.mu.Unlock()\n\treturn d.reqDur\n}\n\nfunc (d *duration) setServerProcessDur() {\n\tserverProcessStart := <-d.serverProcessStartCh\n\td.chMu.Lock()\n\tdefer d.chMu.Unlock()\n\n\tif !d.serverProcessDurChClosed {\n\t\td.serverProcessDurCh <- time.Since(serverProcessStart)\n\t\td.serverProcessDurChClosed = true\n\t\tclose(d.serverProcessDurCh)\n\t}\n\n}\n\nfunc (d *duration) getServerProcessDur() time.Duration {\n\td.getChLock.Lock()\n\tdefer d.getChLock.Unlock()\n\n\tserverProcessDur, ok := <-d.serverProcessDurCh\n\n\tif !ok { // channel closed, dur already set or closed by timer\n\t\treturn d.serverProcessDur\n\t}\n\n\td.serverProcessDur = serverProcessDur\n\treturn d.serverProcessDur\n}\n\nfunc (d *duration) setResDur() {\n\tresStart := <-d.resStartCh\n\n\td.chMu.Lock()\n\tdefer d.chMu.Unlock()\n\n\tif !d.resDurChClosed {\n\t\td.resDurCh <- time.Since(resStart)\n\t\td.resDurChClosed = true\n\t\tclose(d.resDurCh)\n\t}\n\n}\n\nfunc (d *duration) getResDur() time.Duration {\n\td.getChLock.Lock()\n\tdefer d.getChLock.Unlock()\n\n\tresDur, ok := <-d.resDurCh\n\n\tif !ok { // channel closed, probably resDur already set and chan closed by sender\n\t\treturn d.resDur\n\t}\n\n\td.resDur = resDur\n\treturn d.resDur\n\n}\n\nfunc (d *duration) totalDuration() time.Duration {\n\td.mu.Lock()\n\tdefer d.mu.Unlock()\n\n\treturn d.dnsDur + d.connDur + d.tlsDur + d.reqDur + d.getServerProcessDur() + d.getResDur()\n}\n\n// normally channels are closed by sender, but in case of senders are not called, we close them here\nfunc (d *duration) close() {\n\td.chMu.Lock()\n\tdefer d.chMu.Unlock()\n\n\t// close channels\n\tfunc() {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\t// channel already closed\n\t\t\t}\n\t\t}()\n\t\td.serverProcessStartChClosed = true\n\t\tclose(d.serverProcessStartCh)\n\t}()\n\n\tfunc() {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\t// channel already closed\n\t\t\t}\n\t\t}()\n\t\td.serverProcessDurChClosed = true\n\t\tclose(d.serverProcessDurCh)\n\t}()\n\n\tfunc() {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\t// channel already closed\n\t\t\t}\n\t\t}()\n\t\td.resStartChClosed = true\n\t\tclose(d.resStartCh)\n\t}()\n\n\tfunc() {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\t// channel already closed\n\t\t\t}\n\t\t}()\n\t\td.resDurChClosed = true\n\t\tclose(d.resDurCh)\n\t}()\n\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/requester/http_test.go",
    "content": "/*\n*\n*\tDdosify - Load testing tool for any web system.\n*   Copyright (C) 2021  Ddosify (https://ddosify.com)\n*\n*   This program is free software: you can redistribute it and/or modify\n*   it under the terms of the GNU Affero General Public License as published\n*   by the Free Software Foundation, either version 3 of the License, or\n*   (at your option) any later version.\n*\n*   This program is distributed in the hope that it will be useful,\n*   but WITHOUT ANY WARRANTY; without even the implied warranty of\n*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n*   GNU Affero General Public License for more details.\n*\n*   You should have received a copy of the GNU Affero General Public License\n*   along with this program.  If not, see <https://www.gnu.org/licenses/>.\n*\n */\n\npackage requester\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/http/httptrace\"\n\t\"net/url\"\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"go.ddosify.com/ddosify/core/types\"\n\t\"golang.org/x/net/http2\"\n)\n\nfunc TestInit(t *testing.T) {\n\ts := types.ScenarioStep{\n\t\tID:      1,\n\t\tMethod:  http.MethodGet,\n\t\tURL:     \"https://test.com\",\n\t\tTimeout: types.DefaultTimeout,\n\t}\n\tp, _ := url.Parse(\"https://127.0.0.1:80\")\n\tctx := context.TODO()\n\n\th := &HttpRequester{}\n\th.Init(ctx, s, p, false, nil)\n\n\tif !reflect.DeepEqual(h.packet, s) {\n\t\tt.Errorf(\"Expected %v, Found %v\", s, h.packet)\n\t}\n\tif !reflect.DeepEqual(h.proxyAddr, p) {\n\t\tt.Errorf(\"Expected %v, Found %v\", p, h.proxyAddr)\n\t}\n\tif !reflect.DeepEqual(h.ctx, ctx) {\n\t\tt.Errorf(\"Expected %v, Found %v\", ctx, h.ctx)\n\t}\n}\n\nfunc TestInitClient(t *testing.T) {\n\tp, _ := url.Parse(\"https://127.0.0.1:80\")\n\tctx := context.TODO()\n\n\t// Basic Client\n\ts := types.ScenarioStep{\n\t\tID:      1,\n\t\tMethod:  http.MethodGet,\n\t\tURL:     \"https://test.com\",\n\t\tTimeout: types.DefaultTimeout,\n\t}\n\texpectedTLS := &tls.Config{\n\t\tInsecureSkipVerify: true,\n\t}\n\texpectedTr := &http.Transport{\n\t\tTLSClientConfig:   expectedTLS,\n\t\tProxy:             http.ProxyURL(p),\n\t\tDisableKeepAlives: false,\n\t}\n\texpectedClient := &http.Client{\n\t\tTransport: expectedTr,\n\t\tTimeout:   time.Duration(types.DefaultTimeout) * time.Second,\n\t}\n\n\t// Client with custom data\n\tsWithCustomData := types.ScenarioStep{\n\t\tID:      1,\n\t\tMethod:  http.MethodGet,\n\t\tURL:     \"https://test.com\",\n\t\tTimeout: types.DefaultTimeout,\n\t\tHeaders: map[string]string{\"Connection\": \"close\"},\n\t\tCustom: map[string]interface{}{\n\t\t\t\"disable-redirect\":    true,\n\t\t\t\"disable-compression\": true,\n\t\t\t\"hostname\":            \"dummy.com\",\n\t\t},\n\t}\n\texpectedTLSCustomData := &tls.Config{\n\t\tInsecureSkipVerify: true,\n\t\tServerName:         \"dummy.com\",\n\t}\n\texpectedTrCustomData := &http.Transport{\n\t\tTLSClientConfig:    expectedTLSCustomData,\n\t\tProxy:              http.ProxyURL(p),\n\t\tDisableKeepAlives:  true,\n\t\tDisableCompression: true,\n\t}\n\texpectedClientWithCustomData := &http.Client{\n\t\tTransport: expectedTrCustomData,\n\t\tCheckRedirect: func(req *http.Request, via []*http.Request) error {\n\t\t\treturn http.ErrUseLastResponse\n\t\t},\n\t\tTimeout: time.Duration(types.DefaultTimeout) * time.Second,\n\t}\n\n\t// H2 Client\n\tsHTTP2 := types.ScenarioStep{\n\t\tID:      1,\n\t\tMethod:  http.MethodGet,\n\t\tURL:     \"https://test.com\",\n\t\tTimeout: types.DefaultTimeout,\n\t\tCustom: map[string]interface{}{\n\t\t\t\"h2\": true,\n\t\t},\n\t}\n\texpectedTLSHTTP2 := &tls.Config{\n\t\tInsecureSkipVerify: true,\n\t}\n\texpectedTrHTTP2 := &http.Transport{\n\t\tTLSClientConfig:   expectedTLSHTTP2,\n\t\tProxy:             http.ProxyURL(p),\n\t\tDisableKeepAlives: false,\n\t}\n\thttp2.ConfigureTransport(expectedTrHTTP2)\n\texpectedClientHTTP2 := &http.Client{\n\t\tTransport: expectedTrHTTP2,\n\t\tTimeout:   time.Duration(types.DefaultTimeout) * time.Second,\n\t}\n\n\t// Sub Tests\n\ttests := []struct {\n\t\tname         string\n\t\tscenarioItem types.ScenarioStep\n\t\tproxy        *url.URL\n\t\tctx          context.Context\n\t\ttls          *tls.Config\n\t\ttransport    *http.Transport\n\t\tclient       *http.Client\n\t}{\n\t\t{\"Basic\", s, p, ctx, expectedTLS, expectedTr, expectedClient},\n\t\t{\"Custom\", sWithCustomData, p, ctx, expectedTLSCustomData, expectedTrCustomData, expectedClientWithCustomData},\n\t\t{\"HTTP2\", sHTTP2, p, ctx, expectedTLSHTTP2, expectedTrHTTP2, expectedClientHTTP2},\n\t}\n\n\tfor _, test := range tests {\n\t\ttf := func(t *testing.T) {\n\t\t\th := &HttpRequester{}\n\t\t\th.Init(test.ctx, test.scenarioItem, test.proxy, false, nil)\n\n\t\t\ttransport := h.client.Transport.(*http.Transport)\n\t\t\ttls := transport.TLSClientConfig\n\n\t\t\t// TLS Assert (Also check HTTP2 vs HTTP)\n\t\t\tif !reflect.DeepEqual(test.tls, tls) {\n\t\t\t\tt.Errorf(\"\\nTLS Expected %#v, \\nFound %#v\", test.tls, tls)\n\t\t\t}\n\n\t\t\t// Transport Assert\n\t\t\tif reflect.TypeOf(test.transport) != reflect.TypeOf(transport) {\n\t\t\t\t// Compare HTTP2 configured transport vs HTTP transport\n\t\t\t\tt.Errorf(\"Transport Type Expected %#v, Found %#v\", test.transport, transport)\n\t\t\t}\n\n\t\t\tpFunc := transport.Proxy == nil\n\t\t\texpectedPFunc := test.transport.Proxy == nil\n\t\t\tif pFunc != expectedPFunc {\n\t\t\t\tt.Errorf(\"Proxy Expected %v, Found %v\", expectedPFunc, pFunc)\n\t\t\t}\n\t\t\tif test.transport.DisableKeepAlives != transport.DisableKeepAlives {\n\t\t\t\tt.Errorf(\"DisableKeepAlives Expected %v, Found %v\", test.transport.DisableKeepAlives, transport.DisableKeepAlives)\n\t\t\t}\n\t\t\tif test.transport.DisableCompression != transport.DisableCompression {\n\t\t\t\tt.Errorf(\"DisableCompression Expected %v, Found %v\",\n\t\t\t\t\ttest.transport.DisableCompression, transport.DisableCompression)\n\t\t\t}\n\n\t\t\t// Client Assert\n\t\t\tif test.client.Timeout != h.client.Timeout {\n\t\t\t\tt.Errorf(\"Timeout Expected %v, Found %v\", test.client.Timeout, h.client.Timeout)\n\t\t\t}\n\n\t\t\tcrFunc := h.client.CheckRedirect == nil\n\t\t\texpectedCRFunc := test.client.CheckRedirect == nil\n\t\t\tif expectedCRFunc != crFunc {\n\t\t\t\tt.Errorf(\"CheckRedirect Expected %v, Found %v\", expectedCRFunc, crFunc)\n\t\t\t}\n\n\t\t}\n\t\tt.Run(test.name, tf)\n\t}\n}\n\nfunc TestInitRequest(t *testing.T) {\n\tp, _ := url.Parse(\"https://127.0.0.1:80\")\n\tctx := context.TODO()\n\n\t// Invalid request\n\tsInvalid := types.ScenarioStep{\n\t\tID:      1,\n\t\tMethod:  \":31:31:#\",\n\t\tURL:     \"https://test.com\",\n\t\tPayload: \"payloadtest\",\n\t}\n\n\t// Basic request\n\ts := types.ScenarioStep{\n\t\tID:      1,\n\t\tMethod:  http.MethodGet,\n\t\tURL:     \"https://test.com\",\n\t\tPayload: \"payloadtest\",\n\t}\n\texpected, _ := http.NewRequest(s.Method, s.URL, bytes.NewBufferString(s.Payload))\n\texpected.Close = false\n\texpected.Header = make(http.Header)\n\n\t// Request with auth\n\tsWithAuth := types.ScenarioStep{\n\t\tID:      1,\n\t\tMethod:  http.MethodGet,\n\t\tURL:     \"https://test.com\",\n\t\tPayload: \"payloadtest\",\n\t\tAuth: types.Auth{\n\t\t\tUsername: \"test\",\n\t\t\tPassword: \"123\",\n\t\t},\n\t}\n\texpectedWithAuth, _ := http.NewRequest(sWithAuth.Method, sWithAuth.URL, bytes.NewBufferString(sWithAuth.Payload))\n\texpectedWithAuth.Close = false\n\texpectedWithAuth.Header = make(http.Header)\n\texpectedWithAuth.SetBasicAuth(sWithAuth.Auth.Username, sWithAuth.Auth.Password)\n\n\t// Request With Headers\n\tsWithHeaders := types.ScenarioStep{\n\t\tID:      1,\n\t\tMethod:  http.MethodGet,\n\t\tURL:     \"https://test.localhost\",\n\t\tPayload: \"payloadtest\",\n\t\tAuth: types.Auth{\n\t\t\tUsername: \"test\",\n\t\t\tPassword: \"123\",\n\t\t},\n\t\tHeaders: map[string]string{\n\t\t\t\"Header1\":    \"Value1\",\n\t\t\t\"Header2\":    \"Value2\",\n\t\t\t\"User-Agent\": \"Firefox\",\n\t\t\t\"Host\":       \"test.com\",\n\t\t},\n\t}\n\texpectedWithHeaders, _ := http.NewRequest(sWithHeaders.Method,\n\t\tsWithHeaders.URL, bytes.NewBufferString(sWithHeaders.Payload))\n\texpectedWithHeaders.Close = false\n\texpectedWithHeaders.Header = make(http.Header)\n\texpectedWithHeaders.Header.Set(\"Header1\", \"Value1\")\n\texpectedWithHeaders.Header.Set(\"Header2\", \"Value2\")\n\texpectedWithHeaders.Header.Set(\"User-Agent\", \"Firefox\")\n\texpectedWithHeaders.Header.Set(\"Host\", \"test.com\")\n\texpectedWithHeaders.Host = \"test.com\"\n\texpectedWithHeaders.SetBasicAuth(sWithHeaders.Auth.Username, sWithHeaders.Auth.Password)\n\n\t// Request keep-alive condition\n\tsWithoutKeepAlive := types.ScenarioStep{\n\t\tID:      1,\n\t\tMethod:  http.MethodGet,\n\t\tURL:     \"https://test.com\",\n\t\tPayload: \"payloadtest\",\n\t\tAuth: types.Auth{\n\t\t\tUsername: \"test\",\n\t\t\tPassword: \"123\",\n\t\t},\n\t\tHeaders: map[string]string{\n\t\t\t\"Header1\":    \"Value1\",\n\t\t\t\"Header2\":    \"Value2\",\n\t\t\t\"Connection\": \"close\",\n\t\t},\n\t}\n\texpectedWithoutKeepAlive, _ := http.NewRequest(sWithoutKeepAlive.Method,\n\t\tsWithoutKeepAlive.URL, bytes.NewBufferString(sWithoutKeepAlive.Payload))\n\texpectedWithoutKeepAlive.Close = true\n\texpectedWithoutKeepAlive.Header = make(http.Header)\n\texpectedWithoutKeepAlive.Header.Set(\"Header1\", \"Value1\")\n\texpectedWithoutKeepAlive.Header.Set(\"Header2\", \"Value2\")\n\texpectedWithoutKeepAlive.Header.Set(\"Connection\", \"close\")\n\texpectedWithoutKeepAlive.SetBasicAuth(sWithoutKeepAlive.Auth.Username, sWithoutKeepAlive.Auth.Password)\n\n\t// Sub Tests\n\ttests := []struct {\n\t\tname         string\n\t\tscenarioItem types.ScenarioStep\n\t\tshouldErr    bool\n\t\trequest      *http.Request\n\t}{\n\t\t{\"Invalid\", sInvalid, true, nil},\n\t\t{\"Basic\", s, false, expected},\n\t\t{\"WithAuth\", sWithAuth, false, expectedWithAuth},\n\t\t{\"WithHeaders\", sWithHeaders, false, expectedWithHeaders},\n\t\t{\"WithoutKeepAlive\", sWithoutKeepAlive, false, expectedWithoutKeepAlive},\n\t}\n\n\tfor _, test := range tests {\n\t\ttf := func(t *testing.T) {\n\t\t\th := &HttpRequester{}\n\t\t\terr := h.Init(ctx, test.scenarioItem, p, false, nil)\n\n\t\t\tif test.shouldErr {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Errorf(\"Should be errored\")\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"Errored: %v\", err)\n\t\t\t\t}\n\n\t\t\t\t// TODOcorr: we use tempValidUrl for correlation for now\n\t\t\t\t// if !reflect.DeepEqual(h.request.URL, test.request.URL) {\n\t\t\t\t// \tt.Errorf(\"URL Expected: %#v, Found: \\n%#v\", test.request.URL, h.request.URL)\n\t\t\t\t// }\n\t\t\t\t// if !reflect.DeepEqual(h.request.Host, test.request.Host) {\n\t\t\t\t// \tt.Errorf(\"Host Expected: %#v, Found: \\n%#v\", test.request.Host, h.request.Host)\n\t\t\t\t// }\n\t\t\t\tif !reflect.DeepEqual(h.request.Body, test.request.Body) {\n\t\t\t\t\tt.Errorf(\"Body Expected: %#v, Found: \\n%#v\", test.request.Body, h.request.Body)\n\t\t\t\t}\n\t\t\t\tif !reflect.DeepEqual(h.request.Header, test.request.Header) {\n\t\t\t\t\tt.Errorf(\"Header Expected: %#v, Found: \\n%#v\", test.request.Header, h.request.Header)\n\t\t\t\t}\n\t\t\t\tif !reflect.DeepEqual(h.request.Close, test.request.Close) {\n\t\t\t\t\tt.Errorf(\"Close Expected: %#v, Found: \\n%#v\", test.request.Close, h.request.Close)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tt.Run(test.name, tf)\n\t}\n}\n\nfunc TestSendOnDebugModePopulatesDebugInfo(t *testing.T) {\n\tctx := context.TODO()\n\t// Basic request\n\tpayload := \"reqbodypayload\"\n\ts := types.ScenarioStep{\n\t\tID:      1,\n\t\tMethod:  http.MethodGet,\n\t\tURL:     \"https://ddosify.com\",\n\t\tPayload: payload,\n\t\tHeaders: map[string]string{\"X\": \"y\"},\n\t}\n\n\texpectedUrl := \"https://ddosify.com\"\n\texpectedMethod := http.MethodGet\n\texpectedRequestHeaders := http.Header{\"X\": {\"y\"}}\n\texpectedRequestBody := []byte(payload)\n\n\ttf := func(t *testing.T) {\n\t\th := &HttpRequester{}\n\t\tdebug := true\n\t\tvar proxy *url.URL\n\t\t_ = h.Init(ctx, s, proxy, debug, nil)\n\t\tenvs := map[string]interface{}{}\n\t\tres := h.Send(http.DefaultClient, envs)\n\n\t\tif expectedMethod != res.Method {\n\t\t\tt.Errorf(\"Method Expected %#v, Found: \\n%#v\", expectedMethod, res.Method)\n\t\t}\n\t\tif expectedUrl != res.Url {\n\t\t\tt.Errorf(\"Url Expected %#v, Found: \\n%#v\", expectedUrl, res.Url)\n\t\t}\n\t\tif !bytes.Equal(expectedRequestBody, res.ReqBody) {\n\t\t\tt.Errorf(\"RequestBody Expected %#v, Found: \\n%#v\", expectedRequestBody,\n\t\t\t\tres.ReqBody)\n\t\t}\n\n\t\t// stepResult has default request headers added by go client\n\t\tfor expKey, expVal := range expectedRequestHeaders {\n\t\t\tif !reflect.DeepEqual(expVal, res.ReqHeaders.Values(expKey)) {\n\t\t\t\tt.Errorf(\"RequestHeaders Expected %#v, Found: \\n%#v\", expectedRequestHeaders,\n\t\t\t\t\tres.ReqHeaders)\n\t\t\t}\n\t\t}\n\t}\n\tt.Run(\"populate-debug-info\", tf)\n}\n\nfunc TestCaptureEnvShouldSetEmptyStringWhenReqFails(t *testing.T) {\n\tctx := context.TODO()\n\t// Failed request\n\tenvName := \"ENV_NAME\"\n\theaderKey := \"key\"\n\ts := types.ScenarioStep{\n\t\tID:     1,\n\t\tMethod: http.MethodGet,\n\t\tURL:    \"https://ddosifyInvalid.com\",\n\t\tEnvsToCapture: []types.EnvCaptureConf{{\n\t\t\tJsonPath: new(string),\n\t\t\tXpath:    new(string),\n\t\t\tRegExp:   &types.RegexCaptureConf{},\n\t\t\tName:     envName,\n\t\t\tFrom:     types.Header,\n\t\t\tKey:      &headerKey,\n\t\t}},\n\t}\n\n\texpectedExtractedEnvs := map[string]interface{}{\n\t\tenvName: \"\",\n\t}\n\n\t// Sub Tests\n\ttests := []struct {\n\t\tname                  string\n\t\tscenarioStep          types.ScenarioStep\n\t\texpectedExtractedEnvs map[string]interface{}\n\t}{\n\t\t{\"ExtractedEnvShouldBeEmptyStringWhenReqFailure\", s, expectedExtractedEnvs},\n\t}\n\n\tfor _, test := range tests {\n\t\ttf := func(t *testing.T) {\n\t\t\th := &HttpRequester{}\n\t\t\tdebug := true\n\t\t\tvar proxy *url.URL\n\t\t\t_ = h.Init(ctx, test.scenarioStep, proxy, debug, nil)\n\t\t\tenvs := map[string]interface{}{}\n\n\t\t\ttempDurationClose := durationCloseFunc\n\n\t\t\tdurationCloseCalled := false\n\t\t\tdurationCloseFunc = func(d *duration) func() {\n\t\t\t\treturn func() {\n\t\t\t\t\ttempDurationClose(d)()\n\t\t\t\t\tdurationCloseCalled = true\n\t\t\t\t}\n\t\t\t}\n\t\t\tdefer func() { durationCloseFunc = tempDurationClose }()\n\n\t\t\tres := h.Send(http.DefaultClient, envs)\n\n\t\t\tif !durationCloseCalled {\n\t\t\t\tt.Errorf(\"Duration close should be called\")\n\t\t\t}\n\n\t\t\tif !reflect.DeepEqual(res.ExtractedEnvs, test.expectedExtractedEnvs) {\n\t\t\t\tt.Errorf(\"Extracted env should be set empty string on req failure\")\n\t\t\t}\n\t\t}\n\t\tt.Run(test.name, tf)\n\t}\n}\n\nfunc TestAssertions(t *testing.T) {\n\tt.Parallel()\n\n\t// Test server\n\tfirstReqHandler := func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"Argentina\", \"Messi\")\n\t\tw.WriteHeader(http.StatusForbidden)\n\t}\n\n\trule1 := \"equals(status_code,405)\"\n\trule2 := `equals(headers.Argentina,\"Ronaldo\")`\n\tpathFirst := \"/json-body\"\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(pathFirst, firstReqHandler)\n\n\tserver := httptest.NewServer(mux)\n\tdefer server.Close()\n\n\ts := types.ScenarioStep{\n\t\tID:         1,\n\t\tMethod:     \"GET\",\n\t\tURL:        server.URL + pathFirst,\n\t\tAssertions: []string{rule1, rule2},\n\t}\n\n\tctx := context.TODO()\n\th := &HttpRequester{}\n\th.Init(ctx, s, nil, false, nil)\n\n\tres := h.Send(http.DefaultClient, map[string]interface{}{})\n\n\tif !strings.EqualFold(res.FailedAssertions[0].Rule, rule1) {\n\t\tt.Errorf(\"rule expected %s, got %s\", rule1, res.FailedAssertions[0].Rule)\n\t}\n\tif reflect.DeepEqual(res.FailedAssertions[0].Received, 403) {\n\t\tt.Errorf(\"received expected %d, got %v\", 403, res.FailedAssertions[0].Received)\n\t}\n\tif !strings.EqualFold(res.FailedAssertions[1].Rule, rule2) {\n\t\tt.Errorf(\"rule expected %s, got %s\", rule1, res.FailedAssertions[1].Rule)\n\t}\n\tif reflect.DeepEqual(res.FailedAssertions[0].Received, \"Ronaldo\") {\n\t\tt.Errorf(\"received expected %s, got %v\", \"Ronaldo\", res.FailedAssertions[1].Received)\n\t}\n}\n\nfunc TestTraceResDur_TypicalScenario(t *testing.T) {\n\tvar maxDuration int64 = 1<<63 - 1\n\td := &duration{\n\t\tserverProcessDurCh:   make(chan time.Duration, 1),\n\t\tserverProcessStartCh: make(chan time.Time, 1),\n\n\t\tresDurCh:   make(chan time.Duration, 1),\n\t\tresStartCh: make(chan time.Time, 1),\n\t}\n\ttrace := newTrace(d, nil, nil)\n\n\t// below two is called by different goroutines\n\t// typically wroteRequest is called before gotFirstResponseByte\n\n\tgo func() {\n\t\tgo trace.WroteRequest(httptrace.WroteRequestInfo{})\n\t\ttime.Sleep(10 * time.Millisecond)\n\t\tgo trace.GotFirstResponseByte()\n\t}()\n\n\t// called by Send method\n\td.setResDur()\n\n\t// called by Send method\n\tresDur := d.getResDur()\n\n\tif resDur == time.Duration(maxDuration) {\n\t\tt.Errorf(\"resDur should not be %d\", maxDuration)\n\t}\n}\n\nfunc TestTraceResDur_UnusualScenario(t *testing.T) {\n\tvar maxDuration int64 = 1<<63 - 1\n\td := &duration{\n\t\tserverProcessDurCh:   make(chan time.Duration, 1),\n\t\tserverProcessStartCh: make(chan time.Time, 1),\n\n\t\tresDurCh:   make(chan time.Duration, 1),\n\t\tresStartCh: make(chan time.Time, 1),\n\t}\n\ttrace := newTrace(d, nil, nil)\n\n\t// below two is called by different goroutines\n\t// typically wroteRequest is called before gotFirstResponseByte\n\n\t// we will simulate the opposite\n\tgo func() {\n\t\tgo trace.GotFirstResponseByte()\n\t\ttime.Sleep(100 * time.Millisecond)\n\t\tgo trace.WroteRequest(httptrace.WroteRequestInfo{})\n\t}()\n\n\t// called by Send method\n\td.setResDur()\n\n\t// called by Send method\n\tresDur := d.getResDur()\n\n\tif resDur == time.Duration(maxDuration) {\n\t\tt.Errorf(\"resDur should not be %d\", maxDuration)\n\t}\n}\n\nfunc TestTraceServerProcessDur(t *testing.T) {\n\tvar maxDuration int64 = 1<<63 - 1\n\td := &duration{\n\t\tserverProcessDurCh:   make(chan time.Duration, 1),\n\t\tserverProcessStartCh: make(chan time.Time, 1),\n\n\t\tresDurCh:   make(chan time.Duration, 1),\n\t\tresStartCh: make(chan time.Time, 1),\n\t}\n\ttrace := newTrace(d, nil, nil)\n\n\t// below two is called by different goroutines\n\t// typically wroteRequest is called before gotFirstResponseByte\n\tgo func() {\n\t\tgo trace.WroteRequest(httptrace.WroteRequestInfo{})\n\t\ttime.Sleep(10 * time.Millisecond)\n\t\tgo trace.GotFirstResponseByte()\n\t}()\n\n\t// called by Send method\n\t// this get needs to wait for GotFirstResponseByte\n\tserverProcessDur := d.getServerProcessDur()\n\tif serverProcessDur == time.Duration(maxDuration) {\n\t\tt.Errorf(\"serverProcessDur should not be %d\", maxDuration)\n\t}\n}\n\nfunc TestTraceServerProcessDur_2(t *testing.T) {\n\tvar maxDuration int64 = 1<<63 - 1\n\td := &duration{\n\t\tserverProcessDurCh:   make(chan time.Duration, 1),\n\t\tserverProcessStartCh: make(chan time.Time, 1),\n\n\t\tresDurCh:   make(chan time.Duration, 1),\n\t\tresStartCh: make(chan time.Time, 1),\n\t}\n\ttrace := newTrace(d, nil, nil)\n\n\t// below two is called by different goroutines\n\t// typically wroteRequest is called before gotFirstResponseByte\n\t// we will simulate the opposite\n\tgo func() {\n\t\tgo trace.GotFirstResponseByte()\n\t\ttime.Sleep(10 * time.Millisecond)\n\t\tgo trace.WroteRequest(httptrace.WroteRequestInfo{})\n\t}()\n\n\t// called by Send method\n\t// this get needs to wait for GotFirstResponseByte\n\tserverProcessDur := d.getServerProcessDur()\n\n\tif serverProcessDur == time.Duration(maxDuration) {\n\t\tt.Errorf(\"serverProcessDur should not be %d\", maxDuration)\n\t}\n}\n\nfunc TestTraceServerProcessDur_3(t *testing.T) {\n\tvar maxDuration int64 = 1<<63 - 1\n\td := &duration{\n\t\tserverProcessDurCh:   make(chan time.Duration, 1),\n\t\tserverProcessStartCh: make(chan time.Time, 1),\n\n\t\tresDurCh:   make(chan time.Duration, 1),\n\t\tresStartCh: make(chan time.Time, 1),\n\t}\n\ttrace := newTrace(d, nil, nil)\n\n\t// below two is called by different goroutines\n\t// typically wroteRequest is called before gotFirstResponseByte\n\t// we will simulate the opposite\n\tgo func() {\n\t\tgo trace.GotFirstResponseByte()\n\t\ttime.Sleep(10 * time.Millisecond)\n\t\tgo trace.WroteRequest(httptrace.WroteRequestInfo{})\n\t}()\n\n\t// called by Send method\n\t// this get needs to wait for GotFirstResponseByte\n\tserverProcessDur1 := d.getServerProcessDur()\n\n\tif serverProcessDur1 == time.Duration(maxDuration) {\n\t\tt.Errorf(\"serverProcessDur should not be %d\", maxDuration)\n\t}\n\n\tserverProcessDur2 := d.getServerProcessDur()\n\n\tif serverProcessDur1 != serverProcessDur2 {\n\t\tt.Errorf(\"serverProcessDur1 and serverProcessDur2 should be equal\")\n\t}\n}\n\nfunc TestTraceServerProcessDur_ErrCase(t *testing.T) {\n\tvar maxDuration int64 = 1<<63 - 1\n\td := &duration{\n\t\tserverProcessDurCh:   make(chan time.Duration, 1),\n\t\tserverProcessStartCh: make(chan time.Time, 1),\n\t\tresDurCh:             make(chan time.Duration, 1),\n\t\tresStartCh:           make(chan time.Time, 1),\n\t}\n\ttrace := newTrace(d, nil, nil)\n\n\t// below two is called by different goroutines\n\t// typically wroteRequest is called before gotFirstResponseByte\n\tgo func() {\n\t\tgo trace.WroteRequest(httptrace.WroteRequestInfo{})\n\t\ttime.Sleep(10 * time.Millisecond)\n\n\t\t// not called in err case\n\t\t// go trace.GotFirstResponseByte()\n\t}()\n\n\t// called by Send method\n\t// channels should be closed, otherwise get calls can block forever\n\tdurationCloseFunc(d)()\n\n\t// called by Send method\n\t// this get needs to wait for GotFirstResponseByte\n\tserverProcessDur := d.getServerProcessDur()\n\n\tif serverProcessDur == time.Duration(maxDuration) {\n\t\tt.Errorf(\"serverProcessDur should not be %d\", maxDuration)\n\t}\n}\n\nfunc TestResponseCookiesSentToAssertions(t *testing.T) {\n\tt.Parallel()\n\t// Test server\n\tfirstReqHandler := func(w http.ResponseWriter, r *http.Request) {\n\t\thttp.SetCookie(w, &http.Cookie{Name: \"Argentina\", Value: \"Messi\"})\n\t\thttp.SetCookie(w, &http.Cookie{Name: \"Goat\", Value: \"Messi\"})\n\n\t\tw.WriteHeader(http.StatusForbidden)\n\t}\n\n\tpassRule := \"equals(cookies.Argentina.value,\\\"Messi\\\")\"\n\tfailRule := \"equals(cookies.Goat.value,\\\"Ronaldo\\\")\"\n\n\tpathFirst := \"/json-body\"\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(pathFirst, firstReqHandler)\n\n\tserver := httptest.NewServer(mux)\n\tdefer server.Close()\n\n\ts := types.ScenarioStep{\n\t\tID:         1,\n\t\tMethod:     \"GET\",\n\t\tURL:        server.URL + pathFirst,\n\t\tAssertions: []string{passRule, failRule},\n\t}\n\n\tctx := context.TODO()\n\th := &HttpRequester{}\n\th.Init(ctx, s, nil, false, nil)\n\n\tres := h.Send(http.DefaultClient, map[string]interface{}{})\n\n\tif len(res.FailedAssertions) != 1 {\n\t\tt.Errorf(\"expected 1 failed assertion, got %d\", len(res.FailedAssertions))\n\t}\n\n\tif !strings.EqualFold(res.FailedAssertions[0].Rule, failRule) {\n\t\tt.Errorf(\"rule expected %s, got %s\", failRule, res.FailedAssertions[0].Rule)\n\t}\n\n\tif reflect.DeepEqual(res.FailedAssertions[0].Received, \"Ronaldo\") {\n\t\tt.Errorf(\"received expected %s, got %v\", \"Ronaldo\", res.FailedAssertions[0].Received)\n\t}\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/scripting/assertion/assert.go",
    "content": "package assertion\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"go.ddosify.com/ddosify/core/scenario/scripting/assertion/evaluator\"\n\t\"go.ddosify.com/ddosify/core/scenario/scripting/assertion/lexer\"\n\t\"go.ddosify.com/ddosify/core/scenario/scripting/assertion/parser\"\n)\n\ntype AssertionError struct { // UnWrappable\n\tfailedAssertion string\n\treceived        map[string]interface{}\n\twrappedErr      error\n}\n\nfunc (ae AssertionError) Error() string {\n\treturn fmt.Sprintf(\"input : %s, received: %v, wrappedErr: %v\", ae.failedAssertion, ae.received, ae.wrappedErr)\n}\n\nfunc (ae AssertionError) Rule() string {\n\treturn ae.failedAssertion\n}\n\nfunc (ae AssertionError) Received() map[string]interface{} {\n\treturn ae.received\n}\n\nfunc (ae AssertionError) Unwrap() error {\n\treturn ae.wrappedErr\n}\n\nfunc Assert(input string, env *evaluator.AssertEnv) (bool, error) {\n\tl := lexer.New(input)\n\tp := parser.New(l)\n\n\tnode := p.ParseExpressionStatement()\n\tif len(p.Errors()) > 0 {\n\t\treturn false, AssertionError{\n\t\t\tfailedAssertion: input,\n\t\t\treceived:        map[string]interface{}{},\n\t\t\twrappedErr:      fmt.Errorf(strings.Join(p.Errors(), \",\")),\n\t\t}\n\t}\n\n\treceivedMap := make(map[string]interface{})\n\tobj, err := evaluator.Eval(node, env, receivedMap)\n\tif err != nil {\n\t\treturn false, AssertionError{\n\t\t\tfailedAssertion: input,\n\t\t\treceived:        receivedMap,\n\t\t\twrappedErr:      err,\n\t\t}\n\t}\n\n\tb, ok := obj.(bool)\n\tif ok {\n\t\tif b == false {\n\t\t\treturn false, AssertionError{\n\t\t\t\tfailedAssertion: input,\n\t\t\t\treceived:        receivedMap,\n\t\t\t\twrappedErr:      fmt.Errorf(\"expression evaluated to false\"),\n\t\t\t}\n\t\t}\n\t\treturn b, nil\n\t}\n\n\treturn false, AssertionError{\n\t\tfailedAssertion: input,\n\t\treceived:        receivedMap,\n\t\twrappedErr:      fmt.Errorf(\"evaluated value is not bool : %v\", obj),\n\t}\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/scripting/assertion/assert_test.go",
    "content": "package assertion\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"go.ddosify.com/ddosify/core/scenario/scripting/assertion/evaluator\"\n)\n\nfunc TestAssert(t *testing.T) {\n\ttestHeader := http.Header{}\n\ttestHeader.Add(\"Content-Type\", \"application/json\")\n\ttestHeader.Add(\"content-length\", \"222\")\n\n\ttests := []struct {\n\t\tinput         string\n\t\tenvs          *evaluator.AssertEnv\n\t\texpected      bool\n\t\treceived      map[string]interface{}\n\t\texpectedError string\n\t}{\n\t\t{\n\t\t\tinput: \"response_size < 300\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tResponseSize: 200,\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: \"response_size < 300.5\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tResponseSize: 200,\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: \"-response_size < 300.5\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tResponseSize: 200,\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: \"response_time < 300.5\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tResponseTime: 220,\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: \"in(status_code,[200,201])\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tStatusCode: 500,\n\t\t\t},\n\t\t\texpected: false,\n\t\t\treceived: map[string]interface{}{\n\t\t\t\t\"status_code\":               int64(500),\n\t\t\t\t\"in(status_code,[200,201])\": false,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinput: \"in(status_code,[200,201])\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tStatusCode: 201,\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: \"equals(status_code,200)\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tStatusCode: 200,\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: \"status_code == 200\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tStatusCode: 200,\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: \"status_code == \\\"200\\\"\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tStatusCode: 200,\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: \"!(status_code == 200)\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tStatusCode: 200,\n\t\t\t},\n\t\t\texpected: false,\n\t\t\treceived: map[string]interface{}{\n\t\t\t\t\"status_code\": int64(200),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinput: \"not(status_code == 500)\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tStatusCode: 200,\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: `equals(json_path(\"employees.0.name\"),\"Kenan\")`,\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tBody: \"{\\n  \\\"employees\\\": [ {\\\"name\\\":\\\"Kursat\\\"}, {\\\"name\\\":\\\"Kenan\\\"}]\\n}\",\n\t\t\t},\n\t\t\texpected: false,\n\t\t\treceived: map[string]interface{}{\n\t\t\t\t\"json_path(employees.0.name)\":               \"Kursat\",\n\t\t\t\t\"equals(json_path(employees.0.name),Kenan)\": false,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinput: `equals(json_path(\"employees.1.name\"),\"Kursat\")`,\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tBody: \"{\\n  \\\"employees\\\": [{\\\"name\\\":\\\"Kenan\\\"}, {\\\"name\\\":\\\"Kursat\\\"}]\\n}\",\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: `exists(headers.Content-Type)`,\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tHeaders: testHeader,\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: `exists(headers.Not-Exist-Header)`,\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tHeaders: testHeader,\n\t\t\t},\n\t\t\texpected:      false,\n\t\t\texpectedError: \"NotFoundError\",\n\t\t},\n\t\t{\n\t\t\tinput: `contains(body,\"xyz\")`,\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tBody: \"xyza\",\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: `contains(body,\"xyz\")`,\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tBody: \"\",\n\t\t\t},\n\t\t\texpected: false,\n\t\t\treceived: map[string]interface{}{\n\t\t\t\t\"body\":               \"\",\n\t\t\t\t\"contains(body,xyz)\": false,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinput: `regexp(body,\"[a-z]+_[0-9]+\",0) == \"messi_10\"`,\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tBody: \"messi_10alvarez_9\",\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: `equals(variables.arr,[\"Kenan\",\"Faruk\",\"Cakir\"])`,\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tVariables: map[string]interface{}{\n\t\t\t\t\t\"arr\": []interface{}{\"Kenan\", \"Faruk\", \"Cakir\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: `equals(variables.arr,[\"Kenan\",\"Faruk\",\"Cakir\"])`,\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tVariables: map[string]interface{}{\n\t\t\t\t\t\"arr2\": []interface{}{\"Kenan\", \"Faruk\", \"Cakir\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected:      false,\n\t\t\texpectedError: \"NotFoundError\",\n\t\t},\n\t\t{\n\t\t\tinput: `equals(variables.c,null)`,\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tVariables: map[string]interface{}{\n\t\t\t\t\t\"c\": nil,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: `variables.arr != [\"Kenan\",\"Faruk\",\"Cakir\"]`,\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tVariables: map[string]interface{}{\n\t\t\t\t\t\"arr\": []interface{}{\"Cakir\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: `variables.arr !=[\"Kenan\",\"Faruk\",\"Cakir\"])`,\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tVariables: map[string]interface{}{\n\t\t\t\t\t\"arr\": []interface{}{\"Kenan\", \"Faruk\", \"Cakir\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: false,\n\t\t\treceived: map[string]interface{}{\n\t\t\t\t\"variables.arr\": []interface{}{\"Kenan\", \"Faruk\", \"Cakir\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinput: `equals(variables.xint,100)`, // int - int64 comparison\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tVariables: map[string]interface{}{\n\t\t\t\t\t\"xint\": 100,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: `equals(100,variables.xint)`, // int - int64 comparison\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tVariables: map[string]interface{}{\n\t\t\t\t\t\"xint\": 100,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput:    `2*12/3+5-3 != 10`,\n\t\t\texpected: false,\n\t\t},\n\t\t{\n\t\t\tinput: `equals(variables.xint,variables.yint)`, // int - int comparison\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tVariables: map[string]interface{}{\n\t\t\t\t\t\"xint\": 100,\n\t\t\t\t\t\"yint\": 100,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput:    `equals(100.5 + 200.5, 301)`, // float64 +\n\t\t\tenvs:     &evaluator.AssertEnv{},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput:    `equals(100.5 - 200.5, -100)`, // float64 -\n\t\t\tenvs:     &evaluator.AssertEnv{},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput:    `equals(4.0 * 10.5, 42)`, // float64 *\n\t\t\tenvs:     &evaluator.AssertEnv{},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput:    `equals(60.0/5, 12)`, // float64 /\n\t\t\tenvs:     &evaluator.AssertEnv{},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput:    `60.1 == 60.1`, // float64 ==\n\t\t\tenvs:     &evaluator.AssertEnv{},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput:    `60.1 != 60.1`, // float64 !=\n\t\t\tenvs:     &evaluator.AssertEnv{},\n\t\t\texpected: false,\n\t\t},\n\t\t{\n\t\t\tinput:    `60.1 £ 60.1`, // illegal character\n\t\t\tenvs:     &evaluator.AssertEnv{},\n\t\t\texpected: false,\n\t\t},\n\t\t{\n\t\t\tinput: `range(headers.content-length,100,300)`,\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tHeaders: testHeader,\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: `range(headers.content-length,300,400)`,\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tHeaders: testHeader,\n\t\t\t},\n\t\t\texpected: false,\n\t\t},\n\t\t{\n\t\t\tinput: `range(headers.content-length,\"300\",400)`,\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tHeaders: testHeader,\n\t\t\t},\n\t\t\texpected:      false,\n\t\t\texpectedError: \"ArgumentError\", // range params should be integer\n\t\t},\n\t\t{\n\t\t\tinput: `range(headers.content-length,300,\"400\")`,\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tHeaders: testHeader,\n\t\t\t},\n\t\t\texpected:      false,\n\t\t\texpectedError: \"ArgumentError\", // range params should be integer\n\t\t},\n\t\t{\n\t\t\tinput: `range(headers.content-length,200,400.2)`, // range can take floats also\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tHeaders: testHeader,\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: `range(301.2,200,400.2)`, // range can take floats also\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tHeaders: testHeader,\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: `range(301.2,200,400)`, // range can take floats also\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tHeaders: testHeader,\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput:    `equals_on_file(\"abc\",\"./test_files/a.txt\")`,\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput:    `equals_on_file(\"abcx\",\"./test_files/a.txt\")`,\n\t\t\texpected: false,\n\t\t},\n\t\t{\n\t\t\tinput:    `equals_on_file(variables.xyz,\"./test_files/jsonMap.json\")`,\n\t\t\texpected: true,\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tVariables: map[string]interface{}{\n\t\t\t\t\t\"xyz\": map[string]interface{}{\n\t\t\t\t\t\t\"ask\":                  130.75,\n\t\t\t\t\t\t\"askSize\":              float64(10),\n\t\t\t\t\t\t\"averageAnalystRating\": \"2.0 - Buy\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinput:    `equals_on_file(variables.xyz,\"./test_files/jsonArray.json\")`,\n\t\t\texpected: true,\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tVariables: map[string]interface{}{\n\t\t\t\t\t\"xyz\": []interface{}{\"xyz\", \"abc\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinput:    `equals_on_file(body,\"./test_files/currencies.json\")`,\n\t\t\texpected: true,\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tBody: \"[\\n    \\\"AED\\\",\\n    \\\"ARS\\\",\\n    \\\"AUD\\\",\\n    \\\"BGN\\\",\\n    \\\"BHD\\\",\\n    \\\"BRL\\\",\\n    \\\"CAD\\\",\\n    \\\"CHF\\\",\\n    \\\"CNY\\\",\\n    \\\"DKK\\\",\\n    \\\"DZD\\\",\\n    \\\"EUR\\\",\\n    \\\"FKP\\\",\\n    \\\"INR\\\",\\n    \\\"JEP\\\",\\n    \\\"JPY\\\",\\n    \\\"KES\\\",\\n    \\\"KWD\\\",\\n    \\\"KZT\\\",\\n    \\\"MXN\\\",\\n    \\\"NZD\\\",\\n    \\\"RUB\\\",\\n    \\\"SEK\\\",\\n    \\\"SGD\\\",\\n    \\\"TRY\\\",\\n    \\\"USD\\\"\\n]\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinput:    \"equals(body, {\\\"name\\\":\\\"Ar'gentina\\\",\\\"num\\\":25,\\\"isChampion\\\":false})\",\n\t\t\texpected: true,\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tBody: \"{\\\"num\\\":25,\\\"name\\\":\\\"Ar'gentina\\\",\\\"isChampion\\\":false}\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinput:    `equals_on_file(body,\"./test_files/number.json\")`,\n\t\t\texpected: true,\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tBody: \"5\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinput: \"(status_code == 200) || (status_code == 201)\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tStatusCode: 200,\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: \"(status_code == 200) && (status_code == 201)\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tStatusCode: 200,\n\t\t\t},\n\t\t\texpected: false,\n\t\t},\n\t\t{\n\t\t\tinput: \"status_code > variables.envFloatVal\", // int float comparison\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tStatusCode: 200,\n\t\t\t\tVariables: map[string]interface{}{\n\t\t\t\t\t\"envFloatVal\": 12.43,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: \"status_code && true\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tStatusCode: 200,\n\t\t\t},\n\t\t\texpected:      false,\n\t\t\texpectedError: \"OperatorError\", // int && bool, unsupported\n\t\t},\n\t\t{\n\t\t\tinput: \"status_code || true\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tStatusCode: 200,\n\t\t\t},\n\t\t\texpected:      false,\n\t\t\texpectedError: \"OperatorError\", // int || bool, unsupported\n\t\t},\n\t\t{\n\t\t\tinput: \"(status_code > 199) || false\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tStatusCode: 200,\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: \"less_than(status_code,201)\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tStatusCode: 200,\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: \"greater_than(status_code,201)\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tStatusCode: 400,\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: `range(header.content-length,300,400)`,\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tHeaders: testHeader,\n\t\t\t},\n\t\t\texpected:      false,\n\t\t\texpectedError: \"NotFoundError\", // should be headers....\n\t\t},\n\t\t{\n\t\t\tinput: \"greater_than(status_code,201)\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tStatusCode: 400,\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: `less_than(headers.content-length,500)`,\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tHeaders: testHeader,\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: \"exists(headers.Content-Type2)\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tHeaders: testHeader,\n\t\t\t},\n\t\t\texpected: false,\n\t\t},\n\t\t{\n\t\t\tinput: `in(headers.content-length,[222,445])`,\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tHeaders: testHeader,\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: \"equals(variables.x, -48.880005)\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tVariables: map[string]interface{}{\n\t\t\t\t\t\"x\": float64(-48.880005),\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: `equals(xpath(\"//item/title\"),\"ABC\")`,\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tBody: `<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n\t\t<rss version=\"2.0\">\n\t\t<channel>\n\t\t  <item>\n\t\t\t<title>ABC</title>\n\t\t  </item>\n\t\t</channel>\n\t\t</rss>`,\n\t\t\t},\n\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: `equals(html_path(\"//body/h1\"),\"ABC\")`,\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tBody: `<!DOCTYPE html>\n\t\t\t\t<html>\n\t\t\t\t<body>\n\t\t\t\t<h1>ABC</h1>\n\t\t\t\t</body>\n\t\t\t\t</html>`,\n\t\t\t},\n\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: \"equals(cookies.test.value, \\\"value\\\")\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tCookies: map[string]*http.Cookie{\n\t\t\t\t\t\"test\": {\n\t\t\t\t\t\tName:  \"test\",\n\t\t\t\t\t\tValue: \"value\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: \"exists(cookies.test)\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tCookies: map[string]*http.Cookie{\n\t\t\t\t\t\"test\": {\n\t\t\t\t\t\tName:  \"test\",\n\t\t\t\t\t\tValue: \"value\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: \"exists(cookies.test2)\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tCookies: map[string]*http.Cookie{\n\t\t\t\t\t\"test\": {\n\t\t\t\t\t\tName:  \"test\",\n\t\t\t\t\t\tValue: \"value\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: false,\n\t\t},\n\t\t{\n\t\t\tinput: \"cookies.test.secure\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tCookies: map[string]*http.Cookie{\n\t\t\t\t\t\"test\": {\n\t\t\t\t\t\tName:   \"test\",\n\t\t\t\t\t\tValue:  \"value\",\n\t\t\t\t\t\tSecure: true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: \"cookies.test.rawExpires == \\\"Thu, 01 Jan 1970 00:00:00 GMT\\\"\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tCookies: map[string]*http.Cookie{\n\t\t\t\t\t\"test\": {\n\t\t\t\t\t\tName:       \"test\",\n\t\t\t\t\t\tValue:      \"value\",\n\t\t\t\t\t\tSecure:     true,\n\t\t\t\t\t\tRawExpires: \"Thu, 01 Jan 1970 00:00:00 GMT\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: \"cookies.test.path == \\\"/login\\\"\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tCookies: map[string]*http.Cookie{\n\t\t\t\t\t\"test\": {\n\t\t\t\t\t\tName:  \"test\",\n\t\t\t\t\t\tValue: \"value\",\n\t\t\t\t\t\tPath:  \"/login\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: \"cookies.test.expires < time(\\\"Thu, 01 Jan 1990 00:00:00 GMT\\\")\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tCookies: map[string]*http.Cookie{\n\t\t\t\t\t\"test\": {\n\t\t\t\t\t\tName:       \"test\",\n\t\t\t\t\t\tValue:      \"value\",\n\t\t\t\t\t\tSecure:     true,\n\t\t\t\t\t\tRawExpires: \"Thu, 01 Jan 1970 00:00:00 GMT\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: \"cookies.test.httpOnly\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tCookies: map[string]*http.Cookie{\n\t\t\t\t\t\"test\": {\n\t\t\t\t\t\tName:     \"test\",\n\t\t\t\t\t\tValue:    \"value\",\n\t\t\t\t\t\tHttpOnly: true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: \"cookies.test.notexists\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tCookies: map[string]*http.Cookie{\n\t\t\t\t\t\"test\": {\n\t\t\t\t\t\tName:     \"test\",\n\t\t\t\t\t\tValue:    \"value\",\n\t\t\t\t\t\tHttpOnly: true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected:      false,\n\t\t\texpectedError: \"NotFoundError\",\n\t\t},\n\t\t{\n\t\t\tinput: \"cookies.notexists\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tCookies: map[string]*http.Cookie{\n\t\t\t\t\t\"test\": {\n\t\t\t\t\t\tName:     \"test\",\n\t\t\t\t\t\tValue:    \"value\",\n\t\t\t\t\t\tHttpOnly: true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected:      false,\n\t\t\texpectedError: \"NotFoundError\",\n\t\t},\n\t\t{\n\t\t\tinput: \"cookies.notexists.value\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tCookies: map[string]*http.Cookie{\n\t\t\t\t\t\"test\": {\n\t\t\t\t\t\tName:     \"test\",\n\t\t\t\t\t\tValue:    \"value\",\n\t\t\t\t\t\tHttpOnly: true,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected:      false,\n\t\t\texpectedError: \"NotFoundError\",\n\t\t},\n\t\t{\n\t\t\tinput: \"cookies.test.maxAge == 100\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tCookies: map[string]*http.Cookie{\n\t\t\t\t\t\"test\": {\n\t\t\t\t\t\tName:   \"test\",\n\t\t\t\t\t\tValue:  \"value\",\n\t\t\t\t\t\tMaxAge: 100,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: \"cookies.test.domain == \\\"ddosify.com\\\"\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tCookies: map[string]*http.Cookie{\n\t\t\t\t\t\"test\": {\n\t\t\t\t\t\tName:   \"test\",\n\t\t\t\t\t\tValue:  \"value\",\n\t\t\t\t\t\tDomain: \"ddosify.com\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: \"p99(iteration_duration) == 99\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tTotalTime: []int64{34, 37, 39, 44, 45, 55, 66, 67, 72, 75, 77, 89, 92, 98, 99},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: \"p98(iteration_duration) == 99\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tTotalTime: []int64{34, 37, 39, 44, 45, 55, 66, 67, 72, 75, 77, 89, 92, 98, 99},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: \"p95(iteration_duration) == 99\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tTotalTime: []int64{34, 37, 39, 44, 45, 55, 66, 67, 72, 75, 77, 89, 92, 98, 99},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: \"p90(iteration_duration) == 98\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tTotalTime: []int64{34, 37, 39, 44, 45, 55, 66, 67, 72, 75, 77, 89, 92, 98, 99},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: \"p80(iteration_duration) == 89\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tTotalTime: []int64{34, 37, 39, 44, 45, 55, 66, 67, 72, 75, 77, 89, 92, 98, 99},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: \"min(iteration_duration) == 34\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tTotalTime: []int64{34, 37, 39, 44, 45, 55, 66, 67, 72, 75, 77, 89, 92, 98, 99},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: \"max(iteration_duration) == 99\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tTotalTime: []int64{34, 37, 39, 44, 45, 55, 66, 67, 72, 75, 77, 89, 92, 98, 99},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: \"max(iteration_duration) == 2222\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tTotalTime: []int64{34, 37, 39, 44, 45, 55, 66, 67, 2222, 72, 75, 77, 89, 92, 98, 99},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput: \"avg(iteration_duration) == 200.6875\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tTotalTime: []int64{34, 37, 39, 44, 45, 55, 66, 67, 2222, 72, 75, 77, 89, 92, 98, 99},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tinput:    \"percentile([]) == 200.6875\",\n\t\t\texpected: false,\n\t\t},\n\t\t{\n\t\t\tinput:    \"min([]) == 200.6875\",\n\t\t\texpected: false,\n\t\t},\n\t\t{\n\t\t\tinput:    \"max([]) == 200.6875\",\n\t\t\texpected: false,\n\t\t},\n\t\t{\n\t\t\tinput: \"avg(response_size) == 200.6875\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tResponseSize: int64(23),\n\t\t\t},\n\t\t\texpected: false,\n\t\t},\n\t\t{\n\t\t\tinput: \"not(response_size)\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tResponseSize: int64(23),\n\t\t\t},\n\t\t\texpected:      false,\n\t\t\texpectedError: \"ArgumentError\",\n\t\t},\n\t\t{\n\t\t\tinput: \"less_than(10, 20.3)\",\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tResponseSize: int64(23),\n\t\t\t},\n\t\t\texpected:      false,\n\t\t\texpectedError: \"ArgumentError\",\n\t\t},\n\t\t{\n\t\t\tinput:         `equals_on_file(\"abc\", [34,60])`, // filepath must be string\n\t\t\texpected:      false,\n\t\t\texpectedError: \"ArgumentError\",\n\t\t},\n\t\t{\n\t\t\tinput: \"in(response_size,response_size)\", // second arg must be array\n\t\t\tenvs: &evaluator.AssertEnv{\n\t\t\t\tResponseSize: int64(23),\n\t\t\t},\n\t\t\texpected:      false,\n\t\t\texpectedError: \"ArgumentError\",\n\t\t},\n\t\t{\n\t\t\tinput:         \"json_path(23)\", // arg must be string\n\t\t\texpected:      false,\n\t\t\texpectedError: \"ArgumentError\",\n\t\t},\n\t\t{\n\t\t\tinput:         \"xpath(23)\", // arg must be string\n\t\t\texpected:      false,\n\t\t\texpectedError: \"ArgumentError\",\n\t\t},\n\t\t{\n\t\t\tinput:         \"html_path(23)\", // arg must be string\n\t\t\texpected:      false,\n\t\t\texpectedError: \"ArgumentError\",\n\t\t},\n\t\t{\n\t\t\tinput:         \"p99(23)\", // arg must be array\n\t\t\texpected:      false,\n\t\t\texpectedError: \"ArgumentError\",\n\t\t},\n\t\t{\n\t\t\tinput:         \"p98(23)\", // arg must be array\n\t\t\texpected:      false,\n\t\t\texpectedError: \"ArgumentError\",\n\t\t},\n\t\t{\n\t\t\tinput:         \"p95(23)\", // arg must be array\n\t\t\texpected:      false,\n\t\t\texpectedError: \"ArgumentError\",\n\t\t},\n\t\t{\n\t\t\tinput:         \"p90(23)\", // arg must be array\n\t\t\texpected:      false,\n\t\t\texpectedError: \"ArgumentError\",\n\t\t},\n\t\t{\n\t\t\tinput:         \"p80(23)\", // arg must be array\n\t\t\texpected:      false,\n\t\t\texpectedError: \"ArgumentError\",\n\t\t},\n\t\t{\n\t\t\tinput:    \"p80([])\", // empty array\n\t\t\texpected: false,\n\t\t},\n\t\t{\n\t\t\tinput:    \"min([])\", // empty array\n\t\t\texpected: false,\n\t\t},\n\t\t{\n\t\t\tinput:    \"max([])\", // empty array\n\t\t\texpected: false,\n\t\t},\n\t\t{\n\t\t\tinput:    \"avg([])\", // empty interface array, not []int64\n\t\t\texpected: false,\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.input, func(t *testing.T) {\n\t\t\teval, err := Assert(tc.input, tc.envs)\n\n\t\t\tif tc.expected != eval {\n\t\t\t\tt.Errorf(\"assert expected %t\", tc.expected)\n\t\t\t\tt.Log(err)\n\t\t\t}\n\n\t\t\tif err != nil && tc.expectedError != \"\" {\n\t\t\t\tif tc.expectedError == \"NotFoundError\" {\n\t\t\t\t\tvar notFoundError evaluator.NotFoundError\n\t\t\t\t\tif !errors.As(err, &notFoundError) {\n\t\t\t\t\t\tt.Errorf(\"Should be evaluator.NotFoundError, got %v\", err)\n\t\t\t\t\t}\n\t\t\t\t} else if tc.expectedError == \"ArgumentError\" {\n\t\t\t\t\tvar argError evaluator.ArgumentError\n\t\t\t\t\tif !errors.As(err, &argError) {\n\t\t\t\t\t\tt.Errorf(\"Should be evaluator.ArgumentError, got %v\", err)\n\t\t\t\t\t}\n\t\t\t\t} else if tc.expectedError == \"OperatorError\" {\n\t\t\t\t\tvar opError evaluator.OperatorError\n\t\t\t\t\tif !errors.As(err, &opError) {\n\t\t\t\t\t\tt.Errorf(\"Should be evaluator.OperatorError, got %v\", err)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t\tif err != nil && tc.received != nil {\n\t\t\t\tassertErr := err.(AssertionError)\n\t\t\t\tif !reflect.DeepEqual(assertErr.Received(), tc.received) {\n\t\t\t\t\tt.Errorf(\"received expected %v, got %v\", tc.received, assertErr.Received())\n\t\t\t\t}\n\t\t\t}\n\n\t\t})\n\t}\n\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/scripting/assertion/ast/ast.go",
    "content": "package ast\n\nimport (\n\t\"bytes\"\n\t\"strings\"\n\n\t\"go.ddosify.com/ddosify/core/scenario/scripting/assertion/token\"\n)\n\n// The base Node interface\ntype Node interface {\n\tTokenLiteral() string\n\tString() string\n}\n\n// All statement nodes implement this\ntype Statement interface {\n\tNode\n\tstatementNode()\n}\n\n// All expression nodes implement this\ntype Expression interface {\n\tNode\n\texpressionNode()\n}\n\ntype ExpressionStatement struct {\n\tToken      token.Token // the first token of the expression\n\tExpression Expression\n}\n\nfunc (es *ExpressionStatement) statementNode()       {}\nfunc (es *ExpressionStatement) TokenLiteral() string { return es.Token.Literal }\nfunc (es *ExpressionStatement) String() string {\n\tif es.Expression != nil {\n\t\treturn es.Expression.String()\n\t}\n\treturn \"\"\n}\n\n// Expressions\ntype Identifier struct {\n\tToken token.Token // the token.IDENT token\n\tValue string\n}\n\nfunc (i *Identifier) expressionNode()      {}\nfunc (i *Identifier) TokenLiteral() string { return i.Token.Literal }\nfunc (i *Identifier) String() string       { return i.Value }\n\ntype Boolean struct {\n\tToken token.Token\n\tValue bool\n}\n\nfunc (b *Boolean) expressionNode()      {}\nfunc (b *Boolean) TokenLiteral() string { return b.Token.Literal }\nfunc (b *Boolean) String() string       { return b.Token.Literal }\nfunc (il *Boolean) GetVal() interface{} { return il.Value }\n\ntype IntegerLiteral struct {\n\tToken token.Token\n\tValue int64\n}\n\nfunc (il *IntegerLiteral) expressionNode()      {}\nfunc (il *IntegerLiteral) TokenLiteral() string { return il.Token.Literal }\nfunc (il *IntegerLiteral) String() string       { return il.Token.Literal }\nfunc (il *IntegerLiteral) GetVal() interface{}  { return il.Value }\n\ntype FloatLiteral struct {\n\tToken token.Token\n\tValue float64\n}\n\nfunc (il *FloatLiteral) expressionNode()      {}\nfunc (il *FloatLiteral) TokenLiteral() string { return il.Token.Literal }\nfunc (il *FloatLiteral) String() string       { return il.Token.Literal }\nfunc (il *FloatLiteral) GetVal() interface{}  { return il.Value }\n\ntype NullLiteral struct {\n\tToken token.Token\n\tValue interface{}\n}\n\nfunc (il *NullLiteral) expressionNode()      {}\nfunc (il *NullLiteral) TokenLiteral() string { return il.Token.Literal }\nfunc (il *NullLiteral) String() string       { return il.Token.Literal }\nfunc (il *NullLiteral) GetVal() interface{}  { return il.Value }\n\ntype StringLiteral struct {\n\tToken token.Token\n\tValue string\n}\n\nfunc (il *StringLiteral) expressionNode()      {}\nfunc (il *StringLiteral) TokenLiteral() string { return il.Token.Literal }\nfunc (il *StringLiteral) String() string       { return il.Token.Literal }\nfunc (il *StringLiteral) GetVal() interface{}  { return il.Value }\n\ntype ArrayLiteral struct {\n\tToken token.Token\n\tElems []Expression\n}\n\nfunc (il *ArrayLiteral) expressionNode()      {}\nfunc (il *ArrayLiteral) TokenLiteral() string { return il.Token.Literal }\nfunc (il *ArrayLiteral) String() string {\n\tx := []string{}\n\tfor _, e := range il.Elems {\n\t\tx = append(x, e.String())\n\t}\n\n\treturn \"[\" + strings.Join(x, \",\") + \"]\"\n}\n\ntype ObjectLiteral struct {\n\tToken token.Token\n\tElems map[string]Expression\n}\n\nfunc (il *ObjectLiteral) expressionNode()      {}\nfunc (il *ObjectLiteral) TokenLiteral() string { return il.Token.Literal }\nfunc (il *ObjectLiteral) String() string {\n\tx := []string{}\n\tfor k, e := range il.Elems {\n\t\tx = append(x, k+\":\"+e.String())\n\t}\n\n\treturn \"{\" + strings.Join(x, \",\") + \"}\"\n}\n\ntype PrefixExpression struct {\n\tToken    token.Token // The prefix token, e.g. !\n\tOperator string\n\tRight    Expression\n}\n\nfunc (pe *PrefixExpression) expressionNode()      {}\nfunc (pe *PrefixExpression) TokenLiteral() string { return pe.Token.Literal }\nfunc (pe *PrefixExpression) String() string {\n\tvar out bytes.Buffer\n\n\tout.WriteString(\"(\")\n\tout.WriteString(pe.Operator)\n\tout.WriteString(pe.Right.String())\n\tout.WriteString(\")\")\n\n\treturn out.String()\n}\n\ntype InfixExpression struct {\n\tToken    token.Token // The operator token, e.g. +\n\tLeft     Expression\n\tOperator string\n\tRight    Expression\n}\n\nfunc (oe *InfixExpression) expressionNode()      {}\nfunc (oe *InfixExpression) TokenLiteral() string { return oe.Token.Literal }\nfunc (oe *InfixExpression) String() string {\n\tvar out bytes.Buffer\n\n\tout.WriteString(\"(\")\n\tout.WriteString(oe.Left.String())\n\tout.WriteString(\" \" + oe.Operator + \" \")\n\tout.WriteString(oe.Right.String())\n\tout.WriteString(\")\")\n\n\treturn out.String()\n}\n\ntype CallExpression struct {\n\tToken     token.Token // The '(' token\n\tFunction  Expression  // Identifier or FunctionLiteral\n\tArguments []Expression\n}\n\nfunc (ce *CallExpression) expressionNode()      {}\nfunc (ce *CallExpression) TokenLiteral() string { return ce.Token.Literal }\n\nfunc (ce *CallExpression) String() string {\n\tvar out bytes.Buffer\n\n\targs := []string{}\n\tfor _, a := range ce.Arguments {\n\t\targs = append(args, a.String())\n\t}\n\n\tout.WriteString(ce.Function.String())\n\tout.WriteString(\"(\")\n\tout.WriteString(strings.Join(args, \",\"))\n\tout.WriteString(\")\")\n\n\treturn out.String()\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/scripting/assertion/evaluator/env.go",
    "content": "package evaluator\n\nimport \"net/http\"\n\ntype AssertEnv struct {\n\tStatusCode   int64\n\tResponseSize int64\n\tResponseTime int64 // in ms\n\tBody         string\n\tHeaders      http.Header\n\tVariables    map[string]interface{}\n\tCookies      map[string]*http.Cookie // cookies sent by the server, name -> cookie\n\n\t// For test-wide assertions\n\tTotalTime     []int64 // in ms\n\tFailCount     int\n\tFailCountPerc float64 // should be in range [0,1]\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/scripting/assertion/evaluator/evaluator.go",
    "content": "package evaluator\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"go.ddosify.com/ddosify/core/scenario/scripting/assertion/ast\"\n)\n\nfunc Eval(node ast.Node, env *AssertEnv, receivedMap map[string]interface{}) (interface{}, error) {\n\tswitch node := node.(type) {\n\n\tcase *ast.ExpressionStatement:\n\t\treturn Eval(node.Expression, env, receivedMap)\n\n\t// Expressions\n\tcase *ast.IntegerLiteral:\n\t\treturn node.GetVal(), nil\n\tcase *ast.FloatLiteral:\n\t\treturn node.GetVal(), nil\n\tcase *ast.StringLiteral:\n\t\treturn node.GetVal(), nil\n\tcase *ast.NullLiteral:\n\t\treturn node.GetVal(), nil\n\tcase *ast.ArrayLiteral:\n\t\targs, err := evalExpressions(node.Elems, env, receivedMap)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn args, nil\n\tcase *ast.ObjectLiteral:\n\t\targs, err := evalObjectExpressions(node.Elems, env, receivedMap)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn args, nil\n\tcase *ast.Boolean:\n\t\treturn node.GetVal(), nil\n\tcase *ast.PrefixExpression:\n\t\tright, err := Eval(node.Right, env, receivedMap)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn evalPrefixExpression(node.Operator, right)\n\tcase *ast.InfixExpression:\n\t\tleft, err := Eval(node.Left, env, receivedMap)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tright, err := Eval(node.Right, env, receivedMap)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treturn evalInfixExpression(node.Operator, left, right)\n\tcase *ast.Identifier:\n\t\treturn evalIdentifier(node, env, receivedMap)\n\n\tcase *ast.CallExpression:\n\t\tfuncName := node.Function.(*ast.Identifier).Value\n\t\tif _, ok := assertionFuncMap[funcName]; ok {\n\t\t\targs, err := evalExpressions(node.Arguments, env, receivedMap)\n\t\t\tif err != nil {\n\t\t\t\treturn false, err\n\t\t\t}\n\n\t\t\tf := func() (result interface{}, err error) {\n\t\t\t\tdefer func() {\n\t\t\t\t\tif r := recover(); r != nil {\n\t\t\t\t\t\tresult = nil\n\t\t\t\t\t\terr = fmt.Errorf(\"probably error during type conversion , %v\", r)\n\t\t\t\t\t}\n\t\t\t\t}()\n\n\t\t\t\tswitch funcName {\n\t\t\t\tcase NOT:\n\t\t\t\t\tp, ok := args[0].(bool)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn false, ArgumentError{\n\t\t\t\t\t\t\tmsg:        \"arg of not func must be a bool\",\n\t\t\t\t\t\t\twrappedErr: nil,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn not(p), nil\n\t\t\t\tcase LESSTHAN:\n\t\t\t\t\tvariable, ok := args[0].(int64)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\tvariable, _ = strconv.ParseInt(args[0].(string), 0, 64)\n\t\t\t\t\t}\n\t\t\t\t\tlimit, ok := args[1].(int64)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn false, ArgumentError{\n\t\t\t\t\t\t\tmsg:        \"limit of less_than should be integer\",\n\t\t\t\t\t\t\twrappedErr: nil,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn less_than(variable, limit), nil\n\t\t\t\tcase GREATERTHAN:\n\t\t\t\t\tvariable, ok := args[0].(int64)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\tvariable, _ = strconv.ParseInt(args[0].(string), 0, 64)\n\t\t\t\t\t}\n\t\t\t\t\tlimit, ok := args[1].(int64)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn false, ArgumentError{\n\t\t\t\t\t\t\tmsg:        \"limit of greater_than should be integer\",\n\t\t\t\t\t\t\twrappedErr: nil,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn greater_than(variable, limit), nil\n\t\t\t\tcase EQUALS:\n\t\t\t\t\treturn equals(args[0], args[1])\n\t\t\t\tcase EQUALSONFILE:\n\t\t\t\t\tfilepath, ok := args[1].(string)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn false, ArgumentError{\n\t\t\t\t\t\t\tmsg:        \"filepath must be a string\",\n\t\t\t\t\t\t\twrappedErr: nil,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn equalsOnFile(args[0], filepath)\n\t\t\t\tcase IN:\n\t\t\t\t\telems, ok := args[1].([]interface{})\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn false, ArgumentError{\n\t\t\t\t\t\t\tmsg:        \"second arg of in func must be an array\",\n\t\t\t\t\t\t\twrappedErr: nil,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn in(args[0], elems)\n\t\t\t\tcase JSONPATH:\n\t\t\t\t\tjsonpath, ok := args[0].(string)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn false, ArgumentError{\n\t\t\t\t\t\t\tmsg:        \"jsonpath must be a string\",\n\t\t\t\t\t\t\twrappedErr: nil,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn jsonExtract(env.Body, jsonpath)\n\t\t\t\tcase XMLPATH:\n\t\t\t\t\txpath, ok := args[0].(string)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn false, ArgumentError{\n\t\t\t\t\t\t\tmsg:        \"xpath must be a string\",\n\t\t\t\t\t\t\twrappedErr: nil,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn xmlExtract(env.Body, xpath)\n\t\t\t\tcase HTMLPATH:\n\t\t\t\t\thtml, ok := args[0].(string)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn false, ArgumentError{\n\t\t\t\t\t\t\tmsg:        \"htmlpath must be a string\",\n\t\t\t\t\t\t\twrappedErr: nil,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn htmlExtract(env.Body, html)\n\t\t\t\tcase REGEXP:\n\t\t\t\t\tregexp, ok := args[1].(string)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn false, ArgumentError{\n\t\t\t\t\t\t\tmsg:        \"regexp must be a string\",\n\t\t\t\t\t\t\twrappedErr: nil,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tmatchNo, ok := args[2].(int64)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn false, ArgumentError{\n\t\t\t\t\t\t\tmsg:        \"matchNo must be an int64\",\n\t\t\t\t\t\t\twrappedErr: nil,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn regexExtract(env.Body, regexp, matchNo)\n\t\t\t\tcase EXISTS:\n\t\t\t\t\tif args[0] != nil {\n\t\t\t\t\t\treturn true, nil // if identifier evaluated, and exists\n\t\t\t\t\t}\n\t\t\t\t\treturn false, nil\n\t\t\t\tcase TIME:\n\t\t\t\t\treturn timeF(args[0].(string))\n\t\t\t\tcase CONTAINS:\n\t\t\t\t\tp1, ok := args[0].(string)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn false, ArgumentError{\n\t\t\t\t\t\t\tmsg:        \"args of contains func must be string\",\n\t\t\t\t\t\t\twrappedErr: nil,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tp2, ok := args[1].(string)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn false, ArgumentError{\n\t\t\t\t\t\t\tmsg:        \"args of contains func must be string\",\n\t\t\t\t\t\t\twrappedErr: nil,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn contains(p1, p2), nil\n\t\t\t\tcase AVG:\n\t\t\t\t\tarr, ok := args[0].([]int64)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn false, ArgumentError{\n\t\t\t\t\t\t\tmsg:        \"argument of avg func must be an int64 array\",\n\t\t\t\t\t\t\twrappedErr: nil,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn avg(arr)\n\t\t\t\tcase MIN:\n\t\t\t\t\tarr, ok := args[0].([]int64)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn false, ArgumentError{\n\t\t\t\t\t\t\tmsg:        \"argument of min func must be an int64 array\",\n\t\t\t\t\t\t\twrappedErr: nil,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn min(arr)\n\t\t\t\tcase MAX:\n\t\t\t\t\tarr, ok := args[0].([]int64)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn false, ArgumentError{\n\t\t\t\t\t\t\tmsg:        \"argument of max func must be an int64 array\",\n\t\t\t\t\t\t\twrappedErr: nil,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn max(arr)\n\t\t\t\t// TODO only one func percentile(arr, num) ?\n\t\t\t\tcase P99:\n\t\t\t\t\tarr, ok := args[0].([]int64)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn false, ArgumentError{\n\t\t\t\t\t\t\tmsg:        \"argument of percentile funcs must be an int64 array\",\n\t\t\t\t\t\t\twrappedErr: nil,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn percentile(arr, 99)\n\t\t\t\tcase P98:\n\t\t\t\t\tarr, ok := args[0].([]int64)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn false, ArgumentError{\n\t\t\t\t\t\t\tmsg:        \"argument of percentile funcs must be an int64 array\",\n\t\t\t\t\t\t\twrappedErr: nil,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn percentile(arr, 98)\n\t\t\t\tcase P95:\n\t\t\t\t\tarr, ok := args[0].([]int64)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn false, ArgumentError{\n\t\t\t\t\t\t\tmsg:        \"argument of percentile funcs must be an int64 array\",\n\t\t\t\t\t\t\twrappedErr: nil,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn percentile(arr, 95)\n\t\t\t\tcase P90:\n\t\t\t\t\tarr, ok := args[0].([]int64)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn false, ArgumentError{\n\t\t\t\t\t\t\tmsg:        \"argument of percentile funcs must be an int64 array\",\n\t\t\t\t\t\t\twrappedErr: nil,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn percentile(arr, 90)\n\t\t\t\tcase P80:\n\t\t\t\t\tarr, ok := args[0].([]int64)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn false, ArgumentError{\n\t\t\t\t\t\t\tmsg:        \"argument of percentile funcs must be an int64 array\",\n\t\t\t\t\t\t\twrappedErr: nil,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn percentile(arr, 80)\n\t\t\t\tcase RANGE:\n\t\t\t\t\tvar x, low, high float64\n\n\t\t\t\t\tx, ok = args[0].(float64)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\txInt, ok := args[0].(int64)\n\t\t\t\t\t\tif ok {\n\t\t\t\t\t\t\tx = float64(xInt)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// assume that it is string\n\t\t\t\t\t\t\txFloat, err := strconv.ParseFloat(args[0].(string), 64)\n\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\treturn false, ArgumentError{\n\t\t\t\t\t\t\t\t\tmsg:        \"arguments of range should be integer or float\",\n\t\t\t\t\t\t\t\t\twrappedErr: nil,\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tx = xFloat\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tlow, ok = args[1].(float64)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\tlowInt, ok := args[1].(int64)\n\t\t\t\t\t\tif !ok {\n\t\t\t\t\t\t\treturn false, ArgumentError{\n\t\t\t\t\t\t\t\tmsg:        \"arguments of range should be integer or float\",\n\t\t\t\t\t\t\t\twrappedErr: nil,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlow = float64(lowInt)\n\t\t\t\t\t}\n\t\t\t\t\thigh, ok = args[2].(float64)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\thighInt, ok := args[2].(int64)\n\t\t\t\t\t\tif !ok {\n\t\t\t\t\t\t\treturn false, ArgumentError{\n\t\t\t\t\t\t\t\tmsg:        \"arguments of range should be integer or float\",\n\t\t\t\t\t\t\t\twrappedErr: nil,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\thigh = float64(highInt)\n\t\t\t\t\t}\n\n\t\t\t\t\treturn rangeF(x, low, high), nil\n\t\t\t\t}\n\t\t\t\treturn nil, NotFoundError{\n\t\t\t\t\tsource:     fmt.Sprintf(\"func %s not defined\", funcName),\n\t\t\t\t\twrappedErr: nil,\n\t\t\t\t}\n\n\t\t\t}\n\t\t\tres, err := f()\n\t\t\treceivedMap[node.String()] = res\n\t\t\treturn res, err\n\t\t}\n\t}\n\treturn nil, nil\n}\n\nfunc evalPrefixExpression(operator string, right interface{}) (interface{}, error) {\n\tswitch operator {\n\tcase \"!\":\n\t\treturn evalBangOperatorExpression(right)\n\tcase \"-\":\n\t\treturn evalMinusPrefixOperatorExpression(right)\n\tdefault:\n\t\treturn nil, OperatorError{\n\t\t\tmsg:        fmt.Sprintf(\"unknown operator: %s%s\", operator, right),\n\t\t\twrappedErr: nil,\n\t\t}\n\t}\n}\n\nfunc evalInfixExpression(\n\toperator string,\n\tleft, right interface{},\n) (interface{}, error) {\n\tleftType := reflect.ValueOf(left).Kind()\n\trightType := reflect.ValueOf(right).Kind()\n\n\t// int - int\n\tif leftType == reflect.Int64 && rightType == reflect.Int64 {\n\t\treturn evalIntegerInfixExpression(operator, left.(int64), right.(int64))\n\t}\n\tif leftType == reflect.Int64 && rightType == reflect.Int {\n\t\treturn evalIntegerInfixExpression(operator, left.(int64), int64(right.(int)))\n\t}\n\tif leftType == reflect.Int && rightType == reflect.Int64 {\n\t\treturn evalIntegerInfixExpression(operator, int64(left.(int)), right.(int64))\n\t}\n\tif leftType == reflect.Int && rightType == reflect.Int {\n\t\treturn evalIntegerInfixExpression(operator, int64(left.(int)), int64(left.(int)))\n\t}\n\n\t// int - float, convert int64 to float64, data loss for big int64 numbers\n\tif leftType == reflect.Int64 && rightType == reflect.Float64 {\n\t\treturn evalFloatInfixExpression(operator, float64(left.(int64)), right.(float64))\n\t}\n\tif leftType == reflect.Float64 && rightType == reflect.Int64 {\n\t\treturn evalFloatInfixExpression(operator, left.(float64), float64(right.(int64)))\n\t}\n\n\t// float - float\n\tif leftType == reflect.Float64 && rightType == reflect.Float64 {\n\t\treturn evalFloatInfixExpression(operator, left.(float64), right.(float64))\n\t}\n\n\t// string - int\n\tif leftType == reflect.String && rightType == reflect.Int64 {\n\t\tleftInt, _ := strconv.ParseInt(left.(string), 0, 64)\n\t\treturn evalIntegerInfixExpression(operator, leftInt, right.(int64))\n\t}\n\tif leftType == reflect.Int64 && rightType == reflect.String {\n\t\trightInt, _ := strconv.ParseInt(right.(string), 0, 64)\n\t\treturn evalIntegerInfixExpression(operator, left.(int64), rightInt)\n\t}\n\n\tif lTime, lok := left.(time.Time); lok {\n\t\tif rTime, rok := right.(time.Time); rok {\n\t\t\treturn evalTimeInfixExpression(operator, lTime, rTime)\n\t\t}\n\t\treturn nil, OperatorError{\n\t\t\tmsg: \"time can be only compared with time\",\n\t\t}\n\t}\n\n\t// other types\n\n\tif leftType == reflect.String && rightType == reflect.String {\n\t\t// json marshalling is used to compare json strings\n\t\tvar lJson, rJson interface{}\n\n\t\tisLJson := json.Unmarshal([]byte(left.(string)), &lJson)\n\t\tisRJson := json.Unmarshal([]byte(right.(string)), &rJson)\n\n\t\tif isLJson == nil && isRJson == nil {\n\t\t\treturn reflect.DeepEqual(lJson, rJson), nil\n\t\t}\n\t}\n\n\tif leftType == reflect.String && rightType == reflect.Map {\n\t\tvar lJson interface{}\n\t\tisLJson := json.Unmarshal([]byte(left.(string)), &lJson)\n\n\t\tif isLJson == nil {\n\t\t\trJsonBy, _ := json.Marshal(right)\n\t\t\tlJsonBy, _ := json.Marshal(lJson)\n\t\t\treturn reflect.DeepEqual(rJsonBy, lJsonBy), nil\n\t\t}\n\t}\n\tif leftType == reflect.Map && rightType == reflect.String {\n\t\tvar rJson interface{}\n\t\tisRJson := json.Unmarshal([]byte(right.(string)), &rJson)\n\n\t\tif isRJson == nil {\n\t\t\tlJsonBy, _ := json.Marshal(left)\n\t\t\trJsonBy, _ := json.Marshal(rJson)\n\t\t\treturn reflect.DeepEqual(lJsonBy, rJsonBy), nil\n\t\t}\n\t}\n\n\tif operator == \"==\" {\n\t\treturn reflect.DeepEqual(left, right), nil\n\t}\n\n\tif operator == \"!=\" {\n\t\treturn !reflect.DeepEqual(left, right), nil\n\t}\n\n\tif operator == \"&&\" {\n\t\tif leftType == reflect.Bool && rightType == reflect.Bool {\n\t\t\treturn left.(bool) && right.(bool), nil\n\t\t}\n\t\treturn nil, OperatorError{\n\t\t\tmsg:        fmt.Sprintf(\"operator && unsupported for types: %s and %s\", leftType, rightType),\n\t\t\twrappedErr: nil,\n\t\t}\n\t}\n\n\tif operator == \"||\" {\n\t\tif leftType == reflect.Bool && rightType == reflect.Bool {\n\t\t\treturn left.(bool) || right.(bool), nil\n\t\t}\n\t\treturn nil, OperatorError{\n\t\t\tmsg:        fmt.Sprintf(\"operator || unsupported for types: %s and %s\", leftType, rightType),\n\t\t\twrappedErr: nil,\n\t\t}\n\t}\n\n\treturn nil, OperatorError{\n\t\tmsg:        fmt.Sprintf(\"unknown operator: evalInfixExpression %s\", operator),\n\t\twrappedErr: nil,\n\t}\n}\n\nfunc evalBangOperatorExpression(right interface{}) (bool, error) {\n\tb, ok := right.(bool)\n\tif ok {\n\t\treturn !b, nil\n\t}\n\n\treturn false, OperatorError{\n\t\tmsg:        fmt.Sprintf(\"identifier before ! operator must be bool, %s\", right),\n\t\twrappedErr: nil,\n\t}\n}\n\nfunc evalMinusPrefixOperatorExpression(right interface{}) (interface{}, error) {\n\ti, ok := right.(int64)\n\tif ok {\n\t\treturn -i, nil\n\t}\n\n\tvar j float64\n\tj, ok = right.(float64)\n\tif ok {\n\t\treturn -j, nil\n\t}\n\n\tif !ok {\n\t\treturn 0, OperatorError{\n\t\t\tmsg:        fmt.Sprintf(\"- operator not applicable for %v\", right),\n\t\t\twrappedErr: nil,\n\t\t}\n\t}\n\n\treturn -i, nil\n}\n\nfunc evalFloatInfixExpression(operator string,\n\tleft, right float64,\n) (interface{}, error) {\n\n\tswitch operator {\n\tcase \"+\":\n\t\treturn left + right, nil\n\tcase \"-\":\n\t\treturn left - right, nil\n\tcase \"*\":\n\t\treturn left * right, nil\n\tcase \"/\":\n\t\treturn left / right, nil\n\tcase \"<\":\n\t\treturn left < right, nil\n\tcase \">\":\n\t\treturn left > right, nil\n\tcase \"==\":\n\t\treturn left == right, nil\n\tcase \"!=\":\n\t\treturn left != right, nil\n\tdefault:\n\t\treturn 0, OperatorError{\n\t\t\tmsg:        fmt.Sprintf(\"unknown operator %s for floats\", operator),\n\t\t\twrappedErr: nil,\n\t\t}\n\t}\n}\n\nfunc evalTimeInfixExpression(operator string, lTime, rTime time.Time) (interface{}, error) {\n\tswitch operator {\n\tcase \"==\":\n\t\treturn lTime == rTime, nil\n\tcase \"!=\":\n\t\treturn lTime != rTime, nil\n\tcase \"<\":\n\t\treturn lTime.Before(rTime), nil\n\tcase \">\":\n\t\treturn lTime.After(rTime), nil\n\tdefault:\n\t\treturn 0, OperatorError{\n\t\t\tmsg:        fmt.Sprintf(\"unknown operator %s for time.Time\", operator),\n\t\t\twrappedErr: nil,\n\t\t}\n\t}\n}\n\nfunc evalIntegerInfixExpression(\n\toperator string,\n\tleft, right int64,\n) (interface{}, error) {\n\n\tswitch operator {\n\tcase \"+\":\n\t\treturn left + right, nil\n\tcase \"-\":\n\t\treturn left - right, nil\n\tcase \"*\":\n\t\treturn left * right, nil\n\tcase \"/\":\n\t\treturn left / right, nil\n\tcase \"<\":\n\t\treturn left < right, nil\n\tcase \">\":\n\t\treturn left > right, nil\n\tcase \"==\":\n\t\treturn left == right, nil\n\tcase \"!=\":\n\t\treturn left != right, nil\n\tdefault:\n\t\treturn 0, OperatorError{\n\t\t\tmsg:        fmt.Sprintf(\"unknown operator %s for integers\", operator),\n\t\t\twrappedErr: nil,\n\t\t}\n\t}\n}\n\nfunc evalIdentifier(\n\tnode *ast.Identifier,\n\tenv *AssertEnv,\n\treceivedMap map[string]interface{},\n) (interface{}, error) {\n\tident := node.Value\n\tif strings.EqualFold(ident, \"status_code\") {\n\t\treceivedMap[ident] = env.StatusCode\n\t\treturn env.StatusCode, nil\n\t}\n\tif strings.EqualFold(ident, \"response_size\") {\n\t\treceivedMap[ident] = env.ResponseSize\n\t\treturn env.ResponseSize, nil\n\t}\n\tif strings.EqualFold(ident, \"response_time\") {\n\t\treceivedMap[ident] = env.ResponseTime\n\t\treturn env.ResponseTime, nil\n\t}\n\tif strings.EqualFold(ident, \"body\") {\n\t\treceivedMap[ident] = env.Body\n\t\treturn env.Body, nil\n\t}\n\n\t// test-wide identifiers\n\tif strings.EqualFold(ident, \"fail_count\") {\n\t\treceivedMap[ident] = env.FailCount\n\t\treturn env.FailCount, nil\n\t}\n\tif strings.EqualFold(ident, \"fail_count_perc\") {\n\t\treceivedMap[ident] = env.FailCountPerc\n\t\treturn env.FailCountPerc, nil\n\t}\n\tif strings.EqualFold(ident, \"iteration_duration\") {\n\t\treceivedMap[ident] = env.TotalTime\n\t\treturn env.TotalTime, nil\n\t}\n\n\tif strings.HasPrefix(ident, \"variables.\") {\n\t\tvr := strings.TrimPrefix(ident, \"variables.\")\n\t\tif v, ok := env.Variables[vr]; ok {\n\t\t\treceivedMap[ident] = v\n\t\t\treturn v, nil\n\t\t}\n\t\treturn \"\", NotFoundError{\n\t\t\tsource:     fmt.Sprintf(\"variable not found %s\", vr),\n\t\t\twrappedErr: nil,\n\t\t}\n\t}\n\tif strings.HasPrefix(ident, \"headers.\") {\n\t\tvr := strings.TrimPrefix(ident, \"headers.\")\n\t\thv := env.Headers.Get(vr)\n\t\tif hv != \"\" {\n\t\t\treceivedMap[ident] = hv\n\t\t\treturn hv, nil\n\t\t}\n\t\treturn \"\", NotFoundError{ //\n\t\t\tsource:     fmt.Sprintf(\"header not found %s\", vr),\n\t\t\twrappedErr: nil,\n\t\t}\n\t}\n\tif strings.HasPrefix(ident, \"cookies.\") {\n\t\t// cookies.cookie_name.field_name\n\t\t// cookies.csrftoken.expires\n\t\tvr := strings.TrimPrefix(ident, \"cookies.\")\n\t\twords := strings.Split(vr, \".\") // e.g. [\"csrftoken\", \"expires\"] or [\"csrftoken\"]\n\n\t\tif len(words) == 1 {\n\t\t\tname := words[0]\n\t\t\tif v, ok := env.Cookies[name]; ok {\n\t\t\t\treceivedMap[ident] = v\n\t\t\t\treturn v, nil\n\t\t\t}\n\t\t\treturn \"\", NotFoundError{ //\n\t\t\t\tsource:     fmt.Sprintf(\"cookie not found %s\", name),\n\t\t\t\twrappedErr: nil,\n\t\t\t}\n\t\t} else if len(words) == 2 {\n\t\t\tname := words[0]\n\t\t\tif v, ok := env.Cookies[name]; ok {\n\t\t\t\tfieldName := words[1]\n\t\t\t\tvalue, err := evalCookieField(v, fieldName)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn \"\", NotFoundError{ //\n\t\t\t\t\t\tsource:     fmt.Sprintf(\"cookie field not found %s\", fieldName),\n\t\t\t\t\t\twrappedErr: err,\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treceivedMap[ident] = value\n\t\t\t\treturn value, nil\n\t\t\t} else {\n\t\t\t\treturn \"\", NotFoundError{ //\n\t\t\t\t\tsource:     fmt.Sprintf(\"cookie not found %s\", name),\n\t\t\t\t\twrappedErr: nil,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t}\n\n\treturn \"\", NotFoundError{ //\n\t\tsource:     fmt.Sprintf(\"%s not defined\", ident),\n\t\twrappedErr: nil,\n\t}\n}\n\nfunc evalObjectExpressions(\n\texps map[string]ast.Expression,\n\tenv *AssertEnv,\n\treceivedMap map[string]interface{},\n) (map[string]interface{}, error) {\n\tvar result = make(map[string]interface{})\n\tfor k, e := range exps {\n\t\tevaluated, err := Eval(e, env, receivedMap)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tswitch e.(type) {\n\t\tcase *ast.Identifier:\n\t\t\treceivedMap[e.String()] = evaluated\n\t\tcase *ast.CallExpression:\n\t\t\treceivedMap[e.String()] = evaluated\n\t\t}\n\n\t\tresult[k] = evaluated\n\t}\n\n\treturn result, nil\n}\n\nfunc evalExpressions(\n\texps []ast.Expression,\n\tenv *AssertEnv,\n\treceivedMap map[string]interface{},\n) ([]interface{}, error) {\n\tvar result = make([]interface{}, 0)\n\n\tfor _, e := range exps {\n\t\tevaluated, err := Eval(e, env, receivedMap)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tswitch e.(type) {\n\t\tcase *ast.Identifier:\n\t\t\treceivedMap[e.String()] = evaluated\n\t\tcase *ast.CallExpression:\n\t\t\treceivedMap[e.String()] = evaluated\n\t\t}\n\n\t\tresult = append(result, evaluated)\n\t}\n\n\treturn result, nil\n}\n\nfunc evalCookieField(c *http.Cookie, fieldName string) (interface{}, error) {\n\tswitch fieldName {\n\tcase \"name\":\n\t\treturn c.Name, nil\n\tcase \"value\":\n\t\treturn c.Value, nil\n\tcase \"path\":\n\t\treturn c.Path, nil\n\tcase \"domain\":\n\t\treturn c.Domain, nil\n\tcase \"expires\":\n\t\treturn c.Expires, nil\n\tcase \"rawExpires\":\n\t\treturn c.RawExpires, nil\n\tcase \"maxAge\":\n\t\treturn c.MaxAge, nil\n\tcase \"secure\":\n\t\treturn c.Secure, nil\n\tcase \"httpOnly\":\n\t\treturn c.HttpOnly, nil\n\tcase \"raw\":\n\t\treturn c.Raw, nil\n\t// case \"unparsed\":\n\t// \treturn c.Unparsed, nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unknown field %s\", fieldName)\n\t}\n}\n\ntype NotFoundError struct { // UnWrappable\n\tsource     string\n\twrappedErr error\n}\n\nfunc (nf NotFoundError) Error() string {\n\treturn fmt.Sprintf(\"%s\", nf.source)\n}\n\nfunc (nf NotFoundError) Unwrap() error {\n\treturn nf.wrappedErr\n}\n\ntype ArgumentError struct { // UnWrappable\n\tmsg        string\n\twrappedErr error\n}\n\nfunc (nf ArgumentError) Error() string {\n\treturn fmt.Sprintf(\"%s\", nf.msg)\n}\n\nfunc (nf ArgumentError) Unwrap() error {\n\treturn nf.wrappedErr\n}\n\ntype OperatorError struct { // UnWrappable\n\tmsg        string\n\twrappedErr error\n}\n\nfunc (nf OperatorError) Error() string {\n\treturn fmt.Sprintf(\"%s\", nf.msg)\n}\n\nfunc (nf OperatorError) Unwrap() error {\n\treturn nf.wrappedErr\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/scripting/assertion/evaluator/function.go",
    "content": "package evaluator\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"math\"\n\t\"os\"\n\t\"reflect\"\n\t\"strings\"\n\t\"time\"\n\n\t\"go.ddosify.com/ddosify/core/scenario/scripting/extraction\"\n\t\"go.ddosify.com/ddosify/core/types\"\n)\n\nvar less_than = func(variable int64, limit int64) bool {\n\treturn variable < limit\n}\nvar greater_than = func(variable int64, limit int64) bool {\n\treturn variable > limit\n}\n\nvar not = func(b bool) bool {\n\treturn !b\n}\n\n// assumed given array is sorted\nvar percentile = func(arr []int64, num int) (int64, error) {\n\tif len(arr) == 0 {\n\t\treturn 0, fmt.Errorf(\"empty input array on percentile func\")\n\t}\n\n\tindex := int(math.Ceil(float64(len(arr)*num)/100)) - 1\n\n\tif index < 0 {\n\t\tindex = 0\n\t}\n\n\treturn arr[index], nil\n}\n\nvar min = func(arr []int64) (int64, error) {\n\tif len(arr) == 0 {\n\t\treturn 0, fmt.Errorf(\"empty input array on min func\")\n\t}\n\tmin := arr[0]\n\n\tfor _, i := range arr {\n\t\tif min > i {\n\t\t\tmin = i\n\t\t}\n\t}\n\n\treturn min, nil\n}\n\nvar max = func(arr []int64) (int64, error) {\n\tif len(arr) == 0 {\n\t\treturn 0, fmt.Errorf(\"empty input array on max func\")\n\t}\n\tmax := arr[0]\n\n\tfor _, i := range arr {\n\t\tif max < i {\n\t\t\tmax = i\n\t\t}\n\t}\n\n\treturn max, nil\n}\n\nvar avg = func(arr []int64) (float64, error) {\n\tif len(arr) == 0 {\n\t\treturn 0, fmt.Errorf(\"empty input array on avg func\")\n\t}\n\tvar total int64\n\n\tfor _, i := range arr {\n\t\ttotal += i\n\t}\n\n\treturn float64(total) / float64(len(arr)), nil\n}\n\nvar equals = func(a, b interface{}) (bool, error) {\n\tb, err := evalInfixExpression(\"==\", a, b)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn b.(bool), nil\n}\n\nvar in = func(a interface{}, b []interface{}) (bool, error) {\n\tfor _, elem := range b {\n\t\tif eq, err := equals(a, elem); eq {\n\t\t\treturn true, nil\n\t\t} else if err != nil {\n\t\t\treturn false, err\n\t\t}\n\t}\n\treturn false, nil\n}\n\nvar contains = func(source string, substr string) bool {\n\tif strings.Contains(source, substr) {\n\t\treturn true\n\t}\n\treturn false\n}\n\nvar timeF = func(t string) (time.Time, error) {\n\tres, err := time.Parse(time.RFC1123, t)\n\tif err != nil {\n\t\treturn time.Time{}, err\n\t}\n\treturn res, nil\n}\n\nvar rangeF = func(x float64, low float64, hi float64) bool {\n\tif x >= low && x < hi {\n\t\treturn true\n\t}\n\treturn false\n}\n\nvar jsonExtract = func(source interface{}, jsonPath string) (interface{}, error) {\n\tval, err := extraction.ExtractFromJson(source, jsonPath)\n\treturn val, err\n}\n\nvar xmlExtract = func(source interface{}, xPath string) (interface{}, error) {\n\tval, err := extraction.ExtractFromXml(source, xPath)\n\treturn val, err\n}\n\nvar htmlExtract = func(source interface{}, xPath string) (interface{}, error) {\n\tval, err := extraction.ExtractFromHtml(source, xPath)\n\treturn val, err\n}\n\nvar regexExtract = func(source interface{}, xPath string, matchNo int64) (interface{}, error) {\n\tval, err := extraction.ExtractWithRegex(source, types.RegexCaptureConf{\n\t\tExp: &xPath,\n\t\tNo:  int(matchNo),\n\t})\n\treturn val, err\n}\n\nvar equalsOnFile = func(source interface{}, filepath string) (bool, error) {\n\tfileBytes, err := os.ReadFile(filepath)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tif strings.HasSuffix(filepath, \".json\") {\n\t\tsourceType := reflect.ValueOf(source).Kind() // json extracted types may be map or slice etc\n\n\t\tif sourceType == reflect.String {\n\t\t\t// in case of direct body comparison, source param will be string\n\t\t\tvar src interface{}\n\t\t\terr := json.Unmarshal([]byte(source.(string)), &src)\n\t\t\tif err != nil {\n\t\t\t\treturn false, err\n\t\t\t}\n\n\t\t\tvar fileB interface{}\n\t\t\terr = json.Unmarshal(fileBytes, &fileB)\n\t\t\tif err != nil {\n\t\t\t\treturn false, err\n\t\t\t}\n\n\t\t\tif reflect.DeepEqual(src, fileB) {\n\t\t\t\treturn true, nil\n\t\t\t}\n\t\t}\n\n\t\tvar fs interface{}\n\t\tjson.Unmarshal(fileBytes, &fs)\n\t\tif reflect.DeepEqual(source, fs) {\n\t\t\treturn true, nil\n\t\t}\n\n\t\treturn false, nil\n\t}\n\n\tif fmt.Sprint(source) == string(fileBytes) {\n\t\treturn true, nil\n\t}\n\n\treturn false, nil\n}\n\nvar assertionFuncMap = map[string]struct{}{\n\tNOT:          {},\n\tLESSTHAN:     {},\n\tGREATERTHAN:  {},\n\tEQUALS:       {},\n\tEQUALSONFILE: {},\n\tIN:           {},\n\tJSONPATH:     {},\n\tXMLPATH:      {},\n\tHTMLPATH:     {},\n\tREGEXP:       {},\n\tEXISTS:       {},\n\tCONTAINS:     {},\n\tRANGE:        {},\n\tMIN:          {},\n\tMAX:          {},\n\tAVG:          {},\n\tP99:          {},\n\tP98:          {},\n\tP95:          {},\n\tP90:          {},\n\tP80:          {},\n\tTIME:         {},\n}\n\nconst (\n\tNOT          = \"not\"\n\tLESSTHAN     = \"less_than\"\n\tGREATERTHAN  = \"greater_than\"\n\tEQUALS       = \"equals\"\n\tIN           = \"in\"\n\tJSONPATH     = \"json_path\"\n\tXMLPATH      = \"xpath\"\n\tHTMLPATH     = \"html_path\"\n\tREGEXP       = \"regexp\"\n\tEXISTS       = \"exists\"\n\tCONTAINS     = \"contains\"\n\tRANGE        = \"range\"\n\tEQUALSONFILE = \"equals_on_file\"\n\tTIME         = \"time\"\n\n\tMIN = \"min\"\n\tMAX = \"max\"\n\tAVG = \"avg\"\n\tP99 = \"p99\"\n\tP98 = \"p98\"\n\tP95 = \"p95\"\n\tP90 = \"p90\"\n\tP80 = \"p80\"\n)\n"
  },
  {
    "path": "ddosify_engine/core/scenario/scripting/assertion/evaluator/function_test.go",
    "content": "package evaluator\n\nimport \"testing\"\n\nfunc TestEmptyArraysOnMinMaxAvgFuncs(t *testing.T) {\n\tempty := []int64{}\n\t_, err := min(empty)\n\tif err == nil {\n\t\tt.Errorf(\"expected error on empty array on min func\")\n\t}\n\n\t_, err = max(empty)\n\tif err == nil {\n\t\tt.Errorf(\"expected error on empty array on max func\")\n\t}\n\n\t_, err = avg(empty)\n\tif err == nil {\n\t\tt.Errorf(\"expected error on empty array on avg func\")\n\t}\n\n\t_, err = percentile(empty, 99)\n\tif err == nil {\n\t\tt.Errorf(\"expected error on empty array on percentile func\")\n\t}\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/scripting/assertion/lexer/lexer.go",
    "content": "package lexer\n\nimport (\n\t\"strings\"\n\n\t\"go.ddosify.com/ddosify/core/scenario/scripting/assertion/token\"\n)\n\ntype Lexer struct {\n\tinput        string\n\tposition     int  // current position in input (points to current char)\n\treadPosition int  // current reading position in input (after current char)\n\tch           byte // current char under examination\n}\n\nfunc New(input string) *Lexer {\n\tl := &Lexer{input: input}\n\tl.readChar()\n\treturn l\n}\n\nfunc (l *Lexer) NextToken() token.Token {\n\tvar tok token.Token\n\n\tl.skipWhitespace()\n\n\tswitch l.ch {\n\tcase '=':\n\t\tif l.peekChar() == '=' {\n\t\t\tch := l.ch\n\t\t\tl.readChar()\n\t\t\tliteral := string(ch) + string(l.ch)\n\t\t\ttok = token.Token{Type: token.EQ, Literal: literal}\n\t\t} else {\n\t\t\ttok = newToken(token.ILLEGAL, l.ch)\n\t\t}\n\tcase '&':\n\t\tif l.peekChar() == '&' {\n\t\t\tch := l.ch\n\t\t\tl.readChar()\n\t\t\tliteral := string(ch) + string(l.ch)\n\t\t\ttok = token.Token{Type: token.AND, Literal: literal}\n\t\t} else {\n\t\t\ttok = newToken(token.ILLEGAL, l.ch)\n\t\t}\n\tcase '|':\n\t\tif l.peekChar() == '|' {\n\t\t\tch := l.ch\n\t\t\tl.readChar()\n\t\t\tliteral := string(ch) + string(l.ch)\n\t\t\ttok = token.Token{Type: token.OR, Literal: literal}\n\t\t} else {\n\t\t\ttok = newToken(token.ILLEGAL, l.ch)\n\t\t}\n\tcase '+':\n\t\ttok = newToken(token.PLUS, l.ch)\n\tcase '-':\n\t\ttok = newToken(token.MINUS, l.ch)\n\tcase '!':\n\t\tif l.peekChar() == '=' {\n\t\t\tch := l.ch\n\t\t\tl.readChar()\n\t\t\tliteral := string(ch) + string(l.ch)\n\t\t\ttok = token.Token{Type: token.NOT_EQ, Literal: literal}\n\t\t} else {\n\t\t\ttok = newToken(token.BANG, l.ch)\n\t\t}\n\tcase '/':\n\t\ttok = newToken(token.SLASH, l.ch)\n\tcase '*':\n\t\ttok = newToken(token.ASTERISK, l.ch)\n\tcase '<':\n\t\ttok = newToken(token.LT, l.ch)\n\tcase '>':\n\t\ttok = newToken(token.GT, l.ch)\n\tcase ',':\n\t\ttok = newToken(token.COMMA, l.ch)\n\tcase '(':\n\t\ttok = newToken(token.LPAREN, l.ch)\n\tcase ')':\n\t\ttok = newToken(token.RPAREN, l.ch)\n\tcase '{':\n\t\ttok = newToken(token.LBRACE, l.ch)\n\tcase '}':\n\t\ttok = newToken(token.RBRACE, l.ch)\n\tcase '[':\n\t\ttok = newToken(token.LBRACKET, l.ch)\n\tcase ']':\n\t\ttok = newToken(token.RBRACKET, l.ch)\n\tcase ':':\n\t\ttok = newToken(token.COLON, l.ch)\n\tcase 0:\n\t\ttok.Literal = \"\"\n\t\ttok.Type = token.EOF\n\tdefault:\n\t\tif l.ch == 34 { // \"\n\t\t\tl.readChar()\n\t\t\ttok.Literal = l.readString()\n\t\t\ttok.Type = token.STRING\n\t\t\tl.readChar()\n\t\t\treturn tok\n\t\t}\n\t\tif l.ch == 39 { // '\n\t\t\tl.readChar()\n\t\t\ttok.Literal = l.readRawString()\n\t\t\ttok.Type = token.STRING\n\t\t\tl.readChar()\n\t\t\treturn tok\n\t\t}\n\t\tif isDigit(l.ch) { // number\n\t\t\ttok.Type = token.INT\n\t\t\ttok.Literal = l.readNumber()\n\t\t\tif strings.Contains(tok.Literal, \".\") {\n\t\t\t\tif strings.Count(tok.Literal, \".\") > 1 { // more than 1 . is illegal\n\t\t\t\t\ttok.Type = token.ILLEGAL\n\t\t\t\t}\n\t\t\t\ttok.Type = token.FLOAT\n\t\t\t} else {\n\t\t\t\ttok.Type = token.INT\n\t\t\t}\n\t\t\treturn tok\n\t\t} else if isLetter(l.ch) { // identifier\n\t\t\ttok.Literal = l.readIdentifier()\n\t\t\ttok.Type = token.LookupIdent(tok.Literal)\n\t\t\treturn tok\n\t\t} else {\n\t\t\ttok = newToken(token.ILLEGAL, l.ch)\n\t\t}\n\t}\n\n\tl.readChar()\n\treturn tok\n}\n\nfunc (l *Lexer) skipWhitespace() {\n\tfor l.ch == ' ' || l.ch == '\\t' || l.ch == '\\n' || l.ch == '\\r' {\n\t\tl.readChar()\n\t}\n}\n\nfunc (l *Lexer) readChar() {\n\tif l.readPosition >= len(l.input) {\n\t\tl.ch = 0 // ASCII code for NUL char\n\t} else {\n\t\tl.ch = l.input[l.readPosition]\n\t}\n\tl.position = l.readPosition\n\tl.readPosition += 1\n}\n\nfunc (l *Lexer) peekChar() byte {\n\tif l.readPosition >= len(l.input) {\n\t\treturn 0\n\t} else {\n\t\treturn l.input[l.readPosition]\n\t}\n}\n\nfunc (l *Lexer) readIdentifier() string {\n\tposition := l.position\n\tfor isChAllowedInIdent(l.ch) {\n\t\tl.readChar()\n\t}\n\treturn l.input[position:l.position]\n}\n\nfunc (l *Lexer) readString() string {\n\tposition := l.position\n\tfor l.ch != 34 { // \"\n\t\tl.readChar()\n\t}\n\treturn l.input[position:l.position]\n}\n\nfunc (l *Lexer) readRawString() string {\n\tposition := l.position\n\tfor l.ch != 39 { // '\n\t\tl.readChar()\n\t}\n\treturn l.input[position:l.position]\n}\n\nfunc (l *Lexer) readNumber() string {\n\tposition := l.position\n\tfor isDigit(l.ch) {\n\t\tl.readChar()\n\t}\n\treturn l.input[position:l.position]\n}\n\nfunc isChAllowedInIdent(ch byte) bool {\n\treturn 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' ||\n\t\tch == '.' || ch == '-' || '0' <= ch && ch <= '9'\n}\n\nfunc isLetter(ch byte) bool { // identifiers\n\treturn 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z'\n}\n\nfunc isDigit(ch byte) bool {\n\treturn '0' <= ch && ch <= '9' || ch == '.'\n}\n\nfunc newToken(tokenType token.TokenType, ch byte) token.Token {\n\treturn token.Token{Type: tokenType, Literal: string(ch)}\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/scripting/assertion/lexer/lexer_test.go",
    "content": "package lexer\n\nimport (\n\t\"testing\"\n\n\t\"go.ddosify.com/ddosify/core/scenario/scripting/assertion/token\"\n)\n\nfunc TestNextToken(t *testing.T) {\n\ttests := []struct {\n\t\tinput    string\n\t\texpected []struct {\n\t\t\texpectedType    token.TokenType\n\t\t\texpectedLiteral string\n\t\t}\n\t}{\n\t\t{\n\t\t\tinput: \"range(headers_content_length, 100, 300)\",\n\t\t\texpected: []struct {\n\t\t\t\texpectedType    token.TokenType\n\t\t\t\texpectedLiteral string\n\t\t\t}{\n\t\t\t\t{token.IDENT, \"range\"},\n\t\t\t\t{token.LPAREN, \"(\"},\n\t\t\t\t{token.IDENT, \"headers_content_length\"},\n\t\t\t\t{token.COMMA, \",\"},\n\t\t\t\t{token.INT, \"100\"},\n\t\t\t\t{token.COMMA, \",\"},\n\t\t\t\t{token.INT, \"300\"},\n\t\t\t\t{token.RPAREN, \")\"},\n\t\t\t\t{token.EOF, \"\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinput: \"in(status_code, [200, 201])\",\n\t\t\texpected: []struct {\n\t\t\t\texpectedType    token.TokenType\n\t\t\t\texpectedLiteral string\n\t\t\t}{\n\t\t\t\t{token.IDENT, \"in\"},\n\t\t\t\t{token.LPAREN, \"(\"},\n\t\t\t\t{token.IDENT, \"status_code\"},\n\t\t\t\t{token.COMMA, \",\"},\n\t\t\t\t{token.LBRACKET, \"[\"},\n\t\t\t\t{token.INT, \"200\"},\n\t\t\t\t{token.COMMA, \",\"},\n\t\t\t\t{token.INT, \"201\"},\n\t\t\t\t{token.RBRACKET, \"]\"},\n\t\t\t\t{token.RPAREN, \")\"},\n\t\t\t\t{token.EOF, \"\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinput: \"not(in(status_code, [200, 201]))\",\n\t\t\texpected: []struct {\n\t\t\t\texpectedType    token.TokenType\n\t\t\t\texpectedLiteral string\n\t\t\t}{\n\t\t\t\t{token.IDENT, \"not\"},\n\t\t\t\t{token.LPAREN, \"(\"},\n\t\t\t\t{token.IDENT, \"in\"},\n\t\t\t\t{token.LPAREN, \"(\"},\n\t\t\t\t{token.IDENT, \"status_code\"},\n\t\t\t\t{token.COMMA, \",\"},\n\t\t\t\t{token.LBRACKET, \"[\"},\n\t\t\t\t{token.INT, \"200\"},\n\t\t\t\t{token.COMMA, \",\"},\n\t\t\t\t{token.INT, \"201\"},\n\t\t\t\t{token.RBRACKET, \"]\"},\n\t\t\t\t{token.RPAREN, \")\"},\n\t\t\t\t{token.RPAREN, \")\"},\n\t\t\t\t{token.EOF, \"\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinput: \"in(headers_Content_Type, [\\\"application/json\\\", \\\"application/xml\\\"])\",\n\t\t\texpected: []struct {\n\t\t\t\texpectedType    token.TokenType\n\t\t\t\texpectedLiteral string\n\t\t\t}{\n\t\t\t\t{token.IDENT, \"in\"},\n\t\t\t\t{token.LPAREN, \"(\"},\n\t\t\t\t{token.IDENT, \"headers_Content_Type\"},\n\t\t\t\t{token.COMMA, \",\"},\n\t\t\t\t{token.LBRACKET, \"[\"},\n\t\t\t\t{token.STRING, \"application/json\"},\n\t\t\t\t{token.COMMA, \",\"},\n\t\t\t\t{token.STRING, \"application/xml\"},\n\t\t\t\t{token.RBRACKET, \"]\"},\n\t\t\t\t{token.RPAREN, \")\"},\n\t\t\t\t{token.EOF, \"\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinput: \"equals(body, '{\\\"c\\\": \\\"d\\\"}')\",\n\t\t\texpected: []struct {\n\t\t\t\texpectedType    token.TokenType\n\t\t\t\texpectedLiteral string\n\t\t\t}{\n\t\t\t\t{token.IDENT, \"equals\"},\n\t\t\t\t{token.LPAREN, \"(\"},\n\t\t\t\t{token.IDENT, \"body\"},\n\t\t\t\t{token.COMMA, \",\"},\n\t\t\t\t{token.STRING, \"{\\\"c\\\": \\\"d\\\"}\"},\n\t\t\t\t{token.RPAREN, \")\"},\n\t\t\t\t{token.EOF, \"\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinput: \"equals(json_path(employees.percentage), 32.3)\",\n\t\t\texpected: []struct {\n\t\t\t\texpectedType    token.TokenType\n\t\t\t\texpectedLiteral string\n\t\t\t}{\n\t\t\t\t{token.IDENT, \"equals\"},\n\t\t\t\t{token.LPAREN, \"(\"},\n\t\t\t\t{token.IDENT, \"json_path\"},\n\t\t\t\t{token.LPAREN, \"(\"},\n\t\t\t\t{token.IDENT, \"employees.percentage\"},\n\t\t\t\t{token.RPAREN, \")\"},\n\t\t\t\t{token.COMMA, \",\"},\n\t\t\t\t{token.FLOAT, \"32.3\"},\n\t\t\t\t{token.RPAREN, \")\"},\n\t\t\t\t{token.EOF, \"\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinput: \"2+5-3*10/2<>\",\n\t\t\texpected: []struct {\n\t\t\t\texpectedType    token.TokenType\n\t\t\t\texpectedLiteral string\n\t\t\t}{\n\t\t\t\t{token.INT, \"2\"},\n\t\t\t\t{token.PLUS, \"+\"},\n\t\t\t\t{token.INT, \"5\"},\n\t\t\t\t{token.MINUS, \"-\"},\n\t\t\t\t{token.INT, \"3\"},\n\t\t\t\t{token.ASTERISK, \"*\"},\n\t\t\t\t{token.INT, \"10\"},\n\t\t\t\t{token.SLASH, \"/\"},\n\t\t\t\t{token.INT, \"2\"},\n\t\t\t\t{token.LT, \"<\"},\n\t\t\t\t{token.GT, \">\"},\n\t\t\t\t{token.EOF, \"\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinput: \"response_size == 234\",\n\t\t\texpected: []struct {\n\t\t\t\texpectedType    token.TokenType\n\t\t\t\texpectedLiteral string\n\t\t\t}{\n\t\t\t\t{token.IDENT, \"response_size\"},\n\t\t\t\t{token.EQ, \"==\"},\n\t\t\t\t{token.INT, \"234\"},\n\t\t\t\t{token.EOF, \"\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinput: \"response_size != 234\",\n\t\t\texpected: []struct {\n\t\t\t\texpectedType    token.TokenType\n\t\t\t\texpectedLiteral string\n\t\t\t}{\n\t\t\t\t{token.IDENT, \"response_size\"},\n\t\t\t\t{token.NOT_EQ, \"!=\"},\n\t\t\t\t{token.INT, \"234\"},\n\t\t\t\t{token.EOF, \"\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinput: \"!exists(headers.referrer)\",\n\t\t\texpected: []struct {\n\t\t\t\texpectedType    token.TokenType\n\t\t\t\texpectedLiteral string\n\t\t\t}{\n\t\t\t\t{token.BANG, \"!\"},\n\t\t\t\t{token.IDENT, \"exists\"},\n\t\t\t\t{token.LPAREN, \"(\"},\n\t\t\t\t{token.IDENT, \"headers.referrer\"},\n\t\t\t\t{token.RPAREN, \")\"},\n\t\t\t\t{token.EOF, \"\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinput: \"a = 5\",\n\t\t\texpected: []struct {\n\t\t\t\texpectedType    token.TokenType\n\t\t\t\texpectedLiteral string\n\t\t\t}{\n\t\t\t\t{token.IDENT, \"a\"},\n\t\t\t\t{token.ILLEGAL, \"=\"},\n\t\t\t\t{token.INT, \"5\"},\n\t\t\t\t{token.EOF, \"\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinput: \"60.1 $ 60.1\",\n\t\t\texpected: []struct {\n\t\t\t\texpectedType    token.TokenType\n\t\t\t\texpectedLiteral string\n\t\t\t}{\n\t\t\t\t{token.FLOAT, \"60.1\"},\n\t\t\t\t{token.ILLEGAL, \"$\"},\n\t\t\t\t{token.FLOAT, \"60.1\"},\n\t\t\t\t{token.EOF, \"\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinput: \"%\",\n\t\t\texpected: []struct {\n\t\t\t\texpectedType    token.TokenType\n\t\t\t\texpectedLiteral string\n\t\t\t}{\n\t\t\t\t{token.ILLEGAL, \"%\"},\n\t\t\t\t{token.EOF, \"\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinput: \"a =\",\n\t\t\texpected: []struct {\n\t\t\t\texpectedType    token.TokenType\n\t\t\t\texpectedLiteral string\n\t\t\t}{\n\t\t\t\t{token.IDENT, \"a\"},\n\t\t\t\t{token.ILLEGAL, \"=\"},\n\t\t\t\t{token.EOF, \"\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinput: \"not(true) && not(false)\",\n\t\t\texpected: []struct {\n\t\t\t\texpectedType    token.TokenType\n\t\t\t\texpectedLiteral string\n\t\t\t}{\n\t\t\t\t{token.IDENT, \"not\"},\n\t\t\t\t{token.LPAREN, \"(\"},\n\t\t\t\t{token.TRUE, \"true\"},\n\t\t\t\t{token.RPAREN, \")\"},\n\t\t\t\t{token.AND, \"&&\"},\n\t\t\t\t{token.IDENT, \"not\"},\n\t\t\t\t{token.LPAREN, \"(\"},\n\t\t\t\t{token.FALSE, \"false\"},\n\t\t\t\t{token.RPAREN, \")\"},\n\t\t\t\t{token.EOF, \"\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinput: \"equals(status_code,200) || equals(status_code,201)\",\n\t\t\texpected: []struct {\n\t\t\t\texpectedType    token.TokenType\n\t\t\t\texpectedLiteral string\n\t\t\t}{\n\t\t\t\t{token.IDENT, \"equals\"},\n\t\t\t\t{token.LPAREN, \"(\"},\n\t\t\t\t{token.IDENT, \"status_code\"},\n\t\t\t\t{token.COMMA, \",\"},\n\t\t\t\t{token.INT, \"200\"},\n\t\t\t\t{token.RPAREN, \")\"},\n\t\t\t\t{token.OR, \"||\"},\n\t\t\t\t{token.IDENT, \"equals\"},\n\t\t\t\t{token.LPAREN, \"(\"},\n\t\t\t\t{token.IDENT, \"status_code\"},\n\t\t\t\t{token.COMMA, \",\"},\n\t\t\t\t{token.INT, \"201\"},\n\t\t\t\t{token.RPAREN, \")\"},\n\t\t\t\t{token.EOF, \"\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinput: \"equals(json_path(),null)\",\n\t\t\texpected: []struct {\n\t\t\t\texpectedType    token.TokenType\n\t\t\t\texpectedLiteral string\n\t\t\t}{\n\t\t\t\t{token.IDENT, \"equals\"},\n\t\t\t\t{token.LPAREN, \"(\"},\n\t\t\t\t{token.IDENT, \"json_path\"},\n\t\t\t\t{token.LPAREN, \"(\"},\n\t\t\t\t{token.RPAREN, \")\"},\n\t\t\t\t{token.COMMA, \",\"},\n\t\t\t\t{token.NULL, \"null\"},\n\t\t\t\t{token.RPAREN, \")\"},\n\t\t\t\t{token.EOF, \"\"},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tl := New(tt.input)\n\n\t\tvar tok token.Token\n\t\ti := 0\n\t\tfor tok = l.NextToken(); tok.Type != token.EOF; tok = l.NextToken() {\n\t\t\tif tok.Type != tt.expected[i].expectedType {\n\t\t\t\tt.Fatalf(\"tests[%d] - tokentype wrong. expected=%q, got=%q\",\n\t\t\t\t\ti, tt.expected[i].expectedType, tok.Type)\n\t\t\t}\n\n\t\t\tif tok.Literal != tt.expected[i].expectedLiteral {\n\t\t\t\tt.Fatalf(\"tests[%d] - literal wrong. expected=%q, got=%q\",\n\t\t\t\t\ti, tt.expected[i].expectedLiteral, tok.Literal)\n\t\t\t}\n\t\t\ti++\n\t\t}\n\n\t}\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/scripting/assertion/parser/parser.go",
    "content": "package parser\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"go.ddosify.com/ddosify/core/scenario/scripting/assertion/ast\"\n\t\"go.ddosify.com/ddosify/core/scenario/scripting/assertion/lexer\"\n\t\"go.ddosify.com/ddosify/core/scenario/scripting/assertion/token\"\n)\n\n// precedences\nconst (\n\t_ int = iota\n\tLOWEST\n\tANDOR        // && ||\n\tEQUALS       // ==\n\tLESSGREATER  // > or <\n\tSUM          // +\n\tPRODUCT      // *\n\tPREFIX       // -X or !X\n\tARRAYDEFINE  // []\n\tOBJECTDEFINE // {}\n\tCALL         // myFunction(X)\n)\n\nvar precedences = map[token.TokenType]int{\n\ttoken.EQ:       EQUALS,\n\ttoken.NOT_EQ:   EQUALS,\n\ttoken.LT:       LESSGREATER,\n\ttoken.GT:       LESSGREATER,\n\ttoken.PLUS:     SUM,\n\ttoken.MINUS:    SUM,\n\ttoken.SLASH:    PRODUCT,\n\ttoken.ASTERISK: PRODUCT,\n\ttoken.LBRACKET: ARRAYDEFINE,\n\ttoken.LBRACE:   OBJECTDEFINE,\n\ttoken.LPAREN:   CALL,\n\ttoken.AND:      ANDOR,\n\ttoken.OR:       ANDOR,\n}\n\ntype (\n\tprefixParseFn func() ast.Expression\n\tinfixParseFn  func(ast.Expression) ast.Expression\n)\n\ntype Parser struct {\n\tl      *lexer.Lexer\n\terrors []string\n\n\tcurToken  token.Token\n\tpeekToken token.Token\n\n\tprefixParseFns map[token.TokenType]prefixParseFn\n\tinfixParseFns  map[token.TokenType]infixParseFn\n}\n\nfunc New(l *lexer.Lexer) *Parser {\n\tp := &Parser{\n\t\tl:      l,\n\t\terrors: []string{},\n\t}\n\n\tp.prefixParseFns = make(map[token.TokenType]prefixParseFn)\n\tp.prefixParseFns[token.IDENT] = p.parseIdentifier\n\tp.prefixParseFns[token.INT] = p.parseIntegerLiteral\n\tp.prefixParseFns[token.FLOAT] = p.parseFloatLiteral\n\tp.prefixParseFns[token.STRING] = p.parseStringLiteral\n\tp.prefixParseFns[token.BANG] = p.parsePrefixExpression\n\tp.prefixParseFns[token.MINUS] = p.parsePrefixExpression\n\tp.prefixParseFns[token.TRUE] = p.parseBoolean\n\tp.prefixParseFns[token.FALSE] = p.parseBoolean\n\tp.prefixParseFns[token.NULL] = p.parseNull\n\tp.prefixParseFns[token.LPAREN] = p.parseGroupedExpression\n\tp.prefixParseFns[token.LBRACKET] = p.parseArrayLiteral\n\tp.prefixParseFns[token.LBRACE] = p.parseObjectLiteral\n\n\tp.infixParseFns = make(map[token.TokenType]infixParseFn)\n\tp.infixParseFns[token.PLUS] = p.parseInfixExpression\n\tp.infixParseFns[token.MINUS] = p.parseInfixExpression\n\tp.infixParseFns[token.SLASH] = p.parseInfixExpression\n\tp.infixParseFns[token.ASTERISK] = p.parseInfixExpression\n\tp.infixParseFns[token.EQ] = p.parseInfixExpression\n\tp.infixParseFns[token.NOT_EQ] = p.parseInfixExpression\n\tp.infixParseFns[token.LT] = p.parseInfixExpression\n\tp.infixParseFns[token.GT] = p.parseInfixExpression\n\tp.infixParseFns[token.AND] = p.parseInfixExpression\n\tp.infixParseFns[token.OR] = p.parseInfixExpression\n\tp.infixParseFns[token.LPAREN] = p.parseCallExpression\n\n\tp.nextToken()\n\tp.nextToken()\n\n\treturn p\n}\n\nfunc (p *Parser) nextToken() {\n\tp.curToken = p.peekToken\n\tp.peekToken = p.l.NextToken()\n}\n\nfunc (p *Parser) curTokenIs(t token.TokenType) bool {\n\treturn p.curToken.Type == t\n}\n\nfunc (p *Parser) peekTokenIs(t token.TokenType) bool {\n\treturn p.peekToken.Type == t\n}\n\nfunc (p *Parser) expectPeek(t token.TokenType) bool {\n\tif p.peekTokenIs(t) {\n\t\tp.nextToken()\n\t\treturn true\n\t} else {\n\t\tp.peekError(t)\n\t\treturn false\n\t}\n}\n\nfunc (p *Parser) Errors() []string {\n\treturn p.errors\n}\n\nfunc (p *Parser) peekError(t token.TokenType) {\n\tmsg := fmt.Sprintf(\"expected next token to be %s, got %s instead\",\n\t\tt, p.peekToken.Type)\n\tp.errors = append(p.errors, msg)\n}\n\nfunc (p *Parser) noPrefixParseFnError(t token.TokenType) {\n\tmsg := fmt.Sprintf(\"no prefix parse function for %s found\", t)\n\tp.errors = append(p.errors, msg)\n}\n\nfunc (p *Parser) ParseExpressionStatement() *ast.ExpressionStatement {\n\tstmt := &ast.ExpressionStatement{Token: p.curToken}\n\n\tstmt.Expression = p.parseExpression(LOWEST)\n\n\treturn stmt\n}\n\nfunc (p *Parser) parseExpression(precedence int) ast.Expression {\n\tprefix := p.prefixParseFns[p.curToken.Type]\n\tif prefix == nil {\n\t\tp.noPrefixParseFnError(p.curToken.Type)\n\t\treturn nil\n\t}\n\tleftExp := prefix()\n\n\tif p.peekToken.Type == token.ILLEGAL {\n\t\tp.errors = append(p.errors, fmt.Sprintf(\"%s character is illegal\", p.peekToken.Literal))\n\t}\n\t// right side suck in, already parsed piece becomes leftExp for infix op\n\tfor precedence < p.peekPrecedence() {\n\t\tinfix := p.infixParseFns[p.peekToken.Type]\n\t\tif infix == nil {\n\t\t\treturn leftExp\n\t\t}\n\t\t// suck in the left part that has been parsed so far\n\t\tp.nextToken()\n\n\t\tleftExp = infix(leftExp)\n\t}\n\n\treturn leftExp\n}\n\nfunc (p *Parser) peekPrecedence() int {\n\tif pre, ok := precedences[p.peekToken.Type]; ok {\n\t\treturn pre\n\t}\n\n\treturn LOWEST\n}\n\nfunc (p *Parser) curPrecedence() int {\n\tif p, ok := precedences[p.curToken.Type]; ok {\n\t\treturn p\n\t}\n\n\treturn LOWEST\n}\n\nfunc (p *Parser) parseIdentifier() ast.Expression {\n\treturn &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}\n}\n\nfunc (p *Parser) parseIntegerLiteral() ast.Expression {\n\tlit := &ast.IntegerLiteral{Token: p.curToken}\n\n\tvalue, err := strconv.ParseInt(p.curToken.Literal, 0, 64)\n\tif err != nil {\n\t\tmsg := fmt.Sprintf(\"could not parse %q as integer\", p.curToken.Literal)\n\t\tp.errors = append(p.errors, msg)\n\t\treturn nil\n\t}\n\n\tlit.Value = value\n\n\treturn lit\n}\n\nfunc (p *Parser) parseFloatLiteral() ast.Expression {\n\tlit := &ast.FloatLiteral{Token: p.curToken}\n\n\tvalue, err := strconv.ParseFloat(p.curToken.Literal, 64)\n\tif err != nil {\n\t\tmsg := fmt.Sprintf(\"could not parse %q as integer\", p.curToken.Literal)\n\t\tp.errors = append(p.errors, msg)\n\t\treturn nil\n\t}\n\n\tlit.Value = value\n\treturn lit\n}\n\nfunc (p *Parser) parseStringLiteral() ast.Expression {\n\tlit := &ast.StringLiteral{Token: p.curToken}\n\tlit.Value = p.curToken.Literal\n\n\treturn lit\n}\n\nfunc (p *Parser) parsePrefixExpression() ast.Expression {\n\texpression := &ast.PrefixExpression{\n\t\tToken:    p.curToken,\n\t\tOperator: p.curToken.Literal,\n\t}\n\n\tp.nextToken()\n\n\texpression.Right = p.parseExpression(PREFIX)\n\n\treturn expression\n}\n\nfunc (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression {\n\texpression := &ast.InfixExpression{\n\t\tToken:    p.curToken,\n\t\tOperator: p.curToken.Literal,\n\t\tLeft:     left,\n\t}\n\n\tprecedence := p.curPrecedence()\n\tp.nextToken()\n\texpression.Right = p.parseExpression(precedence)\n\n\treturn expression\n}\n\nfunc (p *Parser) parseBoolean() ast.Expression {\n\treturn &ast.Boolean{Token: p.curToken, Value: p.curTokenIs(token.TRUE)}\n}\n\nfunc (p *Parser) parseNull() ast.Expression {\n\treturn &ast.NullLiteral{Token: p.curToken, Value: nil}\n}\n\nfunc (p *Parser) parseGroupedExpression() ast.Expression {\n\tp.nextToken()\n\n\texp := p.parseExpression(LOWEST)\n\n\tif !p.expectPeek(token.RPAREN) {\n\t\treturn nil\n\t}\n\n\treturn exp\n}\n\nfunc (p *Parser) parseObjectLiteral() ast.Expression {\n\tlit := &ast.ObjectLiteral{Token: p.curToken}\n\tlit.Elems = p.parseObjectElements()\n\n\treturn lit\n}\n\nfunc (p *Parser) parseArrayLiteral() ast.Expression {\n\tlit := &ast.ArrayLiteral{Token: p.curToken}\n\tlit.Elems = p.parseArrayElements()\n\n\treturn lit\n}\n\nfunc (p *Parser) parseCallExpression(function ast.Expression) ast.Expression {\n\texp := &ast.CallExpression{Token: p.curToken, Function: function}\n\texp.Arguments = p.parseCallArguments()\n\treturn exp\n}\n\nfunc (p *Parser) parseCallArguments() []ast.Expression {\n\targs := []ast.Expression{}\n\n\tif p.peekTokenIs(token.RPAREN) {\n\t\tp.nextToken()\n\t\treturn args\n\t}\n\n\tp.nextToken()\n\targs = append(args, p.parseExpression(LOWEST))\n\n\tfor p.peekTokenIs(token.COMMA) {\n\t\tp.nextToken()\n\t\tp.nextToken()\n\t\targs = append(args, p.parseExpression(LOWEST))\n\t}\n\n\tif !p.expectPeek(token.RPAREN) {\n\t\treturn nil\n\t}\n\n\treturn args\n}\n\nfunc (p *Parser) parseArrayElements() []ast.Expression {\n\telems := []ast.Expression{}\n\n\tif p.peekTokenIs(token.RBRACKET) {\n\t\tp.nextToken()\n\t\treturn elems\n\t}\n\n\tp.nextToken()\n\telems = append(elems, p.parseExpression(LOWEST))\n\n\tfor p.peekTokenIs(token.COMMA) {\n\t\tp.nextToken()\n\t\tp.nextToken()\n\t\telems = append(elems, p.parseExpression(LOWEST))\n\t}\n\n\tif !p.expectPeek(token.RBRACKET) {\n\t\treturn nil\n\t}\n\n\treturn elems\n}\n\nfunc (p *Parser) parseObjectElements() map[string]ast.Expression {\n\telems := make(map[string]ast.Expression)\n\n\tif p.peekTokenIs(token.RBRACE) {\n\t\tp.nextToken()\n\t\treturn elems\n\t}\n\n\tp.nextToken()\n\tkey := p.parseExpression(LOWEST)\n\tif !p.expectPeek(token.COLON) {\n\t\treturn nil\n\t}\n\n\tp.nextToken()\n\tvalue := p.parseExpression(LOWEST)\n\telems[key.String()] = value\n\n\tfor p.peekTokenIs(token.COMMA) {\n\t\tp.nextToken()\n\t\tp.nextToken()\n\t\tkey := p.parseExpression(LOWEST)\n\t\tif !p.expectPeek(token.COLON) {\n\t\t\treturn nil\n\t\t}\n\n\t\tp.nextToken()\n\t\tvalue := p.parseExpression(LOWEST)\n\t\telems[key.String()] = value\n\t}\n\n\tif !p.expectPeek(token.RBRACE) {\n\t\treturn nil\n\t}\n\n\treturn elems\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/scripting/assertion/parser/parser_test.go",
    "content": "package parser\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"go.ddosify.com/ddosify/core/scenario/scripting/assertion/ast\"\n\t\"go.ddosify.com/ddosify/core/scenario/scripting/assertion/lexer\"\n)\n\nfunc TestIdentifierExpression(t *testing.T) {\n\tinput := \"foobar\"\n\n\tl := lexer.New(input)\n\tp := New(l)\n\texpressionStmt := p.ParseExpressionStatement()\n\tcheckParserErrors(t, p)\n\n\tident, ok := expressionStmt.Expression.(*ast.Identifier)\n\tif !ok {\n\t\tt.Fatalf(\"exp not *ast.Identifier. got=%T\", expressionStmt.Expression)\n\t}\n\tif ident.Value != \"foobar\" {\n\t\tt.Errorf(\"ident.Value not %s. got=%s\", \"foobar\", ident.Value)\n\t}\n\tif ident.TokenLiteral() != \"foobar\" {\n\t\tt.Errorf(\"ident.TokenLiteral not %s. got=%s\", \"foobar\",\n\t\t\tident.TokenLiteral())\n\t}\n}\n\nfunc TestIntegerLiteralExpression(t *testing.T) {\n\tinput := \"5\"\n\n\tl := lexer.New(input)\n\tp := New(l)\n\texpressionStmt := p.ParseExpressionStatement()\n\tcheckParserErrors(t, p)\n\n\tliteral, ok := expressionStmt.Expression.(*ast.IntegerLiteral)\n\tif !ok {\n\t\tt.Fatalf(\"exp not *ast.IntegerLiteral. got=%T\", expressionStmt.Expression)\n\t}\n\tif literal.Value != 5 {\n\t\tt.Errorf(\"literal.Value not %d. got=%d\", 5, literal.Value)\n\t}\n}\nfunc TestArrayLiteralExpression(t *testing.T) {\n\tinput := \"[x,10,\\\"xyz\\\",[243,55]]\"\n\n\tl := lexer.New(input)\n\tp := New(l)\n\texpressionStmt := p.ParseExpressionStatement()\n\tcheckParserErrors(t, p)\n\n\tliteral, ok := expressionStmt.Expression.(*ast.ArrayLiteral)\n\tif !ok {\n\t\tt.Fatalf(\"exp not *ast.ArrayLiteral. got=%T\", expressionStmt.Expression)\n\t}\n\n\tif len(literal.Elems) != 4 {\n\t\tt.Errorf(\"len(literal.Elems) not %d. got=%d\", 4, len(literal.Elems))\n\t}\n\n\t// identifier\n\tif literal.Elems[0].TokenLiteral() != \"x\" {\n\t\tt.Errorf(\"literal.TokenLiteral[0] not %s. got=%s\", \"x\",\n\t\t\tliteral.Elems[0].TokenLiteral())\n\t}\n\n\t// integerLiteral\n\tif literal.Elems[1].(interface{ GetVal() interface{} }).GetVal() != int64(10) {\n\t\tt.Errorf(\"literal.TokenLiteral not %s. got=%s\", \"5\",\n\t\t\tliteral.TokenLiteral())\n\t}\n\t// stringLiteral\n\tif literal.Elems[2].(interface{ GetVal() interface{} }).GetVal() != \"xyz\" {\n\t\tt.Errorf(\"literal.TokenLiteral not %s. got=%s\", \"5\",\n\t\t\tliteral.TokenLiteral())\n\t}\n\n\t// arrayLiteral\n\tif literal.Elems[3].(*ast.ArrayLiteral).Elems[0].(interface{ GetVal() interface{} }).GetVal() != int64(243) {\n\t\tt.Errorf(\"literal.TokenLiteral not %s. got=%s\", \"5\",\n\t\t\tliteral.TokenLiteral())\n\t}\n\tif literal.Elems[3].(*ast.ArrayLiteral).Elems[1].(interface{ GetVal() interface{} }).GetVal() != int64(55) {\n\t\tt.Errorf(\"literal.TokenLiteral not %s. got=%s\", \"5\",\n\t\t\tliteral.TokenLiteral())\n\t}\n}\n\nfunc TestObjectLiteralExpression(t *testing.T) {\n\t// input := \"[x,10,\\\"xyz\\\",[243,55]]\"\n\tinput := `{\"name\":\"John\", \"age\":30, \"cars\":[ \"Honda\", \"Alfa\", \"Opel\" ]}`\n\n\tl := lexer.New(input)\n\tp := New(l)\n\texpressionStmt := p.ParseExpressionStatement()\n\tcheckParserErrors(t, p)\n\n\tliteral, ok := expressionStmt.Expression.(*ast.ObjectLiteral)\n\tif !ok {\n\t\tt.Fatalf(\"exp not *ast.ObjectLiteral. got=%T\", expressionStmt.Expression)\n\t}\n\n\tif len(literal.Elems) != 3 {\n\t\tt.Errorf(\"len(literal.Elems) not %d. got=%d\", 4, len(literal.Elems))\n\t}\n\n\t// identifier\n\tif literal.Elems[\"name\"].TokenLiteral() != \"John\" {\n\t\tt.Errorf(\"literal.TokenLiteral[name] not %s. got=%s\", \"x\",\n\t\t\tliteral.Elems[\"name\"].TokenLiteral())\n\t}\n\n\tif literal.Elems[\"age\"].TokenLiteral() != \"30\" {\n\t\tt.Errorf(\"literal.TokenLiteral[age] not %s. got=%s\", \"x\",\n\t\t\tliteral.Elems[\"age\"].TokenLiteral())\n\t}\n\tarray := literal.Elems[\"cars\"].(*ast.ArrayLiteral)\n\tif array.Elems[0].String() != \"Honda\" {\n\t\tt.Errorf(\"literal.TokenLiteral[0] not %s. got=%s\", \"x\",\n\t\t\tarray.Elems[0].TokenLiteral())\n\t}\n\tif array.Elems[1].String() != \"Alfa\" {\n\t\tt.Errorf(\"literal.TokenLiteral[age] not %s. got=%s\", \"x\",\n\t\t\tarray.Elems[1].TokenLiteral())\n\t}\n\tif array.Elems[2].String() != \"Opel\" {\n\t\tt.Errorf(\"literal.TokenLiteral[age] not %s. got=%s\", \"x\",\n\t\t\tarray.Elems[2].TokenLiteral())\n\t}\n}\n\nfunc TestFloatLiteralExpression(t *testing.T) {\n\tinput := \"5.2\"\n\n\tl := lexer.New(input)\n\tp := New(l)\n\texpressionStmt := p.ParseExpressionStatement()\n\tcheckParserErrors(t, p)\n\n\tliteral, ok := expressionStmt.Expression.(*ast.FloatLiteral)\n\tif !ok {\n\t\tt.Fatalf(\"exp not *ast.IntegerLiteral. got=%T\", expressionStmt.Expression)\n\t}\n\tif literal.Value != 5.2 {\n\t\tt.Errorf(\"literal.Value not %f. got=%f\", 5.2, literal.Value)\n\t}\n}\n\nfunc TestExpectPeek(t *testing.T) {\n\tinput := \"[22,)\"\n\n\tl := lexer.New(input)\n\tp := New(l)\n\t_ = p.ParseExpressionStatement()\n\n\terrors := p.Errors()\n\tif len(errors) == 0 {\n\t\tt.Error(\"parser should have given error\")\n\t}\n}\n\nfunc TestParsingPrefixExpressions(t *testing.T) {\n\tprefixTests := []struct {\n\t\tinput    string\n\t\toperator string\n\t\tvalue    interface{}\n\t}{\n\t\t{\"!5\", \"!\", 5},\n\t\t{\"-15\", \"-\", 15},\n\t\t{\"!foobar\", \"!\", \"foobar\"},\n\t\t{\"-foobar\", \"-\", \"foobar\"},\n\t\t{\"!true\", \"!\", true},\n\t\t{\"!false\", \"!\", false},\n\t}\n\n\tfor _, tt := range prefixTests {\n\t\tl := lexer.New(tt.input)\n\t\tp := New(l)\n\t\texpressionStmt := p.ParseExpressionStatement()\n\t\tcheckParserErrors(t, p)\n\n\t\texp, ok := expressionStmt.Expression.(*ast.PrefixExpression)\n\t\tif !ok {\n\t\t\tt.Fatalf(\"stmt is not ast.PrefixExpression. got=%T\", expressionStmt.Expression)\n\t\t}\n\t\tif exp.Operator != tt.operator {\n\t\t\tt.Fatalf(\"exp.Operator is not '%s'. got=%s\",\n\t\t\t\ttt.operator, exp.Operator)\n\t\t}\n\t\tif !testLiteralExpression(t, exp.Right, tt.value) {\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc TestParsingInfixExpressions(t *testing.T) {\n\tinfixTests := []struct {\n\t\tinput      string\n\t\tleftValue  interface{}\n\t\toperator   string\n\t\trightValue interface{}\n\t}{\n\t\t{\"5 + 5\", 5, \"+\", 5},\n\t\t{\"5 - 5\", 5, \"-\", 5},\n\t\t{\"5 * 5\", 5, \"*\", 5},\n\t\t{\"5 / 5\", 5, \"/\", 5},\n\t\t{\"5 > 5\", 5, \">\", 5},\n\t\t{\"5 < 5\", 5, \"<\", 5},\n\t\t{\"5 == 5\", 5, \"==\", 5},\n\t\t{\"5 != 5\", 5, \"!=\", 5},\n\t\t{\"foobar + barfoo\", \"foobar\", \"+\", \"barfoo\"},\n\t\t{\"foobar - barfoo\", \"foobar\", \"-\", \"barfoo\"},\n\t\t{\"foobar * barfoo\", \"foobar\", \"*\", \"barfoo\"},\n\t\t{\"foobar / barfoo\", \"foobar\", \"/\", \"barfoo\"},\n\t\t{\"foobar > barfoo\", \"foobar\", \">\", \"barfoo\"},\n\t\t{\"foobar < barfoo\", \"foobar\", \"<\", \"barfoo\"},\n\t\t{\"foobar == barfoo\", \"foobar\", \"==\", \"barfoo\"},\n\t\t{\"foobar != barfoo\", \"foobar\", \"!=\", \"barfoo\"},\n\t\t{\"true == true\", true, \"==\", true},\n\t\t{\"true != false\", true, \"!=\", false},\n\t\t{\"false == false\", false, \"==\", false},\n\t\t{\"true && false\", true, \"&&\", false},\n\t\t{\"true || false\", true, \"||\", false},\n\t}\n\n\tfor _, tt := range infixTests {\n\t\tl := lexer.New(tt.input)\n\t\tp := New(l)\n\t\texpressionStmt := p.ParseExpressionStatement()\n\t\tcheckParserErrors(t, p)\n\n\t\tif !testInfixExpression(t, expressionStmt.Expression, tt.leftValue,\n\t\t\ttt.operator, tt.rightValue) {\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc TestOperatorPrecedenceParsing(t *testing.T) {\n\ttests := []struct {\n\t\tinput    string\n\t\texpected string\n\t}{\n\t\t{\n\t\t\t\"-a * b\",\n\t\t\t\"((-a) * b)\",\n\t\t},\n\t\t{\n\t\t\t\"!-a\",\n\t\t\t\"(!(-a))\",\n\t\t},\n\t\t{\n\t\t\t\"a + b + c\",\n\t\t\t\"((a + b) + c)\",\n\t\t},\n\t\t{\n\t\t\t\"a + b - c\",\n\t\t\t\"((a + b) - c)\",\n\t\t},\n\t\t{\n\t\t\t\"a * b * c\",\n\t\t\t\"((a * b) * c)\",\n\t\t},\n\t\t{\n\t\t\t\"a * b / c\",\n\t\t\t\"((a * b) / c)\",\n\t\t},\n\t\t{\n\t\t\t\"a + b / c\",\n\t\t\t\"(a + (b / c))\",\n\t\t},\n\t\t{\n\t\t\t\"a + b * c + d / e - f\",\n\t\t\t\"(((a + (b * c)) + (d / e)) - f)\",\n\t\t},\n\t\t{\n\t\t\t\"5 > 4 == 3 < 4\",\n\t\t\t\"((5 > 4) == (3 < 4))\",\n\t\t},\n\t\t{\n\t\t\t\"5 < 4 != 3 > 4\",\n\t\t\t\"((5 < 4) != (3 > 4))\",\n\t\t},\n\t\t{\n\t\t\t\"3 + 4 * 5 == 3 * 1 + 4 * 5\",\n\t\t\t\"((3 + (4 * 5)) == ((3 * 1) + (4 * 5)))\",\n\t\t},\n\t\t{\n\t\t\t\"true\",\n\t\t\t\"true\",\n\t\t},\n\t\t{\n\t\t\t\"false\",\n\t\t\t\"false\",\n\t\t},\n\t\t{\n\t\t\t\"3 > 5 == false\",\n\t\t\t\"((3 > 5) == false)\",\n\t\t},\n\t\t{\n\t\t\t\"3 < 5 == true\",\n\t\t\t\"((3 < 5) == true)\",\n\t\t},\n\t\t{\n\t\t\t\"1 + (2 + 3) + 4\",\n\t\t\t\"((1 + (2 + 3)) + 4)\",\n\t\t},\n\t\t{\n\t\t\t\"(5 + 5) * 2\",\n\t\t\t\"((5 + 5) * 2)\",\n\t\t},\n\t\t{\n\t\t\t\"2 / (5 + 5)\",\n\t\t\t\"(2 / (5 + 5))\",\n\t\t},\n\t\t{\n\t\t\t\"(5 + 5) * 2 * (5 + 5)\",\n\t\t\t\"(((5 + 5) * 2) * (5 + 5))\",\n\t\t},\n\t\t{\n\t\t\t\"-(5 + 5)\",\n\t\t\t\"(-(5 + 5))\",\n\t\t},\n\t\t{\n\t\t\t\"!(true == true)\",\n\t\t\t\"(!(true == true))\",\n\t\t},\n\t\t{\n\t\t\t\"a + add(b * c) + d\",\n\t\t\t\"((a + add((b * c))) + d)\",\n\t\t},\n\t\t{\n\t\t\t\"add(a, b, 1, 2 * 3, 4 + 5, add(6, 7 * 8))\",\n\t\t\t\"add(a,b,1,(2 * 3),(4 + 5),add(6,(7 * 8)))\",\n\t\t},\n\t\t{\n\t\t\t\"add(a + b + c * d / f + g)\",\n\t\t\t\"add((((a + b) + ((c * d) / f)) + g))\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tl := lexer.New(tt.input)\n\t\tp := New(l)\n\t\texpressionStmt := p.ParseExpressionStatement()\n\t\tcheckParserErrors(t, p)\n\n\t\tactual := expressionStmt.String()\n\t\tif actual != tt.expected {\n\t\t\tt.Errorf(\"expected=%q, got=%q\", tt.expected, actual)\n\t\t}\n\t}\n}\n\nfunc TestBooleanExpression(t *testing.T) {\n\ttests := []struct {\n\t\tinput           string\n\t\texpectedBoolean bool\n\t}{\n\t\t{\"true\", true},\n\t\t{\"false\", false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tl := lexer.New(tt.input)\n\t\tp := New(l)\n\t\texpressionStmt := p.ParseExpressionStatement()\n\t\tcheckParserErrors(t, p)\n\n\t\tboolean, ok := expressionStmt.Expression.(*ast.Boolean)\n\t\tif !ok {\n\t\t\tt.Fatalf(\"exp not *ast.Boolean. got=%T\", expressionStmt.Expression)\n\t\t}\n\t\tif boolean.Value != tt.expectedBoolean {\n\t\t\tt.Errorf(\"boolean.Value not %t. got=%t\", tt.expectedBoolean,\n\t\t\t\tboolean.Value)\n\t\t}\n\t}\n}\n\nfunc TestCallExpressionParsing(t *testing.T) {\n\tinput := \"add(1, 2 * 3, 4 + 5)\"\n\n\tl := lexer.New(input)\n\tp := New(l)\n\texpressionStmt := p.ParseExpressionStatement()\n\tcheckParserErrors(t, p)\n\n\texp, ok := expressionStmt.Expression.(*ast.CallExpression)\n\tif !ok {\n\t\tt.Fatalf(\"stmt.Expression is not ast.CallExpression. got=%T\",\n\t\t\texpressionStmt.Expression)\n\t}\n\n\tif !testIdentifier(t, exp.Function, \"add\") {\n\t\treturn\n\t}\n\n\tif len(exp.Arguments) != 3 {\n\t\tt.Fatalf(\"wrong length of arguments. got=%d\", len(exp.Arguments))\n\t}\n\n\ttestLiteralExpression(t, exp.Arguments[0], 1)\n\ttestInfixExpression(t, exp.Arguments[1], 2, \"*\", 3)\n\ttestInfixExpression(t, exp.Arguments[2], 4, \"+\", 5)\n}\n\nfunc TestCallExpressionParameterParsing(t *testing.T) {\n\ttests := []struct {\n\t\tinput         string\n\t\texpectedIdent string\n\t\texpectedArgs  []string\n\t}{\n\t\t{\n\t\t\tinput:         \"add()\",\n\t\t\texpectedIdent: \"add\",\n\t\t\texpectedArgs:  []string{},\n\t\t},\n\t\t{\n\t\t\tinput:         \"add(1)\",\n\t\t\texpectedIdent: \"add\",\n\t\t\texpectedArgs:  []string{\"1\"},\n\t\t},\n\t\t{\n\t\t\tinput:         \"add(1, 2 * 3, 4 + 5)\",\n\t\t\texpectedIdent: \"add\",\n\t\t\texpectedArgs:  []string{\"1\", \"(2 * 3)\", \"(4 + 5)\"},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tl := lexer.New(tt.input)\n\t\tp := New(l)\n\t\texpressionStmt := p.ParseExpressionStatement()\n\t\tcheckParserErrors(t, p)\n\n\t\texp, ok := expressionStmt.Expression.(*ast.CallExpression)\n\t\tif !ok {\n\t\t\tt.Fatalf(\"stmt.Expression is not ast.CallExpression. got=%T\",\n\t\t\t\texpressionStmt.Expression)\n\t\t}\n\n\t\tif !testIdentifier(t, exp.Function, tt.expectedIdent) {\n\t\t\treturn\n\t\t}\n\n\t\tif len(exp.Arguments) != len(tt.expectedArgs) {\n\t\t\tt.Fatalf(\"wrong number of arguments. want=%d, got=%d\",\n\t\t\t\tlen(tt.expectedArgs), len(exp.Arguments))\n\t\t}\n\n\t\tfor i, arg := range tt.expectedArgs {\n\t\t\tif exp.Arguments[i].String() != arg {\n\t\t\t\tt.Errorf(\"argument %d wrong. want=%q, got=%q\", i,\n\t\t\t\t\targ, exp.Arguments[i].String())\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc testInfixExpression(t *testing.T, exp ast.Expression, left interface{},\n\toperator string, right interface{}) bool {\n\n\topExp, ok := exp.(*ast.InfixExpression)\n\tif !ok {\n\t\tt.Errorf(\"exp is not ast.OperatorExpression. got=%T(%s)\", exp, exp)\n\t\treturn false\n\t}\n\n\tif !testLiteralExpression(t, opExp.Left, left) {\n\t\treturn false\n\t}\n\n\tif opExp.Operator != operator {\n\t\tt.Errorf(\"exp.Operator is not '%s'. got=%q\", operator, opExp.Operator)\n\t\treturn false\n\t}\n\n\tif !testLiteralExpression(t, opExp.Right, right) {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\nfunc testLiteralExpression(\n\tt *testing.T,\n\texp ast.Expression,\n\texpected interface{},\n) bool {\n\tswitch v := expected.(type) {\n\tcase int:\n\t\treturn testIntegerLiteral(t, exp, int64(v))\n\tcase int64:\n\t\treturn testIntegerLiteral(t, exp, v)\n\tcase string:\n\t\treturn testIdentifier(t, exp, v)\n\tcase bool:\n\t\treturn testBooleanLiteral(t, exp, v)\n\t}\n\tt.Errorf(\"type of exp not handled. got=%T\", exp)\n\treturn false\n}\n\nfunc testIntegerLiteral(t *testing.T, il ast.Expression, value int64) bool {\n\tinteg, ok := il.(*ast.IntegerLiteral)\n\tif !ok {\n\t\tt.Errorf(\"il not *ast.IntegerLiteral. got=%T\", il)\n\t\treturn false\n\t}\n\n\tif integ.Value != value {\n\t\tt.Errorf(\"integ.Value not %d. got=%d\", value, integ.Value)\n\t\treturn false\n\t}\n\n\tif integ.TokenLiteral() != fmt.Sprintf(\"%d\", value) {\n\t\tt.Errorf(\"integ.TokenLiteral not %d. got=%s\", value,\n\t\t\tinteg.TokenLiteral())\n\t\treturn false\n\t}\n\n\treturn true\n}\n\nfunc testIdentifier(t *testing.T, exp ast.Expression, value string) bool {\n\tident, ok := exp.(*ast.Identifier)\n\tif !ok {\n\t\tt.Errorf(\"exp not *ast.Identifier. got=%T\", exp)\n\t\treturn false\n\t}\n\n\tif ident.Value != value {\n\t\tt.Errorf(\"ident.Value not %s. got=%s\", value, ident.Value)\n\t\treturn false\n\t}\n\n\tif ident.TokenLiteral() != value {\n\t\tt.Errorf(\"ident.TokenLiteral not %s. got=%s\", value,\n\t\t\tident.TokenLiteral())\n\t\treturn false\n\t}\n\n\treturn true\n}\n\nfunc testBooleanLiteral(t *testing.T, exp ast.Expression, value bool) bool {\n\tbo, ok := exp.(*ast.Boolean)\n\tif !ok {\n\t\tt.Errorf(\"exp not *ast.Boolean. got=%T\", exp)\n\t\treturn false\n\t}\n\n\tif bo.Value != value {\n\t\tt.Errorf(\"bo.Value not %t. got=%t\", value, bo.Value)\n\t\treturn false\n\t}\n\n\tif bo.TokenLiteral() != fmt.Sprintf(\"%t\", value) {\n\t\tt.Errorf(\"bo.TokenLiteral not %t. got=%s\",\n\t\t\tvalue, bo.TokenLiteral())\n\t\treturn false\n\t}\n\n\treturn true\n}\n\nfunc checkParserErrors(t *testing.T, p *Parser) {\n\terrors := p.Errors()\n\tif len(errors) == 0 {\n\t\treturn\n\t}\n\n\tt.Errorf(\"parser has %d errors\", len(errors))\n\tfor _, msg := range errors {\n\t\tt.Errorf(\"parser error: %q\", msg)\n\t}\n\tt.FailNow()\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/scripting/assertion/test_files/a.txt",
    "content": "abc"
  },
  {
    "path": "ddosify_engine/core/scenario/scripting/assertion/test_files/currencies.json",
    "content": "[\n    \"AED\",\n    \"ARS\",\n    \"AUD\",\n    \"BGN\",\n    \"BHD\",\n    \"BRL\",\n    \"CAD\",\n    \"CHF\",\n    \"CNY\",\n    \"DKK\",\n    \"DZD\",\n    \"EUR\",\n    \"FKP\",\n    \"INR\",\n    \"JEP\",\n    \"JPY\",\n    \"KES\",\n    \"KWD\",\n    \"KZT\",\n    \"MXN\",\n    \"NZD\",\n    \"RUB\",\n    \"SEK\",\n    \"SGD\",\n    \"TRY\",\n    \"USD\"\n]"
  },
  {
    "path": "ddosify_engine/core/scenario/scripting/assertion/test_files/jsonArray.json",
    "content": "[\"xyz\",\"abc\"]"
  },
  {
    "path": "ddosify_engine/core/scenario/scripting/assertion/test_files/jsonMap.json",
    "content": "{\n    \"ask\": 130.75, \n    \"askSize\": 10, \n    \"averageAnalystRating\": \"2.0 - Buy\"\n}"
  },
  {
    "path": "ddosify_engine/core/scenario/scripting/assertion/test_files/number.json",
    "content": "5"
  },
  {
    "path": "ddosify_engine/core/scenario/scripting/assertion/token/token.go",
    "content": "package token\n\ntype TokenType string\ntype Token struct {\n\tType    TokenType\n\tLiteral string\n}\n\nconst (\n\tILLEGAL = \"ILLEGAL\"\n\tEOF     = \"EOF\"\n\n\t// Identifiers + literals\n\tIDENT  = \"IDENT\"  // not, equals, json_path, contains, range...\n\tINT    = \"INT\"    // 200, 201\n\tFLOAT  = \"FLOAT\"  // 10.5\n\tSTRING = \"STRING\" // Content-Type\n\n\t// Operators\n\tPLUS     = \"+\"\n\tMINUS    = \"-\"\n\tBANG     = \"!\"\n\tASTERISK = \"*\"\n\tSLASH    = \"/\"\n\tAND      = \"&&\"\n\tOR       = \"||\"\n\n\tLT = \"<\"\n\tGT = \">\"\n\n\tEQ     = \"==\"\n\tNOT_EQ = \"!=\"\n\n\t// Delimiters\n\tCOMMA = \",\"\n\n\tLPAREN   = \"(\"\n\tRPAREN   = \")\"\n\tLBRACE   = \"{\"\n\tRBRACE   = \"}\"\n\tLBRACKET = \"[\"\n\tRBRACKET = \"]\"\n\n\tCOLON = \":\"\n\n\t// Keywords\n\tTRUE  = \"TRUE\"\n\tFALSE = \"FALSE\"\n\tNULL  = \"NULL\"\n)\n\nvar keywords = map[string]TokenType{\n\t\"true\":  TRUE,\n\t\"false\": FALSE,\n\t\"null\":  NULL,\n}\n\nfunc LookupIdent(ident string) TokenType {\n\tif tok, ok := keywords[ident]; ok {\n\t\treturn tok\n\t}\n\treturn IDENT\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/scripting/extraction/base.go",
    "content": "package extraction\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"go.ddosify.com/ddosify/core/types\"\n)\n\nfunc Extract(source interface{}, ce types.EnvCaptureConf) (val interface{}, err error) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tswitch x := r.(type) {\n\t\t\tcase string:\n\t\t\t\terr = errors.New(x)\n\t\t\tcase error:\n\t\t\t\terr = x\n\t\t\tdefault:\n\t\t\t\terr = errors.New(\"Unknown panic\")\n\t\t\t}\n\t\t\tval = nil\n\t\t}\n\t}()\n\n\tif source == nil {\n\t\treturn \"\", ExtractionError{\n\t\t\tmsg: \"source is nil\",\n\t\t}\n\t}\n\n\tswitch ce.From {\n\tcase types.Header:\n\t\theader := source.(http.Header)\n\t\tif ce.Key != nil { // key specified\n\t\t\tval = header.Get(*ce.Key)\n\t\t\tif val == \"\" {\n\t\t\t\terr = fmt.Errorf(\"http header %s not found\", *ce.Key)\n\t\t\t} else if ce.RegExp != nil { // run regex for found value\n\t\t\t\tval, err = ExtractWithRegex(val, *ce.RegExp)\n\t\t\t}\n\t\t} else {\n\t\t\terr = fmt.Errorf(\"http header key not specified\")\n\t\t}\n\tcase types.Body:\n\t\tif ce.JsonPath != nil {\n\t\t\tval, err = ExtractFromJson(source, *ce.JsonPath)\n\t\t} else if ce.RegExp != nil {\n\t\t\tval, err = ExtractWithRegex(source, *ce.RegExp)\n\t\t} else if ce.Xpath != nil {\n\t\t\tval, err = ExtractFromXml(source, *ce.Xpath)\n\t\t} else if ce.XpathHtml != nil {\n\t\t\tval, err = ExtractFromHtml(source, *ce.XpathHtml)\n\t\t}\n\tcase types.Cookie:\n\t\tcookies := source.(map[string]*http.Cookie)\n\t\tif ce.CookieName != nil { // cookie name specified\n\t\t\tc, ok := cookies[*ce.CookieName]\n\t\t\tif !ok {\n\t\t\t\terr = fmt.Errorf(\"cookie %s not found\", *ce.CookieName)\n\t\t\t} else {\n\t\t\t\tval = c.Value\n\t\t\t}\n\t\t} else {\n\t\t\terr = fmt.Errorf(\"cookie name not specified\")\n\t\t}\n\t}\n\n\tif err != nil {\n\t\treturn \"\", ExtractionError{\n\t\t\tmsg:        fmt.Sprintf(\"%v\", err),\n\t\t\twrappedErr: err,\n\t\t}\n\t}\n\treturn val, nil\n\n}\n\nfunc ExtractWithRegex(source interface{}, regexConf types.RegexCaptureConf) (val interface{}, err error) {\n\tre := regexExtractor{}\n\tre.Init(*regexConf.Exp)\n\tswitch s := source.(type) {\n\tcase []byte: // from response body\n\t\treturn re.extractFromByteSlice(s, regexConf.No)\n\tcase string: // from response header\n\t\treturn re.extractFromString(s, regexConf.No)\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"Unsupported type for extraction source\")\n\t}\n}\n\nfunc ExtractFromJson(source interface{}, jsonPath string) (interface{}, error) {\n\tje := jsonExtractor{}\n\tswitch s := source.(type) {\n\tcase []byte: // from response body\n\t\treturn je.extractFromByteSlice(s, jsonPath)\n\tcase string: // from response header\n\t\treturn je.extractFromString(s, jsonPath)\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"Unsupported type for extraction source\")\n\t}\n}\n\nfunc ExtractFromXml(source interface{}, xPath string) (interface{}, error) {\n\txe := xmlExtractor{}\n\tswitch s := source.(type) {\n\tcase []byte: // from response body\n\t\treturn xe.extractFromByteSlice(s, xPath)\n\tcase string: // from response header\n\t\treturn xe.extractFromString(s, xPath)\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"Unsupported type for extraction source\")\n\t}\n}\n\nfunc ExtractFromHtml(source interface{}, xPath string) (interface{}, error) {\n\txe := htmlExtractor{}\n\tswitch s := source.(type) {\n\tcase []byte: // from response body\n\t\treturn xe.extractFromByteSlice(s, xPath)\n\tcase string: // from response header\n\t\treturn xe.extractFromString(s, xPath)\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"Unsupported type for extraction source\")\n\t}\n}\n\ntype ExtractionError struct { // UnWrappable\n\tmsg        string\n\twrappedErr error\n}\n\nfunc (sc ExtractionError) Error() string {\n\treturn sc.msg\n}\n\nfunc (sc ExtractionError) Unwrap() error {\n\treturn sc.wrappedErr\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/scripting/extraction/base_test.go",
    "content": "package extraction\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"runtime\"\n\t\"testing\"\n\n\t\"go.ddosify.com/ddosify/core/types\"\n)\n\nfunc TestHttpHeaderKey_NotSpecified(t *testing.T) {\n\tce := types.EnvCaptureConf{\n\t\tJsonPath: nil,\n\t\tXpath:    nil,\n\t\tRegExp:   &types.RegexCaptureConf{},\n\t\tName:     \"\",\n\t\tFrom:     types.Header,\n\t\tKey:      nil,\n\t}\n\n\t_, err := Extract(http.Header{}, ce)\n\n\tif err == nil {\n\t\tt.Errorf(\"Expected error when header key not specified\")\n\t}\n}\n\nfunc TestExtract_TypeAssertErrorRecover(t *testing.T) {\n\theaderKey := \"x\"\n\tce := types.EnvCaptureConf{\n\t\tJsonPath: nil,\n\t\tXpath:    nil,\n\t\tRegExp:   nil,\n\t\tName:     \"\",\n\t\tFrom:     types.Header,\n\t\tKey:      &headerKey,\n\t}\n\n\t// source should be http.Header\n\t_, err := Extract(\"sdfds\", ce)\n\n\tvar assertError *runtime.TypeAssertionError\n\tif !errors.As(err, &assertError) {\n\t\tt.Errorf(\"Expected error must be TypeAssertionError, got %v\", err)\n\t}\n}\n\nfunc TestExtract_NilSource(t *testing.T) {\n\theaderKey := \"x\"\n\tce := types.EnvCaptureConf{\n\t\tJsonPath: nil,\n\t\tXpath:    nil,\n\t\tRegExp:   nil,\n\t\tName:     \"\",\n\t\tFrom:     types.Header,\n\t\tKey:      &headerKey,\n\t}\n\n\t_, err := Extract(nil, ce)\n\n\tif err == nil {\n\t\tt.Errorf(\"error expected, got nil\")\n\t}\n}\n\nfunc TestExtract_InvalidXml(t *testing.T) {\n\txpath := \"\"\n\tce := types.EnvCaptureConf{\n\t\tJsonPath: nil,\n\t\tXpath:    &xpath,\n\t\tRegExp:   nil,\n\t\tName:     \"\",\n\t\tFrom:     types.Body,\n\t\tKey:      nil,\n\t}\n\n\t_, err := Extract([]byte(\"xxx\"), ce)\n\n\tif err == nil {\n\t\tt.Errorf(\"error expected, got nil\")\n\t}\n}\n\nfunc TestCookieName_NotSpecified(t *testing.T) {\n\tce := types.EnvCaptureConf{\n\t\tJsonPath:   nil,\n\t\tXpath:      nil,\n\t\tRegExp:     &types.RegexCaptureConf{},\n\t\tName:       \"\",\n\t\tFrom:       types.Cookie,\n\t\tKey:        nil,\n\t\tCookieName: nil,\n\t}\n\n\t_, err := Extract(map[string]*http.Cookie{}, ce)\n\n\tif err == nil {\n\t\tt.Errorf(\"Expected error when cookie key not specified\")\n\t}\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/scripting/extraction/html.go",
    "content": "package extraction\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\n\t\"github.com/antchfx/htmlquery\"\n)\n\ntype htmlExtractor struct {\n}\n\nfunc (xe htmlExtractor) extractFromByteSlice(source []byte, xPath string) (interface{}, error) {\n\treader := bytes.NewBuffer(source)\n\trootNode, err := htmlquery.Parse(reader)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// returns the first matched element\n\tfoundNode, err := htmlquery.Query(rootNode, xPath)\n\tif foundNode == nil || err != nil {\n\t\treturn nil, fmt.Errorf(\"no match for the xPath_html: %s\", xPath)\n\t}\n\n\treturn foundNode.FirstChild.Data, nil\n}\n\nfunc (xe htmlExtractor) extractFromString(source string, xPath string) (interface{}, error) {\n\treader := bytes.NewBufferString(source)\n\trootNode, err := htmlquery.Parse(reader)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// returns the first matched element\n\tfoundNode, err := htmlquery.Query(rootNode, xPath)\n\tif foundNode == nil || err != nil {\n\t\treturn nil, fmt.Errorf(\"no match for this xpath_html\")\n\t}\n\n\treturn foundNode.FirstChild.Data, nil\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/scripting/extraction/html_test.go",
    "content": "package extraction\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestHtmlExtraction(t *testing.T) {\n\texpected := \"Html Title\"\n\tHtmlSource := fmt.Sprintf(`<!DOCTYPE html>\n\t<html>\n\t<body>\n\t<h1>%s</h1>\n\t<p>My first paragraph.</p>\n\t</body>\n\t</html>`, expected)\n\n\txe := htmlExtractor{}\n\txpath := \"//body/h1\"\n\tval, err := xe.extractFromByteSlice([]byte(HtmlSource), xpath)\n\n\tif err != nil {\n\t\tt.Errorf(\"TestHtmlExtraction %v\", err)\n\t}\n\n\tif !strings.EqualFold(val.(string), expected) {\n\t\tt.Errorf(\"TestHtmlExtraction expected: %s, got: %s\", expected, val)\n\t}\n}\n\nfunc TestHtmlExtractionSeveralNode(t *testing.T) {\n\t//should extract only the first one\n\texpected := \"Html Title\"\n\tHtmlSource := fmt.Sprintf(`<!DOCTYPE html>\n\t<html>\n\t<body>\n\t<h1>%s</h1>\n\t<h1>another node</h1>\n\t<p>My first paragraph.</p>\n\t</body>\n\t</html>`, expected)\n\n\txe := htmlExtractor{}\n\txpath := \"//h1\"\n\tval, err := xe.extractFromByteSlice([]byte(HtmlSource), xpath)\n\n\tif err != nil {\n\t\tt.Errorf(\"TestHtmlExtraction %v\", err)\n\t}\n\n\tif !strings.EqualFold(val.(string), expected) {\n\t\tt.Errorf(\"TestHtmlExtraction expected: %s, got: %s\", expected, val)\n\t}\n}\n\nfunc TestHtmlExtraction_PathNotFound(t *testing.T) {\n\texpected := \"XML Title\"\n\txmlSource := fmt.Sprintf(`<!DOCTYPE html>\n\t<html>\n\t<body>\n\t<h1>%s</h1>\n\t<h1>another node</h1>\n\t<p>My first paragraph.</p>\n\t</body>\n\t</html>`, expected)\n\n\txe := htmlExtractor{}\n\txpath := \"//h2\"\n\t_, err := xe.extractFromByteSlice([]byte(xmlSource), xpath)\n\n\tif err == nil {\n\t\tt.Errorf(\"TestHtmlExtraction_PathNotFound, should be err, got :%v\", err)\n\t}\n}\n\nfunc TestInvalidHtml(t *testing.T) {\n\txmlSource := `invalid html source`\n\n\txe := htmlExtractor{}\n\txpath := \"//input\"\n\t_, err := xe.extractFromByteSlice([]byte(xmlSource), xpath)\n\n\tif err == nil {\n\t\tt.Errorf(\"TestInvalidXml, should be err, got :%v\", err)\n\t}\n}\n\nfunc TestHtmlComplexExtraction(t *testing.T) {\n\texpected := \"Html Title\"\n\tHtmlSource := fmt.Sprintf(`<!DOCTYPE html>\n\t<html>\n\t<body>\n\t<script>\n\t\tif (typeof resourceLoadedSuccessfully === \"function\") {\n\t\t\tresourceLoadedSuccessfully();\n\t\t}\n\t\t$(() => {\n\t\t\ttypeof cssVars === \"function\" && cssVars({onlyLegacy: true});\n\t\t})\n\t\tvar trackGeoLocation = false;\n\t\talert('#@=$*€');\n\t\t</script>\n\t<h1>%s</h1>\n\t<p>My first paragraph.</p>\n\t</body>\n\t</html>`, expected)\n\n\txe := htmlExtractor{}\n\txpath := \"//body/h1\"\n\tval, err := xe.extractFromByteSlice([]byte(HtmlSource), xpath)\n\n\tif err != nil {\n\t\tt.Errorf(\"TestHtmlExtraction %v\", err)\n\t}\n\n\tif !strings.EqualFold(val.(string), expected) {\n\t\tt.Errorf(\"TestHtmlExtraction expected: %s, got: %s\", expected, val)\n\t}\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/scripting/extraction/json.go",
    "content": "package extraction\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/tidwall/gjson\"\n)\n\ntype jsonExtractor struct {\n}\n\nvar unmarshalJsonCapture = func(result gjson.Result) (interface{}, error) {\n\tbRaw := []byte(result.Raw)\n\tif result.IsObject() {\n\t\tjObject := map[string]interface{}{}\n\t\terr := json.Unmarshal(bRaw, &jObject)\n\t\tif err == nil {\n\t\t\treturn jObject, err\n\t\t}\n\t}\n\n\tif result.IsArray() {\n\t\tjInterfaceSlice := []interface{}{}\n\t\terr := json.Unmarshal(bRaw, &jInterfaceSlice)\n\t\tif err == nil {\n\t\t\treturn jInterfaceSlice, err\n\t\t}\n\t}\n\n\tif result.IsBool() {\n\t\tjBool := false\n\t\terr := json.Unmarshal(bRaw, &jBool)\n\t\tif err == nil {\n\t\t\treturn jBool, err\n\t\t}\n\t}\n\n\treturn nil, fmt.Errorf(\"json could not be unmarshaled\")\n}\n\nfunc (je jsonExtractor) extractFromString(source string, jsonPath string) (interface{}, error) {\n\tresult := gjson.Get(source, jsonPath)\n\n\t// path not found\n\tif result.Raw == \"\" && result.Type == gjson.Null {\n\t\treturn \"\", fmt.Errorf(\"no match for the json path: %s\", jsonPath)\n\t}\n\n\tswitch result.Type {\n\tcase gjson.String:\n\t\treturn result.String(), nil\n\tcase gjson.Null:\n\t\treturn nil, nil\n\tcase gjson.False:\n\t\treturn false, nil\n\tcase gjson.Number:\n\t\tnumber := result.String()\n\t\tif strings.Contains(number, \".\") { // float\n\t\t\treturn result.Float(), nil\n\t\t}\n\t\treturn result.Int(), nil\n\tcase gjson.True:\n\t\treturn true, nil\n\tcase gjson.JSON:\n\t\treturn unmarshalJsonCapture(result)\n\tdefault:\n\t\treturn \"\", nil\n\t}\n}\n\nfunc (je jsonExtractor) extractFromByteSlice(source []byte, jsonPath string) (interface{}, error) {\n\tresult := gjson.GetBytes(source, jsonPath)\n\n\t// path not found\n\tif result.Raw == \"\" && result.Type == gjson.Null {\n\t\treturn \"\", fmt.Errorf(\"no match for the json path: %s\", jsonPath)\n\t}\n\n\tswitch result.Type {\n\tcase gjson.String:\n\t\treturn result.String(), nil\n\tcase gjson.Null:\n\t\treturn nil, nil\n\tcase gjson.False:\n\t\treturn false, nil\n\tcase gjson.Number:\n\t\tnumber := result.String()\n\t\tif strings.Contains(number, \".\") { // float\n\t\t\treturn result.Float(), nil\n\t\t}\n\t\treturn result.Int(), nil\n\tcase gjson.True:\n\t\treturn true, nil\n\tcase gjson.JSON:\n\t\treturn unmarshalJsonCapture(result)\n\tdefault:\n\t\treturn \"\", nil\n\t}\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/scripting/extraction/json_test.go",
    "content": "package extraction\n\nimport (\n\t\"encoding/json\"\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestJsonExtract_String(t *testing.T) {\n\tpayload := map[string]interface{}{\n\t\t\"name\": map[string]interface{}{\n\t\t\t\"first\": \"Janet\",\n\t\t\t\"last\":  \"Prichard\",\n\t\t},\n\t\t\"age\": 47,\n\t}\n\n\tbyteSlice, _ := json.Marshal(payload)\n\tje := jsonExtractor{}\n\tval, _ := je.extractFromByteSlice(byteSlice, \"name.last\")\n\n\tif val != \"Prichard\" {\n\t\tt.Errorf(\"Json Extract Error\")\n\t}\n\n\tval, _ = je.extractFromString(string(byteSlice), \"name.last\")\n\n\tif val != \"Prichard\" {\n\t\tt.Errorf(\"Json Extract Error\")\n\t}\n}\n\nfunc TestJsonExtract_Object(t *testing.T) {\n\texpected := map[string]interface{}{\n\t\t\"first\": \"Janet\",\n\t\t\"last\":  \"Prichard\",\n\t}\n\tpayload := map[string]interface{}{\n\t\t\"name\": expected,\n\t\t\"age\":  47,\n\t}\n\n\tbyteSlice, _ := json.Marshal(payload)\n\tje := jsonExtractor{}\n\tval, _ := je.extractFromByteSlice(byteSlice, \"name\")\n\n\tif !reflect.DeepEqual(val, expected) {\n\t\tt.Errorf(\"TestJsonExtract_Object failed, expected %#v, found %#v\", expected, val)\n\t}\n\n\tval, _ = je.extractFromString(string(byteSlice), \"name\")\n\n\tif !reflect.DeepEqual(val, expected) {\n\t\tt.Errorf(\"TestJsonExtract_Object failed, expected %#v, found %#v\", expected, val)\n\t}\n}\n\nfunc TestJsonExtract_Float(t *testing.T) {\n\tvar expected float64 = 52.2\n\tpayload := map[string]interface{}{\n\t\t\"age\": expected,\n\t}\n\n\tbyteSlice, _ := json.Marshal(payload)\n\tje := jsonExtractor{}\n\tval, _ := je.extractFromByteSlice(byteSlice, \"age\")\n\n\tval2 := val.(float64) // json number -> float64\n\tif !reflect.DeepEqual(val2, expected) {\n\t\tt.Errorf(\"TestJsonExtract_Float failed, expected %#v, found %#v\", expected, val)\n\t}\n\n\tval, _ = je.extractFromString(string(byteSlice), \"age\")\n\n\tval22 := val.(float64) // json number -> float64\n\tif !reflect.DeepEqual(val22, expected) {\n\t\tt.Errorf(\"TestJsonExtract_Float failed, expected %#v, found %#v\", expected, val)\n\t}\n}\n\nfunc TestJsonExtract_Int(t *testing.T) {\n\tvar expected int = 52\n\tpayload := map[string]interface{}{\n\t\t\"age\": expected,\n\t}\n\n\tbyteSlice, _ := json.Marshal(payload)\n\tje := jsonExtractor{}\n\tval, _ := je.extractFromByteSlice(byteSlice, \"age\")\n\n\tval2 := val.(int64) // json number -> float64\n\tif !reflect.DeepEqual(int(val2), expected) {\n\t\tt.Errorf(\"TestJsonExtract_Int failed, expected %#v, found %#v\", expected, val)\n\t}\n\n\tval, _ = je.extractFromString(string(byteSlice), \"age\")\n\n\tval22 := val.(int64) // json number -> float64\n\tif !reflect.DeepEqual(int(val22), expected) {\n\t\tt.Errorf(\"TestJsonExtract_Int failed, expected %#v, found %#v\", expected, val)\n\t}\n}\n\nfunc TestJsonExtract_Nil(t *testing.T) {\n\tpayload := map[string]interface{}{\n\t\t\"age\": nil,\n\t}\n\n\tbyteSlice, _ := json.Marshal(payload)\n\tje := jsonExtractor{}\n\tval, _ := je.extractFromByteSlice(byteSlice, \"age\")\n\n\tif !reflect.DeepEqual(val, nil) {\n\t\tt.Errorf(\"TestJsonExtract_Nil failed, expected %#v, found %#v\", nil, val)\n\t}\n\n\tval, _ = je.extractFromString(string(byteSlice), \"age\")\n\n\tif !reflect.DeepEqual(val, nil) {\n\t\tt.Errorf(\"TestJsonExtract_Nil failed, expected %#v, found %#v\", nil, val)\n\t}\n}\n\nfunc TestJsonExtract_Bool(t *testing.T) {\n\tje := jsonExtractor{}\n\texpected := true\n\texpected1 := false\n\n\tpayload := map[string]interface{}{\n\t\t\"age\":  expected,\n\t\t\"age1\": expected1,\n\t}\n\n\tbyteSlice, _ := json.Marshal(payload)\n\tval, _ := je.extractFromByteSlice(byteSlice, \"age\")\n\tval1, _ := je.extractFromByteSlice(byteSlice, \"age1\")\n\n\tif !reflect.DeepEqual(val, expected) || !reflect.DeepEqual(val1, expected1) {\n\t\tt.Errorf(\"TestJsonExtract_Bool failed, expected %#v, found %#v\", expected, val)\n\t}\n\n\texpected = false\n\tpayload = map[string]interface{}{\n\t\t\"age\": expected,\n\t}\n\tbyteSlice, _ = json.Marshal(payload)\n\n\tval, _ = je.extractFromString(string(byteSlice), \"age\")\n\n\tif !reflect.DeepEqual(val, expected) {\n\t\tt.Errorf(\"TestJsonExtract_Bool failed, expected %#v, found %#v\", expected, val)\n\t}\n}\n\nfunc TestJsonExtract_JsonArray(t *testing.T) {\n\tt.SkipNow()\n\texpected := []string{\"a\", \"b\"}\n\tpayload := map[string]interface{}{\n\t\t\"age\": expected,\n\t}\n\n\tbyteSlice, _ := json.Marshal(payload)\n\tje := jsonExtractor{}\n\tval, _ := je.extractFromByteSlice(byteSlice, \"age\")\n\n\tif !reflect.DeepEqual(val, expected) {\n\t\tt.Errorf(\"TestJsonExtract_JsonArray failed, expected %#v, found %#v\", expected, val)\n\t}\n\n\tval, _ = je.extractFromString(string(byteSlice), \"age\")\n\n\tif !reflect.DeepEqual(val, expected) {\n\t\tt.Errorf(\"TestJsonExtract_JsonArray failed, expected %#v, found %#v\", expected, val)\n\t}\n}\n\nfunc TestJsonExtract_JsonIntArray(t *testing.T) {\n\tt.SkipNow()\n\texpected := []int{2, 4}\n\tpayload := map[string]interface{}{\n\t\t\"age\": expected,\n\t}\n\n\tbyteSlice, _ := json.Marshal(payload)\n\tje := jsonExtractor{}\n\tval, _ := je.extractFromByteSlice(byteSlice, \"age\")\n\n\texpectedFloat := []float64{2, 4}\n\tif !reflect.DeepEqual(val, expectedFloat) {\n\t\tt.Errorf(\"TestJsonExtract_JsonIntArray failed, expected %#v, found %#v\", expected, val)\n\t}\n\n\tval, _ = je.extractFromString(string(byteSlice), \"age\")\n\n\tif !reflect.DeepEqual(val, expectedFloat) {\n\t\tt.Errorf(\"TestJsonExtract_JsonIntArray failed, expected %#v, found %#v\", expected, val)\n\t}\n}\n\nfunc TestJsonExtract_JsonFloatArray(t *testing.T) {\n\tt.SkipNow()\n\texpected := []float64{2.33, 4.55}\n\tpayload := map[string]interface{}{\n\t\t\"age\": expected,\n\t}\n\n\tbyteSlice, _ := json.Marshal(payload)\n\tje := jsonExtractor{}\n\tval, _ := je.extractFromByteSlice(byteSlice, \"age\")\n\n\tif !reflect.DeepEqual(val, expected) {\n\t\tt.Errorf(\"TestJsonExtract_JsonFloatArray failed, expected %#v, found %#v\", expected, val)\n\t}\n\n\tval, _ = je.extractFromString(string(byteSlice), \"age\")\n\n\tif !reflect.DeepEqual(val, expected) {\n\t\tt.Errorf(\"TestJsonExtract_JsonFloatArray failed, expected %#v, found %#v\", expected, val)\n\t}\n}\n\nfunc TestJsonExtract_JsonBoolArray(t *testing.T) {\n\tt.SkipNow()\n\texpected := []bool{true, false}\n\tpayload := map[string]interface{}{\n\t\t\"age\": expected,\n\t}\n\n\tbyteSlice, _ := json.Marshal(payload)\n\tje := jsonExtractor{}\n\tval, _ := je.extractFromByteSlice(byteSlice, \"age\")\n\n\tif !reflect.DeepEqual(val, expected) {\n\t\tt.Errorf(\"TestJsonExtract_JsonBoolArray failed, expected %#v, found %#v\", expected, val)\n\t}\n\n\tval, _ = je.extractFromString(string(byteSlice), \"age\")\n\n\tif !reflect.DeepEqual(val, expected) {\n\t\tt.Errorf(\"TestJsonExtract_JsonBoolArray failed, expected %#v, found %#v\", expected, val)\n\t}\n}\n\nfunc TestJsonExtract_ObjectArray(t *testing.T) {\n\tt.SkipNow()\n\texpected := []map[string]interface{}{\n\t\t{\"x\": \"cc\"},\n\t}\n\tpayload := map[string]interface{}{\n\t\t\"age\": expected,\n\t}\n\n\tbyteSlice, _ := json.Marshal(payload)\n\tje := jsonExtractor{}\n\tval, _ := je.extractFromByteSlice(byteSlice, \"age\")\n\n\tif !reflect.DeepEqual(val, expected) {\n\t\tt.Errorf(\"TestJsonExtract_JsonBoolArray failed, expected %#v, found %#v\", expected, val)\n\t}\n\n\tval, _ = je.extractFromString(string(byteSlice), \"age\")\n\n\tif !reflect.DeepEqual(val, expected) {\n\t\tt.Errorf(\"TestJsonExtract_JsonBoolArray failed, expected %#v, found %#v\", expected, val)\n\t}\n}\n\nfunc TestJsonExtract_JsonPathNotFound(t *testing.T) {\n\tpayload := map[string]interface{}{\n\t\t\"age\": \"24\",\n\t}\n\n\tbyteSlice, _ := json.Marshal(payload)\n\tje := jsonExtractor{}\n\tval, err := je.extractFromByteSlice(byteSlice, \"age2\")\n\n\texpected := \"no match for the json path: age2\"\n\tif !strings.EqualFold(err.Error(), expected) {\n\t\tt.Errorf(\"TestJsonExtract_JsonPathNotFound failed, expected %#v, found %#v\", expected, err)\n\t}\n\n\tif !reflect.DeepEqual(val, \"\") {\n\t\tt.Errorf(\"TestJsonExtract_JsonPathNotFound failed, expected %#v, found %#v\", expected, val)\n\t}\n\n\tval, err = je.extractFromString(string(byteSlice), \"age2\")\n\n\tif !strings.EqualFold(err.Error(), expected) {\n\t\tt.Errorf(\"TestJsonExtract_JsonPathNotFound failed, expected %#v, found %#v\", expected, err)\n\t}\n\n\tif !reflect.DeepEqual(val, \"\") {\n\t\tt.Errorf(\"TestJsonExtract_JsonPathNotFound failed, expected %#v, found %#v\", expected, val)\n\t}\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/scripting/extraction/regex.go",
    "content": "package extraction\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n)\n\ntype regexExtractor struct {\n\tr *regexp.Regexp\n}\n\nfunc (ri *regexExtractor) Init(regex string) {\n\tri.r = regexp.MustCompile(regex)\n}\n\nfunc (ri *regexExtractor) extractFromString(text string, matchNo int) (string, error) {\n\tmatches := ri.r.FindAllString(text, -1)\n\n\tif matches == nil {\n\t\treturn \"\", fmt.Errorf(\"no match for the Regex: %s  Match no: %d\", ri.r.String(), matchNo)\n\t}\n\n\tif len(matches) > matchNo {\n\t\treturn matches[matchNo], nil\n\t}\n\treturn matches[0], nil\n}\n\nfunc (ri *regexExtractor) extractFromByteSlice(text []byte, matchNo int) ([]byte, error) {\n\tmatches := ri.r.FindAll(text, -1)\n\n\tif matches == nil {\n\t\treturn nil, fmt.Errorf(\"no match for the Regex: %s  Match no: %d\", ri.r.String(), matchNo)\n\t}\n\n\tif len(matches) > matchNo {\n\t\treturn matches[matchNo], nil\n\t}\n\treturn matches[0], nil\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/scripting/extraction/regex_test.go",
    "content": "package extraction\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestRegexExtractFromString(t *testing.T) {\n\tregex := \"[a-z]+_[0-9]+\"\n\n\tre := regexExtractor{}\n\tre.Init(regex)\n\n\tsource := \"messi_10alvarez_9\"\n\n\tres, err2 := re.extractFromString(source, 1)\n\tif !strings.EqualFold(res, \"alvarez_9\") || err2 != nil {\n\t\tt.Errorf(\"RegexMatch should return second match\")\n\t}\n\n\tres, err := re.extractFromString(source, 0)\n\tif !strings.EqualFold(res, \"messi_10\") || err != nil {\n\t\tt.Errorf(\"RegexMatch should return first match\")\n\t}\n\n}\n\nfunc TestRegexExtractFromStringNoMatch(t *testing.T) {\n\tregex := \"[a-z]+_[0-9]+\"\n\n\tre := regexExtractor{}\n\tre.Init(regex)\n\n\tsource := \"messialvarez\"\n\n\t_, err := re.extractFromString(source, 0)\n\tif err == nil {\n\t\tt.Errorf(\"Should be error %v\", err)\n\t}\n\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/scripting/extraction/xml.go",
    "content": "package extraction\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\n\t\"github.com/antchfx/xmlquery\"\n)\n\ntype xmlExtractor struct {\n}\n\nfunc (xe xmlExtractor) extractFromByteSlice(source []byte, xPath string) (interface{}, error) {\n\treader := bytes.NewBuffer(source)\n\trootNode, err := xmlquery.Parse(reader)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// returns the first matched element\n\tfoundNode, err := xmlquery.Query(rootNode, xPath)\n\tif foundNode == nil || err != nil {\n\t\treturn nil, fmt.Errorf(\"no match for the xPath: %s\", xPath)\n\t}\n\n\treturn foundNode.InnerText(), nil\n}\n\nfunc (xe xmlExtractor) extractFromString(source string, xPath string) (interface{}, error) {\n\treader := bytes.NewBufferString(source)\n\trootNode, err := xmlquery.Parse(reader)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// returns the first matched element\n\tfoundNode, err := xmlquery.Query(rootNode, xPath)\n\tif foundNode == nil || err != nil {\n\t\treturn nil, fmt.Errorf(\"no match for this xpath\")\n\t}\n\n\treturn foundNode.InnerText(), nil\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/scripting/extraction/xml_test.go",
    "content": "package extraction\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestXmlExtraction(t *testing.T) {\n\texpected := \"XML Title\"\n\txmlSource := fmt.Sprintf(`<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n\t<rss version=\"2.0\">\n\t<channel>\n\t  <item>\n\t\t<title>%s</title>\n\t  </item>\n\t</channel>\n\t</rss>`, expected)\n\n\txe := xmlExtractor{}\n\txpath := \"//item/title\"\n\tval, err := xe.extractFromByteSlice([]byte(xmlSource), xpath)\n\n\tif err != nil {\n\t\tt.Errorf(\"TestXmlExtraction %v\", err)\n\t}\n\n\tif !strings.EqualFold(val.(string), expected) {\n\t\tt.Errorf(\"TestXmlExtraction expected: %s, got: %s\", expected, val)\n\t}\n}\n\nfunc TestXmlExtractionString(t *testing.T) {\n\texpected := \"XML Title\"\n\txmlSource := fmt.Sprintf(`<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n\t<rss version=\"2.0\">\n\t<channel>\n\t  <item>\n\t\t<title>%s</title>\n\t  </item>\n\t</channel>\n\t</rss>`, expected)\n\n\txe := xmlExtractor{}\n\txpath := \"//item/title\"\n\tval, err := xe.extractFromString(xmlSource, xpath)\n\n\tif err != nil {\n\t\tt.Errorf(\"TestXmlExtraction %v\", err)\n\t}\n\n\tif !strings.EqualFold(val.(string), expected) {\n\t\tt.Errorf(\"TestXmlExtraction expected: %s, got: %s\", expected, val)\n\t}\n}\n\nfunc TestXmlExtraction_PathNotFound(t *testing.T) {\n\texpected := \"XML Title\"\n\txmlSource := fmt.Sprintf(`<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n\t<rss version=\"2.0\">\n\t<channel>\n\t  <item>\n\t\t<title>%s</title>\n\t  </item>\n\t</channel>\n\t</rss>`, expected)\n\n\txe := xmlExtractor{}\n\txpath := \"//item3/title\"\n\t_, err := xe.extractFromByteSlice([]byte(xmlSource), xpath)\n\n\tif err == nil {\n\t\tt.Errorf(\"TestXmlExtraction_PathNotFound, should be err, got :%v\", err)\n\t}\n}\n\nfunc TestInvalidXml(t *testing.T) {\n\txmlSource := `invalid xml source`\n\n\txe := xmlExtractor{}\n\txpath := \"//item3/title\"\n\t_, err := xe.extractFromByteSlice([]byte(xmlSource), xpath)\n\n\tif err == nil {\n\t\tt.Errorf(\"TestInvalidXml, should be err, got :%v\", err)\n\t}\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/scripting/injection/dynamic_test.go",
    "content": "package injection\n\nimport (\n\t\"testing\"\n)\n\nfunc TestDynamicVariableRace(t *testing.T) {\n\tnum := 10\n\tei := EnvironmentInjector{}\n\tfor key := range dynamicFakeDataMap {\n\t\tfor i := 0; i < num; i++ {\n\t\t\tgo ei.getFakeData(key)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/scripting/injection/environment.go",
    "content": "package injection\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"math/rand\"\n\t\"os\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\t\"unsafe\"\n\n\t\"go.ddosify.com/ddosify/core/types/regex\"\n)\n\ntype BodyPiece struct {\n\tstart      int\n\tend        int // end is not inclusive\n\tinjectable bool\n\tvalue      string // []byte // exist only if injectable is true\n}\n\ntype DdosifyBodyReader struct {\n\tBody   string // []byte\n\tPieces []BodyPiece\n\n\t// keeps track of the current read position\n\tpi int // piece index\n\tvi int // index in the value of the current piece\n}\n\n// no-op close\nfunc (dbr *DdosifyBodyReader) Close() error { return nil }\n\nfunc (dbr *DdosifyBodyReader) Read(dst []byte) (n int, err error) {\n\tleftSpaceOnDst := len(dst) // assume dst is empty, so we can write to it from the beginning\n\n\tvar readUntilPieceIndex int\n\tvar readUntilPieceValueIndex int\n\n\treadUntilPieceIndex = dbr.pi\n\treadUntilPieceValueIndex = dbr.vi\n\n\t// find the piece index and value index to read until\n\tfor leftSpaceOnDst > 0 && readUntilPieceIndex < len(dbr.Pieces) {\n\t\tvar unReadOnCurrentPiece int\n\t\tpiece := dbr.Pieces[readUntilPieceIndex]\n\t\tif piece.injectable { // has injected value\n\t\t\tunReadOnCurrentPiece = len(piece.value[readUntilPieceValueIndex:])\n\t\t} else {\n\t\t\tunReadOnCurrentPiece = piece.end - piece.start - readUntilPieceValueIndex\n\t\t}\n\n\t\tif unReadOnCurrentPiece > leftSpaceOnDst {\n\t\t\t// will be a partial read\n\t\t\t// set readUntilPieceIndex and readUntilPieceValueIndex\n\t\t\treadUntilPieceValueIndex += leftSpaceOnDst\n\t\t\tleftSpaceOnDst = 0\n\t\t} else {\n\t\t\t// will be a full read of the current piece\n\t\t\t// set readUntilPieceIndex and readUntilPieceValueIndex\n\t\t\tleftSpaceOnDst -= unReadOnCurrentPiece\n\t\t\treadUntilPieceValueIndex += unReadOnCurrentPiece\n\n\t\t\tif leftSpaceOnDst > 0 {\n\t\t\t\t// there is still space on dst\n\t\t\t\treadUntilPieceIndex++\n\t\t\t\treadUntilPieceValueIndex = 0\n\t\t\t}\n\t\t}\n\t}\n\n\t// continue reading from pieceIndex and valIndex\n\t// in first iteration, read from dbr.valIndex till the end of the piece\n\t// later on read from the beginning of the piece till the end of the piece\n\tfor i := dbr.pi; i <= readUntilPieceIndex; i++ {\n\t\tif i == len(dbr.Pieces) {\n\t\t\tdbr.pi = i\n\t\t\treturn n, io.EOF\n\t\t}\n\t\tpiece := dbr.Pieces[i]\n\t\tif piece.injectable {\n\t\t\t// if dst has enough space to hold the whole piece\n\t\t\t// copy the whole piece from where we left off\n\t\t\tif len(dst[n:]) >= len(piece.value)-dbr.vi {\n\t\t\t\tcopy(dst[n:n+len(piece.value)-dbr.vi], piece.value[dbr.vi:])\n\t\t\t\tn += len(piece.value) - dbr.vi\n\t\t\t} else {\n\t\t\t\t// if dst does not have enough space to hold the whole piece\n\t\t\t\t// copy as much as we can and return\n\t\t\t\tleftSpaceOnDst := len(dst[n:])\n\t\t\t\tcopy(dst[n:], piece.value[dbr.vi:dbr.vi+leftSpaceOnDst])\n\t\t\t\tn += leftSpaceOnDst\n\t\t\t\tdbr.pi = i\n\t\t\t\tdbr.vi = dbr.vi + leftSpaceOnDst\n\t\t\t\treturn n, nil\n\t\t\t}\n\t\t} else {\n\t\t\t// if dst has enough space to hold the whole piece\n\t\t\t// copy the whole piece from where we left off\n\t\t\tif len(dst[n:]) >= piece.end-piece.start-dbr.vi {\n\t\t\t\tcopy(dst[n:n+piece.end-piece.start-dbr.vi], dbr.Body[piece.start+dbr.vi:piece.end])\n\t\t\t\tn += piece.end - piece.start - dbr.vi\n\t\t\t} else {\n\t\t\t\t// if dst does not have enough space to hold the whole piece\n\t\t\t\t// copy as much as we can and return\n\t\t\t\tleftSpaceOnDst := len(dst[n:])\n\t\t\t\tcopy(dst[n:], dbr.Body[piece.start+dbr.vi:piece.start+dbr.vi+leftSpaceOnDst])\n\t\t\t\tn += leftSpaceOnDst\n\t\t\t\tdbr.pi = i\n\t\t\t\tdbr.vi = dbr.vi + leftSpaceOnDst\n\t\t\t\treturn n, nil\n\t\t\t}\n\t\t}\n\t\t// jump to the next piece\n\t\tdbr.vi = 0\n\t}\n\n\t// check if we have reached the end of the body\n\tif readUntilPieceIndex == len(dbr.Pieces)-1 {\n\t\tpiece := dbr.Pieces[readUntilPieceIndex]\n\t\tif piece.injectable && readUntilPieceValueIndex == len(piece.value) {\n\t\t\tdbr.pi = readUntilPieceIndex + 1 // consumed the whole body\n\t\t\treturn n, io.EOF\n\t\t} else if !piece.injectable && readUntilPieceValueIndex == piece.end-piece.start {\n\t\t\tdbr.pi = readUntilPieceIndex + 1 // consumed the whole body\n\t\t\treturn n, io.EOF\n\t\t}\n\t}\n\n\t// readUntilPieceIndex is not the last piece, we fully filled dst\n\t// set where we left off\n\tdbr.pi = readUntilPieceIndex\n\tdbr.vi = readUntilPieceValueIndex\n\treturn n, nil\n}\n\ntype EnvironmentInjector struct {\n\tr   *regexp.Regexp\n\tjr  *regexp.Regexp\n\tdr  *regexp.Regexp\n\tjdr *regexp.Regexp\n\tmu  sync.Mutex\n}\n\nfunc (ei *EnvironmentInjector) Init() {\n\tei.r = regexp.MustCompile(regex.EnvironmentVariableRegex)\n\tei.jr = regexp.MustCompile(regex.JsonEnvironmentVarRegex)\n\tei.dr = regexp.MustCompile(regex.DynamicVariableRegex)\n\tei.jdr = regexp.MustCompile(regex.JsonDynamicVariableRegex)\n\trand.Seed(time.Now().UnixNano())\n}\n\nfunc truncateTag(tag string, rx string) string {\n\tif strings.EqualFold(rx, regex.EnvironmentVariableRegex) {\n\t\treturn tag[2 : len(tag)-2] // {{...}}\n\t} else if strings.EqualFold(rx, regex.JsonEnvironmentVarRegex) {\n\t\treturn tag[3 : len(tag)-3] // \"{{...}}\"\n\t} else if strings.EqualFold(rx, regex.DynamicVariableRegex) {\n\t\treturn tag[3 : len(tag)-2] // {{_...}}\n\t} else if strings.EqualFold(rx, regex.JsonDynamicVariableRegex) {\n\t\treturn tag[4 : len(tag)-3] //\"{{_...}}\"\n\t}\n\treturn \"\"\n}\n\nfunc (ei *EnvironmentInjector) InjectEnv(text string, envs map[string]interface{}) (string, error) {\n\terrors := []error{}\n\n\tinjectStrFunc := getInjectStrFunc(regex.EnvironmentVariableRegex, ei, envs, &errors)\n\tinjectToJsonByteFunc := getInjectJsonFunc(regex.JsonEnvironmentVarRegex, ei, envs, &errors)\n\n\t// json injection\n\tbText := StringToBytes(text)\n\tif json.Valid(bText) {\n\t\treplacedBytes := ei.jr.ReplaceAllFunc(bText, injectToJsonByteFunc)\n\t\tif len(errors) == 0 {\n\t\t\ttext = string(replacedBytes)\n\t\t} else {\n\t\t\treturn \"\", unifyErrors(errors)\n\t\t}\n\t}\n\n\t// string injection\n\treplaced := ei.r.ReplaceAllStringFunc(text, injectStrFunc)\n\tif len(errors) == 0 {\n\t\treturn replaced, nil\n\t}\n\n\treturn replaced, unifyErrors(errors)\n\n}\n\nfunc (ei *EnvironmentInjector) getEnv(envs map[string]interface{}, key string) (interface{}, error) {\n\tvar err error\n\tvar val interface{}\n\n\tpickRand := strings.HasPrefix(key, \"rand(\") && strings.HasSuffix(key, \")\")\n\tif pickRand {\n\t\tkey = key[5 : len(key)-1]\n\t}\n\n\tvar exists bool\n\tval, exists = envs[key]\n\n\tisOsEnv := strings.HasPrefix(key, \"$\")\n\n\tif isOsEnv {\n\t\tvarName := key[1:]\n\t\tval, exists = os.LookupEnv(varName)\n\t}\n\n\tif !exists {\n\t\terr = fmt.Errorf(\"env not found\")\n\t}\n\n\tif pickRand {\n\t\tswitch v := val.(type) {\n\t\tcase []interface{}:\n\t\t\tval = v[rand.Intn(len(v))]\n\t\tcase []string:\n\t\t\tval = v[rand.Intn(len(v))]\n\t\tcase []bool:\n\t\t\tval = v[rand.Intn(len(v))]\n\t\tcase []int:\n\t\t\tval = v[rand.Intn(len(v))]\n\t\tcase []float64:\n\t\t\tval = v[rand.Intn(len(v))]\n\t\tdefault:\n\t\t\terr = fmt.Errorf(\"can not perform rand() operation on non-array value\")\n\t\t}\n\t}\n\n\treturn val, err\n}\n\nfunc unifyErrors(errors []error) error {\n\tsb := strings.Builder{}\n\n\tfor _, err := range errors {\n\t\tsb.WriteString(err.Error())\n\t}\n\n\treturn fmt.Errorf(\"%s\", sb.String())\n}\n\nfunc StringToBytes(s string) (b []byte) {\n\tstringHeader := (*reflect.StringHeader)(unsafe.Pointer(&s))\n\tsliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&b))\n\tsliceHeader.Data = stringHeader.Data\n\tsliceHeader.Len = len(s)\n\tsliceHeader.Cap = len(s)\n\treturn b\n}\n\nfunc getInjectStrFunc(rx string,\n\tei *EnvironmentInjector,\n\tenvs map[string]interface{},\n\terrors *[]error,\n) func(string) string {\n\treturn func(s string) string {\n\t\tvar truncated string\n\t\tvar env interface{}\n\t\tvar err error\n\n\t\ttruncated = truncateTag(string(s), rx)\n\n\t\tif rx == regex.EnvironmentVariableRegex {\n\t\t\tenv, err = ei.getEnv(envs, truncated)\n\t\t} else if rx == regex.DynamicVariableRegex {\n\t\t\tenv, err = ei.getFakeData(truncated)\n\t\t} else {\n\t\t\t// this should never happen\n\t\t\tpanic(\"invalid regex\")\n\t\t}\n\n\t\tif err == nil {\n\t\t\tswitch env.(type) {\n\t\t\tcase string:\n\t\t\t\treturn env.(string)\n\t\t\tcase []byte:\n\t\t\t\treturn string(env.([]byte))\n\t\t\tcase int64:\n\t\t\t\treturn fmt.Sprintf(\"%d\", env)\n\t\t\tcase int:\n\t\t\t\treturn fmt.Sprintf(\"%d\", env)\n\t\t\tcase float64:\n\t\t\t\treturn fmt.Sprintf(\"%g\", env) // %g it is the smallest number of digits necessary to identify the value uniquely\n\t\t\tcase bool:\n\t\t\t\treturn fmt.Sprintf(\"%t\", env)\n\t\t\tdefault:\n\t\t\t\treturn fmt.Sprint(env)\n\t\t\t}\n\t\t}\n\t\t*errors = append(*errors,\n\t\t\tfmt.Errorf(\"%s could not be found in vars global and extracted from previous steps\", truncated))\n\t\treturn s\n\t}\n}\n\nfunc getInjectJsonFunc(rx string,\n\tei *EnvironmentInjector,\n\tenvs map[string]interface{},\n\terrors *[]error,\n) func(s []byte) []byte {\n\treturn func(s []byte) []byte {\n\t\tvar truncated string\n\t\tvar env interface{}\n\t\tvar err error\n\n\t\ttruncated = truncateTag(string(s), rx)\n\t\tif rx == regex.JsonDynamicVariableRegex {\n\t\t\tenv, err = ei.getFakeData(truncated)\n\t\t} else if rx == regex.JsonEnvironmentVarRegex {\n\t\t\tenv, err = ei.getEnv(envs, truncated)\n\t\t} else {\n\t\t\t// this should never happen\n\t\t\tpanic(\"invalid regex\")\n\t\t}\n\n\t\tif err == nil {\n\t\t\tmEnv, err := json.Marshal(env)\n\t\t\tif err == nil {\n\t\t\t\treturn mEnv\n\t\t\t}\n\t\t}\n\t\t*errors = append(*errors,\n\t\t\tfmt.Errorf(\"%s could not be found in vars global and extracted from previous steps\", truncated))\n\t\treturn s\n\t}\n}\n\ntype EnvMatch struct {\n\tregex string // matched regex\n\tfound []int  // indexes of match\n}\ntype EnvMatchSlice []EnvMatch\n\nfunc (a EnvMatchSlice) Len() int           { return len(a) }\nfunc (a EnvMatchSlice) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }\nfunc (a EnvMatchSlice) Less(i, j int) bool { return a[i].found[0] < a[j].found[0] }\n\nfunc (ei *EnvironmentInjector) GenerateBodyPieces(body string, envs map[string]interface{}) []BodyPiece {\n\t// generate body pieces\n\tpieces := make([]BodyPiece, 0)\n\tmatches := EnvMatchSlice{}\n\n\tbText := StringToBytes(body)\n\tif json.Valid(bText) {\n\t\tjsonEnvMatches := ei.jr.FindAllStringIndex(body, -1)\n\t\tfor _, match := range jsonEnvMatches {\n\t\t\tmatches = append(matches, EnvMatch{\n\t\t\t\tregex: regex.JsonEnvironmentVarRegex,\n\t\t\t\tfound: match,\n\t\t\t})\n\t\t}\n\n\t\tenvsInJsonStringMatches := ei.r.FindAllStringIndex(body, -1)\n\t\tfor _, match := range envsInJsonStringMatches {\n\t\t\t// exclude ones that are already matched as json envs\n\t\t\talreadyMatched := false\n\t\t\tfor _, jsonMatch := range jsonEnvMatches {\n\t\t\t\tif match[0] >= jsonMatch[0] && match[1] <= jsonMatch[1] {\n\t\t\t\t\talreadyMatched = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif alreadyMatched {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tmatches = append(matches, EnvMatch{\n\t\t\t\tregex: regex.EnvironmentVariableRegex,\n\t\t\t\tfound: match,\n\t\t\t})\n\t\t}\n\n\t\tjsonDynamicMatches := ei.jdr.FindAllStringIndex(body, -1)\n\t\tfor _, match := range jsonDynamicMatches {\n\t\t\tmatches = append(matches, EnvMatch{\n\t\t\t\tregex: regex.JsonDynamicVariableRegex,\n\t\t\t\tfound: match,\n\t\t\t})\n\t\t}\n\n\t\tdynamicEnvsInJsonStringMatches := ei.dr.FindAllStringIndex(body, -1)\n\t\tfor _, match := range dynamicEnvsInJsonStringMatches {\n\t\t\t// exclude ones that are already matched as json dynamic envs\n\t\t\talreadyMatched := false\n\t\t\tfor _, jsonMatch := range jsonDynamicMatches {\n\t\t\t\tif match[0] >= jsonMatch[0] && match[1] <= jsonMatch[1] {\n\t\t\t\t\talreadyMatched = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif alreadyMatched {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tmatches = append(matches, EnvMatch{\n\t\t\t\tregex: regex.DynamicVariableRegex,\n\t\t\t\tfound: match,\n\t\t\t})\n\t\t}\n\t} else {\n\t\t// not json\n\t\tenvMatches := ei.r.FindAllStringIndex(body, -1)\n\t\tfor _, match := range envMatches {\n\t\t\tmatches = append(matches, EnvMatch{\n\t\t\t\tregex: regex.EnvironmentVariableRegex,\n\t\t\t\tfound: match,\n\t\t\t})\n\t\t}\n\n\t\tdynamicMathces := ei.dr.FindAllStringIndex(body, -1)\n\t\tfor _, match := range dynamicMathces {\n\t\t\tmatches = append(matches, EnvMatch{\n\t\t\t\tregex: regex.DynamicVariableRegex,\n\t\t\t\tfound: match,\n\t\t\t})\n\t\t}\n\t}\n\n\tsort.Sort(matches) // by start index\n\n\terrors := make([]error, 0)\n\toff := 0\n\n\tfor _, match := range matches {\n\t\tr := match.regex\n\t\tstart := match.found[0]\n\t\tend := match.found[1]\n\n\t\tif start > off {\n\t\t\tpieces = append(pieces, BodyPiece{\n\t\t\t\tstart:      off,\n\t\t\t\tend:        start,\n\t\t\t\tinjectable: false,\n\t\t\t\t// value:      body[off:match[0]], // no need to put values in here\n\t\t\t})\n\t\t}\n\n\t\tf := getInjectStrFunc(regex.EnvironmentVariableRegex, ei, envs, &errors)\n\t\tfd := getInjectStrFunc(regex.DynamicVariableRegex, ei, nil, &errors)\n\n\t\tjf := getInjectJsonFunc(regex.JsonEnvironmentVarRegex, ei, envs, &errors)\n\t\tjfd := getInjectJsonFunc(regex.JsonDynamicVariableRegex, ei, nil, &errors)\n\n\t\tgetValue := func(s string, r string) string {\n\t\t\tif r == regex.JsonEnvironmentVarRegex {\n\t\t\t\treturn string(jf(StringToBytes(s)))\n\t\t\t} else if r == regex.JsonDynamicVariableRegex {\n\t\t\t\treturn string(jfd(StringToBytes(s)))\n\t\t\t} else if r == regex.EnvironmentVariableRegex {\n\t\t\t\treturn f(s)\n\t\t\t} else if r == regex.DynamicVariableRegex {\n\t\t\t\treturn fd(s)\n\t\t\t}\n\t\t\treturn s // this should never happen\n\t\t}\n\n\t\tval := getValue(body[start:end], r)\n\n\t\tpieces = append(pieces, BodyPiece{\n\t\t\tstart:      start,\n\t\t\tend:        end,\n\t\t\tinjectable: true,\n\t\t\tvalue:      val, // only put values in injected pieces\n\t\t})\n\n\t\toff = end\n\t}\n\n\tif off < len(body) {\n\t\tpieces = append(pieces, BodyPiece{\n\t\t\tstart:      off,\n\t\t\tend:        len(body),\n\t\t\tinjectable: false,\n\t\t\t// value:      body[off:],\n\t\t})\n\t}\n\n\treturn pieces\n}\n\nfunc GetContentLength(pieces []BodyPiece) int {\n\tvar contentLength int\n\tfor _, piece := range pieces {\n\t\tif piece.injectable {\n\t\t\tcontentLength += len(piece.value)\n\t\t} else {\n\t\t\tcontentLength += piece.end - piece.start\n\t\t}\n\t}\n\treturn contentLength\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/scripting/injection/environment_dynamic.go",
    "content": "package injection\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"reflect\"\n\n\t\"go.ddosify.com/ddosify/core/types/regex\"\n)\n\nfunc (ei *EnvironmentInjector) InjectDynamic(text string) (string, error) {\n\terrors := []error{}\n\n\tinjectStrFunc := getInjectStrFunc(regex.DynamicVariableRegex, ei, nil, &errors)\n\tinjectToJsonByteFunc := getInjectJsonFunc(regex.JsonDynamicVariableRegex, ei, nil, &errors)\n\n\t// json injection\n\tbText := StringToBytes(text)\n\tif json.Valid(bText) {\n\t\tif ei.jr.Match(bText) {\n\t\t\treplacedBytes := ei.jdr.ReplaceAllFunc(bText, injectToJsonByteFunc)\n\t\t\treturn string(replacedBytes), nil\n\t\t}\n\t}\n\n\t// string injection\n\treplaced := ei.dr.ReplaceAllStringFunc(text, injectStrFunc)\n\tif len(errors) == 0 {\n\t\treturn replaced, nil\n\t}\n\n\treturn replaced, unifyErrors(errors)\n\n}\n\nfunc (ei *EnvironmentInjector) getFakeData(key string) (interface{}, error) {\n\tvar fakeFunc interface{}\n\tvar keyExists bool\n\tif fakeFunc, keyExists = dynamicFakeDataMap[key]; !keyExists {\n\t\treturn nil, fmt.Errorf(\"%s is not a valid dynamic variable\", key)\n\t}\n\n\tpreventRaceOnRandomFunc := func(fakeFunc interface{}) interface{} {\n\t\tei.mu.Lock()\n\t\tdefer ei.mu.Unlock()\n\t\treturn reflect.ValueOf(fakeFunc).Call(nil)[0].Interface()\n\t}\n\n\treturn preventRaceOnRandomFunc(fakeFunc), nil\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/scripting/injection/environment_test.go",
    "content": "package injection\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/google/uuid\"\n)\n\nfunc TestInjectionRegexReplacer(t *testing.T) {\n\treplacer := EnvironmentInjector{}\n\treplacer.Init()\n\t// injection to text target\n\ttargetURL := \"{{target}}/{{path}}/{{id}}/{{boolField}}/{{floatField}}/{{uuidField}}\"\n\tuuid := uuid.New()\n\tstringEnvs := map[string]interface{}{\n\t\t\"target\":     \"https://app.ddosify.com\",\n\t\t\"path\":       \"load/test-results\",\n\t\t\"id\":         234,\n\t\t\"boolField\":  true,\n\t\t\"floatField\": 22.3,\n\t\t\"uuidField\":  uuid,\n\t}\n\texpectedURL := \"https://app.ddosify.com/load/test-results/234/true/22.3/\" + uuid.String()\n\n\t// injection to flat json target\n\ttargetJson := `{\n\t\t\"{{a}}\": 5,\n\t\t\"name\": \"{{xyz}}\",\n\t\t\"numbers\": \"{{listOfNumbers}}\",\n\t\t\"chars\": \"{{object}}\",\n\t\t\"boolField\": \"{{boolEnv}}\",\n\t\t\"intField\": \"{{intEnv}}\",\n\t\t\"floatField\": \"{{floatEnv}}\"\n\t}`\n\n\tjsonEnvs := map[string]interface{}{\n\t\t\"a\":             \"age\",\n\t\t\"xyz\":           \"kenan\",\n\t\t\"listOfNumbers\": []float64{23, 44, 11},\n\t\t\"object\":        map[string]interface{}{\"abc\": []string{\"a\", \"b\", \"c\"}},\n\t\t\"boolEnv\":       false,\n\t\t\"intEnv\":        52,\n\t\t\"floatEnv\":      52.24,\n\t}\n\n\texpectedJsonPayload := `{\n\t\t\"age\": 5,\n\t\t\"name\": \"kenan\",\n\t\t\"numbers\": [23,44,11],\n\t\t\"chars\": {\"abc\":[\"a\",\"b\",\"c\"]},\n\t\t\"boolField\": false,\n\t\t\"intField\": 52,\n\t\t\"floatField\": 52.24\n\t}`\n\n\t// injection to recusive json target\n\tjsonRecursivePaylaod := `{\n\t\t\"chars\": \"{{object}}\",\n\t\t\"nc\": {\"max\": \"{{numVerstappen}}\"}\n\t}`\n\n\trecursiveJsonEnvs := map[string]interface{}{\n\t\t\"object\":        map[string]interface{}{\"abc\": map[string]interface{}{\"a\": 1, \"b\": 1, \"c\": 1}},\n\t\t\"numVerstappen\": 33,\n\t}\n\n\texpectedRecursiveJsonPayload := `{\n\t\t\"chars\": {\"abc\":{\"a\":1,\"b\":1,\"c\":1}},\n\t\t\"nc\": {\"max\": 33}\n\t}`\n\n\t// Sub Tests\n\ttests := []struct {\n\t\tname     string\n\t\ttarget   string\n\t\texpected interface{}\n\t\tenvs     map[string]interface{}\n\t}{\n\t\t{\"String\", targetURL, expectedURL, stringEnvs},\n\t\t{\"JSONFlat\", targetJson, expectedJsonPayload, jsonEnvs},\n\t\t{\"JSONRecursive\", jsonRecursivePaylaod, expectedRecursiveJsonPayload, recursiveJsonEnvs},\n\t}\n\n\tfor _, test := range tests {\n\t\ttf := func(t *testing.T) {\n\t\t\tbuff, err := replacer.InjectEnv(test.target, test.envs)\n\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"injection failed %v\", err)\n\t\t\t}\n\n\t\t\tif !reflect.DeepEqual(buff, test.expected) {\n\t\t\t\tt.Errorf(\"injection unsuccessful, expected : %s, got :%s\", test.expected, buff)\n\t\t\t}\n\t\t}\n\t\tt.Run(test.name, tf)\n\t}\n}\n\nfunc ExampleEnvironmentInjector() {\n\treplacer := EnvironmentInjector{}\n\treplacer.Init()\n\n\tres, err := replacer.InjectDynamic(\"{{_randomInt}}\")\n\tif err == nil {\n\t\tfmt.Println(res)\n\t}\n}\n\nfunc TestRandomInjectionStringSlice(t *testing.T) {\n\treplacer := EnvironmentInjector{}\n\treplacer.Init()\n\n\tvals := []string{\n\t\t\"Kenan\", \"Kursat\", \"Fatih\",\n\t}\n\n\tenvs := map[string]interface{}{\n\t\t\"vals\": vals,\n\t}\n\n\tval, err := replacer.getEnv(envs, \"rand(vals)\")\n\tif err != nil {\n\t\tt.Errorf(\"%v\", err)\n\t}\n\n\tfound := false\n\n\tfor _, n := range vals {\n\t\tif reflect.DeepEqual(val, n) {\n\t\t\tfound = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif !found {\n\t\tt.Errorf(\"rand method did not return one of the expecteds\")\n\t}\n}\n\nfunc TestRandomInjectionBoolSlice(t *testing.T) {\n\treplacer := EnvironmentInjector{}\n\treplacer.Init()\n\n\tvals := []bool{\n\t\ttrue, false, true,\n\t}\n\n\tenvs := map[string]interface{}{\n\t\t\"vals\": vals,\n\t}\n\n\tval, err := replacer.getEnv(envs, \"rand(vals)\")\n\tif err != nil {\n\t\tt.Errorf(\"%v\", err)\n\t}\n\n\tfound := false\n\n\tfor _, n := range vals {\n\t\tif reflect.DeepEqual(val, n) {\n\t\t\tfound = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif !found {\n\t\tt.Errorf(\"rand method did not return one of the expecteds\")\n\t}\n\n}\n\nfunc TestRandomInjectionIntSlice(t *testing.T) {\n\treplacer := EnvironmentInjector{}\n\treplacer.Init()\n\n\tvals := []int{\n\t\t3, 55, 42,\n\t}\n\n\tenvs := map[string]interface{}{\n\t\t\"vals\": vals,\n\t}\n\n\tval, err := replacer.getEnv(envs, \"rand(vals)\")\n\tif err != nil {\n\t\tt.Errorf(\"%v\", err)\n\t}\n\n\tfound := false\n\n\tfor _, n := range vals {\n\t\tif reflect.DeepEqual(val, n) {\n\t\t\tfound = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif !found {\n\t\tt.Errorf(\"rand method did not return one of the expecteds\")\n\t}\n\n}\n\nfunc TestRandomInjectionFloat64Slice(t *testing.T) {\n\treplacer := EnvironmentInjector{}\n\treplacer.Init()\n\n\tvals := []float64{\n\t\t3.3, 55.23, 42.1,\n\t}\n\n\tenvs := map[string]interface{}{\n\t\t\"vals\": vals,\n\t}\n\n\tval, err := replacer.getEnv(envs, \"rand(vals)\")\n\tif err != nil {\n\t\tt.Errorf(\"%v\", err)\n\t}\n\n\tfound := false\n\n\tfor _, n := range vals {\n\t\tif reflect.DeepEqual(val, n) {\n\t\t\tfound = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif !found {\n\t\tt.Errorf(\"rand method did not return one of the expecteds\")\n\t}\n\n}\n\nfunc TestRandomInjectionInterfaceSlice(t *testing.T) {\n\treplacer := EnvironmentInjector{}\n\treplacer.Init()\n\n\tvals := []interface{}{\n\t\tmap[string]int{\"s\": 33},\n\t\t[]string{\"v\", \"c\"},\n\t}\n\n\tenvs := map[string]interface{}{\n\t\t\"vals\": vals,\n\t}\n\n\tval, err := replacer.getEnv(envs, \"rand(vals)\")\n\tif err != nil {\n\t\tt.Errorf(\"%v\", err)\n\t}\n\n\tfound := false\n\n\tfor _, n := range vals {\n\t\tif reflect.DeepEqual(val, n) {\n\t\t\tfound = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif !found {\n\t\tt.Errorf(\"rand method did not return one of the expecteds\")\n\t}\n\n}\n\nfunc TestConcatVariablesAndInjectAsTyped(t *testing.T) {\n\treplacer := EnvironmentInjector{}\n\treplacer.Init()\n\t// injection to json payload\n\tpayload := `{\"a\":[\"--{{number_int}}--{{number_string}}--\",\"23\",\"{{number_int}}\"]}`\n\n\tenvs := map[string]interface{}{\n\t\t\"number_int\":    1,\n\t\t\"number_string\": \"2\",\n\t}\n\n\texpectedPayload := `{\"a\":[\"--1--2--\",\"23\",1]}`\n\n\texpected := &bytes.Buffer{}\n\tif err := json.Compact(expected, []byte(expectedPayload)); err != nil {\n\t\tpanic(err)\n\t}\n\n\tpieces := replacer.GenerateBodyPieces(payload, envs)\n\tr := DdosifyBodyReader{\n\t\tBody:   payload,\n\t\tPieces: pieces,\n\t}\n\n\tres := make([]byte, 100)\n\tn, err := r.Read(res)\n\n\tif err != io.EOF {\n\t\tt.Errorf(\"injection unsuccessful, expected : %s, got :%s\", expected.String(), res)\n\t}\n\n\tif !reflect.DeepEqual(string(res[0:n]), expected.String()) {\n\t\tt.Errorf(\"injection unsuccessful, expected : %s, got :%s\", expected.String(), res)\n\t}\n\n}\n\nfunc TestConcatVariablesAndInjectAsTyped2(t *testing.T) {\n\treplacer := EnvironmentInjector{}\n\treplacer.Init()\n\t// injection to json payload\n\tpayload := `{\"a\":[\"--{{number_int}}{{number_string}}--\",\"23\",\"{{number_int}}\"]}`\n\n\tenvs := map[string]interface{}{\n\t\t\"number_int\":    1,\n\t\t\"number_string\": \"2\",\n\t}\n\n\texpectedPayload := `{\"a\":[\"--12--\",\"23\",1]}`\n\n\texpected := &bytes.Buffer{}\n\tif err := json.Compact(expected, []byte(expectedPayload)); err != nil {\n\t\tpanic(err)\n\t}\n\n\tpieces := replacer.GenerateBodyPieces(payload, envs)\n\tr := DdosifyBodyReader{\n\t\tBody:   payload,\n\t\tPieces: pieces,\n\t}\n\n\tres := make([]byte, 100)\n\tn, err := r.Read(res)\n\n\tif err != io.EOF {\n\t\tt.Errorf(\"injection unsuccessful, expected : %s, got :%s\", expected.String(), res)\n\t}\n\n\tif !reflect.DeepEqual(string(res[0:n]), expected.String()) {\n\t\tt.Errorf(\"injection unsuccessful, expected : %s, got :%s\", expected.String(), res)\n\t}\n\n}\n\nfunc TestConcatVariablesAndInjectAsTypedDynamic(t *testing.T) {\n\treplacer := EnvironmentInjector{}\n\treplacer.Init()\n\t// injection to json payload\n\tpayload := `{\"a\":[\"--{{_randomInt}}--{{number_string}}--\",\"23\",\"{{number_int}}\"]}`\n\n\tenvs := map[string]interface{}{\n\t\t\"number_int\":    1,\n\t\t\"number_string\": \"2\",\n\t}\n\n\tdynamicInjectFailPayload := `{\"a\":[\"--{{_randomInt}}--2--\",\"23\",1]}`\n\n\tnotExpected := &bytes.Buffer{}\n\tif err := json.Compact(notExpected, []byte(dynamicInjectFailPayload)); err != nil {\n\t\tpanic(err)\n\t}\n\n\tpieces := replacer.GenerateBodyPieces(payload, envs)\n\tr := DdosifyBodyReader{\n\t\tBody:   payload,\n\t\tPieces: pieces,\n\t}\n\n\tres := make([]byte, 100)\n\tn, err := r.Read(res)\n\n\tif err != io.EOF {\n\t\tt.Error(err)\n\t}\n\n\tif reflect.DeepEqual(string(res[0:n]), notExpected.String()) {\n\t\tt.Errorf(\"injection unsuccessful, not expected : %s, got :%s\", notExpected.String(), res)\n\t}\n\n}\n\nfunc TestInvalidDynamicVarInjection(t *testing.T) {\n\ttext := \"http://test.com/{{_invalidVar}}\"\n\n\treplacer := EnvironmentInjector{}\n\treplacer.Init()\n\n\t_, err := replacer.InjectDynamic(text)\n\tif err == nil {\n\t\tt.Errorf(\"expected error not found\")\n\t}\n}\n\nfunc TestOSEnvInjection(t *testing.T) {\n\treplacer := EnvironmentInjector{}\n\treplacer.Init()\n\n\tactualEnvVal := os.Getenv(\"PATH\")\n\n\tenvs := map[string]interface{}{\n\t\t\"key1\": \"val1\",\n\t}\n\n\tval, err := replacer.getEnv(envs, \"$PATH\")\n\tif err != nil {\n\t\tt.Errorf(\"%v\", err)\n\t}\n\n\tfound := false\n\n\tif reflect.DeepEqual(actualEnvVal, val) {\n\t\tfound = true\n\t}\n\n\tif !found {\n\t\tt.Errorf(\"expected os env val not found\")\n\t}\n\n}\n\nfunc TestDdosifyBodyReader(t *testing.T) {\n\tbody := \"test{{env1}}xyz{{env2}}\" // only for env vars for now\n\n\tei := EnvironmentInjector{}\n\tei.Init()\n\n\tenvs := make(map[string]interface{})\n\tenvs[\"env1\"] = \"123\"\n\tenvs[\"env2\"] = \"456\"\n\n\tpieces := ei.GenerateBodyPieces(body, envs)\n\n\tcustomReader := DdosifyBodyReader{\n\t\tBody:   body,\n\t\tPieces: pieces,\n\t}\n\n\tbyteArray := make([]byte, GetContentLength(pieces))\n\tn, err := customReader.Read(byteArray)\n\n\t// expect EOF\n\n\tif err != io.EOF {\n\t\tt.Errorf(\"expected EOF, got %v\", err)\n\t}\n\n\tif n != GetContentLength(pieces) {\n\t\tt.Errorf(\"expected to read %d bytes, read %d\", GetContentLength(pieces), n)\n\t}\n\n\tif string(byteArray) != \"test123xyz456\" {\n\t\tt.Errorf(\"expected test123xyz456, got %s\", string(byteArray))\n\t}\n}\n\nfunc TestDdosifyBodyReaderSplitted(t *testing.T) {\n\tbody := \"test{{env1}}xyz{{env2}}\" // only for env vars for now\n\n\tei := EnvironmentInjector{}\n\tei.Init()\n\n\tenvs := make(map[string]interface{})\n\tenvs[\"env1\"] = \"123\"\n\tenvs[\"env2\"] = \"456\"\n\n\tpieces := ei.GenerateBodyPieces(body, envs)\n\n\tcustomReader := DdosifyBodyReader{\n\t\tBody:   body,\n\t\tPieces: pieces,\n\t}\n\n\tfirstPart := make([]byte, GetContentLength(pieces)-5)\n\tn, err := customReader.Read(firstPart)\n\n\t// do not expect EOF here\n\tif err != nil {\n\t\tt.Errorf(\"expected no error, got %v\", err)\n\t}\n\n\tif n != GetContentLength(pieces)-5 {\n\t\tt.Errorf(\"expected to read %d bytes, read %d\", GetContentLength(pieces)-5, n)\n\t}\n\n\tif string(firstPart) != \"test123x\" {\n\t\tt.Errorf(\"expected test123x, got %s\", string(firstPart))\n\t}\n\n\tsecondPart := make([]byte, 5)\n\tn, err = customReader.Read(secondPart)\n\n\t// expect EOF here\n\n\tif err != io.EOF {\n\t\tt.Errorf(\"expected EOF, got %v\", err)\n\t}\n\n\tif n != 5 {\n\t\tt.Errorf(\"expected to read %d bytes, read %d\", 5, n)\n\t}\n\n\tif string(secondPart) != \"yz456\" {\n\t\tt.Errorf(\"expected yz456, got %s\", string(secondPart))\n\t}\n}\n\nfunc TestDdosifyBodyReaderSplittedPiece(t *testing.T) {\n\tbody := \"test{{env1}}xyz{{env2}}\" // only for env vars for now\n\n\tei := EnvironmentInjector{}\n\tei.Init()\n\n\tenvs := make(map[string]interface{})\n\tenvs[\"env1\"] = \"123\"\n\tenvs[\"env2\"] = \"456\"\n\n\tpieces := ei.GenerateBodyPieces(body, envs)\n\n\tcustomReader := DdosifyBodyReader{\n\t\tBody:   body,\n\t\tPieces: pieces,\n\t}\n\n\tfirstPart := make([]byte, 2)\n\tn, err := customReader.Read(firstPart)\n\n\t// do not expect EOF here\n\tif err != nil {\n\t\tt.Errorf(\"expected no error, got %v\", err)\n\t}\n\n\tif n != 2 {\n\t\tt.Errorf(\"expected to read %d bytes, read %d\", GetContentLength(pieces)-5, n)\n\t}\n\n\tif string(firstPart) != \"te\" {\n\t\tt.Errorf(\"expected te, got %s\", string(firstPart))\n\t}\n\n\tsecondPart := make([]byte, GetContentLength(pieces)-2)\n\tn, err = customReader.Read(secondPart)\n\n\t// expect EOF here\n\n\tif err != io.EOF {\n\t\tt.Errorf(\"expected EOF, got %v\", err)\n\t}\n\n\tif n != GetContentLength(pieces)-2 {\n\t\tt.Errorf(\"expected to read %d bytes, read %d\", GetContentLength(pieces)-2, n)\n\t}\n\n\tif string(secondPart) != \"st123xyz456\" {\n\t\tt.Errorf(\"expected st123xyz456, got %s\", string(secondPart))\n\t}\n}\n\nfunc TestDdosifyBodyReaderSplittedPiece2(t *testing.T) {\n\tbody := \"test{{env1}}xyz{{env2}}\" // only for env vars for now\n\n\tei := EnvironmentInjector{}\n\tei.Init()\n\n\tenvs := make(map[string]interface{})\n\tenvs[\"env1\"] = \"123\"\n\tenvs[\"env2\"] = \"456\"\n\n\tpieces := ei.GenerateBodyPieces(body, envs)\n\n\tcustomReader := DdosifyBodyReader{\n\t\tBody:   body,\n\t\tPieces: pieces,\n\t}\n\n\tfirstPart := make([]byte, 2)\n\tn, err := customReader.Read(firstPart)\n\n\t// do not expect EOF here\n\tif err != nil {\n\t\tt.Errorf(\"expected no error, got %v\", err)\n\t}\n\n\tif n != 2 {\n\t\tt.Errorf(\"expected to read %d bytes, read %d\", GetContentLength(pieces)-5, n)\n\t}\n\n\tif string(firstPart) != \"te\" {\n\t\tt.Errorf(\"expected te, got %s\", string(firstPart))\n\t}\n\n\tsecondPart := make([]byte, GetContentLength(pieces))\n\tn, err = customReader.Read(secondPart)\n\n\t// expect EOF here\n\tif err != io.EOF {\n\t\tt.Errorf(\"expected EOF, got %v\", err)\n\t}\n\n\tif n != GetContentLength(pieces)-2 {\n\t\tt.Errorf(\"expected to read %d bytes, read %d\", GetContentLength(pieces)-2, n)\n\t}\n\n\tif string(secondPart[0:n]) != \"st123xyz456\" {\n\t\tt.Errorf(\"expected st123xyz456, got %s\", string(secondPart))\n\t}\n\n\t// try to read again, should be EOF\n\temptyPart := make([]byte, GetContentLength(pieces))\n\tn, err = customReader.Read(emptyPart)\n\n\tif n != 0 {\n\t\tt.Errorf(\"expected to read %d bytes, read %d\", 0, n)\n\t}\n\n\tif err != io.EOF {\n\t\tt.Errorf(\"expected EOF, got %v\", err)\n\t}\n\n}\n\nfunc TestDdosifyBodyReaderSplittedPiece3(t *testing.T) {\n\tbody := \"test{{env1}}xyz\" // only for env vars for now\n\n\tei := EnvironmentInjector{}\n\tei.Init()\n\n\tenvs := make(map[string]interface{})\n\tenvs[\"env1\"] = \"123\"\n\n\tpieces := ei.GenerateBodyPieces(body, envs)\n\n\tcustomReader := DdosifyBodyReader{\n\t\tBody:   body,\n\t\tPieces: pieces,\n\t}\n\n\tfirstPart := make([]byte, 2)\n\tn, err := customReader.Read(firstPart)\n\n\t// do not expect EOF here\n\tif err != nil {\n\t\tt.Errorf(\"expected no error, got %v\", err)\n\t}\n\n\tif n != 2 {\n\t\tt.Errorf(\"expected to read %d bytes, read %d\", GetContentLength(pieces)-5, n)\n\t}\n\n\tif string(firstPart) != \"te\" {\n\t\tt.Errorf(\"expected te, got %s\", string(firstPart))\n\t}\n\n\tsecondPart := make([]byte, 5)\n\tn, err = customReader.Read(secondPart)\n\n\t// fully read the second part, no EOF\n\tif err == io.EOF {\n\t\tt.Errorf(\"expected no EOF, got %v\", err)\n\t}\n\n\tif n != 5 {\n\t\tt.Errorf(\"expected to read %d bytes, read %d\", 5, n)\n\t}\n\n\tif string(secondPart[0:n]) != \"st123\" {\n\t\tt.Errorf(\"expected st123, got %s\", string(secondPart))\n\t}\n\n\t// try to read again, should be EOF\n\tlastPart := make([]byte, GetContentLength(pieces))\n\tn, err = customReader.Read(lastPart)\n\n\tif n != 3 {\n\t\tt.Errorf(\"expected to read %d bytes, read %d\", 0, n)\n\t}\n\n\tif err != io.EOF {\n\t\tt.Errorf(\"expected EOF, got %v\", err)\n\t}\n\n}\n\nfunc TestDdosifyBodyReaderSplittedPiece4(t *testing.T) {\n\tbody := \"test{{env1}}{{env2}}\" // only for env vars for now\n\n\tei := EnvironmentInjector{}\n\tei.Init()\n\n\tenvs := make(map[string]interface{})\n\tenvs[\"env1\"] = \"123\"\n\tenvs[\"env2\"] = \"456\"\n\n\tpieces := ei.GenerateBodyPieces(body, envs)\n\n\tcustomReader := DdosifyBodyReader{\n\t\tBody:   body,\n\t\tPieces: pieces,\n\t}\n\n\tfirstPart := make([]byte, 2)\n\tn, err := customReader.Read(firstPart)\n\n\t// do not expect EOF here\n\tif err != nil {\n\t\tt.Errorf(\"expected no error, got %v\", err)\n\t}\n\n\tif n != 2 {\n\t\tt.Errorf(\"expected to read %d bytes, read %d\", GetContentLength(pieces)-5, n)\n\t}\n\n\tif string(firstPart) != \"te\" {\n\t\tt.Errorf(\"expected te, got %s\", string(firstPart))\n\t}\n\n\tsecondPart := make([]byte, 5)\n\tn, err = customReader.Read(secondPart)\n\n\t// fully read the second part, no EOF\n\tif err == io.EOF {\n\t\tt.Errorf(\"expected no EOF, got %v\", err)\n\t}\n\n\tif n != 5 {\n\t\tt.Errorf(\"expected to read %d bytes, read %d\", 5, n)\n\t}\n\n\tif string(secondPart[0:n]) != \"st123\" {\n\t\tt.Errorf(\"expected st123, got %s\", string(secondPart))\n\t}\n\n\t// try to read again, should be EOF\n\tlastPart := make([]byte, GetContentLength(pieces))\n\tn, err = customReader.Read(lastPart)\n\n\tif n != 3 {\n\t\tt.Errorf(\"expected to read %d bytes, read %d\", 0, n)\n\t}\n\n\tif err != io.EOF {\n\t\tt.Errorf(\"expected EOF, got %v\", err)\n\t}\n\n}\n\nfunc TestDdosifyBodyReaderSplittedPiece5(t *testing.T) {\n\tbody := \"test{{env1}}xyz\" // only for env vars for now\n\n\tei := EnvironmentInjector{}\n\tei.Init()\n\n\tenvs := make(map[string]interface{})\n\tenvs[\"env1\"] = \"123\"\n\n\tpieces := ei.GenerateBodyPieces(body, envs)\n\n\tcustomReader := DdosifyBodyReader{\n\t\tBody:   body,\n\t\tPieces: pieces,\n\t}\n\n\tfirstPart := make([]byte, 2)\n\tn, err := customReader.Read(firstPart)\n\n\t// do not expect EOF here\n\tif err != nil {\n\t\tt.Errorf(\"expected no error, got %v\", err)\n\t}\n\n\tif n != 2 {\n\t\tt.Errorf(\"expected to read %d bytes, read %d\", GetContentLength(pieces)-5, n)\n\t}\n\n\tif string(firstPart) != \"te\" {\n\t\tt.Errorf(\"expected te, got %s\", string(firstPart))\n\t}\n\n\tsecondPart := make([]byte, 5)\n\tn, err = customReader.Read(secondPart)\n\n\t// fully read the second part, no EOF\n\tif err == io.EOF {\n\t\tt.Errorf(\"expected no EOF, got %v\", err)\n\t}\n\n\tif n != 5 {\n\t\tt.Errorf(\"expected to read %d bytes, read %d\", 5, n)\n\t}\n\n\tif string(secondPart[0:n]) != \"st123\" {\n\t\tt.Errorf(\"expected st123, got %s\", string(secondPart))\n\t}\n\n\t// try to read again, should be EOF\n\tlastPart := make([]byte, 3)\n\tn, err = customReader.Read(lastPart)\n\n\tif n != 3 {\n\t\tt.Errorf(\"expected to read %d bytes, read %d\", 0, n)\n\t}\n\n\tif err != io.EOF {\n\t\tt.Errorf(\"expected EOF, got %v\", err)\n\t}\n\n}\n\nfunc TestGenerateBodyPieces(t *testing.T) {\n\tbody := \"test{{env1}}xyz{{env2}}\" // only for env vars for now\n\n\tei := EnvironmentInjector{}\n\tei.Init()\n\n\tenvs := make(map[string]interface{})\n\tenvs[\"env1\"] = \"123\"\n\tenvs[\"env2\"] = \"456\"\n\n\tpieces := ei.GenerateBodyPieces(body, envs)\n\n\tif len(pieces) != 4 {\n\t\tt.Errorf(\"expected 4 pieces, got %d\", len(pieces))\n\t}\n\n\tif pieces[0].start != 0 && pieces[0].end != 4 {\n\t\tt.Errorf(\"expected start 0 and end 4, got %d and %d\", pieces[0].start, pieces[0].end)\n\t}\n\n\tif pieces[1].start != 4 && pieces[1].end != 12 {\n\t\tt.Errorf(\"expected start 4 and end 12, got %d and %d\", pieces[1].start, pieces[1].end)\n\t}\n\n\tif pieces[2].start != 12 && pieces[2].end != 15 {\n\t\tt.Errorf(\"expected start 12 and end 15, got %d and %d\", pieces[2].start, pieces[2].end)\n\t}\n\n\tif pieces[3].start != 15 && pieces[3].end != 23 {\n\t\tt.Errorf(\"expected start 15 and end 23, got %d and %d\", pieces[3].start, pieces[3].end)\n\t}\n\n\tif !pieces[1].injectable {\n\t\tt.Errorf(\"expected piece 1 to be injectable\")\n\t}\n\tif !pieces[3].injectable {\n\t\tt.Errorf(\"expected piece 3 to be injectable\")\n\t}\n\n\tif pieces[0].injectable {\n\t\tt.Errorf(\"expected piece 0 to not be injectable\")\n\t}\n\tif pieces[2].injectable {\n\t\tt.Errorf(\"expected piece 2 to not be injectable\")\n\t}\n\n\tif pieces[1].value != \"123\" {\n\t\tt.Errorf(\"expected piece 1 value to be 123\")\n\t}\n\tif pieces[3].value != \"456\" {\n\t\tt.Errorf(\"expected piece 3 value to be 456\")\n\t}\n\n\t// test content length\n\t// 4 + {8} + 3 + {8} = 23\n\t// 4 + {3} + 3 + {3} = 13\n\tif GetContentLength(pieces) != 13 {\n\t\tt.Errorf(\"expected content length to be 13\")\n\t}\n}\n\nfunc TestGenerateBodyPiecesWithDynamicVars(t *testing.T) {\n\tbody := \"test{{env1}}xyz{{_randomInt}}\"\n\n\tei := EnvironmentInjector{}\n\tei.Init()\n\n\tenvs := make(map[string]interface{})\n\tenvs[\"env1\"] = \"123\"\n\n\tpieces := ei.GenerateBodyPieces(body, envs)\n\n\tif len(pieces) != 4 {\n\t\tt.Errorf(\"expected 4 pieces, got %d\", len(pieces))\n\t}\n\n\tif pieces[0].start != 0 && pieces[0].end != 4 {\n\t\tt.Errorf(\"expected start 0 and end 4, got %d and %d\", pieces[0].start, pieces[0].end)\n\t}\n\n\tif pieces[1].start != 4 && pieces[1].end != 12 {\n\t\tt.Errorf(\"expected start 4 and end 12, got %d and %d\", pieces[1].start, pieces[1].end)\n\t}\n\n\tif pieces[2].start != 12 && pieces[2].end != 15 {\n\t\tt.Errorf(\"expected start 12 and end 15, got %d and %d\", pieces[2].start, pieces[2].end)\n\t}\n\n\tif pieces[3].start != 15 && pieces[3].end != 15+len(pieces[3].value) {\n\t\tt.Errorf(\"expected start 15 and end %d, got %d and %d\", 15+len(pieces[3].value), pieces[3].start, pieces[3].end)\n\t}\n\n\tif !pieces[1].injectable {\n\t\tt.Errorf(\"expected piece 1 to be injectable\")\n\t}\n\tif !pieces[3].injectable {\n\t\tt.Errorf(\"expected piece 3 to be injectable\")\n\t}\n\n\tif pieces[0].injectable {\n\t\tt.Errorf(\"expected piece 0 to not be injectable\")\n\t}\n\tif pieces[2].injectable {\n\t\tt.Errorf(\"expected piece 2 to not be injectable\")\n\t}\n\n\tif pieces[1].value != \"123\" {\n\t\tt.Errorf(\"expected piece 1 value to be 123\")\n\t}\n\n\t// it will be random, so we can't test it\n\t// if pieces[3].value != \"456\" {\n\t// \tt.Errorf(\"expected piece 3 value to be 456\")\n\t// }\n}\n\nfunc TestGenerateBodyPiecesSorted(t *testing.T) {\n\tbody := \"test{{_randomInt}}xyz{{env1}}{{env2}}{{_randomCity}}\"\n\n\tei := EnvironmentInjector{}\n\tei.Init()\n\n\tenvs := make(map[string]interface{})\n\tenvs[\"env1\"] = \"123\"\n\tenvs[\"env2\"] = \"777\"\n\n\tpieces := ei.GenerateBodyPieces(body, envs)\n\n\tif len(pieces) != 6 {\n\t\tt.Errorf(\"expected 6 pieces, got %d\", len(pieces))\n\t}\n\n\tfor i := 0; i < len(pieces)-1; i++ {\n\t\tif pieces[i].start > pieces[i+1].start {\n\t\t\tt.Errorf(\"expected pieces to be sorted by start\")\n\t\t}\n\t\tif pieces[i].end > pieces[i+1].end {\n\t\t\tt.Errorf(\"expected pieces to be sorted by end\")\n\t\t}\n\n\t\tif pieces[i].end != pieces[i+1].start {\n\t\t\tt.Errorf(\"expected pieces to be contiguous\")\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/scripting/injection/init.go",
    "content": "package injection\n\nimport \"github.com/ddosify/go-faker/faker\"\n\nvar dynamicFakeDataMap map[string]interface{}\nvar dataFaker faker.Faker\n\nfunc init() {\n\tdataFaker = faker.NewFaker()\n\tdynamicFakeDataMap = map[string]interface{}{\n\t\t/*\n\t\t* Postman equivalents: https://learning.postman.com/docs/writing-scripts/script-references/variables-list\n\t\t */\n\n\t\t// Common\n\t\t\"guid\":         dataFaker.RandomGuid,\n\t\t\"timestamp\":    dataFaker.CurrentTimestamp,\n\t\t\"isoTimestamp\": dataFaker.CurrentISOTimestamp,\n\t\t\"randomUUID\":   dataFaker.RandomUUID,\n\n\t\t//Text, numbers, and colors\n\t\t\"randomAlphaNumeric\": dataFaker.RandomAlphanumeric,\n\t\t\"randomBoolean\":      dataFaker.RandomBoolean,\n\t\t\"randomInt\":          dataFaker.RandomInt,\n\t\t\"randomColor\":        dataFaker.RandomSafeColorName,\n\t\t\"randomHexColor\":     dataFaker.RandomSafeColorHex,\n\t\t\"randomAbbreviation\": dataFaker.RandomAbbreviation,\n\n\t\t// Internet and IP addresses\n\t\t\"randomIP\":         dataFaker.RandomIP,\n\t\t\"randomIPV6\":       dataFaker.RandomIpv6,\n\t\t\"randomMACAddress\": dataFaker.RandomMACAddress,\n\t\t\"randomPassword\":   dataFaker.RandomPassword,\n\t\t\"randomLocale\":     dataFaker.RandomLocale,\n\t\t\"randomUserAgent\":  dataFaker.RandomUserAgent,\n\t\t\"randomProtocol\":   dataFaker.RandomProtocol,\n\t\t\"randomSemver\":     dataFaker.RandomSemver,\n\n\t\t// Names\n\t\t\"randomFirstName\":  dataFaker.RandomPersonFirstName,\n\t\t\"randomLastName\":   dataFaker.RandomPersonLastName,\n\t\t\"randomFullName\":   dataFaker.RandomPersonFullName,\n\t\t\"randomNamePrefix\": dataFaker.RandomPersonNamePrefix,\n\t\t\"randomNameSuffix\": dataFaker.RandomPersonNameSuffix,\n\n\t\t// Profession\n\t\t\"randomJobArea\":       dataFaker.RandomJobArea,\n\t\t\"randomJobDescriptor\": dataFaker.RandomJobDescriptor,\n\t\t\"randomJobTitle\":      dataFaker.RandomJobTitle,\n\t\t\"randomJobType\":       dataFaker.RandomJobType,\n\n\t\t// Phone, address, and location\n\t\t\"randomPhoneNumber\":    dataFaker.RandomPhoneNumber,\n\t\t\"randomPhoneNumberExt\": dataFaker.RandomPhoneNumberExt,\n\t\t\"randomCity\":           dataFaker.RandomAddressCity,\n\t\t\"randomStreetName\":     dataFaker.RandomAddresStreetName,\n\t\t\"randomStreetAddress\":  dataFaker.RandomAddressStreetAddress,\n\t\t\"randomCountry\":        dataFaker.RandomAddressCountry,\n\t\t\"randomCountryCode\":    dataFaker.RandomCountryCode,\n\t\t\"randomLatitude\":       dataFaker.RandomAddressLatitude,\n\t\t\"randomLongitude\":      dataFaker.RandomAddressLongitude,\n\n\t\t// Images\n\t\t\"randomAvatarImage\":    dataFaker.RandomAvatarImage,\n\t\t\"randomImageUrl\":       dataFaker.RandomImageURL,\n\t\t\"randomAbstractImage\":  dataFaker.RandomAbstractImage,\n\t\t\"randomAnimalsImage\":   dataFaker.RandomAnimalsImage,\n\t\t\"randomBusinessImage\":  dataFaker.RandomBusinessImage,\n\t\t\"randomCatsImage\":      dataFaker.RandomCatsImage,\n\t\t\"randomCityImage\":      dataFaker.RandomCityImage,\n\t\t\"randomFoodImage\":      dataFaker.RandomFoodImage,\n\t\t\"randomNightlifeImage\": dataFaker.RandomNightlifeImage,\n\t\t\"randomFashionImage\":   dataFaker.RandomFashionImage,\n\t\t\"randomPeopleImage\":    dataFaker.RandomPeopleImage,\n\t\t\"randomNatureImage\":    dataFaker.RandomNatureImage,\n\t\t\"randomSportsImage\":    dataFaker.RandomSportsImage,\n\t\t\"randomTransportImage\": dataFaker.RandomTransportImage,\n\t\t\"randomImageDataUri\":   dataFaker.RandomDataImageUri,\n\n\t\t// Finance\n\t\t\"randomBankAccount\":     dataFaker.RandomBankAccount,\n\t\t\"randomBankAccountName\": dataFaker.RandomBankAccountName,\n\t\t\"randomCreditCardMask\":  dataFaker.RandomCreditCardMask,\n\t\t\"randomBankAccountBic\":  dataFaker.RandomBankAccountBic,\n\t\t\"randomBankAccountIban\": dataFaker.RandomBankAccountIban,\n\t\t\"randomTransactionType\": dataFaker.RandomTransactionType,\n\t\t\"randomCurrencyCode\":    dataFaker.RandomCurrencyCode,\n\t\t\"randomCurrencyName\":    dataFaker.RandomCurrencyName,\n\t\t\"randomCurrencySymbol\":  dataFaker.RandomCurrencySymbol,\n\t\t\"randomBitcoin\":         dataFaker.RandomBitcoin,\n\n\t\t// Business\n\t\t\"randomCompanyName\":   dataFaker.RandomCompanyName,\n\t\t\"randomCompanySuffix\": dataFaker.RandomCompanySuffix,\n\t\t\"randomBs\":            dataFaker.RandomBs,\n\t\t\"randomBsAdjective\":   dataFaker.RandomBsAdjective,\n\t\t\"randomBsBuzz\":        dataFaker.RandomBsBuzzWord,\n\t\t\"randomBsNoun\":        dataFaker.RandomBsNoun,\n\n\t\t// Catchphrases\n\t\t\"randomCatchPhrase\":           dataFaker.RandomCatchPhrase,\n\t\t\"randomCatchPhraseAdjective\":  dataFaker.RandomCatchPhraseAdjective,\n\t\t\"randomCatchPhraseDescriptor\": dataFaker.RandomCatchPhraseDescriptor,\n\t\t\"randomCatchPhraseNoun\":       dataFaker.RandomCatchPhraseNoun,\n\n\t\t// Databases\n\t\t\"randomDatabaseColumn\":    dataFaker.RandomDatabaseColumn,\n\t\t\"randomDatabaseType\":      dataFaker.RandomDatabaseType,\n\t\t\"randomDatabaseCollation\": dataFaker.RandomDatabaseCollation,\n\t\t\"randomDatabaseEngine\":    dataFaker.RandomDatabaseEngine,\n\n\t\t// Dates\n\t\t\"randomDateFuture\": dataFaker.RandomDateFuture,\n\t\t\"randomDatePast\":   dataFaker.RandomDatePast,\n\t\t\"randomDateRecent\": dataFaker.RandomDateRecent,\n\t\t\"randomWeekday\":    dataFaker.RandomWeekday,\n\t\t\"randomMonth\":      dataFaker.RandomMonth,\n\n\t\t// Domains, emails, and usernames\n\t\t\"randomDomainName\":   dataFaker.RandomDomainName,\n\t\t\"randomDomainSuffix\": dataFaker.RandomDomainSuffix,\n\t\t\"randomDomainWord\":   dataFaker.RandomDomainWord,\n\t\t\"randomEmail\":        dataFaker.RandomEmail,\n\t\t\"randomExampleEmail\": dataFaker.RandomExampleEmail,\n\t\t\"randomUserName\":     dataFaker.RandomUsername,\n\t\t\"randomUrl\":          dataFaker.RandomUrl,\n\n\t\t// Files and directories\n\t\t\"randomFileName\":       dataFaker.RandomFileName,\n\t\t\"randomFileType\":       dataFaker.RandomFileType,\n\t\t\"randomFileExt\":        dataFaker.RandomFileExtension,\n\t\t\"randomCommonFileName\": dataFaker.RandomCommonFileName,\n\t\t\"randomCommonFileType\": dataFaker.RandomCommonFileType,\n\t\t\"randomCommonFileExt\":  dataFaker.RandomCommonFileExtension,\n\t\t\"randomFilePath\":       dataFaker.RandomFilePath,\n\t\t\"randomDirectoryPath\":  dataFaker.RandomDirectoryPath,\n\t\t\"randomMimeType\":       dataFaker.RandomMimeType,\n\n\t\t// Stores\n\t\t\"randomPrice\":            dataFaker.RandomPrice,\n\t\t\"randomProduct\":          dataFaker.RandomProduct,\n\t\t\"randomProductAdjective\": dataFaker.RandomProductAdjective,\n\t\t\"randomProductMaterial\":  dataFaker.RandomProductMaterial,\n\t\t\"randomProductName\":      dataFaker.RandomProductName,\n\t\t\"randomDepartment\":       dataFaker.RandomDepartment,\n\n\t\t// Grammar\n\t\t\"randomNoun\":      dataFaker.RandomNoun,\n\t\t\"randomVerb\":      dataFaker.RandomVerb,\n\t\t\"randomIngverb\":   dataFaker.RandomIngVerb,\n\t\t\"randomAdjective\": dataFaker.RandomAdjective,\n\t\t\"randomWord\":      dataFaker.RandomWord,\n\t\t\"randomWords\":     dataFaker.RandomWords,\n\t\t\"randomPhrase\":    dataFaker.RandomPhrase,\n\n\t\t// Lorem ipsum\n\t\t\"randomLoremWord\":       dataFaker.RandomLoremWord,\n\t\t\"randomLoremWords\":      dataFaker.RandomLoremWords,\n\t\t\"randomLoremSentence\":   dataFaker.RandomLoremSentence,\n\t\t\"randomLoremSentences\":  dataFaker.RandomLoremSentences,\n\t\t\"randomLoremParagraph\":  dataFaker.RandomLoremParagraph,\n\t\t\"randomLoremParagraphs\": dataFaker.RandomLoremParagraphs,\n\t\t\"randomLoremText\":       dataFaker.RandomLoremText,\n\t\t\"randomLoremSlug\":       dataFaker.RandomLoremSlug,\n\t\t\"randomLoremLines\":      dataFaker.RandomLoremLines,\n\n\t\t/*\n\t\t * Specific to us.\n\t\t */\n\t\t\"randomFloat\":  dataFaker.RandomFloat,\n\t\t\"randomString\": dataFaker.RandomString,\n\t}\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/service.go",
    "content": "/*\n*\n*\tDdosify - Load testing tool for any web system.\n*   Copyright (C) 2021  Ddosify (https://ddosify.com)\n*\n*   This program is free software: you can redistribute it and/or modify\n*   it under the terms of the GNU Affero General Public License as published\n*   by the Free Software Foundation, either version 3 of the License, or\n*   (at your option) any later version.\n*\n*   This program is distributed in the hope that it will be useful,\n*   but WITHOUT ANY WARRANTY; without even the implied warranty of\n*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n*   GNU Affero General Public License for more details.\n*\n*   You should have received a copy of the GNU Affero General Public License\n*   along with this program.  If not, see <https://www.gnu.org/licenses/>.\n*\n */\n\npackage scenario\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"go.ddosify.com/ddosify/core/scenario/requester\"\n\t\"go.ddosify.com/ddosify/core/scenario/scripting/injection\"\n\t\"go.ddosify.com/ddosify/core/types\"\n\t\"go.ddosify.com/ddosify/core/types/regex\"\n\t\"go.ddosify.com/ddosify/core/util\"\n)\n\n// ScenarioService encapsulates proxy/scenario/requester information and runs the scenario.\ntype ScenarioService struct {\n\t// Client map structure [proxy_addr][]scenarioItemRequester\n\t// Each proxy represents a client.\n\t// Each scenarioItem has a requester\n\tclients map[*url.URL][]scenarioItemRequester\n\n\tcPool *util.Pool[*http.Client]\n\n\tscenario types.Scenario\n\tctx      context.Context\n\n\tclientMutex sync.Mutex\n\tdebug       bool\n\tengineMode  string\n\n\tei        *injection.EnvironmentInjector\n\titerIndex int64\n}\n\n// NewScenarioService is the constructor of the ScenarioService.\nfunc NewScenarioService() *ScenarioService {\n\treturn &ScenarioService{}\n}\n\ntype ScenarioOpts struct {\n\tDebug                  bool\n\tIterationCount         int\n\tMaxConcurrentIterCount int\n\tEngineMode             string\n\tInitialCookies         []*http.Cookie\n}\n\n// Init initializes the ScenarioService.clients with the given types.Scenario and proxies.\n// Passes the given ctx to the underlying requestor so we are able to control the life of each request.\nfunc (s *ScenarioService) Init(ctx context.Context, scenario types.Scenario,\n\tproxies []*url.URL, opts ScenarioOpts) (err error) {\n\ts.scenario = scenario\n\ts.ctx = ctx\n\ts.debug = opts.Debug\n\ts.clients = make(map[*url.URL][]scenarioItemRequester, len(proxies))\n\n\tei := &injection.EnvironmentInjector{}\n\tei.Init()\n\ts.ei = ei\n\n\tfor _, p := range proxies {\n\t\terr = s.createRequesters(p)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\tvi := &injection.EnvironmentInjector{}\n\tvi.Init()\n\ts.ei = vi\n\ts.engineMode = opts.EngineMode\n\n\tif s.engineInUserMode() {\n\t\t// create client pool\n\t\tvar initialCount int\n\t\tvar maxCount int\n\t\tif opts.Debug {\n\t\t\t// just one client\n\t\t\tinitialCount = 1\n\t\t\tmaxCount = 1\n\t\t} else if s.engineMode == types.EngineModeRepeatedUser {\n\t\t\tinitialCount = opts.MaxConcurrentIterCount\n\t\t\tmaxCount = opts.MaxConcurrentIterCount\n\t\t} else if s.engineMode == types.EngineModeDistinctUser {\n\t\t\tinitialCount = opts.MaxConcurrentIterCount\n\t\t\tmaxCount = opts.IterationCount\n\t\t}\n\t\ts.cPool, err = NewClientPool(initialCount, maxCount, s.engineMode, putInitialCookiesInJarFactory(s.engineMode, opts.InitialCookies), func(c *http.Client) { c.CloseIdleConnections() })\n\t}\n\t// s.cPool will be nil otherwise\n\n\treturn\n}\n\nfunc putInitialCookiesInJarFactory(engineMode string, initCookies []*http.Cookie) ClientFactoryMethod {\n\treturn createClientFactoryMethod(engineMode, func(cj http.CookieJar) {\n\t\tfor _, c := range initCookies {\n\t\t\tvar scheme string = \"http\"\n\t\t\tif c.Secure {\n\t\t\t\tscheme = \"https\"\n\t\t\t}\n\t\t\turl := &url.URL{Host: c.Domain, Scheme: scheme}\n\t\t\tcj.SetCookies(url, []*http.Cookie{c})\n\t\t}\n\t})\n}\n\n// Do executes the scenario for the given proxy.\n// Returns \"types.Response\" filled by the requester of the given Proxy, injects the given startTime to the response\n// Returns error only if types.Response.Err.Type is types.ErrorProxy or types.ErrorIntented\nfunc (s *ScenarioService) Do(proxy *url.URL, startTime time.Time) (\n\tresponse *types.ScenarioResult, err *types.RequestError) {\n\tresponse = &types.ScenarioResult{StepResults: []*types.ScenarioStepResult{}}\n\tresponse.StartTime = startTime\n\tresponse.ProxyAddr = proxy\n\trand.Seed(time.Now().UnixNano())\n\n\trequesters, e := s.getOrCreateRequesters(proxy)\n\tif e != nil {\n\t\treturn nil, &types.RequestError{Type: types.ErrorUnkown, Reason: e.Error()}\n\t}\n\n\t// start envs separately for each iteration\n\tenvs := make(map[string]interface{}, len(s.scenario.Envs))\n\tfor k, v := range s.scenario.Envs {\n\t\tenvs[k] = v\n\t}\n\t// inject dynamic variables beforehand for each iteration\n\tinjectDynamicVars(s.ei, envs)\n\t// pass a row from data for each iteration\n\ts.enrichEnvFromData(envs)\n\tatomic.AddInt64(&s.iterIndex, 1)\n\n\tvar client *http.Client\n\tif s.engineInUserMode() {\n\t\t// get client from pool\n\t\tclient = s.cPool.Get()\n\t\tdefer s.cPool.Put(client)\n\t}\n\n\tfor _, sr := range requesters {\n\t\tvar res *types.ScenarioStepResult\n\t\tswitch sr.requester.Type() {\n\t\tcase \"HTTP\":\n\t\t\thttpRequester := sr.requester.(requester.HttpRequesterI)\n\t\t\tres = httpRequester.Send(client, envs)\n\t\tdefault:\n\t\t\tres = &types.ScenarioStepResult{Err: types.RequestError{Type: fmt.Sprintf(\"type not defined: %s\", sr.requester.Type())}}\n\t\t}\n\n\t\tif res.Err.Type == types.ErrorProxy || res.Err.Type == types.ErrorIntented {\n\t\t\terr = &res.Err\n\t\t\tif res.Err.Type == types.ErrorIntented {\n\t\t\t\t// Stop the loop. ErrorProxy can be fixed in time. But ErrorIntented is a signal to stop all.\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tresponse.StepResults = append(response.StepResults, res)\n\n\t\t// Sleep before running the next step\n\t\tif sr.sleeper != nil && len(s.scenario.Steps) > 1 {\n\t\t\tsr.sleeper.sleep()\n\t\t}\n\n\t\tenrichEnvFromPrevStep(envs, res.ExtractedEnvs)\n\t}\n\n\treturn\n}\n\nfunc enrichEnvFromPrevStep(m1 map[string]interface{}, m2 map[string]interface{}) {\n\tfor k, v := range m2 {\n\t\tm1[k] = v\n\t}\n}\n\nfunc (s *ScenarioService) engineInUserMode() bool {\n\tif s.engineMode == types.EngineModeDistinctUser || s.engineMode == types.EngineModeRepeatedUser {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (s *ScenarioService) enrichEnvFromData(envs map[string]interface{}) {\n\tvar row map[string]interface{}\n\tsb := strings.Builder{}\n\tfor key, csvData := range s.scenario.Data {\n\t\tlenRows := len(csvData.Rows)\n\t\tif csvData.Random {\n\t\t\trow = csvData.Rows[rand.Intn(lenRows)]\n\t\t} else {\n\t\t\trow = csvData.Rows[s.iterIndex%int64(lenRows)]\n\t\t}\n\n\t\tfor tag, v := range row {\n\t\t\tsb.WriteString(\"data.\")\n\t\t\tsb.WriteString(key)\n\t\t\tsb.WriteString(\".\")\n\t\t\tsb.WriteString(tag)\n\t\t\t// data.info.name\n\t\t\tenvs[sb.String()] = v\n\t\t\tsb.Reset()\n\t\t}\n\t}\n}\n\nfunc (s *ScenarioService) Done() {\n\tfor _, v := range s.clients {\n\t\tfor _, r := range v {\n\t\t\tr.requester.Done()\n\t\t}\n\t}\n\n\tif s.cPool != nil {\n\t\ts.cPool.Done()\n\t}\n}\n\nfunc (s *ScenarioService) getOrCreateRequesters(proxy *url.URL) (requesters []scenarioItemRequester, err error) {\n\ts.clientMutex.Lock()\n\tdefer s.clientMutex.Unlock()\n\n\trequesters, ok := s.clients[proxy]\n\tif !ok {\n\t\terr = s.createRequesters(proxy)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\treturn s.clients[proxy], err\n}\n\nfunc (s *ScenarioService) createRequesters(proxy *url.URL) (err error) {\n\ts.clients[proxy] = []scenarioItemRequester{}\n\tfor _, si := range s.scenario.Steps {\n\t\tvar r requester.Requester\n\t\tr, err = requester.NewRequester(si)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\ts.clients[proxy] = append(\n\t\t\ts.clients[proxy],\n\t\t\tscenarioItemRequester{\n\t\t\t\tscenarioItemID: si.ID,\n\t\t\t\tsleeper:        newSleeper(si.Sleep),\n\t\t\t\trequester:      r,\n\t\t\t},\n\t\t)\n\n\t\tswitch r.Type() {\n\t\tcase \"HTTP\":\n\t\t\thttpRequester := r.(requester.HttpRequesterI)\n\t\t\terr = httpRequester.Init(s.ctx, si, proxy, s.debug, s.ei)\n\t\tdefault:\n\t\t\terr = fmt.Errorf(\"type not defined: %s\", r.Type())\n\t\t}\n\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\treturn err\n}\n\nfunc injectDynamicVars(vi *injection.EnvironmentInjector, envs map[string]interface{}) {\n\tdynamicRgx := regexp.MustCompile(regex.DynamicVariableRegex)\n\tfor k, v := range envs {\n\t\tvStr, isStr := v.(string)\n\t\tif !isStr {\n\t\t\tcontinue\n\t\t}\n\t\tif dynamicRgx.MatchString(vStr) {\n\t\t\tinjected, err := vi.InjectDynamic(vStr)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tenvs[k] = injected\n\t\t}\n\t}\n}\n\ntype scenarioItemRequester struct {\n\tscenarioItemID uint16\n\tsleeper        Sleeper\n\trequester      requester.Requester\n}\n\n// Sleeper is the interface for implementing different sleep strategies.\ntype Sleeper interface {\n\tsleep()\n}\n\n// RangeSleep is the implementation of the range sleep feature\ntype RangeSleep struct {\n\tmin int\n\tmax int\n}\n\nfunc (rs *RangeSleep) sleep() {\n\trand.Seed(time.Now().UnixNano())\n\tdur := rand.Intn(rs.max-rs.min+1) + rs.min\n\ttime.Sleep(time.Duration(dur) * time.Millisecond)\n}\n\n// DurationSleep is the implementation of the exact duration sleep feature\ntype DurationSleep struct {\n\tduration int\n}\n\nfunc (ds *DurationSleep) sleep() {\n\ttime.Sleep(time.Duration(ds.duration) * time.Millisecond)\n}\n\n// newSleeper is the factor method for the Sleeper implementations.\nfunc newSleeper(sleepStr string) Sleeper {\n\tif sleepStr == \"\" {\n\t\treturn nil\n\t}\n\n\tvar sl Sleeper\n\n\t// Sleep field already validated in types.scenario.validate(). No need to check parsing errors here.\n\ts := strings.Split(sleepStr, \"-\")\n\tif len(s) == 2 {\n\t\tmin, _ := strconv.Atoi(s[0])\n\t\tmax, _ := strconv.Atoi(s[1])\n\t\tif min > max {\n\t\t\tmin, max = max, min\n\t\t}\n\n\t\tsl = &RangeSleep{\n\t\t\tmin: min,\n\t\t\tmax: max,\n\t\t}\n\t} else {\n\t\tdur, _ := strconv.Atoi(s[0])\n\n\t\tsl = &DurationSleep{\n\t\t\tduration: dur,\n\t\t}\n\t}\n\n\treturn sl\n}\n"
  },
  {
    "path": "ddosify_engine/core/scenario/service_test.go",
    "content": "/*\n*\n*\tDdosify - Load testing tool for any web system.\n*   Copyright (C) 2021  Ddosify (https://ddosify.com)\n*\n*   This program is free software: you can redistribute it and/or modify\n*   it under the terms of the GNU Affero General Public License as published\n*   by the Free Software Foundation, either version 3 of the License, or\n*   (at your option) any later version.\n*\n*   This program is distributed in the hope that it will be useful,\n*   but WITHOUT ANY WARRANTY; without even the implied warranty of\n*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n*   GNU Affero General Public License for more details.\n*\n*   You should have received a copy of the GNU Affero General Public License\n*   along with this program.  If not, see <https://www.gnu.org/licenses/>.\n*\n */\n\npackage scenario\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n\n\t\"go.ddosify.com/ddosify/core/scenario/requester\"\n\t\"go.ddosify.com/ddosify/core/scenario/scripting/injection\"\n\t\"go.ddosify.com/ddosify/core/types\"\n)\n\ntype MockHttpRequester struct {\n\tInitCalled bool\n\tSendCalled bool\n\tDoneCalled bool\n\n\tFailInit    bool\n\tFailInitMsg string\n\n\tEnvsSet bool\n\n\tReturnSend *types.ScenarioStepResult\n}\n\nfunc (m *MockHttpRequester) Init(ctx context.Context, s types.ScenarioStep, proxyAddr *url.URL, debug bool, ei *injection.EnvironmentInjector) (err error) {\n\tm.InitCalled = true\n\tif m.FailInit {\n\t\treturn fmt.Errorf(m.FailInitMsg)\n\t}\n\treturn\n}\n\nfunc (m *MockHttpRequester) Send(client *http.Client, envs map[string]interface{}) (res *types.ScenarioStepResult) {\n\tm.SendCalled = true\n\treturn m.ReturnSend\n}\n\nfunc (m *MockHttpRequester) Done() {\n\tm.DoneCalled = true\n}\n\nfunc (m *MockHttpRequester) Type() string {\n\treturn \"HTTP\"\n}\n\ntype MockSleep struct {\n\tSleepCalled    bool\n\tSleepCallCount int\n}\n\nfunc (msl *MockSleep) sleep() {\n\tmsl.SleepCalled = true\n\tmsl.SleepCallCount++\n}\n\nfunc compareScenarioServiceClients(\n\texpectedClients map[*url.URL][]scenarioItemRequester,\n\tclients map[*url.URL][]scenarioItemRequester) error {\n\n\tif len(expectedClients) != len(clients) {\n\t\treturn fmt.Errorf(\"[length] Expected %v, Found %v\", expectedClients, clients)\n\t}\n\n\tfor k, expectedVal := range expectedClients {\n\t\tval, ok := clients[k]\n\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"[key] Expected %#v, Found %#v\", expectedClients, clients)\n\t\t}\n\n\t\tif len(expectedVal) != len(val) {\n\t\t\treturn fmt.Errorf(\"[valLength] Expected %v, Found %v\", expectedVal, val)\n\t\t}\n\n\t\tfor i := 0; i < len(expectedVal); i++ {\n\t\t\tif expectedVal[i].scenarioItemID != val[i].scenarioItemID {\n\t\t\t\treturn fmt.Errorf(\"[scenarioItemID] Expected %#v, Found %#v\", expectedVal, val)\n\t\t\t}\n\n\t\t\tif expectedVal[i].scenarioItemID != val[i].scenarioItemID {\n\t\t\t\treturn fmt.Errorf(\"[scenarioItemID] Expected %#v, Found %#v\", expectedVal, val)\n\t\t\t}\n\n\t\t\tif reflect.TypeOf(expectedVal[i].requester) != reflect.TypeOf(val[i].requester) {\n\t\t\t\treturn fmt.Errorf(\"[requester] Expected %#v, Found %#v\", expectedVal, val)\n\t\t\t}\n\n\t\t\tif reflect.TypeOf(expectedVal[i].sleeper) != reflect.TypeOf(val[i].sleeper) {\n\t\t\t\treturn fmt.Errorf(\"[sleep] Expected %#v, Found %#v\", expectedVal, val)\n\t\t\t}\n\n\t\t\tif !reflect.DeepEqual(expectedVal[i].sleeper, val[i].sleeper) {\n\t\t\t\treturn fmt.Errorf(\"[sleep] Expected %#v, Found %#v\", expectedVal, val)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc TestInitService(t *testing.T) {\n\tt.Parallel()\n\n\t// Arrange\n\tscenario := types.Scenario{\n\t\tSteps: []types.ScenarioStep{\n\t\t\t{\n\t\t\t\tID:      1,\n\t\t\t\tMethod:  types.DefaultMethod,\n\t\t\t\tURL:     \"test.com\",\n\t\t\t\tTimeout: types.DefaultDuration,\n\t\t\t\tSleep:   \"300-500\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:      2,\n\t\t\t\tMethod:  types.DefaultMethod,\n\t\t\t\tURL:     \"test2.com\",\n\t\t\t\tTimeout: types.DefaultDuration,\n\t\t\t\tSleep:   \"1000\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:      3,\n\t\t\t\tMethod:  types.DefaultMethod,\n\t\t\t\tURL:     \"test3.com\",\n\t\t\t\tTimeout: types.DefaultDuration,\n\t\t\t},\n\t\t},\n\t}\n\tp1, _ := url.Parse(\"http://proxy_server.com:80\")\n\tp2, _ := url.Parse(\"http://proxy_server2.com:8000\")\n\tproxies := []*url.URL{p1, p2}\n\tctx := context.TODO()\n\texpectedClients := map[*url.URL][]scenarioItemRequester{\n\t\tp1: {\n\t\t\t{\n\t\t\t\tscenarioItemID: 1,\n\t\t\t\trequester:      &requester.HttpRequester{},\n\t\t\t\tsleeper:        &RangeSleep{min: 300, max: 500},\n\t\t\t},\n\t\t\t{\n\t\t\t\tscenarioItemID: 2,\n\t\t\t\trequester:      &requester.HttpRequester{},\n\t\t\t\tsleeper:        &DurationSleep{duration: 1000},\n\t\t\t},\n\t\t\t{\n\t\t\t\tscenarioItemID: 3,\n\t\t\t\trequester:      &requester.HttpRequester{},\n\t\t\t},\n\t\t},\n\t\tp2: {\n\t\t\t{\n\t\t\t\tscenarioItemID: 1,\n\t\t\t\trequester:      &requester.HttpRequester{},\n\t\t\t\tsleeper:        &RangeSleep{min: 300, max: 500},\n\t\t\t},\n\t\t\t{\n\t\t\t\tscenarioItemID: 2,\n\t\t\t\trequester:      &requester.HttpRequester{},\n\t\t\t\tsleeper:        &DurationSleep{duration: 1000},\n\t\t\t},\n\t\t\t{\n\t\t\t\tscenarioItemID: 3,\n\t\t\t\trequester:      &requester.HttpRequester{},\n\t\t\t},\n\t\t},\n\t}\n\n\t// Act\n\tservice := ScenarioService{}\n\terr := service.Init(ctx, scenario, proxies, ScenarioOpts{\n\t\tDebug:                  false,\n\t\tIterationCount:         1,\n\t\tMaxConcurrentIterCount: 1,\n\t})\n\n\t// Assert\n\tif err != nil {\n\t\tt.Fatalf(\"TestInitFunc error occurred %v\", err)\n\t}\n\n\tif err = compareScenarioServiceClients(expectedClients, service.clients); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestDo(t *testing.T) {\n\tt.Parallel()\n\n\t// Arrange\n\tscenario := types.Scenario{\n\t\tSteps: []types.ScenarioStep{\n\t\t\t{\n\t\t\t\tID:      1,\n\t\t\t\tMethod:  types.DefaultMethod,\n\t\t\t\tURL:     \"test.com\",\n\t\t\t\tTimeout: types.DefaultDuration,\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:      2,\n\t\t\t\tMethod:  types.DefaultMethod,\n\t\t\t\tURL:     \"test.com\",\n\t\t\t\tTimeout: types.DefaultDuration,\n\t\t\t},\n\t\t},\n\t}\n\tp1, _ := url.Parse(\"http://proxy_server.com:80\")\n\tctx := context.TODO()\n\tmockSleep := &MockSleep{}\n\n\trequesters := []scenarioItemRequester{\n\t\t{\n\t\t\tscenarioItemID: 1,\n\t\t\tsleeper:        mockSleep,\n\t\t\trequester:      &MockHttpRequester{ReturnSend: &types.ScenarioStepResult{StepID: 1}},\n\t\t},\n\t\t{\n\t\t\tscenarioItemID: 2,\n\t\t\trequester:      &MockHttpRequester{ReturnSend: &types.ScenarioStepResult{StepID: 2}},\n\t\t},\n\t}\n\tcPool, _ := NewClientPool(1, 1, types.EngineModeDdosify, func() *http.Client { return &http.Client{} }, func(c *http.Client) { c.CloseIdleConnections() })\n\tservice := ScenarioService{\n\t\tclients: map[*url.URL][]scenarioItemRequester{\n\t\t\tp1: requesters,\n\t\t},\n\t\tscenario: scenario,\n\t\tctx:      ctx,\n\t\tcPool:    cPool,\n\t}\n\n\texpectedResponse := types.ScenarioResult{\n\t\tProxyAddr: p1,\n\t\tStepResults: []*types.ScenarioStepResult{\n\t\t\t{StepID: 1}, {StepID: 2},\n\t\t},\n\t}\n\t// Act\n\tresponse, err := service.Do(p1, time.Now())\n\n\t// Assert\n\tif err != nil {\n\t\tt.Fatalf(\"TestDo errored: %v\", err)\n\t}\n\tif response.ProxyAddr != expectedResponse.ProxyAddr {\n\t\tt.Fatalf(\"[ProxyAddr] Expected %v, Found: %v\", expectedResponse.ProxyAddr, response.ProxyAddr)\n\t}\n\tif !reflect.DeepEqual(expectedResponse.StepResults, response.StepResults) {\n\t\tt.Fatalf(\"[ResponseItem] Expected %#v, Found: %#v\", expectedResponse.StepResults, response.StepResults)\n\t}\n\tif !mockSleep.SleepCalled {\n\t\tt.Fatalf(\"[Sleep] Sleep should be called\")\n\t}\n\tif mockSleep.SleepCallCount != 1 {\n\t\tt.Fatalf(\"[Sleep] Sleep call count expected: %d, Found: %d\", 1, mockSleep.SleepCallCount)\n\t}\n}\n\nfunc TestDoErrorOnSend(t *testing.T) {\n\tt.Parallel()\n\n\t// Arrange\n\tscenario := types.Scenario{\n\t\tSteps: []types.ScenarioStep{\n\t\t\t{\n\t\t\t\tID:      1,\n\t\t\t\tMethod:  types.DefaultMethod,\n\t\t\t\tURL:     \"test.com\",\n\t\t\t\tTimeout: types.DefaultDuration,\n\t\t\t},\n\t\t},\n\t}\n\tp1, _ := url.Parse(\"http://proxy_server.com:80\")\n\tctx := context.TODO()\n\n\trequestersProxyError := []scenarioItemRequester{\n\t\t{\n\t\t\tscenarioItemID: 1,\n\t\t\trequester:      &MockHttpRequester{ReturnSend: &types.ScenarioStepResult{Err: types.RequestError{Type: types.ErrorProxy}}},\n\t\t},\n\t}\n\trequestersIntentedError := []scenarioItemRequester{\n\t\t{\n\t\t\tscenarioItemID: 1,\n\t\t\trequester:      &MockHttpRequester{ReturnSend: &types.ScenarioStepResult{Err: types.RequestError{Type: types.ErrorIntented}}},\n\t\t},\n\t}\n\trequestersConnError := []scenarioItemRequester{\n\t\t{\n\t\t\tscenarioItemID: 1,\n\t\t\trequester:      &MockHttpRequester{ReturnSend: &types.ScenarioStepResult{Err: types.RequestError{Type: types.ErrorConn}}},\n\t\t},\n\t}\n\n\ttests := []struct {\n\t\tname                     string\n\t\trequesters               []scenarioItemRequester\n\t\tshouldErr                bool\n\t\terrorType                string\n\t\tresponseItemsShouldEmpty bool\n\t}{\n\t\t{\"ProxyError\", requestersProxyError, true, types.ErrorProxy, false},\n\t\t{\"IntentedError\", requestersIntentedError, true, types.ErrorIntented, true},\n\t\t{\"ConnError\", requestersConnError, false, \"\", false},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tcPool, _ := NewClientPool(1, 1, types.EngineModeDdosify, func() *http.Client { return &http.Client{} }, func(c *http.Client) { c.CloseIdleConnections() })\n\t\t\tservice := ScenarioService{\n\t\t\t\tclients: map[*url.URL][]scenarioItemRequester{\n\t\t\t\t\tp1: test.requesters,\n\t\t\t\t},\n\t\t\t\tscenario: scenario,\n\t\t\t\tctx:      ctx,\n\t\t\t\tcPool:    cPool,\n\t\t\t}\n\n\t\t\t// Act\n\t\t\tres, err := service.Do(p1, time.Now())\n\n\t\t\t// Assert\n\t\t\tif test.shouldErr {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Fatalf(\"Should be errored\")\n\t\t\t\t}\n\t\t\t\tif err.Type != test.errorType {\n\t\t\t\t\tt.Fatalf(\"Expected: %v, Found: %v\", test.errorType, err.Type)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"Errored: %v\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif test.responseItemsShouldEmpty && len(res.StepResults) > 0 {\n\t\t\t\tt.Fatalf(\"ResponseItem should be empty: %v\", res.StepResults)\n\t\t\t}\n\t\t\tif !test.responseItemsShouldEmpty && len(res.StepResults) == 0 {\n\t\t\t\tt.Fatal(\"ResponseItem shouldn't be empty\")\n\t\t\t}\n\n\t\t})\n\t}\n}\n\n// func TestDoErrorOnNewRequester(t *testing.T) {\n// \tt.Parallel()\n\n// \t// Arrange\n// \tscenario := types.Scenario{\n// \t\tSteps: []types.ScenarioStep{\n// \t\t\t{\n// \t\t\t\tID:      1,\n// \t\t\t\tMethod:  types.DefaultMethod,\n// \t\t\t\tURL:     \"test.com\",\n// \t\t\t\tTimeout: types.DefaultDuration,\n// \t\t\t},\n// \t\t},\n// \t}\n// \tp1, _ := url.Parse(\"http://proxy_server.com:80\")\n// \tctx := context.TODO()\n\n// \tservice := ScenarioService{\n// \t\tclients:  map[*url.URL][]scenarioItemRequester{},\n// \t\tscenario: scenario,\n// \t\tctx:      ctx,\n// \t}\n\n// \t// Act\n// \t_, err := service.Do(p1, time.Now())\n\n// \t// Assert\n// \tif err == nil {\n// \t\tt.Fatalf(\"TestDoErrorOnNewRequester should be errored\")\n// \t}\n// \tif err.Type != types.ErrorUnkown {\n// \t\tt.Fatalf(\"Do should return types.ErrorUnkown error type\")\n// \t}\n// }\n\nfunc TestDone(t *testing.T) {\n\tt.Parallel()\n\n\t// Arrange\n\tscenario := types.Scenario{\n\t\tSteps: []types.ScenarioStep{\n\t\t\t{\n\t\t\t\tID:      1,\n\t\t\t\tMethod:  types.DefaultMethod,\n\t\t\t\tURL:     \"test.com\",\n\t\t\t\tTimeout: types.DefaultDuration,\n\t\t\t},\n\t\t},\n\t}\n\tp1, _ := url.Parse(\"http://proxy_server.com:80\")\n\tp2, _ := url.Parse(\"http://proxy_server.com:8080\")\n\tctx := context.TODO()\n\n\tcPool, _ := NewClientPool(1, 1, types.EngineModeDdosify, func() *http.Client { return &http.Client{} }, func(c *http.Client) { c.CloseIdleConnections() })\n\n\trequester1 := &MockHttpRequester{ReturnSend: &types.ScenarioStepResult{StepID: 1}}\n\trequester2 := &MockHttpRequester{ReturnSend: &types.ScenarioStepResult{StepID: 2}}\n\trequester3 := &MockHttpRequester{ReturnSend: &types.ScenarioStepResult{StepID: 1}}\n\trequester4 := &MockHttpRequester{ReturnSend: &types.ScenarioStepResult{StepID: 2}}\n\tservice := ScenarioService{\n\t\tclients: map[*url.URL][]scenarioItemRequester{\n\t\t\tp1: {\n\t\t\t\t{\n\t\t\t\t\tscenarioItemID: 1,\n\t\t\t\t\trequester:      requester1,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tscenarioItemID: 2,\n\t\t\t\t\trequester:      requester2,\n\t\t\t\t},\n\t\t\t},\n\t\t\tp2: {\n\t\t\t\t{\n\t\t\t\t\tscenarioItemID: 1,\n\t\t\t\t\trequester:      requester3,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tscenarioItemID: 2,\n\t\t\t\t\trequester:      requester4,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tscenario: scenario,\n\t\tctx:      ctx,\n\t\tcPool:    cPool,\n\t}\n\t// Act\n\tservice.Done()\n\n\t// Assert\n\tif !requester1.DoneCalled {\n\t\tt.Fatalf(\"Requester1 Done should be called\")\n\t}\n\tif !requester2.DoneCalled {\n\t\tt.Fatalf(\"Requester2 Done should be called\")\n\t}\n\tif !requester3.DoneCalled {\n\t\tt.Fatalf(\"Requester3 Done should be called\")\n\t}\n\tif !requester4.DoneCalled {\n\t\tt.Fatalf(\"Requester4 Done should be called\")\n\t}\n}\n\nfunc TestGetOrCreateRequesters(t *testing.T) {\n\tt.Parallel()\n\n\t// Arrange\n\tscenario := types.Scenario{\n\t\tSteps: []types.ScenarioStep{\n\t\t\t{\n\t\t\t\tID:      1,\n\t\t\t\tMethod:  types.DefaultMethod,\n\t\t\t\tURL:     \"test.com\",\n\t\t\t\tTimeout: types.DefaultDuration,\n\t\t\t},\n\t\t},\n\t}\n\tp1, _ := url.Parse(\"http://proxy_server.com:80\")\n\tproxies := []*url.URL{p1}\n\tctx := context.TODO()\n\n\tservice := ScenarioService{}\n\tservice.Init(ctx, scenario, proxies, ScenarioOpts{\n\t\tDebug:                  false,\n\t\tIterationCount:         1,\n\t\tMaxConcurrentIterCount: 1,\n\t})\n\n\texpectedRequesters := []scenarioItemRequester{{scenarioItemID: 1, requester: &requester.HttpRequester{}}}\n\texpectedClients := map[*url.URL][]scenarioItemRequester{\n\t\tp1: expectedRequesters,\n\t}\n\n\t// Act\n\trequesters, err := service.getOrCreateRequesters(p1)\n\n\t// Assert\n\tif err != nil {\n\t\tt.Fatalf(\"TestGetOrCreateRequesters errored: %v\", err)\n\t}\n\n\tif len(expectedRequesters) != len(requesters) ||\n\t\texpectedRequesters[0].scenarioItemID != requesters[0].scenarioItemID ||\n\t\treflect.TypeOf(expectedRequesters[0].requester) != reflect.TypeOf(requesters[0].requester) {\n\t\tt.Fatalf(\"Expected: %v, Found: %v\", expectedRequesters, requesters)\n\t}\n\n\tif err = compareScenarioServiceClients(expectedClients, service.clients); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestGetOrCreateRequestersNewProxy(t *testing.T) {\n\tt.Parallel()\n\n\t// Arrange\n\tscenario := types.Scenario{\n\t\tSteps: []types.ScenarioStep{\n\t\t\t{\n\t\t\t\tID:      1,\n\t\t\t\tMethod:  types.DefaultMethod,\n\t\t\t\tURL:     \"test.com\",\n\t\t\t\tTimeout: types.DefaultDuration,\n\t\t\t},\n\t\t},\n\t}\n\tp1, _ := url.Parse(\"http://proxy_server.com:80\")\n\tproxies := []*url.URL{p1}\n\tctx := context.TODO()\n\n\tservice := ScenarioService{}\n\tservice.Init(ctx, scenario, proxies, ScenarioOpts{\n\t\tDebug:                  false,\n\t\tIterationCount:         1,\n\t\tMaxConcurrentIterCount: 1,\n\t})\n\n\texpectedRequesters := []scenarioItemRequester{{scenarioItemID: 1, requester: &requester.HttpRequester{}}}\n\n\tp2, _ := url.Parse(\"http://proxy_server2.com:8080\")\n\texpectedClients := map[*url.URL][]scenarioItemRequester{\n\t\tp1: {{scenarioItemID: 1, requester: &requester.HttpRequester{}}},\n\t\tp2: {{scenarioItemID: 1, requester: &requester.HttpRequester{}}},\n\t}\n\n\t// Act\n\trequesters, err := service.getOrCreateRequesters(p2)\n\n\t// Assert\n\tif err != nil {\n\t\tt.Fatalf(\"TestGetOrCreateRequestersNewProxy errored: %v\", err)\n\t}\n\n\tif len(expectedRequesters) != len(requesters) ||\n\t\texpectedRequesters[0].scenarioItemID != requesters[0].scenarioItemID ||\n\t\treflect.TypeOf(expectedRequesters[0].requester) != reflect.TypeOf(requesters[0].requester) {\n\t\tt.Fatalf(\"Expected: %v, Found: %v\", expectedRequesters, requesters)\n\t}\n\n\tif err = compareScenarioServiceClients(expectedClients, service.clients); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestCreateRequestersErrorOnRequesterInit(t *testing.T) {\n\tt.Parallel()\n\n\t// Arrange\n\tscenario := types.Scenario{\n\t\tSteps: []types.ScenarioStep{\n\t\t\t{\n\t\t\t\tID:      1,\n\t\t\t\tMethod:  \"?\", // To fail HttpRequesters.Init method\n\t\t\t\tURL:     \"test.com\",\n\t\t\t\tTimeout: types.DefaultDuration,\n\t\t\t},\n\t\t},\n\t}\n\tp, _ := url.Parse(\"http://proxy_server.com:80\")\n\tctx := context.TODO()\n\n\tservice := ScenarioService{\n\t\tclients:  map[*url.URL][]scenarioItemRequester{},\n\t\tscenario: scenario,\n\t\tctx:      ctx,\n\t}\n\n\t// Act\n\terr := service.createRequesters(p)\n\n\t// Assert\n\tif err == nil {\n\t\tt.Fatal(\"TestCreateRequestersFailOnNewRequester should be errored\")\n\t}\n}\n\nfunc TestnewSleeper(t *testing.T) {\n\tt.Parallel()\n\n\tsleepRange := \"300-500\"\n\tsleepRangeReverse := \"500-300\"\n\tsleepDuration := \"1000\"\n\n\texpectedSleepRange := &RangeSleep{\n\t\tmin: 300,\n\t\tmax: 500,\n\t}\n\texptectedSleepDuration := &DurationSleep{\n\t\tduration: 1000,\n\t}\n\n\t// \"range\" sleep strategy test\n\tsleep := newSleeper(sleepRange)\n\tif !reflect.DeepEqual(sleep, expectedSleepRange) {\n\t\tt.Errorf(\"Expected %v, Found: %v\", expectedSleepRange, sleep)\n\t}\n\tsleep = newSleeper(sleepRangeReverse)\n\tif !reflect.DeepEqual(sleep, expectedSleepRange) {\n\t\tt.Errorf(\"Expected %v, Found: %v\", expectedSleepRange, sleep)\n\t}\n\n\t// \"duration\" sleep strategy test\n\tsleep = newSleeper(sleepDuration)\n\tif !reflect.DeepEqual(sleep, exptectedSleepDuration) {\n\t\tt.Errorf(\"Expected %v, Found: %v\", exptectedSleepDuration, sleep)\n\t}\n}\n\nfunc TestSleep(t *testing.T) {\n\tt.Parallel()\n\n\tdelta := time.Duration(100)\n\tmin := 300\n\tmax := 500\n\tdur := 1000\n\n\tif testing.Short() {\n\t\t// Arrange durations for poor machines\n\t\tdelta = time.Duration(600)\n\t\tmin = 750\n\t\tmax = 1250\n\t\tdur = 1000\n\t}\n\n\tsleepDuration := &DurationSleep{\n\t\tduration: dur,\n\t}\n\tsleepRange := &RangeSleep{\n\t\tmin: min,\n\t\tmax: max,\n\t}\n\n\t// Test range\n\tstart := time.Now()\n\tsleepRange.sleep()\n\telapsed := time.Duration(time.Since(start) / time.Millisecond)\n\tif elapsed > time.Duration(max)+delta || elapsed < time.Duration(min)-delta {\n\t\tt.Errorf(\"Expected: [%d-%d], Found: %d\", min, max, elapsed)\n\t}\n\n\t// Test exact duration\n\tstart = time.Now()\n\tsleepDuration.sleep()\n\telapsed = time.Duration(time.Since(start) / time.Millisecond)\n\tif elapsed > time.Duration(dur)+delta {\n\t\tt.Errorf(\"Expected: %d, Found: %d\", dur, elapsed)\n\t}\n\n}\n\nfunc TestInjectDynamicVars(t *testing.T) {\n\tinvalidDynamicKey := \"{{_randomDdppdd}}\"\n\tenvs := map[string]interface{}{\n\t\t\"country\":            \"{{_randomCountry}}\",\n\t\t\"X\":                  \"Y\",\n\t\t\"{{xx}}\":             \"xx\",\n\t\t\"notFoundDynamicKey\": invalidDynamicKey,\n\t}\n\n\tbeforeLen := len(envs)\n\n\tvi := &injection.EnvironmentInjector{}\n\tvi.Init()\n\tinjectDynamicVars(vi, envs)\n\n\tafterLen := len(envs)\n\n\tif beforeLen != afterLen {\n\t\tt.Errorf(\"number of envs changed during dynamic var injection\")\n\t}\n\n\tif val, ok := envs[\"country\"]; !ok || val == \"{{_randomCountry}}\" {\n\t\tt.Errorf(\"injection failure\")\n\t}\n\n\tif val, ok := envs[\"notFoundDynamicKey\"]; !ok || val != invalidDynamicKey {\n\t\tt.Errorf(\"not found key should stay same\")\n\t}\n}\n\nfunc TestOnlyOneClientInDebugModeInUserMode(t *testing.T) {\n\tt.Parallel()\n\t// Arrange\n\tscenario := types.Scenario{\n\t\tSteps: []types.ScenarioStep{\n\t\t\t{\n\t\t\t\tID:      1,\n\t\t\t\tMethod:  types.DefaultMethod,\n\t\t\t\tURL:     \"test.com\",\n\t\t\t\tTimeout: types.DefaultDuration,\n\t\t\t},\n\t\t},\n\t}\n\tp1, _ := url.Parse(\"http://proxy_server.com:80\")\n\tproxies := []*url.URL{p1}\n\tctx := context.TODO()\n\n\tservice := ScenarioService{}\n\tservice.Init(ctx, scenario, proxies, ScenarioOpts{\n\t\tDebug:                  true,\n\t\tEngineMode:             types.EngineModeDistinctUser,\n\t\tIterationCount:         100,\n\t\tMaxConcurrentIterCount: 5,\n\t})\n\n\tif service.cPool.Len() != 1 {\n\t\tt.Fatal(\"TestOnlyOneClientInDebugModeInUserMode should have only one client\")\n\t}\n}\n"
  },
  {
    "path": "ddosify_engine/core/types/error.go",
    "content": "/*\n*\n*\tDdosify - Load testing tool for any web system.\n*   Copyright (C) 2021  Ddosify (https://ddosify.com)\n*\n*   This program is free software: you can redistribute it and/or modify\n*   it under the terms of the GNU Affero General Public License as published\n*   by the Free Software Foundation, either version 3 of the License, or\n*   (at your option) any later version.\n*\n*   This program is distributed in the hope that it will be useful,\n*   but WITHOUT ANY WARRANTY; without even the implied warranty of\n*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n*   GNU Affero General Public License for more details.\n*\n*   You should have received a copy of the GNU Affero General Public License\n*   along with this program.  If not, see <https://www.gnu.org/licenses/>.\n*\n */\n\npackage types\n\nimport \"fmt\"\n\n// Constants for custom error types and reasons\nconst (\n\t// Types\n\tErrorProxy          = \"proxyError\"\n\tErrorConn           = \"connectionError\"\n\tErrorUnkown         = \"unknownError\"\n\tErrorIntented       = \"intentedError\" // Errors for created intentionally\n\tErrorDns            = \"dnsError\"\n\tErrorParse          = \"parseError\"\n\tErrorAddr           = \"addressError\"\n\tErrorInvalidRequest = \"invalidRequestError\"\n\n\t// Reasons\n\tReasonProxyFailed  = \"proxy connection refused\"\n\tReasonProxyTimeout = \"proxy timeout\"\n\tReasonConnTimeout  = \"connection timeout\"\n\tReasonReadTimeout  = \"read timeout\"\n\tReasonConnRefused  = \"connection refused\"\n\n\t// In gracefully stop, engine cancels the ongoing requests.\n\t// We can detect the canceled requests with the help of this.\n\tReasonCtxCanceled = \"context canceled\"\n)\n\n// RequestError is our custom error struct created in the requester.Requester implementations.\ntype RequestError struct {\n\tType   string\n\tReason string\n}\n\n// Custom error message method of ScenarioError\nfunc (e *RequestError) Error() string {\n\treturn fmt.Sprintf(\"%s: %s\", e.Type, e.Reason)\n}\n\ntype ScenarioValidationError struct { // UnWrappable\n\tmsg        string\n\twrappedErr error\n}\n\nfunc (sc ScenarioValidationError) Error() string {\n\treturn sc.msg\n}\n\nfunc (sc ScenarioValidationError) Unwrap() error {\n\treturn sc.wrappedErr\n}\n\ntype EnvironmentNotDefinedError struct { // UnWrappable\n\tmsg        string\n\twrappedErr error\n}\n\nfunc (sc EnvironmentNotDefinedError) Error() string {\n\treturn sc.msg\n}\n\nfunc (sc EnvironmentNotDefinedError) Unwrap() error {\n\treturn sc.wrappedErr\n}\n\ntype CaptureConfigError struct { // UnWrappable\n\tmsg        string\n\twrappedErr error\n}\n\nfunc (sc CaptureConfigError) Error() string {\n\treturn sc.msg\n}\n\nfunc (sc CaptureConfigError) Unwrap() error {\n\treturn sc.wrappedErr\n}\n\ntype FailedAssertion struct {\n\tRule     string\n\tReceived map[string]interface{}\n\tReason   string\n}\n"
  },
  {
    "path": "ddosify_engine/core/types/hammer.go",
    "content": "/*\n*\n*\tDdosify - Load testing tool for any web system.\n*   Copyright (C) 2021  Ddosify (https://ddosify.com)\n*\n*   This program is free software: you can redistribute it and/or modify\n*   it under the terms of the GNU Affero General Public License as published\n*   by the Free Software Foundation, either version 3 of the License, or\n*   (at your option) any later version.\n*\n*   This program is distributed in the hope that it will be useful,\n*   but WITHOUT ANY WARRANTY; without even the implied warranty of\n*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n*   GNU Affero General Public License for more details.\n*\n*   You should have received a copy of the GNU Affero General Public License\n*   along with this program.  If not, see <https://www.gnu.org/licenses/>.\n*\n */\n\npackage types\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"go.ddosify.com/ddosify/core/proxy\"\n\t\"go.ddosify.com/ddosify/core/util\"\n)\n\n// Constants for Hammer field values\nconst (\n\t// Constants of the Load Types\n\tLoadTypeLinear      = \"linear\"\n\tLoadTypeIncremental = \"incremental\"\n\tLoadTypeWaved       = \"waved\"\n\n\t// EngineModes\n\tEngineModeDistinctUser = \"distinct-user\"\n\tEngineModeRepeatedUser = \"repeated-user\"\n\tEngineModeDdosify      = \"ddosify\"\n\n\t// Default Values\n\tDefaultIterCount     = 100\n\tDefaultLoadType      = LoadTypeLinear\n\tDefaultDuration      = 10\n\tDefaultTimeout       = 5\n\tDefaultMethod        = http.MethodGet\n\tDefaultOutputType    = \"stdout\" // TODO: get this value from report.OutputTypeStdout when import cycle resolved.\n\tDefaultSamplingCount = 3\n\tDefaultSingleMode    = true\n)\n\nvar loadTypes = [...]string{LoadTypeLinear, LoadTypeIncremental, LoadTypeWaved}\nvar engineModes = [...]string{EngineModeDdosify, EngineModeDistinctUser, EngineModeRepeatedUser}\n\ntype TestAssertionOpt struct {\n\tAbort bool\n\tDelay int\n}\n\n// TimeRunCount is the data structure to store manual load type data.\ntype TimeRunCount []struct {\n\tDuration int\n\tCount    int\n}\n\ntype Tag struct {\n\tTag  string `json:\"tag\"`\n\tType string `json:\"type\"`\n}\n\ntype CsvConf struct {\n\tPath          string         `json:\"path\"`\n\tDelimiter     string         `json:\"delimiter\"`\n\tSkipFirstLine bool           `json:\"skip_first_line\"`\n\tVars          map[string]Tag `json:\"vars\"` // \"0\":\"name\", \"1\":\"city\",\"2\":\"team\"\n\tSkipEmptyLine bool           `json:\"skip_empty_line\"`\n\tAllowQuota    bool           `json:\"allow_quota\"`\n\tOrder         string         `json:\"order\"`\n}\n\n// TimeRunCount is the data structure to store manual load type data.\ntype CustomCookie struct {\n\tName     string `json:\"name\"`\n\tValue    string `json:\"value\"`\n\tDomain   string `json:\"domain\"`\n\tPath     string `json:\"path\"`\n\tExpires  string `json:\"expires\"`\n\tMaxAge   int    `json:\"max_age\"`\n\tHttpOnly bool   `json:\"http_only\"`\n\tSecure   bool   `json:\"secure\"`\n\tRaw      string `json:\"raw\"`\n}\n\n// Hammer is like a lighter for the engine.\n// It includes attack metadata and all necessary data to initialize the internal services in the engine.\ntype Hammer struct {\n\t// Total iteration count\n\tIterationCount int\n\n\t// Type of the load.\n\tLoadType string\n\n\t// Total Duration of the test in seconds.\n\tTestDuration int\n\n\t// Duration (in second) - Request count map. Example: {10: 1500, 50: 400, ...}\n\tTimeRunCountMap TimeRunCount\n\n\t// Test Scenario\n\tScenario Scenario\n\n\t// Proxy/Proxies to use\n\tProxy proxy.Proxy\n\n\t// Destination of the results data.\n\tReportDestination string\n\n\t// Dynamic field for extra parameters.\n\tOthers map[string]interface{}\n\n\t// Debug mode on/off\n\tDebug bool\n\n\t// Sampling rate\n\tSamplingRate int\n\n\t// Connection reuse\n\tEngineMode string\n\n\t// Test Data Config\n\tTestDataConf map[string]CsvConf\n\n\t// Custom Cookies\n\tCookies []CustomCookie\n\n\t// Custom Cookies Enabled\n\tCookiesEnabled bool\n\n\t// Test-wide assertions\n\tAssertions map[string]TestAssertionOpt\n\n\t// Engine runs single\n\tSingleMode bool\n}\n\n// Validate validates attack metadata and executes the validation methods of the services.\nfunc (h *Hammer) Validate() error {\n\tif len(h.Scenario.Steps) == 0 {\n\t\treturn fmt.Errorf(\"scenario or target is empty\")\n\t}\n\n\th.Scenario.CsvVars = getCsvEnvs(h.TestDataConf)\n\n\tif err := h.Scenario.validate(); err != nil {\n\t\treturn err\n\t}\n\n\tif h.LoadType != \"\" && !util.StringInSlice(h.LoadType, loadTypes[:]) {\n\t\treturn fmt.Errorf(\"unsupported LoadType: %s\", h.LoadType)\n\t}\n\tif h.EngineMode != \"\" && !util.StringInSlice(h.EngineMode, engineModes[:]) {\n\t\treturn fmt.Errorf(\"unsupported EngineMode: %s\", h.EngineMode)\n\t}\n\n\tif len(h.TimeRunCountMap) > 0 {\n\t\tfor _, t := range h.TimeRunCountMap {\n\t\t\tif t.Duration < 1 {\n\t\t\t\treturn fmt.Errorf(\"duration in manual_load should be greater than 0\")\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc getCsvEnvs(testDataConf map[string]CsvConf) []string {\n\tcsvVars := make([]string, 0)\n\n\tsb := strings.Builder{}\n\tfor key, conf := range testDataConf {\n\t\tfor _, tag := range conf.Vars {\n\t\t\tsb.WriteString(\"data.\")\n\t\t\tsb.WriteString(key)\n\t\t\tsb.WriteString(\".\")\n\t\t\tsb.WriteString(tag.Tag)\n\t\t\t// data.info.name\n\t\t\tcsvVars = append(csvVars, sb.String())\n\t\t\tsb.Reset()\n\t\t}\n\t}\n\n\treturn csvVars\n}\n"
  },
  {
    "path": "ddosify_engine/core/types/hammer_test.go",
    "content": "/*\n*\n*\tDdosify - Load testing tool for any web system.\n*   Copyright (C) 2021  Ddosify (https://ddosify.com)\n*\n*   This program is free software: you can redistribute it and/or modify\n*   it under the terms of the GNU Affero General Public License as published\n*   by the Free Software Foundation, either version 3 of the License, or\n*   (at your option) any later version.\n*\n*   This program is distributed in the hope that it will be useful,\n*   but WITHOUT ANY WARRANTY; without even the implied warranty of\n*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n*   GNU Affero General Public License for more details.\n*\n*   You should have received a copy of the GNU Affero General Public License\n*   along with this program.  If not, see <https://www.gnu.org/licenses/>.\n*\n */\n\npackage types\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"go.ddosify.com/ddosify/core/proxy\"\n)\n\nfunc newDummyHammer() Hammer {\n\treturn Hammer{\n\t\tProxy:             proxy.Proxy{Strategy: proxy.ProxyTypeSingle},\n\t\tReportDestination: DefaultOutputType,\n\t\tScenario: Scenario{\n\t\t\tSteps: []ScenarioStep{\n\t\t\t\t{\n\t\t\t\t\tID:     1,\n\t\t\t\t\tMethod: \"GET\",\n\t\t\t\t\tURL:    \"http://127.0.0.1\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc TestHammerValidAttackType(t *testing.T) {\n\tvar loadTypes = [...]string{\"linear\", \"incremental\", \"waved\"}\n\n\tfor _, l := range loadTypes {\n\t\th := newDummyHammer()\n\t\th.LoadType = l\n\n\t\tif err := h.Validate(); err != nil {\n\t\t\tt.Errorf(\"TestHammerValidAttackType errored: %v\", err)\n\t\t}\n\t}\n}\n\nfunc TestHammerInValidAttackType(t *testing.T) {\n\th := newDummyHammer()\n\th.LoadType = \"strees\"\n\n\tif err := h.Validate(); err == nil {\n\t\tt.Errorf(\"TestHammerInValidAttackType errored\")\n\t}\n}\n\nfunc TestHammerValidAuth(t *testing.T) {\n\tfor _, v := range supportedAuthentications {\n\t\th := newDummyHammer()\n\t\th.Scenario.Steps[0].Auth = Auth{\n\t\t\tType:     v,\n\t\t\tUsername: \"test\",\n\t\t\tPassword: \"123\",\n\t\t}\n\n\t\tif err := h.Validate(); err != nil {\n\t\t\tt.Errorf(\"TestHammerValidAuth errored: %v\", err)\n\t\t}\n\t}\n\n}\n\nfunc TestHammerInValidAuth(t *testing.T) {\n\th := newDummyHammer()\n\th.Scenario.Steps[0].Auth = Auth{\n\t\tType:     \"invalidAuthType\",\n\t\tUsername: \"test\",\n\t\tPassword: \"123\",\n\t}\n\n\tif err := h.Validate(); err == nil {\n\t\tt.Errorf(\"TestHammerInValidReportDestination errored\")\n\t}\n}\n\nfunc TestHammerValidScenario(t *testing.T) {\n\t// Single Scenario\n\tfor _, m := range supportedProtocolMethods {\n\t\th := newDummyHammer()\n\t\th.Scenario = Scenario{\n\t\t\tSteps: []ScenarioStep{\n\t\t\t\t{\n\t\t\t\t\tID:     1,\n\t\t\t\t\tMethod: m,\n\t\t\t\t\tURL:    \"https://127.0.0.1\",\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tif err := h.Validate(); err != nil {\n\t\t\tt.Errorf(\"TestHammerValidScenario single scenario errored: %v\", err)\n\t\t}\n\t}\n\n\t// Multiple Scenario\n\n\tfor _, m := range supportedProtocolMethods {\n\t\th := newDummyHammer()\n\t\th.Scenario = Scenario{\n\t\t\tSteps: []ScenarioStep{\n\t\t\t\t{\n\t\t\t\t\tID:     1,\n\t\t\t\t\tMethod: m,\n\t\t\t\t\tURL:    \"https://127.0.0.1\",\n\t\t\t\t}, {\n\t\t\t\t\tID:     2,\n\t\t\t\t\tURL:    \"https://127.0.0.1\",\n\t\t\t\t\tMethod: m,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tif err := h.Validate(); err != nil {\n\t\t\tt.Errorf(\"TestHammerValidScenario multi scenario errored: %v\", err)\n\t\t}\n\t}\n\n}\n\nfunc TestHammerEmptyScenario(t *testing.T) {\n\th := newDummyHammer()\n\th.Scenario = Scenario{}\n\n\tif err := h.Validate(); err == nil {\n\t\tt.Errorf(\"TestHammerEmptyScenario errored\")\n\t}\n}\n\nfunc TestHammerInvalidScenarioMethod(t *testing.T) {\n\t// Single Scenario\n\th := newDummyHammer()\n\th.Scenario = Scenario{\n\t\tSteps: []ScenarioStep{\n\t\t\t{\n\t\t\t\tID:     1,\n\t\t\t\tMethod: \"GETT\",\n\t\t\t},\n\t\t},\n\t}\n\tif err := h.Validate(); err == nil {\n\t\tt.Errorf(\"TestHammerInvalidScenarioMethod errored\")\n\t}\n\n\t// Multi Scenario\n\th = newDummyHammer()\n\th.Scenario = Scenario{\n\t\tSteps: []ScenarioStep{\n\t\t\t{\n\t\t\t\tID:     1,\n\t\t\t\tMethod: supportedProtocolMethods[1],\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:     1,\n\t\t\t\tMethod: \"GETT\",\n\t\t\t},\n\t\t},\n\t}\n\tif err := h.Validate(); err == nil {\n\t\tt.Errorf(\"TestHammerInvalidScenarioMethod errored\")\n\t}\n}\n\nfunc TestHammerEmptyScenarioStepID(t *testing.T) {\n\t// Single Scenario\n\th := newDummyHammer()\n\th.Scenario = Scenario{\n\t\tSteps: []ScenarioStep{\n\t\t\t{\n\t\t\t\tMethod: supportedProtocolMethods[1],\n\t\t\t},\n\t\t},\n\t}\n\tif err := h.Validate(); err == nil {\n\t\tt.Errorf(\"1- TestHammerEmptyScenarioStepID should be errored\")\n\t}\n\n\t// Multi Scenario\n\th = newDummyHammer()\n\th.Scenario = Scenario{\n\t\tSteps: []ScenarioStep{\n\t\t\t{\n\t\t\t\tID:     1,\n\t\t\t\tMethod: supportedProtocolMethods[1],\n\t\t\t},\n\t\t\t{\n\t\t\t\tMethod: supportedProtocolMethods[1],\n\t\t\t},\n\t\t},\n\t}\n\tif err := h.Validate(); err == nil {\n\t\tt.Errorf(\"2- TestHammerEmptyScenarioStepID should be errored\")\n\t}\n}\n\nfunc TestHammerDuplicateScenarioStepID(t *testing.T) {\n\t// Single Scenario\n\th := newDummyHammer()\n\th.Scenario = Scenario{\n\t\tSteps: []ScenarioStep{\n\t\t\t{\n\t\t\t\tID:     1,\n\t\t\t\tMethod: supportedProtocolMethods[1],\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:     2,\n\t\t\t\tMethod: supportedProtocolMethods[1],\n\t\t\t},\n\t\t\t{\n\t\t\t\tID:     2,\n\t\t\t\tMethod: supportedProtocolMethods[1],\n\t\t\t},\n\t\t},\n\t}\n\tif err := h.Validate(); err == nil {\n\t\tt.Errorf(\"TestHammerDuplicateScenarioStepID should be errored\")\n\t}\n}\n\nfunc TestHammerStepSleep(t *testing.T) {\n\tt.Parallel()\n\n\tinvalidSleeps := []string{\n\t\t\"-300\",\n\t\t\"-300-500\",\n\t\t\"300s\",\n\t\t\"as\",\n\t\t\"100000\", // More than maxSleep\n\t}\n\tvalidSleeps := []string{\n\t\t\"300-500\",\n\t\t\"1000\",\n\t}\n\n\ttests := []struct {\n\t\tname      string\n\t\tsleep     string\n\t\tshouldErr bool\n\t}{\n\t\t{\"Invalid 1\", invalidSleeps[0], true},\n\t\t{\"Invalid 2\", invalidSleeps[1], true},\n\t\t{\"Invalid 3\", invalidSleeps[2], true},\n\t\t{\"Invalid 4\", invalidSleeps[3], true},\n\t\t{\"Invalid 5\", invalidSleeps[4], true},\n\t\t{\"ValidRange\", validSleeps[0], false},\n\t\t{\"ValidDuration\", validSleeps[1], false},\n\t}\n\n\tfor _, tc := range tests {\n\t\ttest := tc\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\th := newDummyHammer()\n\t\t\th.Scenario = Scenario{\n\t\t\t\tSteps: []ScenarioStep{\n\t\t\t\t\t{\n\t\t\t\t\t\tID:     1,\n\t\t\t\t\t\tURL:    \"target.com\",\n\t\t\t\t\t\tMethod: supportedProtocolMethods[1],\n\t\t\t\t\t\tSleep:  test.sleep,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\terr := h.Validate()\n\n\t\t\tif test.shouldErr {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Errorf(\"Should be errored\")\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"Error occurred %v\", err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t})\n\t}\n}\n\nfunc TestHammerInvalidManualLoadDuration(t *testing.T) {\n\t// Duration = 0\n\th := newDummyHammer()\n\th.TimeRunCountMap = TimeRunCount{\n\t\t{Duration: 10, Count: 10},\n\t\t{Duration: 0, Count: 10},\n\t}\n\n\tif err := h.Validate(); err == nil {\n\t\tt.Errorf(\"TestHammerInvalidManualLoadDuration errored\")\n\t}\n\n\t// Duration is negatie\n\th = newDummyHammer()\n\th.TimeRunCountMap = TimeRunCount{\n\t\t{Duration: 10, Count: 10},\n\t\t{Duration: -1, Count: 10},\n\t}\n\n\tif err := h.Validate(); err == nil {\n\t\tt.Errorf(\"TestHammerInvalidManualLoadDuration errored\")\n\t}\n}\n\nfunc TestHammerAccessingNotDefinedCsvEnvs(t *testing.T) {\n\th := newDummyHammer()\n\th.TestDataConf = make(map[string]CsvConf)\n\n\th.TestDataConf[\"info\"] = CsvConf{\n\t\tPath:          \"\",\n\t\tDelimiter:     \"\",\n\t\tSkipFirstLine: false,\n\t\tVars: map[string]Tag{\n\t\t\t\"0\": {\n\t\t\t\tTag:  \"a\",\n\t\t\t\tType: \"string\",\n\t\t\t},\n\t\t},\n\t\tSkipEmptyLine: false,\n\t\tAllowQuota:    false,\n\t\tOrder:         \"\",\n\t}\n\n\th.Scenario.Steps = []ScenarioStep{\n\t\t{\n\t\t\tID:   1,\n\t\t\tName: \"x\",\n\n\t\t\tMethod: \"GET\",\n\t\t\tHeaders: map[string]string{\n\t\t\t\t\"{{data.info.x}}\": \"X\",\n\t\t\t},\n\t\t\tPayload: \"\",\n\t\t\tURL:     \"https://ddosify.com\",\n\t\t},\n\t}\n\terr := h.Validate()\n\n\tvar environmentNotDefined EnvironmentNotDefinedError\n\n\tif !errors.As(err, &environmentNotDefined) {\n\t\tt.Errorf(\"Should be EnvironmentNotDefinedError\")\n\t}\n}\n"
  },
  {
    "path": "ddosify_engine/core/types/regex/regex.go",
    "content": "package regex\n\nconst DynamicVariableRegex = `\\{{(_)[^}]+\\}}`\nconst JsonDynamicVariableRegex = `\\\"{{(_)[^}]+\\}}\"`\n\nconst EnvironmentVariableRegex = `{{[a-zA-Z$][a-zA-Z0-9_().-]*}}`\nconst JsonEnvironmentVarRegex = `\\\"{{[a-zA-Z$][a-zA-Z0-9_().-]*}}\"`\n"
  },
  {
    "path": "ddosify_engine/core/types/regex/regex_test.go",
    "content": "package regex\n\nimport (\n\t\"regexp\"\n\t\"testing\"\n)\n\nfunc TestDynamicVariableRegex(t *testing.T) {\n\tre := regexp.MustCompile(DynamicVariableRegex)\n\t// Sub Tests\n\ttests := []struct {\n\t\tname        string\n\t\turl         string\n\t\tshouldMatch bool\n\t}{\n\t\t{\"Match1\", \"https://example.com/{{_abc}}\", true},\n\t\t{\"Match2\", \"https://example.com/{{_timestamp}}\", true},\n\t\t{\"Match3\", \"https://example.com/aaa/{{_timestamp}}\", true},\n\t\t{\"Match4\", \"https://example.com/aaa/{{_timestamp}}/bbb\", true},\n\t\t{\"Match5\", \"https://example.com/{{_timestamp}}/{_abc}\", true},\n\t\t{\"Match6\", \"https://example.com/{{_abc/{{_timestamp}}\", true},\n\t\t{\"Match7\", \"https://example.com/_aaa/{{_timestamp}}\", true},\n\n\t\t{\"Not Match1\", \"https://example.com/{{_abc\", false},\n\t\t{\"Not Match2\", \"https://example.com/{{_abc}\", false},\n\t\t{\"Not Match3\", \"https://example.com/_abc\", false},\n\t\t{\"Not Match4\", \"https://example.com/{{abc\", false},\n\t\t{\"Not Match5\", \"https://example.com/abc\", false},\n\t\t{\"Not Match6\", \"https://example.com/abc/{{cc}}\", false},\n\t\t{\"Not Match7\", \"https://example.com/abc/{{cc}}/fcf\", false},\n\t}\n\n\tfor _, test := range tests {\n\t\ttf := func(t *testing.T) {\n\t\t\tmatched := re.MatchString(test.url)\n\n\t\t\tif test.shouldMatch != matched {\n\t\t\t\tt.Errorf(\"Name: %s, ShouldMatch: %t, Matched: %t\\n\", test.name, test.shouldMatch, matched)\n\t\t\t}\n\n\t\t}\n\t\tt.Run(test.name, tf)\n\t}\n}\n\nfunc TestEnvironmentVariableRegex(t *testing.T) {\n\tre := regexp.MustCompile(EnvironmentVariableRegex)\n\t// Sub Tests\n\ttests := []struct {\n\t\tname        string\n\t\turl         string\n\t\tshouldMatch bool\n\t}{\n\t\t{\"Match1\", \"{{a}}\", true},\n\t\t{\"Match2\", \"{{ab}}\", true},\n\t\t{\"Match3\", \"{{ab1}}\", true},\n\t\t{\"Match4\", \"{{Ab1}}/bbb\", true},\n\t\t{\"Match5\", \"{{ABC}}/{_abc}\", true},\n\t\t{\"Match6\", \"{{_abc/{{ABC__fc_111}}\", true},\n\t\t{\"Match7\", \"{{a_b}}\", true},\n\t\t{\"Match8\", \"xx{{a}}\", true},\n\t\t{\"Match9\", \"{{a}}bb\", true},\n\t\t{\"Match10\", \"cx{{a}}vc\", true},\n\t\t{\"Match10\", \"cx {{a}}vc\", true},\n\t\t{\"Match11\", \"cx{{a}} vc\", true},\n\t\t{\"Match11\", \"cx{{a}}_-\", true},\n\t\t{\"Match12\", \"{{a-v}}\", true},\n\t\t{\"Match13\", \"{{AV-}}\", true},\n\n\t\t{\"Not Match1\", \"{{}}\", false},\n\t\t{\"Not Match2\", \"{{_abc}}\", false},\n\t\t{\"Not Match4\", \"{{abc!}}\", false},\n\t\t{\"Not Match6\", \"{{_A}}\", false},\n\t\t{\"Not Match7\", \"{{_AB_2}}\", false},\n\t\t{\"Not Match8\", \"{{£AB_2}}\", false},\n\t\t{\"Not Match8\", \"{{3AB_2}}\", false},\n\t\t{\"Not Match8\", \"{{%3AB_2}}\", false},\n\t}\n\n\tfor _, test := range tests {\n\t\ttf := func(t *testing.T) {\n\t\t\tmatched := re.MatchString(test.url)\n\n\t\t\tif test.shouldMatch != matched {\n\t\t\t\tt.Errorf(\"Name: %s, ShouldMatch: %t, Matched: %t\\n\", test.name, test.shouldMatch, matched)\n\t\t\t}\n\n\t\t}\n\t\tt.Run(test.name, tf)\n\t}\n}\n"
  },
  {
    "path": "ddosify_engine/core/types/response.go",
    "content": "/*\n*\n*\tDdosify - Load testing tool for any web system.\n*   Copyright (C) 2021  Ddosify (https://ddosify.com)\n*\n*   This program is free software: you can redistribute it and/or modify\n*   it under the terms of the GNU Affero General Public License as published\n*   by the Free Software Foundation, either version 3 of the License, or\n*   (at your option) any later version.\n*\n*   This program is distributed in the hope that it will be useful,\n*   but WITHOUT ANY WARRANTY; without even the implied warranty of\n*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n*   GNU Affero General Public License for more details.\n*\n*   You should have received a copy of the GNU Affero General Public License\n*   along with this program.  If not, see <https://www.gnu.org/licenses/>.\n*\n */\n\npackage types\n\nimport (\n\t\"net/http\"\n\t\"net/url\"\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n)\n\n// ScenarioResult is corresponding to Scenario. Each Scenario has a ScenarioResult after the scenario is played.\ntype ScenarioResult struct {\n\t// First request start time for the Scenario\n\tStartTime time.Time\n\n\tProxyAddr   *url.URL\n\tStepResults []*ScenarioStepResult\n\n\t// Dynamic field for extra data needs in response object consumers.\n\tOthers map[string]interface{}\n}\n\n// ScenarioStepResult is corresponding to ScenarioStep.\ntype ScenarioStepResult struct {\n\t// ID of the ScenarioStep\n\tStepID uint16\n\n\t// Name of the ScenarioStep\n\tStepName string\n\n\t// Each request has a unique ID.\n\tRequestID uuid.UUID\n\n\t// Returned status code. Has different meaning for different protocols.\n\tStatusCode int\n\n\t// Time of the request call.\n\tRequestTime time.Time\n\n\t// Total duration. From request sending to full response receiving.\n\tDuration time.Duration\n\n\t// Response content length\n\tContentLength int64\n\n\t// Error occurred at request time.\n\tErr RequestError\n\n\t// Url\n\tUrl string\n\n\t// Method\n\tMethod string\n\n\t// Request Headers\n\tReqHeaders http.Header\n\n\t// Request Body\n\tReqBody []byte\n\n\t// Response Headers\n\tRespHeaders http.Header\n\n\t// Response Body\n\tRespBody []byte\n\n\t// Protocol specific metrics. For ex: DNSLookupDuration: 1s for HTTP\n\tCustom map[string]interface{}\n\n\t// Usable envs in this step\n\tUsableEnvs map[string]interface{}\n\n\t// Captured envs in this step\n\tExtractedEnvs map[string]interface{}\n\n\t// Failed captures and their reasons\n\tFailedCaptures map[string]string\n\n\t// Failed assertion rules and received values\n\tFailedAssertions []FailedAssertion\n}\n"
  },
  {
    "path": "ddosify_engine/core/types/scenario.go",
    "content": "/*\n*\n*\tDdosify - Load testing tool for any web system.\n*   Copyright (C) 2021  Ddosify (https://ddosify.com)\n*\n*   This program is free software: you can redistribute it and/or modify\n*   it under the terms of the GNU Affero General Public License as published\n*   by the Free Software Foundation, either version 3 of the License, or\n*   (at your option) any later version.\n*\n*   This program is distributed in the hope that it will be useful,\n*   but WITHOUT ANY WARRANTY; without even the implied warranty of\n*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n*   GNU Affero General Public License for more details.\n*\n*   You should have received a copy of the GNU Affero General Public License\n*   along with this program.  If not, see <https://www.gnu.org/licenses/>.\n*\n */\n\npackage types\n\nimport (\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"os\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\tvalidator \"github.com/asaskevich/govalidator\"\n\t\"go.ddosify.com/ddosify/core/util\"\n)\n\n// Constants for Scenario field values\nconst (\n\t// Constants of the Protocol types\n\tProtocolHTTP  = \"HTTP\"\n\tProtocolHTTPS = \"HTTPS\"\n\n\t// Constants of the Auth types\n\tAuthHttpBasic = \"basic\"\n\n\t// Max sleep in ms (90s)\n\tmaxSleep = 90000\n\n\t// Should match environment variables, reference\n\tEnvironmentVariableRegexStr = `{{[a-zA-Z$][a-zA-Z0-9_().-]*}}`\n\n\t// Should match environment variables, definition, exact match\n\tEnvironmentVariableNameStr = `^[a-zA-Z][a-zA-Z0-9_-]*$`\n)\n\n// SupportedProtocols should be updated whenever a new requester.Requester interface implemented\nvar SupportedProtocols = [...]string{ProtocolHTTP, ProtocolHTTPS}\nvar supportedProtocolMethods = []string{\n\thttp.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete,\n\thttp.MethodPatch, http.MethodHead, http.MethodOptions,\n}\nvar supportedAuthentications = []string{\n\tAuthHttpBasic,\n}\n\nvar envVarRegexp *regexp.Regexp\nvar envVarNameRegexp *regexp.Regexp\n\nfunc init() {\n\tenvVarRegexp = regexp.MustCompile(EnvironmentVariableRegexStr)\n\tenvVarNameRegexp = regexp.MustCompile(EnvironmentVariableNameStr)\n}\n\n// Scenario struct contains a list of ScenarioStep so scenario.ScenarioService can execute the scenario step by step.\ntype Scenario struct {\n\tSteps   []ScenarioStep\n\tEnvs    map[string]interface{}\n\tCsvVars []string           // only for validation\n\tData    map[string]CsvData // populated data\n}\n\nfunc (s *Scenario) validate() error {\n\tstepIds := make(map[uint16]struct{}, len(s.Steps))\n\tdefinedEnvs := map[string]struct{}{}\n\n\t// add global envs\n\tfor key := range s.Envs {\n\t\tif !envVarNameRegexp.MatchString(key) { // not a valid env definition\n\t\t\treturn fmt.Errorf(\"env key is not valid: %s\", key)\n\t\t}\n\t\tdefinedEnvs[key] = struct{}{} // exist\n\t}\n\t// add csv vars\n\tfor _, key := range s.CsvVars { // data.info.name\n\t\tsplitted := strings.Split(key, \".\")\n\t\tif len(splitted) > 3 {\n\t\t\treturn fmt.Errorf(\"csv key can not have dot in it: %s\", key)\n\t\t}\n\t\tfor _, s := range splitted {\n\t\t\tif !envVarNameRegexp.MatchString(s) { // not a valid env definition\n\t\t\t\treturn fmt.Errorf(\"csv key is not valid: %s\", key)\n\t\t\t}\n\t\t}\n\t\tdefinedEnvs[key] = struct{}{} // exist\n\t}\n\n\tfor _, st := range s.Steps {\n\t\tif err := st.validate(definedEnvs); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// enrich Envs map with captured envs from each step\n\t\tfor _, ce := range st.EnvsToCapture {\n\t\t\tif !envVarNameRegexp.MatchString(ce.Name) { // not a valid env definition\n\t\t\t\treturn fmt.Errorf(\"captured env key is not valid: %s\", ce.Name)\n\t\t\t}\n\t\t\tdefinedEnvs[ce.Name] = struct{}{}\n\t\t}\n\n\t\tif _, ok := stepIds[st.ID]; ok {\n\t\t\treturn fmt.Errorf(\"duplicate step id: %d\", st.ID)\n\t\t}\n\t\tstepIds[st.ID] = struct{}{}\n\t}\n\treturn nil\n}\n\nfunc checkEnvsValidInStep(st *ScenarioStep, definedEnvs map[string]struct{}) error {\n\tvar err error\n\tmatchInEnvs := func(matches []string) error {\n\t\tfor _, v := range matches {\n\t\t\tif _, ok := definedEnvs[v[2:len(v)-2]]; !ok { // {{....}}\n\t\t\t\t// utility functions are matched too, check if starts with rand\n\t\t\t\t// TODO: find a better solution about utility functions and validation checks\n\n\t\t\t\tif strings.HasPrefix(v[2:len(v)-2], \"rand(\") {\n\t\t\t\t\tif _, ok := definedEnvs[v[7:len(v)-3]]; ok {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif strings.HasPrefix(v[2:len(v)-2], \"$\") {\n\t\t\t\t\tvarName := v[3 : len(v)-2]\n\t\t\t\t\tif _, ok := os.LookupEnv(varName); ok {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\treturn EnvironmentNotDefinedError{\n\t\t\t\t\t\tmsg: fmt.Sprintf(\"%s is not found in the operating system environment variables\", v),\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn EnvironmentNotDefinedError{\n\t\t\t\t\tmsg: fmt.Sprintf(\"%s is not defined to use by global and captured environments\", v),\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\n\tf := func(source string) error {\n\t\tmatches := envVarRegexp.FindAllString(source, -1)\n\t\treturn matchInEnvs(matches)\n\t}\n\n\t// check env usage in url\n\terr = f(st.URL)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// check env usage in header\n\tfor k, v := range st.Headers {\n\t\terr = f(k)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\terr = f(v)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// check env usage in payload\n\terr = f(st.Payload)\n\treturn err\n\n}\n\n// ScenarioStep represents one step of a Scenario.\n// This struct should be able to include all necessary data in a network packet for SupportedProtocols.\ntype ScenarioStep struct {\n\t// ID of the Item. Should be given by the client.\n\tID uint16\n\n\t// Name of the Item.\n\tName string\n\n\t// Request Method\n\tMethod string\n\n\t// Authentication\n\tAuth Auth\n\n\t// A TLS cert\n\tCert tls.Certificate\n\n\t// A TLS cert pool\n\tCertPool *x509.CertPool\n\n\t// Request Headers\n\tHeaders map[string]string\n\n\t// Request payload\n\tPayload string\n\n\t// Target URL\n\tURL string\n\n\t// Connection timeout duration of the request in seconds\n\tTimeout int\n\n\t// Sleep duration after running the step. Can be a time range like \"300-500\" or an exact duration like \"350\" in ms\n\tSleep string\n\n\t// Protocol specific request parameters. For ex: DisableRedirects:true for Http requests\n\tCustom map[string]interface{}\n\n\t// Envs to capture from response of this step\n\tEnvsToCapture []EnvCaptureConf\n\n\t// assertion expressions\n\tAssertions []string\n}\n\ntype SourceType string\n\nconst (\n\tHeader SourceType = \"header\"\n\tBody   SourceType = \"body\"\n\tCookie SourceType = \"cookies\"\n)\n\ntype RegexCaptureConf struct {\n\tExp *string `json:\"exp\"`\n\tNo  int     `json:\"matchNo\"`\n}\n\ntype EnvCaptureConf struct {\n\tJsonPath   *string           `json:\"json_path\"`\n\tXpath      *string           `json:\"xpath\"`\n\tXpathHtml  *string           `json:\"xpath_html\"`\n\tRegExp     *RegexCaptureConf `json:\"regexp\"`\n\tName       string            `json:\"as\"`\n\tFrom       SourceType        `json:\"from\"`\n\tKey        *string           `json:\"header_key\"`\n\tCookieName *string           `json:\"cookie_name\"`\n}\n\ntype CsvData struct {\n\tRows   []map[string]interface{}\n\tRandom bool\n}\n\n// Auth struct should be able to include all necessary authentication realated data for supportedAuthentications.\ntype Auth struct {\n\tType     string\n\tUsername string\n\tPassword string\n}\n\nfunc (si *ScenarioStep) validate(definedEnvs map[string]struct{}) error {\n\tif !util.StringInSlice(si.Method, supportedProtocolMethods) {\n\t\treturn fmt.Errorf(\"unsupported Request Method: %s\", si.Method)\n\t}\n\tif si.Auth != (Auth{}) && !util.StringInSlice(si.Auth.Type, supportedAuthentications) {\n\t\treturn fmt.Errorf(\"unsupported Authentication Method (%s) \", si.Auth.Type)\n\t}\n\tif si.ID == 0 {\n\t\treturn fmt.Errorf(\"step ID should be greater than zero\")\n\t}\n\tif !envVarRegexp.MatchString(si.URL) && !validator.IsURL(strings.ReplaceAll(si.URL, \" \", \"_\")) {\n\t\treturn fmt.Errorf(\"target is not valid: %s\", si.URL)\n\t}\n\tif si.Sleep != \"\" {\n\t\tsleep := strings.Split(si.Sleep, \"-\")\n\n\t\t// Avoid invalid syntax like \"-300-500\"\n\t\tif len(sleep) > 2 {\n\t\t\treturn fmt.Errorf(\"sleep expression is not valid: %s\", si.Sleep)\n\t\t}\n\n\t\t// Validate string to int conversion\n\t\tfor _, s := range sleep {\n\t\t\tdur, err := strconv.Atoi(s)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"sleep is not valid: %s\", si.Sleep)\n\t\t\t}\n\n\t\t\tif dur > maxSleep {\n\t\t\t\treturn fmt.Errorf(\"maximum sleep limit exceeded. provided: %d ms, maximum: %d ms\", dur, maxSleep)\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, conf := range si.EnvsToCapture {\n\t\terr := validateCaptureConf(conf)\n\t\tif err != nil {\n\t\t\treturn wrapAsScenarioValidationError(err)\n\t\t}\n\t}\n\n\t// check if referred envs in current step has already been defined or not\n\tif err := checkEnvsValidInStep(si, definedEnvs); err != nil {\n\t\treturn wrapAsScenarioValidationError(err)\n\t}\n\n\treturn nil\n}\n\nfunc wrapAsScenarioValidationError(err error) ScenarioValidationError {\n\treturn ScenarioValidationError{\n\t\tmsg:        fmt.Sprintf(\"ScenarioValidationError %v\", err),\n\t\twrappedErr: err,\n\t}\n}\n\nfunc validateCaptureConf(conf EnvCaptureConf) error {\n\tif !(conf.From == Header || conf.From == Body || conf.From == Cookie) {\n\t\treturn CaptureConfigError{\n\t\t\tmsg: fmt.Sprintf(\"invalid \\\"from\\\" type in capture env : %s\", conf.From),\n\t\t}\n\t}\n\n\tif conf.From == Header && conf.Key == nil {\n\t\treturn CaptureConfigError{\n\t\t\tmsg: fmt.Sprintf(\"%s, header key must be specified\", conf.Name),\n\t\t}\n\t}\n\n\tif conf.From == Body && conf.JsonPath == nil && conf.RegExp == nil && conf.Xpath == nil && conf.XpathHtml == nil {\n\t\treturn CaptureConfigError{\n\t\t\tmsg: fmt.Sprintf(\"%s, one of json_path, regexp, xpath or xpath_html key must be specified when extracting from body\", conf.Name),\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc ParseTLS(certFile, keyFile string) (tls.Certificate, *x509.CertPool, error) {\n\tif certFile == \"\" || keyFile == \"\" {\n\t\treturn tls.Certificate{}, nil, nil\n\t}\n\n\t// Read the key pair to create certificate\n\tcert, err := tls.LoadX509KeyPair(certFile, keyFile)\n\tif err != nil {\n\t\treturn tls.Certificate{}, nil, err\n\t}\n\n\t// Create a CA certificate pool and add cert.pem to it\n\tcaCert, err := ioutil.ReadFile(certFile)\n\tif err != nil {\n\t\treturn tls.Certificate{}, nil, err\n\t}\n\tpool := x509.NewCertPool()\n\tpool.AppendCertsFromPEM(caCert)\n\n\treturn cert, pool, nil\n}\n\nfunc IsTargetValid(url string) error {\n\tif !envVarRegexp.MatchString(url) && !validator.IsURL(strings.ReplaceAll(url, \" \", \"_\")) {\n\t\treturn fmt.Errorf(\"target is not valid: %s\", url)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "ddosify_engine/core/types/scenario_test.go",
    "content": "package types\n\nimport (\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"errors\"\n\t\"net/http\"\n\t\"testing\"\n)\n\nfunc TestScenarioStepValid_EnvVariableInHeader(t *testing.T) {\n\turl := \"https://test.com\"\n\tst := ScenarioStep{\n\t\tID:       22,\n\t\tName:     \"\",\n\t\tMethod:   http.MethodGet,\n\t\tAuth:     Auth{},\n\t\tCert:     tls.Certificate{},\n\t\tCertPool: &x509.CertPool{},\n\t\tHeaders: map[string]string{\n\t\t\t\"{{ARGENTINA}}\": \"{{ARGENTINA}}\",\n\t\t},\n\t\tPayload:       \"\",\n\t\tURL:           url,\n\t\tTimeout:       0,\n\t\tSleep:         \"\",\n\t\tCustom:        map[string]interface{}{},\n\t\tEnvsToCapture: []EnvCaptureConf{},\n\t}\n\n\tdefinedEnvs := map[string]struct{}{}\n\terr := st.validate(definedEnvs)\n\n\tvar environmentNotDefined EnvironmentNotDefinedError\n\n\tif !errors.As(err, &environmentNotDefined) {\n\t\tt.Errorf(\"Should be EnvironmentNotDefinedError\")\n\t}\n\n\tt.Logf(\"%v\", environmentNotDefined)\n}\n\nfunc TestScenarioStepValid_EnvVariableInPayload(t *testing.T) {\n\turl := \"https://test.com\"\n\tst := ScenarioStep{\n\t\tID:            22,\n\t\tName:          \"\",\n\t\tMethod:        http.MethodGet,\n\t\tAuth:          Auth{},\n\t\tCert:          tls.Certificate{},\n\t\tCertPool:      &x509.CertPool{},\n\t\tHeaders:       map[string]string{},\n\t\tPayload:       \"{{ARGENTINA}}\",\n\t\tURL:           url,\n\t\tTimeout:       0,\n\t\tSleep:         \"\",\n\t\tCustom:        map[string]interface{}{},\n\t\tEnvsToCapture: []EnvCaptureConf{},\n\t}\n\n\tdefinedEnvs := map[string]struct{}{}\n\terr := st.validate(definedEnvs)\n\n\tvar environmentNotDefined EnvironmentNotDefinedError\n\n\tif !errors.As(err, &environmentNotDefined) {\n\t\tt.Errorf(\"Should be EnvironmentNotDefinedError\")\n\t}\n\n\tt.Logf(\"%v\", environmentNotDefined)\n}\n\nfunc TestScenarioStepValid_EnvVariableInURL(t *testing.T) {\n\turl := \"https://test.com/{{ARGENTINA}}\"\n\tst := ScenarioStep{\n\t\tID:            22,\n\t\tName:          \"\",\n\t\tMethod:        http.MethodGet,\n\t\tAuth:          Auth{},\n\t\tCert:          tls.Certificate{},\n\t\tCertPool:      &x509.CertPool{},\n\t\tHeaders:       map[string]string{},\n\t\tPayload:       \"\",\n\t\tURL:           url,\n\t\tTimeout:       0,\n\t\tSleep:         \"\",\n\t\tCustom:        map[string]interface{}{},\n\t\tEnvsToCapture: []EnvCaptureConf{},\n\t}\n\n\tdefinedEnvs := map[string]struct{}{}\n\terr := st.validate(definedEnvs)\n\n\tvar environmentNotDefined EnvironmentNotDefinedError\n\n\tif !errors.As(err, &environmentNotDefined) {\n\t\tt.Errorf(\"Should be EnvironmentNotDefinedError\")\n\t}\n\n\tt.Logf(\"%v\", environmentNotDefined)\n}\n\nfunc TestScenarioStep_InvalidCaptureConfig(t *testing.T) {\n\turl := \"https://test.com\"\n\n\tstEmptyFromField := ScenarioStep{\n\t\tID:     22,\n\t\tName:   \"\",\n\t\tMethod: http.MethodGet,\n\t\tURL:    url,\n\t\tEnvsToCapture: []EnvCaptureConf{{\n\t\t\tName: \"FromHeader\",\n\t\t\tFrom: \"\",\n\t\t}},\n\t}\n\n\tstNoHeaderKey := ScenarioStep{\n\t\tID:     22,\n\t\tName:   \"\",\n\t\tMethod: http.MethodGet,\n\t\tURL:    url,\n\t\tEnvsToCapture: []EnvCaptureConf{{\n\t\t\tName: \"FromHeader\",\n\t\t\tFrom: SourceType(Header),\n\t\t}},\n\t}\n\n\tstNoBodySpecifierKey := ScenarioStep{\n\t\tID:     22,\n\t\tName:   \"\",\n\t\tMethod: http.MethodGet,\n\t\tURL:    url,\n\t\tEnvsToCapture: []EnvCaptureConf{{\n\t\t\tName: \"FromBody\",\n\t\t\tFrom: SourceType(Body),\n\t\t}},\n\t}\n\n\tdefinedEnvs := map[string]struct{}{}\n\n\ttests := []struct {\n\t\tname string\n\t\tst   ScenarioStep\n\t}{\n\t\t{\"NoHeaderKey\", stNoHeaderKey},\n\t\t{\"NoBodySpecifierKey\", stNoBodySpecifierKey},\n\t\t{\"EmptyFromField\", stEmptyFromField},\n\t}\n\n\tfor _, test := range tests {\n\t\ttf := func(t *testing.T) {\n\t\t\t// Arrange\n\t\t\terr := test.st.validate(definedEnvs)\n\n\t\t\tvar captureConfigError CaptureConfigError\n\n\t\t\tif !errors.As(err, &captureConfigError) {\n\t\t\t\tt.Errorf(\"Should be CaptureConfigError\")\n\t\t\t}\n\t\t}\n\n\t\tt.Run(test.name, tf)\n\t}\n}\n\nfunc TestScenarioStepValid_OSEnvVariableInPayload(t *testing.T) {\n\turl := \"https://test.com\"\n\tst := ScenarioStep{\n\t\tID:            22,\n\t\tName:          \"\",\n\t\tMethod:        http.MethodGet,\n\t\tAuth:          Auth{},\n\t\tCert:          tls.Certificate{},\n\t\tCertPool:      &x509.CertPool{},\n\t\tHeaders:       map[string]string{},\n\t\tPayload:       \"{{$SOME_OS_ENV_XX}}\",\n\t\tURL:           url,\n\t\tTimeout:       0,\n\t\tSleep:         \"\",\n\t\tCustom:        map[string]interface{}{},\n\t\tEnvsToCapture: []EnvCaptureConf{},\n\t}\n\n\tdefinedEnvs := map[string]struct{}{}\n\terr := st.validate(definedEnvs)\n\n\tvar environmentNotDefined EnvironmentNotDefinedError\n\n\tif !errors.As(err, &environmentNotDefined) {\n\t\tt.Errorf(\"Should be EnvironmentNotDefinedError\")\n\t}\n\n\tt.Logf(\"%v\", environmentNotDefined)\n}\n"
  },
  {
    "path": "ddosify_engine/core/util/buffer_pool.go",
    "content": "package util\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n)\n\n// Factory is a function to create new connections.\ntype BufferFactoryMethod func() *bytes.Buffer\ntype BufferCloseMethod func(*bytes.Buffer)\n\nfunc NewBufferPool(initialCap, maxCap int, factory BufferFactoryMethod, close BufferCloseMethod) (*Pool[*bytes.Buffer], error) {\n\tif initialCap < 0 || maxCap <= 0 || initialCap > maxCap {\n\t\treturn nil, errors.New(\"invalid capacity settings\")\n\t}\n\n\tpool := &Pool[*bytes.Buffer]{\n\t\tItems:   make(chan *bytes.Buffer, maxCap),\n\t\tFactory: factory,\n\t\tClose:   close,\n\t}\n\n\t// create initial clients, if something goes wrong,\n\t// just close the pool error out.\n\tfor i := 0; i < initialCap; i++ {\n\t\tclient := pool.Factory()\n\t\tpool.Items <- client\n\t}\n\n\treturn pool, nil\n}\n"
  },
  {
    "path": "ddosify_engine/core/util/helper.go",
    "content": "/*\n*\n*\tDdosify - Load testing tool for any web system.\n*   Copyright (C) 2021  Ddosify (https://ddosify.com)\n*\n*   This program is free software: you can redistribute it and/or modify\n*   it under the terms of the GNU Affero General Public License as published\n*   by the Free Software Foundation, either version 3 of the License, or\n*   (at your option) any later version.\n*\n*   This program is distributed in the hope that it will be useful,\n*   but WITHOUT ANY WARRANTY; without even the implied warranty of\n*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n*   GNU Affero General Public License for more details.\n*\n*   You should have received a copy of the GNU Affero General Public License\n*   along with this program.  If not, see <https://www.gnu.org/licenses/>.\n*\n */\n\npackage util\n\nimport (\n\t\"os\"\n\t\"strings\"\n)\n\n// StringInSlice checks if the given string is in the given list of strings\nfunc StringInSlice(a string, list []string) bool {\n\tfor _, b := range list {\n\t\tif b == a {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// IsSystemInTestMode checks if the system is running for tests.\nfunc IsSystemInTestMode() bool {\n\tfor _, arg := range os.Args {\n\t\tif strings.HasPrefix(arg, \"-test.\") {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "ddosify_engine/core/util/pool.go",
    "content": "package util\n\ntype Pool[T any] struct {\n\tItems    chan T\n\tFactory  func() T\n\tClose    func(T)\n\tAfterPut func(T)\n}\n\nfunc (p *Pool[T]) Get() T {\n\tvar item T\n\tselect {\n\tcase item = <-p.Items:\n\tdefault:\n\t\titem = p.Factory()\n\t}\n\treturn item\n}\n\nfunc (p *Pool[T]) Put(item T) error {\n\tif p.Items == nil {\n\t\t// pool is closed, close passed client\n\t\tp.Close(item)\n\t\treturn nil\n\t}\n\n\t// put the resource back into the pool. If the pool is full, this will\n\t// block and the default case will be executed.\n\tselect {\n\tcase p.Items <- item:\n\t\tif p.AfterPut != nil {\n\t\t\tp.AfterPut(item)\n\t\t}\n\t\treturn nil\n\tdefault:\n\t\t// pool is full, close passed client\n\t\tp.Close(item)\n\t\treturn nil\n\t}\n}\n\nfunc (p *Pool[T]) Len() int {\n\treturn len(p.Items)\n}\n\nfunc (p *Pool[T]) Done() {\n\tclose(p.Items)\n\tfor i := range p.Items {\n\t\tp.Close(i)\n\t}\n}\n"
  },
  {
    "path": "ddosify_engine/go.mod",
    "content": "module go.ddosify.com/ddosify\n\ngo 1.18\n\nrequire (\n\tgithub.com/antchfx/xmlquery v1.3.13\n\tgithub.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d\n\tgithub.com/ddosify/go-faker v0.1.1\n\tgithub.com/enescakir/emoji v1.0.0\n\tgithub.com/fatih/color v1.13.0\n\tgithub.com/google/uuid v1.3.0\n\tgithub.com/mattn/go-colorable v0.1.12\n\tgithub.com/shirou/gopsutil/v3 v3.22.12\n\tgithub.com/tidwall/gjson v1.14.4\n\tgolang.org/x/exp v0.0.0-20230108222341-4b8118a2686a\n\tgolang.org/x/net v0.8.0\n)\n\nrequire (\n\tgithub.com/antchfx/htmlquery v1.3.0\n\tgithub.com/antchfx/xpath v1.2.3 // indirect\n\tgithub.com/go-ole/go-ole v1.2.6 // indirect\n\tgithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect\n\tgithub.com/jaswdr/faker v1.10.2 // indirect\n\tgithub.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect\n\tgithub.com/mattn/go-isatty v0.0.14 // indirect\n\tgithub.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect\n\tgithub.com/tidwall/match v1.1.1 // indirect\n\tgithub.com/tidwall/pretty v1.2.0 // indirect\n\tgithub.com/tklauser/go-sysconf v0.3.11 // indirect\n\tgithub.com/tklauser/numcpus v0.6.0 // indirect\n\tgithub.com/yusufpapurcu/wmi v1.2.2 // indirect\n\tgolang.org/x/sys v0.6.0 // indirect\n\tgolang.org/x/text v0.8.0 // indirect\n)\n"
  },
  {
    "path": "ddosify_engine/go.sum",
    "content": "github.com/antchfx/htmlquery v1.3.0 h1:5I5yNFOVI+egyia5F2s/5Do2nFWxJz41Tr3DyfKD25E=\ngithub.com/antchfx/htmlquery v1.3.0/go.mod h1:zKPDVTMhfOmcwxheXUsx4rKJy8KEY/PU6eXr/2SebQ8=\ngithub.com/antchfx/xmlquery v1.3.13 h1:wqhTv2BN5MzYg9rnPVtZb3IWP8kW6WV/ebAY0FCTI7Y=\ngithub.com/antchfx/xmlquery v1.3.13/go.mod h1:3w2RvQvTz+DaT5fSgsELkSJcdNgkmg6vuXDEuhdwsPQ=\ngithub.com/antchfx/xpath v1.2.1 h1:qhp4EW6aCOVr5XIkT+l6LJ9ck/JsUH/yyauNgTQkBF8=\ngithub.com/antchfx/xpath v1.2.1/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=\ngithub.com/antchfx/xpath v1.2.3 h1:CCZWOzv5bAqjVv0offZ2LVgVYFbeldKQVuLNbViZdes=\ngithub.com/antchfx/xpath v1.2.3/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=\ngithub.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ=\ngithub.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/ddosify/go-faker v0.1.1 h1:S18MhU7p237JLTwkOyjfMND1M/vdTLlEbTvv005kdRY=\ngithub.com/ddosify/go-faker v0.1.1/go.mod h1:59U3tEeBJY+7zXwZyuGpmfblEVb9yJ3hTPRPE8PC8SE=\ngithub.com/enescakir/emoji v1.0.0 h1:W+HsNql8swfCQFtioDGDHCHri8nudlK1n5p2rHCJoog=\ngithub.com/enescakir/emoji v1.0.0/go.mod h1:Bt1EKuLnKDTYpLALApstIkAjdDrS/8IAgTkKp+WKFD0=\ngithub.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=\ngithub.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=\ngithub.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=\ngithub.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=\ngithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=\ngithub.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=\ngithub.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/jaswdr/faker v1.10.2 h1:GK03wuDqa8V6BE+2VRr3DJ/G4T0iUDCzVoBCj5TM4b8=\ngithub.com/jaswdr/faker v1.10.2/go.mod h1:x7ZlyB1AZqwqKZgyQlnqEG8FDptmHlncA5u2zY/yi6w=\ngithub.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=\ngithub.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=\ngithub.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=\ngithub.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=\ngithub.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=\ngithub.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=\ngithub.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=\ngithub.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=\ngithub.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=\ngithub.com/shirou/gopsutil/v3 v3.22.12 h1:oG0ns6poeUSxf78JtOsfygNWuEHYYz8hnnNg7P04TJs=\ngithub.com/shirou/gopsutil/v3 v3.22.12/go.mod h1:Xd7P1kwZcp5VW52+9XsirIKd/BROzbb2wdX3Kqlz9uI=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=\ngithub.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=\ngithub.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=\ngithub.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=\ngithub.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=\ngithub.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=\ngithub.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=\ngithub.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=\ngithub.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=\ngithub.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngithub.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=\ngithub.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/exp v0.0.0-20230108222341-4b8118a2686a h1:tlXy25amD5A7gOfbXdqCGN5k8ESEed/Ee1E5RcrYnqU=\ngolang.org/x/exp v0.0.0-20230108222341-4b8118a2686a/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=\ngolang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=\ngolang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=\ngolang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "ddosify_engine/main.go",
    "content": "/*\n*\n*\tDdosify - Load testing tool for any web system.\n*   Copyright (C) 2021  Ddosify (https://ddosify.com)\n*\n*   This program is free software: you can redistribute it and/or modify\n*   it under the terms of the GNU Affero General Public License as published\n*   by the Free Software Foundation, either version 3 of the License, or\n*   (at your option) any later version.\n*\n*   This program is distributed in the hope that it will be useful,\n*   but WITHOUT ANY WARRANTY; without even the implied warranty of\n*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n*   GNU Affero General Public License for more details.\n*\n*   You should have received a copy of the GNU Affero General Public License\n*   along with this program.  If not, see <https://www.gnu.org/licenses/>.\n*\n */\n\npackage main\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"net/url\"\n\t\"os\"\n\t\"os/signal\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strings\"\n\t\"text/tabwriter\"\n\t\"time\"\n\n\t\"go.ddosify.com/ddosify/config\"\n\t\"go.ddosify.com/ddosify/core\"\n\t\"go.ddosify.com/ddosify/core/proxy\"\n\t\"go.ddosify.com/ddosify/core/types\"\n)\n\n//TODO: what about -preview flag? Users can see how many requests will be sent per second with the given parameters.\n\nconst headerRegexp = `^*(.+):\\s*(.+)`\n\n// We might consider to use Viper: https://github.com/spf13/viper\nvar (\n\titerCount = flag.Int(\"n\", types.DefaultIterCount, \"Total iteration count\")\n\tduration  = flag.Int(\"d\", types.DefaultDuration, \"Test duration in seconds\")\n\tloadType  = flag.String(\"l\", types.DefaultLoadType, \"Type of the load test [linear, incremental, waved]\")\n\n\tmethod = flag.String(\"m\", types.DefaultMethod,\n\t\t\"Request Method Type. For Http(s):[GET, POST, PUT, DELETE, UPDATE, PATCH]\")\n\tpayload = flag.String(\"b\", \"\", \"Payload of the network packet (body)\")\n\tauth    = flag.String(\"a\", \"\", \"Basic authentication, username:password\")\n\theaders header\n\n\ttarget  = flag.String(\"t\", \"\", \"Target URL\")\n\ttimeout = flag.Int(\"T\", types.DefaultTimeout, \"Request timeout in seconds\")\n\n\tproxyFlag = flag.String(\"P\", \"\",\n\t\t\"Proxy address as protocol://username:password@host:port. Supported proxies [http(s), socks]\")\n\toutput = flag.String(\"o\", types.DefaultOutputType, \"Output destination\")\n\n\tconfigPath = flag.String(\"config\", \"\",\n\t\t\"Json config file path. If a config file is provided, other flag values will be ignored\")\n\n\tcertPath    = flag.String(\"cert_path\", \"\", \"A path to a certificate file (usually called 'cert.pem')\")\n\tcertKeyPath = flag.String(\"cert_key_path\", \"\", \"A path to a certificate key file (usually called 'key.pem')\")\n\n\tversion = flag.Bool(\"version\", false, \"Prints version, git commit, built date (utc), go information and quit\")\n\tdebug   = flag.Bool(\"debug\", false, \"Iterates the scenario once and prints curl-like verbose result\")\n)\n\nvar (\n\tGitVersion = \"development\"\n\tGitCommit  = \"unknown\"\n\tBuildDate  = time.Now().UTC().Format(time.RFC3339)\n)\n\nfunc main() {\n\tflag.Var(&headers, \"h\", \"Request Headers. Ex: -h 'Accept: text/html' -h 'Content-Type: application/xml'\")\n\tflag.Parse()\n\n\tif *version {\n\t\tprintVersionAndExit()\n\t}\n\n\tstart()\n}\n\nfunc start() {\n\th, err := createHammer()\n\n\tif err != nil {\n\t\texitWithMsg(err.Error())\n\t}\n\n\tif err := h.Validate(); err != nil {\n\t\texitWithMsg(err.Error())\n\t}\n\n\trun(h)\n}\n\nfunc createHammer() (h types.Hammer, err error) {\n\tif *configPath != \"\" {\n\t\t// running with config and debug mode set from cli\n\t\treturn createHammerFromConfigFile(*debug)\n\t}\n\treturn createHammerFromFlags()\n}\n\nvar createHammerFromConfigFile = func(debug bool) (h types.Hammer, err error) {\n\tf, err := os.Open(*configPath)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer f.Close()\n\n\tbyteValue, err := ioutil.ReadAll(f)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tc, err := config.NewConfigReader(byteValue, config.ConfigTypeJson)\n\tif err != nil {\n\t\treturn\n\t}\n\n\th, err = c.CreateHammer()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif isFlagPassed(\"debug\") {\n\t\th.Debug = debug // debug flag from cli overrides debug in config file\n\t}\n\n\treturn\n}\n\nvar run = func(h types.Hammer) {\n\tctx, cancel := context.WithCancel(context.Background())\n\n\tes, err := core.InitEngineServices(h)\n\tif err != nil {\n\t\texitWithMsg(err.Error())\n\t}\n\n\tengine, err := core.NewEngine(ctx, h, es)\n\tif err != nil {\n\t\texitWithMsg(err.Error())\n\t}\n\n\terr = engine.Init()\n\tif err != nil {\n\t\texitWithMsg(err.Error())\n\t}\n\n\tc := make(chan os.Signal, 1)\n\tsignal.Notify(c, os.Interrupt)\n\tdefer func() {\n\t\tsignal.Stop(c)\n\t\tcancel()\n\t}()\n\n\tgo func() {\n\t\tselect {\n\t\tcase <-c:\n\t\t\tcancel()\n\t\tcase <-ctx.Done():\n\t\t}\n\t}()\n\n\tengine.Start()\n\n\tif engine.IsTestFailed() {\n\t\tos.Exit(1)\n\t}\n}\n\nvar createHammerFromFlags = func() (h types.Hammer, err error) {\n\tif *target == \"\" {\n\t\terr = fmt.Errorf(\"Please provide the target url with -t flag\")\n\t\treturn\n\t}\n\n\ts, err := createScenario()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tp, err := createProxy()\n\tif err != nil {\n\t\treturn\n\t}\n\n\th = types.Hammer{\n\t\tIterationCount:    *iterCount,\n\t\tLoadType:          strings.ToLower(*loadType),\n\t\tTestDuration:      *duration,\n\t\tScenario:          s,\n\t\tProxy:             p,\n\t\tReportDestination: *output,\n\t\tDebug:             *debug,\n\t\tSingleMode:        true,\n\t}\n\treturn\n}\n\nfunc createProxy() (p proxy.Proxy, err error) {\n\tvar proxyURL *url.URL\n\tif *proxyFlag != \"\" {\n\t\tproxyURL, err = url.Parse(*proxyFlag)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tp = proxy.Proxy{\n\t\tStrategy: proxy.ProxyTypeSingle,\n\t\tAddr:     proxyURL,\n\t}\n\treturn\n}\n\nfunc createScenario() (s types.Scenario, err error) {\n\t// Auth\n\tvar a types.Auth\n\tif *auth != \"\" {\n\t\tcreds := strings.Split(*auth, \":\")\n\t\tif len(creds) != 2 {\n\t\t\terr = fmt.Errorf(\"auth credentials couldn't be parsed\")\n\t\t\treturn\n\t\t}\n\n\t\ta = types.Auth{\n\t\t\tType:     types.AuthHttpBasic,\n\t\t\tUsername: creds[0],\n\t\t\tPassword: creds[1],\n\t\t}\n\t}\n\n\terr = types.IsTargetValid(*target)\n\tif err != nil {\n\t\treturn\n\t}\n\n\th, err := parseHeaders(headers)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tstep := types.ScenarioStep{\n\t\tID:      1,\n\t\tMethod:  strings.ToUpper(*method),\n\t\tAuth:    a,\n\t\tHeaders: h,\n\t\tPayload: *payload,\n\t\tURL:     *target,\n\t\tTimeout: *timeout,\n\t}\n\n\t// TODO : if whether certPath or certKeyPath doesn't exist and another one exists, we should return an error to user.\n\tif *certPath != \"\" && *certKeyPath != \"\" {\n\t\tcert, pool, e := types.ParseTLS(*certPath, *certKeyPath)\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\treturn\n\t\t}\n\n\t\tstep.Cert = cert\n\t\tstep.CertPool = pool\n\t}\n\ts = types.Scenario{Steps: []types.ScenarioStep{step}}\n\n\treturn\n}\n\nfunc versionTemplate() string {\n\tb := strings.Builder{}\n\tw := tabwriter.NewWriter(&b, 0, 0, 5, ' ', 0)\n\tfmt.Fprintf(w, \"Version:\\t%s\\n\", GitVersion)\n\tfmt.Fprintf(w, \"Git commit:\\t%s\\n\", GitCommit)\n\tfmt.Fprintf(w, \"Built\\t%s\\n\", BuildDate)\n\tfmt.Fprintf(w, \"Go version:\\t%s\\n\", runtime.Version())\n\tfmt.Fprintf(w, \"OS/Arch:\\t%s\\n\", fmt.Sprintf(\"%s/%s\", runtime.GOOS, runtime.GOARCH))\n\tw.Flush()\n\n\treturn b.String()\n}\n\nfunc printVersionAndExit() {\n\tfmt.Println(versionTemplate())\n\tos.Exit(0)\n}\n\nfunc exitWithMsg(msg string) {\n\tif msg != \"\" {\n\t\tmsg = \"err: \" + msg\n\t\tfmt.Fprintln(os.Stderr, msg)\n\t}\n\tos.Exit(1)\n}\n\nfunc parseHeaders(headersArr []string) (headersMap map[string]string, err error) {\n\tre := regexp.MustCompile(headerRegexp)\n\theadersMap = make(map[string]string)\n\tfor _, h := range headersArr {\n\t\tmatches := re.FindStringSubmatch(h)\n\t\tif len(matches) < 1 {\n\t\t\terr = fmt.Errorf(\"invalid header:  %v\", h)\n\t\t\treturn\n\t\t}\n\t\theadersMap[matches[1]] = matches[2]\n\t}\n\treturn\n}\n\ntype header []string\n\nfunc (h *header) String() string {\n\treturn fmt.Sprintf(\"%s - %d\", *h, len(*h))\n}\n\nfunc (h *header) Set(value string) error {\n\t*h = append(*h, value)\n\treturn nil\n}\n\nfunc isFlagPassed(name string) bool {\n\tfound := false\n\tflag.Visit(func(f *flag.Flag) {\n\t\tif f.Name == name {\n\t\t\tfound = true\n\t\t}\n\t})\n\treturn found\n}\n"
  },
  {
    "path": "ddosify_engine/main_benchmark_test.go",
    "content": "//go:build linux || darwin\n// +build linux darwin\n\n/*\n*\n*\tDdosify - Load testing tool for any web system.\n*   Copyright (C) 2021  Ddosify (https://ddosify.com)\n*\n*   This program is free software: you can redistribute it and/or modify\n*   it under the terms of the GNU Affero General Public License as published\n*   by the Free Software Foundation, either version 3 of the License, or\n*   (at your option) any later version.\n*\n*   This program is distributed in the hope that it will be useful,\n*   but WITHOUT ANY WARRANTY; without even the implied warranty of\n*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n*   GNU Affero General Public License for more details.\n*\n*   You should have received a copy of the GNU Affero General Public License\n*   along with this program.  If not, see <https://www.gnu.org/licenses/>.\n*\n */\n\npackage main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"runtime\"\n\t\"runtime/pprof\"\n\t\"runtime/trace\"\n\t\"strconv\"\n\t\"strings\"\n\t\"syscall\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/shirou/gopsutil/v3/cpu\"\n\tgopsProc \"github.com/shirou/gopsutil/v3/process\"\n\t\"golang.org/x/exp/constraints\"\n)\n\ntype TestType string\n\nconst (\n\tMultipart   TestType = \"multipart\"\n\tCorrelation TestType = \"correlation\"\n\tBasic       TestType = \"basic\"\n)\n\nvar table = []struct {\n\tname             string\n\tpath             string\n\tcpuTimeThreshold float64\n\t// in percents\n\tmaxMemThreshold float32\n\tavgMemThreshold float32\n\n\ttestType TestType\n}{\n\t{\n\t\tname:             \"config_distinct_user\",\n\t\tpath:             \"config/config_testdata/benchmark/config_distinct_user.json\",\n\t\tcpuTimeThreshold: 0.350,\n\t\tmaxMemThreshold:  0.5,\n\t\tavgMemThreshold:  0.35,\n\t\ttestType:         Basic,\n\t},\n\t{\n\t\tname:             \"config_repeated_user\",\n\t\tpath:             \"config/config_testdata/benchmark/config_repeated_user.json\",\n\t\tcpuTimeThreshold: 0.350,\n\t\tmaxMemThreshold:  0.5,\n\t\tavgMemThreshold:  0.35,\n\t\ttestType:         Basic,\n\t},\n\t{\n\t\tname:             \"config_correlation_load_1\",\n\t\tpath:             \"config/config_testdata/benchmark/config_correlation_load_1.json\",\n\t\tcpuTimeThreshold: 0.350,\n\t\tmaxMemThreshold:  0.5,\n\t\tavgMemThreshold:  0.35,\n\t\ttestType:         Correlation,\n\t},\n\t{\n\t\tname:             \"config_correlation_load_2\",\n\t\tpath:             \"config/config_testdata/benchmark/config_correlation_load_2.json\",\n\t\tcpuTimeThreshold: 2.5,\n\t\tmaxMemThreshold:  0.8,\n\t\tavgMemThreshold:  0.7,\n\t\ttestType:         Correlation,\n\t},\n\t{\n\t\tname:             \"config_correlation_load_3\",\n\t\tpath:             \"config/config_testdata/benchmark/config_correlation_load_3.json\",\n\t\tcpuTimeThreshold: 15.5,\n\t\tmaxMemThreshold:  5,\n\t\tavgMemThreshold:  4,\n\t\ttestType:         Correlation,\n\t},\n\t{\n\t\tname:             \"config_correlation_load_4\",\n\t\tpath:             \"config/config_testdata/benchmark/config_correlation_load_4.json\",\n\t\tcpuTimeThreshold: 25,\n\t\tmaxMemThreshold:  7,\n\t\tavgMemThreshold:  5,\n\t\ttestType:         Correlation,\n\t},\n\t{\n\t\tname:             \"config_correlation_load_5\",\n\t\tpath:             \"config/config_testdata/benchmark/config_correlation_load_5.json\",\n\t\tcpuTimeThreshold: 60,\n\t\tmaxMemThreshold:  15,\n\t\tavgMemThreshold:  10,\n\t\ttestType:         Correlation,\n\t},\n\t//{\n\t//\tname:             \"config_multipart_inject_10rps\",\n\t//\tpath:             \"config/config_testdata/benchmark/config_multipart_inject_10rps.json\",\n\t//\tcpuTimeThreshold: 5,\n\t//\tmaxMemThreshold:  2,\n\t//\tavgMemThreshold:  1,\n\t//\ttestType:         Multipart,\n\t//},\n\t//{\n\t//\tname:             \"config_multipart_inject_100rps\",\n\t//\tpath:             \"config/config_testdata/benchmark/config_multipart_inject_100rps.json\",\n\t//\tcpuTimeThreshold: 50,\n\t//\tmaxMemThreshold:  3,\n\t//\tavgMemThreshold:  2,\n\t//\ttestType:         Multipart,\n\t//},\n\t//{\n\t//\tname:             \"config_multipart_inject_200rps\",\n\t//\tpath:             \"config/config_testdata/benchmark/config_multipart_inject_200rps.json\",\n\t//\tcpuTimeThreshold: 100,\n\t//\tmaxMemThreshold:  5,\n\t//\tavgMemThreshold:  4,\n\t//\ttestType:         Multipart,\n\t//},\n\t//{\n\t//\tname:             \"config_multipart_inject_500rps\",\n\t//\tpath:             \"config/config_testdata/benchmark/config_multipart_inject_500rps.json\",\n\t//\tcpuTimeThreshold: 160,\n\t//\tmaxMemThreshold:  7,\n\t//\tavgMemThreshold:  10,\n\t//\ttestType:         Multipart,\n\t//},\n\t{\n\t\tname:             \"config_multipart_inject_1krps\",\n\t\tpath:             \"config/config_testdata/benchmark/config_multipart_inject_1krps.json\",\n\t\tcpuTimeThreshold: 200,\n\t\tmaxMemThreshold:  10,\n\t\tavgMemThreshold:  15,\n\t\ttestType:         Multipart,\n\t},\n\t//{\n\t//\tname:             \"config_multipart_inject_2krps\",\n\t//\tpath:             \"config/config_testdata/benchmark/config_multipart_inject_2krps.json\",\n\t//\tcpuTimeThreshold: 300,\n\t//\tmaxMemThreshold:  15,\n\t//\tavgMemThreshold:  20,\n\t//\ttestType: Multipart,\n\t//},\n}\n\nvar cpuprofile = flag.String(\"cpuprof\", \"\", \"write cpu profiles\")\nvar memprofile = flag.String(\"memprof\", \"\", \"write memory profiles\")\nvar keepTrace = flag.String(\"tracef\", \"\", \"write execution traces\")\nvar runBenchmarkN = flag.Int(\"runN\", 1, \"run benchmarks N times\")\n\nfunc BenchmarkEngines(t *testing.B) {\n\tindex := os.Getenv(\"index\")\n\tif index == \"\" {\n\t\tN := 1\n\t\tif *runBenchmarkN > 1 {\n\t\t\tN = *runBenchmarkN\n\t\t}\n\t\t// parent\n\t\tsuccess := true\n\t\toriginalN := N\n\t\tfor i, _ := range table { // open a new process for each test config\n\t\t\tN = originalN\n\t\t\tif table[i].testType != Multipart {\n\t\t\t\tN = 1 // if not multipart, run only once\n\t\t\t}\n\t\t\tfor j := 0; j < N; j++ { // run each test config N times\n\t\t\t\ttime.Sleep(1 * time.Second) // wait for the previous process to finish\n\t\t\t\t// start a child\n\t\t\t\tenv := fmt.Sprintf(\"index=%d\", i)\n\t\t\t\tcPid, err := syscall.ForkExec(os.Args[0], os.Args, &syscall.ProcAttr{Files: []uintptr{0, 1, 2}, Env: []string{env}})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(err.Error())\n\t\t\t\t}\n\n\t\t\t\tproc, err := os.FindProcess(cPid)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(err.Error())\n\t\t\t\t}\n\n\t\t\t\tpState, err := proc.Wait()\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(err.Error())\n\t\t\t\t}\n\n\t\t\t\tif !pState.Success() {\n\t\t\t\t\tsuccess = false\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !success {\n\t\t\t\tt.Fail()\n\t\t\t}\n\t\t}\n\n\t} else {\n\t\ti, _ := strconv.Atoi(index)\n\t\tconf := table[i]\n\t\toutSuffix := \".out\"\n\t\tvar err error\n\n\t\t// child proc\n\t\tvar cpuProfFile, memProfFile, traceFile *os.File\n\t\tif *cpuprofile != \"\" {\n\t\t\tcpuProfFile, err = os.Create(fmt.Sprintf(\"%s_cpuprof_%s.out\", strings.TrimSuffix(*cpuprofile, outSuffix), conf.name))\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatal(err)\n\t\t\t}\n\t\t\tpprof.StartCPUProfile(cpuProfFile)\n\t\t\tdefer cpuProfFile.Close()\n\t\t\tdefer pprof.StopCPUProfile()\n\t\t}\n\n\t\tif *memprofile != \"\" { // get memory profile at execution finish\n\t\t\tmemProfFile, err = os.Create(fmt.Sprintf(\"%s_memprof_%s.out\", strings.TrimSuffix(*memprofile, outSuffix),\n\t\t\t\tconf.name))\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatal(\"could not create memory profile: \", err)\n\t\t\t}\n\t\t\tdefer memProfFile.Close() // error handling omitted for example\n\t\t\tdefer func() {\n\t\t\t\tpprof.Lookup(\"allocs\").WriteTo(memProfFile, 0)\n\t\t\t\t// if you want to check live heap objects:\n\t\t\t\t// runtime.GC() // get up-to-date statistics\n\t\t\t\t// pprof.Lookup(\"heap\").WriteTo(memProfFile, 0)\n\t\t\t}()\n\t\t}\n\n\t\tif *keepTrace != \"\" {\n\t\t\ttraceFile, err = os.Create(fmt.Sprintf(\"%s_trace_%s.out\", strings.TrimSuffix(*keepTrace, outSuffix), conf.name))\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatalf(\"failed to create trace output file: %v\", err)\n\t\t\t}\n\t\t\tdefer func() {\n\t\t\t\tif err := traceFile.Close(); err != nil {\n\t\t\t\t\tlog.Fatalf(\"failed to close trace file: %v\", err)\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\tif err := trace.Start(traceFile); err != nil {\n\t\t\t\tlog.Fatalf(\"failed to start trace: %v\", err)\n\t\t\t}\n\t\t\tdefer trace.Stop()\n\t\t}\n\n\t\tsuccess := t.Run(fmt.Sprintf(\"config_%s\", conf.path), func(t *testing.B) {\n\t\t\tvar memPercents []float32\n\t\t\tvar cpuStats []*cpu.TimesStat\n\n\t\t\t*configPath = conf.path\n\t\t\trun = tempRun\n\t\t\tdoneChan := make(chan struct{}, 1)\n\t\t\tgo func() {\n\t\t\t\tticker := time.NewTicker(100 * time.Millisecond)\n\t\t\t\tpid := os.Getpid()\n\t\t\t\tproc, _ := gopsProc.NewProcess(int32(pid))\n\t\t\t\tfor {\n\t\t\t\t\tselect {\n\t\t\t\t\tcase <-ticker.C:\n\t\t\t\t\t\tcpuStat, _ := proc.Times()\n\t\t\t\t\t\tcpuStats = append(cpuStats, cpuStat)\n\n\t\t\t\t\t\tmemPerc, _ := proc.MemoryPercent()\n\t\t\t\t\t\tmemPercents = append(memPercents, memPerc)\n\t\t\t\t\tcase <-doneChan:\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}()\n\t\t\tstart()\n\t\t\tdoneChan <- struct{}{}\n\n\t\t\tlastCpuStat := cpuStats[len(cpuStats)-1]\n\t\t\tcpuTime := lastCpuStat.User + lastCpuStat.System\n\t\t\tfmt.Printf(\"cpuTime: %f / %f \\n\", cpuTime, conf.cpuTimeThreshold)\n\n\t\t\tavgMem := sum(memPercents) / float32(len(memPercents))\n\t\t\tmaxMem := max(memPercents)\n\t\t\tfmt.Printf(\"Avg mem: %f / %f \\n\", avgMem, conf.avgMemThreshold)\n\t\t\tfmt.Printf(\"Max mem: %f / %f \\n\\n\", maxMem, conf.maxMemThreshold)\n\n\t\t\tif cpuTime > conf.cpuTimeThreshold {\n\t\t\t\tt.Errorf(\"Cpu time %f, higher than cpuTimeThreshold %f\", cpuTime, conf.cpuTimeThreshold)\n\t\t\t}\n\t\t\tif avgMem > conf.avgMemThreshold {\n\t\t\t\tt.Errorf(\"Avg mem %f, higher than avgMemThreshold %f\", avgMem, conf.avgMemThreshold)\n\t\t\t}\n\t\t\tif maxMem > conf.maxMemThreshold {\n\t\t\t\tt.Errorf(\"Max mem %f, higher than maxMemThreshold %f\", maxMem, conf.maxMemThreshold)\n\t\t\t}\n\n\t\t})\n\n\t\tif !success {\n\t\t\truntime.Goexit()\n\t\t}\n\t}\n}\n\nfunc max[T constraints.Ordered](s []T) T {\n\tif len(s) == 0 {\n\t\tvar zero T\n\t\treturn zero\n\t}\n\tm := s[0]\n\tfor _, v := range s {\n\t\tif m < v {\n\t\t\tm = v\n\t\t}\n\t}\n\treturn m\n}\n\nfunc sum[T constraints.Ordered](s []T) T {\n\tif len(s) == 0 {\n\t\tvar zero T\n\t\treturn zero\n\t}\n\tvar m T\n\tfor _, v := range s {\n\t\tm += v\n\t}\n\treturn m\n}\n"
  },
  {
    "path": "ddosify_engine/main_exit_test.go",
    "content": "//go:build linux || darwin\n// +build linux darwin\n\n/*\n*\n*\tDdosify - Load testing tool for any web system.\n*   Copyright (C) 2021  Ddosify (https://ddosify.com)\n*\n*   This program is free software: you can redistribute it and/or modify\n*   it under the terms of the GNU Affero General Public License as published\n*   by the Free Software Foundation, either version 3 of the License, or\n*   (at your option) any later version.\n*\n*   This program is distributed in the hope that it will be useful,\n*   but WITHOUT ANY WARRANTY; without even the implied warranty of\n*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n*   GNU Affero General Public License for more details.\n*\n*   You should have received a copy of the GNU Affero General Public License\n*   along with this program.  If not, see <https://www.gnu.org/licenses/>.\n*\n */\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"syscall\"\n\t\"testing\"\n\n\t\"go.ddosify.com/ddosify/core/types\"\n)\n\nfunc TestExitStatusOnTestFail(t *testing.T) {\n\tindex := os.Getenv(\"index\")\n\tif index == \"\" { // parent\n\t\t// start a test in child proc, look for its exit status\n\t\tenv := fmt.Sprintf(\"index=%d\", 1)\n\t\tcPid, err := syscall.ForkExec(os.Args[0], os.Args, &syscall.ProcAttr{Files: []uintptr{0, 1, 2}, Env: []string{env}})\n\t\tif err != nil {\n\t\t\tpanic(err.Error())\n\t\t}\n\n\t\tproc, err := os.FindProcess(cPid)\n\t\tif err != nil {\n\t\t\tpanic(err.Error())\n\t\t}\n\n\t\t// expected child to fail with exit code 1\n\t\tpState, err := proc.Wait()\n\t\tif err != nil {\n\t\t\tpanic(err.Error())\n\t\t}\n\t\tif pState.Success() {\n\t\t\tt.Fail()\n\t\t}\n\t} else {\n\t\t// run a failed engine\n\t\t*configPath = \"config/config_testdata/config_test_assertion_fail.json\"\n\t\trun = tempRun\n\t\tstart()\n\t\trun = func(h types.Hammer) {}\n\t}\n}\n"
  },
  {
    "path": "ddosify_engine/main_test.go",
    "content": "/*\n*\n*\tDdosify - Load testing tool for any web system.\n*   Copyright (C) 2021  Ddosify (https://ddosify.com)\n*\n*   This program is free software: you can redistribute it and/or modify\n*   it under the terms of the GNU Affero General Public License as published\n*   by the Free Software Foundation, either version 3 of the License, or\n*   (at your option) any later version.\n*\n*   This program is distributed in the hope that it will be useful,\n*   but WITHOUT ANY WARRANTY; without even the implied warranty of\n*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n*   GNU Affero General Public License for more details.\n*\n*   You should have received a copy of the GNU Affero General Public License\n*   along with this program.  If not, see <https://www.gnu.org/licenses/>.\n*\n */\n\npackage main\n\nimport (\n\t\"crypto/tls\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n\t\"os\"\n\t\"os/exec\"\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"go.ddosify.com/ddosify/core/proxy\"\n\t\"go.ddosify.com/ddosify/core/types\"\n)\n\nvar tempRun func(h types.Hammer)\n\nfunc TestMain(m *testing.M) {\n\t// Mock run function to prevent engine starting\n\ttempRun = run\n\trun = func(h types.Hammer) {}\n\tos.Exit(m.Run())\n}\n\nfunc resetFlags() {\n\t*iterCount = types.DefaultIterCount\n\t*loadType = types.DefaultLoadType\n\t*duration = types.DefaultDuration\n\n\t*method = types.DefaultMethod\n\t*payload = \"\"\n\t*auth = \"\"\n\theaders = header{}\n\n\t*target = \"\"\n\t*timeout = types.DefaultTimeout\n\n\t*proxyFlag = \"\"\n\t*output = types.DefaultOutputType\n\n\t*configPath = \"\"\n\n\t*certPath = \"\"\n\t*certKeyPath = \"\"\n\n\t*debug = false\n}\n\nfunc TestDefaultFlagValues(t *testing.T) {\n\toldArgs := os.Args\n\tdefer func() { os.Args = oldArgs }()\n\tos.Args = []string{\"cmd\", \"-t=example.com\"}\n\n\tflag.Parse()\n\n\tif *iterCount != types.DefaultIterCount {\n\t\tt.Errorf(\"TestDefaultFlagValues failed, expected %#v, found %#v\", types.DefaultIterCount, *iterCount)\n\t}\n\tif *loadType != types.DefaultLoadType {\n\t\tt.Errorf(\"TestDefaultFlagValues failed, expected %#v, found %#v\", types.DefaultLoadType, *loadType)\n\t}\n\tif *duration != types.DefaultDuration {\n\t\tt.Errorf(\"TestDefaultFlagValues failed, expected %#v, found %#v\", types.DefaultDuration, *duration)\n\t}\n\tif *method != types.DefaultMethod {\n\t\tt.Errorf(\"TestDefaultFlagValues failed, expected %#v, found %#v\", types.DefaultMethod, *method)\n\t}\n\tif *payload != \"\" {\n\t\tt.Errorf(\"TestDefaultFlagValues failed, expected %#v, found %#v\", \"\", *payload)\n\t}\n\tif *auth != \"\" {\n\t\tt.Errorf(\"TestDefaultFlagValues failed, expected %#v, found %#v\", \"\", *auth)\n\t}\n\tif reflect.DeepEqual(headers, []string{}) {\n\t\tt.Errorf(\"TestDefaultFlagValues failed, expected %#v, found %#v\", []string{}, headers)\n\t}\n\tif *timeout != types.DefaultTimeout {\n\t\tt.Errorf(\"TestDefaultFlagValues failed, expected %#v, found %#v\", types.DefaultTimeout, *timeout)\n\t}\n\tif *proxyFlag != \"\" {\n\t\tt.Errorf(\"TestDefaultFlagValues failed, expected %#v, found %#v\", \"\", *proxyFlag)\n\t}\n\tif *output != types.DefaultOutputType {\n\t\tt.Errorf(\"TestDefaultFlagValues failed, expected %#v, found %#v\", types.DefaultOutputType, *output)\n\t}\n\tif *configPath != \"\" {\n\t\tt.Errorf(\"TestDefaultFlagValues failed, expected %#v, found %#v\", \"\", *configPath)\n\t}\n\tif *certPath != \"\" {\n\t\tt.Errorf(\"TestDefaultFlagValues failed, expected %#v, found %#v\", \"\", *certPath)\n\t}\n\tif *certKeyPath != \"\" {\n\t\tt.Errorf(\"TestDefaultFlagValues failed, expected %#v, found %#v\", \"\", *certKeyPath)\n\t}\n}\n\nfunc TestCreateHammer(t *testing.T) {\n\ttests := []struct {\n\t\tname      string\n\t\targs      string\n\t\tfromFlags bool\n\t\tfromFile  bool\n\t}{\n\t\t{\"Flag\", \"-t=dummy.com -config=\", true, false},\n\t\t{\"File\", \"-config=dummy.json -t=\", false, true},\n\t}\n\n\tfor _, test := range tests {\n\t\ttf := func(t *testing.T) {\n\t\t\t// Arrange\n\t\t\tresetFlags()\n\t\t\toldArgs := os.Args\n\t\t\toldFileFunc := createHammerFromConfigFile\n\t\t\toldFlagFunc := createHammerFromFlags\n\t\t\tdefer func() {\n\t\t\t\tos.Args = oldArgs\n\t\t\t\tcreateHammerFromConfigFile = oldFileFunc\n\t\t\t\tcreateHammerFromFlags = oldFlagFunc\n\t\t\t}()\n\n\t\t\tfromFileCalled := false\n\t\t\tfromFlagsCalled := false\n\t\t\tcreateHammerFromConfigFile = func(debug bool) (h types.Hammer, err error) {\n\t\t\t\tfromFileCalled = true\n\t\t\t\treturn\n\t\t\t}\n\t\t\tcreateHammerFromFlags = func() (h types.Hammer, err error) {\n\t\t\t\tfromFlagsCalled = true\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Act\n\t\t\tos.Args = []string{\"cmd\", test.args}\n\t\t\tflag.Parse()\n\t\t\tcreateHammer()\n\n\t\t\t// Assert\n\t\t\tif fromFileCalled != test.fromFile {\n\t\t\t\tt.Errorf(\"createHammerFromConfigFileCalled expected %v found %v\", test.fromFile, fromFileCalled)\n\t\t\t}\n\t\t\tif fromFlagsCalled != test.fromFlags {\n\t\t\t\tt.Errorf(\"createHammerFromFlagsCalled expected %v found %v\", test.fromFlags, fromFlagsCalled)\n\t\t\t}\n\t\t}\n\n\t\tt.Run(test.name, tf)\n\t}\n}\n\nfunc TestDebugFlagOverridesConfig(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\targs []string\n\t}{\n\t\t{\"DebugFlagShouldOverrideConfig\",\n\t\t\t[]string{\"-config\", \"config/config_testdata/config_debug_false.json\", \"-debug\"}},\n\t\t{\"UseConfigDebugKeyWhenNoDebugFlagSpecified\",\n\t\t\t[]string{\"-config\", \"config/config_testdata/config_debug_mode.json\", \"-debug\", \"false\"}},\n\t}\n\n\tfor _, test := range tests {\n\t\ttf := func(t *testing.T) {\n\t\t\t// Arrange\n\t\t\tresetFlags()\n\t\t\toldArgs := os.Args\n\t\t\tdefer func() {\n\t\t\t\tos.Args = oldArgs\n\t\t\t}()\n\n\t\t\t// Act\n\t\t\tos.Args = append([]string{\"cmd\"}, test.args...)\n\t\t\tflag.Parse()\n\t\t\th, err := createHammer()\n\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"createHammer return %v\", err)\n\t\t\t}\n\n\t\t\t// Assert\n\t\t\tif h.Debug != *debug {\n\t\t\t\tt.Errorf(\"debug flag did not override config file\")\n\t\t\t}\n\n\t\t}\n\n\t\tt.Run(test.name, tf)\n\t}\n\tresetFlags()\n}\n\nfunc TestCreateScenario(t *testing.T) {\n\turl := \"https://test.com\"\n\tvalid := types.Scenario{\n\t\tSteps: []types.ScenarioStep{\n\t\t\t{\n\t\t\t\tID:      1,\n\t\t\t\tMethod:  types.DefaultMethod,\n\t\t\t\tURL:     url,\n\t\t\t\tTimeout: types.DefaultTimeout,\n\t\t\t\tHeaders: map[string]string{},\n\t\t\t},\n\t\t},\n\t}\n\tvalidWithAuth := types.Scenario{\n\t\tSteps: []types.ScenarioStep{\n\t\t\t{\n\t\t\t\tID:      1,\n\t\t\t\tMethod:  types.DefaultMethod,\n\t\t\t\tURL:     url,\n\t\t\t\tTimeout: types.DefaultTimeout,\n\t\t\t\tHeaders: map[string]string{},\n\t\t\t\tAuth: types.Auth{\n\t\t\t\t\tType:     types.AuthHttpBasic,\n\t\t\t\t\tUsername: \"testuser\",\n\t\t\t\t\tPassword: \"pass\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\ttests := []struct {\n\t\tname      string\n\t\targs      []string\n\t\tshouldErr bool\n\t\texpected  types.Scenario\n\t}{\n\t\t{\"InvalidAuth\", []string{\"-t=https://test.com\", \"-a=no_pass_included\"}, true, types.Scenario{}},\n\t\t{\"InvalidTarget\", []string{\"-t=asds.x.x.x\"}, true, types.Scenario{}},\n\t\t{\"Valid\", []string{\"-t=https://test.com\"}, false, valid},\n\t\t{\"ValidWithAuth\", []string{\"-t=https://test.com\", \"-a=testuser:pass\"}, false, validWithAuth},\n\t}\n\n\tfor _, test := range tests {\n\t\ttf := func(t *testing.T) {\n\t\t\t// Arrange\n\t\t\tresetFlags()\n\t\t\toldArgs := os.Args\n\t\t\tdefer func() {\n\t\t\t\tos.Args = oldArgs\n\t\t\t}()\n\n\t\t\tos.Args = append([]string{\"cmd\"}, test.args...)\n\n\t\t\t// Act\n\t\t\tflag.Parse()\n\t\t\ts, err := createScenario()\n\n\t\t\t// Assert\n\t\t\tif test.shouldErr {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Errorf(\"Should be errored\")\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"Errored: %v\", err)\n\t\t\t\t}\n\t\t\t\tif !reflect.DeepEqual(test.expected, s) {\n\t\t\t\t\tt.Errorf(\"Expected %#v, Found %#v\", test.expected, s)\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\n\t\tt.Run(test.name, tf)\n\t}\n}\n\nfunc TestCreateScenarioTLS(t *testing.T) {\n\t// prepare TLS files\n\tcert, certKey := generateCerts()\n\tcertFile, keyFile, err := createCertPairFiles(cert, certKey)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to prepare certs %v\", err)\n\t}\n\tdefer os.Remove(certFile.Name())\n\tdefer os.Remove(keyFile.Name())\n\n\tcertVal, _, err := types.ParseTLS(certFile.Name(), keyFile.Name())\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to gen certs %v\", err)\n\t}\n\n\tcertPathArg := fmt.Sprintf(\"--cert_path=%s\", certFile.Name())\n\tkeyPathArg := fmt.Sprintf(\"--cert_key_path=%s\", keyFile.Name())\n\n\ttests := []struct {\n\t\tname      string\n\t\targs      []string\n\t\tshouldErr bool\n\t\texpected  tls.Certificate\n\t}{\n\t\t{\"MissingKey\", []string{\"-t=https://test.com\", certPathArg}, false, tls.Certificate{}},\n\t\t{\"MissingCert\", []string{\"-t=https://test.com\", keyPathArg}, false, tls.Certificate{}},\n\t\t{\"WithTLS\", []string{\"-t=https://test.com\", certPathArg, keyPathArg}, false, certVal},\n\t}\n\n\tfor _, test := range tests {\n\t\ttf := func(t *testing.T) {\n\t\t\t// Arrange\n\t\t\tresetFlags()\n\t\t\toldArgs := os.Args\n\t\t\tdefer func() {\n\t\t\t\tos.Args = oldArgs\n\t\t\t}()\n\n\t\t\tos.Args = append([]string{\"cmd\"}, test.args...)\n\n\t\t\t// Act\n\t\t\tflag.Parse()\n\t\t\ts, err := createScenario()\n\n\t\t\t// Assert\n\t\t\tif test.shouldErr {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Errorf(\"Should be errored\")\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"Errored: %v\", err)\n\t\t\t\t}\n\t\t\t\tif !reflect.DeepEqual(test.expected, s.Steps[0].Cert) {\n\t\t\t\t\tt.Errorf(\"Expected %v, Found %v\", test.expected, s)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tt.Run(test.name, tf)\n\t}\n}\n\nfunc TestCreateProxy(t *testing.T) {\n\taddr, _ := url.Parse(\"http://127.0.0.1:80\")\n\twithAddr := proxy.Proxy{\n\t\tStrategy: proxy.ProxyTypeSingle,\n\t\tAddr:     addr,\n\t}\n\twithoutAddr := proxy.Proxy{\n\t\tStrategy: proxy.ProxyTypeSingle,\n\t\tAddr:     nil,\n\t}\n\n\ttests := []struct {\n\t\tname      string\n\t\targs      []string\n\t\tshouldErr bool\n\t\texpected  proxy.Proxy\n\t}{\n\t\t{\"InvalidProxy\", []string{\"-t=https://test.com\", \"-P=127.0.0.1:09\"}, true, proxy.Proxy{}},\n\t\t{\"ValidWithAddr\", []string{\"-t=https://test.com\", \"-P=http://127.0.0.1:80\"}, false, withAddr},\n\t\t{\"ValidWithoutAddr\", []string{\"-t=https://test.com\"}, false, withoutAddr},\n\t}\n\n\tfor _, test := range tests {\n\t\ttf := func(t *testing.T) {\n\t\t\t// Arrange\n\t\t\tresetFlags()\n\t\t\toldArgs := os.Args\n\t\t\tdefer func() {\n\t\t\t\tos.Args = oldArgs\n\t\t\t}()\n\n\t\t\tos.Args = append([]string{\"cmd\"}, test.args...)\n\n\t\t\t// Act\n\t\t\tflag.Parse()\n\t\t\tp, err := createProxy()\n\n\t\t\t// Assert\n\t\t\tt.Log(test.args)\n\t\t\tif test.shouldErr {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Errorf(\"Should be errored\")\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"Errored: %v\", err)\n\t\t\t\t}\n\t\t\t\tif test.expected.Strategy != p.Strategy {\n\t\t\t\t\tt.Errorf(\"Expected Strategy %v, Found %v\", test.expected.Strategy, p.Strategy)\n\t\t\t\t}\n\t\t\t\tif (test.expected.Addr != nil && *test.expected.Addr != *p.Addr) || (test.expected.Addr == nil && p.Addr != nil) {\n\t\t\t\t\tt.Errorf(\"Expected Addr %v, Found %v\", test.expected.Addr, p.Addr)\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\n\t\tt.Run(test.name, tf)\n\t}\n}\n\nfunc TestParseHeaders(t *testing.T) {\n\tvalidSingleHeader := map[string]string{\"header\": \"value\"}\n\tvalidMultiHeader := map[string]string{\"header-1\": \"value-1\", \"header-2\": \"value-2\"}\n\n\tinvalidHeader := header{}\n\tinvalidHeader.Set(\"invalid|header?: value-1\")\n\n\ttests := []struct {\n\t\tname      string\n\t\targs      header\n\t\tshouldErr bool\n\t\texpected  map[string]string\n\t}{\n\t\t{\"ValidSingleHeder\", []string{\"header: value\"}, false, validSingleHeader},\n\t\t{\"ValidMultiHeader\", []string{\"header-1: value-1\", \"header-2: value-2\"}, false, validMultiHeader},\n\t}\n\n\tfor _, test := range tests {\n\t\ttf := func(t *testing.T) {\n\t\t\theaders := header{}\n\t\t\tfor _, h := range test.args {\n\t\t\t\theaders.Set(h)\n\t\t\t}\n\n\t\t\t// Arrange\n\t\t\th, err := parseHeaders(headers)\n\n\t\t\t// Assert\n\t\t\tif test.shouldErr {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Errorf(\"Should be errored\")\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"Errored: %v\", err)\n\t\t\t\t}\n\t\t\t\tif !reflect.DeepEqual(test.expected, h) {\n\t\t\t\t\tt.Errorf(\"Expected  %#v, Found %#v\", test.expected, h)\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\n\t\tt.Run(test.name, tf)\n\t}\n}\n\nfunc TestRun(t *testing.T) {\n\t// Arrange\n\tresetFlags()\n\trunCalled := false\n\trun = func(h types.Hammer) {\n\t\trunCalled = true\n\t}\n\n\toldArgs := os.Args\n\tdefer func() { os.Args = oldArgs }()\n\n\t// Act\n\tos.Args = []string{\"cmd\", \"-t=test.com\"}\n\tmain()\n\n\t// Assert\n\tif !runCalled {\n\t\tt.Errorf(\"Run should be called\")\n\t}\n}\n\nfunc TestTargetEmpty(t *testing.T) {\n\t// Below cmd code triggers this block\n\tif os.Getenv(\"TARGET_EMPTY\") == \"1\" {\n\t\tresetFlags()\n\t\toldArgs := os.Args\n\t\tdefer func() { os.Args = oldArgs }()\n\t\tos.Args = []string{\"cmd\", \"asd\"}\n\t\tmain()\n\t\treturn\n\t}\n\n\t// Since we reexecute the test here, this test case doesnt' increment the coverage.\n\tcmd := exec.Command(os.Args[0], \"-test.run=TestTargetEmpty\")\n\tcmd.Env = append(os.Environ(), \"TARGET_EMPTY=1\")\n\terr := cmd.Run()\n\n\tif e, ok := err.(*exec.ExitError); ok && !e.Success() {\n\t\treturn\n\t}\n\n\tt.Errorf(\"TestTargetEmpty should be failed with exit code 1 found: %v\", err)\n}\n\nfunc TestTargetInvalidHammer(t *testing.T) {\n\t// Below cmd code triggers this block\n\tif os.Getenv(\"TARGET_EMPTY\") == \"1\" {\n\t\tresetFlags()\n\t\toldArgs := os.Args\n\t\tdefer func() { os.Args = oldArgs }()\n\t\tos.Args = []string{\"cmd\", \"-t=dummy.com -l invalidLoadType\"}\n\t\tmain()\n\t\treturn\n\t}\n\n\t// Since we reexecute the test here, this test case doesnt' increment the coverage.\n\tcmd := exec.Command(os.Args[0], \"-test.run=TestTargetEmpty\")\n\tcmd.Env = append(os.Environ(), \"TARGET_EMPTY=1\")\n\terr := cmd.Run()\n\n\tif e, ok := err.(*exec.ExitError); ok && !e.Success() {\n\t\treturn\n\t}\n\n\tt.Errorf(\"TestTargetEmpty should be failed with exit code 1 found: %v\", err)\n}\n\nfunc TestVersion(t *testing.T) {\n\t// Below cmd code triggers this block\n\tif os.Getenv(\"VERSION\") == \"1\" {\n\t\tresetFlags()\n\t\toldArgs := os.Args\n\t\tdefer func() { os.Args = oldArgs }()\n\t\tos.Args = []string{\"cmd\", \"-version\"}\n\t\tmain()\n\t\treturn\n\t}\n\n\t// Since we reexecute the test here, this test case doesnt' increment the coverage.\n\tcmd := exec.Command(os.Args[0], \"-test.run=TestVersion\")\n\tcmd.Env = append(os.Environ(), \"VERSION=1\")\n\terr := cmd.Run()\n\n\tif err == nil {\n\t\treturn\n\t}\n\n\tt.Errorf(\"TestVersion should not be failed\")\n}\n\nfunc Test_versionTemplate(t *testing.T) {\n\tGitVersion = \"v0.0.2\"\n\tGitCommit = \"akjsghsajghas\"\n\tBuildDate = \"2021-10-03T15:16:52Z\"\n\ttests := []struct {\n\t\tname string\n\t\twant string\n\t}{\n\t\t{name: \"version\", want: \"Version:        v0.0.2\\nGit commit:     akjsghsajghas\\nBuilt           2021-10-03T15:16:52Z\\n\"},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := versionTemplate(); !strings.Contains(got, tt.want) {\n\t\t\t\tt.Errorf(\"versionTemplate() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc createCertPairFiles(cert string, certKey string) (*os.File, *os.File, error) {\n\tcertFile, err := os.CreateTemp(\"\", \".pem\")\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\t_, err = io.WriteString(certFile, cert)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tkeyFile, err := os.CreateTemp(\"\", \".pem\")\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\t_, err = io.WriteString(keyFile, certKey)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\treturn certFile, keyFile, nil\n}\n\nfunc generateCerts() (string, string) {\n\tcert := `-----BEGIN CERTIFICATE-----\nMIIDazCCAlOgAwIBAgIUS4UhTks8aRCQ1k9IGn437ZyP3MgwDQYJKoZIhvcNAQEL\nBQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMjEwMDUyMjM5MDVaFw0zMjEw\nMDIyMjM5MDVaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB\nAQUAA4IBDwAwggEKAoIBAQDMbZctKXBx8v63TXIhM/OB7S6VfPqpzfHufhs6kAHu\njfC2ooCUqzqdg0T8bM1bjahYuAbQA1cWKYBsqfd01Po1ltWmbMf7ZvmSB6VN7kC2\nY670zee91dGDQ2yzmorJuIZAtOBVZesYLg8UHSGzSC/smJOrjYidtlbvzOcX0pv3\nRCIUrNMed60EpSch/rzAJLzJmwNSQZ4vJHNlNetSkvTi7cxMWfwpcM/rN1hEmP1X\nJ43hJp/TNRZVnEsvs/yggP/FwUjG74mU3KfnWiv91AkkarNTNquEMJ+f4OFqMcnF\np0wqg47JTqcAAT0n1B0VB+z0hGXEFMN+IJXsHETZNG+JAgMBAAGjUzBRMB0GA1Ud\nDgQWBBSIw+qUKQJjXWti5x/Cnn2GueuX5zAfBgNVHSMEGDAWgBSIw+qUKQJjXWti\n5x/Cnn2GueuX5zAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAA\nDXzf8VXi4s2GScNfHf0BzMjpyrtRZ0Wbp2Vfh7OwVR6xcx+pqXNjlydM/vu2LvOK\nhh7Jbo+JS+o7O24UJ9lLFkCRsZVF+NFqJf+2rdHCaOiZSdZmtjBU0dFuAGS7+lU3\nM8P7WCNOm6NAKbs7VZHVcZPzp81SCPQgQIS19xRf4Irbvsijv4YdyL4Qv7aWcclb\nMdZX9AH9Fx8tJq4VKvUYsCXAD0kuywMLjh+yj5O/2hMvs5rvaQvm2daQNRDNp884\nuTLrNF7W7QaKEL06ZpXJoBqdKsiwn577XTDKvzN0XxQrT+xV9VHO7OXblF+Od3/Y\nSzBR+QiQKy3x+LkOxhkk\n-----END CERTIFICATE-----`\n\n\tcertKey := `-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDMbZctKXBx8v63\nTXIhM/OB7S6VfPqpzfHufhs6kAHujfC2ooCUqzqdg0T8bM1bjahYuAbQA1cWKYBs\nqfd01Po1ltWmbMf7ZvmSB6VN7kC2Y670zee91dGDQ2yzmorJuIZAtOBVZesYLg8U\nHSGzSC/smJOrjYidtlbvzOcX0pv3RCIUrNMed60EpSch/rzAJLzJmwNSQZ4vJHNl\nNetSkvTi7cxMWfwpcM/rN1hEmP1XJ43hJp/TNRZVnEsvs/yggP/FwUjG74mU3Kfn\nWiv91AkkarNTNquEMJ+f4OFqMcnFp0wqg47JTqcAAT0n1B0VB+z0hGXEFMN+IJXs\nHETZNG+JAgMBAAECggEAM+U6NHfJmNPD/8qER5OFpJ0Ob1qL06F5Yj7XMLWwF9wm\nmGaGV7dkKOpTD/Wa6Dv82ZDWAeZnLDQa6vr228zZO9Nvp1EEL3kDsCOKvk7WVLbX\nikPfKZznE/iA1tNLmkvioPiJ3oQB+2Bt6YA/tuCDcf+FtU43uTm5tiSBIdYQS+Om\nxN9OEXihk1svxHXQKa/a3nKPVLvdp3P90hDJ0PcRslXSy1V8az+A94JFEnCvnKsK\nnF2rItCcXkInL0lYHZKgLHQMXGWkNl8e3PA1GZk3yF6LPNtPI1T5Ek9GwkHNw4JZ\nBL/xEWLKB1qR2Z4I3UbWGVyi418kANv1eISb+49egQKBgQDraSRWB8nM5O3Zl9kT\n8S5K924o1oXrO17eqQlVtQVmtUdoVvIBc6uHQZOmV1eHYpr6c95h8apNLexI22AY\nSWkq9smpCnxLUsdkplwzie0F4bAzD6MCR8WIJxapUSPlyCA+8st1hquYBchKGQhd\n6mMY1gzMDacYV/WhtG4E5d0nMQKBgQDeTr793n00VtpKuquFJe6Stu7Ujf64dL0s\n3opLovyI0TmtMz5oCqIezwrjqc0Vy0UksWXaz0AboinDP+5n60cTEIt/6H0kryDc\ndxfSHEA9BBDoQtxOFi3QGcxXbwu0i9QSoexrKY7FhA2xPji6bCcPycthhIrCpUiZ\ns5gVkjHn2QKBgQCGklxLMbiSgGvXb46Qb9be1AMNJVT427+n2UmUzR6BUC+53boK\nSm1LrJkTBerrYdrmQUZnBxcrd40TORT9zTlpbhppn6zeAjwptVAPxlDQg+uNxOqS\nayToaC/0KoYy3OxSD8lvLcT56pRMh3LY/RwZHoPCQiu7Js0r21DpS93YgQKBgAuc\nc09RMprsOmSS0WiX7ZkOIvVJIVfDCSpxySlgLu56dxe7yHOosoUHbVsswEB2KHtd\nJKPEFWYcFzBSg4I8AK9XOuIIY5jp6L57Hexke1p0fumSrG0LrYLkBg8/Bo58iywZ\n9v414nYgipKKXG4oPfYOJShHwvOdrGgSwEvIIgEpAoGAZz0yC9+x+JaoTnyUIRyI\n+Aj5a4KhYjFtsZhcn/yCZHDqzJNDz6gAu579ey+J2CVOhjtgB5lowsDrHu32Hqnn\nSEfyTru/ynQ8obwaRzdDYml+On86YWOw+brpMXkN+KB6bs2okE2N68v0qGPakxjt\nOLDW6kKz5pI4T8lQJhdqjCU=\n-----END PRIVATE KEY-----`\n\n\treturn cert, certKey\n}\n"
  },
  {
    "path": "ddosify_engine/scripts/install.sh",
    "content": "#!/bin/sh\n\nuname_arch() {\n  arch=$(uname -m)\n  case $arch in\n    x86_64) arch=\"amd64\" ;;\n    x86) arch=\"386\" ;;\n    i686) arch=\"386\" ;;\n    i386) arch=\"386\" ;;\n    aarch64) arch=\"arm64\" ;;\n    armv*) arch=\"armv6\" ;;\n    armv*) arch=\"armv6\" ;;\n    armv*) arch=\"armv6\" ;;\n  esac\n  if [ \"$(uname_os)\" == \"darwin\" ]; then\n    arch=\"all\"\n  fi\n  echo ${arch}\n}\n\nuname_os() {\n  os=$(uname -s | tr '[:upper:]' '[:lower:]')\n  echo \"$os\"\n}\n\n\n\nGITHUB_OWNER=\"ddosify\"\nGITHUB_REPO=\"ddosify\"\nTAG=\"latest\"\nINSTALL_DIR=\"/usr/local/bin/\"\nOS=$(uname_os)\nARCH=$(uname_arch)\nPLATFORM=\"${OS}/${ARCH}\"\nGITHUB_RELEASES_PAGE=https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}/releases\nVERSION=$(curl $GITHUB_RELEASES_PAGE/$TAG -sL -H 'Accept:application/json' | tr -s '\\n' ' ' | sed 's/.*\"tag_name\":\"//' | sed 's/\".*//' | tr -d v)\nNAME=${GITHUB_REPO}_${VERSION}_${OS}_${ARCH}\n\nTARBALL=${NAME}.tar.gz\nTARBALL_URL=${GITHUB_RELEASES_PAGE}/download/v${VERSION}/${TARBALL}\n\necho \"Downloading latest $GITHUB_REPO binary from $TARBALL_URL\"\ntmpfolder=$(mktemp -d)\n$(curl $TARBALL_URL -sL -o $tmpfolder/$TARBALL)\n\nif [ ! -f $tmpfolder/$TARBALL ]; then\n    echo \"Can not download. Exiting...\"\n    exit 14\nfi\ncd ${tmpfolder} && tar --no-same-owner -xzf \"$tmpfolder/$TARBALL\"\n\nif [ ! -f $tmpfolder/$GITHUB_REPO ]; then\n    echo \"Can not find $GITHUB_REPO. Exiting...\"\n    exit 15\nfi\n\nbinary=$tmpfolder/$GITHUB_REPO\necho \"Installing $GITHUB_REPO to $INSTALL_DIR (sudo access required to write to $INSTALL_DIR)\"\nsudo install \"$binary\" $INSTALL_DIR\necho \"Installed $GITHUB_REPO to $INSTALL_DIR\"\necho \"Simple usage: ddosify -t https://testserver.ddosify.com\"\nrm -rf \"${tmpdir}\"\n"
  },
  {
    "path": "ddosify_engine/scripts/testing/benchstat.sh",
    "content": "#!/bin/bash\nset -e\nLIMIT=15\nIS_FAILED=0\ntime_op=$(grep -A1 'time/op' gobench_branch_result.txt |tail -1 | awk '{NF--;NF--;print $NF}' | tr -d + | tr -d %)\necho -e \"Max. Delta Time op: $time_op / $LIMIT\" | tee benchstat.txt\nif (( $(echo \"$time_op > $LIMIT\" | bc -l) )); then\n    IS_FAILED=1\nfi\n\nalloc_op=$(grep -A1 'alloc/op' gobench_branch_result.txt |tail -1 | awk '{NF--;NF--;print $NF}' | tr -d + | tr -d %)\necho -e \"Max. Delta Alloc op: $alloc_op / $LIMIT\" | tee --append benchstat.txt\nif (( $(echo \"$alloc_op > $LIMIT\" | bc -l) )); then\n    IS_FAILED=1\nfi\n\nallocs_op=$(grep -A1 'allocs/op' gobench_branch_result.txt |tail -1 | awk '{NF--;NF--;print $NF}' | tr -d + | tr -d %)\necho -e \"Max. Delta Allocs op: $allocs_op / $LIMIT\" | tee --append benchstat.txt\nif (( $(echo \"$allocs_op > $LIMIT\" | bc -l) )); then\n    IS_FAILED=1\nfi\n\ngithub_comment=`jq -Rs '.' benchstat.txt`\ncurl -s -H \"Authorization: token $1\" \\\n                -X POST -d \"{\\\"body\\\": $github_comment\" \\\n                \"https://api.github.com/repos/ddosify/ddosify/issues/$2/comments\"\n\nif [ $IS_FAILED -eq 1 ]; then\n    exit 1\nfi\n"
  },
  {
    "path": "selfhosted/README.md",
    "content": "<div align=\"center\">\n    <img src=\"https://raw.githubusercontent.com/getanteon/anteon/master/assets/anteon-logo-db.svg#gh-dark-mode-only\" alt=\"Anteon logo dark\" width=\"336px\" /><br />\n    <img src=\"https://raw.githubusercontent.com/getanteon/anteon/master/assets/anteon-logo-wb.svg#gh-light-mode-only\" alt=\"Anteon logo light\" width=\"336px\" /><br />\n</div>\n\n<h1 align=\"center\">Anteon Self Hosted: Effortless Kubernetes Monitoring and Performance Testing</h1>\n\n<p align=\"center\">\n<img src=\"https://raw.githubusercontent.com/getanteon/anteon/master/assets/anteon_service_map_filtered.png\" alt=\"Anteon Kubernetes Monitoring Service Map\" />\n<i>Anteon detects high latency service calls on your K8s cluster. So you can easily find the root service causing the problem.</i>\n</p>\n\n## 📚 Documentation\n\n- [🐝 Installing Anteon Self-Hosted](https://getanteon.com/docs/self-hosted/installation/)\n- [⚙ Installing eBPF Agent (Alaz)](https://getanteon.com/docs/self-hosted/install-ebpf-agent-alaz-on-self-hosted/)\n- [📰 Upgrading to Self-Hosted Enterprise](https://getanteon.com/docs/self-hosted/upgrading-to-self-hosted-enterprise/)\n- [📧 Slack Integration](https://getanteon.com/docs/self-hosted/self-hosted-slack-integration/)\n\nSee the [documentation](https://getanteon.com/docs/self-hosted/) for other guides such as [disabling telemetry](https://getanteon.com/docs/self-hosted/disabling-telemetry-data/).\n\n## 📝 License\n\nAnteon Self Hosted is licensed under the [AGPLv3](../LICENSE)\n"
  },
  {
    "path": "selfhosted/VERSION",
    "content": "2.6.4"
  },
  {
    "path": "selfhosted/docker-compose.yml",
    "content": "version: '3.8'\n\nservices:\n  nginx:\n    image: nginx:1.25.5-alpine\n    ports:\n      - '8014:80'\n    volumes:\n      - ./nginx/default_reverseproxy.conf:/etc/nginx/conf.d/default.conf\n    depends_on:\n      - frontend\n      - backend\n    restart: always\n    networks:\n      - anteon\n\n  frontend:\n    image: ddosify/selfhosted_frontend:4.1.5\n    depends_on:\n      - backend\n    restart: always\n    pull_policy: always\n    networks:\n      - anteon\n\n  backend:\n    image: ddosify/selfhosted_backend:3.2.9\n    depends_on:\n      - postgres\n      - influxdb\n      - redis-backend\n      - seaweedfs\n    env_file:\n      - .env\n    networks:\n      - anteon\n    restart: always\n    pull_policy: always\n    command: /workspace/start_scripts/start_app_onprem.sh\n    ports:\n      - '8008:8008'\n\n  backend-celery-worker:\n    image: ddosify/selfhosted_backend:3.2.9\n    depends_on:\n      - postgres\n      - influxdb\n      - redis-backend\n      - seaweedfs\n      - backend\n      - rabbitmq\n    env_file:\n      - .env\n    networks:\n      - anteon\n    restart: always\n    pull_policy: always\n    command: /workspace/start_scripts/start_celery_worker.sh\n\n  backend-celery-beat:\n    image: ddosify/selfhosted_backend:3.2.9\n    depends_on:\n      - postgres\n      - influxdb\n      - redis-backend\n      - seaweedfs\n      - backend\n      - rabbitmq\n    env_file:\n      - .env\n    networks:\n      - anteon\n    restart: always\n    pull_policy: always\n    command: /workspace/start_scripts/start_celery_beat.sh\n\n  alaz-backend:\n    image: ddosify/selfhosted_alaz_backend:2.3.11\n    depends_on:\n      - postgres\n      - influxdb\n      - redis-backend\n      - backend\n    env_file:\n      - .env\n    networks:\n      - anteon\n    restart: always\n    pull_policy: always\n    command: /workspace/start_scripts/start_app_onprem.sh\n    ports:\n      - '8009:8008'\n\n  alaz-backend-celery-worker-1:\n    image: ddosify/selfhosted_alaz_backend:2.3.11\n    depends_on:\n      - postgres\n      - influxdb\n      - redis-alaz-backend\n      - alaz-backend\n      - rabbitmq\n      - backend\n    env_file:\n      - .env\n    networks:\n      - anteon\n    restart: always\n    pull_policy: always\n    command: /workspace/start_scripts/start_celery_worker.sh\n\n  alaz-backend-celery-worker-2:\n    image: ddosify/selfhosted_alaz_backend:2.3.11\n    depends_on:\n      - postgres\n      - influxdb\n      - redis-alaz-backend\n      - alaz-backend\n      - rabbitmq\n      - backend\n    env_file:\n      - .env\n    networks:\n      - anteon\n    restart: always\n    pull_policy: always\n    command: /workspace/start_scripts/start_celery_worker.sh\n\n  alaz-backend-celery-beat:\n    image: ddosify/selfhosted_alaz_backend:2.3.11\n    depends_on:\n      - postgres\n      - influxdb\n      - redis-alaz-backend\n      - alaz-backend\n      - rabbitmq\n      - backend\n    env_file:\n      - .env\n    networks:\n      - anteon\n    restart: always\n    pull_policy: always\n    command: /workspace/start_scripts/start_celery_beat.sh\n\n  alaz-backend-request-writer:\n    image: ddosify/selfhosted_alaz_backend:2.3.11\n    depends_on:\n      - postgres\n      - influxdb\n      - redis-alaz-backend\n      - alaz-backend\n      - rabbitmq\n      - backend\n    env_file:\n      - .env\n    networks:\n      - anteon\n    restart: always\n    pull_policy: always\n    command: /workspace/start_scripts/start_request_writer.sh\n\n  hammermanager:\n    ports:\n      - \"9901:8001\"\n    image: ddosify/selfhosted_hammermanager:2.0.2\n    depends_on:\n      - postgres\n      - rabbitmq\n    env_file:\n      - .env\n    networks:\n      - anteon\n    restart: always\n    pull_policy: always\n    command: /workspace/start_scripts/start_app.sh\n\n  hammermanager-celery-worker:\n    image: ddosify/selfhosted_hammermanager:2.0.2\n    depends_on:\n      - postgres\n      - rabbitmq\n      - hammermanager\n    env_file:\n      - .env\n    networks:\n      - anteon\n    restart: always\n    pull_policy: always\n    command: /workspace/start_scripts/start_celery_worker.sh\n\n  hammermanager-celery-beat:\n    image: ddosify/selfhosted_hammermanager:2.0.2\n    depends_on:\n      - postgres\n      - rabbitmq\n      - hammermanager\n    env_file:\n      - .env\n    networks:\n      - anteon\n    restart: always\n    pull_policy: always\n    command: /workspace/start_scripts/start_celery_beat.sh\n\n  hammer:\n    image: ddosify/selfhosted_hammer:2.0.0\n    volumes:\n      - hammer_id:/root/uuid\n    depends_on:\n      - rabbitmq\n      - influxdb\n      - hammermanager\n      - seaweedfs\n    env_file:\n      - .env\n    networks:\n      - anteon\n    restart: always\n    pull_policy: always\n\n  hammerdebug:\n    image: ddosify/selfhosted_hammer:2.0.0\n    volumes:\n      - hammerdebug_id:/root/uuid\n    depends_on:\n      - rabbitmq\n      - influxdb\n      - hammermanager\n    env_file:\n      - .env\n    environment:\n      - IS_DEBUG=true\n    networks:\n      - anteon\n    restart: always\n    pull_policy: always\n\n  postgres:\n    image: \"postgres:16.2-alpine\"\n    volumes:\n      - postgres_data:/var/lib/postgresql/data\n      - ./init_scripts/postgres:/docker-entrypoint-initdb.d\n    env_file:\n      - .env\n    networks:\n      - anteon\n    restart: always\n\n  rabbitmq:\n    ports:\n      - \"6672:5672\"\n    image: \"rabbitmq:3.13.1-alpine\"\n    networks:\n      - anteon\n    restart: always\n\n  influxdb:\n    ports:\n      - \"9086:8086\"\n    image: \"influxdb:2.6.1-alpine\"\n    volumes:\n      - influxdb_data:/var/lib/influxdb\n      - ./init_scripts/influxdb:/docker-entrypoint-initdb.d\n    environment:\n      - DOCKER_INFLUXDB_INIT_MODE=setup\n      - DOCKER_INFLUXDB_INIT_ORG=ddosify\n      - DOCKER_INFLUXDB_INIT_BUCKET=hammerBucket\n    env_file:\n      - .env\n    networks:\n      - anteon\n    restart: always\n\n  redis-backend:\n    image: \"redis:7.2.4-alpine\"\n    volumes:\n      - redis_backend_data:/data\n    networks:\n      - anteon\n    restart: always\n\n  redis-alaz-backend:\n    image: \"redis:7.2.4-alpine\"\n    volumes:\n      - redis_alaz_backend_data:/data\n    networks:\n      - anteon\n    restart: always\n\n  seaweedfs:\n    image: chrislusf/seaweedfs:3.64\n    ports:\n      - \"8333:8333\"\n    command: 'server -s3 -dir=\"/data\"'\n    networks:\n      - anteon\n    restart: always\n    volumes:\n      - seaweedfs_data:/data\n\n  prometheus:\n    image: prom/prometheus:v2.37.9\n    ports:\n      - \"9090:9090\"\n    command: --config.file=/prometheus/prometheus.yml --storage.tsdb.path=/prometheus --web.console.libraries=/usr/share/prometheus/console_libraries --web.console.templates=/usr/share/prometheus/consoles --storage.tsdb.retention=10d\n    volumes:\n      - ./init_scripts/prometheus/prometheus.yml:/prometheus/prometheus.yml\n      - prometheus_data:/prometheus\n    networks:\n      - anteon\n    restart: always\n\nvolumes:\n  postgres_data:\n  influxdb_data:\n  redis_backend_data:\n  redis_alaz_backend_data:\n  seaweedfs_data:\n  hammer_id:\n  hammerdebug_id:\n  prometheus_data:\n\nnetworks:\n  anteon:\n"
  },
  {
    "path": "selfhosted/init_scripts/influxdb/01_influxdb_create_buckets.sh",
    "content": "#!/bin/bash\nset -e\n\ninflux bucket create -n hammerBucketDetailed -o ddosify\ninflux bucket create -n hammerBucketIteration -o ddosify\n"
  },
  {
    "path": "selfhosted/init_scripts/postgres/01_postgres_create_dbs.sql",
    "content": "CREATE DATABASE backend;\nCREATE DATABASE alazbackend;\nCREATE DATABASE hammermanager;\n"
  },
  {
    "path": "selfhosted/init_scripts/prometheus/prometheus.yml",
    "content": "global:\n  scrape_interval: 10s\n  evaluation_interval: 10s\n\nalerting:\n  alertmanagers:\n    - static_configs:\n        - targets:\n\nrule_files:\n\nscrape_configs:\n  - job_name: \"backend\"\n    metrics_path: '/metrics/scrape'\n    static_configs:\n      - targets: [\"alaz-backend:8008\"]\n    basic_auth:\n        username: alaz-backend\n        password: jRTyHAbUHYE37hRBgEz\n"
  },
  {
    "path": "selfhosted/install.sh",
    "content": "#!/bin/bash\n\nset -e\n\necho \"⚡ Installing Anteon Self Hosted...\"\n\necho \"🔍 Checking prerequisites...\"\n\n# Function to check if a port is available\nis_port_available() {\n  local port=\"$1\"\n\n  if ! command -v lsof >/dev/null 2>&1; then\n    echo \"❌ lsof not found. Please install lsof and try again.\"\n    exit 1\n  fi\n\n  if lsof -i :\"$port\" >/dev/null 2>&1; then\n    echo \"❌ Port $port is already in use. Free up the current port and try again.\"\n    exit 1\n  fi\n}\n\nis_port_available 8014\nis_port_available 9901\nis_port_available 6672\nis_port_available 9086\nis_port_available 8333\n\n# Check if Git is installed\nif ! command -v git >/dev/null 2>&1; then\n  echo \"❌ Git not found. Please install Git and try again.\"\n  exit 1\nfi\n\n# Check if Docker is installed\nif ! command -v docker >/dev/null 2>&1; then\n  echo \"❌ Docker not found. Please install Docker and try again.\"\n  exit 1\nfi\n\n# Check if Docker Compose is installed\nif ! command -v docker-compose >/dev/null 2>&1; then\n  if ! docker compose version >/dev/null 2>&1; then\n    echo \"❌ Docker Compose not found. Please install Docker Compose and try again.\"\n    exit 1\n  fi\nfi\n\n# Check if Docker is running\nif ! docker info >/dev/null 2>&1; then\n  echo \"❌ Docker is not running. Please start Docker and try again.\"\n  exit 1\nfi\n\necho \"🚀 Starting installation of Anteon Self Hosted...\"\n\nREPO_DIR=\"$HOME/.anteon\"\n\n# Check if repository already exists\nif [ -d \"$REPO_DIR\" ]; then\n  echo \"🔄 Repository already exists at $REPO_DIR - Attempting to update...\"\n  cd \"$REPO_DIR\"\n  git checkout master\n  cd \"$REPO_DIR/selfhosted\"\n  git pull 2>&1 || {\n    read -p \"⚠️ Error updating repository. Clean and update? [Y/n]: \" answer\n    answer=${answer:-Y}\n    if [[ $answer =~ ^[Yy]$ ]]; then\n      git reset --hard >/dev/null 2>&1\n      git clean -fd >/dev/null 2>&1\n      git pull >/dev/null 2>&1\n    fi\n  }\nelse\n  # Clone the repository\n  echo \"📦 Cloning repository to $REPO_DIR directory...\"\n  git clone https://github.com/getanteon/anteon.git \"$REPO_DIR\" >/dev/null 2>&1\n  cd \"$REPO_DIR\"\n  git checkout master >/dev/null 2>&1\n  cd \"$REPO_DIR/selfhosted\"\nfi\n\n# Determine which compose command to use\nCOMPOSE_COMMAND=\"docker-compose\"\nif command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then\n  COMPOSE_COMMAND=\"docker compose\"\nfi\n\necho \"🚀 Deploying Anteon Self Hosted...\"\n$COMPOSE_COMMAND -f \"$REPO_DIR/selfhosted/docker-compose.yml\" up -d\ndocker pull busybox:1.34.1 >/dev/null 2>&1\necho \"\"\necho \"⏳ Waiting for services to be ready...\"\ndocker run --rm --network selfhosted_anteon busybox:1.34.1 /bin/sh -c \"until nc -z nginx 80 && nc -z backend 8008 && nc -z hammermanager 8001 && nc -z rabbitmq 5672 && nc -z postgres 5432 && nc -z influxdb 8086 && nc -z seaweedfs 8333; do sleep 5; done\"\necho \"✅ Anteon Self Hosted installation complete!\"\necho \"📁 Installation directory: $REPO_DIR/selfhosted\"\necho \"🔥 To remove Anteon Self Hosted, run: cd $REPO_DIR/selfhosted && $COMPOSE_COMMAND down\"\necho \"\"\necho \"🌐 Open http://localhost:8014 in your browser to access the application.\"\n"
  },
  {
    "path": "selfhosted/nginx/default_reverseproxy.conf",
    "content": "upstream frontend {\n    server frontend:3000;\n}\n\nupstream backend {\n    server backend:8008;\n}\n\nupstream alaz-backend {\n    server alaz-backend:8008;\n}\n\nserver {\n    listen 80;\n    client_max_body_size 96M;\n    http2_max_field_size 64k;\n    http2_max_header_size 512k;\n\n    error_log /var/log/nginx/error.log error;\n    access_log off;\n\n    location / {\n        proxy_pass http://frontend;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n\n    location /api/ {\n        proxy_pass http://backend;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n\n    location /api-alaz/ {\n        proxy_pass http://alaz-backend;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n}\n"
  }
]