[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Report an issue that you've found\ntitle: \"\"\nlabels: \"\"\nassignees: \"\"\n---\n\n##### Description\n\n<!-- A clear and concise description of what the bug is and how to reproduce it. -->\n\n##### Versions\n\n<!-- Please specify real version numbers or git SHAs, not just \"Latest\" since that changes fairly regularly. -->\n\n| Sarama | Kafka | Go  |\n| ------ | ----- | --- |\n|        |       |     |\n\n##### Configuration\n\n<!-- What configuration values are you using for Sarama and Kafka? -->\n\n```go\n\n```\n\n##### Logs\n\n<!-- If applicable, add logs from Sarama and/or Kafka to help explain your problem.\nYou can set `sarama.Logger` to a `log.Logger` to capture Sarama debug output. -->\n\n<details><summary>logs: CLICK ME</summary>\n<p>\n\n```\n\n```\n\n</p>\n</details>\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: \"\"\nassignees: \"\"\n---\n\n#### Description\n\n<!-- A clear and concise description of what the missing capability is, why it's a problem and what you want to happen -->\n\n#### Additional context\n\n<!-- Add any other context or links to existing implementations of the feature in other Kafka clients -->\n<!-- Please link to the relevant KIP from https://cwiki.apache.org/confluence/display/KAFKA/Kafka+Improvement+Proposals if appropriate -->\n"
  },
  {
    "path": ".github/actions/staticcheck-matchers.json",
    "content": "{\n  \"problemMatcher\": [\n    {\n      \"owner\": \"staticcheck\",\n      \"pattern\": [\n        {\n          \"regexp\": \"^\\\\s*(.+\\\\.go):(?:(\\\\d+):(\\\\d+):)? (.*)\",\n          \"file\": 1,\n          \"line\": 2,\n          \"column\": 3,\n          \"message\": 4\n        }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: docker\n    directory: /\n    schedule:\n      interval: daily\n      time: \"21:00\"\n      timezone: \"Etc/UTC\"\n    labels:\n      - \"ignore-for-release\"\n    commit-message:\n      prefix: chore(ci)\n\n  - package-ecosystem: github-actions\n    directory: /\n    open-pull-requests-limit: 2\n    schedule:\n      interval: weekly\n    labels:\n      - \"ignore-for-release\"\n    commit-message:\n      prefix: chore(ci)\n    groups:\n      actions:\n        patterns:\n          - \"actions/*\"\n\n  - package-ecosystem: gomod\n    directories:\n      - /\n      - /examples/consumergroup\n      - /examples/exactly_once\n      - /examples/http_server\n      - /examples/sasl_scram_client\n      - /examples/interceptors\n      - /examples/txn_producer\n    open-pull-requests-limit: 5\n    schedule:\n      interval: daily\n      time: \"15:00\"\n      timezone: \"Etc/UTC\"\n    labels:\n      - \"dependencies\"\n    commit-message:\n      prefix: chore\n      include: \"scope\"\n    groups:\n      otel:\n        patterns:\n          - \"go.opentelemetry.io/otel/*\"\n      golang-x:\n        patterns:\n          - \"golang.org/x/*\"\n    ignore:\n      - dependency-name: \"go.opentelemetry.io/*\"\n        # ignore versions of otel that are equal to or greater than 1.30.0 as they require go1.22\n        versions: [\">=1.30.0\"]\n"
  },
  {
    "path": ".github/release.yaml",
    "content": "changelog:\n  exclude:\n    labels:\n    - ignore-for-release\n    - invalid\n    - no-changelog\n    - skip-changelog\n  categories:\n  - title: \":rotating_light: Breaking Changes\"\n    labels:\n    - breaking\n  - title: \":tada: New Features / Improvements\"\n    labels:\n    - enhancement\n    - feature\n    - feat\n  - title: \":bug: Fixes\"\n    labels:\n    - bug\n    - bugfix\n    - fix\n    - regression\n  - title: \":package: Dependency updates\"\n    labels:\n    - dependencies\n    - deps\n  - title: \":wrench: Maintenance\"\n    labels:\n    - build\n    - chore\n    - ci\n    - housekeeping\n    - internal\n  - title: \":memo: Documentation\"\n    labels:\n    - documentation\n    - docs\n  - title: \":heavy_plus_sign: Other Changes\"\n    labels:\n    - \"*\"\n"
  },
  {
    "path": ".github/renovate.json5",
    "content": "{\n  $schema: \"https://docs.renovatebot.com/renovate-schema.json\",\n  extends: [\n    \"config:best-practices\",\n    \"customManagers:dockerfileVersions\",\n    \"customManagers:githubActionsVersions\",\n    \"customManagers:makefileVersions\",\n    \"security:openssf-scorecard\",\n    \":gitSignOff\",\n  ],\n  branchConcurrentLimit: 4,\n  prConcurrentLimit: 4,\n  prHourlyLimit: 4,\n  branchPrefix: \"mend/\",\n  labels: [\"dependencies\"],\n  timezone: \"Etc/UTC\",\n  major: {\n    addLabels: [\"bump/major\"],\n  },\n  minor: {\n    addLabels: [\"bump/minor\"],\n  },\n  patch: {\n    addLabels: [\"bump/patch\"],\n  },\n  pin: {\n    addLabels: [\"bump/pin\"],\n  },\n  digest: {\n    addLabels: [\"bump/digest\"],\n  },\n  vulnerabilityAlerts: {\n    enabled: true,\n    labels: [\"security\"],\n  },\n  packageRules: [\n    {\n      description: \"Update Docker and Docker Compose dependencies daily at 9:00 PM UTC, grouped into a single PR\",\n      matchManagers: [\"dockerfile\", \"docker-compose\"],\n      schedule: [\"* 21 * * *\"],\n      commitMessagePrefix: \"chore(ci): \",\n      groupName: \"docker dependencies\",\n      addLabels: [\"docker\", \"ignore-for-release\"],\n    },\n    {\n      description: \"Update GitHub Actions weekly, grouped into a single PR for actions/* packages\",\n      matchManagers: [\"github-actions\"],\n      schedule: [\"* 0 * * 0\"],\n      commitMessagePrefix: \"chore(ci): \",\n      groupName: \"actions\",\n      matchPackageNames: [\"actions/*\", \"github/codeql-action\"],\n      prConcurrentLimit: 2,\n      addLabels: [\"github-actions\", \"ignore-for-release\"],\n    },\n    {\n      description: \"Update Go modules daily at 3:00 PM UTC\",\n      matchManagers: [\"gomod\"],\n      matchFileNames: [\"/**\"],\n      schedule: [\"* 15 * * *\"],\n    },\n    {\n      description: \"Group OpenTelemetry Go dependencies into a single PR\",\n      matchManagers: [\"gomod\"],\n      groupName: \"otel\",\n      matchPackageNames: [\n        \"go.opentelemetry.io/otel\",\n        \"go.opentelemetry.io/otel/*\",\n      ],\n    },\n    {\n      description: \"Group golang.org/x Go dependencies into a single PR\",\n      matchManagers: [\"gomod\"],\n      groupName: \"golang-x\",\n      matchDepTypes: [\"direct\", \"indirect\"],\n      enabled: true,\n      matchPackageNames: [\"golang.org/x/*\"],\n    },\n    {\n      description: \"Pin ubi-minimal docker image to major.minor tags only\",\n      matchDatasources: [\"docker\"],\n      matchPackageNames: [\"registry.access.redhat.com/ubi9/ubi-minimal\"],\n      versioning: \"regex:^(?<major>\\\\d+)\\\\.(?<minor>\\\\d+)$\",\n    },\n    {\n      description: \"Pin zookeeper docker image to 3.7.x\",\n      matchDatasources: [\"docker\"],\n      matchPackageNames: [\"docker.io/library/zookeeper\"],\n      allowedVersions: \"< 3.8\",\n    },\n    {\n      description: \"Do not pin digests in docker-compose files\",\n      matchManagers: [\"docker-compose\"],\n      pinDigests: false,\n    },\n  ],\n}\n"
  },
  {
    "path": ".github/workflows/apidiff.yml",
    "content": "name: API Compatibility\non:\n  merge_group:\n  push:\n    branches:\n    - main\n    paths-ignore:\n    - '**/*.md'\n  pull_request:\n    branches:\n    - \"**\"\n    paths-ignore:\n    - '**/*.md'\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}\n\npermissions:\n  contents: read  # for actions/checkout to fetch code\n\nenv:\n  # Use the Go toolchain installed by setup-go\n  GOTOOLCHAIN: local\n\njobs:\n  apidiff:\n    runs-on: ubuntu-latest\n    if: github.base_ref\n    steps:\n    - name: Setup Go\n      uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0\n      with:\n        go-version: stable\n    - name: Add GOBIN to PATH\n      run: echo \"$(go env GOPATH)/bin\" >>$GITHUB_PATH\n    - name: Install apidiff cmd\n      run: go install golang.org/x/exp/cmd/apidiff@v0.0.0-20250813145105-42675adae3e6\n    - name: Checkout base code\n      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      with:\n        ref: ${{ github.base_ref }}\n        path: \"base\"\n        persist-credentials: false\n    - name: Capture apidiff baseline\n      run: apidiff -m -w ../baseline.bin .\n      working-directory: \"base\"\n    - name: Checkout updated code\n      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      with:\n        path: \"updated\"\n        persist-credentials: false\n    - name: Run apidiff check\n      run: apidiff -m -incompatible ../baseline.bin .\n      working-directory: \"updated\"\n"
  },
  {
    "path": ".github/workflows/cache-cleanup.yml",
    "content": "# https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/caching-dependencies-to-speed-up-workflows#force-deleting-cache-entries\nname: Cleanup caches on PR close/merge\non:\n  pull_request:\n    types:\n      - closed\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}\n\npermissions:\n  contents: read # for actions/checkout to fetch code\n\njobs:\n  cleanup:\n    permissions:\n      actions: write # for cache management\n    runs-on: ubuntu-latest\n    steps:\n      - name: Delete Caches\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          GH_REPO: ${{ github.repository }}\n          BRANCH: refs/pull/${{ github.event.pull_request.number }}/merge\n        run: |\n          gh cache list --ref \"$BRANCH\" --limit 100\n          CACHE_KEYS=\"$(gh cache list --ref \"$BRANCH\" --limit 100 --json id --jq '.[].id')\"\n          for KEY in ${CACHE_KEYS}; do\n            echo \"Deleting cache $KEY for ref $BRANCH\"\n            gh cache delete \"$KEY\" || true\n            echo\n          done\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\non:\n  merge_group:\n  push:\n    branches:\n    - main\n    paths-ignore:\n    - '**/*.md'\n  pull_request:\n    branches:\n    - \"**\"\n    paths-ignore:\n    - '**/*.md'\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}\n\npermissions:\n  contents: read # for actions/checkout to fetch code\n\nenv:\n  # Use the Go toolchain installed by setup-go\n  GOTOOLCHAIN: local\n  # renovate: datasource=github-releases depName=golangci/golangci-lint\n  GOLANGCI_LINT_VERSION: v2.11.3\n  # renovate: datasource=github-releases depName=dominikh/go-tools\n  STATICCHECK_VERSION: 2026.1\n  # renovate: datasource=github-releases depName=mfridman/tparse\n  TPARSE_VERSION: v0.18.0\n\njobs:\n  lint:\n    permissions:\n      contents: read # for actions/checkout to fetch code\n      pull-requests: read # for golangci/golangci-lint-action to fetch pull requests\n    name: Linting with Go ${{ matrix.go-version }}\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        go-version: [stable]\n    steps:\n    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      with:\n        persist-credentials: false\n    - name: Setup Go\n      uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0\n      with:\n        go-version: ${{ matrix.go-version }}\n    - name: Staticcheck\n      shell: bash\n      env:\n        BUILDTAGS: \"functional\"\n      run: |\n        go install \"honnef.co/go/tools/cmd/staticcheck@${STATICCHECK_VERSION}\"\n        echo \"::add-matcher::./.github/actions/staticcheck-matchers.json\"\n        $(go env GOPATH)/bin/staticcheck -tags \"${BUILDTAGS}\" ./...\n    - name: golangci-lint\n      env:\n        GOFLAGS: -tags=functional\n      uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0\n      with:\n        version: ${{ env.GOLANGCI_LINT_VERSION }}\n  test:\n    name: Unit Testing with Go ${{ matrix.go-version }}\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        go-version: [oldstable, stable]\n    env:\n      DEBUG: true\n      GOFLAGS: -trimpath\n    steps:\n    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      with:\n        persist-credentials: false\n    - name: Setup Go\n      uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0\n      with:\n        go-version: ${{ matrix.go-version }}\n    - name: Test (Unit)\n      run: make test\n    - name: Report Test Results\n      if: always()\n      run: |\n        go run github.com/mfridman/tparse@${TPARSE_VERSION} -all -format markdown -file _test/unittests.json | tee -a $GITHUB_STEP_SUMMARY\n    - name: Report Per Func Test Coverage\n      if: always()\n      run: |\n        cat >>$GITHUB_STEP_SUMMARY <<EOF\n        <details>\n        <summary>Click for per-func code coverage</summary>\n\n        |Filename|Function|Coverage|\n        |--------|--------|--------|\n        $(go tool cover -func=profile.out | sed -E -e 's/[[:space:]]+/|/g' -e 's/$/|/g' -e 's/^/|/g')\n        </details>\n        EOF\n"
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "content": "name: \"CodeQL\"\non:\n  merge_group:\n  push:\n    branches:\n    - main\n  pull_request:\n    branches:\n    - \"**\"\n  schedule:\n  - cron: \"39 12 * * 1\"\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}\n\npermissions:\n  contents: read  # for actions/checkout to fetch code\n\nenv:\n  # Use the Go toolchain installed by setup-go\n  GOTOOLCHAIN: local\n\njobs:\n  analyze:\n    name: Analyze\n    runs-on: ubuntu-latest\n    permissions:\n      actions: read  # for github/codeql-action to list actions\n      contents: read  # for actions/checkout to fetch code\n      security-events: write  # for github/codeql-action to report security issues\n    strategy:\n      fail-fast: false\n      matrix:\n        language: [\"actions\", \"go\"]\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      with:\n        persist-credentials: false\n    - name: Initialize CodeQL\n      uses: github/codeql-action/init@c793b717bc78562f491db7b0e93a3a178b099162 # v4.32.5\n      with:\n        languages: ${{ matrix.language }}\n    - name: Setup Go\n      uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0\n      with:\n        go-version: stable\n    - name: Autobuild\n      uses: github/codeql-action/autobuild@c793b717bc78562f491db7b0e93a3a178b099162 # v4.32.5\n    - name: Perform CodeQL Analysis\n      uses: github/codeql-action/analyze@c793b717bc78562f491db7b0e93a3a178b099162 # v4.32.5\n"
  },
  {
    "path": ".github/workflows/dependency-review.yml",
    "content": "# Dependency Review Action\n#\n# This Action will scan dependency manifest files that change as part of a Pull Request,\n# surfacing known-vulnerable versions of the packages declared or updated in the PR.\n# Once installed, if the workflow run is marked as required,\n# PRs introducing known-vulnerable packages will be blocked from merging.\n#\n# Source repository: https://github.com/actions/dependency-review-action\nname: 'Dependency Review'\non:\n  pull_request:\n    branches:\n    - \"**\"\n    paths-ignore:\n    - '**/*.md'\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}\n\npermissions:\n  contents: read  # for actions/checkout to fetch code\n\njobs:\n  dependency-review:\n    runs-on: ubuntu-latest\n    steps:\n      - name: 'Checkout Repository'\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          persist-credentials: false\n      - name: 'Dependency Review'\n        uses: actions/dependency-review-action@05fe4576374b728f0c523d6a13d64c25081e0803 # v4.8.3\n"
  },
  {
    "path": ".github/workflows/fuzz.yml",
    "content": "name: Fuzzing\non:\n  merge_group:\n  push:\n    branches:\n    - main\n    paths-ignore:\n    - '**/*.md'\n  pull_request:\n    branches:\n    - \"**\"\n    paths-ignore:\n    - '**/*.md'\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}\n\npermissions:\n  contents: read  # for actions/checkout to fetch code\n\nenv:\n  # Use the Go toolchain installed by setup-go\n  GOTOOLCHAIN: local\n\njobs:\n  test:\n    name: Fuzz\n    runs-on: ubuntu-latest\n    env:\n      GOFLAGS: -trimpath\n    steps:\n    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      with:\n        persist-credentials: false\n    - name: Setup Go\n      uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0\n      with:\n        go-version: stable\n    - name: Run any fuzzing tests\n      run: go test -list . | grep '^Fuzz' | parallel 'go test -v -run=^{}$ -fuzz=^{}$ -fuzztime=5m'\n"
  },
  {
    "path": ".github/workflows/fvt-main.yml",
    "content": "name: FVT (main)\non:\n  merge_group:\n  push:\n    branches:\n    - main\n    paths-ignore:\n    - '**/*.md'\n\npermissions:\n  contents: read  # for actions/checkout to fetch code\n\nenv:\n  # Use the Go toolchain installed by setup-go\n  GOTOOLCHAIN: local\n\njobs:\n  fvt:\n    name: Test with Kafka ${{ matrix.kafka-version }}\n    strategy:\n      fail-fast: false\n      matrix:\n        go-version: [stable]\n        kafka-version: [1.0.2, 2.0.1, 2.2.2, 2.6.3, 2.8.2, 3.0.2, 3.3.2, 3.6.2, 3.8.1, 3.9.2, 4.1.1, 4.2.0]\n        include:\n        - kafka-version: 1.0.2\n          scala-version: 2.11\n        - kafka-version: 2.0.1\n          scala-version: 2.12\n        - kafka-version: 2.2.2\n          scala-version: 2.12\n        - kafka-version: 2.6.3\n          scala-version: 2.12\n        - kafka-version: 2.8.2\n          scala-version: 2.12\n        - kafka-version: 3.0.2\n          scala-version: 2.12\n        - kafka-version: 3.3.2\n          scala-version: 2.13\n        - kafka-version: 3.6.2\n          scala-version: 2.13\n        - kafka-version: 3.8.1\n          scala-version: 2.13\n        - kafka-version: 3.9.2\n          scala-version: 2.13\n        - kafka-version: 4.1.1\n          scala-version: 2.13\n        - kafka-version: 4.2.0\n          scala-version: 2.13\n    uses: ./.github/workflows/fvt.yml\n    with:\n      go-version: ${{ matrix.go-version }}\n      kafka-version: ${{ matrix.kafka-version }}\n      scala-version: ${{ matrix.scala-version }}\n"
  },
  {
    "path": ".github/workflows/fvt-pr.yml",
    "content": "name: FVT (PR)\non:\n  pull_request:\n    branches:\n    - \"**\"\n    paths-ignore:\n    - '**/*.md'\n\npermissions:\n  contents: read  # for actions/checkout to fetch code\n\nenv:\n  # Use the Go toolchain installed by setup-go\n  GOTOOLCHAIN: local\n\njobs:\n  fvt:\n    name: Test with Kafka ${{ matrix.kafka-version }}\n    strategy:\n      fail-fast: false\n      matrix:\n        go-version: [stable]\n        kafka-version: [1.0.2, 2.6.3, 3.6.2, 3.9.2, 4.1.1, 4.2.0]\n        include:\n        - kafka-version: 1.0.2\n          scala-version: 2.11\n        - kafka-version: 2.6.3\n          scala-version: 2.12\n        - kafka-version: 3.6.2\n          scala-version: 2.13\n        - kafka-version: 3.9.2\n          scala-version: 2.13\n        - kafka-version: 4.1.1\n          scala-version: 2.13\n        - kafka-version: 4.2.0\n          scala-version: 2.13\n    uses: ./.github/workflows/fvt.yml\n    with:\n      go-version: ${{ matrix.go-version }}\n      kafka-version: ${{ matrix.kafka-version }}\n      scala-version: ${{ matrix.scala-version }}\n"
  },
  {
    "path": ".github/workflows/fvt.yml",
    "content": "name: FVT\non:\n  workflow_call:\n    inputs:\n      go-version:\n        required: false\n        type: string\n        default: stable\n      kafka-version:\n        required: false\n        type: string\n        default: \"3.9.1\"\n      scala-version:\n        required: false\n        type: string\n        default: \"2.13\"\n\nconcurrency:\n  group: ${{ github.workflow }}-kafka-${{ inputs.kafka-version}}-${{ github.ref }}\n  cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}\n\npermissions:\n  contents: read # for actions/checkout to fetch code\n\nenv:\n  # Use the Go toolchain installed by setup-go\n  GOTOOLCHAIN: local\n\njobs:\n  fvt:\n    name: Test with Kafka ${{ inputs.kafka-version }}\n    runs-on: ubuntu-latest\n    env:\n      DEBUG: true\n      GOFLAGS: -trimpath\n      KAFKA_VERSION: ${{ inputs.kafka-version }}\n      SCALA_VERSION: ${{ inputs.scala-version }}\n    steps:\n    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      with:\n        persist-credentials: false\n    - name: Setup Docker\n      uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0\n      id: buildx\n    - name: Build FVT Docker Image\n      uses: docker/bake-action@5be5f02ff8819ecd3092ea6b2e6261c31774f2b4 # v6.10.0\n      with:\n        builder: ${{ steps.buildx.outputs.name }}\n        files: docker-compose.yml\n        load: true\n        targets: kafka-1\n        set: |\n          *.cache-from=type=gha,scope=fvt-kafka-${{ inputs.kafka-version }}\n          *.cache-to=type=gha,scope=fvt-kafka-${{ inputs.kafka-version }},mode=max\n    - name: Setup Go\n      uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0\n      with:\n        go-version: ${{ inputs.go-version }}\n    - name: Setup Docker Compose\n      run: |\n        curl --fail -sSL \"https://github.com/docker/compose/releases/download/v2.32.1/docker-compose-$(uname -s)-$(uname -m)\" -o /tmp/docker-compose\n        mkdir -p $HOME/.docker/cli-plugins\n        install -m755 /tmp/docker-compose $HOME/.docker/cli-plugins\n        docker version --format 'Docker Engine version v{{.Server.Version}}'\n        docker compose version\n    - name: Test (Functional)\n      run: |\n        nohup sudo tcpdump -i lo -w \"fvt-kafka-${KAFKA_VERSION}.pcap\" portrange 29091-29095 >/dev/null 2>&1 &\n        echo $! >tcpdump.pid\n        make test_functional\n    - name: Report Test Results\n      if: always()\n      run: |\n        # renovate: datasource=github-releases depName=mfridman/tparse\n        go run github.com/mfridman/tparse@v0.18.0 -all -format markdown -file _test/fvt.json | tee -a $GITHUB_STEP_SUMMARY\n    - name: Report Per Func Test Coverage\n      if: always()\n      run: |\n        cat >>$GITHUB_STEP_SUMMARY <<EOF\n        <details>\n        <summary>Click for per-func code coverage</summary>\n\n        |Filename|Function|Coverage|\n        |--------|--------|--------|\n        $(go tool cover -func=profile.out | sed -E -e 's/[[:space:]]+/|/g' -e 's/$/|/g' -e 's/^/|/g')\n        </details>\n        EOF\n    - name: Stop tcpdump\n      if: always()\n      run: |\n        if [ -f \"tcpdump.pid\" ]; then sudo kill \"$(cat tcpdump.pid)\" || true; fi\n        if [ -f \"fvt-kafka-${KAFKA_VERSION}.pcap\" ]; then sudo chmod a+r \"fvt-kafka-${KAFKA_VERSION}.pcap\"; fi\n    - name: Upload pcap file\n      if: always()\n      uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0\n      with:\n        name: fvt-kafka-${{ inputs.kafka-version }}.pcap\n        path: fvt-kafka-${{ inputs.kafka-version }}.pcap\n        retention-days: 5\n        if-no-files-found: ignore\n"
  },
  {
    "path": ".github/workflows/i386.yml",
    "content": "name: i386\non:\n  merge_group:\n  push:\n    branches:\n    - main\n    paths-ignore:\n    - '**/*.md'\n  pull_request:\n    branches:\n    - \"**\"\n    paths-ignore:\n    - '**/*.md'\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}\n\npermissions:\n  contents: read  # for actions/checkout to fetch code\n\nenv:\n  # Use the Go toolchain installed by setup-go\n  GOTOOLCHAIN: local\n\njobs:\n  atomicalign:\n    permissions:\n      contents: read  # for actions/checkout to fetch code\n      pull-requests: read  # for golangci/golangci-lint-action to fetch pull requests\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      with:\n        persist-credentials: false\n    - name: Setup Go\n      uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0\n      with:\n        go-version: stable\n    - name: staticcheck\n      env:\n        GOARCH: 386\n        GOFLAGS: -tags=functional\n      run: |\n          git clone --depth=1 https://github.com/dominikh/go-tools /tmp/go-tools\n          ( cd /tmp/go-tools/cmd/staticcheck && go build -o /tmp/staticcheck )\n          /tmp/staticcheck -checks SA1027 ./...\n"
  },
  {
    "path": ".github/workflows/renovate-config.yml",
    "content": "name: renovate-config-validator\n\non:\n  pull_request:\n    paths:\n      - '.github/renovate.json5'\n\npermissions:\n  contents: read\n\njobs:\n  validate:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          sparse-checkout: |\n            .github/renovate.json5\n          sparse-checkout-cone-mode: false\n      - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0\n      - run: npx --package=renovate@latest -- renovate-config-validator\n"
  },
  {
    "path": ".github/workflows/scorecard.yml",
    "content": "name: Scorecard supply-chain security\non:\n  # For Branch-Protection check. Only the default branch is supported. See\n  # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection\n  branch_protection_rule:\n  # To guarantee Maintained check is occasionally updated. See\n  # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained\n  schedule:\n    - cron: '17 4 * * 5'\n  push:\n    branches: [ \"main\" ]\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}\n\n# Declare default permissions as read only.\npermissions:\n  actions: read\n  checks: read\n  contents: read\n  issues: read\n  pull-requests: read\n  statuses: read\n\njobs:\n  analysis:\n    name: Scorecard analysis\n    runs-on: ubuntu-latest\n    permissions:\n      # Needed to upload the results to code-scanning dashboard.\n      security-events: write\n      # Needed to publish results and get a badge (see publish_results below).\n      id-token: write\n      # Uncomment the permissions below if installing in a private repository.\n      # contents: read\n      # actions: read\n\n    steps:\n      - name: \"Checkout code\"\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          persist-credentials: false\n\n      - name: \"Run analysis\"\n        uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3\n        with:\n          results_file: results.sarif\n          results_format: sarif\n          # (Optional) \"write\" PAT token. Uncomment the `repo_token` line below if:\n          # - you want to enable the Branch-Protection check on a *public* repository, or\n          # - you are installing Scorecard on a *private* repository\n          # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.\n          # repo_token: ${{ secrets.SCORECARD_TOKEN }}\n\n          # Public repositories:\n          #   - Publish results to OpenSSF REST API for easy access by consumers\n          #   - Allows the repository to include the Scorecard badge.\n          #   - See https://github.com/ossf/scorecard-action#publishing-results.\n          # For private repositories:\n          #   - `publish_results` will always be set to `false`, regardless\n          #     of the value entered here.\n          publish_results: true\n\n      # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF\n      # format to the repository Actions tab.\n      - name: \"Upload artifact\"\n        uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0\n        with:\n          name: SARIF file\n          path: results.sarif\n          retention-days: 5\n\n      # Upload the results to GitHub's code scanning dashboard.\n      - name: \"Upload to code-scanning\"\n        uses: github/codeql-action/upload-sarif@c793b717bc78562f491db7b0e93a3a178b099162 # v4.32.5\n        with:\n          sarif_file: results.sarif\n"
  },
  {
    "path": ".github/workflows/stale.yml",
    "content": "# configuration for https://github.com/actions/stale\nname: \"Stale issues and PRs\"\non:\n  schedule:\n  - cron: \"0 */2 * * *\"\n  workflow_dispatch:\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}\n\npermissions:\n  contents: read  # for actions/checkout to fetch code\n\njobs:\n  stale:\n    permissions:\n      issues: write  # for actions/stale to close stale issues\n      pull-requests: write  # for actions/stale to close stale PRs\n    runs-on: ubuntu-latest\n    steps:\n      # pinned to main commit to make use of https://github.com/actions/stale/pull/1033\n    - uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0\n      with:\n        ascending: true\n        days-before-stale: 90\n        days-before-close: 30\n        stale-issue-message: >-\n          Thank you for taking the time to raise this issue. However, it has not had\n          any activity on it in the past 90 days and will be closed in 30 days if\n          no updates occur.\n\n          Please check if the main branch has already resolved the issue since it\n          was raised. If you believe the issue is still valid and you would like input\n          from the maintainers then please comment to ask for it to be reviewed.\n        stale-pr-message: >-\n          Thank you for your contribution! However, this pull request has not had\n          any activity in the past 90 days and will be closed in 30 days if no updates\n          occur.\n\n          If you believe the changes are still valid then please verify your branch\n          has no conflicts with main and rebase if needed. If you are awaiting a (re-)review\n          then please let us know.\n        stale-issue-label: \"stale\"\n        exempt-issue-labels: \"stale/exempt,pinned\"\n        stale-pr-label: \"stale\"\n        exempt-pr-labels: \"stale/exempt,pinned\"\n"
  },
  {
    "path": ".gitignore",
    "content": "# Compiled Object files, Static and Dynamic libs (Shared Objects)\n*.o\n*.a\n*.so\n*.test\n\n# Folders\n_obj\n_test\n.vagrant\n\n# Architecture specific extensions/prefixes\n*.[568vq]\n[568vq].out\n\n*.cgo1.go\n*.cgo2.c\n_cgo_defun.c\n_cgo_gotypes.go\n_cgo_export.*\n\n_testmain.go\n\n*.exe\n\n/bin\n/coverage.txt\n/profile.out\n/output.json\n\n.idea\n"
  },
  {
    "path": ".golangci.yml",
    "content": "# yaml-language-server: $schema=https://golangci-lint.run/jsonschema/golangci.jsonschema.json\nversion: \"2\"\nlinters:\n  default: none\n  enable:\n  - bodyclose\n  - copyloopvar\n  - depguard\n  - dogsled\n  - errcheck\n  - errorlint\n  - funlen\n  - gochecknoinits\n  - gocritic\n  - gocyclo\n  - gosec\n  - govet\n  - misspell\n  - nilerr\n  - unconvert\n  - unused\n  - whitespace\n  settings:\n    depguard:\n      rules:\n        main:\n          deny:\n          - pkg: io/ioutil\n            desc: Use the \"io\" and \"os\" packages instead.\n    dupl:\n      threshold: 100\n    funlen:\n      lines: 300\n      statements: 300\n    goconst:\n      min-len: 2\n      min-occurrences: 3\n    gocritic:\n      enabled-checks:\n      - importShadow\n      - nestingReduce\n      - stringsCompare\n      # - unnamedResult\n      # - whyNoLint\n      disabled-checks:\n      - assignOp\n      - appendAssign\n      - commentedOutCode\n      - hugeParam\n      - ifElseChain\n      - singleCaseSwitch\n      - sloppyReassign\n      enabled-tags:\n      - diagnostic\n      - performance\n      # - experimental\n      # - opinionated\n      # - style\n    gocyclo:\n      min-complexity: 99\n    govet:\n      disable:\n      - fieldalignment\n      - shadow\n      enable-all: true\n    misspell:\n      locale: US\n  # exclude some linters from running on certains files.\n  exclusions:\n    generated: lax\n    presets:\n    - comments\n    - common-false-positives\n    - legacy\n    - std-error-handling\n    rules:\n    - linters:\n      - paralleltest\n      path: functional.*_test\\.go\n    - path: (.+)\\.go$\n      text: 'G115: integer overflow conversion'\n    - path: (.+)\\.go$\n      text: 'G404: Use of weak random number generator'\n    paths:\n    - third_party$\n    - builtin$\n    - examples$\nissues:\n  # maximum count of issues with the same text. set to 0 for unlimited. default is 3.\n  max-same-issues: 0\nformatters:\n  enable:\n  - gofmt\n  - goimports\n  settings:\n    goimports:\n      local-prefixes:\n      - github.com/IBM/sarama\n  exclusions:\n    generated: lax\n    paths:\n    - third_party$\n    - builtin$\n    - examples$\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "fail_fast: false\ndefault_install_hook_types: [pre-commit, commit-msg]\nrepos:\n  - repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: v6.0.0\n    hooks:\n      - id: check-merge-conflict\n      - id: check-yaml\n      - id: end-of-file-fixer\n      - id: fix-byte-order-marker\n      - id: mixed-line-ending\n      - id: trailing-whitespace\n  - repo: local\n    hooks:\n      - id: conventional-commit-msg-validation\n        name: commit message conventional validation\n        language: pygrep\n        entry: '^(?:fixup! )?(breaking|build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test){1}(\\([\\w\\-\\.]+\\))?(!)?: ([\\w `])+([\\s\\S]*)'\n        args: [--multiline, --negate]\n        stages: [commit-msg]\n      - id: commit-msg-needs-to-be-signed-off\n        name: commit message needs to be signed off\n        language: pygrep\n        entry: \"^Signed-off-by:\"\n        args: [--multiline, --negate]\n        stages: [commit-msg]\n      - id: gofmt\n        name: gofmt\n        description: Format files with gofmt.\n        entry: gofmt -l\n        language: golang\n        files: \\.go$\n        args: []\n  - repo: https://github.com/gitleaks/gitleaks\n    rev: v8.28.0\n    hooks:\n      - id: gitleaks\n  - repo: https://github.com/golangci/golangci-lint\n    rev: v2.5.0\n    hooks:\n      - id: golangci-lint\n"
  },
  {
    "path": ".whitesource",
    "content": "{\n  \"settingsInheritedFrom\": \"ibm-mend-config/mend-config@main\"\n}"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## Version 1.42.2 (2024-02-09)\n\n## What's Changed\n\n⚠️ The go.mod directive has been bumped to 1.18 as the minimum version of Go required for the module. This was necessary to continue to receive updates from some of the third party dependencies that Sarama makes use of for compression.\n\n### :tada: New Features / Improvements\n* feat: update go directive to 1.18 by @dnwe in https://github.com/IBM/sarama/pull/2713\n* feat: return KError instead of errors in AlterConfigs and DescribeConfig by @zhuliquan in https://github.com/IBM/sarama/pull/2472\n### :bug: Fixes\n* fix: don't waste time for backoff on member id required error by @lzakharov in https://github.com/IBM/sarama/pull/2759\n* fix: prevent ConsumerGroup.Close infinitely locking by @maqdev in https://github.com/IBM/sarama/pull/2717\n### :package: Dependency updates\n* chore(deps): bump golang.org/x/net from 0.17.0 to 0.18.0 by @dependabot in https://github.com/IBM/sarama/pull/2716\n* chore(deps): bump golang.org/x/sync to v0.5.0 by @dependabot in https://github.com/IBM/sarama/pull/2718\n* chore(deps): bump github.com/pierrec/lz4/v4 from 4.1.18 to 4.1.19 by @dependabot in https://github.com/IBM/sarama/pull/2739\n* chore(deps): bump golang.org/x/crypto from 0.15.0 to 0.17.0 by @dependabot in https://github.com/IBM/sarama/pull/2748\n* chore(deps): bump the golang-org-x group with 1 update by @dependabot in https://github.com/IBM/sarama/pull/2734\n* chore(deps): bump the golang-org-x group with 2 updates by @dependabot in https://github.com/IBM/sarama/pull/2764\n* chore(deps): bump github.com/pierrec/lz4/v4 from 4.1.19 to 4.1.21 by @dependabot in https://github.com/IBM/sarama/pull/2763\n* chore(deps): bump golang.org/x/crypto from 0.15.0 to 0.17.0 in /examples/exactly_once by @dependabot in https://github.com/IBM/sarama/pull/2749\n* chore(deps): bump golang.org/x/crypto from 0.15.0 to 0.17.0 in /examples/consumergroup by @dependabot in https://github.com/IBM/sarama/pull/2750\n* chore(deps): bump golang.org/x/crypto from 0.15.0 to 0.17.0 in /examples/sasl_scram_client by @dependabot in https://github.com/IBM/sarama/pull/2751\n* chore(deps): bump golang.org/x/crypto from 0.15.0 to 0.17.0 in /examples/interceptors by @dependabot in https://github.com/IBM/sarama/pull/2752\n* chore(deps): bump golang.org/x/crypto from 0.15.0 to 0.17.0 in /examples/http_server by @dependabot in https://github.com/IBM/sarama/pull/2753\n* chore(deps): bump github.com/eapache/go-resiliency from 1.4.0 to 1.5.0 by @dependabot in https://github.com/IBM/sarama/pull/2745\n* chore(deps): bump golang.org/x/crypto from 0.15.0 to 0.17.0 in /examples/txn_producer by @dependabot in https://github.com/IBM/sarama/pull/2754\n* chore(deps): bump go.opentelemetry.io/otel/sdk from 1.19.0 to 1.22.0 in /examples/interceptors by @dependabot in https://github.com/IBM/sarama/pull/2767\n* chore(deps): bump the golang-org-x group with 1 update by @dependabot in https://github.com/IBM/sarama/pull/2793\n* chore(deps): bump go.opentelemetry.io/otel/exporters/stdout/stdoutmetric from 0.42.0 to 1.23.1 in /examples/interceptors by @dependabot in https://github.com/IBM/sarama/pull/2792\n### :wrench: Maintenance\n* fix(examples): housekeeping of code and deps by @dnwe in https://github.com/IBM/sarama/pull/2720\n### :heavy_plus_sign: Other Changes\n* fix(test): retry MockBroker Listen for EADDRINUSE by @dnwe in https://github.com/IBM/sarama/pull/2721\n\n## New Contributors\n* @maqdev made their first contribution in https://github.com/IBM/sarama/pull/2717\n* @zhuliquan made their first contribution in https://github.com/IBM/sarama/pull/2472\n\n**Full Changelog**: https://github.com/IBM/sarama/compare/v1.42.1...v1.42.2\n\n## Version 1.42.1 (2023-11-07)\n\n## What's Changed\n### :bug: Fixes\n* fix: make fetchInitialOffset use correct protocol by @dnwe in https://github.com/IBM/sarama/pull/2705\n* fix(config): relax ClientID validation after 1.0.0 by @dnwe in https://github.com/IBM/sarama/pull/2706\n\n**Full Changelog**: https://github.com/IBM/sarama/compare/v1.42.0...v1.42.1\n\n## Version 1.42.0 (2023-11-02)\n\n## What's Changed\n### :bug: Fixes\n* Asynchronously close brokers during a RefreshBrokers by @bmassemin in https://github.com/IBM/sarama/pull/2693\n* Fix data race on Broker.done channel by @prestona in https://github.com/IBM/sarama/pull/2698\n* fix: data race in Broker.AsyncProduce by @lzakharov in https://github.com/IBM/sarama/pull/2678\n* Fix default retention time value in offset commit by @prestona in https://github.com/IBM/sarama/pull/2700\n* fix(txmgr): ErrOffsetsLoadInProgress is retriable by @dnwe in https://github.com/IBM/sarama/pull/2701\n### :wrench: Maintenance\n* chore(ci): improve ossf scorecard result by @dnwe in https://github.com/IBM/sarama/pull/2685\n* chore(ci): add kafka 3.6.0 to FVT and versions by @dnwe in https://github.com/IBM/sarama/pull/2692\n### :heavy_plus_sign: Other Changes\n* chore(ci): ossf scorecard.yml by @dnwe in https://github.com/IBM/sarama/pull/2683\n* fix(ci): always run CodeQL on every commit by @dnwe in https://github.com/IBM/sarama/pull/2689\n* chore(doc): add OpenSSF Scorecard badge by @dnwe in https://github.com/IBM/sarama/pull/2691\n\n## New Contributors\n* @bmassemin made their first contribution in https://github.com/IBM/sarama/pull/2693\n* @lzakharov made their first contribution in https://github.com/IBM/sarama/pull/2678\n\n**Full Changelog**: https://github.com/IBM/sarama/compare/v1.41.3...v1.42.0\n\n## Version 1.41.3 (2023-10-17)\n\n## What's Changed\n### :bug: Fixes\n* fix: pre-compile regex for parsing kafka version by @qshuai in https://github.com/IBM/sarama/pull/2663\n* fix(client): ignore empty Metadata responses when refreshing by @HaoSunUber in https://github.com/IBM/sarama/pull/2672\n### :package: Dependency updates\n* chore(deps): bump the golang-org-x group with 2 updates by @dependabot in https://github.com/IBM/sarama/pull/2661\n* chore(deps): bump golang.org/x/net from 0.16.0 to 0.17.0 by @dependabot in https://github.com/IBM/sarama/pull/2671\n### :memo: Documentation\n* fix(docs): correct topic name in rebalancing strategy example by @maksadbek in https://github.com/IBM/sarama/pull/2657\n\n## New Contributors\n* @maksadbek made their first contribution in https://github.com/IBM/sarama/pull/2657\n* @qshuai made their first contribution in https://github.com/IBM/sarama/pull/2663\n\n**Full Changelog**: https://github.com/IBM/sarama/compare/v1.41.2...v1.41.3\n\n## Version 1.41.2 (2023-09-12)\n\n## What's Changed\n### :tada: New Features / Improvements\n* perf: Alloc records in batch by @ronanh in https://github.com/IBM/sarama/pull/2646\n### :bug: Fixes\n* fix(consumer): guard against nil client by @dnwe in https://github.com/IBM/sarama/pull/2636\n* fix(consumer): don't retry session if ctx canceled by @dnwe in https://github.com/IBM/sarama/pull/2642\n* fix: use least loaded broker to refresh metadata by @HaoSunUber in https://github.com/IBM/sarama/pull/2645\n### :package: Dependency updates\n* chore(deps): bump the golang-org-x group with 1 update by @dependabot in https://github.com/IBM/sarama/pull/2641\n\n## New Contributors\n* @HaoSunUber made their first contribution in https://github.com/IBM/sarama/pull/2645\n\n**Full Changelog**: https://github.com/IBM/sarama/compare/v1.41.1...v1.41.2\n\n## Version 1.41.1 (2023-08-30)\n\n## What's Changed\n### :bug: Fixes\n* fix(proto): handle V3 member metadata and empty owned partitions by @dnwe in https://github.com/IBM/sarama/pull/2618\n* fix: make clear that error is configuration issue not server error by @hindessm in https://github.com/IBM/sarama/pull/2628\n* fix(client): force Event Hubs to use V1_0_0_0 by @dnwe in https://github.com/IBM/sarama/pull/2633\n* fix: add retries to alter user scram creds by @hindessm in https://github.com/IBM/sarama/pull/2632\n### :wrench: Maintenance\n* chore(lint): bump golangci-lint and tweak config by @dnwe in https://github.com/IBM/sarama/pull/2620\n### :memo: Documentation\n* fix(doc): add missing doc for mock consumer by @hsweif in https://github.com/IBM/sarama/pull/2386\n* chore(proto): doc CreateTopics/JoinGroup fields by @dnwe in https://github.com/IBM/sarama/pull/2627\n### :heavy_plus_sign: Other Changes\n* chore(gh): add new style issue templates by @dnwe in https://github.com/IBM/sarama/pull/2624\n\n\n**Full Changelog**: https://github.com/IBM/sarama/compare/v1.41.0...v1.41.1\n\n## Version 1.41.0 (2023-08-21)\n\n## What's Changed\n### :rotating_light: Breaking Changes\n\nNote: this version of Sarama has had a big overhaul in its adherence to the use of the right Kafka protocol versions for the given Config Version. It has also bumped the default Version set in Config (where one is not supplied) to 2.1.0. This is in preparation for Kafka 4.0 dropping support for protocol versions older than 2.1. If you are using Sarama against Kafka clusters older than v2.1.0, or using it against Azure EventHubs then you will likely have to change your application code to pin to the appropriate Version.\n\n* chore(config): make DefaultVersion V2_0_0_0 by @dnwe in https://github.com/IBM/sarama/pull/2572\n* chore(config): make DefaultVersion V2_1_0_0 by @dnwe in https://github.com/IBM/sarama/pull/2574\n### :tada: New Features / Improvements\n* Implement resolve_canonical_bootstrap_servers_only by @gebn in https://github.com/IBM/sarama/pull/2156\n* feat: sleep when throttled (KIP-219) by @hindessm in https://github.com/IBM/sarama/pull/2536\n* feat: add isValidVersion to protocol types by @dnwe in https://github.com/IBM/sarama/pull/2538\n* fix(consumer): use newer LeaveGroup as appropriate by @dnwe in https://github.com/IBM/sarama/pull/2544\n* Add support for up to version 4 List Groups API by @prestona in https://github.com/IBM/sarama/pull/2541\n* fix(producer): use newer ProduceReq as appropriate by @dnwe in https://github.com/IBM/sarama/pull/2546\n* fix(proto): ensure req+resp requiredVersion match by @dnwe in https://github.com/IBM/sarama/pull/2548\n* chore(proto): permit CreatePartitionsRequest V1 by @dnwe in https://github.com/IBM/sarama/pull/2549\n* chore(proto): permit AlterConfigsRequest V1 by @dnwe in https://github.com/IBM/sarama/pull/2550\n* chore(proto): permit DeleteGroupsRequest V1 by @dnwe in https://github.com/IBM/sarama/pull/2551\n* fix(proto): correct JoinGroup usage for wider version range by @dnwe in https://github.com/IBM/sarama/pull/2553\n* fix(consumer): use full range of FetchRequest vers by @dnwe in https://github.com/IBM/sarama/pull/2554\n* fix(proto): use range of OffsetCommitRequest vers by @dnwe in https://github.com/IBM/sarama/pull/2555\n* fix(proto): use full range of MetadataRequest by @dnwe in https://github.com/IBM/sarama/pull/2556\n* fix(proto): use fuller ranges of supported proto by @dnwe in https://github.com/IBM/sarama/pull/2558\n* fix(proto): use full range of SyncGroupRequest by @dnwe in https://github.com/IBM/sarama/pull/2565\n* fix(proto): use full range of ListGroupsRequest by @dnwe in https://github.com/IBM/sarama/pull/2568\n* feat(proto): support for Metadata V6-V10 by @dnwe in https://github.com/IBM/sarama/pull/2566\n* fix(proto): use full ranges for remaining proto by @dnwe in https://github.com/IBM/sarama/pull/2570\n* feat(proto): add remaining protocol for V2.1 by @dnwe in https://github.com/IBM/sarama/pull/2573\n* feat: add new error for MockDeleteTopicsResponse by @javiercri in https://github.com/IBM/sarama/pull/2475\n* feat(gzip): switch to klauspost/compress gzip by @dnwe in https://github.com/IBM/sarama/pull/2600\n### :bug: Fixes\n* fix: correct unsupported version check by @hindessm in https://github.com/IBM/sarama/pull/2528\n* fix: avoiding burning cpu if all partitions are paused by @napallday in https://github.com/IBM/sarama/pull/2532\n* extend throttling metric scope by @hindessm in https://github.com/IBM/sarama/pull/2533\n* Fix printing of final metrics by @prestona in https://github.com/IBM/sarama/pull/2545\n* fix(consumer): cannot automatically fetch newly-added partitions unless restart by @napallday in https://github.com/IBM/sarama/pull/2563\n* bug: implement unsigned modulus for partitioning with crc32 hashing by @csm8118 in https://github.com/IBM/sarama/pull/2560\n* fix: avoid logging value of proxy.Dialer by @prestona in https://github.com/IBM/sarama/pull/2569\n* fix(test): add missing closes to admin client tests by @dnwe in https://github.com/IBM/sarama/pull/2594\n* fix(test): ensure some more clients are closed by @dnwe in https://github.com/IBM/sarama/pull/2595\n* fix(examples): sync exactly_once and consumergroup by @dnwe in https://github.com/IBM/sarama/pull/2614\n* fix(fvt): fresh metrics registry for each test by @dnwe in https://github.com/IBM/sarama/pull/2616\n* fix(test): flaky test TestFuncOffsetManager by @napallday in https://github.com/IBM/sarama/pull/2609\n### :package: Dependency updates\n* chore(deps): bump the golang-org-x group with 1 update by @dependabot in https://github.com/IBM/sarama/pull/2542\n* chore(deps): bump the golang-org-x group with 1 update by @dependabot in https://github.com/IBM/sarama/pull/2561\n* chore(deps): bump module github.com/pierrec/lz4/v4 to v4.1.18 by @dnwe in https://github.com/IBM/sarama/pull/2589\n* chore(deps): bump module github.com/jcmturner/gokrb5/v8 to v8.4.4 by @dnwe in https://github.com/IBM/sarama/pull/2587\n* chore(deps): bump github.com/eapache/go-xerial-snappy digest to c322873 by @dnwe in https://github.com/IBM/sarama/pull/2586\n* chore(deps): bump module github.com/klauspost/compress to v1.16.7 by @dnwe in https://github.com/IBM/sarama/pull/2588\n* chore(deps): bump github.com/eapache/go-resiliency from 1.3.0 to 1.4.0 by @dependabot in https://github.com/IBM/sarama/pull/2598\n### :wrench: Maintenance\n* fix(fvt): ensure fully-replicated at test start by @hindessm in https://github.com/IBM/sarama/pull/2531\n* chore: rollup fvt kafka to latest three by @dnwe in https://github.com/IBM/sarama/pull/2537\n* Merge the two CONTRIBUTING.md's by @prestona in https://github.com/IBM/sarama/pull/2543\n* fix(test): test timing error by @hindessm in https://github.com/IBM/sarama/pull/2552\n* chore(ci): tidyup and improve actions workflows by @dnwe in https://github.com/IBM/sarama/pull/2557\n* fix(test): shutdown MockBroker by @dnwe in https://github.com/IBM/sarama/pull/2571\n* chore(proto): match HeartbeatResponse version by @dnwe in https://github.com/IBM/sarama/pull/2576\n* chore(test): ensure MockBroker closed within test by @dnwe in https://github.com/IBM/sarama/pull/2575\n* chore(test): ensure all mockresponses use version by @dnwe in https://github.com/IBM/sarama/pull/2578\n* chore(ci): use latest Go in actions by @dnwe in https://github.com/IBM/sarama/pull/2580\n* chore(test): speedup some slow tests by @dnwe in https://github.com/IBM/sarama/pull/2579\n* chore(test): use modern protocol versions in FVT by @dnwe in https://github.com/IBM/sarama/pull/2581\n* chore(test): fix a couple of leaks by @dnwe in https://github.com/IBM/sarama/pull/2591\n* feat(fvt): experiment with per-kafka-version image by @dnwe in https://github.com/IBM/sarama/pull/2592\n* chore(ci): replace toxiproxy client dep by @dnwe in https://github.com/IBM/sarama/pull/2593\n* feat(fvt): add healthcheck, depends_on and --wait by @dnwe in https://github.com/IBM/sarama/pull/2601\n* fix(fvt): handle msgset vs batchset by @dnwe in https://github.com/IBM/sarama/pull/2603\n* fix(fvt): Metadata version in ensureFullyReplicated by @dnwe in https://github.com/IBM/sarama/pull/2612\n* fix(fvt): versioned cfg for invalid topic producer by @dnwe in https://github.com/IBM/sarama/pull/2613\n* chore(fvt): tweak to work across more versions by @dnwe in https://github.com/IBM/sarama/pull/2615\n* feat(fvt): test wider range of kafkas by @dnwe in https://github.com/IBM/sarama/pull/2605\n### :memo: Documentation\n* fix(example): check if msg channel is closed by @ioanzicu in https://github.com/IBM/sarama/pull/2479\n* chore: use go install for installing sarama tools by @vigith in https://github.com/IBM/sarama/pull/2599\n\n## New Contributors\n* @gebn made their first contribution in https://github.com/IBM/sarama/pull/2156\n* @prestona made their first contribution in https://github.com/IBM/sarama/pull/2543\n* @ioanzicu made their first contribution in https://github.com/IBM/sarama/pull/2479\n* @csm8118 made their first contribution in https://github.com/IBM/sarama/pull/2560\n* @javiercri made their first contribution in https://github.com/IBM/sarama/pull/2475\n* @vigith made their first contribution in https://github.com/IBM/sarama/pull/2599\n\n**Full Changelog**: https://github.com/IBM/sarama/compare/v1.40.1...v1.41.0\n\n## Version 1.40.1 (2023-07-27)\n\n## What's Changed\n### :tada: New Features / Improvements\n* Use buffer pools for decompression by @ronanh in https://github.com/IBM/sarama/pull/2484\n* feat: support for Kerberos authentication with a credentials cache. by @mrogaski in https://github.com/IBM/sarama/pull/2457\n### :bug: Fixes\n* Fix some retry issues by @hindessm in https://github.com/IBM/sarama/pull/2517\n* fix: admin retry logic by @hindessm in https://github.com/IBM/sarama/pull/2519\n* Add some retry logic to more admin client functions by @hindessm in https://github.com/IBM/sarama/pull/2520\n* fix: concurrent issue on updateMetadataMs by @napallday in https://github.com/IBM/sarama/pull/2522\n* fix(test): allow testing of skipped test without IsTransactional panic by @hindessm in https://github.com/IBM/sarama/pull/2525\n### :package: Dependency updates\n* chore(deps): bump the golang-org-x group with 2 updates by @dependabot in https://github.com/IBM/sarama/pull/2509\n* chore(deps): bump github.com/klauspost/compress from 1.15.14 to 1.16.6 by @dependabot in https://github.com/IBM/sarama/pull/2513\n* chore(deps): bump github.com/stretchr/testify from 1.8.1 to 1.8.3 by @dependabot in https://github.com/IBM/sarama/pull/2512\n### :wrench: Maintenance\n* chore(ci): migrate probot-stale to actions/stale by @dnwe in https://github.com/IBM/sarama/pull/2496\n* chore(ci): bump golangci version, cleanup, depguard config by @EladLeev in https://github.com/IBM/sarama/pull/2504\n* Clean up some typos and docs/help mistakes by @hindessm in https://github.com/IBM/sarama/pull/2514\n### :heavy_plus_sign: Other Changes\n* chore(ci): add simple apidiff workflow by @dnwe in https://github.com/IBM/sarama/pull/2497\n* chore(ci): bump actions/setup-go from 3 to 4 by @dependabot in https://github.com/IBM/sarama/pull/2508\n* fix(comments): PauseAll and ResumeAll by @napallday in https://github.com/IBM/sarama/pull/2523\n\n## New Contributors\n* @EladLeev made their first contribution in https://github.com/IBM/sarama/pull/2504\n* @hindessm made their first contribution in https://github.com/IBM/sarama/pull/2514\n* @ronanh made their first contribution in https://github.com/IBM/sarama/pull/2484\n* @mrogaski made their first contribution in https://github.com/IBM/sarama/pull/2457\n\n**Full Changelog**: https://github.com/IBM/sarama/compare/v1.40.0...v1.40.1\n\n## Version 1.40.0 (2023-07-17)\n\n## What's Changed\n\nNote: this is the first release after the transition of Sarama ownership from Shopify to IBM in https://github.com/IBM/sarama/issues/2461\n\n### :rotating_light: Breaking Changes\n\n- chore: migrate module to github.com/IBM/sarama by @dnwe in https://github.com/IBM/sarama/pull/2492\n- fix: restore (\\*OffsetCommitRequest) AddBlock func by @dnwe in https://github.com/IBM/sarama/pull/2494\n\n### :bug: Fixes\n\n- fix(consumer): don't retry FindCoordinator forever by @dnwe in https://github.com/IBM/sarama/pull/2427\n- fix(metrics): fix race condition when calling Broker.Open() twice by @vincentbernat in https://github.com/IBM/sarama/pull/2428\n- fix: use version 4 of DescribeGroupsRequest only if kafka broker vers… …ion is >= 2.4 by @faillefer in https://github.com/IBM/sarama/pull/2451\n- Fix HighWaterMarkOffset of mocks partition consumer by @gr8web in https://github.com/IBM/sarama/pull/2447\n- fix: prevent data race in balance strategy by @napallday in https://github.com/IBM/sarama/pull/2453\n\n### :package: Dependency updates\n\n- chore(deps): bump golang.org/x/net from 0.5.0 to 0.7.0 by @dependabot in https://github.com/IBM/sarama/pull/2452\n\n### :wrench: Maintenance\n\n- chore: add kafka 3.3.2 by @dnwe in https://github.com/IBM/sarama/pull/2434\n- chore(ci): remove Shopify/shopify-cla-action by @dnwe in https://github.com/IBM/sarama/pull/2489\n- chore: bytes.Equal instead bytes.Compare by @testwill in https://github.com/IBM/sarama/pull/2485\n\n## New Contributors\n\n- @dependabot made their first contribution in https://github.com/IBM/sarama/pull/2452\n- @gr8web made their first contribution in https://github.com/IBM/sarama/pull/2447\n- @testwill made their first contribution in https://github.com/IBM/sarama/pull/2485\n\n**Full Changelog**: https://github.com/IBM/sarama/compare/v1.38.1...v1.40.0\n\n## Version 1.38.1 (2023-01-22)\n\n## What's Changed\n### :bug: Fixes\n* fix(example): correct `records-number` param in txn producer readme by @diallo-han in https://github.com/IBM/sarama/pull/2420\n* fix: use newConsumer method in newConsumerGroup method by @Lumotheninja in https://github.com/IBM/sarama/pull/2424\n### :package: Dependency updates\n* chore(deps): bump module github.com/klauspost/compress to v1.15.14 by @dnwe in https://github.com/IBM/sarama/pull/2410\n* chore(deps): bump module golang.org/x/net to v0.5.0 by @dnwe in https://github.com/IBM/sarama/pull/2413\n* chore(deps): bump module github.com/stretchr/testify to v1.8.1 by @dnwe in https://github.com/IBM/sarama/pull/2411\n* chore(deps): bump module github.com/xdg-go/scram to v1.1.2 by @dnwe in https://github.com/IBM/sarama/pull/2412\n* chore(deps): bump module golang.org/x/sync to v0.1.0 by @dnwe in https://github.com/IBM/sarama/pull/2414\n* chore(deps): bump github.com/eapache/go-xerial-snappy digest to bf00bc1 by @dnwe in https://github.com/IBM/sarama/pull/2418\n\n## New Contributors\n* @diallo-han made their first contribution in https://github.com/IBM/sarama/pull/2420\n* @Lumotheninja made their first contribution in https://github.com/IBM/sarama/pull/2424\n\n**Full Changelog**: https://github.com/IBM/sarama/compare/v1.38.0...v1.38.1\n\n## Version 1.38.0 (2023-01-08)\n\n## What's Changed\n### :tada: New Features / Improvements\n* feat(producer): improve memory usage of zstd encoder by using our own pool management by @rtreffer in https://github.com/IBM/sarama/pull/2375\n* feat(proto): implement and use MetadataRequest v7 by @dnwe in https://github.com/IBM/sarama/pull/2388\n* feat(metrics): add protocol-requests-rate metric by @auntan in https://github.com/IBM/sarama/pull/2373\n### :bug: Fixes\n* fix(proto): track and supply leader epoch to FetchRequest by @dnwe in https://github.com/IBM/sarama/pull/2389\n* fix(example): improve arg name used for tls skip verify by @michaeljmarshall in https://github.com/IBM/sarama/pull/2385\n* fix(zstd): default back to GOMAXPROCS concurrency by @bgreenlee in https://github.com/IBM/sarama/pull/2404\n* fix(producer): add nil check while producer is retrying by @hsweif in https://github.com/IBM/sarama/pull/2387\n* fix(producer): return errors for every message in retryBatch to avoid producer hang forever by @cch123 in https://github.com/IBM/sarama/pull/2378\n* fix(metrics): fix race when accessing metric registry by @vincentbernat in https://github.com/IBM/sarama/pull/2409\n### :package: Dependency updates\n* chore(deps): bump golang.org/x/net to v0.4.0 by @dnwe in https://github.com/IBM/sarama/pull/2403\n### :wrench: Maintenance\n* chore(ci): replace set-output command in GH Action by @dnwe in https://github.com/IBM/sarama/pull/2390\n* chore(ci): include kafka 3.3.1 in testing matrix by @dnwe in https://github.com/IBM/sarama/pull/2406\n\n## New Contributors\n* @michaeljmarshall made their first contribution in https://github.com/IBM/sarama/pull/2385\n* @bgreenlee made their first contribution in https://github.com/IBM/sarama/pull/2404\n* @hsweif made their first contribution in https://github.com/IBM/sarama/pull/2387\n* @cch123 made their first contribution in https://github.com/IBM/sarama/pull/2378\n\n**Full Changelog**: https://github.com/IBM/sarama/compare/v1.37.2...v1.38.0\n\n## Version 1.37.2 (2022-10-04)\n\n## What's Changed\n### :bug: Fixes\n* fix: ensure updateMetaDataMs is 64-bit aligned by @dnwe in https://github.com/IBM/sarama/pull/2356\n### :heavy_plus_sign: Other Changes\n* fix: bump go.mod specification to go 1.17 by @dnwe in https://github.com/IBM/sarama/pull/2357\n\n\n**Full Changelog**: https://github.com/IBM/sarama/compare/v1.37.1...v1.37.2\n\n## Version 1.37.1 (2022-10-04)\n\n## What's Changed\n### :bug: Fixes\n* fix: support existing deprecated Rebalance.Strategy field usage by @spongecaptain in https://github.com/IBM/sarama/pull/2352\n* fix(test): consumer group rebalance strategy compatibility by @Jacob-bzx in https://github.com/IBM/sarama/pull/2353\n* fix(producer): replace time.After with time.Timer to avoid high memory usage by @Jacob-bzx in https://github.com/IBM/sarama/pull/2355\n\n## New Contributors\n* @spongecaptain made their first contribution in https://github.com/IBM/sarama/pull/2352\n\n**Full Changelog**: https://github.com/IBM/sarama/compare/v1.37.0...v1.37.1\n\n## Version 1.37.0 (2022-09-28)\n\n## What's Changed\n\n### :rotating_light: Breaking Changes\n* Due to a change in [github.com/klauspost/compress v1.15.10](https://github.com/klauspost/compress/releases/tag/v1.15.10), Sarama v1.37.0 requires Go 1.17 going forward, unfortunately due to an oversight this wasn't reflected in the go.mod declaration at time of release.\n\n### :tada: New Features / Improvements\n* feat(consumer): support multiple balance strategies by @Jacob-bzx in https://github.com/IBM/sarama/pull/2339\n* feat(producer): transactional API by @ryarnyah in https://github.com/IBM/sarama/pull/2295\n* feat(mocks): support key in MockFetchResponse. by @Skandalik in https://github.com/IBM/sarama/pull/2328\n### :bug: Fixes\n* fix: avoid panic when Metadata.RefreshFrequency is 0 by @Jacob-bzx in https://github.com/IBM/sarama/pull/2329\n* fix(consumer): avoid pushing unrelated responses to paused children by @pkoutsovasilis in https://github.com/IBM/sarama/pull/2317\n* fix: prevent metrics leak with cleanup by @auntan in https://github.com/IBM/sarama/pull/2340\n* fix: race condition(may panic) when closing consumer group by @Jacob-bzx in https://github.com/IBM/sarama/pull/2331\n* fix(consumer): default ResetInvalidOffsets to true by @dnwe in https://github.com/IBM/sarama/pull/2345\n* Validate the `Config` when creating a mock producer/consumer by @joewreschnig in https://github.com/IBM/sarama/pull/2327\n### :package: Dependency updates\n* chore(deps): bump module github.com/pierrec/lz4/v4 to v4.1.16 by @dnwe in https://github.com/IBM/sarama/pull/2335\n* chore(deps): bump golang.org/x/net digest to bea034e by @dnwe in https://github.com/IBM/sarama/pull/2333\n* chore(deps): bump golang.org/x/sync digest to 7f9b162 by @dnwe in https://github.com/IBM/sarama/pull/2334\n* chore(deps): bump golang.org/x/net digest to f486391 by @dnwe in https://github.com/IBM/sarama/pull/2348\n* chore(deps): bump module github.com/shopify/toxiproxy/v2 to v2.5.0 by @dnwe in https://github.com/IBM/sarama/pull/2336\n* chore(deps): bump module github.com/klauspost/compress to v1.15.11 by @dnwe in https://github.com/IBM/sarama/pull/2349\n* chore(deps): bump module github.com/pierrec/lz4/v4 to v4.1.17 by @dnwe in https://github.com/IBM/sarama/pull/2350\n### :wrench: Maintenance\n* chore(ci): bump kafka-versions to latest by @dnwe in https://github.com/IBM/sarama/pull/2346\n* chore(ci): bump go-versions to N and N-1 by @dnwe in https://github.com/IBM/sarama/pull/2347\n\n## New Contributors\n* @Jacob-bzx made their first contribution in https://github.com/IBM/sarama/pull/2329\n* @pkoutsovasilis made their first contribution in https://github.com/IBM/sarama/pull/2317\n* @Skandalik made their first contribution in https://github.com/IBM/sarama/pull/2328\n* @auntan made their first contribution in https://github.com/IBM/sarama/pull/2340\n* @ryarnyah made their first contribution in https://github.com/IBM/sarama/pull/2295\n\n**Full Changelog**: https://github.com/IBM/sarama/compare/v1.36.0...v1.37.0\n\n## Version 1.36.0 (2022-08-11)\n\n## What's Changed\n### :tada: New Features / Improvements\n* feat: add option to propagate OffsetOutOfRange error by @dkolistratova in https://github.com/IBM/sarama/pull/2252\n* feat(producer): expose ProducerMessage.byteSize() function by @k8scat in https://github.com/IBM/sarama/pull/2315\n* feat(metrics): track consumer fetch request rates by @dnwe in https://github.com/IBM/sarama/pull/2299\n### :bug: Fixes\n* fix(consumer): avoid submitting empty fetch requests when paused by @raulnegreiros in https://github.com/IBM/sarama/pull/2143\n### :package: Dependency updates\n* chore(deps): bump module github.com/klauspost/compress to v1.15.9 by @dnwe in https://github.com/IBM/sarama/pull/2304\n* chore(deps): bump golang.org/x/net digest to c7608f3 by @dnwe in https://github.com/IBM/sarama/pull/2301\n* chore(deps): bump golangci/golangci-lint-action action to v3 by @dnwe in https://github.com/IBM/sarama/pull/2311\n* chore(deps): bump golang.org/x/net digest to 07c6da5 by @dnwe in https://github.com/IBM/sarama/pull/2307\n* chore(deps): bump github actions versions (major) by @dnwe in https://github.com/IBM/sarama/pull/2313\n* chore(deps): bump module github.com/jcmturner/gofork to v1.7.6 by @dnwe in https://github.com/IBM/sarama/pull/2305\n* chore(deps): bump golang.org/x/sync digest to 886fb93 by @dnwe in https://github.com/IBM/sarama/pull/2302\n* chore(deps): bump module github.com/jcmturner/gokrb5/v8 to v8.4.3 by @dnwe in https://github.com/IBM/sarama/pull/2303\n### :wrench: Maintenance\n* chore: add kafka 3.1.1 to the version matrix by @dnwe in https://github.com/IBM/sarama/pull/2300\n### :heavy_plus_sign: Other Changes\n* Migrate off probot-CLA to new GitHub Action by @cursedcoder in https://github.com/IBM/sarama/pull/2294\n* Forgot to remove cla probot by @cursedcoder in https://github.com/IBM/sarama/pull/2297\n* chore(lint): re-enable a small amount of go-critic by @dnwe in https://github.com/IBM/sarama/pull/2312\n\n## New Contributors\n* @cursedcoder made their first contribution in https://github.com/IBM/sarama/pull/2294\n* @dkolistratova made their first contribution in https://github.com/IBM/sarama/pull/2252\n* @k8scat made their first contribution in https://github.com/IBM/sarama/pull/2315\n\n**Full Changelog**: https://github.com/IBM/sarama/compare/v1.35.0...v1.36.0\n\n## Version 1.35.0 (2022-07-22)\n\n## What's Changed\n### :bug: Fixes\n* fix: fix metadata retry backoff invalid when get metadata failed by @Stephan14 in https://github.com/IBM/sarama/pull/2256\n* fix(balance): sort and de-deplicate memberIDs by @dnwe in https://github.com/IBM/sarama/pull/2285\n* fix: prevent DescribeLogDirs hang in admin client by @zerowidth in https://github.com/IBM/sarama/pull/2269\n* fix: include assignment-less members in SyncGroup by @dnwe in https://github.com/IBM/sarama/pull/2292\n### :package: Dependency updates\n* chore(deps): bump module github.com/stretchr/testify to v1.8.0 by @dnwe in https://github.com/IBM/sarama/pull/2284\n* chore(deps): bump module github.com/eapache/go-resiliency to v1.3.0 by @dnwe in https://github.com/IBM/sarama/pull/2283\n* chore(deps): bump golang.org/x/net digest to 1185a90 by @dnwe in https://github.com/IBM/sarama/pull/2279\n* chore(deps): bump module github.com/pierrec/lz4/v4 to v4.1.15 by @dnwe in https://github.com/IBM/sarama/pull/2281\n* chore(deps): bump module github.com/klauspost/compress to v1.15.8 by @dnwe in https://github.com/IBM/sarama/pull/2280\n### :wrench: Maintenance\n* chore: rename `any` func to avoid identifier by @dnwe in https://github.com/IBM/sarama/pull/2272\n* chore: add and test against kafka 3.2.0 by @dnwe in https://github.com/IBM/sarama/pull/2288\n* chore: document Fetch protocol fields by @dnwe in https://github.com/IBM/sarama/pull/2289\n### :heavy_plus_sign: Other Changes\n* chore(ci): fix redirect with GITHUB_STEP_SUMMARY by @dnwe in https://github.com/IBM/sarama/pull/2286\n* fix(test): permit ECONNRESET in TestInitProducerID by @dnwe in https://github.com/IBM/sarama/pull/2287\n* fix: ensure empty or devel version valid by @dnwe in https://github.com/IBM/sarama/pull/2291\n\n## New Contributors\n* @zerowidth made their first contribution in https://github.com/IBM/sarama/pull/2269\n\n**Full Changelog**: https://github.com/IBM/sarama/compare/v1.34.1...v1.35.0\n\n##  Version 1.34.1 (2022-06-07)\n\n## What's Changed\n### :bug: Fixes\n* fix(examples): check session.Context().Done() in examples/consumergroup by @zxc111 in https://github.com/IBM/sarama/pull/2240\n* fix(protocol): move AuthorizedOperations into GroupDescription of DescribeGroupsResponse by @aiquestion in https://github.com/IBM/sarama/pull/2247\n* fix(protocol): tidyup DescribeGroupsResponse by @dnwe in https://github.com/IBM/sarama/pull/2248\n* fix(consumer): range balance strategy not like reference by @njhartwell in https://github.com/IBM/sarama/pull/2245\n### :wrench: Maintenance\n* chore(ci): experiment with using tparse by @dnwe in https://github.com/IBM/sarama/pull/2236\n* chore(deps): bump thirdparty dependencies to latest releases by @dnwe in https://github.com/IBM/sarama/pull/2242\n\n## New Contributors\n* @zxc111 made their first contribution in https://github.com/IBM/sarama/pull/2240\n* @njhartwell made their first contribution in https://github.com/IBM/sarama/pull/2245\n\n**Full Changelog**: https://github.com/IBM/sarama/compare/v1.34.0...v1.34.1\n\n## Version 1.34.0 (2022-05-30)\n\n## What's Changed\n### :tada: New Features / Improvements\n* KIP-345: support static membership by @aiquestion in https://github.com/IBM/sarama/pull/2230\n### :bug: Fixes\n* fix: KIP-368 use receiver goroutine to process all sasl v1 responses by @k-wall in https://github.com/IBM/sarama/pull/2234\n### :wrench: Maintenance\n* chore(deps): bump module github.com/pierrec/lz4 to v4 by @dnwe in https://github.com/IBM/sarama/pull/2231\n* chore(deps): bump golang.org/x/net digest to 2e3eb7b by @dnwe in https://github.com/IBM/sarama/pull/2232\n\n## New Contributors\n* @aiquestion made their first contribution in https://github.com/IBM/sarama/pull/2230\n\n**Full Changelog**: https://github.com/IBM/sarama/compare/v1.33.0...v1.34.0\n\n## Version 1.33.0 (2022-05-11)\n\n## What's Changed\n### :rotating_light: Breaking Changes\n\n**Note: with this change, the user of Sarama is required to use Go 1.13's errors.Is etc (rather then ==) when forming conditionals returned by this library.**\n* feat: make `ErrOutOfBrokers` wrap the underlying error that prevented connections to the brokers by @k-wall in https://github.com/IBM/sarama/pull/2131\n\n\n### :tada: New Features / Improvements\n* feat(message): add UnmarshalText method to CompressionCodec by @vincentbernat in https://github.com/IBM/sarama/pull/2172\n* KIP-368 : Allow SASL Connections to Periodically Re-Authenticate by @k-wall in https://github.com/IBM/sarama/pull/2197\n* feat: add batched CreateACLs func to ClusterAdmin by @nkostoulas in https://github.com/IBM/sarama/pull/2191\n### :bug: Fixes\n* fix: TestRecordBatchDecoding failing sporadically by @k-wall in https://github.com/IBM/sarama/pull/2154\n* feat(test): add an fvt for broker deadlock by @dnwe in https://github.com/IBM/sarama/pull/2144\n* fix: avoid starvation in subscriptionManager by @dnwe in https://github.com/IBM/sarama/pull/2109\n* fix: remove \"Is your cluster reachable?\" from msg by @dnwe in https://github.com/IBM/sarama/pull/2165\n* fix: remove trailing fullstop from error strings by @dnwe in https://github.com/IBM/sarama/pull/2166\n* fix: return underlying sasl error message by @dnwe in https://github.com/IBM/sarama/pull/2164\n* fix: potential data race on a global variable by @pior in https://github.com/IBM/sarama/pull/2171\n* fix: AdminClient | CreateACLs | check for error in response, return error if needed by @omris94 in https://github.com/IBM/sarama/pull/2185\n* producer: ensure that the management message (fin) is never \"leaked\" by @niamster in https://github.com/IBM/sarama/pull/2182\n* fix: prevent RefreshBrokers leaking old brokers  by @k-wall in https://github.com/IBM/sarama/pull/2203\n* fix: prevent RefreshController leaking controller by @k-wall in https://github.com/IBM/sarama/pull/2204\n* fix: prevent AsyncProducer retryBatch from leaking  by @k-wall in https://github.com/IBM/sarama/pull/2208\n* fix: prevent metrics leak when authenticate fails  by @Stephan14 in https://github.com/IBM/sarama/pull/2205\n* fix: prevent deadlock between subscription manager and consumer goroutines by @niamster in https://github.com/IBM/sarama/pull/2194\n* fix: prevent idempotent producer epoch exhaustion by @ladislavmacoun in https://github.com/IBM/sarama/pull/2178\n* fix(test): mockbroker offsetResponse vers behavior by @dnwe in https://github.com/IBM/sarama/pull/2213\n* fix: cope with OffsetsLoadInProgress on Join+Sync  by @dnwe in https://github.com/IBM/sarama/pull/2214\n* fix: make default MaxWaitTime 500ms by @dnwe in https://github.com/IBM/sarama/pull/2227\n### :package: Dependency updates\n* chore(deps): bump xdg-go/scram and klauspost/compress by @dnwe in https://github.com/IBM/sarama/pull/2170\n### :wrench: Maintenance\n* fix(test): skip TestReadOnlyAndAllCommittedMessages by @dnwe in https://github.com/IBM/sarama/pull/2161\n* fix(test): remove t.Parallel() by @dnwe in https://github.com/IBM/sarama/pull/2162\n* chore(ci): bump along to Go 1.17+1.18 and bump golangci-lint by @dnwe in https://github.com/IBM/sarama/pull/2183\n* chore: switch to multi-arch compatible docker images by @dnwe in https://github.com/IBM/sarama/pull/2210\n### :heavy_plus_sign: Other Changes\n* Remediate a number go-routine leaks (mainly test issues) by @k-wall in https://github.com/IBM/sarama/pull/2198\n* chore: retract v1.32.0 due to #2150 by @dnwe in https://github.com/IBM/sarama/pull/2199\n* chore: bump functional test timeout to 12m by @dnwe in https://github.com/IBM/sarama/pull/2200\n* fix(admin): make DeleteRecords err consistent by @dnwe in https://github.com/IBM/sarama/pull/2226\n\n## New Contributors\n* @k-wall made their first contribution in https://github.com/IBM/sarama/pull/2154\n* @pior made their first contribution in https://github.com/IBM/sarama/pull/2171\n* @omris94 made their first contribution in https://github.com/IBM/sarama/pull/2185\n* @vincentbernat made their first contribution in https://github.com/IBM/sarama/pull/2172\n* @niamster made their first contribution in https://github.com/IBM/sarama/pull/2182\n* @ladislavmacoun made their first contribution in https://github.com/IBM/sarama/pull/2178\n* @nkostoulas made their first contribution in https://github.com/IBM/sarama/pull/2191\n\n**Full Changelog**: https://github.com/IBM/sarama/compare/v1.32.0...v1.33.0\n\n## Version 1.32.0 (2022-02-24)\n\n### ⚠️ This release has been superseded by v1.33.0 and should _not_ be used.\n\n* chore: retract v1.32.0 due to #2150 by @dnwe in https://github.com/IBM/sarama/pull/2199\n\n---\n\n## What's Changed\n### :bug: Fixes\n* Fix deadlock when closing Broker in brokerProducer by @slaunay in https://github.com/IBM/sarama/pull/2133\n### :package: Dependency updates\n* chore: refresh dependencies to latest by @dnwe in https://github.com/IBM/sarama/pull/2159\n### :wrench: Maintenance\n* fix: rework RebalancingMultiplePartitions test by @dnwe in https://github.com/IBM/sarama/pull/2130\n* fix(test): use Sarama transactional producer by @dnwe in https://github.com/IBM/sarama/pull/1939\n* chore: enable t.Parallel() wherever possible by @dnwe in https://github.com/IBM/sarama/pull/2138\n### :heavy_plus_sign: Other Changes\n* chore: restrict to 1 testbinary at once by @dnwe in https://github.com/IBM/sarama/pull/2145\n* chore: restrict to 1 parallel test at once by @dnwe in https://github.com/IBM/sarama/pull/2146\n* Remove myself from codeowners by @bai in https://github.com/IBM/sarama/pull/2147\n* chore: add retractions for known bad versions by @dnwe in https://github.com/IBM/sarama/pull/2160\n\n\n**Full Changelog**: https://github.com/IBM/sarama/compare/v1.31.1...v1.32.0\n\n## Version 1.31.1 (2022-02-01)\n\n- #2126 - @bai - Populate missing kafka versions\n- #2124 - @bai - Add Kafka 3.1.0 to CI matrix, migrate to bitnami kafka image\n- #2123 - @bai - Update klauspost/compress to 0.14\n- #2122 - @dnwe - fix(test): make it simpler to re-use toxiproxy\n- #2119 - @bai - Add Kafka 3.1.0 version number\n- #2005 - @raulnegreiros - feat: add methods to pause/resume consumer's consumption\n- #2051 - @seveas - Expose the TLS connection state of a broker connection\n- #2117 - @wuhuizuo - feat: add method MockApiVersionsResponse.SetApiKeys\n- #2110 - @dnwe - fix: ensure heartbeats only stop after cleanup\n- #2113 - @mosceo - Fix typo\n\n## Version 1.31.0 (2022-01-18)\n\n## What's Changed\n### :tada: New Features / Improvements\n* feat: expose IncrementalAlterConfigs API in admin.go by @fengyinqiao in https://github.com/IBM/sarama/pull/2088\n* feat: allow AsyncProducer to have MaxOpenRequests inflight produce requests per broker by @xujianhai666 in https://github.com/IBM/sarama/pull/1686\n* Support request pipelining in AsyncProducer by @slaunay in https://github.com/IBM/sarama/pull/2094\n### :bug: Fixes\n* fix(test): add fluent interface for mocks where missing by @grongor in https://github.com/IBM/sarama/pull/2080\n* fix(test): test for ConsumePartition with OffsetOldest by @grongor in https://github.com/IBM/sarama/pull/2081\n* fix: set HWMO during creation of partitionConsumer (fix incorrect HWMO before first fetch) by @grongor in https://github.com/IBM/sarama/pull/2082\n* fix: ignore non-nil but empty error strings in Describe/Alter client quotas responses by @agriffaut in https://github.com/IBM/sarama/pull/2096\n* fix: skip over KIP-482 tagged fields by @dnwe in https://github.com/IBM/sarama/pull/2107\n* fix: clear preferredReadReplica if broker shutdown by @dnwe in https://github.com/IBM/sarama/pull/2108\n* fix(test): correct wrong offsets in mock Consumer by @grongor in https://github.com/IBM/sarama/pull/2078\n* fix: correct bugs in DescribeGroupsResponse by @dnwe in https://github.com/IBM/sarama/pull/2111\n### :wrench: Maintenance\n* chore: bump runtime and test dependencies by @dnwe in https://github.com/IBM/sarama/pull/2100\n### :memo: Documentation\n* docs: refresh README.md for Kafka 3.0.0 by @dnwe in https://github.com/IBM/sarama/pull/2099\n### :heavy_plus_sign: Other Changes\n* Fix typo by @mosceo in https://github.com/IBM/sarama/pull/2084\n\n## New Contributors\n* @grongor made their first contribution in https://github.com/IBM/sarama/pull/2080\n* @fengyinqiao made their first contribution in https://github.com/IBM/sarama/pull/2088\n* @xujianhai666 made their first contribution in https://github.com/IBM/sarama/pull/1686\n* @mosceo made their first contribution in https://github.com/IBM/sarama/pull/2084\n\n**Full Changelog**: https://github.com/IBM/sarama/compare/v1.30.1...v1.31.0\n\n## Version 1.30.1 (2021-12-04)\n\n## What's Changed\n### :tada: New Features / Improvements\n* feat(zstd): pass level param through to compress/zstd encoder by @lizthegrey in https://github.com/IBM/sarama/pull/2045\n### :bug: Fixes\n* fix: set min-go-version to 1.16 by @troyanov in https://github.com/IBM/sarama/pull/2048\n* logger: fix debug logs' formatting directives by @utrack in https://github.com/IBM/sarama/pull/2054\n* fix: stuck on the batch with zero records length by @pachmu in https://github.com/IBM/sarama/pull/2057\n* fix: only update preferredReadReplica if valid by @dnwe in https://github.com/IBM/sarama/pull/2076\n### :wrench: Maintenance\n* chore: add release notes configuration by @dnwe in https://github.com/IBM/sarama/pull/2046\n* chore: confluent platform version bump by @lizthegrey in https://github.com/IBM/sarama/pull/2070\n\n## Notes\n* ℹ️ from Sarama 1.30.x onward the minimum version of Go toolchain required is 1.16.x\n\n## New Contributors\n* @troyanov made their first contribution in https://github.com/IBM/sarama/pull/2048\n* @lizthegrey made their first contribution in https://github.com/IBM/sarama/pull/2045\n* @utrack made their first contribution in https://github.com/IBM/sarama/pull/2054\n* @pachmu made their first contribution in https://github.com/IBM/sarama/pull/2057\n\n**Full Changelog**: https://github.com/IBM/sarama/compare/v1.30.0...v1.30.1\n\n## Version 1.30.0 (2021-09-29)\n\n⚠️ This release has been superseded by v1.30.1 and should _not_ be used.\n\n**regression**: enabling rackawareness causes severe throughput drops (#2071) — fixed in v1.30.1 via #2076\n\n---\n\nℹ️ **Note: from Sarama 1.30.0 the minimum version of Go toolchain required is 1.16.x**\n\n---\n\n# New Features / Improvements\n\n- #1983 - @zifengyu - allow configure AllowAutoTopicCreation argument in metadata refresh\n- #2000 - @matzew - Using xdg-go module for SCRAM\n- #2003 - @gdm85 - feat: add counter metrics for consumer group join/sync and their failures\n- #1992 - @zhaomoran - feat: support SaslHandshakeRequest v0 for SCRAM\n- #2006 - @faillefer - Add support for DeleteOffsets operation\n- #1909 - @agriffaut - KIP-546 Client quota APIs\n- #1633 - @aldelucca1 - feat: allow balance strategies to provide initial state\n- #1275 - @dnwe - log: add a DebugLogger that proxies to Logger\n- #2018 - @dnwe - feat: use DebugLogger reference for goldenpath log\n- #2019 - @dnwe - feat: add logging & a metric for producer throttle\n- #2023 - @dnwe - feat: add Controller() to ClusterAdmin interface\n- #2025 - @dnwe - feat: support ApiVersionsRequest V3 protocol\n- #2028 - @dnwe - feat: send ApiVersionsRequest on broker open\n- #2034 - @bai - Add support for kafka 3.0.0\n\n# Fixes\n\n- #1990 - @doxsch - fix: correctly pass ValidateOnly through to CreatePartitionsRequest\n- #1988 - @LubergAlexander - fix: correct WithCustomFallbackPartitioner implementation\n- #2001 - @HurSungYun - docs: inform AsyncProducer Close pitfalls\n- #1973 - @qiangmzsx - fix: metrics still taking up too much memory when metrics.UseNilMetrics=true\n- #2007 - @bai - Add support for Go 1.17\n- #2009 - @dnwe - fix: enable nilerr linter and fix iferr checks\n- #2010 - @dnwe - chore: enable exportloopref and misspell linters\n- #2013 - @faillefer - fix(test): disable encoded response/request check when map contains multiple elements\n- #2015 - @bai - Change default branch to main\n- #1718 - @crivera-fastly - fix: correct the error handling in client.InitProducerID()\n- #1984 - @null-sleep - fix(test): bump confluentPlatformVersion from 6.1.1 to 6.2.0\n- #2016 - @dnwe - chore: replace deprecated Go calls\n- #2017 - @dnwe - chore: delete legacy vagrant script\n- #2020 - @dnwe - fix(test): remove testLogger from TrackLeader test\n- #2024 - @dnwe - chore: bump toxiproxy container to v2.1.5\n- #2033 - @bai - Update dependencies\n- #2031 - @gdm85 - docs: do not mention buffered messages in sync producer Close method\n- #2035 - @dnwe - chore: populate the missing kafka versions\n- #2038 - @dnwe - feat: add a fuzzing workflow to github actions\n\n## New Contributors\n* @zifengyu made their first contribution in https://github.com/IBM/sarama/pull/1983\n* @doxsch made their first contribution in https://github.com/IBM/sarama/pull/1990\n* @LubergAlexander made their first contribution in https://github.com/IBM/sarama/pull/1988\n* @HurSungYun made their first contribution in https://github.com/IBM/sarama/pull/2001\n* @gdm85 made their first contribution in https://github.com/IBM/sarama/pull/2003\n* @qiangmzsx made their first contribution in https://github.com/IBM/sarama/pull/1973\n* @zhaomoran made their first contribution in https://github.com/IBM/sarama/pull/1992\n* @faillefer made their first contribution in https://github.com/IBM/sarama/pull/2006\n* @crivera-fastly made their first contribution in https://github.com/IBM/sarama/pull/1718\n* @null-sleep made their first contribution in https://github.com/IBM/sarama/pull/1984\n\n**Full Changelog**: https://github.com/IBM/sarama/compare/v1.29.1...v1.30.0\n\n## Version 1.29.1 (2021-06-24)\n\n# New Features / Improvements\n\n- #1966 - @ajanikow - KIP-339: Add Incremental Config updates API\n- #1964 - @ajanikow - Add DelegationToken ResourceType\n\n# Fixes\n\n- #1962 - @hanxiaolin - fix(consumer):  call interceptors when MaxProcessingTime expire\n- #1971 - @KerryJava - fix  kafka-producer-performance throughput panic\n- #1968 - @dnwe - chore: bump golang.org/x versions\n- #1956 - @joewreschnig - Allow checking the entire `ProducerMessage` in the mock producers\n- #1963 - @dnwe - fix: ensure backoff timer is re-used\n- #1949 - @dnwe - fix: explicitly use uint64 for payload length\n\n## Version 1.29.0 (2021-05-07)\n\n### New Features / Improvements\n\n- #1917 - @arkady-emelyanov - KIP-554: Add Broker-side SCRAM Config API\n- #1869 - @wyndhblb - zstd: encode+decode performance improvements\n- #1541 - @izolight - add String, (Un)MarshalText for acl types.\n- #1921 - @bai - Add support for Kafka 2.8.0\n\n### Fixes\n- #1936 - @dnwe - fix(consumer): follow preferred broker\n- #1933 - @ozzieba - Use gofork for encoding/asn1 to fix ASN errors during Kerberos authentication\n- #1929 - @celrenheit - Handle isolation level in Offset(Request|Response) and require stable offset in FetchOffset(Request|Response)\n- #1926 - @dnwe - fix: correct initial CodeQL findings\n- #1925 - @bai - Test out CodeQL\n- #1923 - @bestgopher - Remove redundant switch-case, fix doc typos\n- #1922 - @bai - Update go dependencies\n- #1898 - @mmaslankaprv - Parsing only known control batches value\n- #1887 - @withshubh - Fix: issues affecting code quality\n\n## Version 1.28.0 (2021-02-15)\n\n**Note that with this release we change `RoundRobinBalancer` strategy to match Java client behavior. See #1788 for details.**\n\n- #1870 - @kvch - Update Kerberos library to latest major\n- #1876 - @bai - Update docs, reference pkg.go.dev\n- #1846 - @wclaeys - Do not ignore Consumer.Offsets.AutoCommit.Enable config on Close\n- #1747 - @XSAM - fix: mock sync producer does not handle the offset while sending messages\n- #1863 - @bai - Add support for Kafka 2.7.0 + update lz4 and klauspost/compress dependencies\n- #1788 - @kzinglzy - feat[balance_strategy]: announcing a new round robin balance strategy\n- #1862 - @bai - Fix CI setenv permissions issues\n- #1832 - @ilyakaznacheev - Update Godoc link to pkg.go.dev\n- #1822 - @danp - KIP-392: Allow consumers to fetch from closest replica\n\n## Version 1.27.2 (2020-10-21)\n\n### Improvements\n\n#1750 - @krantideep95 Adds missing mock responses for mocking consumer group\n\n## Fixes\n\n#1817 - reverts #1785 - Add private method to Client interface to prevent implementation\n\n## Version 1.27.1 (2020-10-07)\n\n### Improvements\n\n#1775 - @d1egoaz - Adds a Producer Interceptor example\n#1781 - @justin-chen - Refresh brokers given list of seed brokers\n#1784 - @justin-chen - Add randomize seed broker method\n#1790 - @d1egoaz - remove example binary\n#1798 - @bai - Test against Go 1.15\n#1785 - @justin-chen - Add private method to Client interface to prevent implementation\n#1802 - @uvw - Support Go 1.13 error unwrapping\n\n## Fixes\n\n#1791 - @stanislavkozlovski - bump default version to 1.0.0\n\n## Version 1.27.0 (2020-08-11)\n\n### Improvements\n\n#1466 - @rubenvp8510  - Expose kerberos fast negotiation configuration\n#1695 - @KJTsanaktsidis - Use docker-compose to run the functional tests\n#1699 - @wclaeys  - Consumer group support for manually comitting offsets\n#1714 - @bai - Bump Go to version 1.14.3, golangci-lint to 1.27.0\n#1726 - @d1egoaz - Include zstd on the functional tests\n#1730 - @d1egoaz - KIP-42 Add producer and consumer interceptors\n#1738 - @varun06 - fixed variable names that are named same as some std lib package names\n#1741 - @varun06 - updated zstd dependency to latest v1.10.10\n#1743 - @varun06 - Fixed declaration dependencies and other lint issues in code base\n#1763 - @alrs - remove deprecated tls options from test\n#1769 - @bai - Add support for Kafka 2.6.0\n\n## Fixes\n\n#1697 - @kvch - Use gofork for encoding/asn1 to fix ASN errors during Kerberos authentication\n#1744 - @alrs  - Fix isBalanced Function Signature\n\n## Version 1.26.4 (2020-05-19)\n\n## Fixes\n\n- #1701 - @d1egoaz - Set server name only for the current broker\n- #1694 - @dnwe - testfix: set KAFKA_HEAP_OPTS for zk and kafka\n\n## Version 1.26.3 (2020-05-07)\n\n## Fixes\n\n- #1692 - @d1egoaz - Set tls ServerName to fix issue: either ServerName or InsecureSkipVerify must be specified in the tls.Config\n\n## Version 1.26.2 (2020-05-06)\n\n## ⚠️ Known Issues\n\nThis release has been marked as not ready for production and may be unstable, please use v1.26.4.\n\n### Improvements\n\n- #1560 - @iyacontrol - add sync pool for gzip 1-9\n- #1605 - @dnwe - feat: protocol support for V11 fetch w/ rackID\n- #1617 - @sladkoff / @dwi-di / @random-dwi - Add support for alter/list partition reassignements APIs\n- #1632 - @bai - Add support for Go 1.14\n- #1640 - @random-dwi - Feature/fix list partition reassignments\n- #1646 - @mimaison - Add DescribeLogDirs to admin client\n- #1667 - @bai - Add support for kafka 2.5.0\n\n## Fixes\n\n- #1594 - @sladkoff - Sets ConfigEntry.Default flag in addition to the ConfigEntry.Source for Kafka versions > V1_1_0_0\n- #1601 - @alrs - fix: remove use of testing.T.FailNow() inside goroutine\n- #1602 - @d1egoaz - adds a note about consumer groups Consume method\n- #1607 - @darklore - Fix memory leak when Broker.Open and Broker.Close called repeatedly\n- #1613 - @wblakecaldwell - Updated \"retrying\" log message when BackoffFunc implemented\n- #1614 - @alrs - produce_response.go: Remove Unused Functions\n- #1619 - @alrs - tools/kafka-producer-performance: prune unused flag variables\n- #1639 - @agriffaut - Handle errors with no message but error code\n- #1643 - @kzinglzy - fix `config.net.keepalive`\n- #1644 - @KJTsanaktsidis - Fix brokers continually allocating new Session IDs\n- #1645 - @Stephan14 - Remove broker(s) which no longer exist in metadata\n- #1650 - @lavoiesl - Return the response error in heartbeatLoop\n- #1661 - @KJTsanaktsidis - Fix \"broker received out of order sequence\" when brokers die\n- #1666 - @KevinJCross - Bugfix: Allow TLS connections to work over socks proxy.\n\n## Version 1.26.1 (2020-02-04)\n\nImprovements:\n- Add requests-in-flight metric ([1539](https://github.com/IBM/sarama/pull/1539))\n- Fix misleading example for cluster admin ([1595](https://github.com/IBM/sarama/pull/1595))\n- Replace Travis with GitHub Actions, linters housekeeping ([1573](https://github.com/IBM/sarama/pull/1573))\n- Allow BalanceStrategy to provide custom assignment data ([1592](https://github.com/IBM/sarama/pull/1592))\n\nBug Fixes:\n- Adds back Consumer.Offsets.CommitInterval to fix API ([1590](https://github.com/IBM/sarama/pull/1590))\n- Fix error message s/CommitInterval/AutoCommit.Interval ([1589](https://github.com/IBM/sarama/pull/1589))\n\n## Version 1.26.0 (2020-01-24)\n\nNew Features:\n- Enable zstd compression\n  ([1574](https://github.com/IBM/sarama/pull/1574),\n  [1582](https://github.com/IBM/sarama/pull/1582))\n- Support headers in tools kafka-console-producer\n  ([1549](https://github.com/IBM/sarama/pull/1549))\n\nImprovements:\n- Add SASL AuthIdentity to SASL frames (authzid)\n  ([1585](https://github.com/IBM/sarama/pull/1585)).\n\nBug Fixes:\n- Sending messages with ZStd compression enabled fails in multiple ways\n  ([1252](https://github.com/IBM/sarama/issues/1252)).\n- Use the broker for any admin on BrokerConfig\n  ([1571](https://github.com/IBM/sarama/pull/1571)).\n- Set DescribeConfigRequest Version field\n  ([1576](https://github.com/IBM/sarama/pull/1576)).\n- ConsumerGroup flooding logs with client/metadata update req\n  ([1578](https://github.com/IBM/sarama/pull/1578)).\n- MetadataRequest version in DescribeCluster\n  ([1580](https://github.com/IBM/sarama/pull/1580)).\n- Fix deadlock in consumer group handleError\n  ([1581](https://github.com/IBM/sarama/pull/1581))\n- Fill in the Fetch{Request,Response} protocol\n  ([1582](https://github.com/IBM/sarama/pull/1582)).\n- Retry topic request on ControllerNotAvailable\n  ([1586](https://github.com/IBM/sarama/pull/1586)).\n\n## Version 1.25.0 (2020-01-13)\n\nNew Features:\n- Support TLS protocol in kafka-producer-performance\n  ([1538](https://github.com/IBM/sarama/pull/1538)).\n- Add support for kafka 2.4.0\n  ([1552](https://github.com/IBM/sarama/pull/1552)).\n\nImprovements:\n- Allow the Consumer to disable auto-commit offsets\n  ([1164](https://github.com/IBM/sarama/pull/1164)).\n- Produce records with consistent timestamps\n  ([1455](https://github.com/IBM/sarama/pull/1455)).\n\nBug Fixes:\n- Fix incorrect SetTopicMetadata name mentions\n  ([1534](https://github.com/IBM/sarama/pull/1534)).\n- Fix client.tryRefreshMetadata Println\n  ([1535](https://github.com/IBM/sarama/pull/1535)).\n- Fix panic on calling updateMetadata on closed client\n  ([1531](https://github.com/IBM/sarama/pull/1531)).\n- Fix possible faulty metrics in TestFuncProducing\n  ([1545](https://github.com/IBM/sarama/pull/1545)).\n\n## Version 1.24.1 (2019-10-31)\n\nNew Features:\n- Add DescribeLogDirs Request/Response pair\n  ([1520](https://github.com/IBM/sarama/pull/1520)).\n\nBug Fixes:\n- Fix ClusterAdmin returning invalid controller ID on DescribeCluster\n  ([1518](https://github.com/IBM/sarama/pull/1518)).\n- Fix issue with consumergroup not rebalancing when new partition is added\n  ([1525](https://github.com/IBM/sarama/pull/1525)).\n- Ensure consistent use of read/write deadlines\n  ([1529](https://github.com/IBM/sarama/pull/1529)).\n\n## Version 1.24.0 (2019-10-09)\n\nNew Features:\n- Add sticky partition assignor\n  ([1416](https://github.com/IBM/sarama/pull/1416)).\n- Switch from cgo zstd package to pure Go implementation\n  ([1477](https://github.com/IBM/sarama/pull/1477)).\n\nImprovements:\n- Allow creating ClusterAdmin from client\n  ([1415](https://github.com/IBM/sarama/pull/1415)).\n- Set KafkaVersion in ListAcls method\n  ([1452](https://github.com/IBM/sarama/pull/1452)).\n- Set request version in CreateACL ClusterAdmin method\n  ([1458](https://github.com/IBM/sarama/pull/1458)).\n- Set request version in DeleteACL ClusterAdmin method\n  ([1461](https://github.com/IBM/sarama/pull/1461)).\n- Handle missed error codes on TopicMetaDataRequest and GroupCoordinatorRequest\n  ([1464](https://github.com/IBM/sarama/pull/1464)).\n- Remove direct usage of gofork\n  ([1465](https://github.com/IBM/sarama/pull/1465)).\n- Add support for Go 1.13\n  ([1478](https://github.com/IBM/sarama/pull/1478)).\n- Improve behavior of NewMockListAclsResponse\n  ([1481](https://github.com/IBM/sarama/pull/1481)).\n\nBug Fixes:\n- Fix race condition in consumergroup example\n  ([1434](https://github.com/IBM/sarama/pull/1434)).\n- Fix brokerProducer goroutine leak\n  ([1442](https://github.com/IBM/sarama/pull/1442)).\n- Use released version of lz4 library\n  ([1469](https://github.com/IBM/sarama/pull/1469)).\n- Set correct version in MockDeleteTopicsResponse\n  ([1484](https://github.com/IBM/sarama/pull/1484)).\n- Fix CLI help message typo\n  ([1494](https://github.com/IBM/sarama/pull/1494)).\n\nKnown Issues:\n- Please **don't** use Zstd, as it doesn't work right now.\n  See https://github.com/IBM/sarama/issues/1252\n\n## Version 1.23.1 (2019-07-22)\n\nBug Fixes:\n- Fix fetch delete bug record\n  ([1425](https://github.com/IBM/sarama/pull/1425)).\n- Handle SASL/OAUTHBEARER token rejection\n  ([1428](https://github.com/IBM/sarama/pull/1428)).\n\n## Version 1.23.0 (2019-07-02)\n\nNew Features:\n- Add support for Kafka 2.3.0\n  ([1418](https://github.com/IBM/sarama/pull/1418)).\n- Add support for ListConsumerGroupOffsets v2\n  ([1374](https://github.com/IBM/sarama/pull/1374)).\n- Add support for DeleteConsumerGroup\n  ([1417](https://github.com/IBM/sarama/pull/1417)).\n- Add support for SASLVersion configuration\n  ([1410](https://github.com/IBM/sarama/pull/1410)).\n- Add kerberos support\n  ([1366](https://github.com/IBM/sarama/pull/1366)).\n\nImprovements:\n- Improve sasl_scram_client example\n  ([1406](https://github.com/IBM/sarama/pull/1406)).\n- Fix shutdown and race-condition in consumer-group example\n  ([1404](https://github.com/IBM/sarama/pull/1404)).\n- Add support for error codes 77—81\n  ([1397](https://github.com/IBM/sarama/pull/1397)).\n- Pool internal objects allocated per message\n  ([1385](https://github.com/IBM/sarama/pull/1385)).\n- Reduce packet decoder allocations\n  ([1373](https://github.com/IBM/sarama/pull/1373)).\n- Support timeout when fetching metadata\n  ([1359](https://github.com/IBM/sarama/pull/1359)).\n\nBug Fixes:\n- Fix fetch size integer overflow\n  ([1376](https://github.com/IBM/sarama/pull/1376)).\n- Handle and log throttled FetchResponses\n  ([1383](https://github.com/IBM/sarama/pull/1383)).\n- Refactor misspelled word Resouce to Resource\n  ([1368](https://github.com/IBM/sarama/pull/1368)).\n\n## Version 1.22.1 (2019-04-29)\n\nImprovements:\n- Use zstd 1.3.8\n  ([1350](https://github.com/IBM/sarama/pull/1350)).\n- Add support for SaslHandshakeRequest v1\n  ([1354](https://github.com/IBM/sarama/pull/1354)).\n\nBug Fixes:\n- Fix V5 MetadataRequest nullable topics array\n  ([1353](https://github.com/IBM/sarama/pull/1353)).\n- Use a different SCRAM client for each broker connection\n  ([1349](https://github.com/IBM/sarama/pull/1349)).\n- Fix AllowAutoTopicCreation for MetadataRequest greater than v3\n  ([1344](https://github.com/IBM/sarama/pull/1344)).\n\n## Version 1.22.0 (2019-04-09)\n\nNew Features:\n- Add Offline Replicas Operation to Client\n  ([1318](https://github.com/IBM/sarama/pull/1318)).\n- Allow using proxy when connecting to broker\n  ([1326](https://github.com/IBM/sarama/pull/1326)).\n- Implement ReadCommitted\n  ([1307](https://github.com/IBM/sarama/pull/1307)).\n- Add support for Kafka 2.2.0\n  ([1331](https://github.com/IBM/sarama/pull/1331)).\n- Add SASL SCRAM-SHA-512 and SCRAM-SHA-256 mechanismes\n  ([1331](https://github.com/IBM/sarama/pull/1295)).\n\nImprovements:\n- Unregister all broker metrics on broker stop\n  ([1232](https://github.com/IBM/sarama/pull/1232)).\n- Add SCRAM authentication example\n  ([1303](https://github.com/IBM/sarama/pull/1303)).\n- Add consumergroup examples\n  ([1304](https://github.com/IBM/sarama/pull/1304)).\n- Expose consumer batch size metric\n  ([1296](https://github.com/IBM/sarama/pull/1296)).\n- Add TLS options to console producer and consumer\n  ([1300](https://github.com/IBM/sarama/pull/1300)).\n- Reduce client close bookkeeping\n  ([1297](https://github.com/IBM/sarama/pull/1297)).\n- Satisfy error interface in create responses\n  ([1154](https://github.com/IBM/sarama/pull/1154)).\n- Please lint gods\n  ([1346](https://github.com/IBM/sarama/pull/1346)).\n\nBug Fixes:\n- Fix multi consumer group instance crash\n  ([1338](https://github.com/IBM/sarama/pull/1338)).\n- Update lz4 to latest version\n  ([1347](https://github.com/IBM/sarama/pull/1347)).\n- Retry ErrNotCoordinatorForConsumer in new consumergroup session\n  ([1231](https://github.com/IBM/sarama/pull/1231)).\n- Fix cleanup error handler\n  ([1332](https://github.com/IBM/sarama/pull/1332)).\n- Fix rate condition in PartitionConsumer\n  ([1156](https://github.com/IBM/sarama/pull/1156)).\n\n## Version 1.21.0 (2019-02-24)\n\nNew Features:\n- Add CreateAclRequest, DescribeAclRequest, DeleteAclRequest\n  ([1236](https://github.com/IBM/sarama/pull/1236)).\n- Add DescribeTopic, DescribeConsumerGroup, ListConsumerGroups, ListConsumerGroupOffsets admin requests\n  ([1178](https://github.com/IBM/sarama/pull/1178)).\n- Implement SASL/OAUTHBEARER\n  ([1240](https://github.com/IBM/sarama/pull/1240)).\n\nImprovements:\n- Add Go mod support\n  ([1282](https://github.com/IBM/sarama/pull/1282)).\n- Add error codes 73—76\n  ([1239](https://github.com/IBM/sarama/pull/1239)).\n- Add retry backoff function\n  ([1160](https://github.com/IBM/sarama/pull/1160)).\n- Maintain metadata in the producer even when retries are disabled\n  ([1189](https://github.com/IBM/sarama/pull/1189)).\n- Include ReplicaAssignment in ListTopics\n  ([1274](https://github.com/IBM/sarama/pull/1274)).\n- Add producer performance tool\n  ([1222](https://github.com/IBM/sarama/pull/1222)).\n- Add support LogAppend timestamps\n  ([1258](https://github.com/IBM/sarama/pull/1258)).\n\nBug Fixes:\n- Fix potential deadlock when a heartbeat request fails\n  ([1286](https://github.com/IBM/sarama/pull/1286)).\n- Fix consuming compacted topic\n  ([1227](https://github.com/IBM/sarama/pull/1227)).\n- Set correct Kafka version for DescribeConfigsRequest v1\n  ([1277](https://github.com/IBM/sarama/pull/1277)).\n- Update kafka test version\n  ([1273](https://github.com/IBM/sarama/pull/1273)).\n\n## Version 1.20.1 (2019-01-10)\n\nNew Features:\n- Add optional replica id in offset request\n  ([1100](https://github.com/IBM/sarama/pull/1100)).\n\nImprovements:\n- Implement DescribeConfigs Request + Response v1 & v2\n  ([1230](https://github.com/IBM/sarama/pull/1230)).\n- Reuse compression objects\n  ([1185](https://github.com/IBM/sarama/pull/1185)).\n- Switch from png to svg for GoDoc link in README\n  ([1243](https://github.com/IBM/sarama/pull/1243)).\n- Fix typo in deprecation notice for FetchResponseBlock.Records\n  ([1242](https://github.com/IBM/sarama/pull/1242)).\n- Fix typos in consumer metadata response file\n  ([1244](https://github.com/IBM/sarama/pull/1244)).\n\nBug Fixes:\n- Revert to individual msg retries for non-idempotent\n  ([1203](https://github.com/IBM/sarama/pull/1203)).\n- Respect MaxMessageBytes limit for uncompressed messages\n  ([1141](https://github.com/IBM/sarama/pull/1141)).\n\n## Version 1.20.0 (2018-12-10)\n\nNew Features:\n - Add support for zstd compression\n   ([#1170](https://github.com/IBM/sarama/pull/1170)).\n - Add support for Idempotent Producer\n   ([#1152](https://github.com/IBM/sarama/pull/1152)).\n - Add support support for Kafka 2.1.0\n   ([#1229](https://github.com/IBM/sarama/pull/1229)).\n - Add support support for OffsetCommit request/response pairs versions v1 to v5\n   ([#1201](https://github.com/IBM/sarama/pull/1201)).\n - Add support support for OffsetFetch request/response pair up to version v5\n   ([#1198](https://github.com/IBM/sarama/pull/1198)).\n\nImprovements:\n - Export broker's Rack setting\n   ([#1173](https://github.com/IBM/sarama/pull/1173)).\n - Always use latest patch version of Go on CI\n   ([#1202](https://github.com/IBM/sarama/pull/1202)).\n - Add error codes 61 to 72\n   ([#1195](https://github.com/IBM/sarama/pull/1195)).\n\nBug Fixes:\n - Fix build without cgo\n   ([#1182](https://github.com/IBM/sarama/pull/1182)).\n - Fix go vet suggestion in consumer group file\n   ([#1209](https://github.com/IBM/sarama/pull/1209)).\n - Fix typos in code and comments\n   ([#1228](https://github.com/IBM/sarama/pull/1228)).\n\n## Version 1.19.0 (2018-09-27)\n\nNew Features:\n - Implement a higher-level consumer group\n   ([#1099](https://github.com/IBM/sarama/pull/1099)).\n\nImprovements:\n - Add support for Go 1.11\n   ([#1176](https://github.com/IBM/sarama/pull/1176)).\n\nBug Fixes:\n - Fix encoding of `MetadataResponse` with version 2 and higher\n   ([#1174](https://github.com/IBM/sarama/pull/1174)).\n - Fix race condition in mock async producer\n   ([#1174](https://github.com/IBM/sarama/pull/1174)).\n\n## Version 1.18.0 (2018-09-07)\n\nNew Features:\n - Make `Partitioner.RequiresConsistency` vary per-message\n   ([#1112](https://github.com/IBM/sarama/pull/1112)).\n - Add customizable partitioner\n   ([#1118](https://github.com/IBM/sarama/pull/1118)).\n - Add `ClusterAdmin` support for `CreateTopic`, `DeleteTopic`, `CreatePartitions`,\n   `DeleteRecords`, `DescribeConfig`, `AlterConfig`, `CreateACL`, `ListAcls`, `DeleteACL`\n   ([#1055](https://github.com/IBM/sarama/pull/1055)).\n\nImprovements:\n - Add support for Kafka 2.0.0\n   ([#1149](https://github.com/IBM/sarama/pull/1149)).\n - Allow setting `LocalAddr` when dialing an address to support multi-homed hosts\n   ([#1123](https://github.com/IBM/sarama/pull/1123)).\n - Simpler offset management\n   ([#1127](https://github.com/IBM/sarama/pull/1127)).\n\nBug Fixes:\n - Fix mutation of `ProducerMessage.MetaData` when producing to Kafka\n   ([#1110](https://github.com/IBM/sarama/pull/1110)).\n - Fix consumer block when response did not contain all the\n   expected topic/partition blocks\n   ([#1086](https://github.com/IBM/sarama/pull/1086)).\n - Fix consumer block when response contains only constrol messages\n   ([#1115](https://github.com/IBM/sarama/pull/1115)).\n - Add timeout config for ClusterAdmin requests\n   ([#1142](https://github.com/IBM/sarama/pull/1142)).\n - Add version check when producing message with headers\n   ([#1117](https://github.com/IBM/sarama/pull/1117)).\n - Fix `MetadataRequest` for empty list of topics\n   ([#1132](https://github.com/IBM/sarama/pull/1132)).\n - Fix producer topic metadata on-demand fetch when topic error happens in metadata response\n   ([#1125](https://github.com/IBM/sarama/pull/1125)).\n\n## Version 1.17.0 (2018-05-30)\n\nNew Features:\n - Add support for gzip compression levels\n   ([#1044](https://github.com/IBM/sarama/pull/1044)).\n - Add support for Metadata request/response pairs versions v1 to v5\n   ([#1047](https://github.com/IBM/sarama/pull/1047),\n    [#1069](https://github.com/IBM/sarama/pull/1069)).\n - Add versioning to JoinGroup request/response pairs\n   ([#1098](https://github.com/IBM/sarama/pull/1098))\n - Add support for CreatePartitions, DeleteGroups, DeleteRecords request/response pairs\n   ([#1065](https://github.com/IBM/sarama/pull/1065),\n    [#1096](https://github.com/IBM/sarama/pull/1096),\n    [#1027](https://github.com/IBM/sarama/pull/1027)).\n - Add `Controller()` method to Client interface\n   ([#1063](https://github.com/IBM/sarama/pull/1063)).\n\nImprovements:\n - ConsumerMetadataReq/Resp has been migrated to FindCoordinatorReq/Resp\n   ([#1010](https://github.com/IBM/sarama/pull/1010)).\n - Expose missing protocol parts: `msgSet` and `recordBatch`\n   ([#1049](https://github.com/IBM/sarama/pull/1049)).\n - Add support for v1 DeleteTopics Request\n   ([#1052](https://github.com/IBM/sarama/pull/1052)).\n - Add support for Go 1.10\n   ([#1064](https://github.com/IBM/sarama/pull/1064)).\n - Claim support for Kafka 1.1.0\n   ([#1073](https://github.com/IBM/sarama/pull/1073)).\n\nBug Fixes:\n - Fix FindCoordinatorResponse.encode to allow nil Coordinator\n   ([#1050](https://github.com/IBM/sarama/pull/1050),\n    [#1051](https://github.com/IBM/sarama/pull/1051)).\n - Clear all metadata when we have the latest topic info\n   ([#1033](https://github.com/IBM/sarama/pull/1033)).\n - Make `PartitionConsumer.Close` idempotent\n   ([#1092](https://github.com/IBM/sarama/pull/1092)).\n\n## Version 1.16.0 (2018-02-12)\n\nNew Features:\n - Add support for the Create/Delete Topics request/response pairs\n   ([#1007](https://github.com/IBM/sarama/pull/1007),\n    [#1008](https://github.com/IBM/sarama/pull/1008)).\n - Add support for the Describe/Create/Delete ACL request/response pairs\n   ([#1009](https://github.com/IBM/sarama/pull/1009)).\n - Add support for the five transaction-related request/response pairs\n   ([#1016](https://github.com/IBM/sarama/pull/1016)).\n\nImprovements:\n - Permit setting version on mock producer responses\n   ([#999](https://github.com/IBM/sarama/pull/999)).\n - Add `NewMockBrokerListener` helper for testing TLS connections\n   ([#1019](https://github.com/IBM/sarama/pull/1019)).\n - Changed the default value for `Consumer.Fetch.Default` from 32KiB to 1MiB\n   which results in much higher throughput in most cases\n   ([#1024](https://github.com/IBM/sarama/pull/1024)).\n - Reuse the `time.Ticker` across fetch requests in the PartitionConsumer to\n   reduce CPU and memory usage when processing many partitions\n   ([#1028](https://github.com/IBM/sarama/pull/1028)).\n - Assign relative offsets to messages in the producer to save the brokers a\n   recompression pass\n   ([#1002](https://github.com/IBM/sarama/pull/1002),\n    [#1015](https://github.com/IBM/sarama/pull/1015)).\n\nBug Fixes:\n - Fix producing uncompressed batches with the new protocol format\n   ([#1032](https://github.com/IBM/sarama/issues/1032)).\n - Fix consuming compacted topics with the new protocol format\n   ([#1005](https://github.com/IBM/sarama/issues/1005)).\n - Fix consuming topics with a mix of protocol formats\n   ([#1021](https://github.com/IBM/sarama/issues/1021)).\n - Fix consuming when the broker includes multiple batches in a single response\n   ([#1022](https://github.com/IBM/sarama/issues/1022)).\n - Fix detection of `PartialTrailingMessage` when the partial message was\n   truncated before the magic value indicating its version\n   ([#1030](https://github.com/IBM/sarama/pull/1030)).\n - Fix expectation-checking in the mock of `SyncProducer.SendMessages`\n   ([#1035](https://github.com/IBM/sarama/pull/1035)).\n\n## Version 1.15.0 (2017-12-08)\n\nNew Features:\n - Claim official support for Kafka 1.0, though it did already work\n   ([#984](https://github.com/IBM/sarama/pull/984)).\n - Helper methods for Kafka version numbers to/from strings\n   ([#989](https://github.com/IBM/sarama/pull/989)).\n - Implement CreatePartitions request/response\n   ([#985](https://github.com/IBM/sarama/pull/985)).\n\nImprovements:\n - Add error codes 45-60\n   ([#986](https://github.com/IBM/sarama/issues/986)).\n\nBug Fixes:\n - Fix slow consuming for certain Kafka 0.11/1.0 configurations\n   ([#982](https://github.com/IBM/sarama/pull/982)).\n - Correctly determine when a FetchResponse contains the new message format\n   ([#990](https://github.com/IBM/sarama/pull/990)).\n - Fix producing with multiple headers\n   ([#996](https://github.com/IBM/sarama/pull/996)).\n - Fix handling of truncated record batches\n   ([#998](https://github.com/IBM/sarama/pull/998)).\n - Fix leaking metrics when closing brokers\n   ([#991](https://github.com/IBM/sarama/pull/991)).\n\n## Version 1.14.0 (2017-11-13)\n\nNew Features:\n - Add support for the new Kafka 0.11 record-batch format, including the wire\n   protocol and the necessary behavioural changes in the producer and consumer.\n   Transactions and idempotency are not yet supported, but producing and\n   consuming should work with all the existing bells and whistles (batching,\n   compression, etc) as well as the new custom headers. Thanks to Vlad Hanciuta\n   of Arista Networks for this work. Part of\n   ([#901](https://github.com/IBM/sarama/issues/901)).\n\nBug Fixes:\n - Fix encoding of ProduceResponse versions in test\n   ([#970](https://github.com/IBM/sarama/pull/970)).\n - Return partial replicas list when we have it\n   ([#975](https://github.com/IBM/sarama/pull/975)).\n\n## Version 1.13.0 (2017-10-04)\n\nNew Features:\n - Support for FetchRequest version 3\n   ([#905](https://github.com/IBM/sarama/pull/905)).\n - Permit setting version on mock FetchResponses\n   ([#939](https://github.com/IBM/sarama/pull/939)).\n - Add a configuration option to support storing only minimal metadata for\n   extremely large clusters\n   ([#937](https://github.com/IBM/sarama/pull/937)).\n - Add `PartitionOffsetManager.ResetOffset` for backtracking tracked offsets\n   ([#932](https://github.com/IBM/sarama/pull/932)).\n\nImprovements:\n - Provide the block-level timestamp when consuming compressed messages\n   ([#885](https://github.com/IBM/sarama/issues/885)).\n - `Client.Replicas` and `Client.InSyncReplicas` now respect the order returned\n   by the broker, which can be meaningful\n   ([#930](https://github.com/IBM/sarama/pull/930)).\n - Use a `Ticker` to reduce consumer timer overhead at the cost of higher\n   variance in the actual timeout\n   ([#933](https://github.com/IBM/sarama/pull/933)).\n\nBug Fixes:\n - Gracefully handle messages with negative timestamps\n   ([#907](https://github.com/IBM/sarama/pull/907)).\n - Raise a proper error when encountering an unknown message version\n   ([#940](https://github.com/IBM/sarama/pull/940)).\n\n## Version 1.12.0 (2017-05-08)\n\nNew Features:\n - Added support for the `ApiVersions` request and response pair, and Kafka\n   version 0.10.2 ([#867](https://github.com/IBM/sarama/pull/867)). Note\n   that you still need to specify the Kafka version in the Sarama configuration\n   for the time being.\n - Added a `Brokers` method to the Client which returns the complete set of\n   active brokers ([#813](https://github.com/IBM/sarama/pull/813)).\n - Added an `InSyncReplicas` method to the Client which returns the set of all\n   in-sync broker IDs for the given partition, now that the Kafka versions for\n   which this was misleading are no longer in our supported set\n   ([#872](https://github.com/IBM/sarama/pull/872)).\n - Added a `NewCustomHashPartitioner` method which allows constructing a hash\n   partitioner with a custom hash method in case the default (FNV-1a) is not\n   suitable\n   ([#837](https://github.com/IBM/sarama/pull/837),\n    [#841](https://github.com/IBM/sarama/pull/841)).\n\nImprovements:\n - Recognize more Kafka error codes\n   ([#859](https://github.com/IBM/sarama/pull/859)).\n\nBug Fixes:\n - Fix an issue where decoding a malformed FetchRequest would not return the\n   correct error ([#818](https://github.com/IBM/sarama/pull/818)).\n - Respect ordering of group protocols in JoinGroupRequests. This fix is\n   transparent if you're using the `AddGroupProtocol` or\n   `AddGroupProtocolMetadata` helpers; otherwise you will need to switch from\n   the `GroupProtocols` field (now deprecated) to use `OrderedGroupProtocols`\n   ([#812](https://github.com/IBM/sarama/issues/812)).\n - Fix an alignment-related issue with atomics on 32-bit architectures\n   ([#859](https://github.com/IBM/sarama/pull/859)).\n\n## Version 1.11.0 (2016-12-20)\n\n_Important:_ As of Sarama 1.11 it is necessary to set the config value of\n`Producer.Return.Successes` to true in order to use the SyncProducer. Previous\nversions would silently override this value when instantiating a SyncProducer\nwhich led to unexpected values and data races.\n\nNew Features:\n - Metrics! Thanks to Sébastien Launay for all his work on this feature\n   ([#701](https://github.com/IBM/sarama/pull/701),\n    [#746](https://github.com/IBM/sarama/pull/746),\n    [#766](https://github.com/IBM/sarama/pull/766)).\n - Add support for LZ4 compression\n   ([#786](https://github.com/IBM/sarama/pull/786)).\n - Add support for ListOffsetRequest v1 and Kafka 0.10.1\n   ([#775](https://github.com/IBM/sarama/pull/775)).\n - Added a `HighWaterMarks` method to the Consumer which aggregates the\n   `HighWaterMarkOffset` values of its child topic/partitions\n   ([#769](https://github.com/IBM/sarama/pull/769)).\n\nBug Fixes:\n - Fixed producing when using timestamps, compression and Kafka 0.10\n   ([#759](https://github.com/IBM/sarama/pull/759)).\n - Added missing decoder methods to DescribeGroups response\n   ([#756](https://github.com/IBM/sarama/pull/756)).\n - Fix producer shutdown when `Return.Errors` is disabled\n   ([#787](https://github.com/IBM/sarama/pull/787)).\n - Don't mutate configuration in SyncProducer\n   ([#790](https://github.com/IBM/sarama/pull/790)).\n - Fix crash on SASL initialization failure\n   ([#795](https://github.com/IBM/sarama/pull/795)).\n\n## Version 1.10.1 (2016-08-30)\n\nBug Fixes:\n - Fix the documentation for `HashPartitioner` which was incorrect\n   ([#717](https://github.com/IBM/sarama/pull/717)).\n - Permit client creation even when it is limited by ACLs\n   ([#722](https://github.com/IBM/sarama/pull/722)).\n - Several fixes to the consumer timer optimization code, regressions introduced\n   in v1.10.0. Go's timers are finicky\n   ([#730](https://github.com/IBM/sarama/pull/730),\n    [#733](https://github.com/IBM/sarama/pull/733),\n    [#734](https://github.com/IBM/sarama/pull/734)).\n - Handle consuming compressed relative offsets with Kafka 0.10\n   ([#735](https://github.com/IBM/sarama/pull/735)).\n\n## Version 1.10.0 (2016-08-02)\n\n_Important:_ As of Sarama 1.10 it is necessary to tell Sarama the version of\nKafka you are running against (via the `config.Version` value) in order to use\nfeatures that may not be compatible with old Kafka versions. If you don't\nspecify this value it will default to 0.8.2 (the minimum supported), and trying\nto use more recent features (like the offset manager) will fail with an error.\n\n_Also:_ The offset-manager's behaviour has been changed to match the upstream\njava consumer (see [#705](https://github.com/IBM/sarama/pull/705) and\n[#713](https://github.com/IBM/sarama/pull/713)). If you use the\noffset-manager, please ensure that you are committing one *greater* than the\nlast consumed message offset or else you may end up consuming duplicate\nmessages.\n\nNew Features:\n - Support for Kafka 0.10\n   ([#672](https://github.com/IBM/sarama/pull/672),\n    [#678](https://github.com/IBM/sarama/pull/678),\n    [#681](https://github.com/IBM/sarama/pull/681), and others).\n - Support for configuring the target Kafka version\n   ([#676](https://github.com/IBM/sarama/pull/676)).\n - Batch producing support in the SyncProducer\n   ([#677](https://github.com/IBM/sarama/pull/677)).\n - Extend producer mock to allow setting expectations on message contents\n   ([#667](https://github.com/IBM/sarama/pull/667)).\n\nImprovements:\n - Support `nil` compressed messages for deleting in compacted topics\n   ([#634](https://github.com/IBM/sarama/pull/634)).\n - Pre-allocate decoding errors, greatly reducing heap usage and GC time against\n   misbehaving brokers ([#690](https://github.com/IBM/sarama/pull/690)).\n - Re-use consumer expiry timers, removing one allocation per consumed message\n   ([#707](https://github.com/IBM/sarama/pull/707)).\n\nBug Fixes:\n - Actually default the client ID to \"sarama\" like we say we do\n   ([#664](https://github.com/IBM/sarama/pull/664)).\n - Fix a rare issue where `Client.Leader` could return the wrong error\n   ([#685](https://github.com/IBM/sarama/pull/685)).\n - Fix a possible tight loop in the consumer\n   ([#693](https://github.com/IBM/sarama/pull/693)).\n - Match upstream's offset-tracking behaviour\n   ([#705](https://github.com/IBM/sarama/pull/705)).\n - Report UnknownTopicOrPartition errors from the offset manager\n   ([#706](https://github.com/IBM/sarama/pull/706)).\n - Fix possible negative partition value from the HashPartitioner\n   ([#709](https://github.com/IBM/sarama/pull/709)).\n\n## Version 1.9.0 (2016-05-16)\n\nNew Features:\n - Add support for custom offset manager retention durations\n   ([#602](https://github.com/IBM/sarama/pull/602)).\n - Publish low-level mocks to enable testing of third-party producer/consumer\n   implementations ([#570](https://github.com/IBM/sarama/pull/570)).\n - Declare support for Golang 1.6\n   ([#611](https://github.com/IBM/sarama/pull/611)).\n - Support for SASL plain-text auth\n   ([#648](https://github.com/IBM/sarama/pull/648)).\n\nImprovements:\n - Simplified broker locking scheme slightly\n   ([#604](https://github.com/IBM/sarama/pull/604)).\n - Documentation cleanup\n   ([#605](https://github.com/IBM/sarama/pull/605),\n    [#621](https://github.com/IBM/sarama/pull/621),\n    [#654](https://github.com/IBM/sarama/pull/654)).\n\nBug Fixes:\n - Fix race condition shutting down the OffsetManager\n   ([#658](https://github.com/IBM/sarama/pull/658)).\n\n## Version 1.8.0 (2016-02-01)\n\nNew Features:\n - Full support for Kafka 0.9:\n   - All protocol messages and fields\n   ([#586](https://github.com/IBM/sarama/pull/586),\n   [#588](https://github.com/IBM/sarama/pull/588),\n   [#590](https://github.com/IBM/sarama/pull/590)).\n   - Verified that TLS support works\n   ([#581](https://github.com/IBM/sarama/pull/581)).\n   - Fixed the OffsetManager compatibility\n   ([#585](https://github.com/IBM/sarama/pull/585)).\n\nImprovements:\n - Optimize for fewer system calls when reading from the network\n   ([#584](https://github.com/IBM/sarama/pull/584)).\n - Automatically retry `InvalidMessage` errors to match upstream behaviour\n   ([#589](https://github.com/IBM/sarama/pull/589)).\n\n## Version 1.7.0 (2015-12-11)\n\nNew Features:\n - Preliminary support for Kafka 0.9\n   ([#572](https://github.com/IBM/sarama/pull/572)). This comes with several\n   caveats:\n   - Protocol-layer support is mostly in place\n     ([#577](https://github.com/IBM/sarama/pull/577)), however Kafka 0.9\n     renamed some messages and fields, which we did not in order to preserve API\n     compatibility.\n   - The producer and consumer work against 0.9, but the offset manager does\n     not ([#573](https://github.com/IBM/sarama/pull/573)).\n   - TLS support may or may not work\n     ([#581](https://github.com/IBM/sarama/pull/581)).\n\nImprovements:\n - Don't wait for request timeouts on dead brokers, greatly speeding recovery\n   when the TCP connection is left hanging\n   ([#548](https://github.com/IBM/sarama/pull/548)).\n - Refactored part of the producer. The new version provides a much more elegant\n   solution to [#449](https://github.com/IBM/sarama/pull/449). It is also\n   slightly more efficient, and much more precise in calculating batch sizes\n   when compression is used\n   ([#549](https://github.com/IBM/sarama/pull/549),\n   [#550](https://github.com/IBM/sarama/pull/550),\n   [#551](https://github.com/IBM/sarama/pull/551)).\n\nBug Fixes:\n - Fix race condition in consumer test mock\n   ([#553](https://github.com/IBM/sarama/pull/553)).\n\n## Version 1.6.1 (2015-09-25)\n\nBug Fixes:\n - Fix panic that could occur if a user-supplied message value failed to encode\n   ([#449](https://github.com/IBM/sarama/pull/449)).\n\n## Version 1.6.0 (2015-09-04)\n\nNew Features:\n - Implementation of a consumer offset manager using the APIs introduced in\n   Kafka 0.8.2. The API is designed mainly for integration into a future\n   high-level consumer, not for direct use, although it is *possible* to use it\n   directly.\n   ([#461](https://github.com/IBM/sarama/pull/461)).\n\nImprovements:\n - CRC32 calculation is much faster on machines with SSE4.2 instructions,\n   removing a major hotspot from most profiles\n   ([#255](https://github.com/IBM/sarama/pull/255)).\n\nBug Fixes:\n - Make protocol decoding more robust against some malformed packets generated\n   by go-fuzz ([#523](https://github.com/IBM/sarama/pull/523),\n   [#525](https://github.com/IBM/sarama/pull/525)) or found in other ways\n   ([#528](https://github.com/IBM/sarama/pull/528)).\n - Fix a potential race condition panic in the consumer on shutdown\n   ([#529](https://github.com/IBM/sarama/pull/529)).\n\n## Version 1.5.0 (2015-08-17)\n\nNew Features:\n - TLS-encrypted network connections are now supported. This feature is subject\n   to change when Kafka releases built-in TLS support, but for now this is\n   enough to work with TLS-terminating proxies\n   ([#154](https://github.com/IBM/sarama/pull/154)).\n\nImprovements:\n - The consumer will not block if a single partition is not drained by the user;\n   all other partitions will continue to consume normally\n   ([#485](https://github.com/IBM/sarama/pull/485)).\n - Formatting of error strings has been much improved\n   ([#495](https://github.com/IBM/sarama/pull/495)).\n - Internal refactoring of the producer for code cleanliness and to enable\n   future work ([#300](https://github.com/IBM/sarama/pull/300)).\n\nBug Fixes:\n - Fix a potential deadlock in the consumer on shutdown\n   ([#475](https://github.com/IBM/sarama/pull/475)).\n\n## Version 1.4.3 (2015-07-21)\n\nBug Fixes:\n - Don't include the partitioner in the producer's \"fetch partitions\"\n   circuit-breaker ([#466](https://github.com/IBM/sarama/pull/466)).\n - Don't retry messages until the broker is closed when abandoning a broker in\n   the producer ([#468](https://github.com/IBM/sarama/pull/468)).\n - Update the import path for snappy-go, it has moved again and the API has\n   changed slightly ([#486](https://github.com/IBM/sarama/pull/486)).\n\n## Version 1.4.2 (2015-05-27)\n\nBug Fixes:\n - Update the import path for snappy-go, it has moved from google code to github\n   ([#456](https://github.com/IBM/sarama/pull/456)).\n\n## Version 1.4.1 (2015-05-25)\n\nImprovements:\n - Optimizations when decoding snappy messages, thanks to John Potocny\n   ([#446](https://github.com/IBM/sarama/pull/446)).\n\nBug Fixes:\n - Fix hypothetical race conditions on producer shutdown\n   ([#450](https://github.com/IBM/sarama/pull/450),\n   [#451](https://github.com/IBM/sarama/pull/451)).\n\n## Version 1.4.0 (2015-05-01)\n\nNew Features:\n - The consumer now implements `Topics()` and `Partitions()` methods to enable\n   users to dynamically choose what topics/partitions to consume without\n   instantiating a full client\n   ([#431](https://github.com/IBM/sarama/pull/431)).\n - The partition-consumer now exposes the high water mark offset value returned\n   by the broker via the `HighWaterMarkOffset()` method ([#339](https://github.com/IBM/sarama/pull/339)).\n - Added a `kafka-console-consumer` tool capable of handling multiple\n   partitions, and deprecated the now-obsolete `kafka-console-partitionConsumer`\n   ([#439](https://github.com/IBM/sarama/pull/439),\n   [#442](https://github.com/IBM/sarama/pull/442)).\n\nImprovements:\n - The producer's logging during retry scenarios is more consistent, more\n   useful, and slightly less verbose\n   ([#429](https://github.com/IBM/sarama/pull/429)).\n - The client now shuffles its initial list of seed brokers in order to prevent\n   thundering herd on the first broker in the list\n   ([#441](https://github.com/IBM/sarama/pull/441)).\n\nBug Fixes:\n - The producer now correctly manages its state if retries occur when it is\n   shutting down, fixing several instances of confusing behaviour and at least\n   one potential deadlock ([#419](https://github.com/IBM/sarama/pull/419)).\n - The consumer now handles messages for different partitions asynchronously,\n   making it much more resilient to specific user code ordering\n   ([#325](https://github.com/IBM/sarama/pull/325)).\n\n## Version 1.3.0 (2015-04-16)\n\nNew Features:\n - The client now tracks consumer group coordinators using\n   ConsumerMetadataRequests similar to how it tracks partition leadership using\n   regular MetadataRequests ([#411](https://github.com/IBM/sarama/pull/411)).\n   This adds two methods to the client API:\n   - `Coordinator(consumerGroup string) (*Broker, error)`\n   - `RefreshCoordinator(consumerGroup string) error`\n\nImprovements:\n - ConsumerMetadataResponses now automatically create a Broker object out of the\n   ID/address/port combination for the Coordinator; accessing the fields\n   individually has been deprecated\n   ([#413](https://github.com/IBM/sarama/pull/413)).\n - Much improved handling of `OffsetOutOfRange` errors in the consumer.\n   Consumers will fail to start if the provided offset is out of range\n   ([#418](https://github.com/IBM/sarama/pull/418))\n   and they will automatically shut down if the offset falls out of range\n   ([#424](https://github.com/IBM/sarama/pull/424)).\n - Small performance improvement in encoding and decoding protocol messages\n   ([#427](https://github.com/IBM/sarama/pull/427)).\n\nBug Fixes:\n - Fix a rare race condition in the client's background metadata refresher if\n   it happens to be activated while the client is being closed\n   ([#422](https://github.com/IBM/sarama/pull/422)).\n\n## Version 1.2.0 (2015-04-07)\n\nImprovements:\n - The producer's behaviour when `Flush.Frequency` is set is now more intuitive\n   ([#389](https://github.com/IBM/sarama/pull/389)).\n - The producer is now somewhat more memory-efficient during and after retrying\n   messages due to an improved queue implementation\n   ([#396](https://github.com/IBM/sarama/pull/396)).\n - The consumer produces much more useful logging output when leadership\n   changes ([#385](https://github.com/IBM/sarama/pull/385)).\n - The client's `GetOffset` method will now automatically refresh metadata and\n   retry once in the event of stale information or similar\n   ([#394](https://github.com/IBM/sarama/pull/394)).\n - Broker connections now have support for using TCP keepalives\n   ([#407](https://github.com/IBM/sarama/issues/407)).\n\nBug Fixes:\n - The OffsetCommitRequest message now correctly implements all three possible\n   API versions ([#390](https://github.com/IBM/sarama/pull/390),\n   [#400](https://github.com/IBM/sarama/pull/400)).\n\n## Version 1.1.0 (2015-03-20)\n\nImprovements:\n - Wrap the producer's partitioner call in a circuit-breaker so that repeatedly\n   broken topics don't choke throughput\n   ([#373](https://github.com/IBM/sarama/pull/373)).\n\nBug Fixes:\n - Fix the producer's internal reference counting in certain unusual scenarios\n   ([#367](https://github.com/IBM/sarama/pull/367)).\n - Fix the consumer's internal reference counting in certain unusual scenarios\n   ([#369](https://github.com/IBM/sarama/pull/369)).\n - Fix a condition where the producer's internal control messages could have\n   gotten stuck ([#368](https://github.com/IBM/sarama/pull/368)).\n - Fix an issue where invalid partition lists would be cached when asking for\n   metadata for a non-existant topic ([#372](https://github.com/IBM/sarama/pull/372)).\n\n\n## Version 1.0.0 (2015-03-17)\n\nVersion 1.0.0 is the first tagged version, and is almost a complete rewrite. The primary differences with previous untagged versions are:\n\n- The producer has been rewritten; there is now a `SyncProducer` with a blocking API, and an `AsyncProducer` that is non-blocking.\n- The consumer has been rewritten to only open one connection per broker instead of one connection per partition.\n- The main types of Sarama are now interfaces to make depedency injection easy; mock implementations for `Consumer`, `SyncProducer` and `AsyncProducer` are provided in the `github.com/IBM/sarama/mocks` package.\n- For most uses cases, it is no longer necessary to open a `Client`; this will be done for you.\n- All the configuration values have been unified in the `Config` struct.\n- Much improved test suite.\n"
  },
  {
    "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\ndominic.evans@uk.ibm.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\n\n[fork]: https://github.com/IBM/sarama/fork\n[pr]: https://github.com/IBM/sarama/compare\n[released]: https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license\n\nHi there! We are thrilled that you would like to contribute to Sarama.\nContributions are always welcome, both reporting issues and submitting pull requests!\n\n## AI-Assistance\n\nAI-assisted tools (e.g., code generation or language models) _may_ be used in the course of contributing issues and pull requests.\nHowever, all contributions must meet the same standards as entirely human-written work and the use of AI should ideally be imperceptible.\nYou remain fully responsible for correctness, maintainability, security, style, and licensing compliance.\nYou may mention the tool or model that you used in your issue/PR description, but do _not_ include it in the Co-authored-by or Signed-off-by commit trailer.\n\nAI assistance may be used when drafting issues, proposals, or discussion posts, but a human must remain fully in the loop and all AI-generated content must be reviewed, fact-checked, and edited _before_ submission.\nEnsure your prompts steer it to remove unnecessary fluff, verbosity, filler and irrelevant content.\n\nIf you open a pull request you must be able to clearly explain what your changes do and how they alter the behaviour of Sarama without relying upon AI tools or prompting to roundtrip the reviewer's questions.\nIf you cannot confidently explain and defend your contribution during review, do not submit it until you can.\nSubmissions must be readable, consistent with the existing codebase, and free of fabricated APIs, references, or unnecessary complexity.\n\n## Reporting issues\n\nPlease make sure to include any potentially useful information in the issue, so we can pinpoint the issue faster without going back and forth.\n\n- What SHA of Sarama are you running? If this is not the latest SHA on the main branch, please try if the problem persists with the latest version.\n- You can set `sarama.Logger` to a [log.Logger](http://golang.org/pkg/log/#Logger) instance to capture debug output. Please include it in your issue description.\n- Also look at the logs of the Kafka broker you are connected to. If you see anything out of the ordinary, please include it.\n\nAlso, please include the following information about your environment, so we can help you faster:\n\n- What version of Kafka are you using?\n- What version of Go are you using?\n- What are the values of your Producer/Consumer/Client configuration?\n\n## Contributing a change\n\nContributions to this project are [released][released] to the public under the project's [opensource license](LICENSE.md).\nBy contributing to this project you agree to the [Developer Certificate of Origin](https://developercertificate.org/) (DCO).\nThe DCO was created by the Linux Kernel community and is a simple statement that you, as a contributor, wrote or otherwise have the legal right to contribute those changes.\n\nContributors must _sign-off_ that they adhere to these requirements by adding a `Signed-off-by` line to all commit messages with an email address that matches the commit author:\n\n```\nfeat: this is my commit message\n\nSigned-off-by: Random J Developer <random@developer.example.org>\n```\n\nGit even has a `-s` command line option to append this automatically to your\ncommit message:\n\n```\n$ git commit -s -m 'This is my commit message'\n```\n\nBecause this library is in production use by many people and applications, we code review all additions.\nTo make the review process go as smooth as possible, please consider the following.\n\n- If you plan to work on something major, please open an issue to discuss the design first.\n- Don't break backwards compatibility. If you really have to, open an issue to discuss this first.\n- Make sure to use the `go fmt` command to format your code according to the standards. Even better, set up your editor to do this for you when saving.\n- Run [go vet](https://golang.org/cmd/vet/) to detect any suspicious constructs in your code that could be bugs.\n- Explicitly handle all error return values. If you really want to ignore an error value, you can assign it to `_`. You can use [errcheck](https://github.com/kisielk/errcheck) to verify whether you have handled all errors.\n- You may also want to run [golint](https://github.com/golang/lint) as well to detect style problems.\n- Add tests that cover the changes you made. Make sure to run `go test` with the `-race` argument to test for race conditions.\n- Make sure your code is supported by all the Go versions we support.\n  You can rely on GitHub Actions for testing older Go versions.\n\n## Submitting a pull request\n\n0. [Fork][fork] and clone the repository\n1. Create a new branch: `git checkout -b my-branch-name`\n2. Make your change, push to your fork and [submit a pull request][pr]\n3. Wait for your pull request to be reviewed and merged.\n\nHere are a few things you can do that will increase the likelihood of your pull request being accepted:\n\n- Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests.\n- Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).\n\n## Further Reading\n\n- [Developer Certificate of Origin versus Contributor License Agreements](https://julien.ponge.org/blog/developer-certificate-of-origin-versus-contributor-license-agreements/)\n- [The most powerful contributor agreement](https://lwn.net/Articles/592503/)\n- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)\n- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/)\n- [GitHub Help](https://help.github.com)\n"
  },
  {
    "path": "Dockerfile.kafka",
    "content": "FROM registry.access.redhat.com/ubi9/ubi-minimal:9.7@sha256:83006d535923fcf1345067873524a3980316f51794f01d8655be55d6e9387183\n\nUSER root\n\nRUN microdnf update -y \\\n && microdnf install -y git gzip java-17-openjdk-headless tar tzdata-java \\\n && microdnf reinstall -y tzdata \\\n && microdnf clean all\n\nENV JAVA_HOME=/usr/lib/jvm/jre-17\n\n# https://docs.oracle.com/javase/7/docs/technotes/guides/net/properties.html\n# Ensure Java doesn't cache any dns results\nRUN cd /etc/java/java-17-openjdk/*/conf/security \\\n && sed -e '/networkaddress.cache.ttl/d' -e '/networkaddress.cache.negative.ttl/d' -i java.security \\\n && echo 'networkaddress.cache.ttl=0' >> java.security \\\n && echo 'networkaddress.cache.negative.ttl=0' >> java.security\n\nARG SCALA_VERSION=\"2.13\"\nARG KAFKA_VERSION=\"3.9.1\"\n\nWORKDIR /tmp\n\n# https://github.com/apache/kafka/blob/2e2b0a58eda3e677763af974a44a6aaa3c280214/tests/docker/Dockerfile#L77-L105\nARG KAFKA_MIRROR=\"https://s3-us-west-2.amazonaws.com/kafka-packages\"\nSHELL [\"/bin/bash\", \"-o\", \"pipefail\", \"-c\"]\nRUN --mount=type=bind,target=.,rw=true \\\n    mkdir -p \"/opt/kafka-${KAFKA_VERSION}\" \\\n && chmod a+rw \"/opt/kafka-${KAFKA_VERSION}\" \\\n && curl -s \"$KAFKA_MIRROR/kafka_${SCALA_VERSION}-${KAFKA_VERSION}.tgz\" | tar xz --strip-components=1 -C \"/opt/kafka-${KAFKA_VERSION}\"\n\n# older kafka versions depend upon jaxb-api being bundled with the JDK, but it\n# was removed from Java 11 so work around that by including it in the kafka\n# libs dir regardless\nRUN curl -sLO \"https://repo1.maven.org/maven2/javax/xml/bind/jaxb-api/2.3.0/jaxb-api-2.3.0.jar\" \\\n && for DIR in /opt/kafka-*; do cp -v jaxb-api-2.3.0.jar $DIR/libs/ ; done \\\n && rm -f jaxb-api-2.3.0.jar\n\n# older kafka versions with the zookeeper 3.4.13/3.4.14 client aren't compatible with Java 17 so quietly bump them to 3.5.9\nRUN if ! stat /opt/kafka-${KAFKA_VERSION}/libs/zookeeper-3.4.*.jar; then exit 0; fi ; \\\n    rm -f /opt/kafka-${KAFKA_VERSION}/libs/zookeeper-3.4.*.jar \\\n && curl --fail -sSL -o \"/opt/kafka-${KAFKA_VERSION}/libs/zookeeper-3.5.9.jar\" \"https://repo1.maven.org/maven2/org/apache/zookeeper/zookeeper/3.5.9/zookeeper-3.5.9.jar\" \\\n && curl --fail -sSL -o \"/opt/kafka-${KAFKA_VERSION}/libs/zookeeper-jute-3.5.9.jar\" \"https://repo1.maven.org/maven2/org/apache/zookeeper/zookeeper-jute/3.5.9/zookeeper-jute-3.5.9.jar\"\n\nWORKDIR /opt/kafka-${KAFKA_VERSION}\n\nENV JAVA_MAJOR_VERSION=17\n\nRUN sed -e \"s/JAVA_MAJOR_VERSION=.*/JAVA_MAJOR_VERSION=${JAVA_MAJOR_VERSION}/\" -i\"\" ./bin/kafka-run-class.sh\n\nCOPY entrypoint.sh /\n\nUSER 65534:65534\n\nENTRYPOINT [\"/entrypoint.sh\"]\n"
  },
  {
    "path": "LICENSE.md",
    "content": "# MIT License\n\nCopyright (c) 2013 Shopify\n\nCopyright (c) 2023 IBM Corporation\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "default: fmt get update test lint\n\nGO       := go\nGOBIN    := $(shell pwd)/bin\nGOBUILD  := CGO_ENABLED=0 $(GO) build $(BUILD_FLAG)\n\nGOTESTSUM         := $(GOBIN)/gotestsum\n# renovate: datasource=github-releases depName=gotestyourself/gotestsum\nGOTESTSUM_VERSION := v1.13.0\n$(GOTESTSUM):\n\tGOBIN=$(GOBIN) go install gotest.tools/gotestsum@$(GOTESTSUM_VERSION)\n\nTESTSTAT         := $(GOBIN)/teststat\n# renovate: datasource=github-releases depName=vearutop/teststat\nTESTSTAT_VERSION := v0.1.27\n$(TESTSTAT):\n\tGOBIN=$(GOBIN) go install github.com/vearutop/teststat@$(TESTSTAT_VERSION)\n\nFILES := $(shell find . -name '*.go' -type f -not -name '*.pb.go' -not -name '*_generated.go' -not -name '*_test.go')\nTESTS := $(shell find . -name '*.go' -type f -not -name '*.pb.go' -not -name '*_generated.go' -name '*_test.go')\n\nget:\n\t$(GO) get ./...\n\t$(GO) mod verify\n\t$(GO) mod tidy\n\nupdate:\n\t$(GO) get -u -v ./...\n\t$(GO) mod verify\n\t$(GO) mod tidy\n\nfmt:\n\tgofmt -s -l -w $(FILES) $(TESTS)\n\nlint:\n\tGOFLAGS=\"-tags=functional\" golangci-lint run\n\ntest: $(GOTESTSUM) $(TESTSTAT) $(TPARSE)\n\t@$(GOTESTSUM) $(if ${CI},--format github-actions,--format testdox) --jsonfile _test/unittests.json --junitfile _test/unittests.xml \\\n\t\t--rerun-fails --packages=\"./...\" \\\n\t\t-- -v -race -coverprofile=profile.out -covermode=atomic -timeout 2m\n\t@$(TESTSTAT) _test/unittests.json\n\n.PHONY: test_functional\ntest_functional: $(GOTESTSUM) $(TESTSTAT) $(TPARSE)\n\t@$(GOTESTSUM) $(if ${CI},--format github-actions,--format testdox) --jsonfile _test/fvt.json --junitfile _test/fvt.xml \\\n\t\t--rerun-fails --packages=\"./...\" \\\n\t\t-- -v -race -coverprofile=profile.out -covermode=atomic -timeout 15m -tags=functional\n\t@$(TESTSTAT) _test/fvt.json\n"
  },
  {
    "path": "README.md",
    "content": "# sarama\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/IBM/sarama.svg)](https://pkg.go.dev/github.com/IBM/sarama)\n[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/IBM/sarama/badge?style=flat)](https://securityscorecards.dev/viewer/?uri=github.com/IBM/sarama)\n[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/7996/badge)](https://www.bestpractices.dev/projects/7996)\n\nSarama is an MIT-licensed Go client library for [Apache Kafka](https://kafka.apache.org/).\n\n## Getting started\n\n- API documentation and examples are available via [pkg.go.dev](https://pkg.go.dev/github.com/IBM/sarama).\n- Mocks for testing are available in the [mocks](./mocks) subpackage.\n- The [examples](./examples) directory contains more elaborate example applications.\n- The [tools](./tools) directory contains command line tools that can be useful for testing, diagnostics, and instrumentation.\n\nYou might also want to look at the [Frequently Asked Questions](https://github.com/IBM/sarama/wiki/Frequently-Asked-Questions).\n\n## Compatibility and API stability\n\nSarama provides a \"2 releases + 2 months\" compatibility guarantee: we support\nthe two latest stable releases of Kafka and Go, and we provide a two month\ngrace period for older releases. However, older releases of Kafka are still likely to work.\n\nSarama follows semantic versioning and provides API stability via the standard Go\n[module version numbering](https://go.dev/doc/modules/version-numbers) scheme.\n\nA changelog is available [here](CHANGELOG.md).\n\n## Contributing\n\n- Get started by checking our [contribution guidelines](https://github.com/IBM/sarama/blob/main/CONTRIBUTING.md).\n- Read the [Sarama wiki](https://github.com/IBM/sarama/wiki) for more technical and design details.\n- The [Kafka Protocol Specification](https://kafka.apache.org/protocol.html) contains a wealth of useful information.\n- For more general issues, there is [a google group](https://groups.google.com/forum/#!forum/kafka-clients) for Kafka client developers.\n- If you have any questions, just ask!\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security\n\n## Reporting Security Issues\n\n**Please do not report security vulnerabilities through public GitHub issues.**\n\nThe easiest way to report a security issue is privately through GitHub [here](https://github.com/IBM/sarama/security/advisories/new).\n\nSee [Privately reporting a security vulnerability](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability) for full instructions.\n\nAlternatively, you can report them via e-mail or anonymous form to the IBM Product Security Incident Response Team (PSIRT) following the guidelines under the [IBM Security Vulnerability Management](https://www.ibm.com/support/pages/ibm-security-vulnerability-management) pages.\n"
  },
  {
    "path": "Vagrantfile",
    "content": "# We have 5 * 192MB ZK processes and 5 * 320MB Kafka processes => 2560MB\nMEMORY = 3072\n\nVagrant.configure(\"2\") do |config|\n  config.vm.box = \"ubuntu/bionic64\"\n\n  config.vm.provision :shell, path: \"vagrant/provision.sh\"\n\n  config.vm.network \"private_network\", ip: \"192.168.100.67\"\n\n  config.vm.provider \"virtualbox\" do |v|\n    v.memory = MEMORY\n  end\nend\n"
  },
  {
    "path": "acl_bindings.go",
    "content": "package sarama\n\n// Resource holds information about acl resource type\ntype Resource struct {\n\tResourceType        AclResourceType\n\tResourceName        string\n\tResourcePatternType AclResourcePatternType\n}\n\nfunc (r *Resource) encode(pe packetEncoder, version int16) error {\n\tpe.putInt8(int8(r.ResourceType))\n\n\tif err := pe.putString(r.ResourceName); err != nil {\n\t\treturn err\n\t}\n\n\tif version == 1 {\n\t\tif r.ResourcePatternType == AclPatternUnknown {\n\t\t\tLogger.Print(\"Cannot encode an unknown resource pattern type, using Literal instead\")\n\t\t\tr.ResourcePatternType = AclPatternLiteral\n\t\t}\n\t\tpe.putInt8(int8(r.ResourcePatternType))\n\t}\n\n\treturn nil\n}\n\nfunc (r *Resource) decode(pd packetDecoder, version int16) (err error) {\n\tresourceType, err := pd.getInt8()\n\tif err != nil {\n\t\treturn err\n\t}\n\tr.ResourceType = AclResourceType(resourceType)\n\n\tif r.ResourceName, err = pd.getString(); err != nil {\n\t\treturn err\n\t}\n\tif version == 1 {\n\t\tpattern, err := pd.getInt8()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.ResourcePatternType = AclResourcePatternType(pattern)\n\t}\n\n\treturn nil\n}\n\n// Acl holds information about acl type\ntype Acl struct {\n\tPrincipal      string\n\tHost           string\n\tOperation      AclOperation\n\tPermissionType AclPermissionType\n}\n\nfunc (a *Acl) encode(pe packetEncoder) error {\n\tif err := pe.putString(a.Principal); err != nil {\n\t\treturn err\n\t}\n\n\tif err := pe.putString(a.Host); err != nil {\n\t\treturn err\n\t}\n\n\tpe.putInt8(int8(a.Operation))\n\tpe.putInt8(int8(a.PermissionType))\n\n\treturn nil\n}\n\nfunc (a *Acl) decode(pd packetDecoder, version int16) (err error) {\n\tif a.Principal, err = pd.getString(); err != nil {\n\t\treturn err\n\t}\n\n\tif a.Host, err = pd.getString(); err != nil {\n\t\treturn err\n\t}\n\n\toperation, err := pd.getInt8()\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.Operation = AclOperation(operation)\n\n\tpermissionType, err := pd.getInt8()\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.PermissionType = AclPermissionType(permissionType)\n\n\treturn nil\n}\n\n// ResourceAcls is an acl resource type\ntype ResourceAcls struct {\n\tResource\n\tAcls []*Acl\n}\n\nfunc (r *ResourceAcls) encode(pe packetEncoder, version int16) error {\n\tif err := r.Resource.encode(pe, version); err != nil {\n\t\treturn err\n\t}\n\n\tif err := pe.putArrayLength(len(r.Acls)); err != nil {\n\t\treturn err\n\t}\n\tfor _, acl := range r.Acls {\n\t\tif err := acl.encode(pe); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (r *ResourceAcls) decode(pd packetDecoder, version int16) error {\n\tif err := r.Resource.decode(pd, version); err != nil {\n\t\treturn err\n\t}\n\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr.Acls = make([]*Acl, n)\n\tfor i := 0; i < n; i++ {\n\t\tr.Acls[i] = new(Acl)\n\t\tif err := r.Acls[i].decode(pd, version); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "acl_create_request.go",
    "content": "package sarama\n\n// CreateAclsRequest is an acl creation request\ntype CreateAclsRequest struct {\n\tVersion      int16\n\tAclCreations []*AclCreation\n}\n\nfunc (c *CreateAclsRequest) setVersion(v int16) {\n\tc.Version = v\n}\n\nfunc (c *CreateAclsRequest) encode(pe packetEncoder) error {\n\tif err := pe.putArrayLength(len(c.AclCreations)); err != nil {\n\t\treturn err\n\t}\n\n\tfor _, aclCreation := range c.AclCreations {\n\t\tif err := aclCreation.encode(pe, c.Version); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (c *CreateAclsRequest) decode(pd packetDecoder, version int16) (err error) {\n\tc.Version = version\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tc.AclCreations = make([]*AclCreation, n)\n\n\tfor i := 0; i < n; i++ {\n\t\tc.AclCreations[i] = new(AclCreation)\n\t\tif err := c.AclCreations[i].decode(pd, version); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (c *CreateAclsRequest) key() int16 {\n\treturn apiKeyCreateAcls\n}\n\nfunc (c *CreateAclsRequest) version() int16 {\n\treturn c.Version\n}\n\nfunc (c *CreateAclsRequest) headerVersion() int16 {\n\treturn 1\n}\n\nfunc (c *CreateAclsRequest) isValidVersion() bool {\n\treturn c.Version >= 0 && c.Version <= 1\n}\n\nfunc (c *CreateAclsRequest) requiredVersion() KafkaVersion {\n\tswitch c.Version {\n\tcase 1:\n\t\treturn V2_0_0_0\n\tdefault:\n\t\treturn V0_11_0_0\n\t}\n}\n\n// AclCreation is a wrapper around Resource and Acl type\ntype AclCreation struct {\n\tResource\n\tAcl\n}\n\nfunc (a *AclCreation) encode(pe packetEncoder, version int16) error {\n\tif err := a.Resource.encode(pe, version); err != nil {\n\t\treturn err\n\t}\n\tif err := a.Acl.encode(pe); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (a *AclCreation) decode(pd packetDecoder, version int16) (err error) {\n\tif err := a.Resource.decode(pd, version); err != nil {\n\t\treturn err\n\t}\n\tif err := a.Acl.decode(pd, version); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "acl_create_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nvar (\n\taclCreateRequest = []byte{\n\t\t0, 0, 0, 1,\n\t\t3, // resource type = group\n\t\t0, 5, 'g', 'r', 'o', 'u', 'p',\n\t\t0, 9, 'p', 'r', 'i', 'n', 'c', 'i', 'p', 'a', 'l',\n\t\t0, 4, 'h', 'o', 's', 't',\n\t\t2, // all\n\t\t2, // deny\n\t}\n\taclCreateRequestv1 = []byte{\n\t\t0, 0, 0, 1,\n\t\t3, // resource type = group\n\t\t0, 5, 'g', 'r', 'o', 'u', 'p',\n\t\t3, // resource pattern type = literal\n\t\t0, 9, 'p', 'r', 'i', 'n', 'c', 'i', 'p', 'a', 'l',\n\t\t0, 4, 'h', 'o', 's', 't',\n\t\t2, // all\n\t\t2, // deny\n\t}\n)\n\nfunc TestCreateAclsRequestv0(t *testing.T) {\n\treq := &CreateAclsRequest{\n\t\tVersion: 0,\n\t\tAclCreations: []*AclCreation{\n\t\t\t{\n\t\t\t\tResource: Resource{\n\t\t\t\t\tResourceType: AclResourceGroup,\n\t\t\t\t\tResourceName: \"group\",\n\t\t\t\t},\n\t\t\t\tAcl: Acl{\n\t\t\t\t\tPrincipal:      \"principal\",\n\t\t\t\t\tHost:           \"host\",\n\t\t\t\t\tOperation:      AclOperationAll,\n\t\t\t\t\tPermissionType: AclPermissionDeny,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\ttestRequest(t, \"create request\", req, aclCreateRequest)\n}\n\nfunc TestCreateAclsRequestv1(t *testing.T) {\n\treq := &CreateAclsRequest{\n\t\tVersion: 1,\n\t\tAclCreations: []*AclCreation{\n\t\t\t{\n\t\t\t\tResource: Resource{\n\t\t\t\t\tResourceType:        AclResourceGroup,\n\t\t\t\t\tResourceName:        \"group\",\n\t\t\t\t\tResourcePatternType: AclPatternLiteral,\n\t\t\t\t},\n\t\t\t\tAcl: Acl{\n\t\t\t\t\tPrincipal:      \"principal\",\n\t\t\t\t\tHost:           \"host\",\n\t\t\t\t\tOperation:      AclOperationAll,\n\t\t\t\t\tPermissionType: AclPermissionDeny,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\ttestRequest(t, \"create request v1\", req, aclCreateRequestv1)\n}\n"
  },
  {
    "path": "acl_create_response.go",
    "content": "package sarama\n\nimport \"time\"\n\n// CreateAclsResponse is a an acl response creation type\ntype CreateAclsResponse struct {\n\tVersion              int16\n\tThrottleTime         time.Duration\n\tAclCreationResponses []*AclCreationResponse\n}\n\nfunc (c *CreateAclsResponse) setVersion(v int16) {\n\tc.Version = v\n}\n\nfunc (c *CreateAclsResponse) encode(pe packetEncoder) error {\n\tpe.putDurationMs(c.ThrottleTime)\n\n\tif err := pe.putArrayLength(len(c.AclCreationResponses)); err != nil {\n\t\treturn err\n\t}\n\n\tfor _, aclCreationResponse := range c.AclCreationResponses {\n\t\tif err := aclCreationResponse.encode(pe); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (c *CreateAclsResponse) decode(pd packetDecoder, version int16) (err error) {\n\tc.ThrottleTime, err = pd.getDurationMs()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tc.AclCreationResponses = make([]*AclCreationResponse, n)\n\tfor i := 0; i < n; i++ {\n\t\tc.AclCreationResponses[i] = new(AclCreationResponse)\n\t\tif err := c.AclCreationResponses[i].decode(pd, version); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (c *CreateAclsResponse) key() int16 {\n\treturn apiKeyCreateAcls\n}\n\nfunc (c *CreateAclsResponse) version() int16 {\n\treturn c.Version\n}\n\nfunc (c *CreateAclsResponse) headerVersion() int16 {\n\treturn 0\n}\n\nfunc (c *CreateAclsResponse) isValidVersion() bool {\n\treturn c.Version >= 0 && c.Version <= 1\n}\n\nfunc (c *CreateAclsResponse) requiredVersion() KafkaVersion {\n\tswitch c.Version {\n\tcase 1:\n\t\treturn V2_0_0_0\n\tdefault:\n\t\treturn V0_11_0_0\n\t}\n}\n\nfunc (r *CreateAclsResponse) throttleTime() time.Duration {\n\treturn r.ThrottleTime\n}\n\n// AclCreationResponse is an acl creation response type\ntype AclCreationResponse struct {\n\tErr    KError\n\tErrMsg *string\n}\n\nfunc (a *AclCreationResponse) encode(pe packetEncoder) error {\n\tpe.putKError(a.Err)\n\n\tif err := pe.putNullableString(a.ErrMsg); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (a *AclCreationResponse) decode(pd packetDecoder, version int16) (err error) {\n\ta.Err, err = pd.getKError()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif a.ErrMsg, err = pd.getNullableString(); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "acl_create_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nvar (\n\tcreateResponseWithError = []byte{\n\t\t0, 0, 0, 100,\n\t\t0, 0, 0, 1,\n\t\t0, 42,\n\t\t0, 5, 'e', 'r', 'r', 'o', 'r',\n\t}\n\n\tcreateResponseArray = []byte{\n\t\t0, 0, 0, 100,\n\t\t0, 0, 0, 2,\n\t\t0, 42,\n\t\t0, 5, 'e', 'r', 'r', 'o', 'r',\n\t\t0, 0,\n\t\t255, 255,\n\t}\n)\n\nfunc TestCreateAclsResponse(t *testing.T) {\n\terrmsg := \"error\"\n\tresp := &CreateAclsResponse{\n\t\tThrottleTime: 100 * time.Millisecond,\n\t\tAclCreationResponses: []*AclCreationResponse{{\n\t\t\tErr:    ErrInvalidRequest,\n\t\t\tErrMsg: &errmsg,\n\t\t}},\n\t}\n\n\ttestResponse(t, \"response with error\", resp, createResponseWithError)\n\n\tresp.AclCreationResponses = append(resp.AclCreationResponses, new(AclCreationResponse))\n\n\ttestResponse(t, \"response array\", resp, createResponseArray)\n}\n"
  },
  {
    "path": "acl_delete_request.go",
    "content": "package sarama\n\n// DeleteAclsRequest is a delete acl request\ntype DeleteAclsRequest struct {\n\tVersion int\n\tFilters []*AclFilter\n}\n\nfunc (d *DeleteAclsRequest) setVersion(v int16) {\n\td.Version = int(v)\n}\n\nfunc (d *DeleteAclsRequest) encode(pe packetEncoder) error {\n\tif err := pe.putArrayLength(len(d.Filters)); err != nil {\n\t\treturn err\n\t}\n\n\tfor _, filter := range d.Filters {\n\t\tfilter.Version = d.Version\n\t\tif err := filter.encode(pe); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (d *DeleteAclsRequest) decode(pd packetDecoder, version int16) (err error) {\n\td.Version = int(version)\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\td.Filters = make([]*AclFilter, n)\n\tfor i := 0; i < n; i++ {\n\t\td.Filters[i] = new(AclFilter)\n\t\td.Filters[i].Version = int(version)\n\t\tif err := d.Filters[i].decode(pd, version); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (d *DeleteAclsRequest) key() int16 {\n\treturn apiKeyDeleteAcls\n}\n\nfunc (d *DeleteAclsRequest) version() int16 {\n\treturn int16(d.Version)\n}\n\nfunc (d *DeleteAclsRequest) headerVersion() int16 {\n\treturn 1\n}\n\nfunc (d *DeleteAclsRequest) isValidVersion() bool {\n\treturn d.Version >= 0 && d.Version <= 1\n}\n\nfunc (d *DeleteAclsRequest) requiredVersion() KafkaVersion {\n\tswitch d.Version {\n\tcase 1:\n\t\treturn V2_0_0_0\n\tdefault:\n\t\treturn V0_11_0_0\n\t}\n}\n"
  },
  {
    "path": "acl_delete_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nvar (\n\taclDeleteRequestNullsv1 = []byte{\n\t\t0, 0, 0, 1,\n\t\t1,\n\t\t255, 255,\n\t\t1, // Any\n\t\t255, 255,\n\t\t255, 255,\n\t\t11,\n\t\t3,\n\t}\n\n\taclDeleteRequestv1 = []byte{\n\t\t0, 0, 0, 1,\n\t\t1, // any\n\t\t0, 6, 'f', 'i', 'l', 't', 'e', 'r',\n\t\t1, // Any Filter\n\t\t0, 9, 'p', 'r', 'i', 'n', 'c', 'i', 'p', 'a', 'l',\n\t\t0, 4, 'h', 'o', 's', 't',\n\t\t4, // write\n\t\t3, // allow\n\t}\n\n\taclDeleteRequestNulls = []byte{\n\t\t0, 0, 0, 1,\n\t\t1,\n\t\t255, 255,\n\t\t255, 255,\n\t\t255, 255,\n\t\t11,\n\t\t3,\n\t}\n\n\taclDeleteRequest = []byte{\n\t\t0, 0, 0, 1,\n\t\t1, // any\n\t\t0, 6, 'f', 'i', 'l', 't', 'e', 'r',\n\t\t0, 9, 'p', 'r', 'i', 'n', 'c', 'i', 'p', 'a', 'l',\n\t\t0, 4, 'h', 'o', 's', 't',\n\t\t4, // write\n\t\t3, // allow\n\t}\n\n\taclDeleteRequestArray = []byte{\n\t\t0, 0, 0, 2,\n\t\t1,\n\t\t0, 6, 'f', 'i', 'l', 't', 'e', 'r',\n\t\t0, 9, 'p', 'r', 'i', 'n', 'c', 'i', 'p', 'a', 'l',\n\t\t0, 4, 'h', 'o', 's', 't',\n\t\t4, // write\n\t\t3, // allow\n\t\t2,\n\t\t0, 5, 't', 'o', 'p', 'i', 'c',\n\t\t255, 255,\n\t\t255, 255,\n\t\t6,\n\t\t2,\n\t}\n)\n\nfunc TestDeleteAclsRequest(t *testing.T) {\n\treq := &DeleteAclsRequest{\n\t\tFilters: []*AclFilter{{\n\t\t\tResourceType:   AclResourceAny,\n\t\t\tOperation:      AclOperationAlterConfigs,\n\t\t\tPermissionType: AclPermissionAllow,\n\t\t}},\n\t}\n\n\ttestRequest(t, \"delete request nulls\", req, aclDeleteRequestNulls)\n\n\treq.Filters[0].ResourceName = nullString(\"filter\")\n\treq.Filters[0].Principal = nullString(\"principal\")\n\treq.Filters[0].Host = nullString(\"host\")\n\treq.Filters[0].Operation = AclOperationWrite\n\n\ttestRequest(t, \"delete request\", req, aclDeleteRequest)\n\n\treq.Filters = append(req.Filters, &AclFilter{\n\t\tResourceType:   AclResourceTopic,\n\t\tResourceName:   nullString(\"topic\"),\n\t\tOperation:      AclOperationDelete,\n\t\tPermissionType: AclPermissionDeny,\n\t})\n\n\ttestRequest(t, \"delete request array\", req, aclDeleteRequestArray)\n}\n\nfunc TestDeleteAclsRequestV1(t *testing.T) {\n\treq := &DeleteAclsRequest{\n\t\tVersion: 1,\n\t\tFilters: []*AclFilter{{\n\t\t\tResourceType:              AclResourceAny,\n\t\t\tOperation:                 AclOperationAlterConfigs,\n\t\t\tPermissionType:            AclPermissionAllow,\n\t\t\tResourcePatternTypeFilter: AclPatternAny,\n\t\t}},\n\t}\n\n\ttestRequest(t, \"delete request nulls\", req, aclDeleteRequestNullsv1)\n\n\treq.Filters[0].ResourceName = nullString(\"filter\")\n\treq.Filters[0].Principal = nullString(\"principal\")\n\treq.Filters[0].Host = nullString(\"host\")\n\treq.Filters[0].Operation = AclOperationWrite\n\n\ttestRequest(t, \"delete request\", req, aclDeleteRequestv1)\n}\n"
  },
  {
    "path": "acl_delete_response.go",
    "content": "package sarama\n\nimport \"time\"\n\n// DeleteAclsResponse is a delete acl response\ntype DeleteAclsResponse struct {\n\tVersion         int16\n\tThrottleTime    time.Duration\n\tFilterResponses []*FilterResponse\n}\n\nfunc (d *DeleteAclsResponse) setVersion(v int16) {\n\td.Version = v\n}\n\nfunc (d *DeleteAclsResponse) encode(pe packetEncoder) error {\n\tpe.putDurationMs(d.ThrottleTime)\n\n\tif err := pe.putArrayLength(len(d.FilterResponses)); err != nil {\n\t\treturn err\n\t}\n\n\tfor _, filterResponse := range d.FilterResponses {\n\t\tif err := filterResponse.encode(pe, d.Version); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (d *DeleteAclsResponse) decode(pd packetDecoder, version int16) (err error) {\n\tif d.ThrottleTime, err = pd.getDurationMs(); err != nil {\n\t\treturn err\n\t}\n\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\td.FilterResponses = make([]*FilterResponse, n)\n\n\tfor i := 0; i < n; i++ {\n\t\td.FilterResponses[i] = new(FilterResponse)\n\t\tif err := d.FilterResponses[i].decode(pd, version); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (d *DeleteAclsResponse) key() int16 {\n\treturn apiKeyDeleteAcls\n}\n\nfunc (d *DeleteAclsResponse) version() int16 {\n\treturn d.Version\n}\n\nfunc (d *DeleteAclsResponse) headerVersion() int16 {\n\treturn 0\n}\n\nfunc (d *DeleteAclsResponse) isValidVersion() bool {\n\treturn d.Version >= 0 && d.Version <= 1\n}\n\nfunc (d *DeleteAclsResponse) requiredVersion() KafkaVersion {\n\tswitch d.Version {\n\tcase 1:\n\t\treturn V2_0_0_0\n\tdefault:\n\t\treturn V0_11_0_0\n\t}\n}\n\nfunc (r *DeleteAclsResponse) throttleTime() time.Duration {\n\treturn r.ThrottleTime\n}\n\n// FilterResponse is a filter response type\ntype FilterResponse struct {\n\tErr          KError\n\tErrMsg       *string\n\tMatchingAcls []*MatchingAcl\n}\n\nfunc (f *FilterResponse) encode(pe packetEncoder, version int16) error {\n\tpe.putKError(f.Err)\n\tif err := pe.putNullableString(f.ErrMsg); err != nil {\n\t\treturn err\n\t}\n\n\tif err := pe.putArrayLength(len(f.MatchingAcls)); err != nil {\n\t\treturn err\n\t}\n\tfor _, matchingAcl := range f.MatchingAcls {\n\t\tif err := matchingAcl.encode(pe, version); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (f *FilterResponse) decode(pd packetDecoder, version int16) (err error) {\n\tf.Err, err = pd.getKError()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif f.ErrMsg, err = pd.getNullableString(); err != nil {\n\t\treturn err\n\t}\n\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\tf.MatchingAcls = make([]*MatchingAcl, n)\n\tfor i := 0; i < n; i++ {\n\t\tf.MatchingAcls[i] = new(MatchingAcl)\n\t\tif err := f.MatchingAcls[i].decode(pd, version); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// MatchingAcl is a matching acl type\ntype MatchingAcl struct {\n\tErr    KError\n\tErrMsg *string\n\tResource\n\tAcl\n}\n\nfunc (m *MatchingAcl) encode(pe packetEncoder, version int16) error {\n\tpe.putKError(m.Err)\n\tif err := pe.putNullableString(m.ErrMsg); err != nil {\n\t\treturn err\n\t}\n\n\tif err := m.Resource.encode(pe, version); err != nil {\n\t\treturn err\n\t}\n\n\tif err := m.Acl.encode(pe); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (m *MatchingAcl) decode(pd packetDecoder, version int16) (err error) {\n\tm.Err, err = pd.getKError()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif m.ErrMsg, err = pd.getNullableString(); err != nil {\n\t\treturn err\n\t}\n\n\tif err := m.Resource.decode(pd, version); err != nil {\n\t\treturn err\n\t}\n\n\tif err := m.Acl.decode(pd, version); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "acl_delete_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nvar deleteAclsResponse = []byte{\n\t0, 0, 0, 100,\n\t0, 0, 0, 1,\n\t0, 0, // no error\n\t255, 255, // no error message\n\t0, 0, 0, 1, // 1 matching acl\n\t0, 0, // no error\n\t255, 255, // no error message\n\t2, // resource type\n\t0, 5, 't', 'o', 'p', 'i', 'c',\n\t0, 9, 'p', 'r', 'i', 'n', 'c', 'i', 'p', 'a', 'l',\n\t0, 4, 'h', 'o', 's', 't',\n\t4,\n\t3,\n}\n\nfunc TestDeleteAclsResponse(t *testing.T) {\n\tresp := &DeleteAclsResponse{\n\t\tThrottleTime: 100 * time.Millisecond,\n\t\tFilterResponses: []*FilterResponse{{\n\t\t\tMatchingAcls: []*MatchingAcl{{\n\t\t\t\tResource: Resource{ResourceType: AclResourceTopic, ResourceName: \"topic\"},\n\t\t\t\tAcl:      Acl{Principal: \"principal\", Host: \"host\", Operation: AclOperationWrite, PermissionType: AclPermissionAllow},\n\t\t\t}},\n\t\t}},\n\t}\n\n\ttestResponse(t, \"\", resp, deleteAclsResponse)\n}\n"
  },
  {
    "path": "acl_describe_request.go",
    "content": "package sarama\n\n// DescribeAclsRequest is a describe acl request type\ntype DescribeAclsRequest struct {\n\tVersion int\n\tAclFilter\n}\n\nfunc (d *DescribeAclsRequest) setVersion(v int16) {\n\td.Version = int(v)\n}\n\nfunc (d *DescribeAclsRequest) encode(pe packetEncoder) error {\n\td.AclFilter.Version = d.Version\n\treturn d.AclFilter.encode(pe)\n}\n\nfunc (d *DescribeAclsRequest) decode(pd packetDecoder, version int16) (err error) {\n\td.Version = int(version)\n\td.AclFilter.Version = int(version)\n\treturn d.AclFilter.decode(pd, version)\n}\n\nfunc (d *DescribeAclsRequest) key() int16 {\n\treturn apiKeyDescribeAcls\n}\n\nfunc (d *DescribeAclsRequest) version() int16 {\n\treturn int16(d.Version)\n}\n\nfunc (d *DescribeAclsRequest) headerVersion() int16 {\n\treturn 1\n}\n\nfunc (d *DescribeAclsRequest) isValidVersion() bool {\n\treturn d.Version >= 0 && d.Version <= 1\n}\n\nfunc (d *DescribeAclsRequest) requiredVersion() KafkaVersion {\n\tswitch d.Version {\n\tcase 1:\n\t\treturn V2_0_0_0\n\tdefault:\n\t\treturn V0_11_0_0\n\t}\n}\n"
  },
  {
    "path": "acl_describe_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"testing\"\n)\n\nvar (\n\taclDescribeRequest = []byte{\n\t\t2, // resource type\n\t\t0, 5, 't', 'o', 'p', 'i', 'c',\n\t\t0, 9, 'p', 'r', 'i', 'n', 'c', 'i', 'p', 'a', 'l',\n\t\t0, 4, 'h', 'o', 's', 't',\n\t\t5, // acl operation\n\t\t3, // acl permission type\n\t}\n\taclDescribeRequestV1 = []byte{\n\t\t2, // resource type\n\t\t0, 5, 't', 'o', 'p', 'i', 'c',\n\t\t1, // any Type\n\t\t0, 9, 'p', 'r', 'i', 'n', 'c', 'i', 'p', 'a', 'l',\n\t\t0, 4, 'h', 'o', 's', 't',\n\t\t5, // acl operation\n\t\t3, // acl permission type\n\t}\n)\n\nfunc TestAclDescribeRequestV0(t *testing.T) {\n\tresourcename := \"topic\"\n\tprincipal := \"principal\"\n\thost := \"host\"\n\n\treq := &DescribeAclsRequest{\n\t\tAclFilter: AclFilter{\n\t\t\tResourceType:   AclResourceTopic,\n\t\t\tResourceName:   &resourcename,\n\t\t\tPrincipal:      &principal,\n\t\t\tHost:           &host,\n\t\t\tOperation:      AclOperationCreate,\n\t\t\tPermissionType: AclPermissionAllow,\n\t\t},\n\t}\n\n\ttestRequest(t, \"\", req, aclDescribeRequest)\n}\n\nfunc TestAclDescribeRequestV1(t *testing.T) {\n\tresourcename := \"topic\"\n\tprincipal := \"principal\"\n\thost := \"host\"\n\n\treq := &DescribeAclsRequest{\n\t\tVersion: 1,\n\t\tAclFilter: AclFilter{\n\t\t\tResourceType:              AclResourceTopic,\n\t\t\tResourceName:              &resourcename,\n\t\t\tResourcePatternTypeFilter: AclPatternAny,\n\t\t\tPrincipal:                 &principal,\n\t\t\tHost:                      &host,\n\t\t\tOperation:                 AclOperationCreate,\n\t\t\tPermissionType:            AclPermissionAllow,\n\t\t},\n\t}\n\n\ttestRequest(t, \"\", req, aclDescribeRequestV1)\n}\n"
  },
  {
    "path": "acl_describe_response.go",
    "content": "package sarama\n\nimport \"time\"\n\n// DescribeAclsResponse is a describe acl response type\ntype DescribeAclsResponse struct {\n\tVersion      int16\n\tThrottleTime time.Duration\n\tErr          KError\n\tErrMsg       *string\n\tResourceAcls []*ResourceAcls\n}\n\nfunc (d *DescribeAclsResponse) setVersion(v int16) {\n\td.Version = v\n}\n\nfunc (d *DescribeAclsResponse) encode(pe packetEncoder) error {\n\tpe.putDurationMs(d.ThrottleTime)\n\tpe.putKError(d.Err)\n\n\tif err := pe.putNullableString(d.ErrMsg); err != nil {\n\t\treturn err\n\t}\n\n\tif err := pe.putArrayLength(len(d.ResourceAcls)); err != nil {\n\t\treturn err\n\t}\n\n\tfor _, resourceAcl := range d.ResourceAcls {\n\t\tif err := resourceAcl.encode(pe, d.Version); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (d *DescribeAclsResponse) decode(pd packetDecoder, version int16) (err error) {\n\tif d.ThrottleTime, err = pd.getDurationMs(); err != nil {\n\t\treturn err\n\t}\n\n\td.Err, err = pd.getKError()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terrmsg, err := pd.getString()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif errmsg != \"\" {\n\t\td.ErrMsg = &errmsg\n\t}\n\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\td.ResourceAcls = make([]*ResourceAcls, n)\n\n\tfor i := 0; i < n; i++ {\n\t\td.ResourceAcls[i] = new(ResourceAcls)\n\t\tif err := d.ResourceAcls[i].decode(pd, version); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (d *DescribeAclsResponse) key() int16 {\n\treturn apiKeyDescribeAcls\n}\n\nfunc (d *DescribeAclsResponse) version() int16 {\n\treturn d.Version\n}\n\nfunc (d *DescribeAclsResponse) headerVersion() int16 {\n\treturn 0\n}\n\nfunc (d *DescribeAclsResponse) isValidVersion() bool {\n\treturn d.Version >= 0 && d.Version <= 1\n}\n\nfunc (d *DescribeAclsResponse) requiredVersion() KafkaVersion {\n\tswitch d.Version {\n\tcase 1:\n\t\treturn V2_0_0_0\n\tdefault:\n\t\treturn V0_11_0_0\n\t}\n}\n\nfunc (r *DescribeAclsResponse) throttleTime() time.Duration {\n\treturn r.ThrottleTime\n}\n"
  },
  {
    "path": "acl_describe_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nvar aclDescribeResponseError = []byte{\n\t0, 0, 0, 100,\n\t0, 8, // error\n\t0, 5, 'e', 'r', 'r', 'o', 'r',\n\t0, 0, 0, 1, // 1 resource\n\t2, // cluster type\n\t0, 5, 't', 'o', 'p', 'i', 'c',\n\t0, 0, 0, 1, // 1 acl\n\t0, 9, 'p', 'r', 'i', 'n', 'c', 'i', 'p', 'a', 'l',\n\t0, 4, 'h', 'o', 's', 't',\n\t4, // write\n\t3, // allow\n}\n\nfunc TestAclDescribeResponse(t *testing.T) {\n\terrmsg := \"error\"\n\tresp := &DescribeAclsResponse{\n\t\tThrottleTime: 100 * time.Millisecond,\n\t\tErr:          ErrBrokerNotAvailable,\n\t\tErrMsg:       &errmsg,\n\t\tResourceAcls: []*ResourceAcls{{\n\t\t\tResource: Resource{\n\t\t\t\tResourceName: \"topic\",\n\t\t\t\tResourceType: AclResourceTopic,\n\t\t\t},\n\t\t\tAcls: []*Acl{\n\t\t\t\t{\n\t\t\t\t\tPrincipal:      \"principal\",\n\t\t\t\t\tHost:           \"host\",\n\t\t\t\t\tOperation:      AclOperationWrite,\n\t\t\t\t\tPermissionType: AclPermissionAllow,\n\t\t\t\t},\n\t\t\t},\n\t\t}},\n\t}\n\n\ttestResponse(t, \"describe\", resp, aclDescribeResponseError)\n}\n"
  },
  {
    "path": "acl_filter.go",
    "content": "package sarama\n\ntype AclFilter struct {\n\tVersion                   int\n\tResourceType              AclResourceType\n\tResourceName              *string\n\tResourcePatternTypeFilter AclResourcePatternType\n\tPrincipal                 *string\n\tHost                      *string\n\tOperation                 AclOperation\n\tPermissionType            AclPermissionType\n}\n\nfunc (a *AclFilter) encode(pe packetEncoder) error {\n\tpe.putInt8(int8(a.ResourceType))\n\tif err := pe.putNullableString(a.ResourceName); err != nil {\n\t\treturn err\n\t}\n\n\tif a.Version == 1 {\n\t\tpe.putInt8(int8(a.ResourcePatternTypeFilter))\n\t}\n\n\tif err := pe.putNullableString(a.Principal); err != nil {\n\t\treturn err\n\t}\n\tif err := pe.putNullableString(a.Host); err != nil {\n\t\treturn err\n\t}\n\tpe.putInt8(int8(a.Operation))\n\tpe.putInt8(int8(a.PermissionType))\n\n\treturn nil\n}\n\nfunc (a *AclFilter) decode(pd packetDecoder, version int16) (err error) {\n\tresourceType, err := pd.getInt8()\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.ResourceType = AclResourceType(resourceType)\n\n\tif a.ResourceName, err = pd.getNullableString(); err != nil {\n\t\treturn err\n\t}\n\n\tif a.Version == 1 {\n\t\tpattern, err := pd.getInt8()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\ta.ResourcePatternTypeFilter = AclResourcePatternType(pattern)\n\t}\n\n\tif a.Principal, err = pd.getNullableString(); err != nil {\n\t\treturn err\n\t}\n\n\tif a.Host, err = pd.getNullableString(); err != nil {\n\t\treturn err\n\t}\n\n\toperation, err := pd.getInt8()\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.Operation = AclOperation(operation)\n\n\tpermissionType, err := pd.getInt8()\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.PermissionType = AclPermissionType(permissionType)\n\n\treturn nil\n}\n"
  },
  {
    "path": "acl_types.go",
    "content": "package sarama\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\ntype (\n\tAclOperation int\n\n\tAclPermissionType int\n\n\tAclResourceType int\n\n\tAclResourcePatternType int\n)\n\n// ref: https://github.com/apache/kafka/blob/trunk/clients/src/main/java/org/apache/kafka/common/acl/AclOperation.java\nconst (\n\tAclOperationUnknown AclOperation = iota\n\tAclOperationAny\n\tAclOperationAll\n\tAclOperationRead\n\tAclOperationWrite\n\tAclOperationCreate\n\tAclOperationDelete\n\tAclOperationAlter\n\tAclOperationDescribe\n\tAclOperationClusterAction\n\tAclOperationDescribeConfigs\n\tAclOperationAlterConfigs\n\tAclOperationIdempotentWrite\n)\n\nfunc (a *AclOperation) String() string {\n\tmapping := map[AclOperation]string{\n\t\tAclOperationUnknown:         \"Unknown\",\n\t\tAclOperationAny:             \"Any\",\n\t\tAclOperationAll:             \"All\",\n\t\tAclOperationRead:            \"Read\",\n\t\tAclOperationWrite:           \"Write\",\n\t\tAclOperationCreate:          \"Create\",\n\t\tAclOperationDelete:          \"Delete\",\n\t\tAclOperationAlter:           \"Alter\",\n\t\tAclOperationDescribe:        \"Describe\",\n\t\tAclOperationClusterAction:   \"ClusterAction\",\n\t\tAclOperationDescribeConfigs: \"DescribeConfigs\",\n\t\tAclOperationAlterConfigs:    \"AlterConfigs\",\n\t\tAclOperationIdempotentWrite: \"IdempotentWrite\",\n\t}\n\ts, ok := mapping[*a]\n\tif !ok {\n\t\ts = mapping[AclOperationUnknown]\n\t}\n\treturn s\n}\n\n// MarshalText returns the text form of the AclOperation (name without prefix)\nfunc (a *AclOperation) MarshalText() ([]byte, error) {\n\treturn []byte(a.String()), nil\n}\n\n// UnmarshalText takes a text representation of the operation and converts it to an AclOperation\nfunc (a *AclOperation) UnmarshalText(text []byte) error {\n\tnormalized := strings.ToLower(string(text))\n\tmapping := map[string]AclOperation{\n\t\t\"unknown\":         AclOperationUnknown,\n\t\t\"any\":             AclOperationAny,\n\t\t\"all\":             AclOperationAll,\n\t\t\"read\":            AclOperationRead,\n\t\t\"write\":           AclOperationWrite,\n\t\t\"create\":          AclOperationCreate,\n\t\t\"delete\":          AclOperationDelete,\n\t\t\"alter\":           AclOperationAlter,\n\t\t\"describe\":        AclOperationDescribe,\n\t\t\"clusteraction\":   AclOperationClusterAction,\n\t\t\"describeconfigs\": AclOperationDescribeConfigs,\n\t\t\"alterconfigs\":    AclOperationAlterConfigs,\n\t\t\"idempotentwrite\": AclOperationIdempotentWrite,\n\t}\n\tao, ok := mapping[normalized]\n\tif !ok {\n\t\t*a = AclOperationUnknown\n\t\treturn fmt.Errorf(\"no acl operation with name %s\", normalized)\n\t}\n\t*a = ao\n\treturn nil\n}\n\n// ref: https://github.com/apache/kafka/blob/trunk/clients/src/main/java/org/apache/kafka/common/acl/AclPermissionType.java\nconst (\n\tAclPermissionUnknown AclPermissionType = iota\n\tAclPermissionAny\n\tAclPermissionDeny\n\tAclPermissionAllow\n)\n\nfunc (a *AclPermissionType) String() string {\n\tmapping := map[AclPermissionType]string{\n\t\tAclPermissionUnknown: \"Unknown\",\n\t\tAclPermissionAny:     \"Any\",\n\t\tAclPermissionDeny:    \"Deny\",\n\t\tAclPermissionAllow:   \"Allow\",\n\t}\n\ts, ok := mapping[*a]\n\tif !ok {\n\t\ts = mapping[AclPermissionUnknown]\n\t}\n\treturn s\n}\n\n// MarshalText returns the text form of the AclPermissionType (name without prefix)\nfunc (a *AclPermissionType) MarshalText() ([]byte, error) {\n\treturn []byte(a.String()), nil\n}\n\n// UnmarshalText takes a text representation of the permission type and converts it to an AclPermissionType\nfunc (a *AclPermissionType) UnmarshalText(text []byte) error {\n\tnormalized := strings.ToLower(string(text))\n\tmapping := map[string]AclPermissionType{\n\t\t\"unknown\": AclPermissionUnknown,\n\t\t\"any\":     AclPermissionAny,\n\t\t\"deny\":    AclPermissionDeny,\n\t\t\"allow\":   AclPermissionAllow,\n\t}\n\n\tapt, ok := mapping[normalized]\n\tif !ok {\n\t\t*a = AclPermissionUnknown\n\t\treturn fmt.Errorf(\"no acl permission with name %s\", normalized)\n\t}\n\t*a = apt\n\treturn nil\n}\n\n// ref: https://github.com/apache/kafka/blob/trunk/clients/src/main/java/org/apache/kafka/common/resource/ResourceType.java\nconst (\n\tAclResourceUnknown AclResourceType = iota\n\tAclResourceAny\n\tAclResourceTopic\n\tAclResourceGroup\n\tAclResourceCluster\n\tAclResourceTransactionalID\n\tAclResourceDelegationToken\n)\n\nfunc (a *AclResourceType) String() string {\n\tmapping := map[AclResourceType]string{\n\t\tAclResourceUnknown:         \"Unknown\",\n\t\tAclResourceAny:             \"Any\",\n\t\tAclResourceTopic:           \"Topic\",\n\t\tAclResourceGroup:           \"Group\",\n\t\tAclResourceCluster:         \"Cluster\",\n\t\tAclResourceTransactionalID: \"TransactionalID\",\n\t\tAclResourceDelegationToken: \"DelegationToken\",\n\t}\n\ts, ok := mapping[*a]\n\tif !ok {\n\t\ts = mapping[AclResourceUnknown]\n\t}\n\treturn s\n}\n\n// MarshalText returns the text form of the AclResourceType (name without prefix)\nfunc (a *AclResourceType) MarshalText() ([]byte, error) {\n\treturn []byte(a.String()), nil\n}\n\n// UnmarshalText takes a text representation of the resource type and converts it to an AclResourceType\nfunc (a *AclResourceType) UnmarshalText(text []byte) error {\n\tnormalized := strings.ToLower(string(text))\n\tmapping := map[string]AclResourceType{\n\t\t\"unknown\":         AclResourceUnknown,\n\t\t\"any\":             AclResourceAny,\n\t\t\"topic\":           AclResourceTopic,\n\t\t\"group\":           AclResourceGroup,\n\t\t\"cluster\":         AclResourceCluster,\n\t\t\"transactionalid\": AclResourceTransactionalID,\n\t\t\"delegationtoken\": AclResourceDelegationToken,\n\t}\n\n\tart, ok := mapping[normalized]\n\tif !ok {\n\t\t*a = AclResourceUnknown\n\t\treturn fmt.Errorf(\"no acl resource with name %s\", normalized)\n\t}\n\t*a = art\n\treturn nil\n}\n\n// ref: https://github.com/apache/kafka/blob/trunk/clients/src/main/java/org/apache/kafka/common/resource/PatternType.java\nconst (\n\tAclPatternUnknown AclResourcePatternType = iota\n\tAclPatternAny\n\tAclPatternMatch\n\tAclPatternLiteral\n\tAclPatternPrefixed\n)\n\nfunc (a *AclResourcePatternType) String() string {\n\tmapping := map[AclResourcePatternType]string{\n\t\tAclPatternUnknown:  \"Unknown\",\n\t\tAclPatternAny:      \"Any\",\n\t\tAclPatternMatch:    \"Match\",\n\t\tAclPatternLiteral:  \"Literal\",\n\t\tAclPatternPrefixed: \"Prefixed\",\n\t}\n\ts, ok := mapping[*a]\n\tif !ok {\n\t\ts = mapping[AclPatternUnknown]\n\t}\n\treturn s\n}\n\n// MarshalText returns the text form of the AclResourcePatternType (name without prefix)\nfunc (a *AclResourcePatternType) MarshalText() ([]byte, error) {\n\treturn []byte(a.String()), nil\n}\n\n// UnmarshalText takes a text representation of the resource pattern type and converts it to an AclResourcePatternType\nfunc (a *AclResourcePatternType) UnmarshalText(text []byte) error {\n\tnormalized := strings.ToLower(string(text))\n\tmapping := map[string]AclResourcePatternType{\n\t\t\"unknown\":  AclPatternUnknown,\n\t\t\"any\":      AclPatternAny,\n\t\t\"match\":    AclPatternMatch,\n\t\t\"literal\":  AclPatternLiteral,\n\t\t\"prefixed\": AclPatternPrefixed,\n\t}\n\n\tarpt, ok := mapping[normalized]\n\tif !ok {\n\t\t*a = AclPatternUnknown\n\t\treturn fmt.Errorf(\"no acl resource pattern with name %s\", normalized)\n\t}\n\t*a = arpt\n\treturn nil\n}\n"
  },
  {
    "path": "acl_types_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"testing\"\n)\n\nfunc TestAclOperationTextMarshal(t *testing.T) {\n\tfor i := AclOperationUnknown; i <= AclOperationIdempotentWrite; i++ {\n\t\ttext, err := i.MarshalText()\n\t\tif err != nil {\n\t\t\tt.Errorf(\"couldn't marshal %d to text: %s\", i, err)\n\t\t}\n\t\tvar got AclOperation\n\t\terr = got.UnmarshalText(text)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"couldn't unmarshal %s to acl operation: %s\", text, err)\n\t\t}\n\t\tif got != i {\n\t\t\tt.Errorf(\"got %d, want %d\", got, i)\n\t\t}\n\t}\n}\n\nfunc TestAclPermissionTypeTextMarshal(t *testing.T) {\n\tfor i := AclPermissionUnknown; i <= AclPermissionAllow; i++ {\n\t\ttext, err := i.MarshalText()\n\t\tif err != nil {\n\t\t\tt.Errorf(\"couldn't marshal %d to text: %s\", i, err)\n\t\t}\n\t\tvar got AclPermissionType\n\t\terr = got.UnmarshalText(text)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"couldn't unmarshal %s to acl permission: %s\", text, err)\n\t\t}\n\t\tif got != i {\n\t\t\tt.Errorf(\"got %d, want %d\", got, i)\n\t\t}\n\t}\n}\n\nfunc TestAclResourceTypeTextMarshal(t *testing.T) {\n\tfor i := AclResourceUnknown; i <= AclResourceTransactionalID; i++ {\n\t\ttext, err := i.MarshalText()\n\t\tif err != nil {\n\t\t\tt.Errorf(\"couldn't marshal %d to text: %s\", i, err)\n\t\t}\n\t\tvar got AclResourceType\n\t\terr = got.UnmarshalText(text)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"couldn't unmarshal %s to acl resource: %s\", text, err)\n\t\t}\n\t\tif got != i {\n\t\t\tt.Errorf(\"got %d, want %d\", got, i)\n\t\t}\n\t}\n}\n\nfunc TestAclResourcePatternTypeTextMarshal(t *testing.T) {\n\tfor i := AclPatternUnknown; i <= AclPatternPrefixed; i++ {\n\t\ttext, err := i.MarshalText()\n\t\tif err != nil {\n\t\t\tt.Errorf(\"couldn't marshal %d to text: %s\", i, err)\n\t\t}\n\t\tvar got AclResourcePatternType\n\t\terr = got.UnmarshalText(text)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"couldn't unmarshal %s to acl resource pattern: %s\", text, err)\n\t\t}\n\t\tif got != i {\n\t\t\tt.Errorf(\"got %d, want %d\", got, i)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "add_offsets_to_txn_request.go",
    "content": "package sarama\n\n// AddOffsetsToTxnRequest adds offsets to a transaction request\ntype AddOffsetsToTxnRequest struct {\n\tVersion         int16\n\tTransactionalID string\n\tProducerID      int64\n\tProducerEpoch   int16\n\tGroupID         string\n}\n\nfunc (a *AddOffsetsToTxnRequest) setVersion(v int16) {\n\ta.Version = v\n}\n\nfunc (a *AddOffsetsToTxnRequest) encode(pe packetEncoder) error {\n\tif err := pe.putString(a.TransactionalID); err != nil {\n\t\treturn err\n\t}\n\n\tpe.putInt64(a.ProducerID)\n\n\tpe.putInt16(a.ProducerEpoch)\n\n\tif err := pe.putString(a.GroupID); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (a *AddOffsetsToTxnRequest) decode(pd packetDecoder, version int16) (err error) {\n\tif a.TransactionalID, err = pd.getString(); err != nil {\n\t\treturn err\n\t}\n\tif a.ProducerID, err = pd.getInt64(); err != nil {\n\t\treturn err\n\t}\n\tif a.ProducerEpoch, err = pd.getInt16(); err != nil {\n\t\treturn err\n\t}\n\tif a.GroupID, err = pd.getString(); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (a *AddOffsetsToTxnRequest) key() int16 {\n\treturn apiKeyAddOffsetsToTxn\n}\n\nfunc (a *AddOffsetsToTxnRequest) version() int16 {\n\treturn a.Version\n}\n\nfunc (a *AddOffsetsToTxnRequest) headerVersion() int16 {\n\treturn 1\n}\n\nfunc (a *AddOffsetsToTxnRequest) isValidVersion() bool {\n\treturn a.Version >= 0 && a.Version <= 2\n}\n\nfunc (a *AddOffsetsToTxnRequest) requiredVersion() KafkaVersion {\n\tswitch a.Version {\n\tcase 2:\n\t\treturn V2_7_0_0\n\tcase 1:\n\t\treturn V2_0_0_0\n\tcase 0:\n\t\treturn V0_11_0_0\n\tdefault:\n\t\treturn V2_7_0_0\n\t}\n}\n"
  },
  {
    "path": "add_offsets_to_txn_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nvar addOffsetsToTxnRequest = []byte{\n\t0, 3, 't', 'x', 'n',\n\t0, 0, 0, 0, 0, 0, 31, 64,\n\t0, 0,\n\t0, 7, 'g', 'r', 'o', 'u', 'p', 'i', 'd',\n}\n\nfunc TestAddOffsetsToTxnRequest(t *testing.T) {\n\treq := &AddOffsetsToTxnRequest{\n\t\tTransactionalID: \"txn\",\n\t\tProducerID:      8000,\n\t\tProducerEpoch:   0,\n\t\tGroupID:         \"groupid\",\n\t}\n\n\ttestRequest(t, \"\", req, addOffsetsToTxnRequest)\n}\n"
  },
  {
    "path": "add_offsets_to_txn_response.go",
    "content": "package sarama\n\nimport (\n\t\"time\"\n)\n\n// AddOffsetsToTxnResponse is a response type for adding offsets to txns\ntype AddOffsetsToTxnResponse struct {\n\tVersion      int16\n\tThrottleTime time.Duration\n\tErr          KError\n}\n\nfunc (a *AddOffsetsToTxnResponse) setVersion(v int16) {\n\ta.Version = v\n}\n\nfunc (a *AddOffsetsToTxnResponse) encode(pe packetEncoder) error {\n\tpe.putDurationMs(a.ThrottleTime)\n\tpe.putKError(a.Err)\n\treturn nil\n}\n\nfunc (a *AddOffsetsToTxnResponse) decode(pd packetDecoder, version int16) (err error) {\n\tif a.ThrottleTime, err = pd.getDurationMs(); err != nil {\n\t\treturn err\n\t}\n\n\ta.Err, err = pd.getKError()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (a *AddOffsetsToTxnResponse) key() int16 {\n\treturn apiKeyAddOffsetsToTxn\n}\n\nfunc (a *AddOffsetsToTxnResponse) version() int16 {\n\treturn a.Version\n}\n\nfunc (a *AddOffsetsToTxnResponse) headerVersion() int16 {\n\treturn 0\n}\n\nfunc (a *AddOffsetsToTxnResponse) isValidVersion() bool {\n\treturn a.Version >= 0 && a.Version <= 2\n}\n\nfunc (a *AddOffsetsToTxnResponse) requiredVersion() KafkaVersion {\n\tswitch a.Version {\n\tcase 2:\n\t\treturn V2_7_0_0\n\tcase 1:\n\t\treturn V2_0_0_0\n\tcase 0:\n\t\treturn V0_11_0_0\n\tdefault:\n\t\treturn V2_7_0_0\n\t}\n}\n\nfunc (r *AddOffsetsToTxnResponse) throttleTime() time.Duration {\n\treturn r.ThrottleTime\n}\n"
  },
  {
    "path": "add_offsets_to_txn_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nvar addOffsetsToTxnResponse = []byte{\n\t0, 0, 0, 100,\n\t0, 47,\n}\n\nfunc TestAddOffsetsToTxnResponse(t *testing.T) {\n\tresp := &AddOffsetsToTxnResponse{\n\t\tThrottleTime: 100 * time.Millisecond,\n\t\tErr:          ErrInvalidProducerEpoch,\n\t}\n\n\ttestResponse(t, \"\", resp, addOffsetsToTxnResponse)\n}\n"
  },
  {
    "path": "add_partitions_to_txn_request.go",
    "content": "package sarama\n\n// AddPartitionsToTxnRequest is a add partition request\ntype AddPartitionsToTxnRequest struct {\n\tVersion         int16\n\tTransactionalID string\n\tProducerID      int64\n\tProducerEpoch   int16\n\tTopicPartitions map[string][]int32\n}\n\nfunc (a *AddPartitionsToTxnRequest) setVersion(v int16) {\n\ta.Version = v\n}\n\nfunc (a *AddPartitionsToTxnRequest) encode(pe packetEncoder) error {\n\tif err := pe.putString(a.TransactionalID); err != nil {\n\t\treturn err\n\t}\n\tpe.putInt64(a.ProducerID)\n\tpe.putInt16(a.ProducerEpoch)\n\n\tif err := pe.putArrayLength(len(a.TopicPartitions)); err != nil {\n\t\treturn err\n\t}\n\tfor topic, partitions := range a.TopicPartitions {\n\t\tif err := pe.putString(topic); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := pe.putInt32Array(partitions); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (a *AddPartitionsToTxnRequest) decode(pd packetDecoder, version int16) (err error) {\n\tif a.TransactionalID, err = pd.getString(); err != nil {\n\t\treturn err\n\t}\n\tif a.ProducerID, err = pd.getInt64(); err != nil {\n\t\treturn err\n\t}\n\tif a.ProducerEpoch, err = pd.getInt16(); err != nil {\n\t\treturn err\n\t}\n\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ta.TopicPartitions = make(map[string][]int32)\n\tfor i := 0; i < n; i++ {\n\t\ttopic, err := pd.getString()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tpartitions, err := pd.getInt32Array()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\ta.TopicPartitions[topic] = partitions\n\t}\n\n\treturn nil\n}\n\nfunc (a *AddPartitionsToTxnRequest) key() int16 {\n\treturn apiKeyAddPartitionsToTxn\n}\n\nfunc (a *AddPartitionsToTxnRequest) version() int16 {\n\treturn a.Version\n}\n\nfunc (a *AddPartitionsToTxnRequest) headerVersion() int16 {\n\treturn 1\n}\n\nfunc (a *AddPartitionsToTxnRequest) isValidVersion() bool {\n\treturn a.Version >= 0 && a.Version <= 2\n}\n\nfunc (a *AddPartitionsToTxnRequest) requiredVersion() KafkaVersion {\n\tswitch a.Version {\n\tcase 2:\n\t\treturn V2_7_0_0\n\tcase 1:\n\t\treturn V2_0_0_0\n\tdefault:\n\t\treturn V0_11_0_0\n\t}\n}\n"
  },
  {
    "path": "add_partitions_to_txn_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nvar addPartitionsToTxnRequest = []byte{\n\t0, 3, 't', 'x', 'n',\n\t0, 0, 0, 0, 0, 0, 31, 64, // ProducerID\n\t0, 0, 0, 0, // ProducerEpoch\n\t0, 1, // 1 topic\n\t0, 5, 't', 'o', 'p', 'i', 'c',\n\t0, 0, 0, 1, 0, 0, 0, 1,\n}\n\nfunc TestAddPartitionsToTxnRequest(t *testing.T) {\n\treq := &AddPartitionsToTxnRequest{\n\t\tTransactionalID: \"txn\",\n\t\tProducerID:      8000,\n\t\tProducerEpoch:   0,\n\t\tTopicPartitions: map[string][]int32{\n\t\t\t\"topic\": {1},\n\t\t},\n\t}\n\n\ttestRequest(t, \"\", req, addPartitionsToTxnRequest)\n}\n"
  },
  {
    "path": "add_partitions_to_txn_response.go",
    "content": "package sarama\n\nimport (\n\t\"time\"\n)\n\n// AddPartitionsToTxnResponse is a partition errors to transaction type\ntype AddPartitionsToTxnResponse struct {\n\tVersion      int16\n\tThrottleTime time.Duration\n\tErrors       map[string][]*PartitionError\n}\n\nfunc (a *AddPartitionsToTxnResponse) setVersion(v int16) {\n\ta.Version = v\n}\n\nfunc (a *AddPartitionsToTxnResponse) encode(pe packetEncoder) error {\n\tpe.putDurationMs(a.ThrottleTime)\n\tif err := pe.putArrayLength(len(a.Errors)); err != nil {\n\t\treturn err\n\t}\n\n\tfor topic, e := range a.Errors {\n\t\tif err := pe.putString(topic); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := pe.putArrayLength(len(e)); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor _, partitionError := range e {\n\t\t\tif err := partitionError.encode(pe); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (a *AddPartitionsToTxnResponse) decode(pd packetDecoder, version int16) (err error) {\n\ta.Version = version\n\tif a.ThrottleTime, err = pd.getDurationMs(); err != nil {\n\t\treturn err\n\t}\n\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ta.Errors = make(map[string][]*PartitionError)\n\n\tfor i := 0; i < n; i++ {\n\t\ttopic, err := pd.getString()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tm, err := pd.getArrayLength()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\ta.Errors[topic] = make([]*PartitionError, m)\n\n\t\tfor j := 0; j < m; j++ {\n\t\t\ta.Errors[topic][j] = new(PartitionError)\n\t\t\tif err := a.Errors[topic][j].decode(pd, version); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (a *AddPartitionsToTxnResponse) key() int16 {\n\treturn apiKeyAddPartitionsToTxn\n}\n\nfunc (a *AddPartitionsToTxnResponse) version() int16 {\n\treturn a.Version\n}\n\nfunc (a *AddPartitionsToTxnResponse) headerVersion() int16 {\n\treturn 0\n}\n\nfunc (a *AddPartitionsToTxnResponse) isValidVersion() bool {\n\treturn a.Version >= 0 && a.Version <= 2\n}\n\nfunc (a *AddPartitionsToTxnResponse) requiredVersion() KafkaVersion {\n\tswitch a.Version {\n\tcase 2:\n\t\treturn V2_7_0_0\n\tcase 1:\n\t\treturn V2_0_0_0\n\tdefault:\n\t\treturn V0_11_0_0\n\t}\n}\n\nfunc (r *AddPartitionsToTxnResponse) throttleTime() time.Duration {\n\treturn r.ThrottleTime\n}\n\n// PartitionError is a partition error type\ntype PartitionError struct {\n\tPartition int32\n\tErr       KError\n}\n\nfunc (p *PartitionError) encode(pe packetEncoder) error {\n\tpe.putInt32(p.Partition)\n\tpe.putKError(p.Err)\n\treturn nil\n}\n\nfunc (p *PartitionError) decode(pd packetDecoder, version int16) (err error) {\n\tif p.Partition, err = pd.getInt32(); err != nil {\n\t\treturn err\n\t}\n\n\tp.Err, err = pd.getKError()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "add_partitions_to_txn_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nvar addPartitionsToTxnResponse = []byte{\n\t0, 0, 0, 100,\n\t0, 0, 0, 1,\n\t0, 5, 't', 'o', 'p', 'i', 'c',\n\t0, 0, 0, 1, // 1 partition error\n\t0, 0, 0, 2, // partition 2\n\t0, 48, // error\n}\n\nfunc TestAddPartitionsToTxnResponse(t *testing.T) {\n\tresp := &AddPartitionsToTxnResponse{\n\t\tThrottleTime: 100 * time.Millisecond,\n\t\tErrors: map[string][]*PartitionError{\n\t\t\t\"topic\": {{\n\t\t\t\tErr:       ErrInvalidTxnState,\n\t\t\t\tPartition: 2,\n\t\t\t}},\n\t\t},\n\t}\n\n\ttestResponse(t, \"\", resp, addPartitionsToTxnResponse)\n}\n"
  },
  {
    "path": "admin.go",
    "content": "package sarama\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"maps\"\n\t\"math/rand\"\n\t\"net\"\n\t\"strconv\"\n\t\"sync\"\n\t\"time\"\n)\n\n// ClusterAdmin is the administrative client for Kafka, which supports managing and inspecting topics,\n// brokers, configurations and ACLs. The minimum broker version required is 0.10.0.0.\n// Methods with stricter requirements will specify the minimum broker version required.\n// You MUST call Close() on a client to avoid leaks\ntype ClusterAdmin interface {\n\t// Creates a new topic. This operation is supported by brokers with version 0.10.1.0 or higher.\n\t// It may take several seconds after CreateTopic returns success for all the brokers\n\t// to become aware that the topic has been created. During this time, listTopics\n\t// may not return information about the new topic.The validateOnly option is supported from version 0.10.2.0.\n\tCreateTopic(topic string, detail *TopicDetail, validateOnly bool) error\n\n\t// List the topics available in the cluster with the default options.\n\tListTopics() (map[string]TopicDetail, error)\n\n\t// Describe some topics in the cluster.\n\tDescribeTopics(topics []string) (metadata []*TopicMetadata, err error)\n\n\t// Delete a topic. It may take several seconds after the DeleteTopic to returns success\n\t// and for all the brokers to become aware that the topics are gone.\n\t// During this time, listTopics  may continue to return information about the deleted topic.\n\t// If delete.topic.enable is false on the brokers, deleteTopic will mark\n\t// the topic for deletion, but not actually delete them.\n\t// This operation is supported by brokers with version 0.10.1.0 or higher.\n\tDeleteTopic(topic string) error\n\n\t// Increase the number of partitions of the topics  according to the corresponding values.\n\t// If partitions are increased for a topic that has a key, the partition logic or ordering of\n\t// the messages will be affected. It may take several seconds after this method returns\n\t// success for all the brokers to become aware that the partitions have been created.\n\t// During this time, ClusterAdmin#describeTopics may not return information about the\n\t// new partitions. This operation is supported by brokers with version 1.0.0 or higher.\n\tCreatePartitions(topic string, count int32, assignment [][]int32, validateOnly bool) error\n\n\t// Alter the replica assignment for partitions.\n\t// This operation is supported by brokers with version 2.4.0.0 or higher.\n\tAlterPartitionReassignments(topic string, assignment [][]int32) error\n\n\t// Provides info on ongoing partitions replica reassignments.\n\t// This operation is supported by brokers with version 2.4.0.0 or higher.\n\tListPartitionReassignments(topics string, partitions []int32) (topicStatus map[string]map[int32]*PartitionReplicaReassignmentsStatus, err error)\n\n\t// Delete records whose offset is smaller than the given offset of the corresponding partition.\n\t// This operation is supported by brokers with version 0.11.0.0 or higher.\n\tDeleteRecords(topic string, partitionOffsets map[int32]int64) error\n\n\t// Get the configuration for the specified resources.\n\t// The returned configuration includes default values and the Default is true\n\t// can be used to distinguish them from user supplied values.\n\t// Config entries where ReadOnly is true cannot be updated.\n\t// The value of config entries where Sensitive is true is always nil so\n\t// sensitive information is not disclosed.\n\t// This operation is supported by brokers with version 0.11.0.0 or higher.\n\tDescribeConfig(resource ConfigResource) ([]ConfigEntry, error)\n\n\t// Update the configuration for the specified resources with the default options.\n\t// This operation is supported by brokers with version 0.11.0.0 or higher.\n\t// The resources with their configs (topic is the only resource type with configs\n\t// that can be updated currently Updates are not transactional so they may succeed\n\t// for some resources while fail for others. The configs for a particular resource are updated automatically.\n\tAlterConfig(resourceType ConfigResourceType, name string, entries map[string]*string, validateOnly bool) error\n\n\t// IncrementalAlterConfig Incrementally Update the configuration for the specified resources with the default options.\n\t// This operation is supported by brokers with version 2.3.0.0 or higher.\n\t// Updates are not transactional so they may succeed for some resources while fail for others.\n\t// The configs for a particular resource are updated automatically.\n\tIncrementalAlterConfig(resourceType ConfigResourceType, name string, entries map[string]IncrementalAlterConfigsEntry, validateOnly bool) error\n\n\t// Creates an access control list (ACL) which is bound to a specific resource.\n\t// This operation is not transactional so it may succeed or fail.\n\t// If you attempt to add an ACL that duplicates an existing ACL, no error will be raised, but\n\t// no changes will be made. This operation is supported by brokers with version 0.11.0.0 or higher.\n\t//\n\t// Deprecated: Use CreateACLs instead.\n\tCreateACL(resource Resource, acl Acl) error\n\n\t// Creates access control lists (ACLs) which are bound to specific resources.\n\t// This operation is not transactional so it may succeed for some ACLs while fail for others.\n\t// If you attempt to add an ACL that duplicates an existing ACL, no error will be raised, but\n\t// no changes will be made. This operation is supported by brokers with version 0.11.0.0 or higher.\n\tCreateACLs([]*ResourceAcls) error\n\n\t// Lists access control lists (ACLs) according to the supplied filter.\n\t// it may take some time for changes made by createAcls or deleteAcls to be reflected in the output of ListAcls\n\t// This operation is supported by brokers with version 0.11.0.0 or higher.\n\tListAcls(filter AclFilter) ([]ResourceAcls, error)\n\n\t// Deletes access control lists (ACLs) according to the supplied filters.\n\t// This operation is not transactional so it may succeed for some ACLs while fail for others.\n\t// This operation is supported by brokers with version 0.11.0.0 or higher.\n\tDeleteACL(filter AclFilter, validateOnly bool) ([]MatchingAcl, error)\n\n\t// ElectLeaders allows to trigger the election of preferred leaders for a set of partitions.\n\tElectLeaders(ElectionType, map[string][]int32) (map[string]map[int32]*PartitionResult, error)\n\n\t// List the consumer groups available in the cluster.\n\tListConsumerGroups() (map[string]string, error)\n\n\t// Describe the given consumer groups.\n\tDescribeConsumerGroups(groups []string) ([]*GroupDescription, error)\n\n\t// List the consumer group offsets available in the cluster.\n\tListConsumerGroupOffsets(group string, topicPartitions map[string][]int32) (*OffsetFetchResponse, error)\n\n\t// Deletes a consumer group offset\n\tDeleteConsumerGroupOffset(group string, topic string, partition int32) error\n\n\t// Delete a consumer group.\n\tDeleteConsumerGroup(group string) error\n\n\t// Get information about the nodes in the cluster\n\tDescribeCluster() (brokers []*Broker, controllerID int32, err error)\n\n\t// Get information about all log directories on the given set of brokers\n\tDescribeLogDirs(brokers []int32) (map[int32][]DescribeLogDirsResponseDirMetadata, error)\n\n\t// Get information about SCRAM users\n\tDescribeUserScramCredentials(users []string) ([]*DescribeUserScramCredentialsResult, error)\n\n\t// Delete SCRAM users\n\tDeleteUserScramCredentials(delete []AlterUserScramCredentialsDelete) ([]*AlterUserScramCredentialsResult, error)\n\n\t// Upsert SCRAM users\n\tUpsertUserScramCredentials(upsert []AlterUserScramCredentialsUpsert) ([]*AlterUserScramCredentialsResult, error)\n\n\t// Get client quota configurations corresponding to the specified filter.\n\t// This operation is supported by brokers with version 2.6.0.0 or higher.\n\tDescribeClientQuotas(components []QuotaFilterComponent, strict bool) ([]DescribeClientQuotasEntry, error)\n\n\t// Alters client quota configurations with the specified alterations.\n\t// This operation is supported by brokers with version 2.6.0.0 or higher.\n\tAlterClientQuotas(entity []QuotaEntityComponent, op ClientQuotasOp, validateOnly bool) error\n\n\t// Controller returns the cluster controller broker. It will return a\n\t// locally cached value if it's available.\n\tController() (*Broker, error)\n\n\t// Coordinator returns the coordinating broker for a consumer group. It will\n\t// return a locally cached value if it's available.\n\tCoordinator(group string) (*Broker, error)\n\n\t// Remove members from the consumer group by given member identities.\n\t// This operation is supported by brokers with version 2.3 or higher\n\t// This is for static membership feature. KIP-345\n\tRemoveMemberFromConsumerGroup(groupId string, groupInstanceIds []string) (*LeaveGroupResponse, error)\n\n\t// Close shuts down the admin and closes underlying client.\n\tClose() error\n}\n\ntype clusterAdmin struct {\n\tclient Client\n\tconf   *Config\n}\n\n// NewClusterAdmin creates a new ClusterAdmin using the given broker addresses and configuration.\nfunc NewClusterAdmin(addrs []string, conf *Config) (ClusterAdmin, error) {\n\tclient, err := NewClient(addrs, conf)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tadmin, err := NewClusterAdminFromClient(client)\n\tif err != nil {\n\t\tclient.Close()\n\t}\n\treturn admin, err\n}\n\n// NewClusterAdminFromClient creates a new ClusterAdmin using the given client.\n// Note that underlying client will also be closed on admin's Close() call.\nfunc NewClusterAdminFromClient(client Client) (ClusterAdmin, error) {\n\t// make sure we can retrieve the controller\n\t_, err := client.Controller()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tca := &clusterAdmin{\n\t\tclient: client,\n\t\tconf:   client.Config(),\n\t}\n\treturn ca, nil\n}\n\nfunc (ca *clusterAdmin) Close() error {\n\treturn ca.client.Close()\n}\n\nfunc (ca *clusterAdmin) Controller() (*Broker, error) {\n\treturn ca.client.Controller()\n}\n\nfunc (ca *clusterAdmin) Coordinator(group string) (*Broker, error) {\n\treturn ca.client.Coordinator(group)\n}\n\nfunc (ca *clusterAdmin) refreshController() (*Broker, error) {\n\treturn ca.client.RefreshController()\n}\n\n// isRetriableControllerError returns `true` if the given error type unwraps to\n// an `ErrNotController` or `EOF` response from Kafka\nfunc isRetriableControllerError(err error) bool {\n\treturn errors.Is(err, ErrNotController) || errors.Is(err, io.EOF)\n}\n\n// isRetriableGroupCoordinatorError returns `true` if the given error type\n// unwraps to an `ErrNotCoordinatorForConsumer`,\n// `ErrConsumerCoordinatorNotAvailable` or `EOF` response from Kafka\nfunc isRetriableGroupCoordinatorError(err error) bool {\n\treturn errors.Is(err, ErrNotCoordinatorForConsumer) || errors.Is(err, ErrConsumerCoordinatorNotAvailable) || errors.Is(err, io.EOF)\n}\n\n// retryOnError will repeatedly call the given (error-returning) func in the\n// case that its response is non-nil and retryable (as determined by the\n// provided retryable func) up to the maximum number of tries permitted by\n// the admin client configuration\nfunc (ca *clusterAdmin) retryOnError(retryable func(error) bool, fn func() error) error {\n\tfor attemptsRemaining := ca.conf.Admin.Retry.Max + 1; ; {\n\t\terr := fn()\n\t\tattemptsRemaining--\n\t\tif err == nil || attemptsRemaining <= 0 || !retryable(err) {\n\t\t\treturn err\n\t\t}\n\t\tLogger.Printf(\n\t\t\t\"admin/request retrying after %dms... (%d attempts remaining)\\n\",\n\t\t\tca.conf.Admin.Retry.Backoff/time.Millisecond, attemptsRemaining)\n\t\ttime.Sleep(ca.conf.Admin.Retry.Backoff)\n\t}\n}\n\nfunc (ca *clusterAdmin) CreateTopic(topic string, detail *TopicDetail, validateOnly bool) error {\n\tif topic == \"\" {\n\t\treturn ErrInvalidTopic\n\t}\n\n\tif detail == nil {\n\t\treturn errors.New(\"you must specify topic details\")\n\t}\n\n\ttopicDetails := map[string]*TopicDetail{\n\t\ttopic: detail,\n\t}\n\n\trequest := NewCreateTopicsRequest(\n\t\tca.conf.Version,\n\t\ttopicDetails,\n\t\tca.conf.Admin.Timeout,\n\t\tvalidateOnly,\n\t)\n\n\treturn ca.retryOnError(isRetriableControllerError, func() error {\n\t\tb, err := ca.Controller()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\trsp, err := b.CreateTopics(request)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\ttopicErr, ok := rsp.TopicErrors[topic]\n\t\tif !ok {\n\t\t\treturn ErrIncompleteResponse\n\t\t}\n\n\t\tif !errors.Is(topicErr.Err, ErrNoError) {\n\t\t\tif isRetriableControllerError(topicErr.Err) {\n\t\t\t\t_, _ = ca.refreshController()\n\t\t\t}\n\t\t\treturn topicErr\n\t\t}\n\n\t\treturn nil\n\t})\n}\n\nfunc (ca *clusterAdmin) DescribeTopics(topics []string) (metadata []*TopicMetadata, err error) {\n\tvar response *MetadataResponse\n\terr = ca.retryOnError(isRetriableControllerError, func() error {\n\t\tcontroller, err := ca.Controller()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\trequest := NewMetadataRequest(ca.conf.Version, topics)\n\t\tresponse, err = controller.GetMetadata(request)\n\t\tif isRetriableControllerError(err) {\n\t\t\t_, _ = ca.refreshController()\n\t\t}\n\t\treturn err\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn response.Topics, nil\n}\n\nfunc (ca *clusterAdmin) DescribeCluster() (brokers []*Broker, controllerID int32, err error) {\n\tif ca.conf.Version.IsAtLeast(V2_8_0_0) {\n\t\tbrokers, controllerID, err = ca.describeClusterUsingAPI()\n\t\tif err == nil {\n\t\t\treturn brokers, controllerID, nil\n\t\t}\n\t\tif !errors.Is(err, ErrUnsupportedVersion) {\n\t\t\treturn nil, 0, err\n\t\t}\n\t}\n\treturn ca.describeClusterUsingMetadata()\n}\n\nfunc (ca *clusterAdmin) describeClusterUsingAPI() (brokers []*Broker, controllerID int32, err error) {\n\tvar response *DescribeClusterResponse\n\terr = ca.retryOnError(isRetriableControllerError, func() error {\n\t\tcontroller, err := ca.Controller()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\trequest := NewDescribeClusterRequest(ca.conf.Version)\n\t\tresponse, err = controller.DescribeCluster(request)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !errors.Is(response.Err, ErrNoError) {\n\t\t\tif isRetriableControllerError(response.Err) {\n\t\t\t\t_, _ = ca.refreshController()\n\t\t\t}\n\t\t\tif response.ErrorMessage != nil && *response.ErrorMessage != \"\" {\n\t\t\t\treturn fmt.Errorf(\"%w: %s\", response.Err, *response.ErrorMessage)\n\t\t\t}\n\t\t\treturn response.Err\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tbrokers = convertDescribeClusterBrokers(response.Brokers)\n\treturn brokers, response.ControllerID, nil\n}\n\nfunc (ca *clusterAdmin) describeClusterUsingMetadata() (brokers []*Broker, controllerID int32, err error) {\n\tvar response *MetadataResponse\n\terr = ca.retryOnError(isRetriableControllerError, func() error {\n\t\tcontroller, err := ca.Controller()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\trequest := NewMetadataRequest(ca.conf.Version, nil)\n\t\tresponse, err = controller.GetMetadata(request)\n\t\tif isRetriableControllerError(err) {\n\t\t\t_, _ = ca.refreshController()\n\t\t}\n\t\treturn err\n\t})\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treturn response.Brokers, response.ControllerID, nil\n}\n\nfunc convertDescribeClusterBrokers(entries []*DescribeClusterBroker) []*Broker {\n\t// TODO: DescribeCluster brokers currently drop DescribeCluster-specific fields\n\t// such as IsFenced (KIP-1073) and ClusterAuthorizedOperations because Broker\n\t// has no equivalents yet. This keeps API parity with MetadataResponse for now,\n\t// but the richer fields need to be surfaced in a future change.\n\tif len(entries) == 0 {\n\t\treturn nil\n\t}\n\tresult := make([]*Broker, 0, len(entries))\n\tfor _, info := range entries {\n\t\taddr := net.JoinHostPort(info.Host, strconv.Itoa(int(info.Port)))\n\t\tb := NewBroker(addr)\n\t\tb.id = info.BrokerID\n\t\tb.rack = info.Rack\n\t\tresult = append(result, b)\n\t}\n\treturn result\n}\n\nfunc (ca *clusterAdmin) findBroker(id int32) (*Broker, error) {\n\tbrokers := ca.client.Brokers()\n\tfor _, b := range brokers {\n\t\tif b.ID() == id {\n\t\t\treturn b, nil\n\t\t}\n\t}\n\treturn nil, fmt.Errorf(\"could not find broker id %d\", id)\n}\n\nfunc (ca *clusterAdmin) findAnyBroker() (*Broker, error) {\n\tbrokers := ca.client.Brokers()\n\tif len(brokers) > 0 {\n\t\tindex := rand.Intn(len(brokers))\n\t\treturn brokers[index], nil\n\t}\n\treturn nil, errors.New(\"no available broker\")\n}\n\nfunc (ca *clusterAdmin) ListTopics() (map[string]TopicDetail, error) {\n\t// In order to build TopicDetails we need to first get the list of all\n\t// topics using a MetadataRequest and then get their configs using a\n\t// DescribeConfigsRequest request. To avoid sending many requests to the\n\t// broker, we use a single DescribeConfigsRequest.\n\n\t// Send the all-topic MetadataRequest\n\tb, err := ca.findAnyBroker()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t_ = b.Open(ca.client.Config())\n\n\tmetadataReq := NewMetadataRequest(ca.conf.Version, nil)\n\tmetadataResp, err := b.GetMetadata(metadataReq)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttopicsDetailsMap := make(map[string]TopicDetail, len(metadataResp.Topics))\n\n\tvar describeConfigsResources []*ConfigResource\n\n\tfor _, topic := range metadataResp.Topics {\n\t\ttopicDetails := TopicDetail{\n\t\t\tNumPartitions: int32(len(topic.Partitions)),\n\t\t}\n\t\tif len(topic.Partitions) > 0 {\n\t\t\ttopicDetails.ReplicaAssignment = make(map[int32][]int32, len(topic.Partitions))\n\t\t\tfor _, partition := range topic.Partitions {\n\t\t\t\ttopicDetails.ReplicaAssignment[partition.ID] = partition.Replicas\n\t\t\t}\n\t\t\ttopicDetails.ReplicationFactor = int16(len(topic.Partitions[0].Replicas))\n\t\t}\n\t\ttopicsDetailsMap[topic.Name] = topicDetails\n\n\t\t// we populate the resources we want to describe from the MetadataResponse\n\t\ttopicResource := ConfigResource{\n\t\t\tType: TopicResource,\n\t\t\tName: topic.Name,\n\t\t}\n\t\tdescribeConfigsResources = append(describeConfigsResources, &topicResource)\n\t}\n\n\t// Send the DescribeConfigsRequest\n\tdescribeConfigsReq := &DescribeConfigsRequest{\n\t\tResources: describeConfigsResources,\n\t}\n\n\tif ca.conf.Version.IsAtLeast(V1_1_0_0) {\n\t\tdescribeConfigsReq.Version = 1\n\t}\n\n\tif ca.conf.Version.IsAtLeast(V2_0_0_0) {\n\t\tdescribeConfigsReq.Version = 2\n\t}\n\n\tdescribeConfigsResp, err := b.DescribeConfigs(describeConfigsReq)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, resource := range describeConfigsResp.Resources {\n\t\ttopicDetails := topicsDetailsMap[resource.Name]\n\t\ttopicDetails.ConfigEntries = make(map[string]*string)\n\n\t\tfor _, entry := range resource.Configs {\n\t\t\t// only include non-default non-sensitive config\n\t\t\t// (don't actually think topic config will ever be sensitive)\n\t\t\tif entry.Default || entry.Sensitive {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\ttopicDetails.ConfigEntries[entry.Name] = &entry.Value\n\t\t}\n\n\t\ttopicsDetailsMap[resource.Name] = topicDetails\n\t}\n\n\treturn topicsDetailsMap, nil\n}\n\nfunc (ca *clusterAdmin) DeleteTopic(topic string) error {\n\tif topic == \"\" {\n\t\treturn ErrInvalidTopic\n\t}\n\n\trequest := &DeleteTopicsRequest{\n\t\tTopics:  []string{topic},\n\t\tTimeout: ca.conf.Admin.Timeout,\n\t}\n\n\t// Versions 0, 1, 2, and 3 are the same.\n\t// Version 4 is first flexible version.\n\tif ca.conf.Version.IsAtLeast(V2_4_0_0) {\n\t\trequest.Version = 4\n\t} else if ca.conf.Version.IsAtLeast(V2_1_0_0) {\n\t\trequest.Version = 3\n\t} else if ca.conf.Version.IsAtLeast(V2_0_0_0) {\n\t\trequest.Version = 2\n\t} else if ca.conf.Version.IsAtLeast(V0_11_0_0) {\n\t\trequest.Version = 1\n\t}\n\n\treturn ca.retryOnError(isRetriableControllerError, func() error {\n\t\tb, err := ca.Controller()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\trsp, err := b.DeleteTopics(request)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\ttopicErr, ok := rsp.TopicErrorCodes[topic]\n\t\tif !ok {\n\t\t\treturn ErrIncompleteResponse\n\t\t}\n\n\t\tif !errors.Is(topicErr, ErrNoError) {\n\t\t\tif errors.Is(topicErr, ErrNotController) {\n\t\t\t\t_, _ = ca.refreshController()\n\t\t\t}\n\t\t\treturn topicErr\n\t\t}\n\n\t\treturn nil\n\t})\n}\n\nfunc (ca *clusterAdmin) CreatePartitions(topic string, count int32, assignment [][]int32, validateOnly bool) error {\n\tif topic == \"\" {\n\t\treturn ErrInvalidTopic\n\t}\n\n\ttopicPartitions := map[string]*TopicPartition{\n\t\ttopic: {\n\t\t\tCount:      count,\n\t\t\tAssignment: assignment,\n\t\t},\n\t}\n\n\trequest := &CreatePartitionsRequest{\n\t\tTopicPartitions: topicPartitions,\n\t\tTimeout:         ca.conf.Admin.Timeout,\n\t\tValidateOnly:    validateOnly,\n\t}\n\tif ca.conf.Version.IsAtLeast(V2_0_0_0) {\n\t\trequest.Version = 1\n\t}\n\n\treturn ca.retryOnError(isRetriableControllerError, func() error {\n\t\tb, err := ca.Controller()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\trsp, err := b.CreatePartitions(request)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\ttopicErr, ok := rsp.TopicPartitionErrors[topic]\n\t\tif !ok {\n\t\t\treturn ErrIncompleteResponse\n\t\t}\n\n\t\tif !errors.Is(topicErr.Err, ErrNoError) {\n\t\t\tif errors.Is(topicErr.Err, ErrNotController) {\n\t\t\t\t_, _ = ca.refreshController()\n\t\t\t}\n\t\t\treturn topicErr\n\t\t}\n\n\t\treturn nil\n\t})\n}\n\nfunc (ca *clusterAdmin) AlterPartitionReassignments(topic string, assignment [][]int32) error {\n\tif topic == \"\" {\n\t\treturn ErrInvalidTopic\n\t}\n\n\trequest := &AlterPartitionReassignmentsRequest{\n\t\tTimeoutMs: int32(60000),\n\t\tVersion:   int16(0),\n\t}\n\n\tfor i := 0; i < len(assignment); i++ {\n\t\trequest.AddBlock(topic, int32(i), assignment[i])\n\t}\n\n\treturn ca.retryOnError(isRetriableControllerError, func() error {\n\t\tb, err := ca.Controller()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\terrs := make([]error, 0)\n\n\t\trsp, err := b.AlterPartitionReassignments(request)\n\n\t\tif err != nil {\n\t\t\terrs = append(errs, err)\n\t\t} else {\n\t\t\tif rsp.ErrorCode > 0 {\n\t\t\t\terrs = append(errs, rsp.ErrorCode)\n\t\t\t}\n\n\t\t\tfor topic, topicErrors := range rsp.Errors {\n\t\t\t\tfor partition, partitionError := range topicErrors {\n\t\t\t\t\tif !errors.Is(partitionError.errorCode, ErrNoError) {\n\t\t\t\t\t\terrs = append(errs, fmt.Errorf(\"[%s-%d]: %w\", topic, partition, partitionError.errorCode))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif len(errs) > 0 {\n\t\t\treturn Wrap(ErrReassignPartitions, errs...)\n\t\t}\n\n\t\treturn nil\n\t})\n}\n\nfunc (ca *clusterAdmin) ListPartitionReassignments(topic string, partitions []int32) (topicStatus map[string]map[int32]*PartitionReplicaReassignmentsStatus, err error) {\n\tif topic == \"\" {\n\t\treturn nil, ErrInvalidTopic\n\t}\n\n\trequest := &ListPartitionReassignmentsRequest{\n\t\tTimeoutMs: int32(60000),\n\t\tVersion:   int16(0),\n\t}\n\n\trequest.AddBlock(topic, partitions)\n\n\tvar rsp *ListPartitionReassignmentsResponse\n\terr = ca.retryOnError(isRetriableControllerError, func() error {\n\t\tb, err := ca.Controller()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_ = b.Open(ca.client.Config())\n\n\t\trsp, err = b.ListPartitionReassignments(request)\n\t\tif isRetriableControllerError(err) {\n\t\t\t_, _ = ca.refreshController()\n\t\t}\n\t\treturn err\n\t})\n\n\tif err == nil && rsp != nil {\n\t\treturn rsp.TopicStatus, nil\n\t} else {\n\t\treturn nil, err\n\t}\n}\n\nfunc (ca *clusterAdmin) DeleteRecords(topic string, partitionOffsets map[int32]int64) error {\n\tif topic == \"\" {\n\t\treturn ErrInvalidTopic\n\t}\n\terrs := make([]error, 0)\n\tpartitionPerBroker := make(map[*Broker][]int32)\n\tfor partition := range partitionOffsets {\n\t\tbroker, err := ca.client.Leader(topic, partition)\n\t\tif err != nil {\n\t\t\terrs = append(errs, err)\n\t\t\tcontinue\n\t\t}\n\t\tpartitionPerBroker[broker] = append(partitionPerBroker[broker], partition)\n\t}\n\tfor broker, partitions := range partitionPerBroker {\n\t\trecordsToDelete := make(map[int32]int64, len(partitions))\n\t\tfor _, p := range partitions {\n\t\t\trecordsToDelete[p] = partitionOffsets[p]\n\t\t}\n\t\ttopics := map[string]*DeleteRecordsRequestTopic{\n\t\t\ttopic: {\n\t\t\t\tPartitionOffsets: recordsToDelete,\n\t\t\t},\n\t\t}\n\t\trequest := &DeleteRecordsRequest{\n\t\t\tTopics:  topics,\n\t\t\tTimeout: ca.conf.Admin.Timeout,\n\t\t}\n\t\tif ca.conf.Version.IsAtLeast(V2_0_0_0) {\n\t\t\trequest.Version = 1\n\t\t}\n\t\trsp, err := broker.DeleteRecords(request)\n\t\tif err != nil {\n\t\t\terrs = append(errs, err)\n\t\t\tcontinue\n\t\t}\n\n\t\tdeleteRecordsResponseTopic, ok := rsp.Topics[topic]\n\t\tif !ok {\n\t\t\terrs = append(errs, ErrIncompleteResponse)\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, deleteRecordsResponsePartition := range deleteRecordsResponseTopic.Partitions {\n\t\t\tif !errors.Is(deleteRecordsResponsePartition.Err, ErrNoError) {\n\t\t\t\terrs = append(errs, deleteRecordsResponsePartition.Err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t}\n\tif len(errs) > 0 {\n\t\treturn Wrap(ErrDeleteRecords, errs...)\n\t}\n\t// todo since we are dealing with couple of partitions it would be good if we return slice of errors\n\t// for each partition instead of one error\n\treturn nil\n}\n\n// Returns a bool indicating whether the resource request needs to go to a\n// specific broker\nfunc dependsOnSpecificNode(resource ConfigResource) bool {\n\treturn (resource.Type == BrokerResource && resource.Name != \"\") ||\n\t\tresource.Type == BrokerLoggerResource\n}\n\nfunc (ca *clusterAdmin) DescribeConfig(resource ConfigResource) ([]ConfigEntry, error) {\n\tvar entries []ConfigEntry\n\tvar resources []*ConfigResource\n\tresources = append(resources, &resource)\n\n\trequest := &DescribeConfigsRequest{\n\t\tResources: resources,\n\t}\n\n\tif ca.conf.Version.IsAtLeast(V1_1_0_0) {\n\t\trequest.Version = 1\n\t}\n\n\tif ca.conf.Version.IsAtLeast(V2_0_0_0) {\n\t\trequest.Version = 2\n\t}\n\n\tvar (\n\t\tb   *Broker\n\t\terr error\n\t)\n\n\t// DescribeConfig of broker/broker logger must be sent to the broker in question\n\tif dependsOnSpecificNode(resource) {\n\t\tvar id int64\n\t\tid, err = strconv.ParseInt(resource.Name, 10, 32)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb, err = ca.findBroker(int32(id))\n\t} else {\n\t\tb, err = ca.findAnyBroker()\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t_ = b.Open(ca.client.Config())\n\trsp, err := b.DescribeConfigs(request)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, rspResource := range rsp.Resources {\n\t\tif rspResource.Name == resource.Name {\n\t\t\tif rspResource.ErrorCode != 0 {\n\t\t\t\treturn nil, &DescribeConfigError{Err: KError(rspResource.ErrorCode), ErrMsg: rspResource.ErrorMsg}\n\t\t\t}\n\t\t\tfor _, cfgEntry := range rspResource.Configs {\n\t\t\t\tentries = append(entries, *cfgEntry)\n\t\t\t}\n\t\t}\n\t}\n\treturn entries, nil\n}\n\nfunc (ca *clusterAdmin) AlterConfig(resourceType ConfigResourceType, name string, entries map[string]*string, validateOnly bool) error {\n\tvar resources []*AlterConfigsResource\n\tresources = append(resources, &AlterConfigsResource{\n\t\tType:          resourceType,\n\t\tName:          name,\n\t\tConfigEntries: entries,\n\t})\n\n\trequest := &AlterConfigsRequest{\n\t\tResources:    resources,\n\t\tValidateOnly: validateOnly,\n\t}\n\tif ca.conf.Version.IsAtLeast(V2_0_0_0) {\n\t\trequest.Version = 1\n\t}\n\n\tvar (\n\t\tb   *Broker\n\t\terr error\n\t)\n\n\t// AlterConfig of broker/broker logger must be sent to the broker in question\n\tif dependsOnSpecificNode(ConfigResource{Name: name, Type: resourceType}) {\n\t\tvar id int64\n\t\tid, err = strconv.ParseInt(name, 10, 32)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tb, err = ca.findBroker(int32(id))\n\t} else {\n\t\tb, err = ca.findAnyBroker()\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_ = b.Open(ca.client.Config())\n\trsp, err := b.AlterConfigs(request)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, rspResource := range rsp.Resources {\n\t\tif rspResource.Name == name {\n\t\t\tif rspResource.ErrorCode != 0 {\n\t\t\t\treturn &AlterConfigError{Err: KError(rspResource.ErrorCode), ErrMsg: rspResource.ErrorMsg}\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (ca *clusterAdmin) IncrementalAlterConfig(resourceType ConfigResourceType, name string, entries map[string]IncrementalAlterConfigsEntry, validateOnly bool) error {\n\tvar resources []*IncrementalAlterConfigsResource\n\tresources = append(resources, &IncrementalAlterConfigsResource{\n\t\tType:          resourceType,\n\t\tName:          name,\n\t\tConfigEntries: entries,\n\t})\n\n\trequest := &IncrementalAlterConfigsRequest{\n\t\tResources:    resources,\n\t\tValidateOnly: validateOnly,\n\t}\n\n\tif ca.conf.Version.IsAtLeast(V2_4_0_0) {\n\t\trequest.Version = 1\n\t}\n\n\tvar (\n\t\tb   *Broker\n\t\terr error\n\t)\n\n\t// AlterConfig of broker/broker logger must be sent to the broker in question\n\tif dependsOnSpecificNode(ConfigResource{Name: name, Type: resourceType}) {\n\t\tvar id int64\n\t\tid, err = strconv.ParseInt(name, 10, 32)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tb, err = ca.findBroker(int32(id))\n\t} else {\n\t\tb, err = ca.findAnyBroker()\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_ = b.Open(ca.client.Config())\n\trsp, err := b.IncrementalAlterConfigs(request)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, rspResource := range rsp.Resources {\n\t\tif rspResource.Name == name {\n\t\t\tif rspResource.ErrorCode != int16(ErrNoError) {\n\t\t\t\terr = KError(rspResource.ErrorCode)\n\t\t\t\tif rspResource.ErrorMsg != \"\" {\n\t\t\t\t\terr = fmt.Errorf(\"%w: %s\", err, rspResource.ErrorMsg)\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (ca *clusterAdmin) CreateACL(resource Resource, acl Acl) error {\n\tvar acls []*AclCreation\n\tacls = append(acls, &AclCreation{resource, acl})\n\trequest := &CreateAclsRequest{AclCreations: acls}\n\n\tif ca.conf.Version.IsAtLeast(V2_0_0_0) {\n\t\trequest.Version = 1\n\t}\n\n\tb, err := ca.Controller()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = b.CreateAcls(request)\n\treturn err\n}\n\nfunc (ca *clusterAdmin) CreateACLs(resourceACLs []*ResourceAcls) error {\n\tvar acls []*AclCreation\n\tfor _, resourceACL := range resourceACLs {\n\t\tfor _, acl := range resourceACL.Acls {\n\t\t\tacls = append(acls, &AclCreation{resourceACL.Resource, *acl})\n\t\t}\n\t}\n\trequest := &CreateAclsRequest{AclCreations: acls}\n\n\tif ca.conf.Version.IsAtLeast(V2_0_0_0) {\n\t\trequest.Version = 1\n\t}\n\n\tb, err := ca.Controller()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = b.CreateAcls(request)\n\treturn err\n}\n\nfunc (ca *clusterAdmin) ListAcls(filter AclFilter) ([]ResourceAcls, error) {\n\trequest := &DescribeAclsRequest{AclFilter: filter}\n\n\tif ca.conf.Version.IsAtLeast(V2_0_0_0) {\n\t\trequest.Version = 1\n\t}\n\n\tb, err := ca.Controller()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trsp, err := b.DescribeAcls(request)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar lAcls []ResourceAcls\n\tfor _, rAcl := range rsp.ResourceAcls {\n\t\tlAcls = append(lAcls, *rAcl)\n\t}\n\treturn lAcls, nil\n}\n\nfunc (ca *clusterAdmin) DeleteACL(filter AclFilter, validateOnly bool) ([]MatchingAcl, error) {\n\tvar filters []*AclFilter\n\tfilters = append(filters, &filter)\n\trequest := &DeleteAclsRequest{Filters: filters}\n\n\tif ca.conf.Version.IsAtLeast(V2_0_0_0) {\n\t\trequest.Version = 1\n\t}\n\n\tb, err := ca.Controller()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trsp, err := b.DeleteAcls(request)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar mAcls []MatchingAcl\n\tfor _, fr := range rsp.FilterResponses {\n\t\tfor _, mACL := range fr.MatchingAcls {\n\t\t\tmAcls = append(mAcls, *mACL)\n\t\t}\n\t}\n\treturn mAcls, nil\n}\n\nfunc (ca *clusterAdmin) ElectLeaders(electionType ElectionType, partitions map[string][]int32) (map[string]map[int32]*PartitionResult, error) {\n\trequest := &ElectLeadersRequest{\n\t\tType:            electionType,\n\t\tTopicPartitions: partitions,\n\t\tTimeoutMs:       int32(60000),\n\t}\n\n\tif ca.conf.Version.IsAtLeast(V2_4_0_0) {\n\t\trequest.Version = 2\n\t} else if ca.conf.Version.IsAtLeast(V0_11_0_0) {\n\t\trequest.Version = 1\n\t}\n\n\tvar res *ElectLeadersResponse\n\tif err := ca.retryOnError(isRetriableControllerError, func() error {\n\t\tb, err := ca.Controller()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_ = b.Open(ca.client.Config())\n\n\t\tres, err = b.ElectLeaders(request)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !errors.Is(res.ErrorCode, ErrNoError) {\n\t\t\tif isRetriableControllerError(res.ErrorCode) {\n\t\t\t\t_, _ = ca.refreshController()\n\t\t\t}\n\t\t\treturn res.ErrorCode\n\t\t}\n\t\treturn nil\n\t}); err != nil {\n\t\treturn nil, err\n\t}\n\treturn res.ReplicaElectionResults, nil\n}\n\nfunc (ca *clusterAdmin) DescribeConsumerGroups(groups []string) (result []*GroupDescription, err error) {\n\tgroupsPerBroker := make(map[*Broker][]string)\n\n\tfor _, group := range groups {\n\t\tcoordinator, err := ca.client.Coordinator(group)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgroupsPerBroker[coordinator] = append(groupsPerBroker[coordinator], group)\n\t}\n\n\tfor broker, brokerGroups := range groupsPerBroker {\n\t\tdescribeReq := &DescribeGroupsRequest{\n\t\t\tGroups: brokerGroups,\n\t\t}\n\n\t\tif ca.conf.Version.IsAtLeast(V2_4_0_0) {\n\t\t\t// Starting in version 4, the response will include group.instance.id info for members.\n\t\t\t// Starting in version 5, the response uses flexible encoding\n\t\t\tdescribeReq.Version = 5\n\t\t} else if ca.conf.Version.IsAtLeast(V2_3_0_0) {\n\t\t\t// Starting in version 3, authorized operations can be requested.\n\t\t\tdescribeReq.Version = 3\n\t\t} else if ca.conf.Version.IsAtLeast(V2_0_0_0) {\n\t\t\t// Version 2 is the same as version 0.\n\t\t\tdescribeReq.Version = 2\n\t\t} else if ca.conf.Version.IsAtLeast(V1_1_0_0) {\n\t\t\t// Version 1 is the same as version 0.\n\t\t\tdescribeReq.Version = 1\n\t\t}\n\t\tresponse, err := broker.DescribeGroups(describeReq)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tresult = append(result, response.Groups...)\n\t}\n\treturn result, nil\n}\n\nfunc (ca *clusterAdmin) ListConsumerGroups() (allGroups map[string]string, err error) {\n\tallGroups = make(map[string]string)\n\n\t// Query brokers in parallel, since we have to query *all* brokers\n\tbrokers := ca.client.Brokers()\n\tgroupMaps := make(chan map[string]string, len(brokers))\n\terrChan := make(chan error, len(brokers))\n\twg := sync.WaitGroup{}\n\n\tfor _, b := range brokers {\n\t\twg.Add(1)\n\t\tgo func(b *Broker, conf *Config) {\n\t\t\tdefer wg.Done()\n\t\t\t_ = b.Open(conf) // Ensure that broker is opened\n\n\t\t\trequest := &ListGroupsRequest{}\n\t\t\tif ca.conf.Version.IsAtLeast(V3_8_0_0) {\n\t\t\t\t// Version 5 adds the TypesFilter field (KIP-848).\n\t\t\t\trequest.Version = 5\n\t\t\t} else if ca.conf.Version.IsAtLeast(V2_6_0_0) {\n\t\t\t\t// Version 4 adds the StatesFilter field (KIP-518).\n\t\t\t\trequest.Version = 4\n\t\t\t} else if ca.conf.Version.IsAtLeast(V2_4_0_0) {\n\t\t\t\t// Version 3 is the first flexible version.\n\t\t\t\trequest.Version = 3\n\t\t\t} else if ca.conf.Version.IsAtLeast(V2_0_0_0) {\n\t\t\t\t// Version 2 is the same as version 0.\n\t\t\t\trequest.Version = 2\n\t\t\t} else if ca.conf.Version.IsAtLeast(V0_11_0_0) {\n\t\t\t\t// Version 1 is the same as version 0.\n\t\t\t\trequest.Version = 1\n\t\t\t}\n\n\t\t\tresponse, err := b.ListGroups(request)\n\t\t\tif err != nil {\n\t\t\t\terrChan <- err\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tgroupMaps <- maps.Clone(response.Groups)\n\t\t}(b, ca.conf)\n\t}\n\n\twg.Wait()\n\tclose(groupMaps)\n\tclose(errChan)\n\n\tfor groupMap := range groupMaps {\n\t\tmaps.Copy(allGroups, groupMap)\n\t}\n\n\t// Intentionally return only the first error for simplicity\n\terr = <-errChan\n\treturn\n}\n\nfunc (ca *clusterAdmin) ListConsumerGroupOffsets(group string, topicPartitions map[string][]int32) (*OffsetFetchResponse, error) {\n\tvar response *OffsetFetchResponse\n\trequest := NewOffsetFetchRequest(ca.conf.Version, group, topicPartitions)\n\terr := ca.retryOnError(isRetriableGroupCoordinatorError, func() (err error) {\n\t\tdefer func() {\n\t\t\tif err != nil && isRetriableGroupCoordinatorError(err) {\n\t\t\t\t_ = ca.client.RefreshCoordinator(group)\n\t\t\t}\n\t\t}()\n\n\t\tcoordinator, err := ca.client.Coordinator(group)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tresponse, err = coordinator.FetchOffset(request)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !errors.Is(response.Err, ErrNoError) {\n\t\t\treturn response.Err\n\t\t}\n\n\t\treturn nil\n\t})\n\n\treturn response, err\n}\n\nfunc (ca *clusterAdmin) DeleteConsumerGroupOffset(group string, topic string, partition int32) error {\n\tvar response *DeleteOffsetsResponse\n\trequest := &DeleteOffsetsRequest{\n\t\tGroup: group,\n\t\tpartitions: map[string][]int32{\n\t\t\ttopic: {partition},\n\t\t},\n\t}\n\n\treturn ca.retryOnError(isRetriableGroupCoordinatorError, func() (err error) {\n\t\tdefer func() {\n\t\t\tif err != nil && isRetriableGroupCoordinatorError(err) {\n\t\t\t\t_ = ca.client.RefreshCoordinator(group)\n\t\t\t}\n\t\t}()\n\n\t\tcoordinator, err := ca.client.Coordinator(group)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tresponse, err = coordinator.DeleteOffsets(request)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !errors.Is(response.ErrorCode, ErrNoError) {\n\t\t\treturn response.ErrorCode\n\t\t}\n\t\tif !errors.Is(response.Errors[topic][partition], ErrNoError) {\n\t\t\treturn response.Errors[topic][partition]\n\t\t}\n\n\t\treturn nil\n\t})\n}\n\nfunc (ca *clusterAdmin) DeleteConsumerGroup(group string) error {\n\tvar response *DeleteGroupsResponse\n\trequest := &DeleteGroupsRequest{\n\t\tGroups: []string{group},\n\t}\n\n\tif ca.conf.Version.IsAtLeast(V2_4_0_0) {\n\t\trequest.Version = 2\n\t} else if ca.conf.Version.IsAtLeast(V2_0_0_0) {\n\t\trequest.Version = 1\n\t}\n\n\treturn ca.retryOnError(isRetriableGroupCoordinatorError, func() (err error) {\n\t\tdefer func() {\n\t\t\tif err != nil && isRetriableGroupCoordinatorError(err) {\n\t\t\t\t_ = ca.client.RefreshCoordinator(group)\n\t\t\t}\n\t\t}()\n\n\t\tcoordinator, err := ca.client.Coordinator(group)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tresponse, err = coordinator.DeleteGroups(request)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tgroupErr, ok := response.GroupErrorCodes[group]\n\t\tif !ok {\n\t\t\treturn ErrIncompleteResponse\n\t\t}\n\n\t\tif !errors.Is(groupErr, ErrNoError) {\n\t\t\treturn groupErr\n\t\t}\n\n\t\treturn nil\n\t})\n}\n\nfunc (ca *clusterAdmin) DescribeLogDirs(brokerIds []int32) (allLogDirs map[int32][]DescribeLogDirsResponseDirMetadata, err error) {\n\ttype result struct {\n\t\tid      int32\n\t\tlogdirs []DescribeLogDirsResponseDirMetadata\n\t}\n\t// Query brokers in parallel, since we may have to query multiple brokers\n\tlogDirsResults := make(chan result, len(brokerIds))\n\terrChan := make(chan error, len(brokerIds))\n\twg := sync.WaitGroup{}\n\n\tfor _, b := range brokerIds {\n\t\tbroker, err := ca.findBroker(b)\n\t\tif err != nil {\n\t\t\tLogger.Printf(\"Unable to find broker with ID = %v\\n\", b)\n\t\t\tcontinue\n\t\t}\n\t\twg.Add(1)\n\t\tgo func(b *Broker, conf *Config) {\n\t\t\tdefer wg.Done()\n\t\t\t_ = b.Open(conf) // Ensure that broker is opened\n\n\t\t\trequest := &DescribeLogDirsRequest{}\n\t\t\tif ca.conf.Version.IsAtLeast(V3_3_0_0) {\n\t\t\t\trequest.Version = 4\n\t\t\t} else if ca.conf.Version.IsAtLeast(V3_2_0_0) {\n\t\t\t\trequest.Version = 3\n\t\t\t} else if ca.conf.Version.IsAtLeast(V2_6_0_0) {\n\t\t\t\trequest.Version = 2\n\t\t\t} else if ca.conf.Version.IsAtLeast(V2_0_0_0) {\n\t\t\t\trequest.Version = 1\n\t\t\t}\n\t\t\tresponse, err := b.DescribeLogDirs(request)\n\t\t\tif err != nil {\n\t\t\t\terrChan <- err\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !errors.Is(response.ErrorCode, ErrNoError) {\n\t\t\t\terrChan <- response.ErrorCode\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlogDirsResults <- result{id: b.ID(), logdirs: response.LogDirs}\n\t\t}(broker, ca.conf)\n\t}\n\n\twg.Wait()\n\tclose(logDirsResults)\n\tclose(errChan)\n\n\tallLogDirs = make(map[int32][]DescribeLogDirsResponseDirMetadata, len(brokerIds))\n\tfor logDirsResult := range logDirsResults {\n\t\tallLogDirs[logDirsResult.id] = logDirsResult.logdirs\n\t}\n\n\t// Intentionally return only the first error for simplicity\n\terr = <-errChan\n\treturn\n}\n\nfunc (ca *clusterAdmin) DescribeUserScramCredentials(users []string) ([]*DescribeUserScramCredentialsResult, error) {\n\treq := &DescribeUserScramCredentialsRequest{}\n\tfor _, u := range users {\n\t\treq.DescribeUsers = append(req.DescribeUsers, DescribeUserScramCredentialsRequestUser{\n\t\t\tName: u,\n\t\t})\n\t}\n\n\tb, err := ca.Controller()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trsp, err := b.DescribeUserScramCredentials(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn rsp.Results, nil\n}\n\nfunc (ca *clusterAdmin) UpsertUserScramCredentials(upsert []AlterUserScramCredentialsUpsert) ([]*AlterUserScramCredentialsResult, error) {\n\tres, err := ca.AlterUserScramCredentials(upsert, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn res, nil\n}\n\nfunc (ca *clusterAdmin) DeleteUserScramCredentials(delete []AlterUserScramCredentialsDelete) ([]*AlterUserScramCredentialsResult, error) {\n\tres, err := ca.AlterUserScramCredentials(nil, delete)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn res, nil\n}\n\nfunc (ca *clusterAdmin) AlterUserScramCredentials(u []AlterUserScramCredentialsUpsert, d []AlterUserScramCredentialsDelete) ([]*AlterUserScramCredentialsResult, error) {\n\treq := &AlterUserScramCredentialsRequest{\n\t\tDeletions:  d,\n\t\tUpsertions: u,\n\t}\n\n\tvar rsp *AlterUserScramCredentialsResponse\n\terr := ca.retryOnError(isRetriableControllerError, func() error {\n\t\tb, err := ca.Controller()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\trsp, err = b.AlterUserScramCredentials(req)\n\t\treturn err\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn rsp.Results, nil\n}\n\n// Describe All : use an empty/nil components slice + strict = false\n// Contains components: strict = false\n// Contains only components: strict = true\nfunc (ca *clusterAdmin) DescribeClientQuotas(components []QuotaFilterComponent, strict bool) ([]DescribeClientQuotasEntry, error) {\n\trequest := NewDescribeClientQuotasRequest(\n\t\tca.conf.Version,\n\t\tcomponents,\n\t\tstrict,\n\t)\n\n\tb, err := ca.Controller()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trsp, err := b.DescribeClientQuotas(request)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif rsp.ErrorMsg != nil && len(*rsp.ErrorMsg) > 0 {\n\t\treturn nil, errors.New(*rsp.ErrorMsg)\n\t}\n\tif !errors.Is(rsp.ErrorCode, ErrNoError) {\n\t\treturn nil, rsp.ErrorCode\n\t}\n\n\treturn rsp.Entries, nil\n}\n\nfunc (ca *clusterAdmin) AlterClientQuotas(entity []QuotaEntityComponent, op ClientQuotasOp, validateOnly bool) error {\n\tentry := AlterClientQuotasEntry{\n\t\tEntity: entity,\n\t\tOps:    []ClientQuotasOp{op},\n\t}\n\n\trequest := &AlterClientQuotasRequest{\n\t\tEntries:      []AlterClientQuotasEntry{entry},\n\t\tValidateOnly: validateOnly,\n\t}\n\n\tb, err := ca.Controller()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\trsp, err := b.AlterClientQuotas(request)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, entry := range rsp.Entries {\n\t\tif entry.ErrorMsg != nil && len(*entry.ErrorMsg) > 0 {\n\t\t\treturn errors.New(*entry.ErrorMsg)\n\t\t}\n\t\tif !errors.Is(entry.ErrorCode, ErrNoError) {\n\t\t\treturn entry.ErrorCode\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (ca *clusterAdmin) RemoveMemberFromConsumerGroup(group string, groupInstanceIds []string) (*LeaveGroupResponse, error) {\n\tif !ca.conf.Version.IsAtLeast(V2_4_0_0) {\n\t\treturn nil, ConfigurationError(\"Removing members from a consumer group headers requires Kafka version of at least v2.4.0\")\n\t}\n\tvar response *LeaveGroupResponse\n\trequest := &LeaveGroupRequest{\n\t\tVersion: 3,\n\t\tGroupId: group,\n\t}\n\tfor _, instanceId := range groupInstanceIds {\n\t\tgroupInstanceId := instanceId\n\t\trequest.Members = append(request.Members, MemberIdentity{\n\t\t\tGroupInstanceId: &groupInstanceId,\n\t\t})\n\t}\n\terr := ca.retryOnError(isRetriableGroupCoordinatorError, func() (err error) {\n\t\tdefer func() {\n\t\t\tif err != nil && isRetriableGroupCoordinatorError(err) {\n\t\t\t\t_ = ca.client.RefreshCoordinator(group)\n\t\t\t}\n\t\t}()\n\n\t\tcoordinator, err := ca.client.Coordinator(group)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tresponse, err = coordinator.LeaveGroup(request)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !errors.Is(response.Err, ErrNoError) {\n\t\t\treturn response.Err\n\t\t}\n\n\t\treturn nil\n\t})\n\n\treturn response, err\n}\n"
  },
  {
    "path": "admin_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestClusterAdmin(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V1_0_0_0\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestClusterAdminInvalidController(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V1_0_0_0\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif admin != nil {\n\t\tdefer safeClose(t, admin)\n\t}\n\tif err == nil {\n\t\tt.Fatal(errors.New(\"controller not set still cluster admin was created\"))\n\t}\n\n\tif !errors.Is(err, ErrControllerNotAvailable) {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestClusterAdminCreateTopic(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()),\n\t\t\"CreateTopicsRequest\": NewMockCreateTopicsResponse(t),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V0_10_2_0\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\terr = admin.CreateTopic(\"my_topic\", &TopicDetail{NumPartitions: 1, ReplicationFactor: 1}, false)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestClusterAdminCreateTopicWithInvalidTopicDetail(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()),\n\t\t\"CreateTopicsRequest\": NewMockCreateTopicsResponse(t),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V0_10_2_0\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = admin.CreateTopic(\"my_topic\", nil, false)\n\tif err.Error() != \"you must specify topic details\" {\n\t\tt.Fatal(err)\n\t}\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestClusterAdminCreateTopicWithoutAuthorization(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()),\n\t\t\"CreateTopicsRequest\": NewMockCreateTopicsResponse(t),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V0_11_0_0\n\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = admin.CreateTopic(\"_internal_topic\", &TopicDetail{NumPartitions: 1, ReplicationFactor: 1}, false)\n\twant := \"insufficient permissions to create topic with reserved prefix\"\n\tif !strings.HasSuffix(err.Error(), want) {\n\t\tt.Fatal(err)\n\t}\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestClusterAdminListTopics(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()).\n\t\t\tSetLeader(\"my_topic\", 0, seedBroker.BrokerID()),\n\t\t\"DescribeConfigsRequest\": NewMockDescribeConfigsResponse(t),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V1_1_0_0\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tentries, err := admin.ListTopics()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif len(entries) == 0 {\n\t\tt.Fatal(errors.New(\"no resource present\"))\n\t}\n\n\ttopic, found := entries[\"my_topic\"]\n\tif !found {\n\t\tt.Fatal(errors.New(\"topic not found in response\"))\n\t}\n\t_, found = topic.ConfigEntries[\"max.message.bytes\"]\n\tif found {\n\t\tt.Fatal(errors.New(\"default topic config entry incorrectly found in response\"))\n\t}\n\tvalue := topic.ConfigEntries[\"retention.ms\"]\n\tif value == nil || *value != \"5000\" {\n\t\tt.Fatal(errors.New(\"non-default topic config entry not found in response\"))\n\t}\n\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif topic.ReplicaAssignment == nil || topic.ReplicaAssignment[0][0] != 1 {\n\t\tt.Fatal(errors.New(\"replica assignment not found in response\"))\n\t}\n}\n\nfunc TestClusterAdminDeleteTopic(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()),\n\t\t\"DeleteTopicsRequest\": NewMockDeleteTopicsResponse(t),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V0_10_2_0\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = admin.DeleteTopic(\"my_topic\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestClusterAdminDeleteEmptyTopic(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()),\n\t\t\"DeleteTopicsRequest\": NewMockDeleteTopicsResponse(t),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V0_10_2_0\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = admin.DeleteTopic(\"\")\n\tif !errors.Is(err, ErrInvalidTopic) {\n\t\tt.Fatal(err)\n\t}\n\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestClusterAdminDeleteTopicError(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()),\n\t\t\"DeleteTopicsRequest\": NewMockDeleteTopicsResponse(t).SetError(ErrTopicDeletionDisabled),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V0_10_2_0\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = admin.DeleteTopic(\"my_topic\")\n\tif !errors.Is(err, ErrTopicDeletionDisabled) {\n\t\tt.Fatal(err)\n\t}\n\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestClusterAdminCreatePartitions(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()),\n\t\t\"CreatePartitionsRequest\": NewMockCreatePartitionsResponse(t),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V1_0_0_0\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = admin.CreatePartitions(\"my_topic\", 3, nil, false)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestClusterAdminCreatePartitionsWithDiffVersion(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()),\n\t\t\"CreatePartitionsRequest\": NewMockCreatePartitionsResponse(t),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V0_10_2_0\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = admin.CreatePartitions(\"my_topic\", 3, nil, false)\n\tif !errors.Is(err, ErrUnsupportedVersion) {\n\t\tt.Fatal(err)\n\t}\n\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestClusterAdminCreatePartitionsWithoutAuthorization(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()),\n\t\t\"CreatePartitionsRequest\": NewMockCreatePartitionsResponse(t),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V1_0_0_0\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = admin.CreatePartitions(\"_internal_topic\", 3, nil, false)\n\twant := \"insufficient permissions to create partition on topic with reserved prefix\"\n\tif !strings.HasSuffix(err.Error(), want) {\n\t\tt.Fatal(err)\n\t}\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestClusterAdminAlterPartitionReassignments(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tsecondBroker := NewMockBroker(t, 2)\n\tdefer secondBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"ApiVersionsRequest\": NewMockApiVersionsResponse(t),\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(secondBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()).\n\t\t\tSetBroker(secondBroker.Addr(), secondBroker.BrokerID()),\n\t})\n\n\tsecondBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"ApiVersionsRequest\":                 NewMockApiVersionsResponse(t),\n\t\t\"AlterPartitionReassignmentsRequest\": NewMockAlterPartitionReassignmentsResponse(t),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V2_4_0_0\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttopicAssignment := make([][]int32, 0, 3)\n\n\terr = admin.AlterPartitionReassignments(\"my_topic\", topicAssignment)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestClusterAdminAlterPartitionReassignmentsWithDiffVersion(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tsecondBroker := NewMockBroker(t, 2)\n\tdefer secondBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(secondBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()).\n\t\t\tSetBroker(secondBroker.Addr(), secondBroker.BrokerID()),\n\t})\n\n\tsecondBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"AlterPartitionReassignmentsRequest\": NewMockAlterPartitionReassignmentsResponse(t),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V2_3_0_0\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttopicAssignment := make([][]int32, 0, 3)\n\n\terr = admin.AlterPartitionReassignments(\"my_topic\", topicAssignment)\n\n\tif !strings.ContainsAny(err.Error(), ErrUnsupportedVersion.Error()) {\n\t\tt.Fatal(err)\n\t}\n\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestClusterAdminListPartitionReassignments(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tsecondBroker := NewMockBroker(t, 2)\n\tdefer secondBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"ApiVersionsRequest\": NewMockApiVersionsResponse(t),\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(secondBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()).\n\t\t\tSetBroker(secondBroker.Addr(), secondBroker.BrokerID()),\n\t})\n\n\tsecondBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"ApiVersionsRequest\":                NewMockApiVersionsResponse(t),\n\t\t\"ListPartitionReassignmentsRequest\": NewMockListPartitionReassignmentsResponse(t),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V2_4_0_0\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tresponse, err := admin.ListPartitionReassignments(\"my_topic\", []int32{0, 1})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tpartitionStatus, ok := response[\"my_topic\"]\n\tif !ok {\n\t\tt.Fatalf(\"topic missing in response\")\n\t}\n\n\tif len(partitionStatus) != 2 {\n\t\tt.Fatalf(\"partition missing in response\")\n\t}\n\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestClusterAdminListPartitionReassignmentsWithDiffVersion(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tsecondBroker := NewMockBroker(t, 2)\n\tdefer secondBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(secondBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()).\n\t\t\tSetBroker(secondBroker.Addr(), secondBroker.BrokerID()),\n\t})\n\n\tsecondBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"ListPartitionReassignmentsRequest\": NewMockListPartitionReassignmentsResponse(t),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V2_3_0_0\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tpartitions := make([]int32, 0)\n\n\t_, err = admin.ListPartitionReassignments(\"my_topic\", partitions)\n\n\tif !strings.ContainsAny(err.Error(), ErrUnsupportedVersion.Error()) {\n\t\tt.Fatal(err)\n\t}\n\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestClusterAdminDeleteRecords(t *testing.T) {\n\ttopicName := \"my_topic\"\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()).\n\t\t\tSetLeader(topicName, 1, 1).\n\t\t\tSetLeader(topicName, 2, 1).\n\t\t\tSetLeader(topicName, 3, 1),\n\t\t\"DeleteRecordsRequest\": NewMockDeleteRecordsResponse(t),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V1_0_0_0\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tpartitionOffsetFake := make(map[int32]int64)\n\tpartitionOffsetFake[4] = 1000\n\terrFake := admin.DeleteRecords(topicName, partitionOffsetFake)\n\tif errFake == nil {\n\t\tt.Fatal(err)\n\t}\n\n\tpartitionOffset := make(map[int32]int64)\n\tpartitionOffset[1] = 1000\n\tpartitionOffset[2] = 1000\n\tpartitionOffset[3] = 1000\n\n\terr = admin.DeleteRecords(topicName, partitionOffset)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestClusterAdminDeleteRecordsWithInCorrectBroker(t *testing.T) {\n\ttopicName := \"my_topic\"\n\tseedBroker := NewMockBroker(t, 1)\n\tsecondBroker := NewMockBroker(t, 2)\n\tdefer seedBroker.Close()\n\tdefer secondBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()).\n\t\t\tSetBroker(secondBroker.Addr(), secondBroker.brokerID).\n\t\t\tSetLeader(topicName, 1, 1).\n\t\t\tSetLeader(topicName, 2, 1).\n\t\t\tSetLeader(topicName, 3, 2),\n\t\t\"DeleteRecordsRequest\": NewMockDeleteRecordsResponse(t),\n\t})\n\n\tsecondBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()).\n\t\t\tSetBroker(secondBroker.Addr(), secondBroker.brokerID).\n\t\t\tSetLeader(topicName, 1, 1).\n\t\t\tSetLeader(topicName, 2, 1).\n\t\t\tSetLeader(topicName, 3, 2),\n\t\t\"DeleteRecordsRequest\": NewMockDeleteRecordsResponse(t),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V1_0_0_0\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tpartitionOffset := make(map[int32]int64)\n\tpartitionOffset[1] = 1000\n\tpartitionOffset[2] = 1000\n\tpartitionOffset[3] = 1000\n\n\terr = admin.DeleteRecords(topicName, partitionOffset)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestClusterAdminDeleteRecordsWithUnsupportedVersion(t *testing.T) {\n\ttopicName := \"my_topic\"\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()).\n\t\t\tSetLeader(topicName, 1, 1).\n\t\t\tSetLeader(topicName, 2, 1).\n\t\t\tSetLeader(topicName, 3, 1),\n\t\t\"DeleteRecordsRequest\": NewMockDeleteRecordsResponse(t),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V0_10_2_0\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tpartitionOffset := make(map[int32]int64)\n\tpartitionOffset[1] = 1000\n\tpartitionOffset[2] = 1000\n\tpartitionOffset[3] = 1000\n\n\terr = admin.DeleteRecords(topicName, partitionOffset)\n\tif err == nil {\n\t\tt.Fatal(\"expected an ErrDeleteRecords\")\n\t}\n\n\tif !strings.HasPrefix(err.Error(), \"kafka server: failed to delete records\") {\n\t\tt.Fatal(err)\n\t}\n\n\tif !errors.Is(err, ErrDeleteRecords) {\n\t\tt.Fatal(err)\n\t}\n\n\tif !errors.Is(err, ErrUnsupportedVersion) {\n\t\tt.Fatal(err)\n\t}\n\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestClusterAdminDeleteRecordsWithLeaderNotAvailable(t *testing.T) {\n\ttopicName := \"my_topic\"\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetLeader(\"my_topic\", 1, -1).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V1_0_0_0\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tpartitionOffset := make(map[int32]int64)\n\tpartitionOffset[1] = 1000\n\n\terr = admin.DeleteRecords(topicName, partitionOffset)\n\tif err == nil {\n\t\tt.Fatal(\"expected an ErrDeleteRecords\")\n\t}\n\n\tif !strings.HasPrefix(err.Error(), \"kafka server: failed to delete records\") {\n\t\tt.Fatal(err)\n\t}\n\n\tif !errors.Is(err, ErrDeleteRecords) {\n\t\tt.Fatal(err)\n\t}\n\n\tif !errors.Is(err, ErrLeaderNotAvailable) {\n\t\tt.Fatal(err)\n\t}\n\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestClusterAdminDescribeConfig(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()),\n\t\t\"DescribeConfigsRequest\": NewMockDescribeConfigsResponse(t),\n\t})\n\n\ttests := []struct {\n\t\tsaramaVersion   KafkaVersion\n\t\trequestVersion  int16\n\t\tincludeSynonyms bool\n\t}{\n\t\t{V1_0_0_0, 0, false},\n\t\t{V1_1_0_0, 1, true},\n\t\t{V1_1_1_0, 1, true},\n\t\t{V2_0_0_0, 2, true},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.saramaVersion.String(), func(t *testing.T) {\n\t\t\tconfig := NewTestConfig()\n\t\t\tconfig.Version = tt.saramaVersion\n\t\t\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tdefer func() {\n\t\t\t\t_ = admin.Close()\n\t\t\t}()\n\n\t\t\tresource := ConfigResource{\n\t\t\t\tName:        \"r1\",\n\t\t\t\tType:        TopicResource,\n\t\t\t\tConfigNames: []string{\"my_topic\"},\n\t\t\t}\n\n\t\t\tentries, err := admin.DescribeConfig(resource)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\thistory := seedBroker.History()\n\t\t\tdescribeReq, ok := history[len(history)-1].Request.(*DescribeConfigsRequest)\n\t\t\tif !ok {\n\t\t\t\tt.Fatal(\"failed to find DescribeConfigsRequest in mockBroker history\")\n\t\t\t}\n\n\t\t\tif describeReq.Version != tt.requestVersion {\n\t\t\t\tt.Fatalf(\n\t\t\t\t\t\"requestVersion %v did not match expected %v\",\n\t\t\t\t\tdescribeReq.Version, tt.requestVersion)\n\t\t\t}\n\n\t\t\tif len(entries) == 0 {\n\t\t\t\tt.Fatal(errors.New(\"no resource present\"))\n\t\t\t}\n\t\t\tif tt.includeSynonyms {\n\t\t\t\tif len(entries[0].Synonyms) == 0 {\n\t\t\t\t\tt.Fatal(\"expected synonyms to have been included\")\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestClusterAdminDescribeConfigWithErrorCode(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()),\n\t\t\"DescribeConfigsRequest\": NewMockDescribeConfigsResponseWithErrorCode(t),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V1_1_0_0\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer func() {\n\t\t_ = admin.Close()\n\t}()\n\n\tresource := ConfigResource{\n\t\tName:        \"r1\",\n\t\tType:        TopicResource,\n\t\tConfigNames: []string{\"my_topic\"},\n\t}\n\n\t_, err = admin.DescribeConfig(resource)\n\tif err == nil {\n\t\tt.Fatal(errors.New(\"ErrorCode present but no Error returned\"))\n\t}\n}\n\n// TestClusterAdminDescribeBrokerConfig ensures that a describe broker config\n// is sent to the broker in the resource struct, _not_ the controller\nfunc TestClusterAdminDescribeBrokerConfig(t *testing.T) {\n\tcontrollerBroker := NewMockBroker(t, 1)\n\tdefer controllerBroker.Close()\n\tconfigBroker := NewMockBroker(t, 2)\n\tdefer configBroker.Close()\n\n\tcontrollerBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(controllerBroker.BrokerID()).\n\t\t\tSetBroker(controllerBroker.Addr(), controllerBroker.BrokerID()).\n\t\t\tSetBroker(configBroker.Addr(), configBroker.BrokerID()),\n\t})\n\n\tconfigBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(controllerBroker.BrokerID()).\n\t\t\tSetBroker(controllerBroker.Addr(), controllerBroker.BrokerID()).\n\t\t\tSetBroker(configBroker.Addr(), configBroker.BrokerID()),\n\t\t\"DescribeConfigsRequest\": NewMockDescribeConfigsResponse(t),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V1_0_0_0\n\tadmin, err := NewClusterAdmin(\n\t\t[]string{\n\t\t\tcontrollerBroker.Addr(),\n\t\t\tconfigBroker.Addr(),\n\t\t}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor _, resourceType := range []ConfigResourceType{BrokerResource, BrokerLoggerResource} {\n\t\tresource := ConfigResource{Name: \"2\", Type: resourceType}\n\t\tentries, err := admin.DescribeConfig(resource)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif len(entries) == 0 {\n\t\t\tt.Fatal(errors.New(\"no resource present\"))\n\t\t}\n\t}\n\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestClusterAdminAlterConfig(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()),\n\t\t\"AlterConfigsRequest\": NewMockAlterConfigsResponse(t),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V1_0_0_0\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tvar value string\n\tentries := make(map[string]*string)\n\tvalue = \"60000\"\n\tentries[\"retention.ms\"] = &value\n\terr = admin.AlterConfig(TopicResource, \"my_topic\", entries, false)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestClusterAdminAlterConfigWithErrorCode(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()),\n\t\t\"AlterConfigsRequest\": NewMockAlterConfigsResponseWithErrorCode(t),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V1_0_0_0\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer func() {\n\t\t_ = admin.Close()\n\t}()\n\n\tvar value string\n\tentries := make(map[string]*string)\n\tvalue = \"60000\"\n\tentries[\"retention.ms\"] = &value\n\terr = admin.AlterConfig(TopicResource, \"my_topic\", entries, false)\n\tif err == nil {\n\t\tt.Fatal(errors.New(\"ErrorCode present but no Error returned\"))\n\t}\n}\n\nfunc TestClusterAdminAlterBrokerConfig(t *testing.T) {\n\tcontrollerBroker := NewMockBroker(t, 1)\n\tdefer controllerBroker.Close()\n\tconfigBroker := NewMockBroker(t, 2)\n\tdefer configBroker.Close()\n\n\tcontrollerBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(controllerBroker.BrokerID()).\n\t\t\tSetBroker(controllerBroker.Addr(), controllerBroker.BrokerID()).\n\t\t\tSetBroker(configBroker.Addr(), configBroker.BrokerID()),\n\t})\n\tconfigBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(controllerBroker.BrokerID()).\n\t\t\tSetBroker(controllerBroker.Addr(), controllerBroker.BrokerID()).\n\t\t\tSetBroker(configBroker.Addr(), configBroker.BrokerID()),\n\t\t\"AlterConfigsRequest\": NewMockAlterConfigsResponse(t),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V1_0_0_0\n\tadmin, err := NewClusterAdmin(\n\t\t[]string{\n\t\t\tcontrollerBroker.Addr(),\n\t\t\tconfigBroker.Addr(),\n\t\t}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tvar value string\n\tentries := make(map[string]*string)\n\tvalue = \"3\"\n\tentries[\"min.insync.replicas\"] = &value\n\n\tfor _, resourceType := range []ConfigResourceType{BrokerResource, BrokerLoggerResource} {\n\t\tresource := ConfigResource{Name: \"2\", Type: resourceType}\n\t\terr = admin.AlterConfig(\n\t\t\tresource.Type,\n\t\t\tresource.Name,\n\t\t\tentries,\n\t\t\tfalse)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestClusterAdminIncrementalAlterConfig(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()),\n\t\t\"IncrementalAlterConfigsRequest\": NewMockIncrementalAlterConfigsResponse(t),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V2_3_0_0\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tvar value string\n\tentries := make(map[string]IncrementalAlterConfigsEntry)\n\tvalue = \"60000\"\n\tentries[\"retention.ms\"] = IncrementalAlterConfigsEntry{\n\t\tOperation: IncrementalAlterConfigsOperationSet,\n\t\tValue:     &value,\n\t}\n\tvalue = \"1073741824\"\n\tentries[\"segment.bytes\"] = IncrementalAlterConfigsEntry{\n\t\tOperation: IncrementalAlterConfigsOperationDelete,\n\t\tValue:     &value,\n\t}\n\terr = admin.IncrementalAlterConfig(TopicResource, \"my_topic\", entries, false)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestClusterAdminIncrementalAlterConfigWithErrorCode(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()),\n\t\t\"IncrementalAlterConfigsRequest\": NewMockIncrementalAlterConfigsResponseWithErrorCode(t),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V2_3_0_0\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer func() {\n\t\t_ = admin.Close()\n\t}()\n\n\tvar value string\n\tentries := make(map[string]IncrementalAlterConfigsEntry)\n\tvalue = \"60000\"\n\tentries[\"retention.ms\"] = IncrementalAlterConfigsEntry{\n\t\tOperation: IncrementalAlterConfigsOperationSet,\n\t\tValue:     &value,\n\t}\n\tvalue = \"1073741824\"\n\tentries[\"segment.bytes\"] = IncrementalAlterConfigsEntry{\n\t\tOperation: IncrementalAlterConfigsOperationDelete,\n\t\tValue:     &value,\n\t}\n\terr = admin.IncrementalAlterConfig(TopicResource, \"my_topic\", entries, false)\n\tif err == nil {\n\t\tt.Fatal(errors.New(\"ErrorCode present but no Error returned\"))\n\t}\n\tif !errors.Is(err, ErrInvalidConfig) {\n\t\tt.Fatal(errors.New(\"ErrorCode present but not wrapped into returned error\"))\n\t}\n}\n\nfunc TestClusterAdminIncrementalAlterBrokerConfig(t *testing.T) {\n\tcontrollerBroker := NewMockBroker(t, 1)\n\tdefer controllerBroker.Close()\n\tconfigBroker := NewMockBroker(t, 2)\n\tdefer configBroker.Close()\n\n\tcontrollerBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(controllerBroker.BrokerID()).\n\t\t\tSetBroker(controllerBroker.Addr(), controllerBroker.BrokerID()).\n\t\t\tSetBroker(configBroker.Addr(), configBroker.BrokerID()),\n\t})\n\tconfigBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(controllerBroker.BrokerID()).\n\t\t\tSetBroker(controllerBroker.Addr(), controllerBroker.BrokerID()).\n\t\t\tSetBroker(configBroker.Addr(), configBroker.BrokerID()),\n\t\t\"IncrementalAlterConfigsRequest\": NewMockIncrementalAlterConfigsResponse(t),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V2_3_0_0\n\tadmin, err := NewClusterAdmin(\n\t\t[]string{\n\t\t\tcontrollerBroker.Addr(),\n\t\t\tconfigBroker.Addr(),\n\t\t}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tvar value string\n\tentries := make(map[string]IncrementalAlterConfigsEntry)\n\tvalue = \"3\"\n\tentries[\"min.insync.replicas\"] = IncrementalAlterConfigsEntry{\n\t\tOperation: IncrementalAlterConfigsOperationSet,\n\t\tValue:     &value,\n\t}\n\tvalue = \"2\"\n\tentries[\"log.cleaner.threads\"] = IncrementalAlterConfigsEntry{\n\t\tOperation: IncrementalAlterConfigsOperationDelete,\n\t\tValue:     &value,\n\t}\n\n\tfor _, resourceType := range []ConfigResourceType{BrokerResource, BrokerLoggerResource} {\n\t\tresource := ConfigResource{Name: \"2\", Type: resourceType}\n\t\terr = admin.IncrementalAlterConfig(\n\t\t\tresource.Type,\n\t\t\tresource.Name,\n\t\t\tentries,\n\t\t\tfalse)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestClusterAdminCreateAcl(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()),\n\t\t\"CreateAclsRequest\": NewMockCreateAclsResponse(t),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V1_0_0_0\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tr := Resource{ResourceType: AclResourceTopic, ResourceName: \"my_topic\"}\n\ta := Acl{Host: \"localhost\", Operation: AclOperationAlter, PermissionType: AclPermissionAny}\n\n\terr = admin.CreateACL(r, a)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestClusterAdminCreateAclErrorHandling(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()),\n\t\t\"CreateAclsRequest\": NewMockCreateAclsResponseWithError(t),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V1_0_0_0\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tr := Resource{ResourceType: AclResourceTopic, ResourceName: \"my_topic\"}\n\ta := Acl{Host: \"localhost\", Operation: AclOperationAlter, PermissionType: AclPermissionAny}\n\n\terr = admin.CreateACL(r, a)\n\tif err == nil {\n\t\tt.Fatal(errors.New(\"error should have been thrown\"))\n\t}\n\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestClusterAdminCreateAcls(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()),\n\t\t\"CreateAclsRequest\": NewMockCreateAclsResponse(t),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V1_0_0_0\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\trACLs := []*ResourceAcls{\n\t\t{\n\t\t\tResource: Resource{ResourceType: AclResourceTopic, ResourceName: \"my_topic\"},\n\t\t\tAcls: []*Acl{\n\t\t\t\t{Host: \"localhost\", Operation: AclOperationAlter, PermissionType: AclPermissionAny},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tResource: Resource{ResourceType: AclResourceTopic, ResourceName: \"your_topic\"},\n\t\t\tAcls: []*Acl{\n\t\t\t\t{Host: \"localhost\", Operation: AclOperationAlter, PermissionType: AclPermissionAny},\n\t\t\t},\n\t\t},\n\t}\n\n\terr = admin.CreateACLs(rACLs)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestClusterAdminListAcls(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()),\n\t\t\"DescribeAclsRequest\": NewMockListAclsResponse(t),\n\t\t\"CreateAclsRequest\":   NewMockCreateAclsResponse(t),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V1_0_0_0\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tr := Resource{ResourceType: AclResourceTopic, ResourceName: \"my_topic\"}\n\ta := Acl{Host: \"localhost\", Operation: AclOperationAlter, PermissionType: AclPermissionAny}\n\n\terr = admin.CreateACL(r, a)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tresourceName := \"my_topic\"\n\tfilter := AclFilter{\n\t\tResourceType: AclResourceTopic,\n\t\tOperation:    AclOperationRead,\n\t\tResourceName: &resourceName,\n\t}\n\n\trAcls, err := admin.ListAcls(filter)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif len(rAcls) == 0 {\n\t\tt.Fatal(\"no acls present\")\n\t}\n\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestClusterAdminDeleteAcl(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()),\n\t\t\"DeleteAclsRequest\": NewMockDeleteAclsResponse(t),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V1_0_0_0\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tresourceName := \"my_topic\"\n\tfilter := AclFilter{\n\t\tResourceType: AclResourceTopic,\n\t\tOperation:    AclOperationAlter,\n\t\tResourceName: &resourceName,\n\t}\n\n\t_, err = admin.DeleteACL(filter, false)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestElectLeaders(t *testing.T) {\n\tbroker := NewMockBroker(t, 1)\n\tdefer broker.Close()\n\n\tbroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"ApiVersionsRequest\": NewMockApiVersionsResponse(t),\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(broker.BrokerID()).\n\t\t\tSetBroker(broker.Addr(), broker.BrokerID()),\n\t\t\"ElectLeadersRequest\": NewMockElectLeadersResponse(t),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V2_4_0_0\n\tadmin, err := NewClusterAdmin([]string{broker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tresponse, err := admin.ElectLeaders(PreferredElection, map[string][]int32{\"my_topic\": {0, 1}})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tpartitionResult, ok := response[\"my_topic\"]\n\tif !ok {\n\t\tt.Fatalf(\"topic missing in response\")\n\t}\n\n\tif len(partitionResult) != 1 {\n\t\tt.Fatalf(\"partition missing in response\")\n\t}\n\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestDescribeTopic(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetLeader(\"my_topic\", 0, seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V1_0_0_0\n\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttopics, err := admin.DescribeTopics([]string{\"my_topic\"})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif len(topics) != 1 {\n\t\tt.Fatalf(\"Expected 1 result, got %v\", len(topics))\n\t}\n\n\tif topics[0].Name != \"my_topic\" {\n\t\tt.Fatalf(\"Incorrect topic name: %v\", topics[0].Name)\n\t}\n\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestDescribeTopicWithVersion0_11(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetLeader(\"my_topic\", 0, seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V0_11_0_0\n\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttopics, err := admin.DescribeTopics([]string{\"my_topic\"})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif len(topics) != 1 {\n\t\tt.Fatalf(\"Expected 1 result, got %v\", len(topics))\n\t}\n\n\tif topics[0].Name != \"my_topic\" {\n\t\tt.Fatalf(\"Incorrect topic name: %v\", topics[0].Name)\n\t}\n\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestDescribeConsumerGroup(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\texpectedGroupID := \"my-group\"\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"DescribeGroupsRequest\": NewMockDescribeGroupsResponse(t).AddGroupDescription(expectedGroupID, &GroupDescription{\n\t\t\tGroupId: expectedGroupID,\n\t\t}),\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()),\n\t\t\"FindCoordinatorRequest\": NewMockFindCoordinatorResponse(t).SetCoordinator(CoordinatorGroup, expectedGroupID, seedBroker),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V1_0_0_0\n\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tresult, err := admin.DescribeConsumerGroups([]string{expectedGroupID})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif len(result) != 1 {\n\t\tt.Fatalf(\"Expected 1 result, got %v\", len(result))\n\t}\n\n\tif result[0].GroupId != expectedGroupID {\n\t\tt.Fatalf(\"Expected groupID %v, got %v\", expectedGroupID, result[0].GroupId)\n\t}\n\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestListConsumerGroups(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()),\n\t\t\"ListGroupsRequest\": NewMockListGroupsResponse(t).\n\t\t\tAddGroup(\"my-group\", \"consumer\"),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V1_0_0_0\n\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tgroups, err := admin.ListConsumerGroups()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif len(groups) != 1 {\n\t\tt.Fatalf(\"Expected %v results, got %v\", 1, len(groups))\n\t}\n\n\tprotocolType, ok := groups[\"my-group\"]\n\n\tif !ok {\n\t\tt.Fatal(\"Expected group to be returned, but it did not\")\n\t}\n\n\tif protocolType != \"consumer\" {\n\t\tt.Fatalf(\"Expected protocolType %v, got %v\", \"consumer\", protocolType)\n\t}\n\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestListConsumerGroupsMultiBroker(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tsecondBroker := NewMockBroker(t, 2)\n\tdefer secondBroker.Close()\n\n\tfirstGroup := \"first\"\n\tsecondGroup := \"second\"\n\tnonExistingGroup := \"non-existing-group\"\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()).\n\t\t\tSetBroker(secondBroker.Addr(), secondBroker.BrokerID()),\n\t\t\"ListGroupsRequest\": NewMockListGroupsResponse(t).\n\t\t\tAddGroup(firstGroup, \"consumer\"),\n\t})\n\n\tsecondBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()).\n\t\t\tSetBroker(secondBroker.Addr(), secondBroker.BrokerID()),\n\t\t\"ListGroupsRequest\": NewMockListGroupsResponse(t).\n\t\t\tAddGroup(secondGroup, \"consumer\"),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V1_0_0_0\n\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tgroups, err := admin.ListConsumerGroups()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif len(groups) != 2 {\n\t\tt.Fatalf(\"Expected %v results, got %v\", 1, len(groups))\n\t}\n\n\tif _, found := groups[firstGroup]; !found {\n\t\tt.Fatalf(\"Expected group %v to be present in result set, but it isn't\", firstGroup)\n\t}\n\n\tif _, found := groups[secondGroup]; !found {\n\t\tt.Fatalf(\"Expected group %v to be present in result set, but it isn't\", secondGroup)\n\t}\n\n\tif _, found := groups[nonExistingGroup]; found {\n\t\tt.Fatalf(\"Expected group %v to not exist, but it exists\", nonExistingGroup)\n\t}\n\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestListConsumerGroupOffsets(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tgroup := \"my-group\"\n\ttopic := \"my-topic\"\n\tpartition := int32(0)\n\texpectedOffset := int64(0)\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"OffsetFetchRequest\": NewMockOffsetFetchResponse(t).SetOffset(group, \"my-topic\", partition, expectedOffset, \"\", ErrNoError).SetError(ErrNoError),\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()),\n\t\t\"FindCoordinatorRequest\": NewMockFindCoordinatorResponse(t).SetCoordinator(CoordinatorGroup, group, seedBroker),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V1_0_0_0\n\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tresponse, err := admin.ListConsumerGroupOffsets(group, map[string][]int32{\n\t\ttopic: {0},\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"ListConsumerGroupOffsets failed with error %v\", err)\n\t}\n\n\tblock := response.GetBlock(topic, partition)\n\tif block == nil {\n\t\tt.Fatalf(\"Expected block for topic %v and partition %v to exist, but it doesn't\", topic, partition)\n\t}\n\n\tif block.Offset != expectedOffset {\n\t\tt.Fatalf(\"Expected offset %v, got %v\", expectedOffset, block.Offset)\n\t}\n\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestDeleteConsumerGroup(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tgroup := \"my-group\"\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t// \"OffsetFetchRequest\":  NewMockOffsetFetchResponse(t).SetOffset(group, \"my-topic\", partition, expectedOffset, \"\", ErrNoError),\n\t\t\"DeleteGroupsRequest\": NewMockDeleteGroupsRequest(t).SetDeletedGroups([]string{group}),\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()),\n\t\t\"FindCoordinatorRequest\": NewMockFindCoordinatorResponse(t).SetCoordinator(CoordinatorGroup, group, seedBroker),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V1_1_0_0\n\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer admin.Close()\n\n\terr = admin.DeleteConsumerGroup(group)\n\tif err != nil {\n\t\tt.Fatalf(\"DeleteConsumerGroup failed with error %v\", err)\n\t}\n}\n\nfunc TestDeleteOffset(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tgroup := \"group-delete-offset\"\n\ttopic := \"topic-delete-offset\"\n\tpartition := int32(0)\n\n\thandlerMap := map[string]MockResponse{\n\t\t\"ApiVersionsRequest\": NewMockApiVersionsResponse(t),\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()),\n\t\t\"FindCoordinatorRequest\": NewMockFindCoordinatorResponse(t).SetCoordinator(CoordinatorGroup, group, seedBroker),\n\t}\n\tseedBroker.SetHandlerByMap(handlerMap)\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V2_4_0_0\n\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Test NoError\n\thandlerMap[\"DeleteOffsetsRequest\"] = NewMockDeleteOffsetRequest(t).SetDeletedOffset(ErrNoError, topic, partition, ErrNoError)\n\tseedBroker.SetHandlerByMap(handlerMap)\n\terr = admin.DeleteConsumerGroupOffset(group, topic, partition)\n\tif err != nil {\n\t\tt.Fatalf(\"DeleteConsumerGroupOffset failed with error %v\", err)\n\t}\n\tdefer admin.Close()\n\n\t// Test Error\n\thandlerMap[\"DeleteOffsetsRequest\"] = NewMockDeleteOffsetRequest(t).SetDeletedOffset(ErrNotCoordinatorForConsumer, topic, partition, ErrNoError)\n\tseedBroker.SetHandlerByMap(handlerMap)\n\terr = admin.DeleteConsumerGroupOffset(group, topic, partition)\n\tif !errors.Is(err, ErrNotCoordinatorForConsumer) {\n\t\tt.Fatalf(\"DeleteConsumerGroupOffset should have failed with error %v\", ErrNotCoordinatorForConsumer)\n\t}\n\n\t// Test Error for partition\n\thandlerMap[\"DeleteOffsetsRequest\"] = NewMockDeleteOffsetRequest(t).SetDeletedOffset(ErrNoError, topic, partition, ErrGroupSubscribedToTopic)\n\tseedBroker.SetHandlerByMap(handlerMap)\n\terr = admin.DeleteConsumerGroupOffset(group, topic, partition)\n\tif !errors.Is(err, ErrGroupSubscribedToTopic) {\n\t\tt.Fatalf(\"DeleteConsumerGroupOffset should have failed with error %v\", ErrGroupSubscribedToTopic)\n\t}\n}\n\n// TestRefreshMetaDataWithDifferentController ensures that the cached\n// controller can be forcibly updated from Metadata by the admin client\nfunc TestRefreshMetaDataWithDifferentController(t *testing.T) {\n\tseedBroker1 := NewMockBroker(t, 1)\n\tseedBroker2 := NewMockBroker(t, 2)\n\tdefer seedBroker1.Close()\n\tdefer seedBroker2.Close()\n\n\tseedBroker1.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker1.BrokerID()).\n\t\t\tSetBroker(seedBroker1.Addr(), seedBroker1.BrokerID()).\n\t\t\tSetBroker(seedBroker2.Addr(), seedBroker2.BrokerID()),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V1_1_0_0\n\n\tclient, err := NewClient([]string{seedBroker1.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer client.Close()\n\n\tca := clusterAdmin{client: client, conf: config}\n\n\tif b, _ := ca.Controller(); seedBroker1.BrokerID() != b.ID() {\n\t\tt.Fatalf(\"expected cached controller to be %d rather than %d\",\n\t\t\tseedBroker1.BrokerID(), b.ID())\n\t}\n\n\tmetadataResponse := NewMockMetadataResponse(t).\n\t\tSetController(seedBroker2.BrokerID()).\n\t\tSetBroker(seedBroker1.Addr(), seedBroker1.BrokerID()).\n\t\tSetBroker(seedBroker2.Addr(), seedBroker2.BrokerID())\n\tseedBroker1.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": metadataResponse,\n\t})\n\tseedBroker2.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": metadataResponse,\n\t})\n\n\tif b, _ := ca.refreshController(); seedBroker2.BrokerID() != b.ID() {\n\t\tt.Fatalf(\"expected refreshed controller to be %d rather than %d\",\n\t\t\tseedBroker2.BrokerID(), b.ID())\n\t}\n\n\tif b, _ := ca.Controller(); seedBroker2.BrokerID() != b.ID() {\n\t\tt.Fatalf(\"expected cached controller to be %d rather than %d\",\n\t\t\tseedBroker2.BrokerID(), b.ID())\n\t}\n}\n\nfunc TestDescribeLogDirs(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()),\n\t\t\"DescribeLogDirsRequest\": NewMockDescribeLogDirsResponse(t).\n\t\t\tSetLogDirs(\"/tmp/logs\", map[string]int{\"topic1\": 2, \"topic2\": 2}),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V1_0_0_0\n\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tlogDirsPerBroker, err := admin.DescribeLogDirs([]int32{seedBroker.BrokerID()})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif len(logDirsPerBroker) != 1 {\n\t\tt.Fatalf(\"Expected %v results, got %v\", 1, len(logDirsPerBroker))\n\t}\n\tlogDirs := logDirsPerBroker[seedBroker.BrokerID()]\n\tif len(logDirs) != 1 {\n\t\tt.Fatalf(\"Expected log dirs for broker %v to be returned, but it did not, got %v\", seedBroker.BrokerID(), len(logDirs))\n\t}\n\tlogDirsBroker := logDirs[0]\n\tif !errors.Is(logDirsBroker.ErrorCode, ErrNoError) {\n\t\tt.Fatalf(\"Expected no error for broker %v, but it was %v\", seedBroker.BrokerID(), logDirsBroker.ErrorCode)\n\t}\n\tif logDirsBroker.Path != \"/tmp/logs\" {\n\t\tt.Fatalf(\"Expected log dirs for broker %v to be '/tmp/logs', but it was %v\", seedBroker.BrokerID(), logDirsBroker.Path)\n\t}\n\tif len(logDirsBroker.Topics) != 2 {\n\t\tt.Fatalf(\"Expected log dirs for broker %v to have 2 topics, but it had %v\", seedBroker.BrokerID(), len(logDirsBroker.Topics))\n\t}\n\terr = admin.Close()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestDescribeLogDirsUnknownBroker(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()),\n\t\t\"DescribeLogDirsRequest\": NewMockDescribeLogDirsResponse(t).\n\t\t\tSetLogDirs(\"/tmp/logs\", map[string]int{\"topic1\": 2, \"topic2\": 2}),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V1_0_0_0\n\n\tadmin, err := NewClusterAdmin([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer safeClose(t, admin)\n\n\ttype result struct {\n\t\tmetadata map[int32][]DescribeLogDirsResponseDirMetadata\n\t\terr      error\n\t}\n\n\tres := make(chan result)\n\n\tgo func() {\n\t\tmetadata, err := admin.DescribeLogDirs([]int32{seedBroker.BrokerID() + 1})\n\t\tres <- result{metadata, err}\n\t}()\n\n\tselect {\n\tcase <-time.After(time.Second):\n\t\tt.Fatalf(\"DescribeLogDirs timed out\")\n\tcase returned := <-res:\n\t\tif len(returned.metadata) != 0 {\n\t\t\tt.Fatalf(\"Expected no results, got %v\", len(returned.metadata))\n\t\t}\n\t\tif returned.err != nil {\n\t\t\tt.Fatalf(\"Expected no error, got %v\", returned.err)\n\t\t}\n\t}\n}\n\nfunc Test_retryOnError(t *testing.T) {\n\ttestBackoffTime := 100 * time.Millisecond\n\tconfig := NewTestConfig()\n\tconfig.Version = V1_0_0_0\n\tconfig.Admin.Retry.Max = 3\n\tconfig.Admin.Retry.Backoff = testBackoffTime\n\n\tadmin := &clusterAdmin{conf: config}\n\n\tt.Run(\"immediate success\", func(t *testing.T) {\n\t\tstartTime := time.Now()\n\t\tattempts := 0\n\t\terr := admin.retryOnError(\n\t\t\tfunc(error) bool { return true },\n\t\t\tfunc() error {\n\t\t\t\tattempts++\n\t\t\t\treturn nil\n\t\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"expected no error but was %v\", err)\n\t\t}\n\t\tif attempts != 1 {\n\t\t\tt.Fatalf(\"expected 1 attempt to have been made but was %d\", attempts)\n\t\t}\n\t\tif time.Since(startTime) >= testBackoffTime {\n\t\t\tt.Fatalf(\"single attempt should take less than backoff time\")\n\t\t}\n\t})\n\n\tt.Run(\"immediate failure\", func(t *testing.T) {\n\t\tstartTime := time.Now()\n\t\tattempts := 0\n\t\terr := admin.retryOnError(\n\t\t\tfunc(error) bool { return false },\n\t\t\tfunc() error {\n\t\t\t\tattempts++\n\t\t\t\treturn errors.New(\"mock error\")\n\t\t\t})\n\t\tif err == nil {\n\t\t\tt.Fatalf(\"expected error but was nil\")\n\t\t}\n\t\tif attempts != 1 {\n\t\t\tt.Fatalf(\"expected 1 attempt to have been made but was %d\", attempts)\n\t\t}\n\t\tif time.Since(startTime) >= testBackoffTime {\n\t\t\tt.Fatalf(\"single attempt should take less than backoff time\")\n\t\t}\n\t})\n\n\tt.Run(\"failing all attempts\", func(t *testing.T) {\n\t\tstartTime := time.Now()\n\t\tattempts := 0\n\t\terr := admin.retryOnError(\n\t\t\tfunc(error) bool { return true },\n\t\t\tfunc() error {\n\t\t\t\tattempts++\n\t\t\t\treturn errors.New(\"mock error\")\n\t\t\t})\n\t\tif err == nil {\n\t\t\tt.Errorf(\"expected error but was nil\")\n\t\t}\n\t\tif attempts != 4 {\n\t\t\tt.Errorf(\"expected 4 attempts to have been made but was %d\", attempts)\n\t\t}\n\t\tif time.Since(startTime) >= 4*testBackoffTime {\n\t\t\tt.Errorf(\"attempt+sleep+retry+sleep+retry+sleep+retry should take less than 4 * backoff time\")\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "alter_client_quotas_request.go",
    "content": "package sarama\n\n// AlterClientQuotas Request (Version: 0) => [entries] validate_only\n//   entries => [entity] [ops]\n//     entity => entity_type entity_name\n//       entity_type => STRING\n//       entity_name => NULLABLE_STRING\n//     ops => key value remove\n//       key => STRING\n//       value => FLOAT64\n//       remove => BOOLEAN\n//   validate_only => BOOLEAN\n\ntype AlterClientQuotasRequest struct {\n\tVersion      int16\n\tEntries      []AlterClientQuotasEntry // The quota configuration entries to alter.\n\tValidateOnly bool                     // Whether the alteration should be validated, but not performed.\n}\n\nfunc (a *AlterClientQuotasRequest) setVersion(v int16) {\n\ta.Version = v\n}\n\ntype AlterClientQuotasEntry struct {\n\tEntity []QuotaEntityComponent // The quota entity to alter.\n\tOps    []ClientQuotasOp       // An individual quota configuration entry to alter.\n}\n\ntype ClientQuotasOp struct {\n\tKey    string  // The quota configuration key.\n\tValue  float64 // The value to set, otherwise ignored if the value is to be removed.\n\tRemove bool    // Whether the quota configuration value should be removed, otherwise set.\n}\n\nfunc (a *AlterClientQuotasRequest) encode(pe packetEncoder) error {\n\t// Entries\n\tif err := pe.putArrayLength(len(a.Entries)); err != nil {\n\t\treturn err\n\t}\n\tfor _, e := range a.Entries {\n\t\tif err := e.encode(pe); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// ValidateOnly\n\tpe.putBool(a.ValidateOnly)\n\n\treturn nil\n}\n\nfunc (a *AlterClientQuotasRequest) decode(pd packetDecoder, version int16) error {\n\t// Entries\n\tentryCount, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif entryCount > 0 {\n\t\ta.Entries = make([]AlterClientQuotasEntry, entryCount)\n\t\tfor i := range a.Entries {\n\t\t\te := AlterClientQuotasEntry{}\n\t\t\tif err = e.decode(pd, version); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\ta.Entries[i] = e\n\t\t}\n\t} else {\n\t\ta.Entries = []AlterClientQuotasEntry{}\n\t}\n\n\t// ValidateOnly\n\tvalidateOnly, err := pd.getBool()\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.ValidateOnly = validateOnly\n\n\treturn nil\n}\n\nfunc (a *AlterClientQuotasEntry) encode(pe packetEncoder) error {\n\t// Entity\n\tif err := pe.putArrayLength(len(a.Entity)); err != nil {\n\t\treturn err\n\t}\n\tfor _, component := range a.Entity {\n\t\tif err := component.encode(pe); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Ops\n\tif err := pe.putArrayLength(len(a.Ops)); err != nil {\n\t\treturn err\n\t}\n\tfor _, o := range a.Ops {\n\t\tif err := o.encode(pe); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (a *AlterClientQuotasEntry) decode(pd packetDecoder, version int16) error {\n\t// Entity\n\tcomponentCount, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif componentCount > 0 {\n\t\ta.Entity = make([]QuotaEntityComponent, componentCount)\n\t\tfor i := 0; i < componentCount; i++ {\n\t\t\tcomponent := QuotaEntityComponent{}\n\t\t\tif err := component.decode(pd, version); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\ta.Entity[i] = component\n\t\t}\n\t} else {\n\t\ta.Entity = []QuotaEntityComponent{}\n\t}\n\n\t// Ops\n\topCount, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif opCount > 0 {\n\t\ta.Ops = make([]ClientQuotasOp, opCount)\n\t\tfor i := range a.Ops {\n\t\t\tc := ClientQuotasOp{}\n\t\t\tif err = c.decode(pd, version); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\ta.Ops[i] = c\n\t\t}\n\t} else {\n\t\ta.Ops = []ClientQuotasOp{}\n\t}\n\n\treturn nil\n}\n\nfunc (c *ClientQuotasOp) encode(pe packetEncoder) error {\n\t// Key\n\tif err := pe.putString(c.Key); err != nil {\n\t\treturn err\n\t}\n\n\t// Value\n\tpe.putFloat64(c.Value)\n\n\t// Remove\n\tpe.putBool(c.Remove)\n\n\treturn nil\n}\n\nfunc (c *ClientQuotasOp) decode(pd packetDecoder, version int16) error {\n\t// Key\n\tkey, err := pd.getString()\n\tif err != nil {\n\t\treturn err\n\t}\n\tc.Key = key\n\n\t// Value\n\tvalue, err := pd.getFloat64()\n\tif err != nil {\n\t\treturn err\n\t}\n\tc.Value = value\n\n\t// Remove\n\tremove, err := pd.getBool()\n\tif err != nil {\n\t\treturn err\n\t}\n\tc.Remove = remove\n\n\treturn nil\n}\n\nfunc (a *AlterClientQuotasRequest) key() int16 {\n\treturn apiKeyAlterClientQuotas\n}\n\nfunc (a *AlterClientQuotasRequest) version() int16 {\n\treturn a.Version\n}\n\nfunc (a *AlterClientQuotasRequest) headerVersion() int16 {\n\treturn 1\n}\n\nfunc (a *AlterClientQuotasRequest) isValidVersion() bool {\n\treturn a.Version == 0\n}\n\nfunc (a *AlterClientQuotasRequest) requiredVersion() KafkaVersion {\n\treturn V2_6_0_0\n}\n"
  },
  {
    "path": "alter_client_quotas_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nvar (\n\talterClientQuotasRequestSingleOp = []byte{\n\t\t0, 0, 0, 1, // entries len\n\t\t0, 0, 0, 1, // entity len\n\t\t0, 4, 'u', 's', 'e', 'r', // entity type\n\t\t255, 255, // entity value\n\t\t0, 0, 0, 1, // ops len\n\t\t0, 18, 'p', 'r', 'o', 'd', 'u', 'c', 'e', 'r', '_', 'b', 'y', 't', 'e', '_', 'r', 'a', 't', 'e', // op key\n\t\t65, 46, 132, 128, 0, 0, 0, 0, // op value (1000000)\n\t\t0, // remove\n\t\t0, // validate only\n\t}\n\n\talterClientQuotasRequestRemoveSingleOp = []byte{\n\t\t0, 0, 0, 1, // entries len\n\t\t0, 0, 0, 1, // entity len\n\t\t0, 4, 'u', 's', 'e', 'r', // entity type\n\t\t255, 255, // entity value\n\t\t0, 0, 0, 1, // ops len\n\t\t0, 18, 'p', 'r', 'o', 'd', 'u', 'c', 'e', 'r', '_', 'b', 'y', 't', 'e', '_', 'r', 'a', 't', 'e', // op key\n\t\t0, 0, 0, 0, 0, 0, 0, 0, // op value (ignored)\n\t\t1, // remove\n\t\t1, // validate only\n\t}\n\n\talterClientQuotasRequestMultipleOps = []byte{\n\t\t0, 0, 0, 1, // entries len\n\t\t0, 0, 0, 1, // entity len\n\t\t0, 4, 'u', 's', 'e', 'r', // entity type\n\t\t255, 255, // entity value\n\t\t0, 0, 0, 2, // ops len\n\t\t0, 18, 'p', 'r', 'o', 'd', 'u', 'c', 'e', 'r', '_', 'b', 'y', 't', 'e', '_', 'r', 'a', 't', 'e', // op key\n\t\t65, 46, 132, 128, 0, 0, 0, 0, // op value (1000000)\n\t\t0,                                                                                               // remove\n\t\t0, 18, 'c', 'o', 'n', 's', 'u', 'm', 'e', 'r', '_', 'b', 'y', 't', 'e', '_', 'r', 'a', 't', 'e', // op key\n\t\t65, 46, 132, 128, 0, 0, 0, 0, // op value (1000000)\n\t\t0, // remove\n\t\t0, // validate only\n\t}\n\n\talterClientQuotasRequestMultipleQuotasEntries = []byte{\n\t\t0, 0, 0, 2, // entries len\n\t\t0, 0, 0, 1, // entity len\n\t\t0, 4, 'u', 's', 'e', 'r', // entity type\n\t\t255, 255, // entity value\n\t\t0, 0, 0, 1, // ops len\n\t\t0, 18, 'p', 'r', 'o', 'd', 'u', 'c', 'e', 'r', '_', 'b', 'y', 't', 'e', '_', 'r', 'a', 't', 'e', // op key\n\t\t65, 46, 132, 128, 0, 0, 0, 0, // op value (1000000)\n\t\t0,          // remove\n\t\t0, 0, 0, 1, // entity len\n\t\t0, 9, 'c', 'l', 'i', 'e', 'n', 't', '-', 'i', 'd', // entity type\n\t\t255, 255, // entity value\n\t\t0, 0, 0, 1, // ops len\n\t\t0, 18, 'c', 'o', 'n', 's', 'u', 'm', 'e', 'r', '_', 'b', 'y', 't', 'e', '_', 'r', 'a', 't', 'e', // op key\n\t\t65, 46, 132, 128, 0, 0, 0, 0, // op value (1000000)\n\t\t0, // remove\n\t\t0, // validate only\n\t}\n)\n\nfunc TestAlterClientQuotasRequest(t *testing.T) {\n\t// default user\n\tdefaultUserComponent := QuotaEntityComponent{\n\t\tEntityType: QuotaEntityUser,\n\t\tMatchType:  QuotaMatchDefault,\n\t}\n\n\t// default client-id\n\tdefaultClientIDComponent := QuotaEntityComponent{\n\t\tEntityType: QuotaEntityClientID,\n\t\tMatchType:  QuotaMatchDefault,\n\t}\n\n\t// Add Quota to default user\n\top := ClientQuotasOp{\n\t\tKey:    \"producer_byte_rate\",\n\t\tValue:  1000000,\n\t\tRemove: false,\n\t}\n\tentry := AlterClientQuotasEntry{\n\t\tEntity: []QuotaEntityComponent{defaultUserComponent},\n\t\tOps:    []ClientQuotasOp{op},\n\t}\n\treq := &AlterClientQuotasRequest{\n\t\tEntries:      []AlterClientQuotasEntry{entry},\n\t\tValidateOnly: false,\n\t}\n\ttestRequest(t, \"Add single Quota op\", req, alterClientQuotasRequestSingleOp)\n\n\t// Remove Quota from default user\n\top = ClientQuotasOp{\n\t\tKey:    \"producer_byte_rate\",\n\t\tRemove: true,\n\t}\n\tentry = AlterClientQuotasEntry{\n\t\tEntity: []QuotaEntityComponent{defaultUserComponent},\n\t\tOps:    []ClientQuotasOp{op},\n\t}\n\treq = &AlterClientQuotasRequest{\n\t\tEntries:      []AlterClientQuotasEntry{entry},\n\t\tValidateOnly: true,\n\t}\n\ttestRequest(t, \"Remove single Quota op\", req, alterClientQuotasRequestRemoveSingleOp)\n\n\t// Add multiple Quotas ops\n\top1 := ClientQuotasOp{\n\t\tKey:    \"producer_byte_rate\",\n\t\tValue:  1000000,\n\t\tRemove: false,\n\t}\n\top2 := ClientQuotasOp{\n\t\tKey:    \"consumer_byte_rate\",\n\t\tValue:  1000000,\n\t\tRemove: false,\n\t}\n\tentry = AlterClientQuotasEntry{\n\t\tEntity: []QuotaEntityComponent{defaultUserComponent},\n\t\tOps:    []ClientQuotasOp{op1, op2},\n\t}\n\treq = &AlterClientQuotasRequest{\n\t\tEntries:      []AlterClientQuotasEntry{entry},\n\t\tValidateOnly: false,\n\t}\n\ttestRequest(t, \"Add multiple Quota ops\", req, alterClientQuotasRequestMultipleOps)\n\n\t// Add multiple Quotas Entries\n\top1 = ClientQuotasOp{\n\t\tKey:    \"producer_byte_rate\",\n\t\tValue:  1000000,\n\t\tRemove: false,\n\t}\n\tentry1 := AlterClientQuotasEntry{\n\t\tEntity: []QuotaEntityComponent{defaultUserComponent},\n\t\tOps:    []ClientQuotasOp{op1},\n\t}\n\top2 = ClientQuotasOp{\n\t\tKey:    \"consumer_byte_rate\",\n\t\tValue:  1000000,\n\t\tRemove: false,\n\t}\n\tentry2 := AlterClientQuotasEntry{\n\t\tEntity: []QuotaEntityComponent{defaultClientIDComponent},\n\t\tOps:    []ClientQuotasOp{op2},\n\t}\n\treq = &AlterClientQuotasRequest{\n\t\tEntries:      []AlterClientQuotasEntry{entry1, entry2},\n\t\tValidateOnly: false,\n\t}\n\ttestRequest(t, \"Add multiple Quotas Entries\", req, alterClientQuotasRequestMultipleQuotasEntries)\n}\n"
  },
  {
    "path": "alter_client_quotas_response.go",
    "content": "package sarama\n\nimport (\n\t\"time\"\n)\n\n// AlterClientQuotas Response (Version: 0) => throttle_time_ms [entries]\n//   throttle_time_ms => INT32\n//   entries => error_code error_message [entity]\n//     error_code => INT16\n//     error_message => NULLABLE_STRING\n//     entity => entity_type entity_name\n//       entity_type => STRING\n//       entity_name => NULLABLE_STRING\n\ntype AlterClientQuotasResponse struct {\n\tVersion      int16\n\tThrottleTime time.Duration                    // The duration in milliseconds for which the request was throttled due to a quota violation, or zero if the request did not violate any quota.\n\tEntries      []AlterClientQuotasEntryResponse // The quota configuration entries altered.\n}\n\nfunc (a *AlterClientQuotasResponse) setVersion(v int16) {\n\ta.Version = v\n}\n\ntype AlterClientQuotasEntryResponse struct {\n\tErrorCode KError                 // The error code, or `0` if the quota alteration succeeded.\n\tErrorMsg  *string                // The error message, or `null` if the quota alteration succeeded.\n\tEntity    []QuotaEntityComponent // The quota entity altered.\n}\n\nfunc (a *AlterClientQuotasResponse) encode(pe packetEncoder) error {\n\tpe.putDurationMs(a.ThrottleTime)\n\n\t// Entries\n\tif err := pe.putArrayLength(len(a.Entries)); err != nil {\n\t\treturn err\n\t}\n\tfor _, e := range a.Entries {\n\t\tif err := e.encode(pe); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (a *AlterClientQuotasResponse) decode(pd packetDecoder, version int16) (err error) {\n\tif a.ThrottleTime, err = pd.getDurationMs(); err != nil {\n\t\treturn err\n\t}\n\n\t// Entries\n\tentryCount, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif entryCount > 0 {\n\t\ta.Entries = make([]AlterClientQuotasEntryResponse, entryCount)\n\t\tfor i := range a.Entries {\n\t\t\te := AlterClientQuotasEntryResponse{}\n\t\t\tif err = e.decode(pd, version); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\ta.Entries[i] = e\n\t\t}\n\t} else {\n\t\ta.Entries = []AlterClientQuotasEntryResponse{}\n\t}\n\n\treturn nil\n}\n\nfunc (a *AlterClientQuotasEntryResponse) encode(pe packetEncoder) error {\n\t// ErrorCode\n\tpe.putKError(a.ErrorCode)\n\n\t// ErrorMsg\n\tif err := pe.putNullableString(a.ErrorMsg); err != nil {\n\t\treturn err\n\t}\n\n\t// Entity\n\tif err := pe.putArrayLength(len(a.Entity)); err != nil {\n\t\treturn err\n\t}\n\tfor _, component := range a.Entity {\n\t\tif err := component.encode(pe); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (a *AlterClientQuotasEntryResponse) decode(pd packetDecoder, version int16) (err error) {\n\t// ErrorCode\n\ta.ErrorCode, err = pd.getKError()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// ErrorMsg\n\terrMsg, err := pd.getNullableString()\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.ErrorMsg = errMsg\n\n\t// Entity\n\tcomponentCount, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif componentCount > 0 {\n\t\ta.Entity = make([]QuotaEntityComponent, componentCount)\n\t\tfor i := 0; i < componentCount; i++ {\n\t\t\tcomponent := QuotaEntityComponent{}\n\t\t\tif err := component.decode(pd, version); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\ta.Entity[i] = component\n\t\t}\n\t} else {\n\t\ta.Entity = []QuotaEntityComponent{}\n\t}\n\n\treturn nil\n}\n\nfunc (a *AlterClientQuotasResponse) key() int16 {\n\treturn apiKeyAlterClientQuotas\n}\n\nfunc (a *AlterClientQuotasResponse) version() int16 {\n\treturn a.Version\n}\n\nfunc (a *AlterClientQuotasResponse) headerVersion() int16 {\n\treturn 0\n}\n\nfunc (a *AlterClientQuotasResponse) isValidVersion() bool {\n\treturn a.Version == 0\n}\n\nfunc (a *AlterClientQuotasResponse) requiredVersion() KafkaVersion {\n\treturn V2_6_0_0\n}\n\nfunc (r *AlterClientQuotasResponse) throttleTime() time.Duration {\n\treturn r.ThrottleTime\n}\n"
  },
  {
    "path": "alter_client_quotas_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nvar (\n\talterClientQuotasResponseError = []byte{\n\t\t0, 0, 0, 0, // ThrottleTime\n\t\t0, 0, 0, 1, // Entries len\n\t\t0, 42, // ErrorCode (ErrInvalidRequest)\n\t\t0, 42, 'U', 'n', 'h', 'a', 'n', 'd', 'l', 'e', 'd', ' ', 'c', 'l', 'i', 'e', 'n', 't', ' ', 'q', 'u', 'o', 't', 'a', ' ', 'e', 'n', 't', 'i', 't', 'y', ' ', 't', 'y', 'p', 'e', ':', ' ', 'f', 'a', 'u', 'l', 't', 'y', // ErrorMsg\n\t\t0, 0, 0, 1, // Entity len\n\t\t0, 6, 'f', 'a', 'u', 'l', 't', 'y', // entityType\n\t\t255, 255, // entityName\n\t}\n\n\talterClientQuotasResponseSingleEntry = []byte{\n\t\t0, 0, 0, 0, // ThrottleTime\n\t\t0, 0, 0, 1, // Entries len\n\t\t0, 0, // ErrorCode\n\t\t255, 255, // ErrorMsg\n\t\t0, 0, 0, 1, // Entity len\n\t\t0, 4, 'u', 's', 'e', 'r', // entityType\n\t\t255, 255, // entityName\n\t}\n\n\talterClientQuotasResponseMultipleEntries = []byte{\n\t\t0, 0, 0, 0, // ThrottleTime\n\t\t0, 0, 0, 2, // Entries len\n\t\t0, 0, // ErrorCode\n\t\t255, 255, // ErrorMsg\n\t\t0, 0, 0, 1, // Entity len\n\t\t0, 4, 'u', 's', 'e', 'r', // entityType\n\t\t255, 255, // entityName\n\t\t0, 0, // ErrorCode\n\t\t255, 255, // ErrorMsg\n\t\t0, 0, 0, 1, // Entity len\n\t\t0, 9, 'c', 'l', 'i', 'e', 'n', 't', '-', 'i', 'd', // entityType\n\t\t255, 255, // entityName\n\t}\n)\n\nfunc TestAlterClientQuotasResponse(t *testing.T) {\n\t// default user\n\tdefaultUserComponent := QuotaEntityComponent{\n\t\tEntityType: QuotaEntityUser,\n\t\tMatchType:  QuotaMatchDefault,\n\t}\n\n\t// default client-id\n\tdefaultClientIDComponent := QuotaEntityComponent{\n\t\tEntityType: QuotaEntityClientID,\n\t\tMatchType:  QuotaMatchDefault,\n\t}\n\n\t// Response with error\n\terrMsg := \"Unhandled client quota entity type: faulty\"\n\tfaultEntityComponent := QuotaEntityComponent{\n\t\tEntityType: QuotaEntityType(\"faulty\"),\n\t\tMatchType:  QuotaMatchDefault,\n\t}\n\tentry := AlterClientQuotasEntryResponse{\n\t\tErrorCode: KError(42),\n\t\tErrorMsg:  &errMsg,\n\t\tEntity:    []QuotaEntityComponent{faultEntityComponent},\n\t}\n\tres := &AlterClientQuotasResponse{\n\t\tThrottleTime: 0,\n\t\tEntries:      []AlterClientQuotasEntryResponse{entry},\n\t}\n\ttestResponse(t, \"Response With Error\", res, alterClientQuotasResponseError)\n\n\t// Response Altered single entry\n\tentry = AlterClientQuotasEntryResponse{\n\t\tEntity: []QuotaEntityComponent{defaultUserComponent},\n\t}\n\tres = &AlterClientQuotasResponse{\n\t\tThrottleTime: 0,\n\t\tEntries:      []AlterClientQuotasEntryResponse{entry},\n\t}\n\ttestResponse(t, \"Altered single entry\", res, alterClientQuotasResponseSingleEntry)\n\n\t// Response Altered multiple entries\n\tentry1 := AlterClientQuotasEntryResponse{\n\t\tEntity: []QuotaEntityComponent{defaultUserComponent},\n\t}\n\tentry2 := AlterClientQuotasEntryResponse{\n\t\tEntity: []QuotaEntityComponent{defaultClientIDComponent},\n\t}\n\tres = &AlterClientQuotasResponse{\n\t\tThrottleTime: 0,\n\t\tEntries:      []AlterClientQuotasEntryResponse{entry1, entry2},\n\t}\n\ttestResponse(t, \"Altered multiple entries\", res, alterClientQuotasResponseMultipleEntries)\n}\n"
  },
  {
    "path": "alter_configs_request.go",
    "content": "package sarama\n\n// AlterConfigsRequest is an alter config request type\ntype AlterConfigsRequest struct {\n\tVersion      int16\n\tResources    []*AlterConfigsResource\n\tValidateOnly bool\n}\n\nfunc (a *AlterConfigsRequest) setVersion(v int16) {\n\ta.Version = v\n}\n\n// AlterConfigsResource is an alter config resource type\ntype AlterConfigsResource struct {\n\tType          ConfigResourceType\n\tName          string\n\tConfigEntries map[string]*string\n}\n\nfunc (a *AlterConfigsRequest) encode(pe packetEncoder) error {\n\tif err := pe.putArrayLength(len(a.Resources)); err != nil {\n\t\treturn err\n\t}\n\n\tfor _, r := range a.Resources {\n\t\tif err := r.encode(pe); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tpe.putBool(a.ValidateOnly)\n\treturn nil\n}\n\nfunc (a *AlterConfigsRequest) decode(pd packetDecoder, version int16) error {\n\tresourceCount, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ta.Resources = make([]*AlterConfigsResource, resourceCount)\n\tfor i := range a.Resources {\n\t\tr := &AlterConfigsResource{}\n\t\terr = r.decode(pd, version)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Resources[i] = r\n\t}\n\n\tvalidateOnly, err := pd.getBool()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ta.ValidateOnly = validateOnly\n\n\treturn nil\n}\n\nfunc (a *AlterConfigsResource) encode(pe packetEncoder) error {\n\tpe.putInt8(int8(a.Type))\n\n\tif err := pe.putString(a.Name); err != nil {\n\t\treturn err\n\t}\n\n\tif err := pe.putArrayLength(len(a.ConfigEntries)); err != nil {\n\t\treturn err\n\t}\n\tfor configKey, configValue := range a.ConfigEntries {\n\t\tif err := pe.putString(configKey); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := pe.putNullableString(configValue); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (a *AlterConfigsResource) decode(pd packetDecoder, version int16) error {\n\tt, err := pd.getInt8()\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.Type = ConfigResourceType(t)\n\n\tname, err := pd.getString()\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.Name = name\n\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif n > 0 {\n\t\ta.ConfigEntries = make(map[string]*string, n)\n\t\tfor i := 0; i < n; i++ {\n\t\t\tconfigKey, err := pd.getString()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif a.ConfigEntries[configKey], err = pd.getNullableString(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn err\n}\n\nfunc (a *AlterConfigsRequest) key() int16 {\n\treturn apiKeyAlterConfigs\n}\n\nfunc (a *AlterConfigsRequest) version() int16 {\n\treturn a.Version\n}\n\nfunc (a *AlterConfigsRequest) headerVersion() int16 {\n\treturn 1\n}\n\nfunc (a *AlterConfigsRequest) isValidVersion() bool {\n\treturn a.Version >= 0 && a.Version <= 1\n}\n\nfunc (a *AlterConfigsRequest) requiredVersion() KafkaVersion {\n\tswitch a.Version {\n\tcase 1:\n\t\treturn V2_0_0_0\n\tcase 0:\n\t\treturn V0_11_0_0\n\tdefault:\n\t\treturn V2_0_0_0\n\t}\n}\n"
  },
  {
    "path": "alter_configs_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nvar (\n\temptyAlterConfigsRequest = []byte{\n\t\t0, 0, 0, 0, // 0 configs\n\t\t0, // don't Validate\n\t}\n\n\tsingleAlterConfigsRequest = []byte{\n\t\t0, 0, 0, 1, // 1 config\n\t\t2,                   // a topic\n\t\t0, 3, 'f', 'o', 'o', // topic name: foo\n\t\t0, 0, 0, 1, // 1 config name\n\t\t0, 10, // 10 chars\n\t\t's', 'e', 'g', 'm', 'e', 'n', 't', '.', 'm', 's',\n\t\t0, 4,\n\t\t'1', '0', '0', '0',\n\t\t0, // don't validate\n\t}\n\n\tdoubleAlterConfigsRequest = []byte{\n\t\t0, 0, 0, 2, // 2 config\n\t\t2,                   // a topic\n\t\t0, 3, 'f', 'o', 'o', // topic name: foo\n\t\t0, 0, 0, 1, // 1 config name\n\t\t0, 10, // 10 chars\n\t\t's', 'e', 'g', 'm', 'e', 'n', 't', '.', 'm', 's',\n\t\t0, 4,\n\t\t'1', '0', '0', '0',\n\t\t2,                   // a topic\n\t\t0, 3, 'b', 'a', 'r', // topic name: foo\n\t\t0, 0, 0, 1, // 2 config\n\t\t0, 12, // 12 chars\n\t\t'r', 'e', 't', 'e', 'n', 't', 'i', 'o', 'n', '.', 'm', 's',\n\t\t0, 4,\n\t\t'1', '0', '0', '0',\n\t\t0, // don't validate\n\t}\n)\n\nfunc TestAlterConfigsRequest(t *testing.T) {\n\tvar request *AlterConfigsRequest\n\n\trequest = &AlterConfigsRequest{\n\t\tResources: []*AlterConfigsResource{},\n\t}\n\ttestRequest(t, \"no requests\", request, emptyAlterConfigsRequest)\n\n\tconfigValue := \"1000\"\n\trequest = &AlterConfigsRequest{\n\t\tResources: []*AlterConfigsResource{\n\t\t\t{\n\t\t\t\tType: TopicResource,\n\t\t\t\tName: \"foo\",\n\t\t\t\tConfigEntries: map[string]*string{\n\t\t\t\t\t\"segment.ms\": &configValue,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\ttestRequest(t, \"one config\", request, singleAlterConfigsRequest)\n\n\trequest = &AlterConfigsRequest{\n\t\tResources: []*AlterConfigsResource{\n\t\t\t{\n\t\t\t\tType: TopicResource,\n\t\t\t\tName: \"foo\",\n\t\t\t\tConfigEntries: map[string]*string{\n\t\t\t\t\t\"segment.ms\": &configValue,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: TopicResource,\n\t\t\t\tName: \"bar\",\n\t\t\t\tConfigEntries: map[string]*string{\n\t\t\t\t\t\"retention.ms\": &configValue,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\ttestRequest(t, \"two configs\", request, doubleAlterConfigsRequest)\n}\n"
  },
  {
    "path": "alter_configs_response.go",
    "content": "package sarama\n\nimport (\n\t\"fmt\"\n\t\"time\"\n)\n\n// AlterConfigsResponse is a response type for alter config\ntype AlterConfigsResponse struct {\n\tVersion      int16\n\tThrottleTime time.Duration\n\tResources    []*AlterConfigsResourceResponse\n}\n\nfunc (a *AlterConfigsResponse) setVersion(v int16) {\n\ta.Version = v\n}\n\ntype AlterConfigError struct {\n\tErr    KError\n\tErrMsg string\n}\n\nfunc (c *AlterConfigError) Error() string {\n\ttext := c.Err.Error()\n\tif c.ErrMsg != \"\" {\n\t\ttext = fmt.Sprintf(\"%s - %s\", text, c.ErrMsg)\n\t}\n\treturn text\n}\n\n// AlterConfigsResourceResponse is a response type for alter config resource\ntype AlterConfigsResourceResponse struct {\n\tErrorCode int16\n\tErrorMsg  string\n\tType      ConfigResourceType\n\tName      string\n}\n\nfunc (a *AlterConfigsResponse) encode(pe packetEncoder) error {\n\tpe.putDurationMs(a.ThrottleTime)\n\n\tif err := pe.putArrayLength(len(a.Resources)); err != nil {\n\t\treturn err\n\t}\n\n\tfor _, v := range a.Resources {\n\t\tif err := v.encode(pe); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (a *AlterConfigsResponse) decode(pd packetDecoder, version int16) (err error) {\n\tif a.ThrottleTime, err = pd.getDurationMs(); err != nil {\n\t\treturn err\n\t}\n\n\tresponseCount, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ta.Resources = make([]*AlterConfigsResourceResponse, responseCount)\n\n\tfor i := range a.Resources {\n\t\ta.Resources[i] = new(AlterConfigsResourceResponse)\n\n\t\tif err := a.Resources[i].decode(pd, version); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (a *AlterConfigsResourceResponse) encode(pe packetEncoder) error {\n\tpe.putInt16(a.ErrorCode)\n\terr := pe.putString(a.ErrorMsg)\n\tif err != nil {\n\t\treturn err\n\t}\n\tpe.putInt8(int8(a.Type))\n\terr = pe.putString(a.Name)\n\tif err != nil {\n\t\treturn err\n\t}\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (a *AlterConfigsResourceResponse) decode(pd packetDecoder, version int16) error {\n\terrCode, err := pd.getInt16()\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.ErrorCode = errCode\n\n\te, err := pd.getNullableString()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif e == nil {\n\t\ta.ErrorMsg = \"\"\n\t} else {\n\t\ta.ErrorMsg = *e\n\t}\n\n\tt, err := pd.getInt8()\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.Type = ConfigResourceType(t)\n\n\tname, err := pd.getString()\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.Name = name\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (a *AlterConfigsResponse) key() int16 {\n\treturn apiKeyAlterConfigs\n}\n\nfunc (a *AlterConfigsResponse) version() int16 {\n\treturn a.Version\n}\n\nfunc (a *AlterConfigsResponse) headerVersion() int16 {\n\treturn 0\n}\n\nfunc (a *AlterConfigsResponse) isValidVersion() bool {\n\treturn a.Version >= 0 && a.Version <= 1\n}\n\nfunc (a *AlterConfigsResponse) requiredVersion() KafkaVersion {\n\tswitch a.Version {\n\tcase 1:\n\t\treturn V2_0_0_0\n\tcase 0:\n\t\treturn V0_11_0_0\n\tdefault:\n\t\treturn V2_0_0_0\n\t}\n}\n\nfunc (r *AlterConfigsResponse) throttleTime() time.Duration {\n\treturn r.ThrottleTime\n}\n"
  },
  {
    "path": "alter_configs_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"testing\"\n)\n\nvar (\n\talterResponseEmpty = []byte{\n\t\t0, 0, 0, 0, // throttle\n\t\t0, 0, 0, 0, // no configs\n\t}\n\n\talterResponsePopulated = []byte{\n\t\t0, 0, 0, 0, // throttle\n\t\t0, 0, 0, 1, // response\n\t\t0, 0, // errorcode\n\t\t0, 0, // string\n\t\t2, // topic\n\t\t0, 3, 'f', 'o', 'o',\n\t}\n)\n\nfunc TestAlterConfigsResponse(t *testing.T) {\n\tvar response *AlterConfigsResponse\n\n\tresponse = &AlterConfigsResponse{\n\t\tResources: []*AlterConfigsResourceResponse{},\n\t}\n\ttestVersionDecodable(t, \"empty\", response, alterResponseEmpty, 0)\n\tif len(response.Resources) != 0 {\n\t\tt.Error(\"Expected no groups\")\n\t}\n\n\tresponse = &AlterConfigsResponse{\n\t\tResources: []*AlterConfigsResourceResponse{\n\t\t\t{\n\t\t\t\tErrorCode: 0,\n\t\t\t\tErrorMsg:  \"\",\n\t\t\t\tType:      TopicResource,\n\t\t\t\tName:      \"foo\",\n\t\t\t},\n\t\t},\n\t}\n\ttestResponse(t, \"response with error\", response, alterResponsePopulated)\n}\n"
  },
  {
    "path": "alter_partition_reassignments_request.go",
    "content": "package sarama\n\ntype alterPartitionReassignmentsBlock struct {\n\treplicas []int32\n}\n\nfunc (b *alterPartitionReassignmentsBlock) encode(pe packetEncoder) error {\n\tif err := pe.putNullableInt32Array(b.replicas); err != nil {\n\t\treturn err\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (b *alterPartitionReassignmentsBlock) decode(pd packetDecoder) (err error) {\n\tif b.replicas, err = pd.getInt32Array(); err != nil {\n\t\treturn err\n\t}\n\tif _, err := pd.getEmptyTaggedFieldArray(); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\ntype AlterPartitionReassignmentsRequest struct {\n\tTimeoutMs int32\n\tblocks    map[string]map[int32]*alterPartitionReassignmentsBlock\n\tVersion   int16\n}\n\nfunc (r *AlterPartitionReassignmentsRequest) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *AlterPartitionReassignmentsRequest) encode(pe packetEncoder) error {\n\tpe.putInt32(r.TimeoutMs)\n\n\tif err := pe.putArrayLength(len(r.blocks)); err != nil {\n\t\treturn err\n\t}\n\n\tfor topic, partitions := range r.blocks {\n\t\tif err := pe.putString(topic); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := pe.putArrayLength(len(partitions)); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor partition, block := range partitions {\n\t\t\tpe.putInt32(partition)\n\t\t\tif err := block.encode(pe); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tpe.putEmptyTaggedFieldArray()\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\n\treturn nil\n}\n\nfunc (r *AlterPartitionReassignmentsRequest) decode(pd packetDecoder, version int16) (err error) {\n\tr.Version = version\n\n\tif r.TimeoutMs, err = pd.getInt32(); err != nil {\n\t\treturn err\n\t}\n\n\ttopicCount, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif topicCount > 0 {\n\t\tr.blocks = make(map[string]map[int32]*alterPartitionReassignmentsBlock)\n\t\tfor i := 0; i < topicCount; i++ {\n\t\t\ttopic, err := pd.getString()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tpartitionCount, err := pd.getArrayLength()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tr.blocks[topic] = make(map[int32]*alterPartitionReassignmentsBlock)\n\t\t\tfor j := 0; j < partitionCount; j++ {\n\t\t\t\tpartition, err := pd.getInt32()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tblock := &alterPartitionReassignmentsBlock{}\n\t\t\t\tif err := block.decode(pd); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tr.blocks[topic][partition] = block\n\t\t\t}\n\t\t\tif _, err := pd.getEmptyTaggedFieldArray(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (r *AlterPartitionReassignmentsRequest) key() int16 {\n\treturn apiKeyAlterPartitionReassignments\n}\n\nfunc (r *AlterPartitionReassignmentsRequest) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *AlterPartitionReassignmentsRequest) headerVersion() int16 {\n\treturn 2\n}\n\nfunc (r *AlterPartitionReassignmentsRequest) isValidVersion() bool {\n\treturn r.Version == 0\n}\n\nfunc (r *AlterPartitionReassignmentsRequest) isFlexible() bool {\n\treturn r.isFlexibleVersion(r.Version)\n}\n\nfunc (r *AlterPartitionReassignmentsRequest) isFlexibleVersion(version int16) bool {\n\treturn version >= 0\n}\n\nfunc (r *AlterPartitionReassignmentsRequest) requiredVersion() KafkaVersion {\n\treturn V2_4_0_0\n}\n\nfunc (r *AlterPartitionReassignmentsRequest) AddBlock(topic string, partitionID int32, replicas []int32) {\n\tif r.blocks == nil {\n\t\tr.blocks = make(map[string]map[int32]*alterPartitionReassignmentsBlock)\n\t}\n\n\tif r.blocks[topic] == nil {\n\t\tr.blocks[topic] = make(map[int32]*alterPartitionReassignmentsBlock)\n\t}\n\n\tr.blocks[topic][partitionID] = &alterPartitionReassignmentsBlock{replicas}\n}\n"
  },
  {
    "path": "alter_partition_reassignments_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nvar (\n\talterPartitionReassignmentsRequestNoBlock = []byte{\n\t\t0, 0, 39, 16, // timeout 10000\n\t\t1, // 1-1=0 blocks\n\t\t0, // empty tagged fields\n\t}\n\n\talterPartitionReassignmentsRequestOneBlock = []byte{\n\t\t0, 0, 39, 16, // timeout 10000\n\t\t2,                         // 2-1=1 block\n\t\t6, 116, 111, 112, 105, 99, // topic name \"topic\" as compact string\n\t\t2,          // 2-1=1 partitions\n\t\t0, 0, 0, 0, // partitionId\n\t\t3,            // 3-1=2 replica array size\n\t\t0, 0, 3, 232, // replica 1000\n\t\t0, 0, 3, 233, // replica 1001\n\t\t0, 0, 0, // empty tagged fields\n\t}\n\n\talterPartitionReassignmentsAbortRequest = []byte{\n\t\t0, 0, 39, 16, // timeout 10000\n\t\t2,                         // 2-1=1 block\n\t\t6, 116, 111, 112, 105, 99, // topic name \"topic\" as compact string\n\t\t2,          // 2-1=1 partitions\n\t\t0, 0, 0, 0, // partitionId\n\t\t0,       // replica array is null (indicates that a pending reassignment should be aborted)\n\t\t0, 0, 0, // empty tagged fields\n\t}\n)\n\nfunc TestAlterPartitionReassignmentRequest(t *testing.T) {\n\tvar request *AlterPartitionReassignmentsRequest\n\n\trequest = &AlterPartitionReassignmentsRequest{\n\t\tTimeoutMs: int32(10000),\n\t\tVersion:   int16(0),\n\t}\n\n\ttestRequest(t, \"no block\", request, alterPartitionReassignmentsRequestNoBlock)\n\n\trequest.AddBlock(\"topic\", 0, []int32{1000, 1001})\n\n\ttestRequest(t, \"one block\", request, alterPartitionReassignmentsRequestOneBlock)\n\n\trequest = &AlterPartitionReassignmentsRequest{\n\t\tTimeoutMs: int32(10000),\n\t\tVersion:   int16(0),\n\t}\n\trequest.AddBlock(\"topic\", 0, nil)\n\n\ttestRequest(t, \"abort assignment\", request, alterPartitionReassignmentsAbortRequest)\n}\n"
  },
  {
    "path": "alter_partition_reassignments_response.go",
    "content": "package sarama\n\nimport \"time\"\n\ntype alterPartitionReassignmentsErrorBlock struct {\n\terrorCode    KError\n\terrorMessage *string\n}\n\nfunc (b *alterPartitionReassignmentsErrorBlock) encode(pe packetEncoder) error {\n\tpe.putKError(b.errorCode)\n\tif err := pe.putNullableString(b.errorMessage); err != nil {\n\t\treturn err\n\t}\n\tpe.putEmptyTaggedFieldArray()\n\n\treturn nil\n}\n\nfunc (b *alterPartitionReassignmentsErrorBlock) decode(pd packetDecoder) (err error) {\n\tb.errorCode, err = pd.getKError()\n\tif err != nil {\n\t\treturn err\n\t}\n\tb.errorMessage, err = pd.getNullableString()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\ntype AlterPartitionReassignmentsResponse struct {\n\tVersion        int16\n\tThrottleTimeMs int32\n\tErrorCode      KError\n\tErrorMessage   *string\n\tErrors         map[string]map[int32]*alterPartitionReassignmentsErrorBlock\n}\n\nfunc (r *AlterPartitionReassignmentsResponse) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *AlterPartitionReassignmentsResponse) AddError(topic string, partition int32, kerror KError, message *string) {\n\tif r.Errors == nil {\n\t\tr.Errors = make(map[string]map[int32]*alterPartitionReassignmentsErrorBlock)\n\t}\n\tpartitions := r.Errors[topic]\n\tif partitions == nil {\n\t\tpartitions = make(map[int32]*alterPartitionReassignmentsErrorBlock)\n\t\tr.Errors[topic] = partitions\n\t}\n\n\tpartitions[partition] = &alterPartitionReassignmentsErrorBlock{errorCode: kerror, errorMessage: message}\n}\n\nfunc (r *AlterPartitionReassignmentsResponse) encode(pe packetEncoder) error {\n\tpe.putInt32(r.ThrottleTimeMs)\n\tpe.putKError(r.ErrorCode)\n\tif err := pe.putNullableString(r.ErrorMessage); err != nil {\n\t\treturn err\n\t}\n\n\tif err := pe.putArrayLength(len(r.Errors)); err != nil {\n\t\treturn err\n\t}\n\tfor topic, partitions := range r.Errors {\n\t\tif err := pe.putString(topic); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := pe.putArrayLength(len(partitions)); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor partition, block := range partitions {\n\t\t\tpe.putInt32(partition)\n\n\t\t\tif err := block.encode(pe); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tpe.putEmptyTaggedFieldArray()\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (r *AlterPartitionReassignmentsResponse) decode(pd packetDecoder, version int16) (err error) {\n\tr.Version = version\n\n\tif r.ThrottleTimeMs, err = pd.getInt32(); err != nil {\n\t\treturn err\n\t}\n\n\tr.ErrorCode, err = pd.getKError()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif r.ErrorMessage, err = pd.getNullableString(); err != nil {\n\t\treturn err\n\t}\n\n\tnumTopics, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif numTopics > 0 {\n\t\tr.Errors = make(map[string]map[int32]*alterPartitionReassignmentsErrorBlock, numTopics)\n\t\tfor i := 0; i < numTopics; i++ {\n\t\t\ttopic, err := pd.getString()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tongoingPartitionReassignments, err := pd.getArrayLength()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tr.Errors[topic] = make(map[int32]*alterPartitionReassignmentsErrorBlock, ongoingPartitionReassignments)\n\n\t\t\tfor j := 0; j < ongoingPartitionReassignments; j++ {\n\t\t\t\tpartition, err := pd.getInt32()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tblock := &alterPartitionReassignmentsErrorBlock{}\n\t\t\t\tif err := block.decode(pd); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\tr.Errors[topic][partition] = block\n\t\t\t}\n\t\t\tif _, err = pd.getEmptyTaggedFieldArray(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (r *AlterPartitionReassignmentsResponse) key() int16 {\n\treturn apiKeyAlterPartitionReassignments\n}\n\nfunc (r *AlterPartitionReassignmentsResponse) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *AlterPartitionReassignmentsResponse) headerVersion() int16 {\n\treturn 1\n}\n\nfunc (r *AlterPartitionReassignmentsResponse) isValidVersion() bool {\n\treturn r.Version == 0\n}\n\nfunc (r *AlterPartitionReassignmentsResponse) isFlexible() bool {\n\treturn r.isFlexibleVersion(r.Version)\n}\n\nfunc (r *AlterPartitionReassignmentsResponse) isFlexibleVersion(version int16) bool {\n\treturn version >= 0\n}\n\nfunc (r *AlterPartitionReassignmentsResponse) requiredVersion() KafkaVersion {\n\treturn V2_4_0_0\n}\n\nfunc (r *AlterPartitionReassignmentsResponse) throttleTime() time.Duration {\n\treturn time.Duration(r.ThrottleTimeMs) * time.Millisecond\n}\n"
  },
  {
    "path": "alter_partition_reassignments_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nvar (\n\talterPartitionReassignmentsResponseNoError = []byte{\n\t\t0, 0, 39, 16, // ThrottleTimeMs 10000\n\t\t0, 0, // errorcode\n\t\t0, // null string\n\t\t1, // empty errors array\n\t\t0, // empty tagged fields\n\t}\n\n\talterPartitionReassignmentsResponseWithError = []byte{\n\t\t0, 0, 39, 16, // ThrottleTimeMs 10000\n\t\t0, 12, // errorcode\n\t\t6, 101, 114, 114, 111, 114, // error string \"error\"\n\t\t2,                         // errors array length 1\n\t\t6, 116, 111, 112, 105, 99, // topic name \"topic\"\n\t\t2,          // partition array length 1\n\t\t0, 0, 0, 1, // partitionId\n\t\t0, 3, // kerror\n\t\t7, 101, 114, 114, 111, 114, 50, // error string \"error2\"\n\t\t0, 0, 0, // empty tagged fields\n\t}\n)\n\nfunc TestAlterPartitionReassignmentResponse(t *testing.T) {\n\tvar response *AlterPartitionReassignmentsResponse = &AlterPartitionReassignmentsResponse{\n\t\tThrottleTimeMs: int32(10000),\n\t\tVersion:        int16(0),\n\t}\n\n\ttestResponse(t, \"no error\", response, alterPartitionReassignmentsResponseNoError)\n\n\terrorMessage := \"error\"\n\tpartitionError := \"error2\"\n\tresponse.ErrorCode = 12\n\tresponse.ErrorMessage = &errorMessage\n\tresponse.AddError(\"topic\", 1, 3, &partitionError)\n\n\ttestResponse(t, \"with error\", response, alterPartitionReassignmentsResponseWithError)\n}\n"
  },
  {
    "path": "alter_user_scram_credentials_request.go",
    "content": "package sarama\n\ntype AlterUserScramCredentialsRequest struct {\n\tVersion int16\n\n\t// Deletions represent list of SCRAM credentials to remove\n\tDeletions []AlterUserScramCredentialsDelete\n\n\t// Upsertions represent list of SCRAM credentials to update/insert\n\tUpsertions []AlterUserScramCredentialsUpsert\n}\n\nfunc (r *AlterUserScramCredentialsRequest) setVersion(v int16) {\n\tr.Version = v\n}\n\ntype AlterUserScramCredentialsDelete struct {\n\tName      string\n\tMechanism ScramMechanismType\n}\n\ntype AlterUserScramCredentialsUpsert struct {\n\tName           string\n\tMechanism      ScramMechanismType\n\tIterations     int32\n\tSalt           []byte\n\tsaltedPassword []byte\n\n\t// This field is never transmitted over the wire\n\t// @see: https://tools.ietf.org/html/rfc5802\n\tPassword []byte // #nosec G117 -- SCRAM API requires this exported field name; value is not marshaled or logged.\n}\n\nfunc (r *AlterUserScramCredentialsRequest) encode(pe packetEncoder) error {\n\tif err := pe.putArrayLength(len(r.Deletions)); err != nil {\n\t\treturn err\n\t}\n\tfor _, d := range r.Deletions {\n\t\tif err := pe.putString(d.Name); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tpe.putInt8(int8(d.Mechanism))\n\t\tpe.putEmptyTaggedFieldArray()\n\t}\n\n\tif err := pe.putArrayLength(len(r.Upsertions)); err != nil {\n\t\treturn err\n\t}\n\tfor _, u := range r.Upsertions {\n\t\tif err := pe.putString(u.Name); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tpe.putInt8(int8(u.Mechanism))\n\t\tpe.putInt32(u.Iterations)\n\n\t\tif err := pe.putBytes(u.Salt); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// do not transmit the password over the wire\n\t\tformatter := scramFormatter{mechanism: u.Mechanism}\n\t\tsalted, err := formatter.saltedPassword(u.Password, u.Salt, int(u.Iterations))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := pe.putBytes(salted); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tpe.putEmptyTaggedFieldArray()\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (r *AlterUserScramCredentialsRequest) decode(pd packetDecoder, version int16) error {\n\tnumDeletions, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr.Deletions = make([]AlterUserScramCredentialsDelete, numDeletions)\n\tfor i := 0; i < numDeletions; i++ {\n\t\tr.Deletions[i] = AlterUserScramCredentialsDelete{}\n\t\tif r.Deletions[i].Name, err = pd.getString(); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tmechanism, err := pd.getInt8()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.Deletions[i].Mechanism = ScramMechanismType(mechanism)\n\t\tif _, err = pd.getEmptyTaggedFieldArray(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tnumUpsertions, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr.Upsertions = make([]AlterUserScramCredentialsUpsert, numUpsertions)\n\tfor i := 0; i < numUpsertions; i++ {\n\t\tr.Upsertions[i] = AlterUserScramCredentialsUpsert{}\n\t\tif r.Upsertions[i].Name, err = pd.getString(); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tmechanism, err := pd.getInt8()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tr.Upsertions[i].Mechanism = ScramMechanismType(mechanism)\n\t\tif r.Upsertions[i].Iterations, err = pd.getInt32(); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif r.Upsertions[i].Salt, err = pd.getBytes(); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif r.Upsertions[i].saltedPassword, err = pd.getBytes(); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif _, err = pd.getEmptyTaggedFieldArray(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (r *AlterUserScramCredentialsRequest) key() int16 {\n\treturn apiKeyAlterUserScramCredentials\n}\n\nfunc (r *AlterUserScramCredentialsRequest) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *AlterUserScramCredentialsRequest) headerVersion() int16 {\n\treturn 2\n}\n\nfunc (r *AlterUserScramCredentialsRequest) isValidVersion() bool {\n\treturn r.Version == 0\n}\n\nfunc (r *AlterUserScramCredentialsRequest) isFlexible() bool {\n\treturn r.isFlexibleVersion(r.Version)\n}\n\nfunc (r *AlterUserScramCredentialsRequest) isFlexibleVersion(version int16) bool {\n\treturn version >= 0\n}\n\nfunc (r *AlterUserScramCredentialsRequest) requiredVersion() KafkaVersion {\n\treturn V2_7_0_0\n}\n"
  },
  {
    "path": "alter_user_scram_credentials_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nvar (\n\temptyAlterUserScramCredentialsRequest = []byte{\n\t\t1, // Deletions\n\t\t1, // Upsertions\n\t\t0, // empty tagged fields\n\t}\n\tuserAlterUserScramCredentialsRequest = []byte{\n\t\t2,                            // Deletions array, length 1\n\t\t7,                            // User name length 6\n\t\t'd', 'e', 'l', 'e', 't', 'e', // User name\n\t\t2, // SCRAM_SHA_512\n\t\t0, // empty tagged fields\n\t\t2, // Upsertions array, length 1\n\t\t7, // User name length 6\n\t\t'u', 'p', 's', 'e', 'r', 't',\n\t\t1,           // SCRAM_SHA_256\n\t\t0, 0, 16, 0, // iterations: 4096\n\t\t// salt bytes:\n\t\t6, 119, 111, 114, 108, 100,\n\t\t// saltedPassword:\n\t\t33, 193, 85, 83, 3, 218, 48, 159, 107, 125, 30, 143,\n\t\t228, 86, 54, 191, 221, 220, 75, 245, 100, 5, 231,\n\t\t233, 78, 157, 21, 240, 231, 185, 203, 211, 128,\n\t\t0, // empty tagged fields\n\t\t0, // empty tagged fields\n\t}\n)\n\nfunc TestAlterUserScramCredentialsRequest(t *testing.T) {\n\trequest := &AlterUserScramCredentialsRequest{\n\t\tVersion:    0,\n\t\tDeletions:  []AlterUserScramCredentialsDelete{},\n\t\tUpsertions: []AlterUserScramCredentialsUpsert{},\n\t}\n\n\t// Password is not transmitted, will fail with `testRequest` and `DeepEqual` check\n\ttestRequestEncode(t, \"no upsertions/deletions\", request, emptyAlterUserScramCredentialsRequest)\n\n\trequest.Deletions = []AlterUserScramCredentialsDelete{\n\t\t{\n\t\t\tName:      \"delete\",\n\t\t\tMechanism: SCRAM_MECHANISM_SHA_512,\n\t\t},\n\t}\n\trequest.Upsertions = []AlterUserScramCredentialsUpsert{\n\t\t{\n\t\t\tName:       \"upsert\",\n\t\t\tMechanism:  SCRAM_MECHANISM_SHA_256,\n\t\t\tIterations: 4096,\n\t\t\tSalt:       []byte(\"world\"),\n\t\t\tPassword:   []byte(\"hello\"),\n\t\t},\n\t}\n\t// Password is not transmitted, will fail with `testRequest` and `DeepEqual` check\n\ttestRequestEncode(t, \"single deletion and upsertion\", request, userAlterUserScramCredentialsRequest)\n}\n"
  },
  {
    "path": "alter_user_scram_credentials_response.go",
    "content": "package sarama\n\nimport \"time\"\n\ntype AlterUserScramCredentialsResponse struct {\n\tVersion int16\n\n\tThrottleTime time.Duration\n\n\tResults []*AlterUserScramCredentialsResult\n}\n\nfunc (r *AlterUserScramCredentialsResponse) setVersion(v int16) {\n\tr.Version = v\n}\n\ntype AlterUserScramCredentialsResult struct {\n\tUser string\n\n\tErrorCode    KError\n\tErrorMessage *string\n}\n\nfunc (r *AlterUserScramCredentialsResponse) encode(pe packetEncoder) error {\n\tpe.putDurationMs(r.ThrottleTime)\n\tif err := pe.putArrayLength(len(r.Results)); err != nil {\n\t\treturn err\n\t}\n\n\tfor _, u := range r.Results {\n\t\tif err := pe.putString(u.User); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tpe.putKError(u.ErrorCode)\n\t\tif err := pe.putNullableString(u.ErrorMessage); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tpe.putEmptyTaggedFieldArray()\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (r *AlterUserScramCredentialsResponse) decode(pd packetDecoder, version int16) (err error) {\n\tif r.ThrottleTime, err = pd.getDurationMs(); err != nil {\n\t\treturn err\n\t}\n\n\tnumResults, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif numResults > 0 {\n\t\tr.Results = make([]*AlterUserScramCredentialsResult, numResults)\n\t\tfor i := 0; i < numResults; i++ {\n\t\t\tr.Results[i] = &AlterUserScramCredentialsResult{}\n\t\t\tif r.Results[i].User, err = pd.getString(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tr.Results[i].ErrorCode, err = pd.getKError()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif r.Results[i].ErrorMessage, err = pd.getNullableString(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif _, err := pd.getEmptyTaggedFieldArray(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (r *AlterUserScramCredentialsResponse) key() int16 {\n\treturn apiKeyAlterUserScramCredentials\n}\n\nfunc (r *AlterUserScramCredentialsResponse) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *AlterUserScramCredentialsResponse) headerVersion() int16 {\n\treturn 2\n}\n\nfunc (r *AlterUserScramCredentialsResponse) isValidVersion() bool {\n\treturn r.Version == 0\n}\n\nfunc (r *AlterUserScramCredentialsResponse) isFlexible() bool {\n\treturn r.isFlexibleVersion(r.Version)\n}\n\nfunc (r *AlterUserScramCredentialsResponse) isFlexibleVersion(version int16) bool {\n\treturn version >= 0\n}\n\nfunc (r *AlterUserScramCredentialsResponse) requiredVersion() KafkaVersion {\n\treturn V2_7_0_0\n}\n\nfunc (r *AlterUserScramCredentialsResponse) throttleTime() time.Duration {\n\treturn r.ThrottleTime\n}\n"
  },
  {
    "path": "alter_user_scram_credentials_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nvar (\n\temptyAlterUserScramCredentialsResponse = []byte{\n\t\t0, 0, 11, 184, // throttle time\n\t\t1, // empty results array\n\t\t0, // empty tagged fields\n\t}\n\tuserAlterUserScramCredentialsResponse = []byte{\n\t\t0, 0, 11, 184, // throttle time\n\t\t2,                               // results array length\n\t\t7, 'n', 'o', 'b', 'o', 'd', 'y', // User\n\t\t0, 11, // ErrorCode\n\t\t6, 'e', 'r', 'r', 'o', 'r', // ErrorMessage\n\t\t0, // empty tagged fields\n\t\t0, // empty tagged fields\n\t}\n)\n\nfunc TestAlterUserScramCredentialsResponse(t *testing.T) {\n\tresponse := &AlterUserScramCredentialsResponse{\n\t\tVersion:      0,\n\t\tThrottleTime: time.Second * 3,\n\t}\n\ttestResponse(t, \"empty response\", response, emptyAlterUserScramCredentialsResponse)\n\n\tresultErrorMessage := \"error\"\n\tresponse.Results = append(response.Results, &AlterUserScramCredentialsResult{\n\t\tUser:         \"nobody\",\n\t\tErrorCode:    11,\n\t\tErrorMessage: &resultErrorMessage,\n\t})\n\ttestResponse(t, \"single user response\", response, userAlterUserScramCredentialsResponse)\n}\n"
  },
  {
    "path": "api_versions.go",
    "content": "package sarama\n\ntype apiVersionRange struct {\n\tminVersion int16\n\tmaxVersion int16\n}\n\ntype apiVersionMap map[int16]*apiVersionRange\n\n// restrictApiVersion selects the appropriate API version for a given protocol body according to\n// the client and broker version ranges. By default, it selects the maximum version supported by both\n// client and broker, capped by the maximum Kafka version from Config.\n// It then calls setVersion() on the protocol body.\n// If no valid version is found, an error is returned.\nfunc restrictApiVersion(pb protocolBody, brokerVersions apiVersionMap) error {\n\tkey := pb.key()\n\t// Since message constructors take a Kafka version and select the maximum supported protocol version already, we can\n\t// rely on pb.version() being the max version supported for the user-selected Kafka API version.\n\tclientMax := pb.version()\n\n\tif brokerVersionRange := brokerVersions[key]; brokerVersionRange != nil {\n\t\t// Select the maximum version that both client and server support\n\t\t// Clamp to the client max to respect user preference above broker advertised version range\n\t\tpb.setVersion(min(clientMax, max(min(clientMax, brokerVersionRange.maxVersion), brokerVersionRange.minVersion)))\n\t\treturn nil\n\t}\n\n\treturn nil // no version ranges available, no restriction\n}\n\nconst (\n\tapiKeyProduce                      = 0\n\tapiKeyFetch                        = 1\n\tapiKeyListOffsets                  = 2\n\tapiKeyMetadata                     = 3\n\tapiKeyLeaderAndIsr                 = 4\n\tapiKeyStopReplica                  = 5\n\tapiKeyUpdateMetadata               = 6\n\tapiKeyControlledShutdown           = 7\n\tapiKeyOffsetCommit                 = 8\n\tapiKeyOffsetFetch                  = 9\n\tapiKeyFindCoordinator              = 10\n\tapiKeyJoinGroup                    = 11\n\tapiKeyHeartbeat                    = 12\n\tapiKeyLeaveGroup                   = 13\n\tapiKeySyncGroup                    = 14\n\tapiKeyDescribeGroups               = 15\n\tapiKeyListGroups                   = 16\n\tapiKeySaslHandshake                = 17\n\tapiKeyApiVersions                  = 18\n\tapiKeyCreateTopics                 = 19\n\tapiKeyDeleteTopics                 = 20\n\tapiKeyDeleteRecords                = 21\n\tapiKeyInitProducerId               = 22\n\tapiKeyOffsetForLeaderEpoch         = 23\n\tapiKeyAddPartitionsToTxn           = 24\n\tapiKeyAddOffsetsToTxn              = 25\n\tapiKeyEndTxn                       = 26\n\tapiKeyWriteTxnMarkers              = 27\n\tapiKeyTxnOffsetCommit              = 28\n\tapiKeyDescribeAcls                 = 29\n\tapiKeyCreateAcls                   = 30\n\tapiKeyDeleteAcls                   = 31\n\tapiKeyDescribeConfigs              = 32\n\tapiKeyAlterConfigs                 = 33\n\tapiKeyAlterReplicaLogDirs          = 34\n\tapiKeyDescribeLogDirs              = 35\n\tapiKeySASLAuth                     = 36\n\tapiKeyCreatePartitions             = 37\n\tapiKeyCreateDelegationToken        = 38\n\tapiKeyRenewDelegationToken         = 39\n\tapiKeyExpireDelegationToken        = 40\n\tapiKeyDescribeDelegationToken      = 41\n\tapiKeyDeleteGroups                 = 42\n\tapiKeyElectLeaders                 = 43\n\tapiKeyIncrementalAlterConfigs      = 44\n\tapiKeyAlterPartitionReassignments  = 45\n\tapiKeyListPartitionReassignments   = 46\n\tapiKeyOffsetDelete                 = 47\n\tapiKeyDescribeClientQuotas         = 48\n\tapiKeyAlterClientQuotas            = 49\n\tapiKeyDescribeUserScramCredentials = 50\n\tapiKeyAlterUserScramCredentials    = 51\n\tapiKeyDescribeCluster              = 60\n)\n"
  },
  {
    "path": "api_versions_request.go",
    "content": "package sarama\n\nconst defaultClientSoftwareName = \"sarama\"\n\ntype ApiVersionsRequest struct {\n\t// Version defines the protocol version to use for encode and decode\n\tVersion int16\n\t// ClientSoftwareName contains the name of the client.\n\tClientSoftwareName string\n\t// ClientSoftwareVersion contains the version of the client.\n\tClientSoftwareVersion string\n}\n\nfunc (r *ApiVersionsRequest) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *ApiVersionsRequest) encode(pe packetEncoder) (err error) {\n\tif r.Version >= 3 {\n\t\tif err := pe.putString(r.ClientSoftwareName); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := pe.putString(r.ClientSoftwareVersion); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tpe.putEmptyTaggedFieldArray()\n\t}\n\n\treturn nil\n}\n\nfunc (r *ApiVersionsRequest) decode(pd packetDecoder, version int16) (err error) {\n\tr.Version = version\n\tif r.Version >= 3 {\n\t\tif r.ClientSoftwareName, err = pd.getString(); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif r.ClientSoftwareVersion, err = pd.getString(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (r *ApiVersionsRequest) key() int16 {\n\treturn apiKeyApiVersions\n}\n\nfunc (r *ApiVersionsRequest) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *ApiVersionsRequest) headerVersion() int16 {\n\tif r.Version >= 3 {\n\t\treturn 2\n\t}\n\treturn 1\n}\n\nfunc (r *ApiVersionsRequest) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 3\n}\n\nfunc (r *ApiVersionsRequest) isFlexible() bool {\n\treturn r.isFlexibleVersion(r.Version)\n}\n\nfunc (r *ApiVersionsRequest) isFlexibleVersion(version int16) bool {\n\treturn version >= 3\n}\n\nfunc (r *ApiVersionsRequest) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 3:\n\t\treturn V2_4_0_0\n\tcase 2:\n\t\treturn V2_0_0_0\n\tcase 1:\n\t\treturn V0_11_0_0\n\tcase 0:\n\t\treturn V0_10_0_0\n\tdefault:\n\t\treturn V2_4_0_0\n\t}\n}\n"
  },
  {
    "path": "api_versions_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nvar (\n\tapiVersionRequest []byte\n\n\tapiVersionRequestV3 = []byte{\n\t\t0x07, 's', 'a', 'r', 'a', 'm', 'a',\n\t\t0x07, '0', '.', '1', '0', '.', '0',\n\t\t0x00,\n\t}\n)\n\nfunc TestApiVersionsRequest(t *testing.T) {\n\trequest := new(ApiVersionsRequest)\n\ttestRequest(t, \"basic\", request, apiVersionRequest)\n}\n\nfunc TestApiVersionsRequestV3(t *testing.T) {\n\trequest := new(ApiVersionsRequest)\n\trequest.Version = 3\n\trequest.ClientSoftwareName = \"sarama\"\n\trequest.ClientSoftwareVersion = \"0.10.0\"\n\ttestRequest(t, \"v3\", request, apiVersionRequestV3)\n}\n"
  },
  {
    "path": "api_versions_response.go",
    "content": "package sarama\n\nimport (\n\t\"time\"\n)\n\n// ApiVersionsResponseKey contains the APIs supported by the broker.\ntype ApiVersionsResponseKey struct {\n\t// Version defines the protocol version to use for encode and decode\n\tVersion int16\n\t// ApiKey contains the API index.\n\tApiKey int16\n\t// MinVersion contains the minimum supported version, inclusive.\n\tMinVersion int16\n\t// MaxVersion contains the maximum supported version, inclusive.\n\tMaxVersion int16\n}\n\nfunc (a *ApiVersionsResponseKey) encode(pe packetEncoder, version int16) (err error) {\n\ta.Version = version\n\tpe.putInt16(a.ApiKey)\n\n\tpe.putInt16(a.MinVersion)\n\n\tpe.putInt16(a.MaxVersion)\n\n\tif version >= 3 {\n\t\tpe.putEmptyTaggedFieldArray()\n\t}\n\n\treturn nil\n}\n\nfunc (a *ApiVersionsResponseKey) decode(pd packetDecoder, version int16) (err error) {\n\ta.Version = version\n\tif a.ApiKey, err = pd.getInt16(); err != nil {\n\t\treturn err\n\t}\n\n\tif a.MinVersion, err = pd.getInt16(); err != nil {\n\t\treturn err\n\t}\n\n\tif a.MaxVersion, err = pd.getInt16(); err != nil {\n\t\treturn err\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\ntype ApiVersionsResponse struct {\n\t// Version defines the protocol version to use for encode and decode\n\tVersion int16\n\t// ErrorCode contains the top-level error code.\n\tErrorCode int16\n\t// ApiKeys contains the APIs supported by the broker.\n\tApiKeys []ApiVersionsResponseKey\n\t// ThrottleTimeMs contains the duration in milliseconds for which the request was throttled due to a quota violation, or zero if the request did not violate any quota.\n\tThrottleTimeMs int32\n}\n\nfunc (r *ApiVersionsResponse) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *ApiVersionsResponse) encode(pe packetEncoder) (err error) {\n\tpe.putInt16(r.ErrorCode)\n\n\tif err := pe.putArrayLength(len(r.ApiKeys)); err != nil {\n\t\treturn err\n\t}\n\tfor _, block := range r.ApiKeys {\n\t\tif err := block.encode(pe, r.Version); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif r.Version >= 1 {\n\t\tpe.putInt32(r.ThrottleTimeMs)\n\t}\n\n\tif r.Version >= 3 {\n\t\tpe.putEmptyTaggedFieldArray()\n\t}\n\n\treturn nil\n}\n\nfunc (r *ApiVersionsResponse) decode(pd packetDecoder, version int16) (err error) {\n\tr.Version = version\n\tif r.ErrorCode, err = pd.getInt16(); err != nil {\n\t\treturn err\n\t}\n\n\t// KIP-511: if broker didn't understand the ApiVersionsRequest version then\n\t// it replies with a V0 non-flexible ApiVersionResponse where its supported\n\t// ApiVersionsRequest version is available in ApiKeys\n\tif r.ErrorCode == int16(ErrUnsupportedVersion) {\n\t\t// drop version to 0 and to revert packageDecoder to non-flexible for remaining decoding\n\t\tr.Version = 0\n\t\tpd = downgradeFlexibleDecoder(pd)\n\t}\n\n\tnumApiKeys, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\tr.ApiKeys = make([]ApiVersionsResponseKey, numApiKeys)\n\tfor i := 0; i < numApiKeys; i++ {\n\t\tvar block ApiVersionsResponseKey\n\t\tif err = block.decode(pd, r.Version); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.ApiKeys[i] = block\n\t}\n\n\tif r.Version >= 1 {\n\t\tif r.ThrottleTimeMs, err = pd.getInt32(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (r *ApiVersionsResponse) key() int16 {\n\treturn apiKeyApiVersions\n}\n\nfunc (r *ApiVersionsResponse) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *ApiVersionsResponse) headerVersion() int16 {\n\t// ApiVersionsResponse always includes a v0 header.\n\t// See KIP-511 for details\n\treturn 0\n}\n\nfunc (r *ApiVersionsResponse) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 3\n}\n\nfunc (r *ApiVersionsResponse) isFlexible() bool {\n\treturn r.isFlexibleVersion(r.Version)\n}\n\nfunc (r *ApiVersionsResponse) isFlexibleVersion(version int16) bool {\n\treturn version >= 3\n}\n\nfunc (r *ApiVersionsResponse) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 3:\n\t\treturn V2_4_0_0\n\tcase 2:\n\t\treturn V2_0_0_0\n\tcase 1:\n\t\treturn V0_11_0_0\n\tcase 0:\n\t\treturn V0_10_0_0\n\tdefault:\n\t\treturn V2_4_0_0\n\t}\n}\n\nfunc (r *ApiVersionsResponse) throttleTime() time.Duration {\n\treturn time.Duration(r.ThrottleTimeMs) * time.Millisecond\n}\n"
  },
  {
    "path": "api_versions_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"testing\"\n\n\tassert \"github.com/stretchr/testify/require\"\n)\n\nvar (\n\tapiVersionResponseV0 = []byte{\n\t\t0x00, 0x00, // no error\n\t\t0x00, 0x00, 0x00, 0x04, // array length 4 (APIs)\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // API Version Produce (v0-2)\n\t\t0x00, 0x01, 0x00, 0x00, 0x00, 0x03, // API Version Fetch (v0-3)\n\t\t0x00, 0x02, 0x00, 0x00, 0x00, 0x01, // API Version Offsets (v0-1)\n\t\t0x00, 0x03, 0x00, 0x00, 0x00, 0x02, // API Version Metadata (v0-2)\n\t}\n\n\tapiVersionResponseV1V2 = []byte{\n\t\t0x00, 0x00, // no error\n\t\t0x00, 0x00, 0x00, 0x05, // array length 5 (APIs)\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x07, // API Version Produce (v0-7)\n\t\t0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, // API Version Fetch (v0-11)\n\t\t0x00, 0x02, 0x00, 0x00, 0x00, 0x05, // API Version Offsets (v0-5)\n\t\t0x00, 0x03, 0x00, 0x00, 0x00, 0x08, // API Version Metadata (v0-8)\n\t\t0x00, 0x04, 0x00, 0x00, 0x00, 0x02, // API Version LeaderAndIsr (v0-2)\n\t\t0x00, 0x00, 0x00, 0x40, // throttle time (64ms)\n\t}\n\n\tapiVersionResponseV3 = []byte{\n\t\t0x00, 0x00, // no error\n\t\t0x07,                               // compact array length 6 (APIs)\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x08, // API Version Produce (v0-8)\n\t\t0x00,                               // empty tagged fields\n\t\t0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, // API Version Fetch (v0-11)\n\t\t0x00,                               // empty tagged fields\n\t\t0x00, 0x02, 0x00, 0x00, 0x00, 0x05, // API Version Offsets (v0-5)\n\t\t0x00,                               // empty tagged fields\n\t\t0x00, 0x03, 0x00, 0x00, 0x00, 0x09, // API Version Metadata (v0-9)\n\t\t0x00,                               // empty tagged fields\n\t\t0x00, 0x04, 0x00, 0x00, 0x00, 0x04, // API Version LeaderAndIsr (v0-4)\n\t\t0x00,                               // empty tagged fields\n\t\t0x00, 0x05, 0x00, 0x00, 0x00, 0x02, // API Version StopReplica (v0-2)\n\t\t0x00,                   // empty tagged fields\n\t\t0x00, 0x00, 0x00, 0x80, // throttle time (128ms)\n\t\t0x00, // empty tagged fields\n\t}\n\n\t// unsupported version from kafka 0.10.2.1\n\tapiVersionsResponseUnsupportedVersionV0 = []byte{\n\t\t0x00, 0x23, // unsupported version error\n\t\t0x00, 0x00, 0x00, 0x00, // array length 0\n\t}\n\n\t// unsupported version from kafka 2.3.0\n\tapiVersionsResponseUnsupportedVersionV1V2 = []byte{\n\t\t0x00, 0x23, // unsupported version error\n\t\t0x00, 0x00, 0x00, 0x00, // array length 0\n\t}\n\n\t// unsupported version from kafka 2.4.0\n\tapiVersionsResponseUnsupportedVersionV3 = []byte{\n\t\t0x00, 0x23, // unsupported version error\n\t\t0x00, 0x00, 0x00, 0x01, // array length 1\n\t\t0x00, 0x12, 0x00, 0x00, 0x00, 0x03, // API Version ApiVersions (v0-3)\n\t}\n\n\t// unsupported version from kafka 4.1.0\n\tapiVersionsResponseUnsupportedVersionV4 = []byte{\n\t\t0x00, 0x23, // unsupported version error\n\t\t0x00, 0x00, 0x00, 0x01, // array length 1\n\t\t0x00, 0x12, 0x00, 0x00, 0x00, 0x04, // API Version ApiVersions (v0-4)\n\t}\n)\n\nfunc TestApiVersionsResponseV0(t *testing.T) {\n\tconst v = 0\n\tresponse := new(ApiVersionsResponse)\n\ttestVersionDecodable(t, \"no error V0\", response, apiVersionResponseV0, v)\n\n\tassert.Equal(t, int16(ErrNoError), response.ErrorCode)\n\tassert.Equal(t, []ApiVersionsResponseKey{\n\t\t{v, 0, 0, 2}, // API Version Produce (v0-2)\n\t\t{v, 1, 0, 3}, // API Version Fetch (v0-3)\n\t\t{v, 2, 0, 1}, // API Version Offsets (v0-1)\n\t\t{v, 3, 0, 2}, // API Version Metadata (v0-2)\n\t}, response.ApiKeys)\n}\n\nfunc TestApiVersionsResponseV1V2(t *testing.T) {\n\tresponse := new(ApiVersionsResponse)\n\n\tfor _, v := range []int16{1, 2} {\n\t\ttestVersionDecodable(t, \"no error V1V2\", response, apiVersionResponseV1V2, v)\n\n\t\tassert.Equal(t, int16(ErrNoError), response.ErrorCode)\n\t\tassert.Equal(t, []ApiVersionsResponseKey{\n\t\t\t{v, 0, 0, 7},  // API Version Produce (v0-7)\n\t\t\t{v, 1, 0, 11}, // API Version Fetch (v0-11)\n\t\t\t{v, 2, 0, 5},  // API Version Offsets (v0-5)\n\t\t\t{v, 3, 0, 8},  // API Version Metadata (v0-8)\n\t\t\t{v, 4, 0, 2},  // API Version LeaderAndIsr (v0-2)\n\t\t}, response.ApiKeys)\n\t\tassert.Equal(t, int32(64), response.ThrottleTimeMs)\n\t}\n}\n\nfunc TestApiVersionsResponseV3(t *testing.T) {\n\tconst v = 3\n\tresponse := new(ApiVersionsResponse)\n\tresponse.Version = v\n\ttestVersionDecodable(t, \"no error V3\", response, apiVersionResponseV3, v)\n\tassert.Equal(t, int16(ErrNoError), response.ErrorCode)\n\tassert.Equal(t, []ApiVersionsResponseKey{\n\t\t{v, 0, 0, 8},  // API Version Produce (v0-8)\n\t\t{v, 1, 0, 11}, // API Version Fetch (v0-11)\n\t\t{v, 2, 0, 5},  // API Version Offsets (v0-5)\n\t\t{v, 3, 0, 9},  // API Version Metadata (v0-9)\n\t\t{v, 4, 0, 4},  // API Version LeaderAndIsr (v0-4)\n\t\t{v, 5, 0, 2},  // API Version StopReplica (v0-2)\n\t}, response.ApiKeys)\n\tassert.Equal(t, int32(128), response.ThrottleTimeMs)\n}\n\nfunc TestApiVersionsResponseUnsupportedVersion(t *testing.T) {\n\tt.Run(\"V0\", func(t *testing.T) {\n\t\tresponse := new(ApiVersionsResponse)\n\t\tresponse.Version = 3\n\t\ttestVersionDecodable(t, \"unsupported\", response, apiVersionsResponseUnsupportedVersionV0, 3)\n\t\tassert.Equal(t, int16(ErrUnsupportedVersion), response.ErrorCode)\n\t\tassert.Empty(t, response.ApiKeys)\n\t})\n\n\tt.Run(\"V1V2\", func(t *testing.T) {\n\t\tresponse := new(ApiVersionsResponse)\n\t\tresponse.Version = 3\n\t\ttestVersionDecodable(t, \"unsupported\", response, apiVersionsResponseUnsupportedVersionV1V2, 3)\n\t\tassert.Equal(t, int16(ErrUnsupportedVersion), response.ErrorCode)\n\t\tassert.Empty(t, response.ApiKeys)\n\t})\n\n\tt.Run(\"V3\", func(t *testing.T) {\n\t\tresponse := new(ApiVersionsResponse)\n\t\tresponse.Version = 3\n\t\ttestVersionDecodable(t, \"unsupported\", response, apiVersionsResponseUnsupportedVersionV3, 3)\n\t\tassert.Equal(t, int16(ErrUnsupportedVersion), response.ErrorCode)\n\t\tassert.Equal(t, []ApiVersionsResponseKey{\n\t\t\t{0, 18, 0, 3}, // API Version ApiVersions (v0-3)\n\t\t}, response.ApiKeys)\n\t})\n\n\tt.Run(\"V4\", func(t *testing.T) {\n\t\tresponse := new(ApiVersionsResponse)\n\t\tresponse.Version = 4\n\t\ttestVersionDecodable(t, \"unsupported\", response, apiVersionsResponseUnsupportedVersionV4, 4)\n\t\tassert.Equal(t, int16(ErrUnsupportedVersion), response.ErrorCode)\n\t\tassert.Equal(t, []ApiVersionsResponseKey{\n\t\t\t{0, 18, 0, 4}, // API Version ApiVersions (v0-4)\n\t\t}, response.ApiKeys)\n\t})\n}\n"
  },
  {
    "path": "api_versions_test.go",
    "content": "package sarama\n\nimport \"testing\"\n\nfunc TestRestrictApiVersionLowersVersionToBrokerMax(t *testing.T) {\n\trequest := NewMetadataRequest(V2_8_0_0, []string{\"test-topic\"})\n\n\tif request.version() != 10 {\n\t\tt.Errorf(\"Expected MetadataRequest version to be 10, got %d\", request.version())\n\t}\n\n\tbrokerVersions := apiVersionMap{\n\t\tapiKeyMetadata: &apiVersionRange{\n\t\t\tminVersion: 0,\n\t\t\tmaxVersion: 8,\n\t\t},\n\t}\n\n\terr := restrictApiVersion(request, brokerVersions)\n\tif err != nil {\n\t\tt.Errorf(\"restrictApiVersion returned unexpected error: %v\", err)\n\t}\n\n\tif request.version() != 8 {\n\t\tt.Errorf(\"Expected version to be restricted to 8, got %d\", request.version())\n\t}\n}\n\nfunc TestRestrictApiVersionLeavesVersionUnchangedWhenWithinRange(t *testing.T) {\n\trequest := NewMetadataRequest(V2_4_0_0, []string{\"test-topic\"})\n\toriginalVersion := request.version()\n\n\tif originalVersion != 9 {\n\t\tt.Errorf(\"Expected MetadataRequest version to be 9, got %d\", originalVersion)\n\t}\n\n\tbrokerVersions := apiVersionMap{\n\t\tapiKeyMetadata: &apiVersionRange{\n\t\t\tminVersion: 0,\n\t\t\tmaxVersion: 10,\n\t\t},\n\t}\n\n\terr := restrictApiVersion(request, brokerVersions)\n\tif err != nil {\n\t\tt.Errorf(\"restrictApiVersion returned unexpected error: %v\", err)\n\t}\n\n\tif request.version() != originalVersion {\n\t\tt.Errorf(\"Expected version to remain %d, got %d\", originalVersion, request.version())\n\t}\n}\n\nfunc TestRestrictApiVersionDoesNotRaiseVersionBeyondUserSetMax(t *testing.T) {\n\t// the Kafka version comes from conf.Version, which is the user-set max Kafka API version to use\n\trequest := NewMetadataRequest(V0_10_0_0, []string{\"test-topic\"})\n\n\tif request.version() != 1 {\n\t\tt.Errorf(\"Expected MetadataRequest version to be 1, got %d\", request.version())\n\t}\n\n\t// broker doesn't support versions below 5\n\tbrokerVersions := apiVersionMap{\n\t\tapiKeyMetadata: &apiVersionRange{\n\t\t\tminVersion: 5,\n\t\t\tmaxVersion: 10,\n\t\t},\n\t}\n\n\t// we expect the user's preference to be respected even when it's below the broker's minimum\n\terr := restrictApiVersion(request, brokerVersions)\n\tif err != nil {\n\t\tt.Errorf(\"restrictApiVersion returned unexpected error: %v\", err)\n\t}\n\n\tif request.version() != 1 {\n\t\tt.Errorf(\"Expected version to be set to minimum 1, got %d\", request.version())\n\t}\n}\n\nfunc TestRestrictApiVersionDoesNothingIfBrokerVersionRangeMissing(t *testing.T) {\n\trequest := NewMetadataRequest(V2_8_0_0, []string{\"test-topic\"})\n\toriginalVersion := request.version()\n\n\tbrokerVersions := apiVersionMap{\n\t\t// no entry for apiKeyMetadata\n\t}\n\n\terr := restrictApiVersion(request, brokerVersions)\n\tif err != nil {\n\t\tt.Errorf(\"restrictApiVersion returned unexpected error: %v\", err)\n\t}\n\n\tif request.version() != originalVersion {\n\t\tt.Errorf(\"Expected version to remain %d, got %d\", originalVersion, request.version())\n\t}\n}\n"
  },
  {
    "path": "async_producer.go",
    "content": "package sarama\n\nimport (\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/eapache/go-resiliency/breaker\"\n\t\"github.com/eapache/queue\"\n\t\"github.com/rcrowley/go-metrics\"\n)\n\n// ErrProducerRetryBufferOverflow is returned when the bridging retry buffer is full and OOM prevention needs to be applied.\nvar ErrProducerRetryBufferOverflow = errors.New(\"retry buffer full: message discarded to prevent buffer overflow\")\n\nconst (\n\t// minFunctionalRetryBufferLength defines the minimum number of messages the retry buffer must support.\n\t// If Producer.Retry.MaxBufferLength is set to a non-zero value below this limit, it will be adjusted to this value.\n\t// This ensures the retry buffer remains functional under typical workloads.\n\tminFunctionalRetryBufferLength = 4 * 1024\n\t// minFunctionalRetryBufferBytes defines the minimum total byte size the retry buffer must support.\n\t// If Producer.Retry.MaxBufferBytes is set to a non-zero value below this limit, it will be adjusted to this value.\n\t// A 32 MB lower limit ensures sufficient capacity for retrying larger messages without exhausting resources.\n\tminFunctionalRetryBufferBytes = 32 * 1024 * 1024\n)\n\n// AsyncProducer publishes Kafka messages using a non-blocking API. It routes messages\n// to the correct broker for the provided topic-partition, refreshing metadata as appropriate,\n// and parses responses for errors. You must read from the Errors() channel or the\n// producer will deadlock. You must call Close() or AsyncClose() on a producer to avoid\n// leaks and message lost: it will not be garbage-collected automatically when it passes\n// out of scope and buffered messages may not be flushed.\ntype AsyncProducer interface {\n\t// AsyncClose triggers a shutdown of the producer. The shutdown has completed\n\t// when both the Errors and Successes channels have been closed. When calling\n\t// AsyncClose, you *must* continue to read from those channels in order to\n\t// drain the results of any messages in flight.\n\tAsyncClose()\n\n\t// Close shuts down the producer and waits for any buffered messages to be\n\t// flushed. You must call this function before a producer object passes out of\n\t// scope, as it may otherwise leak memory. You must call this before process\n\t// shutting down, or you may lose messages. You must call this before calling\n\t// Close on the underlying client.\n\tClose() error\n\n\t// Input is the input channel for the user to write messages to that they\n\t// wish to send.\n\tInput() chan<- *ProducerMessage\n\n\t// Successes is the success output channel back to the user when Return.Successes is\n\t// enabled. If Return.Successes is true, you MUST read from this channel or the\n\t// Producer will deadlock. It is suggested that you send and read messages\n\t// together in a single select statement.\n\tSuccesses() <-chan *ProducerMessage\n\n\t// Errors is the error output channel back to the user. You MUST read from this\n\t// channel or the Producer will deadlock when the channel is full. Alternatively,\n\t// you can set Producer.Return.Errors in your config to false, which prevents\n\t// errors to be returned.\n\tErrors() <-chan *ProducerError\n\n\t// IsTransactional return true when current producer is transactional.\n\tIsTransactional() bool\n\n\t// TxnStatus return current producer transaction status.\n\tTxnStatus() ProducerTxnStatusFlag\n\n\t// BeginTxn mark current transaction as ready.\n\tBeginTxn() error\n\n\t// CommitTxn commit current transaction.\n\tCommitTxn() error\n\n\t// AbortTxn abort current transaction.\n\tAbortTxn() error\n\n\t// AddOffsetsToTxn add associated offsets to current transaction.\n\tAddOffsetsToTxn(offsets map[string][]*PartitionOffsetMetadata, groupId string) error\n\n\t// AddMessageToTxn add message offsets to current transaction.\n\tAddMessageToTxn(msg *ConsumerMessage, groupId string, metadata *string) error\n}\n\ntype asyncProducer struct {\n\tclient Client\n\tconf   *Config\n\n\terrors                    chan *ProducerError\n\tinput, successes, retries chan *ProducerMessage\n\tinFlight                  sync.WaitGroup\n\n\tbrokers    map[*Broker]*brokerProducer\n\tbrokerRefs map[*brokerProducer]int\n\tbrokerLock sync.Mutex\n\n\ttxnmgr *transactionManager\n\ttxLock sync.Mutex\n\n\t// muter ensures per-partition ordering by preventing concurrent in-flight requests,\n\t// mirroring Kafka's RecordAccumulator.\n\tmuter *partitionMuter\n\n\tmetricsRegistry metrics.Registry\n}\n\ntype partitionMuter struct {\n\tmu             sync.Mutex\n\tcond           *sync.Cond\n\tclosed         bool\n\tinFlightCounts map[string]map[int32]int // topic -> partition -> in-flight count\n\tunmuteSignal   chan struct{}\n}\n\nfunc newPartitionMuter() *partitionMuter {\n\tm := &partitionMuter{\n\t\tinFlightCounts: make(map[string]map[int32]int),\n\t\tunmuteSignal:   make(chan struct{}),\n\t}\n\tm.cond = sync.NewCond(&m.mu)\n\treturn m\n}\n\n// isMuted reports whether the partition has an in-flight batch.\n// Requires: m.mu held.\nfunc (m *partitionMuter) isMuted(topic string, partition int32) bool {\n\treturn m.inFlightCounts[topic][partition] > 0\n}\n\n// isAnyMuted reports whether any partition in the set has an in-flight batch.\n// Requires: m.mu held.\nfunc (m *partitionMuter) isAnyMuted(set *produceSet) bool {\n\treturn set.anyPartition(func(topic string, partition int32, _ *partitionSet) bool {\n\t\treturn m.isMuted(topic, partition)\n\t})\n}\n\n// mutePartition increments the in-flight count for a single partition.\n// Requires: m.mu held.\nfunc (m *partitionMuter) mutePartition(topic string, partition int32) {\n\tif m.inFlightCounts[topic] == nil {\n\t\tm.inFlightCounts[topic] = make(map[int32]int)\n\t}\n\tm.inFlightCounts[topic][partition]++\n}\n\n// muteSet increments the in-flight count for all partitions in the set.\n// Requires: m.mu held.\nfunc (m *partitionMuter) muteSet(set *produceSet) {\n\tset.eachPartition(func(topic string, partition int32, _ *partitionSet) {\n\t\tm.mutePartition(topic, partition)\n\t})\n}\n\n// tryMute checks if any of the partitions in the given produceSet are already\n// muted, returning false if they are, otherwise it reserves every partition in\n// the set by bumping their in-flight counters\nfunc (m *partitionMuter) tryMute(set *produceSet) bool {\n\tif set == nil || set.empty() {\n\t\treturn false\n\t}\n\n\tm.mu.Lock()\n\tdefer m.mu.Unlock()\n\n\tif m.isAnyMuted(set) {\n\t\treturn false\n\t}\n\tm.muteSet(set)\n\treturn true\n}\n\nfunc (m *partitionMuter) tryMutePartition(topic string, partition int32) bool {\n\tm.mu.Lock()\n\tdefer m.mu.Unlock()\n\n\tif m.isMuted(topic, partition) {\n\t\treturn false\n\t}\n\tm.mutePartition(topic, partition)\n\treturn true\n}\n\n// waitUntilMuted blocks until all partitions in the set can be muted, then mutes them.\n// Returns false if the muter was closed before all partitions could be muted.\nfunc (m *partitionMuter) waitUntilMuted(set *produceSet) bool {\n\tif set == nil || set.empty() {\n\t\treturn false\n\t}\n\n\tm.mu.Lock()\n\tdefer m.mu.Unlock()\n\n\tfor {\n\t\tif m.closed {\n\t\t\treturn false\n\t\t}\n\t\tif !m.isAnyMuted(set) {\n\t\t\tbreak\n\t\t}\n\t\tm.cond.Wait()\n\t}\n\n\tm.muteSet(set)\n\treturn true\n}\n\nfunc (m *partitionMuter) awaitUnmuteChan(set *produceSet) (<-chan struct{}, bool) {\n\tif set == nil || set.empty() {\n\t\treturn nil, false\n\t}\n\n\tm.mu.Lock()\n\tdefer m.mu.Unlock()\n\n\tif !m.isAnyMuted(set) {\n\t\treturn nil, false\n\t}\n\treturn m.unmuteSignal, true\n}\n\n// unmute decrements the in-flight counter for all partitions in the set.\nfunc (m *partitionMuter) unmute(set *produceSet) {\n\tif set == nil {\n\t\treturn\n\t}\n\n\tm.mu.Lock()\n\tdefer m.mu.Unlock()\n\n\tif m.closed {\n\t\treturn\n\t}\n\n\tset.eachPartition(func(topic string, partition int32, _ *partitionSet) {\n\t\tpartitions := m.inFlightCounts[topic]\n\t\tif partitions == nil {\n\t\t\treturn\n\t\t}\n\t\tif partitions[partition] <= 1 {\n\t\t\tdelete(partitions, partition)\n\t\t} else {\n\t\t\tpartitions[partition]--\n\t\t}\n\t\tif len(partitions) == 0 {\n\t\t\tdelete(m.inFlightCounts, topic)\n\t\t}\n\t})\n\tclose(m.unmuteSignal)\n\tm.unmuteSignal = make(chan struct{})\n\tm.cond.Broadcast()\n}\n\n// close shuts down the muter, waking any goroutines blocked in waitUntilMuted.\nfunc (m *partitionMuter) close() {\n\tm.mu.Lock()\n\tdefer m.mu.Unlock()\n\n\tif m.closed {\n\t\treturn\n\t}\n\tm.closed = true\n\tclose(m.unmuteSignal)\n\tm.cond.Broadcast()\n}\n\n// NewAsyncProducer creates a new AsyncProducer using the given broker addresses and configuration.\nfunc NewAsyncProducer(addrs []string, conf *Config) (AsyncProducer, error) {\n\tclient, err := NewClient(addrs, conf)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn newAsyncProducer(client)\n}\n\n// NewAsyncProducerFromClient creates a new Producer using the given client. It is still\n// necessary to call Close() on the underlying client when shutting down this producer.\nfunc NewAsyncProducerFromClient(client Client) (AsyncProducer, error) {\n\t// For clients passed in by the client, ensure we don't\n\t// call Close() on it.\n\tcli := &nopCloserClient{client}\n\treturn newAsyncProducer(cli)\n}\n\nfunc newAsyncProducer(client Client) (AsyncProducer, error) {\n\t// Check that we are not dealing with a closed Client before processing any other arguments\n\tif client.Closed() {\n\t\treturn nil, ErrClosedClient\n\t}\n\n\ttxnmgr, err := newTransactionManager(client.Config(), client)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tp := &asyncProducer{\n\t\tclient:          client,\n\t\tconf:            client.Config(),\n\t\terrors:          make(chan *ProducerError),\n\t\tinput:           make(chan *ProducerMessage),\n\t\tsuccesses:       make(chan *ProducerMessage),\n\t\tretries:         make(chan *ProducerMessage),\n\t\tbrokers:         make(map[*Broker]*brokerProducer),\n\t\tbrokerRefs:      make(map[*brokerProducer]int),\n\t\ttxnmgr:          txnmgr,\n\t\tmuter:           newPartitionMuter(),\n\t\tmetricsRegistry: newCleanupRegistry(client.Config().MetricRegistry),\n\t}\n\n\t// launch our singleton dispatchers\n\tgo withRecover(p.dispatcher)\n\tgo withRecover(p.retryHandler)\n\n\treturn p, nil\n}\n\ntype flagSet int8\n\nconst (\n\tsyn       flagSet = 1 << iota // first message from partitionProducer to brokerProducer\n\tfin                           // final message from partitionProducer to brokerProducer and back\n\tshutdown                      // start the shutdown process\n\tendtxn                        // endtxn\n\tcommittxn                     // endtxn\n\taborttxn                      // endtxn\n)\n\n// ProducerMessage is the collection of elements passed to the Producer in order to send a message.\ntype ProducerMessage struct {\n\tTopic string // The Kafka topic for this message.\n\t// The partitioning key for this message. Pre-existing Encoders include\n\t// StringEncoder and ByteEncoder.\n\tKey Encoder\n\t// The actual message to store in Kafka. Pre-existing Encoders include\n\t// StringEncoder and ByteEncoder.\n\tValue Encoder\n\n\t// The headers are key-value pairs that are transparently passed\n\t// by Kafka between producers and consumers.\n\tHeaders []RecordHeader\n\n\t// This field is used to hold arbitrary data you wish to include so it\n\t// will be available when receiving on the Successes and Errors channels.\n\t// Sarama completely ignores this field and is only to be used for\n\t// pass-through data.\n\tMetadata interface{}\n\n\t// Below this point are filled in by the producer as the message is processed\n\n\t// Offset is the offset of the message stored on the broker. This is only\n\t// guaranteed to be defined if the message was successfully delivered and\n\t// RequiredAcks is not NoResponse.\n\tOffset int64\n\t// Partition is the partition that the message was sent to. This is only\n\t// guaranteed to be defined if the message was successfully delivered.\n\tPartition int32\n\t// Timestamp can vary in behavior depending on broker configuration, being\n\t// in either one of the CreateTime or LogAppendTime modes (default CreateTime),\n\t// and requiring version at least 0.10.0.\n\t//\n\t// When configured to CreateTime, the timestamp is specified by the producer\n\t// either by explicitly setting this field, or when the message is added\n\t// to a produce set.\n\t//\n\t// When configured to LogAppendTime, the timestamp assigned to the message\n\t// by the broker. This is only guaranteed to be defined if the message was\n\t// successfully delivered and RequiredAcks is not NoResponse.\n\tTimestamp time.Time\n\n\tretries        int\n\tflags          flagSet\n\texpectation    chan *ProducerError\n\tsequenceNumber int32\n\tproducerEpoch  int16\n\thasSequence    bool\n}\n\nconst producerMessageOverhead = 26 // the metadata overhead of CRC, flags, etc.\n\nfunc (m *ProducerMessage) ByteSize(version int) int {\n\tvar size int\n\tif version >= 2 {\n\t\tsize = maximumRecordOverhead\n\t\tfor _, h := range m.Headers {\n\t\t\tsize += len(h.Key) + len(h.Value) + 2*binary.MaxVarintLen32\n\t\t}\n\t} else {\n\t\tsize = producerMessageOverhead\n\t}\n\tif m.Key != nil {\n\t\tsize += m.Key.Length()\n\t}\n\tif m.Value != nil {\n\t\tsize += m.Value.Length()\n\t}\n\treturn size\n}\n\nfunc (m *ProducerMessage) clear() {\n\tm.flags = 0\n\tm.retries = 0\n\tm.sequenceNumber = 0\n\tm.producerEpoch = 0\n\tm.hasSequence = false\n}\n\n// ProducerError is the type of error generated when the producer fails to deliver a message.\n// It contains the original ProducerMessage as well as the actual error value.\ntype ProducerError struct {\n\tMsg *ProducerMessage\n\tErr error\n}\n\nfunc (pe ProducerError) Error() string {\n\treturn fmt.Sprintf(\"kafka: Failed to produce message to topic %s: %s\", pe.Msg.Topic, pe.Err)\n}\n\nfunc (pe ProducerError) Unwrap() error {\n\treturn pe.Err\n}\n\n// ProducerErrors is a type that wraps a batch of \"ProducerError\"s and implements the Error interface.\n// It can be returned from the Producer's Close method to avoid the need to manually drain the Errors channel\n// when closing a producer.\ntype ProducerErrors []*ProducerError\n\nfunc (pe ProducerErrors) Error() string {\n\treturn fmt.Sprintf(\"kafka: Failed to deliver %d messages.\", len(pe))\n}\n\nfunc (p *asyncProducer) IsTransactional() bool {\n\treturn p.txnmgr.isTransactional()\n}\n\nfunc (p *asyncProducer) AddMessageToTxn(msg *ConsumerMessage, groupId string, metadata *string) error {\n\toffsets := make(map[string][]*PartitionOffsetMetadata)\n\toffsets[msg.Topic] = []*PartitionOffsetMetadata{\n\t\t{\n\t\t\tPartition: msg.Partition,\n\t\t\tOffset:    msg.Offset + 1,\n\t\t\tMetadata:  metadata,\n\t\t},\n\t}\n\treturn p.AddOffsetsToTxn(offsets, groupId)\n}\n\nfunc (p *asyncProducer) AddOffsetsToTxn(offsets map[string][]*PartitionOffsetMetadata, groupId string) error {\n\tp.txLock.Lock()\n\tdefer p.txLock.Unlock()\n\n\tif !p.IsTransactional() {\n\t\tDebugLogger.Printf(\"producer/txnmgr [%s] attempt to call AddOffsetsToTxn on a non-transactional producer\\n\", p.txnmgr.transactionalID)\n\t\treturn ErrNonTransactedProducer\n\t}\n\n\tDebugLogger.Printf(\"producer/txnmgr [%s] add offsets to transaction\\n\", p.txnmgr.transactionalID)\n\treturn p.txnmgr.addOffsetsToTxn(offsets, groupId)\n}\n\nfunc (p *asyncProducer) TxnStatus() ProducerTxnStatusFlag {\n\treturn p.txnmgr.currentTxnStatus()\n}\n\nfunc (p *asyncProducer) BeginTxn() error {\n\tp.txLock.Lock()\n\tdefer p.txLock.Unlock()\n\n\tif !p.IsTransactional() {\n\t\tDebugLogger.Println(\"producer/txnmgr attempt to call BeginTxn on a non-transactional producer\")\n\t\treturn ErrNonTransactedProducer\n\t}\n\n\treturn p.txnmgr.transitionTo(ProducerTxnFlagInTransaction, nil)\n}\n\nfunc (p *asyncProducer) CommitTxn() error {\n\tp.txLock.Lock()\n\tdefer p.txLock.Unlock()\n\n\tif !p.IsTransactional() {\n\t\tDebugLogger.Printf(\"producer/txnmgr [%s] attempt to call CommitTxn on a non-transactional producer\\n\", p.txnmgr.transactionalID)\n\t\treturn ErrNonTransactedProducer\n\t}\n\n\tDebugLogger.Printf(\"producer/txnmgr [%s] committing transaction\\n\", p.txnmgr.transactionalID)\n\terr := p.finishTransaction(true)\n\tif err != nil {\n\t\treturn err\n\t}\n\tDebugLogger.Printf(\"producer/txnmgr [%s] transaction committed\\n\", p.txnmgr.transactionalID)\n\treturn nil\n}\n\nfunc (p *asyncProducer) AbortTxn() error {\n\tp.txLock.Lock()\n\tdefer p.txLock.Unlock()\n\n\tif !p.IsTransactional() {\n\t\tDebugLogger.Printf(\"producer/txnmgr [%s] attempt to call AbortTxn on a non-transactional producer\\n\", p.txnmgr.transactionalID)\n\t\treturn ErrNonTransactedProducer\n\t}\n\tDebugLogger.Printf(\"producer/txnmgr [%s] aborting transaction\\n\", p.txnmgr.transactionalID)\n\terr := p.finishTransaction(false)\n\tif err != nil {\n\t\treturn err\n\t}\n\tDebugLogger.Printf(\"producer/txnmgr [%s] transaction aborted\\n\", p.txnmgr.transactionalID)\n\treturn nil\n}\n\nfunc (p *asyncProducer) finishTransaction(commit bool) error {\n\tp.inFlight.Add(1)\n\tif commit {\n\t\tp.input <- &ProducerMessage{flags: endtxn | committxn}\n\t} else {\n\t\tp.input <- &ProducerMessage{flags: endtxn | aborttxn}\n\t}\n\tp.inFlight.Wait()\n\treturn p.txnmgr.finishTransaction(commit)\n}\n\nfunc (p *asyncProducer) Errors() <-chan *ProducerError {\n\treturn p.errors\n}\n\nfunc (p *asyncProducer) Successes() <-chan *ProducerMessage {\n\treturn p.successes\n}\n\nfunc (p *asyncProducer) Input() chan<- *ProducerMessage {\n\treturn p.input\n}\n\nfunc (p *asyncProducer) Close() error {\n\tp.AsyncClose()\n\n\tif p.conf.Producer.Return.Successes {\n\t\tgo withRecover(func() {\n\t\t\tfor range p.successes {\n\t\t\t}\n\t\t})\n\t}\n\n\tvar pErrs ProducerErrors\n\tif p.conf.Producer.Return.Errors {\n\t\tfor event := range p.errors {\n\t\t\tpErrs = append(pErrs, event)\n\t\t}\n\t} else {\n\t\t<-p.errors\n\t}\n\n\tif len(pErrs) > 0 {\n\t\treturn pErrs\n\t}\n\treturn nil\n}\n\nfunc (p *asyncProducer) AsyncClose() {\n\tgo withRecover(p.shutdown)\n}\n\n// singleton\n// dispatches messages by topic\nfunc (p *asyncProducer) dispatcher() {\n\thandlers := make(map[string]chan<- *ProducerMessage)\n\tshuttingDown := false\n\n\tfor msg := range p.input {\n\t\tif msg == nil {\n\t\t\tLogger.Println(\"Something tried to send a nil message, it was ignored.\")\n\t\t\tcontinue\n\t\t}\n\n\t\tif msg.flags&endtxn != 0 {\n\t\t\tvar err error\n\t\t\tif msg.flags&committxn != 0 {\n\t\t\t\terr = p.txnmgr.transitionTo(ProducerTxnFlagEndTransaction|ProducerTxnFlagCommittingTransaction, nil)\n\t\t\t} else {\n\t\t\t\terr = p.txnmgr.transitionTo(ProducerTxnFlagEndTransaction|ProducerTxnFlagAbortingTransaction, nil)\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tLogger.Printf(\"producer/txnmgr unable to end transaction %s\", err)\n\t\t\t}\n\t\t\tp.inFlight.Done()\n\t\t\tcontinue\n\t\t}\n\n\t\tif msg.flags&shutdown != 0 {\n\t\t\tshuttingDown = true\n\t\t\tp.inFlight.Done()\n\t\t\tcontinue\n\t\t}\n\n\t\tif msg.retries == 0 {\n\t\t\tif shuttingDown {\n\t\t\t\t// we can't just call returnError here because that decrements the wait group,\n\t\t\t\t// which hasn't been incremented yet for this message, and shouldn't be\n\t\t\t\tpErr := &ProducerError{Msg: msg, Err: ErrShuttingDown}\n\t\t\t\tif p.conf.Producer.Return.Errors {\n\t\t\t\t\tp.errors <- pErr\n\t\t\t\t} else {\n\t\t\t\t\tLogger.Println(pErr)\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tp.inFlight.Add(1)\n\t\t\t// Ignore retried msg, there are already in txn.\n\t\t\t// Can't produce new record when transaction is not started.\n\t\t\tif p.IsTransactional() && p.txnmgr.currentTxnStatus()&ProducerTxnFlagInTransaction == 0 {\n\t\t\t\tLogger.Printf(\"attempt to send message when transaction is not started or is in ending state, got %d, expect %d\\n\", p.txnmgr.currentTxnStatus(), ProducerTxnFlagInTransaction)\n\t\t\t\tp.returnError(msg, ErrTransactionNotReady)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tfor _, interceptor := range p.conf.Producer.Interceptors {\n\t\t\tmsg.safelyApplyInterceptor(interceptor)\n\t\t}\n\n\t\tversion := 1\n\t\tif p.conf.Version.IsAtLeast(V0_11_0_0) {\n\t\t\tversion = 2\n\t\t} else if msg.Headers != nil {\n\t\t\tp.returnError(msg, ConfigurationError(\"Producing headers requires Kafka at least v0.11\"))\n\t\t\tcontinue\n\t\t}\n\n\t\tsize := msg.ByteSize(version)\n\t\tif size > p.conf.Producer.MaxMessageBytes {\n\t\t\tp.returnError(msg, ConfigurationError(fmt.Sprintf(\"Attempt to produce message larger than configured Producer.MaxMessageBytes: %d > %d\", size, p.conf.Producer.MaxMessageBytes)))\n\t\t\tcontinue\n\t\t}\n\n\t\thandler := handlers[msg.Topic]\n\t\tif handler == nil {\n\t\t\thandler = p.newTopicProducer(msg.Topic)\n\t\t\thandlers[msg.Topic] = handler\n\t\t}\n\n\t\thandler <- msg\n\t}\n\n\tfor _, handler := range handlers {\n\t\tclose(handler)\n\t}\n}\n\n// one per topic\n// partitions messages, then dispatches them by partition\ntype topicProducer struct {\n\tparent *asyncProducer\n\ttopic  string\n\tinput  <-chan *ProducerMessage\n\n\tbreaker     *breaker.Breaker\n\thandlers    map[int32]chan<- *ProducerMessage\n\tpartitioner Partitioner\n}\n\nfunc (p *asyncProducer) newTopicProducer(topic string) chan<- *ProducerMessage {\n\tinput := make(chan *ProducerMessage, p.conf.ChannelBufferSize)\n\ttp := &topicProducer{\n\t\tparent:      p,\n\t\ttopic:       topic,\n\t\tinput:       input,\n\t\tbreaker:     breaker.New(3, 1, 10*time.Second),\n\t\thandlers:    make(map[int32]chan<- *ProducerMessage),\n\t\tpartitioner: p.conf.Producer.Partitioner(topic),\n\t}\n\tgo withRecover(tp.dispatch)\n\treturn input\n}\n\nfunc (tp *topicProducer) dispatch() {\n\tfor msg := range tp.input {\n\t\tif msg.retries == 0 {\n\t\t\tif err := tp.partitionMessage(msg); err != nil {\n\t\t\t\ttp.parent.returnError(msg, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\thandler := tp.handlers[msg.Partition]\n\t\tif handler == nil {\n\t\t\thandler = tp.parent.newPartitionProducer(msg.Topic, msg.Partition)\n\t\t\ttp.handlers[msg.Partition] = handler\n\t\t}\n\n\t\thandler <- msg\n\t}\n\n\tfor _, handler := range tp.handlers {\n\t\tclose(handler)\n\t}\n}\n\nfunc (tp *topicProducer) partitionMessage(msg *ProducerMessage) error {\n\tvar partitions []int32\n\n\terr := tp.breaker.Run(func() (err error) {\n\t\trequiresConsistency := false\n\t\tif ep, ok := tp.partitioner.(DynamicConsistencyPartitioner); ok {\n\t\t\trequiresConsistency = ep.MessageRequiresConsistency(msg)\n\t\t} else {\n\t\t\trequiresConsistency = tp.partitioner.RequiresConsistency()\n\t\t}\n\n\t\tif requiresConsistency {\n\t\t\tpartitions, err = tp.parent.client.Partitions(msg.Topic)\n\t\t} else {\n\t\t\tpartitions, err = tp.parent.client.WritablePartitions(msg.Topic)\n\t\t}\n\t\treturn\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tnumPartitions := int32(len(partitions))\n\n\tif numPartitions == 0 {\n\t\treturn ErrLeaderNotAvailable\n\t}\n\n\tchoice, err := tp.partitioner.Partition(msg, numPartitions)\n\n\tif err != nil {\n\t\treturn err\n\t} else if choice < 0 || choice >= numPartitions {\n\t\treturn ErrInvalidPartition\n\t}\n\n\tmsg.Partition = partitions[choice]\n\n\treturn nil\n}\n\n// one per partition per topic\n// dispatches messages to the appropriate broker\n// also responsible for maintaining message order during retries\ntype partitionProducer struct {\n\tparent    *asyncProducer\n\ttopic     string\n\tpartition int32\n\tinput     <-chan *ProducerMessage\n\n\tleader         *Broker\n\tbreaker        *breaker.Breaker\n\tbrokerProducer *brokerProducer\n\n\t// highWatermark tracks the \"current\" retry level, which is the only one where we actually let messages through,\n\t// all other messages get buffered in retryState[msg.retries].buf to preserve ordering\n\t// retryState[msg.retries].expectChaser simply tracks whether we've seen a fin message for a given level (and\n\t// therefore whether our buffer is complete and safe to flush)\n\thighWatermark int\n\tretryState    []partitionRetryState\n}\n\ntype partitionRetryState struct {\n\tbuf          []*ProducerMessage\n\texpectChaser bool\n}\n\nfunc (p *asyncProducer) newPartitionProducer(topic string, partition int32) chan<- *ProducerMessage {\n\tinput := make(chan *ProducerMessage, p.conf.ChannelBufferSize)\n\tpp := &partitionProducer{\n\t\tparent:    p,\n\t\ttopic:     topic,\n\t\tpartition: partition,\n\t\tinput:     input,\n\n\t\tbreaker:    breaker.New(3, 1, 10*time.Second),\n\t\tretryState: make([]partitionRetryState, p.conf.Producer.Retry.Max+1),\n\t}\n\tgo withRecover(pp.dispatch)\n\treturn input\n}\n\nfunc (pp *partitionProducer) backoff(retries int) {\n\tvar backoff time.Duration\n\tif pp.parent.conf.Producer.Retry.BackoffFunc != nil {\n\t\tmaxRetries := pp.parent.conf.Producer.Retry.Max\n\t\tbackoff = pp.parent.conf.Producer.Retry.BackoffFunc(retries, maxRetries)\n\t} else {\n\t\tbackoff = pp.parent.conf.Producer.Retry.Backoff\n\t}\n\tif backoff > 0 {\n\t\ttime.Sleep(backoff)\n\t}\n}\n\nfunc (pp *partitionProducer) updateLeaderIfBrokerProducerIsNil(msg *ProducerMessage) error {\n\tif pp.brokerProducer == nil {\n\t\tif err := pp.updateLeader(); err != nil {\n\t\t\tpp.parent.returnError(msg, err)\n\t\t\tpp.backoff(msg.retries)\n\t\t\treturn err\n\t\t}\n\t\tLogger.Printf(\"producer/leader/%s/%d selected broker %d\\n\", pp.topic, pp.partition, pp.leader.ID())\n\t}\n\treturn nil\n}\n\nfunc (pp *partitionProducer) dispatch() {\n\t// try to prefetch the leader; if this doesn't work, we'll do a proper call to `updateLeader`\n\t// on the first message\n\tpp.leader, _ = pp.parent.client.Leader(pp.topic, pp.partition)\n\tif pp.leader != nil {\n\t\tpp.brokerProducer = pp.parent.getBrokerProducer(pp.leader)\n\t\tpp.parent.inFlight.Add(1) // we're generating a syn message; track it so we don't shut down while it's still inflight\n\t\tpp.brokerProducer.input <- &ProducerMessage{Topic: pp.topic, Partition: pp.partition, flags: syn}\n\t}\n\n\tdefer func() {\n\t\tif pp.brokerProducer != nil {\n\t\t\tpp.parent.unrefBrokerProducer(pp.leader, pp.brokerProducer)\n\t\t}\n\t}()\n\n\tfor msg := range pp.input {\n\t\tif pp.brokerProducer != nil && pp.brokerProducer.abandoned != nil {\n\t\t\tselect {\n\t\t\tcase <-pp.brokerProducer.abandoned:\n\t\t\t\t// a message on the abandoned channel means that our current broker selection is out of date\n\t\t\t\tLogger.Printf(\"producer/leader/%s/%d abandoning broker %d\\n\", pp.topic, pp.partition, pp.leader.ID())\n\t\t\t\tpp.parent.unrefBrokerProducer(pp.leader, pp.brokerProducer)\n\t\t\t\tpp.brokerProducer = nil\n\t\t\t\ttime.Sleep(pp.parent.conf.Producer.Retry.Backoff)\n\t\t\tdefault:\n\t\t\t\t// producer connection is still open.\n\t\t\t}\n\t\t}\n\n\t\tif msg.retries > pp.highWatermark {\n\t\t\tif err := pp.updateLeaderIfBrokerProducerIsNil(msg); err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// a new, higher, retry level; handle it and then back off\n\t\t\tpp.newHighWatermark(msg.retries)\n\t\t\tpp.backoff(msg.retries)\n\t\t} else if pp.highWatermark > 0 {\n\t\t\t// we are retrying something (else highWatermark would be 0) but this message is not a *new* retry level\n\t\t\tif msg.retries < pp.highWatermark {\n\t\t\t\t// in fact this message is not even the current retry level, so buffer it for now (unless it's a just a fin)\n\t\t\t\tif msg.flags&fin == fin {\n\t\t\t\t\tpp.retryState[msg.retries].expectChaser = false\n\t\t\t\t\tpp.parent.inFlight.Done() // this fin is now handled and will be garbage collected\n\t\t\t\t} else {\n\t\t\t\t\tpp.retryState[msg.retries].buf = append(pp.retryState[msg.retries].buf, msg)\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t} else if msg.flags&fin == fin {\n\t\t\t\t// this message is of the current retry level (msg.retries == highWatermark) and the fin flag is set,\n\t\t\t\t// meaning this retry level is done and we can go down (at least) one level and flush that\n\t\t\t\tpp.retryState[pp.highWatermark].expectChaser = false\n\t\t\t\tpp.flushRetryBuffers()\n\t\t\t\tpp.parent.inFlight.Done() // this fin is now handled and will be garbage collected\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\t// if we made it this far then the current msg contains real data, and can be sent to the next goroutine\n\t\t// without breaking any of our ordering guarantees\n\t\tif err := pp.updateLeaderIfBrokerProducerIsNil(msg); err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Now that we know we have a broker to actually try and send this message to, generate the sequence\n\t\t// number for it.\n\t\t// All messages being retried (sent or not) have already had their retry count updated\n\t\t// Also, ignore \"special\" syn/fin messages used to sync the brokerProducer and the topicProducer.\n\t\tif pp.parent.conf.Producer.Idempotent && msg.retries == 0 && msg.flags == 0 {\n\t\t\tmsg.sequenceNumber, msg.producerEpoch = pp.parent.txnmgr.getAndIncrementSequenceNumber(msg.Topic, msg.Partition)\n\t\t\tmsg.hasSequence = true\n\t\t}\n\n\t\tif pp.parent.IsTransactional() {\n\t\t\tpp.parent.txnmgr.maybeAddPartitionToCurrentTxn(pp.topic, pp.partition)\n\t\t}\n\n\t\tpp.brokerProducer.input <- msg\n\t}\n}\n\nfunc (pp *partitionProducer) newHighWatermark(hwm int) {\n\tLogger.Printf(\"producer/leader/%s/%d state change to [retrying-%d]\\n\", pp.topic, pp.partition, hwm)\n\tpp.highWatermark = hwm\n\n\t// send off a fin so that we know when everything \"in between\" has made it\n\t// back to us and we can safely flush the backlog (otherwise we risk re-ordering messages)\n\tpp.retryState[pp.highWatermark].expectChaser = true\n\tpp.parent.inFlight.Add(1) // we're generating a fin message; track it so we don't shut down while it's still inflight\n\tpp.brokerProducer.input <- &ProducerMessage{Topic: pp.topic, Partition: pp.partition, flags: fin, retries: pp.highWatermark - 1}\n\n\t// a new HWM means that our current broker selection is out of date\n\tLogger.Printf(\"producer/leader/%s/%d abandoning broker %d\\n\", pp.topic, pp.partition, pp.leader.ID())\n\tpp.parent.unrefBrokerProducer(pp.leader, pp.brokerProducer)\n\tpp.brokerProducer = nil\n}\n\nfunc (pp *partitionProducer) flushRetryBuffers() {\n\tLogger.Printf(\"producer/leader/%s/%d state change to [flushing-%d]\\n\", pp.topic, pp.partition, pp.highWatermark)\n\tfor {\n\t\tpp.highWatermark--\n\n\t\tif pp.brokerProducer == nil {\n\t\t\tif err := pp.updateLeader(); err != nil {\n\t\t\t\tpp.parent.returnErrors(pp.retryState[pp.highWatermark].buf, err)\n\t\t\t\tgoto flushDone\n\t\t\t}\n\t\t\tLogger.Printf(\"producer/leader/%s/%d selected broker %d\\n\", pp.topic, pp.partition, pp.leader.ID())\n\t\t}\n\n\t\tfor _, msg := range pp.retryState[pp.highWatermark].buf {\n\t\t\tif pp.parent.conf.Producer.Idempotent && msg.retries == 0 && msg.flags == 0 && !msg.hasSequence {\n\t\t\t\tmsg.sequenceNumber, msg.producerEpoch = pp.parent.txnmgr.getAndIncrementSequenceNumber(msg.Topic, msg.Partition)\n\t\t\t\tmsg.hasSequence = true\n\t\t\t}\n\t\t\tpp.brokerProducer.input <- msg\n\t\t}\n\n\tflushDone:\n\t\tpp.retryState[pp.highWatermark].buf = nil\n\t\tif pp.retryState[pp.highWatermark].expectChaser {\n\t\t\tLogger.Printf(\"producer/leader/%s/%d state change to [retrying-%d]\\n\", pp.topic, pp.partition, pp.highWatermark)\n\t\t\tbreak\n\t\t} else if pp.highWatermark == 0 {\n\t\t\tLogger.Printf(\"producer/leader/%s/%d state change to [normal]\\n\", pp.topic, pp.partition)\n\t\t\tbreak\n\t\t}\n\t}\n}\n\nfunc (pp *partitionProducer) updateLeader() error {\n\treturn pp.breaker.Run(func() (err error) {\n\t\tif err = pp.parent.client.RefreshMetadata(pp.topic); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif pp.leader, err = pp.parent.client.Leader(pp.topic, pp.partition); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tpp.brokerProducer = pp.parent.getBrokerProducer(pp.leader)\n\t\tpp.parent.inFlight.Add(1) // we're generating a syn message; track it so we don't shut down while it's still inflight\n\t\tpp.brokerProducer.input <- &ProducerMessage{Topic: pp.topic, Partition: pp.partition, flags: syn}\n\n\t\treturn nil\n\t})\n}\n\n// one per broker; also constructs an associated flusher\nfunc (p *asyncProducer) newBrokerProducer(broker *Broker) *brokerProducer {\n\tvar (\n\t\tinput     = make(chan *ProducerMessage)\n\t\tbridge    = make(chan *produceSet)\n\t\tpending   = make(chan *brokerProducerResponse)\n\t\tresponses = make(chan *brokerProducerResponse)\n\t)\n\n\tbp := &brokerProducer{\n\t\tparent:            p,\n\t\tbroker:            broker,\n\t\tinput:             input,\n\t\toutput:            bridge,\n\t\tresponses:         responses,\n\t\taccumulatingBatch: newProduceSet(p),\n\t\tcurrentRetries:    make(map[string]map[int32]error),\n\t}\n\tgo withRecover(bp.run)\n\n\t// minimal bridge to make the network response `select`able\n\tgo withRecover(func() {\n\t\t// Use a wait group to know if we still have in flight requests\n\t\tvar wg sync.WaitGroup\n\n\t\tfor set := range bridge {\n\t\t\trequest := set.buildRequest()\n\n\t\t\t// Count the in flight requests to know when we can close the pending channel safely\n\t\t\twg.Add(1)\n\t\t\t// capture the muted set. unmuting is deferred to handleResponse to ensure that\n\t\t\t// retries block subsequent batches for the same partition.\n\t\t\tmutedSet := set\n\t\t\tsendResponse := func(response *ProduceResponse, err error) {\n\t\t\t\tpending <- &brokerProducerResponse{\n\t\t\t\t\tset: mutedSet,\n\t\t\t\t\terr: err,\n\t\t\t\t\tres: response,\n\t\t\t\t}\n\t\t\t\twg.Done()\n\t\t\t}\n\n\t\t\tif p.IsTransactional() {\n\t\t\t\t// Add partition to tx before sending current batch\n\t\t\t\terr := p.txnmgr.publishTxnPartitions()\n\t\t\t\tif err != nil {\n\t\t\t\t\t// Request failed to be sent\n\t\t\t\t\tsendResponse(nil, err)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Use AsyncProduce vs Produce to not block waiting for the response\n\t\t\t// so that we can pipeline multiple produce requests and achieve higher throughput, see:\n\t\t\t// https://kafka.apache.org/protocol#protocol_network\n\t\t\terr := broker.AsyncProduce(request, sendResponse)\n\t\t\tif err != nil {\n\t\t\t\t// Request failed to be sent\n\t\t\t\tsendResponse(nil, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// Callback is not called when using NoResponse\n\t\t\tif p.conf.Producer.RequiredAcks == NoResponse {\n\t\t\t\t// Provide the expected nil response\n\t\t\t\tsendResponse(nil, nil)\n\t\t\t}\n\t\t}\n\t\t// Wait for all in flight requests to close the pending channel safely\n\t\twg.Wait()\n\t\tclose(pending)\n\t})\n\n\t// In order to avoid a deadlock when closing the broker on network or malformed response error\n\t// we use an intermediate channel to buffer and send pending responses in order\n\t// This is because the AsyncProduce callback inside the bridge is invoked from the broker\n\t// responseReceiver goroutine and closing the broker requires such goroutine to be finished\n\tgo withRecover(func() {\n\t\tbuf := queue.New()\n\t\tfor {\n\t\t\tif buf.Length() == 0 {\n\t\t\t\tres, ok := <-pending\n\t\t\t\tif !ok {\n\t\t\t\t\t// We are done forwarding the last pending response\n\t\t\t\t\tclose(responses)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tbuf.Add(res)\n\t\t\t}\n\t\t\t// Send the head pending response or buffer another one\n\t\t\t// so that we never block the callback\n\t\t\theadRes := buf.Peek().(*brokerProducerResponse)\n\t\t\tselect {\n\t\t\tcase res, ok := <-pending:\n\t\t\t\tif !ok {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbuf.Add(res)\n\t\t\t\tcontinue\n\t\t\tcase responses <- headRes:\n\t\t\t\tbuf.Remove()\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t})\n\n\tif p.conf.Producer.Retry.Max <= 0 {\n\t\tbp.abandoned = make(chan struct{})\n\t}\n\n\treturn bp\n}\n\ntype brokerProducerResponse struct {\n\tset *produceSet\n\terr error\n\tres *ProduceResponse\n}\n\n// groups messages together into appropriately-sized batches for sending to the broker\n// handles state related to retries etc\ntype brokerProducer struct {\n\tparent *asyncProducer\n\tbroker *Broker\n\n\tinput     chan *ProducerMessage\n\toutput    chan<- *produceSet\n\tresponses <-chan *brokerProducerResponse\n\tabandoned chan struct{}\n\n\taccumulatingBatch *produceSet\n\tflushingBatch     *produceSet // batch that has been muted and is ready to send\n\ttimer             *time.Timer\n\ttimerFired        bool\n\n\tclosing        error\n\tcurrentRetries map[string]map[int32]error\n}\n\nfunc (bp *brokerProducer) run() {\n\tvar output chan<- *produceSet\n\tLogger.Printf(\"producer/broker/%d starting up\\n\", bp.broker.ID())\n\n\tfor {\n\t\tif bp.flushingBatch == nil && (bp.timerFired || bp.accumulatingBatch.readyToFlush()) {\n\t\t\tbp.tryBuildFlushingBatch()\n\t\t}\n\n\t\tvar timerChan <-chan time.Time\n\t\tif bp.timer != nil {\n\t\t\ttimerChan = bp.timer.C\n\t\t}\n\n\t\tif bp.flushingBatch != nil {\n\t\t\toutput = bp.output\n\t\t} else {\n\t\t\toutput = nil\n\t\t}\n\n\t\tselect {\n\t\tcase msg, ok := <-bp.input:\n\t\t\tif !ok {\n\t\t\t\tLogger.Printf(\"producer/broker/%d input chan closed\\n\", bp.broker.ID())\n\t\t\t\tbp.shutdown()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif msg == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif msg.flags&syn == syn {\n\t\t\t\tLogger.Printf(\"producer/broker/%d state change to [open] on %s/%d\\n\",\n\t\t\t\t\tbp.broker.ID(), msg.Topic, msg.Partition)\n\t\t\t\tif bp.currentRetries[msg.Topic] == nil {\n\t\t\t\t\tbp.currentRetries[msg.Topic] = make(map[int32]error)\n\t\t\t\t}\n\t\t\t\tbp.currentRetries[msg.Topic][msg.Partition] = nil\n\t\t\t\tbp.parent.inFlight.Done()\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif reason := bp.needsRetry(msg); reason != nil {\n\t\t\t\tbp.parent.retryMessage(msg, reason)\n\n\t\t\t\tif bp.closing == nil && msg.flags&fin == fin {\n\t\t\t\t\t// we were retrying this partition but we can start processing again\n\t\t\t\t\tdelete(bp.currentRetries[msg.Topic], msg.Partition)\n\t\t\t\t\tLogger.Printf(\"producer/broker/%d state change to [closed] on %s/%d\\n\",\n\t\t\t\t\t\tbp.broker.ID(), msg.Topic, msg.Partition)\n\t\t\t\t}\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif msg.flags&fin == fin {\n\t\t\t\t// New broker producer that was caught up by the retry loop\n\t\t\t\tbp.parent.retryMessage(msg, ErrShuttingDown)\n\t\t\t\tDebugLogger.Printf(\"producer/broker/%d state change to [dying-%d] on %s/%d\\n\",\n\t\t\t\t\tbp.broker.ID(), msg.retries, msg.Topic, msg.Partition)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif bp.accumulatingBatch.wouldOverflow(msg) {\n\t\t\t\tLogger.Printf(\"producer/broker/%d maximum request accumulated, waiting for space\\n\", bp.broker.ID())\n\t\t\t\tif err := bp.waitForSpace(msg, false); err != nil {\n\t\t\t\t\tbp.parent.retryMessage(msg, err)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif bp.parent.txnmgr.producerID != noProducerID && bp.accumulatingBatch.producerEpoch != msg.producerEpoch {\n\t\t\t\t// The epoch was reset, need to roll the buffer over\n\t\t\t\tLogger.Printf(\"producer/broker/%d detected epoch rollover, waiting for new buffer\\n\", bp.broker.ID())\n\t\t\t\tif err := bp.waitForSpace(msg, true); err != nil {\n\t\t\t\t\tbp.parent.retryMessage(msg, err)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t\tif err := bp.accumulatingBatch.add(msg); err != nil {\n\t\t\t\tbp.parent.returnError(msg, err)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif bp.parent.conf.Producer.Flush.Frequency > 0 && bp.timer == nil {\n\t\t\t\tbp.timer = time.NewTimer(bp.parent.conf.Producer.Flush.Frequency)\n\t\t\t}\n\t\tcase <-timerChan:\n\t\t\tbp.timerFired = true\n\t\tcase output <- bp.flushingBatch:\n\t\t\tbp.flushingBatch = nil\n\t\tcase response, ok := <-bp.responses:\n\t\t\tif ok {\n\t\t\t\tbp.handleResponse(response)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (bp *brokerProducer) tryBuildFlushingBatch() bool {\n\tif bp.flushingBatch != nil || bp.accumulatingBatch.empty() {\n\t\treturn false\n\t}\n\tif bp.parent.muter.tryMute(bp.accumulatingBatch) {\n\t\tbp.flushingBatch = bp.accumulatingBatch\n\t\tbp.rollOver()\n\t\treturn true\n\t}\n\n\tpartial := bp.accumulatingBatch.takePartitions(func(topic string, partition int32) bool {\n\t\treturn bp.parent.muter.tryMutePartition(topic, partition)\n\t})\n\tif partial == nil {\n\t\treturn false\n\t}\n\tbp.flushingBatch = partial\n\tif bp.accumulatingBatch.empty() {\n\t\tbp.rollOver()\n\t}\n\treturn true\n}\n\nfunc (bp *brokerProducer) shutdown() {\n\t// flush any ready buffer\n\tfor bp.flushingBatch != nil {\n\t\tselect {\n\t\tcase response := <-bp.responses:\n\t\t\tbp.handleResponse(response)\n\t\tcase bp.output <- bp.flushingBatch:\n\t\t\tbp.flushingBatch = nil\n\t\t}\n\t}\n\t// then flush the current buffer\n\tfor !bp.accumulatingBatch.empty() || bp.flushingBatch != nil {\n\t\tif bp.flushingBatch == nil {\n\t\t\tbp.tryBuildFlushingBatch()\n\t\t}\n\t\tvar unmuteCh <-chan struct{}\n\t\tvar outputCh chan<- *produceSet\n\t\tif bp.flushingBatch != nil {\n\t\t\toutputCh = bp.output\n\t\t} else if ch, blocked := bp.parent.muter.awaitUnmuteChan(bp.accumulatingBatch); blocked {\n\t\t\tunmuteCh = ch\n\t\t}\n\t\tselect {\n\t\tcase response, ok := <-bp.responses:\n\t\t\tif ok {\n\t\t\t\tbp.handleResponse(response)\n\t\t\t}\n\t\tcase outputCh <- bp.flushingBatch:\n\t\t\tbp.flushingBatch = nil\n\t\tcase <-unmuteCh:\n\t\t}\n\t}\n\tclose(bp.output)\n\t// Drain responses from the bridge goroutine\n\tfor response := range bp.responses {\n\t\tbp.handleResponse(response)\n\t}\n\t// No more brokerProducer related goroutine should be running\n\tLogger.Printf(\"producer/broker/%d shut down\\n\", bp.broker.ID())\n}\n\nfunc (bp *brokerProducer) needsRetry(msg *ProducerMessage) error {\n\tif bp.closing != nil {\n\t\treturn bp.closing\n\t}\n\n\treturn bp.currentRetries[msg.Topic][msg.Partition]\n}\n\n// waitForSpace makes space in the accumulating batch by flushing. It loops until the message fits.\nfunc (bp *brokerProducer) waitForSpace(msg *ProducerMessage, forceRollover bool) error {\n\tif bp.accumulatingBatch.empty() && !forceRollover {\n\t\treturn nil\n\t}\n\n\tfor {\n\t\tif !bp.accumulatingBatch.wouldOverflow(msg) && !forceRollover {\n\t\t\treturn nil\n\t\t}\n\n\t\tif bp.flushingBatch != nil {\n\t\t\tselect {\n\t\t\tcase response := <-bp.responses:\n\t\t\t\tbp.handleResponse(response)\n\t\t\t\tif reason := bp.needsRetry(msg); reason != nil {\n\t\t\t\t\treturn reason\n\t\t\t\t}\n\t\t\tcase bp.output <- bp.flushingBatch:\n\t\t\t\tbp.flushingBatch = nil\n\t\t\t}\n\n\t\t\tcontinue\n\t\t}\n\n\t\tif bp.accumulatingBatch.empty() {\n\t\t\tif forceRollover {\n\t\t\t\tbp.rollOver()\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\n\t\tif bp.tryBuildFlushingBatch() {\n\t\t\tcontinue\n\t\t}\n\n\t\tif unmuteCh, blocked := bp.parent.muter.awaitUnmuteChan(bp.accumulatingBatch); blocked {\n\t\t\tselect {\n\t\t\tcase response := <-bp.responses:\n\t\t\t\tbp.handleResponse(response)\n\t\t\t\tif reason := bp.needsRetry(msg); reason != nil {\n\t\t\t\t\treturn reason\n\t\t\t\t}\n\t\t\tcase <-unmuteCh:\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (bp *brokerProducer) rollOver() {\n\tif bp.timer != nil {\n\t\tbp.timer.Stop()\n\t}\n\tbp.timer = nil\n\tbp.timerFired = false\n\tbp.accumulatingBatch = newProduceSet(bp.parent)\n}\n\nfunc (bp *brokerProducer) handleResponse(response *brokerProducerResponse) {\n\tif response.err != nil {\n\t\tbp.handleError(response.set, response.err)\n\t} else {\n\t\tbp.handleSuccess(response.set, response.res)\n\t}\n\n\tif bp.accumulatingBatch.empty() {\n\t\tbp.rollOver() // this can happen if the response invalidated our buffer\n\t}\n}\n\nfunc (bp *brokerProducer) handleSuccess(sent *produceSet, response *ProduceResponse) {\n\t// we iterate through the blocks in the request set, not the response, so that we notice\n\t// if the response is missing a block completely\n\tvar retryTopics []string\n\tkeepMuted := make(map[string]map[int32]struct{})\n\tsent.eachPartition(func(topic string, partition int32, pSet *partitionSet) {\n\t\tif response == nil {\n\t\t\t// this only happens when RequiredAcks is NoResponse, so we have to assume success\n\t\t\tbp.parent.returnSuccesses(pSet.msgs)\n\t\t\treturn\n\t\t}\n\n\t\tblock := response.GetBlock(topic, partition)\n\t\tif block == nil {\n\t\t\tbp.parent.returnErrors(pSet.msgs, ErrIncompleteResponse)\n\t\t\treturn\n\t\t}\n\n\t\tswitch block.Err {\n\t\t// Success\n\t\tcase ErrNoError:\n\t\t\tif bp.parent.conf.Version.IsAtLeast(V0_10_0_0) && !block.Timestamp.IsZero() {\n\t\t\t\tfor _, msg := range pSet.msgs {\n\t\t\t\t\tmsg.Timestamp = block.Timestamp\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor i, msg := range pSet.msgs {\n\t\t\t\tmsg.Offset = block.Offset + int64(i)\n\t\t\t}\n\t\t\tbp.parent.returnSuccesses(pSet.msgs)\n\t\t// Duplicate\n\t\tcase ErrDuplicateSequenceNumber:\n\t\t\tbp.parent.returnSuccesses(pSet.msgs)\n\t\t// Retriable errors\n\t\tcase ErrInvalidMessage, ErrUnknownTopicOrPartition, ErrLeaderNotAvailable, ErrNotLeaderForPartition,\n\t\t\tErrRequestTimedOut, ErrNotEnoughReplicas, ErrNotEnoughReplicasAfterAppend, ErrKafkaStorageError:\n\t\t\tif bp.parent.conf.Producer.Retry.Max <= 0 {\n\t\t\t\tbp.parent.abandonBrokerConnection(bp.broker)\n\t\t\t\tbp.parent.returnErrors(pSet.msgs, block.Err)\n\t\t\t} else {\n\t\t\t\tretryTopics = append(retryTopics, topic)\n\t\t\t\tif bp.parent.conf.Producer.Idempotent {\n\t\t\t\t\tif keepMuted[topic] == nil {\n\t\t\t\t\t\tkeepMuted[topic] = make(map[int32]struct{})\n\t\t\t\t\t}\n\t\t\t\t\tkeepMuted[topic][partition] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t// Other non-retriable errors\n\t\tdefault:\n\t\t\tif bp.parent.conf.Producer.Retry.Max <= 0 {\n\t\t\t\tbp.parent.abandonBrokerConnection(bp.broker)\n\t\t\t}\n\t\t\tbp.parent.returnErrors(pSet.msgs, block.Err)\n\t\t}\n\t})\n\n\tif len(retryTopics) > 0 {\n\t\tif bp.parent.conf.Producer.Idempotent {\n\t\t\terr := bp.parent.client.RefreshMetadata(retryTopics...)\n\t\t\tif err != nil {\n\t\t\t\tLogger.Printf(\"Failed refreshing metadata because of %v\\n\", err)\n\t\t\t}\n\t\t}\n\n\t\tsent.eachPartition(func(topic string, partition int32, pSet *partitionSet) {\n\t\t\tblock := response.GetBlock(topic, partition)\n\t\t\tif block == nil {\n\t\t\t\t// handled in the previous \"eachPartition\" loop\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tswitch block.Err {\n\t\t\tcase ErrInvalidMessage, ErrUnknownTopicOrPartition, ErrLeaderNotAvailable, ErrNotLeaderForPartition,\n\t\t\t\tErrRequestTimedOut, ErrNotEnoughReplicas, ErrNotEnoughReplicasAfterAppend, ErrKafkaStorageError:\n\t\t\t\tLogger.Printf(\"producer/broker/%d state change to [retrying] on %s/%d because %v\\n\",\n\t\t\t\t\tbp.broker.ID(), topic, partition, block.Err)\n\t\t\t\tif bp.currentRetries[topic] == nil {\n\t\t\t\t\tbp.currentRetries[topic] = make(map[int32]error)\n\t\t\t\t}\n\t\t\t\tbp.currentRetries[topic][partition] = block.Err\n\t\t\t\tif bp.parent.conf.Producer.Idempotent {\n\t\t\t\t\tgo bp.parent.retryBatch(topic, partition, pSet, block.Err, true)\n\t\t\t\t} else {\n\t\t\t\t\tbp.parent.retryMessages(pSet.msgs, block.Err)\n\t\t\t\t}\n\t\t\t\t// dropping the following messages has the side effect of incrementing their retry count\n\t\t\t\tbp.parent.retryMessages(bp.accumulatingBatch.dropPartition(topic, partition), block.Err)\n\t\t\t}\n\t\t})\n\t}\n\n\tunmuteSet := sent.copyFunc(func(topic string, partition int32) bool {\n\t\tif partitions := keepMuted[topic]; partitions != nil {\n\t\t\t_, kept := partitions[partition]\n\t\t\treturn !kept\n\t\t}\n\t\treturn true\n\t})\n\tbp.parent.muter.unmute(unmuteSet)\n}\n\nfunc (p *asyncProducer) retryBatch(topic string, partition int32, pSet *partitionSet, retryErr error, alreadyMuted bool) {\n\tLogger.Printf(\"Retrying batch for %v-%d because of %v\\n\", topic, partition, retryErr)\n\tproduceSet := newProduceSet(p)\n\tproduceSet.msgs[topic] = make(map[int32]*partitionSet)\n\tproduceSet.msgs[topic][partition] = pSet\n\tproduceSet.bufferBytes += pSet.bufferBytes\n\tproduceSet.bufferCount += len(pSet.msgs)\n\tfor _, msg := range pSet.msgs {\n\t\tif msg.retries >= p.conf.Producer.Retry.Max {\n\t\t\tp.returnErrors(pSet.msgs, retryErr)\n\t\t\tif alreadyMuted {\n\t\t\t\tp.muter.unmute(produceSet)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tmsg.retries++\n\t}\n\n\t// it's expected that a metadata refresh has been requested prior to calling retryBatch\n\tleader, leaderErr := p.client.Leader(topic, partition)\n\tif leaderErr != nil {\n\t\tLogger.Printf(\"Failed retrying batch for %v-%d because of %v while looking up for new leader\\n\", topic, partition, leaderErr)\n\t\tfor _, msg := range pSet.msgs {\n\t\t\tp.returnError(msg, retryErr)\n\t\t}\n\t\tif alreadyMuted {\n\t\t\tp.muter.unmute(produceSet)\n\t\t}\n\t\treturn\n\t}\n\tif !alreadyMuted {\n\t\tif !p.muter.waitUntilMuted(produceSet) {\n\t\t\tfor _, msg := range pSet.msgs {\n\t\t\t\tp.returnError(msg, retryErr)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\tbp := p.getBrokerProducer(leader)\n\tbp.output <- produceSet\n\tp.unrefBrokerProducer(leader, bp)\n}\n\nfunc (bp *brokerProducer) handleError(sent *produceSet, err error) {\n\tvar target PacketEncodingError\n\tif errors.As(err, &target) {\n\t\tsent.eachPartition(func(topic string, partition int32, pSet *partitionSet) {\n\t\t\tbp.parent.returnErrors(pSet.msgs, err)\n\t\t})\n\t\tbp.parent.muter.unmute(sent)\n\t} else {\n\t\tLogger.Printf(\"producer/broker/%d state change to [closing] because %s\\n\", bp.broker.ID(), err)\n\t\tbp.parent.abandonBrokerConnection(bp.broker)\n\t\t_ = bp.broker.Close()\n\t\tbp.closing = err\n\t\tvar retryTopics []string\n\t\tretryTopicSeen := make(map[string]struct{})\n\t\tsent.eachPartition(func(topic string, partition int32, pSet *partitionSet) {\n\t\t\tif _, ok := retryTopicSeen[topic]; ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tretryTopicSeen[topic] = struct{}{}\n\t\t\tretryTopics = append(retryTopics, topic)\n\t\t})\n\t\tif bp.parent.conf.Producer.Idempotent && len(retryTopics) > 0 {\n\t\t\trefreshErr := bp.parent.client.RefreshMetadata(retryTopics...)\n\t\t\tif refreshErr != nil {\n\t\t\t\tLogger.Printf(\"Failed refreshing metadata because of %v\\n\", refreshErr)\n\t\t\t}\n\t\t}\n\t\tkeepMuted := make(map[string]map[int32]struct{})\n\t\tsent.eachPartition(func(topic string, partition int32, pSet *partitionSet) {\n\t\t\t// keep partition marked as in-flight during retry (connection error)\n\t\t\tif bp.currentRetries[topic] == nil {\n\t\t\t\tbp.currentRetries[topic] = make(map[int32]error)\n\t\t\t}\n\t\t\tbp.currentRetries[topic][partition] = err\n\t\t\tif bp.parent.conf.Producer.Idempotent {\n\t\t\t\tif keepMuted[topic] == nil {\n\t\t\t\t\tkeepMuted[topic] = make(map[int32]struct{})\n\t\t\t\t}\n\t\t\t\tkeepMuted[topic][partition] = struct{}{}\n\t\t\t\tgo bp.parent.retryBatch(topic, partition, pSet, err, true)\n\t\t\t} else {\n\t\t\t\tbp.parent.retryMessages(pSet.msgs, err)\n\t\t\t}\n\t\t})\n\t\tbp.accumulatingBatch.eachPartition(func(topic string, partition int32, pSet *partitionSet) {\n\t\t\tbp.parent.retryMessages(pSet.msgs, err)\n\t\t})\n\t\tbp.rollOver()\n\n\t\tunmuteSet := sent.copyFunc(func(topic string, partition int32) bool {\n\t\t\tif partitions := keepMuted[topic]; partitions != nil {\n\t\t\t\t_, kept := partitions[partition]\n\t\t\t\treturn !kept\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\t\tbp.parent.muter.unmute(unmuteSet)\n\t}\n}\n\n// singleton\n// effectively a \"bridge\" between the flushers and the dispatcher in order to avoid deadlock\n// based on https://godoc.org/github.com/eapache/channels#InfiniteChannel\nfunc (p *asyncProducer) retryHandler() {\n\tmaxBufferLength := p.conf.Producer.Retry.MaxBufferLength\n\tif 0 < maxBufferLength && maxBufferLength < minFunctionalRetryBufferLength {\n\t\tmaxBufferLength = minFunctionalRetryBufferLength\n\t}\n\n\tmaxBufferBytes := p.conf.Producer.Retry.MaxBufferBytes\n\tif 0 < maxBufferBytes && maxBufferBytes < minFunctionalRetryBufferBytes {\n\t\tmaxBufferBytes = minFunctionalRetryBufferBytes\n\t}\n\n\tversion := 1\n\tif p.conf.Version.IsAtLeast(V0_11_0_0) {\n\t\tversion = 2\n\t}\n\n\tvar currentByteSize int64\n\tvar msg *ProducerMessage\n\tbuf := queue.New()\n\n\tfor {\n\t\tif buf.Length() == 0 {\n\t\t\tmsg = <-p.retries\n\t\t} else {\n\t\t\tselect {\n\t\t\tcase msg = <-p.retries:\n\t\t\tcase p.input <- buf.Peek().(*ProducerMessage):\n\t\t\t\tmsgToRemove := buf.Remove().(*ProducerMessage)\n\t\t\t\tcurrentByteSize -= int64(msgToRemove.ByteSize(version))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tif msg == nil {\n\t\t\treturn\n\t\t}\n\n\t\tbuf.Add(msg)\n\t\tcurrentByteSize += int64(msg.ByteSize(version))\n\n\t\tif (maxBufferLength <= 0 || buf.Length() < maxBufferLength) && (maxBufferBytes <= 0 || currentByteSize < maxBufferBytes) {\n\t\t\tcontinue\n\t\t}\n\n\t\tmsgToHandle := buf.Peek().(*ProducerMessage)\n\t\tif msgToHandle.flags == 0 {\n\t\t\tselect {\n\t\t\tcase p.input <- msgToHandle:\n\t\t\t\tbuf.Remove()\n\t\t\t\tcurrentByteSize -= int64(msgToHandle.ByteSize(version))\n\t\t\tdefault:\n\t\t\t\tbuf.Remove()\n\t\t\t\tcurrentByteSize -= int64(msgToHandle.ByteSize(version))\n\t\t\t\tp.returnError(msgToHandle, ErrProducerRetryBufferOverflow)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// utility functions\n\nfunc (p *asyncProducer) shutdown() {\n\tLogger.Println(\"Producer shutting down.\")\n\tp.inFlight.Add(1)\n\tp.input <- &ProducerMessage{flags: shutdown}\n\n\tp.inFlight.Wait()\n\n\terr := p.client.Close()\n\tif err != nil {\n\t\tLogger.Println(\"producer/shutdown failed to close the embedded client:\", err)\n\t}\n\n\tp.muter.close()\n\n\tclose(p.input)\n\tclose(p.retries)\n\tclose(p.errors)\n\tclose(p.successes)\n\n\tp.metricsRegistry.UnregisterAll()\n}\n\nfunc (p *asyncProducer) bumpIdempotentProducerEpoch() {\n\t_, epoch := p.txnmgr.getProducerID()\n\tif epoch == math.MaxInt16 {\n\t\tLogger.Println(\"producer/txnmanager epoch exhausted, requesting new producer ID\")\n\t\ttxnmgr, err := newTransactionManager(p.conf, p.client)\n\t\tif err != nil {\n\t\t\tLogger.Println(err)\n\t\t\treturn\n\t\t}\n\n\t\tp.txnmgr = txnmgr\n\t} else {\n\t\tp.txnmgr.bumpEpoch()\n\t}\n}\n\nfunc (p *asyncProducer) maybeTransitionToErrorState(err error) error {\n\tif errors.Is(err, ErrClusterAuthorizationFailed) ||\n\t\terrors.Is(err, ErrProducerFenced) ||\n\t\terrors.Is(err, ErrUnsupportedVersion) ||\n\t\terrors.Is(err, ErrTransactionalIDAuthorizationFailed) {\n\t\treturn p.txnmgr.transitionTo(ProducerTxnFlagInError|ProducerTxnFlagFatalError, err)\n\t}\n\tif p.txnmgr.coordinatorSupportsBumpingEpoch && p.txnmgr.currentTxnStatus()&ProducerTxnFlagEndTransaction == 0 {\n\t\tp.txnmgr.epochBumpRequired = true\n\t}\n\treturn p.txnmgr.transitionTo(ProducerTxnFlagInError|ProducerTxnFlagAbortableError, err)\n}\n\nfunc (p *asyncProducer) returnError(msg *ProducerMessage, err error) {\n\tif p.IsTransactional() {\n\t\t_ = p.maybeTransitionToErrorState(err)\n\t}\n\t// We need to reset the producer ID epoch if we set a sequence number on it, because the broker\n\t// will never see a message with this number, so we can never continue the sequence.\n\tif !p.IsTransactional() && msg.hasSequence {\n\t\tLogger.Printf(\"producer/txnmanager rolling over epoch due to publish failure on %s/%d\", msg.Topic, msg.Partition)\n\t\tp.bumpIdempotentProducerEpoch()\n\t}\n\n\tmsg.clear()\n\tpErr := &ProducerError{Msg: msg, Err: err}\n\tif p.conf.Producer.Return.Errors {\n\t\tp.errors <- pErr\n\t} else {\n\t\tLogger.Println(pErr)\n\t}\n\tp.inFlight.Done()\n}\n\nfunc (p *asyncProducer) returnErrors(batch []*ProducerMessage, err error) {\n\tfor _, msg := range batch {\n\t\tp.returnError(msg, err)\n\t}\n}\n\nfunc (p *asyncProducer) returnSuccesses(batch []*ProducerMessage) {\n\tfor _, msg := range batch {\n\t\tif p.conf.Producer.Return.Successes {\n\t\t\tmsg.clear()\n\t\t\tp.successes <- msg\n\t\t}\n\t\tp.inFlight.Done()\n\t}\n}\n\nfunc (p *asyncProducer) retryMessage(msg *ProducerMessage, err error) {\n\tif msg.retries >= p.conf.Producer.Retry.Max {\n\t\tp.returnError(msg, err)\n\t} else {\n\t\tmsg.retries++\n\t\tp.retries <- msg\n\t}\n}\n\nfunc (p *asyncProducer) retryMessages(batch []*ProducerMessage, err error) {\n\tfor _, msg := range batch {\n\t\tp.retryMessage(msg, err)\n\t}\n}\n\nfunc (p *asyncProducer) getBrokerProducer(broker *Broker) *brokerProducer {\n\tp.brokerLock.Lock()\n\tdefer p.brokerLock.Unlock()\n\n\tbp := p.brokers[broker]\n\n\tif bp == nil {\n\t\tbp = p.newBrokerProducer(broker)\n\t\tp.brokers[broker] = bp\n\t\tp.brokerRefs[bp] = 0\n\t}\n\n\tp.brokerRefs[bp]++\n\n\treturn bp\n}\n\nfunc (p *asyncProducer) unrefBrokerProducer(broker *Broker, bp *brokerProducer) {\n\tp.brokerLock.Lock()\n\tdefer p.brokerLock.Unlock()\n\n\tp.brokerRefs[bp]--\n\tif p.brokerRefs[bp] == 0 {\n\t\tclose(bp.input)\n\t\tdelete(p.brokerRefs, bp)\n\n\t\tif p.brokers[broker] == bp {\n\t\t\tdelete(p.brokers, broker)\n\t\t}\n\t}\n}\n\nfunc (p *asyncProducer) abandonBrokerConnection(broker *Broker) {\n\tp.brokerLock.Lock()\n\tdefer p.brokerLock.Unlock()\n\n\tbc, ok := p.brokers[broker]\n\tif ok && bc.abandoned != nil {\n\t\tclose(bc.abandoned)\n\t}\n\n\tdelete(p.brokers, broker)\n}\n"
  },
  {
    "path": "async_producer_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"errors\"\n\t\"log\"\n\t\"math\"\n\t\"os\"\n\t\"os/signal\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/fortytw2/leaktest\"\n\t\"github.com/rcrowley/go-metrics\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc expectResultsWithTimeout(t *testing.T, p AsyncProducer, successCount, errorCount int, timeout time.Duration) {\n\tt.Helper()\n\texpect := successCount + errorCount\n\tdefer func() {\n\t\tif successCount != 0 || errorCount != 0 {\n\t\t\tt.Error(\"Unexpected successes\", successCount, \"or errors\", errorCount)\n\t\t}\n\t}()\n\ttimer := time.NewTimer(timeout)\n\tdefer timer.Stop()\n\tfor expect > 0 {\n\t\tselect {\n\t\tcase <-timer.C:\n\t\t\treturn\n\t\tcase msg := <-p.Errors():\n\t\t\tif msg.Msg.flags != 0 {\n\t\t\t\tt.Error(\"Message had flags set\")\n\t\t\t}\n\t\t\terrorCount--\n\t\t\texpect--\n\t\t\tif errorCount < 0 {\n\t\t\t\tt.Error(msg.Err)\n\t\t\t}\n\t\tcase msg := <-p.Successes():\n\t\t\tif msg.flags != 0 {\n\t\t\t\tt.Error(\"Message had flags set\")\n\t\t\t}\n\t\t\tsuccessCount--\n\t\t\texpect--\n\t\t\tif successCount < 0 {\n\t\t\t\tt.Error(\"Too many successes\")\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc expectResults(t *testing.T, p AsyncProducer, successCount, errorCount int) {\n\texpectResultsWithTimeout(t, p, successCount, errorCount, 5*time.Minute)\n}\n\nfunc TestPartitionProducerFlushRetryBuffersAssignsSequence(t *testing.T) {\n\tcfg := NewTestConfig()\n\tcfg.Producer.Idempotent = true\n\n\ttxnmgr := &transactionManager{\n\t\tproducerID:      1,\n\t\tproducerEpoch:   0,\n\t\tsequenceNumbers: map[string]int32{\"topic-0\": 1},\n\t}\n\n\tparent := &asyncProducer{\n\t\tconf:   cfg,\n\t\ttxnmgr: txnmgr,\n\t}\n\n\tbp := &brokerProducer{\n\t\tinput: make(chan *ProducerMessage, 1),\n\t}\n\n\tpp := &partitionProducer{\n\t\tparent:         parent,\n\t\ttopic:          \"topic\",\n\t\tpartition:      0,\n\t\tbrokerProducer: bp,\n\t\tretryState:     make([]partitionRetryState, 1),\n\t\thighWatermark:  1,\n\t}\n\n\tmsg := &ProducerMessage{Topic: \"topic\", Partition: 0}\n\tpp.retryState[0].buf = []*ProducerMessage{msg}\n\n\tpp.flushRetryBuffers()\n\n\tselect {\n\tcase flushed := <-bp.input:\n\t\trequire.True(t, flushed.hasSequence, \"message should have a sequence assigned\")\n\t\trequire.Equal(t, int32(1), flushed.sequenceNumber, \"sequence number should have increased\")\n\t\trequire.Equal(t, txnmgr.producerEpoch, flushed.producerEpoch, \"producer epoch should be the same\")\n\tdefault:\n\t\tt.Fatal(\"expected buffered message to flush\")\n\t}\n}\n\ntype testPartitioner chan *int32\n\nfunc (p testPartitioner) Partition(msg *ProducerMessage, numPartitions int32) (int32, error) {\n\tpart := <-p\n\tif part == nil {\n\t\treturn 0, errors.New(\"BOOM\")\n\t}\n\n\treturn *part, nil\n}\n\nfunc (p testPartitioner) RequiresConsistency() bool {\n\treturn true\n}\n\nfunc (p testPartitioner) feed(partition int32) {\n\tp <- &partition\n}\n\ntype flakyEncoder bool\n\nfunc (f flakyEncoder) Length() int {\n\treturn len(TestMessage)\n}\n\nfunc (f flakyEncoder) Encode() ([]byte, error) {\n\tif !f {\n\t\treturn nil, errors.New(\"flaky encoding error\")\n\t}\n\treturn []byte(TestMessage), nil\n}\n\nfunc TestAsyncProducer(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tleader := NewMockBroker(t, 2)\n\n\tmetadataResponse := new(MetadataResponse)\n\tmetadataResponse.AddBroker(leader.Addr(), leader.BrokerID())\n\tmetadataResponse.AddTopicPartition(\"my_topic\", 0, leader.BrokerID(), nil, nil, nil, ErrNoError)\n\tseedBroker.Returns(metadataResponse)\n\n\tprodSuccess := new(ProduceResponse)\n\tprodSuccess.AddTopicPartition(\"my_topic\", 0, ErrNoError)\n\tleader.Returns(prodSuccess)\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Flush.Messages = 10\n\tconfig.Producer.Return.Successes = true\n\tproducer, err := NewAsyncProducer([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor i := 0; i < 10; i++ {\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(TestMessage), Metadata: i}\n\t}\n\tfor i := 0; i < 10; i++ {\n\t\tselect {\n\t\tcase msg := <-producer.Errors():\n\t\t\tt.Error(msg.Err)\n\t\t\tif msg.Msg.flags != 0 {\n\t\t\t\tt.Error(\"Message had flags set\")\n\t\t\t}\n\t\tcase msg := <-producer.Successes():\n\t\t\tif msg.flags != 0 {\n\t\t\t\tt.Error(\"Message had flags set\")\n\t\t\t}\n\t\t\tif msg.Metadata.(int) != i {\n\t\t\t\tt.Error(\"Message metadata did not match\")\n\t\t\t}\n\t\tcase <-time.After(time.Second):\n\t\t\tt.Errorf(\"Timeout waiting for msg #%d\", i)\n\t\t\tgoto done\n\t\t}\n\t}\ndone:\n\tcloseProducer(t, producer)\n\tleader.Close()\n\tseedBroker.Close()\n}\n\nfunc TestAsyncProducerMultipleFlushes(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tleader := NewMockBroker(t, 2)\n\n\tmetadataResponse := new(MetadataResponse)\n\tmetadataResponse.AddBroker(leader.Addr(), leader.BrokerID())\n\tmetadataResponse.AddTopicPartition(\"my_topic\", 0, leader.BrokerID(), nil, nil, nil, ErrNoError)\n\tseedBroker.Returns(metadataResponse)\n\n\tprodSuccess := new(ProduceResponse)\n\tprodSuccess.AddTopicPartition(\"my_topic\", 0, ErrNoError)\n\tleader.Returns(prodSuccess)\n\tleader.Returns(prodSuccess)\n\tleader.Returns(prodSuccess)\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Flush.Messages = 5\n\tconfig.Producer.Return.Successes = true\n\tproducer, err := NewAsyncProducer([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor flush := 0; flush < 3; flush++ {\n\t\tfor i := 0; i < 5; i++ {\n\t\t\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(TestMessage)}\n\t\t}\n\t\texpectResults(t, producer, 5, 0)\n\t}\n\n\tcloseProducer(t, producer)\n\tleader.Close()\n\tseedBroker.Close()\n}\n\nfunc TestAsyncProducerMultipleBrokers(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tleader0 := NewMockBroker(t, 2)\n\tleader1 := NewMockBroker(t, 3)\n\n\tmetadataResponse := new(MetadataResponse)\n\tmetadataResponse.AddBroker(leader0.Addr(), leader0.BrokerID())\n\tmetadataResponse.AddBroker(leader1.Addr(), leader1.BrokerID())\n\tmetadataResponse.AddTopicPartition(\"my_topic\", 0, leader0.BrokerID(), nil, nil, nil, ErrNoError)\n\tmetadataResponse.AddTopicPartition(\"my_topic\", 1, leader1.BrokerID(), nil, nil, nil, ErrNoError)\n\tseedBroker.Returns(metadataResponse)\n\n\tprodResponse0 := new(ProduceResponse)\n\tprodResponse0.AddTopicPartition(\"my_topic\", 0, ErrNoError)\n\tleader0.Returns(prodResponse0)\n\n\tprodResponse1 := new(ProduceResponse)\n\tprodResponse1.AddTopicPartition(\"my_topic\", 1, ErrNoError)\n\tleader1.Returns(prodResponse1)\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Flush.Messages = 5\n\tconfig.Producer.Return.Successes = true\n\tconfig.Producer.Partitioner = NewRoundRobinPartitioner\n\tproducer, err := NewAsyncProducer([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor i := 0; i < 10; i++ {\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(TestMessage)}\n\t}\n\texpectResults(t, producer, 10, 0)\n\n\tcloseProducer(t, producer)\n\tleader1.Close()\n\tleader0.Close()\n\tseedBroker.Close()\n}\n\nfunc TestAsyncProducerCustomPartitioner(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tleader := NewMockBroker(t, 2)\n\n\tmetadataResponse := new(MetadataResponse)\n\tmetadataResponse.AddBroker(leader.Addr(), leader.BrokerID())\n\tmetadataResponse.AddTopicPartition(\"my_topic\", 0, leader.BrokerID(), nil, nil, nil, ErrNoError)\n\tseedBroker.Returns(metadataResponse)\n\n\tprodResponse := new(ProduceResponse)\n\tprodResponse.AddTopicPartition(\"my_topic\", 0, ErrNoError)\n\tleader.Returns(prodResponse)\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Flush.Messages = 2\n\tconfig.Producer.Return.Successes = true\n\tconfig.Producer.Partitioner = func(topic string) Partitioner {\n\t\tp := make(testPartitioner)\n\t\tgo func() {\n\t\t\tp.feed(0)\n\t\t\tp <- nil\n\t\t\tp <- nil\n\t\t\tp <- nil\n\t\t\tp.feed(0)\n\t\t}()\n\t\treturn p\n\t}\n\tproducer, err := NewAsyncProducer([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor i := 0; i < 5; i++ {\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(TestMessage)}\n\t}\n\texpectResults(t, producer, 2, 3)\n\n\tcloseProducer(t, producer)\n\tleader.Close()\n\tseedBroker.Close()\n}\n\nfunc TestAsyncProducerFailureRetry(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tleader1 := NewMockBroker(t, 2)\n\tleader2 := NewMockBroker(t, 3)\n\n\tmetadataLeader1 := new(MetadataResponse)\n\tmetadataLeader1.AddBroker(leader1.Addr(), leader1.BrokerID())\n\tmetadataLeader1.AddTopicPartition(\"my_topic\", 0, leader1.BrokerID(), nil, nil, nil, ErrNoError)\n\tseedBroker.Returns(metadataLeader1)\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Flush.Messages = 10\n\tconfig.Producer.Return.Successes = true\n\tconfig.Producer.Retry.Backoff = 0\n\tproducer, err := NewAsyncProducer([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tseedBroker.Close()\n\n\tfor i := 0; i < 10; i++ {\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(TestMessage)}\n\t}\n\tprodNotLeader := new(ProduceResponse)\n\tprodNotLeader.AddTopicPartition(\"my_topic\", 0, ErrNotLeaderForPartition)\n\tleader1.Returns(prodNotLeader)\n\n\tmetadataLeader2 := new(MetadataResponse)\n\tmetadataLeader2.AddBroker(leader2.Addr(), leader2.BrokerID())\n\tmetadataLeader2.AddTopicPartition(\"my_topic\", 0, leader2.BrokerID(), nil, nil, nil, ErrNoError)\n\tleader1.Returns(metadataLeader2)\n\n\tprodSuccess := new(ProduceResponse)\n\tprodSuccess.AddTopicPartition(\"my_topic\", 0, ErrNoError)\n\tleader2.Returns(prodSuccess)\n\texpectResults(t, producer, 10, 0)\n\tleader1.Close()\n\n\tfor i := 0; i < 10; i++ {\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(TestMessage)}\n\t}\n\tleader2.Returns(prodSuccess)\n\texpectResults(t, producer, 10, 0)\n\n\tleader2.Close()\n\tcloseProducer(t, producer)\n}\n\nfunc TestAsyncProducerRecoveryWithRetriesDisabled(t *testing.T) {\n\ttt := func(t *testing.T, kErr KError) {\n\t\tseedBroker := NewMockBroker(t, 0)\n\t\tbroker1 := NewMockBroker(t, 1)\n\t\tbroker2 := NewMockBroker(t, 2)\n\n\t\tmockLeader := func(leaderID int32) *MockMetadataResponse {\n\t\t\treturn NewMockMetadataResponse(t).\n\t\t\t\tSetController(seedBroker.BrokerID()).\n\t\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()).\n\t\t\t\tSetBroker(broker1.Addr(), broker1.BrokerID()).\n\t\t\t\tSetBroker(broker2.Addr(), broker2.BrokerID()).\n\t\t\t\tSetLeader(\"my_topic\", 0, leaderID).\n\t\t\t\tSetLeader(\"my_topic\", 1, leaderID)\n\t\t}\n\n\t\tseedBroker.SetHandlerByMap(\n\t\t\tmap[string]MockResponse{\n\t\t\t\t\"MetadataRequest\": mockLeader(broker1.BrokerID()),\n\t\t\t},\n\t\t)\n\n\t\tconfig := NewTestConfig()\n\t\tconfig.ClientID = \"TestAsyncProducerRecoveryWithRetriesDisabled\"\n\t\tconfig.Producer.Flush.Messages = 2\n\t\tconfig.Producer.Flush.Frequency = 100 * time.Millisecond\n\t\tconfig.Producer.Return.Successes = true\n\t\tconfig.Producer.Retry.Max = 0 // disable!\n\t\tconfig.Producer.Retry.Backoff = 0\n\t\tconfig.Producer.Partitioner = NewManualPartitioner\n\t\tproducer, err := NewAsyncProducer([]string{seedBroker.Addr()}, config)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tbroker1.SetHandlerByMap(\n\t\t\tmap[string]MockResponse{\n\t\t\t\t\"MetadataRequest\": mockLeader(broker1.BrokerID()),\n\t\t\t\t\"ProduceRequest\": NewMockProduceResponse(t).\n\t\t\t\t\tSetError(\"my_topic\", 0, kErr).\n\t\t\t\t\tSetError(\"my_topic\", 1, kErr),\n\t\t\t},\n\t\t)\n\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(TestMessage), Partition: 0}\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(TestMessage), Partition: 1}\n\t\texpectResults(t, producer, 0, 2)\n\n\t\tseedBroker.SetHandlerByMap(\n\t\t\tmap[string]MockResponse{\n\t\t\t\t\"MetadataRequest\": mockLeader(broker2.BrokerID()),\n\t\t\t},\n\t\t)\n\t\tbroker1.SetHandlerByMap(\n\t\t\tmap[string]MockResponse{\n\t\t\t\t\"MetadataRequest\": mockLeader(broker2.BrokerID()),\n\t\t\t},\n\t\t)\n\t\tbroker2.SetHandlerByMap(\n\t\t\tmap[string]MockResponse{\n\t\t\t\t\"MetadataRequest\": mockLeader(broker2.BrokerID()),\n\t\t\t\t\"ProduceRequest\": NewMockProduceResponse(t).\n\t\t\t\t\tSetError(\"my_topic\", 0, ErrNoError).\n\t\t\t\t\tSetError(\"my_topic\", 1, ErrNoError),\n\t\t\t},\n\t\t)\n\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(TestMessage), Partition: 0}\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(TestMessage), Partition: 1}\n\t\texpectResults(t, producer, 2, 0)\n\n\t\tcloseProducer(t, producer)\n\t\tseedBroker.Close()\n\t\tbroker1.Close()\n\t\tbroker2.Close()\n\t}\n\n\tt.Run(\"retriable error\", func(t *testing.T) {\n\t\ttt(t, ErrNotLeaderForPartition)\n\t})\n\n\tt.Run(\"non-retriable error\", func(t *testing.T) {\n\t\ttt(t, ErrNotController)\n\t})\n}\n\nfunc TestAsyncProducerEncoderFailures(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tleader := NewMockBroker(t, 2)\n\n\tmetadataResponse := new(MetadataResponse)\n\tmetadataResponse.AddBroker(leader.Addr(), leader.BrokerID())\n\tmetadataResponse.AddTopicPartition(\"my_topic\", 0, leader.BrokerID(), nil, nil, nil, ErrNoError)\n\tseedBroker.Returns(metadataResponse)\n\n\tprodSuccess := new(ProduceResponse)\n\tprodSuccess.AddTopicPartition(\"my_topic\", 0, ErrNoError)\n\tleader.Returns(prodSuccess)\n\tleader.Returns(prodSuccess)\n\tleader.Returns(prodSuccess)\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Flush.Messages = 1\n\tconfig.Producer.Return.Successes = true\n\tconfig.Producer.Partitioner = NewManualPartitioner\n\tproducer, err := NewAsyncProducer([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor flush := 0; flush < 3; flush++ {\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: flakyEncoder(true), Value: flakyEncoder(false)}\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: flakyEncoder(false), Value: flakyEncoder(true)}\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: flakyEncoder(true), Value: flakyEncoder(true)}\n\t\texpectResults(t, producer, 1, 2)\n\t}\n\n\tcloseProducer(t, producer)\n\tleader.Close()\n\tseedBroker.Close()\n}\n\n// If a Kafka broker becomes unavailable and then returns back in service, then\n// producer reconnects to it and continues sending messages.\nfunc TestAsyncProducerBrokerBounce(t *testing.T) {\n\t// Given\n\tseedBroker := NewMockBroker(t, 1)\n\tleader := NewMockBroker(t, 2)\n\tleaderAddr := leader.Addr()\n\n\tmetadataResponse := new(MetadataResponse)\n\tmetadataResponse.AddBroker(leaderAddr, leader.BrokerID())\n\tmetadataResponse.AddTopicPartition(\"my_topic\", 0, leader.BrokerID(), nil, nil, nil, ErrNoError)\n\tseedBroker.Returns(metadataResponse)\n\n\tprodSuccess := new(ProduceResponse)\n\tprodSuccess.AddTopicPartition(\"my_topic\", 0, ErrNoError)\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Flush.Messages = 1\n\tconfig.Producer.Return.Successes = true\n\tconfig.Producer.Retry.Backoff = 0\n\tproducer, err := NewAsyncProducer([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(TestMessage)}\n\tleader.Returns(prodSuccess)\n\texpectResults(t, producer, 1, 0)\n\n\t// When: a broker connection gets reset by a broker (network glitch, restart, you name it).\n\tleader.Close()                               // producer should get EOF\n\tleader = NewMockBrokerAddr(t, 2, leaderAddr) // start it up again right away for giggles\n\tleader.Returns(metadataResponse)             // tell it to go to broker 2 again\n\n\t// Then: a produced message goes through the new broker connection.\n\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(TestMessage)}\n\tleader.Returns(prodSuccess)\n\texpectResults(t, producer, 1, 0)\n\n\tcloseProducer(t, producer)\n\tseedBroker.Close()\n\tleader.Close()\n}\n\nfunc TestAsyncProducerBrokerBounceWithStaleMetadata(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tleader1 := NewMockBroker(t, 2)\n\tleader2 := NewMockBroker(t, 3)\n\n\tmetadataLeader1 := new(MetadataResponse)\n\tmetadataLeader1.AddBroker(leader1.Addr(), leader1.BrokerID())\n\tmetadataLeader1.AddTopicPartition(\"my_topic\", 0, leader1.BrokerID(), nil, nil, nil, ErrNoError)\n\tseedBroker.Returns(metadataLeader1)\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Flush.Messages = 10\n\tconfig.Producer.Return.Successes = true\n\tconfig.Producer.Retry.Max = 3\n\tconfig.Producer.Retry.Backoff = 0\n\tproducer, err := NewAsyncProducer([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor i := 0; i < 10; i++ {\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(TestMessage)}\n\t}\n\tleader1.Close()                     // producer should get EOF\n\tseedBroker.Returns(metadataLeader1) // tell it to go to leader1 again even though it's still down\n\tseedBroker.Returns(metadataLeader1) // tell it to go to leader1 again even though it's still down\n\n\t// ok fine, tell it to go to leader2 finally\n\tmetadataLeader2 := new(MetadataResponse)\n\tmetadataLeader2.AddBroker(leader2.Addr(), leader2.BrokerID())\n\tmetadataLeader2.AddTopicPartition(\"my_topic\", 0, leader2.BrokerID(), nil, nil, nil, ErrNoError)\n\tseedBroker.Returns(metadataLeader2)\n\n\tprodSuccess := new(ProduceResponse)\n\tprodSuccess.AddTopicPartition(\"my_topic\", 0, ErrNoError)\n\tleader2.Returns(prodSuccess)\n\texpectResults(t, producer, 10, 0)\n\tseedBroker.Close()\n\tleader2.Close()\n\n\tcloseProducer(t, producer)\n}\n\nfunc TestAsyncProducerMultipleRetries(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tleader1 := NewMockBroker(t, 2)\n\tleader2 := NewMockBroker(t, 3)\n\n\tmetadataLeader1 := new(MetadataResponse)\n\tmetadataLeader1.AddBroker(leader1.Addr(), leader1.BrokerID())\n\tmetadataLeader1.AddTopicPartition(\"my_topic\", 0, leader1.BrokerID(), nil, nil, nil, ErrNoError)\n\tseedBroker.Returns(metadataLeader1)\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Flush.Messages = 10\n\tconfig.Producer.Return.Successes = true\n\tconfig.Producer.Retry.Max = 4\n\tconfig.Producer.Retry.Backoff = 0\n\tproducer, err := NewAsyncProducer([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor i := 0; i < 10; i++ {\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(TestMessage)}\n\t}\n\tprodNotLeader := new(ProduceResponse)\n\tprodNotLeader.AddTopicPartition(\"my_topic\", 0, ErrNotLeaderForPartition)\n\tleader1.Returns(prodNotLeader)\n\n\tmetadataLeader2 := new(MetadataResponse)\n\tmetadataLeader2.AddBroker(leader2.Addr(), leader2.BrokerID())\n\tmetadataLeader2.AddTopicPartition(\"my_topic\", 0, leader2.BrokerID(), nil, nil, nil, ErrNoError)\n\n\tleader1.Returns(metadataLeader2)\n\tleader2.Returns(prodNotLeader)\n\tleader2.Returns(metadataLeader1)\n\tleader1.Returns(prodNotLeader)\n\tleader1.Returns(metadataLeader1)\n\tleader1.Returns(prodNotLeader)\n\tleader1.Returns(metadataLeader2)\n\n\tprodSuccess := new(ProduceResponse)\n\tprodSuccess.AddTopicPartition(\"my_topic\", 0, ErrNoError)\n\tleader2.Returns(prodSuccess)\n\texpectResults(t, producer, 10, 0)\n\n\tfor i := 0; i < 10; i++ {\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(TestMessage)}\n\t}\n\tleader2.Returns(prodSuccess)\n\texpectResults(t, producer, 10, 0)\n\n\tseedBroker.Close()\n\tleader1.Close()\n\tleader2.Close()\n\tcloseProducer(t, producer)\n}\n\nfunc TestAsyncProducerMultipleRetriesWithBackoffFunc(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tleader1 := NewMockBroker(t, 2)\n\tleader2 := NewMockBroker(t, 3)\n\n\tmetadataLeader1 := new(MetadataResponse)\n\tmetadataLeader1.AddBroker(leader1.Addr(), leader1.BrokerID())\n\tmetadataLeader1.AddTopicPartition(\"my_topic\", 0, leader1.BrokerID(), nil, nil, nil, ErrNoError)\n\tseedBroker.Returns(metadataLeader1)\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Flush.Messages = 1\n\tconfig.Producer.Return.Successes = true\n\tconfig.Producer.Retry.Max = 4\n\n\t// We use a pointer to atomic to prevent the possibility of a reallocation causing a copy.\n\tbackoffCalled := make([]*atomic.Int32, config.Producer.Retry.Max+1)\n\tfor i := range backoffCalled {\n\t\tbackoffCalled[i] = new(atomic.Int32)\n\t}\n\n\tconfig.Producer.Retry.BackoffFunc = func(retries, maxRetries int) time.Duration {\n\t\tbackoffCalled[retries-1].Add(1)\n\t\treturn 0\n\t}\n\tproducer, err := NewAsyncProducer([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(TestMessage)}\n\tprodNotLeader := new(ProduceResponse)\n\tprodNotLeader.AddTopicPartition(\"my_topic\", 0, ErrNotLeaderForPartition)\n\n\tprodSuccess := new(ProduceResponse)\n\tprodSuccess.AddTopicPartition(\"my_topic\", 0, ErrNoError)\n\n\tmetadataLeader2 := new(MetadataResponse)\n\tmetadataLeader2.AddBroker(leader2.Addr(), leader2.BrokerID())\n\tmetadataLeader2.AddTopicPartition(\"my_topic\", 0, leader2.BrokerID(), nil, nil, nil, ErrNoError)\n\n\tleader1.Returns(prodNotLeader)\n\tleader1.Returns(metadataLeader2)\n\tleader2.Returns(prodNotLeader)\n\tleader2.Returns(metadataLeader1)\n\tleader1.Returns(prodNotLeader)\n\tleader1.Returns(metadataLeader1)\n\tleader1.Returns(prodNotLeader)\n\tleader1.Returns(metadataLeader2)\n\tleader2.Returns(prodSuccess)\n\n\texpectResults(t, producer, 1, 0)\n\n\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(TestMessage)}\n\tleader2.Returns(prodSuccess)\n\texpectResults(t, producer, 1, 0)\n\n\tseedBroker.Close()\n\tleader1.Close()\n\tleader2.Close()\n\tcloseProducer(t, producer)\n\n\tfor i := 0; i < config.Producer.Retry.Max; i++ {\n\t\tif backoffCalled[i].Load() != 1 {\n\t\t\tt.Errorf(\"expected one retry attempt #%d\", i)\n\t\t}\n\t}\n\tif backoffCalled[config.Producer.Retry.Max].Load() != 0 {\n\t\tt.Errorf(\"expected no retry attempt #%d\", config.Producer.Retry.Max)\n\t}\n}\n\nfunc TestAsyncProducerWithExponentialBackoffDurations(t *testing.T) {\n\tvar backoffDurations []time.Duration\n\tvar mu sync.Mutex\n\n\ttopic := \"my_topic\"\n\tmaxBackoff := 2 * time.Second\n\tconfig := NewTestConfig()\n\n\tinnerBackoffFunc := NewExponentialBackoff(defaultRetryBackoff, maxBackoff)\n\tbackoffFunc := func(retries, maxRetries int) time.Duration {\n\t\tduration := innerBackoffFunc(retries, maxRetries)\n\t\tmu.Lock()\n\t\tbackoffDurations = append(backoffDurations, duration)\n\t\tmu.Unlock()\n\t\treturn duration\n\t}\n\n\tconfig.Producer.Flush.Messages = 5\n\tconfig.Producer.Return.Successes = true\n\tconfig.Producer.Retry.Max = 3\n\tconfig.Producer.Retry.BackoffFunc = backoffFunc\n\n\tbroker := NewMockBroker(t, 1)\n\n\tmetadataResponse := new(MetadataResponse)\n\tmetadataResponse.AddBroker(broker.Addr(), broker.BrokerID())\n\tmetadataResponse.AddTopicPartition(topic, 0, broker.BrokerID(), nil, nil, nil, ErrNoError)\n\tbroker.Returns(metadataResponse)\n\n\tproducer, err := NewAsyncProducer([]string{broker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfailResponse := new(ProduceResponse)\n\tfailResponse.AddTopicPartition(topic, 0, ErrNotLeaderForPartition)\n\tsuccessResponse := new(ProduceResponse)\n\tsuccessResponse.AddTopicPartition(topic, 0, ErrNoError)\n\n\tbroker.Returns(failResponse)\n\tbroker.Returns(metadataResponse)\n\tbroker.Returns(failResponse)\n\tbroker.Returns(metadataResponse)\n\tbroker.Returns(successResponse)\n\n\tfor i := 0; i < 5; i++ {\n\t\tproducer.Input() <- &ProducerMessage{Topic: topic, Value: StringEncoder(\"test\")}\n\t}\n\n\texpectResults(t, producer, 5, 0)\n\tcloseProducer(t, producer)\n\tbroker.Close()\n\n\tassert.Greater(t, backoffDurations[0], time.Duration(0),\n\t\t\"Expected first backoff duration to be greater than 0\")\n\tfor i := 1; i < len(backoffDurations); i++ {\n\t\tassert.Greater(t, backoffDurations[i], time.Duration(0))\n\t\tassert.GreaterOrEqual(t, backoffDurations[i], backoffDurations[i-1])\n\t\tassert.LessOrEqual(t, backoffDurations[i], maxBackoff)\n\t}\n}\n\n// https://github.com/IBM/sarama/issues/2129\nfunc TestAsyncProducerMultipleRetriesWithConcurrentRequests(t *testing.T) {\n\t// Logger = log.New(os.Stdout, \"[sarama] \", log.LstdFlags)\n\tseedBroker := NewMockBroker(t, 1)\n\tleader := NewMockBroker(t, 2)\n\n\t// The seed broker only handles Metadata request\n\tseedBroker.setHandler(func(req *request) (res encoderWithHeader) {\n\t\tmetadataLeader := new(MetadataResponse)\n\t\tmetadataLeader.AddBroker(leader.Addr(), leader.BrokerID())\n\t\tmetadataLeader.AddTopicPartition(\"my_topic\", 0, leader.BrokerID(), nil, nil, nil, ErrNoError)\n\t\treturn metadataLeader\n\t})\n\n\t// Simulate a slow broker by taking ~200ms to handle requests\n\t// therefore triggering the read timeout and the retry logic\n\tleader.setHandler(func(req *request) (res encoderWithHeader) {\n\t\ttime.Sleep(200 * time.Millisecond)\n\t\t// Will likely not be read by the producer (read timeout)\n\t\tprodSuccess := new(ProduceResponse)\n\t\tprodSuccess.AddTopicPartition(\"my_topic\", 0, ErrNoError)\n\t\treturn prodSuccess\n\t})\n\n\tconfig := NewTestConfig()\n\t// Use very short read to simulate read error on unresponsive broker\n\tconfig.Net.ReadTimeout = 50 * time.Millisecond\n\t// Flush every record to generate up to 5 in-flight Produce requests\n\t// because config.Net.MaxOpenRequests defaults to 5\n\tconfig.Producer.Flush.MaxMessages = 1\n\tconfig.Producer.Return.Successes = true\n\t// Reduce retries to speed up the test while keeping the default backoff\n\tconfig.Producer.Retry.Max = 1\n\tproducer, err := NewAsyncProducer([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor i := 0; i < 10; i++ {\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(TestMessage)}\n\t}\n\n\texpectResults(t, producer, 0, 10)\n\n\tseedBroker.Close()\n\tleader.Close()\n\tcloseProducer(t, producer)\n}\n\nfunc TestAsyncProducerBrokerRestart(t *testing.T) {\n\t// Logger = log.New(os.Stdout, \"[sarama] \", log.LstdFlags)\n\n\tseedBroker := NewMockBroker(t, 1)\n\tleader := NewMockBroker(t, 2)\n\n\tvar leaderLock sync.Mutex\n\tmetadataRequestHandlerFunc := func(req *request) (res encoderWithHeader) {\n\t\tleaderLock.Lock()\n\t\tdefer leaderLock.Unlock()\n\t\tmetadataLeader := new(MetadataResponse)\n\t\tmetadataLeader.AddBroker(leader.Addr(), leader.BrokerID())\n\t\tmetadataLeader.AddTopicPartition(\"my_topic\", 0, leader.BrokerID(), nil, nil, nil, ErrNoError)\n\t\treturn metadataLeader\n\t}\n\n\t// The seed broker only handles Metadata request in bootstrap\n\tseedBroker.setHandler(metadataRequestHandlerFunc)\n\n\tvar emptyValues atomic.Int32\n\n\tcountRecordsWithEmptyValue := func(req *request) {\n\t\tpreq := req.body.(*ProduceRequest)\n\t\tif batch := preq.records[\"my_topic\"][0].RecordBatch; batch != nil {\n\t\t\tfor _, record := range batch.Records {\n\t\t\t\tif len(record.Value) == 0 {\n\t\t\t\t\temptyValues.Add(1)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif batch := preq.records[\"my_topic\"][0].MsgSet; batch != nil {\n\t\t\tfor _, record := range batch.Messages {\n\t\t\t\tif len(record.Msg.Value) == 0 {\n\t\t\t\t\temptyValues.Add(1)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfailedProduceRequestHandlerFunc := func(req *request) (res encoderWithHeader) {\n\t\tcountRecordsWithEmptyValue(req)\n\n\t\ttime.Sleep(50 * time.Millisecond)\n\n\t\tprodSuccess := new(ProduceResponse)\n\t\tprodSuccess.AddTopicPartition(\"my_topic\", 0, ErrNotLeaderForPartition)\n\t\treturn prodSuccess\n\t}\n\n\tsucceededProduceRequestHandlerFunc := func(req *request) (res encoderWithHeader) {\n\t\tcountRecordsWithEmptyValue(req)\n\n\t\tprodSuccess := new(ProduceResponse)\n\t\tprodSuccess.AddTopicPartition(\"my_topic\", 0, ErrNoError)\n\t\treturn prodSuccess\n\t}\n\n\tleader.SetHandlerFuncByMap(map[string]requestHandlerFunc{\n\t\t\"ProduceRequest\":  failedProduceRequestHandlerFunc,\n\t\t\"MetadataRequest\": metadataRequestHandlerFunc,\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Retry.Backoff = 250 * time.Millisecond\n\tconfig.Producer.Flush.MaxMessages = 1\n\tconfig.Producer.Return.Errors = true\n\tconfig.Producer.Return.Successes = true\n\tconfig.Producer.Retry.Max = 10\n\n\tproducer, err := NewAsyncProducer([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tvar wg sync.WaitGroup\n\n\tpushMsg := func() {\n\t\tdefer wg.Done()\n\t\tfor i := 0; i < 10; i++ {\n\t\t\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(TestMessage)}\n\t\t\ttime.Sleep(50 * time.Millisecond)\n\t\t}\n\t}\n\n\twg.Add(1)\n\tgo pushMsg()\n\n\tfor i := 0; i < 3; i++ {\n\t\ttime.Sleep(100 * time.Millisecond)\n\n\t\twg.Add(1)\n\t\tgo pushMsg()\n\t}\n\n\tleader.Close()\n\tleaderLock.Lock()\n\tleader = NewMockBroker(t, 2)\n\tleaderLock.Unlock()\n\tleader.SetHandlerFuncByMap(map[string]requestHandlerFunc{\n\t\t\"ProduceRequest\":  succeededProduceRequestHandlerFunc,\n\t\t\"MetadataRequest\": metadataRequestHandlerFunc,\n\t})\n\n\twg.Wait()\n\n\texpectResultsWithTimeout(t, producer, 40, 0, 10*time.Second)\n\n\tseedBroker.Close()\n\tleader.Close()\n\n\tcloseProducerWithTimeout(t, producer, 5*time.Second)\n\n\tif emptyValues := emptyValues.Load(); emptyValues > 0 {\n\t\tt.Fatalf(\"%d empty values\", emptyValues)\n\t}\n}\n\nfunc TestAsyncProducerOutOfRetries(t *testing.T) {\n\tt.Skip(\"Enable once bug #294 is fixed.\")\n\n\tseedBroker := NewMockBroker(t, 1)\n\tleader := NewMockBroker(t, 2)\n\n\tmetadataResponse := new(MetadataResponse)\n\tmetadataResponse.AddBroker(leader.Addr(), leader.BrokerID())\n\tmetadataResponse.AddTopicPartition(\"my_topic\", 0, leader.BrokerID(), nil, nil, nil, ErrNoError)\n\tseedBroker.Returns(metadataResponse)\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Flush.Messages = 10\n\tconfig.Producer.Return.Successes = true\n\tconfig.Producer.Retry.Backoff = 0\n\tconfig.Producer.Retry.Max = 0\n\tproducer, err := NewAsyncProducer([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor i := 0; i < 10; i++ {\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(TestMessage)}\n\t}\n\n\tprodNotLeader := new(ProduceResponse)\n\tprodNotLeader.AddTopicPartition(\"my_topic\", 0, ErrNotLeaderForPartition)\n\tleader.Returns(prodNotLeader)\n\n\tfor i := 0; i < 10; i++ {\n\t\tselect {\n\t\tcase msg := <-producer.Errors():\n\t\t\tif !errors.Is(msg.Err, ErrNotLeaderForPartition) {\n\t\t\t\tt.Error(msg.Err)\n\t\t\t}\n\t\tcase <-producer.Successes():\n\t\t\tt.Error(\"Unexpected success\")\n\t\t}\n\t}\n\n\tseedBroker.Returns(metadataResponse)\n\n\tfor i := 0; i < 10; i++ {\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(TestMessage)}\n\t}\n\n\tprodSuccess := new(ProduceResponse)\n\tprodSuccess.AddTopicPartition(\"my_topic\", 0, ErrNoError)\n\tleader.Returns(prodSuccess)\n\n\texpectResults(t, producer, 10, 0)\n\n\tleader.Close()\n\tseedBroker.Close()\n\tsafeClose(t, producer)\n}\n\nfunc TestAsyncProducerRetryWithReferenceOpen(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tleader := NewMockBroker(t, 2)\n\tleaderAddr := leader.Addr()\n\n\tmetadataResponse := new(MetadataResponse)\n\tmetadataResponse.AddBroker(leaderAddr, leader.BrokerID())\n\tmetadataResponse.AddTopicPartition(\"my_topic\", 0, leader.BrokerID(), nil, nil, nil, ErrNoError)\n\tmetadataResponse.AddTopicPartition(\"my_topic\", 1, leader.BrokerID(), nil, nil, nil, ErrNoError)\n\tseedBroker.Returns(metadataResponse)\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Return.Successes = true\n\tconfig.Producer.Retry.Backoff = 0\n\tconfig.Producer.Retry.Max = 1\n\tconfig.Producer.Partitioner = NewRoundRobinPartitioner\n\tproducer, err := NewAsyncProducer([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// prime partition 0\n\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(TestMessage)}\n\tprodSuccess := new(ProduceResponse)\n\tprodSuccess.AddTopicPartition(\"my_topic\", 0, ErrNoError)\n\tleader.Returns(prodSuccess)\n\texpectResults(t, producer, 1, 0)\n\n\t// prime partition 1\n\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(TestMessage)}\n\tprodSuccess = new(ProduceResponse)\n\tprodSuccess.AddTopicPartition(\"my_topic\", 1, ErrNoError)\n\tleader.Returns(prodSuccess)\n\texpectResults(t, producer, 1, 0)\n\n\t// reboot the broker (the producer will get EOF on its existing connection)\n\tleader.Close()\n\tleader = NewMockBrokerAddr(t, 2, leaderAddr)\n\n\t// send another message on partition 0 to trigger the EOF and retry\n\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(TestMessage)}\n\n\t// tell partition 0 to go to that broker again\n\tleader.Returns(metadataResponse)\n\n\t// succeed this time\n\tprodSuccess = new(ProduceResponse)\n\tprodSuccess.AddTopicPartition(\"my_topic\", 0, ErrNoError)\n\tleader.Returns(prodSuccess)\n\texpectResults(t, producer, 1, 0)\n\n\t// shutdown\n\tcloseProducer(t, producer)\n\tseedBroker.Close()\n\tleader.Close()\n}\n\nfunc TestAsyncProducerFlusherRetryCondition(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tleader := NewMockBroker(t, 2)\n\n\tmetadataResponse := new(MetadataResponse)\n\tmetadataResponse.AddBroker(leader.Addr(), leader.BrokerID())\n\tmetadataResponse.AddTopicPartition(\"my_topic\", 0, leader.BrokerID(), nil, nil, nil, ErrNoError)\n\tmetadataResponse.AddTopicPartition(\"my_topic\", 1, leader.BrokerID(), nil, nil, nil, ErrNoError)\n\tseedBroker.Returns(metadataResponse)\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Flush.Messages = 5\n\tconfig.Producer.Return.Successes = true\n\tconfig.Producer.Retry.Backoff = 0\n\tconfig.Producer.Retry.Max = 1\n\tconfig.Producer.Partitioner = NewManualPartitioner\n\tproducer, err := NewAsyncProducer([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// prime partitions\n\tfor p := int32(0); p < 2; p++ {\n\t\tfor i := 0; i < 5; i++ {\n\t\t\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(TestMessage), Partition: p}\n\t\t}\n\t\tprodSuccess := new(ProduceResponse)\n\t\tprodSuccess.AddTopicPartition(\"my_topic\", p, ErrNoError)\n\t\tleader.Returns(prodSuccess)\n\t\texpectResults(t, producer, 5, 0)\n\t}\n\n\t// send more messages on partition 0\n\tfor i := 0; i < 5; i++ {\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(TestMessage), Partition: 0}\n\t}\n\tprodNotLeader := new(ProduceResponse)\n\tprodNotLeader.AddTopicPartition(\"my_topic\", 0, ErrNotLeaderForPartition)\n\tleader.Returns(prodNotLeader)\n\n\ttime.Sleep(50 * time.Millisecond)\n\n\t// tell partition 0 to go to that broker again\n\tleader.Returns(metadataResponse)\n\tprodSuccess := new(ProduceResponse)\n\tprodSuccess.AddTopicPartition(\"my_topic\", 0, ErrNoError)\n\tleader.Returns(prodSuccess)\n\n\t// succeed this time\n\texpectResults(t, producer, 5, 0)\n\n\t// put five more through\n\tfor i := 0; i < 5; i++ {\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(TestMessage), Partition: 0}\n\t}\n\tprodSuccess = new(ProduceResponse)\n\tprodSuccess.AddTopicPartition(\"my_topic\", 0, ErrNoError)\n\tleader.Returns(prodSuccess)\n\texpectResults(t, producer, 5, 0)\n\n\t// shutdown\n\tcloseProducer(t, producer)\n\tseedBroker.Close()\n\tleader.Close()\n}\n\nfunc TestAsyncProducerRetryShutdown(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tleader := NewMockBroker(t, 2)\n\n\tmetadataLeader := new(MetadataResponse)\n\tmetadataLeader.AddBroker(leader.Addr(), leader.BrokerID())\n\tmetadataLeader.AddTopicPartition(\"my_topic\", 0, leader.BrokerID(), nil, nil, nil, ErrNoError)\n\tseedBroker.Returns(metadataLeader)\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Flush.Messages = 10\n\tconfig.Producer.Return.Successes = true\n\tconfig.Producer.Retry.Backoff = 0\n\tproducer, err := NewAsyncProducer([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor i := 0; i < 10; i++ {\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(TestMessage)}\n\t}\n\tproducer.AsyncClose()\n\ttime.Sleep(5 * time.Millisecond) // let the shutdown goroutine kick in\n\n\tproducer.Input() <- &ProducerMessage{Topic: \"FOO\"}\n\tif err := <-producer.Errors(); !errors.Is(err.Err, ErrShuttingDown) {\n\t\tt.Error(err)\n\t}\n\n\tprodNotLeader := new(ProduceResponse)\n\tprodNotLeader.AddTopicPartition(\"my_topic\", 0, ErrNotLeaderForPartition)\n\tleader.Returns(prodNotLeader)\n\n\tleader.Returns(metadataLeader)\n\n\tprodSuccess := new(ProduceResponse)\n\tprodSuccess.AddTopicPartition(\"my_topic\", 0, ErrNoError)\n\tleader.Returns(prodSuccess)\n\texpectResults(t, producer, 10, 0)\n\n\tseedBroker.Close()\n\tleader.Close()\n\n\t// wait for the async-closed producer to shut down fully\n\tfor err := range producer.Errors() {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestAsyncProducerNoReturns(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tleader := NewMockBroker(t, 2)\n\n\tmetadataLeader := new(MetadataResponse)\n\tmetadataLeader.AddBroker(leader.Addr(), leader.BrokerID())\n\tmetadataLeader.AddTopicPartition(\"my_topic\", 0, leader.BrokerID(), nil, nil, nil, ErrNoError)\n\tseedBroker.Returns(metadataLeader)\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Flush.Messages = 10\n\tconfig.Producer.Return.Successes = false\n\tconfig.Producer.Return.Errors = false\n\tconfig.Producer.Retry.Backoff = 0\n\tproducer, err := NewAsyncProducer([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor i := 0; i < 10; i++ {\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(TestMessage)}\n\t}\n\n\twait := make(chan bool)\n\tgo func() {\n\t\tif err := producer.Close(); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t\tclose(wait)\n\t}()\n\n\tprodSuccess := new(ProduceResponse)\n\tprodSuccess.AddTopicPartition(\"my_topic\", 0, ErrNoError)\n\tleader.Returns(prodSuccess)\n\n\t<-wait\n\tseedBroker.Close()\n\tleader.Close()\n}\n\nfunc TestAsyncProducerIdempotentGoldenPath(t *testing.T) {\n\tbroker := NewMockBroker(t, 1)\n\n\tmetadataResponse := &MetadataResponse{\n\t\tVersion:      4,\n\t\tControllerID: 1,\n\t}\n\tmetadataResponse.AddBroker(broker.Addr(), broker.BrokerID())\n\tmetadataResponse.AddTopicPartition(\"my_topic\", 0, broker.BrokerID(), nil, nil, nil, ErrNoError)\n\tbroker.Returns(metadataResponse)\n\n\tinitProducerID := &InitProducerIDResponse{\n\t\tThrottleTime:  0,\n\t\tProducerID:    1000,\n\t\tProducerEpoch: 1,\n\t}\n\tbroker.Returns(initProducerID)\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Flush.Messages = 10\n\tconfig.Producer.Return.Successes = true\n\tconfig.Producer.Retry.Max = 4\n\tconfig.Producer.RequiredAcks = WaitForAll\n\tconfig.Producer.Retry.Backoff = 0\n\tconfig.Producer.Idempotent = true\n\tconfig.Net.MaxOpenRequests = 1\n\tconfig.Version = V0_11_0_0\n\tproducer, err := NewAsyncProducer([]string{broker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor i := 0; i < 10; i++ {\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(TestMessage)}\n\t}\n\n\tprodSuccess := &ProduceResponse{\n\t\tVersion:      3,\n\t\tThrottleTime: 0,\n\t}\n\tprodSuccess.AddTopicPartition(\"my_topic\", 0, ErrNoError)\n\tbroker.Returns(prodSuccess)\n\texpectResults(t, producer, 10, 0)\n\n\tbroker.Close()\n\tcloseProducer(t, producer)\n}\n\nfunc TestAsyncProducerIdempotentRetryCheckBatch(t *testing.T) {\n\t// Logger = log.New(os.Stderr, \"\", log.LstdFlags)\n\ttests := []struct {\n\t\tname           string\n\t\tfailAfterWrite bool\n\t}{\n\t\t{\"FailAfterWrite\", true},\n\t\t{\"FailBeforeWrite\", false},\n\t}\n\n\tfor _, test := range tests {\n\t\tbroker := NewMockBroker(t, 1)\n\n\t\tmetadataResponse := &MetadataResponse{\n\t\t\tVersion:      4,\n\t\t\tControllerID: 1,\n\t\t}\n\t\tmetadataResponse.AddBroker(broker.Addr(), broker.BrokerID())\n\t\tmetadataResponse.AddTopicPartition(\"my_topic\", 0, broker.BrokerID(), nil, nil, nil, ErrNoError)\n\n\t\tinitProducerIDResponse := &InitProducerIDResponse{\n\t\t\tThrottleTime:  0,\n\t\t\tProducerID:    1000,\n\t\t\tProducerEpoch: 1,\n\t\t}\n\n\t\tprodNotLeaderResponse := &ProduceResponse{\n\t\t\tVersion:      3,\n\t\t\tThrottleTime: 0,\n\t\t}\n\t\tprodNotLeaderResponse.AddTopicPartition(\"my_topic\", 0, ErrNotEnoughReplicas)\n\n\t\tprodDuplicate := &ProduceResponse{\n\t\t\tVersion:      3,\n\t\t\tThrottleTime: 0,\n\t\t}\n\t\tprodDuplicate.AddTopicPartition(\"my_topic\", 0, ErrDuplicateSequenceNumber)\n\n\t\tprodOutOfSeq := &ProduceResponse{\n\t\t\tVersion:      3,\n\t\t\tThrottleTime: 0,\n\t\t}\n\t\tprodOutOfSeq.AddTopicPartition(\"my_topic\", 0, ErrOutOfOrderSequenceNumber)\n\n\t\tprodSuccessResponse := &ProduceResponse{\n\t\t\tVersion:      3,\n\t\t\tThrottleTime: 0,\n\t\t}\n\t\tprodSuccessResponse.AddTopicPartition(\"my_topic\", 0, ErrNoError)\n\n\t\tprodCounter := 0\n\t\tlastBatchFirstSeq := -1\n\t\tlastBatchSize := -1\n\t\tlastSequenceWrittenToDisk := -1\n\t\thandlerFailBeforeWrite := func(req *request) (res encoderWithHeader) {\n\t\t\tswitch req.body.key() {\n\t\t\tcase 3:\n\t\t\t\treturn metadataResponse\n\t\t\tcase 22:\n\t\t\t\treturn initProducerIDResponse\n\t\t\tcase 0:\n\t\t\t\tprodCounter++\n\n\t\t\t\tpreq := req.body.(*ProduceRequest)\n\t\t\t\tbatch := preq.records[\"my_topic\"][0].RecordBatch\n\t\t\t\tbatchFirstSeq := int(batch.FirstSequence)\n\t\t\t\tbatchSize := len(batch.Records)\n\n\t\t\t\tif lastSequenceWrittenToDisk == batchFirstSeq-1 { // in sequence append\n\t\t\t\t\tif lastBatchFirstSeq == batchFirstSeq { // is a batch retry\n\t\t\t\t\t\tif lastBatchSize == batchSize { // good retry\n\t\t\t\t\t\t\t// mock write to disk\n\t\t\t\t\t\t\tlastSequenceWrittenToDisk = batchFirstSeq + batchSize - 1\n\t\t\t\t\t\t\treturn prodSuccessResponse\n\t\t\t\t\t\t}\n\t\t\t\t\t\tt.Errorf(\"[%s] Retried Batch firstSeq=%d with different size old=%d new=%d\", test.name, batchFirstSeq, lastBatchSize, batchSize)\n\t\t\t\t\t\treturn prodOutOfSeq\n\t\t\t\t\t} // not a retry\n\t\t\t\t\t// save batch just received for future check\n\t\t\t\t\tlastBatchFirstSeq = batchFirstSeq\n\t\t\t\t\tlastBatchSize = batchSize\n\n\t\t\t\t\tif prodCounter%2 == 1 {\n\t\t\t\t\t\tif test.failAfterWrite {\n\t\t\t\t\t\t\t// mock write to disk\n\t\t\t\t\t\t\tlastSequenceWrittenToDisk = batchFirstSeq + batchSize - 1\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn prodNotLeaderResponse\n\t\t\t\t\t}\n\t\t\t\t\t// mock write to disk\n\t\t\t\t\tlastSequenceWrittenToDisk = batchFirstSeq + batchSize - 1\n\t\t\t\t\treturn prodSuccessResponse\n\t\t\t\t}\n\t\t\t\tif lastBatchFirstSeq == batchFirstSeq && lastBatchSize == batchSize { // is a good batch retry\n\t\t\t\t\tif lastSequenceWrittenToDisk == (batchFirstSeq + batchSize - 1) { // we already have the messages\n\t\t\t\t\t\treturn prodDuplicate\n\t\t\t\t\t}\n\t\t\t\t\t// mock write to disk\n\t\t\t\t\tlastSequenceWrittenToDisk = batchFirstSeq + batchSize - 1\n\t\t\t\t\treturn prodSuccessResponse\n\t\t\t\t} // out of sequence / bad retried batch\n\t\t\t\tif lastBatchFirstSeq == batchFirstSeq && lastBatchSize != batchSize {\n\t\t\t\t\tt.Errorf(\"[%s] Retried Batch firstSeq=%d with different size old=%d new=%d\", test.name, batchFirstSeq, lastBatchSize, batchSize)\n\t\t\t\t} else if lastSequenceWrittenToDisk+1 != batchFirstSeq {\n\t\t\t\t\tt.Errorf(\"[%s] Out of sequence message lastSequence=%d new batch starts at=%d\", test.name, lastSequenceWrittenToDisk, batchFirstSeq)\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"[%s] Unexpected error\", test.name)\n\t\t\t\t}\n\n\t\t\t\treturn prodOutOfSeq\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\n\t\tconfig := NewTestConfig()\n\t\tconfig.Version = V0_11_0_0\n\t\tconfig.Producer.Idempotent = true\n\t\tconfig.Net.MaxOpenRequests = 1\n\t\tconfig.Producer.RequiredAcks = WaitForAll\n\t\tconfig.Producer.Return.Successes = true\n\t\tconfig.Producer.Flush.Frequency = 50 * time.Millisecond\n\t\tconfig.Producer.Retry.Backoff = 100 * time.Millisecond\n\n\t\tbroker.setHandler(handlerFailBeforeWrite)\n\t\tproducer, err := NewAsyncProducer([]string{broker.Addr()}, config)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tfor i := 0; i < 3; i++ {\n\t\t\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(TestMessage)}\n\t\t}\n\n\t\tgo func() {\n\t\t\tfor i := 0; i < 7; i++ {\n\t\t\t\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(\"goroutine\")}\n\t\t\t\ttime.Sleep(100 * time.Millisecond)\n\t\t\t}\n\t\t}()\n\n\t\texpectResults(t, producer, 10, 0)\n\n\t\tbroker.Close()\n\t\tcloseProducer(t, producer)\n\t}\n}\n\n// test case for https://github.com/IBM/sarama/pull/2378\nfunc TestAsyncProducerIdempotentRetryCheckBatch_2378(t *testing.T) {\n\tbroker := NewMockBroker(t, 1)\n\n\tmetadataResponse := &MetadataResponse{\n\t\tVersion:      4,\n\t\tControllerID: 1,\n\t}\n\tmetadataResponse.AddBroker(broker.Addr(), broker.BrokerID())\n\tmetadataResponse.AddTopicPartition(\"my_topic\", 0, broker.BrokerID(), nil, nil, nil, ErrNoError)\n\n\tinitProducerIDResponse := &InitProducerIDResponse{\n\t\tThrottleTime:  0,\n\t\tProducerID:    1000,\n\t\tProducerEpoch: 1,\n\t}\n\n\tprodNotLeaderResponse := &ProduceResponse{\n\t\tVersion:      3,\n\t\tThrottleTime: 0,\n\t}\n\tprodNotLeaderResponse.AddTopicPartition(\"my_topic\", 0, ErrNotEnoughReplicas)\n\n\thandlerFailBeforeWrite := func(req *request) (res encoderWithHeader) {\n\t\tswitch req.body.key() {\n\t\tcase 3:\n\t\t\treturn metadataResponse\n\t\tcase 22:\n\t\t\treturn initProducerIDResponse\n\t\tcase 0: // for msg, always return error to trigger retryBatch\n\t\t\treturn prodNotLeaderResponse\n\t\t}\n\t\treturn nil\n\t}\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V0_11_0_0\n\tconfig.Producer.Idempotent = true\n\tconfig.Net.MaxOpenRequests = 1\n\tconfig.Producer.Retry.Max = 1 // set max retry to 1\n\tconfig.Producer.RequiredAcks = WaitForAll\n\tconfig.Producer.Return.Successes = true\n\tconfig.Producer.Flush.Frequency = 50 * time.Millisecond\n\tconfig.Producer.Retry.Backoff = 100 * time.Millisecond\n\n\tbroker.setHandler(handlerFailBeforeWrite)\n\tproducer, err := NewAsyncProducer([]string{broker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor i := 0; i < 3; i++ {\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(TestMessage)}\n\t}\n\n\tgo func() {\n\t\tfor i := 0; i < 7; i++ {\n\t\t\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(\"goroutine\")}\n\t\t\ttime.Sleep(100 * time.Millisecond)\n\t\t}\n\t}()\n\n\t// this will block until 5 minutes timeout before pr 2378 merge\n\texpectResults(t, producer, 0, 10)\n\n\tbroker.Close()\n\tcloseProducer(t, producer)\n}\n\nfunc TestAsyncProducerIdempotentErrorOnOutOfSeq(t *testing.T) {\n\tbroker := NewMockBroker(t, 1)\n\n\tmetadataResponse := &MetadataResponse{\n\t\tVersion:      4,\n\t\tControllerID: 1,\n\t}\n\tmetadataResponse.AddBroker(broker.Addr(), broker.BrokerID())\n\tmetadataResponse.AddTopicPartition(\"my_topic\", 0, broker.BrokerID(), nil, nil, nil, ErrNoError)\n\tbroker.Returns(metadataResponse)\n\n\tinitProducerID := &InitProducerIDResponse{\n\t\tThrottleTime:  0,\n\t\tProducerID:    1000,\n\t\tProducerEpoch: 1,\n\t}\n\tbroker.Returns(initProducerID)\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Flush.Messages = 10\n\tconfig.Producer.Return.Successes = true\n\tconfig.Producer.Retry.Max = 400000\n\tconfig.Producer.RequiredAcks = WaitForAll\n\tconfig.Producer.Retry.Backoff = 0\n\tconfig.Producer.Idempotent = true\n\tconfig.Net.MaxOpenRequests = 1\n\tconfig.Version = V0_11_0_0\n\n\tproducer, err := NewAsyncProducer([]string{broker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor i := 0; i < 10; i++ {\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(TestMessage)}\n\t}\n\n\tprodOutOfSeq := &ProduceResponse{\n\t\tVersion:      3,\n\t\tThrottleTime: 0,\n\t}\n\tprodOutOfSeq.AddTopicPartition(\"my_topic\", 0, ErrOutOfOrderSequenceNumber)\n\tbroker.Returns(prodOutOfSeq)\n\texpectResults(t, producer, 0, 10)\n\n\tbroker.Close()\n\tcloseProducer(t, producer)\n}\n\nfunc TestAsyncProducerIdempotentEpochRollover(t *testing.T) {\n\tbroker := NewMockBroker(t, 1)\n\tdefer broker.Close()\n\n\tmetadataResponse := &MetadataResponse{\n\t\tVersion:      4,\n\t\tControllerID: 1,\n\t}\n\tmetadataResponse.AddBroker(broker.Addr(), broker.BrokerID())\n\tmetadataResponse.AddTopicPartition(\"my_topic\", 0, broker.BrokerID(), nil, nil, nil, ErrNoError)\n\tbroker.Returns(metadataResponse)\n\n\tinitProducerID := &InitProducerIDResponse{\n\t\tThrottleTime:  0,\n\t\tProducerID:    1000,\n\t\tProducerEpoch: 1,\n\t}\n\tbroker.Returns(initProducerID)\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Flush.Messages = 10\n\tconfig.Producer.Flush.Frequency = 10 * time.Millisecond\n\tconfig.Producer.Return.Successes = true\n\tconfig.Producer.Retry.Max = 1 // This test needs to exercise what happens when retries exhaust\n\tconfig.Producer.RequiredAcks = WaitForAll\n\tconfig.Producer.Retry.Backoff = 0\n\tconfig.Producer.Idempotent = true\n\tconfig.Net.MaxOpenRequests = 1\n\tconfig.Version = V0_11_0_0\n\n\tproducer, err := NewAsyncProducer([]string{broker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer closeProducer(t, producer)\n\n\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Value: StringEncoder(\"hello\")}\n\tprodError := &ProduceResponse{\n\t\tVersion:      3,\n\t\tThrottleTime: 0,\n\t}\n\tprodError.AddTopicPartition(\"my_topic\", 0, ErrBrokerNotAvailable)\n\tbroker.Returns(prodError)\n\t<-producer.Errors()\n\n\tlastReqRes := broker.history[len(broker.history)-1]\n\tlastProduceBatch := lastReqRes.Request.(*ProduceRequest).records[\"my_topic\"][0].RecordBatch\n\tif lastProduceBatch.FirstSequence != 0 {\n\t\tt.Error(\"first sequence not zero\")\n\t}\n\tif lastProduceBatch.ProducerEpoch != 1 {\n\t\tt.Error(\"first epoch was not one\")\n\t}\n\n\t// Now if we produce again, the epoch should have rolled over.\n\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Value: StringEncoder(\"hello\")}\n\tbroker.Returns(prodError)\n\t<-producer.Errors()\n\n\tlastReqRes = broker.history[len(broker.history)-1]\n\tlastProduceBatch = lastReqRes.Request.(*ProduceRequest).records[\"my_topic\"][0].RecordBatch\n\tif lastProduceBatch.FirstSequence != 0 {\n\t\tt.Error(\"second sequence not zero\")\n\t}\n\tif lastProduceBatch.ProducerEpoch <= 1 {\n\t\tt.Error(\"second epoch was not > 1\")\n\t}\n}\n\n// TestAsyncProducerIdempotentEpochExhaustion ensures that producer requests\n// a new producerID when producerEpoch is exhausted\nfunc TestAsyncProducerIdempotentEpochExhaustion(t *testing.T) {\n\tbroker := NewMockBroker(t, 1)\n\tdefer broker.Close()\n\n\tvar (\n\t\tinitialProducerID = int64(1000)\n\t\tnewProducerID     = initialProducerID + 1\n\t)\n\n\tmetadataResponse := &MetadataResponse{\n\t\tVersion:      4,\n\t\tControllerID: 1,\n\t}\n\tmetadataResponse.AddBroker(broker.Addr(), broker.BrokerID())\n\tmetadataResponse.AddTopicPartition(\"my_topic\", 0, broker.BrokerID(), nil, nil, nil, ErrNoError)\n\tbroker.Returns(metadataResponse)\n\n\tinitProducerID := &InitProducerIDResponse{\n\t\tThrottleTime:  0,\n\t\tProducerID:    initialProducerID,\n\t\tProducerEpoch: math.MaxInt16, // Mock ProducerEpoch at the exhaustion point\n\t}\n\tbroker.Returns(initProducerID)\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Flush.Messages = 10\n\tconfig.Producer.Flush.Frequency = 10 * time.Millisecond\n\tconfig.Producer.Return.Successes = true\n\tconfig.Producer.Retry.Max = 1 // This test needs to exercise what happens when retries exhaust\n\tconfig.Producer.RequiredAcks = WaitForAll\n\tconfig.Producer.Retry.Backoff = 0\n\tconfig.Producer.Idempotent = true\n\tconfig.Net.MaxOpenRequests = 1\n\tconfig.Version = V0_11_0_0\n\n\tproducer, err := NewAsyncProducer([]string{broker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer closeProducer(t, producer)\n\n\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Value: StringEncoder(\"hello\")}\n\tprodError := &ProduceResponse{\n\t\tVersion:      3,\n\t\tThrottleTime: 0,\n\t}\n\tprodError.AddTopicPartition(\"my_topic\", 0, ErrBrokerNotAvailable)\n\tbroker.Returns(prodError)\n\tbroker.Returns(&InitProducerIDResponse{\n\t\tProducerID: newProducerID,\n\t})\n\n\t<-producer.Errors()\n\n\tlastProduceReqRes := broker.history[len(broker.history)-2] // last is InitProducerIDRequest\n\tlastProduceBatch := lastProduceReqRes.Request.(*ProduceRequest).records[\"my_topic\"][0].RecordBatch\n\tif lastProduceBatch.FirstSequence != 0 {\n\t\tt.Error(\"first sequence not zero\")\n\t}\n\tif lastProduceBatch.ProducerEpoch <= 1 {\n\t\tt.Error(\"first epoch was not at exhaustion point\")\n\t}\n\n\t// Now we should produce with a new ProducerID\n\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Value: StringEncoder(\"hello\")}\n\tbroker.Returns(prodError)\n\t<-producer.Errors()\n\n\tlastProduceReqRes = broker.history[len(broker.history)-1]\n\tlastProduceBatch = lastProduceReqRes.Request.(*ProduceRequest).records[\"my_topic\"][0].RecordBatch\n\tif lastProduceBatch.ProducerID != newProducerID || lastProduceBatch.ProducerEpoch != 0 {\n\t\tt.Error(\"producer did not requested a new producerID\")\n\t}\n}\n\n// TestBrokerProducerShutdown ensures that a call to shutdown stops the\n// brokerProducer run() loop and doesn't leak any goroutines\n//\n//nolint:paralleltest\nfunc TestBrokerProducerShutdown(t *testing.T) {\n\tdefer leaktest.Check(t)()\n\tmetrics.UseNilMetrics = true // disable Sarama's go-metrics library\n\tdefer func() {\n\t\tmetrics.UseNilMetrics = false\n\t}()\n\n\tmockBroker := NewMockBroker(t, 1)\n\tmetadataResponse := &MetadataResponse{}\n\tmetadataResponse.AddBroker(mockBroker.Addr(), mockBroker.BrokerID())\n\tmetadataResponse.AddTopicPartition(\n\t\t\"my_topic\", 0, mockBroker.BrokerID(), nil, nil, nil, ErrNoError)\n\tmockBroker.Returns(metadataResponse)\n\n\tproducer, err := NewAsyncProducer([]string{mockBroker.Addr()}, NewTestConfig())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tbroker := &Broker{\n\t\taddr: mockBroker.Addr(),\n\t\tid:   mockBroker.BrokerID(),\n\t}\n\t// Starts various goroutines in newBrokerProducer\n\tbp := producer.(*asyncProducer).getBrokerProducer(broker)\n\t// Initiate the shutdown of all of them\n\tproducer.(*asyncProducer).unrefBrokerProducer(broker, bp)\n\n\t_ = producer.Close()\n\tmockBroker.Close()\n}\n\n// TestBrokerProducerWaitForSpaceEmptyBufferRollover ensures forced rollovers with an empty buffer\n// do not deadlock waiting for responses when no partitions are muted.\nfunc TestBrokerProducerWaitForSpaceEmptyBufferRollover(t *testing.T) {\n\tconfig := NewTestConfig()\n\tparent := &asyncProducer{\n\t\tconf:   config,\n\t\tmuter:  newPartitionMuter(),\n\t\ttxnmgr: &transactionManager{},\n\t}\n\n\tbp := &brokerProducer{\n\t\tparent:            parent,\n\t\taccumulatingBatch: newProduceSet(parent),\n\t\toutput:            make(chan *produceSet, 1),\n\t\tresponses:         make(chan *brokerProducerResponse),\n\t}\n\n\tdone := make(chan error, 1)\n\tgo func() {\n\t\tdone <- bp.waitForSpace(&ProducerMessage{Topic: \"topic\", Partition: 0}, true)\n\t}()\n\n\tselect {\n\tcase err := <-done:\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"expected nil error, got %v\", err)\n\t\t}\n\tcase <-time.After(2 * time.Second):\n\t\tt.Fatal(\"waitForSpace blocked on empty buffer rollover\")\n\t}\n}\n\nfunc awaitMuterBlocked(t *testing.T, m *partitionMuter, set *produceSet) {\n\tt.Helper()\n\tdeadline := time.Now().Add(500 * time.Millisecond)\n\tfor {\n\t\tif _, blocked := m.awaitUnmuteChan(set); blocked {\n\t\t\treturn\n\t\t}\n\t\tif time.Now().After(deadline) {\n\t\t\tt.Fatal(\"timeout waiting for muter to block on set\")\n\t\t}\n\t\ttime.Sleep(5 * time.Millisecond)\n\t}\n}\n\nfunc assertNotDone[T any](t *testing.T, ch <-chan T, wait time.Duration) {\n\tt.Helper()\n\ttime.Sleep(wait)\n\tselect {\n\tcase <-ch:\n\t\tt.Fatal(\"channel should not be ready\")\n\tdefault:\n\t}\n}\n\nfunc assertDoneWithin[T any](t *testing.T, ch <-chan T, timeout time.Duration) T {\n\tt.Helper()\n\tselect {\n\tcase v := <-ch:\n\t\treturn v\n\tcase <-time.After(timeout):\n\t\tt.Fatal(\"timed out waiting for channel\")\n\t\tvar zero T\n\t\treturn zero\n\t}\n}\n\n// TestBrokerProducerWaitForSpaceRespectsExternalUnmute ensures waitForSpace does not\n// deadlock when partitions are muted by another producer and are unmuted elsewhere.\nfunc TestBrokerProducerWaitForSpaceRespectsExternalUnmute(t *testing.T) {\n\tconfig := NewTestConfig()\n\ttxnMgr := &transactionManager{\n\t\tproducerID:      0,\n\t\tproducerEpoch:   0,\n\t\tsequenceNumbers: make(map[string]int32),\n\t}\n\tparent := &asyncProducer{\n\t\tconf:   config,\n\t\tmuter:  newPartitionMuter(),\n\t\ttxnmgr: txnMgr,\n\t}\n\n\texternallyMutedSet := newProduceSet(parent)\n\tsafeAddMessage(t, externallyMutedSet, &ProducerMessage{Topic: \"topic\", Partition: 0, Value: StringEncoder(\"held\")})\n\tif !parent.muter.tryMute(externallyMutedSet) {\n\t\tt.Fatal(\"expected to mute partition\")\n\t}\n\n\toutput := make(chan *produceSet, 1)\n\tbp := &brokerProducer{\n\t\tparent:            parent,\n\t\taccumulatingBatch: newProduceSet(parent),\n\t\toutput:            output,\n\t\tresponses:         make(chan *brokerProducerResponse),\n\t}\n\tmsg := &ProducerMessage{Topic: \"topic\", Partition: 0, Value: StringEncoder(\"wait\")}\n\tsafeAddMessage(t, bp.accumulatingBatch, msg)\n\n\tdone := make(chan error, 1)\n\tgo func() {\n\t\tdone <- bp.waitForSpace(msg, true)\n\t}()\n\n\tawaitMuterBlocked(t, parent.muter, bp.accumulatingBatch)\n\tparent.muter.unmute(externallyMutedSet)\n\n\tselect {\n\tcase err := <-done:\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"expected nil error, got %v\", err)\n\t\t}\n\tcase <-time.After(2 * time.Second):\n\t\tt.Fatal(\"waitForSpace blocked waiting for external unmute\")\n\t}\n}\n\nfunc TestBrokerProducerFlushSkipsMutedPartitions(t *testing.T) {\n\tconfig := NewTestConfig()\n\tparent := &asyncProducer{\n\t\tconf:   config,\n\t\tmuter:  newPartitionMuter(),\n\t\ttxnmgr: &transactionManager{},\n\t}\n\tbp := &brokerProducer{\n\t\tparent:            parent,\n\t\taccumulatingBatch: newProduceSet(parent),\n\t\tcurrentRetries:    make(map[string]map[int32]error),\n\t}\n\n\tsafeAddMessage(t, bp.accumulatingBatch, &ProducerMessage{Topic: \"topic\", Partition: 0, Value: StringEncoder(\"p0\")})\n\tsafeAddMessage(t, bp.accumulatingBatch, &ProducerMessage{Topic: \"topic\", Partition: 1, Value: StringEncoder(\"p1\")})\n\n\tblocked := newProduceSet(parent)\n\tsafeAddMessage(t, blocked, &ProducerMessage{Topic: \"topic\", Partition: 1, Value: StringEncoder(\"held\")})\n\tif !parent.muter.tryMute(blocked) {\n\t\tt.Fatal(\"expected to mute blocked partition\")\n\t}\n\tdefer parent.muter.unmute(blocked)\n\n\tif !bp.tryBuildFlushingBatch() {\n\t\tt.Fatal(\"expected to flush available partitions\")\n\t}\n\tif bp.flushingBatch == nil {\n\t\tt.Fatal(\"expected flushing batch to be set\")\n\t}\n\tif _, ok := bp.flushingBatch.msgs[\"topic\"][1]; ok {\n\t\tt.Fatal(\"expected muted partition to stay buffered\")\n\t}\n\tif _, ok := bp.accumulatingBatch.msgs[\"topic\"][0]; ok {\n\t\tt.Fatal(\"expected unmuted partition to flush\")\n\t}\n\tif _, ok := bp.accumulatingBatch.msgs[\"topic\"][1]; !ok {\n\t\tt.Fatal(\"expected muted partition to remain in accumulating batch\")\n\t}\n}\n\n// TestBrokerProducerWaitForSpaceAllPartitionsMuted verifies that waitForSpace unblocks\n// when all partitions in the accumulating batch are externally muted and later unmuted.\nfunc TestBrokerProducerWaitForSpaceAllPartitionsMuted(t *testing.T) {\n\tconfig := NewTestConfig()\n\tparent := &asyncProducer{\n\t\tconf:   config,\n\t\tmuter:  newPartitionMuter(),\n\t\ttxnmgr: &transactionManager{},\n\t}\n\n\tblockedSet := newProduceSet(parent)\n\tsafeAddMessage(t, blockedSet, &ProducerMessage{Topic: \"topic\", Partition: 0, Value: StringEncoder(\"held\")})\n\tif !parent.muter.tryMute(blockedSet) {\n\t\tt.Fatal(\"expected to mute partition externally\")\n\t}\n\n\tbp := &brokerProducer{\n\t\tparent:            parent,\n\t\taccumulatingBatch: newProduceSet(parent),\n\t\toutput:            make(chan *produceSet, 1),\n\t\tresponses:         make(chan *brokerProducerResponse),\n\t\tcurrentRetries:    make(map[string]map[int32]error),\n\t}\n\tsafeAddMessage(t, bp.accumulatingBatch, &ProducerMessage{Topic: \"topic\", Partition: 0, Value: StringEncoder(\"waiting\")})\n\n\tdone := make(chan error, 1)\n\tgo func() {\n\t\tdone <- bp.waitForSpace(&ProducerMessage{Topic: \"topic\", Partition: 0}, true)\n\t}()\n\n\tassertNotDone(t, done, 50*time.Millisecond)\n\tparent.muter.unmute(blockedSet)\n\tassertDoneWithin(t, done, 2*time.Second)\n}\n\n// TestPartitionMuterCloseWakesWaitUntilMuted verifies that closing the muter wakes\n// goroutines blocked in waitUntilMuted.\nfunc TestPartitionMuterCloseWakesWaitUntilMuted(t *testing.T) {\n\tconfig := NewTestConfig()\n\tparent := &asyncProducer{\n\t\tconf:   config,\n\t\tmuter:  newPartitionMuter(),\n\t\ttxnmgr: &transactionManager{},\n\t}\n\n\tblockedSet := newProduceSet(parent)\n\tsafeAddMessage(t, blockedSet, &ProducerMessage{Topic: \"topic\", Partition: 0, Value: StringEncoder(\"held\")})\n\tif !parent.muter.tryMute(blockedSet) {\n\t\tt.Fatal(\"expected to mute partition\")\n\t}\n\n\twaitSet := newProduceSet(parent)\n\tsafeAddMessage(t, waitSet, &ProducerMessage{Topic: \"topic\", Partition: 0, Value: StringEncoder(\"waiting\")})\n\n\tdone := make(chan bool, 1)\n\tgo func() {\n\t\tdone <- parent.muter.waitUntilMuted(waitSet)\n\t}()\n\n\tassertNotDone(t, done, 50*time.Millisecond)\n\tparent.muter.close()\n\n\tselect {\n\tcase result := <-done:\n\t\tif result {\n\t\t\tt.Fatal(\"expected waitUntilMuted to return false after close\")\n\t\t}\n\tcase <-time.After(2 * time.Second):\n\t\tt.Fatal(\"timed out\")\n\t}\n}\n\n// TestBrokerProducerRollOverClearsTimer ensures timer events from a previous batch\n// do not cause a flush of a fresh empty batch after rollOver.\nfunc TestBrokerProducerRollOverClearsTimer(t *testing.T) {\n\tdefer leaktest.Check(t)()\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Flush.Frequency = 10 * time.Millisecond\n\tparent := &asyncProducer{\n\t\tconf:   config,\n\t\tmuter:  newPartitionMuter(),\n\t\ttxnmgr: &transactionManager{},\n\t}\n\toutput := make(chan *produceSet, 2)\n\tresponses := make(chan *brokerProducerResponse)\n\tinput := make(chan *ProducerMessage)\n\tbp := &brokerProducer{\n\t\tparent:            parent,\n\t\tbroker:            &Broker{id: 1},\n\t\tinput:             input,\n\t\toutput:            output,\n\t\tresponses:         responses,\n\t\taccumulatingBatch: newProduceSet(parent),\n\t\tcurrentRetries:    make(map[string]map[int32]error),\n\t}\n\n\tdone := make(chan struct{})\n\tgo func() {\n\t\tbp.run()\n\t\tclose(done)\n\t}()\n\n\tmsg := &ProducerMessage{Topic: \"topic\", Partition: 0, Value: StringEncoder(\"v\")}\n\tinput <- msg\n\n\tselect {\n\tcase first := <-output:\n\t\tif first == nil || first.empty() {\n\t\t\tt.Fatal(\"expected flushed batch to contain message\")\n\t\t}\n\tcase <-time.After(500 * time.Millisecond):\n\t\tt.Fatal(\"expected batch flush after timer fired\")\n\t}\n\n\tselect {\n\tcase <-output:\n\t\tt.Fatal(\"unexpected flush after rollOver\")\n\tcase <-time.After(50 * time.Millisecond):\n\t}\n\n\tclose(responses)\n\tclose(input)\n\n\tselect {\n\tcase <-done:\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"brokerProducer did not shut down\")\n\t}\n}\n\nfunc TestRetryBatchRespectsPartitionMuter(t *testing.T) {\n\tconfig := NewTestConfig()\n\tconfig.Producer.Idempotent = true\n\ttxnMgr := &transactionManager{\n\t\tproducerID:      0,\n\t\tproducerEpoch:   0,\n\t\tsequenceNumbers: make(map[string]int32),\n\t}\n\n\tparent := &asyncProducer{\n\t\tconf:       config,\n\t\tmuter:      newPartitionMuter(),\n\t\tbrokers:    make(map[*Broker]*brokerProducer),\n\t\tbrokerRefs: make(map[*brokerProducer]int),\n\t\ttxnmgr:     txnMgr,\n\t}\n\tleader := &Broker{}\n\tparent.client = &stubLeaderClient{leader: leader, cfg: config}\n\n\toutput := make(chan *produceSet, 1)\n\tbp := &brokerProducer{\n\t\tparent: parent,\n\t\tbroker: leader,\n\t\toutput: output,\n\t\tinput:  make(chan *ProducerMessage),\n\t}\n\tparent.brokers[leader] = bp\n\n\tretrySet := newProduceSet(parent)\n\tsafeAddMessage(t, retrySet, &ProducerMessage{Topic: \"topic\", Partition: 0, Value: StringEncoder(\"retry\")})\n\tretryPartitionSet := retrySet.msgs[\"topic\"][0]\n\tif !parent.muter.tryMute(retrySet) {\n\t\tt.Fatal(\"expected retry set to mute partitions\")\n\t}\n\tparent.muter.unmute(retrySet)\n\n\tparent.retryBatch(\"topic\", 0, retryPartitionSet, ErrNotEnoughReplicas, false)\n\n\tselect {\n\tcase sent := <-output:\n\t\tset := sent.msgs[\"topic\"][0]\n\t\trequire.Equal(t, retryPartitionSet, set)\n\tdefault:\n\t\tt.Fatal(\"expected retry batch to be dispatched\")\n\t}\n\n\tcontender := newProduceSet(parent)\n\tsafeAddMessage(t, contender, &ProducerMessage{Topic: \"topic\", Partition: 0, Value: StringEncoder(\"next\")})\n\tif parent.muter.tryMute(contender) {\n\t\tt.Fatal(\"expected partition to remain muted by retry batch\")\n\t}\n}\n\ntype stubLeaderClient struct {\n\tcfg    *Config\n\tleader *Broker\n}\n\nfunc (c *stubLeaderClient) Config() *Config                            { return c.cfg }\nfunc (c *stubLeaderClient) Controller() (*Broker, error)               { return nil, nil }\nfunc (c *stubLeaderClient) RefreshController() (*Broker, error)        { return nil, nil }\nfunc (c *stubLeaderClient) Brokers() []*Broker                         { return nil }\nfunc (c *stubLeaderClient) Broker(int32) (*Broker, error)              { return nil, nil }\nfunc (c *stubLeaderClient) Topics() ([]string, error)                  { return nil, nil }\nfunc (c *stubLeaderClient) Partitions(string) ([]int32, error)         { return nil, nil }\nfunc (c *stubLeaderClient) WritablePartitions(string) ([]int32, error) { return nil, nil }\nfunc (c *stubLeaderClient) Leader(topic string, partitionID int32) (*Broker, error) {\n\treturn c.leader, nil\n}\nfunc (c *stubLeaderClient) LeaderAndEpoch(string, int32) (*Broker, int32, error) {\n\treturn c.leader, 0, nil\n}\nfunc (c *stubLeaderClient) Replicas(string, int32) ([]int32, error)          { return nil, nil }\nfunc (c *stubLeaderClient) InSyncReplicas(string, int32) ([]int32, error)    { return nil, nil }\nfunc (c *stubLeaderClient) OfflineReplicas(string, int32) ([]int32, error)   { return nil, nil }\nfunc (c *stubLeaderClient) RefreshBrokers([]string) error                    { return nil }\nfunc (c *stubLeaderClient) RefreshMetadata(...string) error                  { return nil }\nfunc (c *stubLeaderClient) GetOffset(string, int32, int64) (int64, error)    { return 0, nil }\nfunc (c *stubLeaderClient) Coordinator(string) (*Broker, error)              { return nil, nil }\nfunc (c *stubLeaderClient) RefreshCoordinator(string) error                  { return nil }\nfunc (c *stubLeaderClient) TransactionCoordinator(string) (*Broker, error)   { return nil, nil }\nfunc (c *stubLeaderClient) RefreshTransactionCoordinator(string) error       { return nil }\nfunc (c *stubLeaderClient) InitProducerID() (*InitProducerIDResponse, error) { return nil, nil }\nfunc (c *stubLeaderClient) LeastLoadedBroker() *Broker                       { return c.leader }\nfunc (c *stubLeaderClient) PartitionNotReadable(string, int32) bool          { return false }\nfunc (c *stubLeaderClient) Close() error                                     { return nil }\nfunc (c *stubLeaderClient) Closed() bool                                     { return false }\n\nfunc testProducerInterceptor(\n\tt *testing.T,\n\tinterceptors []ProducerInterceptor,\n\texpectationFn func(*testing.T, int, *ProducerMessage),\n) {\n\tseedBroker := NewMockBroker(t, 1)\n\tleader := NewMockBroker(t, 2)\n\tmetadataLeader := new(MetadataResponse)\n\tmetadataLeader.AddBroker(leader.Addr(), leader.BrokerID())\n\tmetadataLeader.AddTopicPartition(\"my_topic\", 0, leader.BrokerID(), nil, nil, nil, ErrNoError)\n\tseedBroker.Returns(metadataLeader)\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Flush.Messages = 10\n\tconfig.Producer.Return.Successes = true\n\tconfig.Producer.Interceptors = interceptors\n\tproducer, err := NewAsyncProducer([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor i := 0; i < 10; i++ {\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(TestMessage)}\n\t}\n\n\tprodSuccess := new(ProduceResponse)\n\tprodSuccess.AddTopicPartition(\"my_topic\", 0, ErrNoError)\n\tleader.Returns(prodSuccess)\n\n\tfor i := 0; i < 10; i++ {\n\t\tselect {\n\t\tcase msg := <-producer.Errors():\n\t\t\tt.Error(msg.Err)\n\t\tcase msg := <-producer.Successes():\n\t\t\texpectationFn(t, i, msg)\n\t\t}\n\t}\n\n\tcloseProducer(t, producer)\n\tleader.Close()\n\tseedBroker.Close()\n}\n\nfunc TestAsyncProducerInterceptors(t *testing.T) {\n\ttests := []struct {\n\t\tname          string\n\t\tinterceptors  []ProducerInterceptor\n\t\texpectationFn func(*testing.T, int, *ProducerMessage)\n\t}{\n\t\t{\n\t\t\tname:         \"intercept messages\",\n\t\t\tinterceptors: []ProducerInterceptor{&appendInterceptor{i: 0}},\n\t\t\texpectationFn: func(t *testing.T, i int, msg *ProducerMessage) {\n\t\t\t\tv, _ := msg.Value.Encode()\n\t\t\t\texpected := TestMessage + strconv.Itoa(i)\n\t\t\t\tif string(v) != expected {\n\t\t\t\t\tt.Errorf(\"Interceptor should have incremented the value, got %s, expected %s\", v, expected)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:         \"interceptor chain\",\n\t\t\tinterceptors: []ProducerInterceptor{&appendInterceptor{i: 0}, &appendInterceptor{i: 1000}},\n\t\t\texpectationFn: func(t *testing.T, i int, msg *ProducerMessage) {\n\t\t\t\tv, _ := msg.Value.Encode()\n\t\t\t\texpected := TestMessage + strconv.Itoa(i) + strconv.Itoa(i+1000)\n\t\t\t\tif string(v) != expected {\n\t\t\t\t\tt.Errorf(\"Interceptor should have incremented the value, got %s, expected %s\", v, expected)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:         \"interceptor chain with one interceptor failing\",\n\t\t\tinterceptors: []ProducerInterceptor{&appendInterceptor{i: -1}, &appendInterceptor{i: 1000}},\n\t\t\texpectationFn: func(t *testing.T, i int, msg *ProducerMessage) {\n\t\t\t\tv, _ := msg.Value.Encode()\n\t\t\t\texpected := TestMessage + strconv.Itoa(i+1000)\n\t\t\t\tif string(v) != expected {\n\t\t\t\t\tt.Errorf(\"Interceptor should have incremented the value, got %s, expected %s\", v, expected)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:         \"interceptor chain with all interceptors failing\",\n\t\t\tinterceptors: []ProducerInterceptor{&appendInterceptor{i: -1}, &appendInterceptor{i: -1}},\n\t\t\texpectationFn: func(t *testing.T, i int, msg *ProducerMessage) {\n\t\t\t\tv, _ := msg.Value.Encode()\n\t\t\t\texpected := TestMessage\n\t\t\t\tif string(v) != expected {\n\t\t\t\t\tt.Errorf(\"Interceptor should have not changed the value, got %s, expected %s\", v, expected)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttestProducerInterceptor(t, tt.interceptors, tt.expectationFn)\n\t\t})\n\t}\n}\n\nfunc TestProducerError(t *testing.T) {\n\tt.Parallel()\n\terr := ProducerError{Err: ErrOutOfBrokers}\n\tif !errors.Is(err, ErrOutOfBrokers) {\n\t\tt.Error(\"unexpected errors.Is\")\n\t}\n}\n\nfunc TestTxmngInitProducerId(t *testing.T) {\n\tbroker := NewMockBroker(t, 1)\n\tdefer broker.Close()\n\n\tmetadataLeader := new(MetadataResponse)\n\tmetadataLeader.Version = 4\n\tmetadataLeader.AddBroker(broker.Addr(), broker.BrokerID())\n\tbroker.Returns(metadataLeader)\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Idempotent = true\n\tconfig.Version = V0_11_0_0\n\tconfig.Producer.RequiredAcks = WaitForAll\n\tconfig.Net.MaxOpenRequests = 1\n\n\tclient, err := NewClient([]string{broker.Addr()}, config)\n\trequire.NoError(t, err)\n\tdefer client.Close()\n\n\tproducerIdResponse := &InitProducerIDResponse{\n\t\tErr:           ErrNoError,\n\t\tProducerID:    1,\n\t\tProducerEpoch: 0,\n\t}\n\tbroker.Returns(producerIdResponse)\n\n\ttxmng, err := newTransactionManager(config, client)\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, int64(1), txmng.producerID)\n\trequire.Equal(t, int16(0), txmng.producerEpoch)\n}\n\nfunc TestTxnProduceBumpEpoch(t *testing.T) {\n\tbroker := NewMockBroker(t, 1)\n\tdefer broker.Close()\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Idempotent = true\n\tconfig.Producer.Transaction.ID = \"test\"\n\tconfig.Version = V2_6_0_0\n\tconfig.Producer.RequiredAcks = WaitForAll\n\tconfig.Net.MaxOpenRequests = 1\n\tconfig.Producer.Return.Errors = false\n\n\tconfig.ApiVersionsRequest = false\n\n\tmetadataLeader := new(MetadataResponse)\n\tmetadataLeader.Version = 9\n\tmetadataLeader.ControllerID = broker.brokerID\n\tmetadataLeader.AddBroker(broker.Addr(), broker.BrokerID())\n\tmetadataLeader.AddTopic(\"test-topic\", ErrNoError)\n\tmetadataLeader.AddTopicPartition(\"test-topic\", 0, broker.BrokerID(), nil, nil, nil, ErrNoError)\n\tbroker.Returns(metadataLeader)\n\n\tclient, err := NewClient([]string{broker.Addr()}, config)\n\trequire.NoError(t, err)\n\tdefer client.Close()\n\n\tfindCoordinatorResponse := FindCoordinatorResponse{\n\t\tCoordinator: client.Brokers()[0],\n\t\tErr:         ErrNoError,\n\t\tVersion:     1,\n\t}\n\tbroker.Returns(&findCoordinatorResponse)\n\n\tproducerIdResponse := &InitProducerIDResponse{\n\t\tErr:           ErrNoError,\n\t\tProducerID:    1000,\n\t\tProducerEpoch: 0,\n\t\tVersion:       3,\n\t}\n\tbroker.Returns(producerIdResponse)\n\n\tap, err := NewAsyncProducerFromClient(client)\n\tproducer := ap.(*asyncProducer)\n\trequire.NoError(t, err)\n\tdefer ap.Close()\n\trequire.Equal(t, int64(1000), producer.txnmgr.producerID)\n\trequire.Equal(t, int16(0), producer.txnmgr.producerEpoch)\n\n\taddPartitionsToTxnResponse := &AddPartitionsToTxnResponse{\n\t\tErrors: map[string][]*PartitionError{\n\t\t\t\"test-topic\": {\n\t\t\t\t{\n\t\t\t\t\tPartition: 0,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tbroker.Returns(addPartitionsToTxnResponse)\n\n\tproduceResponse := new(ProduceResponse)\n\tproduceResponse.Version = 7\n\tproduceResponse.AddTopicPartition(\"test-topic\", 0, ErrOutOfOrderSequenceNumber)\n\tbroker.Returns(produceResponse)\n\n\terr = producer.BeginTxn()\n\trequire.NoError(t, err)\n\n\tproducer.Input() <- &ProducerMessage{Topic: \"test-topic\", Key: nil, Value: StringEncoder(TestMessage)}\n\n\t// Force send\n\tproducer.inFlight.Add(1)\n\tproducer.Input() <- &ProducerMessage{flags: shutdown}\n\tproducer.inFlight.Wait()\n\n\terr = producer.CommitTxn()\n\trequire.Error(t, err)\n\trequire.Equal(t, ProducerTxnFlagInError|ProducerTxnFlagAbortableError, producer.txnmgr.status)\n\n\terr = producer.CommitTxn()\n\trequire.Error(t, err)\n\trequire.Equal(t, ProducerTxnFlagInError|ProducerTxnFlagAbortableError, producer.txnmgr.status)\n\n\tendTxnResponse := &EndTxnResponse{\n\t\tErr: ErrNoError,\n\t}\n\tbroker.Returns(endTxnResponse)\n\n\tproducerBumpIdResponse := &InitProducerIDResponse{\n\t\tErr:           ErrNoError,\n\t\tProducerID:    1000,\n\t\tProducerEpoch: 1,\n\t\tVersion:       3,\n\t}\n\tbroker.Returns(producerBumpIdResponse)\n\n\terr = producer.AbortTxn()\n\trequire.NoError(t, err)\n\trequire.Equal(t, ProducerTxnFlagReady, producer.txnmgr.status)\n\trequire.Equal(t, int64(1000), producer.txnmgr.producerID)\n\trequire.Equal(t, int16(1), producer.txnmgr.producerEpoch)\n}\n\nfunc TestTxnProduceRecordWithCommit(t *testing.T) {\n\tbroker := NewMockBroker(t, 1)\n\tdefer broker.Close()\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Idempotent = true\n\tconfig.Producer.Transaction.ID = \"test\"\n\tconfig.Version = V0_11_0_0\n\tconfig.Producer.RequiredAcks = WaitForAll\n\tconfig.Net.MaxOpenRequests = 1\n\n\tmetadataLeader := new(MetadataResponse)\n\tmetadataLeader.Version = 4\n\tmetadataLeader.ControllerID = broker.brokerID\n\tmetadataLeader.AddBroker(broker.Addr(), broker.BrokerID())\n\tmetadataLeader.AddTopic(\"test-topic\", ErrNoError)\n\tmetadataLeader.AddTopicPartition(\"test-topic\", 0, broker.BrokerID(), nil, nil, nil, ErrNoError)\n\tbroker.Returns(metadataLeader)\n\n\tclient, err := NewClient([]string{broker.Addr()}, config)\n\trequire.NoError(t, err)\n\tdefer client.Close()\n\n\tfindCoordinatorResponse := FindCoordinatorResponse{\n\t\tCoordinator: client.Brokers()[0],\n\t\tErr:         ErrNoError,\n\t\tVersion:     1,\n\t}\n\tbroker.Returns(&findCoordinatorResponse)\n\n\tproducerIdResponse := &InitProducerIDResponse{\n\t\tErr:           ErrNoError,\n\t\tProducerID:    1,\n\t\tProducerEpoch: 0,\n\t}\n\tbroker.Returns(producerIdResponse)\n\n\tap, err := NewAsyncProducerFromClient(client)\n\tproducer := ap.(*asyncProducer)\n\trequire.NoError(t, err)\n\tdefer ap.Close()\n\n\taddPartitionsToTxnResponse := &AddPartitionsToTxnResponse{\n\t\tErrors: map[string][]*PartitionError{\n\t\t\t\"test-topic\": {\n\t\t\t\t{\n\t\t\t\t\tPartition: 0,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tbroker.Returns(addPartitionsToTxnResponse)\n\n\tproduceResponse := new(ProduceResponse)\n\tproduceResponse.Version = 3\n\tproduceResponse.AddTopicPartition(\"test-topic\", 0, ErrNoError)\n\tbroker.Returns(produceResponse)\n\n\tendTxnResponse := &EndTxnResponse{\n\t\tErr: ErrNoError,\n\t}\n\tbroker.Returns(endTxnResponse)\n\n\trequire.Equal(t, ProducerTxnFlagReady, producer.txnmgr.status)\n\n\terr = producer.BeginTxn()\n\trequire.NoError(t, err)\n\trequire.Equal(t, ProducerTxnFlagInTransaction, producer.txnmgr.status)\n\n\tproducer.Input() <- &ProducerMessage{Topic: \"test-topic\", Key: nil, Value: StringEncoder(TestMessage)}\n\terr = producer.CommitTxn()\n\trequire.NoError(t, err)\n\trequire.Equal(t, ProducerTxnFlagReady, producer.txnmgr.status)\n}\n\nfunc TestTxnProduceBatchAddPartition(t *testing.T) {\n\tbroker := NewMockBroker(t, 1)\n\tdefer broker.Close()\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Idempotent = true\n\tconfig.Producer.Transaction.ID = \"test\"\n\tconfig.Version = V0_11_0_0\n\tconfig.Producer.RequiredAcks = WaitForAll\n\tconfig.Net.MaxOpenRequests = 1\n\n\tconfig.Producer.Retry.Max = 1\n\tconfig.Producer.Flush.Messages = 3\n\tconfig.Producer.Flush.Frequency = 30 * time.Second\n\tconfig.Producer.Flush.Bytes = 1 << 12\n\tconfig.Producer.Partitioner = NewManualPartitioner\n\n\tmetadataLeader := new(MetadataResponse)\n\tmetadataLeader.Version = 4\n\tmetadataLeader.ControllerID = broker.brokerID\n\tmetadataLeader.AddBroker(broker.Addr(), broker.BrokerID())\n\tmetadataLeader.AddTopic(\"test-topic\", ErrNoError)\n\tmetadataLeader.AddTopicPartition(\"test-topic\", 0, broker.BrokerID(), nil, nil, nil, ErrNoError)\n\tmetadataLeader.AddTopicPartition(\"test-topic\", 1, broker.BrokerID(), nil, nil, nil, ErrNoError)\n\tmetadataLeader.AddTopicPartition(\"test-topic\", 2, broker.BrokerID(), nil, nil, nil, ErrNoError)\n\tbroker.Returns(metadataLeader)\n\n\tclient, err := NewClient([]string{broker.Addr()}, config)\n\trequire.NoError(t, err)\n\tdefer client.Close()\n\n\tfindCoordinatorResponse := FindCoordinatorResponse{\n\t\tCoordinator: client.Brokers()[0],\n\t\tErr:         ErrNoError,\n\t\tVersion:     1,\n\t}\n\tbroker.Returns(&findCoordinatorResponse)\n\n\tproducerIdResponse := &InitProducerIDResponse{\n\t\tErr:           ErrNoError,\n\t\tProducerID:    1,\n\t\tProducerEpoch: 0,\n\t}\n\tbroker.Returns(producerIdResponse)\n\n\tap, err := NewAsyncProducerFromClient(client)\n\tproducer := ap.(*asyncProducer)\n\trequire.NoError(t, err)\n\tdefer ap.Close()\n\n\tgo func() {\n\t\tfor err := range producer.Errors() {\n\t\t\trequire.NoError(t, err)\n\t\t}\n\t}()\n\n\tbroker.Returns(&AddPartitionsToTxnResponse{\n\t\tErrors: map[string][]*PartitionError{\n\t\t\t\"test-topic\": {\n\t\t\t\t{\n\t\t\t\t\tPartition: 0,\n\t\t\t\t\tErr:       ErrNoError,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tPartition: 1,\n\t\t\t\t\tErr:       ErrNoError,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tPartition: 2,\n\t\t\t\t\tErr:       ErrNoError,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\n\tproduceResponse := new(ProduceResponse)\n\tproduceResponse.Version = 3\n\tproduceResponse.AddTopicPartition(\"test-topic\", 0, ErrNoError)\n\tproduceResponse.AddTopicPartition(\"test-topic\", 1, ErrNoError)\n\tproduceResponse.AddTopicPartition(\"test-topic\", 2, ErrNoError)\n\tbroker.Returns(produceResponse)\n\n\tendTxnResponse := &EndTxnResponse{\n\t\tErr: ErrNoError,\n\t}\n\tbroker.Returns(endTxnResponse)\n\n\trequire.Equal(t, ProducerTxnFlagReady, producer.txnmgr.status)\n\n\terr = producer.BeginTxn()\n\trequire.NoError(t, err)\n\trequire.Equal(t, ProducerTxnFlagInTransaction, producer.txnmgr.status)\n\n\tproducer.Input() <- &ProducerMessage{Topic: \"test-topic\", Partition: 0, Key: nil, Value: StringEncoder(\"partition-0\")}\n\tproducer.Input() <- &ProducerMessage{Topic: \"test-topic\", Partition: 1, Key: nil, Value: StringEncoder(\"partition-1\")}\n\tproducer.Input() <- &ProducerMessage{Topic: \"test-topic\", Partition: 2, Key: nil, Value: StringEncoder(\"partition-2\")}\n\n\terr = producer.CommitTxn()\n\trequire.NoError(t, err)\n\trequire.Equal(t, ProducerTxnFlagReady, producer.txnmgr.status)\n\n\tproduceExchange := broker.History()[len(broker.History())-2]\n\tproduceRequest := produceExchange.Request.(*ProduceRequest)\n\trequire.Equal(t, 3, len(produceRequest.records[\"test-topic\"]))\n\n\taddPartitionExchange := broker.History()[len(broker.History())-3]\n\taddpartitionRequest := addPartitionExchange.Request.(*AddPartitionsToTxnRequest)\n\trequire.Equal(t, 3, len(addpartitionRequest.TopicPartitions[\"test-topic\"]))\n\trequire.Contains(t, addpartitionRequest.TopicPartitions[\"test-topic\"], int32(0))\n\trequire.Contains(t, addpartitionRequest.TopicPartitions[\"test-topic\"], int32(1))\n\trequire.Contains(t, addpartitionRequest.TopicPartitions[\"test-topic\"], int32(2))\n}\n\nfunc TestTxnProduceRecordWithAbort(t *testing.T) {\n\tbroker := NewMockBroker(t, 1)\n\tdefer broker.Close()\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Idempotent = true\n\tconfig.Producer.Transaction.ID = \"test\"\n\tconfig.Version = V0_11_0_0\n\tconfig.Producer.RequiredAcks = WaitForAll\n\tconfig.Net.MaxOpenRequests = 1\n\n\tmetadataLeader := new(MetadataResponse)\n\tmetadataLeader.Version = 4\n\tmetadataLeader.ControllerID = broker.brokerID\n\tmetadataLeader.AddBroker(broker.Addr(), broker.BrokerID())\n\tmetadataLeader.AddTopic(\"test-topic\", ErrNoError)\n\tmetadataLeader.AddTopicPartition(\"test-topic\", 0, broker.BrokerID(), nil, nil, nil, ErrNoError)\n\tbroker.Returns(metadataLeader)\n\n\tclient, err := NewClient([]string{broker.Addr()}, config)\n\trequire.NoError(t, err)\n\tdefer client.Close()\n\n\tfindCoordinatorResponse := FindCoordinatorResponse{\n\t\tCoordinator: client.Brokers()[0],\n\t\tErr:         ErrNoError,\n\t\tVersion:     1,\n\t}\n\tbroker.Returns(&findCoordinatorResponse)\n\n\tproducerIdResponse := &InitProducerIDResponse{\n\t\tErr:           ErrNoError,\n\t\tProducerID:    1,\n\t\tProducerEpoch: 0,\n\t}\n\tbroker.Returns(producerIdResponse)\n\n\tap, err := NewAsyncProducerFromClient(client)\n\tproducer := ap.(*asyncProducer)\n\trequire.NoError(t, err)\n\tdefer ap.Close()\n\n\tbroker.Returns(&AddPartitionsToTxnResponse{\n\t\tErrors: map[string][]*PartitionError{\n\t\t\t\"test-topic\": {\n\t\t\t\t{\n\t\t\t\t\tPartition: 0,\n\t\t\t\t\tErr:       ErrNoError,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\n\tproduceResponse := new(ProduceResponse)\n\tproduceResponse.Version = 3\n\tproduceResponse.AddTopicPartition(\"test-topic\", 0, ErrNoError)\n\tbroker.Returns(produceResponse)\n\n\tendTxnResponse := &EndTxnResponse{\n\t\tErr: ErrNoError,\n\t}\n\tbroker.Returns(endTxnResponse)\n\n\trequire.Equal(t, ProducerTxnFlagReady, producer.txnmgr.status)\n\n\terr = producer.BeginTxn()\n\trequire.NoError(t, err)\n\trequire.Equal(t, ProducerTxnFlagInTransaction, producer.txnmgr.status)\n\n\tproducer.Input() <- &ProducerMessage{Topic: \"test-topic\", Key: nil, Value: StringEncoder(TestMessage)}\n\terr = producer.AbortTxn()\n\trequire.NoError(t, err)\n\trequire.Equal(t, ProducerTxnFlagReady, producer.txnmgr.status)\n}\n\nfunc TestTxnCanAbort(t *testing.T) {\n\tbroker := NewMockBroker(t, 1)\n\tdefer broker.Close()\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Idempotent = true\n\tconfig.Producer.Transaction.ID = \"test\"\n\tconfig.Version = V0_11_0_0\n\tconfig.Producer.RequiredAcks = WaitForAll\n\tconfig.Producer.Return.Errors = false\n\tconfig.Producer.Return.Successes = true\n\tconfig.Producer.Retry.Backoff = 0\n\tconfig.Producer.Flush.Messages = 1\n\tconfig.Producer.Retry.Max = 1\n\tconfig.Net.MaxOpenRequests = 1\n\n\tvar (\n\t\tmu                   sync.Mutex\n\t\taddPartitionsCount   int\n\t\tproduceRequestsCount int\n\t)\n\n\tbroker.SetHandlerFuncByMap(map[string]requestHandlerFunc{\n\t\t\"MetadataRequest\": func(req *request) encoderWithHeader {\n\t\t\tresp := new(MetadataResponse)\n\t\t\tresp.Version = 4\n\t\t\tresp.ControllerID = broker.BrokerID()\n\t\t\tresp.AddBroker(broker.Addr(), broker.BrokerID())\n\t\t\tresp.AddTopic(\"test-topic\", ErrNoError)\n\t\t\tresp.AddTopic(\"test-topic-2\", ErrNoError)\n\t\t\tresp.AddTopicPartition(\"test-topic\", 0, broker.BrokerID(), nil, nil, nil, ErrNoError)\n\t\t\tresp.AddTopicPartition(\"test-topic-2\", 0, broker.BrokerID(), nil, nil, nil, ErrNoError)\n\t\t\treturn resp\n\t\t},\n\t\t\"FindCoordinatorRequest\": func(req *request) encoderWithHeader {\n\t\t\tresp := new(FindCoordinatorResponse)\n\t\t\tresp.Version = 1\n\t\t\tresp.Coordinator = &Broker{id: broker.BrokerID(), addr: broker.Addr()}\n\t\t\tresp.Err = ErrNoError\n\t\t\treturn resp\n\t\t},\n\t\t\"InitProducerIDRequest\": func(req *request) encoderWithHeader {\n\t\t\treturn &InitProducerIDResponse{\n\t\t\t\tErr:           ErrNoError,\n\t\t\t\tProducerID:    1,\n\t\t\t\tProducerEpoch: 0,\n\t\t\t}\n\t\t},\n\t\t\"AddPartitionsToTxnRequest\": func(req *request) encoderWithHeader {\n\t\t\tmu.Lock()\n\t\t\taddPartitionsCount++\n\t\t\tcount := addPartitionsCount\n\t\t\tmu.Unlock()\n\n\t\t\tif count == 1 {\n\t\t\t\treturn &AddPartitionsToTxnResponse{\n\t\t\t\t\tErrors: map[string][]*PartitionError{\n\t\t\t\t\t\t\"test-topic-2\": {{Partition: 0, Err: ErrNoError}},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn &AddPartitionsToTxnResponse{\n\t\t\t\tErrors: map[string][]*PartitionError{\n\t\t\t\t\t\"test-topic\": {{Partition: 0, Err: ErrTopicAuthorizationFailed}},\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t\t\"ProduceRequest\": func(req *request) encoderWithHeader {\n\t\t\tmu.Lock()\n\t\t\tproduceRequestsCount++\n\t\t\tmu.Unlock()\n\n\t\t\tresp := new(ProduceResponse)\n\t\t\tresp.Version = 3\n\t\t\tresp.AddTopicPartition(\"test-topic-2\", 0, ErrNoError)\n\t\t\treturn resp\n\t\t},\n\t\t\"EndTxnRequest\": func(req *request) encoderWithHeader {\n\t\t\treturn &EndTxnResponse{Err: ErrNoError}\n\t\t},\n\t})\n\n\tclient, err := NewClient([]string{broker.Addr()}, config)\n\trequire.NoError(t, err)\n\tdefer client.Close()\n\n\tap, err := NewAsyncProducerFromClient(client)\n\tproducer := ap.(*asyncProducer)\n\trequire.NoError(t, err)\n\tdefer ap.Close()\n\n\trequire.Equal(t, ProducerTxnFlagReady, producer.txnmgr.status)\n\n\terr = producer.BeginTxn()\n\trequire.NoError(t, err)\n\trequire.Equal(t, ProducerTxnFlagInTransaction, producer.txnmgr.status)\n\n\tproducer.Input() <- &ProducerMessage{Topic: \"test-topic-2\", Partition: 0, Key: nil, Value: StringEncoder(TestMessage)}\n\t<-producer.Successes()\n\n\tproducer.Input() <- &ProducerMessage{Topic: \"test-topic\", Partition: 0, Key: nil, Value: StringEncoder(TestMessage)}\n\n\terr = producer.CommitTxn()\n\trequire.Error(t, err)\n\trequire.NotEqual(t, producer.txnmgr.status&ProducerTxnFlagAbortableError, 0)\n\n\terr = producer.AbortTxn()\n\trequire.NoError(t, err)\n}\n\nfunc TestProducerRetryBufferLimits(t *testing.T) {\n\tbroker := NewMockBroker(t, 1)\n\tdefer broker.Close()\n\ttopic := \"test-topic\"\n\n\tmetadataRequestHandlerFunc := func(req *request) (res encoderWithHeader) {\n\t\tr := new(MetadataResponse)\n\t\tr.AddBroker(broker.Addr(), broker.BrokerID())\n\t\tr.AddTopicPartition(topic, 0, broker.BrokerID(), nil, nil, nil, ErrNoError)\n\t\treturn r\n\t}\n\n\tproduceRequestHandlerFunc := func(req *request) (res encoderWithHeader) {\n\t\tr := new(ProduceResponse)\n\t\tr.AddTopicPartition(topic, 0, ErrNotLeaderForPartition)\n\t\treturn r\n\t}\n\n\tbroker.SetHandlerFuncByMap(map[string]requestHandlerFunc{\n\t\t\"ProduceRequest\":  produceRequestHandlerFunc,\n\t\t\"MetadataRequest\": metadataRequestHandlerFunc,\n\t})\n\n\ttests := []struct {\n\t\tname            string\n\t\tconfigureBuffer func(*Config)\n\t\tmessageSize     int\n\t\tnumMessages     int\n\t}{\n\t\t{\n\t\t\tname: \"MaxBufferLength\",\n\t\t\tconfigureBuffer: func(config *Config) {\n\t\t\t\tconfig.Producer.Flush.MaxMessages = 1\n\t\t\t\tconfig.Producer.Retry.MaxBufferLength = minFunctionalRetryBufferLength\n\t\t\t},\n\t\t\tmessageSize: 1, // Small message size\n\t\t\tnumMessages: 10000,\n\t\t},\n\t\t{\n\t\t\tname: \"MaxBufferBytes\",\n\t\t\tconfigureBuffer: func(config *Config) {\n\t\t\t\tconfig.Producer.Flush.MaxMessages = 1\n\t\t\t\tconfig.Producer.Retry.MaxBufferBytes = minFunctionalRetryBufferBytes\n\t\t\t},\n\t\t\tmessageSize: 950 * 1024, // 950 KB\n\t\t\tnumMessages: 1000,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tconfig := NewTestConfig()\n\t\t\tconfig.Producer.Return.Successes = true\n\t\t\ttt.configureBuffer(config)\n\n\t\t\tproducer, err := NewAsyncProducer([]string{broker.Addr()}, config)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tvar (\n\t\t\t\twg                        sync.WaitGroup\n\t\t\t\tsuccesses, producerErrors int\n\t\t\t\terrorFound                bool\n\t\t\t)\n\n\t\t\twg.Add(1)\n\t\t\tgo func() {\n\t\t\t\tdefer wg.Done()\n\t\t\t\tfor range producer.Successes() {\n\t\t\t\t\tsuccesses++\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\twg.Add(1)\n\t\t\tgo func() {\n\t\t\t\tdefer wg.Done()\n\t\t\t\tfor errMsg := range producer.Errors() {\n\t\t\t\t\tif errors.Is(errMsg.Err, ErrProducerRetryBufferOverflow) {\n\t\t\t\t\t\terrorFound = true\n\t\t\t\t\t}\n\t\t\t\t\tproducerErrors++\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\tlongString := strings.Repeat(\"a\", tt.messageSize)\n\t\t\tval := StringEncoder(longString)\n\n\t\t\tfor i := 0; i < tt.numMessages; i++ {\n\t\t\t\tmsg := &ProducerMessage{\n\t\t\t\t\tTopic: topic,\n\t\t\t\t\tValue: val,\n\t\t\t\t}\n\t\t\t\tproducer.Input() <- msg\n\t\t\t}\n\n\t\t\tproducer.AsyncClose()\n\t\t\twg.Wait()\n\n\t\t\tassert.Equal(t, successes+producerErrors, tt.numMessages, \"Expected all messages to be processed\")\n\t\t\tassert.True(t, errorFound, \"Expected at least one error matching ErrProducerRetryBufferOverflow\")\n\t\t})\n\t}\n}\n\n// This example shows how to use the producer while simultaneously\n// reading the Errors channel to know about any failures.\nfunc ExampleAsyncProducer_select() {\n\tproducer, err := NewAsyncProducer([]string{\"localhost:9092\"}, nil)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tdefer func() {\n\t\tif err := producer.Close(); err != nil {\n\t\t\tlog.Fatalln(err)\n\t\t}\n\t}()\n\n\t// Trap SIGINT to trigger a shutdown.\n\tsignals := make(chan os.Signal, 1)\n\tsignal.Notify(signals, os.Interrupt)\n\n\tvar enqueued, producerErrors int\nProducerLoop:\n\tfor {\n\t\tselect {\n\t\tcase producer.Input() <- &ProducerMessage{Topic: \"my_topic\", Key: nil, Value: StringEncoder(\"testing 123\")}:\n\t\t\tenqueued++\n\t\tcase err := <-producer.Errors():\n\t\t\tlog.Println(\"Failed to produce message\", err)\n\t\t\tproducerErrors++\n\t\tcase <-signals:\n\t\t\tbreak ProducerLoop\n\t\t}\n\t}\n\n\tlog.Printf(\"Enqueued: %d; errors: %d\\n\", enqueued, producerErrors)\n}\n\n// This example shows how to use the producer with separate goroutines\n// reading from the Successes and Errors channels. Note that in order\n// for the Successes channel to be populated, you have to set\n// config.Producer.Return.Successes to true.\nfunc ExampleAsyncProducer_goroutines() {\n\tconfig := NewTestConfig()\n\tconfig.Producer.Return.Successes = true\n\tproducer, err := NewAsyncProducer([]string{\"localhost:9092\"}, config)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Trap SIGINT to trigger a graceful shutdown.\n\tsignals := make(chan os.Signal, 1)\n\tsignal.Notify(signals, os.Interrupt)\n\n\tvar (\n\t\twg                                  sync.WaitGroup\n\t\tenqueued, successes, producerErrors int\n\t)\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tfor range producer.Successes() {\n\t\t\tsuccesses++\n\t\t}\n\t}()\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tfor err := range producer.Errors() {\n\t\t\tlog.Println(err)\n\t\t\tproducerErrors++\n\t\t}\n\t}()\n\nProducerLoop:\n\tfor {\n\t\tmessage := &ProducerMessage{Topic: \"my_topic\", Value: StringEncoder(\"testing 123\")}\n\t\tselect {\n\t\tcase producer.Input() <- message:\n\t\t\tenqueued++\n\n\t\tcase <-signals:\n\t\t\tproducer.AsyncClose() // Trigger a shutdown of the producer.\n\t\t\tbreak ProducerLoop\n\t\t}\n\t}\n\n\twg.Wait()\n\n\tlog.Printf(\"Successfully produced: %d; errors: %d\\n\", successes, producerErrors)\n}\n\n// TestAsyncProducerRetryOrdering verifies that message ordering is preserved during retries,\n// both with and without request pipelining (MaxOpenRequests=1 vs >1).\nfunc TestAsyncProducerRetryOrdering(t *testing.T) {\n\tconst topic = \"my_topic\"\n\n\textractValue := func(pr *ProduceRequest) string {\n\t\trecordsByPartition := pr.records[topic]\n\t\tif recordsByPartition == nil {\n\t\t\treturn \"\"\n\t\t}\n\t\trecords := recordsByPartition[0]\n\t\tif rb := records.RecordBatch; rb != nil && len(rb.Records) > 0 {\n\t\t\treturn string(rb.Records[0].Value)\n\t\t}\n\t\tif ms := records.MsgSet; ms != nil && len(ms.Messages) > 0 {\n\t\t\treturn string(ms.Messages[0].Msg.Value)\n\t\t}\n\t\treturn \"\"\n\t}\n\n\ttests := []struct {\n\t\tname            string\n\t\tmaxOpenRequests int\n\t\tretryBackoff    time.Duration\n\t}{\n\t\t{\n\t\t\tname:            \"no pipelining (MaxOpenRequests=1)\",\n\t\t\tmaxOpenRequests: 1,\n\t\t\tretryBackoff:    0,\n\t\t},\n\t\t{\n\t\t\tname:            \"with pipelining (MaxOpenRequests=5)\",\n\t\t\tmaxOpenRequests: 5,\n\t\t\tretryBackoff:    50 * time.Millisecond,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tseedBroker := NewMockBroker(t, 1)\n\t\t\tleader := NewMockBroker(t, 2)\n\n\t\t\tmetadataResponse := new(MetadataResponse)\n\t\t\tmetadataResponse.AddBroker(leader.Addr(), leader.BrokerID())\n\t\t\tmetadataResponse.AddTopicPartition(topic, 0, leader.BrokerID(), nil, nil, nil, ErrNoError)\n\t\t\tseedBroker.Returns(metadataResponse)\n\n\t\t\tvar (\n\t\t\t\tmu              sync.Mutex\n\t\t\t\tproduceAttempts int\n\t\t\t\tvaluesSeen      []string\n\t\t\t)\n\n\t\t\tleader.setHandler(func(req *request) (res encoderWithHeader) {\n\t\t\t\tswitch typed := req.body.(type) {\n\t\t\t\tcase *MetadataRequest:\n\t\t\t\t\treturn metadataResponse\n\t\t\t\tcase *ProduceRequest:\n\t\t\t\t\tmu.Lock()\n\t\t\t\t\tdefer mu.Unlock()\n\n\t\t\t\t\tproduceAttempts++\n\t\t\t\t\tvalue := extractValue(typed)\n\t\t\t\t\tvaluesSeen = append(valuesSeen, value)\n\n\t\t\t\t\tresp := new(ProduceResponse)\n\t\t\t\t\tresp.Version = typed.Version\n\t\t\t\t\tswitch produceAttempts {\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\tresp.AddTopicPartition(topic, 0, ErrNotLeaderForPartition)\n\t\t\t\t\tcase 2, 3:\n\t\t\t\t\t\tresp.AddTopicPartition(topic, 0, ErrNoError)\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tt.Errorf(\"unexpected attempt %d\", produceAttempts)\n\t\t\t\t\t\tresp.AddTopicPartition(topic, 0, ErrNoError)\n\t\t\t\t\t}\n\t\t\t\t\treturn resp\n\t\t\t\tdefault:\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tconfig := NewTestConfig()\n\t\t\tconfig.Producer.Return.Successes = true\n\t\t\tconfig.Producer.Retry.Max = 3\n\t\t\tconfig.Producer.Retry.Backoff = tt.retryBackoff\n\t\t\tconfig.Producer.Flush.Messages = 1\n\t\t\tconfig.Producer.Flush.MaxMessages = 1\n\t\t\tconfig.Producer.Partitioner = NewManualPartitioner\n\t\t\tconfig.Net.MaxOpenRequests = tt.maxOpenRequests\n\n\t\t\tproducer, err := NewAsyncProducer([]string{seedBroker.Addr()}, config)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tmsgValues := []string{\"msg-0\", \"msg-1\"}\n\t\t\tfor _, val := range msgValues {\n\t\t\t\tproducer.Input() <- &ProducerMessage{Topic: topic, Partition: 0, Value: StringEncoder(val)}\n\t\t\t}\n\n\t\t\texpectResultsWithTimeout(t, producer, len(msgValues), 0, 10*time.Second)\n\n\t\t\tmu.Lock()\n\t\t\tattempts := produceAttempts\n\t\t\tseen := make([]string, len(valuesSeen))\n\t\t\tcopy(seen, valuesSeen)\n\t\t\tmu.Unlock()\n\n\t\t\tcloseProducer(t, producer)\n\t\t\tseedBroker.Close()\n\t\t\tleader.Close()\n\n\t\t\tif attempts != 3 {\n\t\t\t\tt.Errorf(\"expected 3 produce attempts, got %d\", attempts)\n\t\t\t}\n\n\t\t\t// Both configurations should maintain ordering: msg-0 (fail), msg-0 (retry), msg-1\n\t\t\texpectedOrder := []string{\"msg-0\", \"msg-0\", \"msg-1\"}\n\t\t\tif !assert.Equal(t, expectedOrder, seen) {\n\t\t\t\tt.Errorf(\"messages out of order: got %v, want %v\", seen, expectedOrder)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestAsyncProducerPartitionUnmuting verifies that partitions are properly unmuted\n// in all error paths: send errors, NoResponse acks, etc. Without proper unmuting,\n// partitions remain muted and subsequent messages would block indefinitely.\nfunc TestAsyncProducerPartitionUnmuting(t *testing.T) {\n\tconst topic = \"test_topic\"\n\n\tt.Run(\"NoResponse acks unmute partitions\", func(t *testing.T) {\n\t\tbroker := NewMockBroker(t, 1)\n\t\tdefer broker.Close()\n\n\t\tmetadataResponse := NewMockMetadataResponse(t).\n\t\t\tSetBroker(broker.Addr(), broker.BrokerID()).\n\t\t\tSetLeader(topic, 0, broker.BrokerID())\n\t\tbroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\t\"MetadataRequest\": metadataResponse,\n\t\t})\n\n\t\tconfig := NewTestConfig()\n\t\tconfig.Producer.RequiredAcks = NoResponse\n\t\tconfig.Producer.Return.Successes = true\n\t\tconfig.Producer.Flush.Messages = 1\n\t\tconfig.Net.MaxOpenRequests = 5\n\n\t\tproducer, err := NewAsyncProducer([]string{broker.Addr()}, config)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tfor i := 0; i < 3; i++ {\n\t\t\tproducer.Input() <- &ProducerMessage{\n\t\t\t\tTopic:     topic,\n\t\t\t\tPartition: 0,\n\t\t\t\tValue:     StringEncoder(\"msg\"),\n\t\t\t}\n\t\t}\n\n\t\tsuccessCount := 0\n\t\tfor i := 0; i < 3; i++ {\n\t\t\tselect {\n\t\t\tcase <-producer.Successes():\n\t\t\t\tsuccessCount++\n\t\t\tcase err := <-producer.Errors():\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\tcase <-time.After(5 * time.Second):\n\t\t\t\tt.Fatalf(\"timeout waiting for success %d (got %d) - partitions may not be unmuted\", i+1, successCount)\n\t\t\t}\n\t\t}\n\n\t\tif successCount != 3 {\n\t\t\tt.Errorf(\"expected 3 successes, got %d\", successCount)\n\t\t}\n\n\t\tcloseProducer(t, producer)\n\t})\n\n\tt.Run(\"retry keeps partition muted until queued\", func(t *testing.T) {\n\t\tbroker := NewMockBroker(t, 1)\n\t\tdefer broker.Close()\n\n\t\tmetadataResponse := new(MetadataResponse)\n\t\tmetadataResponse.AddBroker(broker.Addr(), broker.BrokerID())\n\t\tmetadataResponse.AddTopicPartition(topic, 0, broker.BrokerID(), nil, nil, nil, ErrNoError)\n\n\t\tvar attemptCount int\n\t\tvar mu sync.Mutex\n\t\tfirstRequestReceived := make(chan struct{})\n\n\t\tbroker.setHandler(func(req *request) (res encoderWithHeader) {\n\t\t\tswitch req.body.(type) {\n\t\t\tcase *MetadataRequest:\n\t\t\t\treturn metadataResponse\n\t\t\tcase *ProduceRequest:\n\t\t\t\tmu.Lock()\n\t\t\t\tattemptCount++\n\t\t\t\tattempt := attemptCount\n\t\t\t\tmu.Unlock()\n\n\t\t\t\tif attempt == 1 {\n\t\t\t\t\tclose(firstRequestReceived)\n\t\t\t\t}\n\n\t\t\t\tresp := new(ProduceResponse)\n\t\t\t\tif attempt == 1 {\n\t\t\t\t\tresp.AddTopicPartition(topic, 0, ErrNotLeaderForPartition)\n\t\t\t\t} else {\n\t\t\t\t\tresp.AddTopicPartition(topic, 0, ErrNoError)\n\t\t\t\t}\n\t\t\t\treturn resp\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\n\t\tconfig := NewTestConfig()\n\t\tconfig.Producer.Return.Successes = true\n\t\tconfig.Producer.Retry.Max = 1\n\t\tconfig.Producer.Retry.Backoff = 10 * time.Millisecond\n\t\tconfig.Producer.Flush.Messages = 1\n\t\tconfig.Net.MaxOpenRequests = 5\n\n\t\tproducer, err := NewAsyncProducer([]string{broker.Addr()}, config)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tproducer.Input() <- &ProducerMessage{\n\t\t\tTopic:     topic,\n\t\t\tPartition: 0,\n\t\t\tValue:     StringEncoder(\"msg-0\"),\n\t\t}\n\n\t\t<-firstRequestReceived\n\n\t\tproducer.Input() <- &ProducerMessage{\n\t\t\tTopic:     topic,\n\t\t\tPartition: 0,\n\t\t\tValue:     StringEncoder(\"msg-1\"),\n\t\t}\n\n\t\tvar successCount int\n\t\tfor i := 0; i < 2; i++ {\n\t\t\tselect {\n\t\t\tcase <-producer.Successes():\n\t\t\t\tsuccessCount++\n\t\t\tcase err := <-producer.Errors():\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\tcase <-time.After(5 * time.Second):\n\t\t\t\tt.Fatalf(\"timeout waiting for success %d/%d - partition may be deadlocked\", successCount, 2)\n\t\t\t}\n\t\t}\n\n\t\tif successCount != 2 {\n\t\t\tt.Errorf(\"expected 2 successes, got %d\", successCount)\n\t\t}\n\n\t\tmu.Lock()\n\t\tattempts := attemptCount\n\t\tmu.Unlock()\n\t\tif attempts != 3 {\n\t\t\tt.Errorf(\"expected 3 produce attempts, got %d\", attempts)\n\t\t}\n\n\t\tcloseProducer(t, producer)\n\t})\n}\n"
  },
  {
    "path": "balance_strategy.go",
    "content": "package sarama\n\nimport (\n\t\"container/heap\"\n\t\"errors\"\n\t\"fmt\"\n\t\"maps\"\n\t\"math\"\n\t\"slices\"\n\t\"sort\"\n\t\"strings\"\n)\n\nconst (\n\t// RangeBalanceStrategyName identifies strategies that use the range partition assignment strategy\n\tRangeBalanceStrategyName = \"range\"\n\n\t// RoundRobinBalanceStrategyName identifies strategies that use the round-robin partition assignment strategy\n\tRoundRobinBalanceStrategyName = \"roundrobin\"\n\n\t// StickyBalanceStrategyName identifies strategies that use the sticky-partition assignment strategy\n\tStickyBalanceStrategyName = \"sticky\"\n\n\tdefaultGeneration = -1\n)\n\n// BalanceStrategyPlan is the results of any BalanceStrategy.Plan attempt.\n// It contains an allocation of topic/partitions by memberID in the form of\n// a `memberID -> topic -> partitions` map.\ntype BalanceStrategyPlan map[string]map[string][]int32\n\n// Add assigns a topic with a number partitions to a member.\nfunc (p BalanceStrategyPlan) Add(memberID, topic string, partitions ...int32) {\n\tif len(partitions) == 0 {\n\t\treturn\n\t}\n\tif _, ok := p[memberID]; !ok {\n\t\tp[memberID] = make(map[string][]int32, 1)\n\t}\n\tp[memberID][topic] = append(p[memberID][topic], partitions...)\n}\n\n// --------------------------------------------------------------------\n\n// BalanceStrategy is used to balance topics and partitions\n// across members of a consumer group\ntype BalanceStrategy interface {\n\t// Name uniquely identifies the strategy.\n\tName() string\n\n\t// Plan accepts a map of `memberID -> metadata` and a map of `topic -> partitions`\n\t// and returns a distribution plan.\n\tPlan(members map[string]ConsumerGroupMemberMetadata, topics map[string][]int32) (BalanceStrategyPlan, error)\n\n\t// AssignmentData returns the serialized assignment data for the specified\n\t// memberID\n\tAssignmentData(memberID string, topics map[string][]int32, generationID int32) ([]byte, error)\n}\n\n// --------------------------------------------------------------------\n\n// NewBalanceStrategyRange returns a range balance strategy,\n// which is the default and assigns partitions as ranges to consumer group members.\n// This follows the same logic as\n// https://kafka.apache.org/31/javadoc/org/apache/kafka/clients/consumer/RangeAssignor.html\n//\n// Example with two topics T1 and T2 with six partitions each (0..5) and two members (M1, M2):\n//\n//\tM1: {T1: [0, 1, 2], T2: [0, 1, 2]}\n//\tM2: {T1: [3, 4, 5], T2: [3, 4, 5]}\nfunc NewBalanceStrategyRange() BalanceStrategy {\n\treturn &balanceStrategy{\n\t\tname: RangeBalanceStrategyName,\n\t\tcoreFn: func(plan BalanceStrategyPlan, memberIDs []string, topic string, partitions []int32) {\n\t\t\tpartitionsPerConsumer := len(partitions) / len(memberIDs)\n\t\t\tconsumersWithExtraPartition := len(partitions) % len(memberIDs)\n\n\t\t\tsort.Strings(memberIDs)\n\n\t\t\tfor i, memberID := range memberIDs {\n\t\t\t\tmin := i*partitionsPerConsumer + int(math.Min(float64(consumersWithExtraPartition), float64(i)))\n\t\t\t\textra := 0\n\t\t\t\tif i < consumersWithExtraPartition {\n\t\t\t\t\textra = 1\n\t\t\t\t}\n\t\t\t\tmax := min + partitionsPerConsumer + extra\n\t\t\t\tplan.Add(memberID, topic, partitions[min:max]...)\n\t\t\t}\n\t\t},\n\t}\n}\n\n// Deprecated: use NewBalanceStrategyRange to avoid data race issue\nvar BalanceStrategyRange = NewBalanceStrategyRange()\n\n// NewBalanceStrategySticky returns a sticky balance strategy,\n// which assigns partitions to members with an attempt to preserve earlier assignments\n// while maintain a balanced partition distribution.\n// Example with topic T with six partitions (0..5) and two members (M1, M2):\n//\n//\tM1: {T: [0, 2, 4]}\n//\tM2: {T: [1, 3, 5]}\n//\n// On reassignment with an additional consumer, you might get an assignment plan like:\n//\n//\tM1: {T: [0, 2]}\n//\tM2: {T: [1, 3]}\n//\tM3: {T: [4, 5]}\nfunc NewBalanceStrategySticky() BalanceStrategy {\n\treturn &stickyBalanceStrategy{}\n}\n\n// Deprecated: use NewBalanceStrategySticky to avoid data race issue\nvar BalanceStrategySticky = NewBalanceStrategySticky()\n\n// --------------------------------------------------------------------\n\ntype balanceStrategy struct {\n\tcoreFn func(plan BalanceStrategyPlan, memberIDs []string, topic string, partitions []int32)\n\tname   string\n}\n\n// Name implements BalanceStrategy.\nfunc (s *balanceStrategy) Name() string { return s.name }\n\n// Plan implements BalanceStrategy.\nfunc (s *balanceStrategy) Plan(members map[string]ConsumerGroupMemberMetadata, topics map[string][]int32) (BalanceStrategyPlan, error) {\n\t// Build members by topic map\n\tmbt := make(map[string][]string)\n\tfor memberID, meta := range members {\n\t\tfor _, topic := range meta.Topics {\n\t\t\tmbt[topic] = append(mbt[topic], memberID)\n\t\t}\n\t}\n\n\t// func to sort and de-duplicate a StringSlice\n\tuniq := func(ss sort.StringSlice) []string {\n\t\tif ss.Len() < 2 {\n\t\t\treturn ss\n\t\t}\n\t\tsort.Sort(ss)\n\t\tvar i, j int\n\t\tfor i = 1; i < ss.Len(); i++ {\n\t\t\tif ss[i] == ss[j] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tj++\n\t\t\tss.Swap(i, j)\n\t\t}\n\t\treturn ss[:j+1]\n\t}\n\n\t// Assemble plan\n\tplan := make(BalanceStrategyPlan, len(members))\n\tfor topic, memberIDs := range mbt {\n\t\ts.coreFn(plan, uniq(memberIDs), topic, topics[topic])\n\t}\n\treturn plan, nil\n}\n\n// AssignmentData simple strategies do not require any shared assignment data\nfunc (s *balanceStrategy) AssignmentData(memberID string, topics map[string][]int32, generationID int32) ([]byte, error) {\n\treturn nil, nil\n}\n\ntype stickyBalanceStrategy struct {\n\tmovements partitionMovements\n}\n\n// Name implements BalanceStrategy.\nfunc (s *stickyBalanceStrategy) Name() string { return StickyBalanceStrategyName }\n\n// Plan implements BalanceStrategy.\nfunc (s *stickyBalanceStrategy) Plan(members map[string]ConsumerGroupMemberMetadata, topics map[string][]int32) (BalanceStrategyPlan, error) {\n\t// track partition movements during generation of the partition assignment plan\n\ts.movements = partitionMovements{\n\t\tMovements:                 make(map[topicPartitionAssignment]consumerPair),\n\t\tPartitionMovementsByTopic: make(map[string]map[consumerPair]map[topicPartitionAssignment]bool),\n\t}\n\n\t// prepopulate the current assignment state from userdata on the consumer group members\n\tcurrentAssignment, prevAssignment, err := prepopulateCurrentAssignments(members)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// determine if we're dealing with a completely fresh assignment, or if there's existing assignment state\n\tisFreshAssignment := len(currentAssignment) == 0\n\n\t// create a mapping of all current topic partitions and the consumers that can be assigned to them\n\tpartition2AllPotentialConsumers := make(map[topicPartitionAssignment][]string)\n\tfor topic, partitions := range topics {\n\t\tfor _, partition := range partitions {\n\t\t\tpartition2AllPotentialConsumers[topicPartitionAssignment{Topic: topic, Partition: partition}] = []string{}\n\t\t}\n\t}\n\n\t// create a mapping of all consumers to all potential topic partitions that can be assigned to them\n\t// also, populate the mapping of partitions to potential consumers\n\tconsumer2AllPotentialPartitions := make(map[string][]topicPartitionAssignment, len(members))\n\tfor memberID, meta := range members {\n\t\tconsumer2AllPotentialPartitions[memberID] = make([]topicPartitionAssignment, 0)\n\t\tfor _, topicSubscription := range meta.Topics {\n\t\t\t// only evaluate topic subscriptions that are present in the supplied topics map\n\t\t\tif _, found := topics[topicSubscription]; found {\n\t\t\t\tfor _, partition := range topics[topicSubscription] {\n\t\t\t\t\ttopicPartition := topicPartitionAssignment{Topic: topicSubscription, Partition: partition}\n\t\t\t\t\tconsumer2AllPotentialPartitions[memberID] = append(consumer2AllPotentialPartitions[memberID], topicPartition)\n\t\t\t\t\tpartition2AllPotentialConsumers[topicPartition] = append(partition2AllPotentialConsumers[topicPartition], memberID)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// add this consumer to currentAssignment (with an empty topic partition assignment) if it does not already exist\n\t\tif _, exists := currentAssignment[memberID]; !exists {\n\t\t\tcurrentAssignment[memberID] = make([]topicPartitionAssignment, 0)\n\t\t}\n\t}\n\n\t// create a mapping of each partition to its current consumer, where possible\n\tcurrentPartitionConsumers := make(map[topicPartitionAssignment]string, len(currentAssignment))\n\tunvisitedPartitions := make(map[topicPartitionAssignment]bool, len(partition2AllPotentialConsumers))\n\tfor partition := range partition2AllPotentialConsumers {\n\t\tunvisitedPartitions[partition] = true\n\t}\n\tvar unassignedPartitions []topicPartitionAssignment\n\tfor memberID, partitions := range currentAssignment {\n\t\tvar keepPartitions []topicPartitionAssignment\n\t\tfor _, partition := range partitions {\n\t\t\t// If this partition no longer exists at all, likely due to the\n\t\t\t// topic being deleted, we remove the partition from the member.\n\t\t\tif _, exists := partition2AllPotentialConsumers[partition]; !exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tdelete(unvisitedPartitions, partition)\n\t\t\tcurrentPartitionConsumers[partition] = memberID\n\n\t\t\tif !slices.Contains(members[memberID].Topics, partition.Topic) {\n\t\t\t\tunassignedPartitions = append(unassignedPartitions, partition)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tkeepPartitions = append(keepPartitions, partition)\n\t\t}\n\t\tcurrentAssignment[memberID] = keepPartitions\n\t}\n\tfor unvisited := range unvisitedPartitions {\n\t\tunassignedPartitions = append(unassignedPartitions, unvisited)\n\t}\n\n\t// sort the topic partitions in order of priority for reassignment\n\tsortedPartitions := sortPartitions(currentAssignment, prevAssignment, isFreshAssignment, partition2AllPotentialConsumers, consumer2AllPotentialPartitions)\n\n\t// at this point we have preserved all valid topic partition to consumer assignments and removed\n\t// all invalid topic partitions and invalid consumers. Now we need to assign unassignedPartitions\n\t// to consumers so that the topic partition assignments are as balanced as possible.\n\n\t// an ascending sorted set of consumers based on how many topic partitions are already assigned to them\n\tsortedCurrentSubscriptions := sortMemberIDsByPartitionAssignments(currentAssignment)\n\ts.balance(currentAssignment, prevAssignment, sortedPartitions, unassignedPartitions, sortedCurrentSubscriptions, consumer2AllPotentialPartitions, partition2AllPotentialConsumers, currentPartitionConsumers)\n\n\t// Assemble plan\n\tplan := make(BalanceStrategyPlan, len(currentAssignment))\n\tfor memberID, assignments := range currentAssignment {\n\t\tif len(assignments) == 0 {\n\t\t\tplan[memberID] = make(map[string][]int32)\n\t\t} else {\n\t\t\tfor _, assignment := range assignments {\n\t\t\t\tplan.Add(memberID, assignment.Topic, assignment.Partition)\n\t\t\t}\n\t\t}\n\t}\n\treturn plan, nil\n}\n\n// AssignmentData serializes the set of topics currently assigned to the\n// specified member as part of the supplied balance plan\nfunc (s *stickyBalanceStrategy) AssignmentData(memberID string, topics map[string][]int32, generationID int32) ([]byte, error) {\n\treturn encode(&StickyAssignorUserDataV1{\n\t\tTopics:     topics,\n\t\tGeneration: generationID,\n\t}, nil)\n}\n\n// Balance assignments across consumers for maximum fairness and stickiness.\nfunc (s *stickyBalanceStrategy) balance(currentAssignment map[string][]topicPartitionAssignment, prevAssignment map[topicPartitionAssignment]consumerGenerationPair, sortedPartitions []topicPartitionAssignment, unassignedPartitions []topicPartitionAssignment, sortedCurrentSubscriptions []string, consumer2AllPotentialPartitions map[string][]topicPartitionAssignment, partition2AllPotentialConsumers map[topicPartitionAssignment][]string, currentPartitionConsumer map[topicPartitionAssignment]string) {\n\tinitializing := len(sortedCurrentSubscriptions) == 0 || len(currentAssignment[sortedCurrentSubscriptions[0]]) == 0\n\n\t// assign all unassigned partitions\n\tfor _, partition := range unassignedPartitions {\n\t\t// skip if there is no potential consumer for the partition\n\t\tif len(partition2AllPotentialConsumers[partition]) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tsortedCurrentSubscriptions = assignPartition(partition, sortedCurrentSubscriptions, currentAssignment, consumer2AllPotentialPartitions, currentPartitionConsumer)\n\t}\n\n\t// narrow down the reassignment scope to only those partitions that can actually be reassigned\n\tfor partition := range partition2AllPotentialConsumers {\n\t\tif !canTopicPartitionParticipateInReassignment(partition, partition2AllPotentialConsumers) {\n\t\t\tsortedPartitions = removeTopicPartitionFromMemberAssignments(sortedPartitions, partition)\n\t\t}\n\t}\n\n\t// narrow down the reassignment scope to only those consumers that are subject to reassignment\n\tfixedAssignments := make(map[string][]topicPartitionAssignment)\n\tfor memberID := range consumer2AllPotentialPartitions {\n\t\tif !canConsumerParticipateInReassignment(memberID, currentAssignment, consumer2AllPotentialPartitions, partition2AllPotentialConsumers) {\n\t\t\tfixedAssignments[memberID] = currentAssignment[memberID]\n\t\t\tdelete(currentAssignment, memberID)\n\t\t\tsortedCurrentSubscriptions = sortMemberIDsByPartitionAssignments(currentAssignment)\n\t\t}\n\t}\n\n\t// create a deep copy of the current assignment so we can revert to it if we do not get a more balanced assignment later\n\tpreBalanceAssignment := deepCopyAssignment(currentAssignment)\n\tpreBalancePartitionConsumers := maps.Clone(currentPartitionConsumer)\n\n\treassignmentPerformed := s.performReassignments(sortedPartitions, currentAssignment, prevAssignment, sortedCurrentSubscriptions, consumer2AllPotentialPartitions, partition2AllPotentialConsumers, currentPartitionConsumer)\n\n\t// if we are not preserving existing assignments and we have made changes to the current assignment\n\t// make sure we are getting a more balanced assignment; otherwise, revert to previous assignment\n\tif !initializing && reassignmentPerformed && getBalanceScore(currentAssignment) >= getBalanceScore(preBalanceAssignment) {\n\t\tcurrentAssignment = deepCopyAssignment(preBalanceAssignment)\n\t\tclear(currentPartitionConsumer)\n\t\tmaps.Copy(currentPartitionConsumer, preBalancePartitionConsumers)\n\t}\n\n\t// add the fixed assignments (those that could not change) back\n\tmaps.Copy(currentAssignment, fixedAssignments)\n}\n\n// NewBalanceStrategyRoundRobin returns a round-robin balance strategy,\n// which assigns partitions to members in alternating order.\n// For example, there are two topics (t0, t1) and two consumer (m0, m1), and each topic has three partitions (p0, p1, p2):\n// M0: [t0p0, t0p2, t1p1]\n// M1: [t0p1, t1p0, t1p2]\nfunc NewBalanceStrategyRoundRobin() BalanceStrategy {\n\treturn new(roundRobinBalancer)\n}\n\n// Deprecated: use NewBalanceStrategyRoundRobin to avoid data race issue\nvar BalanceStrategyRoundRobin = NewBalanceStrategyRoundRobin()\n\ntype roundRobinBalancer struct{}\n\nfunc (b *roundRobinBalancer) Name() string {\n\treturn RoundRobinBalanceStrategyName\n}\n\nfunc (b *roundRobinBalancer) Plan(memberAndMetadata map[string]ConsumerGroupMemberMetadata, topics map[string][]int32) (BalanceStrategyPlan, error) {\n\tif len(memberAndMetadata) == 0 || len(topics) == 0 {\n\t\treturn nil, errors.New(\"members and topics are not provided\")\n\t}\n\t// sort partitions\n\tvar topicPartitions []topicAndPartition\n\tfor topic, partitions := range topics {\n\t\tfor _, partition := range partitions {\n\t\t\ttopicPartitions = append(topicPartitions, topicAndPartition{topic: topic, partition: partition})\n\t\t}\n\t}\n\tsort.SliceStable(topicPartitions, func(i, j int) bool {\n\t\tpi := topicPartitions[i]\n\t\tpj := topicPartitions[j]\n\t\treturn pi.comparedValue() < pj.comparedValue()\n\t})\n\n\t// sort members\n\tvar members []memberAndTopic\n\tfor memberID, meta := range memberAndMetadata {\n\t\tm := memberAndTopic{\n\t\t\tmemberID: memberID,\n\t\t\ttopics:   make(map[string]struct{}),\n\t\t}\n\t\tfor _, t := range meta.Topics {\n\t\t\tm.topics[t] = struct{}{}\n\t\t}\n\t\tmembers = append(members, m)\n\t}\n\tsort.SliceStable(members, func(i, j int) bool {\n\t\tmi := members[i]\n\t\tmj := members[j]\n\t\treturn mi.memberID < mj.memberID\n\t})\n\n\t// assign partitions\n\tplan := make(BalanceStrategyPlan, len(members))\n\ti := 0\n\tn := len(members)\n\tfor _, tp := range topicPartitions {\n\t\tm := members[i%n]\n\t\tfor !m.hasTopic(tp.topic) {\n\t\t\ti++\n\t\t\tm = members[i%n]\n\t\t}\n\t\tplan.Add(m.memberID, tp.topic, tp.partition)\n\t\ti++\n\t}\n\treturn plan, nil\n}\n\nfunc (b *roundRobinBalancer) AssignmentData(memberID string, topics map[string][]int32, generationID int32) ([]byte, error) {\n\treturn nil, nil // do nothing for now\n}\n\ntype topicAndPartition struct {\n\ttopic     string\n\tpartition int32\n}\n\nfunc (tp *topicAndPartition) comparedValue() string {\n\treturn fmt.Sprintf(\"%s-%d\", tp.topic, tp.partition)\n}\n\ntype memberAndTopic struct {\n\ttopics   map[string]struct{}\n\tmemberID string\n}\n\nfunc (m *memberAndTopic) hasTopic(topic string) bool {\n\t_, isExist := m.topics[topic]\n\treturn isExist\n}\n\n// Calculate the balance score of the given assignment, as the sum of assigned partitions size difference of all consumer pairs.\n// A perfectly balanced assignment (with all consumers getting the same number of partitions) has a balance score of 0.\n// Lower balance score indicates a more balanced assignment.\nfunc getBalanceScore(assignment map[string][]topicPartitionAssignment) int {\n\tconsumer2AssignmentSize := make(map[string]int, len(assignment))\n\tfor memberID, partitions := range assignment {\n\t\tconsumer2AssignmentSize[memberID] = len(partitions)\n\t}\n\n\tvar score float64\n\tfor memberID, consumerAssignmentSize := range consumer2AssignmentSize {\n\t\tdelete(consumer2AssignmentSize, memberID)\n\t\tfor _, otherConsumerAssignmentSize := range consumer2AssignmentSize {\n\t\t\tscore += math.Abs(float64(consumerAssignmentSize - otherConsumerAssignmentSize))\n\t\t}\n\t}\n\treturn int(score)\n}\n\n// Determine whether the current assignment plan is balanced.\nfunc isBalanced(currentAssignment map[string][]topicPartitionAssignment, allSubscriptions map[string][]topicPartitionAssignment) bool {\n\tsortedCurrentSubscriptions := sortMemberIDsByPartitionAssignments(currentAssignment)\n\tmin := len(currentAssignment[sortedCurrentSubscriptions[0]])\n\tmax := len(currentAssignment[sortedCurrentSubscriptions[len(sortedCurrentSubscriptions)-1]])\n\tif min >= max-1 {\n\t\t// if minimum and maximum numbers of partitions assigned to consumers differ by at most one return true\n\t\treturn true\n\t}\n\n\t// create a mapping from partitions to the consumer assigned to them\n\tallPartitions := make(map[topicPartitionAssignment]string)\n\tfor memberID, partitions := range currentAssignment {\n\t\tfor _, partition := range partitions {\n\t\t\tif _, exists := allPartitions[partition]; exists {\n\t\t\t\tLogger.Printf(\"Topic %s Partition %d is assigned more than one consumer\", partition.Topic, partition.Partition)\n\t\t\t}\n\t\t\tallPartitions[partition] = memberID\n\t\t}\n\t}\n\n\t// for each consumer that does not have all the topic partitions it can get make sure none of the topic partitions it\n\t// could but did not get cannot be moved to it (because that would break the balance)\n\tfor _, memberID := range sortedCurrentSubscriptions {\n\t\tconsumerPartitions := currentAssignment[memberID]\n\t\tconsumerPartitionCount := len(consumerPartitions)\n\n\t\t// skip if this consumer already has all the topic partitions it can get\n\t\tif consumerPartitionCount == len(allSubscriptions[memberID]) {\n\t\t\tcontinue\n\t\t}\n\n\t\t// otherwise make sure it cannot get any more\n\t\tpotentialTopicPartitions := allSubscriptions[memberID]\n\t\tfor _, partition := range potentialTopicPartitions {\n\t\t\tif !memberAssignmentsIncludeTopicPartition(currentAssignment[memberID], partition) {\n\t\t\t\totherConsumer := allPartitions[partition]\n\t\t\t\totherConsumerPartitionCount := len(currentAssignment[otherConsumer])\n\t\t\t\tif consumerPartitionCount < otherConsumerPartitionCount {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn true\n}\n\n// Reassign all topic partitions that need reassignment until balanced.\nfunc (s *stickyBalanceStrategy) performReassignments(reassignablePartitions []topicPartitionAssignment, currentAssignment map[string][]topicPartitionAssignment, prevAssignment map[topicPartitionAssignment]consumerGenerationPair, sortedCurrentSubscriptions []string, consumer2AllPotentialPartitions map[string][]topicPartitionAssignment, partition2AllPotentialConsumers map[topicPartitionAssignment][]string, currentPartitionConsumer map[topicPartitionAssignment]string) bool {\n\treassignmentPerformed := false\n\tmodified := false\n\n\t// repeat reassignment until no partition can be moved to improve the balance\n\tfor {\n\t\tmodified = false\n\t\t// reassign all reassignable partitions (starting from the partition with least potential consumers and if needed)\n\t\t// until the full list is processed or a balance is achieved\n\t\tfor _, partition := range reassignablePartitions {\n\t\t\tif isBalanced(currentAssignment, consumer2AllPotentialPartitions) {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\t// the partition must have at least two consumers\n\t\t\tif len(partition2AllPotentialConsumers[partition]) <= 1 {\n\t\t\t\tLogger.Printf(\"Expected more than one potential consumer for partition %s topic %d\", partition.Topic, partition.Partition)\n\t\t\t}\n\n\t\t\t// the partition must have a consumer\n\t\t\tconsumer := currentPartitionConsumer[partition]\n\t\t\tif consumer == \"\" {\n\t\t\t\tLogger.Printf(\"Expected topic %s partition %d to be assigned to a consumer\", partition.Topic, partition.Partition)\n\t\t\t}\n\n\t\t\tif _, exists := prevAssignment[partition]; exists {\n\t\t\t\tif len(currentAssignment[consumer]) > (len(currentAssignment[prevAssignment[partition].MemberID]) + 1) {\n\t\t\t\t\tsortedCurrentSubscriptions = s.reassignPartition(partition, currentAssignment, sortedCurrentSubscriptions, currentPartitionConsumer, prevAssignment[partition].MemberID)\n\t\t\t\t\treassignmentPerformed = true\n\t\t\t\t\tmodified = true\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// check if a better-suited consumer exists for the partition; if so, reassign it\n\t\t\tfor _, otherConsumer := range partition2AllPotentialConsumers[partition] {\n\t\t\t\tif len(currentAssignment[consumer]) > (len(currentAssignment[otherConsumer]) + 1) {\n\t\t\t\t\tsortedCurrentSubscriptions = s.reassignPartitionToNewConsumer(partition, currentAssignment, sortedCurrentSubscriptions, currentPartitionConsumer, consumer2AllPotentialPartitions)\n\t\t\t\t\treassignmentPerformed = true\n\t\t\t\t\tmodified = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif !modified {\n\t\t\treturn reassignmentPerformed\n\t\t}\n\t}\n}\n\n// Identify a new consumer for a topic partition and reassign it.\nfunc (s *stickyBalanceStrategy) reassignPartitionToNewConsumer(partition topicPartitionAssignment, currentAssignment map[string][]topicPartitionAssignment, sortedCurrentSubscriptions []string, currentPartitionConsumer map[topicPartitionAssignment]string, consumer2AllPotentialPartitions map[string][]topicPartitionAssignment) []string {\n\tfor _, anotherConsumer := range sortedCurrentSubscriptions {\n\t\tif memberAssignmentsIncludeTopicPartition(consumer2AllPotentialPartitions[anotherConsumer], partition) {\n\t\t\treturn s.reassignPartition(partition, currentAssignment, sortedCurrentSubscriptions, currentPartitionConsumer, anotherConsumer)\n\t\t}\n\t}\n\treturn sortedCurrentSubscriptions\n}\n\n// Reassign a specific partition to a new consumer\nfunc (s *stickyBalanceStrategy) reassignPartition(partition topicPartitionAssignment, currentAssignment map[string][]topicPartitionAssignment, sortedCurrentSubscriptions []string, currentPartitionConsumer map[topicPartitionAssignment]string, newConsumer string) []string {\n\tconsumer := currentPartitionConsumer[partition]\n\t// find the correct partition movement considering the stickiness requirement\n\tpartitionToBeMoved := s.movements.getTheActualPartitionToBeMoved(partition, consumer, newConsumer)\n\treturn s.processPartitionMovement(partitionToBeMoved, newConsumer, currentAssignment, sortedCurrentSubscriptions, currentPartitionConsumer)\n}\n\n// Track the movement of a topic partition after assignment\nfunc (s *stickyBalanceStrategy) processPartitionMovement(partition topicPartitionAssignment, newConsumer string, currentAssignment map[string][]topicPartitionAssignment, sortedCurrentSubscriptions []string, currentPartitionConsumer map[topicPartitionAssignment]string) []string {\n\toldConsumer := currentPartitionConsumer[partition]\n\ts.movements.movePartition(partition, oldConsumer, newConsumer)\n\n\tcurrentAssignment[oldConsumer] = removeTopicPartitionFromMemberAssignments(currentAssignment[oldConsumer], partition)\n\tcurrentAssignment[newConsumer] = append(currentAssignment[newConsumer], partition)\n\tcurrentPartitionConsumer[partition] = newConsumer\n\treturn sortMemberIDsByPartitionAssignments(currentAssignment)\n}\n\n// Determine whether a specific consumer should be considered for topic partition assignment.\nfunc canConsumerParticipateInReassignment(memberID string, currentAssignment map[string][]topicPartitionAssignment, consumer2AllPotentialPartitions map[string][]topicPartitionAssignment, partition2AllPotentialConsumers map[topicPartitionAssignment][]string) bool {\n\tcurrentPartitions := currentAssignment[memberID]\n\tcurrentAssignmentSize := len(currentPartitions)\n\tmaxAssignmentSize := len(consumer2AllPotentialPartitions[memberID])\n\tif currentAssignmentSize > maxAssignmentSize {\n\t\tLogger.Printf(\"The consumer %s is assigned more partitions than the maximum possible\", memberID)\n\t}\n\tif currentAssignmentSize < maxAssignmentSize {\n\t\t// if a consumer is not assigned all its potential partitions it is subject to reassignment\n\t\treturn true\n\t}\n\tfor _, partition := range currentPartitions {\n\t\tif canTopicPartitionParticipateInReassignment(partition, partition2AllPotentialConsumers) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// Only consider reassigning those topic partitions that have two or more potential consumers.\nfunc canTopicPartitionParticipateInReassignment(partition topicPartitionAssignment, partition2AllPotentialConsumers map[topicPartitionAssignment][]string) bool {\n\treturn len(partition2AllPotentialConsumers[partition]) >= 2\n}\n\n// The assignment should improve the overall balance of the partition assignments to consumers.\nfunc assignPartition(partition topicPartitionAssignment, sortedCurrentSubscriptions []string, currentAssignment map[string][]topicPartitionAssignment, consumer2AllPotentialPartitions map[string][]topicPartitionAssignment, currentPartitionConsumer map[topicPartitionAssignment]string) []string {\n\tfor _, memberID := range sortedCurrentSubscriptions {\n\t\tif memberAssignmentsIncludeTopicPartition(consumer2AllPotentialPartitions[memberID], partition) {\n\t\t\tcurrentAssignment[memberID] = append(currentAssignment[memberID], partition)\n\t\t\tcurrentPartitionConsumer[partition] = memberID\n\t\t\tbreak\n\t\t}\n\t}\n\treturn sortMemberIDsByPartitionAssignments(currentAssignment)\n}\n\n// Deserialize topic partition assignment data to aid with creation of a sticky assignment.\nfunc deserializeTopicPartitionAssignment(userDataBytes []byte) (StickyAssignorUserData, error) {\n\tuserDataV1 := &StickyAssignorUserDataV1{}\n\tif err := decode(userDataBytes, userDataV1, nil); err != nil {\n\t\tuserDataV0 := &StickyAssignorUserDataV0{}\n\t\tif err := decode(userDataBytes, userDataV0, nil); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn userDataV0, nil\n\t}\n\treturn userDataV1, nil\n}\n\n// filterAssignedPartitions returns a map of consumer group members to their list of previously-assigned topic partitions, limited\n// to those topic partitions currently reported by the Kafka cluster.\nfunc filterAssignedPartitions(currentAssignment map[string][]topicPartitionAssignment, partition2AllPotentialConsumers map[topicPartitionAssignment][]string) map[string][]topicPartitionAssignment {\n\tassignments := deepCopyAssignment(currentAssignment)\n\tfor memberID, partitions := range assignments {\n\t\t// perform in-place filtering\n\t\ti := 0\n\t\tfor _, partition := range partitions {\n\t\t\tif _, exists := partition2AllPotentialConsumers[partition]; exists {\n\t\t\t\tpartitions[i] = partition\n\t\t\t\ti++\n\t\t\t}\n\t\t}\n\t\tassignments[memberID] = partitions[:i]\n\t}\n\treturn assignments\n}\n\nfunc removeTopicPartitionFromMemberAssignments(assignments []topicPartitionAssignment, topic topicPartitionAssignment) []topicPartitionAssignment {\n\tfor i, assignment := range assignments {\n\t\tif assignment == topic {\n\t\t\treturn append(assignments[:i], assignments[i+1:]...)\n\t\t}\n\t}\n\treturn assignments\n}\n\nfunc memberAssignmentsIncludeTopicPartition(assignments []topicPartitionAssignment, topic topicPartitionAssignment) bool {\n\treturn slices.Contains(assignments, topic)\n}\n\nfunc sortPartitions(currentAssignment map[string][]topicPartitionAssignment, partitionsWithADifferentPreviousAssignment map[topicPartitionAssignment]consumerGenerationPair, isFreshAssignment bool, partition2AllPotentialConsumers map[topicPartitionAssignment][]string, consumer2AllPotentialPartitions map[string][]topicPartitionAssignment) []topicPartitionAssignment {\n\tunassignedPartitions := make(map[topicPartitionAssignment]bool, len(partition2AllPotentialConsumers))\n\tfor partition := range partition2AllPotentialConsumers {\n\t\tunassignedPartitions[partition] = true\n\t}\n\n\tsortedPartitions := make([]topicPartitionAssignment, 0)\n\tif !isFreshAssignment && areSubscriptionsIdentical(partition2AllPotentialConsumers, consumer2AllPotentialPartitions) {\n\t\t// if this is a reassignment and the subscriptions are identical (all consumers can consumer from all topics)\n\t\t// then we just need to simply list partitions in a round robin fashion (from consumers with\n\t\t// most assigned partitions to those with least)\n\t\tassignments := filterAssignedPartitions(currentAssignment, partition2AllPotentialConsumers)\n\n\t\t// use priority-queue to evaluate consumer group members in descending-order based on\n\t\t// the number of topic partition assignments (i.e. consumers with most assignments first)\n\t\tpq := make(assignmentPriorityQueue, len(assignments))\n\t\ti := 0\n\t\tfor consumerID, consumerAssignments := range assignments {\n\t\t\tpq[i] = &consumerGroupMember{\n\t\t\t\tid:          consumerID,\n\t\t\t\tassignments: consumerAssignments,\n\t\t\t}\n\t\t\ti++\n\t\t}\n\t\theap.Init(&pq)\n\n\t\t// loop until no consumer-group members remain\n\t\tfor pq.Len() != 0 {\n\t\t\tmember := pq[0]\n\n\t\t\t// partitions that were assigned to a different consumer last time\n\t\t\tvar prevPartitionIndex int\n\t\t\tfor i, partition := range member.assignments {\n\t\t\t\tif _, exists := partitionsWithADifferentPreviousAssignment[partition]; exists {\n\t\t\t\t\tprevPartitionIndex = i\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif len(member.assignments) > 0 {\n\t\t\t\tpartition := member.assignments[prevPartitionIndex]\n\t\t\t\tsortedPartitions = append(sortedPartitions, partition)\n\t\t\t\tdelete(unassignedPartitions, partition)\n\t\t\t\tif prevPartitionIndex == 0 {\n\t\t\t\t\tmember.assignments = member.assignments[1:]\n\t\t\t\t} else {\n\t\t\t\t\tmember.assignments = append(member.assignments[:prevPartitionIndex], member.assignments[prevPartitionIndex+1:]...)\n\t\t\t\t}\n\t\t\t\theap.Fix(&pq, 0)\n\t\t\t} else {\n\t\t\t\theap.Pop(&pq)\n\t\t\t}\n\t\t}\n\n\t\tfor partition := range unassignedPartitions {\n\t\t\tsortedPartitions = append(sortedPartitions, partition)\n\t\t}\n\t} else {\n\t\t// an ascending sorted set of topic partitions based on how many consumers can potentially use them\n\t\tsortedPartitions = sortPartitionsByPotentialConsumerAssignments(partition2AllPotentialConsumers)\n\t}\n\treturn sortedPartitions\n}\n\nfunc sortMemberIDsByPartitionAssignments(assignments map[string][]topicPartitionAssignment) []string {\n\t// sort the members by the number of partition assignments in ascending order\n\tsortedMemberIDs := make([]string, 0, len(assignments))\n\tfor memberID := range assignments {\n\t\tsortedMemberIDs = append(sortedMemberIDs, memberID)\n\t}\n\tsort.SliceStable(sortedMemberIDs, func(i, j int) bool {\n\t\tret := len(assignments[sortedMemberIDs[i]]) - len(assignments[sortedMemberIDs[j]])\n\t\tif ret == 0 {\n\t\t\treturn sortedMemberIDs[i] < sortedMemberIDs[j]\n\t\t}\n\t\treturn len(assignments[sortedMemberIDs[i]]) < len(assignments[sortedMemberIDs[j]])\n\t})\n\treturn sortedMemberIDs\n}\n\nfunc sortPartitionsByPotentialConsumerAssignments(partition2AllPotentialConsumers map[topicPartitionAssignment][]string) []topicPartitionAssignment {\n\t// sort the members by the number of partition assignments in descending order\n\tsortedPartionIDs := make([]topicPartitionAssignment, len(partition2AllPotentialConsumers))\n\ti := 0\n\tfor partition := range partition2AllPotentialConsumers {\n\t\tsortedPartionIDs[i] = partition\n\t\ti++\n\t}\n\tsort.Slice(sortedPartionIDs, func(i, j int) bool {\n\t\tif len(partition2AllPotentialConsumers[sortedPartionIDs[i]]) == len(partition2AllPotentialConsumers[sortedPartionIDs[j]]) {\n\t\t\tret := strings.Compare(sortedPartionIDs[i].Topic, sortedPartionIDs[j].Topic)\n\t\t\tif ret == 0 {\n\t\t\t\treturn sortedPartionIDs[i].Partition < sortedPartionIDs[j].Partition\n\t\t\t}\n\t\t\treturn ret < 0\n\t\t}\n\t\treturn len(partition2AllPotentialConsumers[sortedPartionIDs[i]]) < len(partition2AllPotentialConsumers[sortedPartionIDs[j]])\n\t})\n\treturn sortedPartionIDs\n}\n\nfunc deepCopyAssignment(assignment map[string][]topicPartitionAssignment) map[string][]topicPartitionAssignment {\n\tm := make(map[string][]topicPartitionAssignment, len(assignment))\n\tfor memberID, subscriptions := range assignment {\n\t\tm[memberID] = append(subscriptions[:0:0], subscriptions...)\n\t}\n\treturn m\n}\n\nfunc areSubscriptionsIdentical(partition2AllPotentialConsumers map[topicPartitionAssignment][]string, consumer2AllPotentialPartitions map[string][]topicPartitionAssignment) bool {\n\tcurMembers := make(map[string]int)\n\tfor _, cur := range partition2AllPotentialConsumers {\n\t\tif len(curMembers) == 0 {\n\t\t\tfor _, curMembersElem := range cur {\n\t\t\t\tcurMembers[curMembersElem]++\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tif len(curMembers) != len(cur) {\n\t\t\treturn false\n\t\t}\n\n\t\tyMap := make(map[string]int)\n\t\tfor _, yElem := range cur {\n\t\t\tyMap[yElem]++\n\t\t}\n\n\t\tfor curMembersMapKey, curMembersMapVal := range curMembers {\n\t\t\tif yMap[curMembersMapKey] != curMembersMapVal {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n\n\tcurPartitions := make(map[topicPartitionAssignment]int)\n\tfor _, cur := range consumer2AllPotentialPartitions {\n\t\tif len(curPartitions) == 0 {\n\t\t\tfor _, curPartitionElem := range cur {\n\t\t\t\tcurPartitions[curPartitionElem]++\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tif len(curPartitions) != len(cur) {\n\t\t\treturn false\n\t\t}\n\n\t\tyMap := make(map[topicPartitionAssignment]int)\n\t\tfor _, yElem := range cur {\n\t\t\tyMap[yElem]++\n\t\t}\n\n\t\tfor curMembersMapKey, curMembersMapVal := range curPartitions {\n\t\t\tif yMap[curMembersMapKey] != curMembersMapVal {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n\treturn true\n}\n\n// We need to process subscriptions' user data with each consumer's reported generation in mind\n// higher generations overwrite lower generations in case of a conflict\n// note that a conflict could exist only if user data is for different generations\nfunc prepopulateCurrentAssignments(members map[string]ConsumerGroupMemberMetadata) (map[string][]topicPartitionAssignment, map[topicPartitionAssignment]consumerGenerationPair, error) {\n\tcurrentAssignment := make(map[string][]topicPartitionAssignment)\n\tprevAssignment := make(map[topicPartitionAssignment]consumerGenerationPair)\n\n\t// for each partition we create a sorted map of its consumers by generation\n\tsortedPartitionConsumersByGeneration := make(map[topicPartitionAssignment]map[int]string)\n\tfor memberID, meta := range members {\n\t\tconsumerUserData, err := deserializeTopicPartitionAssignment(meta.UserData)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t\tfor _, partition := range consumerUserData.partitions() {\n\t\t\tif consumers, exists := sortedPartitionConsumersByGeneration[partition]; exists {\n\t\t\t\tif consumerUserData.hasGeneration() {\n\t\t\t\t\tif _, generationExists := consumers[consumerUserData.generation()]; generationExists {\n\t\t\t\t\t\t// same partition is assigned to two consumers during the same rebalance.\n\t\t\t\t\t\t// log a warning and skip this record\n\t\t\t\t\t\tLogger.Printf(\"Topic %s Partition %d is assigned to multiple consumers following sticky assignment generation %d\", partition.Topic, partition.Partition, consumerUserData.generation())\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconsumers[consumerUserData.generation()] = memberID\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tconsumers[defaultGeneration] = memberID\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tgeneration := defaultGeneration\n\t\t\t\tif consumerUserData.hasGeneration() {\n\t\t\t\t\tgeneration = consumerUserData.generation()\n\t\t\t\t}\n\t\t\t\tsortedPartitionConsumersByGeneration[partition] = map[int]string{generation: memberID}\n\t\t\t}\n\t\t}\n\t}\n\n\t// prevAssignment holds the prior ConsumerGenerationPair (before current) of each partition\n\t// current and previous consumers are the last two consumers of each partition in the above sorted map\n\tfor partition, consumers := range sortedPartitionConsumersByGeneration {\n\t\t// sort consumers by generation in decreasing order\n\t\tvar generations []int\n\t\tfor generation := range consumers {\n\t\t\tgenerations = append(generations, generation)\n\t\t}\n\t\tsort.Sort(sort.Reverse(sort.IntSlice(generations)))\n\n\t\tconsumer := consumers[generations[0]]\n\t\tif _, exists := currentAssignment[consumer]; !exists {\n\t\t\tcurrentAssignment[consumer] = []topicPartitionAssignment{partition}\n\t\t} else {\n\t\t\tcurrentAssignment[consumer] = append(currentAssignment[consumer], partition)\n\t\t}\n\n\t\t// check for previous assignment, if any\n\t\tif len(generations) > 1 {\n\t\t\tprevAssignment[partition] = consumerGenerationPair{\n\t\t\t\tMemberID:   consumers[generations[1]],\n\t\t\t\tGeneration: generations[1],\n\t\t\t}\n\t\t}\n\t}\n\treturn currentAssignment, prevAssignment, nil\n}\n\ntype consumerGenerationPair struct {\n\tMemberID   string\n\tGeneration int\n}\n\n// consumerPair represents a pair of Kafka consumer ids involved in a partition reassignment.\ntype consumerPair struct {\n\tSrcMemberID string\n\tDstMemberID string\n}\n\n// partitionMovements maintains some data structures to simplify lookup of partition movements among consumers.\ntype partitionMovements struct {\n\tPartitionMovementsByTopic map[string]map[consumerPair]map[topicPartitionAssignment]bool\n\tMovements                 map[topicPartitionAssignment]consumerPair\n}\n\nfunc (p *partitionMovements) removeMovementRecordOfPartition(partition topicPartitionAssignment) consumerPair {\n\tpair := p.Movements[partition]\n\tdelete(p.Movements, partition)\n\n\tpartitionMovementsForThisTopic := p.PartitionMovementsByTopic[partition.Topic]\n\tdelete(partitionMovementsForThisTopic[pair], partition)\n\tif len(partitionMovementsForThisTopic[pair]) == 0 {\n\t\tdelete(partitionMovementsForThisTopic, pair)\n\t}\n\tif len(p.PartitionMovementsByTopic[partition.Topic]) == 0 {\n\t\tdelete(p.PartitionMovementsByTopic, partition.Topic)\n\t}\n\treturn pair\n}\n\nfunc (p *partitionMovements) addPartitionMovementRecord(partition topicPartitionAssignment, pair consumerPair) {\n\tp.Movements[partition] = pair\n\tif _, exists := p.PartitionMovementsByTopic[partition.Topic]; !exists {\n\t\tp.PartitionMovementsByTopic[partition.Topic] = make(map[consumerPair]map[topicPartitionAssignment]bool)\n\t}\n\tpartitionMovementsForThisTopic := p.PartitionMovementsByTopic[partition.Topic]\n\tif _, exists := partitionMovementsForThisTopic[pair]; !exists {\n\t\tpartitionMovementsForThisTopic[pair] = make(map[topicPartitionAssignment]bool)\n\t}\n\tpartitionMovementsForThisTopic[pair][partition] = true\n}\n\nfunc (p *partitionMovements) movePartition(partition topicPartitionAssignment, oldConsumer, newConsumer string) {\n\tpair := consumerPair{\n\t\tSrcMemberID: oldConsumer,\n\t\tDstMemberID: newConsumer,\n\t}\n\tif _, exists := p.Movements[partition]; exists {\n\t\t// this partition has previously moved\n\t\texistingPair := p.removeMovementRecordOfPartition(partition)\n\t\tif existingPair.DstMemberID != oldConsumer {\n\t\t\tLogger.Printf(\"Existing pair DstMemberID %s was not equal to the oldConsumer ID %s\", existingPair.DstMemberID, oldConsumer)\n\t\t}\n\t\tif existingPair.SrcMemberID != newConsumer {\n\t\t\t// the partition is not moving back to its previous consumer\n\t\t\tp.addPartitionMovementRecord(partition, consumerPair{\n\t\t\t\tSrcMemberID: existingPair.SrcMemberID,\n\t\t\t\tDstMemberID: newConsumer,\n\t\t\t})\n\t\t}\n\t} else {\n\t\tp.addPartitionMovementRecord(partition, pair)\n\t}\n}\n\nfunc (p *partitionMovements) getTheActualPartitionToBeMoved(partition topicPartitionAssignment, oldConsumer, newConsumer string) topicPartitionAssignment {\n\tif _, exists := p.PartitionMovementsByTopic[partition.Topic]; !exists {\n\t\treturn partition\n\t}\n\tif _, exists := p.Movements[partition]; exists {\n\t\t// this partition has previously moved\n\t\tif oldConsumer != p.Movements[partition].DstMemberID {\n\t\t\tLogger.Printf(\"Partition movement DstMemberID %s was not equal to the oldConsumer ID %s\", p.Movements[partition].DstMemberID, oldConsumer)\n\t\t}\n\t\toldConsumer = p.Movements[partition].SrcMemberID\n\t}\n\n\tpartitionMovementsForThisTopic := p.PartitionMovementsByTopic[partition.Topic]\n\treversePair := consumerPair{\n\t\tSrcMemberID: newConsumer,\n\t\tDstMemberID: oldConsumer,\n\t}\n\tif _, exists := partitionMovementsForThisTopic[reversePair]; !exists {\n\t\treturn partition\n\t}\n\tvar reversePairPartition topicPartitionAssignment\n\tfor otherPartition := range partitionMovementsForThisTopic[reversePair] {\n\t\treversePairPartition = otherPartition\n\t}\n\treturn reversePairPartition\n}\n\n//lint:ignore U1000 // this is used but only in unittests as a helper (which are excluded by the integration build tag)\nfunc (p *partitionMovements) isLinked(src, dst string, pairs []consumerPair, currentPath []string) ([]string, bool) {\n\tif src == dst {\n\t\treturn currentPath, false\n\t}\n\tif len(pairs) == 0 {\n\t\treturn currentPath, false\n\t}\n\tfor _, pair := range pairs {\n\t\tif src == pair.SrcMemberID && dst == pair.DstMemberID {\n\t\t\tcurrentPath = append(currentPath, src, dst)\n\t\t\treturn currentPath, true\n\t\t}\n\t}\n\n\tfor _, pair := range pairs {\n\t\tif pair.SrcMemberID != src {\n\t\t\tcontinue\n\t\t}\n\t\t// create a deep copy of the pairs, excluding the current pair\n\t\treducedSet := make([]consumerPair, len(pairs)-1)\n\t\ti := 0\n\t\tfor _, p := range pairs {\n\t\t\tif p != pair {\n\t\t\t\treducedSet[i] = pair\n\t\t\t\ti++\n\t\t\t}\n\t\t}\n\n\t\tcurrentPath = append(currentPath, pair.SrcMemberID)\n\t\treturn p.isLinked(pair.DstMemberID, dst, reducedSet, currentPath)\n\t}\n\treturn currentPath, false\n}\n\n//lint:ignore U1000 // this is used but only in unittests as a helper (which are excluded by the integration build tag)\nfunc (p *partitionMovements) in(cycle []string, cycles [][]string) bool {\n\tsuperCycle := make([]string, len(cycle)-1)\n\tfor i := 0; i < len(cycle)-1; i++ {\n\t\tsuperCycle[i] = cycle[i]\n\t}\n\tsuperCycle = append(superCycle, cycle...)\n\tfor _, foundCycle := range cycles {\n\t\tif len(foundCycle) == len(cycle) && indexOfSubList(superCycle, foundCycle) != -1 {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n//lint:ignore U1000 // this is used but only in unittests as a helper (which are excluded by the integration build tag)\nfunc (p *partitionMovements) hasCycles(pairs []consumerPair) bool {\n\tcycles := make([][]string, 0)\n\tfor _, pair := range pairs {\n\t\t// create a deep copy of the pairs, excluding the current pair\n\t\treducedPairs := make([]consumerPair, len(pairs)-1)\n\t\ti := 0\n\t\tfor _, p := range pairs {\n\t\t\tif p != pair {\n\t\t\t\treducedPairs[i] = pair\n\t\t\t\ti++\n\t\t\t}\n\t\t}\n\t\tif path, linked := p.isLinked(pair.DstMemberID, pair.SrcMemberID, reducedPairs, []string{pair.SrcMemberID}); linked {\n\t\t\tif !p.in(path, cycles) {\n\t\t\t\tcycles = append(cycles, path)\n\t\t\t\tLogger.Printf(\"A cycle of length %d was found: %v\", len(path)-1, path)\n\t\t\t}\n\t\t}\n\t}\n\n\t// for now we want to make sure there is no partition movements of the same topic between a pair of consumers.\n\t// the odds of finding a cycle among more than two consumers seem to be very low (according to various randomized\n\t// tests with the given sticky algorithm) that it should not worth the added complexity of handling those cases.\n\tfor _, cycle := range cycles {\n\t\tif len(cycle) == 3 {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n//lint:ignore U1000 // this is used but only in unittests as a helper (which are excluded by the integration build tag)\nfunc (p *partitionMovements) isSticky() bool {\n\tfor topic, movements := range p.PartitionMovementsByTopic {\n\t\tmovementPairs := make([]consumerPair, len(movements))\n\t\ti := 0\n\t\tfor pair := range movements {\n\t\t\tmovementPairs[i] = pair\n\t\t\ti++\n\t\t}\n\t\tif p.hasCycles(movementPairs) {\n\t\t\tLogger.Printf(\"Stickiness is violated for topic %s\", topic)\n\t\t\tLogger.Printf(\"Partition movements for this topic occurred among the following consumer pairs: %v\", movements)\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n//lint:ignore U1000 // this is used but only in unittests as a helper (which are excluded by the integration build tag)\nfunc indexOfSubList(source []string, target []string) int {\n\ttargetSize := len(target)\n\tmaxCandidate := len(source) - targetSize\nnextCand:\n\tfor candidate := 0; candidate <= maxCandidate; candidate++ {\n\t\tj := candidate\n\t\tfor i := 0; i < targetSize; i++ {\n\t\t\tif target[i] != source[j] {\n\t\t\t\t// Element mismatch, try next cand\n\t\t\t\tcontinue nextCand\n\t\t\t}\n\t\t\tj++\n\t\t}\n\t\t// All elements of candidate matched target\n\t\treturn candidate\n\t}\n\treturn -1\n}\n\ntype consumerGroupMember struct {\n\tid          string\n\tassignments []topicPartitionAssignment\n}\n\n// assignmentPriorityQueue is a priority-queue of consumer group members that is sorted\n// in descending order (most assignments to least assignments).\ntype assignmentPriorityQueue []*consumerGroupMember\n\nfunc (pq assignmentPriorityQueue) Len() int { return len(pq) }\n\nfunc (pq assignmentPriorityQueue) Less(i, j int) bool {\n\t// order assignment priority queue in descending order using assignment-count/member-id\n\tif len(pq[i].assignments) == len(pq[j].assignments) {\n\t\treturn pq[i].id > pq[j].id\n\t}\n\treturn len(pq[i].assignments) > len(pq[j].assignments)\n}\n\nfunc (pq assignmentPriorityQueue) Swap(i, j int) {\n\tpq[i], pq[j] = pq[j], pq[i]\n}\n\nfunc (pq *assignmentPriorityQueue) Push(x interface{}) {\n\tmember := x.(*consumerGroupMember)\n\t*pq = append(*pq, member)\n}\n\nfunc (pq *assignmentPriorityQueue) Pop() interface{} {\n\told := *pq\n\tn := len(old)\n\tmember := old[n-1]\n\t*pq = old[0 : n-1]\n\treturn member\n}\n"
  },
  {
    "path": "balance_strategy_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"math\"\n\t\"math/rand\"\n\t\"reflect\"\n\t\"slices\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestBalanceStrategyRange(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tmembers  map[string][]string\n\t\ttopics   map[string][]int32\n\t\texpected BalanceStrategyPlan\n\t}{\n\t\t{\n\t\t\tname:    \"2 members, 2 topics, 4 partitions each\",\n\t\t\tmembers: map[string][]string{\"M1\": {\"T1\", \"T2\"}, \"M2\": {\"T1\", \"T2\"}},\n\t\t\ttopics:  map[string][]int32{\"T1\": {0, 1, 2, 3}, \"T2\": {0, 1, 2, 3}},\n\t\t\texpected: BalanceStrategyPlan{\n\t\t\t\t\"M1\": map[string][]int32{\"T1\": {0, 1}, \"T2\": {0, 1}},\n\t\t\t\t\"M2\": map[string][]int32{\"T1\": {2, 3}, \"T2\": {2, 3}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"2 members, 2 topics, 4 partitions each (different member ids)\",\n\t\t\tmembers: map[string][]string{\"M3\": {\"T1\", \"T2\"}, \"M4\": {\"T1\", \"T2\"}},\n\t\t\ttopics:  map[string][]int32{\"T1\": {0, 1, 2, 3}, \"T2\": {0, 1, 2, 3}},\n\t\t\texpected: BalanceStrategyPlan{\n\t\t\t\t\"M3\": map[string][]int32{\"T1\": {0, 1}, \"T2\": {0, 1}},\n\t\t\t\t\"M4\": map[string][]int32{\"T1\": {2, 3}, \"T2\": {2, 3}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"3 members, 1 topic, 1 partition each\",\n\t\t\tmembers: map[string][]string{\"M1\": {\"T1\"}, \"M2\": {\"T1\"}, \"M3\": {\"T1\"}},\n\t\t\ttopics:  map[string][]int32{\"T1\": {0}},\n\t\t\texpected: BalanceStrategyPlan{\n\t\t\t\t\"M1\": map[string][]int32{\"T1\": {0}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"2 members, 2 topics, 3 partitions each\",\n\t\t\tmembers: map[string][]string{\"M1\": {\"T1\", \"T2\"}, \"M2\": {\"T1\", \"T2\"}},\n\t\t\ttopics:  map[string][]int32{\"T1\": {0, 1, 2}, \"T2\": {0, 1, 2}},\n\t\t\texpected: BalanceStrategyPlan{\n\t\t\t\t\"M1\": map[string][]int32{\"T1\": {0, 1}, \"T2\": {0, 1}},\n\t\t\t\t\"M2\": map[string][]int32{\"T1\": {2}, \"T2\": {2}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"2 members, 2 topics, different subscriptions\",\n\t\t\tmembers: map[string][]string{\"M1\": {\"T1\"}, \"M2\": {\"T1\", \"T2\"}},\n\t\t\ttopics:  map[string][]int32{\"T1\": {0, 1}, \"T2\": {0, 1}},\n\t\t\texpected: BalanceStrategyPlan{\n\t\t\t\t\"M1\": map[string][]int32{\"T1\": {0}},\n\t\t\t\t\"M2\": map[string][]int32{\"T1\": {1}, \"T2\": {0, 1}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"2 members, 1 topic with duplicate assignments, 8 partitions each\",\n\t\t\tmembers: map[string][]string{\"M1\": {\"T1\", \"T1\", \"T1\", \"T1\", \"T1\", \"T1\", \"T1\", \"T1\"}, \"M2\": {\"T1\", \"T1\", \"T1\", \"T1\", \"T1\", \"T1\", \"T1\", \"T1\"}},\n\t\t\ttopics:  map[string][]int32{\"T1\": {0, 1, 2, 3, 4, 5, 6, 7}},\n\t\t\texpected: BalanceStrategyPlan{\n\t\t\t\t\"M1\": map[string][]int32{\"T1\": {0, 1, 2, 3}},\n\t\t\t\t\"M2\": map[string][]int32{\"T1\": {4, 5, 6, 7}},\n\t\t\t},\n\t\t},\n\t}\n\n\tstrategy := NewBalanceStrategyRange()\n\tif strategy.Name() != \"range\" {\n\t\tt.Errorf(\"Unexpected stategy name\\nexpected: range\\nactual: %v\", strategy.Name())\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmembers := make(map[string]ConsumerGroupMemberMetadata)\n\t\t\tfor memberID, topics := range test.members {\n\t\t\t\tmembers[memberID] = ConsumerGroupMemberMetadata{Topics: topics}\n\t\t\t}\n\n\t\t\tactual, err := strategy.Plan(members, test.topics)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Unexpected error %v\", err)\n\t\t\t} else if !reflect.DeepEqual(actual, test.expected) {\n\t\t\t\tt.Errorf(\"Plan does not match expectation\\nexpected: %#v\\nactual: %#v\", test.expected, actual)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBalanceStrategyRangeAssignmentData(t *testing.T) {\n\tstrategy := NewBalanceStrategyRange()\n\n\tmembers := make(map[string]ConsumerGroupMemberMetadata, 2)\n\tmembers[\"consumer1\"] = ConsumerGroupMemberMetadata{\n\t\tTopics: []string{\"topic1\"},\n\t}\n\tmembers[\"consumer2\"] = ConsumerGroupMemberMetadata{\n\t\tTopics: []string{\"topic1\"},\n\t}\n\n\tactual, err := strategy.AssignmentData(\"consumer1\", map[string][]int32{\"topic1\": {0, 1}}, 1)\n\tif err != nil {\n\t\tt.Errorf(\"Error building assignment data: %v\", err)\n\t}\n\tif actual != nil {\n\t\tt.Error(\"Invalid assignment data returned from AssignmentData\")\n\t}\n}\n\nfunc TestBalanceStrategyRoundRobin(t *testing.T) {\n\ttests := []struct {\n\t\tmembers  map[string][]string\n\t\ttopics   map[string][]int32\n\t\texpected BalanceStrategyPlan\n\t}{\n\t\t{\n\t\t\tmembers: map[string][]string{\"M1\": {\"T1\", \"T2\", \"T3\"}, \"M2\": {\"T1\", \"T2\", \"T3\"}},\n\t\t\ttopics:  map[string][]int32{\"T1\": {0}, \"T2\": {0}, \"T3\": {0}},\n\t\t\texpected: BalanceStrategyPlan{\n\t\t\t\t\"M1\": map[string][]int32{\"T1\": {0}, \"T3\": {0}},\n\t\t\t\t\"M2\": map[string][]int32{\"T2\": {0}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tmembers: map[string][]string{\"M1\": {\"T1\", \"T2\", \"T3\"}, \"M2\": {\"T1\", \"T2\", \"T3\"}},\n\t\t\ttopics:  map[string][]int32{\"T1\": {0}, \"T2\": {0, 1}, \"T3\": {0, 1, 2, 3}},\n\t\t\texpected: BalanceStrategyPlan{\n\t\t\t\t\"M1\": map[string][]int32{\"T1\": {0}, \"T2\": {1}, \"T3\": {1, 3}},\n\t\t\t\t\"M2\": map[string][]int32{\"T2\": {0}, \"T3\": {0, 2}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tmembers: map[string][]string{\"M1\": {\"T1\"}, \"M2\": {\"T1\"}},\n\t\t\ttopics:  map[string][]int32{\"T1\": {0}},\n\t\t\texpected: BalanceStrategyPlan{\n\t\t\t\t\"M1\": map[string][]int32{\"T1\": {0}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tmembers: map[string][]string{\"M1\": {\"T1\", \"T2\", \"T3\"}},\n\t\t\ttopics:  map[string][]int32{\"T1\": {0}, \"T2\": {0}, \"T3\": {0, 1, 2}},\n\t\t\texpected: BalanceStrategyPlan{\n\t\t\t\t\"M1\": map[string][]int32{\"T1\": {0}, \"T2\": {0}, \"T3\": {0, 1, 2}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tmembers: map[string][]string{\"M1\": {\"T1\", \"T2\", \"T3\"}, \"M2\": {\"T1\"}},\n\t\t\ttopics:  map[string][]int32{\"T1\": {0}, \"T2\": {0}, \"T3\": {0}},\n\t\t\texpected: BalanceStrategyPlan{\n\t\t\t\t\"M1\": map[string][]int32{\"T1\": {0}, \"T2\": {0}, \"T3\": {0}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tmembers: map[string][]string{\"M1\": {\"T1\", \"T2\", \"T3\"}, \"M2\": {\"T1\", \"T3\"}},\n\t\t\ttopics:  map[string][]int32{\"T1\": {0}, \"T2\": {0}, \"T3\": {0}},\n\t\t\texpected: BalanceStrategyPlan{\n\t\t\t\t\"M1\": map[string][]int32{\"T1\": {0}, \"T2\": {0}},\n\t\t\t\t\"M2\": map[string][]int32{\"T3\": {0}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tmembers: map[string][]string{\"M\": {\"T1\", \"T2\", \"TT2\"}, \"M2\": {\"T1\", \"T2\", \"TT2\"}, \"M3\": {\"T1\", \"T2\", \"TT2\"}},\n\t\t\ttopics:  map[string][]int32{\"T1\": {0}, \"T2\": {0}, \"TT2\": {0}},\n\t\t\texpected: BalanceStrategyPlan{\n\t\t\t\t\"M\":  map[string][]int32{\"T1\": {0}},\n\t\t\t\t\"M2\": map[string][]int32{\"T2\": {0}},\n\t\t\t\t\"M3\": map[string][]int32{\"TT2\": {0}},\n\t\t\t},\n\t\t},\n\t}\n\n\tstrategy := NewBalanceStrategyRoundRobin()\n\tif strategy.Name() != \"roundrobin\" {\n\t\tt.Errorf(\"Unexpected strategy name\\nexpected: roundrobin\\nactual: %v\", strategy.Name())\n\t}\n\n\tfor _, test := range tests {\n\t\tmembers := make(map[string]ConsumerGroupMemberMetadata)\n\t\tfor memberID, topics := range test.members {\n\t\t\tmembers[memberID] = ConsumerGroupMemberMetadata{Topics: topics}\n\t\t}\n\n\t\tactual, err := strategy.Plan(members, test.topics)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"Unexpected error %v\", err)\n\t\t} else if !reflect.DeepEqual(actual, test.expected) {\n\t\t\tt.Errorf(\"Plan does not match expectation\\nexpected: %#v\\nactual: %#v\", test.expected, actual)\n\t\t}\n\t}\n}\n\nfunc Test_deserializeTopicPartitionAssignment(t *testing.T) {\n\ttype args struct {\n\t\tuserDataBytes []byte\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\targs    args\n\t\twant    StickyAssignorUserData\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"Nil userdata bytes\",\n\t\t\targs: args{},\n\t\t\twant: &StickyAssignorUserDataV1{},\n\t\t},\n\t\t{\n\t\t\tname: \"Non-empty invalid userdata bytes\",\n\t\t\targs: args{\n\t\t\t\tuserDataBytes: []byte{\n\t\t\t\t\t0x00, 0x00,\n\t\t\t\t\t0x00, 0x00, 0x00, 0x01,\n\t\t\t\t\t0x00, 0x03, 'f', 'o', 'o',\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Valid v0 userdata bytes\",\n\t\t\targs: args{\n\t\t\t\tuserDataBytes: []byte{\n\t\t\t\t\t0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x74, 0x30,\n\t\t\t\t\t0x33, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n\t\t\t\t\t0x05,\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: &StickyAssignorUserDataV0{\n\t\t\t\tTopics: map[string][]int32{\"t03\": {5}},\n\t\t\t\ttopicPartitions: []topicPartitionAssignment{\n\t\t\t\t\t{\n\t\t\t\t\t\tTopic:     \"t03\",\n\t\t\t\t\t\tPartition: 5,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Valid v1 userdata bytes\",\n\t\t\targs: args{\n\t\t\t\tuserDataBytes: []byte{\n\t\t\t\t\t0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x74, 0x30,\n\t\t\t\t\t0x36, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,\n\t\t\t\t\t0x00, 0x00, 0x00, 0x00, 0x04, 0xff, 0xff, 0xff,\n\t\t\t\t\t0xff,\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: &StickyAssignorUserDataV1{\n\t\t\t\tTopics:     map[string][]int32{\"t06\": {0, 4}},\n\t\t\t\tGeneration: -1,\n\t\t\t\ttopicPartitions: []topicPartitionAssignment{\n\t\t\t\t\t{\n\t\t\t\t\t\tTopic:     \"t06\",\n\t\t\t\t\t\tPartition: 0,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tTopic:     \"t06\",\n\t\t\t\t\t\tPartition: 4,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := deserializeTopicPartitionAssignment(tt.args.userDataBytes)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"deserializeTopicPartitionAssignment() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"deserializeTopicPartitionAssignment() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBalanceStrategyRoundRobinAssignmentData(t *testing.T) {\n\tstrategy := NewBalanceStrategyRoundRobin()\n\n\tmembers := make(map[string]ConsumerGroupMemberMetadata, 2)\n\tmembers[\"consumer1\"] = ConsumerGroupMemberMetadata{\n\t\tTopics: []string{\"topic1\"},\n\t}\n\tmembers[\"consumer2\"] = ConsumerGroupMemberMetadata{\n\t\tTopics: []string{\"topic1\"},\n\t}\n\n\tactual, err := strategy.AssignmentData(\"consumer1\", map[string][]int32{\"topic1\": {0, 1}}, 1)\n\tif err != nil {\n\t\tt.Errorf(\"Error building assignment data: %v\", err)\n\t}\n\tif actual != nil {\n\t\tt.Error(\"Invalid assignment data returned from AssignmentData\")\n\t}\n}\n\nfunc Test_prepopulateCurrentAssignments(t *testing.T) {\n\ttype args struct {\n\t\tmembers map[string]ConsumerGroupMemberMetadata\n\t}\n\ttests := []struct {\n\t\tname                   string\n\t\targs                   args\n\t\twantCurrentAssignments map[string][]topicPartitionAssignment\n\t\twantPrevAssignments    map[topicPartitionAssignment]consumerGenerationPair\n\t\twantErr                bool\n\t}{\n\t\t{\n\t\t\tname:                   \"Empty map\",\n\t\t\twantCurrentAssignments: map[string][]topicPartitionAssignment{},\n\t\t\twantPrevAssignments:    map[topicPartitionAssignment]consumerGenerationPair{},\n\t\t},\n\t\t{\n\t\t\tname: \"Single consumer\",\n\t\t\targs: args{\n\t\t\t\tmembers: map[string]ConsumerGroupMemberMetadata{\n\t\t\t\t\t\"c01\": {\n\t\t\t\t\t\tVersion: 2,\n\t\t\t\t\t\tUserData: []byte{\n\t\t\t\t\t\t\t0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x74, 0x30,\n\t\t\t\t\t\t\t0x36, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,\n\t\t\t\t\t\t\t0x00, 0x00, 0x00, 0x00, 0x04, 0xff, 0xff, 0xff,\n\t\t\t\t\t\t\t0xff,\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\twantCurrentAssignments: map[string][]topicPartitionAssignment{\n\t\t\t\t\"c01\": {\n\t\t\t\t\t{\n\t\t\t\t\t\tTopic:     \"t06\",\n\t\t\t\t\t\tPartition: 0,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tTopic:     \"t06\",\n\t\t\t\t\t\tPartition: 4,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantPrevAssignments: map[topicPartitionAssignment]consumerGenerationPair{},\n\t\t},\n\t\t{\n\t\t\tname: \"Duplicate consumer assignments in metadata\",\n\t\t\targs: args{\n\t\t\t\tmembers: map[string]ConsumerGroupMemberMetadata{\n\t\t\t\t\t\"c01\": {\n\t\t\t\t\t\tVersion: 2,\n\t\t\t\t\t\tUserData: []byte{\n\t\t\t\t\t\t\t0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x74, 0x30,\n\t\t\t\t\t\t\t0x36, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,\n\t\t\t\t\t\t\t0x00, 0x00, 0x00, 0x00, 0x04, 0xff, 0xff, 0xff,\n\t\t\t\t\t\t\t0xff,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t\"c02\": {\n\t\t\t\t\t\tVersion: 2,\n\t\t\t\t\t\tUserData: []byte{\n\t\t\t\t\t\t\t0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x74, 0x30,\n\t\t\t\t\t\t\t0x36, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,\n\t\t\t\t\t\t\t0x00, 0x00, 0x00, 0x00, 0x04, 0xff, 0xff, 0xff,\n\t\t\t\t\t\t\t0xff,\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\twantCurrentAssignments: map[string][]topicPartitionAssignment{\n\t\t\t\t\"c01\": {\n\t\t\t\t\t{\n\t\t\t\t\t\tTopic:     \"t06\",\n\t\t\t\t\t\tPartition: 0,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tTopic:     \"t06\",\n\t\t\t\t\t\tPartition: 4,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantPrevAssignments: map[topicPartitionAssignment]consumerGenerationPair{},\n\t\t},\n\t\t{\n\t\t\tname: \"Different generations (5, 6) of consumer assignments in metadata\",\n\t\t\targs: args{\n\t\t\t\tmembers: map[string]ConsumerGroupMemberMetadata{\n\t\t\t\t\t\"c01\": {\n\t\t\t\t\t\tVersion: 2,\n\t\t\t\t\t\tUserData: []byte{\n\t\t\t\t\t\t\t0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x74, 0x30,\n\t\t\t\t\t\t\t0x36, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,\n\t\t\t\t\t\t\t0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,\n\t\t\t\t\t\t\t0x05,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t\"c02\": {\n\t\t\t\t\t\tVersion: 2,\n\t\t\t\t\t\tUserData: []byte{\n\t\t\t\t\t\t\t0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x74, 0x30,\n\t\t\t\t\t\t\t0x36, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,\n\t\t\t\t\t\t\t0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,\n\t\t\t\t\t\t\t0x06,\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\twantCurrentAssignments: map[string][]topicPartitionAssignment{\n\t\t\t\t\"c01\": {\n\t\t\t\t\t{\n\t\t\t\t\t\tTopic:     \"t06\",\n\t\t\t\t\t\tPartition: 0,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tTopic:     \"t06\",\n\t\t\t\t\t\tPartition: 4,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantPrevAssignments: map[topicPartitionAssignment]consumerGenerationPair{\n\t\t\t\t{\n\t\t\t\t\tTopic:     \"t06\",\n\t\t\t\t\tPartition: 0,\n\t\t\t\t}: {\n\t\t\t\t\tGeneration: 5,\n\t\t\t\t\tMemberID:   \"c01\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tTopic:     \"t06\",\n\t\t\t\t\tPartition: 4,\n\t\t\t\t}: {\n\t\t\t\t\tGeneration: 5,\n\t\t\t\t\tMemberID:   \"c01\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\t_, gotPrevAssignments, err := prepopulateCurrentAssignments(tt.args.members)\n\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"prepopulateCurrentAssignments() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\n\t\t\tif !reflect.DeepEqual(gotPrevAssignments, tt.wantPrevAssignments) {\n\t\t\t\tt.Errorf(\"deserializeTopicPartitionAssignment() prevAssignments = %v, want %v\", gotPrevAssignments, tt.wantPrevAssignments)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_areSubscriptionsIdentical(t *testing.T) {\n\ttype args struct {\n\t\tpartition2AllPotentialConsumers map[topicPartitionAssignment][]string\n\t\tconsumer2AllPotentialPartitions map[string][]topicPartitionAssignment\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"Empty consumers and partitions\",\n\t\t\targs: args{\n\t\t\t\tpartition2AllPotentialConsumers: make(map[topicPartitionAssignment][]string),\n\t\t\t\tconsumer2AllPotentialPartitions: make(map[string][]topicPartitionAssignment),\n\t\t\t},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Topic partitions with identical consumer entries\",\n\t\t\targs: args{\n\t\t\t\tpartition2AllPotentialConsumers: map[topicPartitionAssignment][]string{\n\t\t\t\t\t{Topic: \"t1\", Partition: 0}: {\"c1\", \"c2\", \"c3\"},\n\t\t\t\t\t{Topic: \"t1\", Partition: 1}: {\"c1\", \"c2\", \"c3\"},\n\t\t\t\t\t{Topic: \"t1\", Partition: 2}: {\"c1\", \"c2\", \"c3\"},\n\t\t\t\t},\n\t\t\t\tconsumer2AllPotentialPartitions: make(map[string][]topicPartitionAssignment),\n\t\t\t},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Topic partitions with mixed up consumer entries\",\n\t\t\targs: args{\n\t\t\t\tpartition2AllPotentialConsumers: map[topicPartitionAssignment][]string{\n\t\t\t\t\t{Topic: \"t1\", Partition: 0}: {\"c1\", \"c2\", \"c3\"},\n\t\t\t\t\t{Topic: \"t1\", Partition: 1}: {\"c2\", \"c3\", \"c1\"},\n\t\t\t\t\t{Topic: \"t1\", Partition: 2}: {\"c3\", \"c1\", \"c2\"},\n\t\t\t\t},\n\t\t\t\tconsumer2AllPotentialPartitions: make(map[string][]topicPartitionAssignment),\n\t\t\t},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Topic partitions with different consumer entries\",\n\t\t\targs: args{\n\t\t\t\tpartition2AllPotentialConsumers: map[topicPartitionAssignment][]string{\n\t\t\t\t\t{Topic: \"t1\", Partition: 0}: {\"c1\", \"c2\", \"c3\"},\n\t\t\t\t\t{Topic: \"t1\", Partition: 1}: {\"c2\", \"c3\", \"c1\"},\n\t\t\t\t\t{Topic: \"t1\", Partition: 2}: {\"cX\", \"c1\", \"c2\"},\n\t\t\t\t},\n\t\t\t\tconsumer2AllPotentialPartitions: make(map[string][]topicPartitionAssignment),\n\t\t\t},\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Topic partitions with different number of consumer entries\",\n\t\t\targs: args{\n\t\t\t\tpartition2AllPotentialConsumers: map[topicPartitionAssignment][]string{\n\t\t\t\t\t{Topic: \"t1\", Partition: 0}: {\"c1\", \"c2\", \"c3\"},\n\t\t\t\t\t{Topic: \"t1\", Partition: 1}: {\"c2\", \"c3\", \"c1\"},\n\t\t\t\t\t{Topic: \"t1\", Partition: 2}: {\"c1\", \"c2\"},\n\t\t\t\t},\n\t\t\t\tconsumer2AllPotentialPartitions: make(map[string][]topicPartitionAssignment),\n\t\t\t},\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Consumers with identical topic partitions\",\n\t\t\targs: args{\n\t\t\t\tpartition2AllPotentialConsumers: make(map[topicPartitionAssignment][]string),\n\t\t\t\tconsumer2AllPotentialPartitions: map[string][]topicPartitionAssignment{\n\t\t\t\t\t\"c1\": {{Topic: \"t1\", Partition: 0}, {Topic: \"t1\", Partition: 1}, {Topic: \"t1\", Partition: 2}},\n\t\t\t\t\t\"c2\": {{Topic: \"t1\", Partition: 0}, {Topic: \"t1\", Partition: 1}, {Topic: \"t1\", Partition: 2}},\n\t\t\t\t\t\"c3\": {{Topic: \"t1\", Partition: 0}, {Topic: \"t1\", Partition: 1}, {Topic: \"t1\", Partition: 2}},\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Consumer2 with mixed up consumer entries\",\n\t\t\targs: args{\n\t\t\t\tpartition2AllPotentialConsumers: make(map[topicPartitionAssignment][]string),\n\t\t\t\tconsumer2AllPotentialPartitions: map[string][]topicPartitionAssignment{\n\t\t\t\t\t\"c1\": {{Topic: \"t1\", Partition: 0}, {Topic: \"t1\", Partition: 1}, {Topic: \"t1\", Partition: 2}},\n\t\t\t\t\t\"c2\": {{Topic: \"t1\", Partition: 1}, {Topic: \"t1\", Partition: 2}, {Topic: \"t1\", Partition: 0}},\n\t\t\t\t\t\"c3\": {{Topic: \"t1\", Partition: 2}, {Topic: \"t1\", Partition: 0}, {Topic: \"t1\", Partition: 1}},\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Consumer2 with different consumer entries\",\n\t\t\targs: args{\n\t\t\t\tpartition2AllPotentialConsumers: make(map[topicPartitionAssignment][]string),\n\t\t\t\tconsumer2AllPotentialPartitions: map[string][]topicPartitionAssignment{\n\t\t\t\t\t\"c1\": {{Topic: \"t1\", Partition: 0}, {Topic: \"t1\", Partition: 1}, {Topic: \"t1\", Partition: 2}},\n\t\t\t\t\t\"c2\": {{Topic: \"t1\", Partition: 1}, {Topic: \"t1\", Partition: 2}, {Topic: \"t1\", Partition: 0}},\n\t\t\t\t\t\"c3\": {{Topic: \"tX\", Partition: 2}, {Topic: \"t1\", Partition: 0}, {Topic: \"t1\", Partition: 1}},\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Consumer2 with different number of consumer entries\",\n\t\t\targs: args{\n\t\t\t\tpartition2AllPotentialConsumers: make(map[topicPartitionAssignment][]string),\n\t\t\t\tconsumer2AllPotentialPartitions: map[string][]topicPartitionAssignment{\n\t\t\t\t\t\"c1\": {{Topic: \"t1\", Partition: 0}, {Topic: \"t1\", Partition: 1}, {Topic: \"t1\", Partition: 2}},\n\t\t\t\t\t\"c2\": {{Topic: \"t1\", Partition: 1}, {Topic: \"t1\", Partition: 2}, {Topic: \"t1\", Partition: 0}},\n\t\t\t\t\t\"c3\": {{Topic: \"t1\", Partition: 0}, {Topic: \"t1\", Partition: 1}},\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := areSubscriptionsIdentical(tt.args.partition2AllPotentialConsumers, tt.args.consumer2AllPotentialPartitions); got != tt.want {\n\t\t\t\tt.Errorf(\"areSubscriptionsIdentical() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_sortMemberIDsByPartitionAssignments(t *testing.T) {\n\ttype args struct {\n\t\tassignments map[string][]topicPartitionAssignment\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant []string\n\t}{\n\t\t{\n\t\t\tname: \"Null assignments\",\n\t\t\twant: make([]string, 0),\n\t\t},\n\t\t{\n\t\t\tname: \"Single assignment\",\n\t\t\targs: args{\n\t\t\t\tassignments: map[string][]topicPartitionAssignment{\n\t\t\t\t\t\"c1\": {\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 0},\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 1},\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 2},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: []string{\"c1\"},\n\t\t},\n\t\t{\n\t\t\tname: \"Multiple assignments with different partition counts\",\n\t\t\targs: args{\n\t\t\t\tassignments: map[string][]topicPartitionAssignment{\n\t\t\t\t\t\"c1\": {\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 0},\n\t\t\t\t\t},\n\t\t\t\t\t\"c2\": {\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 1},\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 2},\n\t\t\t\t\t},\n\t\t\t\t\t\"c3\": {\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 3},\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 4},\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 5},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: []string{\"c1\", \"c2\", \"c3\"},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := sortMemberIDsByPartitionAssignments(tt.args.assignments); !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"sortMemberIDsByPartitionAssignments() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_sortPartitions(t *testing.T) {\n\ttype args struct {\n\t\tcurrentAssignment                          map[string][]topicPartitionAssignment\n\t\tpartitionsWithADifferentPreviousAssignment map[topicPartitionAssignment]consumerGenerationPair\n\t\tisFreshAssignment                          bool\n\t\tpartition2AllPotentialConsumers            map[topicPartitionAssignment][]string\n\t\tconsumer2AllPotentialPartitions            map[string][]topicPartitionAssignment\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant []topicPartitionAssignment\n\t}{\n\t\t{\n\t\t\tname: \"Empty everything\",\n\t\t\twant: make([]topicPartitionAssignment, 0),\n\t\t},\n\t\t{\n\t\t\tname: \"Base case\",\n\t\t\targs: args{\n\t\t\t\tcurrentAssignment: map[string][]topicPartitionAssignment{\n\t\t\t\t\t\"c1\": {{Topic: \"t1\", Partition: 0}},\n\t\t\t\t\t\"c2\": {{Topic: \"t1\", Partition: 1}},\n\t\t\t\t\t\"c3\": {{Topic: \"t1\", Partition: 2}},\n\t\t\t\t},\n\t\t\t\tconsumer2AllPotentialPartitions: map[string][]topicPartitionAssignment{\n\t\t\t\t\t\"c1\": {{Topic: \"t1\", Partition: 0}, {Topic: \"t1\", Partition: 1}, {Topic: \"t1\", Partition: 2}},\n\t\t\t\t\t\"c2\": {{Topic: \"t1\", Partition: 0}, {Topic: \"t1\", Partition: 1}, {Topic: \"t1\", Partition: 2}},\n\t\t\t\t\t\"c3\": {{Topic: \"t1\", Partition: 0}, {Topic: \"t1\", Partition: 1}, {Topic: \"t1\", Partition: 2}},\n\t\t\t\t},\n\t\t\t\tpartition2AllPotentialConsumers: map[topicPartitionAssignment][]string{\n\t\t\t\t\t{Topic: \"t1\", Partition: 0}: {\"c1\", \"c2\", \"c3\"},\n\t\t\t\t\t{Topic: \"t1\", Partition: 1}: {\"c2\", \"c3\", \"c1\"},\n\t\t\t\t\t{Topic: \"t1\", Partition: 2}: {\"c3\", \"c1\", \"c2\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Partitions assigned to a different consumer last time\",\n\t\t\targs: args{\n\t\t\t\tcurrentAssignment: map[string][]topicPartitionAssignment{\n\t\t\t\t\t\"c1\": {{Topic: \"t1\", Partition: 0}},\n\t\t\t\t},\n\t\t\t\tconsumer2AllPotentialPartitions: map[string][]topicPartitionAssignment{\n\t\t\t\t\t\"c1\": {{Topic: \"t1\", Partition: 0}, {Topic: \"t1\", Partition: 1}, {Topic: \"t1\", Partition: 2}},\n\t\t\t\t\t\"c2\": {{Topic: \"t1\", Partition: 0}, {Topic: \"t1\", Partition: 1}, {Topic: \"t1\", Partition: 2}},\n\t\t\t\t\t\"c3\": {{Topic: \"t1\", Partition: 0}, {Topic: \"t1\", Partition: 1}, {Topic: \"t1\", Partition: 2}},\n\t\t\t\t},\n\t\t\t\tpartition2AllPotentialConsumers: map[topicPartitionAssignment][]string{\n\t\t\t\t\t{Topic: \"t1\", Partition: 0}: {\"c1\", \"c2\", \"c3\"},\n\t\t\t\t\t{Topic: \"t1\", Partition: 1}: {\"c2\", \"c3\", \"c1\"},\n\t\t\t\t\t{Topic: \"t1\", Partition: 2}: {\"c3\", \"c1\", \"c2\"},\n\t\t\t\t},\n\t\t\t\tpartitionsWithADifferentPreviousAssignment: map[topicPartitionAssignment]consumerGenerationPair{\n\t\t\t\t\t{Topic: \"t1\", Partition: 0}: {Generation: 1, MemberID: \"c2\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Partitions assigned to a different consumer last time\",\n\t\t\targs: args{\n\t\t\t\tcurrentAssignment: map[string][]topicPartitionAssignment{\n\t\t\t\t\t\"c1\": {{Topic: \"t1\", Partition: 0}},\n\t\t\t\t\t\"c2\": {{Topic: \"t1\", Partition: 1}},\n\t\t\t\t},\n\t\t\t\tconsumer2AllPotentialPartitions: map[string][]topicPartitionAssignment{\n\t\t\t\t\t\"c1\": {{Topic: \"t1\", Partition: 0}, {Topic: \"t1\", Partition: 1}, {Topic: \"t1\", Partition: 2}},\n\t\t\t\t\t\"c2\": {{Topic: \"t1\", Partition: 0}, {Topic: \"t1\", Partition: 1}, {Topic: \"t1\", Partition: 2}},\n\t\t\t\t\t\"c3\": {{Topic: \"t1\", Partition: 0}, {Topic: \"t1\", Partition: 1}, {Topic: \"t1\", Partition: 2}},\n\t\t\t\t},\n\t\t\t\tpartition2AllPotentialConsumers: map[topicPartitionAssignment][]string{\n\t\t\t\t\t{Topic: \"t1\", Partition: 0}: {\"c1\", \"c2\", \"c3\"},\n\t\t\t\t\t{Topic: \"t1\", Partition: 1}: {\"c2\", \"c3\", \"c1\"},\n\t\t\t\t\t{Topic: \"t1\", Partition: 2}: {\"c3\", \"c1\", \"c2\"},\n\t\t\t\t},\n\t\t\t\tpartitionsWithADifferentPreviousAssignment: map[topicPartitionAssignment]consumerGenerationPair{\n\t\t\t\t\t{Topic: \"t1\", Partition: 0}: {Generation: 1, MemberID: \"c2\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Fresh assignment\",\n\t\t\targs: args{\n\t\t\t\tisFreshAssignment: true,\n\t\t\t\tcurrentAssignment: map[string][]topicPartitionAssignment{},\n\t\t\t\tconsumer2AllPotentialPartitions: map[string][]topicPartitionAssignment{\n\t\t\t\t\t\"c1\": {{Topic: \"t1\", Partition: 0}, {Topic: \"t1\", Partition: 1}, {Topic: \"t1\", Partition: 2}},\n\t\t\t\t\t\"c2\": {{Topic: \"t1\", Partition: 0}, {Topic: \"t1\", Partition: 1}, {Topic: \"t1\", Partition: 2}},\n\t\t\t\t\t\"c3\": {{Topic: \"t1\", Partition: 0}, {Topic: \"t1\", Partition: 1}, {Topic: \"t1\", Partition: 2}},\n\t\t\t\t},\n\t\t\t\tpartition2AllPotentialConsumers: map[topicPartitionAssignment][]string{\n\t\t\t\t\t{Topic: \"t1\", Partition: 0}: {\"c1\", \"c2\", \"c3\"},\n\t\t\t\t\t{Topic: \"t1\", Partition: 1}: {\"c2\", \"c3\", \"c1\"},\n\t\t\t\t\t{Topic: \"t1\", Partition: 2}: {\"c3\", \"c1\", \"c2\"},\n\t\t\t\t},\n\t\t\t\tpartitionsWithADifferentPreviousAssignment: map[topicPartitionAssignment]consumerGenerationPair{\n\t\t\t\t\t{Topic: \"t1\", Partition: 0}: {Generation: 1, MemberID: \"c2\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot := sortPartitions(tt.args.currentAssignment, tt.args.partitionsWithADifferentPreviousAssignment, tt.args.isFreshAssignment, tt.args.partition2AllPotentialConsumers, tt.args.consumer2AllPotentialPartitions)\n\t\t\tif tt.want != nil && !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"sortPartitions() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_filterAssignedPartitions(t *testing.T) {\n\ttype args struct {\n\t\tcurrentAssignment               map[string][]topicPartitionAssignment\n\t\tpartition2AllPotentialConsumers map[topicPartitionAssignment][]string\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant map[string][]topicPartitionAssignment\n\t}{\n\t\t{\n\t\t\tname: \"All partitions accounted for\",\n\t\t\targs: args{\n\t\t\t\tcurrentAssignment: map[string][]topicPartitionAssignment{\n\t\t\t\t\t\"c1\": {{Topic: \"t1\", Partition: 0}},\n\t\t\t\t\t\"c2\": {{Topic: \"t1\", Partition: 1}},\n\t\t\t\t},\n\t\t\t\tpartition2AllPotentialConsumers: map[topicPartitionAssignment][]string{\n\t\t\t\t\t{Topic: \"t1\", Partition: 0}: {\"c1\"},\n\t\t\t\t\t{Topic: \"t1\", Partition: 1}: {\"c2\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: map[string][]topicPartitionAssignment{\n\t\t\t\t\"c1\": {{Topic: \"t1\", Partition: 0}},\n\t\t\t\t\"c2\": {{Topic: \"t1\", Partition: 1}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"One consumer using an unrecognized partition\",\n\t\t\targs: args{\n\t\t\t\tcurrentAssignment: map[string][]topicPartitionAssignment{\n\t\t\t\t\t\"c1\": {{Topic: \"t1\", Partition: 0}},\n\t\t\t\t\t\"c2\": {{Topic: \"t1\", Partition: 1}},\n\t\t\t\t},\n\t\t\t\tpartition2AllPotentialConsumers: map[topicPartitionAssignment][]string{\n\t\t\t\t\t{Topic: \"t1\", Partition: 0}: {\"c1\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: map[string][]topicPartitionAssignment{\n\t\t\t\t\"c1\": {{Topic: \"t1\", Partition: 0}},\n\t\t\t\t\"c2\": {},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Interleaved consumer removal\",\n\t\t\targs: args{\n\t\t\t\tcurrentAssignment: map[string][]topicPartitionAssignment{\n\t\t\t\t\t\"c1\": {{Topic: \"t1\", Partition: 0}},\n\t\t\t\t\t\"c2\": {{Topic: \"t1\", Partition: 1}},\n\t\t\t\t\t\"c3\": {{Topic: \"t1\", Partition: 2}},\n\t\t\t\t},\n\t\t\t\tpartition2AllPotentialConsumers: map[topicPartitionAssignment][]string{\n\t\t\t\t\t{Topic: \"t1\", Partition: 0}: {\"c1\"},\n\t\t\t\t\t{Topic: \"t1\", Partition: 2}: {\"c3\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: map[string][]topicPartitionAssignment{\n\t\t\t\t\"c1\": {{Topic: \"t1\", Partition: 0}},\n\t\t\t\t\"c2\": {},\n\t\t\t\t\"c3\": {{Topic: \"t1\", Partition: 2}},\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := filterAssignedPartitions(tt.args.currentAssignment, tt.args.partition2AllPotentialConsumers); !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"filterAssignedPartitions() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_canConsumerParticipateInReassignment(t *testing.T) {\n\ttype args struct {\n\t\tmemberID                        string\n\t\tcurrentAssignment               map[string][]topicPartitionAssignment\n\t\tconsumer2AllPotentialPartitions map[string][]topicPartitionAssignment\n\t\tpartition2AllPotentialConsumers map[topicPartitionAssignment][]string\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"Consumer has been assigned partitions not available to it\",\n\t\t\targs: args{\n\t\t\t\tmemberID: \"c1\",\n\t\t\t\tcurrentAssignment: map[string][]topicPartitionAssignment{\n\t\t\t\t\t\"c1\": {\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 0},\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 1},\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 2},\n\t\t\t\t\t},\n\t\t\t\t\t\"c2\": {},\n\t\t\t\t},\n\t\t\t\tconsumer2AllPotentialPartitions: map[string][]topicPartitionAssignment{\n\t\t\t\t\t\"c1\": {\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 0},\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 1},\n\t\t\t\t\t},\n\t\t\t\t\t\"c2\": {\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 0},\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 1},\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 2},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tpartition2AllPotentialConsumers: map[topicPartitionAssignment][]string{\n\t\t\t\t\t{Topic: \"t1\", Partition: 0}: {\"c1\", \"c2\"},\n\t\t\t\t\t{Topic: \"t1\", Partition: 1}: {\"c1\", \"c2\"},\n\t\t\t\t\t{Topic: \"t1\", Partition: 2}: {\"c2\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Consumer has been assigned all available partitions\",\n\t\t\targs: args{\n\t\t\t\tmemberID: \"c1\",\n\t\t\t\tcurrentAssignment: map[string][]topicPartitionAssignment{\n\t\t\t\t\t\"c1\": {\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 0},\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 1},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tconsumer2AllPotentialPartitions: map[string][]topicPartitionAssignment{\n\t\t\t\t\t\"c1\": {\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 0},\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 1},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tpartition2AllPotentialConsumers: map[topicPartitionAssignment][]string{\n\t\t\t\t\t{Topic: \"t1\", Partition: 0}: {\"c1\"},\n\t\t\t\t\t{Topic: \"t1\", Partition: 1}: {\"c1\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Consumer has not been assigned all available partitions\",\n\t\t\targs: args{\n\t\t\t\tmemberID: \"c1\",\n\t\t\t\tcurrentAssignment: map[string][]topicPartitionAssignment{\n\t\t\t\t\t\"c1\": {\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 0},\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 1},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tconsumer2AllPotentialPartitions: map[string][]topicPartitionAssignment{\n\t\t\t\t\t\"c1\": {\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 0},\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 1},\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 2},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tpartition2AllPotentialConsumers: map[topicPartitionAssignment][]string{\n\t\t\t\t\t{Topic: \"t1\", Partition: 0}: {\"c1\"},\n\t\t\t\t\t{Topic: \"t1\", Partition: 1}: {\"c1\"},\n\t\t\t\t\t{Topic: \"t1\", Partition: 2}: {\"c1\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := canConsumerParticipateInReassignment(tt.args.memberID, tt.args.currentAssignment, tt.args.consumer2AllPotentialPartitions, tt.args.partition2AllPotentialConsumers); got != tt.want {\n\t\t\t\tt.Errorf(\"canConsumerParticipateInReassignment() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_removeTopicPartitionFromMemberAssignments(t *testing.T) {\n\ttype args struct {\n\t\tassignments []topicPartitionAssignment\n\t\ttopic       topicPartitionAssignment\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant []topicPartitionAssignment\n\t}{\n\t\t{\n\t\t\tname: \"Empty\",\n\t\t\targs: args{\n\t\t\t\tassignments: make([]topicPartitionAssignment, 0),\n\t\t\t\ttopic:       topicPartitionAssignment{Topic: \"t1\", Partition: 0},\n\t\t\t},\n\t\t\twant: make([]topicPartitionAssignment, 0),\n\t\t},\n\t\t{\n\t\t\tname: \"Remove first entry\",\n\t\t\targs: args{\n\t\t\t\tassignments: []topicPartitionAssignment{\n\t\t\t\t\t{Topic: \"t1\", Partition: 0},\n\t\t\t\t\t{Topic: \"t1\", Partition: 1},\n\t\t\t\t\t{Topic: \"t1\", Partition: 2},\n\t\t\t\t},\n\t\t\t\ttopic: topicPartitionAssignment{Topic: \"t1\", Partition: 0},\n\t\t\t},\n\t\t\twant: []topicPartitionAssignment{\n\t\t\t\t{Topic: \"t1\", Partition: 1},\n\t\t\t\t{Topic: \"t1\", Partition: 2},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Remove middle entry\",\n\t\t\targs: args{\n\t\t\t\tassignments: []topicPartitionAssignment{\n\t\t\t\t\t{Topic: \"t1\", Partition: 0},\n\t\t\t\t\t{Topic: \"t1\", Partition: 1},\n\t\t\t\t\t{Topic: \"t1\", Partition: 2},\n\t\t\t\t},\n\t\t\t\ttopic: topicPartitionAssignment{Topic: \"t1\", Partition: 1},\n\t\t\t},\n\t\t\twant: []topicPartitionAssignment{\n\t\t\t\t{Topic: \"t1\", Partition: 0},\n\t\t\t\t{Topic: \"t1\", Partition: 2},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Remove last entry\",\n\t\t\targs: args{\n\t\t\t\tassignments: []topicPartitionAssignment{\n\t\t\t\t\t{Topic: \"t1\", Partition: 0},\n\t\t\t\t\t{Topic: \"t1\", Partition: 1},\n\t\t\t\t\t{Topic: \"t1\", Partition: 2},\n\t\t\t\t},\n\t\t\t\ttopic: topicPartitionAssignment{Topic: \"t1\", Partition: 2},\n\t\t\t},\n\t\t\twant: []topicPartitionAssignment{\n\t\t\t\t{Topic: \"t1\", Partition: 0},\n\t\t\t\t{Topic: \"t1\", Partition: 1},\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := removeTopicPartitionFromMemberAssignments(tt.args.assignments, tt.args.topic); !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"removeTopicPartitionFromMemberAssignments() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_assignPartition(t *testing.T) {\n\ttype args struct {\n\t\tpartition                       topicPartitionAssignment\n\t\tsortedCurrentSubscriptions      []string\n\t\tcurrentAssignment               map[string][]topicPartitionAssignment\n\t\tconsumer2AllPotentialPartitions map[string][]topicPartitionAssignment\n\t\tcurrentPartitionConsumer        map[topicPartitionAssignment]string\n\t}\n\ttests := []struct {\n\t\tname                         string\n\t\targs                         args\n\t\twant                         []string\n\t\twantCurrentAssignment        map[string][]topicPartitionAssignment\n\t\twantCurrentPartitionConsumer map[topicPartitionAssignment]string\n\t}{\n\t\t{\n\t\t\tname: \"Base\",\n\t\t\targs: args{\n\t\t\t\tpartition:                  topicPartitionAssignment{Topic: \"t1\", Partition: 2},\n\t\t\t\tsortedCurrentSubscriptions: []string{\"c3\", \"c1\", \"c2\"},\n\t\t\t\tcurrentAssignment: map[string][]topicPartitionAssignment{\n\t\t\t\t\t\"c1\": {\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 0},\n\t\t\t\t\t},\n\t\t\t\t\t\"c2\": {\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 1},\n\t\t\t\t\t},\n\t\t\t\t\t\"c3\": {},\n\t\t\t\t},\n\t\t\t\tconsumer2AllPotentialPartitions: map[string][]topicPartitionAssignment{\n\t\t\t\t\t\"c1\": {\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 0},\n\t\t\t\t\t},\n\t\t\t\t\t\"c2\": {\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 1},\n\t\t\t\t\t},\n\t\t\t\t\t\"c3\": {\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 2},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tcurrentPartitionConsumer: map[topicPartitionAssignment]string{\n\t\t\t\t\t{Topic: \"t1\", Partition: 0}: \"c1\",\n\t\t\t\t\t{Topic: \"t1\", Partition: 1}: \"c2\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: []string{\"c1\", \"c2\", \"c3\"},\n\t\t\twantCurrentAssignment: map[string][]topicPartitionAssignment{\n\t\t\t\t\"c1\": {\n\t\t\t\t\t{Topic: \"t1\", Partition: 0},\n\t\t\t\t},\n\t\t\t\t\"c2\": {\n\t\t\t\t\t{Topic: \"t1\", Partition: 1},\n\t\t\t\t},\n\t\t\t\t\"c3\": {\n\t\t\t\t\t{Topic: \"t1\", Partition: 2},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantCurrentPartitionConsumer: map[topicPartitionAssignment]string{\n\t\t\t\t{Topic: \"t1\", Partition: 0}: \"c1\",\n\t\t\t\t{Topic: \"t1\", Partition: 1}: \"c2\",\n\t\t\t\t{Topic: \"t1\", Partition: 2}: \"c3\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Unassignable Partition\",\n\t\t\targs: args{\n\t\t\t\tpartition:                  topicPartitionAssignment{Topic: \"t1\", Partition: 3},\n\t\t\t\tsortedCurrentSubscriptions: []string{\"c3\", \"c1\", \"c2\"},\n\t\t\t\tcurrentAssignment: map[string][]topicPartitionAssignment{\n\t\t\t\t\t\"c1\": {\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 0},\n\t\t\t\t\t},\n\t\t\t\t\t\"c2\": {\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 1},\n\t\t\t\t\t},\n\t\t\t\t\t\"c3\": {},\n\t\t\t\t},\n\t\t\t\tconsumer2AllPotentialPartitions: map[string][]topicPartitionAssignment{\n\t\t\t\t\t\"c1\": {\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 0},\n\t\t\t\t\t},\n\t\t\t\t\t\"c2\": {\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 1},\n\t\t\t\t\t},\n\t\t\t\t\t\"c3\": {\n\t\t\t\t\t\t{Topic: \"t1\", Partition: 2},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tcurrentPartitionConsumer: map[topicPartitionAssignment]string{\n\t\t\t\t\t{Topic: \"t1\", Partition: 0}: \"c1\",\n\t\t\t\t\t{Topic: \"t1\", Partition: 1}: \"c2\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: []string{\"c3\", \"c1\", \"c2\"},\n\t\t\twantCurrentAssignment: map[string][]topicPartitionAssignment{\n\t\t\t\t\"c1\": {\n\t\t\t\t\t{Topic: \"t1\", Partition: 0},\n\t\t\t\t},\n\t\t\t\t\"c2\": {\n\t\t\t\t\t{Topic: \"t1\", Partition: 1},\n\t\t\t\t},\n\t\t\t\t\"c3\": {},\n\t\t\t},\n\t\t\twantCurrentPartitionConsumer: map[topicPartitionAssignment]string{\n\t\t\t\t{Topic: \"t1\", Partition: 0}: \"c1\",\n\t\t\t\t{Topic: \"t1\", Partition: 1}: \"c2\",\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := assignPartition(tt.args.partition, tt.args.sortedCurrentSubscriptions, tt.args.currentAssignment, tt.args.consumer2AllPotentialPartitions, tt.args.currentPartitionConsumer); !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"assignPartition() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(tt.args.currentAssignment, tt.wantCurrentAssignment) {\n\t\t\t\tt.Errorf(\"assignPartition() currentAssignment = %v, want %v\", tt.args.currentAssignment, tt.wantCurrentAssignment)\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(tt.args.currentPartitionConsumer, tt.wantCurrentPartitionConsumer) {\n\t\t\t\tt.Errorf(\"assignPartition() currentPartitionConsumer = %v, want %v\", tt.args.currentPartitionConsumer, tt.wantCurrentPartitionConsumer)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_stickyBalanceStrategy_Plan(t *testing.T) {\n\ttype args struct {\n\t\tmembers map[string]ConsumerGroupMemberMetadata\n\t\ttopics  map[string][]int32\n\t}\n\ttests := []struct {\n\t\tname string\n\t\ts    *stickyBalanceStrategy\n\t\targs args\n\t}{\n\t\t{\n\t\t\tname: \"One consumer with no topics\",\n\t\t\targs: args{\n\t\t\t\tmembers: map[string]ConsumerGroupMemberMetadata{\n\t\t\t\t\t\"consumer\": {},\n\t\t\t\t},\n\t\t\t\ttopics: make(map[string][]int32),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"One consumer with non-existent topic\",\n\t\t\targs: args{\n\t\t\t\tmembers: map[string]ConsumerGroupMemberMetadata{\n\t\t\t\t\t\"consumer\": {\n\t\t\t\t\t\tTopics: []string{\"topic\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\ttopics: map[string][]int32{\n\t\t\t\t\t\"topic\": make([]int32, 0),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"One consumer with one topic\",\n\t\t\targs: args{\n\t\t\t\tmembers: map[string]ConsumerGroupMemberMetadata{\n\t\t\t\t\t\"consumer\": {\n\t\t\t\t\t\tTopics: []string{\"topic\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\ttopics: map[string][]int32{\n\t\t\t\t\t\"topic\": {0, 1, 2},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Only assigns partitions from subscribed topics\",\n\t\t\targs: args{\n\t\t\t\tmembers: map[string]ConsumerGroupMemberMetadata{\n\t\t\t\t\t\"consumer\": {\n\t\t\t\t\t\tTopics: []string{\"topic\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\ttopics: map[string][]int32{\n\t\t\t\t\t\"topic\": {0, 1, 2},\n\t\t\t\t\t\"other\": {0, 1, 2},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"One consumer with multiple topics\",\n\t\t\targs: args{\n\t\t\t\tmembers: map[string]ConsumerGroupMemberMetadata{\n\t\t\t\t\t\"consumer\": {\n\t\t\t\t\t\tTopics: []string{\"topic1\", \"topic2\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\ttopics: map[string][]int32{\n\t\t\t\t\t\"topic1\": {0},\n\t\t\t\t\t\"topic2\": {0, 1},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Two consumers with one topic and one partition\",\n\t\t\targs: args{\n\t\t\t\tmembers: map[string]ConsumerGroupMemberMetadata{\n\t\t\t\t\t\"consumer1\": {\n\t\t\t\t\t\tTopics: []string{\"topic\"},\n\t\t\t\t\t},\n\t\t\t\t\t\"consumer2\": {\n\t\t\t\t\t\tTopics: []string{\"topic\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\ttopics: map[string][]int32{\n\t\t\t\t\t\"topic\": {0},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Two consumers with one topic and two partitions\",\n\t\t\targs: args{\n\t\t\t\tmembers: map[string]ConsumerGroupMemberMetadata{\n\t\t\t\t\t\"consumer1\": {\n\t\t\t\t\t\tTopics: []string{\"topic\"},\n\t\t\t\t\t},\n\t\t\t\t\t\"consumer2\": {\n\t\t\t\t\t\tTopics: []string{\"topic\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\ttopics: map[string][]int32{\n\t\t\t\t\t\"topic\": {0, 1},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Multiple consumers with mixed topic subscriptions\",\n\t\t\targs: args{\n\t\t\t\tmembers: map[string]ConsumerGroupMemberMetadata{\n\t\t\t\t\t\"consumer1\": {\n\t\t\t\t\t\tTopics: []string{\"topic1\"},\n\t\t\t\t\t},\n\t\t\t\t\t\"consumer2\": {\n\t\t\t\t\t\tTopics: []string{\"topic1\", \"topic2\"},\n\t\t\t\t\t},\n\t\t\t\t\t\"consumer3\": {\n\t\t\t\t\t\tTopics: []string{\"topic1\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\ttopics: map[string][]int32{\n\t\t\t\t\t\"topic1\": {0, 1, 2},\n\t\t\t\t\t\"topic2\": {0, 1},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Two consumers with two topics and six partitions\",\n\t\t\targs: args{\n\t\t\t\tmembers: map[string]ConsumerGroupMemberMetadata{\n\t\t\t\t\t\"consumer1\": {\n\t\t\t\t\t\tTopics: []string{\"topic1\", \"topic2\"},\n\t\t\t\t\t},\n\t\t\t\t\t\"consumer2\": {\n\t\t\t\t\t\tTopics: []string{\"topic1\", \"topic2\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\ttopics: map[string][]int32{\n\t\t\t\t\t\"topic1\": {0, 1, 2},\n\t\t\t\t\t\"topic2\": {0, 1, 2},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Three consumers (two old, one new) with one topic and twelve partitions\",\n\t\t\targs: args{\n\t\t\t\tmembers: map[string]ConsumerGroupMemberMetadata{\n\t\t\t\t\t\"consumer1\": {\n\t\t\t\t\t\tTopics:   []string{\"topic1\"},\n\t\t\t\t\t\tUserData: encodeSubscriberPlanWithGeneration(t, map[string][]int32{\"topic1\": {4, 11, 8, 5, 9, 2}}, 1),\n\t\t\t\t\t},\n\t\t\t\t\t\"consumer2\": {\n\t\t\t\t\t\tTopics:   []string{\"topic1\"},\n\t\t\t\t\t\tUserData: encodeSubscriberPlanWithGeneration(t, map[string][]int32{\"topic1\": {1, 3, 0, 7, 10, 6}}, 1),\n\t\t\t\t\t},\n\t\t\t\t\t\"consumer3\": {\n\t\t\t\t\t\tTopics: []string{\"topic1\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\ttopics: map[string][]int32{\n\t\t\t\t\t\"topic1\": {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Three consumers (two old, one new) with one topic and 13 partitions\",\n\t\t\targs: args{\n\t\t\t\tmembers: map[string]ConsumerGroupMemberMetadata{\n\t\t\t\t\t\"consumer1\": {\n\t\t\t\t\t\tTopics:   []string{\"topic1\"},\n\t\t\t\t\t\tUserData: encodeSubscriberPlanWithGeneration(t, map[string][]int32{\"topic1\": {4, 11, 8, 5, 9, 2, 6}}, 1),\n\t\t\t\t\t},\n\t\t\t\t\t\"consumer2\": {\n\t\t\t\t\t\tTopics:   []string{\"topic1\"},\n\t\t\t\t\t\tUserData: encodeSubscriberPlanWithGeneration(t, map[string][]int32{\"topic1\": {1, 3, 0, 7, 10, 12}}, 1),\n\t\t\t\t\t},\n\t\t\t\t\t\"consumer3\": {\n\t\t\t\t\t\tTopics: []string{\"topic1\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\ttopics: map[string][]int32{\n\t\t\t\t\t\"topic1\": {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"One consumer that is no longer subscribed to a topic that it had previously been consuming from\",\n\t\t\targs: args{\n\t\t\t\tmembers: map[string]ConsumerGroupMemberMetadata{\n\t\t\t\t\t\"consumer1\": {\n\t\t\t\t\t\tTopics:   []string{\"topic2\"},\n\t\t\t\t\t\tUserData: encodeSubscriberPlanWithGeneration(t, map[string][]int32{\"topic1\": {0}}, 1),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\ttopics: map[string][]int32{\n\t\t\t\t\t\"topic1\": {0},\n\t\t\t\t\t\"topic2\": {0},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Two consumers where one is no longer interested in consuming from a topic that it had been consuming from\",\n\t\t\targs: args{\n\t\t\t\tmembers: map[string]ConsumerGroupMemberMetadata{\n\t\t\t\t\t\"consumer1\": {\n\t\t\t\t\t\tTopics:   []string{\"topic2\"},\n\t\t\t\t\t\tUserData: encodeSubscriberPlanWithGeneration(t, map[string][]int32{\"topic1\": {0}}, 1),\n\t\t\t\t\t},\n\t\t\t\t\t\"consumer2\": {\n\t\t\t\t\t\tTopics:   []string{\"topic1\", \"topic2\"},\n\t\t\t\t\t\tUserData: encodeSubscriberPlanWithGeneration(t, map[string][]int32{\"topic1\": {1}}, 1),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\ttopics: map[string][]int32{\n\t\t\t\t\t\"topic1\": {0, 1},\n\t\t\t\t\t\"topic2\": {0, 1},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := &stickyBalanceStrategy{}\n\t\t\tplan, err := s.Plan(tt.args.members, tt.args.topics)\n\t\t\tverifyPlanIsBalancedAndSticky(t, s, tt.args.members, plan, err)\n\t\t\tverifyFullyBalanced(t, plan)\n\t\t})\n\t}\n}\n\nfunc Test_stickyBalanceStrategy_Plan_KIP54_ExampleOne(t *testing.T) {\n\ts := &stickyBalanceStrategy{}\n\n\t// PLAN 1\n\tmembers := map[string]ConsumerGroupMemberMetadata{\n\t\t\"consumer1\": {\n\t\t\tTopics: []string{\"topic1\", \"topic2\", \"topic3\", \"topic4\"},\n\t\t},\n\t\t\"consumer2\": {\n\t\t\tTopics: []string{\"topic1\", \"topic2\", \"topic3\", \"topic4\"},\n\t\t},\n\t\t\"consumer3\": {\n\t\t\tTopics: []string{\"topic1\", \"topic2\", \"topic3\", \"topic4\"},\n\t\t},\n\t}\n\ttopics := map[string][]int32{\n\t\t\"topic1\": {0, 1},\n\t\t\"topic2\": {0, 1},\n\t\t\"topic3\": {0, 1},\n\t\t\"topic4\": {0, 1},\n\t}\n\tplan1, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan1, err)\n\tverifyFullyBalanced(t, plan1)\n\n\t// PLAN 2\n\tdelete(members, \"consumer1\")\n\tmembers[\"consumer2\"] = ConsumerGroupMemberMetadata{\n\t\tTopics:   []string{\"topic1\", \"topic2\", \"topic3\", \"topic4\"},\n\t\tUserData: encodeSubscriberPlan(t, plan1[\"consumer2\"]),\n\t}\n\tmembers[\"consumer3\"] = ConsumerGroupMemberMetadata{\n\t\tTopics:   []string{\"topic1\", \"topic2\", \"topic3\", \"topic4\"},\n\t\tUserData: encodeSubscriberPlan(t, plan1[\"consumer3\"]),\n\t}\n\tplan2, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan2, err)\n\tverifyFullyBalanced(t, plan2)\n}\n\nfunc Test_stickyBalanceStrategy_Plan_KIP54_ExampleTwo(t *testing.T) {\n\ts := &stickyBalanceStrategy{}\n\n\t// PLAN 1\n\tmembers := map[string]ConsumerGroupMemberMetadata{\n\t\t\"consumer1\": {\n\t\t\tTopics: []string{\"topic1\"},\n\t\t},\n\t\t\"consumer2\": {\n\t\t\tTopics: []string{\"topic1\", \"topic2\"},\n\t\t},\n\t\t\"consumer3\": {\n\t\t\tTopics: []string{\"topic1\", \"topic2\", \"topic3\"},\n\t\t},\n\t}\n\ttopics := map[string][]int32{\n\t\t\"topic1\": {0},\n\t\t\"topic2\": {0, 1},\n\t\t\"topic3\": {0, 1, 2},\n\t}\n\tplan1, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan1, err)\n\tif len(plan1[\"consumer1\"][\"topic1\"]) != 1 || len(plan1[\"consumer2\"][\"topic2\"]) != 2 || len(plan1[\"consumer3\"][\"topic3\"]) != 3 {\n\t\tt.Error(\"Incorrect distribution of topic partition assignments\")\n\t}\n\n\t// PLAN 2\n\tdelete(members, \"consumer1\")\n\tmembers[\"consumer2\"] = ConsumerGroupMemberMetadata{\n\t\tTopics:   members[\"consumer2\"].Topics,\n\t\tUserData: encodeSubscriberPlan(t, plan1[\"consumer2\"]),\n\t}\n\tmembers[\"consumer3\"] = ConsumerGroupMemberMetadata{\n\t\tTopics:   members[\"consumer3\"].Topics,\n\t\tUserData: encodeSubscriberPlan(t, plan1[\"consumer3\"]),\n\t}\n\tplan2, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan2, err)\n\tverifyFullyBalanced(t, plan2)\n\tif len(plan2[\"consumer2\"][\"topic1\"]) != 1 || len(plan2[\"consumer2\"][\"topic2\"]) != 2 || len(plan2[\"consumer3\"][\"topic3\"]) != 3 {\n\t\tt.Error(\"Incorrect distribution of topic partition assignments\")\n\t}\n}\n\nfunc Test_stickyBalanceStrategy_Plan_KIP54_ExampleThree(t *testing.T) {\n\ts := &stickyBalanceStrategy{}\n\ttopicNames := []string{\"topic1\", \"topic2\"}\n\n\t// PLAN 1\n\tmembers := map[string]ConsumerGroupMemberMetadata{\n\t\t\"consumer1\": {\n\t\t\tTopics: topicNames,\n\t\t},\n\t\t\"consumer2\": {\n\t\t\tTopics: topicNames,\n\t\t},\n\t}\n\ttopics := map[string][]int32{\n\t\t\"topic1\": {0, 1},\n\t\t\"topic2\": {0, 1},\n\t}\n\tplan1, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan1, err)\n\n\t// PLAN 2\n\tmembers[\"consumer1\"] = ConsumerGroupMemberMetadata{\n\t\tTopics: topicNames,\n\t}\n\tmembers[\"consumer2\"] = ConsumerGroupMemberMetadata{\n\t\tTopics:   topicNames,\n\t\tUserData: encodeSubscriberPlan(t, plan1[\"consumer2\"]),\n\t}\n\tmembers[\"consumer3\"] = ConsumerGroupMemberMetadata{\n\t\tTopics:   topicNames,\n\t\tUserData: encodeSubscriberPlan(t, plan1[\"consumer3\"]),\n\t}\n\tplan2, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan2, err)\n\tverifyFullyBalanced(t, plan2)\n}\n\nfunc Test_stickyBalanceStrategy_Plan_AddRemoveConsumerOneTopic(t *testing.T) {\n\ts := &stickyBalanceStrategy{}\n\n\t// PLAN 1\n\tmembers := map[string]ConsumerGroupMemberMetadata{\n\t\t\"consumer1\": {\n\t\t\tTopics: []string{\"topic\"},\n\t\t},\n\t}\n\ttopics := map[string][]int32{\n\t\t\"topic\": {0, 1, 2},\n\t}\n\tplan1, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan1, err)\n\n\t// PLAN 2\n\tmembers[\"consumer1\"] = ConsumerGroupMemberMetadata{\n\t\tTopics:   []string{\"topic\"},\n\t\tUserData: encodeSubscriberPlan(t, plan1[\"consumer1\"]),\n\t}\n\tmembers[\"consumer2\"] = ConsumerGroupMemberMetadata{\n\t\tTopics: []string{\"topic\"},\n\t}\n\tplan2, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan2, err)\n\n\t// PLAN 3\n\tdelete(members, \"consumer1\")\n\tmembers[\"consumer2\"] = ConsumerGroupMemberMetadata{\n\t\tTopics:   []string{\"topic\"},\n\t\tUserData: encodeSubscriberPlan(t, plan2[\"consumer2\"]),\n\t}\n\tplan3, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan3, err)\n}\n\nfunc Test_stickyBalanceStrategy_Plan_PoorRoundRobinAssignmentScenario(t *testing.T) {\n\ts := &stickyBalanceStrategy{}\n\n\t// PLAN 1\n\tmembers := map[string]ConsumerGroupMemberMetadata{\n\t\t\"consumer1\": {\n\t\t\tTopics: []string{\"topic1\", \"topic2\", \"topic3\", \"topic4\", \"topic5\"},\n\t\t},\n\t\t\"consumer2\": {\n\t\t\tTopics: []string{\"topic1\", \"topic3\", \"topic5\"},\n\t\t},\n\t\t\"consumer3\": {\n\t\t\tTopics: []string{\"topic1\", \"topic3\", \"topic5\"},\n\t\t},\n\t\t\"consumer4\": {\n\t\t\tTopics: []string{\"topic1\", \"topic2\", \"topic3\", \"topic4\", \"topic5\"},\n\t\t},\n\t}\n\ttopics := make(map[string][]int32, 5)\n\tfor i := 1; i <= 5; i++ {\n\t\tpartitions := make([]int32, i%2+1)\n\t\tfor j := 0; j < i%2+1; j++ {\n\t\t\tpartitions[j] = int32(j)\n\t\t}\n\t\ttopics[fmt.Sprintf(\"topic%d\", i)] = partitions\n\t}\n\n\tplan, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan, err)\n}\n\nfunc Test_stickyBalanceStrategy_Plan_AddRemoveTopicTwoConsumers(t *testing.T) {\n\ts := &stickyBalanceStrategy{}\n\n\t// PLAN 1\n\tmembers := map[string]ConsumerGroupMemberMetadata{\n\t\t\"consumer1\": {\n\t\t\tTopics: []string{\"topic1\"},\n\t\t},\n\t\t\"consumer2\": {\n\t\t\tTopics: []string{\"topic1\"},\n\t\t},\n\t}\n\ttopics := map[string][]int32{\n\t\t\"topic1\": {0, 1, 2},\n\t}\n\tplan1, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan1, err)\n\tverifyFullyBalanced(t, plan1)\n\n\t// PLAN 2\n\tmembers[\"consumer1\"] = ConsumerGroupMemberMetadata{\n\t\tTopics:   []string{\"topic1\", \"topic2\"},\n\t\tUserData: encodeSubscriberPlan(t, plan1[\"consumer1\"]),\n\t}\n\tmembers[\"consumer2\"] = ConsumerGroupMemberMetadata{\n\t\tTopics:   []string{\"topic1\", \"topic2\"},\n\t\tUserData: encodeSubscriberPlan(t, plan1[\"consumer2\"]),\n\t}\n\ttopics[\"topic2\"] = []int32{0, 1, 2}\n\n\tplan2, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan2, err)\n\tverifyFullyBalanced(t, plan2)\n\n\t// PLAN 3\n\tmembers[\"consumer1\"] = ConsumerGroupMemberMetadata{\n\t\tTopics:   []string{\"topic1\", \"topic2\"},\n\t\tUserData: encodeSubscriberPlan(t, plan2[\"consumer1\"]),\n\t}\n\tmembers[\"consumer2\"] = ConsumerGroupMemberMetadata{\n\t\tTopics:   []string{\"topic1\", \"topic2\"},\n\t\tUserData: encodeSubscriberPlan(t, plan2[\"consumer2\"]),\n\t}\n\tdelete(topics, \"topic1\")\n\n\tplan3, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan3, err)\n\tverifyFullyBalanced(t, plan3)\n}\n\nfunc Test_stickyBalanceStrategy_Plan_ReassignmentAfterOneConsumerLeaves(t *testing.T) {\n\ts := &stickyBalanceStrategy{}\n\n\t// PLAN 1\n\tmembers := make(map[string]ConsumerGroupMemberMetadata, 20)\n\tfor i := 0; i < 20; i++ {\n\t\ttopics := make([]string, 20)\n\t\tfor j := 0; j < 20; j++ {\n\t\t\ttopics[j] = fmt.Sprintf(\"topic%d\", j)\n\t\t}\n\t\tmembers[fmt.Sprintf(\"consumer%d\", i)] = ConsumerGroupMemberMetadata{Topics: topics}\n\t}\n\ttopics := make(map[string][]int32, 20)\n\tfor i := 0; i < 20; i++ {\n\t\tpartitions := make([]int32, 20)\n\t\tfor j := 0; j < 20; j++ {\n\t\t\tpartitions[j] = int32(j)\n\t\t}\n\t\ttopics[fmt.Sprintf(\"topic%d\", i)] = partitions\n\t}\n\n\tplan1, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan1, err)\n\n\tfor i := 0; i < 20; i++ {\n\t\ttopics := make([]string, 20)\n\t\tfor j := 0; j < 20; j++ {\n\t\t\ttopics[j] = fmt.Sprintf(\"topic%d\", j)\n\t\t}\n\t\tmembers[fmt.Sprintf(\"consumer%d\", i)] = ConsumerGroupMemberMetadata{\n\t\t\tTopics:   members[fmt.Sprintf(\"consumer%d\", i)].Topics,\n\t\t\tUserData: encodeSubscriberPlan(t, plan1[fmt.Sprintf(\"consumer%d\", i)]),\n\t\t}\n\t}\n\tdelete(members, \"consumer10\")\n\n\tplan2, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan2, err)\n}\n\nfunc Test_stickyBalanceStrategy_Plan_ReassignmentAfterOneConsumerAdded(t *testing.T) {\n\ts := &stickyBalanceStrategy{}\n\n\t// PLAN 1\n\tmembers := make(map[string]ConsumerGroupMemberMetadata)\n\tfor i := 0; i < 10; i++ {\n\t\tmembers[fmt.Sprintf(\"consumer%d\", i)] = ConsumerGroupMemberMetadata{Topics: []string{\"topic1\"}}\n\t}\n\tpartitions := make([]int32, 20)\n\tfor j := 0; j < 20; j++ {\n\t\tpartitions[j] = int32(j)\n\t}\n\ttopics := map[string][]int32{\"topic1\": partitions}\n\n\tplan1, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan1, err)\n\n\t// add a new consumer\n\tmembers[\"consumer10\"] = ConsumerGroupMemberMetadata{Topics: []string{\"topic1\"}}\n\n\tplan2, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan2, err)\n}\n\nfunc Test_stickyBalanceStrategy_Plan_SameSubscriptions(t *testing.T) {\n\ts := &stickyBalanceStrategy{}\n\n\t// PLAN 1\n\tmembers := make(map[string]ConsumerGroupMemberMetadata, 20)\n\tfor i := 0; i < 9; i++ {\n\t\ttopics := make([]string, 15)\n\t\tfor j := 0; j < 15; j++ {\n\t\t\ttopics[j] = fmt.Sprintf(\"topic%d\", j)\n\t\t}\n\t\tmembers[fmt.Sprintf(\"consumer%d\", i)] = ConsumerGroupMemberMetadata{Topics: topics}\n\t}\n\ttopics := make(map[string][]int32, 15)\n\tfor i := 0; i < 15; i++ {\n\t\tpartitions := make([]int32, i)\n\t\tfor j := 0; j < i; j++ {\n\t\t\tpartitions[j] = int32(j)\n\t\t}\n\t\ttopics[fmt.Sprintf(\"topic%d\", i)] = partitions\n\t}\n\n\tplan1, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan1, err)\n\n\t// PLAN 2\n\tfor i := 0; i < 9; i++ {\n\t\tmembers[fmt.Sprintf(\"consumer%d\", i)] = ConsumerGroupMemberMetadata{\n\t\t\tTopics:   members[fmt.Sprintf(\"consumer%d\", i)].Topics,\n\t\t\tUserData: encodeSubscriberPlan(t, plan1[fmt.Sprintf(\"consumer%d\", i)]),\n\t\t}\n\t}\n\tdelete(members, \"consumer5\")\n\n\tplan2, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan2, err)\n}\n\nfunc Test_stickyBalanceStrategy_Plan_LargeAssignmentWithMultipleConsumersLeaving(t *testing.T) {\n\ts := &stickyBalanceStrategy{}\n\tr := rand.New(rand.NewSource(time.Now().UnixNano()))\n\n\t// PLAN 1\n\tmembers := make(map[string]ConsumerGroupMemberMetadata, 20)\n\tfor i := 0; i < 200; i++ {\n\t\ttopics := make([]string, 200)\n\t\tfor j := 0; j < 200; j++ {\n\t\t\ttopics[j] = fmt.Sprintf(\"topic%d\", j)\n\t\t}\n\t\tmembers[fmt.Sprintf(\"consumer%d\", i)] = ConsumerGroupMemberMetadata{Topics: topics}\n\t}\n\ttopics := make(map[string][]int32, 40)\n\tfor i := 0; i < 40; i++ {\n\t\tpartitionCount := r.Intn(20)\n\t\tpartitions := make([]int32, partitionCount)\n\t\tfor j := 0; j < partitionCount; j++ {\n\t\t\tpartitions[j] = int32(j)\n\t\t}\n\t\ttopics[fmt.Sprintf(\"topic%d\", i)] = partitions\n\t}\n\n\tplan1, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan1, err)\n\n\tfor i := 0; i < 200; i++ {\n\t\tmembers[fmt.Sprintf(\"consumer%d\", i)] = ConsumerGroupMemberMetadata{\n\t\t\tTopics:   members[fmt.Sprintf(\"consumer%d\", i)].Topics,\n\t\t\tUserData: encodeSubscriberPlan(t, plan1[fmt.Sprintf(\"consumer%d\", i)]),\n\t\t}\n\t}\n\tfor i := 0; i < 50; i++ {\n\t\tdelete(members, fmt.Sprintf(\"consumer%d\", i))\n\t}\n\n\tplan2, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan2, err)\n}\n\nfunc Test_stickyBalanceStrategy_Plan_NewSubscription(t *testing.T) {\n\ts := &stickyBalanceStrategy{}\n\n\tmembers := make(map[string]ConsumerGroupMemberMetadata, 20)\n\tfor i := 0; i < 3; i++ {\n\t\ttopics := make([]string, 0)\n\t\tfor j := i; j <= 3*i-2; j++ {\n\t\t\ttopics = append(topics, fmt.Sprintf(\"topic%d\", j))\n\t\t}\n\t\tmembers[fmt.Sprintf(\"consumer%d\", i)] = ConsumerGroupMemberMetadata{Topics: topics}\n\t}\n\ttopics := make(map[string][]int32, 5)\n\tfor i := 1; i < 5; i++ {\n\t\ttopics[fmt.Sprintf(\"topic%d\", i)] = []int32{0}\n\t}\n\n\tplan1, err := s.Plan(members, topics)\n\tif err != nil {\n\t\tt.Errorf(\"stickyBalanceStrategy.Plan() error = %v\", err)\n\t\treturn\n\t}\n\tverifyValidityAndBalance(t, members, plan1)\n\n\tmembers[\"consumer0\"] = ConsumerGroupMemberMetadata{Topics: []string{\"topic1\"}}\n\n\tplan2, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan2, err)\n}\n\nfunc Test_stickyBalanceStrategy_Plan_ReassignmentWithRandomSubscriptionsAndChanges(t *testing.T) {\n\tr := rand.New(rand.NewSource(time.Now().UnixNano()))\n\n\tminNumConsumers := 20\n\tmaxNumConsumers := 40\n\tminNumTopics := 10\n\tmaxNumTopics := 20\n\n\tfor round := 0; round < 100; round++ {\n\t\tnumTopics := minNumTopics + r.Intn(maxNumTopics-minNumTopics)\n\t\ttopics := make([]string, numTopics)\n\t\tpartitionsPerTopic := make(map[string][]int32, numTopics)\n\t\tfor i := 0; i < numTopics; i++ {\n\t\t\ttopicName := fmt.Sprintf(\"topic%d\", i)\n\t\t\ttopics[i] = topicName\n\t\t\tpartitions := make([]int32, maxNumTopics)\n\t\t\tfor j := 0; j < maxNumTopics; j++ {\n\t\t\t\tpartitions[j] = int32(j)\n\t\t\t}\n\t\t\tpartitionsPerTopic[topicName] = partitions\n\t\t}\n\n\t\tnumConsumers := minNumConsumers + r.Intn(maxNumConsumers-minNumConsumers)\n\t\tmembers := make(map[string]ConsumerGroupMemberMetadata, numConsumers)\n\t\tfor i := 0; i < numConsumers; i++ {\n\t\t\tsub := getRandomSublist(r, topics)\n\t\t\tsort.Strings(sub)\n\t\t\tmembers[fmt.Sprintf(\"consumer%d\", i)] = ConsumerGroupMemberMetadata{Topics: sub}\n\t\t}\n\n\t\ts := &stickyBalanceStrategy{}\n\t\tplan, err := s.Plan(members, partitionsPerTopic)\n\t\tverifyPlanIsBalancedAndSticky(t, s, members, plan, err)\n\n\t\t// PLAN 2\n\t\tmembersPlan2 := make(map[string]ConsumerGroupMemberMetadata, numConsumers)\n\t\tfor i := 0; i < numConsumers; i++ {\n\t\t\tsub := getRandomSublist(r, topics)\n\t\t\tsort.Strings(sub)\n\t\t\tmembersPlan2[fmt.Sprintf(\"consumer%d\", i)] = ConsumerGroupMemberMetadata{\n\t\t\t\tTopics:   sub,\n\t\t\t\tUserData: encodeSubscriberPlan(t, plan[fmt.Sprintf(\"consumer%d\", i)]),\n\t\t\t}\n\t\t}\n\t\tplan2, err := s.Plan(membersPlan2, partitionsPerTopic)\n\t\tverifyPlanIsBalancedAndSticky(t, s, membersPlan2, plan2, err)\n\t}\n}\n\nfunc Test_stickyBalanceStrategy_Plan_MoveExistingAssignments(t *testing.T) {\n\ts := &stickyBalanceStrategy{}\n\n\ttopics := make(map[string][]int32, 6)\n\tfor i := 1; i <= 6; i++ {\n\t\ttopics[fmt.Sprintf(\"topic%d\", i)] = []int32{0}\n\t}\n\tmembers := make(map[string]ConsumerGroupMemberMetadata, 3)\n\tmembers[\"consumer1\"] = ConsumerGroupMemberMetadata{\n\t\tTopics:   []string{\"topic1\", \"topic2\"},\n\t\tUserData: encodeSubscriberPlan(t, map[string][]int32{\"topic1\": {0}}),\n\t}\n\tmembers[\"consumer2\"] = ConsumerGroupMemberMetadata{\n\t\tTopics:   []string{\"topic1\", \"topic2\", \"topic3\", \"topic4\"},\n\t\tUserData: encodeSubscriberPlan(t, map[string][]int32{\"topic2\": {0}, \"topic3\": {0}}),\n\t}\n\tmembers[\"consumer3\"] = ConsumerGroupMemberMetadata{\n\t\tTopics:   []string{\"topic2\", \"topic3\", \"topic4\", \"topic5\", \"topic6\"},\n\t\tUserData: encodeSubscriberPlan(t, map[string][]int32{\"topic4\": {0}, \"topic5\": {0}, \"topic6\": {0}}),\n\t}\n\n\tplan, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan, err)\n}\n\nfunc Test_stickyBalanceStrategy_Plan_Stickiness(t *testing.T) {\n\ts := &stickyBalanceStrategy{}\n\n\ttopics := map[string][]int32{\"topic1\": {0, 1, 2}}\n\tmembers := map[string]ConsumerGroupMemberMetadata{\n\t\t\"consumer1\": {Topics: []string{\"topic1\"}},\n\t\t\"consumer2\": {Topics: []string{\"topic1\"}},\n\t\t\"consumer3\": {Topics: []string{\"topic1\"}},\n\t\t\"consumer4\": {Topics: []string{\"topic1\"}},\n\t}\n\n\tplan1, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan1, err)\n\n\t// PLAN 2\n\t// remove the potential group leader\n\tdelete(members, \"consumer1\")\n\tfor i := 2; i <= 4; i++ {\n\t\tmembers[fmt.Sprintf(\"consumer%d\", i)] = ConsumerGroupMemberMetadata{\n\t\t\tTopics:   []string{\"topic1\"},\n\t\t\tUserData: encodeSubscriberPlan(t, plan1[fmt.Sprintf(\"consumer%d\", i)]),\n\t\t}\n\t}\n\n\tplan2, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan2, err)\n}\n\nfunc Test_stickyBalanceStrategy_Plan_AssignmentUpdatedForDeletedTopic(t *testing.T) {\n\ts := &stickyBalanceStrategy{}\n\n\ttopics := make(map[string][]int32, 2)\n\ttopics[\"topic1\"] = []int32{0}\n\ttopics[\"topic3\"] = make([]int32, 100)\n\tfor i := 0; i < 100; i++ {\n\t\ttopics[\"topic3\"][i] = int32(i)\n\t}\n\tmembers := map[string]ConsumerGroupMemberMetadata{\n\t\t\"consumer1\": {Topics: []string{\"topic1\", \"topic2\", \"topic3\"}},\n\t}\n\n\tplan, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan, err)\n\tverifyFullyBalanced(t, plan)\n\tif (len(plan[\"consumer1\"][\"topic1\"]) + len(plan[\"consumer1\"][\"topic3\"])) != 101 {\n\t\tt.Error(\"Incorrect number of partitions assigned\")\n\t\treturn\n\t}\n}\n\nfunc Test_stickyBalanceStrategy_Plan_NoExceptionRaisedWhenOnlySubscribedTopicDeleted(t *testing.T) {\n\ts := &stickyBalanceStrategy{}\n\n\ttopics := map[string][]int32{\"topic1\": {0, 1, 2}}\n\tmembers := map[string]ConsumerGroupMemberMetadata{\n\t\t\"consumer1\": {Topics: []string{\"topic1\"}},\n\t}\n\tplan1, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan1, err)\n\n\t// PLAN 2\n\tmembers[\"consumer1\"] = ConsumerGroupMemberMetadata{\n\t\tTopics:   members[\"consumer1\"].Topics,\n\t\tUserData: encodeSubscriberPlan(t, plan1[\"consumer1\"]),\n\t}\n\n\tplan2, err := s.Plan(members, map[string][]int32{})\n\tif len(plan2) != 1 {\n\t\tt.Error(\"Incorrect number of consumers\")\n\t\treturn\n\t}\n\tif len(plan2[\"consumer1\"]) != 0 {\n\t\tt.Error(\"Incorrect number of consumer topic assignments\")\n\t\treturn\n\t}\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan2, err)\n}\n\nfunc Test_stickyBalanceStrategy_Plan_AssignmentWithMultipleGenerations1(t *testing.T) {\n\ts := &stickyBalanceStrategy{}\n\n\ttopics := map[string][]int32{\"topic1\": {0, 1, 2, 3, 4, 5}}\n\tmembers := map[string]ConsumerGroupMemberMetadata{\n\t\t\"consumer1\": {Topics: []string{\"topic1\"}},\n\t\t\"consumer2\": {Topics: []string{\"topic1\"}},\n\t\t\"consumer3\": {Topics: []string{\"topic1\"}},\n\t}\n\tplan1, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan1, err)\n\tverifyFullyBalanced(t, plan1)\n\n\t// PLAN 2\n\tmembers[\"consumer1\"] = ConsumerGroupMemberMetadata{\n\t\tTopics:   []string{\"topic1\"},\n\t\tUserData: encodeSubscriberPlanWithGeneration(t, plan1[\"consumer1\"], 1),\n\t}\n\tmembers[\"consumer2\"] = ConsumerGroupMemberMetadata{\n\t\tTopics:   []string{\"topic1\"},\n\t\tUserData: encodeSubscriberPlanWithGeneration(t, plan1[\"consumer2\"], 1),\n\t}\n\tdelete(members, \"consumer3\")\n\n\tplan2, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan2, err)\n\tverifyFullyBalanced(t, plan2)\n\tif len(intersection(plan1[\"consumer1\"][\"topic1\"], plan2[\"consumer1\"][\"topic1\"])) != 2 {\n\t\tt.Error(\"stickyBalanceStrategy.Plan() consumer1 didn't maintain partitions across reassignment\")\n\t}\n\tif len(intersection(plan1[\"consumer2\"][\"topic1\"], plan2[\"consumer2\"][\"topic1\"])) != 2 {\n\t\tt.Error(\"stickyBalanceStrategy.Plan() consumer1 didn't maintain partitions across reassignment\")\n\t}\n\n\t// PLAN 3\n\tdelete(members, \"consumer1\")\n\tmembers[\"consumer2\"] = ConsumerGroupMemberMetadata{\n\t\tTopics:   []string{\"topic1\"},\n\t\tUserData: encodeSubscriberPlanWithGeneration(t, plan2[\"consumer2\"], 2),\n\t}\n\tmembers[\"consumer3\"] = ConsumerGroupMemberMetadata{\n\t\tTopics:   []string{\"topic1\"},\n\t\tUserData: encodeSubscriberPlanWithGeneration(t, plan1[\"consumer3\"], 1),\n\t}\n\n\tplan3, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan3, err)\n\tverifyFullyBalanced(t, plan3)\n}\n\nfunc Test_stickyBalanceStrategy_Plan_AssignmentWithMultipleGenerations2(t *testing.T) {\n\ts := &stickyBalanceStrategy{}\n\n\ttopics := map[string][]int32{\"topic1\": {0, 1, 2, 3, 4, 5}}\n\tmembers := map[string]ConsumerGroupMemberMetadata{\n\t\t\"consumer1\": {Topics: []string{\"topic1\"}},\n\t\t\"consumer2\": {Topics: []string{\"topic1\"}},\n\t\t\"consumer3\": {Topics: []string{\"topic1\"}},\n\t}\n\tplan1, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan1, err)\n\tverifyFullyBalanced(t, plan1)\n\n\t// PLAN 2\n\tdelete(members, \"consumer1\")\n\tmembers[\"consumer2\"] = ConsumerGroupMemberMetadata{\n\t\tTopics:   []string{\"topic1\"},\n\t\tUserData: encodeSubscriberPlanWithGeneration(t, plan1[\"consumer2\"], 1),\n\t}\n\tdelete(members, \"consumer3\")\n\n\tplan2, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan2, err)\n\tverifyFullyBalanced(t, plan2)\n\tif len(intersection(plan1[\"consumer2\"][\"topic1\"], plan2[\"consumer2\"][\"topic1\"])) != 2 {\n\t\tt.Error(\"stickyBalanceStrategy.Plan() consumer1 didn't maintain partitions across reassignment\")\n\t}\n\n\t// PLAN 3\n\tmembers[\"consumer1\"] = ConsumerGroupMemberMetadata{\n\t\tTopics:   []string{\"topic1\"},\n\t\tUserData: encodeSubscriberPlanWithGeneration(t, plan1[\"consumer1\"], 1),\n\t}\n\tmembers[\"consumer2\"] = ConsumerGroupMemberMetadata{\n\t\tTopics:   []string{\"topic1\"},\n\t\tUserData: encodeSubscriberPlanWithGeneration(t, plan2[\"consumer2\"], 2),\n\t}\n\tmembers[\"consumer3\"] = ConsumerGroupMemberMetadata{\n\t\tTopics:   []string{\"topic1\"},\n\t\tUserData: encodeSubscriberPlanWithGeneration(t, plan1[\"consumer3\"], 1),\n\t}\n\tplan3, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan3, err)\n\tverifyFullyBalanced(t, plan3)\n}\n\nfunc Test_stickyBalanceStrategy_Plan_AssignmentWithConflictingPreviousGenerations(t *testing.T) {\n\ts := &stickyBalanceStrategy{}\n\n\ttopics := map[string][]int32{\"topic1\": {0, 1, 2, 3, 4, 5}}\n\tmembers := make(map[string]ConsumerGroupMemberMetadata, 3)\n\tmembers[\"consumer1\"] = ConsumerGroupMemberMetadata{\n\t\tTopics:   []string{\"topic1\"},\n\t\tUserData: encodeSubscriberPlanWithGeneration(t, map[string][]int32{\"topic1\": {0, 1, 4}}, 1),\n\t}\n\tmembers[\"consumer2\"] = ConsumerGroupMemberMetadata{\n\t\tTopics:   []string{\"topic1\"},\n\t\tUserData: encodeSubscriberPlanWithGeneration(t, map[string][]int32{\"topic1\": {0, 2, 3}}, 1),\n\t}\n\tmembers[\"consumer3\"] = ConsumerGroupMemberMetadata{\n\t\tTopics:   []string{\"topic1\"},\n\t\tUserData: encodeSubscriberPlanWithGeneration(t, map[string][]int32{\"topic1\": {3, 4, 5}}, 2),\n\t}\n\n\tplan, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan, err)\n\tverifyFullyBalanced(t, plan)\n}\n\nfunc Test_stickyBalanceStrategy_Plan_SchemaBackwardCompatibility(t *testing.T) {\n\ts := &stickyBalanceStrategy{}\n\n\ttopics := map[string][]int32{\"topic1\": {0, 1, 2}}\n\tmembers := make(map[string]ConsumerGroupMemberMetadata, 3)\n\tmembers[\"consumer1\"] = ConsumerGroupMemberMetadata{\n\t\tTopics:   []string{\"topic1\"},\n\t\tUserData: encodeSubscriberPlanWithGeneration(t, map[string][]int32{\"topic1\": {0, 2}}, 1),\n\t}\n\tmembers[\"consumer2\"] = ConsumerGroupMemberMetadata{\n\t\tTopics:   []string{\"topic1\"},\n\t\tUserData: encodeSubscriberPlanWithOldSchema(t, map[string][]int32{\"topic1\": {1}}),\n\t}\n\tmembers[\"consumer3\"] = ConsumerGroupMemberMetadata{Topics: []string{\"topic1\"}}\n\n\tplan, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan, err)\n\tverifyFullyBalanced(t, plan)\n}\n\nfunc Test_stickyBalanceStrategy_Plan_ConflictingPreviousAssignments(t *testing.T) {\n\ts := &stickyBalanceStrategy{}\n\n\ttopics := map[string][]int32{\"topic1\": {0, 1}}\n\tmembers := make(map[string]ConsumerGroupMemberMetadata, 2)\n\tmembers[\"consumer1\"] = ConsumerGroupMemberMetadata{\n\t\tTopics:   []string{\"topic1\"},\n\t\tUserData: encodeSubscriberPlanWithGeneration(t, map[string][]int32{\"topic1\": {0, 1}}, 1),\n\t}\n\tmembers[\"consumer2\"] = ConsumerGroupMemberMetadata{\n\t\tTopics:   []string{\"topic1\"},\n\t\tUserData: encodeSubscriberPlanWithGeneration(t, map[string][]int32{\"topic1\": {0, 1}}, 1),\n\t}\n\n\tplan, err := s.Plan(members, topics)\n\tverifyPlanIsBalancedAndSticky(t, s, members, plan, err)\n\tverifyFullyBalanced(t, plan)\n}\n\nfunc Test_stickyBalanceStrategy_Plan_AssignmentData(t *testing.T) {\n\ts := &stickyBalanceStrategy{}\n\n\tmembers := make(map[string]ConsumerGroupMemberMetadata, 2)\n\tmembers[\"consumer1\"] = ConsumerGroupMemberMetadata{\n\t\tTopics: []string{\"topic1\"},\n\t}\n\tmembers[\"consumer2\"] = ConsumerGroupMemberMetadata{\n\t\tTopics: []string{\"topic1\"},\n\t}\n\n\texpected := encodeSubscriberPlanWithGeneration(t, map[string][]int32{\"topic1\": {0, 1}}, 1)\n\n\tactual, err := s.AssignmentData(\"consumer1\", map[string][]int32{\"topic1\": {0, 1}}, 1)\n\tif err != nil {\n\t\tt.Errorf(\"Error building assignment data: %v\", err)\n\t}\n\tif !bytes.Equal(expected, actual) {\n\t\tt.Error(\"Invalid assignment data returned from AssignmentData\")\n\t}\n}\n\nfunc Test_stickyBalanceStrategy_Plan_data_race(t *testing.T) {\n\tfor i := 0; i < 1000; i++ {\n\t\tgo func(bs BalanceStrategy) {\n\t\t\tmembers := map[string]ConsumerGroupMemberMetadata{\n\t\t\t\t\"m1\": {\n\t\t\t\t\tVersion: 3,\n\t\t\t\t\tTopics:  []string{\"topic\"},\n\t\t\t\t},\n\t\t\t}\n\t\t\ttopics := map[string][]int32{\n\t\t\t\t\"topic\": {0, 1, 2},\n\t\t\t}\n\t\t\t_, _ = bs.Plan(members, topics)\n\t\t}(NewBalanceStrategySticky())\n\t}\n}\n\nfunc BenchmarkStickAssignmentWithLargeNumberOfConsumersAndTopics(b *testing.B) {\n\ts := &stickyBalanceStrategy{}\n\tr := rand.New(rand.NewSource(time.Now().UnixNano()))\n\tmembers := make(map[string]ConsumerGroupMemberMetadata, 20)\n\tfor i := 0; i < 200; i++ {\n\t\ttopics := make([]string, 200)\n\t\tfor j := 0; j < 200; j++ {\n\t\t\ttopics[j] = fmt.Sprintf(\"topic%d\", j)\n\t\t}\n\t\tmembers[fmt.Sprintf(\"consumer%d\", i)] = ConsumerGroupMemberMetadata{Topics: topics}\n\t}\n\ttopics := make(map[string][]int32, 40)\n\tfor i := 0; i < 40; i++ {\n\t\tpartitionCount := r.Intn(20)\n\t\tpartitions := make([]int32, partitionCount)\n\t\tfor j := 0; j < partitionCount; j++ {\n\t\t\tpartitions[j] = int32(j)\n\t\t}\n\t\ttopics[fmt.Sprintf(\"topic%d\", i)] = partitions\n\t}\n\n\tfor b.Loop() {\n\t\tif _, err := s.Plan(members, topics); err != nil {\n\t\t\tb.Errorf(\"Error building plan in benchmark: %v\", err)\n\t\t}\n\t}\n}\n\nfunc BenchmarkStickAssignmentWithLargeNumberOfConsumersAndTopicsAndExistingAssignments(b *testing.B) {\n\ts := &stickyBalanceStrategy{}\n\tr := rand.New(rand.NewSource(time.Now().UnixNano()))\n\tmembers := make(map[string]ConsumerGroupMemberMetadata, 20)\n\tfor i := 0; i < 200; i++ {\n\t\ttopics := make([]string, 200)\n\t\tfor j := 0; j < 200; j++ {\n\t\t\ttopics[j] = fmt.Sprintf(\"topic%d\", j)\n\t\t}\n\t\tmembers[fmt.Sprintf(\"consumer%d\", i)] = ConsumerGroupMemberMetadata{Topics: topics}\n\t}\n\ttopics := make(map[string][]int32, 40)\n\tfor i := 0; i < 40; i++ {\n\t\tpartitionCount := r.Intn(20)\n\t\tpartitions := make([]int32, partitionCount)\n\t\tfor j := 0; j < partitionCount; j++ {\n\t\t\tpartitions[j] = int32(j)\n\t\t}\n\t\ttopics[fmt.Sprintf(\"topic%d\", i)] = partitions\n\t}\n\tplan, _ := s.Plan(members, topics)\n\n\tfor i := 0; i < 200; i++ {\n\t\tmembers[fmt.Sprintf(\"consumer%d\", i)] = ConsumerGroupMemberMetadata{\n\t\t\tTopics:   members[fmt.Sprintf(\"consumer%d\", i)].Topics,\n\t\t\tUserData: encodeSubscriberPlanWithGenerationForBenchmark(b, plan[fmt.Sprintf(\"consumer%d\", i)], 1),\n\t\t}\n\t}\n\tfor i := 0; i < 1; i++ {\n\t\tdelete(members, fmt.Sprintf(\"consumer%d\", i))\n\t}\n\n\tfor b.Loop() {\n\t\tif _, err := s.Plan(members, topics); err != nil {\n\t\t\tb.Errorf(\"Error building plan in benchmark: %v\", err)\n\t\t}\n\t}\n}\n\nfunc verifyPlanIsBalancedAndSticky(t *testing.T, s *stickyBalanceStrategy, members map[string]ConsumerGroupMemberMetadata, plan BalanceStrategyPlan, err error) {\n\tt.Helper()\n\tif err != nil {\n\t\tt.Errorf(\"stickyBalanceStrategy.Plan() error = %v\", err)\n\t\treturn\n\t}\n\tif !s.movements.isSticky() {\n\t\tt.Error(\"stickyBalanceStrategy.Plan() not sticky\")\n\t\treturn\n\t}\n\tverifyValidityAndBalance(t, members, plan)\n}\n\nfunc verifyValidityAndBalance(t *testing.T, consumers map[string]ConsumerGroupMemberMetadata, plan BalanceStrategyPlan) {\n\tt.Helper()\n\tsize := len(consumers)\n\tif size != len(plan) {\n\t\tt.Errorf(\"Subscription size (%d) not equal to plan size (%d)\", size, len(plan))\n\t\tt.FailNow()\n\t}\n\n\tmembers := make([]string, size)\n\ti := 0\n\tfor memberID := range consumers {\n\t\tmembers[i] = memberID\n\t\ti++\n\t}\n\tsort.Strings(members)\n\n\tfor i, memberID := range members {\n\t\tfor assignedTopic := range plan[memberID] {\n\t\t\tif !slices.Contains(consumers[memberID].Topics, assignedTopic) {\n\t\t\t\tt.Errorf(\"Consumer %s had assigned topic %q that wasn't in the list of assignable topics %v\", memberID, assignedTopic, consumers[memberID].Topics)\n\t\t\t\tt.FailNow()\n\t\t\t}\n\t\t}\n\n\t\t// skip last consumer\n\t\tif i == len(members)-1 {\n\t\t\tcontinue\n\t\t}\n\n\t\tconsumerAssignments := make([]topicPartitionAssignment, 0)\n\t\tfor topic, partitions := range plan[memberID] {\n\t\t\tfor _, partition := range partitions {\n\t\t\t\tconsumerAssignments = append(consumerAssignments, topicPartitionAssignment{Topic: topic, Partition: partition})\n\t\t\t}\n\t\t}\n\n\t\tfor j := i + 1; j < size; j++ {\n\t\t\totherConsumer := members[j]\n\t\t\totherConsumerAssignments := make([]topicPartitionAssignment, 0)\n\t\t\tfor topic, partitions := range plan[otherConsumer] {\n\t\t\t\tfor _, partition := range partitions {\n\t\t\t\t\totherConsumerAssignments = append(otherConsumerAssignments, topicPartitionAssignment{Topic: topic, Partition: partition})\n\t\t\t\t}\n\t\t\t}\n\t\t\tassignmentsIntersection := intersection(consumerAssignments, otherConsumerAssignments)\n\t\t\tif len(assignmentsIntersection) > 0 {\n\t\t\t\tt.Errorf(\"Consumers %s and %s have common partitions assigned to them: %v\", memberID, otherConsumer, assignmentsIntersection)\n\t\t\t\tt.FailNow()\n\t\t\t}\n\n\t\t\tif math.Abs(float64(len(consumerAssignments)-len(otherConsumerAssignments))) <= 1 {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif len(consumerAssignments) > len(otherConsumerAssignments) {\n\t\t\t\tfor _, topic := range consumerAssignments {\n\t\t\t\t\tif _, exists := plan[otherConsumer][topic.Topic]; exists {\n\t\t\t\t\t\tt.Errorf(\"Some partitions can be moved from %s to %s to achieve a better balance, %s has %d assignments, and %s has %d assignments\", otherConsumer, memberID, memberID, len(consumerAssignments), otherConsumer, len(otherConsumerAssignments))\n\t\t\t\t\t\tt.FailNow()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif len(otherConsumerAssignments) > len(consumerAssignments) {\n\t\t\t\tfor _, topic := range otherConsumerAssignments {\n\t\t\t\t\tif _, exists := plan[memberID][topic.Topic]; exists {\n\t\t\t\t\t\tt.Errorf(\"Some partitions can be moved from %s to %s to achieve a better balance, %s has %d assignments, and %s has %d assignments\", memberID, otherConsumer, otherConsumer, len(otherConsumerAssignments), memberID, len(consumerAssignments))\n\t\t\t\t\t\tt.FailNow()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Produces the intersection of two slices\n// From https://github.com/juliangruber/go-intersect\nfunc intersection(a interface{}, b interface{}) []interface{} {\n\tset := make([]interface{}, 0)\n\thash := make(map[interface{}]bool)\n\tav := reflect.ValueOf(a)\n\tbv := reflect.ValueOf(b)\n\n\tfor i := 0; i < av.Len(); i++ {\n\t\tel := av.Index(i).Interface()\n\t\thash[el] = true\n\t}\n\n\tfor i := 0; i < bv.Len(); i++ {\n\t\tel := bv.Index(i).Interface()\n\t\tif _, found := hash[el]; found {\n\t\t\tset = append(set, el)\n\t\t}\n\t}\n\n\treturn set\n}\n\nfunc encodeSubscriberPlan(t *testing.T, assignments map[string][]int32) []byte {\n\treturn encodeSubscriberPlanWithGeneration(t, assignments, defaultGeneration)\n}\n\nfunc encodeSubscriberPlanWithGeneration(t *testing.T, assignments map[string][]int32, generation int32) []byte {\n\tuserDataBytes, err := encode(&StickyAssignorUserDataV1{\n\t\tTopics:     assignments,\n\t\tGeneration: generation,\n\t}, nil)\n\tif err != nil {\n\t\tt.Errorf(\"encodeSubscriberPlan error = %v\", err)\n\t\tt.FailNow()\n\t}\n\treturn userDataBytes\n}\n\nfunc encodeSubscriberPlanWithGenerationForBenchmark(b *testing.B, assignments map[string][]int32, generation int32) []byte {\n\tuserDataBytes, err := encode(&StickyAssignorUserDataV1{\n\t\tTopics:     assignments,\n\t\tGeneration: generation,\n\t}, nil)\n\tif err != nil {\n\t\tb.Errorf(\"encodeSubscriberPlan error = %v\", err)\n\t\tb.FailNow()\n\t}\n\treturn userDataBytes\n}\n\nfunc encodeSubscriberPlanWithOldSchema(t *testing.T, assignments map[string][]int32) []byte {\n\tuserDataBytes, err := encode(&StickyAssignorUserDataV0{\n\t\tTopics: assignments,\n\t}, nil)\n\tif err != nil {\n\t\tt.Errorf(\"encodeSubscriberPlan error = %v\", err)\n\t\tt.FailNow()\n\t}\n\treturn userDataBytes\n}\n\n// verify that the plan is fully balanced, assumes that all consumers can\n// consume from the same set of topics\nfunc verifyFullyBalanced(t *testing.T, plan BalanceStrategyPlan) {\n\tmin := math.MaxInt32\n\tmax := math.MinInt32\n\tfor _, topics := range plan {\n\t\tassignedPartitionsCount := 0\n\t\tfor _, partitions := range topics {\n\t\t\tassignedPartitionsCount += len(partitions)\n\t\t}\n\t\tif assignedPartitionsCount < min {\n\t\t\tmin = assignedPartitionsCount\n\t\t}\n\t\tif assignedPartitionsCount > max {\n\t\t\tmax = assignedPartitionsCount\n\t\t}\n\t}\n\tif (max - min) > 1 {\n\t\tt.Errorf(\"Plan partition assignment is not fully balanced: min=%d, max=%d\", min, max)\n\t}\n}\n\nfunc getRandomSublist(r *rand.Rand, s []string) []string {\n\thowManyToRemove := r.Intn(len(s))\n\tallEntriesMap := make(map[int]string)\n\tfor i, s := range s {\n\t\tallEntriesMap[i] = s\n\t}\n\tfor i := 0; i < howManyToRemove; i++ {\n\t\tdelete(allEntriesMap, r.Intn(len(allEntriesMap)))\n\t}\n\n\tsubList := make([]string, len(allEntriesMap))\n\ti := 0\n\tfor _, s := range allEntriesMap {\n\t\tsubList[i] = s\n\t\ti++\n\t}\n\treturn subList\n}\n\nfunc Test_sortPartitionsByPotentialConsumerAssignments(t *testing.T) {\n\ttype args struct {\n\t\tpartition2AllPotentialConsumers map[topicPartitionAssignment][]string\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant []topicPartitionAssignment\n\t}{\n\t\t{\n\t\t\tname: \"Single topic partition\",\n\t\t\targs: args{\n\t\t\t\tpartition2AllPotentialConsumers: map[topicPartitionAssignment][]string{\n\t\t\t\t\t{\n\t\t\t\t\t\tTopic:     \"t1\",\n\t\t\t\t\t\tPartition: 0,\n\t\t\t\t\t}: {\"c1\", \"c2\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: []topicPartitionAssignment{\n\t\t\t\t{\n\t\t\t\t\tTopic:     \"t1\",\n\t\t\t\t\tPartition: 0,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Multiple topic partitions with the same number of consumers but different topic names\",\n\t\t\targs: args{\n\t\t\t\tpartition2AllPotentialConsumers: map[topicPartitionAssignment][]string{\n\t\t\t\t\t{\n\t\t\t\t\t\tTopic:     \"t1\",\n\t\t\t\t\t\tPartition: 0,\n\t\t\t\t\t}: {\"c1\", \"c2\"},\n\t\t\t\t\t{\n\t\t\t\t\t\tTopic:     \"t2\",\n\t\t\t\t\t\tPartition: 0,\n\t\t\t\t\t}: {\"c1\", \"c2\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: []topicPartitionAssignment{\n\t\t\t\t{\n\t\t\t\t\tTopic:     \"t1\",\n\t\t\t\t\tPartition: 0,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tTopic:     \"t2\",\n\t\t\t\t\tPartition: 0,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Multiple topic partitions with the same number of consumers and topic names\",\n\t\t\targs: args{\n\t\t\t\tpartition2AllPotentialConsumers: map[topicPartitionAssignment][]string{\n\t\t\t\t\t{\n\t\t\t\t\t\tTopic:     \"t1\",\n\t\t\t\t\t\tPartition: 0,\n\t\t\t\t\t}: {\"c1\", \"c2\"},\n\t\t\t\t\t{\n\t\t\t\t\t\tTopic:     \"t1\",\n\t\t\t\t\t\tPartition: 1,\n\t\t\t\t\t}: {\"c1\", \"c2\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: []topicPartitionAssignment{\n\t\t\t\t{\n\t\t\t\t\tTopic:     \"t1\",\n\t\t\t\t\tPartition: 0,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tTopic:     \"t1\",\n\t\t\t\t\tPartition: 1,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := sortPartitionsByPotentialConsumerAssignments(tt.args.partition2AllPotentialConsumers); !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"sortPartitionsByPotentialConsumerAssignments() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "broker.go",
    "content": "package sarama\n\nimport (\n\t\"crypto/tls\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"math/rand\"\n\t\"net\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/rcrowley/go-metrics\"\n)\n\n// Broker represents a single Kafka broker connection. All operations on this object are entirely concurrency-safe.\ntype Broker struct {\n\tconf *Config\n\track *string\n\n\tid            int32\n\taddr          string\n\tcorrelationID int32\n\tconn          net.Conn\n\tconnErr       error\n\tlock          sync.Mutex\n\topened        atomic.Bool\n\tresponses     chan *responsePromise\n\tdone          chan bool\n\n\tmetricRegistry             metrics.Registry\n\tincomingByteRate           metrics.Meter\n\trequestRate                metrics.Meter\n\tfetchRate                  metrics.Meter\n\trequestSize                metrics.Histogram\n\trequestLatency             metrics.Histogram\n\toutgoingByteRate           metrics.Meter\n\tresponseRate               metrics.Meter\n\tresponseSize               metrics.Histogram\n\trequestsInFlight           metrics.Counter\n\tprotocolRequestsRate       map[int16]metrics.Meter\n\tbrokerIncomingByteRate     metrics.Meter\n\tbrokerRequestRate          metrics.Meter\n\tbrokerFetchRate            metrics.Meter\n\tbrokerRequestSize          metrics.Histogram\n\tbrokerRequestLatency       metrics.Histogram\n\tbrokerOutgoingByteRate     metrics.Meter\n\tbrokerResponseRate         metrics.Meter\n\tbrokerResponseSize         metrics.Histogram\n\tbrokerRequestsInFlight     metrics.Counter\n\tbrokerThrottleTime         metrics.Histogram\n\tbrokerProtocolRequestsRate map[int16]metrics.Meter\n\tbrokerAPIVersions          apiVersionMap\n\n\tkerberosAuthenticator               GSSAPIKerberosAuth\n\tclientSessionReauthenticationTimeMs int64\n\n\tthrottleTimer     *time.Timer\n\tthrottleTimerLock sync.Mutex\n}\n\n// SASLMechanism specifies the SASL mechanism the client uses to authenticate with the broker\ntype SASLMechanism string\n\nconst (\n\t// SASLTypeOAuth represents the SASL/OAUTHBEARER mechanism (Kafka 2.0.0+)\n\tSASLTypeOAuth = \"OAUTHBEARER\"\n\t// SASLTypePlaintext represents the SASL/PLAIN mechanism\n\tSASLTypePlaintext = \"PLAIN\"\n\t// SASLTypeSCRAMSHA256 represents the SCRAM-SHA-256 mechanism.\n\tSASLTypeSCRAMSHA256 = \"SCRAM-SHA-256\"\n\t// SASLTypeSCRAMSHA512 represents the SCRAM-SHA-512 mechanism.\n\tSASLTypeSCRAMSHA512 = \"SCRAM-SHA-512\"\n\tSASLTypeGSSAPI      = \"GSSAPI\"\n\t// SASLHandshakeV0 is v0 of the Kafka SASL handshake protocol. Client and\n\t// server negotiate SASL auth using opaque packets.\n\tSASLHandshakeV0 = int16(0)\n\t// SASLHandshakeV1 is v1 of the Kafka SASL handshake protocol. Client and\n\t// server negotiate SASL by wrapping tokens with Kafka protocol headers.\n\tSASLHandshakeV1 = int16(1)\n\t// SASLExtKeyAuth is the reserved extension key name sent as part of the\n\t// SASL/OAUTHBEARER initial client response\n\tSASLExtKeyAuth = \"auth\"\n)\n\n// AccessToken contains an access token used to authenticate a\n// SASL/OAUTHBEARER client along with associated metadata.\ntype AccessToken struct {\n\t// Token is the access token payload.\n\tToken string\n\t// Extensions is a optional map of arbitrary key-value pairs that can be\n\t// sent with the SASL/OAUTHBEARER initial client response. These values are\n\t// ignored by the SASL server if they are unexpected. This feature is only\n\t// supported by Kafka >= 2.1.0.\n\tExtensions map[string]string\n}\n\n// AccessTokenProvider is the interface that encapsulates how implementors\n// can generate access tokens for Kafka broker authentication.\ntype AccessTokenProvider interface {\n\t// Token returns an access token. The implementation should ensure token\n\t// reuse so that multiple calls at connect time do not create multiple\n\t// tokens. The implementation should also periodically refresh the token in\n\t// order to guarantee that each call returns an unexpired token.  This\n\t// method should not block indefinitely--a timeout error should be returned\n\t// after a short period of inactivity so that the broker connection logic\n\t// can log debugging information and retry.\n\tToken() (*AccessToken, error)\n}\n\n// SCRAMClient is a an interface to a SCRAM\n// client implementation.\ntype SCRAMClient interface {\n\t// Begin prepares the client for the SCRAM exchange\n\t// with the server with a user name and a password\n\tBegin(userName, password, authzID string) error\n\t// Step steps client through the SCRAM exchange. It is\n\t// called repeatedly until it errors or `Done` returns true.\n\tStep(challenge string) (response string, err error)\n\t// Done should return true when the SCRAM conversation\n\t// is over.\n\tDone() bool\n}\n\ntype responsePromise struct {\n\trequestTime   time.Time\n\tcorrelationID int32\n\tresponse      protocolBody\n\thandler       func([]byte, error)\n\tpackets       chan []byte\n\terrors        chan error\n}\n\nfunc (p *responsePromise) handle(packets []byte, err error) {\n\t// Use callback when provided\n\tif p.handler != nil {\n\t\tp.handler(packets, err)\n\t\treturn\n\t}\n\t// Otherwise fallback to using channels\n\tif err != nil {\n\t\tp.errors <- err\n\t\treturn\n\t}\n\tp.packets <- packets\n}\n\n// NewBroker creates and returns a Broker targeting the given host:port address.\n// This does not attempt to actually connect, you have to call Open() for that.\nfunc NewBroker(addr string) *Broker {\n\treturn &Broker{id: -1, addr: addr}\n}\n\n// Open tries to connect to the Broker if it is not already connected or connecting, but does not block\n// waiting for the connection to complete. This means that any subsequent operations on the broker will\n// block waiting for the connection to succeed or fail. To get the effect of a fully synchronous Open call,\n// follow it by a call to Connected(). The only errors Open will return directly are ConfigurationError or\n// AlreadyConnected. If conf is nil, the result of NewConfig() is used.\nfunc (b *Broker) Open(conf *Config) error {\n\tif !b.opened.CompareAndSwap(false, true) {\n\t\treturn ErrAlreadyConnected\n\t}\n\n\tif conf == nil {\n\t\tconf = NewConfig()\n\t}\n\n\terr := conf.Validate()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tb.lock.Lock()\n\n\tif b.metricRegistry == nil {\n\t\tb.metricRegistry = newCleanupRegistry(conf.MetricRegistry)\n\t}\n\n\tgo withRecover(func() {\n\t\tdefer b.lock.Unlock()\n\n\t\tdialer := conf.getDialer()\n\t\tb.conn, b.connErr = dialer.Dial(\"tcp\", b.addr)\n\t\tif b.connErr != nil {\n\t\t\tLogger.Printf(\"Failed to connect to broker %s: %s\\n\", b.addr, b.connErr)\n\t\t\tb.conn = nil\n\t\t\tb.opened.Store(false)\n\t\t\treturn\n\t\t}\n\t\tif conf.Net.TLS.Enable {\n\t\t\tb.conn = tls.Client(b.conn, validServerNameTLS(b.addr, conf.Net.TLS.Config))\n\t\t}\n\n\t\tb.conn = newBufConn(b.conn)\n\t\tb.conf = conf\n\n\t\t// Create or reuse the global metrics shared between brokers\n\t\tb.incomingByteRate = metrics.GetOrRegisterMeter(\"incoming-byte-rate\", b.metricRegistry)\n\t\tb.requestRate = metrics.GetOrRegisterMeter(\"request-rate\", b.metricRegistry)\n\t\tb.fetchRate = metrics.GetOrRegisterMeter(\"consumer-fetch-rate\", b.metricRegistry)\n\t\tb.requestSize = getOrRegisterHistogram(\"request-size\", b.metricRegistry)\n\t\tb.requestLatency = getOrRegisterHistogram(\"request-latency-in-ms\", b.metricRegistry)\n\t\tb.outgoingByteRate = metrics.GetOrRegisterMeter(\"outgoing-byte-rate\", b.metricRegistry)\n\t\tb.responseRate = metrics.GetOrRegisterMeter(\"response-rate\", b.metricRegistry)\n\t\tb.responseSize = getOrRegisterHistogram(\"response-size\", b.metricRegistry)\n\t\tb.requestsInFlight = metrics.GetOrRegisterCounter(\"requests-in-flight\", b.metricRegistry)\n\t\tb.protocolRequestsRate = map[int16]metrics.Meter{}\n\t\t// Do not gather metrics for seeded broker (only used during bootstrap) because they share\n\t\t// the same id (-1) and are already exposed through the global metrics above\n\t\tif b.id >= 0 && !metrics.UseNilMetrics {\n\t\t\tb.registerMetrics()\n\t\t}\n\n\t\t// Send an ApiVersionsRequest to identify the client (KIP-511).\n\t\t// Store the response in the brokerAPIVersions map.\n\t\t// It will be used to determine the supported API versions for each request.\n\t\t// This should happen before SASL authentication: https://kafka.apache.org/26/protocol.html#api_versions\n\t\tif conf.ApiVersionsRequest {\n\t\t\tapiVersionsResponse, err := b.sendAndReceiveApiVersions(3)\n\t\t\tif err != nil {\n\t\t\t\tif b.maybeCloseLocked(err) {\n\t\t\t\t\t// Open is async, so we can't return the error here; surface it via Connected().\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tLogger.Printf(\"Error while sending ApiVersionsRequest V3 to broker %s: %s\\n\", b.addr, err)\n\t\t\t\t// send a lower version request in case remote cluster is <= 2.4.0.0\n\t\t\t\tmaxVersion := int16(0)\n\t\t\t\tif apiVersionsResponse != nil {\n\t\t\t\t\tfor _, k := range apiVersionsResponse.ApiKeys {\n\t\t\t\t\t\tif k.ApiKey == apiKeyApiVersions {\n\t\t\t\t\t\t\tmaxVersion = k.MaxVersion\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tapiVersionsResponse, err = b.sendAndReceiveApiVersions(maxVersion)\n\t\t\t\tif err != nil {\n\t\t\t\t\tif b.maybeCloseLocked(err) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tLogger.Printf(\"Error while sending ApiVersionsRequest V%d to broker %s: %s\\n\", maxVersion, b.addr, err)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif apiVersionsResponse != nil {\n\t\t\t\tb.brokerAPIVersions = make(apiVersionMap, len(apiVersionsResponse.ApiKeys))\n\t\t\t\tfor _, key := range apiVersionsResponse.ApiKeys {\n\t\t\t\t\tb.brokerAPIVersions[key.ApiKey] = &apiVersionRange{\n\t\t\t\t\t\tminVersion: key.MinVersion,\n\t\t\t\t\t\tmaxVersion: key.MaxVersion,\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif conf.Net.SASL.Mechanism == SASLTypeOAuth && conf.Net.SASL.Version == SASLHandshakeV0 {\n\t\t\tconf.Net.SASL.Version = SASLHandshakeV1\n\t\t}\n\n\t\tuseSaslV0 := conf.Net.SASL.Version == SASLHandshakeV0\n\t\tif conf.Net.SASL.Enable && useSaslV0 {\n\t\t\tb.connErr = b.authenticateViaSASLv0()\n\n\t\t\tif b.connErr != nil {\n\t\t\t\terr = b.conn.Close()\n\t\t\t\tif err == nil {\n\t\t\t\t\tDebugLogger.Printf(\"Closed connection to broker %s due to SASL v0 auth error: %s\\n\", b.addr, b.connErr)\n\t\t\t\t} else {\n\t\t\t\t\tLogger.Printf(\"Error while closing connection to broker %s (due to SASL v0 auth error: %s): %s\\n\", b.addr, b.connErr, err)\n\t\t\t\t}\n\t\t\t\tb.conn = nil\n\t\t\t\tb.opened.Store(false)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tb.done = make(chan bool)\n\t\tb.responses = make(chan *responsePromise, b.conf.Net.MaxOpenRequests-1)\n\n\t\tgo withRecover(b.responseReceiver)\n\t\tif conf.Net.SASL.Enable && !useSaslV0 {\n\t\t\tb.connErr = b.authenticateViaSASLv1()\n\t\t\tif b.connErr != nil {\n\t\t\t\tclose(b.responses)\n\t\t\t\t<-b.done\n\t\t\t\tb.responses = nil\n\t\t\t\tb.done = nil\n\t\t\t\terr = b.conn.Close()\n\t\t\t\tif err == nil {\n\t\t\t\t\tDebugLogger.Printf(\"Closed connection to broker %s due to SASL v1 auth error: %s\\n\", b.addr, b.connErr)\n\t\t\t\t} else {\n\t\t\t\t\tLogger.Printf(\"Error while closing connection to broker %s (due to SASL v1 auth error: %s): %s\\n\", b.addr, b.connErr, err)\n\t\t\t\t}\n\t\t\t\tb.conn = nil\n\t\t\t\tb.opened.Store(false)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tif b.id >= 0 {\n\t\t\tDebugLogger.Printf(\"Connected to broker at %s (registered as #%d)\\n\", b.addr, b.id)\n\t\t} else {\n\t\t\tDebugLogger.Printf(\"Connected to broker at %s (unregistered)\\n\", b.addr)\n\t\t}\n\t})\n\n\treturn nil\n}\n\nfunc (b *Broker) ResponseSize() int {\n\tb.lock.Lock()\n\tdefer b.lock.Unlock()\n\n\treturn len(b.responses)\n}\n\n// Connected returns true if the broker is connected and false otherwise. If the broker is not\n// connected but it had tried to connect, the error from that connection attempt is also returned.\nfunc (b *Broker) Connected() (bool, error) {\n\tb.lock.Lock()\n\tdefer b.lock.Unlock()\n\n\treturn b.conn != nil, b.connErr\n}\n\n// TLSConnectionState returns the client's TLS connection state. The second return value is false if this is not a tls connection or the connection has not yet been established.\nfunc (b *Broker) TLSConnectionState() (state tls.ConnectionState, ok bool) {\n\tb.lock.Lock()\n\tdefer b.lock.Unlock()\n\n\tif b.conn == nil {\n\t\treturn state, false\n\t}\n\tconn := b.conn\n\tif bconn, ok := b.conn.(*bufConn); ok {\n\t\tconn = bconn.Conn\n\t}\n\tif tc, ok := conn.(*tls.Conn); ok {\n\t\treturn tc.ConnectionState(), true\n\t}\n\treturn state, false\n}\n\n// Close closes the broker resources\nfunc (b *Broker) Close() error {\n\tb.lock.Lock()\n\tdefer b.lock.Unlock()\n\n\tb.connErr = nil\n\treturn b.closeLocked()\n}\n\n// maybeCloseLocked closes on transport errors and reports whether a close was performed.\n// NOTE: caller must hold b.lock.\nfunc (b *Broker) maybeCloseLocked(err error) bool {\n\tif !shouldCloseBrokerConn(err) {\n\t\treturn false\n\t}\n\n\tb.connErr = err\n\t_ = b.closeLocked()\n\treturn true\n}\n\n// closeLocked closes the broker connection and resets state.\n// NOTE: caller must hold b.lock.\nfunc (b *Broker) closeLocked() error {\n\tif b.conn == nil {\n\t\treturn ErrNotConnected\n\t}\n\n\tif b.responses != nil {\n\t\tclose(b.responses)\n\t}\n\tif b.done != nil {\n\t\t<-b.done\n\t}\n\n\terr := b.conn.Close()\n\n\tb.conn = nil\n\tb.responses = nil\n\tb.done = nil\n\n\tb.metricRegistry.UnregisterAll()\n\n\tif err == nil {\n\t\tDebugLogger.Printf(\"Closed connection to broker %s\\n\", b.addr)\n\t} else {\n\t\tLogger.Printf(\"Error while closing connection to broker %s: %s\\n\", b.addr, err)\n\t}\n\tb.opened.Store(false)\n\n\treturn err\n}\n\n// ID returns the broker ID retrieved from Kafka's metadata, or -1 if that is not known.\nfunc (b *Broker) ID() int32 {\n\treturn b.id\n}\n\n// Addr returns the broker address as either retrieved from Kafka's metadata or passed to NewBroker.\nfunc (b *Broker) Addr() string {\n\treturn b.addr\n}\n\n// Rack returns the broker's rack as retrieved from Kafka's metadata or the\n// empty string if it is not known.  The returned value corresponds to the\n// broker's broker.rack configuration setting.  Requires protocol version to be\n// at least v0.10.0.0.\nfunc (b *Broker) Rack() string {\n\tif b.rack == nil {\n\t\treturn \"\"\n\t}\n\treturn *b.rack\n}\n\n// GetMetadata send a metadata request and returns a metadata response or error\nfunc (b *Broker) GetMetadata(request *MetadataRequest) (*MetadataResponse, error) {\n\tresponse := new(MetadataResponse)\n\tresponse.Version = request.Version // Required to ensure use of the correct response header version\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\nfunc (b *Broker) DescribeCluster(request *DescribeClusterRequest) (*DescribeClusterResponse, error) {\n\tresponse := new(DescribeClusterResponse)\n\tresponse.Version = request.Version\n\n\tif err := b.sendAndReceive(request, response); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// GetConsumerMetadata send a consumer metadata request and returns a consumer metadata response or error\nfunc (b *Broker) GetConsumerMetadata(request *ConsumerMetadataRequest) (*ConsumerMetadataResponse, error) {\n\tresponse := new(ConsumerMetadataResponse)\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// FindCoordinator sends a find coordinate request and returns a response or error\nfunc (b *Broker) FindCoordinator(request *FindCoordinatorRequest) (*FindCoordinatorResponse, error) {\n\tresponse := new(FindCoordinatorResponse)\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// GetAvailableOffsets return an offset response or error\nfunc (b *Broker) GetAvailableOffsets(request *OffsetRequest) (*OffsetResponse, error) {\n\tresponse := new(OffsetResponse)\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// ProduceCallback function is called once the produce response has been parsed\n// or could not be read.\ntype ProduceCallback func(*ProduceResponse, error)\n\n// AsyncProduce sends a produce request and eventually call the provided callback\n// with a produce response or an error.\n//\n// Waiting for the response is generally not blocking on the contrary to using Produce.\n// If the maximum number of in flight request configured is reached then\n// the request will be blocked till a previous response is received.\n//\n// When configured with RequiredAcks == NoResponse, the callback will not be invoked.\n// If an error is returned because the request could not be sent then the callback\n// will not be invoked either.\n//\n// Make sure not to Close the broker in the callback as it will lead to a deadlock.\nfunc (b *Broker) AsyncProduce(request *ProduceRequest, cb ProduceCallback) error {\n\tb.lock.Lock()\n\tdefer b.lock.Unlock()\n\n\tneedAcks := request.RequiredAcks != NoResponse\n\t// Use a nil promise when no acks is required\n\tvar promise *responsePromise\n\n\tif needAcks {\n\t\tmetricRegistry := b.metricRegistry\n\n\t\t// Create ProduceResponse early to provide the header version\n\t\tres := new(ProduceResponse)\n\t\tpromise = &responsePromise{\n\t\t\tresponse: res,\n\t\t\t// Packets will be converted to a ProduceResponse in the responseReceiver goroutine\n\t\t\thandler: func(packets []byte, err error) {\n\t\t\t\tif err != nil {\n\t\t\t\t\t// Failed request\n\t\t\t\t\tcb(nil, err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif err := versionedDecode(packets, res, request.version(), metricRegistry); err != nil {\n\t\t\t\t\t// Malformed response\n\t\t\t\t\tcb(nil, err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t// Well-formed response\n\t\t\t\tb.handleThrottledResponse(res)\n\t\t\t\tcb(res, nil)\n\t\t\t},\n\t\t}\n\t}\n\n\treturn b.sendWithPromise(request, promise)\n}\n\n// Produce returns a produce response or error\nfunc (b *Broker) Produce(request *ProduceRequest) (*ProduceResponse, error) {\n\tvar (\n\t\tresponse *ProduceResponse\n\t\terr      error\n\t)\n\n\tif request.RequiredAcks == NoResponse {\n\t\terr = b.sendAndReceive(request, nil)\n\t} else {\n\t\tresponse = new(ProduceResponse)\n\t\terr = b.sendAndReceive(request, response)\n\t}\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// Fetch returns a FetchResponse or error\nfunc (b *Broker) Fetch(request *FetchRequest) (*FetchResponse, error) {\n\tdefer func() {\n\t\tif b.fetchRate != nil {\n\t\t\tb.fetchRate.Mark(1)\n\t\t}\n\t\tif b.brokerFetchRate != nil {\n\t\t\tb.brokerFetchRate.Mark(1)\n\t\t}\n\t}()\n\n\tresponse := new(FetchResponse)\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// CommitOffset return an Offset commit response or error\nfunc (b *Broker) CommitOffset(request *OffsetCommitRequest) (*OffsetCommitResponse, error) {\n\tresponse := new(OffsetCommitResponse)\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// FetchOffset returns an offset fetch response or error\nfunc (b *Broker) FetchOffset(request *OffsetFetchRequest) (*OffsetFetchResponse, error) {\n\tresponse := new(OffsetFetchResponse)\n\tresponse.Version = request.Version // needed to handle the two header versions\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// JoinGroup returns a join group response or error\nfunc (b *Broker) JoinGroup(request *JoinGroupRequest) (*JoinGroupResponse, error) {\n\tresponse := new(JoinGroupResponse)\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// SyncGroup returns a sync group response or error\nfunc (b *Broker) SyncGroup(request *SyncGroupRequest) (*SyncGroupResponse, error) {\n\tresponse := new(SyncGroupResponse)\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// LeaveGroup return a leave group response or error\nfunc (b *Broker) LeaveGroup(request *LeaveGroupRequest) (*LeaveGroupResponse, error) {\n\tresponse := new(LeaveGroupResponse)\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// Heartbeat returns a heartbeat response or error\nfunc (b *Broker) Heartbeat(request *HeartbeatRequest) (*HeartbeatResponse, error) {\n\tresponse := new(HeartbeatResponse)\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// ListGroups return a list group response or error\nfunc (b *Broker) ListGroups(request *ListGroupsRequest) (*ListGroupsResponse, error) {\n\tresponse := new(ListGroupsResponse)\n\tresponse.Version = request.Version // Required to ensure use of the correct response header version\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// DescribeGroups return describe group response or error\nfunc (b *Broker) DescribeGroups(request *DescribeGroupsRequest) (*DescribeGroupsResponse, error) {\n\tresponse := new(DescribeGroupsResponse)\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// ApiVersions return api version response or error\nfunc (b *Broker) ApiVersions(request *ApiVersionsRequest) (*ApiVersionsResponse, error) {\n\tresponse := new(ApiVersionsResponse)\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// CreateTopics send a create topic request and returns create topic response\nfunc (b *Broker) CreateTopics(request *CreateTopicsRequest) (*CreateTopicsResponse, error) {\n\tresponse := new(CreateTopicsResponse)\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// DeleteTopics sends a delete topic request and returns delete topic response\nfunc (b *Broker) DeleteTopics(request *DeleteTopicsRequest) (*DeleteTopicsResponse, error) {\n\tresponse := new(DeleteTopicsResponse)\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// CreatePartitions sends a create partition request and returns create\n// partitions response or error\nfunc (b *Broker) CreatePartitions(request *CreatePartitionsRequest) (*CreatePartitionsResponse, error) {\n\tresponse := new(CreatePartitionsResponse)\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// AlterPartitionReassignments sends a alter partition reassignments request and\n// returns alter partition reassignments response\nfunc (b *Broker) AlterPartitionReassignments(request *AlterPartitionReassignmentsRequest) (*AlterPartitionReassignmentsResponse, error) {\n\tresponse := new(AlterPartitionReassignmentsResponse)\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// ListPartitionReassignments sends a list partition reassignments request and\n// returns list partition reassignments response\nfunc (b *Broker) ListPartitionReassignments(request *ListPartitionReassignmentsRequest) (*ListPartitionReassignmentsResponse, error) {\n\tresponse := new(ListPartitionReassignmentsResponse)\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// ElectLeaders sends aa elect leaders request and returns list partitions elect result\nfunc (b *Broker) ElectLeaders(request *ElectLeadersRequest) (*ElectLeadersResponse, error) {\n\tresponse := new(ElectLeadersResponse)\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// DeleteRecords send a request to delete records and return delete record\n// response or error\nfunc (b *Broker) DeleteRecords(request *DeleteRecordsRequest) (*DeleteRecordsResponse, error) {\n\tresponse := new(DeleteRecordsResponse)\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// DescribeAcls sends a describe acl request and returns a response or error\nfunc (b *Broker) DescribeAcls(request *DescribeAclsRequest) (*DescribeAclsResponse, error) {\n\tresponse := new(DescribeAclsResponse)\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// CreateAcls sends a create acl request and returns a response or error\nfunc (b *Broker) CreateAcls(request *CreateAclsRequest) (*CreateAclsResponse, error) {\n\tresponse := new(CreateAclsResponse)\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terrs := make([]error, 0)\n\tfor _, res := range response.AclCreationResponses {\n\t\tif !errors.Is(res.Err, ErrNoError) {\n\t\t\terrs = append(errs, res.Err)\n\t\t}\n\t}\n\n\tif len(errs) > 0 {\n\t\treturn response, Wrap(ErrCreateACLs, errs...)\n\t}\n\n\treturn response, nil\n}\n\n// DeleteAcls sends a delete acl request and returns a response or error\nfunc (b *Broker) DeleteAcls(request *DeleteAclsRequest) (*DeleteAclsResponse, error) {\n\tresponse := new(DeleteAclsResponse)\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// InitProducerID sends an init producer request and returns a response or error\nfunc (b *Broker) InitProducerID(request *InitProducerIDRequest) (*InitProducerIDResponse, error) {\n\tresponse := new(InitProducerIDResponse)\n\tresponse.Version = request.version()\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// AddPartitionsToTxn send a request to add partition to txn and returns\n// a response or error\nfunc (b *Broker) AddPartitionsToTxn(request *AddPartitionsToTxnRequest) (*AddPartitionsToTxnResponse, error) {\n\tresponse := new(AddPartitionsToTxnResponse)\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// AddOffsetsToTxn sends a request to add offsets to txn and returns a response\n// or error\nfunc (b *Broker) AddOffsetsToTxn(request *AddOffsetsToTxnRequest) (*AddOffsetsToTxnResponse, error) {\n\tresponse := new(AddOffsetsToTxnResponse)\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// EndTxn sends a request to end txn and returns a response or error\nfunc (b *Broker) EndTxn(request *EndTxnRequest) (*EndTxnResponse, error) {\n\tresponse := new(EndTxnResponse)\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// TxnOffsetCommit sends a request to commit transaction offsets and returns\n// a response or error\nfunc (b *Broker) TxnOffsetCommit(request *TxnOffsetCommitRequest) (*TxnOffsetCommitResponse, error) {\n\tresponse := new(TxnOffsetCommitResponse)\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// DescribeConfigs sends a request to describe config and returns a response or\n// error\nfunc (b *Broker) DescribeConfigs(request *DescribeConfigsRequest) (*DescribeConfigsResponse, error) {\n\tresponse := new(DescribeConfigsResponse)\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// AlterConfigs sends a request to alter config and return a response or error\nfunc (b *Broker) AlterConfigs(request *AlterConfigsRequest) (*AlterConfigsResponse, error) {\n\tresponse := new(AlterConfigsResponse)\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// IncrementalAlterConfigs sends a request to incremental alter config and return a response or error\nfunc (b *Broker) IncrementalAlterConfigs(request *IncrementalAlterConfigsRequest) (*IncrementalAlterConfigsResponse, error) {\n\tresponse := new(IncrementalAlterConfigsResponse)\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// DeleteGroups sends a request to delete groups and returns a response or error\nfunc (b *Broker) DeleteGroups(request *DeleteGroupsRequest) (*DeleteGroupsResponse, error) {\n\tresponse := new(DeleteGroupsResponse)\n\n\tif err := b.sendAndReceive(request, response); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// DeleteOffsets sends a request to delete group offsets and returns a response or error\nfunc (b *Broker) DeleteOffsets(request *DeleteOffsetsRequest) (*DeleteOffsetsResponse, error) {\n\tresponse := new(DeleteOffsetsResponse)\n\n\tif err := b.sendAndReceive(request, response); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// DescribeLogDirs sends a request to get the broker's log dir paths and sizes\nfunc (b *Broker) DescribeLogDirs(request *DescribeLogDirsRequest) (*DescribeLogDirsResponse, error) {\n\tresponse := new(DescribeLogDirsResponse)\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// DescribeUserScramCredentials sends a request to get SCRAM users\nfunc (b *Broker) DescribeUserScramCredentials(req *DescribeUserScramCredentialsRequest) (*DescribeUserScramCredentialsResponse, error) {\n\tres := new(DescribeUserScramCredentialsResponse)\n\n\terr := b.sendAndReceive(req, res)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn res, err\n}\n\nfunc (b *Broker) AlterUserScramCredentials(req *AlterUserScramCredentialsRequest) (*AlterUserScramCredentialsResponse, error) {\n\tres := new(AlterUserScramCredentialsResponse)\n\n\terr := b.sendAndReceive(req, res)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn res, nil\n}\n\n// DescribeClientQuotas sends a request to get the broker's quotas\nfunc (b *Broker) DescribeClientQuotas(request *DescribeClientQuotasRequest) (*DescribeClientQuotasResponse, error) {\n\tresponse := new(DescribeClientQuotasResponse)\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// AlterClientQuotas sends a request to alter the broker's quotas\nfunc (b *Broker) AlterClientQuotas(request *AlterClientQuotasRequest) (*AlterClientQuotasResponse, error) {\n\tresponse := new(AlterClientQuotasResponse)\n\n\terr := b.sendAndReceive(request, response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn response, nil\n}\n\n// readFull ensures the conn ReadDeadline has been setup before making a\n// call to io.ReadFull\nfunc (b *Broker) readFull(buf []byte) (n int, err error) {\n\tif err := b.conn.SetReadDeadline(time.Now().Add(b.conf.Net.ReadTimeout)); err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn io.ReadFull(b.conn, buf)\n}\n\n// write ensures the conn Deadline has been setup before making a\n// call to conn.Write\nfunc (b *Broker) write(buf []byte) (n int, err error) {\n\tnow := time.Now()\n\tif err := b.conn.SetWriteDeadline(now.Add(b.conf.Net.WriteTimeout)); err != nil {\n\t\treturn 0, err\n\t}\n\t// TLS connections require both read and write deadlines to be set\n\t// to avoid handshake indefinite blocking\n\t// see https://github.com/golang/go/blob/go1.23.0/src/crypto/tls/conn.go#L1192-L1195\n\tif b.conf.Net.TLS.Enable {\n\t\tif err := b.conn.SetReadDeadline(now.Add(b.conf.Net.ReadTimeout)); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t}\n\n\treturn b.conn.Write(buf)\n}\n\n// b.lock must be held by caller\n//\n// a non-nil res results in a response promise being created\nfunc (b *Broker) send(req, res protocolBody) (*responsePromise, error) {\n\tvar promise *responsePromise\n\tif res != nil {\n\t\t// Packets or error will be sent to the following channels\n\t\t// once the response is received\n\t\tpromise = makeResponsePromise(res)\n\t}\n\n\tif err := b.sendWithPromise(req, promise); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn promise, nil\n}\n\nfunc makeResponsePromise(res protocolBody) *responsePromise {\n\tpromise := &responsePromise{\n\t\tresponse: res,\n\t\tpackets:  make(chan []byte),\n\t\terrors:   make(chan error),\n\t}\n\treturn promise\n}\n\n// b.lock must be held by caller\nfunc (b *Broker) sendWithPromise(rb protocolBody, promise *responsePromise) error {\n\tif b.conn == nil {\n\t\tif b.connErr != nil {\n\t\t\treturn b.connErr\n\t\t}\n\t\treturn ErrNotConnected\n\t}\n\n\tif b.clientSessionReauthenticationTimeMs > 0 && currentUnixMilli() > b.clientSessionReauthenticationTimeMs {\n\t\terr := b.authenticateViaSASLv1()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn b.sendInternal(rb, promise)\n}\n\n// b.lock must be held by caller\nfunc (b *Broker) sendInternal(rb protocolBody, promise *responsePromise) error {\n\t// try restricting API version to ranges advertised by the broker\n\tif err := restrictApiVersion(rb, b.brokerAPIVersions); err != nil {\n\t\treturn err\n\t}\n\n\t// response versions must always match their corresponding request's\n\tif promise != nil && promise.response != nil {\n\t\tpromise.response.setVersion(rb.version())\n\t}\n\n\tif !b.conf.Version.IsAtLeast(rb.requiredVersion()) {\n\t\treturn ErrUnsupportedVersion\n\t}\n\n\treq := &request{correlationID: b.correlationID, clientID: b.conf.ClientID, body: rb}\n\tbuf, err := encode(req, b.metricRegistry)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// check and wait if throttled\n\tb.waitIfThrottled()\n\n\trequestTime := time.Now()\n\t// Will be decremented in responseReceiver (except error or request with NoResponse)\n\tb.addRequestInFlightMetrics(1)\n\tbytes, err := b.write(buf)\n\tb.updateOutgoingCommunicationMetrics(bytes)\n\tb.updateProtocolMetrics(rb)\n\tif err != nil {\n\t\tb.addRequestInFlightMetrics(-1)\n\t\treturn err\n\t}\n\tb.correlationID++\n\n\tif promise == nil {\n\t\t// Record request latency without the response\n\t\tb.updateRequestLatencyAndInFlightMetrics(time.Since(requestTime))\n\t\treturn nil\n\t}\n\n\tpromise.requestTime = requestTime\n\tpromise.correlationID = req.correlationID\n\tb.responses <- promise\n\n\treturn nil\n}\n\nfunc (b *Broker) sendAndReceive(req protocolBody, res protocolBody) error {\n\tb.lock.Lock()\n\tdefer b.lock.Unlock()\n\n\tpromise, err := b.send(req, res)\n\tif err != nil {\n\t\tb.maybeCloseLocked(err)\n\t\treturn err\n\t}\n\n\tif promise == nil {\n\t\treturn nil\n\t}\n\n\terr = handleResponsePromise(req, res, promise, b.metricRegistry)\n\tif err != nil {\n\t\tb.maybeCloseLocked(err)\n\t\treturn err\n\t}\n\tif res != nil {\n\t\tb.handleThrottledResponse(res)\n\t}\n\treturn nil\n}\n\nfunc handleResponsePromise(req protocolBody, res protocolBody, promise *responsePromise, metricRegistry metrics.Registry) error {\n\tselect {\n\tcase buf := <-promise.packets:\n\t\treturn versionedDecode(buf, res, req.version(), metricRegistry)\n\tcase err := <-promise.errors:\n\t\treturn err\n\t}\n}\n\nfunc (b *Broker) decode(pd packetDecoder, version int16) (err error) {\n\tb.id, err = pd.getInt32()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\thost, err := pd.getString()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tport, err := pd.getInt32()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif version >= 1 {\n\t\tb.rack, err = pd.getNullableString()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tb.addr = net.JoinHostPort(host, fmt.Sprint(port))\n\tif _, _, err := net.SplitHostPort(b.addr); err != nil {\n\t\treturn err\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (b *Broker) encode(pe packetEncoder, version int16) (err error) {\n\thost, portstr, err := net.SplitHostPort(b.addr)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tport, err := strconv.ParseInt(portstr, 10, 32)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tpe.putInt32(b.id)\n\n\terr = pe.putString(host)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tpe.putInt32(int32(port))\n\n\tif version >= 1 {\n\t\terr = pe.putNullableString(b.rack)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (b *Broker) responseReceiver() {\n\tvar dead error\n\n\tfor promise := range b.responses {\n\t\tif dead != nil {\n\t\t\t// This was previously incremented in send() and\n\t\t\t// we are not calling updateIncomingCommunicationMetrics()\n\t\t\tb.addRequestInFlightMetrics(-1)\n\t\t\tpromise.handle(nil, dead)\n\t\t\tcontinue\n\t\t}\n\n\t\theaderLength := getHeaderLength(promise.response.headerVersion())\n\t\theader := make([]byte, headerLength)\n\n\t\tbytesReadHeader, err := b.readFull(header)\n\t\trequestLatency := time.Since(promise.requestTime)\n\t\tif err != nil {\n\t\t\tb.updateIncomingCommunicationMetrics(bytesReadHeader, requestLatency)\n\t\t\tdead = err\n\t\t\tpromise.handle(nil, err)\n\t\t\tcontinue\n\t\t}\n\n\t\tdecodedHeader := responseHeader{}\n\t\terr = versionedDecode(header, &decodedHeader, promise.response.headerVersion(), b.metricRegistry)\n\t\tif err != nil {\n\t\t\tb.updateIncomingCommunicationMetrics(bytesReadHeader, requestLatency)\n\t\t\tdead = err\n\t\t\tpromise.handle(nil, err)\n\t\t\tcontinue\n\t\t}\n\t\tif decodedHeader.correlationID != promise.correlationID {\n\t\t\tb.updateIncomingCommunicationMetrics(bytesReadHeader, requestLatency)\n\t\t\t// TODO if decoded ID < cur ID, discard until we catch up\n\t\t\t// TODO if decoded ID > cur ID, save it so when cur ID catches up we have a response\n\t\t\tdead = PacketDecodingError{fmt.Sprintf(\"correlation ID didn't match, wanted %d, got %d\", promise.correlationID, decodedHeader.correlationID)}\n\t\t\tpromise.handle(nil, dead)\n\t\t\tcontinue\n\t\t}\n\n\t\tbuf := make([]byte, decodedHeader.length-int32(headerLength)+4)\n\t\tbytesReadBody, err := b.readFull(buf)\n\t\tb.updateIncomingCommunicationMetrics(bytesReadHeader+bytesReadBody, requestLatency)\n\t\tif err != nil {\n\t\t\tdead = err\n\t\t\tpromise.handle(nil, err)\n\t\t\tcontinue\n\t\t}\n\n\t\tpromise.handle(buf, nil)\n\t}\n\tclose(b.done)\n}\n\nfunc getHeaderLength(headerVersion int16) int8 {\n\tif headerVersion < 1 {\n\t\treturn 8\n\t} else {\n\t\t// header contains additional tagged field length (0), we don't support actual tags yet.\n\t\treturn 9\n\t}\n}\n\n// shouldCloseBrokerConn reports whether a transport error should trigger closing.\nfunc shouldCloseBrokerConn(err error) bool {\n\tif err == nil {\n\t\treturn false\n\t}\n\n\tif errors.Is(err, io.EOF) {\n\t\treturn true\n\t}\n\n\tif errors.Is(err, io.ErrUnexpectedEOF) {\n\t\treturn true\n\t}\n\n\tif errors.Is(err, io.ErrClosedPipe) {\n\t\treturn true\n\t}\n\n\tif errors.Is(err, net.ErrClosed) {\n\t\treturn true\n\t}\n\n\tif errors.Is(err, syscall.ECONNRESET) || errors.Is(err, syscall.ECONNABORTED) || errors.Is(err, syscall.EPIPE) || errors.Is(err, syscall.ENOTCONN) {\n\t\treturn true\n\t}\n\n\tvar netErr net.Error\n\tif errors.As(err, &netErr) {\n\t\treturn !netErr.Timeout()\n\t}\n\n\treturn false\n}\n\nfunc (b *Broker) sendAndReceiveApiVersions(v int16) (*ApiVersionsResponse, error) {\n\trb := &ApiVersionsRequest{\n\t\tVersion:               v,\n\t\tClientSoftwareName:    defaultClientSoftwareName,\n\t\tClientSoftwareVersion: version(),\n\t}\n\n\treq := &request{correlationID: b.correlationID, clientID: b.conf.ClientID, body: rb}\n\tbuf, err := encode(req, b.metricRegistry)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trequestTime := time.Now()\n\t// Will be decremented in updateIncomingCommunicationMetrics (except error)\n\tb.addRequestInFlightMetrics(1)\n\tbytes, err := b.write(buf)\n\tb.updateOutgoingCommunicationMetrics(bytes)\n\tif err != nil {\n\t\tb.addRequestInFlightMetrics(-1)\n\t\tLogger.Printf(\"Failed to send ApiVersionsRequest V%d to %s: %s\\n\", v, b.addr, err)\n\t\treturn nil, err\n\t}\n\tb.correlationID++\n\n\t// Kafka protocol response structure:\n\t// - Message length (4 bytes): Total length of the response excluding this field\n\t// - ResponseHeader v0 (4 bytes): Contains correlation ID for request-response matching\n\theader := make([]byte, 8)\n\t_, err = b.readFull(header)\n\tif err != nil {\n\t\tb.addRequestInFlightMetrics(-1)\n\t\tLogger.Printf(\"Failed to read ApiVersionsResponse V%d header from %s: %s\\n\", v, b.addr, err)\n\t\treturn nil, err\n\t}\n\n\tlength := binary.BigEndian.Uint32(header[:4])\n\t// we're not using the correlation ID here, but it is part of the response header\n\t// correlationID := binary.BigEndian.Uint32(header[4:])\n\n\tpayload := make([]byte, length-4)\n\tn, err := b.readFull(payload)\n\tif err != nil {\n\t\tb.addRequestInFlightMetrics(-1)\n\t\tLogger.Printf(\"Failed to read ApiVersionsResponse V%d payload from %s: %s\\n\", v, b.addr, err)\n\t\treturn nil, err\n\t}\n\n\tb.updateIncomingCommunicationMetrics(n+8, time.Since(requestTime))\n\tres := &ApiVersionsResponse{Version: rb.version()}\n\terr = versionedDecode(payload, res, rb.version(), b.metricRegistry)\n\tif err != nil {\n\t\tLogger.Printf(\"Failed to parse ApiVersionsResponse V%d from %s: %s\\n\", v, b.addr, err)\n\t\treturn nil, err\n\t}\n\n\tkerr := KError(res.ErrorCode)\n\tif kerr != ErrNoError {\n\t\treturn res, fmt.Errorf(\"Error in ApiVersionsResponse V%d from %s: %w\", res.Version, b.addr, kerr)\n\t}\n\n\tDebugLogger.Printf(\"Completed ApiVersionsRequest V%d to %s. Broker supports %d APIs\\n\", v, b.addr, len(res.ApiKeys))\n\treturn res, nil\n}\n\nfunc (b *Broker) authenticateViaSASLv0() error {\n\tswitch b.conf.Net.SASL.Mechanism {\n\tcase SASLTypeSCRAMSHA256, SASLTypeSCRAMSHA512:\n\t\treturn b.sendAndReceiveSASLSCRAMv0()\n\tcase SASLTypeGSSAPI:\n\t\treturn b.sendAndReceiveKerberos()\n\tdefault:\n\t\treturn b.sendAndReceiveSASLPlainAuthV0()\n\t}\n}\n\nfunc (b *Broker) authenticateViaSASLv1() error {\n\tmetricRegistry := b.metricRegistry\n\tif b.conf.Net.SASL.Handshake {\n\t\thandshakeRequest := &SaslHandshakeRequest{Mechanism: string(b.conf.Net.SASL.Mechanism), Version: b.conf.Net.SASL.Version}\n\t\thandshakeResponse := new(SaslHandshakeResponse)\n\t\tprom := makeResponsePromise(handshakeResponse)\n\n\t\thandshakeErr := b.sendInternal(handshakeRequest, prom)\n\t\tif handshakeErr != nil {\n\t\t\tLogger.Printf(\"Error while performing SASL handshake %s: %s\\n\", b.addr, handshakeErr)\n\t\t\treturn handshakeErr\n\t\t}\n\t\thandshakeErr = handleResponsePromise(handshakeRequest, handshakeResponse, prom, metricRegistry)\n\t\tif handshakeErr != nil {\n\t\t\tLogger.Printf(\"Error while handling SASL handshake response %s: %s\\n\", b.addr, handshakeErr)\n\t\t\treturn handshakeErr\n\t\t}\n\n\t\tif !errors.Is(handshakeResponse.Err, ErrNoError) {\n\t\t\treturn handshakeResponse.Err\n\t\t}\n\t}\n\n\tauthSendReceiver := func(authBytes []byte) (*SaslAuthenticateResponse, error) {\n\t\tauthenticateRequest := b.createSaslAuthenticateRequest(authBytes)\n\t\tauthenticateResponse := new(SaslAuthenticateResponse)\n\t\tprom := makeResponsePromise(authenticateResponse)\n\t\tauthErr := b.sendInternal(authenticateRequest, prom)\n\t\tif authErr != nil {\n\t\t\tLogger.Printf(\"Error while performing SASL Auth %s\\n\", b.addr)\n\t\t\treturn nil, authErr\n\t\t}\n\t\tauthErr = handleResponsePromise(authenticateRequest, authenticateResponse, prom, metricRegistry)\n\t\tif authErr != nil {\n\t\t\tLogger.Printf(\"Error while performing SASL Auth %s: %s\\n\", b.addr, authErr)\n\t\t\treturn nil, authErr\n\t\t}\n\n\t\tif !errors.Is(authenticateResponse.Err, ErrNoError) {\n\t\t\tvar err error = authenticateResponse.Err\n\t\t\tif authenticateResponse.ErrorMessage != nil {\n\t\t\t\terr = Wrap(authenticateResponse.Err, errors.New(*authenticateResponse.ErrorMessage))\n\t\t\t}\n\t\t\treturn nil, err\n\t\t}\n\n\t\tb.computeSaslSessionLifetime(authenticateResponse)\n\t\treturn authenticateResponse, nil\n\t}\n\n\tswitch b.conf.Net.SASL.Mechanism {\n\tcase SASLTypeGSSAPI:\n\t\tb.kerberosAuthenticator.Config = &b.conf.Net.SASL.GSSAPI\n\t\tif b.kerberosAuthenticator.NewKerberosClientFunc == nil {\n\t\t\tb.kerberosAuthenticator.NewKerberosClientFunc = NewKerberosClient\n\t\t}\n\t\treturn b.kerberosAuthenticator.AuthorizeV2(b, authSendReceiver)\n\tcase SASLTypeOAuth:\n\t\tprovider := b.conf.Net.SASL.TokenProvider\n\t\treturn b.sendAndReceiveSASLOAuth(authSendReceiver, provider)\n\tcase SASLTypeSCRAMSHA256, SASLTypeSCRAMSHA512:\n\t\treturn b.sendAndReceiveSASLSCRAMv1(authSendReceiver, b.conf.Net.SASL.SCRAMClientGeneratorFunc())\n\tdefault:\n\t\treturn b.sendAndReceiveSASLPlainAuthV1(authSendReceiver)\n\t}\n}\n\nfunc (b *Broker) sendAndReceiveKerberos() error {\n\tb.kerberosAuthenticator.Config = &b.conf.Net.SASL.GSSAPI\n\tif b.kerberosAuthenticator.NewKerberosClientFunc == nil {\n\t\tb.kerberosAuthenticator.NewKerberosClientFunc = NewKerberosClient\n\t}\n\treturn b.kerberosAuthenticator.Authorize(b)\n}\n\nfunc (b *Broker) sendAndReceiveSASLHandshake(saslType SASLMechanism, version int16) error {\n\trb := &SaslHandshakeRequest{Mechanism: string(saslType), Version: version}\n\n\treq := &request{correlationID: b.correlationID, clientID: b.conf.ClientID, body: rb}\n\tbuf, err := encode(req, b.metricRegistry)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\trequestTime := time.Now()\n\t// Will be decremented in updateIncomingCommunicationMetrics (except error)\n\tb.addRequestInFlightMetrics(1)\n\tbytes, err := b.write(buf)\n\tb.updateOutgoingCommunicationMetrics(bytes)\n\tif err != nil {\n\t\tb.addRequestInFlightMetrics(-1)\n\t\tLogger.Printf(\"Failed to send SASL handshake %s: %s\\n\", b.addr, err.Error())\n\t\treturn err\n\t}\n\tb.correlationID++\n\n\theader := make([]byte, 8) // response header\n\t_, err = b.readFull(header)\n\tif err != nil {\n\t\tb.addRequestInFlightMetrics(-1)\n\t\tLogger.Printf(\"Failed to read SASL handshake header : %s\\n\", err.Error())\n\t\treturn err\n\t}\n\n\tlength := binary.BigEndian.Uint32(header[:4])\n\tpayload := make([]byte, length-4)\n\tn, err := b.readFull(payload)\n\tif err != nil {\n\t\tb.addRequestInFlightMetrics(-1)\n\t\tLogger.Printf(\"Failed to read SASL handshake payload : %s\\n\", err.Error())\n\t\treturn err\n\t}\n\n\tb.updateIncomingCommunicationMetrics(n+8, time.Since(requestTime))\n\tres := &SaslHandshakeResponse{}\n\n\terr = versionedDecode(payload, res, 0, b.metricRegistry)\n\tif err != nil {\n\t\tLogger.Printf(\"Failed to parse SASL handshake : %s\\n\", err.Error())\n\t\treturn err\n\t}\n\n\tif !errors.Is(res.Err, ErrNoError) {\n\t\tLogger.Printf(\"Invalid SASL Mechanism : %s\\n\", res.Err.Error())\n\t\treturn res.Err\n\t}\n\n\tDebugLogger.Print(\"Completed pre-auth SASL handshake. Available mechanisms: \", res.EnabledMechanisms)\n\treturn nil\n}\n\n//\n// In SASL Plain, Kafka expects the auth header to be in the following format\n// Message format (from https://tools.ietf.org/html/rfc4616):\n//\n//   message   = [authzid] UTF8NUL authcid UTF8NUL passwd\n//   authcid   = 1*SAFE ; MUST accept up to 255 octets\n//   authzid   = 1*SAFE ; MUST accept up to 255 octets\n//   passwd    = 1*SAFE ; MUST accept up to 255 octets\n//   UTF8NUL   = %x00 ; UTF-8 encoded NUL character\n//\n//   SAFE      = UTF1 / UTF2 / UTF3 / UTF4\n//                  ;; any UTF-8 encoded Unicode character except NUL\n//\n//\n\n// Kafka 0.10.x supported SASL PLAIN/Kerberos via KAFKA-3149 (KIP-43).\n// sendAndReceiveSASLPlainAuthV0 flows the v0 sasl auth NOT wrapped in the kafka protocol\n//\n// With SASL v0 handshake and auth then:\n// When credentials are valid, Kafka returns a 4 byte array of null characters.\n// When credentials are invalid, Kafka closes the connection.\nfunc (b *Broker) sendAndReceiveSASLPlainAuthV0() error {\n\t// default to V0 to allow for backward compatibility when SASL is enabled\n\t// but not the handshake\n\tif b.conf.Net.SASL.Handshake {\n\t\thandshakeErr := b.sendAndReceiveSASLHandshake(SASLTypePlaintext, b.conf.Net.SASL.Version)\n\t\tif handshakeErr != nil {\n\t\t\tLogger.Printf(\"Error while performing SASL handshake %s: %s\\n\", b.addr, handshakeErr)\n\t\t\treturn handshakeErr\n\t\t}\n\t}\n\n\tlength := len(b.conf.Net.SASL.AuthIdentity) + 1 + len(b.conf.Net.SASL.User) + 1 + len(b.conf.Net.SASL.Password)\n\tauthBytes := make([]byte, length+4) // 4 byte length header + auth data\n\tbinary.BigEndian.PutUint32(authBytes, uint32(length))\n\tcopy(authBytes[4:], b.conf.Net.SASL.AuthIdentity+\"\\x00\"+b.conf.Net.SASL.User+\"\\x00\"+b.conf.Net.SASL.Password)\n\n\trequestTime := time.Now()\n\t// Will be decremented in updateIncomingCommunicationMetrics (except error)\n\tb.addRequestInFlightMetrics(1)\n\tbytesWritten, err := b.write(authBytes)\n\tb.updateOutgoingCommunicationMetrics(bytesWritten)\n\tif err != nil {\n\t\tb.addRequestInFlightMetrics(-1)\n\t\tLogger.Printf(\"Failed to write SASL auth header to broker %s: %s\\n\", b.addr, err.Error())\n\t\treturn err\n\t}\n\n\theader := make([]byte, 4)\n\tn, err := b.readFull(header)\n\tb.updateIncomingCommunicationMetrics(n, time.Since(requestTime))\n\t// If the credentials are valid, we would get a 4 byte response filled with null characters.\n\t// Otherwise, the broker closes the connection and we get an EOF\n\tif err != nil {\n\t\tLogger.Printf(\"Failed to read response while authenticating with SASL to broker %s: %s\\n\", b.addr, err.Error())\n\t\treturn err\n\t}\n\n\tDebugLogger.Printf(\"SASL authentication successful with broker %s:%v - %v\\n\", b.addr, n, header)\n\treturn nil\n}\n\n// Kafka 1.x.x onward added a SaslAuthenticate request/response message which\n// wraps the SASL flow in the Kafka protocol, which allows for returning\n// meaningful errors on authentication failure.\nfunc (b *Broker) sendAndReceiveSASLPlainAuthV1(authSendReceiver func(authBytes []byte) (*SaslAuthenticateResponse, error)) error {\n\tauthBytes := []byte(b.conf.Net.SASL.AuthIdentity + \"\\x00\" + b.conf.Net.SASL.User + \"\\x00\" + b.conf.Net.SASL.Password)\n\t_, err := authSendReceiver(authBytes)\n\treturn err\n}\n\nfunc currentUnixMilli() int64 {\n\treturn time.Now().UnixNano() / int64(time.Millisecond)\n}\n\n// sendAndReceiveSASLOAuth performs the authentication flow as described by KIP-255\n// https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=75968876\nfunc (b *Broker) sendAndReceiveSASLOAuth(authSendReceiver func(authBytes []byte) (*SaslAuthenticateResponse, error), provider AccessTokenProvider) error {\n\ttoken, err := provider.Token()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tmessage, err := buildClientFirstMessage(token)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tres, err := authSendReceiver(message)\n\tif err != nil {\n\t\treturn err\n\t}\n\tisChallenge := len(res.SaslAuthBytes) > 0\n\n\tif isChallenge {\n\t\t// Abort the token exchange. The broker returns the failure code.\n\t\t_, err = authSendReceiver([]byte(`\\x01`))\n\t}\n\treturn err\n}\n\nfunc (b *Broker) sendAndReceiveSASLSCRAMv0() error {\n\tif err := b.sendAndReceiveSASLHandshake(b.conf.Net.SASL.Mechanism, SASLHandshakeV0); err != nil {\n\t\treturn err\n\t}\n\n\tscramClient := b.conf.Net.SASL.SCRAMClientGeneratorFunc()\n\tif err := scramClient.Begin(b.conf.Net.SASL.User, b.conf.Net.SASL.Password, b.conf.Net.SASL.SCRAMAuthzID); err != nil {\n\t\treturn fmt.Errorf(\"failed to start SCRAM exchange with the server: %w\", err)\n\t}\n\n\tmsg, err := scramClient.Step(\"\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to advance the SCRAM exchange: %w\", err)\n\t}\n\n\tfor !scramClient.Done() {\n\t\trequestTime := time.Now()\n\t\t// Will be decremented in updateIncomingCommunicationMetrics (except error)\n\t\tb.addRequestInFlightMetrics(1)\n\t\tlength := len(msg)\n\t\tauthBytes := make([]byte, length+4) // 4 byte length header + auth data\n\t\tbinary.BigEndian.PutUint32(authBytes, uint32(length))\n\t\tcopy(authBytes[4:], msg)\n\t\t_, err := b.write(authBytes)\n\t\tb.updateOutgoingCommunicationMetrics(length + 4)\n\t\tif err != nil {\n\t\t\tb.addRequestInFlightMetrics(-1)\n\t\t\tLogger.Printf(\"Failed to write SASL auth header to broker %s: %s\\n\", b.addr, err.Error())\n\t\t\treturn err\n\t\t}\n\t\tb.correlationID++\n\t\theader := make([]byte, 4)\n\t\t_, err = b.readFull(header)\n\t\tif err != nil {\n\t\t\tb.addRequestInFlightMetrics(-1)\n\t\t\tLogger.Printf(\"Failed to read response header while authenticating with SASL to broker %s: %s\\n\", b.addr, err.Error())\n\t\t\treturn err\n\t\t}\n\t\tpayload := make([]byte, int32(binary.BigEndian.Uint32(header)))\n\t\tn, err := b.readFull(payload)\n\t\tif err != nil {\n\t\t\tb.addRequestInFlightMetrics(-1)\n\t\t\tLogger.Printf(\"Failed to read response payload while authenticating with SASL to broker %s: %s\\n\", b.addr, err.Error())\n\t\t\treturn err\n\t\t}\n\t\tb.updateIncomingCommunicationMetrics(n+4, time.Since(requestTime))\n\t\tmsg, err = scramClient.Step(string(payload))\n\t\tif err != nil {\n\t\t\tLogger.Println(\"SASL authentication failed\", err)\n\t\t\treturn err\n\t\t}\n\t}\n\n\tDebugLogger.Println(\"SASL authentication succeeded\")\n\treturn nil\n}\n\nfunc (b *Broker) sendAndReceiveSASLSCRAMv1(authSendReceiver func(authBytes []byte) (*SaslAuthenticateResponse, error), scramClient SCRAMClient) error {\n\tif err := scramClient.Begin(b.conf.Net.SASL.User, b.conf.Net.SASL.Password, b.conf.Net.SASL.SCRAMAuthzID); err != nil {\n\t\treturn fmt.Errorf(\"failed to start SCRAM exchange with the server: %w\", err)\n\t}\n\n\tmsg, err := scramClient.Step(\"\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to advance the SCRAM exchange: %w\", err)\n\t}\n\n\tfor !scramClient.Done() {\n\t\tres, err := authSendReceiver([]byte(msg))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tmsg, err = scramClient.Step(string(res.SaslAuthBytes))\n\t\tif err != nil {\n\t\t\tLogger.Println(\"SASL authentication failed\", err)\n\t\t\treturn err\n\t\t}\n\t}\n\n\tDebugLogger.Println(\"SASL authentication succeeded\")\n\n\treturn nil\n}\n\nfunc (b *Broker) createSaslAuthenticateRequest(msg []byte) *SaslAuthenticateRequest {\n\tauthenticateRequest := SaslAuthenticateRequest{SaslAuthBytes: msg}\n\tif b.conf.Version.IsAtLeast(V2_2_0_0) {\n\t\tauthenticateRequest.Version = 1\n\t}\n\n\treturn &authenticateRequest\n}\n\n// Build SASL/OAUTHBEARER initial client response as described by RFC-7628\n// https://tools.ietf.org/html/rfc7628\nfunc buildClientFirstMessage(token *AccessToken) ([]byte, error) {\n\tvar ext string\n\n\tif token == nil {\n\t\treturn []byte{}, fmt.Errorf(\"failed to build client first message: token is nil\")\n\t}\n\n\tif len(token.Extensions) > 0 {\n\t\tif _, ok := token.Extensions[SASLExtKeyAuth]; ok {\n\t\t\treturn []byte{}, fmt.Errorf(\"the extension `%s` is invalid\", SASLExtKeyAuth)\n\t\t}\n\t\text = \"\\x01\" + mapToString(token.Extensions, \"=\", \"\\x01\")\n\t}\n\n\tresp := fmt.Appendf(nil, \"n,,\\x01auth=Bearer %s%s\\x01\\x01\", token.Token, ext)\n\n\treturn resp, nil\n}\n\n// mapToString returns a list of key-value pairs ordered by key.\n// keyValSep separates the key from the value. elemSep separates each pair.\nfunc mapToString(extensions map[string]string, keyValSep string, elemSep string) string {\n\tbuf := make([]string, 0, len(extensions))\n\n\tfor k, v := range extensions {\n\t\tbuf = append(buf, k+keyValSep+v)\n\t}\n\n\tsort.Strings(buf)\n\n\treturn strings.Join(buf, elemSep)\n}\n\nfunc (b *Broker) computeSaslSessionLifetime(res *SaslAuthenticateResponse) {\n\tif res.SessionLifetimeMs > 0 {\n\t\t// Follows the Java Kafka implementation from SaslClientAuthenticator.ReauthInfo#setAuthenticationEndAndSessionReauthenticationTimes\n\t\t// pick a random percentage between 85% and 95% for session re-authentication\n\t\tpositiveSessionLifetimeMs := res.SessionLifetimeMs\n\t\tauthenticationEndMs := currentUnixMilli()\n\t\tpctWindowFactorToTakeNetworkLatencyAndClockDriftIntoAccount := 0.85\n\t\tpctWindowJitterToAvoidReauthenticationStormAcrossManyChannelsSimultaneously := 0.10\n\t\tpctToUse := pctWindowFactorToTakeNetworkLatencyAndClockDriftIntoAccount + rand.Float64()*pctWindowJitterToAvoidReauthenticationStormAcrossManyChannelsSimultaneously\n\t\tsessionLifetimeMsToUse := int64(float64(positiveSessionLifetimeMs) * pctToUse)\n\t\tDebugLogger.Printf(\"Session expiration in %d ms and session re-authentication on or after %d ms\", positiveSessionLifetimeMs, sessionLifetimeMsToUse)\n\t\tb.clientSessionReauthenticationTimeMs = authenticationEndMs + sessionLifetimeMsToUse\n\t} else {\n\t\tb.clientSessionReauthenticationTimeMs = 0\n\t}\n}\n\nfunc (b *Broker) updateIncomingCommunicationMetrics(bytes int, requestLatency time.Duration) {\n\tb.updateRequestLatencyAndInFlightMetrics(requestLatency)\n\tb.responseRate.Mark(1)\n\n\tif b.brokerResponseRate != nil {\n\t\tb.brokerResponseRate.Mark(1)\n\t}\n\n\tresponseSize := int64(bytes)\n\tb.incomingByteRate.Mark(responseSize)\n\tif b.brokerIncomingByteRate != nil {\n\t\tb.brokerIncomingByteRate.Mark(responseSize)\n\t}\n\n\tb.responseSize.Update(responseSize)\n\tif b.brokerResponseSize != nil {\n\t\tb.brokerResponseSize.Update(responseSize)\n\t}\n}\n\nfunc (b *Broker) updateRequestLatencyAndInFlightMetrics(requestLatency time.Duration) {\n\trequestLatencyInMs := int64(requestLatency / time.Millisecond)\n\tb.requestLatency.Update(requestLatencyInMs)\n\n\tif b.brokerRequestLatency != nil {\n\t\tb.brokerRequestLatency.Update(requestLatencyInMs)\n\t}\n\n\tb.addRequestInFlightMetrics(-1)\n}\n\nfunc (b *Broker) addRequestInFlightMetrics(i int64) {\n\tb.requestsInFlight.Inc(i)\n\tif b.brokerRequestsInFlight != nil {\n\t\tb.brokerRequestsInFlight.Inc(i)\n\t}\n}\n\nfunc (b *Broker) updateOutgoingCommunicationMetrics(bytes int) {\n\tb.requestRate.Mark(1)\n\tif b.brokerRequestRate != nil {\n\t\tb.brokerRequestRate.Mark(1)\n\t}\n\n\trequestSize := int64(bytes)\n\tb.outgoingByteRate.Mark(requestSize)\n\tif b.brokerOutgoingByteRate != nil {\n\t\tb.brokerOutgoingByteRate.Mark(requestSize)\n\t}\n\n\tb.requestSize.Update(requestSize)\n\tif b.brokerRequestSize != nil {\n\t\tb.brokerRequestSize.Update(requestSize)\n\t}\n}\n\nfunc (b *Broker) updateProtocolMetrics(rb protocolBody) {\n\tprotocolRequestsRate := b.protocolRequestsRate[rb.key()]\n\tif protocolRequestsRate == nil {\n\t\tprotocolRequestsRate = metrics.GetOrRegisterMeter(fmt.Sprintf(\"protocol-requests-rate-%d\", rb.key()), b.metricRegistry)\n\t\tb.protocolRequestsRate[rb.key()] = protocolRequestsRate\n\t}\n\tprotocolRequestsRate.Mark(1)\n\n\tif b.brokerProtocolRequestsRate != nil {\n\t\tbrokerProtocolRequestsRate := b.brokerProtocolRequestsRate[rb.key()]\n\t\tif brokerProtocolRequestsRate == nil {\n\t\t\tbrokerProtocolRequestsRate = b.registerMeter(fmt.Sprintf(\"protocol-requests-rate-%d\", rb.key()))\n\t\t\tb.brokerProtocolRequestsRate[rb.key()] = brokerProtocolRequestsRate\n\t\t}\n\t\tbrokerProtocolRequestsRate.Mark(1)\n\t}\n}\n\ntype throttleSupport interface {\n\tthrottleTime() time.Duration\n}\n\nfunc (b *Broker) handleThrottledResponse(resp protocolBody) {\n\tthrottledResponse, ok := resp.(throttleSupport)\n\tif !ok {\n\t\treturn\n\t}\n\tthrottleTime := throttledResponse.throttleTime()\n\tif throttleTime == time.Duration(0) {\n\t\treturn\n\t}\n\tDebugLogger.Printf(\n\t\t\"broker/%d %T throttled %v\\n\", b.ID(), resp, throttleTime)\n\tb.setThrottle(throttleTime)\n\tb.updateThrottleMetric(throttleTime)\n}\n\nfunc (b *Broker) setThrottle(throttleTime time.Duration) {\n\tb.throttleTimerLock.Lock()\n\tdefer b.throttleTimerLock.Unlock()\n\tif b.throttleTimer != nil {\n\t\t// if there is an existing timer stop/clear it\n\t\tif !b.throttleTimer.Stop() {\n\t\t\t<-b.throttleTimer.C\n\t\t}\n\t}\n\tb.throttleTimer = time.NewTimer(throttleTime)\n}\n\nfunc (b *Broker) waitIfThrottled() {\n\tb.throttleTimerLock.Lock()\n\tdefer b.throttleTimerLock.Unlock()\n\tif b.throttleTimer != nil {\n\t\tDebugLogger.Printf(\"broker/%d waiting for throttle timer\\n\", b.ID())\n\t\t<-b.throttleTimer.C\n\t\tb.throttleTimer = nil\n\t}\n}\n\nfunc (b *Broker) updateThrottleMetric(throttleTime time.Duration) {\n\tif b.brokerThrottleTime != nil {\n\t\tthrottleTimeInMs := int64(throttleTime / time.Millisecond)\n\t\tb.brokerThrottleTime.Update(throttleTimeInMs)\n\t}\n}\n\nfunc (b *Broker) registerMetrics() {\n\tb.brokerIncomingByteRate = b.registerMeter(\"incoming-byte-rate\")\n\tb.brokerRequestRate = b.registerMeter(\"request-rate\")\n\tb.brokerFetchRate = b.registerMeter(\"consumer-fetch-rate\")\n\tb.brokerRequestSize = b.registerHistogram(\"request-size\")\n\tb.brokerRequestLatency = b.registerHistogram(\"request-latency-in-ms\")\n\tb.brokerOutgoingByteRate = b.registerMeter(\"outgoing-byte-rate\")\n\tb.brokerResponseRate = b.registerMeter(\"response-rate\")\n\tb.brokerResponseSize = b.registerHistogram(\"response-size\")\n\tb.brokerRequestsInFlight = b.registerCounter(\"requests-in-flight\")\n\tb.brokerThrottleTime = b.registerHistogram(\"throttle-time-in-ms\")\n\tb.brokerProtocolRequestsRate = map[int16]metrics.Meter{}\n}\n\nfunc (b *Broker) registerMeter(name string) metrics.Meter {\n\tnameForBroker := getMetricNameForBroker(name, b)\n\treturn metrics.GetOrRegisterMeter(nameForBroker, b.metricRegistry)\n}\n\nfunc (b *Broker) registerHistogram(name string) metrics.Histogram {\n\tnameForBroker := getMetricNameForBroker(name, b)\n\treturn getOrRegisterHistogram(nameForBroker, b.metricRegistry)\n}\n\nfunc (b *Broker) registerCounter(name string) metrics.Counter {\n\tnameForBroker := getMetricNameForBroker(name, b)\n\treturn metrics.GetOrRegisterCounter(nameForBroker, b.metricRegistry)\n}\n\nfunc validServerNameTLS(addr string, cfg *tls.Config) *tls.Config {\n\tif cfg == nil {\n\t\tcfg = &tls.Config{\n\t\t\tMinVersion: tls.VersionTLS12,\n\t\t}\n\t}\n\tif cfg.ServerName != \"\" {\n\t\treturn cfg\n\t}\n\n\tc := cfg.Clone()\n\tsn, _, err := net.SplitHostPort(addr)\n\tif err != nil {\n\t\tLogger.Println(fmt.Errorf(\"failed to get ServerName from addr %w\", err))\n\t}\n\tc.ServerName = sn\n\treturn c\n}\n"
  },
  {
    "path": "broker_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/jcmturner/gokrb5/v8/krberror\"\n\t\"github.com/rcrowley/go-metrics\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc ExampleBroker() {\n\tbroker := NewBroker(\"localhost:9092\")\n\terr := broker.Open(nil)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\trequest := MetadataRequest{Topics: []string{\"myTopic\"}}\n\tresponse, err := broker.GetMetadata(&request)\n\tif err != nil {\n\t\t_ = broker.Close()\n\t\tpanic(err)\n\t}\n\n\tfmt.Println(\"There are\", len(response.Topics), \"topics active in the cluster.\")\n\n\tif err = broker.Close(); err != nil {\n\t\tpanic(err)\n\t}\n}\n\ntype mockEncoder struct {\n\tbytes []byte\n}\n\nfunc (m mockEncoder) encode(pe packetEncoder) error {\n\treturn pe.putRawBytes(m.bytes)\n}\n\nfunc (m mockEncoder) headerVersion() int16 {\n\treturn 0\n}\n\ntype brokerMetrics struct {\n\tbytesRead    int\n\tbytesWritten int\n}\n\nfunc TestBrokerAccessors(t *testing.T) {\n\tbroker := NewBroker(\"abc:123\")\n\n\tif broker.ID() != -1 {\n\t\tt.Error(\"New broker didn't have an ID of -1.\")\n\t}\n\n\tif broker.Addr() != \"abc:123\" {\n\t\tt.Error(\"New broker didn't have the correct address\")\n\t}\n\n\tif broker.Rack() != \"\" {\n\t\tt.Error(\"New broker didn't have an unknown rack.\")\n\t}\n\n\tbroker.id = 34\n\tif broker.ID() != 34 {\n\t\tt.Error(\"Manually setting broker ID did not take effect.\")\n\t}\n\n\track := \"dc1\"\n\tbroker.rack = &rack\n\tif broker.Rack() != rack {\n\t\tt.Error(\"Manually setting broker rack did not take effect.\")\n\t}\n}\n\ntype produceResponsePromise struct {\n\tc chan produceResOrError\n}\n\ntype produceResOrError struct {\n\tres *ProduceResponse\n\terr error\n}\n\nfunc newProduceResponsePromise() produceResponsePromise {\n\treturn produceResponsePromise{\n\t\tc: make(chan produceResOrError, 0),\n\t}\n}\n\nfunc (p produceResponsePromise) callback(res *ProduceResponse, err error) {\n\tif err != nil {\n\t\tp.c <- produceResOrError{\n\t\t\terr: err,\n\t\t}\n\t\treturn\n\t}\n\tp.c <- produceResOrError{\n\t\tres: res,\n\t}\n}\n\nfunc (p produceResponsePromise) Get() (*ProduceResponse, error) {\n\tresOrError := <-p.c\n\treturn resOrError.res, resOrError.err\n}\n\nfunc TestSimpleBrokerCommunication(t *testing.T) {\n\tfor _, tt := range brokerTestTable {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tLogger.Printf(\"Testing broker communication for %s\", tt.name)\n\t\t\tmb := NewMockBroker(t, 0)\n\t\t\tmb.Returns(&mockEncoder{tt.response})\n\t\t\tpendingNotify := make(chan brokerMetrics)\n\t\t\t// Register a callback to be notified about successful requests\n\t\t\tmb.SetNotifier(func(bytesRead, bytesWritten int) {\n\t\t\t\tpendingNotify <- brokerMetrics{bytesRead, bytesWritten}\n\t\t\t})\n\t\t\tbroker := NewBroker(mb.Addr())\n\t\t\t// Set the broker id in order to validate local broker metrics\n\t\t\tbroker.id = 0\n\t\t\tconf := NewTestConfig()\n\t\t\tconf.ApiVersionsRequest = false\n\t\t\tconf.Version = tt.version\n\t\t\terr := broker.Open(conf)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif _, err := broker.Connected(); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\ttt.runner(t, broker)\n\t\t\t// Wait up to 500 ms for the remote broker to process the request and\n\t\t\t// notify us about the metrics\n\t\t\ttimeout := 500 * time.Millisecond\n\t\t\tselect {\n\t\t\tcase mockBrokerMetrics := <-pendingNotify:\n\t\t\t\tvalidateBrokerMetrics(t, broker, mockBrokerMetrics)\n\t\t\tcase <-time.After(timeout):\n\t\t\t\tt.Errorf(\"No request received for: %s after waiting for %v\", tt.name, timeout)\n\t\t\t}\n\t\t\tmb.Close()\n\t\t\terr = broker.Close()\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBrokerFailedRequest(t *testing.T) {\n\tfor _, tt := range brokerFailedReqTestTable {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tt.Logf(\"Testing broker communication for %s\", tt.name)\n\t\t\tmb := NewMockBroker(t, 0)\n\t\t\tif !tt.stopBroker {\n\t\t\t\tmb.Returns(&mockEncoder{tt.response})\n\t\t\t}\n\t\t\tbroker := NewBroker(mb.Addr())\n\t\t\t// Stop the broker before calling the runner to purposefully\n\t\t\t// make the request fail right away, the port will be closed\n\t\t\t// and should not be reused right away\n\t\t\tif tt.stopBroker {\n\t\t\t\tt.Log(\"Closing broker:\", mb.Addr())\n\t\t\t\tmb.Close()\n\t\t\t}\n\t\t\tconf := NewTestConfig()\n\t\t\tconf.ApiVersionsRequest = false\n\t\t\tconf.Version = tt.version\n\t\t\t// Tune read timeout to speed up some test cases\n\t\t\tconf.Net.ReadTimeout = 1 * time.Second\n\t\t\terr := broker.Open(conf)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\ttt.runner(t, broker)\n\t\t\tif !tt.stopBroker {\n\t\t\t\tmb.Close()\n\t\t\t}\n\t\t\terr = broker.Close()\n\t\t\tif err != nil {\n\t\t\t\tif tt.stopBroker && errors.Is(err, ErrNotConnected) {\n\t\t\t\t\t// We expect the broker to not close properly\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// closeImmediatelyDialer is a test dialer that returns a net.Conn whose peer is\n// already closed. This reliably triggers a transport-level failure (e.g. EOF)\n// during ApiVersions negotiation in Broker.Open.\ntype closeImmediatelyDialer struct{}\n\nfunc (closeImmediatelyDialer) Dial(_, _ string) (net.Conn, error) {\n\tclient, server := net.Pipe()\n\t_ = server.Close()\n\treturn client, nil\n}\n\nfunc TestBrokerOpenApiVersionsTransportError(t *testing.T) {\n\tt.Parallel()\n\n\tconf := NewConfig()\n\tconf.ApiVersionsRequest = true\n\tconf.Net.Proxy.Enable = true\n\tconf.Net.Proxy.Dialer = closeImmediatelyDialer{}\n\n\tbroker := NewBroker(\"127.0.0.1:9092\")\n\n\tif err := broker.Open(conf); err != nil {\n\t\tt.Fatalf(\"unexpected Open error: %v\", err)\n\t}\n\n\tconnected, connErr := broker.Connected()\n\tif connected {\n\t\tt.Fatalf(\"expected broker to be disconnected\")\n\t}\n\tif connErr == nil {\n\t\tt.Fatalf(\"expected connection error\")\n\t}\n\tif !shouldCloseBrokerConn(connErr) {\n\t\tt.Fatalf(\"expected transport-level error, got: %v\", connErr)\n\t}\n\n\t// Subsequent operations should surface the original connection error.\n\t_, err := broker.GetMetadata(&MetadataRequest{})\n\tif err == nil {\n\t\tt.Fatalf(\"expected metadata request to fail\")\n\t}\n\tif err != connErr {\n\t\tt.Fatalf(\"expected original connection error, got: %v want: %v\", err, connErr)\n\t}\n\n\t// Open() should be retryable after a fatal ApiVersions transport error.\n\tif err := broker.Open(conf); errors.Is(err, ErrAlreadyConnected) {\n\t\tt.Fatalf(\"expected Open retry allowed, got: %v\", err)\n\t}\n\t_, _ = broker.Connected()\n}\n\nfunc TestBrokerOpenSASLv1FailThenReopenTransportError(t *testing.T) {\n\tt.Parallel()\n\n\tmockBroker := NewMockBroker(t, 0)\n\tdefer mockBroker.Close()\n\n\tmockBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"ApiVersionsRequest\":      NewMockApiVersionsResponse(t),\n\t\t\"SaslHandshakeRequest\":    NewMockSaslHandshakeResponse(t).SetEnabledMechanisms([]string{SASLTypeOAuth}),\n\t\t\"SaslAuthenticateRequest\": NewMockSaslAuthenticateResponse(t).SetError(ErrSASLAuthenticationFailed),\n\t})\n\n\tbroker := NewBroker(mockBroker.Addr())\n\n\t// first Open: SASL v1 auth fails after b.responses channel is created\n\tconf := NewTestConfig()\n\tconf.Net.SASL.Enable = true\n\tconf.Net.SASL.Mechanism = SASLTypeOAuth\n\tconf.Net.SASL.TokenProvider = newTokenProvider(&AccessToken{Token: \"test\"}, nil)\n\tconf.Version = V1_0_0_0\n\n\terr := broker.Open(conf)\n\trequire.NoError(t, err)\n\n\tconnected, connErr := broker.Connected()\n\trequire.False(t, connected)\n\trequire.Error(t, connErr)\n\n\t// second Open with a transport error during ApiVersions must not panic\n\t// with \"close of closed channel\"\n\tconf2 := NewTestConfig()\n\tconf2.ApiVersionsRequest = true\n\tconf2.Net.Proxy.Enable = true\n\tconf2.Net.Proxy.Dialer = closeImmediatelyDialer{}\n\n\trequire.NotErrorIs(t, broker.Open(conf2), ErrAlreadyConnected)\n\t_, _ = broker.Connected()\n}\n\nvar ErrTokenFailure = errors.New(\"Failure generating token\")\n\ntype TokenProvider struct {\n\taccessToken *AccessToken\n\terr         error\n}\n\nfunc (t *TokenProvider) Token() (*AccessToken, error) {\n\treturn t.accessToken, t.err\n}\n\nfunc newTokenProvider(token *AccessToken, err error) *TokenProvider {\n\treturn &TokenProvider{\n\t\taccessToken: token,\n\t\terr:         err,\n\t}\n}\n\nfunc TestSASLOAuthBearer(t *testing.T) {\n\ttestTable := []struct {\n\t\tname                      string\n\t\tauthidentity              string\n\t\tmockSASLHandshakeResponse MockResponse // Mock SaslHandshakeRequest response from broker\n\t\tmockSASLAuthResponse      MockResponse // Mock SaslAuthenticateRequest response from broker\n\t\texpectClientErr           bool         // Expect an internal client-side error\n\t\texpectedBrokerError       KError       // Expected Kafka error returned by client\n\t\ttokProvider               *TokenProvider\n\t}{\n\t\t{\n\t\t\tname: \"SASL/OAUTHBEARER OK server response\",\n\t\t\tmockSASLHandshakeResponse: NewMockSaslHandshakeResponse(t).\n\t\t\t\tSetEnabledMechanisms([]string{SASLTypeOAuth}),\n\t\t\tmockSASLAuthResponse: NewMockSaslAuthenticateResponse(t),\n\t\t\texpectClientErr:      false,\n\t\t\texpectedBrokerError:  ErrNoError,\n\t\t\ttokProvider:          newTokenProvider(&AccessToken{Token: \"access-token-123\"}, nil),\n\t\t},\n\t\t{\n\t\t\tname: \"SASL/OAUTHBEARER authentication failure response\",\n\t\t\tmockSASLHandshakeResponse: NewMockSaslHandshakeResponse(t).\n\t\t\t\tSetEnabledMechanisms([]string{SASLTypeOAuth}),\n\t\t\tmockSASLAuthResponse: NewMockSequence(\n\t\t\t\t// First, the broker response with a challenge\n\t\t\t\tNewMockSaslAuthenticateResponse(t).\n\t\t\t\t\tSetAuthBytes([]byte(`{\"status\":\"invalid_request1\"}`)),\n\t\t\t\t// Next, the client terminates the token exchange. Finally, the\n\t\t\t\t// broker responds with an error message.\n\t\t\t\tNewMockSaslAuthenticateResponse(t).\n\t\t\t\t\tSetAuthBytes([]byte(`{\"status\":\"invalid_request2\"}`)).\n\t\t\t\t\tSetError(ErrSASLAuthenticationFailed),\n\t\t\t),\n\t\t\texpectClientErr:     true,\n\t\t\texpectedBrokerError: ErrSASLAuthenticationFailed,\n\t\t\ttokProvider:         newTokenProvider(&AccessToken{Token: \"access-token-123\"}, nil),\n\t\t},\n\t\t{\n\t\t\tname: \"SASL/OAUTHBEARER handshake failure response\",\n\t\t\tmockSASLHandshakeResponse: NewMockSaslHandshakeResponse(t).\n\t\t\t\tSetEnabledMechanisms([]string{SASLTypeOAuth}).\n\t\t\t\tSetError(ErrSASLAuthenticationFailed),\n\t\t\tmockSASLAuthResponse: NewMockSaslAuthenticateResponse(t),\n\t\t\texpectClientErr:      true,\n\t\t\texpectedBrokerError:  ErrSASLAuthenticationFailed,\n\t\t\ttokProvider:          newTokenProvider(&AccessToken{Token: \"access-token-123\"}, nil),\n\t\t},\n\t\t{\n\t\t\tname: \"SASL/OAUTHBEARER token generation error\",\n\t\t\tmockSASLHandshakeResponse: NewMockSaslHandshakeResponse(t).\n\t\t\t\tSetEnabledMechanisms([]string{SASLTypeOAuth}),\n\t\t\tmockSASLAuthResponse: NewMockSaslAuthenticateResponse(t),\n\t\t\texpectClientErr:      true,\n\t\t\texpectedBrokerError:  ErrNoError,\n\t\t\ttokProvider:          newTokenProvider(&AccessToken{Token: \"access-token-123\"}, ErrTokenFailure),\n\t\t},\n\t\t{\n\t\t\tname: \"SASL/OAUTHBEARER invalid extension\",\n\t\t\tmockSASLHandshakeResponse: NewMockSaslHandshakeResponse(t).\n\t\t\t\tSetEnabledMechanisms([]string{SASLTypeOAuth}),\n\t\t\tmockSASLAuthResponse: NewMockSaslAuthenticateResponse(t),\n\t\t\texpectClientErr:      true,\n\t\t\texpectedBrokerError:  ErrNoError,\n\t\t\ttokProvider: newTokenProvider(&AccessToken{\n\t\t\t\tToken:      \"access-token-123\",\n\t\t\t\tExtensions: map[string]string{\"auth\": \"auth-value\"},\n\t\t\t}, nil),\n\t\t},\n\t}\n\n\tfor i, test := range testTable {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\t// mockBroker mocks underlying network logic and broker responses\n\t\t\tmockBroker := NewMockBroker(t, 0)\n\n\t\t\tmockBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\t\t\"SaslAuthenticateRequest\": test.mockSASLAuthResponse,\n\t\t\t\t\"SaslHandshakeRequest\":    test.mockSASLHandshakeResponse,\n\t\t\t})\n\n\t\t\t// broker executes SASL requests against mockBroker\n\t\t\tbroker := NewBroker(mockBroker.Addr())\n\t\t\tbroker.requestRate = metrics.NilMeter{}\n\t\t\tbroker.outgoingByteRate = metrics.NilMeter{}\n\t\t\tbroker.incomingByteRate = metrics.NilMeter{}\n\t\t\tbroker.requestSize = metrics.NilHistogram{}\n\t\t\tbroker.responseSize = metrics.NilHistogram{}\n\t\t\tbroker.responseRate = metrics.NilMeter{}\n\t\t\tbroker.requestLatency = metrics.NilHistogram{}\n\t\t\tbroker.requestsInFlight = metrics.NilCounter{}\n\n\t\t\tconf := NewTestConfig()\n\t\t\tconf.Net.SASL.Mechanism = SASLTypeOAuth\n\t\t\tconf.Net.SASL.TokenProvider = test.tokProvider\n\t\t\tconf.Net.SASL.Enable = true\n\t\t\tconf.Version = V1_0_0_0\n\n\t\t\terr := broker.Open(conf)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tt.Cleanup(func() { _ = broker.Close() })\n\n\t\t\t_, err = broker.Connected()\n\t\t\tif !errors.Is(test.expectedBrokerError, ErrNoError) {\n\t\t\t\tif !errors.Is(err, test.expectedBrokerError) {\n\t\t\t\t\tt.Errorf(\"[%d]:[%s] Expected %s auth error, got %s\\n\", i, test.name, test.expectedBrokerError, err)\n\t\t\t\t}\n\t\t\t} else if test.expectClientErr && err == nil {\n\t\t\t\tt.Errorf(\"[%d]:[%s] Expected a client error and got none\\n\", i, test.name)\n\t\t\t} else if !test.expectClientErr && err != nil {\n\t\t\t\tt.Errorf(\"[%d]:[%s] Unexpected error, got %s\\n\", i, test.name, err)\n\t\t\t}\n\n\t\t\tmockBroker.Close()\n\t\t})\n\t}\n}\n\n// A mock scram client.\ntype MockSCRAMClient struct {\n\tdone bool\n}\n\nfunc (m *MockSCRAMClient) Begin(_, _, _ string) (err error) {\n\treturn nil\n}\n\nfunc (m *MockSCRAMClient) Step(challenge string) (response string, err error) {\n\tif challenge == \"\" {\n\t\treturn \"ping\", nil\n\t}\n\tif challenge == \"pong\" {\n\t\tm.done = true\n\t\treturn \"\", nil\n\t}\n\treturn \"\", errors.New(\"failed to authenticate :(\")\n}\n\nfunc (m *MockSCRAMClient) Done() bool {\n\treturn m.done\n}\n\nvar _ SCRAMClient = &MockSCRAMClient{}\n\nfunc TestSASLSCRAMSHAXXX(t *testing.T) {\n\ttestTable := []struct {\n\t\tname               string\n\t\tmockHandshakeErr   KError\n\t\tmockSASLAuthErr    KError\n\t\texpectClientErr    bool\n\t\tscramClient        *MockSCRAMClient\n\t\tscramChallengeResp string\n\t}{\n\t\t{\n\t\t\tname:               \"SASL/SCRAMSHAXXX successful authentication\",\n\t\t\tmockHandshakeErr:   ErrNoError,\n\t\t\tscramClient:        &MockSCRAMClient{},\n\t\t\tscramChallengeResp: \"pong\",\n\t\t},\n\t\t{\n\t\t\tname:               \"SASL/SCRAMSHAXXX SCRAM client step error client\",\n\t\t\tmockHandshakeErr:   ErrNoError,\n\t\t\tmockSASLAuthErr:    ErrNoError,\n\t\t\tscramClient:        &MockSCRAMClient{},\n\t\t\tscramChallengeResp: \"gong\",\n\t\t\texpectClientErr:    true,\n\t\t},\n\t\t{\n\t\t\tname:               \"SASL/SCRAMSHAXXX server authentication error\",\n\t\t\tmockHandshakeErr:   ErrNoError,\n\t\t\tmockSASLAuthErr:    ErrSASLAuthenticationFailed,\n\t\t\tscramClient:        &MockSCRAMClient{},\n\t\t\tscramChallengeResp: \"pong\",\n\t\t},\n\t\t{\n\t\t\tname:               \"SASL/SCRAMSHAXXX unsupported SCRAM mechanism\",\n\t\t\tmockHandshakeErr:   ErrUnsupportedSASLMechanism,\n\t\t\tmockSASLAuthErr:    ErrNoError,\n\t\t\tscramClient:        &MockSCRAMClient{},\n\t\t\tscramChallengeResp: \"pong\",\n\t\t},\n\t}\n\n\tfor i, test := range testTable {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\t// mockBroker mocks underlying network logic and broker responses\n\t\t\tmockBroker := NewMockBroker(t, 0)\n\t\t\tbroker := NewBroker(mockBroker.Addr())\n\t\t\t// broker executes SASL requests against mockBroker\n\t\t\tbroker.requestRate = metrics.NilMeter{}\n\t\t\tbroker.outgoingByteRate = metrics.NilMeter{}\n\t\t\tbroker.incomingByteRate = metrics.NilMeter{}\n\t\t\tbroker.requestSize = metrics.NilHistogram{}\n\t\t\tbroker.responseSize = metrics.NilHistogram{}\n\t\t\tbroker.responseRate = metrics.NilMeter{}\n\t\t\tbroker.requestLatency = metrics.NilHistogram{}\n\t\t\tbroker.requestsInFlight = metrics.NilCounter{}\n\n\t\t\tmockSASLAuthResponse := NewMockSaslAuthenticateResponse(t).SetAuthBytes([]byte(test.scramChallengeResp))\n\t\t\tmockSASLHandshakeResponse := NewMockSaslHandshakeResponse(t).SetEnabledMechanisms([]string{SASLTypeSCRAMSHA256, SASLTypeSCRAMSHA512})\n\n\t\t\tif !errors.Is(test.mockSASLAuthErr, ErrNoError) {\n\t\t\t\tmockSASLAuthResponse = mockSASLAuthResponse.SetError(test.mockSASLAuthErr)\n\t\t\t}\n\t\t\tif !errors.Is(test.mockHandshakeErr, ErrNoError) {\n\t\t\t\tmockSASLHandshakeResponse = mockSASLHandshakeResponse.SetError(test.mockHandshakeErr)\n\t\t\t}\n\n\t\t\tmockBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\t\t\"SaslAuthenticateRequest\": mockSASLAuthResponse,\n\t\t\t\t\"SaslHandshakeRequest\":    mockSASLHandshakeResponse,\n\t\t\t})\n\n\t\t\tconf := NewTestConfig()\n\t\t\tconf.Net.SASL.Mechanism = SASLTypeSCRAMSHA512\n\t\t\tconf.Net.SASL.Version = SASLHandshakeV1\n\t\t\tconf.Net.SASL.User = \"user\"\n\t\t\tconf.Net.SASL.Password = \"pass\"\n\t\t\tconf.Net.SASL.Enable = true\n\t\t\tconf.Net.SASL.SCRAMClientGeneratorFunc = func() SCRAMClient { return test.scramClient }\n\t\t\tconf.Version = V1_0_0_0\n\n\t\t\terr := broker.Open(conf)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tt.Cleanup(func() { _ = broker.Close() })\n\n\t\t\t_, err = broker.Connected()\n\n\t\t\tif !errors.Is(test.mockSASLAuthErr, ErrNoError) {\n\t\t\t\tif !errors.Is(err, test.mockSASLAuthErr) {\n\t\t\t\t\tt.Errorf(\"[%d]:[%s] Expected %s SASL authentication error, got %s\\n\", i, test.name, test.mockHandshakeErr, err)\n\t\t\t\t}\n\t\t\t} else if !errors.Is(test.mockHandshakeErr, ErrNoError) {\n\t\t\t\tif !errors.Is(err, test.mockHandshakeErr) {\n\t\t\t\t\tt.Errorf(\"[%d]:[%s] Expected %s handshake error, got %s\\n\", i, test.name, test.mockHandshakeErr, err)\n\t\t\t\t}\n\t\t\t} else if test.expectClientErr && err == nil {\n\t\t\t\tt.Errorf(\"[%d]:[%s] Expected a client error and got none\\n\", i, test.name)\n\t\t\t} else if !test.expectClientErr && err != nil {\n\t\t\t\tt.Errorf(\"[%d]:[%s] Unexpected error, got %s\\n\", i, test.name, err)\n\t\t\t}\n\n\t\t\tmockBroker.Close()\n\t\t})\n\t}\n}\n\nfunc TestSASLPlainAuth(t *testing.T) {\n\ttestTable := []struct {\n\t\tname             string\n\t\tauthidentity     string\n\t\tmockAuthErr      KError // Mock and expect error returned from SaslAuthenticateRequest\n\t\tmockHandshakeErr KError // Mock and expect error returned from SaslHandshakeRequest\n\t\texpectClientErr  bool   // Expect an internal client-side error\n\t}{\n\t\t{\n\t\t\tname:             \"SASL Plain OK server response\",\n\t\t\tmockAuthErr:      ErrNoError,\n\t\t\tmockHandshakeErr: ErrNoError,\n\t\t},\n\t\t{\n\t\t\tname:             \"SASL Plain OK server response with authidentity\",\n\t\t\tauthidentity:     \"authid\",\n\t\t\tmockAuthErr:      ErrNoError,\n\t\t\tmockHandshakeErr: ErrNoError,\n\t\t},\n\t\t{\n\t\t\tname:             \"SASL Plain authentication failure response\",\n\t\t\tmockAuthErr:      ErrSASLAuthenticationFailed,\n\t\t\tmockHandshakeErr: ErrNoError,\n\t\t},\n\t\t{\n\t\t\tname:             \"SASL Plain handshake failure response\",\n\t\t\tmockAuthErr:      ErrNoError,\n\t\t\tmockHandshakeErr: ErrSASLAuthenticationFailed,\n\t\t},\n\t}\n\n\tfor i, test := range testTable {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\t// mockBroker mocks underlying network logic and broker responses\n\t\t\tmockBroker := NewMockBroker(t, 0)\n\n\t\t\tmockSASLAuthResponse := NewMockSaslAuthenticateResponse(t).\n\t\t\t\tSetAuthBytes([]byte(`response_payload`))\n\n\t\t\tif !errors.Is(test.mockAuthErr, ErrNoError) {\n\t\t\t\tmockSASLAuthResponse = mockSASLAuthResponse.SetError(test.mockAuthErr)\n\t\t\t}\n\n\t\t\tmockSASLHandshakeResponse := NewMockSaslHandshakeResponse(t).\n\t\t\t\tSetEnabledMechanisms([]string{SASLTypePlaintext})\n\n\t\t\tif !errors.Is(test.mockHandshakeErr, ErrNoError) {\n\t\t\t\tmockSASLHandshakeResponse = mockSASLHandshakeResponse.SetError(test.mockHandshakeErr)\n\t\t\t}\n\n\t\t\tmockBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\t\t\"SaslAuthenticateRequest\": mockSASLAuthResponse,\n\t\t\t\t\"SaslHandshakeRequest\":    mockSASLHandshakeResponse,\n\t\t\t})\n\n\t\t\t// broker executes SASL requests against mockBroker\n\t\t\tbroker := NewBroker(mockBroker.Addr())\n\t\t\tbroker.requestRate = metrics.NilMeter{}\n\t\t\tbroker.outgoingByteRate = metrics.NilMeter{}\n\t\t\tbroker.incomingByteRate = metrics.NilMeter{}\n\t\t\tbroker.requestSize = metrics.NilHistogram{}\n\t\t\tbroker.responseSize = metrics.NilHistogram{}\n\t\t\tbroker.responseRate = metrics.NilMeter{}\n\t\t\tbroker.requestLatency = metrics.NilHistogram{}\n\t\t\tbroker.requestsInFlight = metrics.NilCounter{}\n\n\t\t\tconf := NewTestConfig()\n\t\t\tconf.Net.SASL.Mechanism = SASLTypePlaintext\n\t\t\tconf.Net.SASL.AuthIdentity = test.authidentity\n\t\t\tconf.Net.SASL.Enable = true\n\t\t\tconf.Net.SASL.User = \"token\"\n\t\t\tconf.Net.SASL.Password = \"password\"\n\t\t\tconf.Net.SASL.Version = SASLHandshakeV1\n\t\t\tconf.Version = V1_0_0_0\n\n\t\t\terr := broker.Open(conf)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tt.Cleanup(func() { _ = broker.Close() })\n\n\t\t\t_, err = broker.Connected()\n\n\t\t\tif err == nil {\n\t\t\t\tfor _, rr := range mockBroker.History() {\n\t\t\t\t\tswitch r := rr.Request.(type) {\n\t\t\t\t\tcase *SaslAuthenticateRequest:\n\t\t\t\t\t\tx := bytes.SplitN(r.SaslAuthBytes, []byte(\"\\x00\"), 3)\n\t\t\t\t\t\tif string(x[0]) != conf.Net.SASL.AuthIdentity {\n\t\t\t\t\t\t\tt.Errorf(\"[%d]:[%s] expected %s auth identity, got %s\\n\", i, test.name, conf.Net.SASL.AuthIdentity, x[0])\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif string(x[1]) != conf.Net.SASL.User {\n\t\t\t\t\t\t\tt.Errorf(\"[%d]:[%s] expected %s user, got %s\\n\", i, test.name, conf.Net.SASL.User, x[1])\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif string(x[2]) != conf.Net.SASL.Password {\n\t\t\t\t\t\t\tt.Errorf(\"[%d]:[%s] expected %s password, got %s\\n\", i, test.name, conf.Net.SASL.Password, x[2])\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !errors.Is(test.mockAuthErr, ErrNoError) {\n\t\t\t\tif !errors.Is(err, test.mockAuthErr) {\n\t\t\t\t\tt.Errorf(\"[%d]:[%s] Expected %s auth error, got %s\\n\", i, test.name, test.mockAuthErr, err)\n\t\t\t\t}\n\t\t\t} else if !errors.Is(test.mockHandshakeErr, ErrNoError) {\n\t\t\t\tif !errors.Is(err, test.mockHandshakeErr) {\n\t\t\t\t\tt.Errorf(\"[%d]:[%s] Expected %s handshake error, got %s\\n\", i, test.name, test.mockHandshakeErr, err)\n\t\t\t\t}\n\t\t\t} else if test.expectClientErr && err == nil {\n\t\t\t\tt.Errorf(\"[%d]:[%s] Expected a client error and got none\\n\", i, test.name)\n\t\t\t} else if !test.expectClientErr && err != nil {\n\t\t\t\tt.Errorf(\"[%d]:[%s] Unexpected error, got %s\\n\", i, test.name, err)\n\t\t\t}\n\n\t\t\tmockBroker.Close()\n\t\t})\n\t}\n}\n\n// TestSASLReadTimeout ensures that the broker connection won't block forever\n// if the remote end never responds after the handshake\nfunc TestSASLReadTimeout(t *testing.T) {\n\tmockBroker := NewMockBroker(t, 0)\n\tdefer mockBroker.Close()\n\n\tmockSASLAuthResponse := NewMockSaslAuthenticateResponse(t).\n\t\tSetAuthBytes([]byte(`response_payload`))\n\n\tmockBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"SaslAuthenticateRequest\": mockSASLAuthResponse,\n\t})\n\n\tbroker := NewBroker(mockBroker.Addr())\n\t{\n\t\tbroker.requestRate = metrics.NilMeter{}\n\t\tbroker.outgoingByteRate = metrics.NilMeter{}\n\t\tbroker.incomingByteRate = metrics.NilMeter{}\n\t\tbroker.requestSize = metrics.NilHistogram{}\n\t\tbroker.responseSize = metrics.NilHistogram{}\n\t\tbroker.responseRate = metrics.NilMeter{}\n\t\tbroker.requestLatency = metrics.NilHistogram{}\n\t\tbroker.requestsInFlight = metrics.NilCounter{}\n\t}\n\n\tconf := NewTestConfig()\n\t{\n\t\tconf.Net.ReadTimeout = time.Millisecond\n\t\tconf.Net.SASL.Mechanism = SASLTypePlaintext\n\t\tconf.Net.SASL.User = \"token\"\n\t\tconf.Net.SASL.Password = \"password\"\n\t\tconf.Net.SASL.Version = SASLHandshakeV1\n\t\tconf.Net.SASL.Enable = true\n\t\tconf.Version = V1_0_0_0\n\t}\n\n\terr := broker.Open(conf)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Cleanup(func() { _ = broker.Close() })\n\n\t_, err = broker.Connected()\n\n\tvar nerr net.Error\n\tif !(errors.As(err, &nerr) && nerr.Timeout()) {\n\t\tt.Errorf(\"should never happen - expected read timeout got: %v\", err)\n\t}\n}\n\nfunc TestGSSAPIKerberosAuth_Authorize(t *testing.T) {\n\ttestTable := []struct {\n\t\tname               string\n\t\terror              error\n\t\tmockKerberosClient bool\n\t\terrorStage         string\n\t\tbadResponse        bool\n\t\tbadKeyChecksum     bool\n\t}{\n\t\t{\n\t\t\tname:               \"Kerberos authentication success\",\n\t\t\terror:              nil,\n\t\t\tmockKerberosClient: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Kerberos login fails\",\n\t\t\terror: krberror.NewErrorf(krberror.KDCError, \"KDC_Error: AS Exchange Error: \"+\n\t\t\t\t\"kerberos error response from KDC: KRB Error: (24) KDC_ERR_PREAUTH_FAILED Pre-authenti\"+\n\t\t\t\t\"cation information was invalid - PREAUTH_FAILED\"),\n\t\t\tmockKerberosClient: true,\n\t\t\terrorStage:         \"login\",\n\t\t},\n\t\t{\n\t\t\tname: \"Kerberos service ticket fails\",\n\t\t\terror: krberror.NewErrorf(krberror.KDCError, \"KDC_Error: AS Exchange Error: \"+\n\t\t\t\t\"kerberos error response from KDC: KRB Error: (24) KDC_ERR_PREAUTH_FAILED Pre-authenti\"+\n\t\t\t\t\"cation information was invalid - PREAUTH_FAILED\"),\n\t\t\tmockKerberosClient: true,\n\t\t\terrorStage:         \"service_ticket\",\n\t\t},\n\t\t{\n\t\t\tname:  \"Kerberos client creation fails\",\n\t\t\terror: errors.New(\"configuration file could not be opened: testdata/krb5.conf open testdata/krb5.conf: no such file or directory\"),\n\t\t},\n\t\t{\n\t\t\tname:               \"Bad server response, unmarshall key error\",\n\t\t\terror:              errors.New(\"bytes shorter than header length\"),\n\t\t\tbadResponse:        true,\n\t\t\tmockKerberosClient: true,\n\t\t},\n\t\t{\n\t\t\tname:               \"Bad token checksum\",\n\t\t\terror:              errors.New(\"checksum mismatch. Computed: 39feb88ac2459f2b77738493, Contained in token: ffffffffffffffff00000000\"),\n\t\t\tbadResponse:        false,\n\t\t\tbadKeyChecksum:     true,\n\t\t\tmockKerberosClient: true,\n\t\t},\n\t}\n\tfor i, test := range testTable {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmockBroker := NewMockBroker(t, 0)\n\t\t\t// broker executes SASL requests against mockBroker\n\n\t\t\tmockBroker.SetGSSAPIHandler(func(bytes []byte) []byte {\n\t\t\t\treturn nil\n\t\t\t})\n\t\t\tbroker := NewBroker(mockBroker.Addr())\n\t\t\tbroker.requestRate = metrics.NilMeter{}\n\t\t\tbroker.outgoingByteRate = metrics.NilMeter{}\n\t\t\tbroker.incomingByteRate = metrics.NilMeter{}\n\t\t\tbroker.requestSize = metrics.NilHistogram{}\n\t\t\tbroker.responseSize = metrics.NilHistogram{}\n\t\t\tbroker.responseRate = metrics.NilMeter{}\n\t\t\tbroker.requestLatency = metrics.NilHistogram{}\n\t\t\tbroker.requestsInFlight = metrics.NilCounter{}\n\n\t\t\tconf := NewTestConfig()\n\t\t\tconf.Net.SASL.Version = SASLHandshakeV0\n\t\t\tconf.Net.SASL.Mechanism = SASLTypeGSSAPI\n\t\t\tconf.Net.SASL.Enable = true\n\t\t\tconf.Net.SASL.GSSAPI.ServiceName = \"kafka\"\n\t\t\tconf.Net.SASL.GSSAPI.KerberosConfigPath = \"testdata/krb5.conf\"\n\t\t\tconf.Net.SASL.GSSAPI.Realm = \"EXAMPLE.COM\"\n\t\t\tconf.Net.SASL.GSSAPI.Username = \"kafka\"\n\t\t\tconf.Net.SASL.GSSAPI.Password = \"kafka\"\n\t\t\tconf.Net.SASL.GSSAPI.KeyTabPath = \"kafka.keytab\"\n\t\t\tconf.Net.SASL.GSSAPI.AuthType = KRB5_USER_AUTH\n\t\t\tconf.Version = V1_0_0_0\n\n\t\t\tgssapiHandler := KafkaGSSAPIHandler{\n\t\t\t\tclient:         &MockKerberosClient{},\n\t\t\t\tbadResponse:    test.badResponse,\n\t\t\t\tbadKeyChecksum: test.badKeyChecksum,\n\t\t\t}\n\t\t\tmockBroker.SetGSSAPIHandler(gssapiHandler.MockKafkaGSSAPI)\n\t\t\tif test.mockKerberosClient {\n\t\t\t\tbroker.kerberosAuthenticator.NewKerberosClientFunc = func(config *GSSAPIConfig) (KerberosClient, error) {\n\t\t\t\t\treturn &MockKerberosClient{\n\t\t\t\t\t\tmockError:  test.error,\n\t\t\t\t\t\terrorStage: test.errorStage,\n\t\t\t\t\t}, nil\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tbroker.kerberosAuthenticator.NewKerberosClientFunc = nil\n\t\t\t}\n\n\t\t\terr := broker.Open(conf)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tt.Cleanup(func() { _ = broker.Close() })\n\n\t\t\t_, err = broker.Connected()\n\n\t\t\tif err != nil && test.error != nil {\n\t\t\t\tif test.error.Error() != err.Error() {\n\t\t\t\t\tt.Errorf(\"[%d] Expected error:%s, got:%s.\", i, test.error, err)\n\t\t\t\t}\n\t\t\t} else if (err == nil && test.error != nil) || (err != nil && test.error == nil) {\n\t\t\t\tt.Errorf(\"[%d] Expected error:%s, got:%s.\", i, test.error, err)\n\t\t\t}\n\n\t\t\tmockBroker.Close()\n\t\t})\n\t}\n}\n\nfunc TestBuildClientFirstMessage(t *testing.T) {\n\ttestTable := []struct {\n\t\tname        string\n\t\ttoken       *AccessToken\n\t\texpected    []byte\n\t\texpectError bool\n\t}{\n\t\t{\n\t\t\tname: \"Build SASL client initial response with two extensions\",\n\t\t\ttoken: &AccessToken{\n\t\t\t\tToken: \"the-token\",\n\t\t\t\tExtensions: map[string]string{\n\t\t\t\t\t\"x\": \"1\",\n\t\t\t\t\t\"y\": \"2\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: []byte(\"n,,\\x01auth=Bearer the-token\\x01x=1\\x01y=2\\x01\\x01\"),\n\t\t},\n\t\t{\n\t\t\tname:     \"Build SASL client initial response with no extensions\",\n\t\t\ttoken:    &AccessToken{Token: \"the-token\"},\n\t\t\texpected: []byte(\"n,,\\x01auth=Bearer the-token\\x01\\x01\"),\n\t\t},\n\t\t{\n\t\t\tname: \"Build SASL client initial response using reserved extension\",\n\t\t\ttoken: &AccessToken{\n\t\t\t\tToken: \"the-token\",\n\t\t\t\tExtensions: map[string]string{\n\t\t\t\t\t\"auth\": \"auth-value\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected:    []byte(\"\"),\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor i, test := range testTable {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tactual, err := buildClientFirstMessage(test.token)\n\n\t\t\tif !reflect.DeepEqual(test.expected, actual) {\n\t\t\t\tt.Errorf(\"Expected %s, got %s\\n\", test.expected, actual)\n\t\t\t}\n\t\t\tif test.expectError && err == nil {\n\t\t\t\tt.Errorf(\"[%d]:[%s] Expected an error but did not get one\", i, test.name)\n\t\t\t}\n\t\t\tif !test.expectError && err != nil {\n\t\t\t\tt.Errorf(\"[%d]:[%s] Expected no error but got %s\\n\", i, test.name, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestKip368ReAuthenticationSuccess(t *testing.T) {\n\tsessionLifetimeMs := int64(100)\n\n\tmockBroker := NewMockBroker(t, 0)\n\n\tcountSaslAuthRequests := func() (count int) {\n\t\tfor _, rr := range mockBroker.History() {\n\t\t\tswitch rr.Request.(type) {\n\t\t\tcase *SaslAuthenticateRequest:\n\t\t\t\tcount++\n\t\t\t}\n\t\t}\n\t\treturn\n\t}\n\n\tmockSASLAuthResponse := NewMockSaslAuthenticateResponse(t).\n\t\tSetAuthBytes([]byte(`response_payload`)).\n\t\tSetSessionLifetimeMs(sessionLifetimeMs)\n\n\tmockSASLHandshakeResponse := NewMockSaslHandshakeResponse(t).\n\t\tSetEnabledMechanisms([]string{SASLTypePlaintext})\n\n\tmockApiVersions := NewMockApiVersionsResponse(t)\n\n\tmockBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"SaslAuthenticateRequest\": mockSASLAuthResponse,\n\t\t\"SaslHandshakeRequest\":    mockSASLHandshakeResponse,\n\t\t\"ApiVersionsRequest\":      mockApiVersions,\n\t})\n\n\tbroker := NewBroker(mockBroker.Addr())\n\n\tconf := NewTestConfig()\n\tconf.Net.SASL.Enable = true\n\tconf.Net.SASL.Mechanism = SASLTypePlaintext\n\tconf.Net.SASL.Version = SASLHandshakeV1\n\tconf.Net.SASL.AuthIdentity = \"authid\"\n\tconf.Net.SASL.User = \"token\"\n\tconf.Net.SASL.Password = \"password\"\n\n\tbroker.conf = conf\n\tbroker.conf.Version = V2_2_0_0\n\n\terr := broker.Open(conf)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Cleanup(func() { _ = broker.Close() })\n\n\tconnected, err := broker.Connected()\n\tif err != nil || !connected {\n\t\tt.Fatal(err)\n\t}\n\n\tactualSaslAuthRequests := countSaslAuthRequests()\n\tif actualSaslAuthRequests != 1 {\n\t\tt.Fatalf(\"unexpected number of SaslAuthRequests during initial authentication: %d\", actualSaslAuthRequests)\n\t}\n\n\ttimeout := time.After(time.Duration(sessionLifetimeMs) * time.Millisecond)\n\nloop:\n\tfor actualSaslAuthRequests < 2 {\n\t\tselect {\n\t\tcase <-timeout:\n\t\t\tbreak loop\n\t\tdefault:\n\t\t\ttime.Sleep(10 * time.Millisecond)\n\t\t\t// put some traffic on the wire\n\t\t\t_, err = broker.ApiVersions(&ApiVersionsRequest{})\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tactualSaslAuthRequests = countSaslAuthRequests()\n\t\t}\n\t}\n\n\tif actualSaslAuthRequests < 2 {\n\t\tt.Fatalf(\"sasl reauth has not occurred within expected timeframe\")\n\t}\n\n\tmockBroker.Close()\n}\n\nfunc TestKip368ReAuthenticationFailure(t *testing.T) {\n\tsessionLifetimeMs := int64(100)\n\n\tmockBroker := NewMockBroker(t, 0)\n\n\tmockSASLAuthResponse := NewMockSaslAuthenticateResponse(t).\n\t\tSetAuthBytes([]byte(`response_payload`)).\n\t\tSetSessionLifetimeMs(sessionLifetimeMs)\n\n\tmockSASLAuthErrorResponse := NewMockSaslAuthenticateResponse(t).\n\t\tSetError(ErrSASLAuthenticationFailed)\n\n\tmockSASLHandshakeResponse := NewMockSaslHandshakeResponse(t).\n\t\tSetEnabledMechanisms([]string{SASLTypePlaintext})\n\n\tmockApiVersions := NewMockApiVersionsResponse(t)\n\n\tmockBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"SaslAuthenticateRequest\": mockSASLAuthResponse,\n\t\t\"SaslHandshakeRequest\":    mockSASLHandshakeResponse,\n\t\t\"ApiVersionsRequest\":      mockApiVersions,\n\t})\n\n\tbroker := NewBroker(mockBroker.Addr())\n\n\tconf := NewTestConfig()\n\tconf.Net.SASL.Enable = true\n\tconf.Net.SASL.Mechanism = SASLTypePlaintext\n\tconf.Net.SASL.Version = SASLHandshakeV1\n\tconf.Net.SASL.AuthIdentity = \"authid\"\n\tconf.Net.SASL.User = \"token\"\n\tconf.Net.SASL.Password = \"password\"\n\n\tbroker.conf = conf\n\tbroker.conf.Version = V2_2_0_0\n\n\terr := broker.Open(conf)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Cleanup(func() { _ = broker.Close() })\n\n\tconnected, err := broker.Connected()\n\tif err != nil || !connected {\n\t\tt.Fatal(err)\n\t}\n\n\tmockBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"SaslAuthenticateRequest\": mockSASLAuthErrorResponse,\n\t\t\"SaslHandshakeRequest\":    mockSASLHandshakeResponse,\n\t\t\"ApiVersionsRequest\":      mockApiVersions,\n\t})\n\n\ttimeout := time.After(time.Duration(sessionLifetimeMs) * time.Millisecond)\n\n\tvar apiVersionError error\nloop:\n\tfor apiVersionError == nil {\n\t\tselect {\n\t\tcase <-timeout:\n\t\t\tbreak loop\n\t\tdefault:\n\t\t\ttime.Sleep(10 * time.Millisecond)\n\t\t\t// put some traffic on the wire\n\t\t\t_, apiVersionError = broker.ApiVersions(&ApiVersionsRequest{})\n\t\t}\n\t}\n\n\tif !errors.Is(apiVersionError, ErrSASLAuthenticationFailed) {\n\t\tt.Fatalf(\"sasl reauth has not failed in the expected way %v\", apiVersionError)\n\t}\n\n\tmockBroker.Close()\n}\n\n// We're not testing encoding/decoding here, so most of the requests/responses will be empty for simplicity's sake\nvar brokerTestTable = []struct {\n\tversion  KafkaVersion\n\tname     string\n\tresponse []byte\n\trunner   func(*testing.T, *Broker)\n}{\n\t{\n\t\tV0_10_0_0,\n\t\t\"MetadataRequest\",\n\t\t[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},\n\t\tfunc(t *testing.T, broker *Broker) {\n\t\t\trequest := MetadataRequest{}\n\t\t\tresponse, err := broker.GetMetadata(&request)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\tif response == nil {\n\t\t\t\tt.Error(\"Metadata request got no response!\")\n\t\t\t}\n\t\t},\n\t},\n\n\t{\n\t\tV0_10_0_0,\n\t\t\"ConsumerMetadataRequest\",\n\t\t[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 't', 0x00, 0x00, 0x00, 0x00},\n\t\tfunc(t *testing.T, broker *Broker) {\n\t\t\trequest := ConsumerMetadataRequest{}\n\t\t\tresponse, err := broker.GetConsumerMetadata(&request)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\tif response == nil {\n\t\t\t\tt.Error(\"Consumer Metadata request got no response!\")\n\t\t\t}\n\t\t},\n\t},\n\n\t{\n\t\tV0_10_0_0,\n\t\t\"ProduceRequest (NoResponse)\",\n\t\t[]byte{},\n\t\tfunc(t *testing.T, broker *Broker) {\n\t\t\trequest := ProduceRequest{}\n\t\t\trequest.RequiredAcks = NoResponse\n\t\t\tresponse, err := broker.Produce(&request)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\tif response != nil {\n\t\t\t\tt.Error(\"Produce request with NoResponse got a response!\")\n\t\t\t}\n\t\t},\n\t},\n\n\t{\n\t\tV0_10_0_0,\n\t\t\"ProduceRequest (NoResponse) using AsyncProduce\",\n\t\t[]byte{},\n\t\tfunc(t *testing.T, broker *Broker) {\n\t\t\trequest := ProduceRequest{}\n\t\t\trequest.RequiredAcks = NoResponse\n\t\t\terr := broker.AsyncProduce(&request, nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t},\n\t},\n\n\t{\n\t\tV0_10_0_0,\n\t\t\"ProduceRequest (WaitForLocal)\",\n\t\t[]byte{0x00, 0x00, 0x00, 0x00},\n\t\tfunc(t *testing.T, broker *Broker) {\n\t\t\trequest := ProduceRequest{}\n\t\t\trequest.RequiredAcks = WaitForLocal\n\t\t\tresponse, err := broker.Produce(&request)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\tif response == nil {\n\t\t\t\tt.Error(\"Produce request without NoResponse got no response!\")\n\t\t\t}\n\t\t},\n\t},\n\n\t{\n\t\tV0_10_0_0,\n\t\t\"ProduceRequest (WaitForLocal) using AsyncProduce\",\n\t\t[]byte{0x00, 0x00, 0x00, 0x00},\n\t\tfunc(t *testing.T, broker *Broker) {\n\t\t\trequest := ProduceRequest{}\n\t\t\trequest.RequiredAcks = WaitForLocal\n\t\t\tproduceResPromise := newProduceResponsePromise()\n\t\t\terr := broker.AsyncProduce(&request, produceResPromise.callback)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\tresponse, err := produceResPromise.Get()\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\tif response == nil {\n\t\t\t\tt.Error(\"Produce request without NoResponse got no response!\")\n\t\t\t}\n\t\t},\n\t},\n\n\t{\n\t\tV0_10_0_0,\n\t\t\"FetchRequest\",\n\t\t[]byte{0x00, 0x00, 0x00, 0x00},\n\t\tfunc(t *testing.T, broker *Broker) {\n\t\t\trequest := FetchRequest{}\n\t\t\tresponse, err := broker.Fetch(&request)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\tif response == nil {\n\t\t\t\tt.Error(\"Fetch request got no response!\")\n\t\t\t}\n\t\t},\n\t},\n\n\t{\n\t\tV0_10_0_0,\n\t\t\"OffsetFetchRequest\",\n\t\t[]byte{0x00, 0x00, 0x00, 0x00},\n\t\tfunc(t *testing.T, broker *Broker) {\n\t\t\trequest := OffsetFetchRequest{}\n\t\t\tresponse, err := broker.FetchOffset(&request)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\tif response == nil {\n\t\t\t\tt.Error(\"OffsetFetch request got no response!\")\n\t\t\t}\n\t\t},\n\t},\n\n\t{\n\t\tV0_10_0_0,\n\t\t\"OffsetCommitRequest\",\n\t\t[]byte{0x00, 0x00, 0x00, 0x00},\n\t\tfunc(t *testing.T, broker *Broker) {\n\t\t\trequest := OffsetCommitRequest{}\n\t\t\tresponse, err := broker.CommitOffset(&request)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\tif response == nil {\n\t\t\t\tt.Error(\"OffsetCommit request got no response!\")\n\t\t\t}\n\t\t},\n\t},\n\n\t{\n\t\tV0_10_0_0,\n\t\t\"OffsetRequest\",\n\t\t[]byte{0x00, 0x00, 0x00, 0x00},\n\t\tfunc(t *testing.T, broker *Broker) {\n\t\t\trequest := OffsetRequest{}\n\t\t\tresponse, err := broker.GetAvailableOffsets(&request)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\tif response == nil {\n\t\t\t\tt.Error(\"Offset request got no response!\")\n\t\t\t}\n\t\t},\n\t},\n\n\t{\n\t\tV0_10_0_0,\n\t\t\"JoinGroupRequest\",\n\t\t[]byte{0x00, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},\n\t\tfunc(t *testing.T, broker *Broker) {\n\t\t\trequest := JoinGroupRequest{}\n\t\t\tresponse, err := broker.JoinGroup(&request)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\tif response == nil {\n\t\t\t\tt.Error(\"JoinGroup request got no response!\")\n\t\t\t}\n\t\t},\n\t},\n\n\t{\n\t\tV0_10_0_0,\n\t\t\"SyncGroupRequest\",\n\t\t[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00},\n\t\tfunc(t *testing.T, broker *Broker) {\n\t\t\trequest := SyncGroupRequest{}\n\t\t\tresponse, err := broker.SyncGroup(&request)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\tif response == nil {\n\t\t\t\tt.Error(\"SyncGroup request got no response!\")\n\t\t\t}\n\t\t},\n\t},\n\n\t{\n\t\tV0_10_0_0,\n\t\t\"LeaveGroupRequest\",\n\t\t[]byte{0x00, 0x00},\n\t\tfunc(t *testing.T, broker *Broker) {\n\t\t\trequest := LeaveGroupRequest{}\n\t\t\tresponse, err := broker.LeaveGroup(&request)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\tif response == nil {\n\t\t\t\tt.Error(\"LeaveGroup request got no response!\")\n\t\t\t}\n\t\t},\n\t},\n\n\t{\n\t\tV0_10_0_0,\n\t\t\"HeartbeatRequest\",\n\t\t[]byte{0x00, 0x00},\n\t\tfunc(t *testing.T, broker *Broker) {\n\t\t\trequest := HeartbeatRequest{}\n\t\t\tresponse, err := broker.Heartbeat(&request)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\tif response == nil {\n\t\t\t\tt.Error(\"Heartbeat request got no response!\")\n\t\t\t}\n\t\t},\n\t},\n\n\t{\n\t\tV0_10_0_0,\n\t\t\"ListGroupsRequest\",\n\t\t[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00},\n\t\tfunc(t *testing.T, broker *Broker) {\n\t\t\trequest := ListGroupsRequest{}\n\t\t\tresponse, err := broker.ListGroups(&request)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\tif response == nil {\n\t\t\t\tt.Error(\"ListGroups request got no response!\")\n\t\t\t}\n\t\t},\n\t},\n\n\t{\n\t\tV0_10_0_0,\n\t\t\"DescribeGroupsRequest\",\n\t\t[]byte{0x00, 0x00, 0x00, 0x00},\n\t\tfunc(t *testing.T, broker *Broker) {\n\t\t\trequest := DescribeGroupsRequest{}\n\t\t\tresponse, err := broker.DescribeGroups(&request)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\tif response == nil {\n\t\t\t\tt.Error(\"DescribeGroups request got no response!\")\n\t\t\t}\n\t\t},\n\t},\n\n\t{\n\t\tV0_10_0_0,\n\t\t\"ApiVersionsRequest\",\n\t\t[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00},\n\t\tfunc(t *testing.T, broker *Broker) {\n\t\t\trequest := ApiVersionsRequest{}\n\t\t\tresponse, err := broker.ApiVersions(&request)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\tif response == nil {\n\t\t\t\tt.Error(\"ApiVersions request got no response!\")\n\t\t\t}\n\t\t},\n\t},\n\n\t{\n\t\tV1_1_0_0,\n\t\t\"DeleteGroupsRequest\",\n\t\t[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},\n\t\tfunc(t *testing.T, broker *Broker) {\n\t\t\trequest := DeleteGroupsRequest{}\n\t\t\tresponse, err := broker.DeleteGroups(&request)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\tif response == nil {\n\t\t\t\tt.Error(\"DeleteGroups request got no response!\")\n\t\t\t}\n\t\t},\n\t},\n\n\t{\n\t\tV2_4_0_0,\n\t\t\"DeleteOffsetsRequest\",\n\t\t[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},\n\t\tfunc(t *testing.T, broker *Broker) {\n\t\t\trequest := DeleteOffsetsRequest{}\n\t\t\tresponse, err := broker.DeleteOffsets(&request)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\tif response == nil {\n\t\t\t\tt.Error(\"DeleteGroups request got no response!\")\n\t\t\t}\n\t\t},\n\t},\n}\n\n// We are testing the handling of failed request or corrupt responses.\nvar brokerFailedReqTestTable = []struct {\n\tversion    KafkaVersion\n\tname       string\n\tstopBroker bool\n\tresponse   []byte\n\trunner     func(*testing.T, *Broker)\n}{\n\t{\n\t\tversion:    V0_10_0_0,\n\t\tname:       \"ProduceRequest (NoResponse) using AsyncProduce and stopped broker\",\n\t\tstopBroker: true,\n\t\trunner: func(t *testing.T, broker *Broker) {\n\t\t\trequest := ProduceRequest{}\n\t\t\trequest.RequiredAcks = NoResponse\n\t\t\terr := broker.AsyncProduce(&request, nil)\n\t\t\tif err == nil {\n\t\t\t\tt.Fatal(\"Expected a non nil error because broker is not listening\")\n\t\t\t}\n\t\t\tt.Log(\"Got error:\", err)\n\t\t},\n\t},\n\n\t{\n\t\tversion:    V0_10_0_0,\n\t\tname:       \"ProduceRequest (WaitForLocal) using AsyncProduce and stopped broker\",\n\t\tstopBroker: true,\n\t\trunner: func(t *testing.T, broker *Broker) {\n\t\t\trequest := ProduceRequest{}\n\t\t\trequest.RequiredAcks = WaitForLocal\n\t\t\terr := broker.AsyncProduce(&request, nil)\n\t\t\tif err == nil {\n\t\t\t\tt.Fatal(\"Expected a non nil error because broker is not listening\")\n\t\t\t}\n\t\t\tt.Log(\"Got error:\", err)\n\t\t},\n\t},\n\n\t{\n\t\tversion: V0_10_0_0,\n\t\tname:    \"ProduceRequest (WaitForLocal) using AsyncProduce and no response\",\n\t\t// A nil response means the mock broker will ignore the request leading to a read timeout\n\t\tresponse: nil,\n\t\trunner: func(t *testing.T, broker *Broker) {\n\t\t\trequest := ProduceRequest{}\n\t\t\trequest.RequiredAcks = WaitForLocal\n\t\t\tproduceResPromise := newProduceResponsePromise()\n\t\t\terr := broker.AsyncProduce(&request, produceResPromise.callback)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\tresponse, err := produceResPromise.Get()\n\t\t\tif err == nil {\n\t\t\t\tt.Fatal(\"Expected a non nil error because broker is not listening\")\n\t\t\t}\n\t\t\tt.Log(\"Got error:\", err)\n\t\t\tif response != nil {\n\t\t\t\tt.Error(\"Produce request should have failed, got response:\", response)\n\t\t\t}\n\t\t},\n\t},\n\n\t{\n\t\tversion: V0_10_0_0,\n\t\tname:    \"ProduceRequest (WaitForLocal) using AsyncProduce and corrupt response\",\n\t\t// Corrupt response (3 bytes vs 4)\n\t\tresponse: []byte{0x00, 0x00, 0x00},\n\t\trunner: func(t *testing.T, broker *Broker) {\n\t\t\trequest := ProduceRequest{}\n\t\t\trequest.RequiredAcks = WaitForLocal\n\t\t\tproduceResPromise := newProduceResponsePromise()\n\t\t\terr := broker.AsyncProduce(&request, produceResPromise.callback)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\tresponse, err := produceResPromise.Get()\n\t\t\tif err == nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tt.Log(\"Got error:\", err)\n\t\t\tif response != nil {\n\t\t\t\tt.Error(\"Produce request should have failed, got response:\", response)\n\t\t\t}\n\t\t},\n\t},\n}\n\nfunc validateBrokerMetrics(t *testing.T, broker *Broker, mockBrokerMetrics brokerMetrics) {\n\tmetricValidators := newMetricValidators()\n\tmockBrokerBytesRead := mockBrokerMetrics.bytesRead\n\tmockBrokerBytesWritten := mockBrokerMetrics.bytesWritten\n\n\t// Check that the number of bytes sent corresponds to what the mock broker received\n\tmetricValidators.registerForAllBrokers(broker, countMeterValidator(\"incoming-byte-rate\", mockBrokerBytesWritten))\n\tif mockBrokerBytesWritten == 0 {\n\t\t// This a ProduceRequest with NoResponse\n\t\tmetricValidators.registerForAllBrokers(broker, countMeterValidator(\"response-rate\", 0))\n\t\tmetricValidators.registerForAllBrokers(broker, countHistogramValidator(\"response-size\", 0))\n\t\tmetricValidators.registerForAllBrokers(broker, minMaxHistogramValidator(\"response-size\", 0, 0))\n\t} else {\n\t\tmetricValidators.registerForAllBrokers(broker, countMeterValidator(\"response-rate\", 1))\n\t\tmetricValidators.registerForAllBrokers(broker, countHistogramValidator(\"response-size\", 1))\n\t\tmetricValidators.registerForAllBrokers(broker, minMaxHistogramValidator(\"response-size\", mockBrokerBytesWritten, mockBrokerBytesWritten))\n\t}\n\n\t// Check that the number of bytes received corresponds to what the mock broker sent\n\tmetricValidators.registerForAllBrokers(broker, countMeterValidator(\"outgoing-byte-rate\", mockBrokerBytesRead))\n\tmetricValidators.registerForAllBrokers(broker, countMeterValidator(\"request-rate\", 1))\n\tmetricValidators.registerForAllBrokers(broker, countHistogramValidator(\"request-size\", 1))\n\tmetricValidators.registerForAllBrokers(broker, minMaxHistogramValidator(\"request-size\", mockBrokerBytesRead, mockBrokerBytesRead))\n\n\t// Check that there is no more requests in flight\n\tmetricValidators.registerForAllBrokers(broker, counterValidator(\"requests-in-flight\", 0))\n\n\t// Run the validators\n\tmetricValidators.run(t, broker.conf.MetricRegistry)\n}\n\nfunc BenchmarkBroker_Open(b *testing.B) {\n\tmb := NewMockBroker(nil, 0)\n\tdefer mb.Close()\n\tbroker := NewBroker(mb.Addr())\n\t// Set the broker id in order to validate local broker metrics\n\tbroker.id = 0\n\tmetrics.UseNilMetrics = false\n\tconf := NewTestConfig()\n\tconf.Version = V1_0_0_0\n\tfor b.Loop() {\n\t\terr := broker.Open(conf)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t\tbroker.Close()\n\t}\n}\n\nfunc BenchmarkBroker_No_Metrics_Open(b *testing.B) {\n\tmb := NewMockBroker(nil, 0)\n\tdefer mb.Close()\n\tbroker := NewBroker(mb.Addr())\n\tbroker.id = 0\n\tmetrics.UseNilMetrics = true\n\tconf := NewTestConfig()\n\tconf.Version = V1_0_0_0\n\tfor b.Loop() {\n\t\terr := broker.Open(conf)\n\t\tif err != nil {\n\t\t\tb.Fatal(err)\n\t\t}\n\t\tbroker.Close()\n\t}\n}\n\nfunc Test_handleThrottledResponse(t *testing.T) {\n\tmb := NewMockBroker(nil, 0)\n\tdefer mb.Close()\n\tbroker := NewBroker(mb.Addr())\n\tbroker.id = 0\n\tconf := NewTestConfig()\n\tconf.Version = V1_0_0_0\n\tthrottleTimeMs := 100\n\tthrottleTime := time.Duration(throttleTimeMs) * time.Millisecond\n\ttests := []struct {\n\t\tname        string\n\t\tresponse    protocolBody\n\t\texpectDelay bool\n\t}{\n\t\t{\n\t\t\tname: \"throttled response w/millisecond field\",\n\t\t\tresponse: &MetadataResponse{\n\t\t\t\tThrottleTimeMs: int32(throttleTimeMs),\n\t\t\t},\n\t\t\texpectDelay: true,\n\t\t},\n\t\t{\n\t\t\tname: \"not throttled response w/millisecond field\",\n\t\t\tresponse: &MetadataResponse{\n\t\t\t\tThrottleTimeMs: 0,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"throttled response w/time.Duration field\",\n\t\t\tresponse: &ProduceResponse{\n\t\t\t\tThrottleTime: throttleTime,\n\t\t\t},\n\t\t\texpectDelay: true,\n\t\t},\n\t\t{\n\t\t\tname: \"not throttled response w/time.Duration field\",\n\t\t\tresponse: &ProduceResponse{\n\t\t\t\tThrottleTime: time.Duration(0),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"not throttled response with no throttle time field\",\n\t\t\tresponse: &SaslHandshakeResponse{},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tbroker.metricRegistry = metrics.NewRegistry()\n\t\t\tbroker.brokerThrottleTime = broker.registerHistogram(\"throttle-time-in-ms\")\n\t\t\tstartTime := time.Now()\n\t\t\tbroker.handleThrottledResponse(tt.response)\n\t\t\tbroker.waitIfThrottled()\n\t\t\tif tt.expectDelay {\n\t\t\t\tif time.Since(startTime) < throttleTime {\n\t\t\t\t\tt.Fatal(\"expected throttling to cause delay\")\n\t\t\t\t}\n\t\t\t\tif broker.brokerThrottleTime.Min() != int64(throttleTimeMs) {\n\t\t\t\t\tt.Fatal(\"expected throttling to update metrics\")\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif time.Since(startTime) > throttleTime {\n\t\t\t\t\tt.Fatal(\"expected no throttling delay\")\n\t\t\t\t}\n\t\t\t\tif broker.brokerThrottleTime.Count() != 0 {\n\t\t\t\t\tt.Fatal(\"expected no metrics update\")\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n\tt.Run(\"test second throttle timer overrides first\", func(t *testing.T) {\n\t\tbroker.metricRegistry = metrics.NewRegistry()\n\t\tbroker.brokerThrottleTime = broker.registerHistogram(\"throttle-time-in-ms\")\n\t\tbroker.handleThrottledResponse(&MetadataResponse{\n\t\t\tThrottleTimeMs: int32(throttleTimeMs),\n\t\t})\n\t\tfirstTimer := broker.throttleTimer\n\t\tbroker.handleThrottledResponse(&MetadataResponse{\n\t\t\tThrottleTimeMs: int32(throttleTimeMs * 2),\n\t\t})\n\t\tif firstTimer.Stop() {\n\t\t\tt.Fatal(\"expected first timer to be stopped\")\n\t\t}\n\t\tstartTime := time.Now()\n\t\tbroker.waitIfThrottled()\n\t\tif time.Since(startTime) < throttleTime*2 {\n\t\t\tt.Fatal(\"expected throttling to use second delay\")\n\t\t}\n\t\tif broker.brokerThrottleTime.Min() != int64(throttleTimeMs) {\n\t\t\tt.Fatal(\"expected throttling to update metrics\")\n\t\t}\n\t\tif broker.brokerThrottleTime.Max() != int64(throttleTimeMs*2) {\n\t\t\tt.Fatal(\"expected throttling to update metrics\")\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "client.go",
    "content": "package sarama\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"math\"\n\t\"math/rand\"\n\t\"net\"\n\t\"slices\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"golang.org/x/net/proxy\"\n)\n\n// Client is a generic Kafka client. It manages connections to one or more Kafka brokers.\n// You MUST call Close() on a client to avoid leaks, it will not be garbage-collected\n// automatically when it passes out of scope. It is safe to share a client amongst many\n// users, however Kafka will process requests from a single client strictly in serial,\n// so it is generally more efficient to use the default one client per producer/consumer.\ntype Client interface {\n\t// Config returns the Config struct of the client. This struct should not be\n\t// altered after it has been created.\n\tConfig() *Config\n\n\t// Controller returns the cluster controller broker. It will return a\n\t// locally cached value if it's available. You can call RefreshController\n\t// to update the cached value. Requires Kafka 0.10 or higher.\n\tController() (*Broker, error)\n\n\t// RefreshController retrieves the cluster controller from fresh metadata\n\t// and stores it in the local cache. Requires Kafka 0.10 or higher.\n\tRefreshController() (*Broker, error)\n\n\t// Brokers returns the current set of active brokers as retrieved from cluster metadata.\n\tBrokers() []*Broker\n\n\t// Broker returns the active Broker if available for the broker ID.\n\tBroker(brokerID int32) (*Broker, error)\n\n\t// Topics returns the set of available topics as retrieved from cluster metadata.\n\tTopics() ([]string, error)\n\n\t// Partitions returns the sorted list of all partition IDs for the given topic.\n\tPartitions(topic string) ([]int32, error)\n\n\t// WritablePartitions returns the sorted list of all writable partition IDs for\n\t// the given topic, where \"writable\" means \"having a valid leader accepting\n\t// writes\".\n\tWritablePartitions(topic string) ([]int32, error)\n\n\t// Leader returns the broker object that is the leader of the current\n\t// topic/partition, as determined by querying the cluster metadata.\n\tLeader(topic string, partitionID int32) (*Broker, error)\n\n\t// LeaderAndEpoch returns the leader and its epoch for the current\n\t// topic/partition, as determined by querying the cluster metadata.\n\tLeaderAndEpoch(topic string, partitionID int32) (*Broker, int32, error)\n\n\t// Replicas returns the set of all replica IDs for the given partition.\n\tReplicas(topic string, partitionID int32) ([]int32, error)\n\n\t// InSyncReplicas returns the set of all in-sync replica IDs for the given\n\t// partition. In-sync replicas are replicas which are fully caught up with\n\t// the partition leader.\n\tInSyncReplicas(topic string, partitionID int32) ([]int32, error)\n\n\t// OfflineReplicas returns the set of all offline replica IDs for the given\n\t// partition. Offline replicas are replicas which are offline\n\tOfflineReplicas(topic string, partitionID int32) ([]int32, error)\n\n\t// RefreshBrokers takes a list of addresses to be used as seed brokers.\n\t// Existing broker connections are closed and the updated list of seed brokers\n\t// will be used for the next metadata fetch.\n\tRefreshBrokers(addrs []string) error\n\n\t// RefreshMetadata takes a list of topics and queries the cluster to refresh the\n\t// available metadata for those topics. If no topics are provided, it will refresh\n\t// metadata for all topics.\n\tRefreshMetadata(topics ...string) error\n\n\t// GetOffset queries the cluster to get the most recent available offset at the\n\t// given time (in milliseconds) on the topic/partition combination.\n\t// Time should be OffsetOldest for the earliest available offset,\n\t// OffsetNewest for the offset of the message that will be produced next, or a time.\n\tGetOffset(topic string, partitionID int32, time int64) (int64, error)\n\n\t// Coordinator returns the coordinating broker for a consumer group. It will\n\t// return a locally cached value if it's available. You can call\n\t// RefreshCoordinator to update the cached value. This function only works on\n\t// Kafka 0.8.2 and higher.\n\tCoordinator(consumerGroup string) (*Broker, error)\n\n\t// RefreshCoordinator retrieves the coordinator for a consumer group and stores it\n\t// in local cache. This function only works on Kafka 0.8.2 and higher.\n\tRefreshCoordinator(consumerGroup string) error\n\n\t// TransactionCoordinator returns the coordinating broker for a transaction id. It will\n\t// return a locally cached value if it's available. You can call\n\t// RefreshCoordinator to update the cached value. This function only works on\n\t// Kafka 0.11.0.0 and higher.\n\tTransactionCoordinator(transactionID string) (*Broker, error)\n\n\t// RefreshTransactionCoordinator retrieves the coordinator for a transaction id and stores it\n\t// in local cache. This function only works on Kafka 0.11.0.0 and higher.\n\tRefreshTransactionCoordinator(transactionID string) error\n\n\t// InitProducerID retrieves information required for Idempotent Producer\n\tInitProducerID() (*InitProducerIDResponse, error)\n\n\t// LeastLoadedBroker retrieves broker that has the least responses pending\n\tLeastLoadedBroker() *Broker\n\n\t// PartitionNotReadable checks if partition is not readable\n\tPartitionNotReadable(topic string, partition int32) bool\n\n\t// Close shuts down all broker connections managed by this client. It is required\n\t// to call this function before a client object passes out of scope, as it will\n\t// otherwise leak memory. You must close any Producers or Consumers using a client\n\t// before you close the client.\n\tClose() error\n\n\t// Closed returns true if the client has already had Close called on it\n\tClosed() bool\n}\n\nconst (\n\t// OffsetNewest stands for the log head offset, i.e. the offset that will be\n\t// assigned to the next message that will be produced to the partition. You\n\t// can send this to a client's GetOffset method to get this offset, or when\n\t// calling ConsumePartition to start consuming new messages.\n\tOffsetNewest int64 = -1\n\t// OffsetOldest stands for the oldest offset available on the broker for a\n\t// partition. You can send this to a client's GetOffset method to get this\n\t// offset, or when calling ConsumePartition to start consuming from the\n\t// oldest offset that is still available on the broker.\n\tOffsetOldest int64 = -2\n)\n\ntype client struct {\n\t// updateMetadataMs stores the time at which metadata was lasted updated.\n\t// Note: this accessed atomically so must be the first word in the struct\n\t// as per golang/go#41970\n\tupdateMetadataMs atomic.Int64\n\n\tconf           *Config\n\tcloser, closed chan none // for shutting down background metadata updater\n\n\t// the broker addresses given to us through the constructor are not guaranteed to be returned in\n\t// the cluster metadata (I *think* it only returns brokers who are currently leading partitions?)\n\t// so we store them separately\n\tseedBrokers []*Broker\n\tdeadSeeds   []*Broker\n\n\tcontrollerID            int32                                   // cluster controller broker id\n\tbrokers                 map[int32]*Broker                       // maps broker ids to brokers\n\tmetadata                map[string]map[int32]*PartitionMetadata // maps topics to partition ids to metadata\n\tmetadataTopics          map[string]none                         // topics that need to collect metadata\n\tcoordinators            map[string]int32                        // Maps consumer group names to coordinating broker IDs\n\ttransactionCoordinators map[string]int32                        // Maps transaction ids to coordinating broker IDs\n\n\t// If the number of partitions is large, we can get some churn calling cachedPartitions,\n\t// so the result is cached.  It is important to update this value whenever metadata is changed\n\tcachedPartitionsResults map[string][maxPartitionIndex][]int32\n\n\tlock sync.RWMutex // protects access to the maps that hold cluster state.\n\n\tmetadataRefresh metadataRefresh\n}\n\n// NewClient creates a new Client. It connects to one of the given broker addresses\n// and uses that broker to automatically fetch metadata on the rest of the kafka cluster. If metadata cannot\n// be retrieved from any of the given broker addresses, the client is not created.\nfunc NewClient(addrs []string, conf *Config) (Client, error) {\n\tDebugLogger.Println(\"Initializing new client\")\n\n\tif conf == nil {\n\t\tconf = NewConfig()\n\t}\n\n\tif err := conf.Validate(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(addrs) < 1 {\n\t\treturn nil, ConfigurationError(\"You must provide at least one broker address\")\n\t}\n\n\tif strings.Contains(addrs[0], \".servicebus.windows.net\") {\n\t\tif conf.Version.IsAtLeast(V1_1_0_0) || !conf.Version.IsAtLeast(V0_11_0_0) {\n\t\t\tLogger.Println(\"Connecting to Azure Event Hubs, forcing version to V1_0_0_0 for compatibility\")\n\t\t\tconf.Version = V1_0_0_0\n\t\t}\n\t}\n\tclient := &client{\n\t\tconf:                    conf,\n\t\tcloser:                  make(chan none),\n\t\tclosed:                  make(chan none),\n\t\tbrokers:                 make(map[int32]*Broker),\n\t\tmetadata:                make(map[string]map[int32]*PartitionMetadata),\n\t\tmetadataTopics:          make(map[string]none),\n\t\tcachedPartitionsResults: make(map[string][maxPartitionIndex][]int32),\n\t\tcoordinators:            make(map[string]int32),\n\t\ttransactionCoordinators: make(map[string]int32),\n\t}\n\trefresh := func(topics []string) error {\n\t\tdeadline := time.Time{}\n\t\tif client.conf.Metadata.Timeout > 0 {\n\t\t\tdeadline = time.Now().Add(client.conf.Metadata.Timeout)\n\t\t}\n\t\treturn client.tryRefreshMetadata(topics, client.conf.Metadata.Retry.Max, deadline)\n\t}\n\tif conf.Metadata.SingleFlight {\n\t\tclient.metadataRefresh = newSingleFlightRefresher(refresh)\n\t} else {\n\t\tclient.metadataRefresh = refresh\n\t}\n\n\tif conf.Net.ResolveCanonicalBootstrapServers {\n\t\tvar err error\n\t\taddrs, err = client.resolveCanonicalNames(addrs)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tclient.randomizeSeedBrokers(addrs)\n\n\tif conf.Metadata.Full {\n\t\t// do an initial fetch of all cluster metadata by specifying an empty list of topics\n\t\terr := client.RefreshMetadata()\n\t\tif err == nil {\n\t\t} else if errors.Is(err, ErrLeaderNotAvailable) || errors.Is(err, ErrReplicaNotAvailable) || errors.Is(err, ErrTopicAuthorizationFailed) || errors.Is(err, ErrClusterAuthorizationFailed) {\n\t\t\t// indicates that maybe part of the cluster is down, but is not fatal to creating the client\n\t\t\tLogger.Println(err)\n\t\t} else {\n\t\t\tclose(client.closed) // we haven't started the background updater yet, so we have to do this manually\n\t\t\t_ = client.Close()\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tgo withRecover(client.backgroundMetadataUpdater)\n\n\tDebugLogger.Println(\"Successfully initialized new client\")\n\n\treturn client, nil\n}\n\nfunc (client *client) Config() *Config {\n\treturn client.conf\n}\n\nfunc (client *client) Brokers() []*Broker {\n\tclient.lock.RLock()\n\tdefer client.lock.RUnlock()\n\tbrokers := make([]*Broker, 0, len(client.brokers))\n\tfor _, broker := range client.brokers {\n\t\tbrokers = append(brokers, broker)\n\t}\n\treturn brokers\n}\n\nfunc (client *client) Broker(brokerID int32) (*Broker, error) {\n\tclient.lock.RLock()\n\tdefer client.lock.RUnlock()\n\tbroker, ok := client.brokers[brokerID]\n\tif !ok {\n\t\treturn nil, ErrBrokerNotFound\n\t}\n\t_ = broker.Open(client.conf)\n\treturn broker, nil\n}\n\nfunc (client *client) InitProducerID() (*InitProducerIDResponse, error) {\n\t// FIXME: this InitProducerID seems to only be called from client_test.go (TestInitProducerIDConnectionRefused) and has been superceded by transaction_manager.go?\n\tbrokerErrors := make([]error, 0)\n\tfor broker := client.LeastLoadedBroker(); broker != nil; broker = client.LeastLoadedBroker() {\n\t\trequest := &InitProducerIDRequest{}\n\n\t\tif client.conf.Version.IsAtLeast(V2_7_0_0) {\n\t\t\t// Version 4 adds the support for new error code PRODUCER_FENCED.\n\t\t\trequest.Version = 4\n\t\t} else if client.conf.Version.IsAtLeast(V2_5_0_0) {\n\t\t\t// Version 3 adds ProducerId and ProducerEpoch, allowing producers to try to resume after an INVALID_PRODUCER_EPOCH error\n\t\t\trequest.Version = 3\n\t\t} else if client.conf.Version.IsAtLeast(V2_4_0_0) {\n\t\t\t// Version 2 is the first flexible version.\n\t\t\trequest.Version = 2\n\t\t} else if client.conf.Version.IsAtLeast(V2_0_0_0) {\n\t\t\t// Version 1 is the same as version 0.\n\t\t\trequest.Version = 1\n\t\t}\n\n\t\tresponse, err := broker.InitProducerID(request)\n\t\tif err == nil {\n\t\t\treturn response, nil\n\t\t} else {\n\t\t\t// some error, remove that broker and try again\n\t\t\tLogger.Printf(\"Client got error from broker %d when issuing InitProducerID : %v\\n\", broker.ID(), err)\n\t\t\t_ = broker.Close()\n\t\t\tbrokerErrors = append(brokerErrors, err)\n\t\t\tclient.deregisterBroker(broker)\n\t\t}\n\t}\n\n\treturn nil, Wrap(ErrOutOfBrokers, brokerErrors...)\n}\n\nfunc (client *client) Close() error {\n\tif client.Closed() {\n\t\t// Chances are this is being called from a defer() and the error will go unobserved\n\t\t// so we go ahead and log the event in this case.\n\t\tLogger.Printf(\"Close() called on already closed client\")\n\t\treturn ErrClosedClient\n\t}\n\n\t// shutdown and wait for the background thread before we take the lock, to avoid races\n\tclose(client.closer)\n\t<-client.closed\n\n\tclient.lock.Lock()\n\tdefer client.lock.Unlock()\n\tDebugLogger.Println(\"Closing Client\")\n\n\tfor _, broker := range client.brokers {\n\t\tsafeAsyncClose(broker)\n\t}\n\n\tfor _, broker := range client.seedBrokers {\n\t\tsafeAsyncClose(broker)\n\t}\n\n\tclient.brokers = nil\n\tclient.metadata = nil\n\tclient.metadataTopics = nil\n\n\treturn nil\n}\n\nfunc (client *client) Closed() bool {\n\tclient.lock.RLock()\n\tdefer client.lock.RUnlock()\n\n\treturn client.brokers == nil\n}\n\nfunc (client *client) Topics() ([]string, error) {\n\tif client.Closed() {\n\t\treturn nil, ErrClosedClient\n\t}\n\n\tclient.lock.RLock()\n\tdefer client.lock.RUnlock()\n\n\tret := make([]string, 0, len(client.metadata))\n\tfor topic := range client.metadata {\n\t\tret = append(ret, topic)\n\t}\n\n\treturn ret, nil\n}\n\nfunc (client *client) MetadataTopics() ([]string, error) {\n\tif client.Closed() {\n\t\treturn nil, ErrClosedClient\n\t}\n\n\tclient.lock.RLock()\n\tdefer client.lock.RUnlock()\n\n\tret := make([]string, 0, len(client.metadataTopics))\n\tfor topic := range client.metadataTopics {\n\t\tret = append(ret, topic)\n\t}\n\n\treturn ret, nil\n}\n\nfunc (client *client) Partitions(topic string) ([]int32, error) {\n\treturn client.getPartitions(topic, allPartitions)\n}\n\nfunc (client *client) WritablePartitions(topic string) ([]int32, error) {\n\treturn client.getPartitions(topic, writablePartitions)\n}\n\nfunc (client *client) getPartitions(topic string, pt partitionType) ([]int32, error) {\n\tif client.Closed() {\n\t\treturn nil, ErrClosedClient\n\t}\n\n\tpartitions := client.cachedPartitions(topic, pt)\n\n\t// len==0 catches when it's nil (no such topic) and the odd case when every single\n\t// partition is undergoing leader election simultaneously. Callers have to be able to handle\n\t// this function returning an empty slice (which is a valid return value) but catching it\n\t// here the first time (note we *don't* catch it below where we return ErrUnknownTopicOrPartition) triggers\n\t// a metadata refresh as a nicety so callers can just try again and don't have to manually\n\t// trigger a refresh (otherwise they'd just keep getting a stale cached copy).\n\tif len(partitions) == 0 {\n\t\terr := client.RefreshMetadata(topic)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tpartitions = client.cachedPartitions(topic, pt)\n\t}\n\n\tif partitions == nil {\n\t\treturn nil, ErrUnknownTopicOrPartition\n\t}\n\n\treturn partitions, nil\n}\n\nfunc (client *client) Replicas(topic string, partitionID int32) ([]int32, error) {\n\treturn client.getReplicas(topic, partitionID, func(metadata *PartitionMetadata) []int32 {\n\t\treturn metadata.Replicas\n\t})\n}\n\nfunc (client *client) InSyncReplicas(topic string, partitionID int32) ([]int32, error) {\n\treturn client.getReplicas(topic, partitionID, func(metadata *PartitionMetadata) []int32 {\n\t\treturn metadata.Isr\n\t})\n}\n\nfunc (client *client) OfflineReplicas(topic string, partitionID int32) ([]int32, error) {\n\treturn client.getReplicas(topic, partitionID, func(metadata *PartitionMetadata) []int32 {\n\t\treturn metadata.OfflineReplicas\n\t})\n}\n\nfunc (client *client) getReplicas(topic string, partitionID int32, extractor func(metadata *PartitionMetadata) []int32) ([]int32, error) {\n\tif client.Closed() {\n\t\treturn nil, ErrClosedClient\n\t}\n\n\tmetadata := client.cachedMetadata(topic, partitionID)\n\n\tif metadata == nil {\n\t\terr := client.RefreshMetadata(topic)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tmetadata = client.cachedMetadata(topic, partitionID)\n\t}\n\n\tif metadata == nil {\n\t\treturn nil, ErrUnknownTopicOrPartition\n\t}\n\n\treplicas := extractor(metadata)\n\tif errors.Is(metadata.Err, ErrReplicaNotAvailable) {\n\t\treturn dupInt32Slice(replicas), metadata.Err\n\t}\n\treturn dupInt32Slice(replicas), nil\n}\n\nfunc (client *client) Leader(topic string, partitionID int32) (*Broker, error) {\n\tleader, _, err := client.LeaderAndEpoch(topic, partitionID)\n\treturn leader, err\n}\n\nfunc (client *client) LeaderAndEpoch(topic string, partitionID int32) (*Broker, int32, error) {\n\tif client.Closed() {\n\t\treturn nil, -1, ErrClosedClient\n\t}\n\n\tleader, epoch, err := client.cachedLeader(topic, partitionID)\n\tif leader == nil {\n\t\terr = client.RefreshMetadata(topic)\n\t\tif err != nil {\n\t\t\treturn nil, -1, err\n\t\t}\n\t\tleader, epoch, err = client.cachedLeader(topic, partitionID)\n\t}\n\n\treturn leader, epoch, err\n}\n\nfunc (client *client) RefreshBrokers(addrs []string) error {\n\tif client.Closed() {\n\t\treturn ErrClosedClient\n\t}\n\n\tclient.lock.Lock()\n\tdefer client.lock.Unlock()\n\n\tfor _, broker := range client.brokers {\n\t\tsafeAsyncClose(broker)\n\t}\n\tclient.brokers = make(map[int32]*Broker)\n\n\tfor _, broker := range client.seedBrokers {\n\t\tsafeAsyncClose(broker)\n\t}\n\n\tfor _, broker := range client.deadSeeds {\n\t\tsafeAsyncClose(broker)\n\t}\n\n\tclient.seedBrokers = nil\n\tclient.deadSeeds = nil\n\n\tclient.randomizeSeedBrokers(addrs)\n\n\treturn nil\n}\n\nfunc (client *client) RefreshMetadata(topics ...string) error {\n\tif client.Closed() {\n\t\treturn ErrClosedClient\n\t}\n\n\t// Prior to 0.8.2, Kafka will throw exceptions on an empty topic and not return a proper\n\t// error. This handles the case by returning an error instead of sending it\n\t// off to Kafka. See: https://github.com/IBM/sarama/pull/38#issuecomment-26362310\n\tif slices.Contains(topics, \"\") {\n\t\treturn ErrInvalidTopic // this is the error that 0.8.2 and later correctly return\n\t}\n\treturn client.metadataRefresh(topics)\n}\n\nfunc (client *client) GetOffset(topic string, partitionID int32, timestamp int64) (int64, error) {\n\tif client.Closed() {\n\t\treturn -1, ErrClosedClient\n\t}\n\n\toffset, err := client.getOffset(topic, partitionID, timestamp)\n\tif err != nil {\n\t\tif err := client.RefreshMetadata(topic); err != nil {\n\t\t\treturn -1, err\n\t\t}\n\t\treturn client.getOffset(topic, partitionID, timestamp)\n\t}\n\n\treturn offset, err\n}\n\nfunc (client *client) Controller() (*Broker, error) {\n\tif client.Closed() {\n\t\treturn nil, ErrClosedClient\n\t}\n\n\tif !client.conf.Version.IsAtLeast(V0_10_0_0) {\n\t\treturn nil, ErrUnsupportedVersion\n\t}\n\n\tcontroller := client.cachedController()\n\tif controller == nil {\n\t\tif err := client.refreshMetadata(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tcontroller = client.cachedController()\n\t}\n\n\tif controller == nil {\n\t\treturn nil, ErrControllerNotAvailable\n\t}\n\n\t_ = controller.Open(client.conf)\n\treturn controller, nil\n}\n\n// deregisterController removes the cached controllerID\nfunc (client *client) deregisterController() {\n\tclient.lock.Lock()\n\tdefer client.lock.Unlock()\n\tif controller, ok := client.brokers[client.controllerID]; ok {\n\t\t_ = controller.Close()\n\t\tdelete(client.brokers, client.controllerID)\n\t}\n}\n\n// RefreshController retrieves the cluster controller from fresh metadata\n// and stores it in the local cache. Requires Kafka 0.10 or higher.\nfunc (client *client) RefreshController() (*Broker, error) {\n\tif client.Closed() {\n\t\treturn nil, ErrClosedClient\n\t}\n\n\tclient.deregisterController()\n\n\tif err := client.refreshMetadata(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tcontroller := client.cachedController()\n\tif controller == nil {\n\t\treturn nil, ErrControllerNotAvailable\n\t}\n\n\t_ = controller.Open(client.conf)\n\treturn controller, nil\n}\n\nfunc (client *client) Coordinator(consumerGroup string) (*Broker, error) {\n\tif client.Closed() {\n\t\treturn nil, ErrClosedClient\n\t}\n\n\tcoordinator := client.cachedCoordinator(consumerGroup)\n\n\tif coordinator == nil {\n\t\tif err := client.RefreshCoordinator(consumerGroup); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tcoordinator = client.cachedCoordinator(consumerGroup)\n\t}\n\n\tif coordinator == nil {\n\t\treturn nil, ErrConsumerCoordinatorNotAvailable\n\t}\n\n\t_ = coordinator.Open(client.conf)\n\treturn coordinator, nil\n}\n\nfunc (client *client) RefreshCoordinator(consumerGroup string) error {\n\tif client.Closed() {\n\t\treturn ErrClosedClient\n\t}\n\n\tresponse, err := client.findCoordinator(consumerGroup, CoordinatorGroup, client.conf.Metadata.Retry.Max)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient.lock.Lock()\n\tdefer client.lock.Unlock()\n\tclient.registerBroker(response.Coordinator)\n\tclient.coordinators[consumerGroup] = response.Coordinator.ID()\n\treturn nil\n}\n\nfunc (client *client) TransactionCoordinator(transactionID string) (*Broker, error) {\n\tif client.Closed() {\n\t\treturn nil, ErrClosedClient\n\t}\n\n\tcoordinator := client.cachedTransactionCoordinator(transactionID)\n\n\tif coordinator == nil {\n\t\tif err := client.RefreshTransactionCoordinator(transactionID); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tcoordinator = client.cachedTransactionCoordinator(transactionID)\n\t}\n\n\tif coordinator == nil {\n\t\treturn nil, ErrConsumerCoordinatorNotAvailable\n\t}\n\n\t_ = coordinator.Open(client.conf)\n\treturn coordinator, nil\n}\n\nfunc (client *client) RefreshTransactionCoordinator(transactionID string) error {\n\tif client.Closed() {\n\t\treturn ErrClosedClient\n\t}\n\n\tresponse, err := client.findCoordinator(transactionID, CoordinatorTransaction, client.conf.Metadata.Retry.Max)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclient.lock.Lock()\n\tdefer client.lock.Unlock()\n\tclient.registerBroker(response.Coordinator)\n\tclient.transactionCoordinators[transactionID] = response.Coordinator.ID()\n\treturn nil\n}\n\n// private broker management helpers\n\nfunc (client *client) randomizeSeedBrokers(addrs []string) {\n\trandom := rand.New(rand.NewSource(time.Now().UnixNano()))\n\tfor _, index := range random.Perm(len(addrs)) {\n\t\tclient.seedBrokers = append(client.seedBrokers, NewBroker(addrs[index]))\n\t}\n}\n\nfunc (client *client) updateBroker(brokers []*Broker) {\n\tif client.brokers == nil {\n\t\treturn\n\t}\n\n\tcurrentBroker := make(map[int32]*Broker, len(brokers))\n\n\tfor _, broker := range brokers {\n\t\tif broker == nil {\n\t\t\tcontinue\n\t\t}\n\t\tcurrentBroker[broker.ID()] = broker\n\t\tif client.brokers[broker.ID()] == nil { // add new broker\n\t\t\tclient.brokers[broker.ID()] = broker\n\t\t\tDebugLogger.Printf(\"client/brokers registered new broker #%d at %s\", broker.ID(), broker.Addr())\n\t\t} else if broker.Addr() != client.brokers[broker.ID()].Addr() { // replace broker with new address\n\t\t\tsafeAsyncClose(client.brokers[broker.ID()])\n\t\t\tclient.brokers[broker.ID()] = broker\n\t\t\tLogger.Printf(\"client/brokers replaced registered broker #%d with %s\", broker.ID(), broker.Addr())\n\t\t}\n\t}\n\n\tfor id, broker := range client.brokers {\n\t\tif _, exist := currentBroker[id]; !exist { // remove old broker\n\t\t\tsafeAsyncClose(broker)\n\t\t\tdelete(client.brokers, id)\n\t\t\tLogger.Printf(\"client/broker remove invalid broker #%d with %s\", broker.ID(), broker.Addr())\n\t\t}\n\t}\n}\n\n// registerBroker makes sure a broker received by a Metadata or Coordinator request is registered\n// in the brokers map. It returns the broker that is registered, which may be the provided broker,\n// or a previously registered Broker instance. You must hold the write lock before calling this function.\nfunc (client *client) registerBroker(broker *Broker) {\n\tif client.brokers == nil {\n\t\tLogger.Printf(\"cannot register broker #%d at %s, client already closed\", broker.ID(), broker.Addr())\n\t\treturn\n\t}\n\n\tif client.brokers[broker.ID()] == nil {\n\t\tclient.brokers[broker.ID()] = broker\n\t\tDebugLogger.Printf(\"client/brokers registered new broker #%d at %s\", broker.ID(), broker.Addr())\n\t} else if broker.Addr() != client.brokers[broker.ID()].Addr() {\n\t\tsafeAsyncClose(client.brokers[broker.ID()])\n\t\tclient.brokers[broker.ID()] = broker\n\t\tLogger.Printf(\"client/brokers replaced registered broker #%d with %s\", broker.ID(), broker.Addr())\n\t}\n}\n\n// deregisterBroker removes a broker from the broker list, and if it's\n// not in the broker list, removes it from seedBrokers.\nfunc (client *client) deregisterBroker(broker *Broker) {\n\tclient.lock.Lock()\n\tdefer client.lock.Unlock()\n\n\t_, ok := client.brokers[broker.ID()]\n\tif ok {\n\t\tLogger.Printf(\"client/brokers deregistered broker #%d at %s\", broker.ID(), broker.Addr())\n\t\tdelete(client.brokers, broker.ID())\n\t\treturn\n\t}\n\tif len(client.seedBrokers) > 0 && broker == client.seedBrokers[0] {\n\t\tclient.deadSeeds = append(client.deadSeeds, broker)\n\t\tclient.seedBrokers = client.seedBrokers[1:]\n\t}\n}\n\nfunc (client *client) resurrectDeadBrokers() {\n\tclient.lock.Lock()\n\tdefer client.lock.Unlock()\n\n\tLogger.Printf(\"client/brokers resurrecting %d dead seed brokers\", len(client.deadSeeds))\n\tclient.seedBrokers = append(client.seedBrokers, client.deadSeeds...)\n\tclient.deadSeeds = nil\n}\n\n// LeastLoadedBroker returns the broker with the least pending requests.\n// Firstly, choose the broker from cached broker list. If the broker list is empty, choose from seed brokers.\nfunc (client *client) LeastLoadedBroker() *Broker {\n\tclient.lock.RLock()\n\tdefer client.lock.RUnlock()\n\n\tvar leastLoadedBroker *Broker\n\tpendingRequests := math.MaxInt\n\tfor _, broker := range client.brokers {\n\t\tif pendingRequests > broker.ResponseSize() {\n\t\t\tpendingRequests = broker.ResponseSize()\n\t\t\tleastLoadedBroker = broker\n\t\t}\n\t}\n\tif leastLoadedBroker != nil {\n\t\t_ = leastLoadedBroker.Open(client.conf)\n\t\treturn leastLoadedBroker\n\t}\n\n\tif len(client.seedBrokers) > 0 {\n\t\t_ = client.seedBrokers[0].Open(client.conf)\n\t\treturn client.seedBrokers[0]\n\t}\n\n\treturn leastLoadedBroker\n}\n\n// private caching/lazy metadata helpers\n\ntype partitionType int\n\nconst (\n\tallPartitions partitionType = iota\n\twritablePartitions\n\t// If you add any more types, update the partition cache in update()\n\n\t// Ensure this is the last partition type value\n\tmaxPartitionIndex\n)\n\nfunc (client *client) cachedMetadata(topic string, partitionID int32) *PartitionMetadata {\n\tclient.lock.RLock()\n\tdefer client.lock.RUnlock()\n\n\tpartitions := client.metadata[topic]\n\tif partitions != nil {\n\t\treturn partitions[partitionID]\n\t}\n\n\treturn nil\n}\n\nfunc (client *client) cachedPartitions(topic string, partitionSet partitionType) []int32 {\n\tclient.lock.RLock()\n\tdefer client.lock.RUnlock()\n\n\tpartitions, exists := client.cachedPartitionsResults[topic]\n\n\tif !exists {\n\t\treturn nil\n\t}\n\treturn partitions[partitionSet]\n}\n\nfunc (client *client) setPartitionCache(topic string, partitionSet partitionType) []int32 {\n\tpartitions := client.metadata[topic]\n\n\tif partitions == nil {\n\t\treturn nil\n\t}\n\n\tret := make([]int32, 0, len(partitions))\n\tfor _, partition := range partitions {\n\t\tif partitionSet == writablePartitions && errors.Is(partition.Err, ErrLeaderNotAvailable) {\n\t\t\tcontinue\n\t\t}\n\t\tret = append(ret, partition.ID)\n\t}\n\n\tsort.Sort(int32Slice(ret))\n\treturn ret\n}\n\nfunc (client *client) cachedLeader(topic string, partitionID int32) (*Broker, int32, error) {\n\tclient.lock.RLock()\n\tdefer client.lock.RUnlock()\n\n\tpartitions := client.metadata[topic]\n\tif partitions != nil {\n\t\tmetadata, ok := partitions[partitionID]\n\t\tif ok {\n\t\t\tif errors.Is(metadata.Err, ErrLeaderNotAvailable) {\n\t\t\t\treturn nil, -1, ErrLeaderNotAvailable\n\t\t\t}\n\t\t\tb := client.brokers[metadata.Leader]\n\t\t\tif b == nil {\n\t\t\t\treturn nil, -1, ErrLeaderNotAvailable\n\t\t\t}\n\t\t\t_ = b.Open(client.conf)\n\t\t\treturn b, metadata.LeaderEpoch, nil\n\t\t}\n\t}\n\n\treturn nil, -1, ErrUnknownTopicOrPartition\n}\n\nfunc (client *client) getOffset(topic string, partitionID int32, timestamp int64) (int64, error) {\n\tbroker, err := client.Leader(topic, partitionID)\n\tif err != nil {\n\t\treturn -1, err\n\t}\n\n\trequest := NewOffsetRequest(client.conf.Version)\n\trequest.AddBlock(topic, partitionID, timestamp, 1)\n\n\tresponse, err := broker.GetAvailableOffsets(request)\n\tif err != nil {\n\t\t_ = broker.Close()\n\t\treturn -1, err\n\t}\n\n\tblock := response.GetBlock(topic, partitionID)\n\tif block == nil {\n\t\t_ = broker.Close()\n\t\treturn -1, ErrIncompleteResponse\n\t}\n\tif !errors.Is(block.Err, ErrNoError) {\n\t\treturn -1, block.Err\n\t}\n\tif len(block.Offsets) != 1 {\n\t\treturn -1, ErrOffsetOutOfRange\n\t}\n\n\treturn block.Offsets[0], nil\n}\n\n// core metadata update logic\n\nfunc (client *client) backgroundMetadataUpdater() {\n\tdefer close(client.closed)\n\n\tif client.conf.Metadata.RefreshFrequency == time.Duration(0) {\n\t\treturn\n\t}\n\n\tticker := time.NewTicker(client.conf.Metadata.RefreshFrequency)\n\tdefer ticker.Stop()\n\n\tfor {\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\t\tif err := client.refreshMetadata(); err != nil {\n\t\t\t\tLogger.Println(\"Client background metadata update:\", err)\n\t\t\t}\n\t\tcase <-client.closer:\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (client *client) refreshMetadata() error {\n\tvar topics []string\n\n\tif !client.conf.Metadata.Full {\n\t\tif specificTopics, err := client.MetadataTopics(); err != nil {\n\t\t\treturn err\n\t\t} else if len(specificTopics) == 0 {\n\t\t\treturn ErrNoTopicsToUpdateMetadata\n\t\t} else {\n\t\t\ttopics = specificTopics\n\t\t}\n\t}\n\n\tif err := client.RefreshMetadata(topics...); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (client *client) tryRefreshMetadata(topics []string, attemptsRemaining int, deadline time.Time) error {\n\tpastDeadline := func(backoff time.Duration) bool {\n\t\tif !deadline.IsZero() && time.Now().Add(backoff).After(deadline) {\n\t\t\t// we are past the deadline\n\t\t\treturn true\n\t\t}\n\t\treturn false\n\t}\n\tretry := func(err error) error {\n\t\tif attemptsRemaining > 0 {\n\t\t\tbackoff := client.computeBackoff(attemptsRemaining)\n\t\t\tif pastDeadline(backoff) {\n\t\t\t\tLogger.Println(\"client/metadata skipping last retries as we would go past the metadata timeout\")\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif backoff > 0 {\n\t\t\t\ttime.Sleep(backoff)\n\t\t\t}\n\n\t\t\tt := client.updateMetadataMs.Load()\n\t\t\tif time.Since(time.UnixMilli(t)) < backoff {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tattemptsRemaining--\n\t\t\tLogger.Printf(\"client/metadata retrying after %dms... (%d attempts remaining)\\n\", backoff/time.Millisecond, attemptsRemaining)\n\n\t\t\treturn client.tryRefreshMetadata(topics, attemptsRemaining, deadline)\n\t\t}\n\t\treturn err\n\t}\n\n\tbroker := client.LeastLoadedBroker()\n\tbrokerErrors := make([]error, 0)\n\tfor ; broker != nil && !pastDeadline(0); broker = client.LeastLoadedBroker() {\n\t\tallowAutoTopicCreation := client.conf.Metadata.AllowAutoTopicCreation\n\t\tif len(topics) > 0 {\n\t\t\tDebugLogger.Printf(\"client/metadata fetching metadata for %v from broker %s\\n\", topics, broker.addr)\n\t\t} else {\n\t\t\tallowAutoTopicCreation = false\n\t\t\tDebugLogger.Printf(\"client/metadata fetching metadata for all topics from broker %s\\n\", broker.addr)\n\t\t}\n\n\t\treq := NewMetadataRequest(client.conf.Version, topics)\n\t\treq.AllowAutoTopicCreation = allowAutoTopicCreation\n\t\tclient.updateMetadataMs.Store(time.Now().UnixMilli())\n\n\t\tresponse, err := broker.GetMetadata(req)\n\t\tvar kerror KError\n\t\tvar packetEncodingError PacketEncodingError\n\t\tif err == nil {\n\t\t\t// When talking to the startup phase of a broker, it is possible to receive an empty metadata set. We should remove that broker and try next broker (https://issues.apache.org/jira/browse/KAFKA-7924).\n\t\t\tif len(response.Brokers) == 0 {\n\t\t\t\tLogger.Printf(\"client/metadata receiving empty brokers from the metadata response when requesting the broker #%d at %s\", broker.ID(), broker.addr)\n\t\t\t\t_ = broker.Close()\n\t\t\t\tclient.deregisterBroker(broker)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tallKnownMetaData := len(topics) == 0\n\t\t\t// valid response, use it\n\t\t\tshouldRetry, err := client.updateMetadata(response, allKnownMetaData)\n\t\t\tif shouldRetry {\n\t\t\t\tLogger.Println(\"client/metadata found some partitions to be leaderless\")\n\t\t\t\treturn retry(err) // note: err can be nil\n\t\t\t}\n\t\t\treturn err\n\t\t} else if errors.As(err, &packetEncodingError) {\n\t\t\t// didn't even send, return the error\n\t\t\treturn err\n\t\t} else if errors.As(err, &kerror) {\n\t\t\t// if SASL auth error return as this _should_ be a non retryable err for all brokers\n\t\t\tif errors.Is(err, ErrSASLAuthenticationFailed) {\n\t\t\t\tLogger.Println(\"client/metadata failed SASL authentication\")\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif errors.Is(err, ErrTopicAuthorizationFailed) {\n\t\t\t\tLogger.Println(\"client is not authorized to access this topic. The topics were: \", topics)\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t// else remove that broker and try again\n\t\t\tLogger.Printf(\"client/metadata got error from broker %d while fetching metadata: %v\\n\", broker.ID(), err)\n\t\t\t_ = broker.Close()\n\t\t\tclient.deregisterBroker(broker)\n\t\t} else {\n\t\t\t// some other error, remove that broker and try again\n\t\t\tLogger.Printf(\"client/metadata got error from broker %d while fetching metadata: %v\\n\", broker.ID(), err)\n\t\t\tbrokerErrors = append(brokerErrors, err)\n\t\t\t_ = broker.Close()\n\t\t\tclient.deregisterBroker(broker)\n\t\t}\n\t}\n\n\terror := Wrap(ErrOutOfBrokers, brokerErrors...)\n\tif broker != nil {\n\t\tLogger.Printf(\"client/metadata not fetching metadata from broker %s as we would go past the metadata timeout\\n\", broker.addr)\n\t\treturn retry(error)\n\t}\n\n\tLogger.Println(\"client/metadata no available broker to send metadata request to\")\n\tclient.resurrectDeadBrokers()\n\treturn retry(error)\n}\n\n// if no fatal error, returns a list of topics that need retrying due to ErrLeaderNotAvailable\nfunc (client *client) updateMetadata(data *MetadataResponse, allKnownMetaData bool) (retry bool, err error) {\n\tif client.Closed() {\n\t\treturn\n\t}\n\n\tclient.lock.Lock()\n\tdefer client.lock.Unlock()\n\n\t// For all the brokers we received:\n\t// - if it is a new ID, save it\n\t// - if it is an existing ID, but the address we have is stale, discard the old one and save it\n\t// - if some brokers is not exist in it, remove old broker\n\t// - otherwise ignore it, replacing our existing one would just bounce the connection\n\tclient.updateBroker(data.Brokers)\n\n\tclient.controllerID = data.ControllerID\n\n\tif allKnownMetaData {\n\t\tclient.metadata = make(map[string]map[int32]*PartitionMetadata)\n\t\tclient.metadataTopics = make(map[string]none)\n\t\tclient.cachedPartitionsResults = make(map[string][maxPartitionIndex][]int32)\n\t}\n\tfor _, topic := range data.Topics {\n\t\t// topics must be added firstly to `metadataTopics` to guarantee that all\n\t\t// requested topics must be recorded to keep them trackable for periodically\n\t\t// metadata refresh.\n\t\tif _, exists := client.metadataTopics[topic.Name]; !exists {\n\t\t\tclient.metadataTopics[topic.Name] = none{}\n\t\t}\n\t\tdelete(client.metadata, topic.Name)\n\t\tdelete(client.cachedPartitionsResults, topic.Name)\n\n\t\tswitch topic.Err {\n\t\tcase ErrNoError:\n\t\t\t// no-op\n\t\tcase ErrInvalidTopic, ErrTopicAuthorizationFailed: // don't retry, don't store partial results\n\t\t\terr = topic.Err\n\t\t\tcontinue\n\t\tcase ErrUnknownTopicOrPartition: // retry, do not store partial partition results\n\t\t\terr = topic.Err\n\t\t\tretry = true\n\t\t\tcontinue\n\t\tcase ErrLeaderNotAvailable: // retry, but store partial partition results\n\t\t\tretry = true\n\t\tdefault: // don't retry, don't store partial results\n\t\t\tLogger.Printf(\"Unexpected topic-level metadata error: %s\", topic.Err)\n\t\t\terr = topic.Err\n\t\t\tcontinue\n\t\t}\n\n\t\tclient.metadata[topic.Name] = make(map[int32]*PartitionMetadata, len(topic.Partitions))\n\t\tfor _, partition := range topic.Partitions {\n\t\t\tclient.metadata[topic.Name][partition.ID] = partition\n\t\t\tif errors.Is(partition.Err, ErrLeaderNotAvailable) {\n\t\t\t\tretry = true\n\t\t\t}\n\t\t}\n\n\t\tvar partitionCache [maxPartitionIndex][]int32\n\t\tpartitionCache[allPartitions] = client.setPartitionCache(topic.Name, allPartitions)\n\t\tpartitionCache[writablePartitions] = client.setPartitionCache(topic.Name, writablePartitions)\n\t\tclient.cachedPartitionsResults[topic.Name] = partitionCache\n\t}\n\n\treturn\n}\n\nfunc (client *client) cachedCoordinator(consumerGroup string) *Broker {\n\tclient.lock.RLock()\n\tdefer client.lock.RUnlock()\n\tif coordinatorID, ok := client.coordinators[consumerGroup]; ok {\n\t\treturn client.brokers[coordinatorID]\n\t}\n\treturn nil\n}\n\nfunc (client *client) cachedTransactionCoordinator(transactionID string) *Broker {\n\tclient.lock.RLock()\n\tdefer client.lock.RUnlock()\n\tif coordinatorID, ok := client.transactionCoordinators[transactionID]; ok {\n\t\treturn client.brokers[coordinatorID]\n\t}\n\treturn nil\n}\n\nfunc (client *client) cachedController() *Broker {\n\tclient.lock.RLock()\n\tdefer client.lock.RUnlock()\n\n\treturn client.brokers[client.controllerID]\n}\n\nfunc (client *client) computeBackoff(attemptsRemaining int) time.Duration {\n\tif client.conf.Metadata.Retry.BackoffFunc != nil {\n\t\tmaxRetries := client.conf.Metadata.Retry.Max\n\t\tretries := maxRetries - attemptsRemaining\n\t\treturn client.conf.Metadata.Retry.BackoffFunc(retries, maxRetries)\n\t}\n\treturn client.conf.Metadata.Retry.Backoff\n}\n\nfunc (client *client) findCoordinator(coordinatorKey string, coordinatorType CoordinatorType, attemptsRemaining int) (*FindCoordinatorResponse, error) {\n\tretry := func(err error) (*FindCoordinatorResponse, error) {\n\t\tif attemptsRemaining > 0 {\n\t\t\tbackoff := client.computeBackoff(attemptsRemaining)\n\t\t\tattemptsRemaining--\n\t\t\tLogger.Printf(\"client/coordinator retrying after %dms... (%d attempts remaining)\\n\", backoff/time.Millisecond, attemptsRemaining)\n\t\t\ttime.Sleep(backoff)\n\t\t\treturn client.findCoordinator(coordinatorKey, coordinatorType, attemptsRemaining)\n\t\t}\n\t\treturn nil, err\n\t}\n\n\tbrokerErrors := make([]error, 0)\n\tfor broker := client.LeastLoadedBroker(); broker != nil; broker = client.LeastLoadedBroker() {\n\t\tDebugLogger.Printf(\"client/coordinator requesting coordinator for %s from %s\\n\", coordinatorKey, broker.Addr())\n\n\t\trequest := new(FindCoordinatorRequest)\n\t\trequest.CoordinatorKey = coordinatorKey\n\t\trequest.CoordinatorType = coordinatorType\n\n\t\t// Version 1 adds KeyType.\n\t\tif client.conf.Version.IsAtLeast(V0_11_0_0) {\n\t\t\trequest.Version = 1\n\t\t}\n\t\t// Version 2 is the same as version 1.\n\t\tif client.conf.Version.IsAtLeast(V2_0_0_0) {\n\t\t\trequest.Version = 2\n\t\t}\n\n\t\tresponse, err := broker.FindCoordinator(request)\n\t\tif err != nil {\n\t\t\tLogger.Printf(\"client/coordinator request to broker %s failed: %s\\n\", broker.Addr(), err)\n\n\t\t\tvar packetEncodingError PacketEncodingError\n\t\t\tif errors.As(err, &packetEncodingError) {\n\t\t\t\treturn nil, err\n\t\t\t} else {\n\t\t\t\t_ = broker.Close()\n\t\t\t\tbrokerErrors = append(brokerErrors, err)\n\t\t\t\tclient.deregisterBroker(broker)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tif errors.Is(response.Err, ErrNoError) {\n\t\t\tDebugLogger.Printf(\"client/coordinator coordinator for %s is #%d (%s)\\n\", coordinatorKey, response.Coordinator.ID(), response.Coordinator.Addr())\n\t\t\treturn response, nil\n\t\t} else if errors.Is(response.Err, ErrConsumerCoordinatorNotAvailable) {\n\t\t\tLogger.Printf(\"client/coordinator coordinator for %s is not available\\n\", coordinatorKey)\n\n\t\t\t// This is very ugly, but this scenario will only happen once per cluster.\n\t\t\t// The __consumer_offsets topic only has to be created one time.\n\t\t\t// The number of partitions not configurable, but partition 0 should always exist.\n\t\t\tif _, err := client.Leader(\"__consumer_offsets\", 0); err != nil {\n\t\t\t\tLogger.Printf(\"client/coordinator the __consumer_offsets topic is not initialized completely yet. Waiting 2 seconds...\\n\")\n\t\t\t\ttime.Sleep(2 * time.Second)\n\t\t\t}\n\t\t\tif coordinatorType == CoordinatorTransaction {\n\t\t\t\tif _, err := client.Leader(\"__transaction_state\", 0); err != nil {\n\t\t\t\t\tLogger.Printf(\"client/coordinator the __transaction_state topic is not initialized completely yet. Waiting 2 seconds...\\n\")\n\t\t\t\t\ttime.Sleep(2 * time.Second)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn retry(ErrConsumerCoordinatorNotAvailable)\n\t\t} else if errors.Is(response.Err, ErrGroupAuthorizationFailed) {\n\t\t\tLogger.Printf(\"client was not authorized to access group %s while attempting to find coordinator\", coordinatorKey)\n\t\t\treturn retry(ErrGroupAuthorizationFailed)\n\t\t} else {\n\t\t\treturn nil, response.Err\n\t\t}\n\t}\n\n\tLogger.Println(\"client/coordinator no available broker to send consumer metadata request to\")\n\tclient.resurrectDeadBrokers()\n\treturn retry(Wrap(ErrOutOfBrokers, brokerErrors...))\n}\n\nfunc (client *client) resolveCanonicalNames(addrs []string) ([]string, error) {\n\tctx := context.Background()\n\n\tdialer := client.Config().getDialer()\n\tresolver := net.Resolver{\n\t\tDial: func(ctx context.Context, network, address string) (net.Conn, error) {\n\t\t\t// dial func should only be called once, so switching within is acceptable\n\t\t\tswitch d := dialer.(type) {\n\t\t\tcase proxy.ContextDialer:\n\t\t\t\treturn d.DialContext(ctx, network, address)\n\t\t\tdefault:\n\t\t\t\t// we have no choice but to ignore the context\n\t\t\t\treturn d.Dial(network, address)\n\t\t\t}\n\t\t},\n\t}\n\n\tcanonicalAddrs := make(map[string]struct{}, len(addrs)) // dedupe as we go\n\tfor _, addr := range addrs {\n\t\thost, port, err := net.SplitHostPort(addr)\n\t\tif err != nil {\n\t\t\treturn nil, err // message includes addr\n\t\t}\n\n\t\tips, err := resolver.LookupHost(ctx, host)\n\t\tif err != nil {\n\t\t\treturn nil, err // message includes host\n\t\t}\n\t\tfor _, ip := range ips {\n\t\t\tptrs, err := resolver.LookupAddr(ctx, ip)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err // message includes ip\n\t\t\t}\n\n\t\t\t// unlike the Java client, we do not further check that PTRs resolve\n\t\t\tptr := strings.TrimSuffix(ptrs[0], \".\") // trailing dot breaks GSSAPI\n\t\t\tcanonicalAddrs[net.JoinHostPort(ptr, port)] = struct{}{}\n\t\t}\n\t}\n\n\taddrs = make([]string, 0, len(canonicalAddrs))\n\tfor addr := range canonicalAddrs {\n\t\taddrs = append(addrs, addr)\n\t}\n\treturn addrs, nil\n}\n\n// nopCloserClient embeds an existing Client, but disables\n// the Close method (yet all other methods pass\n// through unchanged). This is for use in larger structs\n// where it is undesirable to close the client that was\n// passed in by the caller.\ntype nopCloserClient struct {\n\tClient\n}\n\n// Close intercepts and purposely does not call the underlying\n// client's Close() method.\nfunc (ncc *nopCloserClient) Close() error {\n\treturn nil\n}\n\nfunc (client *client) PartitionNotReadable(topic string, partition int32) bool {\n\tclient.lock.RLock()\n\tdefer client.lock.RUnlock()\n\n\tpm := client.metadata[topic][partition]\n\tif pm == nil {\n\t\treturn true\n\t}\n\treturn pm.Leader == -1\n}\n"
  },
  {
    "path": "client_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/rcrowley/go-metrics\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSimpleClient(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\n\tmetadataResponse := new(MetadataResponse)\n\tmetadataResponse.AddBroker(seedBroker.Addr(), seedBroker.BrokerID())\n\tseedBroker.Returns(metadataResponse)\n\n\tclient, err := NewClient([]string{seedBroker.Addr()}, NewTestConfig())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tseedBroker.Close()\n\tsafeClose(t, client)\n}\n\nfunc TestCachedPartitions(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\n\treplicas := []int32{3, 1, 5}\n\tisr := []int32{5, 1}\n\n\tmetadataResponse := new(MetadataResponse)\n\tmetadataResponse.AddBroker(\"localhost:12345\", 2)\n\tmetadataResponse.AddTopicPartition(\"my_topic\", 0, 2, replicas, isr, []int32{}, ErrNoError)\n\tmetadataResponse.AddTopicPartition(\"my_topic\", 1, 2, replicas, isr, []int32{}, ErrLeaderNotAvailable)\n\tseedBroker.Returns(metadataResponse)\n\n\tconfig := NewTestConfig()\n\tconfig.Metadata.Retry.Max = 0\n\tc, err := NewClient([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer safeClose(t, c)\n\tclient := c.(*client)\n\n\t// Verify they aren't cached the same\n\tallP := client.cachedPartitionsResults[\"my_topic\"][allPartitions]\n\twriteP := client.cachedPartitionsResults[\"my_topic\"][writablePartitions]\n\tif len(allP) == len(writeP) {\n\t\tt.Fatal(\"Invalid lengths!\")\n\t}\n\n\ttmp := client.cachedPartitionsResults[\"my_topic\"]\n\t// Verify we actually use the cache at all!\n\ttmp[allPartitions] = []int32{1, 2, 3, 4}\n\tclient.cachedPartitionsResults[\"my_topic\"] = tmp\n\tif len(client.cachedPartitions(\"my_topic\", allPartitions)) != 4 {\n\t\tt.Fatal(\"Not using the cache!\")\n\t}\n\n\tseedBroker.Close()\n}\n\nfunc TestClientDoesntCachePartitionsForTopicsWithErrors(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\n\treplicas := []int32{seedBroker.BrokerID()}\n\n\tmetadataResponse := new(MetadataResponse)\n\tmetadataResponse.AddBroker(seedBroker.Addr(), seedBroker.BrokerID())\n\tmetadataResponse.AddTopicPartition(\"my_topic\", 1, replicas[0], replicas, replicas, []int32{}, ErrNoError)\n\tmetadataResponse.AddTopicPartition(\"my_topic\", 2, replicas[0], replicas, replicas, []int32{}, ErrNoError)\n\tseedBroker.Returns(metadataResponse)\n\n\tconfig := NewTestConfig()\n\tconfig.Metadata.Retry.Max = 0\n\tclient, err := NewClient([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tmetadataResponse = new(MetadataResponse)\n\tmetadataResponse.AddBroker(seedBroker.Addr(), seedBroker.BrokerID())\n\tmetadataResponse.AddTopic(\"unknown\", ErrUnknownTopicOrPartition)\n\tseedBroker.Returns(metadataResponse)\n\n\tpartitions, err := client.Partitions(\"unknown\")\n\n\tif !errors.Is(err, ErrUnknownTopicOrPartition) {\n\t\tt.Error(\"Expected ErrUnknownTopicOrPartition, found\", err)\n\t}\n\tif partitions != nil {\n\t\tt.Errorf(\"Should return nil as partition list, found %v\", partitions)\n\t}\n\n\t// Should still use the cache of a known topic\n\t_, err = client.Partitions(\"my_topic\")\n\tif err != nil {\n\t\tt.Errorf(\"Expected no error, found %v\", err)\n\t}\n\n\tmetadataResponse = new(MetadataResponse)\n\tmetadataResponse.AddBroker(seedBroker.Addr(), seedBroker.BrokerID())\n\tmetadataResponse.AddTopic(\"unknown\", ErrUnknownTopicOrPartition)\n\tseedBroker.Returns(metadataResponse)\n\n\t// Should not use cache for unknown topic\n\tpartitions, err = client.Partitions(\"unknown\")\n\tif !errors.Is(err, ErrUnknownTopicOrPartition) {\n\t\tt.Error(\"Expected ErrUnknownTopicOrPartition, found\", err)\n\t}\n\tif partitions != nil {\n\t\tt.Errorf(\"Should return nil as partition list, found %v\", partitions)\n\t}\n\n\tseedBroker.Close()\n\tsafeClose(t, client)\n}\n\nfunc TestClientSeedBrokers(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\n\tmetadataResponse := new(MetadataResponse)\n\tmetadataResponse.AddBroker(\"localhost:12345\", 2)\n\tseedBroker.Returns(metadataResponse)\n\n\tclient, err := NewClient([]string{seedBroker.Addr()}, NewTestConfig())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tseedBroker.Close()\n\tsafeClose(t, client)\n}\n\nfunc TestClientMetadata(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tleader := NewMockBroker(t, 5)\n\n\treplicas := []int32{3, 1, 5}\n\tisr := []int32{5, 1}\n\n\tmetadataResponse := new(MetadataResponse)\n\tmetadataResponse.AddBroker(leader.Addr(), leader.BrokerID())\n\tmetadataResponse.AddTopicPartition(\"my_topic\", 0, leader.BrokerID(), replicas, isr, []int32{}, ErrNoError)\n\tmetadataResponse.AddTopicPartition(\"my_topic\", 1, leader.BrokerID(), replicas, isr, []int32{}, ErrLeaderNotAvailable)\n\tseedBroker.Returns(metadataResponse)\n\n\tconfig := NewTestConfig()\n\tconfig.Metadata.Retry.Max = 0\n\tclient, err := NewClient([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttopics, err := client.Topics()\n\tif err != nil {\n\t\tt.Error(err)\n\t} else if len(topics) != 1 || topics[0] != \"my_topic\" {\n\t\tt.Error(\"Client returned incorrect topics:\", topics)\n\t}\n\n\tparts, err := client.Partitions(\"my_topic\")\n\tif err != nil {\n\t\tt.Error(err)\n\t} else if len(parts) != 2 || parts[0] != 0 || parts[1] != 1 {\n\t\tt.Error(\"Client returned incorrect partitions for my_topic:\", parts)\n\t}\n\n\tparts, err = client.WritablePartitions(\"my_topic\")\n\tif err != nil {\n\t\tt.Error(err)\n\t} else if len(parts) != 1 || parts[0] != 0 {\n\t\tt.Error(\"Client returned incorrect writable partitions for my_topic:\", parts)\n\t}\n\n\ttst, err := client.Leader(\"my_topic\", 0)\n\tif err != nil {\n\t\tt.Error(err)\n\t} else if tst.ID() != 5 {\n\t\tt.Error(\"Leader for my_topic had incorrect ID.\")\n\t}\n\n\treplicas, err = client.Replicas(\"my_topic\", 0)\n\tif err != nil {\n\t\tt.Error(err)\n\t} else if replicas[0] != 3 {\n\t\tt.Error(\"Incorrect (or sorted) replica\")\n\t} else if replicas[1] != 1 {\n\t\tt.Error(\"Incorrect (or sorted) replica\")\n\t} else if replicas[2] != 5 {\n\t\tt.Error(\"Incorrect (or sorted) replica\")\n\t}\n\n\tisr, err = client.InSyncReplicas(\"my_topic\", 0)\n\tif err != nil {\n\t\tt.Error(err)\n\t} else if len(isr) != 2 {\n\t\tt.Error(\"Client returned incorrect ISRs for partition:\", isr)\n\t} else if isr[0] != 5 {\n\t\tt.Error(\"Incorrect (or sorted) ISR:\", isr)\n\t} else if isr[1] != 1 {\n\t\tt.Error(\"Incorrect (or sorted) ISR:\", isr)\n\t}\n\n\tleader.Close()\n\tseedBroker.Close()\n\tsafeClose(t, client)\n}\n\nfunc TestClientMetadataWithOfflineReplicas(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tleader := NewMockBroker(t, 5)\n\n\treplicas := []int32{1, 2, 3}\n\tisr := []int32{1, 2}\n\tofflineReplicas := []int32{3}\n\n\tmetadataResponse := new(MetadataResponse)\n\tmetadataResponse.AddBroker(leader.Addr(), leader.BrokerID())\n\tmetadataResponse.AddTopicPartition(\"my_topic\", 0, leader.BrokerID(), replicas, isr, offlineReplicas, ErrNoError)\n\tmetadataResponse.AddTopicPartition(\"my_topic\", 1, leader.BrokerID(), replicas, isr, []int32{}, ErrNoError)\n\tmetadataResponse.Version = 5\n\n\tseedBroker.Returns(metadataResponse)\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V1_0_0_0\n\tconfig.Metadata.Retry.Max = 0\n\tclient, err := NewClient([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttopics, err := client.Topics()\n\tif err != nil {\n\t\tt.Error(err)\n\t} else if len(topics) != 1 || topics[0] != \"my_topic\" {\n\t\tt.Error(\"Client returned incorrect topics:\", topics)\n\t}\n\n\tparts, err := client.Partitions(\"my_topic\")\n\tif err != nil {\n\t\tt.Error(err)\n\t} else if len(parts) != 2 || parts[0] != 0 || parts[1] != 1 {\n\t\tt.Error(\"Client returned incorrect partitions for my_topic:\", parts)\n\t}\n\n\tparts, err = client.WritablePartitions(\"my_topic\")\n\tif err != nil {\n\t\tt.Error(err)\n\t} else if len(parts) != 2 {\n\t\tt.Error(\"Client returned incorrect writable partitions for my_topic:\", parts)\n\t}\n\n\ttst, err := client.Leader(\"my_topic\", 0)\n\tif err != nil {\n\t\tt.Error(err)\n\t} else if tst.ID() != 5 {\n\t\tt.Error(\"Leader for my_topic had incorrect ID.\")\n\t}\n\n\treplicas, err = client.Replicas(\"my_topic\", 0)\n\tif err != nil {\n\t\tt.Error(err)\n\t} else if replicas[0] != 1 {\n\t\tt.Error(\"Incorrect (or sorted) replica\")\n\t} else if replicas[1] != 2 {\n\t\tt.Error(\"Incorrect (or sorted) replica\")\n\t} else if replicas[2] != 3 {\n\t\tt.Error(\"Incorrect (or sorted) replica\")\n\t}\n\n\tisr, err = client.InSyncReplicas(\"my_topic\", 0)\n\tif err != nil {\n\t\tt.Error(err)\n\t} else if len(isr) != 2 {\n\t\tt.Error(\"Client returned incorrect ISRs for partition:\", isr)\n\t} else if isr[0] != 1 {\n\t\tt.Error(\"Incorrect (or sorted) ISR:\", isr)\n\t} else if isr[1] != 2 {\n\t\tt.Error(\"Incorrect (or sorted) ISR:\", isr)\n\t}\n\n\tofflineReplicas, err = client.OfflineReplicas(\"my_topic\", 0)\n\tif err != nil {\n\t\tt.Error(err)\n\t} else if len(offlineReplicas) != 1 {\n\t\tt.Error(\"Client returned incorrect offline replicas for partition:\", offlineReplicas)\n\t} else if offlineReplicas[0] != 3 {\n\t\tt.Error(\"Incorrect offline replica:\", offlineReplicas)\n\t}\n\n\tleader.Close()\n\tseedBroker.Close()\n\tsafeClose(t, client)\n}\n\nfunc TestClientGetOffset(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tleader := NewMockBroker(t, 2)\n\tleaderAddr := leader.Addr()\n\n\tmetadata := new(MetadataResponse)\n\tmetadata.AddTopicPartition(\"foo\", 0, leader.BrokerID(), nil, nil, nil, ErrNoError)\n\tmetadata.AddBroker(leaderAddr, leader.BrokerID())\n\tseedBroker.Returns(metadata)\n\n\tclient, err := NewClient([]string{seedBroker.Addr()}, NewTestConfig())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\toffsetResponse := new(OffsetResponse)\n\toffsetResponse.AddTopicPartition(\"foo\", 0, 123)\n\tleader.Returns(offsetResponse)\n\n\toffset, err := client.GetOffset(\"foo\", 0, OffsetNewest)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tif offset != 123 {\n\t\tt.Error(\"Unexpected offset, got \", offset)\n\t}\n\n\tleader.Close()\n\n\tleader = NewMockBrokerAddr(t, 2, leaderAddr)\n\toffsetResponse = new(OffsetResponse)\n\toffsetResponse.AddTopicPartition(\"foo\", 0, 456)\n\tleader.Returns(metadata)\n\tleader.Returns(offsetResponse)\n\n\toffset, err = client.GetOffset(\"foo\", 0, OffsetNewest)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tif offset != 456 {\n\t\tt.Error(\"Unexpected offset, got \", offset)\n\t}\n\n\tseedBroker.Close()\n\tleader.Close()\n\tsafeClose(t, client)\n}\n\nfunc TestClientReceivingUnknownTopicWithBackoffFunc(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\n\tmetadataResponse1 := new(MetadataResponse)\n\tmetadataResponse1.AddBroker(seedBroker.Addr(), seedBroker.BrokerID())\n\tseedBroker.Returns(metadataResponse1)\n\n\tvar retryCount atomic.Int32\n\n\tconfig := NewTestConfig()\n\tconfig.Metadata.Retry.Max = 1\n\tconfig.Metadata.Retry.BackoffFunc = func(retries, maxRetries int) time.Duration {\n\t\tretryCount.Add(1)\n\t\treturn 0\n\t}\n\tclient, err := NewClient([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tmetadataUnknownTopic := new(MetadataResponse)\n\tmetadataUnknownTopic.AddTopic(\"new_topic\", ErrUnknownTopicOrPartition)\n\tmetadataUnknownTopic.AddBroker(seedBroker.Addr(), seedBroker.BrokerID())\n\tseedBroker.Returns(metadataUnknownTopic)\n\tseedBroker.Returns(metadataUnknownTopic)\n\n\tif err := client.RefreshMetadata(\"new_topic\"); !errors.Is(err, ErrUnknownTopicOrPartition) {\n\t\tt.Error(\"ErrUnknownTopicOrPartition expected, got\", err)\n\t}\n\n\tsafeClose(t, client)\n\tseedBroker.Close()\n\n\tactualRetryCount := retryCount.Load()\n\tif actualRetryCount != 1 {\n\t\tt.Fatalf(\"Expected BackoffFunc to be called exactly once, but saw %d\", actualRetryCount)\n\t}\n}\n\nfunc TestClientReceivingUnknownTopic(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\n\tmetadataResponse1 := new(MetadataResponse)\n\tmetadataResponse1.AddBroker(seedBroker.Addr(), seedBroker.BrokerID())\n\tseedBroker.Returns(metadataResponse1)\n\n\tconfig := NewTestConfig()\n\tconfig.Metadata.Retry.Max = 1\n\tconfig.Metadata.Retry.Backoff = 0\n\tclient, err := NewClient([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tmetadataUnknownTopic := new(MetadataResponse)\n\tmetadataUnknownTopic.AddBroker(seedBroker.Addr(), seedBroker.BrokerID())\n\tmetadataUnknownTopic.AddTopic(\"new_topic\", ErrUnknownTopicOrPartition)\n\tseedBroker.Returns(metadataUnknownTopic)\n\tseedBroker.Returns(metadataUnknownTopic)\n\n\tif err := client.RefreshMetadata(\"new_topic\"); !errors.Is(err, ErrUnknownTopicOrPartition) {\n\t\tt.Error(\"ErrUnknownTopicOrPartition expected, got\", err)\n\t}\n\n\t// If we are asking for the leader of a partition of the non-existing topic.\n\t// we will request metadata again.\n\tseedBroker.Returns(metadataUnknownTopic)\n\tseedBroker.Returns(metadataUnknownTopic)\n\n\tif _, err = client.Leader(\"new_topic\", 1); !errors.Is(err, ErrUnknownTopicOrPartition) {\n\t\tt.Error(\"Expected ErrUnknownTopicOrPartition, got\", err)\n\t}\n\n\tsafeClose(t, client)\n\tseedBroker.Close()\n}\n\nfunc TestClientReceivingPartialMetadata(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tleader := NewMockBroker(t, 5)\n\n\tmetadataResponse1 := new(MetadataResponse)\n\tmetadataResponse1.AddBroker(leader.Addr(), leader.BrokerID())\n\tseedBroker.Returns(metadataResponse1)\n\n\tconfig := NewTestConfig()\n\tconfig.Metadata.Retry.Max = 0\n\tclient, err := NewClient([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treplicas := []int32{leader.BrokerID(), seedBroker.BrokerID()}\n\n\tmetadataPartial := new(MetadataResponse)\n\tmetadataPartial.AddBroker(leader.Addr(), 5)\n\tmetadataPartial.AddTopic(\"new_topic\", ErrLeaderNotAvailable)\n\tmetadataPartial.AddTopicPartition(\"new_topic\", 0, leader.BrokerID(), replicas, replicas, []int32{}, ErrNoError)\n\tmetadataPartial.AddTopicPartition(\"new_topic\", 1, -1, replicas, []int32{}, []int32{}, ErrLeaderNotAvailable)\n\tleader.Returns(metadataPartial)\n\n\tif err := client.RefreshMetadata(\"new_topic\"); err != nil {\n\t\tt.Error(\"ErrLeaderNotAvailable should not make RefreshMetadata respond with an error\")\n\t}\n\n\t// Even though the metadata was incomplete, we should be able to get the leader of a partition\n\t// for which we did get a useful response, without doing additional requests.\n\n\tpartition0Leader, err := client.Leader(\"new_topic\", 0)\n\tif err != nil {\n\t\tt.Error(err)\n\t} else if partition0Leader.Addr() != leader.Addr() {\n\t\tt.Error(\"Unexpected leader returned\", partition0Leader.Addr())\n\t}\n\n\t// If we are asking for the leader of a partition that didn't have a leader before,\n\t// we will do another metadata request.\n\n\tleader.Returns(metadataPartial)\n\n\t// Still no leader for the partition, so asking for it should return an error.\n\t_, err = client.Leader(\"new_topic\", 1)\n\tif !errors.Is(err, ErrLeaderNotAvailable) {\n\t\tt.Error(\"Expected ErrLeaderNotAvailable, got\", err)\n\t}\n\n\tsafeClose(t, client)\n\tseedBroker.Close()\n\tleader.Close()\n}\n\nfunc TestClientRefreshBehaviourWhenEmptyMetadataResponse(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tbroker := NewMockBroker(t, 2)\n\n\tmetadataResponse1 := new(MetadataResponse)\n\tmetadataResponse1.AddBroker(seedBroker.Addr(), seedBroker.BrokerID())\n\tseedBroker.Returns(metadataResponse1)\n\n\tc, err := NewClient([]string{seedBroker.Addr()}, NewTestConfig())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tclient := c.(*client)\n\tif len(client.seedBrokers) != 1 {\n\t\tt.Error(\"incorrect number of live seeds\")\n\t}\n\tif len(client.deadSeeds) != 0 {\n\t\tt.Error(\"incorrect number of dead seeds\")\n\t}\n\tif len(client.brokers) != 1 {\n\t\tt.Error(\"incorrect number of brokers\")\n\t}\n\n\t// Empty metadata response\n\tseedBroker.Returns(new(MetadataResponse))\n\tmetadataResponse2 := new(MetadataResponse)\n\tmetadataResponse2.AddBroker(seedBroker.Addr(), seedBroker.BrokerID())\n\tmetadataResponse2.AddBroker(broker.Addr(), broker.BrokerID())\n\tseedBroker.Returns(metadataResponse2)\n\terr = c.RefreshMetadata()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif len(client.seedBrokers) != 1 {\n\t\tt.Error(\"incorrect number of live seeds\")\n\t}\n\tif len(client.deadSeeds) != 0 {\n\t\tt.Error(\"incorrect number of dead seeds\")\n\t}\n\tif len(client.brokers) != 2 {\n\t\tt.Error(\"incorrect number of brokers\")\n\t}\n\tbroker.Close()\n\tseedBroker.Close()\n\tsafeClose(t, client)\n}\n\nfunc TestClientRefreshBehaviour(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tleader := NewMockBroker(t, 5)\n\n\tmetadataResponse1 := new(MetadataResponse)\n\tmetadataResponse1.AddBroker(leader.Addr(), leader.BrokerID())\n\tseedBroker.Returns(metadataResponse1)\n\n\tmetadataResponse2 := new(MetadataResponse)\n\tmetadataResponse2.AddBroker(leader.Addr(), leader.BrokerID())\n\tmetadataResponse2.AddTopicPartition(\"my_topic\", 0xb, leader.BrokerID(), nil, nil, nil, ErrNoError)\n\tleader.Returns(metadataResponse2)\n\n\tclient, err := NewClient([]string{seedBroker.Addr()}, NewTestConfig())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tparts, err := client.Partitions(\"my_topic\")\n\tif err != nil {\n\t\tt.Error(err)\n\t} else if len(parts) != 1 || parts[0] != 0xb {\n\t\tt.Error(\"Client returned incorrect partitions for my_topic:\", parts)\n\t}\n\n\ttst, err := client.Leader(\"my_topic\", 0xb)\n\tif err != nil {\n\t\tt.Error(err)\n\t} else if tst.ID() != 5 {\n\t\tt.Error(\"Leader for my_topic had incorrect ID.\")\n\t}\n\n\tleader.Close()\n\tseedBroker.Close()\n\tsafeClose(t, client)\n}\n\nfunc TestClientRefreshBrokers(t *testing.T) {\n\tinitialSeed := NewMockBroker(t, 0)\n\tdefer initialSeed.Close()\n\tleader := NewMockBroker(t, 5)\n\tdefer leader.Close()\n\n\tmetadataResponse1 := new(MetadataResponse)\n\tmetadataResponse1.AddBroker(leader.Addr(), leader.BrokerID())\n\tmetadataResponse1.AddBroker(initialSeed.Addr(), initialSeed.BrokerID())\n\tinitialSeed.Returns(metadataResponse1)\n\n\tc, err := NewClient([]string{initialSeed.Addr()}, NewTestConfig())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer c.Close()\n\tclient := c.(*client)\n\n\tif len(client.Brokers()) != 2 {\n\t\tt.Error(\"Meta broker is not 2\")\n\t}\n\n\tnewSeedBrokers := []string{\"localhost:12345\"}\n\t_ = client.RefreshBrokers(newSeedBrokers)\n\n\tif len(client.seedBrokers) == 0 || client.seedBrokers[0].addr != newSeedBrokers[0] {\n\t\tt.Error(\"Seed broker not updated\")\n\t}\n\tif len(client.Brokers()) != 0 {\n\t\tt.Error(\"Old brokers not closed\")\n\t}\n}\n\nfunc TestClientRefreshMetadataBrokerOffline(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\tleader := NewMockBroker(t, 5)\n\tdefer leader.Close()\n\n\tmetadataResponse1 := new(MetadataResponse)\n\tmetadataResponse1.AddBroker(leader.Addr(), leader.BrokerID())\n\tmetadataResponse1.AddBroker(seedBroker.Addr(), seedBroker.BrokerID())\n\tseedBroker.Returns(metadataResponse1)\n\n\tclient, err := NewClient([]string{seedBroker.Addr()}, NewTestConfig())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer client.Close()\n\n\tif len(client.Brokers()) != 2 {\n\t\tt.Error(\"Meta broker is not 2\")\n\t}\n\n\tmetadataResponse2 := NewMockMetadataResponse(t).SetBroker(leader.Addr(), leader.BrokerID())\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": metadataResponse2,\n\t})\n\tleader.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": metadataResponse2,\n\t})\n\n\tif err := client.RefreshMetadata(); err != nil {\n\t\tt.Error(err)\n\t}\n\tif len(client.Brokers()) != 1 {\n\t\tt.Error(\"Meta broker is not 1\")\n\t}\n}\n\nfunc TestClientGetBroker(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\tleader := NewMockBroker(t, 5)\n\tdefer leader.Close()\n\n\tmetadataResponse1 := new(MetadataResponse)\n\tmetadataResponse1.AddBroker(leader.Addr(), leader.BrokerID())\n\tmetadataResponse1.AddBroker(seedBroker.Addr(), seedBroker.BrokerID())\n\tseedBroker.Returns(metadataResponse1)\n\n\tclient, err := NewClient([]string{seedBroker.Addr()}, NewTestConfig())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer client.Close()\n\n\tbroker, err := client.Broker(leader.BrokerID())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif broker.Addr() != leader.Addr() {\n\t\tt.Errorf(\"Expected broker to have address %s, found %s\", leader.Addr(), broker.Addr())\n\t}\n\n\tmetadataResponse2 := NewMockMetadataResponse(t).SetBroker(seedBroker.Addr(), seedBroker.BrokerID())\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": metadataResponse2,\n\t})\n\tleader.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": metadataResponse2,\n\t})\n\n\tif err := client.RefreshMetadata(); err != nil {\n\t\tt.Error(err)\n\t}\n\t_, err = client.Broker(leader.BrokerID())\n\tif !errors.Is(err, ErrBrokerNotFound) {\n\t\tt.Errorf(\"Expected Broker(brokerID) to return %v found %v\", ErrBrokerNotFound, err)\n\t}\n}\n\nfunc TestClientResurrectDeadSeeds(t *testing.T) {\n\tinitialSeed := NewMockBroker(t, 0)\n\tmetadataResponse := new(MetadataResponse)\n\tmetadataResponse.AddBroker(initialSeed.Addr(), initialSeed.BrokerID())\n\tinitialSeed.Returns(metadataResponse)\n\n\tconf := NewTestConfig()\n\tconf.Metadata.Retry.Backoff = 0\n\tconf.Metadata.RefreshFrequency = 0\n\tc, err := NewClient([]string{initialSeed.Addr()}, conf)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tclient := c.(*client)\n\n\tseed1 := NewMockBroker(t, 1)\n\tseed2 := NewMockBroker(t, 2)\n\tseed3 := NewMockBroker(t, 3)\n\taddr1 := seed1.Addr()\n\taddr2 := seed2.Addr()\n\taddr3 := seed3.Addr()\n\n\t// Overwrite the seed brokers with a fixed ordering to make this test deterministic.\n\tsafeClose(t, client.seedBrokers[0])\n\tclient.seedBrokers = []*Broker{NewBroker(addr1), NewBroker(addr2), NewBroker(addr3)}\n\tclient.deadSeeds = []*Broker{}\n\tclient.brokers = map[int32]*Broker{}\n\n\twg := sync.WaitGroup{}\n\twg.Add(1)\n\tgo func() {\n\t\tif err := client.RefreshMetadata(); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t\twg.Done()\n\t}()\n\tseed1.Close()\n\tseed2.Close()\n\n\tseed1 = NewMockBrokerAddr(t, 1, addr1)\n\tseed2 = NewMockBrokerAddr(t, 2, addr2)\n\n\tseed3.Close()\n\n\tseed1.Close()\n\tmetadataResponse2 := new(MetadataResponse)\n\tmetadataResponse2.AddBroker(seed2.Addr(), seed2.BrokerID())\n\tseed2.Returns(metadataResponse2)\n\n\twg.Wait()\n\n\tif len(client.seedBrokers) != 2 {\n\t\tt.Error(\"incorrect number of live seeds\")\n\t}\n\tif len(client.deadSeeds) != 1 {\n\t\tt.Error(\"incorrect number of dead seeds\")\n\t}\n\n\tseed2.Close()\n\tsafeClose(t, c)\n}\n\n//nolint:paralleltest\nfunc TestClientController(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\tcontrollerBroker := NewMockBroker(t, 2)\n\tdefer controllerBroker.Close()\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(controllerBroker.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()).\n\t\t\tSetBroker(controllerBroker.Addr(), controllerBroker.BrokerID()),\n\t})\n\n\tcfg := NewTestConfig()\n\n\t// test kafka version greater than 0.10.0.0\n\tt.Run(\"V0_10_0_0\", func(t *testing.T) {\n\t\tcfg.Version = V0_10_0_0\n\t\tclient1, err := NewClient([]string{seedBroker.Addr()}, cfg)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer safeClose(t, client1)\n\t\tbroker, err := client1.Controller()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif broker.Addr() != controllerBroker.Addr() {\n\t\t\tt.Errorf(\"Expected controller to have address %s, found %s\", controllerBroker.Addr(), broker.Addr())\n\t\t}\n\t})\n\n\t// test kafka version earlier than 0.10.0.0\n\tt.Run(\"V0_9_0_1\", func(t *testing.T) {\n\t\tcfg.Version = V0_9_0_1\n\t\tclient2, err := NewClient([]string{seedBroker.Addr()}, cfg)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer safeClose(t, client2)\n\t\tif _, err = client2.Controller(); !errors.Is(err, ErrUnsupportedVersion) {\n\t\t\tt.Errorf(\"Expected Controller() to return %s, found %s\", ErrUnsupportedVersion, err)\n\t\t}\n\t})\n}\n\nfunc TestClientMetadataTimeout(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\ttimeout time.Duration\n\t}{\n\t\t{\n\t\t\t\"timeout=250ms\",\n\t\t\t250 * time.Millisecond, // Will cut the first retry pass\n\t\t},\n\t\t{\n\t\t\t\"timeout=500ms\",\n\t\t\t500 * time.Millisecond, // Will cut the second retry pass\n\t\t},\n\t\t{\n\t\t\t\"timeout=750ms\",\n\t\t\t750 * time.Millisecond, // Will cut the third retry pass\n\t\t},\n\t\t{\n\t\t\t\"timeout=900ms\",\n\t\t\t900 * time.Millisecond, // Will stop after the three retries\n\t\t},\n\t}\n\n\tfor _, singleFlight := range []bool{true, false} {\n\t\tfor _, tc := range tests {\n\t\t\tt.Run(fmt.Sprintf(\"%s_singleflight_is_%t\", tc.name, singleFlight), func(t *testing.T) {\n\t\t\t\t// Use a responsive broker to create a working client\n\t\t\t\tinitialSeed := NewMockBroker(t, 0)\n\t\t\t\temptyMetadata := new(MetadataResponse)\n\t\t\t\temptyMetadata.AddBroker(initialSeed.Addr(), initialSeed.BrokerID())\n\t\t\t\tinitialSeed.Returns(emptyMetadata)\n\n\t\t\t\tconf := NewTestConfig()\n\t\t\t\t// Speed up the metadata request failure because of a read timeout\n\t\t\t\tconf.Net.ReadTimeout = 100 * time.Millisecond\n\t\t\t\t// Disable backoff and refresh\n\t\t\t\tconf.Metadata.Retry.Backoff = 0\n\t\t\t\tconf.Metadata.RefreshFrequency = 0\n\t\t\t\t// But configure a \"global\" timeout\n\t\t\t\tconf.Metadata.Timeout = tc.timeout\n\t\t\t\tconf.Metadata.SingleFlight = singleFlight\n\t\t\t\tc, err := NewClient([]string{initialSeed.Addr()}, conf)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t\tinitialSeed.Close()\n\n\t\t\t\tclient := c.(*client)\n\n\t\t\t\t// Start seed brokers that do not reply to anything and therefore a read\n\t\t\t\t// on the TCP connection will timeout to simulate unresponsive brokers\n\t\t\t\tseed1 := NewMockBroker(t, 1)\n\t\t\t\tdefer seed1.Close()\n\t\t\t\tseed2 := NewMockBroker(t, 2)\n\t\t\t\tdefer seed2.Close()\n\n\t\t\t\t// Overwrite the seed brokers with a fixed ordering to make this test deterministic\n\t\t\t\tsafeClose(t, client.seedBrokers[0])\n\t\t\t\tclient.seedBrokers = []*Broker{NewBroker(seed1.Addr()), NewBroker(seed2.Addr())}\n\t\t\t\tclient.deadSeeds = []*Broker{}\n\n\t\t\t\t// Start refreshing metadata in the background\n\t\t\t\terrChan := make(chan error)\n\t\t\t\tgo func() {\n\t\t\t\t\terrChan <- c.RefreshMetadata()\n\t\t\t\t}()\n\n\t\t\t\t// Check that the refresh fails fast enough (less than twice the configured timeout)\n\t\t\t\t// instead of at least: 100 ms * 2 brokers * 3 retries = 800 ms\n\t\t\t\tmaxRefreshDuration := 2 * tc.timeout\n\t\t\t\tselect {\n\t\t\t\tcase err := <-errChan:\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\tt.Fatal(\"Expected failed RefreshMetadata, got nil\")\n\t\t\t\t\t}\n\t\t\t\t\tif !errors.Is(err, ErrOutOfBrokers) {\n\t\t\t\t\t\tt.Error(\"Expected failed RefreshMetadata with ErrOutOfBrokers, got:\", err)\n\t\t\t\t\t}\n\t\t\t\tcase <-time.After(maxRefreshDuration):\n\t\t\t\t\tt.Fatalf(\"RefreshMetadata did not fail fast enough after waiting for %v\", maxRefreshDuration)\n\t\t\t\t}\n\n\t\t\t\tsafeClose(t, c)\n\t\t\t})\n\t\t}\n\t}\n}\n\nfunc TestClientUpdateMetadataErrorAndRetry(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tvar called atomic.Int32\n\n\tseedBroker.setHandler(func(req *request) (res encoderWithHeader) {\n\t\tif req.body.key() != 3 {\n\t\t\tt.Error(\"this test sends only Metadata requests\")\n\t\t\treturn\n\t\t}\n\t\tresp := new(MetadataResponse)\n\t\tfor _, topic := range req.body.(*MetadataRequest).Topics {\n\t\t\tif topic == \"new_topic\" {\n\t\t\t\tresp.Topics = append(resp.Topics, &TopicMetadata{\n\t\t\t\t\tVersion: 1,\n\t\t\t\t\tName:    \"new_topic\",\n\t\t\t\t\tErr:     ErrUnknownTopicOrPartition,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t\tcalled.Add(1)\n\t\tresp.AddBroker(seedBroker.Addr(), 1)\n\t\treturn resp\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Metadata.Retry.Max = 3\n\tconfig.Metadata.Retry.Backoff = 200 * time.Millisecond\n\tconfig.Metadata.RefreshFrequency = 0\n\tconfig.Net.ReadTimeout = 10 * time.Millisecond\n\tconfig.Net.WriteTimeout = 10 * time.Millisecond\n\tconfig.Metadata.SingleFlight = true\n\tclient, err := NewClient([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\twaitGroup := sync.WaitGroup{}\n\twaitGroup.Add(10)\n\tfor i := 0; i < 10; i++ {\n\t\tgo func() {\n\t\t\tdefer waitGroup.Done()\n\t\t\terr := client.RefreshMetadata(\"new_topic\")\n\t\t\tif err == nil {\n\t\t\t\tt.Error(\"should return error\")\n\t\t\t\treturn\n\t\t\t}\n\t\t}()\n\t}\n\twaitGroup.Wait()\n\tsafeClose(t, client)\n\tseedBroker.Close()\n\t// The refresh metadata is always for the same topic,\n\t// it should have been batched.\n\tif count := called.Load(); count >= 7 {\n\t\tt.Errorf(\"Refresh metadata was called %d times, this should be less than 7.\", count)\n\t}\n}\n\nfunc TestClientRefreshesMetadataConcurrently(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\n\tseedBroker.setHandler(func(req *request) (res encoderWithHeader) {\n\t\ttime.Sleep(10 * time.Millisecond)\n\t\tif req.body.key() != 3 {\n\t\t\tt.Error(\"this test sends only Metadata requests\")\n\t\t\treturn\n\t\t}\n\t\ttopics := req.body.(*MetadataRequest).Topics\n\t\tresp := new(MetadataResponse)\n\t\tfor _, topic := range topics {\n\t\t\tswitch topic {\n\t\t\tcase \"topic1\":\n\t\t\t\tresp.Topics = append(resp.Topics, &TopicMetadata{\n\t\t\t\t\tVersion:    1,\n\t\t\t\t\tName:       \"topic1\",\n\t\t\t\t\tPartitions: []*PartitionMetadata{},\n\t\t\t\t})\n\t\t\tcase \"topic2\":\n\t\t\t\tresp.Topics = append(resp.Topics, &TopicMetadata{\n\t\t\t\t\tVersion:    1,\n\t\t\t\t\tName:       \"topic2\",\n\t\t\t\t\tPartitions: []*PartitionMetadata{},\n\t\t\t\t})\n\t\t\tcase \"topic3\":\n\t\t\t\tresp.Topics = append(resp.Topics, &TopicMetadata{\n\t\t\t\t\tVersion: 1,\n\t\t\t\t\tName:    \"topic3\",\n\t\t\t\t\tErr:     ErrUnknownTopicOrPartition,\n\t\t\t\t})\n\t\t\tdefault:\n\t\t\t\tt.Errorf(\"unexpected topic: %s\", topic)\n\t\t\t}\n\t\t}\n\t\tresp.AddBroker(seedBroker.Addr(), 1)\n\t\treturn resp\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Metadata.SingleFlight = true\n\tclient, err := NewClient([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tvar waitGroup sync.WaitGroup\n\twaitGroup.Add(1000)\n\tfor i := 0; i < 1000; i++ {\n\t\tgo func() {\n\t\t\tdefer waitGroup.Done()\n\t\t\tassert.NoError(t, client.RefreshMetadata(\"topic1\"))\n\t\t\tassert.NoError(t, client.RefreshMetadata(\"topic2\"))\n\t\t\tassert.Error(t, client.RefreshMetadata(\"topic3\"))\n\t\t\ttopics, err := client.Topics()\n\t\t\tassert.NoError(t, err)\n\t\t\tassert.Len(t, topics, 2)\n\t\t}()\n\t}\n\twaitGroup.Wait()\n\tsafeClose(t, client)\n\tseedBroker.Close()\n}\n\nfunc TestClientCoordinatorWithConsumerOffsetsTopic(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tcoordinator := NewMockBroker(t, 2)\n\n\treplicas := []int32{coordinator.BrokerID()}\n\tmetadataResponse1 := new(MetadataResponse)\n\tmetadataResponse1.AddBroker(coordinator.Addr(), coordinator.BrokerID())\n\tmetadataResponse1.AddTopicPartition(\"__consumer_offsets\", 0, replicas[0], replicas, replicas, []int32{}, ErrNoError)\n\tseedBroker.Returns(metadataResponse1)\n\n\tclient, err := NewClient([]string{seedBroker.Addr()}, NewTestConfig())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcoordinatorResponse1 := new(ConsumerMetadataResponse)\n\tcoordinatorResponse1.Err = ErrConsumerCoordinatorNotAvailable\n\tcoordinator.Returns(coordinatorResponse1)\n\n\tcoordinatorResponse2 := new(ConsumerMetadataResponse)\n\tcoordinatorResponse2.CoordinatorID = coordinator.BrokerID()\n\tcoordinatorResponse2.CoordinatorHost = \"127.0.0.1\"\n\tcoordinatorResponse2.CoordinatorPort = coordinator.Port()\n\n\tcoordinator.Returns(coordinatorResponse2)\n\n\tbroker, err := client.Coordinator(\"my_group\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif coordinator.Addr() != broker.Addr() {\n\t\tt.Errorf(\"Expected coordinator to have address %s, found %s\", coordinator.Addr(), broker.Addr())\n\t}\n\n\tif coordinator.BrokerID() != broker.ID() {\n\t\tt.Errorf(\"Expected coordinator to have ID %d, found %d\", coordinator.BrokerID(), broker.ID())\n\t}\n\n\t// Grab the cached value\n\tbroker2, err := client.Coordinator(\"my_group\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif broker2.Addr() != broker.Addr() {\n\t\tt.Errorf(\"Expected the coordinator to be the same, but found %s vs. %s\", broker2.Addr(), broker.Addr())\n\t}\n\n\tcoordinator.Close()\n\tseedBroker.Close()\n\tsafeClose(t, client)\n}\n\nfunc TestClientCoordinatorChangeWithConsumerOffsetsTopic(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tstaleCoordinator := NewMockBroker(t, 2)\n\tfreshCoordinator := NewMockBroker(t, 3)\n\n\treplicas := []int32{staleCoordinator.BrokerID(), freshCoordinator.BrokerID()}\n\tmetadataResponse1 := new(MetadataResponse)\n\tmetadataResponse1.AddBroker(staleCoordinator.Addr(), staleCoordinator.BrokerID())\n\tmetadataResponse1.AddBroker(freshCoordinator.Addr(), freshCoordinator.BrokerID())\n\tmetadataResponse1.AddTopicPartition(\"__consumer_offsets\", 0, replicas[0], replicas, replicas, []int32{}, ErrNoError)\n\tseedBroker.Returns(metadataResponse1)\n\n\tclient, err := NewClient([]string{seedBroker.Addr()}, NewTestConfig())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfindCoordinatorResponse := NewMockFindCoordinatorResponse(t).SetCoordinator(CoordinatorGroup, \"my_group\", staleCoordinator)\n\tstaleCoordinator.SetHandlerByMap(map[string]MockResponse{\n\t\t\"FindCoordinatorRequest\": findCoordinatorResponse,\n\t})\n\tfreshCoordinator.SetHandlerByMap(map[string]MockResponse{\n\t\t\"FindCoordinatorRequest\": findCoordinatorResponse,\n\t})\n\tbroker, err := client.Coordinator(\"my_group\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif staleCoordinator.Addr() != broker.Addr() {\n\t\tt.Errorf(\"Expected coordinator to have address %s, found %s\", staleCoordinator.Addr(), broker.Addr())\n\t}\n\n\tif staleCoordinator.BrokerID() != broker.ID() {\n\t\tt.Errorf(\"Expected coordinator to have ID %d, found %d\", staleCoordinator.BrokerID(), broker.ID())\n\t}\n\n\t// Grab the cached value\n\tbroker2, err := client.Coordinator(\"my_group\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif broker2.Addr() != broker.Addr() {\n\t\tt.Errorf(\"Expected the coordinator to be the same, but found %s vs. %s\", broker2.Addr(), broker.Addr())\n\t}\n\n\tfindCoordinatorResponse2 := NewMockFindCoordinatorResponse(t).SetCoordinator(CoordinatorGroup, \"my_group\", freshCoordinator)\n\tstaleCoordinator.SetHandlerByMap(map[string]MockResponse{\n\t\t\"FindCoordinatorRequest\": findCoordinatorResponse2,\n\t})\n\tfreshCoordinator.SetHandlerByMap(map[string]MockResponse{\n\t\t\"FindCoordinatorRequest\": findCoordinatorResponse2,\n\t})\n\n\t// Refresh the locally cached value because it's stale\n\tif err := client.RefreshCoordinator(\"my_group\"); err != nil {\n\t\tt.Error(err)\n\t}\n\n\t// Grab the fresh value\n\tbroker3, err := client.Coordinator(\"my_group\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif broker3.Addr() != freshCoordinator.Addr() {\n\t\tt.Errorf(\"Expected the freshCoordinator to be returned, but found %s.\", broker3.Addr())\n\t}\n\n\tfreshCoordinator.Close()\n\tstaleCoordinator.Close()\n\tseedBroker.Close()\n\tsafeClose(t, client)\n}\n\nfunc TestClientCoordinatorWithoutConsumerOffsetsTopic(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tcoordinator := NewMockBroker(t, 2)\n\n\tmetadataResponse1 := new(MetadataResponse)\n\tmetadataResponse1.AddBroker(seedBroker.Addr(), seedBroker.BrokerID())\n\tseedBroker.Returns(metadataResponse1)\n\n\tconfig := NewTestConfig()\n\tconfig.Metadata.Retry.Max = 1\n\tconfig.Metadata.Retry.Backoff = 0\n\tclient, err := NewClient([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcoordinatorResponse1 := new(ConsumerMetadataResponse)\n\tcoordinatorResponse1.Err = ErrConsumerCoordinatorNotAvailable\n\tseedBroker.Returns(coordinatorResponse1)\n\n\tmetadataResponse2 := new(MetadataResponse)\n\tmetadataResponse2.AddBroker(seedBroker.Addr(), seedBroker.BrokerID())\n\tmetadataResponse2.AddTopic(\"__consumer_offsets\", ErrUnknownTopicOrPartition)\n\tseedBroker.Returns(metadataResponse2)\n\n\treplicas := []int32{coordinator.BrokerID()}\n\tmetadataResponse3 := new(MetadataResponse)\n\tmetadataResponse3.AddBroker(seedBroker.Addr(), seedBroker.BrokerID())\n\tmetadataResponse3.AddTopicPartition(\"__consumer_offsets\", 0, replicas[0], replicas, replicas, []int32{}, ErrNoError)\n\tseedBroker.Returns(metadataResponse3)\n\n\tcoordinatorResponse2 := new(ConsumerMetadataResponse)\n\tcoordinatorResponse2.CoordinatorID = coordinator.BrokerID()\n\tcoordinatorResponse2.CoordinatorHost = \"127.0.0.1\"\n\tcoordinatorResponse2.CoordinatorPort = coordinator.Port()\n\n\tseedBroker.Returns(coordinatorResponse2)\n\n\tbroker, err := client.Coordinator(\"my_group\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif coordinator.Addr() != broker.Addr() {\n\t\tt.Errorf(\"Expected coordinator to have address %s, found %s\", coordinator.Addr(), broker.Addr())\n\t}\n\n\tif coordinator.BrokerID() != broker.ID() {\n\t\tt.Errorf(\"Expected coordinator to have ID %d, found %d\", coordinator.BrokerID(), broker.ID())\n\t}\n\n\tcoordinator.Close()\n\tseedBroker.Close()\n\tsafeClose(t, client)\n}\n\nfunc TestClientAutorefreshShutdownRace(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\tmetadataResponse := new(MetadataResponse)\n\tmetadataResponse.AddBroker(seedBroker.Addr(), seedBroker.BrokerID())\n\tseedBroker.Returns(metadataResponse)\n\n\tconf := NewTestConfig()\n\tconf.Metadata.RefreshFrequency = 100 * time.Millisecond\n\tclient, err := NewClient([]string{seedBroker.Addr()}, conf)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Wait for the background refresh to kick in\n\ttime.Sleep(110 * time.Millisecond)\n\n\terrCh := make(chan error, 1)\n\tgo func() {\n\t\t// Close the client\n\t\terrCh <- client.Close()\n\t\tclose(errCh)\n\t}()\n\n\t// Wait for the Close to kick in\n\ttime.Sleep(10 * time.Millisecond)\n\n\t// Then return some metadata to the still-running background thread\n\tleader := NewMockBroker(t, 2)\n\tdefer leader.Close()\n\tmetadataResponse.AddBroker(leader.Addr(), leader.BrokerID())\n\tmetadataResponse.AddTopicPartition(\"foo\", 0, leader.BrokerID(), []int32{2}, []int32{2}, []int32{}, ErrNoError)\n\tseedBroker.Returns(metadataResponse)\n\n\terr = <-errCh\n\tif err != nil {\n\t\tt.Fatalf(\"goroutine client.Close():%s\", err)\n\t}\n\n\t// give the update time to happen so we get a panic if it's still running (which it shouldn't)\n\ttime.Sleep(10 * time.Millisecond)\n}\n\nfunc TestClientConnectionRefused(t *testing.T) {\n\tt.Parallel()\n\tseedBroker := NewMockBroker(t, 1)\n\tseedBroker.Close()\n\n\t_, err := NewClient([]string{seedBroker.Addr()}, NewTestConfig())\n\tif !errors.Is(err, ErrOutOfBrokers) {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tif !errors.Is(err, syscall.ECONNREFUSED) {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n}\n\nfunc TestClientCoordinatorConnectionRefused(t *testing.T) {\n\tt.Parallel()\n\tseedBroker := NewMockBroker(t, 1)\n\tmetadataResponse := new(MetadataResponse)\n\tmetadataResponse.AddBroker(seedBroker.Addr(), seedBroker.BrokerID())\n\tseedBroker.Returns(metadataResponse)\n\n\tclient, err := NewClient([]string{seedBroker.Addr()}, NewTestConfig())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tseedBroker.Close()\n\n\t_, err = client.Coordinator(\"my_group\")\n\n\tif !errors.Is(err, ErrOutOfBrokers) {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tif !errors.Is(err, syscall.ECONNREFUSED) {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tsafeClose(t, client)\n}\n\nfunc TestInitProducerIDConnectionRefused(t *testing.T) {\n\tt.Parallel()\n\tseedBroker := NewMockBroker(t, 1)\n\tmetadataResponse := new(MetadataResponse)\n\tmetadataResponse.AddBroker(seedBroker.Addr(), seedBroker.BrokerID())\n\tmetadataResponse.Version = 4\n\tseedBroker.Returns(metadataResponse)\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Idempotent = true\n\tconfig.Version = V0_11_0_0\n\tconfig.Producer.RequiredAcks = WaitForAll\n\tconfig.Net.MaxOpenRequests = 1\n\n\tclient, err := NewClient([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tseedBroker.Close()\n\n\t_, err = client.InitProducerID()\n\n\tif !errors.Is(err, ErrOutOfBrokers) {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tif !errors.Is(err, io.EOF) && !errors.Is(err, syscall.ECONNRESET) {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tsafeClose(t, client)\n}\n\nfunc TestMetricsCleanup(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\tmetadataResponse := new(MetadataResponse)\n\tmetadataResponse.AddBroker(seedBroker.Addr(), seedBroker.BrokerID())\n\tseedBroker.Returns(metadataResponse)\n\n\tconfig := NewTestConfig()\n\tmetrics.GetOrRegisterMeter(\"a\", config.MetricRegistry)\n\n\tclient, err := NewClient([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tsafeClose(t, client)\n\n\t// Wait async close\n\ttime.Sleep(10 * time.Millisecond)\n\n\tall := config.MetricRegistry.GetAll()\n\tif len(all) != 1 || all[\"a\"] == nil {\n\t\tt.Errorf(\"excepted 1 metric, found: %v\", all)\n\t}\n}\n\nfunc TestUpdateBroker(t *testing.T) {\n\tt.Run(\"closed client doesn't panic\", func(t *testing.T) {\n\t\tc := &client{}\n\t\tfn := func() {\n\t\t\tc.updateBroker(nil)\n\t\t\tc.updateBroker([]*Broker{\n\t\t\t\t{\n\t\t\t\t\tid:   0,\n\t\t\t\t\taddr: \"127.0.0.1:9092\",\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\t\trequire.NotPanics(t, fn)\n\t})\n\n\tt.Run(\"open client adds new broker entries\", func(t *testing.T) {\n\t\tc := &client{\n\t\t\tbrokers: make(map[int32]*Broker),\n\t\t}\n\t\tfn := func() {\n\t\t\tc.updateBroker([]*Broker{\n\t\t\t\t{\n\t\t\t\t\tid:   0,\n\t\t\t\t\taddr: \"127.0.0.1:9092\",\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\t\trequire.NotPanics(t, fn)\n\t\trequire.Len(t, c.brokers, 1)\n\t\tassert.Equal(t, 0, int(c.brokers[0].ID()))\n\t\tassert.Equal(t, \"127.0.0.1:9092\", c.brokers[0].Addr())\n\t})\n\n\tt.Run(\"open client adds, updates and removes broker entries\", func(t *testing.T) {\n\t\tc := &client{\n\t\t\tbrokers: map[int32]*Broker{\n\t\t\t\t0: {\n\t\t\t\t\tid:   0,\n\t\t\t\t\taddr: \"127.0.0.1:9092\",\n\t\t\t\t},\n\t\t\t\t1: {\n\t\t\t\t\tid:   1,\n\t\t\t\t\taddr: \"127.0.0.1:9093\",\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tfn := func() {\n\t\t\tc.updateBroker([]*Broker{\n\t\t\t\t{\n\t\t\t\t\tid:   1,\n\t\t\t\t\taddr: \"127.0.0.1:19093\", // new addr for existing broker\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tid:   2,\n\t\t\t\t\taddr: \"127.0.0.1:19094\",\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\t\trequire.NotPanics(t, fn)\n\t\trequire.Len(t, c.brokers, 2)\n\t\tassert.Equal(t, 1, int(c.brokers[1].ID()))\n\t\tassert.Equal(t, \"127.0.0.1:19093\", c.brokers[1].Addr())\n\t\tassert.Equal(t, 2, int(c.brokers[2].ID()))\n\t\tassert.Equal(t, \"127.0.0.1:19094\", c.brokers[2].Addr())\n\t})\n}\n"
  },
  {
    "path": "client_tls_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"crypto/x509/pkix\"\n\t\"math/big\"\n\t\"net\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestTLS(t *testing.T) {\n\tcakey, err := rsa.GenerateKey(rand.Reader, 2048)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tclientkey, err := rsa.GenerateKey(rand.Reader, 2048)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\thostkey, err := rsa.GenerateKey(rand.Reader, 2048)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tnvb := time.Now().Add(-1 * time.Hour)\n\tnva := time.Now().Add(1 * time.Hour)\n\n\tcaTemplate := &x509.Certificate{\n\t\tSubject:               pkix.Name{CommonName: \"ca\"},\n\t\tIssuer:                pkix.Name{CommonName: \"ca\"},\n\t\tSerialNumber:          big.NewInt(0),\n\t\tNotAfter:              nva,\n\t\tNotBefore:             nvb,\n\t\tIsCA:                  true,\n\t\tBasicConstraintsValid: true,\n\t\tKeyUsage:              x509.KeyUsageCertSign,\n\t}\n\tcaDer, err := x509.CreateCertificate(rand.Reader, caTemplate, caTemplate, &cakey.PublicKey, cakey)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcaFinalCert, err := x509.ParseCertificate(caDer)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\thostDer, err := x509.CreateCertificate(rand.Reader, &x509.Certificate{\n\t\tSubject:      pkix.Name{CommonName: \"host\"},\n\t\tIssuer:       pkix.Name{CommonName: \"ca\"},\n\t\tIPAddresses:  []net.IP{net.IPv4(127, 0, 0, 1)},\n\t\tSerialNumber: big.NewInt(0),\n\t\tNotAfter:     nva,\n\t\tNotBefore:    nvb,\n\t\tExtKeyUsage:  []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},\n\t}, caFinalCert, &hostkey.PublicKey, cakey)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tclientDer, err := x509.CreateCertificate(rand.Reader, &x509.Certificate{\n\t\tSubject:      pkix.Name{CommonName: \"client\"},\n\t\tIssuer:       pkix.Name{CommonName: \"ca\"},\n\t\tSerialNumber: big.NewInt(0),\n\t\tNotAfter:     nva,\n\t\tNotBefore:    nvb,\n\t\tExtKeyUsage:  []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},\n\t}, caFinalCert, &clientkey.PublicKey, cakey)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tpool := x509.NewCertPool()\n\tpool.AddCert(caFinalCert)\n\n\tsystemCerts, err := x509.SystemCertPool()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Keep server the same - it's the client that we're testing\n\tserverTLSConfig := &tls.Config{\n\t\tCertificates: []tls.Certificate{{\n\t\t\tCertificate: [][]byte{hostDer},\n\t\t\tPrivateKey:  hostkey,\n\t\t}},\n\t\tClientAuth: tls.RequireAndVerifyClientCert,\n\t\tClientCAs:  pool,\n\t\tMinVersion: tls.VersionTLS12,\n\t}\n\n\tfor _, tc := range []struct {\n\t\tname           string\n\t\tSucceed        bool\n\t\tServer, Client *tls.Config\n\t}{\n\t\t{\n\t\t\tname:    \"Verify client fails if wrong CA cert pool is specified\",\n\t\t\tSucceed: false,\n\t\t\tServer:  serverTLSConfig,\n\t\t\tClient: &tls.Config{\n\t\t\t\tRootCAs: systemCerts,\n\t\t\t\tCertificates: []tls.Certificate{{\n\t\t\t\t\tCertificate: [][]byte{clientDer},\n\t\t\t\t\tPrivateKey:  clientkey,\n\t\t\t\t}},\n\t\t\t\tMinVersion: tls.VersionTLS12,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"Verify client fails if wrong key is specified\",\n\t\t\tSucceed: false,\n\t\t\tServer:  serverTLSConfig,\n\t\t\tClient: &tls.Config{\n\t\t\t\tRootCAs: pool,\n\t\t\t\tCertificates: []tls.Certificate{{\n\t\t\t\t\tCertificate: [][]byte{clientDer},\n\t\t\t\t\tPrivateKey:  hostkey,\n\t\t\t\t}},\n\t\t\t\tMinVersion: tls.VersionTLS12,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"Verify client fails if wrong cert is specified\",\n\t\t\tSucceed: false,\n\t\t\tServer:  serverTLSConfig,\n\t\t\tClient: &tls.Config{\n\t\t\t\tRootCAs: pool,\n\t\t\t\tCertificates: []tls.Certificate{{\n\t\t\t\t\tCertificate: [][]byte{hostDer},\n\t\t\t\t\tPrivateKey:  clientkey,\n\t\t\t\t}},\n\t\t\t\tMinVersion: tls.VersionTLS12,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"Verify client fails if no CAs are specified\",\n\t\t\tSucceed: false,\n\t\t\tServer:  serverTLSConfig,\n\t\t\tClient: &tls.Config{\n\t\t\t\tCertificates: []tls.Certificate{{\n\t\t\t\t\tCertificate: [][]byte{clientDer},\n\t\t\t\t\tPrivateKey:  clientkey,\n\t\t\t\t}},\n\t\t\t\tMinVersion: tls.VersionTLS12,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"Verify client fails if no keys are specified\",\n\t\t\tSucceed: false,\n\t\t\tServer:  serverTLSConfig,\n\t\t\tClient: &tls.Config{\n\t\t\t\tRootCAs:    pool,\n\t\t\t\tMinVersion: tls.VersionTLS12,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"Finally, verify it all works happily with client and server cert in place\",\n\t\t\tSucceed: true,\n\t\t\tServer:  serverTLSConfig,\n\t\t\tClient: &tls.Config{\n\t\t\t\tRootCAs: pool,\n\t\t\t\tCertificates: []tls.Certificate{{\n\t\t\t\t\tCertificate: [][]byte{clientDer},\n\t\t\t\t\tPrivateKey:  clientkey,\n\t\t\t\t}},\n\t\t\t\tMinVersion: tls.VersionTLS12,\n\t\t\t},\n\t\t},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tdoListenerTLSTest(t, tc.Succeed, tc.Server, tc.Client)\n\t\t})\n\t}\n}\n\nfunc doListenerTLSTest(t *testing.T, expectSuccess bool, serverConfig, clientConfig *tls.Config) {\n\tseedListener, err := tls.Listen(\"tcp\", \"127.0.0.1:0\", serverConfig)\n\tif err != nil {\n\t\tt.Fatal(\"cannot open listener\", err)\n\t}\n\n\tvar childT *testing.T\n\tif expectSuccess {\n\t\tchildT = t\n\t} else {\n\t\tchildT = &testing.T{} // we want to swallow errors\n\t}\n\n\tseedBroker := NewMockBrokerListener(childT, 1, seedListener)\n\tdefer seedBroker.Close()\n\n\tmetadataResponse := new(MetadataResponse)\n\tmetadataResponse.AddBroker(seedBroker.Addr(), seedBroker.BrokerID())\n\tseedBroker.Returns(metadataResponse)\n\n\tconfig := NewTestConfig()\n\tconfig.Net.TLS.Enable = true\n\tconfig.Net.TLS.Config = clientConfig\n\n\tclient, err := NewClient([]string{seedBroker.Addr()}, config)\n\tif err == nil {\n\t\tsafeClose(t, client)\n\t}\n\n\tif expectSuccess {\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t} else {\n\t\tif err == nil {\n\t\t\tt.Fatal(\"expected failure\")\n\t\t}\n\t}\n}\n\nfunc TestSetServerName(t *testing.T) {\n\tif validServerNameTLS(\"kafka-server.domain.com:9093\", nil).ServerName != \"kafka-server.domain.com\" {\n\t\tt.Fatal(\"Expected kafka-server.domain.com as tls.ServerName when tls config is nil\")\n\t}\n\n\tif validServerNameTLS(\"kafka-server.domain.com:9093\", &tls.Config{MinVersion: tls.VersionTLS12}).ServerName != \"kafka-server.domain.com\" {\n\t\tt.Fatal(\"Expected kafka-server.domain.com as tls.ServerName when tls config ServerName is not provided\")\n\t}\n\n\tc := &tls.Config{ServerName: \"kafka-server-other.domain.com\", MinVersion: tls.VersionTLS12}\n\tif validServerNameTLS(\"\", c).ServerName != \"kafka-server-other.domain.com\" {\n\t\tt.Fatal(\"Expected kafka-server-other.domain.com as tls.ServerName when tls config ServerName is provided\")\n\t}\n\n\tif validServerNameTLS(\"host-no-port\", nil).ServerName != \"\" {\n\t\tt.Fatal(\"Expected empty ServerName as the broker addr is missing the port\")\n\t}\n}\n"
  },
  {
    "path": "compress.go",
    "content": "package sarama\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"sync\"\n\n\t\"github.com/klauspost/compress/gzip\"\n\tsnappy \"github.com/klauspost/compress/snappy/xerial\"\n\t\"github.com/pierrec/lz4/v4\"\n)\n\nvar (\n\tlz4WriterPool = sync.Pool{\n\t\tNew: func() interface{} {\n\t\t\tlz := lz4.NewWriter(nil)\n\t\t\tif err := lz.Apply(lz4.BlockSizeOption(lz4.Block64Kb)); err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\treturn lz\n\t\t},\n\t}\n\n\tgzipWriterPool = sync.Pool{\n\t\tNew: func() interface{} {\n\t\t\treturn gzip.NewWriter(nil)\n\t\t},\n\t}\n\tgzipWriterPoolForCompressionLevel1 = sync.Pool{\n\t\tNew: func() interface{} {\n\t\t\tgz, err := gzip.NewWriterLevel(nil, 1)\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\treturn gz\n\t\t},\n\t}\n\tgzipWriterPoolForCompressionLevel2 = sync.Pool{\n\t\tNew: func() interface{} {\n\t\t\tgz, err := gzip.NewWriterLevel(nil, 2)\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\treturn gz\n\t\t},\n\t}\n\tgzipWriterPoolForCompressionLevel3 = sync.Pool{\n\t\tNew: func() interface{} {\n\t\t\tgz, err := gzip.NewWriterLevel(nil, 3)\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\treturn gz\n\t\t},\n\t}\n\tgzipWriterPoolForCompressionLevel4 = sync.Pool{\n\t\tNew: func() interface{} {\n\t\t\tgz, err := gzip.NewWriterLevel(nil, 4)\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\treturn gz\n\t\t},\n\t}\n\tgzipWriterPoolForCompressionLevel5 = sync.Pool{\n\t\tNew: func() interface{} {\n\t\t\tgz, err := gzip.NewWriterLevel(nil, 5)\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\treturn gz\n\t\t},\n\t}\n\tgzipWriterPoolForCompressionLevel6 = sync.Pool{\n\t\tNew: func() interface{} {\n\t\t\tgz, err := gzip.NewWriterLevel(nil, 6)\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\treturn gz\n\t\t},\n\t}\n\tgzipWriterPoolForCompressionLevel7 = sync.Pool{\n\t\tNew: func() interface{} {\n\t\t\tgz, err := gzip.NewWriterLevel(nil, 7)\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\treturn gz\n\t\t},\n\t}\n\tgzipWriterPoolForCompressionLevel8 = sync.Pool{\n\t\tNew: func() interface{} {\n\t\t\tgz, err := gzip.NewWriterLevel(nil, 8)\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\treturn gz\n\t\t},\n\t}\n\tgzipWriterPoolForCompressionLevel9 = sync.Pool{\n\t\tNew: func() interface{} {\n\t\t\tgz, err := gzip.NewWriterLevel(nil, 9)\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\treturn gz\n\t\t},\n\t}\n)\n\nfunc compress(cc CompressionCodec, level int, data []byte) ([]byte, error) {\n\tswitch cc {\n\tcase CompressionNone:\n\t\treturn data, nil\n\tcase CompressionGZIP:\n\t\treturn gzipCompress(level, data)\n\tcase CompressionSnappy:\n\t\treturn snappy.Encode(nil, data), nil\n\tcase CompressionLZ4:\n\t\treturn lz4Compress(data)\n\tcase CompressionZSTD:\n\t\treturn zstdCompress(ZstdEncoderParams{level}, nil, data)\n\tdefault:\n\t\treturn nil, PacketEncodingError{fmt.Sprintf(\"unsupported compression codec (%d)\", cc)}\n\t}\n}\n\nfunc gzipCompress(level int, data []byte) ([]byte, error) {\n\tvar (\n\t\tbuf    bytes.Buffer\n\t\twriter *gzip.Writer\n\t\tpool   *sync.Pool\n\t)\n\n\tswitch level {\n\tcase CompressionLevelDefault:\n\t\tpool = &gzipWriterPool\n\tcase 1:\n\t\tpool = &gzipWriterPoolForCompressionLevel1\n\tcase 2:\n\t\tpool = &gzipWriterPoolForCompressionLevel2\n\tcase 3:\n\t\tpool = &gzipWriterPoolForCompressionLevel3\n\tcase 4:\n\t\tpool = &gzipWriterPoolForCompressionLevel4\n\tcase 5:\n\t\tpool = &gzipWriterPoolForCompressionLevel5\n\tcase 6:\n\t\tpool = &gzipWriterPoolForCompressionLevel6\n\tcase 7:\n\t\tpool = &gzipWriterPoolForCompressionLevel7\n\tcase 8:\n\t\tpool = &gzipWriterPoolForCompressionLevel8\n\tcase 9:\n\t\tpool = &gzipWriterPoolForCompressionLevel9\n\tdefault:\n\t\tvar err error\n\t\twriter, err = gzip.NewWriterLevel(&buf, level)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tif pool != nil {\n\t\twriter = pool.Get().(*gzip.Writer)\n\t\twriter.Reset(&buf)\n\t\tdefer pool.Put(writer)\n\t}\n\tif _, err := writer.Write(data); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := writer.Close(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn buf.Bytes(), nil\n}\n\nfunc lz4Compress(data []byte) ([]byte, error) {\n\twriter := lz4WriterPool.Get().(*lz4.Writer)\n\tdefer lz4WriterPool.Put(writer)\n\n\tvar buf bytes.Buffer\n\twriter.Reset(&buf)\n\tif _, err := writer.Write(data); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := writer.Close(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn buf.Bytes(), nil\n}\n"
  },
  {
    "path": "config.go",
    "content": "package sarama\n\nimport (\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"regexp\"\n\t\"time\"\n\n\t\"github.com/klauspost/compress/gzip\"\n\t\"github.com/rcrowley/go-metrics\"\n\t\"golang.org/x/net/proxy\"\n)\n\nconst defaultClientID = \"sarama\"\n\n// validClientID specifies the permitted characters for a client.id when\n// connecting to Kafka versions before 1.0.0 (KIP-190)\nvar validClientID = regexp.MustCompile(`\\A[A-Za-z0-9._-]+\\z`)\n\n// Config is used to pass multiple configuration options to Sarama's constructors.\ntype Config struct {\n\t// Admin is the namespace for ClusterAdmin properties used by the administrative Kafka client.\n\tAdmin struct {\n\t\tRetry struct {\n\t\t\t// The total number of times to retry sending (retriable) admin requests (default 5).\n\t\t\t// Similar to the `retries` setting of the JVM AdminClientConfig.\n\t\t\tMax int\n\t\t\t// Backoff time between retries of a failed request (default 100ms)\n\t\t\tBackoff time.Duration\n\t\t}\n\t\t// The maximum duration the administrative Kafka client will wait for ClusterAdmin operations,\n\t\t// including topics, brokers, configurations and ACLs (defaults to 3 seconds).\n\t\tTimeout time.Duration\n\t}\n\n\t// Net is the namespace for network-level properties used by the Broker, and\n\t// shared by the Client/Producer/Consumer.\n\tNet struct {\n\t\t// How many outstanding requests a connection is allowed to have before\n\t\t// sending on it blocks (default 5).\n\t\t// Throughput can improve but message ordering is not guaranteed if Producer.Idempotent is disabled, see:\n\t\t// https://kafka.apache.org/protocol#protocol_network\n\t\t// https://kafka.apache.org/28/documentation.html#producerconfigs_max.in.flight.requests.per.connection\n\t\tMaxOpenRequests int\n\n\t\t// All three of the below configurations are similar to the\n\t\t// `socket.timeout.ms` setting in JVM kafka. All of them default\n\t\t// to 30 seconds.\n\t\tDialTimeout  time.Duration // How long to wait for the initial connection.\n\t\tReadTimeout  time.Duration // How long to wait for a response.\n\t\tWriteTimeout time.Duration // How long to wait for a transmit.\n\n\t\t// ResolveCanonicalBootstrapServers turns each bootstrap broker address\n\t\t// into a set of IPs, then does a reverse lookup on each one to get its\n\t\t// canonical hostname. This list of hostnames then replaces the\n\t\t// original address list. Similar to the `client.dns.lookup` option in\n\t\t// the JVM client, this is especially useful with GSSAPI, where it\n\t\t// allows providing an alias record instead of individual broker\n\t\t// hostnames. Defaults to false.\n\t\tResolveCanonicalBootstrapServers bool\n\n\t\tTLS struct {\n\t\t\t// Whether or not to use TLS when connecting to the broker\n\t\t\t// (defaults to false).\n\t\t\tEnable bool\n\t\t\t// The TLS configuration to use for secure connections if\n\t\t\t// enabled (defaults to nil).\n\t\t\tConfig *tls.Config\n\t\t}\n\n\t\t// SASL based authentication with broker. While there are multiple SASL authentication methods\n\t\t// the current implementation is limited to plaintext (SASL/PLAIN) authentication\n\t\tSASL struct {\n\t\t\t// Whether or not to use SASL authentication when connecting to the broker\n\t\t\t// (defaults to false).\n\t\t\tEnable bool\n\t\t\t// SASLMechanism is the name of the enabled SASL mechanism.\n\t\t\t// Possible values: OAUTHBEARER, PLAIN (defaults to PLAIN).\n\t\t\tMechanism SASLMechanism\n\t\t\t// Version is the SASL Protocol Version to use\n\t\t\t// Kafka > 1.x should use V1, except on Azure EventHub which use V0\n\t\t\tVersion int16\n\t\t\t// Whether or not to send the Kafka SASL handshake first if enabled\n\t\t\t// (defaults to true). You should only set this to false if you're using\n\t\t\t// a non-Kafka SASL proxy.\n\t\t\tHandshake bool\n\t\t\t// AuthIdentity is an (optional) authorization identity (authzid) to\n\t\t\t// use for SASL/PLAIN authentication (if different from User) when\n\t\t\t// an authenticated user is permitted to act as the presented\n\t\t\t// alternative user. See RFC4616 for details.\n\t\t\tAuthIdentity string\n\t\t\t// User is the authentication identity (authcid) to present for\n\t\t\t// SASL/PLAIN or SASL/SCRAM authentication\n\t\t\tUser string\n\t\t\t// Password for SASL/PLAIN authentication\n\t\t\tPassword string // #nosec G117 -- public SASL config schema; callers set this credential explicitly.\n\t\t\t// authz id used for SASL/SCRAM authentication\n\t\t\tSCRAMAuthzID string\n\t\t\t// SCRAMClientGeneratorFunc is a generator of a user provided implementation of a SCRAM\n\t\t\t// client used to perform the SCRAM exchange with the server.\n\t\t\tSCRAMClientGeneratorFunc func() SCRAMClient\n\t\t\t// TokenProvider is a user-defined callback for generating\n\t\t\t// access tokens for SASL/OAUTHBEARER auth. See the\n\t\t\t// AccessTokenProvider interface docs for proper implementation\n\t\t\t// guidelines.\n\t\t\tTokenProvider AccessTokenProvider\n\n\t\t\tGSSAPI GSSAPIConfig\n\t\t}\n\n\t\t// KeepAlive specifies the keep-alive period for an active network connection (defaults to 0).\n\t\t// If zero or positive, keep-alives are enabled.\n\t\t// If negative, keep-alives are disabled.\n\t\tKeepAlive time.Duration\n\n\t\t// LocalAddr is the local address to use when dialing an\n\t\t// address. The address must be of a compatible type for the\n\t\t// network being dialed.\n\t\t// If nil, a local address is automatically chosen.\n\t\tLocalAddr net.Addr\n\n\t\tProxy struct {\n\t\t\t// Whether or not to use proxy when connecting to the broker\n\t\t\t// (defaults to false).\n\t\t\tEnable bool\n\t\t\t// The proxy dialer to use enabled (defaults to nil).\n\t\t\tDialer proxy.Dialer\n\t\t}\n\t}\n\n\t// Metadata is the namespace for metadata management properties used by the\n\t// Client, and shared by the Producer/Consumer.\n\tMetadata struct {\n\t\tRetry struct {\n\t\t\t// The total number of times to retry a metadata request when the\n\t\t\t// cluster is in the middle of a leader election (default 3).\n\t\t\tMax int\n\t\t\t// How long to wait for leader election to occur before retrying\n\t\t\t// (default 250ms). Similar to the JVM's `retry.backoff.ms`.\n\t\t\tBackoff time.Duration\n\t\t\t// Called to compute backoff time dynamically. Useful for implementing\n\t\t\t// more sophisticated backoff strategies. This takes precedence over\n\t\t\t// `Backoff` if set.\n\t\t\tBackoffFunc func(retries, maxRetries int) time.Duration\n\t\t}\n\t\t// How frequently to refresh the cluster metadata in the background.\n\t\t// Defaults to 10 minutes. Set to 0 to disable. Similar to\n\t\t// `topic.metadata.refresh.interval.ms` in the JVM version.\n\t\tRefreshFrequency time.Duration\n\n\t\t// Whether to maintain a full set of metadata for all topics, or just\n\t\t// the minimal set that has been necessary so far. The full set is simpler\n\t\t// and usually more convenient, but can take up a substantial amount of\n\t\t// memory if you have many topics and partitions. Defaults to true.\n\t\tFull bool\n\n\t\t// How long to wait for a successful metadata response.\n\t\t// Disabled by default which means a metadata request against an unreachable\n\t\t// cluster (all brokers are unreachable or unresponsive) can take up to\n\t\t// `Net.[Dial|Read]Timeout * BrokerCount * (Metadata.Retry.Max + 1) + Metadata.Retry.Backoff * Metadata.Retry.Max`\n\t\t// to fail.\n\t\tTimeout time.Duration\n\n\t\t// Whether to allow auto-create topics in metadata refresh. If set to true,\n\t\t// the broker may auto-create topics that we requested which do not already exist,\n\t\t// if it is configured to do so (`auto.create.topics.enable` is true). Defaults to true.\n\t\tAllowAutoTopicCreation bool\n\n\t\t// SingleFlight controls whether to send a single metadata refresh request at a given time\n\t\t// or whether to allow anyone to refresh the metadata concurrently.\n\t\t// If this is set to true and the client needs to refresh the metadata from different goroutines,\n\t\t// the requests will be batched together so that a single refresh is sent at a time.\n\t\t// See https://github.com/IBM/sarama/issues/3224 for more details.\n\t\t// SingleFlight defaults to true.\n\t\tSingleFlight bool\n\t}\n\n\t// Producer is the namespace for configuration related to producing messages,\n\t// used by the Producer.\n\tProducer struct {\n\t\t// The maximum permitted size of a message (defaults to 1000000). Should be\n\t\t// set equal to or smaller than the broker's `message.max.bytes`.\n\t\tMaxMessageBytes int\n\t\t// The level of acknowledgement reliability needed from the broker (defaults\n\t\t// to WaitForLocal). Equivalent to the `request.required.acks` setting of the\n\t\t// JVM producer.\n\t\tRequiredAcks RequiredAcks\n\t\t// The maximum duration the broker will wait the receipt of the number of\n\t\t// RequiredAcks (defaults to 10 seconds). This is only relevant when\n\t\t// RequiredAcks is set to WaitForAll or a number > 1. Only supports\n\t\t// millisecond resolution, nanoseconds will be truncated. Equivalent to\n\t\t// the JVM producer's `request.timeout.ms` setting.\n\t\tTimeout time.Duration\n\t\t// The type of compression to use on messages (defaults to no compression).\n\t\t// Similar to `compression.codec` setting of the JVM producer.\n\t\tCompression CompressionCodec\n\t\t// The level of compression to use on messages. The meaning depends\n\t\t// on the actual compression type used and defaults to default compression\n\t\t// level for the codec.\n\t\tCompressionLevel int\n\t\t// Generates partitioners for choosing the partition to send messages to\n\t\t// (defaults to hashing the message key). Similar to the `partitioner.class`\n\t\t// setting for the JVM producer.\n\t\tPartitioner PartitionerConstructor\n\t\t// If enabled, the producer will ensure that exactly one copy of each message is\n\t\t// written, and it will enforce stricter ordering by requiring MaxOpenRequests=1\n\t\t// and WaitForAll acks, which can reduce throughput.\n\t\tIdempotent bool\n\t\t// Transaction specify\n\t\tTransaction struct {\n\t\t\t// Used in transactions to identify an instance of a producer through restarts\n\t\t\tID string\n\t\t\t// Amount of time a transaction can remain unresolved (neither committed nor aborted)\n\t\t\t// default is 1 min\n\t\t\tTimeout time.Duration\n\n\t\t\tRetry struct {\n\t\t\t\t// The total number of times to retry sending a message (default 50).\n\t\t\t\t// Similar to the `message.send.max.retries` setting of the JVM producer.\n\t\t\t\tMax int\n\t\t\t\t// How long to wait for the cluster to settle between retries\n\t\t\t\t// (default 10ms). Similar to the `retry.backoff.ms` setting of the\n\t\t\t\t// JVM producer.\n\t\t\t\tBackoff time.Duration\n\t\t\t\t// Called to compute backoff time dynamically. Useful for implementing\n\t\t\t\t// more sophisticated backoff strategies. This takes precedence over\n\t\t\t\t// `Backoff` if set.\n\t\t\t\tBackoffFunc func(retries, maxRetries int) time.Duration\n\t\t\t}\n\t\t}\n\n\t\t// Return specifies what channels will be populated. If they are set to true,\n\t\t// you must read from the respective channels to prevent deadlock. If,\n\t\t// however, this config is used to create a `SyncProducer`, both must be set\n\t\t// to true and you shall not read from the channels since the producer does\n\t\t// this internally.\n\t\tReturn struct {\n\t\t\t// If enabled, successfully delivered messages will be returned on the\n\t\t\t// Successes channel (default disabled).\n\t\t\tSuccesses bool\n\n\t\t\t// If enabled, messages that failed to deliver will be returned on the\n\t\t\t// Errors channel, including error (default enabled).\n\t\t\tErrors bool\n\t\t}\n\n\t\t// The following config options control how often messages are batched up and\n\t\t// sent to the broker. By default, messages are sent as fast as possible, and\n\t\t// all messages received while the current batch is in-flight are placed\n\t\t// into the subsequent batch.\n\t\tFlush struct {\n\t\t\t// The best-effort number of bytes needed to trigger a flush. Use the\n\t\t\t// global sarama.MaxRequestSize to set a hard upper limit.\n\t\t\tBytes int\n\t\t\t// The best-effort number of messages needed to trigger a flush. Use\n\t\t\t// `MaxMessages` to set a hard upper limit.\n\t\t\tMessages int\n\t\t\t// The best-effort frequency of flushes. Equivalent to\n\t\t\t// `queue.buffering.max.ms` setting of JVM producer.\n\t\t\tFrequency time.Duration\n\t\t\t// The maximum number of messages the producer will send in a single\n\t\t\t// broker request. Defaults to 0 for unlimited. Similar to\n\t\t\t// `queue.buffering.max.messages` in the JVM producer.\n\t\t\tMaxMessages int\n\t\t}\n\n\t\tRetry struct {\n\t\t\t// The total number of times to retry sending a message (default 3).\n\t\t\t// Similar to the `message.send.max.retries` setting of the JVM producer.\n\t\t\tMax int\n\t\t\t// How long to wait for the cluster to settle between retries\n\t\t\t// (default 100ms). Similar to the `retry.backoff.ms` setting of the\n\t\t\t// JVM producer.\n\t\t\tBackoff time.Duration\n\t\t\t// Called to compute backoff time dynamically. Useful for implementing\n\t\t\t// more sophisticated backoff strategies. This takes precedence over\n\t\t\t// `Backoff` if set.\n\t\t\tBackoffFunc func(retries, maxRetries int) time.Duration\n\t\t\t// The maximum length of the bridging buffer between `input` and `retries` channels\n\t\t\t// in AsyncProducer#retryHandler.\n\t\t\t// The limit is to prevent this buffer from overflowing or causing OOM.\n\t\t\t// Defaults to 0 for unlimited.\n\t\t\t// Any value between 0 and 4096 is pushed to 4096.\n\t\t\t// A zero or negative value indicates unlimited.\n\t\t\tMaxBufferLength int\n\t\t\t// The maximum total byte size of messages in the bridging buffer between `input`\n\t\t\t// and `retries` channels in AsyncProducer#retryHandler.\n\t\t\t// This limit prevents the buffer from consuming excessive memory.\n\t\t\t// Defaults to 0 for unlimited.\n\t\t\t// Any value between 0 and 32 MB is pushed to 32 MB.\n\t\t\t// A zero or negative value indicates unlimited.\n\t\t\tMaxBufferBytes int64\n\t\t}\n\n\t\t// Interceptors to be called when the producer dispatcher reads the\n\t\t// message for the first time. Interceptors allows to intercept and\n\t\t// possible mutate the message before they are published to Kafka\n\t\t// cluster. *ProducerMessage modified by the first interceptor's\n\t\t// OnSend() is passed to the second interceptor OnSend(), and so on in\n\t\t// the interceptor chain.\n\t\tInterceptors []ProducerInterceptor\n\t}\n\n\t// Consumer is the namespace for configuration related to consuming messages,\n\t// used by the Consumer.\n\tConsumer struct {\n\t\t// Group is the namespace for configuring consumer group.\n\t\tGroup struct {\n\t\t\tSession struct {\n\t\t\t\t// The timeout used to detect consumer failures when using Kafka's group management facility.\n\t\t\t\t// The consumer sends periodic heartbeats to indicate its liveness to the broker.\n\t\t\t\t// If no heartbeats are received by the broker before the expiration of this session timeout,\n\t\t\t\t// then the broker will remove this consumer from the group and initiate a rebalance.\n\t\t\t\t// Note that the value must be in the allowable range as configured in the broker configuration\n\t\t\t\t// by `group.min.session.timeout.ms` and `group.max.session.timeout.ms` (default 10s)\n\t\t\t\tTimeout time.Duration\n\t\t\t}\n\t\t\tHeartbeat struct {\n\t\t\t\t// The expected time between heartbeats to the consumer coordinator when using Kafka's group\n\t\t\t\t// management facilities. Heartbeats are used to ensure that the consumer's session stays active and\n\t\t\t\t// to facilitate rebalancing when new consumers join or leave the group.\n\t\t\t\t// The value must be set lower than Consumer.Group.Session.Timeout, but typically should be set no\n\t\t\t\t// higher than 1/3 of that value.\n\t\t\t\t// It can be adjusted even lower to control the expected time for normal rebalances (default 3s)\n\t\t\t\tInterval time.Duration\n\t\t\t}\n\t\t\tRebalance struct {\n\t\t\t\t// Strategy for allocating topic partitions to members.\n\t\t\t\t//\n\t\t\t\t// Deprecated: Strategy exists for historical compatibility\n\t\t\t\t// and should not be used. Please use GroupStrategies.\n\t\t\t\tStrategy BalanceStrategy\n\n\t\t\t\t// GroupStrategies is the priority-ordered list of client-side consumer group\n\t\t\t\t// balancing strategies that will be offered to the coordinator. The first\n\t\t\t\t// strategy that all group members support will be chosen by the leader.\n\t\t\t\t// default: [ NewBalanceStrategyRange() ]\n\t\t\t\tGroupStrategies []BalanceStrategy\n\n\t\t\t\t// The maximum allowed time for each worker to join the group once a rebalance has begun.\n\t\t\t\t// This is basically a limit on the amount of time needed for all tasks to flush any pending\n\t\t\t\t// data and commit offsets. If the timeout is exceeded, then the worker will be removed from\n\t\t\t\t// the group, which will cause offset commit failures (default 60s).\n\t\t\t\tTimeout time.Duration\n\n\t\t\t\tRetry struct {\n\t\t\t\t\t// When a new consumer joins a consumer group the set of consumers attempt to \"rebalance\"\n\t\t\t\t\t// the load to assign partitions to each consumer. If the set of consumers changes while\n\t\t\t\t\t// this assignment is taking place the rebalance will fail and retry. This setting controls\n\t\t\t\t\t// the maximum number of attempts before giving up (default 4).\n\t\t\t\t\tMax int\n\t\t\t\t\t// Backoff time between retries during rebalance (default 2s)\n\t\t\t\t\tBackoff time.Duration\n\t\t\t\t}\n\t\t\t}\n\t\t\tMember struct {\n\t\t\t\t// Custom metadata to include when joining the group. The user data for all joined members\n\t\t\t\t// can be retrieved by sending a DescribeGroupRequest to the broker that is the\n\t\t\t\t// coordinator for the group.\n\t\t\t\tUserData []byte\n\t\t\t}\n\n\t\t\t// support KIP-345\n\t\t\tInstanceId string\n\n\t\t\t// If true, consumer offsets will be automatically reset to configured Initial value\n\t\t\t// if the fetched consumer offset is out of range of available offsets. Out of range\n\t\t\t// can happen if the data has been deleted from the server, or during situations of\n\t\t\t// under-replication where a replica does not have all the data yet. It can be\n\t\t\t// dangerous to reset the offset automatically, particularly in the latter case. Defaults\n\t\t\t// to true to maintain existing behavior.\n\t\t\tResetInvalidOffsets bool\n\t\t}\n\n\t\tRetry struct {\n\t\t\t// How long to wait after a failing to read from a partition before\n\t\t\t// trying again (default 2s).\n\t\t\tBackoff time.Duration\n\t\t\t// Called to compute backoff time dynamically. Useful for implementing\n\t\t\t// more sophisticated backoff strategies. This takes precedence over\n\t\t\t// `Backoff` if set.\n\t\t\tBackoffFunc func(retries int) time.Duration\n\t\t}\n\n\t\t// Fetch is the namespace for controlling how many bytes are retrieved by any\n\t\t// given request.\n\t\tFetch struct {\n\t\t\t// The minimum number of message bytes to fetch in a request - the broker\n\t\t\t// will wait until at least this many are available. The default is 1,\n\t\t\t// as 0 causes the consumer to spin when no messages are available.\n\t\t\t// Equivalent to the JVM's `fetch.min.bytes`.\n\t\t\tMin int32\n\t\t\t// The default number of message bytes to fetch from the broker in each\n\t\t\t// request (default 1MB). This should be larger than the majority of\n\t\t\t// your messages, or else the consumer will spend a lot of time\n\t\t\t// negotiating sizes and not actually consuming. Similar to the JVM's\n\t\t\t// `fetch.message.max.bytes`.\n\t\t\tDefault int32\n\t\t\t// The maximum number of message bytes to fetch from the broker in a\n\t\t\t// single request. Messages larger than this will return\n\t\t\t// ErrMessageTooLarge and will not be consumable, so you must be sure\n\t\t\t// this is at least as large as your largest message. Defaults to 0\n\t\t\t// (no limit). Similar to the JVM's `fetch.message.max.bytes`. The\n\t\t\t// global `sarama.MaxResponseSize` still applies.\n\t\t\tMax int32\n\t\t}\n\t\t// The maximum amount of time the broker will wait for Consumer.Fetch.Min\n\t\t// bytes to become available before it returns fewer than that anyways. The\n\t\t// default is 250ms, since 0 causes the consumer to spin when no events are\n\t\t// available. 100-500ms is a reasonable range for most cases. Kafka only\n\t\t// supports precision up to milliseconds; nanoseconds will be truncated.\n\t\t// Equivalent to the JVM's `fetch.max.wait.ms`.\n\t\tMaxWaitTime time.Duration\n\n\t\t// The maximum amount of time the consumer expects a message takes to\n\t\t// process for the user. If writing to the Messages channel takes longer\n\t\t// than this, that partition will stop fetching more messages until it\n\t\t// can proceed again.\n\t\t// Note that, since the Messages channel is buffered, the actual grace time is\n\t\t// (MaxProcessingTime * ChannelBufferSize). Defaults to 100ms.\n\t\t// If a message is not written to the Messages channel between two ticks\n\t\t// of the expiryTicker then a timeout is detected.\n\t\t// Using a ticker instead of a timer to detect timeouts should typically\n\t\t// result in many fewer calls to Timer functions which may result in a\n\t\t// significant performance improvement if many messages are being sent\n\t\t// and timeouts are infrequent.\n\t\t// The disadvantage of using a ticker instead of a timer is that\n\t\t// timeouts will be less accurate. That is, the effective timeout could\n\t\t// be between `MaxProcessingTime` and `2 * MaxProcessingTime`. For\n\t\t// example, if `MaxProcessingTime` is 100ms then a delay of 180ms\n\t\t// between two messages being sent may not be recognized as a timeout.\n\t\tMaxProcessingTime time.Duration\n\n\t\t// Return specifies what channels will be populated. If they are set to true,\n\t\t// you must read from them to prevent deadlock.\n\t\tReturn struct {\n\t\t\t// If enabled, any errors that occurred while consuming are returned on\n\t\t\t// the Errors channel (default disabled).\n\t\t\tErrors bool\n\t\t}\n\n\t\t// Offsets specifies configuration for how and when to commit consumed\n\t\t// offsets. This currently requires the manual use of an OffsetManager\n\t\t// but will eventually be automated.\n\t\tOffsets struct {\n\t\t\t// Deprecated: CommitInterval exists for historical compatibility\n\t\t\t// and should not be used. Please use Consumer.Offsets.AutoCommit\n\t\t\tCommitInterval time.Duration\n\n\t\t\t// AutoCommit specifies configuration for commit messages automatically.\n\t\t\tAutoCommit struct {\n\t\t\t\t// Whether or not to auto-commit updated offsets back to the broker.\n\t\t\t\t// (default enabled).\n\t\t\t\tEnable bool\n\n\t\t\t\t// How frequently to commit updated offsets. Ineffective unless\n\t\t\t\t// auto-commit is enabled (default 1s)\n\t\t\t\tInterval time.Duration\n\t\t\t}\n\n\t\t\t// The initial offset to use if no offset was previously committed.\n\t\t\t// Should be OffsetNewest or OffsetOldest. Defaults to OffsetNewest.\n\t\t\tInitial int64\n\n\t\t\t// The retention duration for committed offsets. If zero, disabled\n\t\t\t// (in which case the `offsets.retention.minutes` option on the\n\t\t\t// broker will be used).  Kafka only supports precision up to\n\t\t\t// milliseconds; nanoseconds will be truncated. Requires Kafka\n\t\t\t// broker version 0.9.0 or later.\n\t\t\t// (default is 0: disabled).\n\t\t\tRetention time.Duration\n\n\t\t\tRetry struct {\n\t\t\t\t// The total number of times to retry failing commit\n\t\t\t\t// requests during OffsetManager shutdown (default 3).\n\t\t\t\tMax int\n\t\t\t}\n\t\t}\n\n\t\t// IsolationLevel support 2 mode:\n\t\t// \t- use `ReadUncommitted` (default) to consume and return all messages in message channel\n\t\t//\t- use `ReadCommitted` to hide messages that are part of an aborted transaction\n\t\tIsolationLevel IsolationLevel\n\n\t\t// Interceptors to be called just before the record is sent to the\n\t\t// messages channel. Interceptors allows to intercept and possible\n\t\t// mutate the message before they are returned to the client.\n\t\t// *ConsumerMessage modified by the first interceptor's OnConsume() is\n\t\t// passed to the second interceptor OnConsume(), and so on in the\n\t\t// interceptor chain.\n\t\tInterceptors []ConsumerInterceptor\n\t}\n\n\t// A user-provided string sent with every request to the brokers for logging,\n\t// debugging, and auditing purposes. Defaults to \"sarama\", but you should\n\t// probably set it to something specific to your application.\n\tClientID string\n\t// A rack identifier for this client. This can be any string value which\n\t// indicates where this client is physically located.\n\t// It corresponds with the broker config 'broker.rack'\n\tRackID string\n\t// The number of events to buffer in internal and external channels. This\n\t// permits the producer and consumer to continue processing some messages\n\t// in the background while user code is working, greatly improving throughput.\n\t// Defaults to 256.\n\tChannelBufferSize int\n\t// ApiVersionsRequest determines whether Sarama should send an\n\t// ApiVersionsRequest message to each broker as part of its initial\n\t// connection. This defaults to `true` to match the official Java client\n\t// and most 3rdparty ones.\n\tApiVersionsRequest bool\n\t// The version of Kafka that Sarama will assume it is running against.\n\t// Defaults to the oldest supported stable version. Since Kafka provides\n\t// backwards-compatibility, setting it to a version older than you have\n\t// will not break anything, although it may prevent you from using the\n\t// latest features. Setting it to a version greater than you are actually\n\t// running may lead to random breakage.\n\tVersion KafkaVersion\n\t// The registry to define metrics into.\n\t// Defaults to a local registry.\n\t// If you want to disable metrics gathering, set \"metrics.UseNilMetrics\" to \"true\"\n\t// prior to starting Sarama.\n\t// See Examples on how to use the metrics registry\n\tMetricRegistry metrics.Registry\n}\n\n// NewConfig returns a new configuration instance with sane defaults.\nfunc NewConfig() *Config {\n\tc := &Config{}\n\n\tc.Admin.Retry.Max = 5\n\tc.Admin.Retry.Backoff = 100 * time.Millisecond\n\tc.Admin.Timeout = 3 * time.Second\n\n\tc.Net.MaxOpenRequests = 5\n\tc.Net.DialTimeout = 30 * time.Second\n\tc.Net.ReadTimeout = 30 * time.Second\n\tc.Net.WriteTimeout = 30 * time.Second\n\tc.Net.SASL.Handshake = true\n\tc.Net.SASL.Version = SASLHandshakeV1\n\n\tc.Metadata.Retry.Max = 3\n\tc.Metadata.Retry.Backoff = 250 * time.Millisecond\n\tc.Metadata.RefreshFrequency = 10 * time.Minute\n\tc.Metadata.Full = true\n\tc.Metadata.AllowAutoTopicCreation = true\n\tc.Metadata.SingleFlight = true\n\n\tc.Producer.MaxMessageBytes = 1024 * 1024\n\tc.Producer.RequiredAcks = WaitForLocal\n\tc.Producer.Timeout = 10 * time.Second\n\tc.Producer.Partitioner = NewHashPartitioner\n\tc.Producer.Retry.Max = 3\n\tc.Producer.Retry.Backoff = 100 * time.Millisecond\n\tc.Producer.Return.Errors = true\n\tc.Producer.CompressionLevel = CompressionLevelDefault\n\n\tc.Producer.Transaction.Timeout = 1 * time.Minute\n\tc.Producer.Transaction.Retry.Max = 50\n\tc.Producer.Transaction.Retry.Backoff = 100 * time.Millisecond\n\n\tc.Consumer.Fetch.Min = 1\n\tc.Consumer.Fetch.Default = 1024 * 1024\n\tc.Consumer.Retry.Backoff = 2 * time.Second\n\tc.Consumer.MaxWaitTime = 500 * time.Millisecond\n\tc.Consumer.MaxProcessingTime = 100 * time.Millisecond\n\tc.Consumer.Return.Errors = false\n\tc.Consumer.Offsets.AutoCommit.Enable = true\n\tc.Consumer.Offsets.AutoCommit.Interval = 1 * time.Second\n\tc.Consumer.Offsets.Initial = OffsetNewest\n\tc.Consumer.Offsets.Retry.Max = 3\n\n\tc.Consumer.Group.Session.Timeout = 10 * time.Second\n\tc.Consumer.Group.Heartbeat.Interval = 3 * time.Second\n\tc.Consumer.Group.Rebalance.GroupStrategies = []BalanceStrategy{NewBalanceStrategyRange()}\n\tc.Consumer.Group.Rebalance.Timeout = 60 * time.Second\n\tc.Consumer.Group.Rebalance.Retry.Max = 4\n\tc.Consumer.Group.Rebalance.Retry.Backoff = 2 * time.Second\n\tc.Consumer.Group.ResetInvalidOffsets = true\n\n\tc.ClientID = defaultClientID\n\tc.ChannelBufferSize = 256\n\tc.ApiVersionsRequest = true\n\tc.Version = DefaultVersion\n\tc.MetricRegistry = metrics.NewRegistry()\n\n\treturn c\n}\n\n// Validate checks a Config instance. It will return a\n// ConfigurationError if the specified values don't make sense.\n//\n//nolint:gocyclo // This function's cyclomatic complexity has go beyond 100\nfunc (c *Config) Validate() error {\n\t// some configuration values should be warned on but not fail completely, do those first\n\tif !c.Net.TLS.Enable && c.Net.TLS.Config != nil {\n\t\tLogger.Println(\"Net.TLS is disabled but a non-nil configuration was provided.\")\n\t}\n\tif !c.Net.SASL.Enable {\n\t\tif c.Net.SASL.User != \"\" {\n\t\t\tLogger.Println(\"Net.SASL is disabled but a non-empty username was provided.\")\n\t\t}\n\t\tif c.Net.SASL.Password != \"\" {\n\t\t\tLogger.Println(\"Net.SASL is disabled but a non-empty password was provided.\")\n\t\t}\n\t}\n\tif c.Producer.RequiredAcks > 1 {\n\t\tLogger.Println(\"Producer.RequiredAcks > 1 is deprecated and will raise an exception with kafka >= 0.8.2.0.\")\n\t}\n\tif c.Producer.MaxMessageBytes >= int(MaxRequestSize) {\n\t\tLogger.Println(\"Producer.MaxMessageBytes must be smaller than MaxRequestSize; it will be ignored.\")\n\t}\n\tif c.Producer.Flush.Bytes >= int(MaxRequestSize) {\n\t\tLogger.Println(\"Producer.Flush.Bytes must be smaller than MaxRequestSize; it will be ignored.\")\n\t}\n\tif (c.Producer.Flush.Bytes > 0 || c.Producer.Flush.Messages > 0) && c.Producer.Flush.Frequency == 0 {\n\t\tLogger.Println(\"Producer.Flush: Bytes or Messages are set, but Frequency is not; messages may not get flushed.\")\n\t}\n\tif c.Producer.Timeout%time.Millisecond != 0 {\n\t\tLogger.Println(\"Producer.Timeout only supports millisecond resolution; nanoseconds will be truncated.\")\n\t}\n\tif c.Consumer.MaxWaitTime < 100*time.Millisecond {\n\t\tLogger.Println(\"Consumer.MaxWaitTime is very low, which can cause high CPU and network usage. See documentation for details.\")\n\t}\n\tif c.Consumer.MaxWaitTime%time.Millisecond != 0 {\n\t\tLogger.Println(\"Consumer.MaxWaitTime only supports millisecond precision; nanoseconds will be truncated.\")\n\t}\n\tif c.Consumer.Offsets.Retention%time.Millisecond != 0 {\n\t\tLogger.Println(\"Consumer.Offsets.Retention only supports millisecond precision; nanoseconds will be truncated.\")\n\t}\n\tif c.Consumer.Group.Session.Timeout%time.Millisecond != 0 {\n\t\tLogger.Println(\"Consumer.Group.Session.Timeout only supports millisecond precision; nanoseconds will be truncated.\")\n\t}\n\tif c.Consumer.Group.Heartbeat.Interval%time.Millisecond != 0 {\n\t\tLogger.Println(\"Consumer.Group.Heartbeat.Interval only supports millisecond precision; nanoseconds will be truncated.\")\n\t}\n\tif c.Consumer.Group.Rebalance.Timeout%time.Millisecond != 0 {\n\t\tLogger.Println(\"Consumer.Group.Rebalance.Timeout only supports millisecond precision; nanoseconds will be truncated.\")\n\t}\n\tif c.ClientID == defaultClientID {\n\t\tLogger.Println(\"ClientID is the default of 'sarama', you should consider setting it to something application-specific.\")\n\t}\n\n\t// validate Net values\n\tswitch {\n\tcase c.Net.MaxOpenRequests <= 0:\n\t\treturn ConfigurationError(\"Net.MaxOpenRequests must be > 0\")\n\tcase c.Net.DialTimeout <= 0:\n\t\treturn ConfigurationError(\"Net.DialTimeout must be > 0\")\n\tcase c.Net.ReadTimeout <= 0:\n\t\treturn ConfigurationError(\"Net.ReadTimeout must be > 0\")\n\tcase c.Net.WriteTimeout <= 0:\n\t\treturn ConfigurationError(\"Net.WriteTimeout must be > 0\")\n\tcase c.Net.SASL.Enable:\n\t\tif c.Net.SASL.Mechanism == \"\" {\n\t\t\tc.Net.SASL.Mechanism = SASLTypePlaintext\n\t\t}\n\t\tif c.Net.SASL.Version == SASLHandshakeV0 && c.ApiVersionsRequest {\n\t\t\treturn ConfigurationError(\"ApiVersionsRequest must be disabled when SASL v0 is enabled\")\n\t\t}\n\t\tswitch c.Net.SASL.Mechanism {\n\t\tcase SASLTypePlaintext:\n\t\t\tif c.Net.SASL.User == \"\" {\n\t\t\t\treturn ConfigurationError(\"Net.SASL.User must not be empty when SASL is enabled\")\n\t\t\t}\n\t\t\tif c.Net.SASL.Password == \"\" {\n\t\t\t\treturn ConfigurationError(\"Net.SASL.Password must not be empty when SASL is enabled\")\n\t\t\t}\n\t\tcase SASLTypeOAuth:\n\t\t\tif c.Net.SASL.TokenProvider == nil {\n\t\t\t\treturn ConfigurationError(\"An AccessTokenProvider instance must be provided to Net.SASL.TokenProvider\")\n\t\t\t}\n\t\tcase SASLTypeSCRAMSHA256, SASLTypeSCRAMSHA512:\n\t\t\tif c.Net.SASL.User == \"\" {\n\t\t\t\treturn ConfigurationError(\"Net.SASL.User must not be empty when SASL is enabled\")\n\t\t\t}\n\t\t\tif c.Net.SASL.Password == \"\" {\n\t\t\t\treturn ConfigurationError(\"Net.SASL.Password must not be empty when SASL is enabled\")\n\t\t\t}\n\t\t\tif c.Net.SASL.SCRAMClientGeneratorFunc == nil {\n\t\t\t\treturn ConfigurationError(\"A SCRAMClientGeneratorFunc function must be provided to Net.SASL.SCRAMClientGeneratorFunc\")\n\t\t\t}\n\t\tcase SASLTypeGSSAPI:\n\t\t\tif c.Net.SASL.GSSAPI.ServiceName == \"\" {\n\t\t\t\treturn ConfigurationError(\"Net.SASL.GSSAPI.ServiceName must not be empty when GSS-API mechanism is used\")\n\t\t\t}\n\n\t\t\tswitch c.Net.SASL.GSSAPI.AuthType {\n\t\t\tcase KRB5_USER_AUTH:\n\t\t\t\tif c.Net.SASL.GSSAPI.Password == \"\" {\n\t\t\t\t\treturn ConfigurationError(\"Net.SASL.GSSAPI.Password must not be empty when GSS-API \" +\n\t\t\t\t\t\t\"mechanism is used and Net.SASL.GSSAPI.AuthType = KRB5_USER_AUTH\")\n\t\t\t\t}\n\t\t\tcase KRB5_KEYTAB_AUTH:\n\t\t\t\tif c.Net.SASL.GSSAPI.KeyTabPath == \"\" {\n\t\t\t\t\treturn ConfigurationError(\"Net.SASL.GSSAPI.KeyTabPath must not be empty when GSS-API mechanism is used\" +\n\t\t\t\t\t\t\" and Net.SASL.GSSAPI.AuthType = KRB5_KEYTAB_AUTH\")\n\t\t\t\t}\n\t\t\tcase KRB5_CCACHE_AUTH:\n\t\t\t\tif c.Net.SASL.GSSAPI.CCachePath == \"\" {\n\t\t\t\t\treturn ConfigurationError(\"Net.SASL.GSSAPI.CCachePath must not be empty when GSS-API mechanism is used\" +\n\t\t\t\t\t\t\" and Net.SASL.GSSAPI.AuthType = KRB5_CCACHE_AUTH\")\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\treturn ConfigurationError(\"Net.SASL.GSSAPI.AuthType is invalid. Possible values are KRB5_USER_AUTH, KRB5_KEYTAB_AUTH, and KRB5_CCACHE_AUTH\")\n\t\t\t}\n\n\t\t\tif c.Net.SASL.GSSAPI.KerberosConfigPath == \"\" {\n\t\t\t\treturn ConfigurationError(\"Net.SASL.GSSAPI.KerberosConfigPath must not be empty when GSS-API mechanism is used\")\n\t\t\t}\n\t\t\tif c.Net.SASL.GSSAPI.Username == \"\" {\n\t\t\t\treturn ConfigurationError(\"Net.SASL.GSSAPI.Username must not be empty when GSS-API mechanism is used\")\n\t\t\t}\n\t\t\tif c.Net.SASL.GSSAPI.Realm == \"\" {\n\t\t\t\treturn ConfigurationError(\"Net.SASL.GSSAPI.Realm must not be empty when GSS-API mechanism is used\")\n\t\t\t}\n\t\tdefault:\n\t\t\tmsg := fmt.Sprintf(\"The SASL mechanism configuration is invalid. Possible values are `%s`, `%s`, `%s`, `%s` and `%s`\",\n\t\t\t\tSASLTypeOAuth, SASLTypePlaintext, SASLTypeSCRAMSHA256, SASLTypeSCRAMSHA512, SASLTypeGSSAPI)\n\t\t\treturn ConfigurationError(msg)\n\t\t}\n\t}\n\n\t// validate the Admin values\n\tswitch {\n\tcase c.Admin.Timeout <= 0:\n\t\treturn ConfigurationError(\"Admin.Timeout must be > 0\")\n\t}\n\n\t// validate the Metadata values\n\tswitch {\n\tcase c.Metadata.Retry.Max < 0:\n\t\treturn ConfigurationError(\"Metadata.Retry.Max must be >= 0\")\n\tcase c.Metadata.Retry.Backoff < 0:\n\t\treturn ConfigurationError(\"Metadata.Retry.Backoff must be >= 0\")\n\tcase c.Metadata.RefreshFrequency < 0:\n\t\treturn ConfigurationError(\"Metadata.RefreshFrequency must be >= 0\")\n\t}\n\n\t// validate the Producer values\n\tswitch {\n\tcase c.Producer.MaxMessageBytes <= 0:\n\t\treturn ConfigurationError(\"Producer.MaxMessageBytes must be > 0\")\n\tcase c.Producer.RequiredAcks < -1:\n\t\treturn ConfigurationError(\"Producer.RequiredAcks must be >= -1\")\n\tcase c.Producer.Timeout <= 0:\n\t\treturn ConfigurationError(\"Producer.Timeout must be > 0\")\n\tcase c.Producer.Partitioner == nil:\n\t\treturn ConfigurationError(\"Producer.Partitioner must not be nil\")\n\tcase c.Producer.Flush.Bytes < 0:\n\t\treturn ConfigurationError(\"Producer.Flush.Bytes must be >= 0\")\n\tcase c.Producer.Flush.Messages < 0:\n\t\treturn ConfigurationError(\"Producer.Flush.Messages must be >= 0\")\n\tcase c.Producer.Flush.Frequency < 0:\n\t\treturn ConfigurationError(\"Producer.Flush.Frequency must be >= 0\")\n\tcase c.Producer.Flush.MaxMessages < 0:\n\t\treturn ConfigurationError(\"Producer.Flush.MaxMessages must be >= 0\")\n\tcase c.Producer.Flush.MaxMessages > 0 && c.Producer.Flush.MaxMessages < c.Producer.Flush.Messages:\n\t\treturn ConfigurationError(\"Producer.Flush.MaxMessages must be >= Producer.Flush.Messages when set\")\n\tcase c.Producer.Retry.Max < 0:\n\t\treturn ConfigurationError(\"Producer.Retry.Max must be >= 0\")\n\tcase c.Producer.Retry.Backoff < 0:\n\t\treturn ConfigurationError(\"Producer.Retry.Backoff must be >= 0\")\n\t}\n\n\tif c.Producer.Compression == CompressionLZ4 && !c.Version.IsAtLeast(V0_10_0_0) {\n\t\treturn ConfigurationError(\"lz4 compression requires Version >= V0_10_0_0\")\n\t}\n\n\tif c.Producer.Compression == CompressionGZIP {\n\t\tif c.Producer.CompressionLevel != CompressionLevelDefault {\n\t\t\tif _, err := gzip.NewWriterLevel(io.Discard, c.Producer.CompressionLevel); err != nil {\n\t\t\t\treturn ConfigurationError(fmt.Sprintf(\"gzip compression does not work with level %d: %v\", c.Producer.CompressionLevel, err))\n\t\t\t}\n\t\t}\n\t}\n\n\tif c.Producer.Compression == CompressionZSTD && !c.Version.IsAtLeast(V2_1_0_0) {\n\t\treturn ConfigurationError(\"zstd compression requires Version >= V2_1_0_0\")\n\t}\n\n\tif c.Producer.Idempotent {\n\t\tif !c.Version.IsAtLeast(V0_11_0_0) {\n\t\t\treturn ConfigurationError(\"Idempotent producer requires Version >= V0_11_0_0\")\n\t\t}\n\t\tif c.Producer.Retry.Max == 0 {\n\t\t\treturn ConfigurationError(\"Idempotent producer requires Producer.Retry.Max >= 1\")\n\t\t}\n\t\tif c.Producer.RequiredAcks != WaitForAll {\n\t\t\treturn ConfigurationError(\"Idempotent producer requires Producer.RequiredAcks to be WaitForAll\")\n\t\t}\n\t\tif c.Net.MaxOpenRequests > 1 {\n\t\t\treturn ConfigurationError(\"Idempotent producer requires Net.MaxOpenRequests to be 1\")\n\t\t}\n\t}\n\n\tif c.Producer.Transaction.ID != \"\" && !c.Producer.Idempotent {\n\t\treturn ConfigurationError(\"Transactional producer requires Idempotent to be true\")\n\t}\n\n\t// validate the Consumer values\n\tswitch {\n\tcase c.Consumer.Fetch.Min <= 0:\n\t\treturn ConfigurationError(\"Consumer.Fetch.Min must be > 0\")\n\tcase c.Consumer.Fetch.Default <= 0:\n\t\treturn ConfigurationError(\"Consumer.Fetch.Default must be > 0\")\n\tcase c.Consumer.Fetch.Max < 0:\n\t\treturn ConfigurationError(\"Consumer.Fetch.Max must be >= 0\")\n\tcase c.Consumer.MaxWaitTime < 1*time.Millisecond:\n\t\treturn ConfigurationError(\"Consumer.MaxWaitTime must be >= 1ms\")\n\tcase c.Consumer.MaxProcessingTime <= 0:\n\t\treturn ConfigurationError(\"Consumer.MaxProcessingTime must be > 0\")\n\tcase c.Consumer.Retry.Backoff < 0:\n\t\treturn ConfigurationError(\"Consumer.Retry.Backoff must be >= 0\")\n\tcase c.Consumer.Offsets.AutoCommit.Interval <= 0:\n\t\treturn ConfigurationError(\"Consumer.Offsets.AutoCommit.Interval must be > 0\")\n\tcase c.Consumer.Offsets.Initial != OffsetOldest && c.Consumer.Offsets.Initial != OffsetNewest:\n\t\treturn ConfigurationError(\"Consumer.Offsets.Initial must be OffsetOldest or OffsetNewest\")\n\tcase c.Consumer.Offsets.Retry.Max < 0:\n\t\treturn ConfigurationError(\"Consumer.Offsets.Retry.Max must be >= 0\")\n\tcase c.Consumer.IsolationLevel != ReadUncommitted && c.Consumer.IsolationLevel != ReadCommitted:\n\t\treturn ConfigurationError(\"Consumer.IsolationLevel must be ReadUncommitted or ReadCommitted\")\n\t}\n\n\tif c.Consumer.Offsets.CommitInterval != 0 {\n\t\tLogger.Println(\"Deprecation warning: Consumer.Offsets.CommitInterval exists for historical compatibility\" +\n\t\t\t\" and should not be used. Please use Consumer.Offsets.AutoCommit, the current value will be ignored\")\n\t}\n\tif c.Consumer.Group.Rebalance.Strategy != nil {\n\t\tLogger.Println(\"Deprecation warning: Consumer.Group.Rebalance.Strategy exists for historical compatibility\" +\n\t\t\t\" and should not be used. Please use Consumer.Group.Rebalance.GroupStrategies\")\n\t}\n\n\t// validate IsolationLevel\n\tif c.Consumer.IsolationLevel == ReadCommitted && !c.Version.IsAtLeast(V0_11_0_0) {\n\t\treturn ConfigurationError(\"ReadCommitted requires Version >= V0_11_0_0\")\n\t}\n\n\t// validate the Consumer Group values\n\tswitch {\n\tcase c.Consumer.Group.Session.Timeout <= 2*time.Millisecond:\n\t\treturn ConfigurationError(\"Consumer.Group.Session.Timeout must be >= 2ms\")\n\tcase c.Consumer.Group.Heartbeat.Interval < 1*time.Millisecond:\n\t\treturn ConfigurationError(\"Consumer.Group.Heartbeat.Interval must be >= 1ms\")\n\tcase c.Consumer.Group.Heartbeat.Interval >= c.Consumer.Group.Session.Timeout:\n\t\treturn ConfigurationError(\"Consumer.Group.Heartbeat.Interval must be < Consumer.Group.Session.Timeout\")\n\tcase c.Consumer.Group.Rebalance.Strategy == nil && len(c.Consumer.Group.Rebalance.GroupStrategies) == 0:\n\t\treturn ConfigurationError(\"Consumer.Group.Rebalance.GroupStrategies or Consumer.Group.Rebalance.Strategy must not be empty\")\n\tcase c.Consumer.Group.Rebalance.Timeout <= time.Millisecond:\n\t\treturn ConfigurationError(\"Consumer.Group.Rebalance.Timeout must be >= 1ms\")\n\tcase c.Consumer.Group.Rebalance.Retry.Max < 0:\n\t\treturn ConfigurationError(\"Consumer.Group.Rebalance.Retry.Max must be >= 0\")\n\tcase c.Consumer.Group.Rebalance.Retry.Backoff < 0:\n\t\treturn ConfigurationError(\"Consumer.Group.Rebalance.Retry.Backoff must be >= 0\")\n\t}\n\n\tfor _, strategy := range c.Consumer.Group.Rebalance.GroupStrategies {\n\t\tif strategy == nil {\n\t\t\treturn ConfigurationError(\"elements in Consumer.Group.Rebalance.Strategies must not be empty\")\n\t\t}\n\t}\n\n\tif c.Consumer.Group.InstanceId != \"\" {\n\t\tif !c.Version.IsAtLeast(V2_3_0_0) {\n\t\t\treturn ConfigurationError(\"Consumer.Group.InstanceId need Version >= 2.3\")\n\t\t}\n\t\tif err := validateGroupInstanceId(c.Consumer.Group.InstanceId); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// validate misc shared values\n\tswitch {\n\tcase c.ChannelBufferSize < 0:\n\t\treturn ConfigurationError(\"ChannelBufferSize must be >= 0\")\n\t}\n\n\t// only validate clientID locally for Kafka versions before KIP-190 was implemented\n\tif !c.Version.IsAtLeast(V1_0_0_0) && !validClientID.MatchString(c.ClientID) {\n\t\treturn ConfigurationError(fmt.Sprintf(\"ClientID value %q is not valid for Kafka versions before 1.0.0\", c.ClientID))\n\t}\n\n\treturn nil\n}\n\nfunc (c *Config) getDialer() proxy.Dialer {\n\tif c.Net.Proxy.Enable {\n\t\tLogger.Println(\"using proxy\")\n\t\treturn c.Net.Proxy.Dialer\n\t} else {\n\t\treturn &net.Dialer{\n\t\t\tTimeout:   c.Net.DialTimeout,\n\t\t\tKeepAlive: c.Net.KeepAlive,\n\t\t\tLocalAddr: c.Net.LocalAddr,\n\t\t}\n\t}\n}\n\nconst MAX_GROUP_INSTANCE_ID_LENGTH = 249\n\nvar GROUP_INSTANCE_ID_REGEXP = regexp.MustCompile(`^[0-9a-zA-Z\\._\\-]+$`)\n\nfunc validateGroupInstanceId(id string) error {\n\tif id == \"\" {\n\t\treturn ConfigurationError(\"Group instance id must be non-empty string\")\n\t}\n\tif id == \".\" || id == \"..\" {\n\t\treturn ConfigurationError(`Group instance id cannot be \".\" or \"..\"`)\n\t}\n\tif len(id) > MAX_GROUP_INSTANCE_ID_LENGTH {\n\t\treturn ConfigurationError(fmt.Sprintf(`Group instance id cannot be longer than %v, characters: %s`, MAX_GROUP_INSTANCE_ID_LENGTH, id))\n\t}\n\tif !GROUP_INSTANCE_ID_REGEXP.MatchString(id) {\n\t\treturn ConfigurationError(fmt.Sprintf(`Group instance id %s is illegal, it contains a character other than, '.', '_' and '-'`, id))\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "config_resource_type.go",
    "content": "package sarama\n\n// ConfigResourceType is a type for resources that have configs.\ntype ConfigResourceType int8\n\n// Taken from:\n// https://github.com/apache/kafka/blob/ed7c071e07f1f90e4c2895582f61ca090ced3c42/clients/src/main/java/org/apache/kafka/common/config/ConfigResource.java#L32-L55\n\nconst (\n\t// UnknownResource constant type\n\tUnknownResource ConfigResourceType = 0\n\t// TopicResource constant type\n\tTopicResource ConfigResourceType = 2\n\t// BrokerResource constant type\n\tBrokerResource ConfigResourceType = 4\n\t// BrokerLoggerResource constant type\n\tBrokerLoggerResource ConfigResourceType = 8\n)\n"
  },
  {
    "path": "config_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/rcrowley/go-metrics\"\n\tassert \"github.com/stretchr/testify/require\"\n)\n\n// NewTestConfig returns a config meant to be used by tests.\n// Due to inconsistencies with the request versions the clients send using the default Kafka version\n// and the response versions our mocks use, we default to the minimum Kafka version in most tests\nfunc NewTestConfig() *Config {\n\tconfig := NewConfig()\n\tconfig.ApiVersionsRequest = false\n\tconfig.Consumer.Retry.Backoff = 0\n\tconfig.Producer.Retry.Backoff = 0\n\tconfig.Version = MinVersion\n\treturn config\n}\n\nfunc TestDefaultConfigValidates(t *testing.T) {\n\tconfig := NewTestConfig()\n\tif err := config.Validate(); err != nil {\n\t\tt.Error(err)\n\t}\n\tif config.MetricRegistry == nil {\n\t\tt.Error(\"Expected non nil metrics.MetricRegistry, got nil\")\n\t}\n}\n\n// TestInvalidClientIDValidated ensures that the ClientID field is checked\n// when Version is set to anything less than 1_0_0_0, but otherwise accepted\nfunc TestInvalidClientIDValidated(t *testing.T) {\n\tfor _, version := range SupportedVersions {\n\t\tfor _, clientID := range []string{\"\", \"foo:bar\", \"foo|bar\"} {\n\t\t\tconfig := NewTestConfig()\n\t\t\tconfig.ClientID = clientID\n\t\t\tconfig.Version = version\n\t\t\terr := config.Validate()\n\t\t\tif config.Version.IsAtLeast(V1_0_0_0) {\n\t\t\t\tassert.NoError(t, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tvar target ConfigurationError\n\t\t\tassert.ErrorAs(t, err, &target)\n\t\t\tassert.ErrorContains(t, err, fmt.Sprintf(\"ClientID value %q is not valid for Kafka versions before 1.0.0\", clientID))\n\t\t}\n\t}\n}\n\ntype DummyTokenProvider struct{}\n\nfunc (t *DummyTokenProvider) Token() (*AccessToken, error) {\n\treturn &AccessToken{Token: \"access-token-string\"}, nil\n}\n\nfunc TestNetConfigValidates(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tcfg  func(*Config) // resorting to using a function as a param because of internal composite structs\n\t\terr  string\n\t}{\n\t\t{\n\t\t\t\"OpenRequests\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Net.MaxOpenRequests = 0\n\t\t\t},\n\t\t\t\"Net.MaxOpenRequests must be > 0\",\n\t\t},\n\t\t{\n\t\t\t\"DialTimeout\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Net.DialTimeout = 0\n\t\t\t},\n\t\t\t\"Net.DialTimeout must be > 0\",\n\t\t},\n\t\t{\n\t\t\t\"ReadTimeout\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Net.ReadTimeout = 0\n\t\t\t},\n\t\t\t\"Net.ReadTimeout must be > 0\",\n\t\t},\n\t\t{\n\t\t\t\"WriteTimeout\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Net.WriteTimeout = 0\n\t\t\t},\n\t\t\t\"Net.WriteTimeout must be > 0\",\n\t\t},\n\t\t{\n\t\t\t\"SASL.User\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Net.SASL.Enable = true\n\t\t\t\tcfg.Net.SASL.User = \"\"\n\t\t\t},\n\t\t\t\"Net.SASL.User must not be empty when SASL is enabled\",\n\t\t},\n\t\t{\n\t\t\t\"SASL.Password\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Net.SASL.Enable = true\n\t\t\t\tcfg.Net.SASL.User = \"user\"\n\t\t\t\tcfg.Net.SASL.Password = \"\"\n\t\t\t},\n\t\t\t\"Net.SASL.Password must not be empty when SASL is enabled\",\n\t\t},\n\t\t{\n\t\t\t\"SASL.Mechanism - Invalid mechanism type\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Net.SASL.Enable = true\n\t\t\t\tcfg.Net.SASL.Mechanism = \"AnIncorrectSASLMechanism\"\n\t\t\t\tcfg.Net.SASL.TokenProvider = &DummyTokenProvider{}\n\t\t\t},\n\t\t\t\"The SASL mechanism configuration is invalid. Possible values are `OAUTHBEARER`, `PLAIN`, `SCRAM-SHA-256`, `SCRAM-SHA-512` and `GSSAPI`\",\n\t\t},\n\t\t{\n\t\t\t\"SASL.Mechanism.OAUTHBEARER - Missing token provider\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Net.SASL.Enable = true\n\t\t\t\tcfg.Net.SASL.Mechanism = SASLTypeOAuth\n\t\t\t\tcfg.Net.SASL.TokenProvider = nil\n\t\t\t},\n\t\t\t\"An AccessTokenProvider instance must be provided to Net.SASL.TokenProvider\",\n\t\t},\n\t\t{\n\t\t\t\"SASL.Mechanism SCRAM-SHA-256 - Missing SCRAM client\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Net.SASL.Enable = true\n\t\t\t\tcfg.Net.SASL.Mechanism = SASLTypeSCRAMSHA256\n\t\t\t\tcfg.Net.SASL.SCRAMClientGeneratorFunc = nil\n\t\t\t\tcfg.Net.SASL.User = \"user\"\n\t\t\t\tcfg.Net.SASL.Password = \"strong_password\"\n\t\t\t},\n\t\t\t\"A SCRAMClientGeneratorFunc function must be provided to Net.SASL.SCRAMClientGeneratorFunc\",\n\t\t},\n\t\t{\n\t\t\t\"SASL.Mechanism SCRAM-SHA-512 - Missing SCRAM client\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Net.SASL.Enable = true\n\t\t\t\tcfg.Net.SASL.Mechanism = SASLTypeSCRAMSHA512\n\t\t\t\tcfg.Net.SASL.SCRAMClientGeneratorFunc = nil\n\t\t\t\tcfg.Net.SASL.User = \"user\"\n\t\t\t\tcfg.Net.SASL.Password = \"strong_password\"\n\t\t\t},\n\t\t\t\"A SCRAMClientGeneratorFunc function must be provided to Net.SASL.SCRAMClientGeneratorFunc\",\n\t\t},\n\t\t{\n\t\t\t\"SASL.Mechanism GSSAPI (Kerberos) - Using User/Password, Missing password field\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Net.SASL.Enable = true\n\t\t\t\tcfg.Net.SASL.Mechanism = SASLTypeGSSAPI\n\t\t\t\tcfg.Net.SASL.GSSAPI.AuthType = KRB5_USER_AUTH\n\t\t\t\tcfg.Net.SASL.GSSAPI.Username = \"sarama\"\n\t\t\t\tcfg.Net.SASL.GSSAPI.ServiceName = \"kafka\"\n\t\t\t\tcfg.Net.SASL.GSSAPI.Realm = \"kafka\"\n\t\t\t\tcfg.Net.SASL.GSSAPI.KerberosConfigPath = \"/etc/krb5.conf\"\n\t\t\t},\n\t\t\t\"Net.SASL.GSSAPI.Password must not be empty when GSS-API \" +\n\t\t\t\t\"mechanism is used and Net.SASL.GSSAPI.AuthType = KRB5_USER_AUTH\",\n\t\t},\n\t\t{\n\t\t\t\"SASL.Mechanism GSSAPI (Kerberos) - Using User/Password, Missing KeyTabPath field\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Net.SASL.Enable = true\n\t\t\t\tcfg.Net.SASL.Mechanism = SASLTypeGSSAPI\n\t\t\t\tcfg.Net.SASL.GSSAPI.AuthType = KRB5_KEYTAB_AUTH\n\t\t\t\tcfg.Net.SASL.GSSAPI.Username = \"sarama\"\n\t\t\t\tcfg.Net.SASL.GSSAPI.ServiceName = \"kafka\"\n\t\t\t\tcfg.Net.SASL.GSSAPI.Realm = \"kafka\"\n\t\t\t\tcfg.Net.SASL.GSSAPI.KerberosConfigPath = \"/etc/krb5.conf\"\n\t\t\t},\n\t\t\t\"Net.SASL.GSSAPI.KeyTabPath must not be empty when GSS-API mechanism is used\" +\n\t\t\t\t\" and Net.SASL.GSSAPI.AuthType = KRB5_KEYTAB_AUTH\",\n\t\t},\n\t\t{\n\t\t\t\"SASL.Mechanism GSSAPI (Kerberos) - Missing username\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Net.SASL.Enable = true\n\t\t\t\tcfg.Net.SASL.Mechanism = SASLTypeGSSAPI\n\t\t\t\tcfg.Net.SASL.GSSAPI.AuthType = KRB5_USER_AUTH\n\t\t\t\tcfg.Net.SASL.GSSAPI.Password = \"sarama\"\n\t\t\t\tcfg.Net.SASL.GSSAPI.ServiceName = \"kafka\"\n\t\t\t\tcfg.Net.SASL.GSSAPI.Realm = \"kafka\"\n\t\t\t\tcfg.Net.SASL.GSSAPI.KerberosConfigPath = \"/etc/krb5.conf\"\n\t\t\t},\n\t\t\t\"Net.SASL.GSSAPI.Username must not be empty when GSS-API mechanism is used\",\n\t\t},\n\t\t{\n\t\t\t\"SASL.Mechanism GSSAPI (Kerberos) - Missing ServiceName\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Net.SASL.Enable = true\n\t\t\t\tcfg.Net.SASL.Mechanism = SASLTypeGSSAPI\n\t\t\t\tcfg.Net.SASL.GSSAPI.AuthType = KRB5_USER_AUTH\n\t\t\t\tcfg.Net.SASL.GSSAPI.Username = \"sarama\"\n\t\t\t\tcfg.Net.SASL.GSSAPI.Password = \"sarama\"\n\t\t\t\tcfg.Net.SASL.GSSAPI.Realm = \"kafka\"\n\t\t\t\tcfg.Net.SASL.GSSAPI.KerberosConfigPath = \"/etc/krb5.conf\"\n\t\t\t},\n\t\t\t\"Net.SASL.GSSAPI.ServiceName must not be empty when GSS-API mechanism is used\",\n\t\t},\n\t\t{\n\t\t\t\"SASL.Mechanism GSSAPI (Kerberos) - Missing AuthType\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Net.SASL.Enable = true\n\t\t\t\tcfg.Net.SASL.GSSAPI.ServiceName = \"kafka\"\n\t\t\t\tcfg.Net.SASL.Mechanism = SASLTypeGSSAPI\n\t\t\t\tcfg.Net.SASL.GSSAPI.Username = \"sarama\"\n\t\t\t\tcfg.Net.SASL.GSSAPI.Password = \"sarama\"\n\t\t\t\tcfg.Net.SASL.GSSAPI.Realm = \"kafka\"\n\t\t\t\tcfg.Net.SASL.GSSAPI.KerberosConfigPath = \"/etc/krb5.conf\"\n\t\t\t},\n\t\t\t\"Net.SASL.GSSAPI.AuthType is invalid. Possible values are KRB5_USER_AUTH, KRB5_KEYTAB_AUTH, and KRB5_CCACHE_AUTH\",\n\t\t},\n\t\t{\n\t\t\t\"SASL.Mechanism GSSAPI (Kerberos) - Missing KerberosConfigPath\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Net.SASL.Enable = true\n\t\t\t\tcfg.Net.SASL.GSSAPI.ServiceName = \"kafka\"\n\t\t\t\tcfg.Net.SASL.Mechanism = SASLTypeGSSAPI\n\t\t\t\tcfg.Net.SASL.GSSAPI.AuthType = KRB5_USER_AUTH\n\t\t\t\tcfg.Net.SASL.GSSAPI.Username = \"sarama\"\n\t\t\t\tcfg.Net.SASL.GSSAPI.Password = \"sarama\"\n\t\t\t\tcfg.Net.SASL.GSSAPI.Realm = \"kafka\"\n\t\t\t},\n\t\t\t\"Net.SASL.GSSAPI.KerberosConfigPath must not be empty when GSS-API mechanism is used\",\n\t\t},\n\t\t{\n\t\t\t\"SASL.Mechanism GSSAPI (Kerberos) - Missing Realm\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Net.SASL.Enable = true\n\t\t\t\tcfg.Net.SASL.GSSAPI.ServiceName = \"kafka\"\n\t\t\t\tcfg.Net.SASL.Mechanism = SASLTypeGSSAPI\n\t\t\t\tcfg.Net.SASL.GSSAPI.AuthType = KRB5_USER_AUTH\n\t\t\t\tcfg.Net.SASL.GSSAPI.Username = \"sarama\"\n\t\t\t\tcfg.Net.SASL.GSSAPI.Password = \"sarama\"\n\t\t\t\tcfg.Net.SASL.GSSAPI.KerberosConfigPath = \"/etc/krb5.conf\"\n\t\t\t},\n\t\t\t\"Net.SASL.GSSAPI.Realm must not be empty when GSS-API mechanism is used\",\n\t\t},\n\t\t{\n\t\t\t\"SASL.Mechanism GSSAPI (Kerberos) - Using Credentials Cache, Missing CCachePath field\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Net.SASL.Enable = true\n\t\t\t\tcfg.Net.SASL.GSSAPI.ServiceName = \"kafka\"\n\t\t\t\tcfg.Net.SASL.Mechanism = SASLTypeGSSAPI\n\t\t\t\tcfg.Net.SASL.GSSAPI.AuthType = KRB5_CCACHE_AUTH\n\t\t\t\tcfg.Net.SASL.GSSAPI.KerberosConfigPath = \"/etc/krb5.conf\"\n\t\t\t},\n\t\t\t\"Net.SASL.GSSAPI.CCachePath must not be empty when GSS-API mechanism is used\" +\n\t\t\t\t\" and Net.SASL.GSSAPI.AuthType = KRB5_CCACHE_AUTH\",\n\t\t},\n\t}\n\n\tfor i, test := range tests {\n\t\tc := NewTestConfig()\n\t\ttest.cfg(c)\n\t\terr := c.Validate()\n\t\tvar target ConfigurationError\n\t\tif !errors.As(err, &target) || string(target) != test.err {\n\t\t\tt.Errorf(\"[%d]:[%s] Expected %s, Got %s\\n\", i, test.name, test.err, err)\n\t\t}\n\t}\n}\n\nfunc TestMetadataConfigValidates(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tcfg  func(*Config) // resorting to using a function as a param because of internal composite structs\n\t\terr  string\n\t}{\n\t\t{\n\t\t\t\"Retry.Max\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Metadata.Retry.Max = -1\n\t\t\t},\n\t\t\t\"Metadata.Retry.Max must be >= 0\",\n\t\t},\n\t\t{\n\t\t\t\"Retry.Backoff\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Metadata.Retry.Backoff = -1\n\t\t\t},\n\t\t\t\"Metadata.Retry.Backoff must be >= 0\",\n\t\t},\n\t\t{\n\t\t\t\"RefreshFrequency\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Metadata.RefreshFrequency = -1\n\t\t\t},\n\t\t\t\"Metadata.RefreshFrequency must be >= 0\",\n\t\t},\n\t}\n\n\tfor i, test := range tests {\n\t\tc := NewTestConfig()\n\t\ttest.cfg(c)\n\t\terr := c.Validate()\n\t\tvar target ConfigurationError\n\t\tif !errors.As(err, &target) || string(target) != test.err {\n\t\t\tt.Errorf(\"[%d]:[%s] Expected %s, Got %s\\n\", i, test.name, test.err, err)\n\t\t}\n\t}\n}\n\nfunc TestAdminConfigValidates(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tcfg  func(*Config) // resorting to using a function as a param because of internal composite structs\n\t\terr  string\n\t}{\n\t\t{\n\t\t\t\"Timeout\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Admin.Timeout = 0\n\t\t\t},\n\t\t\t\"Admin.Timeout must be > 0\",\n\t\t},\n\t}\n\n\tfor i, test := range tests {\n\t\tc := NewTestConfig()\n\t\ttest.cfg(c)\n\t\terr := c.Validate()\n\t\tvar target ConfigurationError\n\t\tif !errors.As(err, &target) || string(target) != test.err {\n\t\t\tt.Errorf(\"[%d]:[%s] Expected %s, Got %s\\n\", i, test.name, test.err, err)\n\t\t}\n\t}\n}\n\nfunc TestProducerConfigValidates(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tcfg  func(*Config) // resorting to using a function as a param because of internal composite structs\n\t\terr  string\n\t}{\n\t\t{\n\t\t\t\"MaxMessageBytes\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Producer.MaxMessageBytes = 0\n\t\t\t},\n\t\t\t\"Producer.MaxMessageBytes must be > 0\",\n\t\t},\n\t\t{\n\t\t\t\"RequiredAcks\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Producer.RequiredAcks = -2\n\t\t\t},\n\t\t\t\"Producer.RequiredAcks must be >= -1\",\n\t\t},\n\t\t{\n\t\t\t\"Timeout\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Producer.Timeout = 0\n\t\t\t},\n\t\t\t\"Producer.Timeout must be > 0\",\n\t\t},\n\t\t{\n\t\t\t\"Partitioner\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Producer.Partitioner = nil\n\t\t\t},\n\t\t\t\"Producer.Partitioner must not be nil\",\n\t\t},\n\t\t{\n\t\t\t\"Flush.Bytes\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Producer.Flush.Bytes = -1\n\t\t\t},\n\t\t\t\"Producer.Flush.Bytes must be >= 0\",\n\t\t},\n\t\t{\n\t\t\t\"Flush.Messages\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Producer.Flush.Messages = -1\n\t\t\t},\n\t\t\t\"Producer.Flush.Messages must be >= 0\",\n\t\t},\n\t\t{\n\t\t\t\"Flush.Frequency\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Producer.Flush.Frequency = -1\n\t\t\t},\n\t\t\t\"Producer.Flush.Frequency must be >= 0\",\n\t\t},\n\t\t{\n\t\t\t\"Flush.MaxMessages\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Producer.Flush.MaxMessages = -1\n\t\t\t},\n\t\t\t\"Producer.Flush.MaxMessages must be >= 0\",\n\t\t},\n\t\t{\n\t\t\t\"Flush.MaxMessages with Producer.Flush.Messages\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Producer.Flush.MaxMessages = 1\n\t\t\t\tcfg.Producer.Flush.Messages = 2\n\t\t\t},\n\t\t\t\"Producer.Flush.MaxMessages must be >= Producer.Flush.Messages when set\",\n\t\t},\n\t\t{\n\t\t\t\"Flush.Retry.Max\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Producer.Retry.Max = -1\n\t\t\t},\n\t\t\t\"Producer.Retry.Max must be >= 0\",\n\t\t},\n\t\t{\n\t\t\t\"Flush.Retry.Backoff\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Producer.Retry.Backoff = -1\n\t\t\t},\n\t\t\t\"Producer.Retry.Backoff must be >= 0\",\n\t\t},\n\t\t{\n\t\t\t\"Idempotent Version\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Producer.Idempotent = true\n\t\t\t\tcfg.Version = V0_10_0_0\n\t\t\t},\n\t\t\t\"Idempotent producer requires Version >= V0_11_0_0\",\n\t\t},\n\t\t{\n\t\t\t\"Idempotent with Producer.Retry.Max\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Version = V0_11_0_0\n\t\t\t\tcfg.Producer.Idempotent = true\n\t\t\t\tcfg.Producer.Retry.Max = 0\n\t\t\t},\n\t\t\t\"Idempotent producer requires Producer.Retry.Max >= 1\",\n\t\t},\n\t\t{\n\t\t\t\"Idempotent with Producer.RequiredAcks\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Version = V0_11_0_0\n\t\t\t\tcfg.Producer.Idempotent = true\n\t\t\t},\n\t\t\t\"Idempotent producer requires Producer.RequiredAcks to be WaitForAll\",\n\t\t},\n\t\t{\n\t\t\t\"Idempotent with Net.MaxOpenRequests\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Version = V0_11_0_0\n\t\t\t\tcfg.Producer.Idempotent = true\n\t\t\t\tcfg.Producer.RequiredAcks = WaitForAll\n\t\t\t},\n\t\t\t\"Idempotent producer requires Net.MaxOpenRequests to be 1\",\n\t\t},\n\t}\n\n\tfor i, test := range tests {\n\t\tc := NewTestConfig()\n\t\ttest.cfg(c)\n\t\terr := c.Validate()\n\t\tvar target ConfigurationError\n\t\tif !errors.As(err, &target) || string(target) != test.err {\n\t\t\tt.Errorf(\"[%d]:[%s] Expected %s, Got %s\\n\", i, test.name, test.err, err)\n\t\t}\n\t}\n}\n\nfunc TestConsumerConfigValidates(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tcfg  func(*Config)\n\t\terr  string\n\t}{\n\t\t{\n\t\t\t\"ReadCommitted Version\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Version = V0_10_0_0\n\t\t\t\tcfg.Consumer.IsolationLevel = ReadCommitted\n\t\t\t},\n\t\t\t\"ReadCommitted requires Version >= V0_11_0_0\",\n\t\t},\n\t\t{\n\t\t\t\"Incorrect isolation level\",\n\t\t\tfunc(cfg *Config) {\n\t\t\t\tcfg.Version = V0_11_0_0\n\t\t\t\tcfg.Consumer.IsolationLevel = IsolationLevel(42)\n\t\t\t},\n\t\t\t\"Consumer.IsolationLevel must be ReadUncommitted or ReadCommitted\",\n\t\t},\n\t}\n\n\tfor i, test := range tests {\n\t\tc := NewTestConfig()\n\t\ttest.cfg(c)\n\t\terr := c.Validate()\n\t\tvar target ConfigurationError\n\t\tif !errors.As(err, &target) || string(target) != test.err {\n\t\t\tt.Errorf(\"[%d]:[%s] Expected %s, Got %s\\n\", i, test.name, test.err, err)\n\t\t}\n\t}\n}\n\nfunc TestLZ4ConfigValidation(t *testing.T) {\n\tconfig := NewTestConfig()\n\tconfig.Producer.Compression = CompressionLZ4\n\terr := config.Validate()\n\tvar target ConfigurationError\n\tif !errors.As(err, &target) || string(target) != \"lz4 compression requires Version >= V0_10_0_0\" {\n\t\tt.Error(\"Expected invalid lz4/kafka version error, got \", err)\n\t}\n\tconfig.Version = V0_10_0_0\n\tif err := config.Validate(); err != nil {\n\t\tt.Error(\"Expected lz4 to work, got \", err)\n\t}\n}\n\nfunc TestZstdConfigValidation(t *testing.T) {\n\tconfig := NewTestConfig()\n\tconfig.Producer.Compression = CompressionZSTD\n\terr := config.Validate()\n\tvar target ConfigurationError\n\tif !errors.As(err, &target) || string(target) != \"zstd compression requires Version >= V2_1_0_0\" {\n\t\tt.Error(\"Expected invalid zstd/kafka version error, got \", err)\n\t}\n\tconfig.Version = V2_1_0_0\n\tif err := config.Validate(); err != nil {\n\t\tt.Error(\"Expected zstd to work, got \", err)\n\t}\n}\n\nfunc TestValidGroupInstanceId(t *testing.T) {\n\ttests := []struct {\n\t\tgrouptInstanceId string\n\t\tshouldHaveErr    bool\n\t}{\n\t\t{\"groupInstanceId1\", false},\n\t\t{\"\", true},\n\t\t{\".\", true},\n\t\t{\"..\", true},\n\t\t{strings.Repeat(\"a\", 250), true},\n\t\t{\"group_InstanceId.1\", false},\n\t\t{\"group-InstanceId1\", false},\n\t\t{\"group#InstanceId1\", true},\n\t}\n\tfor _, testcase := range tests {\n\t\terr := validateGroupInstanceId(testcase.grouptInstanceId)\n\t\tif !testcase.shouldHaveErr {\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Expected validGroupInstanceId %s to pass, got error %v\", testcase.grouptInstanceId, err)\n\t\t\t}\n\t\t} else {\n\t\t\tif err == nil {\n\t\t\t\tt.Errorf(\"Expected validGroupInstanceId %s to be error, got nil\", testcase.grouptInstanceId)\n\t\t\t}\n\t\t\tvar target ConfigurationError\n\t\t\tif !errors.As(err, &target) {\n\t\t\t\tt.Errorf(\"Excepted err to be ConfigurationError, got %v\", err)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestGroupInstanceIdAndVersionValidation(t *testing.T) {\n\tconfig := NewTestConfig()\n\tconfig.Consumer.Group.InstanceId = \"groupInstanceId1\"\n\tif err := config.Validate(); !strings.Contains(err.Error(), \"Consumer.Group.InstanceId need Version >= 2.3\") {\n\t\tt.Error(\"Expected invalid group instance error, got \", err)\n\t}\n\tconfig.Version = V2_3_0_0\n\tif err := config.Validate(); err != nil {\n\t\tt.Error(\"Expected group instance to work, got \", err)\n\t}\n}\n\nfunc TestConsumerGroupStrategyCompatibility(t *testing.T) {\n\tconfig := NewTestConfig()\n\tconfig.Consumer.Group.Rebalance.Strategy = NewBalanceStrategySticky()\n\tif err := config.Validate(); err != nil {\n\t\tt.Error(\"Expected passing config validation, got \", err)\n\t}\n}\n\n// This example shows how to integrate with an existing registry as well as publishing metrics\n// on the standard output\nfunc ExampleConfig_metrics() {\n\t// Our application registry\n\tappMetricRegistry := metrics.NewRegistry()\n\tappGauge := metrics.GetOrRegisterGauge(\"m1\", appMetricRegistry)\n\tappGauge.Update(1)\n\n\tconfig := NewTestConfig()\n\t// Use a prefix registry instead of the default local one\n\tconfig.MetricRegistry = metrics.NewPrefixedChildRegistry(appMetricRegistry, \"sarama.\")\n\n\t// Simulate a metric created by sarama without starting a broker\n\tsaramaGauge := metrics.GetOrRegisterGauge(\"m2\", config.MetricRegistry)\n\tsaramaGauge.Update(2)\n\n\tmetrics.WriteOnce(appMetricRegistry, os.Stdout)\n\t// Output:\n\t// gauge m1\n\t//   value:               1\n\t// gauge sarama.m2\n\t//   value:               2\n}\n"
  },
  {
    "path": "consumer.go",
    "content": "package sarama\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/rcrowley/go-metrics\"\n)\n\n// ConsumerMessage encapsulates a Kafka message returned by the consumer.\ntype ConsumerMessage struct {\n\tHeaders        []*RecordHeader // only set if kafka is version 0.11+\n\tTimestamp      time.Time       // only set if kafka is version 0.10+, inner message timestamp\n\tBlockTimestamp time.Time       // only set if kafka is version 0.10+, outer (compressed) block timestamp\n\n\tKey, Value []byte\n\tTopic      string\n\tPartition  int32\n\tOffset     int64\n}\n\n// ConsumerError is what is provided to the user when an error occurs.\n// It wraps an error and includes the topic and partition.\ntype ConsumerError struct {\n\tTopic     string\n\tPartition int32\n\tErr       error\n}\n\nfunc (ce ConsumerError) Error() string {\n\treturn fmt.Sprintf(\"kafka: error while consuming %s/%d: %s\", ce.Topic, ce.Partition, ce.Err)\n}\n\nfunc (ce ConsumerError) Unwrap() error {\n\treturn ce.Err\n}\n\n// ConsumerErrors is a type that wraps a batch of errors and implements the Error interface.\n// It can be returned from the PartitionConsumer's Close methods to avoid the need to manually drain errors\n// when stopping.\ntype ConsumerErrors []*ConsumerError\n\nfunc (ce ConsumerErrors) Error() string {\n\treturn fmt.Sprintf(\"kafka: %d errors while consuming\", len(ce))\n}\n\n// Consumer manages PartitionConsumers which process Kafka messages from brokers. You MUST call Close()\n// on a consumer to avoid leaks, it will not be garbage-collected automatically when it passes out of\n// scope.\ntype Consumer interface {\n\t// Topics returns the set of available topics as retrieved from the cluster\n\t// metadata. This method is the same as Client.Topics(), and is provided for\n\t// convenience.\n\tTopics() ([]string, error)\n\n\t// Partitions returns the sorted list of all partition IDs for the given topic.\n\t// This method is the same as Client.Partitions(), and is provided for convenience.\n\tPartitions(topic string) ([]int32, error)\n\n\t// ConsumePartition creates a PartitionConsumer on the given topic/partition with\n\t// the given offset. It will return an error if this Consumer is already consuming\n\t// on the given topic/partition. Offset can be a literal offset, or OffsetNewest\n\t// or OffsetOldest\n\tConsumePartition(topic string, partition int32, offset int64) (PartitionConsumer, error)\n\n\t// HighWaterMarks returns the current high water marks for each topic and partition.\n\t// Consistency between partitions is not guaranteed since high water marks are updated separately.\n\tHighWaterMarks() map[string]map[int32]int64\n\n\t// Close shuts down the consumer. It must be called after all child\n\t// PartitionConsumers have already been closed.\n\tClose() error\n\n\t// Pause suspends fetching from the requested partitions. Future calls to the broker will not return any\n\t// records from these partitions until they have been resumed using Resume()/ResumeAll().\n\t// Note that this method does not affect partition subscription.\n\t// In particular, it does not cause a group rebalance when automatic assignment is used.\n\tPause(topicPartitions map[string][]int32)\n\n\t// Resume resumes specified partitions which have been paused with Pause()/PauseAll().\n\t// New calls to the broker will return records from these partitions if there are any to be fetched.\n\tResume(topicPartitions map[string][]int32)\n\n\t// PauseAll suspends fetching from all partitions. Future calls to the broker will not return any\n\t// records from these partitions until they have been resumed using Resume()/ResumeAll().\n\t// Note that this method does not affect partition subscription.\n\t// In particular, it does not cause a group rebalance when automatic assignment is used.\n\tPauseAll()\n\n\t// ResumeAll resumes all partitions which have been paused with Pause()/PauseAll().\n\t// New calls to the broker will return records from these partitions if there are any to be fetched.\n\tResumeAll()\n}\n\n// max time to wait for more partition subscriptions\nconst partitionConsumersBatchTimeout = 100 * time.Millisecond\n\ntype consumer struct {\n\tconf            *Config\n\tchildren        map[string]map[int32]*partitionConsumer\n\tbrokerConsumers map[*Broker]*brokerConsumer\n\tclient          Client\n\tmetricRegistry  metrics.Registry\n\tlock            sync.Mutex\n}\n\n// NewConsumer creates a new consumer using the given broker addresses and configuration.\nfunc NewConsumer(addrs []string, config *Config) (Consumer, error) {\n\tclient, err := NewClient(addrs, config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn newConsumer(client)\n}\n\n// NewConsumerFromClient creates a new consumer using the given client. It is still\n// necessary to call Close() on the underlying client when shutting down this consumer.\nfunc NewConsumerFromClient(client Client) (Consumer, error) {\n\t// For clients passed in by the client, ensure we don't\n\t// call Close() on it.\n\tcli := &nopCloserClient{client}\n\treturn newConsumer(cli)\n}\n\nfunc newConsumer(client Client) (Consumer, error) {\n\t// Check that we are not dealing with a closed Client before processing any other arguments\n\tif client.Closed() {\n\t\treturn nil, ErrClosedClient\n\t}\n\n\tc := &consumer{\n\t\tclient:          client,\n\t\tconf:            client.Config(),\n\t\tchildren:        make(map[string]map[int32]*partitionConsumer),\n\t\tbrokerConsumers: make(map[*Broker]*brokerConsumer),\n\t\tmetricRegistry:  newCleanupRegistry(client.Config().MetricRegistry),\n\t}\n\n\treturn c, nil\n}\n\nfunc (c *consumer) Close() error {\n\tc.metricRegistry.UnregisterAll()\n\treturn c.client.Close()\n}\n\nfunc (c *consumer) Topics() ([]string, error) {\n\treturn c.client.Topics()\n}\n\nfunc (c *consumer) Partitions(topic string) ([]int32, error) {\n\treturn c.client.Partitions(topic)\n}\n\nfunc (c *consumer) ConsumePartition(topic string, partition int32, offset int64) (PartitionConsumer, error) {\n\tchild := &partitionConsumer{\n\t\tconsumer:             c,\n\t\tconf:                 c.conf,\n\t\ttopic:                topic,\n\t\tpartition:            partition,\n\t\tmessages:             make(chan *ConsumerMessage, c.conf.ChannelBufferSize),\n\t\terrors:               make(chan *ConsumerError, c.conf.ChannelBufferSize),\n\t\tfeeder:               make(chan *FetchResponse, 1),\n\t\tleaderEpoch:          invalidLeaderEpoch,\n\t\tpreferredReadReplica: invalidPreferredReplicaID,\n\t\ttrigger:              make(chan none, 1),\n\t\tdying:                make(chan none),\n\t\tfetchSize:            c.conf.Consumer.Fetch.Default,\n\t}\n\n\tif err := child.chooseStartingOffset(offset); err != nil {\n\t\treturn nil, err\n\t}\n\n\tleader, epoch, err := c.client.LeaderAndEpoch(child.topic, child.partition)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := c.addChild(child); err != nil {\n\t\treturn nil, err\n\t}\n\n\tgo withRecover(child.dispatcher)\n\tgo withRecover(child.responseFeeder)\n\n\tchild.leaderEpoch = epoch\n\tchild.broker = c.refBrokerConsumer(leader)\n\tchild.broker.input <- child\n\n\treturn child, nil\n}\n\nfunc (c *consumer) HighWaterMarks() map[string]map[int32]int64 {\n\tc.lock.Lock()\n\tdefer c.lock.Unlock()\n\n\thwms := make(map[string]map[int32]int64)\n\tfor topic, p := range c.children {\n\t\thwm := make(map[int32]int64, len(p))\n\t\tfor partition, pc := range p {\n\t\t\thwm[partition] = pc.HighWaterMarkOffset()\n\t\t}\n\t\thwms[topic] = hwm\n\t}\n\n\treturn hwms\n}\n\nfunc (c *consumer) addChild(child *partitionConsumer) error {\n\tc.lock.Lock()\n\tdefer c.lock.Unlock()\n\n\ttopicChildren := c.children[child.topic]\n\tif topicChildren == nil {\n\t\ttopicChildren = make(map[int32]*partitionConsumer)\n\t\tc.children[child.topic] = topicChildren\n\t}\n\n\tif topicChildren[child.partition] != nil {\n\t\treturn ConfigurationError(\"That topic/partition is already being consumed\")\n\t}\n\n\ttopicChildren[child.partition] = child\n\treturn nil\n}\n\nfunc (c *consumer) removeChild(child *partitionConsumer) {\n\tc.lock.Lock()\n\tdefer c.lock.Unlock()\n\n\tdelete(c.children[child.topic], child.partition)\n}\n\nfunc (c *consumer) refBrokerConsumer(broker *Broker) *brokerConsumer {\n\tc.lock.Lock()\n\tdefer c.lock.Unlock()\n\n\tbc := c.brokerConsumers[broker]\n\tif bc == nil {\n\t\tbc = c.newBrokerConsumer(broker)\n\t\tc.brokerConsumers[broker] = bc\n\t}\n\n\tbc.refs++\n\n\treturn bc\n}\n\nfunc (c *consumer) unrefBrokerConsumer(brokerWorker *brokerConsumer) {\n\tc.lock.Lock()\n\tdefer c.lock.Unlock()\n\n\tbrokerWorker.refs--\n\n\tif brokerWorker.refs == 0 {\n\t\tclose(brokerWorker.input)\n\t\tif c.brokerConsumers[brokerWorker.broker] == brokerWorker {\n\t\t\tdelete(c.brokerConsumers, brokerWorker.broker)\n\t\t}\n\t}\n}\n\nfunc (c *consumer) abandonBrokerConsumer(brokerWorker *brokerConsumer) {\n\tc.lock.Lock()\n\tdefer c.lock.Unlock()\n\n\tdelete(c.brokerConsumers, brokerWorker.broker)\n}\n\n// Pause implements Consumer.\nfunc (c *consumer) Pause(topicPartitions map[string][]int32) {\n\tc.lock.Lock()\n\tdefer c.lock.Unlock()\n\n\tfor topic, partitions := range topicPartitions {\n\t\tfor _, partition := range partitions {\n\t\t\tif topicConsumers, ok := c.children[topic]; ok {\n\t\t\t\tif partitionConsumer, ok := topicConsumers[partition]; ok {\n\t\t\t\t\tpartitionConsumer.Pause()\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Resume implements Consumer.\nfunc (c *consumer) Resume(topicPartitions map[string][]int32) {\n\tc.lock.Lock()\n\tdefer c.lock.Unlock()\n\n\tfor topic, partitions := range topicPartitions {\n\t\tfor _, partition := range partitions {\n\t\t\tif topicConsumers, ok := c.children[topic]; ok {\n\t\t\t\tif partitionConsumer, ok := topicConsumers[partition]; ok {\n\t\t\t\t\tpartitionConsumer.Resume()\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n// PauseAll implements Consumer.\nfunc (c *consumer) PauseAll() {\n\tc.lock.Lock()\n\tdefer c.lock.Unlock()\n\n\tfor _, partitions := range c.children {\n\t\tfor _, partitionConsumer := range partitions {\n\t\t\tpartitionConsumer.Pause()\n\t\t}\n\t}\n}\n\n// ResumeAll implements Consumer.\nfunc (c *consumer) ResumeAll() {\n\tc.lock.Lock()\n\tdefer c.lock.Unlock()\n\n\tfor _, partitions := range c.children {\n\t\tfor _, partitionConsumer := range partitions {\n\t\t\tpartitionConsumer.Resume()\n\t\t}\n\t}\n}\n\n// PartitionConsumer\n\n// PartitionConsumer processes Kafka messages from a given topic and partition. You MUST call one of Close() or\n// AsyncClose() on a PartitionConsumer to avoid leaks; it will not be garbage-collected automatically when it passes out\n// of scope.\n//\n// The simplest way of using a PartitionConsumer is to loop over its Messages channel using a for/range\n// loop. The PartitionConsumer will only stop itself in one case: when the offset being consumed is reported\n// as out of range by the brokers. In this case you should decide what you want to do (try a different offset,\n// notify a human, etc) and handle it appropriately. For all other error cases, it will just keep retrying.\n// By default, it logs these errors to sarama.Logger; if you want to be notified directly of all errors, set\n// your config's Consumer.Return.Errors to true and read from the Errors channel, using a select statement\n// or a separate goroutine. Check out the Consumer examples to see implementations of these different approaches.\n//\n// To terminate such a for/range loop while the loop is executing, call AsyncClose. This will kick off the process of\n// consumer tear-down & return immediately. Continue to loop, servicing the Messages channel until the teardown process\n// AsyncClose initiated closes it (thus terminating the for/range loop). If you've already ceased reading Messages, call\n// Close; this will signal the PartitionConsumer's goroutines to begin shutting down (just like AsyncClose), but will\n// also drain the Messages channel, harvest all errors & return them once cleanup has completed.\ntype PartitionConsumer interface {\n\t// AsyncClose initiates a shutdown of the PartitionConsumer. This method will return immediately, after which you\n\t// should continue to service the 'Messages' and 'Errors' channels until they are empty. It is required to call this\n\t// function, or Close before a consumer object passes out of scope, as it will otherwise leak memory. You must call\n\t// this before calling Close on the underlying client.\n\tAsyncClose()\n\n\t// Close stops the PartitionConsumer from fetching messages. It will initiate a shutdown just like AsyncClose, drain\n\t// the Messages channel, harvest any errors & return them to the caller. Note that if you are continuing to service\n\t// the Messages channel when this function is called, you will be competing with Close for messages; consider\n\t// calling AsyncClose, instead. It is required to call this function (or AsyncClose) before a consumer object passes\n\t// out of scope, as it will otherwise leak memory. You must call this before calling Close on the underlying client.\n\tClose() error\n\n\t// Messages returns the read channel for the messages that are returned by\n\t// the broker.\n\tMessages() <-chan *ConsumerMessage\n\n\t// Errors returns a read channel of errors that occurred during consuming, if\n\t// enabled. By default, errors are logged and not returned over this channel.\n\t// If you want to implement any custom error handling, set your config's\n\t// Consumer.Return.Errors setting to true, and read from this channel.\n\tErrors() <-chan *ConsumerError\n\n\t// HighWaterMarkOffset returns the high water mark offset of the partition,\n\t// i.e. the offset that will be used for the next message that will be produced.\n\t// You can use this to determine how far behind the processing is.\n\tHighWaterMarkOffset() int64\n\n\t// Pause suspends fetching from this partition. Future calls to the broker will not return\n\t// any records from these partition until it have been resumed using Resume().\n\t// Note that this method does not affect partition subscription.\n\t// In particular, it does not cause a group rebalance when automatic assignment is used.\n\tPause()\n\n\t// Resume resumes this partition which have been paused with Pause().\n\t// New calls to the broker will return records from these partitions if there are any to be fetched.\n\t// If the partition was not previously paused, this method is a no-op.\n\tResume()\n\n\t// IsPaused indicates if this partition consumer is paused or not\n\tIsPaused() bool\n}\n\ntype partitionConsumer struct {\n\thighWaterMarkOffset atomic.Int64 // must be at the top of the struct because https://golang.org/pkg/sync/atomic/#pkg-note-BUG\n\n\tconsumer *consumer\n\tconf     *Config\n\tbroker   *brokerConsumer\n\tmessages chan *ConsumerMessage\n\terrors   chan *ConsumerError\n\tfeeder   chan *FetchResponse\n\n\tleaderEpoch          int32\n\tpreferredReadReplica int32\n\n\ttrigger, dying chan none\n\tcloseOnce      sync.Once\n\ttopic          string\n\tpartition      int32\n\tresponseResult error\n\tfetchSize      int32\n\toffset         int64\n\tretries        atomic.Int32\n\n\tpaused atomic.Bool // accessed atomically, 0 = not paused, 1 = paused\n}\n\nvar errTimedOut = errors.New(\"timed out feeding messages to the user\") // not user-facing\n\nfunc (child *partitionConsumer) sendError(err error) {\n\tcErr := &ConsumerError{\n\t\tTopic:     child.topic,\n\t\tPartition: child.partition,\n\t\tErr:       err,\n\t}\n\n\tif child.conf.Consumer.Return.Errors {\n\t\tchild.errors <- cErr\n\t} else {\n\t\tLogger.Println(cErr)\n\t}\n}\n\nfunc (child *partitionConsumer) computeBackoff() time.Duration {\n\tif child.conf.Consumer.Retry.BackoffFunc != nil {\n\t\tretries := child.retries.Add(1)\n\t\treturn child.conf.Consumer.Retry.BackoffFunc(int(retries))\n\t}\n\treturn child.conf.Consumer.Retry.Backoff\n}\n\nfunc (child *partitionConsumer) dispatcher() {\n\tfor range child.trigger {\n\t\tselect {\n\t\tcase <-child.dying:\n\t\t\tclose(child.trigger)\n\t\tcase <-time.After(child.computeBackoff()):\n\t\t\tif child.broker != nil {\n\t\t\t\tchild.consumer.unrefBrokerConsumer(child.broker)\n\t\t\t\tchild.broker = nil\n\t\t\t}\n\n\t\t\tif err := child.dispatch(); err != nil {\n\t\t\t\tchild.sendError(err)\n\t\t\t\tchild.trigger <- none{}\n\t\t\t}\n\t\t}\n\t}\n\n\tif child.broker != nil {\n\t\tchild.consumer.unrefBrokerConsumer(child.broker)\n\t}\n\tchild.consumer.removeChild(child)\n\tclose(child.feeder)\n}\n\nfunc (child *partitionConsumer) preferredBroker() (*Broker, int32, error) {\n\tif child.preferredReadReplica >= 0 {\n\t\tbroker, err := child.consumer.client.Broker(child.preferredReadReplica)\n\t\tif err == nil {\n\t\t\treturn broker, child.leaderEpoch, nil\n\t\t}\n\t\tLogger.Printf(\n\t\t\t\"consumer/%s/%d failed to find active broker for preferred read replica %d - will fallback to leader\",\n\t\t\tchild.topic, child.partition, child.preferredReadReplica)\n\n\t\t// if we couldn't find it, discard the replica preference and trigger a\n\t\t// metadata refresh whilst falling back to consuming from the leader again\n\t\tchild.preferredReadReplica = invalidPreferredReplicaID\n\t\t_ = child.consumer.client.RefreshMetadata(child.topic)\n\t}\n\n\t// if preferred replica cannot be found fallback to leader\n\treturn child.consumer.client.LeaderAndEpoch(child.topic, child.partition)\n}\n\nfunc (child *partitionConsumer) dispatch() error {\n\tif err := child.consumer.client.RefreshMetadata(child.topic); err != nil {\n\t\treturn err\n\t}\n\n\tbroker, epoch, err := child.preferredBroker()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tchild.leaderEpoch = epoch\n\tchild.broker = child.consumer.refBrokerConsumer(broker)\n\tchild.broker.input <- child\n\n\treturn nil\n}\n\nfunc (child *partitionConsumer) chooseStartingOffset(offset int64) error {\n\tnewestOffset, err := child.consumer.client.GetOffset(child.topic, child.partition, OffsetNewest)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tchild.highWaterMarkOffset.Store(newestOffset)\n\n\toldestOffset, err := child.consumer.client.GetOffset(child.topic, child.partition, OffsetOldest)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch {\n\tcase offset == OffsetNewest:\n\t\tchild.offset = newestOffset\n\tcase offset == OffsetOldest:\n\t\tchild.offset = oldestOffset\n\tcase offset >= oldestOffset && offset <= newestOffset:\n\t\tchild.offset = offset\n\tdefault:\n\t\treturn ErrOffsetOutOfRange\n\t}\n\n\treturn nil\n}\n\nfunc (child *partitionConsumer) Messages() <-chan *ConsumerMessage {\n\treturn child.messages\n}\n\nfunc (child *partitionConsumer) Errors() <-chan *ConsumerError {\n\treturn child.errors\n}\n\nfunc (child *partitionConsumer) AsyncClose() {\n\t// this triggers whatever broker owns this child to abandon it and close its trigger channel, which causes\n\t// the dispatcher to exit its loop, which removes it from the consumer then closes its 'messages' and\n\t// 'errors' channel (alternatively, if the child is already at the dispatcher for some reason, that will\n\t// also just close itself)\n\tchild.closeOnce.Do(func() {\n\t\tclose(child.dying)\n\t})\n}\n\nfunc (child *partitionConsumer) Close() error {\n\tchild.AsyncClose()\n\n\tvar consumerErrors ConsumerErrors\n\tfor err := range child.errors {\n\t\tconsumerErrors = append(consumerErrors, err)\n\t}\n\n\tif len(consumerErrors) > 0 {\n\t\treturn consumerErrors\n\t}\n\treturn nil\n}\n\nfunc (child *partitionConsumer) HighWaterMarkOffset() int64 {\n\treturn child.highWaterMarkOffset.Load()\n}\n\nfunc (child *partitionConsumer) responseFeeder() {\n\tvar msgs []*ConsumerMessage\n\texpiryTicker := time.NewTicker(child.conf.Consumer.MaxProcessingTime)\n\tfirstAttempt := true\n\nfeederLoop:\n\tfor response := range child.feeder {\n\t\tmsgs, child.responseResult = child.parseResponse(response)\n\n\t\tif child.responseResult == nil {\n\t\t\tchild.retries.Store(0)\n\t\t}\n\n\t\tfor i, msg := range msgs {\n\t\t\tchild.interceptors(msg)\n\t\tmessageSelect:\n\t\t\tselect {\n\t\t\tcase <-child.dying:\n\t\t\t\tchild.broker.acks.Done()\n\t\t\t\tcontinue feederLoop\n\t\t\tcase child.messages <- msg:\n\t\t\t\tfirstAttempt = true\n\t\t\tcase <-expiryTicker.C:\n\t\t\t\tif !firstAttempt {\n\t\t\t\t\tchild.responseResult = errTimedOut\n\t\t\t\t\tchild.broker.acks.Done()\n\t\t\t\tremainingLoop:\n\t\t\t\t\tfor _, msg = range msgs[i:] {\n\t\t\t\t\t\tchild.interceptors(msg)\n\t\t\t\t\t\tselect {\n\t\t\t\t\t\tcase child.messages <- msg:\n\t\t\t\t\t\tcase <-child.dying:\n\t\t\t\t\t\t\tbreak remainingLoop\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tchild.broker.input <- child\n\t\t\t\t\tcontinue feederLoop\n\t\t\t\t} else {\n\t\t\t\t\t// current message has not been sent, return to select\n\t\t\t\t\t// statement\n\t\t\t\t\tfirstAttempt = false\n\t\t\t\t\tgoto messageSelect\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tchild.broker.acks.Done()\n\t}\n\n\texpiryTicker.Stop()\n\tclose(child.messages)\n\tclose(child.errors)\n}\n\nfunc (child *partitionConsumer) parseMessages(msgSet *MessageSet) ([]*ConsumerMessage, error) {\n\tvar messages []*ConsumerMessage\n\tfor _, msgBlock := range msgSet.Messages {\n\t\tfor _, msg := range msgBlock.Messages() {\n\t\t\toffset := msg.Offset\n\t\t\ttimestamp := msg.Msg.Timestamp\n\t\t\tif msg.Msg.Version >= 1 {\n\t\t\t\tbaseOffset := msgBlock.Offset - msgBlock.Messages()[len(msgBlock.Messages())-1].Offset\n\t\t\t\toffset += baseOffset\n\t\t\t\tif msg.Msg.LogAppendTime {\n\t\t\t\t\ttimestamp = msgBlock.Msg.Timestamp\n\t\t\t\t}\n\t\t\t}\n\t\t\tif offset < child.offset {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tmessages = append(messages, &ConsumerMessage{\n\t\t\t\tTopic:          child.topic,\n\t\t\t\tPartition:      child.partition,\n\t\t\t\tKey:            msg.Msg.Key,\n\t\t\t\tValue:          msg.Msg.Value,\n\t\t\t\tOffset:         offset,\n\t\t\t\tTimestamp:      timestamp,\n\t\t\t\tBlockTimestamp: msgBlock.Msg.Timestamp,\n\t\t\t})\n\t\t\tchild.offset = offset + 1\n\t\t}\n\t}\n\tif len(messages) == 0 {\n\t\tchild.offset++\n\t}\n\treturn messages, nil\n}\n\nfunc (child *partitionConsumer) parseRecords(batch *RecordBatch) ([]*ConsumerMessage, error) {\n\tmessages := make([]*ConsumerMessage, 0, len(batch.Records))\n\n\tfor _, rec := range batch.Records {\n\t\toffset := batch.FirstOffset + rec.OffsetDelta\n\t\tif offset < child.offset {\n\t\t\tcontinue\n\t\t}\n\t\ttimestamp := batch.FirstTimestamp.Add(rec.TimestampDelta)\n\t\tif batch.LogAppendTime {\n\t\t\ttimestamp = batch.MaxTimestamp\n\t\t}\n\t\tmessages = append(messages, &ConsumerMessage{\n\t\t\tTopic:     child.topic,\n\t\t\tPartition: child.partition,\n\t\t\tKey:       rec.Key,\n\t\t\tValue:     rec.Value,\n\t\t\tOffset:    offset,\n\t\t\tTimestamp: timestamp,\n\t\t\tHeaders:   rec.Headers,\n\t\t})\n\t\tchild.offset = offset + 1\n\t}\n\tif len(messages) == 0 {\n\t\tchild.offset++\n\t}\n\treturn messages, nil\n}\n\nfunc (child *partitionConsumer) parseResponse(response *FetchResponse) ([]*ConsumerMessage, error) {\n\tvar consumerBatchSizeMetric metrics.Histogram\n\tif child.consumer != nil && child.consumer.metricRegistry != nil {\n\t\tconsumerBatchSizeMetric = getOrRegisterHistogram(\"consumer-batch-size\", child.consumer.metricRegistry)\n\t}\n\n\t// If request was throttled and empty we log and return without error\n\tif response.ThrottleTime != time.Duration(0) && len(response.Blocks) == 0 {\n\t\tLogger.Printf(\n\t\t\t\"consumer/broker/%d FetchResponse throttled %v\\n\",\n\t\t\tchild.broker.broker.ID(), response.ThrottleTime)\n\t\treturn nil, nil\n\t}\n\n\tblock := response.GetBlock(child.topic, child.partition)\n\tif block == nil {\n\t\treturn nil, ErrIncompleteResponse\n\t}\n\n\tif !errors.Is(block.Err, ErrNoError) {\n\t\treturn nil, block.Err\n\t}\n\n\tnRecs, err := block.numRecords()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif consumerBatchSizeMetric != nil {\n\t\tconsumerBatchSizeMetric.Update(int64(nRecs))\n\t}\n\n\tif block.PreferredReadReplica != invalidPreferredReplicaID {\n\t\tchild.preferredReadReplica = block.PreferredReadReplica\n\t}\n\n\tif nRecs == 0 {\n\t\tpartialTrailingMessage, err := block.isPartial()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\t// We got no messages. If we got a trailing one then we need to ask for more data.\n\t\t// Otherwise we just poll again and wait for one to be produced...\n\t\tif partialTrailingMessage {\n\t\t\tif child.conf.Consumer.Fetch.Max > 0 && child.fetchSize == child.conf.Consumer.Fetch.Max {\n\t\t\t\t// we can't ask for more data, we've hit the configured limit\n\t\t\t\tchild.sendError(ErrMessageTooLarge)\n\t\t\t\tchild.offset++ // skip this one so we can keep processing future messages\n\t\t\t} else {\n\t\t\t\tchild.fetchSize *= 2\n\t\t\t\t// check int32 overflow\n\t\t\t\tif child.fetchSize < 0 {\n\t\t\t\t\tchild.fetchSize = math.MaxInt32\n\t\t\t\t}\n\t\t\t\tif child.conf.Consumer.Fetch.Max > 0 && child.fetchSize > child.conf.Consumer.Fetch.Max {\n\t\t\t\t\tchild.fetchSize = child.conf.Consumer.Fetch.Max\n\t\t\t\t}\n\t\t\t}\n\t\t} else if block.recordsNextOffset != nil && *block.recordsNextOffset <= block.HighWaterMarkOffset {\n\t\t\t// check last record next offset to avoid stuck if high watermark was not reached\n\t\t\tLogger.Printf(\"consumer/broker/%d received batch with zero records but high watermark was not reached, topic %s, partition %d, next offset %d\\n\", child.broker.broker.ID(), child.topic, child.partition, *block.recordsNextOffset)\n\t\t\tchild.offset = *block.recordsNextOffset\n\t\t}\n\n\t\treturn nil, nil\n\t}\n\n\t// we got messages, reset our fetch size in case it was increased for a previous request\n\tchild.fetchSize = child.conf.Consumer.Fetch.Default\n\tchild.highWaterMarkOffset.Store(block.HighWaterMarkOffset)\n\n\t// abortedProducerIDs contains producerID which message should be ignored as uncommitted\n\t// - producerID are added when the partitionConsumer iterate over the offset at which an aborted transaction begins (abortedTransaction.FirstOffset)\n\t// - producerID are removed when partitionConsumer iterate over an aborted controlRecord, meaning the aborted transaction for this producer is over\n\tabortedProducerIDs := make(map[int64]struct{}, len(block.AbortedTransactions))\n\tabortedTransactions := block.getAbortedTransactions()\n\n\tvar messages []*ConsumerMessage\n\tfor _, records := range block.RecordsSet {\n\t\tswitch records.recordsType {\n\t\tcase legacyRecords:\n\t\t\tmessageSetMessages, err := child.parseMessages(records.MsgSet)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tmessages = append(messages, messageSetMessages...)\n\t\tcase defaultRecords:\n\t\t\t// Consume remaining abortedTransaction up to last offset of current batch\n\t\t\tfor _, txn := range abortedTransactions {\n\t\t\t\tif txn.FirstOffset > records.RecordBatch.LastOffset() {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tabortedProducerIDs[txn.ProducerID] = struct{}{}\n\t\t\t\t// Pop abortedTransactions so that we never add it again\n\t\t\t\tabortedTransactions = abortedTransactions[1:]\n\t\t\t}\n\n\t\t\trecordBatchMessages, err := child.parseRecords(records.RecordBatch)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\t// Parse and commit offset but do not expose messages that are:\n\t\t\t// - control records\n\t\t\t// - part of an aborted transaction when set to `ReadCommitted`\n\n\t\t\t// control record\n\t\t\tisControl, err := records.isControl()\n\t\t\tif err != nil {\n\t\t\t\t// I don't know why there is this continue in case of error to begin with\n\t\t\t\t// Safe bet is to ignore control messages if ReadUncommitted\n\t\t\t\t// and block on them in case of error and ReadCommitted\n\t\t\t\tif child.conf.Consumer.IsolationLevel == ReadCommitted {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif isControl {\n\t\t\t\tcontrolRecord, err := records.getControlRecord()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tif controlRecord.Type == ControlRecordAbort {\n\t\t\t\t\tdelete(abortedProducerIDs, records.RecordBatch.ProducerID)\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// filter aborted transactions\n\t\t\tif child.conf.Consumer.IsolationLevel == ReadCommitted {\n\t\t\t\t_, isAborted := abortedProducerIDs[records.RecordBatch.ProducerID]\n\t\t\t\tif records.RecordBatch.IsTransactional && isAborted {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tmessages = append(messages, recordBatchMessages...)\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"unknown records type: %v\", records.recordsType)\n\t\t}\n\t}\n\n\treturn messages, nil\n}\n\nfunc (child *partitionConsumer) interceptors(msg *ConsumerMessage) {\n\tfor _, interceptor := range child.conf.Consumer.Interceptors {\n\t\tmsg.safelyApplyInterceptor(interceptor)\n\t}\n}\n\n// Pause implements PartitionConsumer.\nfunc (child *partitionConsumer) Pause() {\n\tchild.paused.Store(true)\n}\n\n// Resume implements PartitionConsumer.\nfunc (child *partitionConsumer) Resume() {\n\tchild.paused.Store(false)\n}\n\n// IsPaused implements PartitionConsumer.\nfunc (child *partitionConsumer) IsPaused() bool {\n\treturn child.paused.Load()\n}\n\ntype brokerConsumer struct {\n\tconsumer         *consumer\n\tbroker           *Broker\n\tinput            chan *partitionConsumer\n\tnewSubscriptions chan []*partitionConsumer\n\tsubscriptions    map[*partitionConsumer]none\n\tacks             sync.WaitGroup\n\trefs             int\n}\n\nfunc (c *consumer) newBrokerConsumer(broker *Broker) *brokerConsumer {\n\tbc := &brokerConsumer{\n\t\tconsumer:         c,\n\t\tbroker:           broker,\n\t\tinput:            make(chan *partitionConsumer),\n\t\tnewSubscriptions: make(chan []*partitionConsumer),\n\t\tsubscriptions:    make(map[*partitionConsumer]none),\n\t\trefs:             0,\n\t}\n\n\tgo withRecover(bc.subscriptionManager)\n\tgo withRecover(bc.subscriptionConsumer)\n\n\treturn bc\n}\n\n// The subscriptionManager constantly accepts new subscriptions on `input` (even when the main subscriptionConsumer\n// goroutine is in the middle of a network request) and batches it up. The main worker goroutine picks\n// up a batch of new subscriptions between every network request by reading from `newSubscriptions`, so we give\n// it nil if no new subscriptions are available.\nfunc (bc *brokerConsumer) subscriptionManager() {\n\tdefer close(bc.newSubscriptions)\n\n\tfor {\n\t\tvar partitionConsumers []*partitionConsumer\n\n\t\t// Check for any partition consumer asking to subscribe if there aren't\n\t\t// any, trigger the network request (to fetch Kafka messages) by sending \"nil\" to the\n\t\t// newSubscriptions channel\n\t\tselect {\n\t\tcase pc, ok := <-bc.input:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tpartitionConsumers = append(partitionConsumers, pc)\n\t\tcase bc.newSubscriptions <- nil:\n\t\t\tcontinue\n\t\t}\n\n\t\t// drain input of any further incoming subscriptions\n\t\ttimer := time.NewTimer(partitionConsumersBatchTimeout)\n\t\tfor batchComplete := false; !batchComplete; {\n\t\t\tselect {\n\t\t\tcase pc := <-bc.input:\n\t\t\t\tpartitionConsumers = append(partitionConsumers, pc)\n\t\t\tcase <-timer.C:\n\t\t\t\tbatchComplete = true\n\t\t\t}\n\t\t}\n\t\ttimer.Stop()\n\n\t\tLogger.Printf(\n\t\t\t\"consumer/broker/%d accumulated %d new subscriptions\\n\",\n\t\t\tbc.broker.ID(), len(partitionConsumers))\n\n\t\tbc.newSubscriptions <- partitionConsumers\n\t}\n}\n\n// subscriptionConsumer ensures we will get nil right away if no new subscriptions is available\n// this is the main loop that fetches Kafka messages\nfunc (bc *brokerConsumer) subscriptionConsumer() {\n\tfor newSubscriptions := range bc.newSubscriptions {\n\t\tbc.updateSubscriptions(newSubscriptions)\n\n\t\tif len(bc.subscriptions) == 0 {\n\t\t\t// We're about to be shut down or we're about to receive more subscriptions.\n\t\t\t// Take a small nap to avoid burning the CPU.\n\t\t\ttime.Sleep(partitionConsumersBatchTimeout)\n\t\t\tcontinue\n\t\t}\n\n\t\tresponse, err := bc.fetchNewMessages()\n\t\tif err != nil {\n\t\t\tLogger.Printf(\"consumer/broker/%d disconnecting due to error processing FetchRequest: %s\\n\", bc.broker.ID(), err)\n\t\t\tbc.abort(err)\n\t\t\treturn\n\t\t}\n\n\t\t// if there isn't response, it means that not fetch was made\n\t\t// so we don't need to handle any response\n\t\tif response == nil {\n\t\t\ttime.Sleep(partitionConsumersBatchTimeout)\n\t\t\tcontinue\n\t\t}\n\n\t\tbc.acks.Add(len(bc.subscriptions))\n\t\tfor child := range bc.subscriptions {\n\t\t\tif _, ok := response.Blocks[child.topic]; !ok {\n\t\t\t\tbc.acks.Done()\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif _, ok := response.Blocks[child.topic][child.partition]; !ok {\n\t\t\t\tbc.acks.Done()\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tchild.feeder <- response\n\t\t}\n\t\tbc.acks.Wait()\n\t\tbc.handleResponses()\n\t}\n}\n\nfunc (bc *brokerConsumer) updateSubscriptions(newSubscriptions []*partitionConsumer) {\n\tfor _, child := range newSubscriptions {\n\t\tbc.subscriptions[child] = none{}\n\t\tLogger.Printf(\"consumer/broker/%d added subscription to %s/%d\\n\", bc.broker.ID(), child.topic, child.partition)\n\t}\n\n\tfor child := range bc.subscriptions {\n\t\tselect {\n\t\tcase <-child.dying:\n\t\t\tLogger.Printf(\"consumer/broker/%d closed dead subscription to %s/%d\\n\", bc.broker.ID(), child.topic, child.partition)\n\t\t\tclose(child.trigger)\n\t\t\tdelete(bc.subscriptions, child)\n\t\tdefault:\n\t\t\t// no-op\n\t\t}\n\t}\n}\n\n// handleResponses handles the response codes left for us by our subscriptions, and abandons ones that have been closed\nfunc (bc *brokerConsumer) handleResponses() {\n\tfor child := range bc.subscriptions {\n\t\tresult := child.responseResult\n\t\tchild.responseResult = nil\n\n\t\tif result == nil {\n\t\t\tif preferredBroker, _, err := child.preferredBroker(); err == nil {\n\t\t\t\tif bc.broker.ID() != preferredBroker.ID() {\n\t\t\t\t\t// not an error but needs redispatching to consume from preferred replica\n\t\t\t\t\tLogger.Printf(\n\t\t\t\t\t\t\"consumer/broker/%d abandoned in favor of preferred replica broker/%d\\n\",\n\t\t\t\t\t\tbc.broker.ID(), preferredBroker.ID())\n\t\t\t\t\tchild.trigger <- none{}\n\t\t\t\t\tdelete(bc.subscriptions, child)\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\t// Discard any replica preference.\n\t\tchild.preferredReadReplica = invalidPreferredReplicaID\n\n\t\tif errors.Is(result, errTimedOut) {\n\t\t\tLogger.Printf(\"consumer/broker/%d abandoned subscription to %s/%d because consuming was taking too long\\n\",\n\t\t\t\tbc.broker.ID(), child.topic, child.partition)\n\t\t\tdelete(bc.subscriptions, child)\n\t\t} else if errors.Is(result, ErrOffsetOutOfRange) {\n\t\t\t// there's no point in retrying this it will just fail the same way again\n\t\t\t// shut it down and force the user to choose what to do\n\t\t\tchild.sendError(result)\n\t\t\tLogger.Printf(\"consumer/%s/%d shutting down because %s\\n\", child.topic, child.partition, result)\n\t\t\tclose(child.trigger)\n\t\t\tdelete(bc.subscriptions, child)\n\t\t} else if errors.Is(result, ErrUnknownTopicOrPartition) ||\n\t\t\terrors.Is(result, ErrNotLeaderForPartition) ||\n\t\t\terrors.Is(result, ErrLeaderNotAvailable) ||\n\t\t\terrors.Is(result, ErrReplicaNotAvailable) ||\n\t\t\terrors.Is(result, ErrFencedLeaderEpoch) ||\n\t\t\terrors.Is(result, ErrUnknownLeaderEpoch) {\n\t\t\t// not an error, but does need redispatching\n\t\t\tLogger.Printf(\"consumer/broker/%d abandoned subscription to %s/%d because %s\\n\",\n\t\t\t\tbc.broker.ID(), child.topic, child.partition, result)\n\t\t\tchild.trigger <- none{}\n\t\t\tdelete(bc.subscriptions, child)\n\t\t} else {\n\t\t\t// dunno, tell the user and try redispatching\n\t\t\tchild.sendError(result)\n\t\t\tLogger.Printf(\"consumer/broker/%d abandoned subscription to %s/%d because %s\\n\",\n\t\t\t\tbc.broker.ID(), child.topic, child.partition, result)\n\t\t\tchild.trigger <- none{}\n\t\t\tdelete(bc.subscriptions, child)\n\t\t}\n\t}\n}\n\nfunc (bc *brokerConsumer) abort(err error) {\n\tbc.consumer.abandonBrokerConsumer(bc)\n\t_ = bc.broker.Close() // we don't care about the error this might return, we already have one\n\n\tfor child := range bc.subscriptions {\n\t\tchild.sendError(err)\n\t\tchild.trigger <- none{}\n\t}\n\n\tfor newSubscriptions := range bc.newSubscriptions {\n\t\tif len(newSubscriptions) == 0 {\n\t\t\t// Take a small nap to avoid burning the CPU.\n\t\t\ttime.Sleep(partitionConsumersBatchTimeout)\n\t\t\tcontinue\n\t\t}\n\t\tfor _, child := range newSubscriptions {\n\t\t\tchild.sendError(err)\n\t\t\tchild.trigger <- none{}\n\t\t}\n\t}\n}\n\n// fetchNewMessages can be nil if no fetch is made, it can occur when\n// all partitions are paused\nfunc (bc *brokerConsumer) fetchNewMessages() (*FetchResponse, error) {\n\trequest := &FetchRequest{\n\t\tMinBytes:    bc.consumer.conf.Consumer.Fetch.Min,\n\t\tMaxWaitTime: int32(bc.consumer.conf.Consumer.MaxWaitTime / time.Millisecond),\n\t}\n\t// Version 1 is the same as version 0.\n\tif bc.consumer.conf.Version.IsAtLeast(V0_9_0_0) {\n\t\trequest.Version = 1\n\t}\n\t// Starting in Version 2, the requestor must be able to handle Kafka Log\n\t// Message format version 1.\n\tif bc.consumer.conf.Version.IsAtLeast(V0_10_0_0) {\n\t\trequest.Version = 2\n\t}\n\t// Version 3 adds MaxBytes.  Starting in version 3, the partition ordering in\n\t// the request is now relevant.  Partitions will be processed in the order\n\t// they appear in the request.\n\tif bc.consumer.conf.Version.IsAtLeast(V0_10_1_0) {\n\t\trequest.Version = 3\n\t\trequest.MaxBytes = MaxResponseSize\n\t}\n\t// Version 4 adds IsolationLevel.  Starting in version 4, the reqestor must be\n\t// able to handle Kafka log message format version 2.\n\t// Version 5 adds LogStartOffset to indicate the earliest available offset of\n\t// partition data that can be consumed.\n\tif bc.consumer.conf.Version.IsAtLeast(V0_11_0_0) {\n\t\trequest.Version = 5\n\t\trequest.Isolation = bc.consumer.conf.Consumer.IsolationLevel\n\t}\n\t// Version 6 is the same as version 5.\n\tif bc.consumer.conf.Version.IsAtLeast(V1_0_0_0) {\n\t\trequest.Version = 6\n\t}\n\t// Version 7 adds incremental fetch request support.\n\tif bc.consumer.conf.Version.IsAtLeast(V1_1_0_0) {\n\t\trequest.Version = 7\n\t\t// We do not currently implement KIP-227 FetchSessions. Setting the id to 0\n\t\t// and the epoch to -1 tells the broker not to generate as session ID we're going\n\t\t// to just ignore anyway.\n\t\trequest.SessionID = 0\n\t\trequest.SessionEpoch = -1\n\t}\n\t// Version 8 is the same as version 7.\n\tif bc.consumer.conf.Version.IsAtLeast(V2_0_0_0) {\n\t\trequest.Version = 8\n\t}\n\t// Version 9 adds CurrentLeaderEpoch, as described in KIP-320.\n\t// Version 10 indicates that we can use the ZStd compression algorithm, as\n\t// described in KIP-110.\n\tif bc.consumer.conf.Version.IsAtLeast(V2_1_0_0) {\n\t\trequest.Version = 10\n\t}\n\t// Version 11 adds RackID for KIP-392 fetch from closest replica\n\tif bc.consumer.conf.Version.IsAtLeast(V2_3_0_0) {\n\t\trequest.Version = 11\n\t\trequest.RackID = bc.consumer.conf.RackID\n\t}\n\n\tfor child := range bc.subscriptions {\n\t\tif !child.IsPaused() {\n\t\t\trequest.AddBlock(child.topic, child.partition, child.offset, child.fetchSize, child.leaderEpoch)\n\t\t}\n\t}\n\n\t// avoid to fetch when there is no block\n\tif len(request.blocks) == 0 {\n\t\treturn nil, nil\n\t}\n\n\treturn bc.broker.Fetch(request)\n}\n"
  },
  {
    "path": "consumer_group.go",
    "content": "package sarama\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"sort\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/rcrowley/go-metrics\"\n)\n\n// ErrClosedConsumerGroup is the error returned when a method is called on a consumer group that has been closed.\nvar ErrClosedConsumerGroup = errors.New(\"kafka: tried to use a consumer group that was closed\")\n\n// ConsumerGroup is responsible for dividing up processing of topics and partitions\n// over a collection of processes (the members of the consumer group).\ntype ConsumerGroup interface {\n\t// Consume joins a cluster of consumers for a given list of topics and\n\t// starts a blocking ConsumerGroupSession through the ConsumerGroupHandler.\n\t//\n\t// The life-cycle of a session is represented by the following steps:\n\t//\n\t// 1. The consumers join the group (as explained in https://kafka.apache.org/documentation/#intro_consumers)\n\t//    and is assigned their \"fair share\" of partitions, aka 'claims'.\n\t// 2. Before processing starts, the handler's Setup() hook is called to notify the user\n\t//    of the claims and allow any necessary preparation or alteration of state.\n\t// 3. For each of the assigned claims the handler's ConsumeClaim() function is then called\n\t//    in a separate goroutine which requires it to be thread-safe. Any state must be carefully protected\n\t//    from concurrent reads/writes.\n\t// 4. The session will persist until one of the ConsumeClaim() functions exits. This can be either when the\n\t//    parent context is canceled or when a server-side rebalance cycle is initiated.\n\t// 5. Once all the ConsumeClaim() loops have exited, the handler's Cleanup() hook is called\n\t//    to allow the user to perform any final tasks before a rebalance.\n\t// 6. Finally, marked offsets are committed one last time before claims are released.\n\t//\n\t// Please note, that once a rebalance is triggered, sessions must be completed within\n\t// Config.Consumer.Group.Rebalance.Timeout. This means that ConsumeClaim() functions must exit\n\t// as quickly as possible to allow time for Cleanup() and the final offset commit. If the timeout\n\t// is exceeded, the consumer will be removed from the group by Kafka, which will cause offset\n\t// commit failures.\n\t// This method should be called inside an infinite loop, when a\n\t// server-side rebalance happens, the consumer session will need to be\n\t// recreated to get the new claims.\n\tConsume(ctx context.Context, topics []string, handler ConsumerGroupHandler) error\n\n\t// Errors returns a read channel of errors that occurred during the consumer life-cycle.\n\t// By default, errors are logged and not returned over this channel.\n\t// If you want to implement any custom error handling, set your config's\n\t// Consumer.Return.Errors setting to true, and read from this channel.\n\tErrors() <-chan error\n\n\t// Close stops the ConsumerGroup and detaches any running sessions. It is required to call\n\t// this function before the object passes out of scope, as it will otherwise leak memory.\n\tClose() error\n\n\t// Pause suspends fetching from the requested partitions. Future calls to the broker will not return any\n\t// records from these partitions until they have been resumed using Resume()/ResumeAll().\n\t// Note that this method does not affect partition subscription.\n\t// In particular, it does not cause a group rebalance when automatic assignment is used.\n\tPause(partitions map[string][]int32)\n\n\t// Resume resumes specified partitions which have been paused with Pause()/PauseAll().\n\t// New calls to the broker will return records from these partitions if there are any to be fetched.\n\tResume(partitions map[string][]int32)\n\n\t// Pause suspends fetching from all partitions. Future calls to the broker will not return any\n\t// records from these partitions until they have been resumed using Resume()/ResumeAll().\n\t// Note that this method does not affect partition subscription.\n\t// In particular, it does not cause a group rebalance when automatic assignment is used.\n\tPauseAll()\n\n\t// Resume resumes all partitions which have been paused with Pause()/PauseAll().\n\t// New calls to the broker will return records from these partitions if there are any to be fetched.\n\tResumeAll()\n}\n\ntype consumerGroup struct {\n\tclient Client\n\n\tconfig          *Config\n\tconsumer        Consumer\n\tgroupID         string\n\tgroupInstanceId *string\n\tmemberID        string\n\terrors          chan error\n\n\tlock       sync.Mutex\n\terrorsLock sync.RWMutex\n\tclosed     chan none\n\tcloseOnce  sync.Once\n\n\tuserData []byte\n\n\tmetricRegistry metrics.Registry\n}\n\n// NewConsumerGroup creates a new consumer group the given broker addresses and configuration.\nfunc NewConsumerGroup(addrs []string, groupID string, config *Config) (ConsumerGroup, error) {\n\tclient, err := NewClient(addrs, config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tc, err := newConsumerGroup(groupID, client)\n\tif err != nil {\n\t\t_ = client.Close()\n\t}\n\treturn c, err\n}\n\n// NewConsumerGroupFromClient creates a new consumer group using the given client. It is still\n// necessary to call Close() on the underlying client when shutting down this consumer.\n// PLEASE NOTE: consumer groups can only re-use but not share clients.\nfunc NewConsumerGroupFromClient(groupID string, client Client) (ConsumerGroup, error) {\n\tif client == nil {\n\t\treturn nil, ConfigurationError(\"client must not be nil\")\n\t}\n\t// For clients passed in by the client, ensure we don't\n\t// call Close() on it.\n\tcli := &nopCloserClient{client}\n\treturn newConsumerGroup(groupID, cli)\n}\n\nfunc newConsumerGroup(groupID string, client Client) (ConsumerGroup, error) {\n\tconfig := client.Config()\n\tif !config.Version.IsAtLeast(V0_10_2_0) {\n\t\treturn nil, ConfigurationError(\"consumer groups require Version to be >= V0_10_2_0\")\n\t}\n\n\tconsumer, err := newConsumer(client)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcg := &consumerGroup{\n\t\tclient:         client,\n\t\tconsumer:       consumer,\n\t\tconfig:         config,\n\t\tgroupID:        groupID,\n\t\terrors:         make(chan error, config.ChannelBufferSize),\n\t\tclosed:         make(chan none),\n\t\tuserData:       config.Consumer.Group.Member.UserData,\n\t\tmetricRegistry: newCleanupRegistry(config.MetricRegistry),\n\t}\n\tif config.Consumer.Group.InstanceId != \"\" && config.Version.IsAtLeast(V2_3_0_0) {\n\t\tcg.groupInstanceId = &config.Consumer.Group.InstanceId\n\t}\n\treturn cg, nil\n}\n\n// Errors implements ConsumerGroup.\nfunc (c *consumerGroup) Errors() <-chan error { return c.errors }\n\n// Close implements ConsumerGroup.\nfunc (c *consumerGroup) Close() (err error) {\n\tc.closeOnce.Do(func() {\n\t\tclose(c.closed)\n\n\t\t// leave group\n\t\tif e := c.leave(); e != nil {\n\t\t\terr = e\n\t\t}\n\n\t\tgo func() {\n\t\t\tc.errorsLock.Lock()\n\t\t\tdefer c.errorsLock.Unlock()\n\t\t\tclose(c.errors)\n\t\t}()\n\n\t\t// drain errors\n\t\tfor e := range c.errors {\n\t\t\terr = e\n\t\t}\n\n\t\tif e := c.client.Close(); e != nil {\n\t\t\terr = e\n\t\t}\n\n\t\tc.metricRegistry.UnregisterAll()\n\t})\n\treturn\n}\n\n// Consume implements ConsumerGroup.\nfunc (c *consumerGroup) Consume(ctx context.Context, topics []string, handler ConsumerGroupHandler) error {\n\t// Ensure group is not closed\n\tselect {\n\tcase <-c.closed:\n\t\treturn ErrClosedConsumerGroup\n\tdefault:\n\t}\n\n\tc.lock.Lock()\n\tdefer c.lock.Unlock()\n\n\t// Quick exit when no topics are provided\n\tif len(topics) == 0 {\n\t\treturn fmt.Errorf(\"no topics provided\")\n\t}\n\n\t// Refresh metadata for requested topics\n\tif err := c.client.RefreshMetadata(topics...); err != nil {\n\t\treturn err\n\t}\n\n\t// Init session\n\tsess, err := c.newSession(ctx, topics, handler, c.config.Consumer.Group.Rebalance.Retry.Max)\n\tif errors.Is(err, ErrClosedClient) {\n\t\treturn ErrClosedConsumerGroup\n\t} else if err != nil {\n\t\treturn err\n\t}\n\n\t// Wait for session exit signal or Close() call\n\tselect {\n\tcase <-c.closed:\n\tcase <-sess.ctx.Done():\n\t}\n\n\t// Gracefully release session claims\n\treturn sess.release(true)\n}\n\n// Pause implements ConsumerGroup.\nfunc (c *consumerGroup) Pause(partitions map[string][]int32) {\n\tc.consumer.Pause(partitions)\n}\n\n// Resume implements ConsumerGroup.\nfunc (c *consumerGroup) Resume(partitions map[string][]int32) {\n\tc.consumer.Resume(partitions)\n}\n\n// PauseAll implements ConsumerGroup.\nfunc (c *consumerGroup) PauseAll() {\n\tc.consumer.PauseAll()\n}\n\n// ResumeAll implements ConsumerGroup.\nfunc (c *consumerGroup) ResumeAll() {\n\tc.consumer.ResumeAll()\n}\n\nfunc (c *consumerGroup) retryNewSession(ctx context.Context, topics []string, handler ConsumerGroupHandler, retries int, refreshCoordinator bool) (*consumerGroupSession, error) {\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn nil, ctx.Err()\n\tcase <-c.closed:\n\t\treturn nil, ErrClosedConsumerGroup\n\tcase <-time.After(c.config.Consumer.Group.Rebalance.Retry.Backoff):\n\t}\n\n\tif refreshCoordinator {\n\t\terr := c.client.RefreshCoordinator(c.groupID)\n\t\tif err != nil {\n\t\t\tif retries <= 0 {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn c.retryNewSession(ctx, topics, handler, retries-1, true)\n\t\t}\n\t}\n\n\treturn c.newSession(ctx, topics, handler, retries-1)\n}\n\nfunc (c *consumerGroup) newSession(ctx context.Context, topics []string, handler ConsumerGroupHandler, retries int) (*consumerGroupSession, error) {\n\tif ctx.Err() != nil {\n\t\treturn nil, ctx.Err()\n\t}\n\tcoordinator, err := c.client.Coordinator(c.groupID)\n\tif err != nil {\n\t\tif retries <= 0 {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treturn c.retryNewSession(ctx, topics, handler, retries, true)\n\t}\n\n\tvar (\n\t\tmetricRegistry          = c.metricRegistry\n\t\tconsumerGroupJoinTotal  metrics.Counter\n\t\tconsumerGroupJoinFailed metrics.Counter\n\t\tconsumerGroupSyncTotal  metrics.Counter\n\t\tconsumerGroupSyncFailed metrics.Counter\n\t)\n\n\tif metricRegistry != nil {\n\t\tconsumerGroupJoinTotal = metrics.GetOrRegisterCounter(fmt.Sprintf(\"consumer-group-join-total-%s\", c.groupID), metricRegistry)\n\t\tconsumerGroupJoinFailed = metrics.GetOrRegisterCounter(fmt.Sprintf(\"consumer-group-join-failed-%s\", c.groupID), metricRegistry)\n\t\tconsumerGroupSyncTotal = metrics.GetOrRegisterCounter(fmt.Sprintf(\"consumer-group-sync-total-%s\", c.groupID), metricRegistry)\n\t\tconsumerGroupSyncFailed = metrics.GetOrRegisterCounter(fmt.Sprintf(\"consumer-group-sync-failed-%s\", c.groupID), metricRegistry)\n\t}\n\n\t// Join consumer group\n\tjoin, err := c.joinGroupRequest(coordinator, topics)\n\tif consumerGroupJoinTotal != nil {\n\t\tconsumerGroupJoinTotal.Inc(1)\n\t}\n\tif err != nil {\n\t\t_ = coordinator.Close()\n\t\tif consumerGroupJoinFailed != nil {\n\t\t\tconsumerGroupJoinFailed.Inc(1)\n\t\t}\n\t\treturn nil, err\n\t}\n\tif !errors.Is(join.Err, ErrNoError) {\n\t\tif consumerGroupJoinFailed != nil {\n\t\t\tconsumerGroupJoinFailed.Inc(1)\n\t\t}\n\t}\n\tswitch join.Err {\n\tcase ErrNoError:\n\t\tc.memberID = join.MemberId\n\tcase ErrUnknownMemberId, ErrIllegalGeneration:\n\t\t// reset member ID and retry immediately\n\t\tc.memberID = \"\"\n\t\treturn c.newSession(ctx, topics, handler, retries)\n\tcase ErrNotCoordinatorForConsumer, ErrRebalanceInProgress, ErrOffsetsLoadInProgress:\n\t\t// retry after backoff\n\t\tif retries <= 0 {\n\t\t\treturn nil, join.Err\n\t\t}\n\t\treturn c.retryNewSession(ctx, topics, handler, retries, true)\n\tcase ErrMemberIdRequired:\n\t\t// from JoinGroupRequest v4 onwards (due to KIP-394) if the client starts\n\t\t// with an empty member id, it needs to get the assigned id from the\n\t\t// response and send another join request with that id to actually join the\n\t\t// group\n\t\tc.memberID = join.MemberId\n\t\treturn c.newSession(ctx, topics, handler, retries)\n\tcase ErrFencedInstancedId:\n\t\tif c.groupInstanceId != nil {\n\t\t\tLogger.Printf(\"JoinGroup failed: group instance id %s has been fenced\\n\", *c.groupInstanceId)\n\t\t}\n\t\treturn nil, join.Err\n\tdefault:\n\t\treturn nil, join.Err\n\t}\n\n\tvar strategy BalanceStrategy\n\tvar ok bool\n\tif strategy = c.config.Consumer.Group.Rebalance.Strategy; strategy == nil {\n\t\tstrategy, ok = c.findStrategy(join.GroupProtocol, c.config.Consumer.Group.Rebalance.GroupStrategies)\n\t\tif !ok {\n\t\t\t// this case shouldn't happen in practice, since the leader will choose the protocol\n\t\t\t// that all the members support\n\t\t\treturn nil, fmt.Errorf(\"unable to find selected strategy: %s\", join.GroupProtocol)\n\t\t}\n\t}\n\n\t// Prepare distribution plan if we joined as the leader\n\tvar plan BalanceStrategyPlan\n\tvar members map[string]ConsumerGroupMemberMetadata\n\tvar allSubscribedTopicPartitions map[string][]int32\n\tvar allSubscribedTopics []string\n\tif join.LeaderId == join.MemberId {\n\t\tmembers, err = join.GetMembers()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tallSubscribedTopicPartitions, allSubscribedTopics, plan, err = c.balance(strategy, members)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\t// Sync consumer group\n\tsyncGroupResponse, err := c.syncGroupRequest(coordinator, members, plan, join.GenerationId, strategy)\n\tif consumerGroupSyncTotal != nil {\n\t\tconsumerGroupSyncTotal.Inc(1)\n\t}\n\tif err != nil {\n\t\t_ = coordinator.Close()\n\t\tif consumerGroupSyncFailed != nil {\n\t\t\tconsumerGroupSyncFailed.Inc(1)\n\t\t}\n\t\treturn nil, err\n\t}\n\tif !errors.Is(syncGroupResponse.Err, ErrNoError) {\n\t\tif consumerGroupSyncFailed != nil {\n\t\t\tconsumerGroupSyncFailed.Inc(1)\n\t\t}\n\t}\n\n\tswitch syncGroupResponse.Err {\n\tcase ErrNoError:\n\tcase ErrUnknownMemberId, ErrIllegalGeneration:\n\t\t// reset member ID and retry immediately\n\t\tc.memberID = \"\"\n\t\treturn c.newSession(ctx, topics, handler, retries)\n\tcase ErrNotCoordinatorForConsumer, ErrRebalanceInProgress, ErrOffsetsLoadInProgress:\n\t\t// retry after backoff\n\t\tif retries <= 0 {\n\t\t\treturn nil, syncGroupResponse.Err\n\t\t}\n\t\treturn c.retryNewSession(ctx, topics, handler, retries, true)\n\tcase ErrFencedInstancedId:\n\t\tif c.groupInstanceId != nil {\n\t\t\tLogger.Printf(\"JoinGroup failed: group instance id %s has been fenced\\n\", *c.groupInstanceId)\n\t\t}\n\t\treturn nil, syncGroupResponse.Err\n\tdefault:\n\t\treturn nil, syncGroupResponse.Err\n\t}\n\n\t// Retrieve and sort claims\n\tvar claims map[string][]int32\n\tif len(syncGroupResponse.MemberAssignment) > 0 {\n\t\tmembers, err := syncGroupResponse.GetMemberAssignment()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tclaims = members.Topics\n\n\t\t// in the case of stateful balance strategies, hold on to the returned\n\t\t// assignment metadata, otherwise, reset the statically defined consumer\n\t\t// group metadata\n\t\tif members.UserData != nil {\n\t\t\tc.userData = members.UserData\n\t\t} else {\n\t\t\tc.userData = c.config.Consumer.Group.Member.UserData\n\t\t}\n\n\t\tfor _, partitions := range claims {\n\t\t\tsort.Sort(int32Slice(partitions))\n\t\t}\n\t}\n\n\tsession, err := newConsumerGroupSession(ctx, c, claims, join.MemberId, join.GenerationId, handler)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// only the leader needs to check whether there are newly-added partitions in order to trigger a rebalance\n\tif join.LeaderId == join.MemberId {\n\t\tgo c.loopCheckPartitionNumbers(allSubscribedTopicPartitions, allSubscribedTopics, session)\n\t}\n\n\treturn session, err\n}\n\nfunc (c *consumerGroup) joinGroupRequest(coordinator *Broker, topics []string) (*JoinGroupResponse, error) {\n\treq := &JoinGroupRequest{\n\t\tGroupId:        c.groupID,\n\t\tMemberId:       c.memberID,\n\t\tSessionTimeout: int32(c.config.Consumer.Group.Session.Timeout / time.Millisecond),\n\t\tProtocolType:   \"consumer\",\n\t}\n\tif c.config.Version.IsAtLeast(V0_10_1_0) {\n\t\treq.Version = 1\n\t\treq.RebalanceTimeout = int32(c.config.Consumer.Group.Rebalance.Timeout / time.Millisecond)\n\t}\n\tif c.config.Version.IsAtLeast(V0_11_0_0) {\n\t\treq.Version = 2\n\t}\n\tif c.config.Version.IsAtLeast(V0_11_0_0) {\n\t\treq.Version = 2\n\t}\n\tif c.config.Version.IsAtLeast(V2_0_0_0) {\n\t\treq.Version = 3\n\t}\n\t// from JoinGroupRequest v4 onwards (due to KIP-394) the client will actually\n\t// send two JoinGroupRequests, once with the empty member id, and then again\n\t// with the assigned id from the first response. This is handled via the\n\t// ErrMemberIdRequired case.\n\tif c.config.Version.IsAtLeast(V2_2_0_0) {\n\t\treq.Version = 4\n\t}\n\tif c.config.Version.IsAtLeast(V2_3_0_0) {\n\t\treq.Version = 5\n\t\treq.GroupInstanceId = c.groupInstanceId\n\t\tif c.config.Version.IsAtLeast(V2_4_0_0) {\n\t\t\treq.Version = 6\n\t\t}\n\t}\n\n\tmeta := &ConsumerGroupMemberMetadata{\n\t\tTopics:   topics,\n\t\tUserData: c.userData,\n\t}\n\tvar strategy BalanceStrategy\n\tif strategy = c.config.Consumer.Group.Rebalance.Strategy; strategy != nil {\n\t\tif err := req.AddGroupProtocolMetadata(strategy.Name(), meta); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else {\n\t\tfor _, strategy = range c.config.Consumer.Group.Rebalance.GroupStrategies {\n\t\t\tif err := req.AddGroupProtocolMetadata(strategy.Name(), meta); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn coordinator.JoinGroup(req)\n}\n\n// findStrategy returns the BalanceStrategy with the specified protocolName\n// from the slice provided.\nfunc (c *consumerGroup) findStrategy(name string, groupStrategies []BalanceStrategy) (BalanceStrategy, bool) {\n\tfor _, strategy := range groupStrategies {\n\t\tif strategy.Name() == name {\n\t\t\treturn strategy, true\n\t\t}\n\t}\n\treturn nil, false\n}\n\nfunc (c *consumerGroup) syncGroupRequest(\n\tcoordinator *Broker,\n\tmembers map[string]ConsumerGroupMemberMetadata,\n\tplan BalanceStrategyPlan,\n\tgenerationID int32,\n\tstrategy BalanceStrategy,\n) (*SyncGroupResponse, error) {\n\treq := &SyncGroupRequest{\n\t\tGroupId:      c.groupID,\n\t\tMemberId:     c.memberID,\n\t\tGenerationId: generationID,\n\t}\n\n\t// Versions 1 and 2 are the same as version 0.\n\tif c.config.Version.IsAtLeast(V0_11_0_0) {\n\t\treq.Version = 1\n\t}\n\tif c.config.Version.IsAtLeast(V2_0_0_0) {\n\t\treq.Version = 2\n\t}\n\t// Starting from version 3, we add a new field called groupInstanceId to indicate member identity across restarts.\n\tif c.config.Version.IsAtLeast(V2_3_0_0) {\n\t\treq.Version = 3\n\t\treq.GroupInstanceId = c.groupInstanceId\n\t\tif c.config.Version.IsAtLeast(V2_4_0_0) {\n\t\t\treq.Version = 4\n\t\t}\n\t}\n\n\tfor memberID, topics := range plan {\n\t\tassignment := &ConsumerGroupMemberAssignment{Topics: topics}\n\t\tuserDataBytes, err := strategy.AssignmentData(memberID, topics, generationID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tassignment.UserData = userDataBytes\n\t\tif err := req.AddGroupAssignmentMember(memberID, assignment); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdelete(members, memberID)\n\t}\n\t// add empty assignments for any remaining members\n\tfor memberID := range members {\n\t\tif err := req.AddGroupAssignmentMember(memberID, &ConsumerGroupMemberAssignment{}); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn coordinator.SyncGroup(req)\n}\n\nfunc (c *consumerGroup) heartbeatRequest(coordinator *Broker, memberID string, generationID int32) (*HeartbeatResponse, error) {\n\treq := &HeartbeatRequest{\n\t\tGroupId:      c.groupID,\n\t\tMemberId:     memberID,\n\t\tGenerationId: generationID,\n\t}\n\n\t// Version 1 and version 2 are the same as version 0.\n\tif c.config.Version.IsAtLeast(V0_11_0_0) {\n\t\treq.Version = 1\n\t}\n\tif c.config.Version.IsAtLeast(V2_0_0_0) {\n\t\treq.Version = 2\n\t}\n\t// Starting from version 3, we add a new field called groupInstanceId to indicate member identity across restarts.\n\tif c.config.Version.IsAtLeast(V2_3_0_0) {\n\t\treq.Version = 3\n\t\treq.GroupInstanceId = c.groupInstanceId\n\t\t// Version 4 is the first flexible version\n\t\tif c.config.Version.IsAtLeast(V2_4_0_0) {\n\t\t\treq.Version = 4\n\t\t}\n\t}\n\n\treturn coordinator.Heartbeat(req)\n}\n\nfunc (c *consumerGroup) balance(strategy BalanceStrategy, members map[string]ConsumerGroupMemberMetadata) (map[string][]int32, []string, BalanceStrategyPlan, error) {\n\ttopicPartitions := make(map[string][]int32)\n\tfor _, meta := range members {\n\t\tfor _, topic := range meta.Topics {\n\t\t\ttopicPartitions[topic] = nil\n\t\t}\n\t}\n\n\tallSubscribedTopics := make([]string, 0, len(topicPartitions))\n\tfor topic := range topicPartitions {\n\t\tallSubscribedTopics = append(allSubscribedTopics, topic)\n\t}\n\n\t// refresh metadata for all the subscribed topics in the consumer group\n\t// to avoid using stale metadata to assigning partitions\n\terr := c.client.RefreshMetadata(allSubscribedTopics...)\n\tif err != nil {\n\t\treturn nil, nil, nil, err\n\t}\n\n\tfor topic := range topicPartitions {\n\t\tpartitions, err := c.client.Partitions(topic)\n\t\tif err != nil {\n\t\t\treturn nil, nil, nil, err\n\t\t}\n\t\ttopicPartitions[topic] = partitions\n\t}\n\n\tplan, err := strategy.Plan(members, topicPartitions)\n\treturn topicPartitions, allSubscribedTopics, plan, err\n}\n\n// Leaves the cluster, called by Close.\nfunc (c *consumerGroup) leave() error {\n\tc.lock.Lock()\n\tdefer c.lock.Unlock()\n\tif c.memberID == \"\" {\n\t\treturn nil\n\t}\n\n\tcoordinator, err := c.client.Coordinator(c.groupID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// as per KIP-345 if groupInstanceId is set, i.e. static membership is in action, then do not leave group when consumer closed, just clear memberID\n\tif c.groupInstanceId != nil {\n\t\tc.memberID = \"\"\n\t\treturn nil\n\t}\n\treq := &LeaveGroupRequest{\n\t\tGroupId:  c.groupID,\n\t\tMemberId: c.memberID,\n\t}\n\tif c.config.Version.IsAtLeast(V0_11_0_0) {\n\t\treq.Version = 1\n\t}\n\tif c.config.Version.IsAtLeast(V2_0_0_0) {\n\t\treq.Version = 2\n\t}\n\tif c.config.Version.IsAtLeast(V2_4_0_0) {\n\t\treq.Version = 4\n\t\treq.Members = append(req.Members, MemberIdentity{\n\t\t\tMemberId: c.memberID,\n\t\t})\n\t}\n\n\tresp, err := coordinator.LeaveGroup(req)\n\tif err != nil {\n\t\t_ = coordinator.Close()\n\t\treturn err\n\t}\n\n\t// clear the memberID\n\tc.memberID = \"\"\n\n\tswitch resp.Err {\n\tcase ErrRebalanceInProgress, ErrUnknownMemberId, ErrNoError:\n\t\treturn nil\n\tdefault:\n\t\treturn resp.Err\n\t}\n}\n\nfunc (c *consumerGroup) handleError(err error, topic string, partition int32) {\n\tvar consumerError *ConsumerError\n\tif ok := errors.As(err, &consumerError); !ok && topic != \"\" && partition > -1 {\n\t\terr = &ConsumerError{\n\t\t\tTopic:     topic,\n\t\t\tPartition: partition,\n\t\t\tErr:       err,\n\t\t}\n\t}\n\n\tif !c.config.Consumer.Return.Errors {\n\t\tLogger.Println(err)\n\t\treturn\n\t}\n\n\tc.errorsLock.RLock()\n\tdefer c.errorsLock.RUnlock()\n\tselect {\n\tcase <-c.closed:\n\t\t// consumer is closed\n\t\treturn\n\tdefault:\n\t}\n\n\tselect {\n\tcase c.errors <- err:\n\tdefault:\n\t\t// no error listener\n\t}\n}\n\nfunc (c *consumerGroup) loopCheckPartitionNumbers(allSubscribedTopicPartitions map[string][]int32, topics []string, session *consumerGroupSession) {\n\tif c.config.Metadata.RefreshFrequency == time.Duration(0) {\n\t\treturn\n\t}\n\n\tdefer session.cancel()\n\n\toldTopicToPartitionNum := make(map[string]int, len(allSubscribedTopicPartitions))\n\tfor topic, partitions := range allSubscribedTopicPartitions {\n\t\toldTopicToPartitionNum[topic] = len(partitions)\n\t}\n\n\tpause := time.NewTicker(c.config.Metadata.RefreshFrequency)\n\tdefer pause.Stop()\n\tfor {\n\t\tif newTopicToPartitionNum, err := c.topicToPartitionNumbers(topics); err != nil {\n\t\t\treturn\n\t\t} else {\n\t\t\tfor topic, num := range oldTopicToPartitionNum {\n\t\t\t\tif newTopicToPartitionNum[topic] != num {\n\t\t\t\t\tLogger.Printf(\n\t\t\t\t\t\t\"consumergroup/%s loop check partition number goroutine find partitions in topics %s changed from %d to %d\\n\",\n\t\t\t\t\t\tc.groupID, topics, num, newTopicToPartitionNum[topic])\n\t\t\t\t\treturn // trigger the end of the session on exit\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tselect {\n\t\tcase <-pause.C:\n\t\tcase <-session.ctx.Done():\n\t\t\tLogger.Printf(\n\t\t\t\t\"consumergroup/%s loop check partition number goroutine will exit, topics %s\\n\",\n\t\t\t\tc.groupID, topics)\n\t\t\t// if session closed by other, should be exited\n\t\t\treturn\n\t\tcase <-c.closed:\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (c *consumerGroup) topicToPartitionNumbers(topics []string) (map[string]int, error) {\n\ttopicToPartitionNum := make(map[string]int, len(topics))\n\tfor _, topic := range topics {\n\t\tif partitionNum, err := c.client.Partitions(topic); err != nil {\n\t\t\tLogger.Printf(\n\t\t\t\t\"consumergroup/%s topic %s get partition number failed due to '%v'\\n\",\n\t\t\t\tc.groupID, topic, err)\n\t\t\treturn nil, err\n\t\t} else {\n\t\t\ttopicToPartitionNum[topic] = len(partitionNum)\n\t\t}\n\t}\n\treturn topicToPartitionNum, nil\n}\n\n// --------------------------------------------------------------------\n\n// ConsumerGroupSession represents a consumer group member session.\ntype ConsumerGroupSession interface {\n\t// Claims returns information about the claimed partitions by topic.\n\tClaims() map[string][]int32\n\n\t// MemberID returns the cluster member ID.\n\tMemberID() string\n\n\t// GenerationID returns the current generation ID.\n\tGenerationID() int32\n\n\t// MarkOffset marks the provided offset, alongside a metadata string\n\t// that represents the state of the partition consumer at that point in time. The\n\t// metadata string can be used by another consumer to restore that state, so it\n\t// can resume consumption.\n\t//\n\t// To follow upstream conventions, you are expected to mark the offset of the\n\t// next message to read, not the last message read. Thus, when calling `MarkOffset`\n\t// you should typically add one to the offset of the last consumed message.\n\t//\n\t// Note: calling MarkOffset does not necessarily commit the offset to the backend\n\t// store immediately for efficiency reasons, and it may never be committed if\n\t// your application crashes. This means that you may end up processing the same\n\t// message twice, and your processing should ideally be idempotent.\n\tMarkOffset(topic string, partition int32, offset int64, metadata string)\n\n\t// Commit the offset to the backend\n\t//\n\t// Note: calling Commit performs a blocking synchronous operation.\n\tCommit()\n\n\t// ResetOffset resets to the provided offset, alongside a metadata string that\n\t// represents the state of the partition consumer at that point in time. Reset\n\t// acts as a counterpart to MarkOffset, the difference being that it allows to\n\t// reset an offset to an earlier or smaller value, where MarkOffset only\n\t// allows incrementing the offset. cf MarkOffset for more details.\n\tResetOffset(topic string, partition int32, offset int64, metadata string)\n\n\t// MarkMessage marks a message as consumed.\n\tMarkMessage(msg *ConsumerMessage, metadata string)\n\n\t// Context returns the session context.\n\tContext() context.Context\n}\n\ntype consumerGroupSession struct {\n\tparent       *consumerGroup\n\tmemberID     string\n\tgenerationID int32\n\thandler      ConsumerGroupHandler\n\n\tclaims  map[string][]int32\n\toffsets *offsetManager\n\tctx     context.Context\n\tcancel  func()\n\n\twaitGroup       sync.WaitGroup\n\treleaseOnce     sync.Once\n\thbDying, hbDead chan none\n}\n\nfunc newConsumerGroupSession(ctx context.Context, parent *consumerGroup, claims map[string][]int32, memberID string, generationID int32, handler ConsumerGroupHandler) (*consumerGroupSession, error) {\n\t// init context\n\tctx, cancel := context.WithCancel(ctx)\n\n\t// init offset manager\n\toffsets, err := newOffsetManagerFromClient(parent.groupID, memberID, generationID, parent.client, cancel)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// init session\n\tsess := &consumerGroupSession{\n\t\tparent:       parent,\n\t\tmemberID:     memberID,\n\t\tgenerationID: generationID,\n\t\thandler:      handler,\n\t\toffsets:      offsets,\n\t\tclaims:       claims,\n\t\tctx:          ctx,\n\t\tcancel:       cancel,\n\t\thbDying:      make(chan none),\n\t\thbDead:       make(chan none),\n\t}\n\n\t// start heartbeat loop\n\tgo sess.heartbeatLoop()\n\n\t// create a POM for each claim\n\tfor topic, partitions := range claims {\n\t\tfor _, partition := range partitions {\n\t\t\tpom, err := offsets.ManagePartition(topic, partition)\n\t\t\tif err != nil {\n\t\t\t\t_ = sess.release(false)\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\t// handle POM errors\n\t\t\tgo func(topic string, partition int32) {\n\t\t\t\tfor err := range pom.Errors() {\n\t\t\t\t\tsess.parent.handleError(err, topic, partition)\n\t\t\t\t}\n\t\t\t}(topic, partition)\n\t\t}\n\t}\n\n\t// perform setup\n\tif err := handler.Setup(sess); err != nil {\n\t\t_ = sess.release(true)\n\t\treturn nil, err\n\t}\n\n\t// start consuming each topic partition in its own goroutine\n\tfor topic, partitions := range claims {\n\t\tfor _, partition := range partitions {\n\t\t\tsess.waitGroup.Add(1) // increment wait group before spawning goroutine\n\t\t\tgo func(topic string, partition int32) {\n\t\t\t\tdefer sess.waitGroup.Done()\n\t\t\t\t// cancel the group session as soon as any of the consume calls return\n\t\t\t\tdefer sess.cancel()\n\n\t\t\t\t// if partition not currently readable, wait for it to become readable\n\t\t\t\tif sess.parent.client.PartitionNotReadable(topic, partition) {\n\t\t\t\t\ttimer := time.NewTimer(5 * time.Second)\n\t\t\t\t\tdefer timer.Stop()\n\n\t\t\t\t\tfor sess.parent.client.PartitionNotReadable(topic, partition) {\n\t\t\t\t\t\tselect {\n\t\t\t\t\t\tcase <-ctx.Done():\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\tcase <-parent.closed:\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\tcase <-timer.C:\n\t\t\t\t\t\t\ttimer.Reset(5 * time.Second)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// consume a single topic/partition, blocking\n\t\t\t\tsess.consume(topic, partition)\n\t\t\t}(topic, partition)\n\t\t}\n\t}\n\treturn sess, nil\n}\n\nfunc (s *consumerGroupSession) Claims() map[string][]int32 { return s.claims }\nfunc (s *consumerGroupSession) MemberID() string           { return s.memberID }\nfunc (s *consumerGroupSession) GenerationID() int32        { return s.generationID }\n\nfunc (s *consumerGroupSession) MarkOffset(topic string, partition int32, offset int64, metadata string) {\n\tif pom := s.offsets.findPOM(topic, partition); pom != nil {\n\t\tpom.MarkOffset(offset, metadata)\n\t}\n}\n\nfunc (s *consumerGroupSession) Commit() {\n\ts.offsets.Commit()\n}\n\nfunc (s *consumerGroupSession) ResetOffset(topic string, partition int32, offset int64, metadata string) {\n\tif pom := s.offsets.findPOM(topic, partition); pom != nil {\n\t\tpom.ResetOffset(offset, metadata)\n\t}\n}\n\nfunc (s *consumerGroupSession) MarkMessage(msg *ConsumerMessage, metadata string) {\n\ts.MarkOffset(msg.Topic, msg.Partition, msg.Offset+1, metadata)\n}\n\nfunc (s *consumerGroupSession) Context() context.Context {\n\treturn s.ctx\n}\n\nfunc (s *consumerGroupSession) consume(topic string, partition int32) {\n\t// quick exit if rebalance is due\n\tselect {\n\tcase <-s.ctx.Done():\n\t\treturn\n\tcase <-s.parent.closed:\n\t\treturn\n\tdefault:\n\t}\n\n\t// get next offset\n\toffset := s.parent.config.Consumer.Offsets.Initial\n\tif pom := s.offsets.findPOM(topic, partition); pom != nil {\n\t\toffset, _ = pom.NextOffset()\n\t}\n\n\t// create new claim\n\tclaim, err := newConsumerGroupClaim(s, topic, partition, offset)\n\tif err != nil {\n\t\ts.parent.handleError(err, topic, partition)\n\t\treturn\n\t}\n\n\t// handle errors\n\tgo func() {\n\t\tfor err := range claim.Errors() {\n\t\t\ts.parent.handleError(err, topic, partition)\n\t\t}\n\t}()\n\n\t// trigger close when session is done\n\tgo func() {\n\t\tselect {\n\t\tcase <-s.ctx.Done():\n\t\tcase <-s.parent.closed:\n\t\t}\n\t\tclaim.AsyncClose()\n\t}()\n\n\t// start processing\n\tif err := s.handler.ConsumeClaim(s, claim); err != nil {\n\t\ts.parent.handleError(err, topic, partition)\n\t}\n\n\t// ensure consumer is closed & drained\n\tclaim.AsyncClose()\n\tfor _, err := range claim.waitClosed() {\n\t\ts.parent.handleError(err, topic, partition)\n\t}\n}\n\nfunc (s *consumerGroupSession) release(withCleanup bool) (err error) {\n\t// signal release, stop heartbeat\n\ts.cancel()\n\n\t// wait for consumers to exit\n\ts.waitGroup.Wait()\n\n\t// perform release\n\ts.releaseOnce.Do(func() {\n\t\tif withCleanup {\n\t\t\tif e := s.handler.Cleanup(s); e != nil {\n\t\t\t\ts.parent.handleError(e, \"\", -1)\n\t\t\t\terr = e\n\t\t\t}\n\t\t}\n\n\t\tif e := s.offsets.Close(); e != nil {\n\t\t\terr = e\n\t\t}\n\n\t\tclose(s.hbDying)\n\t\t<-s.hbDead\n\t})\n\n\tLogger.Printf(\n\t\t\"consumergroup/session/%s/%d released\\n\",\n\t\ts.MemberID(), s.GenerationID())\n\n\treturn\n}\n\nfunc (s *consumerGroupSession) heartbeatLoop() {\n\tdefer close(s.hbDead)\n\tdefer s.cancel() // trigger the end of the session on exit\n\tdefer func() {\n\t\tLogger.Printf(\n\t\t\t\"consumergroup/session/%s/%d heartbeat loop stopped\\n\",\n\t\t\ts.MemberID(), s.GenerationID())\n\t}()\n\n\tpause := time.NewTicker(s.parent.config.Consumer.Group.Heartbeat.Interval)\n\tdefer pause.Stop()\n\n\tretryBackoff := time.NewTimer(s.parent.config.Metadata.Retry.Backoff)\n\tdefer retryBackoff.Stop()\n\n\tretries := s.parent.config.Metadata.Retry.Max\n\tfor {\n\t\tcoordinator, err := s.parent.client.Coordinator(s.parent.groupID)\n\t\tif err != nil {\n\t\t\tif retries <= 0 {\n\t\t\t\ts.parent.handleError(err, \"\", -1)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tretryBackoff.Reset(s.parent.config.Metadata.Retry.Backoff)\n\t\t\tselect {\n\t\t\tcase <-s.hbDying:\n\t\t\t\treturn\n\t\t\tcase <-retryBackoff.C:\n\t\t\t\tretries--\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tresp, err := s.parent.heartbeatRequest(coordinator, s.memberID, s.generationID)\n\t\tif err != nil {\n\t\t\t_ = coordinator.Close()\n\n\t\t\tif retries <= 0 {\n\t\t\t\ts.parent.handleError(err, \"\", -1)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tretries--\n\t\t\tcontinue\n\t\t}\n\n\t\tswitch resp.Err {\n\t\tcase ErrNoError:\n\t\t\tretries = s.parent.config.Metadata.Retry.Max\n\t\tcase ErrRebalanceInProgress:\n\t\t\tretries = s.parent.config.Metadata.Retry.Max\n\t\t\ts.cancel()\n\t\tcase ErrUnknownMemberId, ErrIllegalGeneration:\n\t\t\treturn\n\t\tcase ErrFencedInstancedId:\n\t\t\tif s.parent.groupInstanceId != nil {\n\t\t\t\tLogger.Printf(\"JoinGroup failed: group instance id %s has been fenced\\n\", *s.parent.groupInstanceId)\n\t\t\t}\n\t\t\ts.parent.handleError(resp.Err, \"\", -1)\n\t\t\treturn\n\t\tdefault:\n\t\t\ts.parent.handleError(resp.Err, \"\", -1)\n\t\t\treturn\n\t\t}\n\n\t\tselect {\n\t\tcase <-pause.C:\n\t\tcase <-s.hbDying:\n\t\t\treturn\n\t\t}\n\t}\n}\n\n// --------------------------------------------------------------------\n\n// ConsumerGroupHandler instances are used to handle individual topic/partition claims.\n// It also provides hooks for your consumer group session life-cycle and allow you to\n// trigger logic before or after the consume loop(s).\n//\n// PLEASE NOTE that handlers are likely be called from several goroutines concurrently,\n// ensure that all state is safely protected against race conditions.\ntype ConsumerGroupHandler interface {\n\t// Setup is run at the beginning of a new session, before ConsumeClaim.\n\tSetup(ConsumerGroupSession) error\n\n\t// Cleanup is run at the end of a session, once all ConsumeClaim goroutines have exited\n\t// but before the offsets are committed for the very last time.\n\tCleanup(ConsumerGroupSession) error\n\n\t// ConsumeClaim must start a consumer loop of ConsumerGroupClaim's Messages().\n\t// Once the Messages() channel is closed, the Handler must finish its processing\n\t// loop and exit.\n\tConsumeClaim(ConsumerGroupSession, ConsumerGroupClaim) error\n}\n\n// ConsumerGroupClaim processes Kafka messages from a given topic and partition within a consumer group.\ntype ConsumerGroupClaim interface {\n\t// Topic returns the consumed topic name.\n\tTopic() string\n\n\t// Partition returns the consumed partition.\n\tPartition() int32\n\n\t// InitialOffset returns the initial offset that was used as a starting point for this claim.\n\tInitialOffset() int64\n\n\t// HighWaterMarkOffset returns the high watermark offset of the partition,\n\t// i.e. the offset that will be used for the next message that will be produced.\n\t// You can use this to determine how far behind the processing is.\n\tHighWaterMarkOffset() int64\n\n\t// Messages returns the read channel for the messages that are returned by\n\t// the broker. The messages channel will be closed when a new rebalance cycle\n\t// is due. You must finish processing and mark offsets within\n\t// Config.Consumer.Group.Session.Timeout before the topic/partition is eventually\n\t// re-assigned to another group member.\n\tMessages() <-chan *ConsumerMessage\n}\n\ntype consumerGroupClaim struct {\n\ttopic     string\n\tpartition int32\n\toffset    int64\n\tPartitionConsumer\n}\n\nfunc newConsumerGroupClaim(sess *consumerGroupSession, topic string, partition int32, offset int64) (*consumerGroupClaim, error) {\n\tpcm, err := sess.parent.consumer.ConsumePartition(topic, partition, offset)\n\n\tif errors.Is(err, ErrOffsetOutOfRange) && sess.parent.config.Consumer.Group.ResetInvalidOffsets {\n\t\toffset = sess.parent.config.Consumer.Offsets.Initial\n\t\tpcm, err = sess.parent.consumer.ConsumePartition(topic, partition, offset)\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tgo func() {\n\t\tfor err := range pcm.Errors() {\n\t\t\tsess.parent.handleError(err, topic, partition)\n\t\t}\n\t}()\n\n\treturn &consumerGroupClaim{\n\t\ttopic:             topic,\n\t\tpartition:         partition,\n\t\toffset:            offset,\n\t\tPartitionConsumer: pcm,\n\t}, nil\n}\n\nfunc (c *consumerGroupClaim) Topic() string        { return c.topic }\nfunc (c *consumerGroupClaim) Partition() int32     { return c.partition }\nfunc (c *consumerGroupClaim) InitialOffset() int64 { return c.offset }\n\n// Drains messages and errors, ensures the claim is fully closed.\nfunc (c *consumerGroupClaim) waitClosed() (errs ConsumerErrors) {\n\tgo func() {\n\t\tfor range c.Messages() {\n\t\t}\n\t}()\n\n\tfor err := range c.Errors() {\n\t\terrs = append(errs, err)\n\t}\n\treturn\n}\n"
  },
  {
    "path": "consumer_group_example_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"context\"\n\t\"fmt\"\n)\n\ntype exampleConsumerGroupHandler struct{}\n\nfunc (exampleConsumerGroupHandler) Setup(_ ConsumerGroupSession) error   { return nil }\nfunc (exampleConsumerGroupHandler) Cleanup(_ ConsumerGroupSession) error { return nil }\nfunc (h exampleConsumerGroupHandler) ConsumeClaim(sess ConsumerGroupSession, claim ConsumerGroupClaim) error {\n\tfor msg := range claim.Messages() {\n\t\tfmt.Printf(\"Message topic:%q partition:%d offset:%d\\n\", msg.Topic, msg.Partition, msg.Offset)\n\t\tsess.MarkMessage(msg, \"\")\n\t}\n\treturn nil\n}\n\nfunc ExampleConsumerGroup() {\n\tconfig := NewTestConfig()\n\tconfig.Version = V2_0_0_0 // specify appropriate version\n\tconfig.Consumer.Return.Errors = true\n\n\tgroup, err := NewConsumerGroup([]string{\"localhost:9092\"}, \"my-group\", config)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer func() { _ = group.Close() }()\n\n\t// Track errors\n\tgo func() {\n\t\tfor err := range group.Errors() {\n\t\t\tfmt.Println(\"ERROR\", err)\n\t\t}\n\t}()\n\n\t// Iterate over consumer sessions.\n\tctx := context.Background()\n\tfor {\n\t\ttopics := []string{\"my-topic\"}\n\t\thandler := exampleConsumerGroupHandler{}\n\n\t\t// `Consume` should be called inside an infinite loop, when a\n\t\t// server-side rebalance happens, the consumer session will need to be\n\t\t// recreated to get the new claims\n\t\terr := group.Consume(ctx, topics, handler)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "consumer_group_members.go",
    "content": "package sarama\n\nimport \"errors\"\n\n// ConsumerGroupMemberMetadata holds the metadata for consumer group\n// https://github.com/apache/kafka/blob/trunk/clients/src/main/resources/common/message/ConsumerProtocolSubscription.json\ntype ConsumerGroupMemberMetadata struct {\n\tVersion         int16\n\tTopics          []string\n\tUserData        []byte\n\tOwnedPartitions []*OwnedPartition\n\tGenerationID    int32\n\tRackID          *string\n}\n\nfunc (m *ConsumerGroupMemberMetadata) encode(pe packetEncoder) error {\n\tpe.putInt16(m.Version)\n\n\tif err := pe.putStringArray(m.Topics); err != nil {\n\t\treturn err\n\t}\n\n\tif err := pe.putBytes(m.UserData); err != nil {\n\t\treturn err\n\t}\n\n\tif m.Version >= 1 {\n\t\tif err := pe.putArrayLength(len(m.OwnedPartitions)); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor _, op := range m.OwnedPartitions {\n\t\t\tif err := op.encode(pe); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\tif m.Version >= 2 {\n\t\tpe.putInt32(m.GenerationID)\n\t}\n\n\tif m.Version >= 3 {\n\t\tif err := pe.putNullableString(m.RackID); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (m *ConsumerGroupMemberMetadata) decode(pd packetDecoder) (err error) {\n\tif m.Version, err = pd.getInt16(); err != nil {\n\t\treturn\n\t}\n\n\tif m.Topics, err = pd.getStringArray(); err != nil {\n\t\treturn\n\t}\n\n\tif m.UserData, err = pd.getBytes(); err != nil {\n\t\treturn\n\t}\n\tif m.Version >= 1 {\n\t\tn, err := pd.getArrayLength()\n\t\tif err != nil {\n\t\t\t// permit missing data here in case of misbehaving 3rd party\n\t\t\t// clients who incorrectly marked the member metadata as V1 in\n\t\t\t// their JoinGroup request\n\t\t\tif errors.Is(err, ErrInsufficientData) {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\t\tif n > 0 {\n\t\t\tm.OwnedPartitions = make([]*OwnedPartition, n)\n\t\t\tfor i := 0; i < n; i++ {\n\t\t\t\tm.OwnedPartitions[i] = &OwnedPartition{}\n\t\t\t\tif err := m.OwnedPartitions[i].decode(pd); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif m.Version >= 2 {\n\t\tif m.GenerationID, err = pd.getInt32(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif m.Version >= 3 {\n\t\tif m.RackID, err = pd.getNullableString(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\ntype OwnedPartition struct {\n\tTopic      string\n\tPartitions []int32\n}\n\nfunc (m *OwnedPartition) encode(pe packetEncoder) error {\n\tif err := pe.putString(m.Topic); err != nil {\n\t\treturn err\n\t}\n\tif err := pe.putInt32Array(m.Partitions); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (m *OwnedPartition) decode(pd packetDecoder) (err error) {\n\tif m.Topic, err = pd.getString(); err != nil {\n\t\treturn err\n\t}\n\tif m.Partitions, err = pd.getInt32Array(); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// ConsumerGroupMemberAssignment holds the member assignment for a consume group\n// https://github.com/apache/kafka/blob/trunk/clients/src/main/resources/common/message/ConsumerProtocolAssignment.json\ntype ConsumerGroupMemberAssignment struct {\n\tVersion  int16\n\tTopics   map[string][]int32\n\tUserData []byte\n}\n\nfunc (m *ConsumerGroupMemberAssignment) encode(pe packetEncoder) error {\n\tpe.putInt16(m.Version)\n\n\tif err := pe.putArrayLength(len(m.Topics)); err != nil {\n\t\treturn err\n\t}\n\n\tfor topic, partitions := range m.Topics {\n\t\tif err := pe.putString(topic); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := pe.putInt32Array(partitions); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif err := pe.putBytes(m.UserData); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (m *ConsumerGroupMemberAssignment) decode(pd packetDecoder) (err error) {\n\tif m.Version, err = pd.getInt16(); err != nil {\n\t\treturn\n\t}\n\n\tvar topicLen int\n\tif topicLen, err = pd.getArrayLength(); err != nil {\n\t\treturn\n\t}\n\n\tm.Topics = make(map[string][]int32, topicLen)\n\tfor i := 0; i < topicLen; i++ {\n\t\tvar topic string\n\t\tif topic, err = pd.getString(); err != nil {\n\t\t\treturn\n\t\t}\n\t\tif m.Topics[topic], err = pd.getInt32Array(); err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif m.UserData, err = pd.getBytes(); err != nil {\n\t\treturn\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "consumer_group_members_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"bytes\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nvar (\n\tgroupMemberMetadataV0 = []byte{\n\t\t0, 0, // Version\n\t\t0, 0, 0, 2, // Topic array length\n\t\t0, 3, 'o', 'n', 'e', // Topic one\n\t\t0, 3, 't', 'w', 'o', // Topic two\n\t\t0, 0, 0, 3, 0x01, 0x02, 0x03, // Userdata\n\t}\n\tgroupMemberAssignmentV0 = []byte{\n\t\t0, 0, // Version\n\t\t0, 0, 0, 1, // Topic array length\n\t\t0, 3, 'o', 'n', 'e', // Topic one\n\t\t0, 0, 0, 3, // Topic one, partition array length\n\t\t0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 4, // 0, 2, 4\n\t\t0, 0, 0, 3, 0x01, 0x02, 0x03, // Userdata\n\t}\n\n\t// notably it looks like the old 3rdparty bsm/sarama-cluster incorrectly\n\t// set V1 in the member metadata when it sent the JoinGroup request so\n\t// we need to cope with that one being too short\n\tgroupMemberMetadataV1Bad = []byte{\n\t\t0, 1, // Version\n\t\t0, 0, 0, 2, // Topic array length\n\t\t0, 3, 'o', 'n', 'e', // Topic one\n\t\t0, 3, 't', 'w', 'o', // Topic two\n\t\t0, 0, 0, 3, 0x01, 0x02, 0x03, // Userdata\n\t}\n\n\tgroupMemberMetadataV1 = []byte{\n\t\t0, 1, // Version\n\t\t0, 0, 0, 2, // Topic array length\n\t\t0, 3, 'o', 'n', 'e', // Topic one\n\t\t0, 3, 't', 'w', 'o', // Topic two\n\t\t0, 0, 0, 3, 0x01, 0x02, 0x03, // Userdata\n\t\t0, 0, 0, 0, // OwnedPartitions KIP-429\n\t}\n\n\tgroupMemberMetadataV3NilOwned = []byte{\n\t\t0, 3, // Version\n\t\t0, 0, 0, 1, // Topic array length\n\t\t0, 3, 'o', 'n', 'e', // Topic one\n\t\t0, 0, 0, 3, 0x01, 0x02, 0x03, // Userdata\n\t\t0, 0, 0, 0, // OwnedPartitions KIP-429\n\t\t0, 0, 0, 64, // GenerationID\n\t\t0, 4, 'r', 'a', 'c', 'k', // RackID\n\t}\n)\n\nfunc TestConsumerGroupMemberMetadata(t *testing.T) {\n\tmeta := &ConsumerGroupMemberMetadata{\n\t\tVersion:  0,\n\t\tTopics:   []string{\"one\", \"two\"},\n\t\tUserData: []byte{0x01, 0x02, 0x03},\n\t}\n\n\tbuf, err := encode(meta, nil)\n\tif err != nil {\n\t\tt.Error(\"Failed to encode data\", err)\n\t} else if !bytes.Equal(groupMemberMetadataV0, buf) {\n\t\tt.Errorf(\"Encoded data does not match expectation\\nexpected: %v\\nactual: %v\", groupMemberMetadataV0, buf)\n\t}\n\n\tmeta2 := new(ConsumerGroupMemberMetadata)\n\terr = decode(buf, meta2, nil)\n\tif err != nil {\n\t\tt.Error(\"Failed to decode data\", err)\n\t} else if !reflect.DeepEqual(meta, meta2) {\n\t\tt.Errorf(\"Encoded data does not match expectation\\nexpected: %v\\nactual: %v\", meta, meta2)\n\t}\n}\n\nfunc TestConsumerGroupMemberMetadataV1Decode(t *testing.T) {\n\tmeta := new(ConsumerGroupMemberMetadata)\n\tif err := decode(groupMemberMetadataV1, meta, nil); err != nil {\n\t\tt.Error(\"Failed to decode V1 data\", err)\n\t}\n\tif err := decode(groupMemberMetadataV1Bad, meta, nil); err != nil {\n\t\tt.Error(\"Failed to decode V1 'bad' data\", err)\n\t}\n}\n\nfunc TestConsumerGroupMemberMetadataV3Decode(t *testing.T) {\n\tmeta := new(ConsumerGroupMemberMetadata)\n\tif err := decode(groupMemberMetadataV3NilOwned, meta, nil); err != nil {\n\t\tt.Error(\"Failed to decode V3 data\", err)\n\t}\n}\n\nfunc TestConsumerGroupMemberAssignment(t *testing.T) {\n\tamt := &ConsumerGroupMemberAssignment{\n\t\tVersion: 0,\n\t\tTopics: map[string][]int32{\n\t\t\t\"one\": {0, 2, 4},\n\t\t},\n\t\tUserData: []byte{0x01, 0x02, 0x03},\n\t}\n\n\tbuf, err := encode(amt, nil)\n\tif err != nil {\n\t\tt.Error(\"Failed to encode data\", err)\n\t} else if !bytes.Equal(groupMemberAssignmentV0, buf) {\n\t\tt.Errorf(\"Encoded data does not match expectation\\nexpected: %v\\nactual: %v\", groupMemberAssignmentV0, buf)\n\t}\n\n\tamt2 := new(ConsumerGroupMemberAssignment)\n\terr = decode(buf, amt2, nil)\n\tif err != nil {\n\t\tt.Error(\"Failed to decode data\", err)\n\t} else if !reflect.DeepEqual(amt, amt2) {\n\t\tt.Errorf(\"Encoded data does not match expectation\\nexpected: %v\\nactual: %v\", amt, amt2)\n\t}\n}\n"
  },
  {
    "path": "consumer_group_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\tassert \"github.com/stretchr/testify/require\"\n)\n\ntype handler struct {\n\tmessageCh chan *ConsumerMessage\n}\n\nfunc (h *handler) Setup(s ConsumerGroupSession) error   { return nil }\nfunc (h *handler) Cleanup(s ConsumerGroupSession) error { return nil }\nfunc (h *handler) ConsumeClaim(sess ConsumerGroupSession, claim ConsumerGroupClaim) error {\n\tfor {\n\t\tselect {\n\t\tcase msg := <-claim.Messages():\n\t\t\tsess.MarkMessage(msg, \"\")\n\t\t\th.messageCh <- msg\n\t\tcase <-sess.Context().Done():\n\t\t\th.messageCh <- &ConsumerMessage{Value: []byte(\"session done\")}\n\t\t\treturn nil\n\t\t}\n\t}\n}\n\nfunc TestNewConsumerGroupFromClient(t *testing.T) {\n\tt.Run(\"should not permit nil client\", func(t *testing.T) {\n\t\tgroup, err := NewConsumerGroupFromClient(\"group\", nil)\n\t\tassert.Nil(t, group)\n\t\tassert.Error(t, err)\n\t})\n}\n\n// TestConsumerGroupNewSessionDuringOffsetLoad ensures that the consumer group\n// will retry Join and Sync group operations, if it receives a temporary\n// OffsetsLoadInProgress error response, in the same way as it would for a\n// RebalanceInProgress.\nfunc TestConsumerGroupNewSessionDuringOffsetLoad(t *testing.T) {\n\tconfig := NewTestConfig()\n\tconfig.ClientID = t.Name()\n\tconfig.Version = V2_0_0_0\n\tconfig.Consumer.Return.Errors = true\n\tconfig.Consumer.Group.Rebalance.Retry.Max = 2\n\tconfig.Consumer.Group.Rebalance.Retry.Backoff = 0\n\tconfig.Consumer.Offsets.AutoCommit.Enable = false\n\n\tbroker0 := NewMockBroker(t, 0)\n\tdefer broker0.Close()\n\n\tbroker0.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetBroker(broker0.Addr(), broker0.BrokerID()).\n\t\t\tSetLeader(\"my-topic\", 0, broker0.BrokerID()),\n\t\t\"OffsetRequest\": NewMockOffsetResponse(t).\n\t\t\tSetOffset(\"my-topic\", 0, OffsetOldest, 0).\n\t\t\tSetOffset(\"my-topic\", 0, OffsetNewest, 1),\n\t\t\"FindCoordinatorRequest\": NewMockFindCoordinatorResponse(t).\n\t\t\tSetCoordinator(CoordinatorGroup, \"my-group\", broker0),\n\t\t\"HeartbeatRequest\": NewMockHeartbeatResponse(t),\n\t\t\"JoinGroupRequest\": NewMockSequence(\n\t\t\tNewMockJoinGroupResponse(t).SetError(ErrOffsetsLoadInProgress),\n\t\t\tNewMockJoinGroupResponse(t).SetGroupProtocol(RangeBalanceStrategyName),\n\t\t),\n\t\t\"SyncGroupRequest\": NewMockSequence(\n\t\t\tNewMockSyncGroupResponse(t).SetError(ErrOffsetsLoadInProgress),\n\t\t\tNewMockSyncGroupResponse(t).SetMemberAssignment(\n\t\t\t\t&ConsumerGroupMemberAssignment{\n\t\t\t\t\tVersion: 0,\n\t\t\t\t\tTopics: map[string][]int32{\n\t\t\t\t\t\t\"my-topic\": {0},\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t),\n\t\t\"OffsetFetchRequest\": NewMockOffsetFetchResponse(t).SetOffset(\n\t\t\t\"my-group\", \"my-topic\", 0, 0, \"\", ErrNoError,\n\t\t).SetError(ErrNoError),\n\t\t\"FetchRequest\": NewMockSequence(\n\t\t\tNewMockFetchResponse(t, 1).\n\t\t\t\tSetMessage(\"my-topic\", 0, 0, StringEncoder(\"foo\")),\n\t\t\tNewMockFetchResponse(t, 1).\n\t\t\t\tSetMessage(\"my-topic\", 0, 1, StringEncoder(\"bar\")),\n\t\t),\n\t})\n\n\tgroup, err := NewConsumerGroup([]string{broker0.Addr()}, \"my-group\", config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tctx := context.Background()\n\th := &handler{make(chan *ConsumerMessage)}\n\tdefer close(h.messageCh)\n\n\tgo func() {\n\t\ttopics := []string{\"my-topic\"}\n\t\tif err := group.Consume(ctx, topics, h); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}()\n\n\tassert.Equal(t, \"foo\", string((<-h.messageCh).Value))\n\tassert.Equal(t, \"bar\", string((<-h.messageCh).Value))\n\tgo func() {\n\t\tif err := group.Close(); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}()\n\tassert.Equal(t, \"session done\", string((<-h.messageCh).Value))\n}\n\nfunc TestConsume_RaceTest(t *testing.T) {\n\tconst (\n\t\tgroupID     = \"test-group\"\n\t\ttopic       = \"test-topic\"\n\t\toffsetStart = int64(1234)\n\t)\n\n\tcfg := NewTestConfig()\n\tcfg.Version = V2_8_1_0\n\tcfg.Consumer.Return.Errors = true\n\tcfg.Metadata.Full = true\n\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\n\thandlerMap := map[string]MockResponse{\n\t\t\"ApiVersionsRequest\": NewMockApiVersionsResponse(t),\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()).\n\t\t\tSetError(\"mismatched-topic\", ErrUnknownTopicOrPartition),\n\t\t\"OffsetRequest\": NewMockOffsetResponse(t).\n\t\t\tSetOffset(topic, 0, -1, offsetStart),\n\t\t\"OffsetFetchRequest\": NewMockOffsetFetchResponse(t).\n\t\t\tSetOffset(groupID, topic, 0, offsetStart, \"\", ErrNoError),\n\t\t\"FindCoordinatorRequest\": NewMockFindCoordinatorResponse(t).\n\t\t\tSetCoordinator(CoordinatorGroup, groupID, seedBroker),\n\t\t\"JoinGroupRequest\": NewMockJoinGroupResponse(t),\n\t\t\"SyncGroupRequest\": NewMockSyncGroupResponse(t).SetMemberAssignment(\n\t\t\t&ConsumerGroupMemberAssignment{\n\t\t\t\tVersion:  1,\n\t\t\t\tTopics:   map[string][]int32{topic: {0}}, // map \"test-topic\" to partition 0\n\t\t\t\tUserData: []byte{0x01},\n\t\t\t},\n\t\t),\n\t\t\"HeartbeatRequest\": NewMockHeartbeatResponse(t),\n\t}\n\tseedBroker.SetHandlerByMap(handlerMap)\n\n\tcancelCtx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Second))\n\n\tretryWait := 10 * time.Millisecond\n\tvar err error\n\tclientRetries := 0\nouterFor:\n\tfor {\n\t\t_, err = NewConsumerGroup([]string{seedBroker.Addr()}, groupID, cfg)\n\t\tif err == nil {\n\t\t\tbreak\n\t\t}\n\n\t\tif retryWait < time.Minute {\n\t\t\tretryWait *= 2\n\t\t}\n\n\t\tclientRetries++\n\n\t\ttimer := time.NewTimer(retryWait)\n\t\tselect {\n\t\tcase <-cancelCtx.Done():\n\t\t\terr = cancelCtx.Err()\n\t\t\ttimer.Stop()\n\t\t\tbreak outerFor\n\t\tcase <-timer.C:\n\t\t}\n\t\ttimer.Stop()\n\t}\n\tif err == nil {\n\t\tt.Fatalf(\"should not proceed to Consume\")\n\t}\n\n\tif clientRetries <= 1 {\n\t\tt.Errorf(\"clientRetries = %v; want > 1\", clientRetries)\n\t}\n\n\tif err != nil && !errors.Is(err, context.DeadlineExceeded) {\n\t\tt.Fatal(err)\n\t}\n\n\tcancel()\n}\n\n// TestConsumerGroupSessionDoesNotRetryForever ensures that an error fetching\n// the coordinator decrements the retry attempts and doesn't end up retrying\n// forever\nfunc TestConsumerGroupSessionDoesNotRetryForever(t *testing.T) {\n\tconfig := NewTestConfig()\n\tconfig.ClientID = t.Name()\n\tconfig.Version = V2_0_0_0\n\tconfig.Consumer.Return.Errors = true\n\tconfig.Consumer.Group.Rebalance.Retry.Max = 1\n\tconfig.Consumer.Group.Rebalance.Retry.Backoff = 0\n\n\tbroker0 := NewMockBroker(t, 0)\n\tdefer broker0.Close()\n\n\tbroker0.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetBroker(broker0.Addr(), broker0.BrokerID()).\n\t\t\tSetLeader(\"my-topic\", 0, broker0.BrokerID()),\n\t\t\"FindCoordinatorRequest\": NewMockFindCoordinatorResponse(t).\n\t\t\tSetError(CoordinatorGroup, \"my-group\", ErrGroupAuthorizationFailed),\n\t})\n\n\tgroup, err := NewConsumerGroup([]string{broker0.Addr()}, \"my-group\", config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer func() { _ = group.Close() }()\n\n\tctx := context.Background()\n\th := &handler{}\n\n\tvar wg sync.WaitGroup\n\twg.Add(1)\n\n\tgo func() {\n\t\ttopics := []string{\"my-topic\"}\n\t\terr := group.Consume(ctx, topics, h)\n\t\tassert.Error(t, err)\n\t\twg.Done()\n\t}()\n\n\twg.Wait()\n}\n\nfunc TestConsumerShouldNotRetrySessionIfContextCancelled(t *testing.T) {\n\tc := &consumerGroup{\n\t\tconfig: NewTestConfig(),\n\t}\n\tctx, cancel := context.WithCancel(context.Background())\n\tcancel()\n\t_, err := c.newSession(ctx, nil, nil, 1024)\n\tassert.Equal(t, context.Canceled, err)\n\t_, err = c.retryNewSession(ctx, nil, nil, 1024, true)\n\tassert.Equal(t, context.Canceled, err)\n}\n"
  },
  {
    "path": "consumer_metadata_request.go",
    "content": "package sarama\n\n// ConsumerMetadataRequest is used for metadata requests\ntype ConsumerMetadataRequest struct {\n\tVersion       int16\n\tConsumerGroup string\n}\n\nfunc (r *ConsumerMetadataRequest) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *ConsumerMetadataRequest) encode(pe packetEncoder) error {\n\ttmp := new(FindCoordinatorRequest)\n\ttmp.CoordinatorKey = r.ConsumerGroup\n\ttmp.CoordinatorType = CoordinatorGroup\n\ttmp.Version = r.Version\n\treturn tmp.encode(pe)\n}\n\nfunc (r *ConsumerMetadataRequest) decode(pd packetDecoder, version int16) (err error) {\n\ttmp := new(FindCoordinatorRequest)\n\tif err := tmp.decode(pd, version); err != nil {\n\t\treturn err\n\t}\n\tr.ConsumerGroup = tmp.CoordinatorKey\n\treturn nil\n}\n\nfunc (r *ConsumerMetadataRequest) key() int16 {\n\treturn apiKeyFindCoordinator\n}\n\nfunc (r *ConsumerMetadataRequest) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *ConsumerMetadataRequest) headerVersion() int16 {\n\treturn 1\n}\n\nfunc (r *ConsumerMetadataRequest) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 2\n}\n\nfunc (r *ConsumerMetadataRequest) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 2:\n\t\treturn V2_0_0_0\n\tcase 1:\n\t\treturn V0_11_0_0\n\tdefault:\n\t\treturn V0_8_2_0\n\t}\n}\n"
  },
  {
    "path": "consumer_metadata_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"testing\"\n)\n\nvar (\n\tconsumerMetadataRequestEmpty = []byte{\n\t\t0x00, 0x00,\n\t}\n\n\tconsumerMetadataRequestString = []byte{\n\t\t0x00, 0x06, 'f', 'o', 'o', 'b', 'a', 'r',\n\t}\n)\n\nfunc TestConsumerMetadataRequest(t *testing.T) {\n\trequest := new(ConsumerMetadataRequest)\n\ttestEncodable(t, \"empty string\", request, consumerMetadataRequestEmpty)\n\ttestVersionDecodable(t, \"empty string\", request, consumerMetadataRequestEmpty, 0)\n\n\trequest.ConsumerGroup = \"foobar\"\n\ttestEncodable(t, \"with string\", request, consumerMetadataRequestString)\n\ttestVersionDecodable(t, \"with string\", request, consumerMetadataRequestString, 0)\n}\n"
  },
  {
    "path": "consumer_metadata_response.go",
    "content": "package sarama\n\nimport (\n\t\"net\"\n\t\"strconv\"\n)\n\n// ConsumerMetadataResponse holds the response for a consumer group meta data requests\ntype ConsumerMetadataResponse struct {\n\tVersion         int16\n\tErr             KError\n\tCoordinator     *Broker\n\tCoordinatorID   int32  // deprecated: use Coordinator.ID()\n\tCoordinatorHost string // deprecated: use Coordinator.Addr()\n\tCoordinatorPort int32  // deprecated: use Coordinator.Addr()\n}\n\nfunc (r *ConsumerMetadataResponse) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *ConsumerMetadataResponse) decode(pd packetDecoder, version int16) (err error) {\n\ttmp := new(FindCoordinatorResponse)\n\n\tif err := tmp.decode(pd, version); err != nil {\n\t\treturn err\n\t}\n\n\tr.Err = tmp.Err\n\n\tr.Coordinator = tmp.Coordinator\n\tif tmp.Coordinator == nil {\n\t\treturn nil\n\t}\n\n\t// this can all go away in 2.0, but we have to fill in deprecated fields to maintain\n\t// backwards compatibility\n\thost, portstr, err := net.SplitHostPort(r.Coordinator.Addr())\n\tif err != nil {\n\t\treturn err\n\t}\n\tport, err := strconv.ParseInt(portstr, 10, 32)\n\tif err != nil {\n\t\treturn err\n\t}\n\tr.CoordinatorID = r.Coordinator.ID()\n\tr.CoordinatorHost = host\n\tr.CoordinatorPort = int32(port)\n\n\treturn nil\n}\n\nfunc (r *ConsumerMetadataResponse) encode(pe packetEncoder) error {\n\tif r.Coordinator == nil {\n\t\tr.Coordinator = new(Broker)\n\t\tr.Coordinator.id = r.CoordinatorID\n\t\tr.Coordinator.addr = net.JoinHostPort(r.CoordinatorHost, strconv.Itoa(int(r.CoordinatorPort)))\n\t}\n\n\ttmp := &FindCoordinatorResponse{\n\t\tVersion:     r.Version,\n\t\tErr:         r.Err,\n\t\tCoordinator: r.Coordinator,\n\t}\n\n\tif err := tmp.encode(pe); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (r *ConsumerMetadataResponse) key() int16 {\n\treturn apiKeyFindCoordinator\n}\n\nfunc (r *ConsumerMetadataResponse) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *ConsumerMetadataResponse) headerVersion() int16 {\n\treturn 0\n}\n\nfunc (r *ConsumerMetadataResponse) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 2\n}\n\nfunc (r *ConsumerMetadataResponse) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 2:\n\t\treturn V2_0_0_0\n\tcase 1:\n\t\treturn V0_11_0_0\n\tdefault:\n\t\treturn V0_8_2_0\n\t}\n}\n"
  },
  {
    "path": "consumer_metadata_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"errors\"\n\t\"testing\"\n)\n\nvar (\n\tconsumerMetadataResponseError = []byte{\n\t\t0x00, 0x0E,\n\t\t0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x00,\n\t}\n\n\tconsumerMetadataResponseSuccess = []byte{\n\t\t0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0xAB,\n\t\t0x00, 0x03, 'f', 'o', 'o',\n\t\t0x00, 0x00, 0xCC, 0xDD,\n\t}\n)\n\nfunc TestConsumerMetadataResponseError(t *testing.T) {\n\tresponse := &ConsumerMetadataResponse{Err: ErrOffsetsLoadInProgress}\n\ttestEncodable(t, \"\", response, consumerMetadataResponseError)\n\n\tdecodedResp := &ConsumerMetadataResponse{}\n\tif err := versionedDecode(consumerMetadataResponseError, decodedResp, 0, nil); err != nil {\n\t\tt.Error(\"could not decode: \", err)\n\t}\n\n\tif !errors.Is(decodedResp.Err, ErrOffsetsLoadInProgress) {\n\t\tt.Errorf(\"got %s, want %s\", decodedResp.Err, ErrOffsetsLoadInProgress)\n\t}\n}\n\nfunc TestConsumerMetadataResponseSuccess(t *testing.T) {\n\tbroker := NewBroker(\"foo:52445\")\n\tbroker.id = 0xAB\n\tresponse := ConsumerMetadataResponse{\n\t\tCoordinator:     broker,\n\t\tCoordinatorID:   0xAB,\n\t\tCoordinatorHost: \"foo\",\n\t\tCoordinatorPort: 0xCCDD,\n\t\tErr:             ErrNoError,\n\t}\n\ttestResponse(t, \"success\", &response, consumerMetadataResponseSuccess)\n}\n"
  },
  {
    "path": "consumer_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"log\"\n\t\"os\"\n\t\"os/signal\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n)\n\nvar (\n\ttestMsg = StringEncoder(\"Foo\")\n\ttestKey = StringEncoder(\"Bar\")\n)\n\n// If a particular offset is provided then messages are consumed starting from\n// that offset.\nfunc TestConsumerOffsetManual(t *testing.T) {\n\t// Given\n\tbroker0 := NewMockBroker(t, 0)\n\n\tmanualOffset := int64(1234)\n\toffsetNewest := int64(2345)\n\toffsetNewestAfterFetchRequest := int64(3456)\n\n\tmockFetchResponse := NewMockFetchResponse(t, 1)\n\n\t// skipped because parseRecords(): offset < child.offset\n\tmockFetchResponse.SetMessage(\"my_topic\", 0, manualOffset-1, testMsg)\n\n\tfor i := int64(0); i < 10; i++ {\n\t\tmockFetchResponse.SetMessage(\"my_topic\", 0, i+manualOffset, testMsg)\n\t}\n\n\tmockFetchResponse.SetHighWaterMark(\"my_topic\", 0, offsetNewestAfterFetchRequest)\n\n\tbroker0.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetBroker(broker0.Addr(), broker0.BrokerID()).\n\t\t\tSetLeader(\"my_topic\", 0, broker0.BrokerID()),\n\t\t\"OffsetRequest\": NewMockOffsetResponse(t).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetOldest, 0).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetNewest, offsetNewest),\n\t\t\"FetchRequest\": mockFetchResponse,\n\t})\n\n\t// When\n\tmaster, err := NewConsumer([]string{broker0.Addr()}, NewTestConfig())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tconsumer, err := master.ConsumePartition(\"my_topic\", 0, manualOffset)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Then\n\tif hwmo := consumer.HighWaterMarkOffset(); hwmo != offsetNewest {\n\t\tt.Errorf(\"Expected high water mark offset %d, found %d\", offsetNewest, hwmo)\n\t}\n\tfor i := int64(0); i < 10; i++ {\n\t\tselect {\n\t\tcase message := <-consumer.Messages():\n\t\t\tassertMessageOffset(t, message, i+manualOffset)\n\t\tcase err := <-consumer.Errors():\n\t\t\tt.Error(err)\n\t\t}\n\t}\n\n\tif hwmo := consumer.HighWaterMarkOffset(); hwmo != offsetNewestAfterFetchRequest {\n\t\tt.Errorf(\"Expected high water mark offset %d, found %d\", offsetNewestAfterFetchRequest, hwmo)\n\t}\n\n\tsafeClose(t, consumer)\n\tsafeClose(t, master)\n\tbroker0.Close()\n}\n\n// If a message is given a key, it can be correctly collected while consuming.\nfunc TestConsumerMessageWithKey(t *testing.T) {\n\t// Given\n\tbroker0 := NewMockBroker(t, 0)\n\n\tmanualOffset := int64(1234)\n\toffsetNewest := int64(2345)\n\toffsetNewestAfterFetchRequest := int64(3456)\n\n\tmockFetchResponse := NewMockFetchResponse(t, 1)\n\n\t// skipped because parseRecords(): offset < child.offset\n\tmockFetchResponse.SetMessageWithKey(\"my_topic\", 0, manualOffset-1, testKey, testMsg)\n\n\tfor i := int64(0); i < 10; i++ {\n\t\tmockFetchResponse.SetMessageWithKey(\"my_topic\", 0, i+manualOffset, testKey, testMsg)\n\t}\n\n\tmockFetchResponse.SetHighWaterMark(\"my_topic\", 0, offsetNewestAfterFetchRequest)\n\n\tbroker0.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetBroker(broker0.Addr(), broker0.BrokerID()).\n\t\t\tSetLeader(\"my_topic\", 0, broker0.BrokerID()),\n\t\t\"OffsetRequest\": NewMockOffsetResponse(t).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetOldest, 0).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetNewest, offsetNewest),\n\t\t\"FetchRequest\": mockFetchResponse,\n\t})\n\n\t// When\n\tmaster, err := NewConsumer([]string{broker0.Addr()}, NewTestConfig())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tconsumer, err := master.ConsumePartition(\"my_topic\", 0, manualOffset)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Then\n\tif hwmo := consumer.HighWaterMarkOffset(); hwmo != offsetNewest {\n\t\tt.Errorf(\"Expected high water mark offset %d, found %d\", offsetNewest, hwmo)\n\t}\n\tfor i := int64(0); i < 10; i++ {\n\t\tselect {\n\t\tcase message := <-consumer.Messages():\n\t\t\tassertMessageOffset(t, message, i+manualOffset)\n\t\t\tassertMessageKey(t, message, testKey)\n\t\t\tassertMessageValue(t, message, testMsg)\n\t\tcase err := <-consumer.Errors():\n\t\t\tt.Error(err)\n\t\t}\n\t}\n\n\tif hwmo := consumer.HighWaterMarkOffset(); hwmo != offsetNewestAfterFetchRequest {\n\t\tt.Errorf(\"Expected high water mark offset %d, found %d\", offsetNewestAfterFetchRequest, hwmo)\n\t}\n\n\tsafeClose(t, consumer)\n\tsafeClose(t, master)\n\tbroker0.Close()\n}\n\nfunc TestPauseResumeConsumption(t *testing.T) {\n\t// Given\n\tbroker0 := NewMockBroker(t, 0)\n\n\tconst newestOffsetBroker = 1233\n\tconst maxOffsetBroker = newestOffsetBroker + 10\n\toffsetBroker := newestOffsetBroker\n\toffsetClient := offsetBroker\n\n\tmockFetchResponse := NewMockFetchResponse(t, 1)\n\tmockFetchResponse.SetMessage(\"my_topic\", 0, int64(newestOffsetBroker), testMsg)\n\toffsetBroker++\n\n\tbrokerResponses := map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetBroker(broker0.Addr(), broker0.BrokerID()).\n\t\t\tSetLeader(\"my_topic\", 0, broker0.BrokerID()),\n\t\t\"OffsetRequest\": NewMockOffsetResponse(t).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetOldest, 0).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetNewest, int64(newestOffsetBroker)),\n\t\t\"FetchRequest\": mockFetchResponse,\n\t}\n\n\tbroker0.SetHandlerByMap(brokerResponses)\n\n\t// When\n\tmaster, err := NewConsumer([]string{broker0.Addr()}, NewTestConfig())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tconsumer, err := master.ConsumePartition(\"my_topic\", 0, OffsetNewest)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// pause the consumption\n\tconsumer.Pause()\n\n\t// set more msgs on broker\n\tfor ; offsetBroker < maxOffsetBroker; offsetBroker++ {\n\t\tmockFetchResponse = mockFetchResponse.SetMessage(\"my_topic\", 0, int64(offsetBroker), testMsg)\n\t}\n\tbrokerResponses[\"FetchRequest\"] = mockFetchResponse\n\tbroker0.SetHandlerByMap(brokerResponses)\n\n\tkeepConsuming := true\n\tfor keepConsuming {\n\t\tselect {\n\t\tcase message := <-consumer.Messages():\n\t\t\t// only the first msg is expected to be consumed\n\t\t\toffsetClient++\n\t\t\tassertMessageOffset(t, message, int64(newestOffsetBroker))\n\t\tcase err := <-consumer.Errors():\n\t\t\tt.Fatal(err)\n\t\tcase <-time.After(time.Second):\n\t\t\t// is expected to timedout once the consumption is pauses\n\t\t\tkeepConsuming = false\n\t\t}\n\t}\n\n\t// lets resume the consumption in order to consume the new msgs\n\tconsumer.Resume()\n\n\tfor offsetClient < maxOffsetBroker {\n\t\tselect {\n\t\tcase message := <-consumer.Messages():\n\t\t\tassertMessageOffset(t, message, int64(offsetClient))\n\t\t\toffsetClient += 1\n\t\tcase err := <-consumer.Errors():\n\t\t\tt.Fatal(\"Error: \", err)\n\t\tcase <-time.After(time.Second * 10):\n\t\t\tt.Fatal(\"consumer timed out . Offset: \", offsetClient)\n\t\t}\n\t}\n\n\tsafeClose(t, consumer)\n\tsafeClose(t, master)\n\tbroker0.Close()\n}\n\n// If `OffsetNewest` is passed as the initial offset then the first consumed\n// message indeed corresponds to the offset that broker claims to be the\n// newest in its metadata response.\nfunc TestConsumerOffsetNewest(t *testing.T) {\n\t// Given\n\toffsetNewest := int64(10)\n\toffsetNewestAfterFetchRequest := int64(50)\n\tbroker0 := NewMockBroker(t, 0)\n\tbroker0.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetBroker(broker0.Addr(), broker0.BrokerID()).\n\t\t\tSetLeader(\"my_topic\", 0, broker0.BrokerID()),\n\t\t\"OffsetRequest\": NewMockOffsetResponse(t).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetNewest, offsetNewest).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetOldest, 7),\n\t\t\"FetchRequest\": NewMockFetchResponse(t, 1).\n\t\t\tSetMessage(\"my_topic\", 0, 9, testMsg). // skipped because parseRecords(): offset < child.offset\n\t\t\tSetMessage(\"my_topic\", 0, 10, testMsg).\n\t\t\tSetMessage(\"my_topic\", 0, 11, testMsg).\n\t\t\tSetHighWaterMark(\"my_topic\", 0, offsetNewestAfterFetchRequest),\n\t})\n\n\tmaster, err := NewConsumer([]string{broker0.Addr()}, NewTestConfig())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// When\n\tconsumer, err := master.ConsumePartition(\"my_topic\", 0, OffsetNewest)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Then\n\tif hwmo := consumer.HighWaterMarkOffset(); hwmo != offsetNewest {\n\t\tt.Errorf(\"Expected high water mark offset %d, found %d\", offsetNewest, hwmo)\n\t}\n\tassertMessageOffset(t, <-consumer.Messages(), 10)\n\tif hwmo := consumer.HighWaterMarkOffset(); hwmo != offsetNewestAfterFetchRequest {\n\t\tt.Errorf(\"Expected high water mark offset %d, found %d\", offsetNewestAfterFetchRequest, hwmo)\n\t}\n\n\tsafeClose(t, consumer)\n\tsafeClose(t, master)\n\tbroker0.Close()\n}\n\n// If `OffsetOldest` is passed as the initial offset then the first consumed\n// message is indeed the first available in the partition.\nfunc TestConsumerOffsetOldest(t *testing.T) {\n\t// Given\n\toffsetNewest := int64(10)\n\tbroker0 := NewMockBroker(t, 0)\n\tbroker0.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetBroker(broker0.Addr(), broker0.BrokerID()).\n\t\t\tSetLeader(\"my_topic\", 0, broker0.BrokerID()),\n\t\t\"OffsetRequest\": NewMockOffsetResponse(t).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetNewest, offsetNewest).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetOldest, 7),\n\t\t\"FetchRequest\": NewMockFetchResponse(t, 1).\n\t\t\t// skipped because parseRecords(): offset < child.offset\n\t\t\tSetMessage(\"my_topic\", 0, 6, testMsg).\n\t\t\t// these will get to the Messages() channel\n\t\t\tSetMessage(\"my_topic\", 0, 7, testMsg).\n\t\t\tSetMessage(\"my_topic\", 0, 8, testMsg).\n\t\t\tSetMessage(\"my_topic\", 0, 9, testMsg).\n\t\t\tSetHighWaterMark(\"my_topic\", 0, offsetNewest),\n\t})\n\n\tmaster, err := NewConsumer([]string{broker0.Addr()}, NewTestConfig())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// When\n\tconsumer, err := master.ConsumePartition(\"my_topic\", 0, OffsetOldest)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Then\n\tif hwmo := consumer.HighWaterMarkOffset(); hwmo != offsetNewest {\n\t\tt.Errorf(\"Expected high water mark offset %d, found %d\", offsetNewest, hwmo)\n\t}\n\tassertMessageOffset(t, <-consumer.Messages(), int64(7))\n\tif hwmo := consumer.HighWaterMarkOffset(); hwmo != offsetNewest {\n\t\tt.Errorf(\"Expected high water mark offset %d, found %d\", offsetNewest, hwmo)\n\t}\n\n\tsafeClose(t, consumer)\n\tsafeClose(t, master)\n\tbroker0.Close()\n}\n\n// It is possible to close a partition consumer and create the same anew.\nfunc TestConsumerRecreate(t *testing.T) {\n\t// Given\n\tbroker0 := NewMockBroker(t, 0)\n\tbroker0.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetBroker(broker0.Addr(), broker0.BrokerID()).\n\t\t\tSetLeader(\"my_topic\", 0, broker0.BrokerID()),\n\t\t\"OffsetRequest\": NewMockOffsetResponse(t).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetOldest, 0).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetNewest, 1000),\n\t\t\"FetchRequest\": NewMockFetchResponse(t, 1).\n\t\t\tSetMessage(\"my_topic\", 0, 10, testMsg),\n\t})\n\n\tc, err := NewConsumer([]string{broker0.Addr()}, NewTestConfig())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tpc, err := c.ConsumePartition(\"my_topic\", 0, 10)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tassertMessageOffset(t, <-pc.Messages(), 10)\n\n\t// When\n\tsafeClose(t, pc)\n\tpc, err = c.ConsumePartition(\"my_topic\", 0, 10)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Then\n\tassertMessageOffset(t, <-pc.Messages(), 10)\n\n\tsafeClose(t, pc)\n\tsafeClose(t, c)\n\tbroker0.Close()\n}\n\n// An attempt to consume the same partition twice should fail.\nfunc TestConsumerDuplicate(t *testing.T) {\n\t// Given\n\tbroker0 := NewMockBroker(t, 0)\n\tbroker0.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetBroker(broker0.Addr(), broker0.BrokerID()).\n\t\t\tSetLeader(\"my_topic\", 0, broker0.BrokerID()),\n\t\t\"OffsetRequest\": NewMockOffsetResponse(t).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetOldest, 0).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetNewest, 1000),\n\t\t\"FetchRequest\": NewMockFetchResponse(t, 1),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.ChannelBufferSize = 0\n\tc, err := NewConsumer([]string{broker0.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tpc1, err := c.ConsumePartition(\"my_topic\", 0, 0)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// When\n\tpc2, err := c.ConsumePartition(\"my_topic\", 0, 0)\n\n\t// Then\n\tvar target ConfigurationError\n\tok := errors.As(err, &target)\n\tif pc2 != nil || !ok || string(target) != \"That topic/partition is already being consumed\" {\n\t\tt.Fatal(\"A partition cannot be consumed twice at the same time\")\n\t}\n\n\tsafeClose(t, pc1)\n\tsafeClose(t, c)\n\tbroker0.Close()\n}\n\nfunc runConsumerLeaderRefreshErrorTestWithConfig(t *testing.T, config *Config) {\n\t// Given\n\tbroker0 := NewMockBroker(t, 100)\n\n\t// Stage 1: my_topic/0 served by broker0\n\tLogger.Printf(\"    STAGE 1\")\n\n\tbroker0.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetBroker(broker0.Addr(), broker0.BrokerID()).\n\t\t\tSetLeader(\"my_topic\", 0, broker0.BrokerID()),\n\t\t\"OffsetRequest\": NewMockOffsetResponse(t).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetOldest, 123).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetNewest, 1000),\n\t\t\"FetchRequest\": NewMockFetchResponse(t, 1).\n\t\t\tSetMessage(\"my_topic\", 0, 123, testMsg),\n\t})\n\n\tc, err := NewConsumer([]string{broker0.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tpc, err := c.ConsumePartition(\"my_topic\", 0, OffsetOldest)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tassertMessageOffset(t, <-pc.Messages(), 123)\n\n\t// Stage 2: broker0 says that it is no longer the leader for my_topic/0,\n\t// but the requests to retrieve metadata fail with network timeout.\n\tLogger.Printf(\"    STAGE 2\")\n\n\tfetchResponse2 := &FetchResponse{}\n\tfetchResponse2.AddError(\"my_topic\", 0, ErrNotLeaderForPartition)\n\n\tbroker0.SetHandlerByMap(map[string]MockResponse{\n\t\t\"FetchRequest\": NewMockWrapper(fetchResponse2),\n\t})\n\n\tif consErr := <-pc.Errors(); !errors.Is(consErr.Err, ErrOutOfBrokers) {\n\t\tt.Errorf(\"Unexpected error: %v\", consErr.Err)\n\t}\n\n\t// Stage 3: finally the metadata returned by broker0 tells that broker1 is\n\t// a new leader for my_topic/0. Consumption resumes.\n\n\tLogger.Printf(\"    STAGE 3\")\n\n\tbroker1 := NewMockBroker(t, 101)\n\n\tbroker1.SetHandlerByMap(map[string]MockResponse{\n\t\t\"FetchRequest\": NewMockFetchResponse(t, 1).\n\t\t\tSetMessage(\"my_topic\", 0, 124, testMsg),\n\t})\n\tbroker0.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetBroker(broker0.Addr(), broker0.BrokerID()).\n\t\t\tSetBroker(broker1.Addr(), broker1.BrokerID()).\n\t\t\tSetLeader(\"my_topic\", 0, broker1.BrokerID()),\n\t})\n\n\tassertMessageOffset(t, <-pc.Messages(), 124)\n\n\tsafeClose(t, pc)\n\tsafeClose(t, c)\n\tbroker1.Close()\n\tbroker0.Close()\n}\n\n// If consumer fails to refresh metadata it keeps retrying with frequency\n// specified by `Config.Consumer.Retry.Backoff`.\nfunc TestConsumerLeaderRefreshError(t *testing.T) {\n\tconfig := NewTestConfig()\n\tconfig.Net.ReadTimeout = 100 * time.Millisecond\n\tconfig.Consumer.Retry.Backoff = 200 * time.Millisecond\n\tconfig.Consumer.Return.Errors = true\n\tconfig.Metadata.Retry.Max = 0\n\n\trunConsumerLeaderRefreshErrorTestWithConfig(t, config)\n}\n\nfunc TestConsumerLeaderRefreshErrorWithBackoffFunc(t *testing.T) {\n\tvar calls atomic.Int32\n\n\tconfig := NewTestConfig()\n\tconfig.Net.ReadTimeout = 100 * time.Millisecond\n\tconfig.Consumer.Retry.BackoffFunc = func(retries int) time.Duration {\n\t\tcalls.Add(1)\n\t\treturn 200 * time.Millisecond\n\t}\n\tconfig.Consumer.Return.Errors = true\n\tconfig.Metadata.Retry.Max = 0\n\n\trunConsumerLeaderRefreshErrorTestWithConfig(t, config)\n\n\t// we expect at least one call to our backoff function\n\tif calls.Load() == 0 {\n\t\tt.Fail()\n\t}\n}\n\nfunc TestConsumerInvalidTopic(t *testing.T) {\n\t// Given\n\tbroker0 := NewMockBroker(t, 100)\n\tbroker0.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetBroker(broker0.Addr(), broker0.BrokerID()),\n\t})\n\n\tc, err := NewConsumer([]string{broker0.Addr()}, NewTestConfig())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// When\n\tpc, err := c.ConsumePartition(\"my_topic\", 0, OffsetOldest)\n\n\t// Then\n\tif pc != nil || !errors.Is(err, ErrUnknownTopicOrPartition) {\n\t\tt.Errorf(\"Should fail with, err=%v\", err)\n\t}\n\n\tsafeClose(t, c)\n\tbroker0.Close()\n}\n\n// Nothing bad happens if a partition consumer that has no leader assigned at\n// the moment is closed.\nfunc TestConsumerClosePartitionWithoutLeader(t *testing.T) {\n\t// Given\n\tbroker0 := NewMockBroker(t, 100)\n\tbroker0.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetBroker(broker0.Addr(), broker0.BrokerID()).\n\t\t\tSetLeader(\"my_topic\", 0, broker0.BrokerID()),\n\t\t\"OffsetRequest\": NewMockOffsetResponse(t).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetOldest, 123).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetNewest, 1000),\n\t\t\"FetchRequest\": NewMockFetchResponse(t, 1).\n\t\t\tSetMessage(\"my_topic\", 0, 123, testMsg),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Net.ReadTimeout = 100 * time.Millisecond\n\tconfig.Consumer.Retry.Backoff = 100 * time.Millisecond\n\tconfig.Consumer.Return.Errors = true\n\tconfig.Metadata.Retry.Max = 0\n\tc, err := NewConsumer([]string{broker0.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tpc, err := c.ConsumePartition(\"my_topic\", 0, OffsetOldest)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tassertMessageOffset(t, <-pc.Messages(), 123)\n\n\t// broker0 says that it is no longer the leader for my_topic/0, but the\n\t// requests to retrieve metadata fail with network timeout.\n\tfetchResponse2 := &FetchResponse{}\n\tfetchResponse2.AddError(\"my_topic\", 0, ErrNotLeaderForPartition)\n\n\tbroker0.SetHandlerByMap(map[string]MockResponse{\n\t\t\"FetchRequest\": NewMockWrapper(fetchResponse2),\n\t})\n\n\t// When\n\tif consErr := <-pc.Errors(); !errors.Is(consErr.Err, ErrOutOfBrokers) {\n\t\tt.Errorf(\"Unexpected error: %v\", consErr.Err)\n\t}\n\n\t// Then: the partition consumer can be closed without any problem.\n\tsafeClose(t, pc)\n\tsafeClose(t, c)\n\tbroker0.Close()\n}\n\n// If the initial offset passed on partition consumer creation is out of the\n// actual offset range for the partition, then the partition consumer stops\n// immediately closing its output channels.\nfunc TestConsumerShutsDownOutOfRange(t *testing.T) {\n\t// Given\n\tbroker0 := NewMockBroker(t, 0)\n\tfetchResponse := new(FetchResponse)\n\tfetchResponse.AddError(\"my_topic\", 0, ErrOffsetOutOfRange)\n\tbroker0.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetBroker(broker0.Addr(), broker0.BrokerID()).\n\t\t\tSetLeader(\"my_topic\", 0, broker0.BrokerID()),\n\t\t\"OffsetRequest\": NewMockOffsetResponse(t).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetNewest, 1234).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetOldest, 7),\n\t\t\"FetchRequest\": NewMockWrapper(fetchResponse),\n\t})\n\n\tmaster, err := NewConsumer([]string{broker0.Addr()}, NewTestConfig())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// When\n\tconsumer, err := master.ConsumePartition(\"my_topic\", 0, 101)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Then: consumer should shut down closing its messages and errors channels.\n\tif _, ok := <-consumer.Messages(); ok {\n\t\tt.Error(\"Expected the consumer to shut down\")\n\t}\n\tsafeClose(t, consumer)\n\n\tsafeClose(t, master)\n\tbroker0.Close()\n}\n\n// If a fetch response contains messages with offsets that are smaller then\n// requested, then such messages are ignored.\nfunc TestConsumerExtraOffsets(t *testing.T) {\n\t// Given\n\tlegacyFetchResponse := &FetchResponse{}\n\tlegacyFetchResponse.AddMessage(\"my_topic\", 0, nil, testMsg, 1)\n\tlegacyFetchResponse.AddMessage(\"my_topic\", 0, nil, testMsg, 2)\n\tlegacyFetchResponse.AddMessage(\"my_topic\", 0, nil, testMsg, 3)\n\tlegacyFetchResponse.AddMessage(\"my_topic\", 0, nil, testMsg, 4)\n\tnewFetchResponse := &FetchResponse{Version: 5}\n\tnewFetchResponse.AddRecord(\"my_topic\", 0, nil, testMsg, 1)\n\tnewFetchResponse.AddRecord(\"my_topic\", 0, nil, testMsg, 2)\n\tnewFetchResponse.AddRecord(\"my_topic\", 0, nil, testMsg, 3)\n\tnewFetchResponse.AddRecord(\"my_topic\", 0, nil, testMsg, 4)\n\tnewFetchResponse.SetLastOffsetDelta(\"my_topic\", 0, 4)\n\tnewFetchResponse.SetLastStableOffset(\"my_topic\", 0, 4)\n\tfor _, fetchResponse1 := range []*FetchResponse{legacyFetchResponse, newFetchResponse} {\n\t\tcfg := NewTestConfig()\n\t\tcfg.Consumer.Return.Errors = true\n\t\tif fetchResponse1.Version >= 5 {\n\t\t\tcfg.Version = V0_11_0_0\n\t\t}\n\n\t\tbroker0 := NewMockBroker(t, 0)\n\t\tfetchResponse2 := &FetchResponse{}\n\t\tfetchResponse2.Version = fetchResponse1.Version\n\t\tfetchResponse2.AddError(\"my_topic\", 0, ErrNoError)\n\t\tbroker0.SetHandlerByMap(map[string]MockResponse{\n\t\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\t\tSetBroker(broker0.Addr(), broker0.BrokerID()).\n\t\t\t\tSetLeader(\"my_topic\", 0, broker0.BrokerID()),\n\t\t\t\"OffsetRequest\": NewMockOffsetResponse(t).\n\t\t\t\tSetOffset(\"my_topic\", 0, OffsetNewest, 1234).\n\t\t\t\tSetOffset(\"my_topic\", 0, OffsetOldest, 0),\n\t\t\t\"FetchRequest\": NewMockSequence(fetchResponse1, fetchResponse2),\n\t\t})\n\n\t\tmaster, err := NewConsumer([]string{broker0.Addr()}, cfg)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// When\n\t\tconsumer, err := master.ConsumePartition(\"my_topic\", 0, 3)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// Then: messages with offsets 1 and 2 are not returned even though they\n\t\t// are present in the response.\n\t\tselect {\n\t\tcase msg := <-consumer.Messages():\n\t\t\tassertMessageOffset(t, msg, 3)\n\t\tcase err := <-consumer.Errors():\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tselect {\n\t\tcase msg := <-consumer.Messages():\n\t\t\tassertMessageOffset(t, msg, 4)\n\t\tcase err := <-consumer.Errors():\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tsafeClose(t, consumer)\n\t\tsafeClose(t, master)\n\t\tbroker0.Close()\n\t}\n}\n\n// In some situations broker may return a block containing only\n// messages older then requested, even though there would be\n// more messages if higher offset was requested.\nfunc TestConsumerReceivingFetchResponseWithTooOldRecords(t *testing.T) {\n\t// Given\n\tfetchResponse1 := &FetchResponse{Version: 5}\n\tfetchResponse1.AddRecord(\"my_topic\", 0, nil, testMsg, 1)\n\n\tfetchResponse2 := &FetchResponse{Version: 5}\n\tfetchResponse2.AddRecord(\"my_topic\", 0, nil, testMsg, 1000000)\n\n\tcfg := NewTestConfig()\n\tcfg.Consumer.Return.Errors = true\n\tcfg.Version = V0_11_0_0\n\n\tbroker0 := NewMockBroker(t, 0)\n\n\tbroker0.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetBroker(broker0.Addr(), broker0.BrokerID()).\n\t\t\tSetLeader(\"my_topic\", 0, broker0.BrokerID()),\n\t\t\"OffsetRequest\": NewMockOffsetResponse(t).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetNewest, 1234).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetOldest, 0),\n\t\t\"FetchRequest\": NewMockSequence(fetchResponse1, fetchResponse2),\n\t})\n\n\tmaster, err := NewConsumer([]string{broker0.Addr()}, cfg)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// When\n\tconsumer, err := master.ConsumePartition(\"my_topic\", 0, 2)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tselect {\n\tcase msg := <-consumer.Messages():\n\t\tassertMessageOffset(t, msg, 1000000)\n\tcase err := <-consumer.Errors():\n\t\tt.Fatal(err)\n\t}\n\n\tsafeClose(t, consumer)\n\tsafeClose(t, master)\n\tbroker0.Close()\n}\n\nfunc TestConsumeMessageWithNewerFetchAPIVersion(t *testing.T) {\n\t// Given\n\tfetchResponse1 := &FetchResponse{Version: 5}\n\tfetchResponse1.AddMessage(\"my_topic\", 0, nil, testMsg, 1)\n\tfetchResponse1.AddMessage(\"my_topic\", 0, nil, testMsg, 2)\n\n\tcfg := NewTestConfig()\n\tcfg.Version = V0_11_0_0\n\n\tbroker0 := NewMockBroker(t, 0)\n\tfetchResponse2 := &FetchResponse{}\n\tfetchResponse2.Version = 4\n\tfetchResponse2.AddError(\"my_topic\", 0, ErrNoError)\n\tbroker0.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetBroker(broker0.Addr(), broker0.BrokerID()).\n\t\t\tSetLeader(\"my_topic\", 0, broker0.BrokerID()),\n\t\t\"OffsetRequest\": NewMockOffsetResponse(t).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetNewest, 1234).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetOldest, 0),\n\t\t\"FetchRequest\": NewMockSequence(fetchResponse1, fetchResponse2),\n\t})\n\n\tmaster, err := NewConsumer([]string{broker0.Addr()}, cfg)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// When\n\tconsumer, err := master.ConsumePartition(\"my_topic\", 0, 1)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tassertMessageOffset(t, <-consumer.Messages(), 1)\n\tassertMessageOffset(t, <-consumer.Messages(), 2)\n\n\tsafeClose(t, consumer)\n\tsafeClose(t, master)\n\tbroker0.Close()\n}\n\nfunc TestConsumeMessageWithSessionIDs(t *testing.T) {\n\t// Given\n\tfetchResponse1 := &FetchResponse{Version: 7}\n\tfetchResponse1.AddMessage(\"my_topic\", 0, nil, testMsg, 1)\n\tfetchResponse1.AddMessage(\"my_topic\", 0, nil, testMsg, 2)\n\n\tcfg := NewTestConfig()\n\tcfg.Version = V1_1_0_0\n\n\tbroker0 := NewMockBroker(t, 0)\n\tfetchResponse2 := &FetchResponse{}\n\tfetchResponse2.Version = 7\n\tfetchResponse2.AddError(\"my_topic\", 0, ErrNoError)\n\n\tbroker0.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetBroker(broker0.Addr(), broker0.BrokerID()).\n\t\t\tSetLeader(\"my_topic\", 0, broker0.BrokerID()),\n\t\t\"OffsetRequest\": NewMockOffsetResponse(t).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetNewest, 1234).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetOldest, 0),\n\t\t\"FetchRequest\": NewMockSequence(fetchResponse1, fetchResponse2),\n\t})\n\n\tmaster, err := NewConsumer([]string{broker0.Addr()}, cfg)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// When\n\tconsumer, err := master.ConsumePartition(\"my_topic\", 0, 1)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tassertMessageOffset(t, <-consumer.Messages(), 1)\n\tassertMessageOffset(t, <-consumer.Messages(), 2)\n\n\tsafeClose(t, consumer)\n\tsafeClose(t, master)\n\tbroker0.Close()\n\n\tfetchReq := broker0.History()[3].Request.(*FetchRequest)\n\tif fetchReq.SessionID != 0 || fetchReq.SessionEpoch != -1 {\n\t\tt.Error(\"Expected session ID to be zero & Epoch to be -1\")\n\t}\n}\n\nfunc TestConsumeMessagesFromReadReplica(t *testing.T) {\n\t// Given\n\tfetchResponse1 := &FetchResponse{Version: 11}\n\tfetchResponse1.AddMessage(\"my_topic\", 0, nil, testMsg, 1)\n\tfetchResponse1.AddMessage(\"my_topic\", 0, nil, testMsg, 2)\n\tblock1 := fetchResponse1.GetBlock(\"my_topic\", 0)\n\tblock1.PreferredReadReplica = -1\n\n\tfetchResponse2 := &FetchResponse{Version: 11}\n\t// Create a block with no records.\n\tblock2 := fetchResponse1.getOrCreateBlock(\"my_topic\", 0)\n\tblock2.PreferredReadReplica = 1\n\n\tfetchResponse3 := &FetchResponse{Version: 11}\n\tfetchResponse3.AddMessage(\"my_topic\", 0, nil, testMsg, 3)\n\tfetchResponse3.AddMessage(\"my_topic\", 0, nil, testMsg, 4)\n\tblock3 := fetchResponse3.GetBlock(\"my_topic\", 0)\n\tblock3.PreferredReadReplica = -1\n\n\tfetchResponse4 := &FetchResponse{Version: 11}\n\tfetchResponse4.AddMessage(\"my_topic\", 0, nil, testMsg, 5)\n\tfetchResponse4.AddMessage(\"my_topic\", 0, nil, testMsg, 6)\n\tblock4 := fetchResponse4.GetBlock(\"my_topic\", 0)\n\tblock4.PreferredReadReplica = -1\n\n\tcfg := NewTestConfig()\n\tcfg.Version = V2_3_0_0\n\tcfg.RackID = \"consumer_rack\"\n\n\tleader := NewMockBroker(t, 0)\n\tbroker0 := NewMockBroker(t, 1)\n\n\tleader.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetBroker(broker0.Addr(), broker0.BrokerID()).\n\t\t\tSetBroker(leader.Addr(), leader.BrokerID()).\n\t\t\tSetLeader(\"my_topic\", 0, leader.BrokerID()),\n\t\t\"OffsetRequest\": NewMockOffsetResponse(t).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetNewest, 1234).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetOldest, 0),\n\t\t\"FetchRequest\": NewMockSequence(fetchResponse1, fetchResponse2),\n\t})\n\n\tbroker0.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetBroker(broker0.Addr(), broker0.BrokerID()).\n\t\t\tSetBroker(leader.Addr(), leader.BrokerID()).\n\t\t\tSetLeader(\"my_topic\", 0, leader.BrokerID()),\n\t\t\"OffsetRequest\": NewMockOffsetResponse(t).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetNewest, 1234).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetOldest, 0),\n\t\t\"FetchRequest\": NewMockSequence(fetchResponse3, fetchResponse4),\n\t})\n\n\tmaster, err := NewConsumer([]string{broker0.Addr()}, cfg)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// When\n\tconsumer, err := master.ConsumePartition(\"my_topic\", 0, 1)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tassertMessageOffset(t, <-consumer.Messages(), 1)\n\tassertMessageOffset(t, <-consumer.Messages(), 2)\n\tassertMessageOffset(t, <-consumer.Messages(), 3)\n\tassertMessageOffset(t, <-consumer.Messages(), 4)\n\n\tsafeClose(t, consumer)\n\tsafeClose(t, master)\n\tbroker0.Close()\n\tleader.Close()\n}\n\nfunc TestConsumeMessagesFromReadReplicaLeaderFallback(t *testing.T) {\n\t// Given\n\tfetchResponse1 := &FetchResponse{Version: 11}\n\tfetchResponse1.AddMessage(\"my_topic\", 0, nil, testMsg, 1)\n\tfetchResponse1.AddMessage(\"my_topic\", 0, nil, testMsg, 2)\n\tblock1 := fetchResponse1.GetBlock(\"my_topic\", 0)\n\tblock1.PreferredReadReplica = 5 // Does not exist.\n\n\tfetchResponse2 := &FetchResponse{Version: 11}\n\tfetchResponse2.AddMessage(\"my_topic\", 0, nil, testMsg, 3)\n\tfetchResponse2.AddMessage(\"my_topic\", 0, nil, testMsg, 4)\n\tblock2 := fetchResponse2.GetBlock(\"my_topic\", 0)\n\tblock2.PreferredReadReplica = -1\n\n\tcfg := NewTestConfig()\n\tcfg.Version = V2_3_0_0\n\tcfg.RackID = \"consumer_rack\"\n\n\tleader := NewMockBroker(t, 0)\n\n\tleader.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetBroker(leader.Addr(), leader.BrokerID()).\n\t\t\tSetLeader(\"my_topic\", 0, leader.BrokerID()),\n\t\t\"OffsetRequest\": NewMockOffsetResponse(t).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetNewest, 1234).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetOldest, 0),\n\t\t\"FetchRequest\": NewMockSequence(fetchResponse1, fetchResponse2),\n\t})\n\n\tmaster, err := NewConsumer([]string{leader.Addr()}, cfg)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// When\n\tconsumer, err := master.ConsumePartition(\"my_topic\", 0, 1)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tassertMessageOffset(t, <-consumer.Messages(), 1)\n\tassertMessageOffset(t, <-consumer.Messages(), 2)\n\tassertMessageOffset(t, <-consumer.Messages(), 3)\n\tassertMessageOffset(t, <-consumer.Messages(), 4)\n\n\tsafeClose(t, consumer)\n\tsafeClose(t, master)\n\tleader.Close()\n}\n\nfunc TestConsumeMessagesFromReadReplicaErrorReplicaNotAvailable(t *testing.T) {\n\t// Given\n\tfetchResponse1 := &FetchResponse{Version: 11}\n\tblock1 := fetchResponse1.getOrCreateBlock(\"my_topic\", 0)\n\tblock1.PreferredReadReplica = 1\n\n\tfetchResponse2 := &FetchResponse{Version: 11}\n\tfetchResponse2.AddMessage(\"my_topic\", 0, nil, testMsg, 1)\n\tfetchResponse2.AddMessage(\"my_topic\", 0, nil, testMsg, 2)\n\tblock2 := fetchResponse2.GetBlock(\"my_topic\", 0)\n\tblock2.PreferredReadReplica = -1\n\n\tfetchResponse3 := &FetchResponse{Version: 11}\n\tfetchResponse3.AddError(\"my_topic\", 0, ErrReplicaNotAvailable)\n\n\tfetchResponse4 := &FetchResponse{Version: 11}\n\tfetchResponse4.AddMessage(\"my_topic\", 0, nil, testMsg, 3)\n\tfetchResponse4.AddMessage(\"my_topic\", 0, nil, testMsg, 4)\n\n\tcfg := NewTestConfig()\n\tcfg.Version = V2_3_0_0\n\tcfg.RackID = \"consumer_rack\"\n\n\tleader := NewMockBroker(t, 0)\n\tbroker0 := NewMockBroker(t, 1)\n\n\tleader.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetBroker(broker0.Addr(), broker0.BrokerID()).\n\t\t\tSetBroker(leader.Addr(), leader.BrokerID()).\n\t\t\tSetLeader(\"my_topic\", 0, leader.BrokerID()),\n\t\t\"OffsetRequest\": NewMockOffsetResponse(t).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetNewest, 1234).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetOldest, 0),\n\t\t\"FetchRequest\": NewMockSequence(fetchResponse1, fetchResponse4),\n\t})\n\n\tbroker0.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetBroker(broker0.Addr(), broker0.BrokerID()).\n\t\t\tSetBroker(leader.Addr(), leader.BrokerID()).\n\t\t\tSetLeader(\"my_topic\", 0, leader.BrokerID()),\n\t\t\"OffsetRequest\": NewMockOffsetResponse(t).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetNewest, 1234).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetOldest, 0),\n\t\t\"FetchRequest\": NewMockSequence(fetchResponse2, fetchResponse3),\n\t})\n\n\tmaster, err := NewConsumer([]string{broker0.Addr()}, cfg)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// When\n\tconsumer, err := master.ConsumePartition(\"my_topic\", 0, 1)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tassertMessageOffset(t, <-consumer.Messages(), 1)\n\tassertMessageOffset(t, <-consumer.Messages(), 2)\n\tassertMessageOffset(t, <-consumer.Messages(), 3)\n\tassertMessageOffset(t, <-consumer.Messages(), 4)\n\n\tsafeClose(t, consumer)\n\tsafeClose(t, master)\n\tbroker0.Close()\n\tleader.Close()\n}\n\nfunc TestConsumeMessagesFromReadReplicaErrorUnknown(t *testing.T) {\n\t// Given\n\tfetchResponse1 := &FetchResponse{Version: 11}\n\tblock1 := fetchResponse1.getOrCreateBlock(\"my_topic\", 0)\n\tblock1.PreferredReadReplica = 1\n\n\tfetchResponse2 := &FetchResponse{Version: 11}\n\tfetchResponse2.AddMessage(\"my_topic\", 0, nil, testMsg, 1)\n\tfetchResponse2.AddMessage(\"my_topic\", 0, nil, testMsg, 2)\n\tblock2 := fetchResponse2.GetBlock(\"my_topic\", 0)\n\tblock2.PreferredReadReplica = -1\n\n\tfetchResponse3 := &FetchResponse{Version: 11}\n\tfetchResponse3.AddError(\"my_topic\", 0, ErrUnknown)\n\n\tfetchResponse4 := &FetchResponse{Version: 11}\n\tfetchResponse4.AddMessage(\"my_topic\", 0, nil, testMsg, 3)\n\tfetchResponse4.AddMessage(\"my_topic\", 0, nil, testMsg, 4)\n\n\tcfg := NewTestConfig()\n\tcfg.Version = V2_3_0_0\n\tcfg.RackID = \"consumer_rack\"\n\n\tleader := NewMockBroker(t, 0)\n\tbroker0 := NewMockBroker(t, 1)\n\n\tleader.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetBroker(broker0.Addr(), broker0.BrokerID()).\n\t\t\tSetBroker(leader.Addr(), leader.BrokerID()).\n\t\t\tSetLeader(\"my_topic\", 0, leader.BrokerID()),\n\t\t\"OffsetRequest\": NewMockOffsetResponse(t).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetNewest, 1234).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetOldest, 0),\n\t\t\"FetchRequest\": NewMockSequence(fetchResponse1, fetchResponse4),\n\t})\n\n\tbroker0.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetBroker(broker0.Addr(), broker0.BrokerID()).\n\t\t\tSetBroker(leader.Addr(), leader.BrokerID()).\n\t\t\tSetLeader(\"my_topic\", 0, leader.BrokerID()),\n\t\t\"OffsetRequest\": NewMockOffsetResponse(t).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetNewest, 1234).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetOldest, 0),\n\t\t\"FetchRequest\": NewMockSequence(fetchResponse2, fetchResponse3),\n\t})\n\n\tmaster, err := NewConsumer([]string{broker0.Addr()}, cfg)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// When\n\tconsumer, err := master.ConsumePartition(\"my_topic\", 0, 1)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tassertMessageOffset(t, <-consumer.Messages(), 1)\n\tassertMessageOffset(t, <-consumer.Messages(), 2)\n\tassertMessageOffset(t, <-consumer.Messages(), 3)\n\tassertMessageOffset(t, <-consumer.Messages(), 4)\n\n\tsafeClose(t, consumer)\n\tsafeClose(t, master)\n\tbroker0.Close()\n\tleader.Close()\n}\n\n// TestConsumeMessagesTrackLeader ensures that in the event that leadership of\n// a topicPartition changes and no preferredReadReplica is specified, the\n// consumer connects back to the new leader to resume consumption and doesn't\n// continue consuming from the follower.\n//\n// See https://github.com/IBM/sarama/issues/1927\nfunc TestConsumeMessagesTrackLeader(t *testing.T) {\n\tcfg := NewTestConfig()\n\tcfg.ClientID = t.Name()\n\tcfg.Metadata.RefreshFrequency = time.Millisecond * 50\n\tcfg.Consumer.Retry.Backoff = 0\n\tcfg.Net.MaxOpenRequests = 1\n\tcfg.Version = V2_1_0_0\n\n\tleader1 := NewMockBroker(t, 1)\n\tleader2 := NewMockBroker(t, 2)\n\n\tmockMetadataResponse1 := NewMockMetadataResponse(t).\n\t\tSetBroker(leader1.Addr(), leader1.BrokerID()).\n\t\tSetBroker(leader2.Addr(), leader2.BrokerID()).\n\t\tSetLeader(\"my_topic\", 0, leader1.BrokerID())\n\tmockMetadataResponse2 := NewMockMetadataResponse(t).\n\t\tSetBroker(leader1.Addr(), leader1.BrokerID()).\n\t\tSetBroker(leader2.Addr(), leader2.BrokerID()).\n\t\tSetLeader(\"my_topic\", 0, leader2.BrokerID())\n\tmockMetadataResponse3 := NewMockMetadataResponse(t).\n\t\tSetBroker(leader1.Addr(), leader1.BrokerID()).\n\t\tSetBroker(leader2.Addr(), leader2.BrokerID()).\n\t\tSetLeader(\"my_topic\", 0, leader1.BrokerID())\n\n\tleader1.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": mockMetadataResponse1,\n\t\t\"OffsetRequest\": NewMockOffsetResponse(t).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetNewest, 1234).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetOldest, 0),\n\t\t\"FetchRequest\": NewMockFetchResponse(t, 1).\n\t\t\tSetMessage(\"my_topic\", 0, 1, testMsg).\n\t\t\tSetMessage(\"my_topic\", 0, 2, testMsg),\n\t})\n\n\tleader2.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": mockMetadataResponse1,\n\t})\n\n\tclient, err := NewClient([]string{leader1.Addr()}, cfg)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tconsumer, err := NewConsumerFromClient(client)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tpConsumer, err := consumer.ConsumePartition(\"my_topic\", 0, 1)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tassertMessageOffset(t, <-pConsumer.Messages(), 1)\n\tassertMessageOffset(t, <-pConsumer.Messages(), 2)\n\n\tfetchEmptyResponse := &FetchResponse{Version: 10}\n\tfetchEmptyResponse.AddError(\"my_topic\", 0, ErrNoError)\n\tleader1.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": mockMetadataResponse2,\n\t\t\"FetchRequest\":    NewMockWrapper(fetchEmptyResponse),\n\t})\n\tleader2.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": mockMetadataResponse2,\n\t\t\"FetchRequest\": NewMockFetchResponse(t, 1).\n\t\t\tSetMessage(\"my_topic\", 0, 3, testMsg).\n\t\t\tSetMessage(\"my_topic\", 0, 4, testMsg),\n\t})\n\n\t// wait for client to be aware that leadership has changed\n\tfor {\n\t\tb, _ := client.Leader(\"my_topic\", 0)\n\t\tif b.ID() == int32(2) {\n\t\t\tbreak\n\t\t}\n\t\ttime.Sleep(time.Millisecond * 50)\n\t}\n\n\tassertMessageOffset(t, <-pConsumer.Messages(), 3)\n\tassertMessageOffset(t, <-pConsumer.Messages(), 4)\n\n\tleader1.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": mockMetadataResponse3,\n\t\t\"FetchRequest\": NewMockFetchResponse(t, 1).\n\t\t\tSetMessage(\"my_topic\", 0, 5, testMsg).\n\t\t\tSetMessage(\"my_topic\", 0, 6, testMsg),\n\t})\n\tleader2.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": mockMetadataResponse3,\n\t\t\"FetchRequest\":    NewMockWrapper(fetchEmptyResponse),\n\t})\n\n\t// wait for client to be aware that leadership has changed back again\n\tfor {\n\t\tb, _ := client.Leader(\"my_topic\", 0)\n\t\tif b.ID() == int32(1) {\n\t\t\tbreak\n\t\t}\n\t\ttime.Sleep(time.Millisecond * 50)\n\t}\n\n\tassertMessageOffset(t, <-pConsumer.Messages(), 5)\n\tassertMessageOffset(t, <-pConsumer.Messages(), 6)\n\n\tsafeClose(t, pConsumer)\n\tsafeClose(t, consumer)\n\tsafeClose(t, client)\n\tleader1.Close()\n\tleader2.Close()\n}\n\n// It is fine if offsets of fetched messages are not sequential (although\n// strictly increasing!).\nfunc TestConsumerNonSequentialOffsets(t *testing.T) {\n\t// Given\n\tlegacyFetchResponse := &FetchResponse{}\n\tlegacyFetchResponse.AddMessage(\"my_topic\", 0, nil, testMsg, 5)\n\tlegacyFetchResponse.AddMessage(\"my_topic\", 0, nil, testMsg, 7)\n\tlegacyFetchResponse.AddMessage(\"my_topic\", 0, nil, testMsg, 11)\n\tnewFetchResponse := &FetchResponse{Version: 5}\n\tnewFetchResponse.AddRecord(\"my_topic\", 0, nil, testMsg, 5)\n\tnewFetchResponse.AddRecord(\"my_topic\", 0, nil, testMsg, 7)\n\tnewFetchResponse.AddRecord(\"my_topic\", 0, nil, testMsg, 11)\n\tnewFetchResponse.SetLastOffsetDelta(\"my_topic\", 0, 11)\n\tnewFetchResponse.SetLastStableOffset(\"my_topic\", 0, 11)\n\tfor _, fetchResponse1 := range []*FetchResponse{legacyFetchResponse, newFetchResponse} {\n\t\tcfg := NewTestConfig()\n\t\tif fetchResponse1.Version >= 4 {\n\t\t\tcfg.Version = V0_11_0_0\n\t\t}\n\n\t\tbroker0 := NewMockBroker(t, 0)\n\t\tfetchResponse2 := &FetchResponse{Version: fetchResponse1.Version}\n\t\tfetchResponse2.AddError(\"my_topic\", 0, ErrNoError)\n\t\tbroker0.SetHandlerByMap(map[string]MockResponse{\n\t\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\t\tSetBroker(broker0.Addr(), broker0.BrokerID()).\n\t\t\t\tSetLeader(\"my_topic\", 0, broker0.BrokerID()),\n\t\t\t\"OffsetRequest\": NewMockOffsetResponse(t).\n\t\t\t\tSetOffset(\"my_topic\", 0, OffsetNewest, 1234).\n\t\t\t\tSetOffset(\"my_topic\", 0, OffsetOldest, 0),\n\t\t\t\"FetchRequest\": NewMockSequence(fetchResponse1, fetchResponse2),\n\t\t})\n\n\t\tmaster, err := NewConsumer([]string{broker0.Addr()}, cfg)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// When\n\t\tconsumer, err := master.ConsumePartition(\"my_topic\", 0, 3)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\t// Then: messages with offsets 1 and 2 are not returned even though they\n\t\t// are present in the response.\n\t\tassertMessageOffset(t, <-consumer.Messages(), 5)\n\t\tassertMessageOffset(t, <-consumer.Messages(), 7)\n\t\tassertMessageOffset(t, <-consumer.Messages(), 11)\n\n\t\tsafeClose(t, consumer)\n\t\tsafeClose(t, master)\n\t\tbroker0.Close()\n\t}\n}\n\n// If leadership for a partition is changing then consumer resolves the new\n// leader and switches to it.\nfunc TestConsumerRebalancingMultiplePartitions(t *testing.T) {\n\t// initial setup\n\tseedBroker := NewMockBroker(t, 10)\n\tleader0 := NewMockBroker(t, 0)\n\tleader1 := NewMockBroker(t, 1)\n\n\tseedBroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetBroker(leader0.Addr(), leader0.BrokerID()).\n\t\t\tSetBroker(leader1.Addr(), leader1.BrokerID()).\n\t\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID()).\n\t\t\tSetLeader(\"my_topic\", 0, leader0.BrokerID()).\n\t\t\tSetLeader(\"my_topic\", 1, leader1.BrokerID()),\n\t})\n\n\tmockOffsetResponse1 := NewMockOffsetResponse(t).\n\t\tSetOffset(\"my_topic\", 0, OffsetOldest, 0).\n\t\tSetOffset(\"my_topic\", 0, OffsetNewest, 1000).\n\t\tSetOffset(\"my_topic\", 1, OffsetOldest, 0).\n\t\tSetOffset(\"my_topic\", 1, OffsetNewest, 1000)\n\tleader0.SetHandlerByMap(map[string]MockResponse{\n\t\t\"OffsetRequest\": mockOffsetResponse1,\n\t\t\"FetchRequest\":  NewMockFetchResponse(t, 1),\n\t})\n\tleader1.SetHandlerByMap(map[string]MockResponse{\n\t\t\"OffsetRequest\": mockOffsetResponse1,\n\t\t\"FetchRequest\":  NewMockFetchResponse(t, 1),\n\t})\n\n\t// launch test goroutines\n\tconfig := NewTestConfig()\n\tconfig.ClientID = t.Name()\n\tconfig.Consumer.Retry.Backoff = 50\n\tmaster, err := NewConsumer([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tconsumers := map[int32]PartitionConsumer{}\n\tcheckMessage := func(partition int32, offset int) {\n\t\tc := consumers[partition]\n\t\tmessage := <-c.Messages()\n\t\tt.Logf(\"Received message my_topic-%d offset=%d\", partition, message.Offset)\n\t\tif message.Offset != int64(offset) {\n\t\t\tt.Error(\"Incorrect message offset!\", offset, partition, message.Offset)\n\t\t}\n\t\tif message.Partition != partition {\n\t\t\tt.Error(\"Incorrect message partition!\")\n\t\t}\n\t}\n\n\tfor i := int32(0); i < 2; i++ {\n\t\tconsumer, err := master.ConsumePartition(\"my_topic\", i, 0)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tgo func(c PartitionConsumer) {\n\t\t\tfor err := range c.Errors() {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t}(consumer)\n\n\t\tconsumers[i] = consumer\n\t}\n\n\ttime.Sleep(50 * time.Millisecond)\n\tt.Log(`    STAGE 1:\n\t  * my_topic/0 -> leader0 will serve 4 messages\n\t  * my_topic/1 -> leader1 will serve 0 messages`)\n\n\tmockFetchResponse := NewMockFetchResponse(t, 1)\n\tfor i := 0; i < 4; i++ {\n\t\tmockFetchResponse.SetMessage(\"my_topic\", 0, int64(i), testMsg)\n\t}\n\tleader0.SetHandlerByMap(map[string]MockResponse{\n\t\t\"FetchRequest\": mockFetchResponse,\n\t})\n\n\tfor i := 0; i < 4; i++ {\n\t\tcheckMessage(0, i)\n\t}\n\n\ttime.Sleep(50 * time.Millisecond)\n\tt.Log(`    STAGE 2:\n\t  * my_topic/0 -> leader0 will return NotLeaderForPartition\n\t                  seedBroker will give leader1 as serving my_topic/0 now\n\t  * my_topic/1 -> leader1 will serve 0 messages`)\n\n\t// leader0 says no longer leader of partition 0\n\tfetchResponse := new(FetchResponse)\n\tfetchResponse.AddError(\"my_topic\", 0, ErrNotLeaderForPartition)\n\tmetadataResponse := NewMockMetadataResponse(t).\n\t\tSetLeader(\"my_topic\", 0, leader1.BrokerID()).\n\t\tSetLeader(\"my_topic\", 1, leader1.BrokerID()).\n\t\tSetBroker(leader0.Addr(), leader0.BrokerID()).\n\t\tSetBroker(leader1.Addr(), leader1.BrokerID()).\n\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID())\n\n\tleader0.SetHandlerByMap(map[string]MockResponse{\n\t\t\"FetchRequest\":    NewMockWrapper(fetchResponse),\n\t\t\"MetadataRequest\": metadataResponse,\n\t})\n\tleader1.SetHandlerByMap(map[string]MockResponse{\n\t\t\"FetchRequest\":    NewMockFetchResponse(t, 1),\n\t\t\"MetadataRequest\": metadataResponse,\n\t})\n\n\ttime.Sleep(50 * time.Millisecond)\n\tt.Log(`    STAGE 3:\n\t  * my_topic/0 -> leader1 will serve 3 messages\n\t  * my_topic/1 -> leader1 will serve 8 messages`)\n\n\t// leader1 provides 3 message on partition 0, and 8 messages on partition 1\n\tmockFetchResponse2 := NewMockFetchResponse(t, 11)\n\tfor i := 4; i < 7; i++ {\n\t\tmockFetchResponse2.SetMessage(\"my_topic\", 0, int64(i), testMsg)\n\t}\n\tfor i := 0; i < 8; i++ {\n\t\tmockFetchResponse2.SetMessage(\"my_topic\", 1, int64(i), testMsg)\n\t}\n\tleader1.SetHandlerByMap(map[string]MockResponse{\n\t\t\"FetchRequest\":    mockFetchResponse2,\n\t\t\"MetadataRequest\": metadataResponse,\n\t})\n\n\tfor i := 0; i < 8; i++ {\n\t\tcheckMessage(1, i)\n\t}\n\tfor i := 4; i < 7; i++ {\n\t\tcheckMessage(0, i)\n\t}\n\n\ttime.Sleep(50 * time.Millisecond)\n\tt.Log(`    STAGE 4:\n\t  * my_topic/0 -> leader1 will serve 3 messages\n\t  * my_topic/1 -> leader1 will return NotLeaderForPartition\n\t                  seedBroker will give leader0 as serving my_topic/1 now`)\n\n\tmetadataResponse2 := NewMockMetadataResponse(t).\n\t\tSetLeader(\"my_topic\", 0, leader1.BrokerID()).\n\t\tSetLeader(\"my_topic\", 1, leader0.BrokerID()).\n\t\tSetBroker(leader0.Addr(), leader0.BrokerID()).\n\t\tSetBroker(leader1.Addr(), leader1.BrokerID()).\n\t\tSetBroker(seedBroker.Addr(), seedBroker.BrokerID())\n\tleader0.SetHandlerByMap(map[string]MockResponse{\n\t\t\"FetchRequest\": NewMockFetchResponse(t, 1),\n\t})\n\tleader1.SetHandlerByMap(map[string]MockResponse{\n\t\t\"FetchRequest\": NewMockFetchResponse(t, 1),\n\t})\n\n\t// leader1 provides three more messages on partition0, says no longer leader of partition1\n\tmockFetchResponse3 := NewMockFetchResponse(t, 3).\n\t\tSetMessage(\"my_topic\", 0, int64(7), testMsg).\n\t\tSetMessage(\"my_topic\", 0, int64(8), testMsg).\n\t\tSetMessage(\"my_topic\", 0, int64(9), testMsg)\n\tfetchResponse4 := new(FetchResponse)\n\tfetchResponse4.AddError(\"my_topic\", 0, ErrNoError)\n\tfetchResponse4.AddError(\"my_topic\", 1, ErrNotLeaderForPartition)\n\tleader1.SetHandlerByMap(map[string]MockResponse{\n\t\t\"FetchRequest\":    NewMockSequence(mockFetchResponse3, fetchResponse4),\n\t\t\"MetadataRequest\": metadataResponse2,\n\t})\n\tleader0.SetHandlerByMap(map[string]MockResponse{\n\t\t\"FetchRequest\":    NewMockFetchResponse(t, 1),\n\t\t\"MetadataRequest\": metadataResponse2,\n\t})\n\n\tt.Log(`    STAGE 5:\n\t  * my_topic/0 -> leader1 will serve 0 messages\n\t  * my_topic/1 -> leader0 will serve 2 messages`)\n\n\t// leader0 provides two messages on partition 1\n\tmockFetchResponse4 := NewMockFetchResponse(t, 2)\n\tfor i := 8; i < 10; i++ {\n\t\tmockFetchResponse4.SetMessage(\"my_topic\", 1, int64(i), testMsg)\n\t}\n\tleader0.SetHandlerByMap(map[string]MockResponse{\n\t\t\"FetchRequest\":    mockFetchResponse4,\n\t\t\"MetadataRequest\": metadataResponse2,\n\t})\n\n\tfor i := 7; i < 10; i++ {\n\t\tcheckMessage(0, i)\n\t}\n\n\tfor i := 8; i < 10; i++ {\n\t\tcheckMessage(1, i)\n\t}\n\n\tfor _, pc := range consumers {\n\t\tsafeClose(t, pc)\n\t}\n\tsafeClose(t, master)\n\tleader1.Close()\n\tleader0.Close()\n\tseedBroker.Close()\n}\n\n// When two partitions have the same broker as the leader, if one partition\n// consumer channel buffer is full then that does not affect the ability to\n// read messages by the other consumer.\nfunc TestConsumerInterleavedClose(t *testing.T) {\n\t// Given\n\tbroker0 := NewMockBroker(t, 0)\n\tbroker0.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetBroker(broker0.Addr(), broker0.BrokerID()).\n\t\t\tSetLeader(\"my_topic\", 0, broker0.BrokerID()).\n\t\t\tSetLeader(\"my_topic\", 1, broker0.BrokerID()),\n\t\t\"OffsetRequest\": NewMockOffsetResponse(t).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetOldest, 1000).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetNewest, 1100).\n\t\t\tSetOffset(\"my_topic\", 1, OffsetOldest, 2000).\n\t\t\tSetOffset(\"my_topic\", 1, OffsetNewest, 2100),\n\t\t\"FetchRequest\": NewMockFetchResponse(t, 1).\n\t\t\tSetMessage(\"my_topic\", 0, 1000, testMsg).\n\t\t\tSetMessage(\"my_topic\", 0, 1001, testMsg).\n\t\t\tSetMessage(\"my_topic\", 0, 1002, testMsg).\n\t\t\tSetMessage(\"my_topic\", 1, 2000, testMsg),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.ChannelBufferSize = 0\n\tmaster, err := NewConsumer([]string{broker0.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tc0, err := master.ConsumePartition(\"my_topic\", 0, 1000)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tc1, err := master.ConsumePartition(\"my_topic\", 1, 2000)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// When/Then: we can read from partition 0 even if nobody reads from partition 1\n\tassertMessageOffset(t, <-c0.Messages(), 1000)\n\tassertMessageOffset(t, <-c0.Messages(), 1001)\n\tassertMessageOffset(t, <-c0.Messages(), 1002)\n\n\tsafeClose(t, c1)\n\tsafeClose(t, c0)\n\tsafeClose(t, master)\n\tbroker0.Close()\n}\n\nfunc TestConsumerBounceWithReferenceOpen(t *testing.T) {\n\tbroker0 := NewMockBroker(t, 0)\n\tbroker0Addr := broker0.Addr()\n\tbroker1 := NewMockBroker(t, 1)\n\n\tmockMetadataResponse := NewMockMetadataResponse(t).\n\t\tSetBroker(broker0.Addr(), broker0.BrokerID()).\n\t\tSetBroker(broker1.Addr(), broker1.BrokerID()).\n\t\tSetLeader(\"my_topic\", 0, broker0.BrokerID()).\n\t\tSetLeader(\"my_topic\", 1, broker1.BrokerID())\n\n\tmockOffsetResponse := NewMockOffsetResponse(t).\n\t\tSetOffset(\"my_topic\", 0, OffsetOldest, 1000).\n\t\tSetOffset(\"my_topic\", 0, OffsetNewest, 1100).\n\t\tSetOffset(\"my_topic\", 1, OffsetOldest, 2000).\n\t\tSetOffset(\"my_topic\", 1, OffsetNewest, 2100)\n\n\tmockFetchResponse := NewMockFetchResponse(t, 1)\n\tfor i := 0; i < 10; i++ {\n\t\tmockFetchResponse.SetMessage(\"my_topic\", 0, int64(1000+i), testMsg)\n\t\tmockFetchResponse.SetMessage(\"my_topic\", 1, int64(2000+i), testMsg)\n\t}\n\n\tbroker0.SetHandlerByMap(map[string]MockResponse{\n\t\t\"OffsetRequest\": mockOffsetResponse,\n\t\t\"FetchRequest\":  mockFetchResponse,\n\t})\n\tbroker1.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": mockMetadataResponse,\n\t\t\"OffsetRequest\":   mockOffsetResponse,\n\t\t\"FetchRequest\":    mockFetchResponse,\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.Consumer.Return.Errors = true\n\tconfig.Consumer.Retry.Backoff = 100 * time.Millisecond\n\tconfig.ChannelBufferSize = 1\n\tmaster, err := NewConsumer([]string{broker1.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tc0, err := master.ConsumePartition(\"my_topic\", 0, 1000)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tc1, err := master.ConsumePartition(\"my_topic\", 1, 2000)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// read messages from both partition to make sure that both brokers operate\n\t// normally.\n\tassertMessageOffset(t, <-c0.Messages(), 1000)\n\tassertMessageOffset(t, <-c1.Messages(), 2000)\n\n\t// Simulate broker shutdown. Note that metadata response does not change,\n\t// that is the leadership does not move to another broker. So partition\n\t// consumer will keep retrying to restore the connection with the broker.\n\tbroker0.Close()\n\n\t// Make sure that while the partition/0 leader is down, consumer/partition/1\n\t// is capable of pulling messages from broker1.\n\tfor i := 1; i < 7; i++ {\n\t\toffset := (<-c1.Messages()).Offset\n\t\tif offset != int64(2000+i) {\n\t\t\tt.Errorf(\"Expected offset %d from consumer/partition/1\", int64(2000+i))\n\t\t}\n\t}\n\n\t// Bring broker0 back to service.\n\tbroker0 = NewMockBrokerAddr(t, 0, broker0Addr)\n\tbroker0.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": mockMetadataResponse,\n\t\t\"FetchRequest\":    mockFetchResponse,\n\t})\n\n\t// Read the rest of messages from both partitions.\n\tfor i := 7; i < 10; i++ {\n\t\tassertMessageOffset(t, <-c1.Messages(), int64(2000+i))\n\t}\n\tfor i := 1; i < 10; i++ {\n\t\tassertMessageOffset(t, <-c0.Messages(), int64(1000+i))\n\t}\n\n\tselect {\n\tcase <-c0.Errors():\n\tdefault:\n\t\tt.Errorf(\"Partition consumer should have detected broker restart\")\n\t}\n\n\tsafeClose(t, c1)\n\tsafeClose(t, c0)\n\tsafeClose(t, master)\n\tbroker0.Close()\n\tbroker1.Close()\n}\n\nfunc TestConsumerOffsetOutOfRange(t *testing.T) {\n\t// Given\n\tbroker0 := NewMockBroker(t, 2)\n\tbroker0.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetBroker(broker0.Addr(), broker0.BrokerID()).\n\t\t\tSetLeader(\"my_topic\", 0, broker0.BrokerID()),\n\t\t\"OffsetRequest\": NewMockOffsetResponse(t).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetNewest, 1234).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetOldest, 2345),\n\t})\n\n\tmaster, err := NewConsumer([]string{broker0.Addr()}, NewTestConfig())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// When/Then\n\tif _, err := master.ConsumePartition(\"my_topic\", 0, 0); !errors.Is(err, ErrOffsetOutOfRange) {\n\t\tt.Fatal(\"Should return ErrOffsetOutOfRange, got:\", err)\n\t}\n\tif _, err := master.ConsumePartition(\"my_topic\", 0, 3456); !errors.Is(err, ErrOffsetOutOfRange) {\n\t\tt.Fatal(\"Should return ErrOffsetOutOfRange, got:\", err)\n\t}\n\tif _, err := master.ConsumePartition(\"my_topic\", 0, -3); !errors.Is(err, ErrOffsetOutOfRange) {\n\t\tt.Fatal(\"Should return ErrOffsetOutOfRange, got:\", err)\n\t}\n\n\tsafeClose(t, master)\n\tbroker0.Close()\n}\n\nfunc TestConsumerExpiryTicker(t *testing.T) {\n\t// Given\n\tbroker0 := NewMockBroker(t, 0)\n\tfetchResponse1 := &FetchResponse{}\n\tfor i := 1; i <= 8; i++ {\n\t\tfetchResponse1.AddMessage(\"my_topic\", 0, nil, testMsg, int64(i))\n\t}\n\tbroker0.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetBroker(broker0.Addr(), broker0.BrokerID()).\n\t\t\tSetLeader(\"my_topic\", 0, broker0.BrokerID()),\n\t\t\"OffsetRequest\": NewMockOffsetResponse(t).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetNewest, 1234).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetOldest, 1),\n\t\t\"FetchRequest\": NewMockSequence(fetchResponse1),\n\t})\n\n\tconfig := NewTestConfig()\n\tconfig.ChannelBufferSize = 0\n\tconfig.Consumer.MaxProcessingTime = 10 * time.Millisecond\n\tmaster, err := NewConsumer([]string{broker0.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// When\n\tconsumer, err := master.ConsumePartition(\"my_topic\", 0, 1)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Then: messages with offsets 1 through 8 are read\n\tfor i := 1; i <= 8; i++ {\n\t\tassertMessageOffset(t, <-consumer.Messages(), int64(i))\n\t\ttime.Sleep(2 * time.Millisecond)\n\t}\n\n\tsafeClose(t, consumer)\n\tsafeClose(t, master)\n\tbroker0.Close()\n}\n\nfunc TestConsumerTimestamps(t *testing.T) {\n\tnow := time.Now().Truncate(time.Millisecond)\n\ttype testMessage struct {\n\t\tkey       Encoder\n\t\toffset    int64\n\t\ttimestamp time.Time\n\t}\n\tfor _, d := range []struct {\n\t\tkversion          KafkaVersion\n\t\tlogAppendTime     bool\n\t\tmessages          []testMessage\n\t\texpectedTimestamp []time.Time\n\t}{\n\t\t{MinVersion, false, []testMessage{\n\t\t\t{testMsg, 1, now},\n\t\t\t{testMsg, 2, now},\n\t\t}, []time.Time{{}, {}}},\n\t\t{V0_9_0_0, false, []testMessage{\n\t\t\t{testMsg, 1, now},\n\t\t\t{testMsg, 2, now},\n\t\t}, []time.Time{{}, {}}},\n\t\t{V0_10_0_0, false, []testMessage{\n\t\t\t{testMsg, 1, now},\n\t\t\t{testMsg, 2, now},\n\t\t}, []time.Time{{}, {}}},\n\t\t{V0_10_2_1, false, []testMessage{\n\t\t\t{testMsg, 1, now.Add(time.Second)},\n\t\t\t{testMsg, 2, now.Add(2 * time.Second)},\n\t\t}, []time.Time{now.Add(time.Second), now.Add(2 * time.Second)}},\n\t\t{V0_10_2_1, true, []testMessage{\n\t\t\t{testMsg, 1, now.Add(time.Second)},\n\t\t\t{testMsg, 2, now.Add(2 * time.Second)},\n\t\t}, []time.Time{now, now}},\n\t\t{V0_11_0_0, false, []testMessage{\n\t\t\t{testMsg, 1, now.Add(time.Second)},\n\t\t\t{testMsg, 2, now.Add(2 * time.Second)},\n\t\t}, []time.Time{now.Add(time.Second), now.Add(2 * time.Second)}},\n\t\t{V0_11_0_0, true, []testMessage{\n\t\t\t{testMsg, 1, now.Add(time.Second)},\n\t\t\t{testMsg, 2, now.Add(2 * time.Second)},\n\t\t}, []time.Time{now, now}},\n\t} {\n\t\tvar fr *FetchResponse\n\t\tcfg := NewTestConfig()\n\t\tcfg.Version = d.kversion\n\t\tswitch {\n\t\tcase d.kversion.IsAtLeast(V0_11_0_0):\n\t\t\tfr = &FetchResponse{Version: 5, LogAppendTime: d.logAppendTime, Timestamp: now}\n\t\t\tfor _, m := range d.messages {\n\t\t\t\tfr.AddRecordWithTimestamp(\"my_topic\", 0, m.key, testMsg, m.offset, m.timestamp)\n\t\t\t}\n\t\t\tfr.SetLastOffsetDelta(\"my_topic\", 0, 2)\n\t\t\tfr.SetLastStableOffset(\"my_topic\", 0, 2)\n\t\tcase d.kversion.IsAtLeast(V0_10_1_0):\n\t\t\tfr = &FetchResponse{Version: 3, LogAppendTime: d.logAppendTime, Timestamp: now}\n\t\t\tfor _, m := range d.messages {\n\t\t\t\tfr.AddMessageWithTimestamp(\"my_topic\", 0, m.key, testMsg, m.offset, m.timestamp, 1)\n\t\t\t}\n\t\tdefault:\n\t\t\tvar version int16\n\t\t\tswitch {\n\t\t\tcase d.kversion.IsAtLeast(V0_10_0_0):\n\t\t\t\tversion = 2\n\t\t\tcase d.kversion.IsAtLeast(V0_9_0_0):\n\t\t\t\tversion = 1\n\t\t\t}\n\t\t\tfr = &FetchResponse{Version: version}\n\t\t\tfor _, m := range d.messages {\n\t\t\t\tfr.AddMessageWithTimestamp(\"my_topic\", 0, m.key, testMsg, m.offset, m.timestamp, 0)\n\t\t\t}\n\t\t}\n\n\t\tbroker0 := NewMockBroker(t, 0)\n\t\tbroker0.SetHandlerByMap(map[string]MockResponse{\n\t\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\t\tSetBroker(broker0.Addr(), broker0.BrokerID()).\n\t\t\t\tSetLeader(\"my_topic\", 0, broker0.BrokerID()),\n\t\t\t\"OffsetRequest\": NewMockOffsetResponse(t).\n\t\t\t\tSetOffset(\"my_topic\", 0, OffsetNewest, 1234).\n\t\t\t\tSetOffset(\"my_topic\", 0, OffsetOldest, 0),\n\t\t\t\"FetchRequest\": NewMockSequence(fr),\n\t\t})\n\n\t\tmaster, err := NewConsumer([]string{broker0.Addr()}, cfg)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tconsumer, err := master.ConsumePartition(\"my_topic\", 0, 1)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tfor i, ts := range d.expectedTimestamp {\n\t\t\tselect {\n\t\t\tcase msg := <-consumer.Messages():\n\t\t\t\tassertMessageOffset(t, msg, int64(i)+1)\n\t\t\t\tif !msg.Timestamp.Equal(ts) {\n\t\t\t\t\tt.Errorf(\"Wrong timestamp (kversion:%v, logAppendTime:%v): got: %v, want: %v\",\n\t\t\t\t\t\td.kversion, d.logAppendTime, msg.Timestamp, ts)\n\t\t\t\t}\n\t\t\tcase err := <-consumer.Errors():\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t}\n\n\t\tsafeClose(t, consumer)\n\t\tsafeClose(t, master)\n\t\tbroker0.Close()\n\t}\n}\n\n// When set to ReadCommitted, no uncommitted message should be available in messages channel\nfunc TestExcludeUncommitted(t *testing.T) {\n\t// Given\n\tbroker0 := NewMockBroker(t, 0)\n\n\tfetchResponse := &FetchResponse{\n\t\tVersion: 5,\n\t\tBlocks: map[string]map[int32]*FetchResponseBlock{\"my_topic\": {0: {\n\t\t\tAbortedTransactions: []*AbortedTransaction{{ProducerID: 7, FirstOffset: 1235}},\n\t\t}}},\n\t}\n\tfetchResponse.AddRecordBatch(\"my_topic\", 0, nil, testMsg, 1234, 7, true)   // committed msg\n\tfetchResponse.AddRecordBatch(\"my_topic\", 0, nil, testMsg, 1235, 7, true)   // uncommitted msg\n\tfetchResponse.AddRecordBatch(\"my_topic\", 0, nil, testMsg, 1236, 7, true)   // uncommitted msg\n\tfetchResponse.AddControlRecord(\"my_topic\", 0, 1237, 7, ControlRecordAbort) // abort control record\n\tfetchResponse.AddRecordBatch(\"my_topic\", 0, nil, testMsg, 1238, 7, true)   // committed msg\n\n\tbroker0.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetBroker(broker0.Addr(), broker0.BrokerID()).\n\t\t\tSetLeader(\"my_topic\", 0, broker0.BrokerID()),\n\t\t\"OffsetRequest\": NewMockOffsetResponse(t).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetOldest, 0).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetNewest, 1237),\n\t\t\"FetchRequest\": NewMockWrapper(fetchResponse),\n\t})\n\n\tcfg := NewTestConfig()\n\tcfg.Consumer.Return.Errors = true\n\tcfg.Version = V0_11_0_0\n\tcfg.Consumer.IsolationLevel = ReadCommitted\n\n\t// When\n\tmaster, err := NewConsumer([]string{broker0.Addr()}, cfg)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tconsumer, err := master.ConsumePartition(\"my_topic\", 0, 1234)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Then: only the 2 committed messages are returned\n\tselect {\n\tcase message := <-consumer.Messages():\n\t\tassertMessageOffset(t, message, int64(1234))\n\tcase err := <-consumer.Errors():\n\t\tt.Error(err)\n\t}\n\tselect {\n\tcase message := <-consumer.Messages():\n\t\tassertMessageOffset(t, message, int64(1238))\n\tcase err := <-consumer.Errors():\n\t\tt.Error(err)\n\t}\n\n\tsafeClose(t, consumer)\n\tsafeClose(t, master)\n\tbroker0.Close()\n}\n\nfunc assertMessageKey(t *testing.T, msg *ConsumerMessage, expectedKey Encoder) {\n\tt.Helper()\n\n\twantKey, _ := expectedKey.Encode()\n\tif !bytes.Equal(msg.Key, wantKey) {\n\t\tt.Fatalf(\"Incorrect key for message. expected=%s, actual=%s\", expectedKey, msg.Key)\n\t}\n}\n\nfunc assertMessageValue(t *testing.T, msg *ConsumerMessage, expectedValue Encoder) {\n\tt.Helper()\n\n\twantValue, _ := expectedValue.Encode()\n\tif !bytes.Equal(msg.Value, wantValue) {\n\t\tt.Fatalf(\"Incorrect value for message. expected=%s, actual=%s\", expectedValue, msg.Key)\n\t}\n}\n\nfunc assertMessageOffset(t *testing.T, msg *ConsumerMessage, expectedOffset int64) {\n\tt.Helper()\n\tif msg.Offset != expectedOffset {\n\t\tt.Fatalf(\"Incorrect message offset: expected=%d, actual=%d\", expectedOffset, msg.Offset)\n\t}\n}\n\n// This example shows how to use the consumer to read messages\n// from a single partition.\nfunc ExampleConsumer() {\n\tconsumer, err := NewConsumer([]string{\"localhost:9092\"}, NewTestConfig())\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tdefer func() {\n\t\tif err := consumer.Close(); err != nil {\n\t\t\tlog.Fatalln(err)\n\t\t}\n\t}()\n\n\tpartitionConsumer, err := consumer.ConsumePartition(\"my_topic\", 0, OffsetNewest)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tdefer func() {\n\t\tif err := partitionConsumer.Close(); err != nil {\n\t\t\tlog.Fatalln(err)\n\t\t}\n\t}()\n\n\t// Trap SIGINT to trigger a shutdown.\n\tsignals := make(chan os.Signal, 1)\n\tsignal.Notify(signals, os.Interrupt)\n\n\tconsumed := 0\nConsumerLoop:\n\tfor {\n\t\tselect {\n\t\tcase msg := <-partitionConsumer.Messages():\n\t\t\tlog.Printf(\"Consumed message offset %d\\n\", msg.Offset)\n\t\t\tconsumed++\n\t\tcase <-signals:\n\t\t\tbreak ConsumerLoop\n\t\t}\n\t}\n\n\tlog.Printf(\"Consumed: %d\\n\", consumed)\n}\n\nfunc Test_partitionConsumer_parseResponse(t *testing.T) {\n\ttype args struct {\n\t\tresponse *FetchResponse\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\targs    args\n\t\twant    []*ConsumerMessage\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"empty but throttled FetchResponse is not considered an error\",\n\t\t\targs: args{\n\t\t\t\tresponse: &FetchResponse{\n\t\t\t\t\tThrottleTime: time.Millisecond,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"empty FetchResponse is considered an incomplete response by default\",\n\t\t\targs: args{\n\t\t\t\tresponse: &FetchResponse{},\n\t\t\t},\n\t\t\twantErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tchild := &partitionConsumer{\n\t\t\t\tbroker: &brokerConsumer{\n\t\t\t\t\tbroker: &Broker{},\n\t\t\t\t},\n\t\t\t\tconf: &Config{},\n\t\t\t}\n\t\t\tgot, err := child.parseResponse(tt.args.response)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"partitionConsumer.parseResponse() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"partitionConsumer.parseResponse() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_partitionConsumer_parseResponseEmptyBatch(t *testing.T) {\n\tlrbOffset := int64(6)\n\tblock := &FetchResponseBlock{\n\t\tHighWaterMarkOffset: 10,\n\t\tLastStableOffset:    10,\n\t\trecordsNextOffset:   &lrbOffset,\n\t\tLogStartOffset:      0,\n\t}\n\tresponse := &FetchResponse{\n\t\tBlocks:  map[string]map[int32]*FetchResponseBlock{\"my_topic\": {0: block}},\n\t\tVersion: 2,\n\t}\n\tchild := &partitionConsumer{\n\t\tbroker: &brokerConsumer{\n\t\t\tbroker: &Broker{},\n\t\t},\n\t\tconf:      NewTestConfig(),\n\t\ttopic:     \"my_topic\",\n\t\tpartition: 0,\n\t}\n\tgot, err := child.parseResponse(response)\n\tif err != nil {\n\t\tt.Errorf(\"partitionConsumer.parseResponse() error = %v\", err)\n\t\treturn\n\t}\n\tif got != nil {\n\t\tt.Errorf(\"partitionConsumer.parseResponse() should be nil, got %v\", got)\n\t}\n\tif child.offset != 6 {\n\t\tt.Errorf(\"child.offset should be recordsNextOffset: %d, got %d\", lrbOffset, child.offset)\n\t}\n}\n\nfunc testConsumerInterceptor(\n\tt *testing.T,\n\tinterceptors []ConsumerInterceptor,\n\texpectationFn func(*testing.T, int, *ConsumerMessage),\n) {\n\t// Given\n\tbroker0 := NewMockBroker(t, 0)\n\n\tmockFetchResponse := NewMockFetchResponse(t, 1)\n\tfor i := 0; i < 10; i++ {\n\t\tmockFetchResponse.SetMessage(\"my_topic\", 0, int64(i), testMsg)\n\t}\n\n\tbroker0.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetBroker(broker0.Addr(), broker0.BrokerID()).\n\t\t\tSetLeader(\"my_topic\", 0, broker0.BrokerID()),\n\t\t\"OffsetRequest\": NewMockOffsetResponse(t).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetOldest, 0).\n\t\t\tSetOffset(\"my_topic\", 0, OffsetNewest, 0),\n\t\t\"FetchRequest\": mockFetchResponse,\n\t})\n\tconfig := NewTestConfig()\n\tconfig.Consumer.Interceptors = interceptors\n\t// When\n\tmaster, err := NewConsumer([]string{broker0.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tconsumer, err := master.ConsumePartition(\"my_topic\", 0, 0)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor i := 0; i < 10; i++ {\n\t\tselect {\n\t\tcase msg := <-consumer.Messages():\n\t\t\texpectationFn(t, i, msg)\n\t\tcase err := <-consumer.Errors():\n\t\t\tt.Error(err)\n\t\t}\n\t}\n\n\tsafeClose(t, consumer)\n\tsafeClose(t, master)\n\tbroker0.Close()\n}\n\nfunc TestConsumerInterceptors(t *testing.T) {\n\ttests := []struct {\n\t\tname          string\n\t\tinterceptors  []ConsumerInterceptor\n\t\texpectationFn func(*testing.T, int, *ConsumerMessage)\n\t}{\n\t\t{\n\t\t\tname:         \"intercept messages\",\n\t\t\tinterceptors: []ConsumerInterceptor{&appendInterceptor{i: 0}},\n\t\t\texpectationFn: func(t *testing.T, i int, msg *ConsumerMessage) {\n\t\t\t\tev, _ := testMsg.Encode()\n\t\t\t\texpected := string(ev) + strconv.Itoa(i)\n\t\t\t\tv := string(msg.Value)\n\t\t\t\tif v != expected {\n\t\t\t\t\tt.Errorf(\"Interceptor should have incremented the value, got %s, expected %s\", v, expected)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:         \"interceptor chain\",\n\t\t\tinterceptors: []ConsumerInterceptor{&appendInterceptor{i: 0}, &appendInterceptor{i: 1000}},\n\t\t\texpectationFn: func(t *testing.T, i int, msg *ConsumerMessage) {\n\t\t\t\tev, _ := testMsg.Encode()\n\t\t\t\texpected := string(ev) + strconv.Itoa(i) + strconv.Itoa(i+1000)\n\t\t\t\tv := string(msg.Value)\n\t\t\t\tif v != expected {\n\t\t\t\t\tt.Errorf(\"Interceptor should have incremented the value, got %s, expected %s\", v, expected)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:         \"interceptor chain with one interceptor failing\",\n\t\t\tinterceptors: []ConsumerInterceptor{&appendInterceptor{i: -1}, &appendInterceptor{i: 1000}},\n\t\t\texpectationFn: func(t *testing.T, i int, msg *ConsumerMessage) {\n\t\t\t\tev, _ := testMsg.Encode()\n\t\t\t\texpected := string(ev) + strconv.Itoa(i+1000)\n\t\t\t\tv := string(msg.Value)\n\t\t\t\tif v != expected {\n\t\t\t\t\tt.Errorf(\"Interceptor should have not changed the value, got %s, expected %s\", v, expected)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:         \"interceptor chain with all interceptors failing\",\n\t\t\tinterceptors: []ConsumerInterceptor{&appendInterceptor{i: -1}, &appendInterceptor{i: -1}},\n\t\t\texpectationFn: func(t *testing.T, i int, msg *ConsumerMessage) {\n\t\t\t\tev, _ := testMsg.Encode()\n\t\t\t\texpected := string(ev)\n\t\t\t\tv := string(msg.Value)\n\t\t\t\tif v != expected {\n\t\t\t\t\tt.Errorf(\"Interceptor should have incremented the value, got %s, expected %s\", v, expected)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttestConsumerInterceptor(t, tt.interceptors, tt.expectationFn)\n\t\t})\n\t}\n}\n\nfunc TestConsumerError(t *testing.T) {\n\tt.Parallel()\n\terr := ConsumerError{Err: ErrOutOfBrokers}\n\tif !errors.Is(err, ErrOutOfBrokers) {\n\t\tt.Error(\"unexpected errors.Is\")\n\t}\n}\n"
  },
  {
    "path": "control_record.go",
    "content": "package sarama\n\n// ControlRecordType ...\ntype ControlRecordType int\n\nconst (\n\t// ControlRecordAbort is a control record for abort\n\tControlRecordAbort ControlRecordType = iota\n\t// ControlRecordCommit is a control record for commit\n\tControlRecordCommit\n\t// ControlRecordUnknown is a control record of unknown type\n\tControlRecordUnknown\n)\n\n// Control records are returned as a record by fetchRequest\n// However unlike \"normal\" records, they mean nothing application wise.\n// They only serve internal logic for supporting transactions.\ntype ControlRecord struct {\n\tVersion          int16\n\tCoordinatorEpoch int32\n\tType             ControlRecordType\n}\n\nfunc (cr *ControlRecord) decode(key, value packetDecoder) error {\n\tvar err error\n\t// There a version for the value part AND the key part. And I have no idea if they are supposed to match or not\n\t// Either way, all these version can only be 0 for now\n\tcr.Version, err = key.getInt16()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\trecordType, err := key.getInt16()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch recordType {\n\tcase 0:\n\t\tcr.Type = ControlRecordAbort\n\tcase 1:\n\t\tcr.Type = ControlRecordCommit\n\tdefault:\n\t\t// from JAVA implementation:\n\t\t// UNKNOWN is used to indicate a control type which the client is not aware of and should be ignored\n\t\tcr.Type = ControlRecordUnknown\n\t}\n\t// we want to parse value only if we are decoding control record of known type\n\tif cr.Type != ControlRecordUnknown {\n\t\tcr.Version, err = value.getInt16()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tcr.CoordinatorEpoch, err = value.getInt32()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (cr *ControlRecord) encode(key, value packetEncoder) {\n\tvalue.putInt16(cr.Version)\n\tvalue.putInt32(cr.CoordinatorEpoch)\n\tkey.putInt16(cr.Version)\n\n\tswitch cr.Type {\n\tcase ControlRecordAbort:\n\t\tkey.putInt16(0)\n\tcase ControlRecordCommit:\n\t\tkey.putInt16(1)\n\t}\n}\n"
  },
  {
    "path": "control_record_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"testing\"\n)\n\nvar (\n\tabortTxCtrlRecKey = []byte{\n\t\t0, 0, // version\n\t\t0, 0, // TX_ABORT = 0\n\t}\n\tabortTxCtrlRecValue = []byte{\n\t\t0, 0, // version\n\t\t0, 0, 0, 10, // coordinator epoch\n\t}\n\tcommitTxCtrlRecKey = []byte{\n\t\t0, 0, // version\n\t\t0, 1, // TX_COMMIT = 1\n\t}\n\tcommitTxCtrlRecValue = []byte{\n\t\t0, 0, // version\n\t\t0, 0, 0, 15, // coordinator epoch\n\t}\n\tunknownCtrlRecKey = []byte{\n\t\t0, 0, // version\n\t\t0, 128, // UNKNOWN = -1\n\t}\n\t// empty value for unknown record\n\tunknownCtrlRecValue = []byte{}\n)\n\nfunc testDecode(t *testing.T, tp string, key []byte, value []byte) ControlRecord {\n\tcontrolRecord := ControlRecord{}\n\terr := controlRecord.decode(&realDecoder{raw: key}, &realDecoder{raw: value})\n\tif err != nil {\n\t\tt.Error(\"Decoding control record of type \" + tp + \" failed\")\n\t\treturn ControlRecord{}\n\t}\n\treturn controlRecord\n}\n\nfunc assertRecordType(t *testing.T, r *ControlRecord, expected ControlRecordType) {\n\tif r.Type != expected {\n\t\tt.Errorf(\"control record type mismatch, expected: %v, have %v\", expected, r.Type)\n\t}\n}\n\nfunc TestDecodingControlRecords(t *testing.T) {\n\tabortTx := testDecode(t, \"abort transaction\", abortTxCtrlRecKey, abortTxCtrlRecValue)\n\n\tassertRecordType(t, &abortTx, ControlRecordAbort)\n\n\tif abortTx.CoordinatorEpoch != 10 {\n\t\tt.Errorf(\"abort tx control record coordinator epoch mismatch\")\n\t}\n\n\tcommitTx := testDecode(t, \"commit transaction\", commitTxCtrlRecKey, commitTxCtrlRecValue)\n\n\tif commitTx.CoordinatorEpoch != 15 {\n\t\tt.Errorf(\"commit tx control record coordinator epoch mismatch\")\n\t}\n\tassertRecordType(t, &commitTx, ControlRecordCommit)\n\n\tunknown := testDecode(t, \"unknown\", unknownCtrlRecKey, unknownCtrlRecValue)\n\n\tassertRecordType(t, &unknown, ControlRecordUnknown)\n}\n"
  },
  {
    "path": "crc32_field.go",
    "content": "package sarama\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"hash/crc32\"\n\t\"sync\"\n)\n\ntype crcPolynomial int8\n\nconst (\n\tcrcIEEE crcPolynomial = iota\n\tcrcCastagnoli\n)\n\nvar crc32FieldPool = sync.Pool{}\n\nfunc acquireCrc32Field(polynomial crcPolynomial) *crc32Field {\n\tval := crc32FieldPool.Get()\n\tif val != nil {\n\t\tc := val.(*crc32Field)\n\t\tc.polynomial = polynomial\n\t\treturn c\n\t}\n\treturn newCRC32Field(polynomial)\n}\n\nfunc releaseCrc32Field(c *crc32Field) {\n\tcrc32FieldPool.Put(c)\n}\n\nvar castagnoliTable = crc32.MakeTable(crc32.Castagnoli)\n\n// crc32Field implements the pushEncoder and pushDecoder interfaces for calculating CRC32s.\ntype crc32Field struct {\n\tstartOffset int\n\tpolynomial  crcPolynomial\n}\n\nfunc (c *crc32Field) saveOffset(in int) {\n\tc.startOffset = in\n}\n\nfunc (c *crc32Field) reserveLength() int {\n\treturn 4\n}\n\nfunc newCRC32Field(polynomial crcPolynomial) *crc32Field {\n\treturn &crc32Field{polynomial: polynomial}\n}\n\nfunc (c *crc32Field) run(curOffset int, buf []byte) error {\n\tcrc, err := c.crc(curOffset, buf)\n\tif err != nil {\n\t\treturn err\n\t}\n\tbinary.BigEndian.PutUint32(buf[c.startOffset:], crc)\n\treturn nil\n}\n\nfunc (c *crc32Field) check(curOffset int, buf []byte) error {\n\tcrc, err := c.crc(curOffset, buf)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\texpected := binary.BigEndian.Uint32(buf[c.startOffset:])\n\tif crc != expected {\n\t\treturn PacketDecodingError{fmt.Sprintf(\"CRC didn't match expected %#x got %#x\", expected, crc)}\n\t}\n\n\treturn nil\n}\n\nfunc (c *crc32Field) crc(curOffset int, buf []byte) (uint32, error) {\n\tvar tab *crc32.Table\n\tswitch c.polynomial {\n\tcase crcIEEE:\n\t\ttab = crc32.IEEETable\n\tcase crcCastagnoli:\n\t\ttab = castagnoliTable\n\tdefault:\n\t\treturn 0, PacketDecodingError{\"invalid CRC type\"}\n\t}\n\treturn crc32.Checksum(buf[c.startOffset+4:curOffset], tab), nil\n}\n"
  },
  {
    "path": "create_partitions_request.go",
    "content": "package sarama\n\nimport \"time\"\n\ntype CreatePartitionsRequest struct {\n\tVersion         int16\n\tTopicPartitions map[string]*TopicPartition\n\tTimeout         time.Duration\n\tValidateOnly    bool\n}\n\nfunc (c *CreatePartitionsRequest) setVersion(v int16) {\n\tc.Version = v\n}\n\nfunc (c *CreatePartitionsRequest) encode(pe packetEncoder) error {\n\tif err := pe.putArrayLength(len(c.TopicPartitions)); err != nil {\n\t\treturn err\n\t}\n\n\tfor topic, partition := range c.TopicPartitions {\n\t\tif err := pe.putString(topic); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := partition.encode(pe); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tpe.putInt32(int32(c.Timeout / time.Millisecond))\n\n\tpe.putBool(c.ValidateOnly)\n\n\treturn nil\n}\n\nfunc (c *CreatePartitionsRequest) decode(pd packetDecoder, version int16) (err error) {\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\tc.TopicPartitions = make(map[string]*TopicPartition, n)\n\tfor i := 0; i < n; i++ {\n\t\ttopic, err := pd.getString()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tc.TopicPartitions[topic] = new(TopicPartition)\n\t\tif err := c.TopicPartitions[topic].decode(pd, version); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\ttimeout, err := pd.getInt32()\n\tif err != nil {\n\t\treturn err\n\t}\n\tc.Timeout = time.Duration(timeout) * time.Millisecond\n\n\tif c.ValidateOnly, err = pd.getBool(); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (r *CreatePartitionsRequest) key() int16 {\n\treturn apiKeyCreatePartitions\n}\n\nfunc (r *CreatePartitionsRequest) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *CreatePartitionsRequest) headerVersion() int16 {\n\treturn 1\n}\n\nfunc (r *CreatePartitionsRequest) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 1\n}\n\nfunc (r *CreatePartitionsRequest) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 1:\n\t\treturn V2_0_0_0\n\tcase 0:\n\t\treturn V1_0_0_0\n\tdefault:\n\t\treturn V2_0_0_0\n\t}\n}\n\ntype TopicPartition struct {\n\tCount      int32\n\tAssignment [][]int32\n}\n\nfunc (t *TopicPartition) encode(pe packetEncoder) error {\n\tpe.putInt32(t.Count)\n\n\tif len(t.Assignment) == 0 {\n\t\tpe.putInt32(-1)\n\t\treturn nil\n\t}\n\n\tif err := pe.putArrayLength(len(t.Assignment)); err != nil {\n\t\treturn err\n\t}\n\n\tfor _, assign := range t.Assignment {\n\t\tif err := pe.putInt32Array(assign); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (t *TopicPartition) decode(pd packetDecoder, version int16) (err error) {\n\tif t.Count, err = pd.getInt32(); err != nil {\n\t\treturn err\n\t}\n\n\tn, err := pd.getInt32()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif n <= 0 {\n\t\treturn nil\n\t}\n\tt.Assignment = make([][]int32, n)\n\n\tfor i := 0; i < int(n); i++ {\n\t\tif t.Assignment[i], err = pd.getInt32Array(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "create_partitions_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nvar (\n\tcreatePartitionRequestNoAssignment = []byte{\n\t\t0, 0, 0, 1, // one topic\n\t\t0, 5, 't', 'o', 'p', 'i', 'c',\n\t\t0, 0, 0, 3, // 3 partitions\n\t\t255, 255, 255, 255, // no assignments\n\t\t0, 0, 0, 100, // timeout\n\t\t0, // validate only = false\n\t}\n\n\tcreatePartitionRequestAssignment = []byte{\n\t\t0, 0, 0, 1,\n\t\t0, 5, 't', 'o', 'p', 'i', 'c',\n\t\t0, 0, 0, 3, // 3 partitions\n\t\t0, 0, 0, 2,\n\t\t0, 0, 0, 2,\n\t\t0, 0, 0, 2, 0, 0, 0, 3,\n\t\t0, 0, 0, 2,\n\t\t0, 0, 0, 3, 0, 0, 0, 1,\n\t\t0, 0, 0, 100,\n\t\t1, // validate only = true\n\t}\n)\n\nfunc TestCreatePartitionsRequest(t *testing.T) {\n\treq := &CreatePartitionsRequest{\n\t\tTopicPartitions: map[string]*TopicPartition{\n\t\t\t\"topic\": {\n\t\t\t\tCount: 3,\n\t\t\t},\n\t\t},\n\t\tTimeout: 100 * time.Millisecond,\n\t}\n\n\tbuf := testRequestEncode(t, \"no assignment\", req, createPartitionRequestNoAssignment)\n\ttestRequestDecode(t, \"no assignment\", req, buf)\n\n\treq.ValidateOnly = true\n\treq.TopicPartitions[\"topic\"].Assignment = [][]int32{{2, 3}, {3, 1}}\n\n\tbuf = testRequestEncode(t, \"assignment\", req, createPartitionRequestAssignment)\n\ttestRequestDecode(t, \"assignment\", req, buf)\n}\n"
  },
  {
    "path": "create_partitions_response.go",
    "content": "package sarama\n\nimport (\n\t\"fmt\"\n\t\"time\"\n)\n\ntype CreatePartitionsResponse struct {\n\tVersion              int16\n\tThrottleTime         time.Duration\n\tTopicPartitionErrors map[string]*TopicPartitionError\n}\n\nfunc (c *CreatePartitionsResponse) setVersion(v int16) {\n\tc.Version = v\n}\n\nfunc (c *CreatePartitionsResponse) encode(pe packetEncoder) error {\n\tpe.putDurationMs(c.ThrottleTime)\n\tif err := pe.putArrayLength(len(c.TopicPartitionErrors)); err != nil {\n\t\treturn err\n\t}\n\n\tfor topic, partitionError := range c.TopicPartitionErrors {\n\t\tif err := pe.putString(topic); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := partitionError.encode(pe); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (c *CreatePartitionsResponse) decode(pd packetDecoder, version int16) (err error) {\n\tif c.ThrottleTime, err = pd.getDurationMs(); err != nil {\n\t\treturn err\n\t}\n\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tc.TopicPartitionErrors = make(map[string]*TopicPartitionError, n)\n\tfor i := 0; i < n; i++ {\n\t\ttopic, err := pd.getString()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tc.TopicPartitionErrors[topic] = new(TopicPartitionError)\n\t\tif err := c.TopicPartitionErrors[topic].decode(pd, version); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (r *CreatePartitionsResponse) key() int16 {\n\treturn apiKeyCreatePartitions\n}\n\nfunc (r *CreatePartitionsResponse) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *CreatePartitionsResponse) headerVersion() int16 {\n\treturn 0\n}\n\nfunc (r *CreatePartitionsResponse) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 1\n}\n\nfunc (r *CreatePartitionsResponse) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 1:\n\t\treturn V2_0_0_0\n\tcase 0:\n\t\treturn V1_0_0_0\n\tdefault:\n\t\treturn V2_0_0_0\n\t}\n}\n\nfunc (r *CreatePartitionsResponse) throttleTime() time.Duration {\n\treturn r.ThrottleTime\n}\n\ntype TopicPartitionError struct {\n\tErr    KError\n\tErrMsg *string\n}\n\nfunc (t *TopicPartitionError) Error() string {\n\ttext := t.Err.Error()\n\tif t.ErrMsg != nil {\n\t\ttext = fmt.Sprintf(\"%s - %s\", text, *t.ErrMsg)\n\t}\n\treturn text\n}\n\nfunc (t *TopicPartitionError) Unwrap() error {\n\treturn t.Err\n}\n\nfunc (t *TopicPartitionError) encode(pe packetEncoder) error {\n\tpe.putKError(t.Err)\n\n\tif err := pe.putNullableString(t.ErrMsg); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (t *TopicPartitionError) decode(pd packetDecoder, version int16) (err error) {\n\tt.Err, err = pd.getKError()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif t.ErrMsg, err = pd.getNullableString(); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "create_partitions_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"errors\"\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n)\n\nvar (\n\tcreatePartitionResponseSuccess = []byte{\n\t\t0, 0, 0, 100, // throttleTimeMs\n\t\t0, 0, 0, 1,\n\t\t0, 5, 't', 'o', 'p', 'i', 'c',\n\t\t0, 0, // no error\n\t\t255, 255, // no error message\n\t}\n\n\tcreatePartitionResponseFail = []byte{\n\t\t0, 0, 0, 100, // throttleTimeMs\n\t\t0, 0, 0, 1,\n\t\t0, 5, 't', 'o', 'p', 'i', 'c',\n\t\t0, 37, // partition error\n\t\t0, 5, 'e', 'r', 'r', 'o', 'r',\n\t}\n)\n\nfunc TestCreatePartitionsResponse(t *testing.T) {\n\tresp := &CreatePartitionsResponse{\n\t\tThrottleTime: 100 * time.Millisecond,\n\t\tTopicPartitionErrors: map[string]*TopicPartitionError{\n\t\t\t\"topic\": {},\n\t\t},\n\t}\n\n\ttestResponse(t, \"success\", resp, createPartitionResponseSuccess)\n\tdecodedresp := new(CreatePartitionsResponse)\n\ttestVersionDecodable(t, \"success\", decodedresp, createPartitionResponseSuccess, 0)\n\tif !reflect.DeepEqual(decodedresp, resp) {\n\t\tt.Errorf(\"Decoding error: expected %v but got %v\", decodedresp, resp)\n\t}\n\n\terrMsg := \"error\"\n\tresp.TopicPartitionErrors[\"topic\"].Err = ErrInvalidPartitions\n\tresp.TopicPartitionErrors[\"topic\"].ErrMsg = &errMsg\n\n\ttestResponse(t, \"with errors\", resp, createPartitionResponseFail)\n\tdecodedresp = new(CreatePartitionsResponse)\n\ttestVersionDecodable(t, \"with errors\", decodedresp, createPartitionResponseFail, 0)\n\tif !reflect.DeepEqual(decodedresp, resp) {\n\t\tt.Errorf(\"Decoding error: expected %v but got %v\", decodedresp, resp)\n\t}\n}\n\nfunc TestTopicPartitionError(t *testing.T) {\n\t// Assert that TopicPartitionError satisfies error interface\n\tvar err error = &TopicPartitionError{\n\t\tErr: ErrTopicAuthorizationFailed,\n\t}\n\n\tif !errors.Is(err, ErrTopicAuthorizationFailed) {\n\t\tt.Errorf(\"unexpected errors.Is\")\n\t}\n\n\tgot := err.Error()\n\twant := ErrTopicAuthorizationFailed.Error()\n\tif got != want {\n\t\tt.Errorf(\"TopicPartitionError.Error() = %v; want %v\", got, want)\n\t}\n\n\tmsg := \"reason why topic authorization failed\"\n\terr = &TopicPartitionError{\n\t\tErr:    ErrTopicAuthorizationFailed,\n\t\tErrMsg: &msg,\n\t}\n\tgot = err.Error()\n\twant = ErrTopicAuthorizationFailed.Error() + \" - \" + msg\n\tif got != want {\n\t\tt.Errorf(\"TopicPartitionError.Error() = %v; want %v\", got, want)\n\t}\n}\n"
  },
  {
    "path": "create_topics_request.go",
    "content": "package sarama\n\nimport (\n\t\"time\"\n)\n\ntype CreateTopicsRequest struct {\n\t// Version defines the protocol version to use for encode and decode\n\tVersion int16\n\t// TopicDetails contains the topics to create.\n\tTopicDetails map[string]*TopicDetail\n\t// Timeout contains how long to wait before timing out the request.\n\tTimeout time.Duration\n\t// ValidateOnly if true, check that the topics can be created as specified,\n\t// but don't create anything.\n\tValidateOnly bool\n}\n\nfunc (c *CreateTopicsRequest) setVersion(v int16) {\n\tc.Version = v\n}\n\nfunc NewCreateTopicsRequest(\n\tversion KafkaVersion,\n\ttopicDetails map[string]*TopicDetail,\n\ttimeout time.Duration,\n\tvalidateOnly bool,\n) *CreateTopicsRequest {\n\tr := &CreateTopicsRequest{\n\t\tTopicDetails: topicDetails,\n\t\tTimeout:      timeout,\n\t\tValidateOnly: validateOnly,\n\t}\n\tswitch {\n\tcase version.IsAtLeast(V2_4_0_0):\n\t\t// Version 5 is the first flexible version\n\t\t// Version 4 makes partitions/replicationFactor optional even when assignments are not present (KIP-464)\n\t\tr.Version = 5\n\tcase version.IsAtLeast(V2_0_0_0):\n\t\t// Version 3 is the same as version 2 (brokers response before throttling)\n\t\tr.Version = 3\n\tcase version.IsAtLeast(V0_11_0_0):\n\t\t// Version 2 is the same as version 1 (response has ThrottleTime)\n\t\tr.Version = 2\n\tcase version.IsAtLeast(V0_10_2_0):\n\t\t// Version 1 adds validateOnly.\n\t\tr.Version = 1\n\t}\n\treturn r\n}\n\nfunc (c *CreateTopicsRequest) encode(pe packetEncoder) error {\n\tif err := pe.putArrayLength(len(c.TopicDetails)); err != nil {\n\t\treturn err\n\t}\n\tfor topic, detail := range c.TopicDetails {\n\t\tif err := pe.putString(topic); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := detail.encode(pe); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tpe.putInt32(int32(c.Timeout / time.Millisecond))\n\n\tif c.Version >= 1 {\n\t\tpe.putBool(c.ValidateOnly)\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (c *CreateTopicsRequest) decode(pd packetDecoder, version int16) (err error) {\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tc.TopicDetails = make(map[string]*TopicDetail, n)\n\n\tfor i := 0; i < n; i++ {\n\t\ttopic, err := pd.getString()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tc.TopicDetails[topic] = new(TopicDetail)\n\t\tif err = c.TopicDetails[topic].decode(pd, version); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\ttimeout, err := pd.getInt32()\n\tif err != nil {\n\t\treturn err\n\t}\n\tc.Timeout = time.Duration(timeout) * time.Millisecond\n\n\tif version >= 1 {\n\t\tc.ValidateOnly, err = pd.getBool()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tc.Version = version\n\t}\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (c *CreateTopicsRequest) key() int16 {\n\treturn apiKeyCreateTopics\n}\n\nfunc (c *CreateTopicsRequest) version() int16 {\n\treturn c.Version\n}\n\nfunc (c *CreateTopicsRequest) headerVersion() int16 {\n\tif c.Version >= 5 {\n\t\treturn 2\n\t}\n\treturn 1\n}\n\nfunc (c *CreateTopicsRequest) isFlexible() bool {\n\treturn c.isFlexibleVersion(c.Version)\n}\n\nfunc (c *CreateTopicsRequest) isFlexibleVersion(version int16) bool {\n\treturn version >= 5\n}\n\nfunc (c *CreateTopicsRequest) isValidVersion() bool {\n\treturn c.Version >= 0 && c.Version <= 5\n}\n\nfunc (c *CreateTopicsRequest) requiredVersion() KafkaVersion {\n\tswitch c.Version {\n\tcase 5:\n\t\treturn V2_4_0_0\n\tcase 4:\n\t\treturn V2_4_0_0\n\tcase 3:\n\t\treturn V2_0_0_0\n\tcase 2:\n\t\treturn V0_11_0_0\n\tcase 1:\n\t\treturn V0_10_2_0\n\tcase 0:\n\t\treturn V0_10_1_0\n\tdefault:\n\t\treturn V2_8_0_0\n\t}\n}\n\ntype TopicDetail struct {\n\t// NumPartitions contains the number of partitions to create in the topic, or\n\t// -1 if we are either specifying a manual partition assignment or using the\n\t// default partitions.\n\tNumPartitions int32\n\t// ReplicationFactor contains the number of replicas to create for each\n\t// partition in the topic, or -1 if we are either specifying a manual\n\t// partition assignment or using the default replication factor.\n\tReplicationFactor int16\n\t// ReplicaAssignment contains the manual partition assignment, or the empty\n\t// array if we are using automatic assignment.\n\tReplicaAssignment map[int32][]int32\n\t// ConfigEntries contains the custom topic configurations to set.\n\tConfigEntries map[string]*string\n}\n\nfunc (t *TopicDetail) encode(pe packetEncoder) error {\n\tpe.putInt32(t.NumPartitions)\n\tpe.putInt16(t.ReplicationFactor)\n\n\tif err := pe.putArrayLength(len(t.ReplicaAssignment)); err != nil {\n\t\treturn err\n\t}\n\tfor partition, assignment := range t.ReplicaAssignment {\n\t\tpe.putInt32(partition)\n\t\tif err := pe.putInt32Array(assignment); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tpe.putEmptyTaggedFieldArray()\n\t}\n\n\tif err := pe.putArrayLength(len(t.ConfigEntries)); err != nil {\n\t\treturn err\n\t}\n\tfor configKey, configValue := range t.ConfigEntries {\n\t\tif err := pe.putString(configKey); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := pe.putNullableString(configValue); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tpe.putEmptyTaggedFieldArray()\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (t *TopicDetail) decode(pd packetDecoder, version int16) (err error) {\n\tif t.NumPartitions, err = pd.getInt32(); err != nil {\n\t\treturn err\n\t}\n\tif t.ReplicationFactor, err = pd.getInt16(); err != nil {\n\t\treturn err\n\t}\n\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif n > 0 {\n\t\tt.ReplicaAssignment = make(map[int32][]int32, n)\n\t\tfor i := 0; i < n; i++ {\n\t\t\treplica, err := pd.getInt32()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif t.ReplicaAssignment[replica], err = pd.getInt32Array(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif _, err := pd.getEmptyTaggedFieldArray(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\tn, err = pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif n > 0 {\n\t\tt.ConfigEntries = make(map[string]*string, n)\n\t\tfor i := 0; i < n; i++ {\n\t\t\tconfigKey, err := pd.getString()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif t.ConfigEntries[configKey], err = pd.getNullableString(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif _, err := pd.getEmptyTaggedFieldArray(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n"
  },
  {
    "path": "create_topics_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nvar (\n\tcreateTopicsRequestV0 = []byte{\n\t\t0, 0, 0, 1,\n\t\t0, 5, 't', 'o', 'p', 'i', 'c',\n\t\t255, 255, 255, 255,\n\t\t255, 255,\n\t\t0, 0, 0, 1, // 1 replica assignment\n\t\t0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2,\n\t\t0, 0, 0, 1, // 1 config\n\t\t0, 12, 'r', 'e', 't', 'e', 'n', 't', 'i', 'o', 'n', '.', 'm', 's',\n\t\t0, 2, '-', '1',\n\t\t0, 0, 0, 100,\n\t}\n\n\tcreateTopicsRequestV1 = append(createTopicsRequestV0, byte(1))\n\n\tcreateTopicsRequestV5 = []byte{\n\t\t2,\n\t\t6, 't', 'o', 'p', 'i', 'c',\n\t\t255, 255, 255, 255,\n\t\t255, 255,\n\t\t2,          // 1 replica assignment\n\t\t0, 0, 0, 0, // partition index\n\t\t4, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, // broker ids\n\t\t0, // empty tagged fields\n\t\t2, // 1 config\n\t\t13, 'r', 'e', 't', 'e', 'n', 't', 'i', 'o', 'n', '.', 'm', 's',\n\t\t3, '-', '1',\n\t\t0, // empty tagged fields\n\t\t0, // empty tagged fields\n\t\t0, 0, 0, 100,\n\t\t1,\n\t\t0, // empty tagged fields\n\t}\n)\n\nfunc TestCreateTopicsRequest(t *testing.T) {\n\tretention := \"-1\"\n\n\treq := &CreateTopicsRequest{\n\t\tTopicDetails: map[string]*TopicDetail{\n\t\t\t\"topic\": {\n\t\t\t\tNumPartitions:     -1,\n\t\t\t\tReplicationFactor: -1,\n\t\t\t\tReplicaAssignment: map[int32][]int32{\n\t\t\t\t\t0: {0, 1, 2},\n\t\t\t\t},\n\t\t\t\tConfigEntries: map[string]*string{\n\t\t\t\t\t\"retention.ms\": &retention,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tTimeout: 100 * time.Millisecond,\n\t}\n\n\ttestRequest(t, \"version 0\", req, createTopicsRequestV0)\n\n\treq.Version = 1\n\treq.ValidateOnly = true\n\n\ttestRequest(t, \"version 1\", req, createTopicsRequestV1)\n\n\treq.Version = 5\n\ttestRequest(t, \"version 5\", req, createTopicsRequestV5)\n}\n"
  },
  {
    "path": "create_topics_response.go",
    "content": "package sarama\n\nimport (\n\t\"fmt\"\n\t\"time\"\n)\n\ntype CreateTopicsResponse struct {\n\t// Version defines the protocol version to use for encode and decode\n\tVersion int16\n\t// ThrottleTime contains the duration for which the request was throttled due\n\t// to a quota violation, or zero if the request did not violate any quota.\n\tThrottleTime time.Duration\n\t// TopicErrors contains a map of any errors for the topics we tried to create.\n\tTopicErrors map[string]*TopicError\n\t// TopicResults contains a map of the results for the topics we tried to create.\n\tTopicResults map[string]*CreatableTopicResult\n}\n\nfunc (c *CreateTopicsResponse) setVersion(v int16) {\n\tc.Version = v\n}\n\nfunc (c *CreateTopicsResponse) encode(pe packetEncoder) error {\n\tif c.Version >= 2 {\n\t\tpe.putDurationMs(c.ThrottleTime)\n\t}\n\n\tif err := pe.putArrayLength(len(c.TopicErrors)); err != nil {\n\t\treturn err\n\t}\n\tfor topic, topicError := range c.TopicErrors {\n\t\tif err := pe.putString(topic); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := topicError.encode(pe, c.Version); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif c.Version >= 5 {\n\t\t\tresult, ok := c.TopicResults[topic]\n\t\t\tif !ok {\n\t\t\t\treturn fmt.Errorf(\"expected TopicResult for topic, %s, for V5 protocol\", topic)\n\t\t\t}\n\t\t\tif err := result.encode(pe, c.Version); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (c *CreateTopicsResponse) decode(pd packetDecoder, version int16) (err error) {\n\tc.Version = version\n\n\tif version >= 2 {\n\t\tif c.ThrottleTime, err = pd.getDurationMs(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tc.TopicErrors = make(map[string]*TopicError, n)\n\tif version >= 5 {\n\t\tc.TopicResults = make(map[string]*CreatableTopicResult, n)\n\t}\n\tfor i := 0; i < n; i++ {\n\t\ttopic, err := pd.getString()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tc.TopicErrors[topic] = new(TopicError)\n\t\tif err := c.TopicErrors[topic].decode(pd, version); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif version >= 5 {\n\t\t\tc.TopicResults[topic] = &CreatableTopicResult{}\n\t\t\tif err := c.TopicResults[topic].decode(pd, version); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\tif _, err := pd.getEmptyTaggedFieldArray(); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (c *CreateTopicsResponse) key() int16 {\n\treturn apiKeyCreateTopics\n}\n\nfunc (c *CreateTopicsResponse) version() int16 {\n\treturn c.Version\n}\n\nfunc (c *CreateTopicsResponse) headerVersion() int16 {\n\tif c.Version >= 5 {\n\t\treturn 1\n\t}\n\treturn 0\n}\n\nfunc (c *CreateTopicsResponse) isFlexible() bool {\n\treturn c.isFlexibleVersion(c.Version)\n}\n\nfunc (c *CreateTopicsResponse) isFlexibleVersion(version int16) bool {\n\treturn version >= 5\n}\n\nfunc (c *CreateTopicsResponse) isValidVersion() bool {\n\treturn c.Version >= 0 && c.Version <= 5\n}\n\nfunc (c *CreateTopicsResponse) requiredVersion() KafkaVersion {\n\tswitch c.Version {\n\tcase 5:\n\t\treturn V2_4_0_0\n\tcase 4:\n\t\treturn V2_4_0_0\n\tcase 3:\n\t\treturn V2_0_0_0\n\tcase 2:\n\t\treturn V0_11_0_0\n\tcase 1:\n\t\treturn V0_10_2_0\n\tcase 0:\n\t\treturn V0_10_1_0\n\tdefault:\n\t\treturn V2_8_0_0\n\t}\n}\n\nfunc (r *CreateTopicsResponse) throttleTime() time.Duration {\n\treturn r.ThrottleTime\n}\n\ntype TopicError struct {\n\tErr    KError\n\tErrMsg *string\n}\n\nfunc (t *TopicError) Error() string {\n\ttext := t.Err.Error()\n\tif t.ErrMsg != nil {\n\t\ttext = fmt.Sprintf(\"%s - %s\", text, *t.ErrMsg)\n\t}\n\treturn text\n}\n\nfunc (t *TopicError) Unwrap() error {\n\treturn t.Err\n}\n\nfunc (t *TopicError) encode(pe packetEncoder, version int16) error {\n\tpe.putKError(t.Err)\n\n\tif version >= 1 {\n\t\tif err := pe.putNullableString(t.ErrMsg); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (t *TopicError) decode(pd packetDecoder, version int16) (err error) {\n\tt.Err, err = pd.getKError()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif version >= 1 {\n\t\tif t.ErrMsg, err = pd.getNullableString(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// CreatableTopicResult struct {\ntype CreatableTopicResult struct {\n\t// TopicConfigErrorCode contains a Optional topic config error returned if configs are not returned in the response.\n\tTopicConfigErrorCode KError\n\t// NumPartitions contains a Number of partitions of the topic.\n\tNumPartitions int32\n\t// ReplicationFactor contains a Replication factor of the topic.\n\tReplicationFactor int16\n\t// Configs contains a Configuration of the topic.\n\tConfigs map[string]*CreatableTopicConfigs\n}\n\nfunc (r *CreatableTopicResult) encode(pe packetEncoder, version int16) error {\n\tpe.putInt32(r.NumPartitions)\n\tpe.putInt16(r.ReplicationFactor)\n\n\tif err := pe.putArrayLength(len(r.Configs)); err != nil {\n\t\treturn err\n\t}\n\tfor name, config := range r.Configs {\n\t\tif err := pe.putString(name); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := config.encode(pe, version); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif r.TopicConfigErrorCode == ErrNoError {\n\t\tpe.putEmptyTaggedFieldArray()\n\t\treturn nil\n\t}\n\n\t// TODO: refactor to helper for tagged fields\n\tpe.putUVarint(1) // number of tagged fields\n\n\tpe.putUVarint(0) // tag\n\n\tpe.putUVarint(2) // value length\n\n\tpe.putKError(r.TopicConfigErrorCode) // tag value\n\n\treturn nil\n}\n\nfunc (r *CreatableTopicResult) decode(pd packetDecoder, version int16) (err error) {\n\tr.NumPartitions, err = pd.getInt32()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr.ReplicationFactor, err = pd.getInt16()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\tr.Configs = make(map[string]*CreatableTopicConfigs, n)\n\tfor i := 0; i < n; i++ {\n\t\tname, err := pd.getString()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.Configs[name] = &CreatableTopicConfigs{}\n\t\tif err := r.Configs[name].decode(pd, version); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\terr = pd.getTaggedFieldArray(taggedFieldDecoders{\n\t\t0: func(pd packetDecoder) error {\n\t\t\tr.TopicConfigErrorCode, err = pd.getKError()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// CreatableTopicConfigs contains a Configuration of the topic.\ntype CreatableTopicConfigs struct {\n\t// Value contains the configuration value.\n\tValue *string\n\t// ReadOnly contains a True if the configuration is read-only.\n\tReadOnly bool\n\t// ConfigSource contains the configuration source.\n\tConfigSource ConfigSource\n\t// IsSensitive contains a True if this configuration is sensitive.\n\tIsSensitive bool\n}\n\nfunc (c *CreatableTopicConfigs) encode(pe packetEncoder, version int16) (err error) {\n\tif err = pe.putNullableString(c.Value); err != nil {\n\t\treturn err\n\t}\n\tpe.putBool(c.ReadOnly)\n\tpe.putInt8(int8(c.ConfigSource))\n\tpe.putBool(c.IsSensitive)\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (c *CreatableTopicConfigs) decode(pd packetDecoder, version int16) (err error) {\n\tc.Value, err = pd.getNullableString()\n\tif err != nil {\n\t\treturn err\n\t}\n\tc.ReadOnly, err = pd.getBool()\n\tif err != nil {\n\t\treturn err\n\t}\n\tsource, err := pd.getInt8()\n\tif err != nil {\n\t\treturn err\n\t}\n\tc.ConfigSource = ConfigSource(source)\n\tc.IsSensitive, err = pd.getBool()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif _, err := pd.getEmptyTaggedFieldArray(); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "create_topics_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\t\"time\"\n)\n\nvar (\n\tcreateTopicsResponseV0 = []byte{\n\t\t0, 0, 0, 1,\n\t\t0, 5, 't', 'o', 'p', 'i', 'c',\n\t\t0, 42,\n\t}\n\n\tcreateTopicsResponseV1 = []byte{\n\t\t0, 0, 0, 1,\n\t\t0, 5, 't', 'o', 'p', 'i', 'c',\n\t\t0, 42,\n\t\t0, 3, 'm', 's', 'g',\n\t}\n\n\tcreateTopicsResponseV2 = []byte{\n\t\t0, 0, 0, 100,\n\t\t0, 0, 0, 1,\n\t\t0, 5, 't', 'o', 'p', 'i', 'c',\n\t\t0, 42,\n\t\t0, 3, 'm', 's', 'g',\n\t}\n\n\tcreateTopicsResponseV5 = []byte{\n\t\t0, 0, 0, 100,\n\t\t2,\n\t\t6, 't', 'o', 'p', 'i', 'c',\n\t\t0, 42, // invalid request error\n\t\t4, 'm', 's', 'g', // error message\n\t\t0, 0, 0, 1, // num partitions\n\t\t0, 2, // replication factor\n\t\t2,                // 1 config\n\t\t4, 'b', 'a', 'r', // name\n\t\t4, 'b', 'a', 'z', // value\n\t\t0, // read only\n\t\t5, // source default\n\t\t0, // is sensitive\n\t\t0, // empty tagged fields\n\t\t0, // empty tagged fields\n\t\t0, // empty tagged fields\n\t}\n\n\tcreateTopicsResponseV5WithTopicConfigError = []byte{\n\t\t0, 0, 0, 100,\n\t\t2,\n\t\t6, 't', 'o', 'p', 'i', 'c',\n\t\t0, 42, // invalid request error\n\t\t4, 'm', 's', 'g', // error message\n\t\t0, 0, 0, 1, // num partitions\n\t\t0, 2, // replication factor\n\t\t2,                // 1 config\n\t\t4, 'b', 'a', 'r', // name\n\t\t4, 'b', 'a', 'z', // value\n\t\t0,     // read only\n\t\t5,     // source default\n\t\t0,     // is sensitive\n\t\t0,     // empty tagged fields\n\t\t1,     // one tagged field\n\t\t0,     // tag identifier\n\t\t2,     // 2 length of data\n\t\t0, 29, // TOPIC_AUTHORIZATION_FAILED error (see KIP-525)\n\t\t0, // empty tagged fields\n\t}\n)\n\nfunc TestCreateTopicsResponse(t *testing.T) {\n\tresp := &CreateTopicsResponse{\n\t\tTopicErrors: map[string]*TopicError{\n\t\t\t\"topic\": {\n\t\t\t\tErr: ErrInvalidRequest,\n\t\t\t},\n\t\t},\n\t}\n\n\ttestResponse(t, \"version 0\", resp, createTopicsResponseV0)\n\n\tresp.Version = 1\n\tmsg := \"msg\"\n\tresp.TopicErrors[\"topic\"].ErrMsg = &msg\n\n\ttestResponse(t, \"version 1\", resp, createTopicsResponseV1)\n\n\tresp.Version = 2\n\tresp.ThrottleTime = 100 * time.Millisecond\n\n\ttestResponse(t, \"version 2\", resp, createTopicsResponseV2)\n\n\tresp.Version = 5\n\tresp.TopicResults = map[string]*CreatableTopicResult{\n\t\t\"topic\": {\n\t\t\tNumPartitions:     1,\n\t\t\tReplicationFactor: 2,\n\t\t\tConfigs: map[string]*CreatableTopicConfigs{\n\t\t\t\t\"bar\": {\n\t\t\t\t\tValue:        nullString(\"baz\"),\n\t\t\t\t\tConfigSource: SourceDefault,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\ttestResponse(t, \"version 5\", resp, createTopicsResponseV5)\n\n\tresp.TopicResults[\"topic\"].TopicConfigErrorCode = ErrTopicAuthorizationFailed\n\ttestResponse(t, \"version 5\", resp, createTopicsResponseV5WithTopicConfigError)\n}\n\nfunc TestTopicError(t *testing.T) {\n\t// Assert that TopicError satisfies error interface\n\tvar err error = &TopicError{\n\t\tErr: ErrTopicAuthorizationFailed,\n\t}\n\n\tif !errors.Is(err, ErrTopicAuthorizationFailed) {\n\t\tt.Errorf(\"unexpected errors.Is\")\n\t}\n\n\tgot := err.Error()\n\twant := ErrTopicAuthorizationFailed.Error()\n\tif got != want {\n\t\tt.Errorf(\"TopicError.Error() = %v; want %v\", got, want)\n\t}\n\n\tmsg := \"reason why topic authorization failed\"\n\terr = &TopicError{\n\t\tErr:    ErrTopicAuthorizationFailed,\n\t\tErrMsg: &msg,\n\t}\n\tgot = err.Error()\n\twant = ErrTopicAuthorizationFailed.Error() + \" - \" + msg\n\tif got != want {\n\t\tt.Errorf(\"TopicError.Error() = %v; want %v\", got, want)\n\t}\n}\n"
  },
  {
    "path": "decompress.go",
    "content": "package sarama\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"sync\"\n\n\t\"github.com/klauspost/compress/gzip\"\n\tsnappy \"github.com/klauspost/compress/snappy/xerial\"\n\t\"github.com/pierrec/lz4/v4\"\n)\n\nvar (\n\tlz4ReaderPool = sync.Pool{\n\t\tNew: func() interface{} {\n\t\t\treturn lz4.NewReader(nil)\n\t\t},\n\t}\n\n\tgzipReaderPool sync.Pool\n\n\tbufferPool = sync.Pool{\n\t\tNew: func() interface{} {\n\t\t\treturn new(bytes.Buffer)\n\t\t},\n\t}\n\n\tbytesPool = sync.Pool{\n\t\tNew: func() interface{} {\n\t\t\tres := make([]byte, 0, 4096)\n\t\t\treturn &res\n\t\t},\n\t}\n)\n\nfunc decompress(cc CompressionCodec, data []byte) ([]byte, error) {\n\tswitch cc {\n\tcase CompressionNone:\n\t\treturn data, nil\n\tcase CompressionGZIP:\n\t\tvar err error\n\t\treader, ok := gzipReaderPool.Get().(*gzip.Reader)\n\t\tif !ok {\n\t\t\treader, err = gzip.NewReader(bytes.NewReader(data))\n\t\t} else {\n\t\t\terr = reader.Reset(bytes.NewReader(data))\n\t\t}\n\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tbuffer := bufferPool.Get().(*bytes.Buffer)\n\t\t_, err = buffer.ReadFrom(reader)\n\t\t// copy the buffer to a new slice with the correct length\n\t\t// reuse gzipReader and buffer\n\t\tgzipReaderPool.Put(reader)\n\t\tres := make([]byte, buffer.Len())\n\t\tcopy(res, buffer.Bytes())\n\t\tbuffer.Reset()\n\t\tbufferPool.Put(buffer)\n\n\t\treturn res, err\n\tcase CompressionSnappy:\n\t\treturn snappy.Decode(data)\n\tcase CompressionLZ4:\n\t\treader, ok := lz4ReaderPool.Get().(*lz4.Reader)\n\t\tif !ok {\n\t\t\treader = lz4.NewReader(bytes.NewReader(data))\n\t\t} else {\n\t\t\treader.Reset(bytes.NewReader(data))\n\t\t}\n\t\tbuffer := bufferPool.Get().(*bytes.Buffer)\n\t\t_, err := buffer.ReadFrom(reader)\n\t\t// copy the buffer to a new slice with the correct length\n\t\t// reuse lz4Reader and buffer\n\t\tlz4ReaderPool.Put(reader)\n\t\tres := make([]byte, buffer.Len())\n\t\tcopy(res, buffer.Bytes())\n\t\tbuffer.Reset()\n\t\tbufferPool.Put(buffer)\n\n\t\treturn res, err\n\tcase CompressionZSTD:\n\t\tbuffer := *bytesPool.Get().(*[]byte)\n\t\tvar err error\n\t\tbuffer, err = zstdDecompress(ZstdDecoderParams{}, buffer, data)\n\t\t// copy the buffer to a new slice with the correct length and reuse buffer\n\t\tres := make([]byte, len(buffer))\n\t\tcopy(res, buffer)\n\t\tbuffer = buffer[:0]\n\t\tbytesPool.Put(&buffer)\n\n\t\treturn res, err\n\tdefault:\n\t\treturn nil, PacketDecodingError{fmt.Sprintf(\"invalid compression specified (%d)\", cc)}\n\t}\n}\n"
  },
  {
    "path": "delete_groups_request.go",
    "content": "package sarama\n\ntype DeleteGroupsRequest struct {\n\tVersion int16\n\tGroups  []string\n}\n\nfunc (r *DeleteGroupsRequest) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *DeleteGroupsRequest) encode(pe packetEncoder) error {\n\tif err := pe.putStringArray(r.Groups); err != nil {\n\t\treturn err\n\t}\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (r *DeleteGroupsRequest) decode(pd packetDecoder, version int16) (err error) {\n\tr.Groups, err = pd.getStringArray()\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn\n}\n\nfunc (r *DeleteGroupsRequest) key() int16 {\n\treturn apiKeyDeleteGroups\n}\n\nfunc (r *DeleteGroupsRequest) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *DeleteGroupsRequest) headerVersion() int16 {\n\tif r.Version >= 2 {\n\t\treturn 2\n\t}\n\treturn 1\n}\n\nfunc (r *DeleteGroupsRequest) isFlexible() bool {\n\treturn r.isFlexibleVersion(r.Version)\n}\n\nfunc (r *DeleteGroupsRequest) isFlexibleVersion(version int16) bool {\n\treturn version >= 2\n}\n\nfunc (r *DeleteGroupsRequest) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 2\n}\n\nfunc (r *DeleteGroupsRequest) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 2:\n\t\treturn V2_4_0_0\n\tcase 1:\n\t\treturn V2_0_0_0\n\tcase 0:\n\t\treturn V1_1_0_0\n\tdefault:\n\t\treturn V2_0_0_0\n\t}\n}\n\nfunc (r *DeleteGroupsRequest) AddGroup(group string) {\n\tr.Groups = append(r.Groups, group)\n}\n"
  },
  {
    "path": "delete_groups_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nvar (\n\temptyDeleteGroupsRequest = []byte{0, 0, 0, 0}\n\n\tsingleDeleteGroupsRequest = []byte{\n\t\t0, 0, 0, 1, // 1 group\n\t\t0, 3, 'f', 'o', 'o', // group name: foo\n\t}\n\n\tdoubleDeleteGroupsRequest = []byte{\n\t\t0, 0, 0, 2, // 2 groups\n\t\t0, 3, 'f', 'o', 'o', // group name: foo\n\t\t0, 3, 'b', 'a', 'r', // group name: foo\n\t}\n\n\temptyDeleteGroupsRequestV2 = []byte{\n\t\t1,\n\t\t0, // empty tagged fields\n\t}\n\n\tsingleDeleteGroupsRequestV2 = []byte{\n\t\t2,                // 1 group\n\t\t4, 'f', 'o', 'o', // group name: foo\n\t\t0, // empty tagged fields\n\t}\n\n\tdoubleDeleteGroupsRequestV2 = []byte{\n\t\t3,                // 2 groups\n\t\t4, 'f', 'o', 'o', // group name: foo\n\t\t4, 'b', 'a', 'r', // group name: foo\n\t\t0, // empty tagged fields\n\t}\n)\n\nfunc TestDeleteGroupsRequest(t *testing.T) {\n\tvar request *DeleteGroupsRequest\n\n\trequest = new(DeleteGroupsRequest)\n\ttestRequest(t, \"no groups\", request, emptyDeleteGroupsRequest)\n\n\trequest = new(DeleteGroupsRequest)\n\trequest.AddGroup(\"foo\")\n\ttestRequest(t, \"one group\", request, singleDeleteGroupsRequest)\n\n\trequest = new(DeleteGroupsRequest)\n\trequest.AddGroup(\"foo\")\n\trequest.AddGroup(\"bar\")\n\ttestRequest(t, \"two groups\", request, doubleDeleteGroupsRequest)\n}\n\nfunc TestDeleteGroupsRequestV2(t *testing.T) {\n\tvar request *DeleteGroupsRequest\n\n\trequest = &DeleteGroupsRequest{\n\t\tVersion: 2,\n\t}\n\ttestRequest(t, \"no groups\", request, emptyDeleteGroupsRequestV2)\n\n\trequest = &DeleteGroupsRequest{\n\t\tVersion: 2,\n\t}\n\trequest.AddGroup(\"foo\")\n\ttestRequest(t, \"one group\", request, singleDeleteGroupsRequestV2)\n\n\trequest = &DeleteGroupsRequest{\n\t\tVersion: 2,\n\t}\n\trequest.AddGroup(\"foo\")\n\trequest.AddGroup(\"bar\")\n\ttestRequest(t, \"two groups\", request, doubleDeleteGroupsRequestV2)\n}\n"
  },
  {
    "path": "delete_groups_response.go",
    "content": "package sarama\n\nimport (\n\t\"time\"\n)\n\ntype DeleteGroupsResponse struct {\n\tVersion         int16\n\tThrottleTime    time.Duration\n\tGroupErrorCodes map[string]KError\n}\n\nfunc (r *DeleteGroupsResponse) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *DeleteGroupsResponse) encode(pe packetEncoder) error {\n\tpe.putDurationMs(r.ThrottleTime)\n\n\tif err := pe.putArrayLength(len(r.GroupErrorCodes)); err != nil {\n\t\treturn err\n\t}\n\tfor groupID, errorCode := range r.GroupErrorCodes {\n\t\tif err := pe.putString(groupID); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tpe.putKError(errorCode)\n\t\tpe.putEmptyTaggedFieldArray()\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (r *DeleteGroupsResponse) decode(pd packetDecoder, version int16) (err error) {\n\tif r.ThrottleTime, err = pd.getDurationMs(); err != nil {\n\t\treturn err\n\t}\n\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif n == 0 {\n\t\t_, err = pd.getEmptyTaggedFieldArray()\n\t\treturn err\n\t}\n\n\tr.GroupErrorCodes = make(map[string]KError, n)\n\tfor i := 0; i < n; i++ {\n\t\tgroupID, err := pd.getString()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.GroupErrorCodes[groupID], err = pd.getKError()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif _, err := pd.getEmptyTaggedFieldArray(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (r *DeleteGroupsResponse) key() int16 {\n\treturn apiKeyDeleteGroups\n}\n\nfunc (r *DeleteGroupsResponse) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *DeleteGroupsResponse) headerVersion() int16 {\n\tif r.Version >= 2 {\n\t\treturn 1\n\t}\n\treturn 0\n}\n\nfunc (r *DeleteGroupsResponse) isFlexible() bool {\n\treturn r.isFlexibleVersion(r.Version)\n}\n\nfunc (r *DeleteGroupsResponse) isFlexibleVersion(version int16) bool {\n\treturn version >= 2\n}\n\nfunc (r *DeleteGroupsResponse) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 2\n}\n\nfunc (r *DeleteGroupsResponse) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 2:\n\t\treturn V2_4_0_0\n\tcase 1:\n\t\treturn V2_0_0_0\n\tcase 0:\n\t\treturn V1_1_0_0\n\tdefault:\n\t\treturn V2_0_0_0\n\t}\n}\n\nfunc (r *DeleteGroupsResponse) throttleTime() time.Duration {\n\treturn r.ThrottleTime\n}\n"
  },
  {
    "path": "delete_groups_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"errors\"\n\t\"testing\"\n)\n\nvar (\n\temptyDeleteGroupsResponse = []byte{\n\t\t0, 0, 0, 0, // does not violate any quota\n\t\t0, 0, 0, 0, // no groups\n\t}\n\n\terrorDeleteGroupsResponse = []byte{\n\t\t0, 0, 0, 0, // does not violate any quota\n\t\t0, 0, 0, 1, // 1 group\n\t\t0, 3, 'f', 'o', 'o', // group name\n\t\t0, 31, // error ErrClusterAuthorizationFailed\n\t}\n\n\tnoErrorDeleteGroupsResponse = []byte{\n\t\t0, 0, 0, 0, // does not violate any quota\n\t\t0, 0, 0, 1, // 1 group\n\t\t0, 3, 'f', 'o', 'o', // group name\n\t\t0, 0, // no error\n\t}\n\n\temptyDeleteGroupsResponseV2 = []byte{\n\t\t0, 0, 0, 0, // does not violate any quota\n\t\t1, // no groups\n\t\t0, // empty tagged fields\n\t}\n\n\terrorDeleteGroupsResponseV2 = []byte{\n\t\t0, 0, 0, 0, // does not violate any quota\n\t\t2,                // 1 group\n\t\t4, 'f', 'o', 'o', // group name\n\t\t0, 31, // error ErrClusterAuthorizationFailed\n\t\t0, // empty tagged fields\n\t\t0, // empty tagged fields\n\t}\n\n\tnoErrorDeleteGroupsResponseV2 = []byte{\n\t\t0, 0, 0, 0, // does not violate any quota\n\t\t2,                // 1 group\n\t\t4, 'f', 'o', 'o', // group name\n\t\t0, 0, // no error\n\t\t0, // empty tagged fields\n\t\t0, // empty tagged fields\n\t}\n)\n\nfunc TestDeleteGroupsResponse(t *testing.T) {\n\tvar response *DeleteGroupsResponse\n\n\tresponse = new(DeleteGroupsResponse)\n\ttestVersionDecodable(t, \"empty\", response, emptyDeleteGroupsResponse, 0)\n\tif response.ThrottleTime != 0 {\n\t\tt.Error(\"Expected no violation\")\n\t}\n\tif len(response.GroupErrorCodes) != 0 {\n\t\tt.Error(\"Expected no groups\")\n\t}\n\n\tresponse = new(DeleteGroupsResponse)\n\ttestVersionDecodable(t, \"error\", response, errorDeleteGroupsResponse, 0)\n\tif response.ThrottleTime != 0 {\n\t\tt.Error(\"Expected no violation\")\n\t}\n\tif !errors.Is(response.GroupErrorCodes[\"foo\"], ErrClusterAuthorizationFailed) {\n\t\tt.Error(\"Expected error ErrClusterAuthorizationFailed, found:\", response.GroupErrorCodes[\"foo\"])\n\t}\n\n\tresponse = new(DeleteGroupsResponse)\n\ttestVersionDecodable(t, \"no error\", response, noErrorDeleteGroupsResponse, 0)\n\tif response.ThrottleTime != 0 {\n\t\tt.Error(\"Expected no violation\")\n\t}\n\tif !errors.Is(response.GroupErrorCodes[\"foo\"], ErrNoError) {\n\t\tt.Error(\"Expected error ErrClusterAuthorizationFailed, found:\", response.GroupErrorCodes[\"foo\"])\n\t}\n}\n\nfunc TestDeleteGroupsResponseV2(t *testing.T) {\n\tvar response *DeleteGroupsResponse\n\n\tresponse = new(DeleteGroupsResponse)\n\ttestVersionDecodable(t, \"empty\", response, emptyDeleteGroupsResponseV2, 2)\n\tif response.ThrottleTime != 0 {\n\t\tt.Error(\"Expected no violation\")\n\t}\n\tif len(response.GroupErrorCodes) != 0 {\n\t\tt.Error(\"Expected no groups\")\n\t}\n\n\tresponse = new(DeleteGroupsResponse)\n\ttestVersionDecodable(t, \"error\", response, errorDeleteGroupsResponseV2, 2)\n\tif response.ThrottleTime != 0 {\n\t\tt.Error(\"Expected no violation\")\n\t}\n\tif !errors.Is(response.GroupErrorCodes[\"foo\"], ErrClusterAuthorizationFailed) {\n\t\tt.Error(\"Expected error ErrClusterAuthorizationFailed, found:\", response.GroupErrorCodes[\"foo\"])\n\t}\n\n\tresponse = new(DeleteGroupsResponse)\n\ttestVersionDecodable(t, \"no error\", response, noErrorDeleteGroupsResponseV2, 2)\n\tif response.ThrottleTime != 0 {\n\t\tt.Error(\"Expected no violation\")\n\t}\n\tif !errors.Is(response.GroupErrorCodes[\"foo\"], ErrNoError) {\n\t\tt.Error(\"Expected error ErrClusterAuthorizationFailed, found:\", response.GroupErrorCodes[\"foo\"])\n\t}\n}\n"
  },
  {
    "path": "delete_offsets_request.go",
    "content": "package sarama\n\ntype DeleteOffsetsRequest struct {\n\tVersion    int16\n\tGroup      string\n\tpartitions map[string][]int32\n}\n\nfunc (r *DeleteOffsetsRequest) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *DeleteOffsetsRequest) encode(pe packetEncoder) (err error) {\n\terr = pe.putString(r.Group)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif r.partitions == nil {\n\t\tpe.putInt32(0)\n\t} else {\n\t\tif err = pe.putArrayLength(len(r.partitions)); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tfor topic, partitions := range r.partitions {\n\t\terr = pe.putString(topic)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = pe.putInt32Array(partitions)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn\n}\n\nfunc (r *DeleteOffsetsRequest) decode(pd packetDecoder, version int16) (err error) {\n\tr.Group, err = pd.getString()\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar partitionCount int\n\n\tpartitionCount, err = pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif (partitionCount == 0 && version < 2) || partitionCount < 0 {\n\t\treturn nil\n\t}\n\n\tr.partitions = make(map[string][]int32, partitionCount)\n\tfor i := 0; i < partitionCount; i++ {\n\t\tvar topic string\n\t\ttopic, err = pd.getString()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tvar partitions []int32\n\t\tpartitions, err = pd.getInt32Array()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tr.partitions[topic] = partitions\n\t}\n\n\treturn nil\n}\n\nfunc (r *DeleteOffsetsRequest) key() int16 {\n\treturn apiKeyOffsetDelete\n}\n\nfunc (r *DeleteOffsetsRequest) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *DeleteOffsetsRequest) headerVersion() int16 {\n\treturn 1\n}\n\nfunc (r *DeleteOffsetsRequest) isValidVersion() bool {\n\treturn r.Version == 0\n}\n\nfunc (r *DeleteOffsetsRequest) requiredVersion() KafkaVersion {\n\treturn V2_4_0_0\n}\n\nfunc (r *DeleteOffsetsRequest) AddPartition(topic string, partitionID int32) {\n\tif r.partitions == nil {\n\t\tr.partitions = make(map[string][]int32)\n\t}\n\n\tr.partitions[topic] = append(r.partitions[topic], partitionID)\n}\n"
  },
  {
    "path": "delete_offsets_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nvar (\n\temptyDeleteOffsetsRequest = []byte{\n\t\t0, 3, 'f', 'o', 'o', // group name: foo\n\t\t0, 0, 0, 0, // 0 partition\n\t}\n)\n\nfunc TestDeleteOffsetsRequest(t *testing.T) {\n\tvar request *DeleteOffsetsRequest\n\n\trequest = new(DeleteOffsetsRequest)\n\trequest.Group = \"foo\"\n\n\ttestRequest(t, \"no offset\", request, emptyDeleteOffsetsRequest)\n\n\trequest = new(DeleteOffsetsRequest)\n\trequest.Group = \"foo\"\n\trequest.AddPartition(\"bar\", 6)\n\trequest.AddPartition(\"bar\", 7)\n\t// The response encoded form cannot be checked for it varies due to\n\t// unpredictable map traversal order.\n\ttestRequest(t, \"two offsets on one topic\", request, nil)\n\n\trequest = new(DeleteOffsetsRequest)\n\trequest.Group = \"foo\"\n\trequest.AddPartition(\"bar\", 6)\n\trequest.AddPartition(\"bar\", 7)\n\trequest.AddPartition(\"baz\", 0)\n\t// The response encoded form cannot be checked for it varies due to\n\t// unpredictable map traversal order.\n\ttestRequest(t, \"three offsets on two topics\", request, nil)\n}\n"
  },
  {
    "path": "delete_offsets_response.go",
    "content": "package sarama\n\nimport (\n\t\"time\"\n)\n\ntype DeleteOffsetsResponse struct {\n\tVersion int16\n\t// The top-level error code, or 0 if there was no error.\n\tErrorCode    KError\n\tThrottleTime time.Duration\n\t// The responses for each partition of the topics.\n\tErrors map[string]map[int32]KError\n}\n\nfunc (r *DeleteOffsetsResponse) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *DeleteOffsetsResponse) AddError(topic string, partition int32, errorCode KError) {\n\tif r.Errors == nil {\n\t\tr.Errors = make(map[string]map[int32]KError)\n\t}\n\tpartitions := r.Errors[topic]\n\tif partitions == nil {\n\t\tpartitions = make(map[int32]KError)\n\t\tr.Errors[topic] = partitions\n\t}\n\tpartitions[partition] = errorCode\n}\n\nfunc (r *DeleteOffsetsResponse) encode(pe packetEncoder) error {\n\tpe.putKError(r.ErrorCode)\n\tpe.putDurationMs(r.ThrottleTime)\n\n\tif err := pe.putArrayLength(len(r.Errors)); err != nil {\n\t\treturn err\n\t}\n\tfor topic, partitions := range r.Errors {\n\t\tif err := pe.putString(topic); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := pe.putArrayLength(len(partitions)); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor partition, errorCode := range partitions {\n\t\t\tpe.putInt32(partition)\n\t\t\tpe.putKError(errorCode)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (r *DeleteOffsetsResponse) decode(pd packetDecoder, version int16) (err error) {\n\tr.ErrorCode, err = pd.getKError()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif r.ThrottleTime, err = pd.getDurationMs(); err != nil {\n\t\treturn err\n\t}\n\n\tnumTopics, err := pd.getArrayLength()\n\tif err != nil || numTopics == 0 {\n\t\treturn err\n\t}\n\n\tr.Errors = make(map[string]map[int32]KError, numTopics)\n\tfor i := 0; i < numTopics; i++ {\n\t\tname, err := pd.getString()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tnumErrors, err := pd.getArrayLength()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tr.Errors[name] = make(map[int32]KError, numErrors)\n\n\t\tfor j := 0; j < numErrors; j++ {\n\t\t\tid, err := pd.getInt32()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tr.Errors[name][id], err = pd.getKError()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (r *DeleteOffsetsResponse) key() int16 {\n\treturn apiKeyOffsetDelete\n}\n\nfunc (r *DeleteOffsetsResponse) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *DeleteOffsetsResponse) headerVersion() int16 {\n\treturn 0\n}\n\nfunc (r *DeleteOffsetsResponse) isValidVersion() bool {\n\treturn r.Version == 0\n}\n\nfunc (r *DeleteOffsetsResponse) requiredVersion() KafkaVersion {\n\treturn V2_4_0_0\n}\n\nfunc (r *DeleteOffsetsResponse) throttleTime() time.Duration {\n\treturn r.ThrottleTime\n}\n"
  },
  {
    "path": "delete_offsets_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"testing\"\n)\n\nvar (\n\temptyDeleteOffsetsResponse = []byte{\n\t\t0, 0, // no error\n\t\t0, 0, 0, 0, // 0 throttle\n\t\t0, 0, 0, 0, // 0 topics\n\t}\n\n\terrorDeleteOffsetsResponse = []byte{\n\t\t0, 16, // error 16 : ErrNotCoordinatorForConsumer\n\t\t0, 0, 0, 0, // 0 throttle\n\t\t0, 0, 0, 1, // 1 topic\n\t\t0, 3, 'b', 'a', 'r', // topic name: bar\n\t\t0, 0, 0, 1, // 1 partition\n\t\t0, 0, 0, 6, // partition 6\n\t\t0, 0, // no error\n\t}\n\n\terrorOnPartitionResponse = []byte{\n\t\t0, 0, // no error\n\t\t0, 0, 0, 0, // 0 throttle\n\t\t0, 0, 0, 1, // 1 topic\n\t\t0, 3, 'b', 'a', 'r', // topic name: bar\n\t\t0, 0, 0, 1, // 1 partition\n\t\t0, 0, 0, 6, // partition 6\n\t\t0, 86, // error ErrGroupSubscribedToTopic=86\n\t}\n)\n\nfunc TestDeleteOffsetsResponse(t *testing.T) {\n\tvar response *DeleteOffsetsResponse\n\n\tresponse = &DeleteOffsetsResponse{\n\t\tErrorCode:    0,\n\t\tThrottleTime: 0,\n\t}\n\ttestResponse(t, \"empty no error\", response, emptyDeleteOffsetsResponse)\n\n\tresponse = &DeleteOffsetsResponse{\n\t\tErrorCode:    0,\n\t\tThrottleTime: 0,\n\t\tErrors: map[string]map[int32]KError{\n\t\t\t\"bar\": {\n\t\t\t\t6: 0,\n\t\t\t\t7: 0,\n\t\t\t},\n\t\t},\n\t}\n\t// The response encoded form cannot be checked for it varies due to\n\t// unpredictable map traversal order.\n\ttestResponse(t, \"no error\", response, nil)\n\n\tresponse = &DeleteOffsetsResponse{\n\t\tErrorCode:    16,\n\t\tThrottleTime: 0,\n\t\tErrors: map[string]map[int32]KError{\n\t\t\t\"bar\": {\n\t\t\t\t6: 0,\n\t\t\t},\n\t\t},\n\t}\n\ttestResponse(t, \"error global\", response, errorDeleteOffsetsResponse)\n\n\tresponse = &DeleteOffsetsResponse{\n\t\tErrorCode:    0,\n\t\tThrottleTime: 0,\n\t}\n\tresponse.AddError(\"bar\", 6, ErrGroupSubscribedToTopic)\n\ttestResponse(t, \"error partition\", response, errorOnPartitionResponse)\n}\n"
  },
  {
    "path": "delete_records_request.go",
    "content": "package sarama\n\nimport (\n\t\"slices\"\n\t\"sort\"\n\t\"time\"\n)\n\n// request message format is:\n// [topic] timeout(int32)\n// where topic is:\n//  name(string) [partition]\n// where partition is:\n//  id(int32) offset(int64)\n\ntype DeleteRecordsRequest struct {\n\tVersion int16\n\tTopics  map[string]*DeleteRecordsRequestTopic\n\tTimeout time.Duration\n}\n\nfunc (d *DeleteRecordsRequest) setVersion(v int16) {\n\td.Version = v\n}\n\nfunc (d *DeleteRecordsRequest) encode(pe packetEncoder) error {\n\tif err := pe.putArrayLength(len(d.Topics)); err != nil {\n\t\treturn err\n\t}\n\tkeys := make([]string, 0, len(d.Topics))\n\tfor topic := range d.Topics {\n\t\tkeys = append(keys, topic)\n\t}\n\tsort.Strings(keys)\n\tfor _, topic := range keys {\n\t\tif err := pe.putString(topic); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := d.Topics[topic].encode(pe); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tpe.putInt32(int32(d.Timeout / time.Millisecond))\n\n\treturn nil\n}\n\nfunc (d *DeleteRecordsRequest) decode(pd packetDecoder, version int16) error {\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif n > 0 {\n\t\td.Topics = make(map[string]*DeleteRecordsRequestTopic, n)\n\t\tfor i := 0; i < n; i++ {\n\t\t\ttopic, err := pd.getString()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdetails := new(DeleteRecordsRequestTopic)\n\t\t\tif err = details.decode(pd, version); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\td.Topics[topic] = details\n\t\t}\n\t}\n\n\ttimeout, err := pd.getInt32()\n\tif err != nil {\n\t\treturn err\n\t}\n\td.Timeout = time.Duration(timeout) * time.Millisecond\n\n\treturn nil\n}\n\nfunc (d *DeleteRecordsRequest) key() int16 {\n\treturn apiKeyDeleteRecords\n}\n\nfunc (d *DeleteRecordsRequest) version() int16 {\n\treturn d.Version\n}\n\nfunc (d *DeleteRecordsRequest) headerVersion() int16 {\n\treturn 1\n}\n\nfunc (d *DeleteRecordsRequest) isValidVersion() bool {\n\treturn d.Version >= 0 && d.Version <= 1\n}\n\nfunc (d *DeleteRecordsRequest) requiredVersion() KafkaVersion {\n\tswitch d.Version {\n\tcase 1:\n\t\treturn V2_0_0_0\n\tdefault:\n\t\treturn V0_11_0_0\n\t}\n}\n\ntype DeleteRecordsRequestTopic struct {\n\tPartitionOffsets map[int32]int64 // partition => offset\n}\n\nfunc (t *DeleteRecordsRequestTopic) encode(pe packetEncoder) error {\n\tif err := pe.putArrayLength(len(t.PartitionOffsets)); err != nil {\n\t\treturn err\n\t}\n\tkeys := make([]int32, 0, len(t.PartitionOffsets))\n\tfor partition := range t.PartitionOffsets {\n\t\tkeys = append(keys, partition)\n\t}\n\tslices.Sort(keys)\n\tfor _, partition := range keys {\n\t\tpe.putInt32(partition)\n\t\tpe.putInt64(t.PartitionOffsets[partition])\n\t}\n\treturn nil\n}\n\nfunc (t *DeleteRecordsRequestTopic) decode(pd packetDecoder, version int16) error {\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif n > 0 {\n\t\tt.PartitionOffsets = make(map[int32]int64, n)\n\t\tfor i := 0; i < n; i++ {\n\t\t\tpartition, err := pd.getInt32()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\toffset, err := pd.getInt64()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tt.PartitionOffsets[partition] = offset\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "delete_records_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nvar deleteRecordsRequest = []byte{\n\t0, 0, 0, 2,\n\t0, 5, 'o', 't', 'h', 'e', 'r',\n\t0, 0, 0, 0,\n\t0, 5, 't', 'o', 'p', 'i', 'c',\n\t0, 0, 0, 2,\n\t0, 0, 0, 19,\n\t0, 0, 0, 0, 0, 0, 0, 200,\n\t0, 0, 0, 20,\n\t0, 0, 0, 0, 0, 0, 0, 190,\n\t0, 0, 0, 100,\n}\n\nfunc TestDeleteRecordsRequest(t *testing.T) {\n\treq := &DeleteRecordsRequest{\n\t\tTopics: map[string]*DeleteRecordsRequestTopic{\n\t\t\t\"topic\": {\n\t\t\t\tPartitionOffsets: map[int32]int64{\n\t\t\t\t\t19: 200,\n\t\t\t\t\t20: 190,\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"other\": {},\n\t\t},\n\t\tTimeout: 100 * time.Millisecond,\n\t}\n\n\ttestRequest(t, \"\", req, deleteRecordsRequest)\n}\n"
  },
  {
    "path": "delete_records_response.go",
    "content": "package sarama\n\nimport (\n\t\"slices\"\n\t\"sort\"\n\t\"time\"\n)\n\n// response message format is:\n// throttleMs(int32) [topic]\n// where topic is:\n//  name(string) [partition]\n// where partition is:\n//  id(int32) low_watermark(int64) error_code(int16)\n\ntype DeleteRecordsResponse struct {\n\tVersion      int16\n\tThrottleTime time.Duration\n\tTopics       map[string]*DeleteRecordsResponseTopic\n}\n\nfunc (d *DeleteRecordsResponse) setVersion(v int16) {\n\td.Version = v\n}\n\nfunc (d *DeleteRecordsResponse) encode(pe packetEncoder) error {\n\tpe.putDurationMs(d.ThrottleTime)\n\n\tif err := pe.putArrayLength(len(d.Topics)); err != nil {\n\t\treturn err\n\t}\n\tkeys := make([]string, 0, len(d.Topics))\n\tfor topic := range d.Topics {\n\t\tkeys = append(keys, topic)\n\t}\n\tsort.Strings(keys)\n\tfor _, topic := range keys {\n\t\tif err := pe.putString(topic); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := d.Topics[topic].encode(pe); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (d *DeleteRecordsResponse) decode(pd packetDecoder, version int16) (err error) {\n\td.Version = version\n\n\tif d.ThrottleTime, err = pd.getDurationMs(); err != nil {\n\t\treturn err\n\t}\n\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif n > 0 {\n\t\td.Topics = make(map[string]*DeleteRecordsResponseTopic, n)\n\t\tfor i := 0; i < n; i++ {\n\t\t\ttopic, err := pd.getString()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdetails := new(DeleteRecordsResponseTopic)\n\t\t\tif err = details.decode(pd, version); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\td.Topics[topic] = details\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (d *DeleteRecordsResponse) key() int16 {\n\treturn apiKeyDeleteRecords\n}\n\nfunc (d *DeleteRecordsResponse) version() int16 {\n\treturn d.Version\n}\n\nfunc (d *DeleteRecordsResponse) headerVersion() int16 {\n\treturn 0\n}\n\nfunc (d *DeleteRecordsResponse) isValidVersion() bool {\n\treturn d.Version >= 0 && d.Version <= 1\n}\n\nfunc (d *DeleteRecordsResponse) requiredVersion() KafkaVersion {\n\tswitch d.Version {\n\tcase 1:\n\t\treturn V2_0_0_0\n\tdefault:\n\t\treturn V0_11_0_0\n\t}\n}\n\nfunc (r *DeleteRecordsResponse) throttleTime() time.Duration {\n\treturn r.ThrottleTime\n}\n\ntype DeleteRecordsResponseTopic struct {\n\tPartitions map[int32]*DeleteRecordsResponsePartition\n}\n\nfunc (t *DeleteRecordsResponseTopic) encode(pe packetEncoder) error {\n\tif err := pe.putArrayLength(len(t.Partitions)); err != nil {\n\t\treturn err\n\t}\n\tkeys := make([]int32, 0, len(t.Partitions))\n\tfor partition := range t.Partitions {\n\t\tkeys = append(keys, partition)\n\t}\n\tslices.Sort(keys)\n\tfor _, partition := range keys {\n\t\tpe.putInt32(partition)\n\t\tif err := t.Partitions[partition].encode(pe); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (t *DeleteRecordsResponseTopic) decode(pd packetDecoder, version int16) error {\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif n > 0 {\n\t\tt.Partitions = make(map[int32]*DeleteRecordsResponsePartition, n)\n\t\tfor i := 0; i < n; i++ {\n\t\t\tpartition, err := pd.getInt32()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdetails := new(DeleteRecordsResponsePartition)\n\t\t\tif err = details.decode(pd, version); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tt.Partitions[partition] = details\n\t\t}\n\t}\n\n\treturn nil\n}\n\ntype DeleteRecordsResponsePartition struct {\n\tLowWatermark int64\n\tErr          KError\n}\n\nfunc (t *DeleteRecordsResponsePartition) encode(pe packetEncoder) error {\n\tpe.putInt64(t.LowWatermark)\n\tpe.putKError(t.Err)\n\treturn nil\n}\n\nfunc (t *DeleteRecordsResponsePartition) decode(pd packetDecoder, version int16) error {\n\tlowWatermark, err := pd.getInt64()\n\tif err != nil {\n\t\treturn err\n\t}\n\tt.LowWatermark = lowWatermark\n\n\tt.Err, err = pd.getKError()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "delete_records_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nvar deleteRecordsResponse = []byte{\n\t0, 0, 0, 100,\n\t0, 0, 0, 2,\n\t0, 5, 'o', 't', 'h', 'e', 'r',\n\t0, 0, 0, 0,\n\t0, 5, 't', 'o', 'p', 'i', 'c',\n\t0, 0, 0, 2,\n\t0, 0, 0, 19,\n\t0, 0, 0, 0, 0, 0, 0, 200,\n\t0, 0,\n\t0, 0, 0, 20,\n\t255, 255, 255, 255, 255, 255, 255, 255,\n\t0, 3,\n}\n\nfunc TestDeleteRecordsResponse(t *testing.T) {\n\tresp := &DeleteRecordsResponse{\n\t\tVersion:      0,\n\t\tThrottleTime: 100 * time.Millisecond,\n\t\tTopics: map[string]*DeleteRecordsResponseTopic{\n\t\t\t\"topic\": {\n\t\t\t\tPartitions: map[int32]*DeleteRecordsResponsePartition{\n\t\t\t\t\t19: {LowWatermark: 200, Err: 0},\n\t\t\t\t\t20: {LowWatermark: -1, Err: 3},\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"other\": {},\n\t\t},\n\t}\n\n\ttestResponse(t, \"\", resp, deleteRecordsResponse)\n}\n"
  },
  {
    "path": "delete_topics_request.go",
    "content": "package sarama\n\nimport \"time\"\n\ntype DeleteTopicsRequest struct {\n\tVersion int16\n\tTopics  []string\n\tTimeout time.Duration\n}\n\nfunc (d *DeleteTopicsRequest) setVersion(v int16) {\n\td.Version = v\n}\n\nfunc NewDeleteTopicsRequest(version KafkaVersion, topics []string, timeout time.Duration) *DeleteTopicsRequest {\n\td := &DeleteTopicsRequest{\n\t\tTopics:  topics,\n\t\tTimeout: timeout,\n\t}\n\tif version.IsAtLeast(V2_4_0_0) {\n\t\td.Version = 4\n\t} else if version.IsAtLeast(V2_1_0_0) {\n\t\td.Version = 3\n\t} else if version.IsAtLeast(V2_0_0_0) {\n\t\td.Version = 2\n\t} else if version.IsAtLeast(V0_11_0_0) {\n\t\td.Version = 1\n\t}\n\treturn d\n}\n\nfunc (d *DeleteTopicsRequest) encode(pe packetEncoder) error {\n\tif err := pe.putStringArray(d.Topics); err != nil {\n\t\treturn err\n\t}\n\tpe.putInt32(int32(d.Timeout / time.Millisecond))\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (d *DeleteTopicsRequest) decode(pd packetDecoder, version int16) (err error) {\n\tif d.Topics, err = pd.getStringArray(); err != nil {\n\t\treturn err\n\t}\n\ttimeout, err := pd.getInt32()\n\tif err != nil {\n\t\treturn err\n\t}\n\td.Timeout = time.Duration(timeout) * time.Millisecond\n\td.Version = version\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (d *DeleteTopicsRequest) key() int16 {\n\treturn apiKeyDeleteTopics\n}\n\nfunc (d *DeleteTopicsRequest) version() int16 {\n\treturn d.Version\n}\n\nfunc (d *DeleteTopicsRequest) headerVersion() int16 {\n\tif d.Version >= 4 {\n\t\treturn 2\n\t}\n\treturn 1\n}\n\nfunc (d *DeleteTopicsRequest) isFlexible() bool {\n\treturn d.isFlexibleVersion(d.Version)\n}\n\nfunc (d *DeleteTopicsRequest) isFlexibleVersion(version int16) bool {\n\treturn version >= 4\n}\n\nfunc (d *DeleteTopicsRequest) isValidVersion() bool {\n\treturn d.Version >= 0 && d.Version <= 4\n}\n\nfunc (d *DeleteTopicsRequest) requiredVersion() KafkaVersion {\n\tswitch d.Version {\n\tcase 4:\n\t\treturn V2_4_0_0\n\tcase 3:\n\t\treturn V2_1_0_0\n\tcase 2:\n\t\treturn V2_0_0_0\n\tcase 1:\n\t\treturn V0_11_0_0\n\tcase 0:\n\t\treturn V0_10_1_0\n\tdefault:\n\t\treturn V2_2_0_0\n\t}\n}\n"
  },
  {
    "path": "delete_topics_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nvar (\n\tdeleteTopicsRequest = []byte{\n\t\t0, 0, 0, 2,\n\t\t0, 5, 't', 'o', 'p', 'i', 'c',\n\t\t0, 5, 'o', 't', 'h', 'e', 'r',\n\t\t0, 0, 0, 100,\n\t}\n\tdeleteTopicsRequestV4 = []byte{\n\t\t3,\n\t\t6, 't', 'o', 'p', 'i', 'c',\n\t\t6, 'o', 't', 'h', 'e', 'r',\n\t\t0, 0, 0, 100,\n\t\t0, // empty tagged fields\n\t}\n)\n\nfunc TestDeleteTopicsRequestV0(t *testing.T) {\n\treq := &DeleteTopicsRequest{\n\t\tVersion: 0,\n\t\tTopics:  []string{\"topic\", \"other\"},\n\t\tTimeout: 100 * time.Millisecond,\n\t}\n\n\ttestRequest(t, \"\", req, deleteTopicsRequest)\n}\n\nfunc TestDeleteTopicsRequestV1(t *testing.T) {\n\treq := &DeleteTopicsRequest{\n\t\tVersion: 1,\n\t\tTopics:  []string{\"topic\", \"other\"},\n\t\tTimeout: 100 * time.Millisecond,\n\t}\n\n\ttestRequest(t, \"\", req, deleteTopicsRequest)\n}\n\nfunc TestDeleteTopicsRequestV4(t *testing.T) {\n\treq := &DeleteTopicsRequest{\n\t\tVersion: 4,\n\t\tTopics:  []string{\"topic\", \"other\"},\n\t\tTimeout: 100 * time.Millisecond,\n\t}\n\n\ttestRequest(t, \"\", req, deleteTopicsRequestV4)\n}\n"
  },
  {
    "path": "delete_topics_response.go",
    "content": "package sarama\n\nimport (\n\t\"time\"\n)\n\ntype DeleteTopicsResponse struct {\n\tVersion         int16\n\tThrottleTime    time.Duration\n\tTopicErrorCodes map[string]KError\n}\n\nfunc (d *DeleteTopicsResponse) setVersion(v int16) {\n\td.Version = v\n}\n\nfunc (d *DeleteTopicsResponse) encode(pe packetEncoder) error {\n\tif d.Version >= 1 {\n\t\tpe.putDurationMs(d.ThrottleTime)\n\t}\n\n\tif err := pe.putArrayLength(len(d.TopicErrorCodes)); err != nil {\n\t\treturn err\n\t}\n\tfor topic, errorCode := range d.TopicErrorCodes {\n\t\tif err := pe.putString(topic); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tpe.putKError(errorCode)\n\t\tpe.putEmptyTaggedFieldArray()\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (d *DeleteTopicsResponse) decode(pd packetDecoder, version int16) (err error) {\n\tif version >= 1 {\n\t\tif d.ThrottleTime, err = pd.getDurationMs(); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\td.Version = version\n\t}\n\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\td.TopicErrorCodes = make(map[string]KError, n)\n\n\tfor i := 0; i < n; i++ {\n\t\ttopic, err := pd.getString()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\td.TopicErrorCodes[topic], err = pd.getKError()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif _, err := pd.getEmptyTaggedFieldArray(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (d *DeleteTopicsResponse) key() int16 {\n\treturn apiKeyDeleteTopics\n}\n\nfunc (d *DeleteTopicsResponse) version() int16 {\n\treturn d.Version\n}\n\nfunc (d *DeleteTopicsResponse) headerVersion() int16 {\n\tif d.Version >= 4 {\n\t\treturn 1\n\t}\n\treturn 0\n}\n\nfunc (d *DeleteTopicsResponse) isFlexible() bool {\n\treturn d.isFlexibleVersion(d.Version)\n}\n\nfunc (d *DeleteTopicsResponse) isFlexibleVersion(version int16) bool {\n\treturn version >= 4\n}\n\nfunc (d *DeleteTopicsResponse) isValidVersion() bool {\n\treturn d.Version >= 0 && d.Version <= 4\n}\n\nfunc (d *DeleteTopicsResponse) requiredVersion() KafkaVersion {\n\tswitch d.Version {\n\tcase 4:\n\t\treturn V2_4_0_0\n\tcase 3:\n\t\treturn V2_1_0_0\n\tcase 2:\n\t\treturn V2_0_0_0\n\tcase 1:\n\t\treturn V0_11_0_0\n\tcase 0:\n\t\treturn V0_10_1_0\n\tdefault:\n\t\treturn V2_2_0_0\n\t}\n}\n\nfunc (r *DeleteTopicsResponse) throttleTime() time.Duration {\n\treturn r.ThrottleTime\n}\n"
  },
  {
    "path": "delete_topics_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nvar (\n\tdeleteTopicsResponseV0 = []byte{\n\t\t0, 0, 0, 1,\n\t\t0, 5, 't', 'o', 'p', 'i', 'c',\n\t\t0, 0,\n\t}\n\n\tdeleteTopicsResponseV1 = []byte{\n\t\t0, 0, 0, 100,\n\t\t0, 0, 0, 1,\n\t\t0, 5, 't', 'o', 'p', 'i', 'c',\n\t\t0, 0,\n\t}\n\n\tdeleteTopicsResponseV4 = []byte{\n\t\t0, 0, 0, 100,\n\t\t2,\n\t\t6, 't', 'o', 'p', 'i', 'c',\n\t\t0, 0,\n\t\t0, // empty tagged fields\n\t\t0, // empty tagged fields\n\t}\n)\n\nfunc TestDeleteTopicsResponse(t *testing.T) {\n\tresp := &DeleteTopicsResponse{\n\t\tTopicErrorCodes: map[string]KError{\n\t\t\t\"topic\": ErrNoError,\n\t\t},\n\t}\n\n\ttestResponse(t, \"version 0\", resp, deleteTopicsResponseV0)\n\n\tresp.Version = 1\n\tresp.ThrottleTime = 100 * time.Millisecond\n\n\ttestResponse(t, \"version 1\", resp, deleteTopicsResponseV1)\n\n\tresp.Version = 4\n\ttestResponse(t, \"version 4\", resp, deleteTopicsResponseV4)\n}\n"
  },
  {
    "path": "describe_client_quotas_request.go",
    "content": "package sarama\n\n// DescribeClientQuotas Request (Version: 0) => [components] strict\n//   components => entity_type match_type match\n//     entity_type => STRING\n//     match_type => INT8\n//     match => NULLABLE_STRING\n//   strict => BOOLEAN\n// DescribeClientQuotas Request (Version: 1) => [components] strict _tagged_fields\n//   components => entity_type match_type match _tagged_fields\n//     entity_type => COMPACT_STRING\n//     match_type => INT8\n//     match => COMPACT_NULLABLE_STRING\n//   strict => BOOLEAN\n\n// DescribeClientQuotasRequest contains a filter to be applied to matching\n// client quotas.\n// Components: the components to filter on\n// Strict: whether the filter only includes specified components\ntype DescribeClientQuotasRequest struct {\n\tVersion    int16\n\tComponents []QuotaFilterComponent\n\tStrict     bool\n}\n\nfunc NewDescribeClientQuotasRequest(version KafkaVersion, components []QuotaFilterComponent, strict bool) *DescribeClientQuotasRequest {\n\td := &DescribeClientQuotasRequest{\n\t\tComponents: components,\n\t\tStrict:     strict,\n\t}\n\tif version.IsAtLeast(V2_8_0_0) {\n\t\td.Version = 1\n\t}\n\treturn d\n}\n\nfunc (d *DescribeClientQuotasRequest) setVersion(v int16) {\n\td.Version = v\n}\n\n// QuotaFilterComponent describes a component for applying a client quota filter.\n// EntityType: the entity type the filter component applies to (\"user\", \"client-id\", \"ip\")\n// MatchType: the match type of the filter component (any, exact, default)\n// Match: the name that's matched exactly (used when MatchType is QuotaMatchExact)\ntype QuotaFilterComponent struct {\n\tEntityType QuotaEntityType\n\tMatchType  QuotaMatchType\n\tMatch      string\n}\n\nfunc (d *DescribeClientQuotasRequest) encode(pe packetEncoder) error {\n\t// Components\n\tif err := pe.putArrayLength(len(d.Components)); err != nil {\n\t\treturn err\n\t}\n\tfor _, c := range d.Components {\n\t\tif err := c.encode(pe); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Strict\n\tpe.putBool(d.Strict)\n\n\tpe.putEmptyTaggedFieldArray()\n\n\treturn nil\n}\n\nfunc (d *DescribeClientQuotasRequest) decode(pd packetDecoder, version int16) error {\n\t// Components\n\tcomponentCount, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif componentCount > 0 {\n\t\td.Components = make([]QuotaFilterComponent, componentCount)\n\t\tfor i := range d.Components {\n\t\t\tc := QuotaFilterComponent{}\n\t\t\tif err = c.decode(pd, version); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\td.Components[i] = c\n\t\t}\n\t} else {\n\t\td.Components = []QuotaFilterComponent{}\n\t}\n\n\t// Strict\n\tstrict, err := pd.getBool()\n\tif err != nil {\n\t\treturn err\n\t}\n\td.Strict = strict\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (d *QuotaFilterComponent) encode(pe packetEncoder) error {\n\t// EntityType\n\tif err := pe.putString(string(d.EntityType)); err != nil {\n\t\treturn err\n\t}\n\n\t// MatchType\n\tpe.putInt8(int8(d.MatchType))\n\n\t// Match\n\tif d.MatchType == QuotaMatchAny {\n\t\tif err := pe.putNullableString(nil); err != nil {\n\t\t\treturn err\n\t\t}\n\t} else if d.MatchType == QuotaMatchDefault {\n\t\tif err := pe.putNullableString(nil); err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\tif err := pe.putString(d.Match); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\n\treturn nil\n}\n\nfunc (d *QuotaFilterComponent) decode(pd packetDecoder, version int16) error {\n\t// EntityType\n\tentityType, err := pd.getString()\n\tif err != nil {\n\t\treturn err\n\t}\n\td.EntityType = QuotaEntityType(entityType)\n\n\t// MatchType\n\tmatchType, err := pd.getInt8()\n\tif err != nil {\n\t\treturn err\n\t}\n\td.MatchType = QuotaMatchType(matchType)\n\n\t// Match\n\tmatch, err := pd.getNullableString()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif match != nil {\n\t\td.Match = *match\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (d *DescribeClientQuotasRequest) key() int16 {\n\treturn apiKeyDescribeClientQuotas\n}\n\nfunc (d *DescribeClientQuotasRequest) version() int16 {\n\treturn d.Version\n}\n\nfunc (d *DescribeClientQuotasRequest) headerVersion() int16 {\n\tif d.Version >= 1 {\n\t\treturn 2\n\t}\n\n\treturn 1\n}\n\nfunc (d *DescribeClientQuotasRequest) isValidVersion() bool {\n\treturn d.Version >= 0 && d.Version <= 1\n}\n\nfunc (d *DescribeClientQuotasRequest) isFlexible() bool {\n\treturn d.isFlexibleVersion(d.Version)\n}\n\nfunc (d *DescribeClientQuotasRequest) isFlexibleVersion(version int16) bool {\n\treturn version >= 1\n}\n\nfunc (d *DescribeClientQuotasRequest) requiredVersion() KafkaVersion {\n\tswitch d.Version {\n\tcase 1:\n\t\treturn V2_8_0_0\n\tcase 0:\n\t\treturn V2_6_0_0\n\tdefault:\n\t\treturn V2_8_0_0\n\t}\n}\n"
  },
  {
    "path": "describe_client_quotas_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nvar (\n\tdescribeClientQuotasRequestAll = []byte{\n\t\t0, 0, 0, 0, // components len\n\t\t0, // strict\n\t}\n\n\tdescribeClientQuotasRequestDefaultUser = []byte{\n\t\t0, 0, 0, 1, // components len\n\t\t0, 4, 'u', 's', 'e', 'r', // entity type\n\t\t1,        // match type (default)\n\t\t255, 255, // match *string\n\t\t0, // strict\n\t}\n\n\tdescribeClientQuotasRequestOnlySpecificUser = []byte{\n\t\t0, 0, 0, 1, // components len\n\t\t0, 4, 'u', 's', 'e', 'r', // entity type\n\t\t0,                                  // match type (exact)\n\t\t0, 6, 's', 'a', 'r', 'a', 'm', 'a', // match *string\n\t\t1, // strict\n\t}\n\n\tdescribeClientQuotasRequestMultiComponents = []byte{\n\t\t0, 0, 0, 2, // components len\n\t\t0, 4, 'u', 's', 'e', 'r', // entity type\n\t\t2,        // match type (any)\n\t\t255, 255, // match *string\n\t\t0, 9, 'c', 'l', 'i', 'e', 'n', 't', '-', 'i', 'd', // entity type\n\t\t1,        // match type (default)\n\t\t255, 255, // match *string\n\t\t0, // strict\n\t}\n\n\tdescribeClientQuotasV1 = []byte{\n\t\t0x02,                     // components len\n\t\t0x05, 'u', 's', 'e', 'r', // entity type\n\t\t0x01, // match type (default name)\n\t\t0x00, // match (NULL)\n\t\t0x00, // empty tagged fields\n\t\t0x01, // strict (true)\n\t\t0x00, // empty tagged fields,\n\t}\n)\n\nfunc TestDescribeClientQuotasRequest(t *testing.T) {\n\t// Match All\n\treq := &DescribeClientQuotasRequest{\n\t\tComponents: []QuotaFilterComponent{},\n\t\tStrict:     false,\n\t}\n\ttestRequest(t, \"Match All\", req, describeClientQuotasRequestAll)\n\n\t// Match Default User\n\tdefaultUser := QuotaFilterComponent{\n\t\tEntityType: QuotaEntityUser,\n\t\tMatchType:  QuotaMatchDefault,\n\t}\n\treq = &DescribeClientQuotasRequest{\n\t\tComponents: []QuotaFilterComponent{defaultUser},\n\t\tStrict:     false,\n\t}\n\ttestRequest(t, \"Match Default User\", req, describeClientQuotasRequestDefaultUser)\n\n\t// Match Only Specific User\n\tspecificUser := QuotaFilterComponent{\n\t\tEntityType: QuotaEntityUser,\n\t\tMatchType:  QuotaMatchExact,\n\t\tMatch:      \"sarama\",\n\t}\n\treq = &DescribeClientQuotasRequest{\n\t\tComponents: []QuotaFilterComponent{specificUser},\n\t\tStrict:     true,\n\t}\n\ttestRequest(t, \"Match Only Specific User\", req, describeClientQuotasRequestOnlySpecificUser)\n\n\t// Match default client-id of any user\n\tanyUser := QuotaFilterComponent{\n\t\tEntityType: QuotaEntityUser,\n\t\tMatchType:  QuotaMatchAny,\n\t}\n\tdefaultClientId := QuotaFilterComponent{\n\t\tEntityType: QuotaEntityClientID,\n\t\tMatchType:  QuotaMatchDefault,\n\t}\n\treq = &DescribeClientQuotasRequest{\n\t\tComponents: []QuotaFilterComponent{anyUser, defaultClientId},\n\t\tStrict:     false,\n\t}\n\ttestRequest(t, \"Match default client-id of any user\", req, describeClientQuotasRequestMultiComponents)\n}\n\nfunc TestDescribeClientQuotasRequestV1(t *testing.T) {\n\treq := &DescribeClientQuotasRequest{\n\t\tVersion: 1,\n\t\tComponents: []QuotaFilterComponent{\n\t\t\t{\n\t\t\t\tEntityType: \"user\",\n\t\t\t\tMatchType:  1,\n\t\t\t},\n\t\t},\n\t\tStrict: true,\n\t}\n\ttestRequest(t, \"V1\", req, describeClientQuotasV1)\n}\n"
  },
  {
    "path": "describe_client_quotas_response.go",
    "content": "package sarama\n\nimport (\n\t\"time\"\n)\n\n// DescribeClientQuotas Response (Version: 0) => throttle_time_ms error_code error_message [entries]\n//   throttle_time_ms => INT32\n//   error_code => INT16\n//   error_message => NULLABLE_STRING\n//   entries => [entity] [values]\n//     entity => entity_type entity_name\n//       entity_type => STRING\n//       entity_name => NULLABLE_STRING\n//     values => key value\n//       key => STRING\n//       value => FLOAT64\n// DescribeClientQuotas Response (Version: 1) => throttle_time_ms error_code error_message [entries] _tagged_fields\n//   throttle_time_ms => INT32\n//   error_code => INT16\n//   error_message => COMPACT_NULLABLE_STRING\n//   entries => [entity] [values] _tagged_fields\n//     entity => entity_type entity_name _tagged_fields\n//       entity_type => COMPACT_STRING\n//       entity_name => COMPACT_NULLABLE_STRING\n//     values => key value _tagged_fields\n//       key => COMPACT_STRING\n//       value => FLOAT64\n\ntype DescribeClientQuotasResponse struct {\n\tVersion      int16\n\tThrottleTime time.Duration               // The duration in milliseconds for which the request was throttled due to a quota violation, or zero if the request did not violate any quota.\n\tErrorCode    KError                      // The error code, or `0` if the quota description succeeded.\n\tErrorMsg     *string                     // The error message, or `null` if the quota description succeeded.\n\tEntries      []DescribeClientQuotasEntry // A result entry.\n}\n\nfunc (d *DescribeClientQuotasResponse) setVersion(v int16) {\n\td.Version = v\n}\n\ntype DescribeClientQuotasEntry struct {\n\tEntity []QuotaEntityComponent // The quota entity description.\n\tValues map[string]float64     // The quota values for the entity.\n}\n\ntype QuotaEntityComponent struct {\n\tEntityType QuotaEntityType\n\tMatchType  QuotaMatchType\n\tName       string\n}\n\nfunc (d *DescribeClientQuotasResponse) encode(pe packetEncoder) error {\n\t// ThrottleTime\n\tpe.putDurationMs(d.ThrottleTime)\n\n\t// ErrorCode\n\tpe.putKError(d.ErrorCode)\n\n\t// ErrorMsg\n\tif err := pe.putNullableString(d.ErrorMsg); err != nil {\n\t\treturn err\n\t}\n\n\t// Entries\n\tif err := pe.putArrayLength(len(d.Entries)); err != nil {\n\t\treturn err\n\t}\n\tfor _, e := range d.Entries {\n\t\tif err := e.encode(pe); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (d *DescribeClientQuotasResponse) decode(pd packetDecoder, version int16) (err error) {\n\tif d.ThrottleTime, err = pd.getDurationMs(); err != nil {\n\t\treturn err\n\t}\n\n\t// ErrorCode\n\td.ErrorCode, err = pd.getKError()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// ErrorMsg\n\terrMsg, err := pd.getNullableString()\n\tif err != nil {\n\t\treturn err\n\t}\n\td.ErrorMsg = errMsg\n\n\t// Entries\n\tentryCount, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif entryCount > 0 {\n\t\td.Entries = make([]DescribeClientQuotasEntry, entryCount)\n\t\tfor i := range d.Entries {\n\t\t\te := DescribeClientQuotasEntry{}\n\t\t\tif err = e.decode(pd, version); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\td.Entries[i] = e\n\t\t}\n\t} else {\n\t\td.Entries = []DescribeClientQuotasEntry{}\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (d *DescribeClientQuotasEntry) encode(pe packetEncoder) error {\n\t// Entity\n\tif err := pe.putArrayLength(len(d.Entity)); err != nil {\n\t\treturn err\n\t}\n\tfor _, e := range d.Entity {\n\t\tif err := e.encode(pe); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Values\n\tif err := pe.putArrayLength(len(d.Values)); err != nil {\n\t\treturn err\n\t}\n\tfor key, value := range d.Values {\n\t\t// key\n\t\tif err := pe.putString(key); err != nil {\n\t\t\treturn err\n\t\t}\n\t\t// value\n\t\tpe.putFloat64(value)\n\t\tpe.putEmptyTaggedFieldArray()\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (d *DescribeClientQuotasEntry) decode(pd packetDecoder, version int16) error {\n\t// Entity\n\tcomponentCount, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif componentCount > 0 {\n\t\td.Entity = make([]QuotaEntityComponent, componentCount)\n\t\tfor i := 0; i < componentCount; i++ {\n\t\t\tcomponent := QuotaEntityComponent{}\n\t\t\tif err := component.decode(pd, version); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\td.Entity[i] = component\n\t\t}\n\t} else {\n\t\td.Entity = []QuotaEntityComponent{}\n\t}\n\n\t// Values\n\tvalueCount, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif valueCount > 0 {\n\t\td.Values = make(map[string]float64, valueCount)\n\t\tfor i := 0; i < valueCount; i++ {\n\t\t\t// key\n\t\t\tkey, err := pd.getString()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t// value\n\t\t\tvalue, err := pd.getFloat64()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\td.Values[key] = value\n\t\t\t_, err = pd.getEmptyTaggedFieldArray()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t} else {\n\t\td.Values = map[string]float64{}\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (c *QuotaEntityComponent) encode(pe packetEncoder) error {\n\t// entity_type\n\tif err := pe.putString(string(c.EntityType)); err != nil {\n\t\treturn err\n\t}\n\t// entity_name\n\tif c.MatchType == QuotaMatchDefault {\n\t\tif err := pe.putNullableString(nil); err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\tif err := pe.putString(c.Name); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (c *QuotaEntityComponent) decode(pd packetDecoder, version int16) error {\n\t// entity_type\n\tentityType, err := pd.getString()\n\tif err != nil {\n\t\treturn err\n\t}\n\tc.EntityType = QuotaEntityType(entityType)\n\n\t// entity_name\n\tentityName, err := pd.getNullableString()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif entityName == nil {\n\t\tc.MatchType = QuotaMatchDefault\n\t} else {\n\t\tc.MatchType = QuotaMatchExact\n\t\tc.Name = *entityName\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (d *DescribeClientQuotasResponse) key() int16 {\n\treturn apiKeyDescribeClientQuotas\n}\n\nfunc (d *DescribeClientQuotasResponse) version() int16 {\n\treturn d.Version\n}\n\nfunc (d *DescribeClientQuotasResponse) headerVersion() int16 {\n\tif d.Version >= 1 {\n\t\treturn 1\n\t}\n\n\treturn 0\n}\n\nfunc (d *DescribeClientQuotasResponse) isValidVersion() bool {\n\treturn d.Version >= 0 && d.Version <= 1\n}\n\nfunc (d *DescribeClientQuotasResponse) isFlexible() bool {\n\treturn d.isFlexibleVersion(d.Version)\n}\n\nfunc (d *DescribeClientQuotasResponse) isFlexibleVersion(version int16) bool {\n\treturn version >= 1\n}\n\nfunc (d *DescribeClientQuotasResponse) requiredVersion() KafkaVersion {\n\tswitch d.Version {\n\tcase 1:\n\t\treturn V2_8_0_0\n\tcase 0:\n\t\treturn V2_6_0_0\n\tdefault:\n\t\treturn V2_8_0_0\n\t}\n}\n\nfunc (r *DescribeClientQuotasResponse) throttleTime() time.Duration {\n\treturn r.ThrottleTime\n}\n"
  },
  {
    "path": "describe_client_quotas_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\tassert \"github.com/stretchr/testify/require\"\n)\n\nvar (\n\tdescribeClientQuotasResponseError = []byte{\n\t\t0, 0, 0, 0, // ThrottleTime\n\t\t0, 35, // ErrorCode\n\t\t0, 41, 'C', 'u', 's', 't', 'o', 'm', ' ', 'e', 'n', 't', 'i', 't', 'y', ' ', 't', 'y', 'p', 'e', ' ', '\\'', 'f', 'a', 'u', 'l', 't', 'y', '\\'', ' ', 'n', 'o', 't', ' ', 's', 'u', 'p', 'p', 'o', 'r', 't', 'e', 'd',\n\t\t0, 0, 0, 0, // Entries\n\t}\n\n\tdescribeClientQuotasResponseSingleValue = []byte{\n\t\t0, 0, 0, 0, // ThrottleTime\n\t\t0, 0, // ErrorCode\n\t\t255, 255, // ErrorMsg (nil)\n\t\t0, 0, 0, 1, // Entries\n\t\t0, 0, 0, 1, // Entity\n\t\t0, 4, 'u', 's', 'e', 'r', // Entity type\n\t\t255, 255, // Entity name (nil)\n\t\t0, 0, 0, 1, // Values\n\t\t0, 18, 'p', 'r', 'o', 'd', 'u', 'c', 'e', 'r', '_', 'b', 'y', 't', 'e', '_', 'r', 'a', 't', 'e',\n\t\t65, 46, 132, 128, 0, 0, 0, 0, // 1000000\n\t}\n\n\tdescribeClientQuotasResponseComplexEntity = []byte{\n\t\t0, 0, 0, 0, // ThrottleTime\n\t\t0, 0, // ErrorCode\n\t\t255, 255, // ErrorMsg (nil)\n\t\t0, 0, 0, 2, // Entries\n\t\t0, 0, 0, 1, // Entity\n\t\t0, 4, 'u', 's', 'e', 'r', // Entity type\n\t\t255, 255, // Entity name (nil)\n\t\t0, 0, 0, 1, // Values\n\t\t0, 18, 'p', 'r', 'o', 'd', 'u', 'c', 'e', 'r', '_', 'b', 'y', 't', 'e', '_', 'r', 'a', 't', 'e',\n\t\t65, 46, 132, 128, 0, 0, 0, 0, // 1000000\n\t\t0, 0, 0, 1, // Entity\n\t\t0, 9, 'c', 'l', 'i', 'e', 'n', 't', '-', 'i', 'd', // Entity type\n\t\t0, 6, 's', 'a', 'r', 'a', 'm', 'a', // Entity name\n\t\t0, 0, 0, 1, // Values\n\t\t0, 18, 'c', 'o', 'n', 's', 'u', 'm', 'e', 'r', '_', 'b', 'y', 't', 'e', '_', 'r', 'a', 't', 'e',\n\t\t65, 46, 132, 128, 0, 0, 0, 0, // 1000000\n\t}\n\n\tdescribeClientQuotasResponseV1 = []byte{\n\t\t0x00, 0x00, 0x00, 0x80, // ThrottleTime (128ms)\n\t\t0x00, 0x00, // ErrorCode\n\t\t0x01, // ErrorMsg (nil)\n\t\t0x02, // Entries (2)\n\t\t0x02,\n\t\t0x05, 'u', 's', 'e', 'r', // Entity Type\n\t\t0x00, // Entity Name (nil)\n\t\t0x00, // Empty tagged fields\n\t\t0x02,\n\t\t// 0x13, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x65, 0x72, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65,\n\t\t0x13, 'p', 'r', 'o', 'd', 'u', 'c', 'e', 'r', '_', 'b', 'y', 't', 'e', '_', 'r', 'a', 't', 'e',\n\t\t0x41, 0x2f, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // 1024000\n\t\t0x00, // Empty tagged fields\n\t\t0x00, // Empty tagged fields\n\t\t0x00, // Empty tagged fields\n\t}\n)\n\nfunc TestDescribeClientQuotasResponse(t *testing.T) {\n\t// Response With Error\n\terrMsg := \"Custom entity type 'faulty' not supported\"\n\tres := &DescribeClientQuotasResponse{\n\t\tThrottleTime: 0,\n\t\tErrorCode:    ErrUnsupportedVersion,\n\t\tErrorMsg:     &errMsg,\n\t\tEntries:      []DescribeClientQuotasEntry{},\n\t}\n\ttestResponse(t, \"Response With Error\", res, describeClientQuotasResponseError)\n\n\t// Single Quota entry\n\tdefaultUserComponent := QuotaEntityComponent{\n\t\tEntityType: QuotaEntityUser,\n\t\tMatchType:  QuotaMatchDefault,\n\t}\n\tentry := DescribeClientQuotasEntry{\n\t\tEntity: []QuotaEntityComponent{defaultUserComponent},\n\t\tValues: map[string]float64{\"producer_byte_rate\": 1000000},\n\t}\n\tres = &DescribeClientQuotasResponse{\n\t\tThrottleTime: 0,\n\t\tErrorCode:    ErrNoError,\n\t\tErrorMsg:     nil,\n\t\tEntries:      []DescribeClientQuotasEntry{entry},\n\t}\n\ttestResponse(t, \"Single Value\", res, describeClientQuotasResponseSingleValue)\n\n\t// Complex Quota entry\n\tsaramaClientIDComponent := QuotaEntityComponent{\n\t\tEntityType: QuotaEntityClientID,\n\t\tMatchType:  QuotaMatchExact,\n\t\tName:       \"sarama\",\n\t}\n\tuserEntry := DescribeClientQuotasEntry{\n\t\tEntity: []QuotaEntityComponent{defaultUserComponent},\n\t\tValues: map[string]float64{\"producer_byte_rate\": 1000000},\n\t}\n\tclientEntry := DescribeClientQuotasEntry{\n\t\tEntity: []QuotaEntityComponent{saramaClientIDComponent},\n\t\tValues: map[string]float64{\"consumer_byte_rate\": 1000000},\n\t}\n\tres = &DescribeClientQuotasResponse{\n\t\tThrottleTime: 0,\n\t\tErrorCode:    ErrNoError,\n\t\tErrorMsg:     nil,\n\t\tEntries:      []DescribeClientQuotasEntry{userEntry, clientEntry},\n\t}\n\ttestResponse(t, \"Complex Quota\", res, describeClientQuotasResponseComplexEntity)\n}\n\nfunc TestDescribeClientQuotasResponseV1(t *testing.T) {\n\tres := &DescribeClientQuotasResponse{Version: 1}\n\ttestVersionDecodable(t, \"V1\", res, describeClientQuotasResponseV1, 1)\n\tassert.Equal(t, res.ThrottleTime, time.Millisecond*128)\n\tassert.Len(t, res.Entries, 1)\n\tassert.Equal(t, []DescribeClientQuotasEntry{\n\t\t{\n\t\t\tEntity: []QuotaEntityComponent{\n\t\t\t\t{\n\t\t\t\t\tEntityType: \"user\",\n\t\t\t\t\tMatchType:  1,\n\t\t\t\t},\n\t\t\t},\n\t\t\tValues: map[string]float64{\n\t\t\t\t\"producer_byte_rate\": 1024000,\n\t\t\t},\n\t\t},\n\t}, res.Entries)\n}\n"
  },
  {
    "path": "describe_cluster_request.go",
    "content": "package sarama\n\n// DescribeClusterEndpointType enumerates the DescribeCluster endpoint types\n// defined by the Kafka protocol.\nconst (\n\tDescribeClusterEndpointTypeBrokers     int8 = 1\n\tDescribeClusterEndpointTypeControllers int8 = 2\n)\n\ntype DescribeClusterRequest struct {\n\tVersion int16\n\n\tIncludeClusterAuthorizedOperations bool\n\tEndpointType                       int8\n\tIncludeFencedBrokers               bool\n}\n\nfunc NewDescribeClusterRequest(version KafkaVersion) *DescribeClusterRequest {\n\treq := &DescribeClusterRequest{}\n\n\tswitch {\n\tcase version.IsAtLeast(V4_0_0_0):\n\t\treq.Version = 2\n\tcase version.IsAtLeast(V3_7_0_0):\n\t\treq.Version = 1\n\tdefault:\n\t\treq.Version = 0\n\t}\n\n\tif req.Version >= 1 {\n\t\treq.EndpointType = DescribeClusterEndpointTypeBrokers\n\t}\n\tif req.Version >= 2 {\n\t\treq.IncludeFencedBrokers = true\n\t}\n\n\treturn req\n}\n\nfunc (r *DescribeClusterRequest) encode(pe packetEncoder) error {\n\tif r.Version < 0 || r.Version > 2 {\n\t\treturn PacketEncodingError{\"invalid or unsupported DescribeClusterRequest version\"}\n\t}\n\n\tpe.putBool(r.IncludeClusterAuthorizedOperations)\n\tif r.Version >= 1 {\n\t\tpe.putInt8(r.EndpointType)\n\t}\n\tif r.Version >= 2 {\n\t\tpe.putBool(r.IncludeFencedBrokers)\n\t}\n\tpe.putEmptyTaggedFieldArray()\n\n\treturn nil\n}\n\nfunc (r *DescribeClusterRequest) decode(pd packetDecoder, version int16) error {\n\tr.Version = version\n\tif r.Version < 0 || r.Version > 2 {\n\t\treturn PacketDecodingError{\"invalid or unsupported DescribeClusterRequest version\"}\n\t}\n\n\tvar err error\n\tif r.IncludeClusterAuthorizedOperations, err = pd.getBool(); err != nil {\n\t\treturn err\n\t}\n\tif r.Version >= 1 {\n\t\tif r.EndpointType, err = pd.getInt8(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif r.Version >= 2 {\n\t\tif r.IncludeFencedBrokers, err = pd.getBool(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (r *DescribeClusterRequest) key() int16 { return apiKeyDescribeCluster }\n\nfunc (r *DescribeClusterRequest) version() int16 { return r.Version }\n\nfunc (r *DescribeClusterRequest) setVersion(v int16) { r.Version = v }\n\nfunc (r *DescribeClusterRequest) headerVersion() int16 { return 2 }\n\nfunc (r *DescribeClusterRequest) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 2\n}\n\nfunc (r *DescribeClusterRequest) isFlexible() bool { return true }\n\nfunc (r *DescribeClusterRequest) isFlexibleVersion(version int16) bool { return version >= 0 }\n\nfunc (r *DescribeClusterRequest) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 2:\n\t\treturn V4_0_0_0\n\tcase 1:\n\t\treturn V3_7_0_0\n\tdefault:\n\t\treturn V2_8_0_0\n\t}\n}\n"
  },
  {
    "path": "describe_cluster_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nfunc TestDescribeClusterRequestVersions(t *testing.T) {\n\tt.Run(\"v0\", func(t *testing.T) {\n\t\treq := &DescribeClusterRequest{Version: 0, IncludeClusterAuthorizedOperations: true}\n\t\ttestRequestWithoutByteComparison(t, \"DescribeClusterRequest v0\", req)\n\t})\n\n\tt.Run(\"v1\", func(t *testing.T) {\n\t\treq := &DescribeClusterRequest{\n\t\t\tVersion:      1,\n\t\t\tEndpointType: DescribeClusterEndpointTypeControllers,\n\t\t}\n\t\ttestRequestWithoutByteComparison(t, \"DescribeClusterRequest v1\", req)\n\t})\n\n\tt.Run(\"v2\", func(t *testing.T) {\n\t\treq := &DescribeClusterRequest{\n\t\t\tVersion:                            2,\n\t\t\tEndpointType:                       DescribeClusterEndpointTypeBrokers,\n\t\t\tIncludeFencedBrokers:               true,\n\t\t\tIncludeClusterAuthorizedOperations: true,\n\t\t}\n\t\ttestRequestWithoutByteComparison(t, \"DescribeClusterRequest v2\", req)\n\t})\n}\n\nfunc TestNewDescribeClusterRequest(t *testing.T) {\n\ttestCases := []struct {\n\t\tversion     KafkaVersion\n\t\texpectedVer int16\n\t}{\n\t\t{V2_8_0_0, 0},\n\t\t{V3_7_0_0, 1},\n\t\t{V4_0_0_0, 2},\n\t}\n\n\tfor _, tc := range testCases {\n\t\treq := NewDescribeClusterRequest(tc.version)\n\t\tif req.Version != tc.expectedVer {\n\t\t\tt.Fatalf(\"version mismatch for %v: got %d\", tc.version, req.Version)\n\t\t}\n\t\tif req.Version >= 1 && req.EndpointType == 0 {\n\t\t\tt.Fatalf(\"endpoint type not set for version %d\", req.Version)\n\t\t}\n\t\tif req.Version < 2 && req.IncludeFencedBrokers {\n\t\t\tt.Fatalf(\"include fenced brokers unexpectedly enabled for version %d\", req.Version)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "describe_cluster_response.go",
    "content": "package sarama\n\nimport \"time\"\n\ntype DescribeClusterResponse struct {\n\tVersion int16\n\n\tThrottleTimeMs int32\n\tErr            KError\n\tErrorMessage   *string\n\tEndpointType   int8\n\tClusterID      string\n\tControllerID   int32\n\tBrokers        []*DescribeClusterBroker\n\n\tClusterAuthorizedOperations int32\n}\n\ntype DescribeClusterBroker struct {\n\tBrokerID int32\n\tHost     string\n\tPort     int32\n\tRack     *string\n\tIsFenced bool\n}\n\nfunc (r *DescribeClusterResponse) encode(pe packetEncoder) error {\n\tif r.Version < 0 || r.Version > 2 {\n\t\treturn PacketEncodingError{\"invalid or unsupported DescribeClusterResponse version\"}\n\t}\n\n\tpe.putInt32(r.ThrottleTimeMs)\n\tpe.putInt16(int16(r.Err))\n\tif err := pe.putNullableString(r.ErrorMessage); err != nil {\n\t\treturn err\n\t}\n\tif r.Version >= 1 {\n\t\tpe.putInt8(r.EndpointType)\n\t}\n\tif err := pe.putString(r.ClusterID); err != nil {\n\t\treturn err\n\t}\n\tpe.putInt32(r.ControllerID)\n\n\tif err := pe.putArrayLength(len(r.Brokers)); err != nil {\n\t\treturn err\n\t}\n\tfor _, broker := range r.Brokers {\n\t\tif err := broker.encode(pe, r.Version); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tpe.putInt32(r.ClusterAuthorizedOperations)\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (r *DescribeClusterResponse) decode(pd packetDecoder, version int16) error {\n\tr.Version = version\n\tif r.Version < 0 || r.Version > 2 {\n\t\treturn PacketDecodingError{\"invalid or unsupported DescribeClusterResponse version\"}\n\t}\n\n\tvar err error\n\tif r.ThrottleTimeMs, err = pd.getInt32(); err != nil {\n\t\treturn err\n\t}\n\tif r.Err, err = pd.getKError(); err != nil {\n\t\treturn err\n\t}\n\tif r.ErrorMessage, err = pd.getNullableString(); err != nil {\n\t\treturn err\n\t}\n\tif r.Version >= 1 {\n\t\tif r.EndpointType, err = pd.getInt8(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif r.ClusterID, err = pd.getString(); err != nil {\n\t\treturn err\n\t}\n\tif r.ControllerID, err = pd.getInt32(); err != nil {\n\t\treturn err\n\t}\n\n\tbrokerCount, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\tr.Brokers = make([]*DescribeClusterBroker, brokerCount)\n\tfor i := 0; i < brokerCount; i++ {\n\t\tbroker := &DescribeClusterBroker{}\n\t\tif err := broker.decode(pd, r.Version); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.Brokers[i] = broker\n\t}\n\n\tif r.ClusterAuthorizedOperations, err = pd.getInt32(); err != nil {\n\t\treturn err\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (r *DescribeClusterResponse) key() int16 { return apiKeyDescribeCluster }\n\nfunc (r *DescribeClusterResponse) version() int16 { return r.Version }\n\nfunc (r *DescribeClusterResponse) setVersion(v int16) { r.Version = v }\n\nfunc (r *DescribeClusterResponse) headerVersion() int16 { return 1 }\n\nfunc (r *DescribeClusterResponse) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 2\n}\n\nfunc (r *DescribeClusterResponse) isFlexible() bool { return true }\n\nfunc (r *DescribeClusterResponse) isFlexibleVersion(version int16) bool { return version >= 0 }\n\nfunc (r *DescribeClusterResponse) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 2:\n\t\treturn V4_0_0_0\n\tcase 1:\n\t\treturn V3_7_0_0\n\tdefault:\n\t\treturn V2_8_0_0\n\t}\n}\n\nfunc (r *DescribeClusterResponse) throttleTime() time.Duration {\n\treturn time.Duration(r.ThrottleTimeMs) * time.Millisecond\n}\n\nfunc (b *DescribeClusterBroker) encode(pe packetEncoder, version int16) error {\n\tpe.putInt32(b.BrokerID)\n\tif err := pe.putString(b.Host); err != nil {\n\t\treturn err\n\t}\n\tpe.putInt32(b.Port)\n\tif err := pe.putNullableString(b.Rack); err != nil {\n\t\treturn err\n\t}\n\tif version >= 2 {\n\t\tpe.putBool(b.IsFenced)\n\t}\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (b *DescribeClusterBroker) decode(pd packetDecoder, version int16) error {\n\tvar err error\n\tif b.BrokerID, err = pd.getInt32(); err != nil {\n\t\treturn err\n\t}\n\tif b.Host, err = pd.getString(); err != nil {\n\t\treturn err\n\t}\n\tif b.Port, err = pd.getInt32(); err != nil {\n\t\treturn err\n\t}\n\tif b.Rack, err = pd.getNullableString(); err != nil {\n\t\treturn err\n\t}\n\tif version >= 2 {\n\t\tif b.IsFenced, err = pd.getBool(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n"
  },
  {
    "path": "describe_cluster_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nfunc TestDescribeClusterResponse(t *testing.T) {\n\trib := &DescribeClusterBroker{\n\t\tBrokerID: 1,\n\t\tHost:     \"localhost\",\n\t\tPort:     9092,\n\t\tRack:     nullString(\"rack-a\"),\n\t\tIsFenced: true,\n\t}\n\n\tresp := &DescribeClusterResponse{\n\t\tVersion:                     2,\n\t\tThrottleTimeMs:              10,\n\t\tErr:                         ErrNoError,\n\t\tEndpointType:                DescribeClusterEndpointTypeBrokers,\n\t\tClusterID:                   \"cluster-1\",\n\t\tControllerID:                1,\n\t\tBrokers:                     []*DescribeClusterBroker{rib},\n\t\tClusterAuthorizedOperations: 7,\n\t}\n\n\ttestResponse(t, \"DescribeClusterResponse\", resp, nil)\n}\n"
  },
  {
    "path": "describe_configs_request.go",
    "content": "package sarama\n\ntype DescribeConfigsRequest struct {\n\tVersion         int16\n\tResources       []*ConfigResource\n\tIncludeSynonyms bool\n}\n\nfunc (r *DescribeConfigsRequest) setVersion(v int16) {\n\tr.Version = v\n}\n\ntype ConfigResource struct {\n\tType        ConfigResourceType\n\tName        string\n\tConfigNames []string\n}\n\nfunc (r *DescribeConfigsRequest) encode(pe packetEncoder) error {\n\tif err := pe.putArrayLength(len(r.Resources)); err != nil {\n\t\treturn err\n\t}\n\n\tfor _, c := range r.Resources {\n\t\tpe.putInt8(int8(c.Type))\n\t\tif err := pe.putString(c.Name); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif len(c.ConfigNames) == 0 {\n\t\t\tpe.putInt32(-1)\n\t\t\tcontinue\n\t\t}\n\t\tif err := pe.putStringArray(c.ConfigNames); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif r.Version >= 1 {\n\t\tpe.putBool(r.IncludeSynonyms)\n\t}\n\n\treturn nil\n}\n\nfunc (r *DescribeConfigsRequest) decode(pd packetDecoder, version int16) (err error) {\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr.Resources = make([]*ConfigResource, n)\n\n\tfor i := 0; i < n; i++ {\n\t\tr.Resources[i] = &ConfigResource{}\n\t\tt, err := pd.getInt8()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.Resources[i].Type = ConfigResourceType(t)\n\t\tname, err := pd.getString()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.Resources[i].Name = name\n\n\t\tconfLength, err := pd.getArrayLength()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif confLength == -1 {\n\t\t\tcontinue\n\t\t}\n\n\t\tcfnames := make([]string, confLength)\n\t\tfor i := 0; i < confLength; i++ {\n\t\t\ts, err := pd.getString()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tcfnames[i] = s\n\t\t}\n\t\tr.Resources[i].ConfigNames = cfnames\n\t}\n\tr.Version = version\n\tif r.Version >= 1 {\n\t\tb, err := pd.getBool()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.IncludeSynonyms = b\n\t}\n\n\treturn nil\n}\n\nfunc (r *DescribeConfigsRequest) key() int16 {\n\treturn apiKeyDescribeConfigs\n}\n\nfunc (r *DescribeConfigsRequest) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *DescribeConfigsRequest) headerVersion() int16 {\n\treturn 1\n}\n\nfunc (r *DescribeConfigsRequest) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 2\n}\n\nfunc (r *DescribeConfigsRequest) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 2:\n\t\treturn V2_0_0_0\n\tcase 1:\n\t\treturn V1_1_0_0\n\tcase 0:\n\t\treturn V0_11_0_0\n\tdefault:\n\t\treturn V2_0_0_0\n\t}\n}\n"
  },
  {
    "path": "describe_configs_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nvar (\n\temptyDescribeConfigsRequest = []byte{\n\t\t0, 0, 0, 0, // 0 configs\n\t}\n\n\tsingleDescribeConfigsRequest = []byte{\n\t\t0, 0, 0, 1, // 1 config\n\t\t2,                   // a topic\n\t\t0, 3, 'f', 'o', 'o', // topic name: foo\n\t\t0, 0, 0, 1, // 1 config name\n\t\t0, 10, // 10 chars\n\t\t's', 'e', 'g', 'm', 'e', 'n', 't', '.', 'm', 's',\n\t}\n\n\tdoubleDescribeConfigsRequest = []byte{\n\t\t0, 0, 0, 2, // 2 configs\n\t\t2,                   // a topic\n\t\t0, 3, 'f', 'o', 'o', // topic name: foo\n\t\t0, 0, 0, 2, // 2 config name\n\t\t0, 10, // 10 chars\n\t\t's', 'e', 'g', 'm', 'e', 'n', 't', '.', 'm', 's',\n\t\t0, 12, // 12 chars\n\t\t'r', 'e', 't', 'e', 'n', 't', 'i', 'o', 'n', '.', 'm', 's',\n\t\t2,                   // a topic\n\t\t0, 3, 'b', 'a', 'r', // topic name: foo\n\t\t0, 0, 0, 1, // 1 config\n\t\t0, 10, // 10 chars\n\t\t's', 'e', 'g', 'm', 'e', 'n', 't', '.', 'm', 's',\n\t}\n\n\tsingleDescribeConfigsRequestAllConfigs = []byte{\n\t\t0, 0, 0, 1, // 1 config\n\t\t2,                   // a topic\n\t\t0, 3, 'f', 'o', 'o', // topic name: foo\n\t\t255, 255, 255, 255, // all configs\n\t}\n\n\tsingleDescribeConfigsRequestAllConfigsv1 = []byte{\n\t\t0, 0, 0, 1, // 1 config\n\t\t2,                   // a topic\n\t\t0, 3, 'f', 'o', 'o', // topic name: foo\n\t\t255, 255, 255, 255, // no configs\n\t\t1, // synonyms\n\t}\n)\n\nfunc TestDescribeConfigsRequestv0(t *testing.T) {\n\tvar request *DescribeConfigsRequest\n\n\trequest = &DescribeConfigsRequest{\n\t\tVersion:   0,\n\t\tResources: []*ConfigResource{},\n\t}\n\ttestRequest(t, \"no requests\", request, emptyDescribeConfigsRequest)\n\n\tconfigs := []string{\"segment.ms\"}\n\trequest = &DescribeConfigsRequest{\n\t\tVersion: 0,\n\t\tResources: []*ConfigResource{\n\t\t\t{\n\t\t\t\tType:        TopicResource,\n\t\t\t\tName:        \"foo\",\n\t\t\t\tConfigNames: configs,\n\t\t\t},\n\t\t},\n\t}\n\n\ttestRequest(t, \"one config\", request, singleDescribeConfigsRequest)\n\n\trequest = &DescribeConfigsRequest{\n\t\tVersion: 0,\n\t\tResources: []*ConfigResource{\n\t\t\t{\n\t\t\t\tType:        TopicResource,\n\t\t\t\tName:        \"foo\",\n\t\t\t\tConfigNames: []string{\"segment.ms\", \"retention.ms\"},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType:        TopicResource,\n\t\t\t\tName:        \"bar\",\n\t\t\t\tConfigNames: []string{\"segment.ms\"},\n\t\t\t},\n\t\t},\n\t}\n\ttestRequest(t, \"two configs\", request, doubleDescribeConfigsRequest)\n\n\trequest = &DescribeConfigsRequest{\n\t\tVersion: 0,\n\t\tResources: []*ConfigResource{\n\t\t\t{\n\t\t\t\tType: TopicResource,\n\t\t\t\tName: \"foo\",\n\t\t\t},\n\t\t},\n\t}\n\n\ttestRequest(t, \"one topic, all configs\", request, singleDescribeConfigsRequestAllConfigs)\n}\n\nfunc TestDescribeConfigsRequestv1(t *testing.T) {\n\trequest := &DescribeConfigsRequest{\n\t\tVersion: 1,\n\t\tResources: []*ConfigResource{\n\t\t\t{\n\t\t\t\tType: TopicResource,\n\t\t\t\tName: \"foo\",\n\t\t\t},\n\t\t},\n\t\tIncludeSynonyms: true,\n\t}\n\n\ttestRequest(t, \"one topic, all configs\", request, singleDescribeConfigsRequestAllConfigsv1)\n}\n"
  },
  {
    "path": "describe_configs_response.go",
    "content": "package sarama\n\nimport (\n\t\"fmt\"\n\t\"time\"\n)\n\ntype ConfigSource int8\n\nfunc (s ConfigSource) String() string {\n\tswitch s {\n\tcase SourceUnknown:\n\t\treturn \"Unknown\"\n\tcase SourceTopic:\n\t\treturn \"Topic\"\n\tcase SourceDynamicBroker:\n\t\treturn \"DynamicBroker\"\n\tcase SourceDynamicDefaultBroker:\n\t\treturn \"DynamicDefaultBroker\"\n\tcase SourceStaticBroker:\n\t\treturn \"StaticBroker\"\n\tcase SourceDefault:\n\t\treturn \"Default\"\n\t}\n\treturn fmt.Sprintf(\"Source Invalid: %d\", int(s))\n}\n\nconst (\n\tSourceUnknown ConfigSource = iota\n\tSourceTopic\n\tSourceDynamicBroker\n\tSourceDynamicDefaultBroker\n\tSourceStaticBroker\n\tSourceDefault\n)\n\ntype DescribeConfigError struct {\n\tErr    KError\n\tErrMsg string\n}\n\nfunc (c *DescribeConfigError) Error() string {\n\ttext := c.Err.Error()\n\tif c.ErrMsg != \"\" {\n\t\ttext = fmt.Sprintf(\"%s - %s\", text, c.ErrMsg)\n\t}\n\treturn text\n}\n\ntype DescribeConfigsResponse struct {\n\tVersion      int16\n\tThrottleTime time.Duration\n\tResources    []*ResourceResponse\n}\n\nfunc (r *DescribeConfigsResponse) setVersion(v int16) {\n\tr.Version = v\n}\n\ntype ResourceResponse struct {\n\tErrorCode int16\n\tErrorMsg  string\n\tType      ConfigResourceType\n\tName      string\n\tConfigs   []*ConfigEntry\n}\n\ntype ConfigEntry struct {\n\tName      string\n\tValue     string\n\tReadOnly  bool\n\tDefault   bool\n\tSource    ConfigSource\n\tSensitive bool\n\tSynonyms  []*ConfigSynonym\n}\n\ntype ConfigSynonym struct {\n\tConfigName  string\n\tConfigValue string\n\tSource      ConfigSource\n}\n\nfunc (r *DescribeConfigsResponse) encode(pe packetEncoder) (err error) {\n\tpe.putDurationMs(r.ThrottleTime)\n\tif err = pe.putArrayLength(len(r.Resources)); err != nil {\n\t\treturn err\n\t}\n\n\tfor _, c := range r.Resources {\n\t\tif err = c.encode(pe, r.Version); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (r *DescribeConfigsResponse) decode(pd packetDecoder, version int16) (err error) {\n\tr.Version = version\n\tif r.ThrottleTime, err = pd.getDurationMs(); err != nil {\n\t\treturn err\n\t}\n\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr.Resources = make([]*ResourceResponse, n)\n\tfor i := 0; i < n; i++ {\n\t\trr := &ResourceResponse{}\n\t\tif err := rr.decode(pd, version); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.Resources[i] = rr\n\t}\n\n\treturn nil\n}\n\nfunc (r *DescribeConfigsResponse) key() int16 {\n\treturn apiKeyDescribeConfigs\n}\n\nfunc (r *DescribeConfigsResponse) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *DescribeConfigsResponse) headerVersion() int16 {\n\treturn 0\n}\n\nfunc (r *DescribeConfigsResponse) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 2\n}\n\nfunc (r *DescribeConfigsResponse) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 2:\n\t\treturn V2_0_0_0\n\tcase 1:\n\t\treturn V1_1_0_0\n\tcase 0:\n\t\treturn V0_11_0_0\n\tdefault:\n\t\treturn V2_0_0_0\n\t}\n}\n\nfunc (r *DescribeConfigsResponse) throttleTime() time.Duration {\n\treturn r.ThrottleTime\n}\n\nfunc (r *ResourceResponse) encode(pe packetEncoder, version int16) (err error) {\n\tpe.putInt16(r.ErrorCode)\n\n\tif err = pe.putString(r.ErrorMsg); err != nil {\n\t\treturn err\n\t}\n\n\tpe.putInt8(int8(r.Type))\n\n\tif err = pe.putString(r.Name); err != nil {\n\t\treturn err\n\t}\n\n\tif err = pe.putArrayLength(len(r.Configs)); err != nil {\n\t\treturn err\n\t}\n\n\tfor _, c := range r.Configs {\n\t\tif err = c.encode(pe, version); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (r *ResourceResponse) decode(pd packetDecoder, version int16) (err error) {\n\tec, err := pd.getInt16()\n\tif err != nil {\n\t\treturn err\n\t}\n\tr.ErrorCode = ec\n\n\tem, err := pd.getString()\n\tif err != nil {\n\t\treturn err\n\t}\n\tr.ErrorMsg = em\n\n\tt, err := pd.getInt8()\n\tif err != nil {\n\t\treturn err\n\t}\n\tr.Type = ConfigResourceType(t)\n\n\tname, err := pd.getString()\n\tif err != nil {\n\t\treturn err\n\t}\n\tr.Name = name\n\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr.Configs = make([]*ConfigEntry, n)\n\tfor i := 0; i < n; i++ {\n\t\tc := &ConfigEntry{}\n\t\tif err := c.decode(pd, version); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.Configs[i] = c\n\t}\n\treturn nil\n}\n\nfunc (r *ConfigEntry) encode(pe packetEncoder, version int16) (err error) {\n\tif err = pe.putString(r.Name); err != nil {\n\t\treturn err\n\t}\n\n\tif err = pe.putString(r.Value); err != nil {\n\t\treturn err\n\t}\n\n\tpe.putBool(r.ReadOnly)\n\n\tif version <= 0 {\n\t\tpe.putBool(r.Default)\n\t\tpe.putBool(r.Sensitive)\n\t} else {\n\t\tpe.putInt8(int8(r.Source))\n\t\tpe.putBool(r.Sensitive)\n\n\t\tif err := pe.putArrayLength(len(r.Synonyms)); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor _, c := range r.Synonyms {\n\t\t\tif err = c.encode(pe, version); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// https://cwiki.apache.org/confluence/display/KAFKA/KIP-226+-+Dynamic+Broker+Configuration\nfunc (r *ConfigEntry) decode(pd packetDecoder, version int16) (err error) {\n\tif version == 0 {\n\t\tr.Source = SourceUnknown\n\t}\n\tname, err := pd.getString()\n\tif err != nil {\n\t\treturn err\n\t}\n\tr.Name = name\n\n\tvalue, err := pd.getString()\n\tif err != nil {\n\t\treturn err\n\t}\n\tr.Value = value\n\n\tread, err := pd.getBool()\n\tif err != nil {\n\t\treturn err\n\t}\n\tr.ReadOnly = read\n\n\tif version == 0 {\n\t\tdefaultB, err := pd.getBool()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.Default = defaultB\n\t\tif defaultB {\n\t\t\tr.Source = SourceDefault\n\t\t}\n\t} else {\n\t\tsource, err := pd.getInt8()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.Source = ConfigSource(source)\n\t\tr.Default = r.Source == SourceDefault\n\t}\n\n\tsensitive, err := pd.getBool()\n\tif err != nil {\n\t\treturn err\n\t}\n\tr.Sensitive = sensitive\n\n\tif version > 0 {\n\t\tn, err := pd.getArrayLength()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.Synonyms = make([]*ConfigSynonym, n)\n\n\t\tfor i := 0; i < n; i++ {\n\t\t\ts := &ConfigSynonym{}\n\t\t\tif err := s.decode(pd, version); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tr.Synonyms[i] = s\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (c *ConfigSynonym) encode(pe packetEncoder, version int16) (err error) {\n\terr = pe.putString(c.ConfigName)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = pe.putString(c.ConfigValue)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tpe.putInt8(int8(c.Source))\n\n\treturn nil\n}\n\nfunc (c *ConfigSynonym) decode(pd packetDecoder, version int16) error {\n\tname, err := pd.getString()\n\tif err != nil {\n\t\treturn err\n\t}\n\tc.ConfigName = name\n\n\tvalue, err := pd.getString()\n\tif err != nil {\n\t\treturn err\n\t}\n\tc.ConfigValue = value\n\n\tsource, err := pd.getInt8()\n\tif err != nil {\n\t\treturn err\n\t}\n\tc.Source = ConfigSource(source)\n\treturn nil\n}\n"
  },
  {
    "path": "describe_configs_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"testing\"\n)\n\nvar (\n\tdescribeConfigsResponseEmpty = []byte{\n\t\t0, 0, 0, 0, // throttle\n\t\t0, 0, 0, 0, // no configs\n\t}\n\n\tdescribeConfigsResponsePopulatedv0 = []byte{\n\t\t0, 0, 0, 0, // throttle\n\t\t0, 0, 0, 1, // response\n\t\t0, 0, // errorcode\n\t\t0, 0, // string\n\t\t2, // topic\n\t\t0, 3, 'f', 'o', 'o',\n\t\t0, 0, 0, 1, // configs\n\t\t0, 10, 's', 'e', 'g', 'm', 'e', 'n', 't', '.', 'm', 's',\n\t\t0, 4, '1', '0', '0', '0',\n\t\t0, // ReadOnly\n\t\t0, // Default\n\t\t0, // Sensitive\n\t}\n\n\tdescribeConfigsResponseWithDefaultv0 = []byte{\n\t\t0, 0, 0, 0, // throttle\n\t\t0, 0, 0, 1, // response\n\t\t0, 0, // errorcode\n\t\t0, 0, // string\n\t\t2, // topic\n\t\t0, 3, 'f', 'o', 'o',\n\t\t0, 0, 0, 1, // configs\n\t\t0, 10, 's', 'e', 'g', 'm', 'e', 'n', 't', '.', 'm', 's',\n\t\t0, 4, '1', '0', '0', '0',\n\t\t0, // ReadOnly\n\t\t1, // Default\n\t\t0, // Sensitive\n\t}\n\n\tdescribeConfigsResponsePopulatedv1 = []byte{\n\t\t0, 0, 0, 0, // throttle\n\t\t0, 0, 0, 1, // response\n\t\t0, 0, // errorcode\n\t\t0, 0, // string\n\t\t2, // topic\n\t\t0, 3, 'f', 'o', 'o',\n\t\t0, 0, 0, 1, // configs\n\t\t0, 10, 's', 'e', 'g', 'm', 'e', 'n', 't', '.', 'm', 's',\n\t\t0, 4, '1', '0', '0', '0',\n\t\t0,          // ReadOnly\n\t\t4,          // Source\n\t\t0,          // Sensitive\n\t\t0, 0, 0, 0, // No Synonym\n\t}\n\n\tdescribeConfigsResponseWithSynonymv1 = []byte{\n\t\t0, 0, 0, 0, // throttle\n\t\t0, 0, 0, 1, // response\n\t\t0, 0, // errorcode\n\t\t0, 0, // string\n\t\t2, // topic\n\t\t0, 3, 'f', 'o', 'o',\n\t\t0, 0, 0, 1, // configs\n\t\t0, 10, 's', 'e', 'g', 'm', 'e', 'n', 't', '.', 'm', 's',\n\t\t0, 4, '1', '0', '0', '0',\n\t\t0,          // ReadOnly\n\t\t4,          // Source\n\t\t0,          // Sensitive\n\t\t0, 0, 0, 1, // 1 Synonym\n\t\t0, 14, 'l', 'o', 'g', '.', 's', 'e', 'g', 'm', 'e', 'n', 't', '.', 'm', 's',\n\t\t0, 4, '1', '0', '0', '0',\n\t\t4, // Source\n\t}\n\n\tdescribeConfigsResponseWithDefaultv1 = []byte{\n\t\t0, 0, 0, 0, // throttle\n\t\t0, 0, 0, 1, // response\n\t\t0, 0, // errorcode\n\t\t0, 0, // string\n\t\t2, // topic\n\t\t0, 3, 'f', 'o', 'o',\n\t\t0, 0, 0, 1, // configs\n\t\t0, 10, 's', 'e', 'g', 'm', 'e', 'n', 't', '.', 'm', 's',\n\t\t0, 4, '1', '0', '0', '0',\n\t\t0,          // ReadOnly\n\t\t5,          // Source\n\t\t0,          // Sensitive\n\t\t0, 0, 0, 0, // No Synonym\n\t}\n)\n\nfunc TestDescribeConfigsResponsev0(t *testing.T) {\n\tvar response *DescribeConfigsResponse\n\n\tresponse = &DescribeConfigsResponse{\n\t\tResources: []*ResourceResponse{},\n\t}\n\ttestVersionDecodable(t, \"empty\", response, describeConfigsResponseEmpty, 0)\n\tif len(response.Resources) != 0 {\n\t\tt.Error(\"Expected no groups\")\n\t}\n\n\tresponse = &DescribeConfigsResponse{\n\t\tVersion: 0, Resources: []*ResourceResponse{\n\t\t\t{\n\t\t\t\tErrorCode: 0,\n\t\t\t\tErrorMsg:  \"\",\n\t\t\t\tType:      TopicResource,\n\t\t\t\tName:      \"foo\",\n\t\t\t\tConfigs: []*ConfigEntry{\n\t\t\t\t\t{\n\t\t\t\t\t\tName:      \"segment.ms\",\n\t\t\t\t\t\tValue:     \"1000\",\n\t\t\t\t\t\tReadOnly:  false,\n\t\t\t\t\t\tDefault:   false,\n\t\t\t\t\t\tSensitive: false,\n\t\t\t\t\t\tSource:    SourceUnknown,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\ttestResponse(t, \"response with error\", response, describeConfigsResponsePopulatedv0)\n}\n\nfunc TestDescribeConfigsResponseWithDefaultv0(t *testing.T) {\n\tvar response *DescribeConfigsResponse\n\n\tresponse = &DescribeConfigsResponse{\n\t\tResources: []*ResourceResponse{},\n\t}\n\ttestVersionDecodable(t, \"empty\", response, describeConfigsResponseEmpty, 0)\n\tif len(response.Resources) != 0 {\n\t\tt.Error(\"Expected no groups\")\n\t}\n\n\tresponse = &DescribeConfigsResponse{\n\t\tVersion: 0, Resources: []*ResourceResponse{\n\t\t\t{\n\t\t\t\tErrorCode: 0,\n\t\t\t\tErrorMsg:  \"\",\n\t\t\t\tType:      TopicResource,\n\t\t\t\tName:      \"foo\",\n\t\t\t\tConfigs: []*ConfigEntry{\n\t\t\t\t\t{\n\t\t\t\t\t\tName:      \"segment.ms\",\n\t\t\t\t\t\tValue:     \"1000\",\n\t\t\t\t\t\tReadOnly:  false,\n\t\t\t\t\t\tDefault:   true,\n\t\t\t\t\t\tSensitive: false,\n\t\t\t\t\t\tSource:    SourceDefault,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\ttestResponse(t, \"response with default\", response, describeConfigsResponseWithDefaultv0)\n}\n\nfunc TestDescribeConfigsResponsev1(t *testing.T) {\n\tvar response *DescribeConfigsResponse\n\n\tresponse = &DescribeConfigsResponse{\n\t\tResources: []*ResourceResponse{},\n\t}\n\ttestVersionDecodable(t, \"empty\", response, describeConfigsResponseEmpty, 0)\n\tif len(response.Resources) != 0 {\n\t\tt.Error(\"Expected no groups\")\n\t}\n\n\tresponse = &DescribeConfigsResponse{\n\t\tVersion: 1,\n\t\tResources: []*ResourceResponse{\n\t\t\t{\n\t\t\t\tErrorCode: 0,\n\t\t\t\tErrorMsg:  \"\",\n\t\t\t\tType:      TopicResource,\n\t\t\t\tName:      \"foo\",\n\t\t\t\tConfigs: []*ConfigEntry{\n\t\t\t\t\t{\n\t\t\t\t\t\tName:      \"segment.ms\",\n\t\t\t\t\t\tValue:     \"1000\",\n\t\t\t\t\t\tReadOnly:  false,\n\t\t\t\t\t\tSource:    SourceStaticBroker,\n\t\t\t\t\t\tDefault:   false,\n\t\t\t\t\t\tSensitive: false,\n\t\t\t\t\t\tSynonyms:  []*ConfigSynonym{},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\ttestResponse(t, \"response with error\", response, describeConfigsResponsePopulatedv1)\n}\n\nfunc TestDescribeConfigsResponseWithSynonym(t *testing.T) {\n\tvar response *DescribeConfigsResponse\n\n\tresponse = &DescribeConfigsResponse{\n\t\tResources: []*ResourceResponse{},\n\t}\n\ttestVersionDecodable(t, \"empty\", response, describeConfigsResponseEmpty, 0)\n\tif len(response.Resources) != 0 {\n\t\tt.Error(\"Expected no groups\")\n\t}\n\n\tresponse = &DescribeConfigsResponse{\n\t\tVersion: 1,\n\t\tResources: []*ResourceResponse{\n\t\t\t{\n\t\t\t\tErrorCode: 0,\n\t\t\t\tErrorMsg:  \"\",\n\t\t\t\tType:      TopicResource,\n\t\t\t\tName:      \"foo\",\n\t\t\t\tConfigs: []*ConfigEntry{\n\t\t\t\t\t{\n\t\t\t\t\t\tName:      \"segment.ms\",\n\t\t\t\t\t\tValue:     \"1000\",\n\t\t\t\t\t\tReadOnly:  false,\n\t\t\t\t\t\tSource:    SourceStaticBroker,\n\t\t\t\t\t\tDefault:   false,\n\t\t\t\t\t\tSensitive: false,\n\t\t\t\t\t\tSynonyms: []*ConfigSynonym{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tConfigName:  \"log.segment.ms\",\n\t\t\t\t\t\t\t\tConfigValue: \"1000\",\n\t\t\t\t\t\t\t\tSource:      SourceStaticBroker,\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},\n\t}\n\ttestResponse(t, \"response with error\", response, describeConfigsResponseWithSynonymv1)\n}\n\nfunc TestDescribeConfigsResponseWithDefaultv1(t *testing.T) {\n\tvar response *DescribeConfigsResponse\n\n\tresponse = &DescribeConfigsResponse{\n\t\tResources: []*ResourceResponse{},\n\t}\n\ttestVersionDecodable(t, \"empty\", response, describeConfigsResponseEmpty, 0)\n\tif len(response.Resources) != 0 {\n\t\tt.Error(\"Expected no groups\")\n\t}\n\n\tresponse = &DescribeConfigsResponse{\n\t\tVersion: 1,\n\t\tResources: []*ResourceResponse{\n\t\t\t{\n\t\t\t\tErrorCode: 0,\n\t\t\t\tErrorMsg:  \"\",\n\t\t\t\tType:      TopicResource,\n\t\t\t\tName:      \"foo\",\n\t\t\t\tConfigs: []*ConfigEntry{\n\t\t\t\t\t{\n\t\t\t\t\t\tName:      \"segment.ms\",\n\t\t\t\t\t\tValue:     \"1000\",\n\t\t\t\t\t\tReadOnly:  false,\n\t\t\t\t\t\tSource:    SourceDefault,\n\t\t\t\t\t\tDefault:   true,\n\t\t\t\t\t\tSensitive: false,\n\t\t\t\t\t\tSynonyms:  []*ConfigSynonym{},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\ttestResponse(t, \"response with error\", response, describeConfigsResponseWithDefaultv1)\n}\n"
  },
  {
    "path": "describe_groups_request.go",
    "content": "package sarama\n\ntype DescribeGroupsRequest struct {\n\tVersion                     int16\n\tGroups                      []string\n\tIncludeAuthorizedOperations bool\n}\n\nfunc (r *DescribeGroupsRequest) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *DescribeGroupsRequest) encode(pe packetEncoder) error {\n\tif err := pe.putStringArray(r.Groups); err != nil {\n\t\treturn err\n\t}\n\tif r.Version >= 3 {\n\t\tpe.putBool(r.IncludeAuthorizedOperations)\n\t}\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (r *DescribeGroupsRequest) decode(pd packetDecoder, version int16) (err error) {\n\tr.Version = version\n\tr.Groups, err = pd.getStringArray()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif r.Version >= 3 {\n\t\tif r.IncludeAuthorizedOperations, err = pd.getBool(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (r *DescribeGroupsRequest) key() int16 {\n\treturn apiKeyDescribeGroups\n}\n\nfunc (r *DescribeGroupsRequest) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *DescribeGroupsRequest) headerVersion() int16 {\n\tif r.Version >= 5 {\n\t\treturn 2\n\t}\n\treturn 1\n}\n\nfunc (r *DescribeGroupsRequest) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 5\n}\n\nfunc (r *DescribeGroupsRequest) isFlexible() bool {\n\treturn r.isFlexibleVersion(r.Version)\n}\n\nfunc (r *DescribeGroupsRequest) isFlexibleVersion(version int16) bool {\n\treturn version >= 5\n}\n\nfunc (r *DescribeGroupsRequest) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 5:\n\t\treturn V2_4_0_0\n\tcase 4:\n\t\treturn V2_4_0_0\n\tcase 3:\n\t\treturn V2_3_0_0\n\tcase 2:\n\t\treturn V2_0_0_0\n\tcase 1:\n\t\treturn V0_11_0_0\n\tcase 0:\n\t\treturn V0_9_0_0\n\tdefault:\n\t\treturn V2_4_0_0\n\t}\n}\n\nfunc (r *DescribeGroupsRequest) AddGroup(group string) {\n\tr.Groups = append(r.Groups, group)\n}\n"
  },
  {
    "path": "describe_groups_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nvar (\n\temptyDescribeGroupsRequest = []byte{0, 0, 0, 0}\n\n\tsingleDescribeGroupsRequestV0 = []byte{\n\t\t0, 0, 0, 1, // 1 group\n\t\t0, 3, 'f', 'o', 'o', // group name: foo\n\t}\n\n\tdoubleDescribeGroupsRequestV0 = []byte{\n\t\t0, 0, 0, 2, // 2 groups\n\t\t0, 3, 'f', 'o', 'o', // group name: foo\n\t\t0, 3, 'b', 'a', 'r', // group name: bar\n\t}\n)\n\nfunc TestDescribeGroupsRequestV0(t *testing.T) {\n\tvar request *DescribeGroupsRequest\n\n\trequest = new(DescribeGroupsRequest)\n\ttestRequest(t, \"no groups\", request, emptyDescribeGroupsRequest)\n\n\trequest = new(DescribeGroupsRequest)\n\trequest.AddGroup(\"foo\")\n\ttestRequest(t, \"one group\", request, singleDescribeGroupsRequestV0)\n\n\trequest = new(DescribeGroupsRequest)\n\trequest.AddGroup(\"foo\")\n\trequest.AddGroup(\"bar\")\n\ttestRequest(t, \"two groups\", request, doubleDescribeGroupsRequestV0)\n}\n\nvar (\n\temptyDescribeGroupsRequestV3 = []byte{0, 0, 0, 0, 0}\n\n\tsingleDescribeGroupsRequestV3 = []byte{\n\t\t0, 0, 0, 1, // 1 group\n\t\t0, 3, 'f', 'o', 'o', // group name: foo\n\t\t0,\n\t}\n\n\tdoubleDescribeGroupsRequestV3 = []byte{\n\t\t0, 0, 0, 2, // 2 groups\n\t\t0, 3, 'f', 'o', 'o', // group name: foo\n\t\t0, 3, 'b', 'a', 'r', // group name: bar\n\t\t1,\n\t}\n)\n\nfunc TestDescribeGroupsRequestV3(t *testing.T) {\n\tvar request *DescribeGroupsRequest\n\n\trequest = new(DescribeGroupsRequest)\n\trequest.Version = 3\n\ttestRequest(t, \"no groups\", request, emptyDescribeGroupsRequestV3)\n\n\trequest = new(DescribeGroupsRequest)\n\trequest.Version = 3\n\trequest.AddGroup(\"foo\")\n\ttestRequest(t, \"one group\", request, singleDescribeGroupsRequestV3)\n\n\trequest = new(DescribeGroupsRequest)\n\trequest.Version = 3\n\trequest.AddGroup(\"foo\")\n\trequest.AddGroup(\"bar\")\n\trequest.IncludeAuthorizedOperations = true\n\ttestRequest(t, \"two groups\", request, doubleDescribeGroupsRequestV3)\n}\n\nvar (\n\temptyDescribeGroupsRequestV5 = []byte{\n\t\t1, // 1+0 no groups\n\t\t0, // do not include authorized operations\n\t\t0, // empty tagged fields\n\t}\n\n\tsingleDescribeGroupsRequestV5 = []byte{\n\t\t2,                // 1+1 group\n\t\t4, 'f', 'o', 'o', // group name: foo\n\t\t0, // do not include authorized operations\n\t\t0, // empty tagged fields\n\t}\n\n\tdoubleDescribeGroupsRequestV5 = []byte{\n\t\t3,                // 1+2 groups\n\t\t4, 'f', 'o', 'o', // group name: foo\n\t\t4, 'b', 'a', 'r', // group name: bar\n\t\t1, // do include authorized operations\n\t\t0, // empty tagged fields\n\t}\n)\n\nfunc TestDescribeGroupsRequestV5(t *testing.T) {\n\tvar request *DescribeGroupsRequest\n\n\trequest = &DescribeGroupsRequest{Version: 5}\n\ttestRequest(t, \"no groups\", request, emptyDescribeGroupsRequestV5)\n\n\trequest = &DescribeGroupsRequest{Version: 5}\n\trequest.AddGroup(\"foo\")\n\ttestRequest(t, \"one group\", request, singleDescribeGroupsRequestV5)\n\n\trequest = &DescribeGroupsRequest{Version: 5}\n\trequest.AddGroup(\"foo\")\n\trequest.AddGroup(\"bar\")\n\trequest.IncludeAuthorizedOperations = true\n\ttestRequest(t, \"two groups\", request, doubleDescribeGroupsRequestV5)\n}\n"
  },
  {
    "path": "describe_groups_response.go",
    "content": "package sarama\n\nimport \"time\"\n\ntype DescribeGroupsResponse struct {\n\t// Version defines the protocol version to use for encode and decode\n\tVersion int16\n\t// ThrottleTimeMs contains the duration in milliseconds for which the\n\t// request was throttled due to a quota violation, or zero if the request\n\t// did not violate any quota.\n\tThrottleTimeMs int32\n\t// Groups contains each described group.\n\tGroups []*GroupDescription\n}\n\nfunc (r *DescribeGroupsResponse) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *DescribeGroupsResponse) encode(pe packetEncoder) (err error) {\n\tif r.Version >= 1 {\n\t\tpe.putInt32(r.ThrottleTimeMs)\n\t}\n\tif err := pe.putArrayLength(len(r.Groups)); err != nil {\n\t\treturn err\n\t}\n\n\tfor _, block := range r.Groups {\n\t\tif err := block.encode(pe, r.Version); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (r *DescribeGroupsResponse) decode(pd packetDecoder, version int16) (err error) {\n\tr.Version = version\n\tif r.Version >= 1 {\n\t\tif r.ThrottleTimeMs, err = pd.getInt32(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif numGroups, err := pd.getArrayLength(); err != nil {\n\t\treturn err\n\t} else if numGroups > 0 {\n\t\tr.Groups = make([]*GroupDescription, numGroups)\n\t\tfor i := 0; i < numGroups; i++ {\n\t\t\tblock := &GroupDescription{}\n\t\t\tif err := block.decode(pd, r.Version); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tr.Groups[i] = block\n\t\t}\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (r *DescribeGroupsResponse) key() int16 {\n\treturn apiKeyDescribeGroups\n}\n\nfunc (r *DescribeGroupsResponse) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *DescribeGroupsResponse) headerVersion() int16 {\n\tif r.Version >= 5 {\n\t\treturn 1\n\t}\n\treturn 0\n}\n\nfunc (r *DescribeGroupsResponse) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 5\n}\n\nfunc (r *DescribeGroupsResponse) isFlexible() bool {\n\treturn r.isFlexibleVersion(r.Version)\n}\n\nfunc (r *DescribeGroupsResponse) isFlexibleVersion(version int16) bool {\n\treturn version >= 5\n}\n\nfunc (r *DescribeGroupsResponse) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 5:\n\t\treturn V2_4_0_0\n\tcase 4:\n\t\treturn V2_4_0_0\n\tcase 3:\n\t\treturn V2_3_0_0\n\tcase 2:\n\t\treturn V2_0_0_0\n\tcase 1:\n\t\treturn V0_11_0_0\n\tcase 0:\n\t\treturn V0_9_0_0\n\tdefault:\n\t\treturn V2_4_0_0\n\t}\n}\n\nfunc (r *DescribeGroupsResponse) throttleTime() time.Duration {\n\treturn time.Duration(r.ThrottleTimeMs) * time.Millisecond\n}\n\n// GroupDescription contains each described group.\ntype GroupDescription struct {\n\t// Version defines the protocol version to use for encode and decode\n\tVersion int16\n\t// Err contains the describe error as the KError type.\n\tErr KError\n\t// ErrorCode contains the describe error, or 0 if there was no error.\n\tErrorCode int16\n\t// GroupId contains the group ID string.\n\tGroupId string\n\t// State contains the group state string, or the empty string.\n\tState string\n\t// ProtocolType contains the group protocol type, or the empty string.\n\tProtocolType string\n\t// Protocol contains the group protocol data, or the empty string.\n\tProtocol string\n\t// Members contains the group members.\n\tMembers map[string]*GroupMemberDescription\n\t// AuthorizedOperations contains a 32-bit bitfield to represent authorized\n\t// operations for this group.\n\tAuthorizedOperations int32\n}\n\nfunc (gd *GroupDescription) encode(pe packetEncoder, version int16) (err error) {\n\tgd.Version = version\n\tpe.putInt16(gd.ErrorCode)\n\n\tif err := pe.putString(gd.GroupId); err != nil {\n\t\treturn err\n\t}\n\tif err := pe.putString(gd.State); err != nil {\n\t\treturn err\n\t}\n\tif err := pe.putString(gd.ProtocolType); err != nil {\n\t\treturn err\n\t}\n\tif err := pe.putString(gd.Protocol); err != nil {\n\t\treturn err\n\t}\n\n\tif err := pe.putArrayLength(len(gd.Members)); err != nil {\n\t\treturn err\n\t}\n\n\tfor _, block := range gd.Members {\n\t\tif err := block.encode(pe, gd.Version); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif gd.Version >= 3 {\n\t\tpe.putInt32(gd.AuthorizedOperations)\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (gd *GroupDescription) decode(pd packetDecoder, version int16) (err error) {\n\tgd.Version = version\n\tif gd.ErrorCode, err = pd.getInt16(); err != nil {\n\t\treturn err\n\t}\n\n\tgd.Err = KError(gd.ErrorCode)\n\n\tif gd.GroupId, err = pd.getString(); err != nil {\n\t\treturn err\n\t}\n\tif gd.State, err = pd.getString(); err != nil {\n\t\treturn err\n\t}\n\tif gd.ProtocolType, err = pd.getString(); err != nil {\n\t\treturn err\n\t}\n\tif gd.Protocol, err = pd.getString(); err != nil {\n\t\treturn err\n\t}\n\n\tif numMembers, err := pd.getArrayLength(); err != nil {\n\t\treturn err\n\t} else if numMembers > 0 {\n\t\tgd.Members = make(map[string]*GroupMemberDescription, numMembers)\n\t\tfor i := 0; i < numMembers; i++ {\n\t\t\tblock := &GroupMemberDescription{}\n\t\t\tif err := block.decode(pd, gd.Version); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tgd.Members[block.MemberId] = block\n\t\t}\n\t}\n\n\tif gd.Version >= 3 {\n\t\tif gd.AuthorizedOperations, err = pd.getInt32(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\n// GroupMemberDescription contains the group members.\ntype GroupMemberDescription struct {\n\t// Version defines the protocol version to use for encode and decode\n\tVersion int16\n\t// MemberId contains the member ID assigned by the group coordinator.\n\tMemberId string\n\t// GroupInstanceId contains the unique identifier of the consumer instance\n\t// provided by end user.\n\tGroupInstanceId *string\n\t// ClientId contains the client ID used in the member's latest join group\n\t// request.\n\tClientId string\n\t// ClientHost contains the client host.\n\tClientHost string\n\t// MemberMetadata contains the metadata corresponding to the current group\n\t// protocol in use.\n\tMemberMetadata []byte\n\t// MemberAssignment contains the current assignment provided by the group\n\t// leader.\n\tMemberAssignment []byte\n}\n\nfunc (gmd *GroupMemberDescription) encode(pe packetEncoder, version int16) (err error) {\n\tgmd.Version = version\n\tif err := pe.putString(gmd.MemberId); err != nil {\n\t\treturn err\n\t}\n\tif gmd.Version >= 4 {\n\t\tif err := pe.putNullableString(gmd.GroupInstanceId); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif err := pe.putString(gmd.ClientId); err != nil {\n\t\treturn err\n\t}\n\tif err := pe.putString(gmd.ClientHost); err != nil {\n\t\treturn err\n\t}\n\tif err := pe.putBytes(gmd.MemberMetadata); err != nil {\n\t\treturn err\n\t}\n\tif err := pe.putBytes(gmd.MemberAssignment); err != nil {\n\t\treturn err\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (gmd *GroupMemberDescription) decode(pd packetDecoder, version int16) (err error) {\n\tgmd.Version = version\n\tif gmd.MemberId, err = pd.getString(); err != nil {\n\t\treturn err\n\t}\n\tif gmd.Version >= 4 {\n\t\tif gmd.GroupInstanceId, err = pd.getNullableString(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif gmd.ClientId, err = pd.getString(); err != nil {\n\t\treturn err\n\t}\n\tif gmd.ClientHost, err = pd.getString(); err != nil {\n\t\treturn err\n\t}\n\tif gmd.MemberMetadata, err = pd.getBytes(); err != nil {\n\t\treturn err\n\t}\n\tif gmd.MemberAssignment, err = pd.getBytes(); err != nil {\n\t\treturn err\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (gmd *GroupMemberDescription) GetMemberAssignment() (*ConsumerGroupMemberAssignment, error) {\n\tif len(gmd.MemberAssignment) == 0 {\n\t\treturn nil, nil\n\t}\n\tassignment := new(ConsumerGroupMemberAssignment)\n\terr := decode(gmd.MemberAssignment, assignment, nil)\n\treturn assignment, err\n}\n\nfunc (gmd *GroupMemberDescription) GetMemberMetadata() (*ConsumerGroupMemberMetadata, error) {\n\tif len(gmd.MemberMetadata) == 0 {\n\t\treturn nil, nil\n\t}\n\tmetadata := new(ConsumerGroupMemberMetadata)\n\terr := decode(gmd.MemberMetadata, metadata, nil)\n\treturn metadata, err\n}\n"
  },
  {
    "path": "describe_groups_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nvar (\n\tdescribeGroupsResponseEmptyV0 = []byte{\n\t\t0, 0, 0, 0, // no groups\n\t}\n\n\tdescribeGroupsResponsePopulatedV0 = []byte{\n\t\t0, 0, 0, 2, // 2 groups\n\n\t\t0, 0, // no error\n\t\t0, 3, 'f', 'o', 'o', // Group ID\n\t\t0, 3, 'b', 'a', 'r', // State\n\t\t0, 8, 'c', 'o', 'n', 's', 'u', 'm', 'e', 'r', // ConsumerProtocol type\n\t\t0, 3, 'b', 'a', 'z', // Protocol name\n\t\t0, 0, 0, 1, // 1 member\n\t\t0, 2, 'i', 'd', // Member ID\n\t\t0, 6, 's', 'a', 'r', 'a', 'm', 'a', // Client ID\n\t\t0, 9, 'l', 'o', 'c', 'a', 'l', 'h', 'o', 's', 't', // Client Host\n\t\t0, 0, 0, 3, 0x01, 0x02, 0x03, // MemberMetadata\n\t\t0, 0, 0, 3, 0x04, 0x05, 0x06, // MemberAssignment\n\n\t\t0, 30, // ErrGroupAuthorizationFailed\n\t\t0, 0,\n\t\t0, 0,\n\t\t0, 0,\n\t\t0, 0,\n\t\t0, 0, 0, 0,\n\t}\n)\n\nfunc TestDescribeGroupsResponseV0(t *testing.T) {\n\tvar response *DescribeGroupsResponse\n\n\tresponse = new(DescribeGroupsResponse)\n\ttestVersionDecodable(t, \"empty\", response, describeGroupsResponseEmptyV0, 0)\n\tif len(response.Groups) != 0 {\n\t\tt.Error(\"Expected no groups\")\n\t}\n\n\tresponse = new(DescribeGroupsResponse)\n\ttestVersionDecodable(t, \"populated\", response, describeGroupsResponsePopulatedV0, 0)\n\tif len(response.Groups) != 2 {\n\t\tt.Error(\"Expected two groups\")\n\t}\n\n\tgroup0 := response.Groups[0]\n\tif !errors.Is(group0.Err, ErrNoError) {\n\t\tt.Error(\"Unxpected groups[0].Err, found\", group0.Err)\n\t}\n\tif group0.GroupId != \"foo\" {\n\t\tt.Error(\"Unxpected groups[0].GroupId, found\", group0.GroupId)\n\t}\n\tif group0.State != \"bar\" {\n\t\tt.Error(\"Unxpected groups[0].State, found\", group0.State)\n\t}\n\tif group0.ProtocolType != \"consumer\" {\n\t\tt.Error(\"Unxpected groups[0].ProtocolType, found\", group0.ProtocolType)\n\t}\n\tif group0.Protocol != \"baz\" {\n\t\tt.Error(\"Unxpected groups[0].Protocol, found\", group0.Protocol)\n\t}\n\tif len(group0.Members) != 1 {\n\t\tt.Error(\"Unxpected groups[0].Members, found\", group0.Members)\n\t}\n\tif group0.Members[\"id\"].ClientId != \"sarama\" {\n\t\tt.Error(\"Unxpected groups[0].Members[id].ClientId, found\", group0.Members[\"id\"].ClientId)\n\t}\n\tif group0.Members[\"id\"].ClientHost != \"localhost\" {\n\t\tt.Error(\"Unxpected groups[0].Members[id].ClientHost, found\", group0.Members[\"id\"].ClientHost)\n\t}\n\tif !reflect.DeepEqual(group0.Members[\"id\"].MemberMetadata, []byte{0x01, 0x02, 0x03}) {\n\t\tt.Error(\"Unxpected groups[0].Members[id].MemberMetadata, found\", group0.Members[\"id\"].MemberMetadata)\n\t}\n\tif !reflect.DeepEqual(group0.Members[\"id\"].MemberAssignment, []byte{0x04, 0x05, 0x06}) {\n\t\tt.Error(\"Unxpected groups[0].Members[id].MemberAssignment, found\", group0.Members[\"id\"].MemberAssignment)\n\t}\n\n\tgroup1 := response.Groups[1]\n\tif !errors.Is(group1.Err, ErrGroupAuthorizationFailed) {\n\t\tt.Error(\"Unxpected groups[1].Err, found\", group0.Err)\n\t}\n\tif len(group1.Members) != 0 {\n\t\tt.Error(\"Unxpected groups[1].Members, found\", group0.Members)\n\t}\n}\n\nvar (\n\tdescribeGroupsResponseEmptyV3 = []byte{\n\t\t0, 0, 0, 0, // throttle time 0\n\t\t0, 0, 0, 0, // no groups\n\t}\n\n\tdescribeGroupsResponsePopulatedV3 = []byte{\n\t\t0, 0, 0, 0, // throttle time 0\n\t\t0, 0, 0, 2, // 2 groups\n\n\t\t0, 0, // no error\n\t\t0, 3, 'f', 'o', 'o', // Group ID\n\t\t0, 3, 'b', 'a', 'r', // State\n\t\t0, 8, 'c', 'o', 'n', 's', 'u', 'm', 'e', 'r', // ConsumerProtocol type\n\t\t0, 3, 'b', 'a', 'z', // Protocol name\n\t\t0, 0, 0, 1, // 1 member\n\t\t0, 2, 'i', 'd', // Member ID\n\t\t0, 6, 's', 'a', 'r', 'a', 'm', 'a', // Client ID\n\t\t0, 9, 'l', 'o', 'c', 'a', 'l', 'h', 'o', 's', 't', // Client Host\n\t\t0, 0, 0, 3, 0x01, 0x02, 0x03, // MemberMetadata\n\t\t0, 0, 0, 3, 0x04, 0x05, 0x06, // MemberAssignment\n\t\t0, 0, 0, 0, // authorizedOperations 0\n\n\t\t0, 30, // ErrGroupAuthorizationFailed\n\t\t0, 0,\n\t\t0, 0,\n\t\t0, 0,\n\t\t0, 0,\n\t\t0, 0, 0, 0,\n\t\t0, 0, 0, 0, // authorizedOperations 0\n\n\t}\n\n\tdescribeGroupsResponseEmptyV4 = []byte{\n\t\t0, 0, 0, 0, // throttle time 0\n\t\t0, 0, 0, 0, // no groups\n\t}\n\n\tdescribeGroupsResponsePopulatedV4 = []byte{\n\t\t0, 0, 0, 0, // throttle time 0\n\t\t0, 0, 0, 2, // 2 groups\n\n\t\t0, 0, // no error\n\t\t0, 3, 'f', 'o', 'o', // Group ID\n\t\t0, 3, 'b', 'a', 'r', // State\n\t\t0, 8, 'c', 'o', 'n', 's', 'u', 'm', 'e', 'r', // ConsumerProtocol type\n\t\t0, 3, 'b', 'a', 'z', // Protocol name\n\t\t0, 0, 0, 1, // 1 member\n\t\t0, 2, 'i', 'd', // Member ID\n\t\t0, 3, 'g', 'i', 'd', // Group Instance ID\n\t\t0, 6, 's', 'a', 'r', 'a', 'm', 'a', // Client ID\n\t\t0, 9, 'l', 'o', 'c', 'a', 'l', 'h', 'o', 's', 't', // Client Host\n\t\t0, 0, 0, 3, 0x01, 0x02, 0x03, // MemberMetadata\n\t\t0, 0, 0, 3, 0x04, 0x05, 0x06, // MemberAssignment\n\t\t0, 0, 0, 0, // authorizedOperations 0\n\n\t\t0, 30, // ErrGroupAuthorizationFailed\n\t\t0, 0,\n\t\t0, 0,\n\t\t0, 0,\n\t\t0, 0,\n\t\t0, 0, 0, 0,\n\t\t0, 0, 0, 0, // authorizedOperations 0\n\n\t}\n\n\tdescribeGroupsResponseEmptyV5 = []byte{\n\t\t0, 0, 0, 0, // throttle time 0\n\t\t1, // no groups\n\t\t0, // empty tagged fields\n\t}\n\n\tdescribeGroupsResponsePopulatedV5 = []byte{\n\t\t0, 0, 0, 0, // throttle time 0\n\t\t3, // 1+2 groups\n\n\t\t0, 0, // no error\n\t\t4, 'f', 'o', 'o', // Group ID\n\t\t4, 'b', 'a', 'r', // State\n\t\t9, 'c', 'o', 'n', 's', 'u', 'm', 'e', 'r', // ConsumerProtocol type\n\t\t4, 'b', 'a', 'z', // Protocol name\n\t\t2,           // 1 member\n\t\t3, 'i', 'd', // Member ID\n\t\t4, 'g', 'i', 'd', // Group Instance ID\n\t\t7, 's', 'a', 'r', 'a', 'm', 'a', // Client ID\n\t\t10, 'l', 'o', 'c', 'a', 'l', 'h', 'o', 's', 't', // Client Host\n\t\t4, 0x01, 0x02, 0x03, // MemberMetadata\n\t\t4, 0x04, 0x05, 0x06, // MemberAssignment\n\t\t0,          // empty tagged fields\n\t\t0, 0, 0, 0, // authorizedOperations 0\n\t\t0, // empty tagged fields\n\n\t\t0, 30, // ErrGroupAuthorizationFailed\n\t\t1,          // empty Group ID\n\t\t1,          // empty State\n\t\t1,          // empty Type\n\t\t1,          // empty Data\n\t\t1,          // no members\n\t\t0, 0, 0, 0, // authorizedOperations 0\n\t\t0, // empty tagged fields\n\t\t0, // empty tagged fields\n\n\t}\n)\n\nfunc TestDescribeGroupsResponseV1plus(t *testing.T) {\n\tgroupInstanceId := \"gid\"\n\ttests := []struct {\n\t\tName         string\n\t\tVersion      int16\n\t\tMessageBytes []byte\n\t\tMessage      *DescribeGroupsResponse\n\t}{\n\t\t{\n\t\t\t\"empty\",\n\t\t\t3,\n\t\t\tdescribeGroupsResponseEmptyV3,\n\t\t\t&DescribeGroupsResponse{\n\t\t\t\tVersion: 3,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"populated\",\n\t\t\t3,\n\t\t\tdescribeGroupsResponsePopulatedV3,\n\t\t\t&DescribeGroupsResponse{\n\t\t\t\tVersion:        3,\n\t\t\t\tThrottleTimeMs: int32(0),\n\t\t\t\tGroups: []*GroupDescription{\n\t\t\t\t\t{\n\t\t\t\t\t\tVersion:      3,\n\t\t\t\t\t\tErr:          KError(0),\n\t\t\t\t\t\tGroupId:      \"foo\",\n\t\t\t\t\t\tState:        \"bar\",\n\t\t\t\t\t\tProtocolType: \"consumer\",\n\t\t\t\t\t\tProtocol:     \"baz\",\n\t\t\t\t\t\tMembers: map[string]*GroupMemberDescription{\n\t\t\t\t\t\t\t\"id\": {\n\t\t\t\t\t\t\t\tVersion:          3,\n\t\t\t\t\t\t\t\tMemberId:         \"id\",\n\t\t\t\t\t\t\t\tClientId:         \"sarama\",\n\t\t\t\t\t\t\t\tClientHost:       \"localhost\",\n\t\t\t\t\t\t\t\tMemberMetadata:   []byte{1, 2, 3},\n\t\t\t\t\t\t\t\tMemberAssignment: []byte{4, 5, 6},\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\t{\n\t\t\t\t\t\tVersion:   3,\n\t\t\t\t\t\tErr:       KError(30),\n\t\t\t\t\t\tErrorCode: 30,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"empty\",\n\t\t\t4,\n\t\t\tdescribeGroupsResponseEmptyV4,\n\t\t\t&DescribeGroupsResponse{\n\t\t\t\tVersion: 4,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"populated\",\n\t\t\t4,\n\t\t\tdescribeGroupsResponsePopulatedV4,\n\t\t\t&DescribeGroupsResponse{\n\t\t\t\tVersion:        4,\n\t\t\t\tThrottleTimeMs: int32(0),\n\t\t\t\tGroups: []*GroupDescription{\n\t\t\t\t\t{\n\t\t\t\t\t\tVersion:      4,\n\t\t\t\t\t\tErr:          KError(0),\n\t\t\t\t\t\tGroupId:      \"foo\",\n\t\t\t\t\t\tState:        \"bar\",\n\t\t\t\t\t\tProtocolType: \"consumer\",\n\t\t\t\t\t\tProtocol:     \"baz\",\n\t\t\t\t\t\tMembers: map[string]*GroupMemberDescription{\n\t\t\t\t\t\t\t\"id\": {\n\t\t\t\t\t\t\t\tVersion:          4,\n\t\t\t\t\t\t\t\tMemberId:         \"id\",\n\t\t\t\t\t\t\t\tGroupInstanceId:  &groupInstanceId,\n\t\t\t\t\t\t\t\tClientId:         \"sarama\",\n\t\t\t\t\t\t\t\tClientHost:       \"localhost\",\n\t\t\t\t\t\t\t\tMemberMetadata:   []byte{1, 2, 3},\n\t\t\t\t\t\t\t\tMemberAssignment: []byte{4, 5, 6},\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\t{\n\t\t\t\t\t\tVersion:   4,\n\t\t\t\t\t\tErr:       KError(30),\n\t\t\t\t\t\tErrorCode: 30,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\n\t\t{\n\t\t\t\"empty\",\n\t\t\t5,\n\t\t\tdescribeGroupsResponseEmptyV5,\n\t\t\t&DescribeGroupsResponse{\n\t\t\t\tVersion: 5,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"populated\",\n\t\t\t5,\n\t\t\tdescribeGroupsResponsePopulatedV5,\n\t\t\t&DescribeGroupsResponse{\n\t\t\t\tVersion:        5,\n\t\t\t\tThrottleTimeMs: int32(0),\n\t\t\t\tGroups: []*GroupDescription{\n\t\t\t\t\t{\n\t\t\t\t\t\tVersion:      5,\n\t\t\t\t\t\tErr:          KError(0),\n\t\t\t\t\t\tGroupId:      \"foo\",\n\t\t\t\t\t\tState:        \"bar\",\n\t\t\t\t\t\tProtocolType: \"consumer\",\n\t\t\t\t\t\tProtocol:     \"baz\",\n\t\t\t\t\t\tMembers: map[string]*GroupMemberDescription{\n\t\t\t\t\t\t\t\"id\": {\n\t\t\t\t\t\t\t\tVersion:          5,\n\t\t\t\t\t\t\t\tMemberId:         \"id\",\n\t\t\t\t\t\t\t\tGroupInstanceId:  &groupInstanceId,\n\t\t\t\t\t\t\t\tClientId:         \"sarama\",\n\t\t\t\t\t\t\t\tClientHost:       \"localhost\",\n\t\t\t\t\t\t\t\tMemberMetadata:   []byte{1, 2, 3},\n\t\t\t\t\t\t\t\tMemberAssignment: []byte{4, 5, 6},\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\t{\n\t\t\t\t\t\tVersion:   5,\n\t\t\t\t\t\tErr:       KError(30),\n\t\t\t\t\t\tErrorCode: 30,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, c := range tests {\n\t\tt.Run(fmt.Sprintf(\"%s-v%d\", c.Name, c.Version), func(t *testing.T) {\n\t\t\tresponse := new(DescribeGroupsResponse)\n\t\t\ttestVersionDecodable(t, c.Name, response, c.MessageBytes, c.Version)\n\t\t\tif !assert.Equal(t, c.Message, response) {\n\t\t\t\tt.Errorf(\"case %s decode failed, expected:%+v got %+v\", c.Name, c.Message, response)\n\t\t\t}\n\t\t\ttestEncodable(t, c.Name, c.Message, c.MessageBytes)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "describe_log_dirs_request.go",
    "content": "package sarama\n\n// DescribeLogDirsRequest is a describe request to get partitions' log size\ntype DescribeLogDirsRequest struct {\n\t// Version 0 and 1 are equal\n\t// The version number is bumped to indicate that on quota violation brokers send out responses before throttling.\n\tVersion int16\n\n\t// If this is an empty array, all topics will be queried\n\tDescribeTopics []DescribeLogDirsRequestTopic\n}\n\nfunc (r *DescribeLogDirsRequest) setVersion(v int16) {\n\tr.Version = v\n}\n\n// DescribeLogDirsRequestTopic is a describe request about the log dir of one or more partitions within a Topic\ntype DescribeLogDirsRequestTopic struct {\n\tTopic        string\n\tPartitionIDs []int32\n}\n\nfunc (r *DescribeLogDirsRequest) encode(pe packetEncoder) error {\n\tlength := len(r.DescribeTopics)\n\tif length == 0 {\n\t\t// In order to query all topics we must send null\n\t\tlength = -1\n\t}\n\tif err := pe.putArrayLength(length); err != nil {\n\t\treturn err\n\t}\n\n\tfor _, d := range r.DescribeTopics {\n\t\tif err := pe.putString(d.Topic); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := pe.putInt32Array(d.PartitionIDs); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tpe.putEmptyTaggedFieldArray()\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (r *DescribeLogDirsRequest) decode(pd packetDecoder, version int16) error {\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif n == -1 {\n\t\tn = 0\n\t}\n\n\ttopics := make([]DescribeLogDirsRequestTopic, n)\n\tfor i := 0; i < n; i++ {\n\t\ttopics[i] = DescribeLogDirsRequestTopic{}\n\n\t\ttopic, err := pd.getString()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ttopics[i].Topic = topic\n\n\t\tpIDs, err := pd.getInt32Array()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ttopics[i].PartitionIDs = pIDs\n\t\t_, err = pd.getEmptyTaggedFieldArray()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tr.DescribeTopics = topics\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (r *DescribeLogDirsRequest) key() int16 {\n\treturn apiKeyDescribeLogDirs\n}\n\nfunc (r *DescribeLogDirsRequest) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *DescribeLogDirsRequest) headerVersion() int16 {\n\tif r.Version >= 2 {\n\t\treturn 2\n\t}\n\treturn 1\n}\n\nfunc (r *DescribeLogDirsRequest) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 4\n}\n\nfunc (r *DescribeLogDirsRequest) isFlexible() bool {\n\treturn r.isFlexibleVersion(r.Version)\n}\n\nfunc (r *DescribeLogDirsRequest) isFlexibleVersion(version int16) bool {\n\treturn version >= 2\n}\n\nfunc (r *DescribeLogDirsRequest) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 4:\n\t\treturn V3_3_0_0\n\tcase 3:\n\t\treturn V3_2_0_0\n\tcase 2:\n\t\treturn V2_6_0_0\n\tcase 1:\n\t\treturn V2_0_0_0\n\tdefault:\n\t\treturn V1_0_0_0\n\t}\n}\n"
  },
  {
    "path": "describe_log_dirs_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nvar (\n\temptyDescribeLogDirsRequest = []byte{255, 255, 255, 255} // Empty array (array length -1 sent)\n\ttopicDescribeLogDirsRequest = []byte{\n\t\t0, 0, 0, 1, // DescribeTopics array, Array length 1\n\t\t0, 6, // Topic name length 6\n\t\t'r', 'a', 'n', 'd', 'o', 'm', // Topic name\n\t\t0, 0, 0, 2, // PartitionIDs int32 array, Array length 2\n\t\t0, 0, 0, 25, // PartitionID 25\n\t\t0, 0, 0, 26, // PartitionID 26\n\t}\n\temptyDescribeLogDirsRequestV2 = []byte{0, 0} // Compact empty array length\n\ttopicDescribeLogDirsRequestV2 = []byte{\n\t\t2,                            // DescribeTopics array, Array length 1+1\n\t\t7,                            // Topic name length 6+1\n\t\t'r', 'a', 'n', 'd', 'o', 'm', // Topic name\n\t\t3,           // PartitionIDs int32 array, Array length 2+1\n\t\t0, 0, 0, 25, // PartitionID 25\n\t\t0, 0, 0, 26, // PartitionID 26\n\t\t0, 0, // empty tagged fields\n\t}\n)\n\nfunc TestDescribeLogDirsRequest(t *testing.T) {\n\trequest := &DescribeLogDirsRequest{\n\t\tVersion:        0,\n\t\tDescribeTopics: []DescribeLogDirsRequestTopic{},\n\t}\n\ttestRequest(t, \"no topics\", request, emptyDescribeLogDirsRequest)\n\n\trequest.DescribeTopics = []DescribeLogDirsRequestTopic{\n\t\t{\n\t\t\tTopic:        \"random\",\n\t\t\tPartitionIDs: []int32{25, 26},\n\t\t},\n\t}\n\ttestRequest(t, \"with topics\", request, topicDescribeLogDirsRequest)\n\n\trequest = &DescribeLogDirsRequest{\n\t\tVersion:        2,\n\t\tDescribeTopics: []DescribeLogDirsRequestTopic{},\n\t}\n\ttestRequest(t, \"no topics v2\", request, emptyDescribeLogDirsRequestV2)\n\n\trequest.DescribeTopics = []DescribeLogDirsRequestTopic{\n\t\t{\n\t\t\tTopic:        \"random\",\n\t\t\tPartitionIDs: []int32{25, 26},\n\t\t},\n\t}\n\ttestRequest(t, \"with topics v2\", request, topicDescribeLogDirsRequestV2)\n}\n"
  },
  {
    "path": "describe_log_dirs_response.go",
    "content": "package sarama\n\nimport \"time\"\n\ntype DescribeLogDirsResponse struct {\n\tThrottleTime time.Duration\n\n\t// Version 0 and 1 are equal\n\t// The version number is bumped to indicate that on quota violation brokers send out responses before throttling.\n\tVersion int16\n\n\tLogDirs []DescribeLogDirsResponseDirMetadata\n\n\tErrorCode KError\n}\n\nfunc (r *DescribeLogDirsResponse) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *DescribeLogDirsResponse) encode(pe packetEncoder) error {\n\tpe.putDurationMs(r.ThrottleTime)\n\n\tif r.Version >= 3 {\n\t\tpe.putKError(r.ErrorCode)\n\t}\n\n\tif err := pe.putArrayLength(len(r.LogDirs)); err != nil {\n\t\treturn err\n\t}\n\n\tfor _, dir := range r.LogDirs {\n\t\tif err := dir.encode(pe, r.Version); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (r *DescribeLogDirsResponse) decode(pd packetDecoder, version int16) (err error) {\n\tif r.ThrottleTime, err = pd.getDurationMs(); err != nil {\n\t\treturn err\n\t}\n\n\tif version >= 3 {\n\t\tr.ErrorCode, err = pd.getKError()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Decode array of DescribeLogDirsResponseDirMetadata\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr.LogDirs = make([]DescribeLogDirsResponseDirMetadata, n)\n\tfor i := 0; i < n; i++ {\n\t\tdir := DescribeLogDirsResponseDirMetadata{}\n\t\tif err := dir.decode(pd, version); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.LogDirs[i] = dir\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (r *DescribeLogDirsResponse) key() int16 {\n\treturn apiKeyDescribeLogDirs\n}\n\nfunc (r *DescribeLogDirsResponse) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *DescribeLogDirsResponse) headerVersion() int16 {\n\tif r.Version >= 2 {\n\t\treturn 1\n\t}\n\treturn 0\n}\n\nfunc (r *DescribeLogDirsResponse) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 4\n}\n\nfunc (r *DescribeLogDirsResponse) isFlexible() bool {\n\treturn r.isFlexibleVersion(r.Version)\n}\n\nfunc (r *DescribeLogDirsResponse) isFlexibleVersion(version int16) bool {\n\treturn version >= 2\n}\n\nfunc (r *DescribeLogDirsResponse) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 4:\n\t\treturn V3_3_0_0\n\tcase 3:\n\t\treturn V3_2_0_0\n\tcase 2:\n\t\treturn V2_6_0_0\n\tcase 1:\n\t\treturn V2_0_0_0\n\tdefault:\n\t\treturn V1_0_0_0\n\t}\n}\n\nfunc (r *DescribeLogDirsResponse) throttleTime() time.Duration {\n\treturn r.ThrottleTime\n}\n\ntype DescribeLogDirsResponseDirMetadata struct {\n\tErrorCode KError\n\n\t// The absolute log directory path\n\tPath   string\n\tTopics []DescribeLogDirsResponseTopic\n\n\tTotalBytes  int64\n\tUsableBytes int64\n}\n\nfunc (r *DescribeLogDirsResponseDirMetadata) encode(pe packetEncoder, version int16) error {\n\tpe.putKError(r.ErrorCode)\n\n\terr := pe.putString(r.Path)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif err := pe.putArrayLength(len(r.Topics)); err != nil {\n\t\treturn err\n\t}\n\tfor _, topic := range r.Topics {\n\t\tif err := topic.encode(pe, version); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif version >= 4 {\n\t\tpe.putInt64(r.TotalBytes)\n\t\tpe.putInt64(r.UsableBytes)\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (r *DescribeLogDirsResponseDirMetadata) decode(pd packetDecoder, version int16) (err error) {\n\tr.ErrorCode, err = pd.getKError()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tpath, err := pd.getString()\n\tif err != nil {\n\t\treturn err\n\t}\n\tr.Path = path\n\n\t// Decode array of DescribeLogDirsResponseTopic\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr.Topics = make([]DescribeLogDirsResponseTopic, n)\n\tfor i := 0; i < n; i++ {\n\t\tt := DescribeLogDirsResponseTopic{}\n\n\t\tif err := t.decode(pd, version); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tr.Topics[i] = t\n\t}\n\n\tif version >= 4 {\n\t\ttotalBytes, err := pd.getInt64()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.TotalBytes = totalBytes\n\t\tusableBytes, err := pd.getInt64()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.UsableBytes = usableBytes\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\n// DescribeLogDirsResponseTopic contains a topic's partitions descriptions\ntype DescribeLogDirsResponseTopic struct {\n\tTopic      string\n\tPartitions []DescribeLogDirsResponsePartition\n}\n\nfunc (r *DescribeLogDirsResponseTopic) encode(pe packetEncoder, version int16) error {\n\tif err := pe.putString(r.Topic); err != nil {\n\t\treturn err\n\t}\n\tif err := pe.putArrayLength(len(r.Partitions)); err != nil {\n\t\treturn err\n\t}\n\tfor _, partition := range r.Partitions {\n\t\tif err := partition.encode(pe, version); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (r *DescribeLogDirsResponseTopic) decode(pd packetDecoder, version int16) error {\n\tt, err := pd.getString()\n\tif err != nil {\n\t\treturn err\n\t}\n\tr.Topic = t\n\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr.Partitions = make([]DescribeLogDirsResponsePartition, n)\n\tfor i := 0; i < n; i++ {\n\t\tp := DescribeLogDirsResponsePartition{}\n\t\tif err := p.decode(pd, version); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.Partitions[i] = p\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\n// DescribeLogDirsResponsePartition describes a partition's log directory\ntype DescribeLogDirsResponsePartition struct {\n\tPartitionID int32\n\n\t// The size of the log segments of the partition in bytes.\n\tSize int64\n\n\t// The lag of the log's LEO w.r.t. partition's HW (if it is the current log for the partition) or\n\t// current replica's LEO (if it is the future log for the partition)\n\tOffsetLag int64\n\n\t// True if this log is created by AlterReplicaLogDirsRequest and will replace the current log of\n\t// the replica in the future.\n\tIsTemporary bool\n}\n\nfunc (r *DescribeLogDirsResponsePartition) encode(pe packetEncoder, version int16) error {\n\tisFlexible := version >= 2\n\tpe.putInt32(r.PartitionID)\n\tpe.putInt64(r.Size)\n\tpe.putInt64(r.OffsetLag)\n\tpe.putBool(r.IsTemporary)\n\tif isFlexible {\n\t\tpe.putEmptyTaggedFieldArray()\n\t}\n\n\treturn nil\n}\n\nfunc (r *DescribeLogDirsResponsePartition) decode(pd packetDecoder, version int16) error {\n\tpID, err := pd.getInt32()\n\tif err != nil {\n\t\treturn err\n\t}\n\tr.PartitionID = pID\n\n\tsize, err := pd.getInt64()\n\tif err != nil {\n\t\treturn err\n\t}\n\tr.Size = size\n\n\tlag, err := pd.getInt64()\n\tif err != nil {\n\t\treturn err\n\t}\n\tr.OffsetLag = lag\n\n\tisTemp, err := pd.getBool()\n\tif err != nil {\n\t\treturn err\n\t}\n\tr.IsTemporary = isTemp\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n"
  },
  {
    "path": "describe_log_dirs_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"testing\"\n)\n\nvar (\n\tdescribeLogDirsResponseEmpty = []byte{\n\t\t0, 0, 0, 0, // no throttle time\n\t\t0, 0, 0, 0, // no log dirs\n\t}\n\n\tdescribeLogDirsResponseTwoPartitions = []byte{\n\t\t0, 0, 0, 0, // no throttle time\n\t\t0, 0, 0, 1, // One describe log dir (array length)\n\t\t0, 0, // No error code\n\t\t0, 6, // Character length of path (6 chars)\n\t\t'/', 'k', 'a', 'f', 'k', 'a',\n\t\t0, 0, 0, 1, // One DescribeLogDirsResponseTopic (array length)\n\t\t0, 6, // Character length of \"random\" topic (6 chars)\n\t\t'r', 'a', 'n', 'd', 'o', 'm', // Topic name\n\t\t0, 0, 0, 2, // Two DescribeLogDirsResponsePartition (array length)\n\t\t0, 0, 0, 25, // PartitionID 25\n\t\t0, 0, 0, 0, 0, 0, 0, 125, // Log Size\n\t\t0, 0, 0, 0, 0, 0, 0, 0, // OffsetLag\n\t\t0,           // IsTemporary = false\n\t\t0, 0, 0, 26, // PartitionID 25\n\t\t0, 0, 0, 0, 0, 0, 0, 100, // Log Size\n\t\t0, 0, 0, 0, 0, 0, 0, 0, // OffsetLag\n\t\t0, // IsTemporary = false\n\t}\n\n\tdescribeLogDirsResponseEmptyV2 = []byte{\n\t\t0, 0, 0, 0, // no throttle time\n\t\t0, 0, // no log dirs\n\t}\n\n\tdescribeLogDirsResponseTwoPartitionsV2 = []byte{\n\t\t0, 0, 0, 0, // no throttle time\n\t\t2,    // One describe log dir (array length + 1)\n\t\t0, 0, // No error code\n\t\t7, // Character length of path (6 chars + 1)\n\t\t'/', 'k', 'a', 'f', 'k', 'a',\n\t\t2,                            // One DescribeLogDirsResponseTopic (array length + 1)\n\t\t7,                            // Character length of \"random\" topic (6 chars + 1)\n\t\t'r', 'a', 'n', 'd', 'o', 'm', // Topic name\n\t\t3,           // Two DescribeLogDirsResponsePartition (array length)\n\t\t0, 0, 0, 25, // PartitionID 25\n\t\t0, 0, 0, 0, 0, 0, 0, 125, // Log Size\n\t\t0, 0, 0, 0, 0, 0, 0, 0, // OffsetLag\n\t\t0,           // IsTemporary = false\n\t\t0,           // empty tagged fields\n\t\t0, 0, 0, 26, // PartitionID 25\n\t\t0, 0, 0, 0, 0, 0, 0, 100, // Log Size\n\t\t0, 0, 0, 0, 0, 0, 0, 0, // OffsetLag\n\t\t0,    // IsTemporary = false\n\t\t0, 0, // empty tagged fields\n\t\t0, 0, // empty tagged fields\n\t}\n\n\tdescribeLogDirsResponseEmptyV3 = []byte{\n\t\t0, 0, 0, 0, // no throttle time\n\t\t0, 0, // No error code\n\t\t1, // no log dirs\n\t\t0, // empty tagged fields\n\t}\n\n\tdescribeLogDirsResponseTwoPartitionsV3 = []byte{\n\t\t0, 0, 0, 0, // no throttle time\n\t\t0, 0, // No error code\n\t\t2,    // One describe log dir (array length + 1)\n\t\t0, 0, // No error code\n\t\t7, // Character length of path (6 chars + 1)\n\t\t'/', 'k', 'a', 'f', 'k', 'a',\n\t\t2,                            // One DescribeLogDirsResponseTopic (array length + 1)\n\t\t7,                            // Character length of \"random\" topic (6 chars + 1)\n\t\t'r', 'a', 'n', 'd', 'o', 'm', // Topic name\n\t\t3,           // Two DescribeLogDirsResponsePartition (array length)\n\t\t0, 0, 0, 25, // PartitionID 25\n\t\t0, 0, 0, 0, 0, 0, 0, 125, // Log Size\n\t\t0, 0, 0, 0, 0, 0, 0, 0, // OffsetLag\n\t\t0,           // IsTemporary = false\n\t\t0,           // empty tagged fields\n\t\t0, 0, 0, 26, // PartitionID 25\n\t\t0, 0, 0, 0, 0, 0, 0, 100, // Log Size\n\t\t0, 0, 0, 0, 0, 0, 0, 0, // OffsetLag\n\t\t0,    // IsTemporary = false\n\t\t0, 0, // empty tagged fields\n\t\t0, 0, // empty tagged fields\n\t}\n\n\tdescribeLogDirsResponseEmptyV4 = []byte{\n\t\t0, 0, 0, 0, // no throttle time\n\t\t0, 0, // No error code\n\t\t0, 0, // no log dirs\n\t}\n\n\tdescribeLogDirsResponseTwoPartitionsV4 = []byte{\n\t\t0, 0, 0, 0, // no throttle time\n\t\t0, 0, // No error code\n\t\t2,    // One describe log dir (array length + 1)\n\t\t0, 0, // No error code\n\t\t7, // Character length of path (6 chars + 1)\n\t\t'/', 'k', 'a', 'f', 'k', 'a',\n\t\t2,                            // One DescribeLogDirsResponseTopic (array length + 1)\n\t\t7,                            // Character length of \"random\" topic (6 chars + 1)\n\t\t'r', 'a', 'n', 'd', 'o', 'm', // Topic name\n\t\t3,           // Two DescribeLogDirsResponsePartition (array length)\n\t\t0, 0, 0, 25, // PartitionID 25\n\t\t0, 0, 0, 0, 0, 0, 0, 125, // Log Size\n\t\t0, 0, 0, 0, 0, 0, 0, 0, // OffsetLag\n\t\t0,           // IsTemporary = false\n\t\t0,           // empty tagged fields\n\t\t0, 0, 0, 26, // PartitionID 25\n\t\t0, 0, 0, 0, 0, 0, 0, 100, // Log Size\n\t\t0, 0, 0, 0, 0, 0, 0, 0, // OffsetLag\n\t\t0,    // IsTemporary = false\n\t\t0, 0, // empty tagged fields\n\t\t0, 0, 0, 63, 247, 0, 32, 0,\n\t\t0, 0, 0, 18, 189, 95, 128, 0,\n\t\t0, // empty tagged fields\n\t\t0, // empty tagged fields\n\t}\n)\n\nfunc TestDescribeLogDirsResponse(t *testing.T) {\n\t// Test empty response\n\tresponse := &DescribeLogDirsResponse{\n\t\tLogDirs: []DescribeLogDirsResponseDirMetadata{},\n\t}\n\ttestVersionDecodable(t, \"empty\", response, describeLogDirsResponseEmpty, 0)\n\tif len(response.LogDirs) != 0 {\n\t\tt.Error(\"Expected no log dirs\")\n\t}\n\n\tresponse.LogDirs = []DescribeLogDirsResponseDirMetadata{\n\t\t{\n\t\t\tErrorCode: 0,\n\t\t\tPath:      \"/kafka\",\n\t\t\tTopics: []DescribeLogDirsResponseTopic{\n\t\t\t\t{\n\t\t\t\t\tTopic: \"random\",\n\t\t\t\t\tPartitions: []DescribeLogDirsResponsePartition{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tPartitionID: 25,\n\t\t\t\t\t\t\tSize:        125,\n\t\t\t\t\t\t\tOffsetLag:   0,\n\t\t\t\t\t\t\tIsTemporary: false,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tPartitionID: 26,\n\t\t\t\t\t\t\tSize:        100,\n\t\t\t\t\t\t\tOffsetLag:   0,\n\t\t\t\t\t\t\tIsTemporary: false,\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}\n\ttestVersionDecodable(t, \"two partitions\", response, describeLogDirsResponseTwoPartitions, 0)\n\tif len(response.LogDirs) != 1 {\n\t\tt.Error(\"Expected one log dirs\")\n\t}\n\tif len(response.LogDirs[0].Topics) != 1 {\n\t\tt.Error(\"Expected one topic in log dirs\")\n\t}\n\tif len(response.LogDirs[0].Topics[0].Partitions) != 2 {\n\t\tt.Error(\"Expected two partitions\")\n\t}\n}\n\nfunc TestDescribeLogDirsResponseV2(t *testing.T) {\n\t// Test empty response\n\tresponse := &DescribeLogDirsResponse{\n\t\tLogDirs: []DescribeLogDirsResponseDirMetadata{},\n\t}\n\ttestVersionDecodable(t, \"empty\", response, describeLogDirsResponseEmptyV2, 2)\n\tif len(response.LogDirs) != 0 {\n\t\tt.Error(\"Expected no log dirs\")\n\t}\n\n\tresponse.LogDirs = []DescribeLogDirsResponseDirMetadata{\n\t\t{\n\t\t\tErrorCode: 0,\n\t\t\tPath:      \"/kafka\",\n\t\t\tTopics: []DescribeLogDirsResponseTopic{\n\t\t\t\t{\n\t\t\t\t\tTopic: \"random\",\n\t\t\t\t\tPartitions: []DescribeLogDirsResponsePartition{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tPartitionID: 25,\n\t\t\t\t\t\t\tSize:        125,\n\t\t\t\t\t\t\tOffsetLag:   0,\n\t\t\t\t\t\t\tIsTemporary: false,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tPartitionID: 26,\n\t\t\t\t\t\t\tSize:        100,\n\t\t\t\t\t\t\tOffsetLag:   0,\n\t\t\t\t\t\t\tIsTemporary: false,\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}\n\ttestVersionDecodable(t, \"two partitions\", response, describeLogDirsResponseTwoPartitionsV2, 2)\n\tif len(response.LogDirs) != 1 {\n\t\tt.Error(\"Expected one log dirs\")\n\t}\n\tif len(response.LogDirs[0].Topics) != 1 {\n\t\tt.Error(\"Expected one topic in log dirs\")\n\t}\n\tif len(response.LogDirs[0].Topics[0].Partitions) != 2 {\n\t\tt.Error(\"Expected two partitions\")\n\t}\n}\n\nfunc TestDescribeLogDirsResponseV3(t *testing.T) {\n\t// Test empty response\n\tresponse := &DescribeLogDirsResponse{\n\t\tLogDirs: []DescribeLogDirsResponseDirMetadata{},\n\t}\n\ttestVersionDecodable(t, \"empty\", response, describeLogDirsResponseEmptyV3, 3)\n\tif len(response.LogDirs) != 0 {\n\t\tt.Error(\"Expected no log dirs\")\n\t}\n\n\tresponse.LogDirs = []DescribeLogDirsResponseDirMetadata{\n\t\t{\n\t\t\tErrorCode: 0,\n\t\t\tPath:      \"/kafka\",\n\t\t\tTopics: []DescribeLogDirsResponseTopic{\n\t\t\t\t{\n\t\t\t\t\tTopic: \"random\",\n\t\t\t\t\tPartitions: []DescribeLogDirsResponsePartition{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tPartitionID: 25,\n\t\t\t\t\t\t\tSize:        125,\n\t\t\t\t\t\t\tOffsetLag:   0,\n\t\t\t\t\t\t\tIsTemporary: false,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tPartitionID: 26,\n\t\t\t\t\t\t\tSize:        100,\n\t\t\t\t\t\t\tOffsetLag:   0,\n\t\t\t\t\t\t\tIsTemporary: false,\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}\n\ttestVersionDecodable(t, \"two partitions\", response, describeLogDirsResponseTwoPartitionsV3, 3)\n\tif len(response.LogDirs) != 1 {\n\t\tt.Error(\"Expected one log dirs\")\n\t}\n\tif len(response.LogDirs[0].Topics) != 1 {\n\t\tt.Error(\"Expected one topic in log dirs\")\n\t}\n\tif len(response.LogDirs[0].Topics[0].Partitions) != 2 {\n\t\tt.Error(\"Expected two partitions\")\n\t}\n}\n\nfunc TestDescribeLogDirsResponseV4(t *testing.T) {\n\t// Test empty response\n\tresponse := &DescribeLogDirsResponse{\n\t\tLogDirs: []DescribeLogDirsResponseDirMetadata{},\n\t}\n\ttestVersionDecodable(t, \"empty\", response, describeLogDirsResponseEmptyV4, 4)\n\tif len(response.LogDirs) != 0 {\n\t\tt.Error(\"Expected no log dirs\")\n\t}\n\n\tresponse.LogDirs = []DescribeLogDirsResponseDirMetadata{\n\t\t{\n\t\t\tErrorCode: 0,\n\t\t\tPath:      \"/kafka\",\n\t\t\tTopics: []DescribeLogDirsResponseTopic{\n\t\t\t\t{\n\t\t\t\t\tTopic: \"random\",\n\t\t\t\t\tPartitions: []DescribeLogDirsResponsePartition{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tPartitionID: 25,\n\t\t\t\t\t\t\tSize:        125,\n\t\t\t\t\t\t\tOffsetLag:   0,\n\t\t\t\t\t\t\tIsTemporary: false,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tPartitionID: 26,\n\t\t\t\t\t\t\tSize:        100,\n\t\t\t\t\t\t\tOffsetLag:   0,\n\t\t\t\t\t\t\tIsTemporary: false,\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}\n\ttestVersionDecodable(t, \"two partitions\", response, describeLogDirsResponseTwoPartitionsV4, 4)\n\tif len(response.LogDirs) != 1 {\n\t\tt.Error(\"Expected one log dirs\")\n\t}\n\tif len(response.LogDirs[0].Topics) != 1 {\n\t\tt.Error(\"Expected one topic in log dirs\")\n\t}\n\tif len(response.LogDirs[0].Topics[0].Partitions) != 2 {\n\t\tt.Error(\"Expected two partitions\")\n\t}\n\tif response.LogDirs[0].TotalBytes != 274726920192 {\n\t\tt.Error(\"Expected total bytes, 274726920192; found:\", response.LogDirs[0].TotalBytes)\n\t}\n\tif response.LogDirs[0].UsableBytes != 80486563840 {\n\t\tt.Error(\"Expected total bytes, 80486563840; found:\", response.LogDirs[0].UsableBytes)\n\t}\n}\n"
  },
  {
    "path": "describe_user_scram_credentials_request.go",
    "content": "package sarama\n\n// DescribeUserScramCredentialsRequest is a request to get list of SCRAM user names\ntype DescribeUserScramCredentialsRequest struct {\n\t// Version 0 is currently only supported\n\tVersion int16\n\n\t// If this is an empty array, all users will be queried\n\tDescribeUsers []DescribeUserScramCredentialsRequestUser\n}\n\nfunc (r *DescribeUserScramCredentialsRequest) setVersion(v int16) {\n\tr.Version = v\n}\n\n// DescribeUserScramCredentialsRequestUser is a describe request about specific user name\ntype DescribeUserScramCredentialsRequestUser struct {\n\tName string\n}\n\nfunc (r *DescribeUserScramCredentialsRequest) encode(pe packetEncoder) error {\n\tif err := pe.putArrayLength(len(r.DescribeUsers)); err != nil {\n\t\treturn err\n\t}\n\tfor _, d := range r.DescribeUsers {\n\t\tif err := pe.putString(d.Name); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tpe.putEmptyTaggedFieldArray()\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (r *DescribeUserScramCredentialsRequest) decode(pd packetDecoder, version int16) error {\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif n == -1 {\n\t\tn = 0\n\t}\n\n\tr.DescribeUsers = make([]DescribeUserScramCredentialsRequestUser, n)\n\tfor i := 0; i < n; i++ {\n\t\tr.DescribeUsers[i] = DescribeUserScramCredentialsRequestUser{}\n\t\tif r.DescribeUsers[i].Name, err = pd.getString(); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif _, err = pd.getEmptyTaggedFieldArray(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (r *DescribeUserScramCredentialsRequest) key() int16 {\n\treturn apiKeyDescribeUserScramCredentials\n}\n\nfunc (r *DescribeUserScramCredentialsRequest) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *DescribeUserScramCredentialsRequest) headerVersion() int16 {\n\treturn 2\n}\n\nfunc (r *DescribeUserScramCredentialsRequest) isValidVersion() bool {\n\treturn r.Version == 0\n}\n\nfunc (r *DescribeUserScramCredentialsRequest) isFlexible() bool {\n\treturn r.isFlexibleVersion(r.Version)\n}\n\nfunc (r *DescribeUserScramCredentialsRequest) isFlexibleVersion(version int16) bool {\n\treturn version >= 0\n}\n\nfunc (r *DescribeUserScramCredentialsRequest) requiredVersion() KafkaVersion {\n\treturn V2_7_0_0\n}\n"
  },
  {
    "path": "describe_user_scram_credentials_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nvar (\n\temptyDescribeUserScramCredentialsRequest = []byte{\n\t\t1, 0, // empty tagged fields\n\t}\n\tuserDescribeUserScramCredentialsRequest = []byte{\n\t\t2,                            // DescribeUsers array, Array length 1\n\t\t7,                            // User name length 6\n\t\t'r', 'a', 'n', 'd', 'o', 'm', // User name\n\t\t0, 0, // empty tagged fields\n\t}\n)\n\nfunc TestDescribeUserScramCredentialsRequest(t *testing.T) {\n\trequest := &DescribeUserScramCredentialsRequest{\n\t\tVersion:       0,\n\t\tDescribeUsers: []DescribeUserScramCredentialsRequestUser{},\n\t}\n\ttestRequest(t, \"no users\", request, emptyDescribeUserScramCredentialsRequest)\n\n\trequest.DescribeUsers = []DescribeUserScramCredentialsRequestUser{\n\t\t{\n\t\t\tName: \"random\",\n\t\t},\n\t}\n\ttestRequest(t, \"single user\", request, userDescribeUserScramCredentialsRequest)\n}\n"
  },
  {
    "path": "describe_user_scram_credentials_response.go",
    "content": "package sarama\n\nimport \"time\"\n\ntype ScramMechanismType int8\n\nconst (\n\tSCRAM_MECHANISM_UNKNOWN ScramMechanismType = iota // 0\n\tSCRAM_MECHANISM_SHA_256                           // 1\n\tSCRAM_MECHANISM_SHA_512                           // 2\n)\n\nfunc (s ScramMechanismType) String() string {\n\tswitch s {\n\tcase 1:\n\t\treturn SASLTypeSCRAMSHA256\n\tcase 2:\n\t\treturn SASLTypeSCRAMSHA512\n\tdefault:\n\t\treturn \"Unknown\"\n\t}\n}\n\ntype DescribeUserScramCredentialsResponse struct {\n\t// Version 0 is currently only supported\n\tVersion int16\n\n\tThrottleTime time.Duration\n\n\tErrorCode    KError\n\tErrorMessage *string\n\n\tResults []*DescribeUserScramCredentialsResult\n}\n\nfunc (r *DescribeUserScramCredentialsResponse) setVersion(v int16) {\n\tr.Version = v\n}\n\ntype DescribeUserScramCredentialsResult struct {\n\tUser string\n\n\tErrorCode    KError\n\tErrorMessage *string\n\n\tCredentialInfos []*UserScramCredentialsResponseInfo\n}\n\ntype UserScramCredentialsResponseInfo struct {\n\tMechanism  ScramMechanismType\n\tIterations int32\n}\n\nfunc (r *DescribeUserScramCredentialsResponse) encode(pe packetEncoder) error {\n\tpe.putDurationMs(r.ThrottleTime)\n\n\tpe.putKError(r.ErrorCode)\n\tif err := pe.putNullableString(r.ErrorMessage); err != nil {\n\t\treturn err\n\t}\n\n\tif err := pe.putArrayLength(len(r.Results)); err != nil {\n\t\treturn err\n\t}\n\tfor _, u := range r.Results {\n\t\tif err := pe.putString(u.User); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tpe.putInt16(int16(u.ErrorCode))\n\t\tif err := pe.putNullableString(u.ErrorMessage); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := pe.putArrayLength(len(u.CredentialInfos)); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor _, c := range u.CredentialInfos {\n\t\t\tpe.putInt8(int8(c.Mechanism))\n\t\t\tpe.putInt32(c.Iterations)\n\t\t\tpe.putEmptyTaggedFieldArray()\n\t\t}\n\n\t\tpe.putEmptyTaggedFieldArray()\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (r *DescribeUserScramCredentialsResponse) decode(pd packetDecoder, version int16) (err error) {\n\tif r.ThrottleTime, err = pd.getDurationMs(); err != nil {\n\t\treturn err\n\t}\n\n\tr.ErrorCode, err = pd.getKError()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif r.ErrorMessage, err = pd.getNullableString(); err != nil {\n\t\treturn err\n\t}\n\n\tnumUsers, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif numUsers > 0 {\n\t\tr.Results = make([]*DescribeUserScramCredentialsResult, numUsers)\n\t\tfor i := 0; i < numUsers; i++ {\n\t\t\tr.Results[i] = &DescribeUserScramCredentialsResult{}\n\t\t\tif r.Results[i].User, err = pd.getString(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tr.Results[i].ErrorCode, err = pd.getKError()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif r.Results[i].ErrorMessage, err = pd.getNullableString(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tnumCredentialInfos, err := pd.getArrayLength()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tr.Results[i].CredentialInfos = make([]*UserScramCredentialsResponseInfo, numCredentialInfos)\n\t\t\tfor j := 0; j < numCredentialInfos; j++ {\n\t\t\t\tr.Results[i].CredentialInfos[j] = &UserScramCredentialsResponseInfo{}\n\t\t\t\tscramMechanism, err := pd.getInt8()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tr.Results[i].CredentialInfos[j].Mechanism = ScramMechanismType(scramMechanism)\n\t\t\t\tif r.Results[i].CredentialInfos[j].Iterations, err = pd.getInt32(); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif _, err = pd.getEmptyTaggedFieldArray(); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif _, err = pd.getEmptyTaggedFieldArray(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (r *DescribeUserScramCredentialsResponse) key() int16 {\n\treturn apiKeyDescribeUserScramCredentials\n}\n\nfunc (r *DescribeUserScramCredentialsResponse) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *DescribeUserScramCredentialsResponse) headerVersion() int16 {\n\treturn 2\n}\n\nfunc (r *DescribeUserScramCredentialsResponse) isValidVersion() bool {\n\treturn r.Version == 0\n}\n\nfunc (r *DescribeUserScramCredentialsResponse) isFlexible() bool {\n\treturn r.isFlexibleVersion(r.Version)\n}\n\nfunc (r *DescribeUserScramCredentialsResponse) isFlexibleVersion(version int16) bool {\n\treturn version >= 0\n}\n\nfunc (r *DescribeUserScramCredentialsResponse) requiredVersion() KafkaVersion {\n\treturn V2_7_0_0\n}\n\nfunc (r *DescribeUserScramCredentialsResponse) throttleTime() time.Duration {\n\treturn r.ThrottleTime\n}\n"
  },
  {
    "path": "describe_user_scram_credentials_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nvar (\n\temptyDescribeUserScramCredentialsResponse = []byte{\n\t\t0, 0, 11, 184, // throttle time (3000 ms)\n\t\t0, 0, // no error code\n\t\t0, // no error message\n\t\t1, // empty array\n\t\t0, // tagged fields\n\t}\n\n\tuserDescribeUserScramCredentialsResponse = []byte{\n\t\t0, 0, 11, 184, // throttle time (3000 ms)\n\t\t0, 11, // Error Code\n\t\t6, 'e', 'r', 'r', 'o', 'r', // ErrorMessage\n\t\t2,                               // Results array length\n\t\t7, 'n', 'o', 'b', 'o', 'd', 'y', // User\n\t\t0, 13, // User ErrorCode\n\t\t11, 'e', 'r', 'r', 'o', 'r', '_', 'u', 's', 'e', 'r', // User ErrorMessage\n\t\t2,           // CredentialInfos array length\n\t\t2,           // Mechanism\n\t\t0, 0, 16, 0, // Iterations\n\t\t0, 0, 0,\n\t}\n)\n\nfunc TestDescribeUserScramCredentialsResponse(t *testing.T) {\n\tresponse := &DescribeUserScramCredentialsResponse{\n\t\tVersion:      0,\n\t\tThrottleTime: time.Second * 3,\n\t}\n\ttestResponse(t, \"empty\", response, emptyDescribeUserScramCredentialsResponse)\n\n\tresponseErrorMessage := \"error\"\n\tresponseUserErrorMessage := \"error_user\"\n\n\tresponse.ErrorCode = 11\n\tresponse.ErrorMessage = &responseErrorMessage\n\tresponse.Results = append(response.Results, &DescribeUserScramCredentialsResult{\n\t\tUser:         \"nobody\",\n\t\tErrorCode:    13,\n\t\tErrorMessage: &responseUserErrorMessage,\n\t\tCredentialInfos: []*UserScramCredentialsResponseInfo{\n\t\t\t{\n\t\t\t\tMechanism:  SCRAM_MECHANISM_SHA_512,\n\t\t\t\tIterations: 4096,\n\t\t\t},\n\t\t},\n\t})\n\ttestResponse(t, \"empty\", response, userDescribeUserScramCredentialsResponse)\n}\n"
  },
  {
    "path": "dev.yml",
    "content": "name: sarama\n\nup:\n  - go:\n      version: '1.17.6'\n\ncommands:\n  test:\n    run: make test\n    desc: 'run unit tests'\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "x-zookeeper-base: &zookeeper-base\n  image: 'docker.io/library/zookeeper:3.7.2'\n  init: true\n  restart: always\n  profiles:\n    - zookeeper\n  environment: &zookeeper-base-env\n    ZOO_SERVERS: 'server.1=zookeeper-1:2888:3888 server.2=zookeeper-2:2888:3888 server.3=zookeeper-3:2888:3888'\n    ZOO_CFG_EXTRA: 'clientPort=2181 peerPort=2888 leaderPort=3888'\n    ZOO_INIT_LIMIT: '10'\n    ZOO_SYNC_LIMIT: '5'\n    ZOO_MAX_CLIENT_CNXNS: '0'\n    ZOO_4LW_COMMANDS_WHITELIST: 'mntr,conf,ruok'\n\nx-kafka-base: &kafka-base\n  image: 'sarama/fv-kafka-${KAFKA_VERSION:-3.9.1}'\n  init: true\n  build:\n    context: .\n    dockerfile: Dockerfile.kafka\n    args:\n      KAFKA_VERSION: ${KAFKA_VERSION:-3.9.1}\n      SCALA_VERSION: ${SCALA_VERSION:-2.13}\n  depends_on:\n    - toxiproxy\n  restart: always\n  environment: &kafka-base-env\n    KAFKA_VERSION: ${KAFKA_VERSION:-3.9.1}\n    KAFKA_CFG_DEFAULT_REPLICATION_FACTOR: '2'\n    KAFKA_CFG_OFFSETS_TOPIC_REPLICATION_FACTOR: '2'\n    KAFKA_CFG_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: '2'\n    KAFKA_CFG_ZOOKEEPER_SESSION_TIMEOUT_MS: '6000'\n    KAFKA_CFG_ZOOKEEPER_CONNECTION_TIMEOUT_MS: '6000'\n    KAFKA_CFG_REPLICA_SELECTOR_CLASS: 'org.apache.kafka.common.replica.RackAwareReplicaSelector'\n    KAFKA_CFG_DELETE_TOPIC_ENABLE: 'true'\n    KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE: 'false'\n    KAFKA_CFG_GROUP_INITIAL_REBALANCE_DELAY_MS: 0\n    KAFKA_JVM_PERFORMANCE_OPTS: \"-XX:+IgnoreUnrecognizedVMOptions\"\n    KAFKA_CFG_INTER_BROKER_LISTENER_NAME: 'LISTENER_INTERNAL'\n    KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: 'LISTENER_INTERNAL:PLAINTEXT,LISTENER_LOCAL:PLAINTEXT,CONTROLLER:PLAINTEXT'\n    # ZooKeeper-specific\n    KAFKA_CFG_ZOOKEEPER_CONNECT: 'zookeeper-1:2181,zookeeper-2:2181,zookeeper-3:2181'\n    # KRaft-specific\n    KAFKA_CFG_PROCESS_ROLES: 'broker,controller'\n    KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: '1@kafka-1:9093,2@kafka-2:9093,3@kafka-3:9093,4@kafka-4:9093,5@kafka-5:9093'\n    KAFKA_CFG_CONTROLLER_LISTENER_NAMES: 'CONTROLLER'\n    KAFKA_CFG_CLUSTER_ID: 'cDZEekk4T3hTNGVlNzB3LUtUbkxaQQo='\n\nservices:\n  zookeeper-1:\n    <<: *zookeeper-base\n    container_name: 'zookeeper-1'\n    environment:\n      <<: *zookeeper-base-env\n      ZOO_MY_ID: '1'\n\n  zookeeper-2:\n    <<: *zookeeper-base\n    container_name: 'zookeeper-2'\n    environment:\n      <<: *zookeeper-base-env\n      ZOO_MY_ID: '2'\n\n  zookeeper-3:\n    <<: *zookeeper-base\n    container_name: 'zookeeper-3'\n    environment:\n      <<: *zookeeper-base-env\n      ZOO_MY_ID: '3'\n\n  kafka-1:\n    <<: *kafka-base\n    container_name: 'kafka-1'\n    healthcheck:\n      test: ['CMD', '/opt/kafka-${KAFKA_VERSION:-3.9.1}/bin/kafka-broker-api-versions.sh', '--bootstrap-server', 'kafka-1:9091']\n      interval: 15s\n      timeout: 15s\n      retries: 10\n      start_period: 360s\n    environment:\n      <<: *kafka-base-env\n      KAFKA_CFG_BROKER_ID: '1'\n      KAFKA_CFG_NODE_ID: '1'\n      KAFKA_CFG_BROKER_RACK: '1'\n      KAFKA_CFG_LISTENERS: 'LISTENER_INTERNAL://:9091,LISTENER_LOCAL://:29091,CONTROLLER://:9093'\n      KAFKA_CFG_ADVERTISED_LISTENERS: 'LISTENER_INTERNAL://kafka-1:9091,LISTENER_LOCAL://localhost:29091'\n\n  kafka-2:\n    <<: *kafka-base\n    container_name: 'kafka-2'\n    healthcheck:\n      test: ['CMD', '/opt/kafka-${KAFKA_VERSION:-3.9.1}/bin/kafka-broker-api-versions.sh', '--bootstrap-server', 'kafka-2:9091']\n      interval: 15s\n      timeout: 15s\n      retries: 10\n      start_period: 360s\n    environment:\n      <<: *kafka-base-env\n      KAFKA_CFG_BROKER_ID: '2'\n      KAFKA_CFG_NODE_ID: '2'\n      KAFKA_CFG_BROKER_RACK: '2'\n      KAFKA_CFG_LISTENERS: 'LISTENER_INTERNAL://:9091,LISTENER_LOCAL://:29092,CONTROLLER://:9093'\n      KAFKA_CFG_ADVERTISED_LISTENERS: 'LISTENER_INTERNAL://kafka-2:9091,LISTENER_LOCAL://localhost:29092'\n\n  kafka-3:\n    <<: *kafka-base\n    container_name: 'kafka-3'\n    healthcheck:\n      test: ['CMD', '/opt/kafka-${KAFKA_VERSION:-3.9.1}/bin/kafka-broker-api-versions.sh', '--bootstrap-server', 'kafka-3:9091']\n      interval: 15s\n      timeout: 15s\n      retries: 10\n      start_period: 360s\n    environment:\n      <<: *kafka-base-env\n      KAFKA_CFG_BROKER_ID: '3'\n      KAFKA_CFG_NODE_ID: '3'\n      KAFKA_CFG_BROKER_RACK: '3'\n      KAFKA_CFG_LISTENERS: 'LISTENER_INTERNAL://:9091,LISTENER_LOCAL://:29093,CONTROLLER://:9093'\n      KAFKA_CFG_ADVERTISED_LISTENERS: 'LISTENER_INTERNAL://kafka-3:9091,LISTENER_LOCAL://localhost:29093'\n\n  kafka-4:\n    <<: *kafka-base\n    container_name: 'kafka-4'\n    healthcheck:\n      test: ['CMD', '/opt/kafka-${KAFKA_VERSION:-3.9.1}/bin/kafka-broker-api-versions.sh', '--bootstrap-server', 'kafka-4:9091']\n      interval: 15s\n      timeout: 15s\n      retries: 10\n      start_period: 360s\n    environment:\n      <<: *kafka-base-env\n      KAFKA_CFG_BROKER_ID: '4'\n      KAFKA_CFG_NODE_ID: '4'\n      KAFKA_CFG_BROKER_RACK: '4'\n      KAFKA_CFG_LISTENERS: 'LISTENER_INTERNAL://:9091,LISTENER_LOCAL://:29094,CONTROLLER://:9093'\n      KAFKA_CFG_ADVERTISED_LISTENERS: 'LISTENER_INTERNAL://kafka-4:9091,LISTENER_LOCAL://localhost:29094'\n\n  kafka-5:\n    <<: *kafka-base\n    container_name: 'kafka-5'\n    healthcheck:\n      test: ['CMD', '/opt/kafka-${KAFKA_VERSION:-3.9.1}/bin/kafka-broker-api-versions.sh', '--bootstrap-server', 'kafka-5:9091']\n      interval: 15s\n      timeout: 15s\n      retries: 10\n      start_period: 360s\n    environment:\n      <<: *kafka-base-env\n      KAFKA_CFG_BROKER_ID: '5'\n      KAFKA_CFG_NODE_ID: '5'\n      KAFKA_CFG_BROKER_RACK: '5'\n      KAFKA_CFG_LISTENERS: 'LISTENER_INTERNAL://:9091,LISTENER_LOCAL://:29095,CONTROLLER://:9093'\n      KAFKA_CFG_ADVERTISED_LISTENERS: 'LISTENER_INTERNAL://kafka-5:9091,LISTENER_LOCAL://localhost:29095'\n\n  toxiproxy:\n    container_name: 'toxiproxy'\n    image: 'ghcr.io/shopify/toxiproxy:2.12.0'\n    init: true\n    healthcheck:\n      test: ['CMD', '/toxiproxy-cli', 'l']\n      interval: 15s\n      timeout: 15s\n      retries: 3\n      start_period: 30s\n    ports:\n      # The tests themselves actually start the proxies on these ports\n      - '29091:29091'\n      - '29092:29092'\n      - '29093:29093'\n      - '29094:29094'\n      - '29095:29095'\n\n      # This is the toxiproxy API port\n      - '8474:8474'\n"
  },
  {
    "path": "elect_leaders_request.go",
    "content": "package sarama\n\ntype ElectLeadersRequest struct {\n\tVersion         int16\n\tType            ElectionType\n\tTopicPartitions map[string][]int32\n\tTimeoutMs       int32\n}\n\nfunc (r *ElectLeadersRequest) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *ElectLeadersRequest) encode(pe packetEncoder) error {\n\tif r.Version > 0 {\n\t\tpe.putInt8(int8(r.Type))\n\t}\n\n\tif err := pe.putArrayLength(len(r.TopicPartitions)); err != nil {\n\t\treturn err\n\t}\n\n\tfor topic, partitions := range r.TopicPartitions {\n\t\tif err := pe.putString(topic); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := pe.putInt32Array(partitions); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tpe.putEmptyTaggedFieldArray()\n\t}\n\n\tpe.putInt32(r.TimeoutMs)\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (r *ElectLeadersRequest) decode(pd packetDecoder, version int16) (err error) {\n\tr.Version = version\n\tif r.Version > 0 {\n\t\tt, err := pd.getInt8()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.Type = ElectionType(t)\n\t}\n\n\ttopicCount, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif topicCount > 0 {\n\t\tr.TopicPartitions = make(map[string][]int32)\n\t\tfor i := 0; i < topicCount; i++ {\n\t\t\ttopic, err := pd.getString()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tpartitionCount, err := pd.getArrayLength()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tpartitions := make([]int32, partitionCount)\n\t\t\tfor j := 0; j < partitionCount; j++ {\n\t\t\t\tpartition, err := pd.getInt32()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tpartitions[j] = partition\n\t\t\t}\n\t\t\tr.TopicPartitions[topic] = partitions\n\t\t\tif _, err := pd.getEmptyTaggedFieldArray(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\tr.TimeoutMs, err = pd.getInt32()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (r *ElectLeadersRequest) key() int16 {\n\treturn apiKeyElectLeaders\n}\n\nfunc (r *ElectLeadersRequest) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *ElectLeadersRequest) headerVersion() int16 {\n\tif r.isFlexible() {\n\t\treturn 2\n\t}\n\treturn 1\n}\n\nfunc (r *ElectLeadersRequest) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 2\n}\n\nfunc (r *ElectLeadersRequest) isFlexible() bool {\n\treturn r.isFlexibleVersion(r.Version)\n}\n\nfunc (r *ElectLeadersRequest) isFlexibleVersion(version int16) bool {\n\treturn version >= 2\n}\n\nfunc (r *ElectLeadersRequest) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 2:\n\t\treturn V2_4_0_0\n\tcase 1:\n\t\treturn V0_11_0_0\n\tcase 0:\n\t\treturn V0_10_0_0\n\tdefault:\n\t\treturn V2_4_0_0\n\t}\n}\n"
  },
  {
    "path": "elect_leaders_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n)\n\nvar (\n\telectLeadersRequestOneTopicV1 = []byte{\n\t\t0,          // preferred election type\n\t\t0, 0, 0, 1, // 1 topic\n\t\t0, 5, 116, 111, 112, 105, 99, // topic name \"topic\" as compact string\n\t\t0, 0, 0, 1, // 1 partition\n\t\t0, 0, 0, 0, // partition 0\n\t\t0, 0, 39, 16, // timeout 10000\n\t}\n\telectLeadersRequestOneTopicV2 = []byte{\n\t\t0,                         // preferred election type\n\t\t2,                         // 2-1=1 topic\n\t\t6, 116, 111, 112, 105, 99, // topic name \"topic\" as compact string\n\t\t2,          // 2-1=1 partition\n\t\t0, 0, 0, 0, // partition 0\n\t\t0,            // empty tagged fields\n\t\t0, 0, 39, 16, // timeout 10000\n\t\t0, // empty tagged fields\n\t}\n)\n\nfunc TestElectLeadersRequest(t *testing.T) {\n\tvar request = &ElectLeadersRequest{\n\t\tTimeoutMs: int32(10000),\n\t\tVersion:   int16(1),\n\t\tTopicPartitions: map[string][]int32{\n\t\t\t\"topic\": {0},\n\t\t},\n\t\tType: PreferredElection,\n\t}\n\n\ttestRequest(t, \"one topic V1\", request, electLeadersRequestOneTopicV1)\n\n\trequest.Version = 2\n\ttestRequest(t, \"one topic V2\", request, electLeadersRequestOneTopicV2)\n}\n\nfunc TestElectLeadersRequestHeaderVersion(t *testing.T) {\n\treqBody := &ElectLeadersRequest{\n\t\tTimeoutMs: int32(10000),\n\t\tVersion:   int16(1),\n\t\tTopicPartitions: map[string][]int32{\n\t\t\t\"topic\": {0},\n\t\t},\n\t\tType: PreferredElection,\n\t}\n\n\tpacket, err := encode(&request{\n\t\tcorrelationID: 123,\n\t\tclientID:      \"foo\",\n\t\tbody:          reqBody,\n\t}, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tnonFlexibleHeaderSize := 14 + len(\"foo\")\n\tif !bytes.Equal(packet[nonFlexibleHeaderSize:], electLeadersRequestOneTopicV1) {\n\t\tt.Fatalf(\"expected V1 body to start immediately after request header\")\n\t}\n\n\treqBody.Version = 2\n\tpacket, err = encode(&request{\n\t\tcorrelationID: 123,\n\t\tclientID:      \"foo\",\n\t\tbody:          reqBody,\n\t}, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tflexibleHeaderSize := nonFlexibleHeaderSize + 1\n\tif packet[nonFlexibleHeaderSize] != 0 {\n\t\tt.Fatalf(\"expected V2 flexible request header to include empty tagged fields, got byte %d\", packet[nonFlexibleHeaderSize])\n\t}\n\tif !bytes.Equal(packet[flexibleHeaderSize:], electLeadersRequestOneTopicV2) {\n\t\tt.Fatalf(\"expected V2 body to start after flexible request header\")\n\t}\n}\n\nfunc TestElectLeadersResponseHeaderVersion(t *testing.T) {\n\tresponse := &ElectLeadersResponse{Version: 1}\n\tif response.headerVersion() != 0 {\n\t\tt.Fatalf(\"expected V1 response header version 0, got %d\", response.headerVersion())\n\t}\n\n\tresponse.Version = 2\n\tif response.headerVersion() != 1 {\n\t\tt.Fatalf(\"expected V2 response header version 1, got %d\", response.headerVersion())\n\t}\n}\n"
  },
  {
    "path": "elect_leaders_response.go",
    "content": "package sarama\n\nimport \"time\"\n\ntype PartitionResult struct {\n\tErrorCode    KError\n\tErrorMessage *string\n}\n\nfunc (b *PartitionResult) encode(pe packetEncoder, version int16) error {\n\tpe.putKError(b.ErrorCode)\n\tif err := pe.putNullableString(b.ErrorMessage); err != nil {\n\t\treturn err\n\t}\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (b *PartitionResult) decode(pd packetDecoder, version int16) (err error) {\n\tb.ErrorCode, err = pd.getKError()\n\tif err != nil {\n\t\treturn err\n\t}\n\tb.ErrorMessage, err = pd.getNullableString()\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\ntype ElectLeadersResponse struct {\n\tVersion                int16\n\tThrottleTimeMs         int32\n\tErrorCode              KError\n\tReplicaElectionResults map[string]map[int32]*PartitionResult\n}\n\nfunc (r *ElectLeadersResponse) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *ElectLeadersResponse) encode(pe packetEncoder) error {\n\tpe.putInt32(r.ThrottleTimeMs)\n\n\tif r.Version > 0 {\n\t\tpe.putKError(r.ErrorCode)\n\t}\n\n\tif err := pe.putArrayLength(len(r.ReplicaElectionResults)); err != nil {\n\t\treturn err\n\t}\n\tfor topic, partitions := range r.ReplicaElectionResults {\n\t\tif err := pe.putString(topic); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := pe.putArrayLength(len(partitions)); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor partition, result := range partitions {\n\t\t\tpe.putInt32(partition)\n\t\t\tif err := result.encode(pe, r.Version); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tpe.putEmptyTaggedFieldArray()\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (r *ElectLeadersResponse) decode(pd packetDecoder, version int16) (err error) {\n\tr.Version = version\n\tif r.ThrottleTimeMs, err = pd.getInt32(); err != nil {\n\t\treturn err\n\t}\n\tif r.Version > 0 {\n\t\tr.ErrorCode, err = pd.getKError()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tnumTopics, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr.ReplicaElectionResults = make(map[string]map[int32]*PartitionResult, numTopics)\n\tfor i := 0; i < numTopics; i++ {\n\t\ttopic, err := pd.getString()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tnumPartitions, err := pd.getArrayLength()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.ReplicaElectionResults[topic] = make(map[int32]*PartitionResult, numPartitions)\n\t\tfor j := 0; j < numPartitions; j++ {\n\t\t\tpartition, err := pd.getInt32()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tresult := new(PartitionResult)\n\t\t\tif err := result.decode(pd, r.Version); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tr.ReplicaElectionResults[topic][partition] = result\n\t\t}\n\t\tif _, err := pd.getEmptyTaggedFieldArray(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (r *ElectLeadersResponse) key() int16 {\n\treturn apiKeyElectLeaders\n}\n\nfunc (r *ElectLeadersResponse) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *ElectLeadersResponse) headerVersion() int16 {\n\tif r.isFlexible() {\n\t\treturn 1\n\t}\n\treturn 0\n}\n\nfunc (r *ElectLeadersResponse) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 2\n}\n\nfunc (r *ElectLeadersResponse) isFlexible() bool {\n\treturn r.isFlexibleVersion(r.Version)\n}\n\nfunc (r *ElectLeadersResponse) isFlexibleVersion(version int16) bool {\n\treturn version >= 2\n}\n\nfunc (r *ElectLeadersResponse) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 2:\n\t\treturn V2_4_0_0\n\tcase 1:\n\t\treturn V0_11_0_0\n\tcase 0:\n\t\treturn V0_10_0_0\n\tdefault:\n\t\treturn V2_4_0_0\n\t}\n}\n\nfunc (r *ElectLeadersResponse) throttleTime() time.Duration {\n\treturn time.Duration(r.ThrottleTimeMs) * time.Millisecond\n}\n"
  },
  {
    "path": "elect_leaders_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nvar electLeadersResponseOneTopic = []byte{\n\t0, 0, 3, 232, // ThrottleTimeMs 1000\n\t0, 0, // errorCode\n\t2,                         // number of topics\n\t6, 116, 111, 112, 105, 99, // topic name \"topic\"\n\t2,          // number of partitions\n\t0, 0, 0, 0, // partition 0\n\t0, 0, // empty tagged fields\n\t0, 0, // empty tagged fields\n\t0, 0, // empty tagged fields\n}\n\nfunc TestElectLeadersResponse(t *testing.T) {\n\tvar response = &ElectLeadersResponse{\n\t\tVersion:        int16(2),\n\t\tThrottleTimeMs: int32(1000),\n\t\tReplicaElectionResults: map[string]map[int32]*PartitionResult{\n\t\t\t\"topic\": {\n\t\t\t\t0: {},\n\t\t\t},\n\t\t},\n\t}\n\n\ttestResponse(t, \"one topic\", response, electLeadersResponseOneTopic)\n}\n"
  },
  {
    "path": "election_type.go",
    "content": "package sarama\n\ntype ElectionType int8\n\nconst (\n\t// PreferredElection constant type\n\tPreferredElection ElectionType = 0\n\t// UncleanElection constant type\n\tUncleanElection ElectionType = 1\n)\n"
  },
  {
    "path": "encoder_decoder.go",
    "content": "package sarama\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/rcrowley/go-metrics\"\n)\n\n// Encoder is the interface that wraps the basic Encode method.\n// Anything implementing Encoder can be turned into bytes using Kafka's encoding rules.\ntype encoder interface {\n\tencode(pe packetEncoder) error\n}\n\ntype encoderWithHeader interface {\n\tencoder\n\theaderVersion() int16\n}\n\n// Encode takes an Encoder and turns it into bytes while potentially recording metrics.\nfunc encode(e encoder, metricRegistry metrics.Registry) ([]byte, error) {\n\tif e == nil {\n\t\treturn nil, nil\n\t}\n\n\tvar prepEnc prepEncoder\n\tvar realEnc realEncoder\n\n\terr := e.encode(prepareFlexibleEncoder(&prepEnc, e))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif prepEnc.length < 0 || prepEnc.length > int(MaxRequestSize) {\n\t\treturn nil, PacketEncodingError{fmt.Sprintf(\"invalid request size (%d)\", prepEnc.length)}\n\t}\n\n\trealEnc.raw = make([]byte, prepEnc.length)\n\trealEnc.registry = metricRegistry\n\terr = e.encode(prepareFlexibleEncoder(&realEnc, e))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn realEnc.raw, nil\n}\n\n// decoder is the interface that wraps the basic Decode method.\n// Anything implementing Decoder can be extracted from bytes using Kafka's encoding rules.\ntype decoder interface {\n\tdecode(pd packetDecoder) error\n}\n\ntype versionedDecoder interface {\n\tdecode(pd packetDecoder, version int16) error\n}\n\ntype flexibleVersion interface {\n\tisFlexibleVersion(version int16) bool\n\tisFlexible() bool\n}\n\n// decode takes bytes and a decoder and fills the fields of the decoder from the bytes,\n// interpreted using Kafka's encoding rules.\nfunc decode(buf []byte, in decoder, metricRegistry metrics.Registry) error {\n\tif buf == nil {\n\t\treturn nil\n\t}\n\thelper := realDecoder{\n\t\traw:      buf,\n\t\tregistry: metricRegistry,\n\t}\n\terr := in.decode(&helper)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif helper.off != len(buf) {\n\t\treturn PacketDecodingError{fmt.Sprintf(\"invalid length: buf=%d decoded=%d %#v\", len(buf), helper.off, in)}\n\t}\n\n\treturn nil\n}\n\nfunc versionedDecode(buf []byte, in versionedDecoder, version int16, metricRegistry metrics.Registry) error {\n\tif buf == nil {\n\t\treturn nil\n\t}\n\n\thelper := prepareFlexibleDecoder(&realDecoder{\n\t\traw:      buf,\n\t\tregistry: metricRegistry,\n\t}, in, version)\n\terr := in.decode(helper, version)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif remaining := helper.remaining(); remaining != 0 {\n\t\treturn PacketDecodingError{\n\t\t\tInfo: fmt.Sprintf(\"invalid length len=%d remaining=%d\", len(buf), remaining),\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc prepareFlexibleDecoder(pd *realDecoder, in versionedDecoder, version int16) packetDecoder {\n\tif flexibleDecoder, ok := in.(flexibleVersion); ok && flexibleDecoder.isFlexibleVersion(version) {\n\t\treturn &realFlexibleDecoder{pd}\n\t}\n\treturn pd\n}\n\nfunc prepareFlexibleEncoder(pe packetEncoder, req encoder) packetEncoder {\n\tif flexibleEncoder, ok := req.(flexibleVersion); ok && flexibleEncoder.isFlexible() {\n\t\tswitch e := pe.(type) {\n\t\tcase *prepEncoder:\n\t\t\treturn &prepFlexibleEncoder{e}\n\t\tcase *realEncoder:\n\t\t\treturn &realFlexibleEncoder{e}\n\t\tdefault:\n\t\t\treturn pe\n\t\t}\n\t}\n\treturn pe\n}\n\nfunc downgradeFlexibleDecoder(pd packetDecoder) packetDecoder {\n\tif f, ok := pd.(*realFlexibleDecoder); ok {\n\t\treturn f.realDecoder\n\t}\n\treturn pd\n}\n"
  },
  {
    "path": "encoder_decoder_fuzz_test.go",
    "content": "//go:build go1.18 && !functional\n\npackage sarama\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n)\n\nfunc FuzzDecodeEncodeProduceRequest(f *testing.F) {\n\tfor _, seed := range [][]byte{\n\t\tproduceRequestEmpty,\n\t\tproduceRequestHeader,\n\t\tproduceRequestOneMessage,\n\t\tproduceRequestOneRecord,\n\t} {\n\t\tf.Add(seed)\n\t}\n\tf.Fuzz(func(t *testing.T, in []byte) {\n\t\tfor i := int16(0); i < 8; i++ {\n\t\t\treq := &ProduceRequest{}\n\t\t\terr := versionedDecode(in, req, i, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tout, err := encode(req, nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Logf(\"%v: encode: %v\", in, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif !bytes.Equal(in, out) {\n\t\t\t\tt.Logf(\"%v: not equal after round trip: %v\", in, out)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc FuzzDecodeEncodeFetchRequest(f *testing.F) {\n\tfor _, seed := range [][]byte{\n\t\tfetchRequestNoBlocks,\n\t\tfetchRequestWithProperties,\n\t\tfetchRequestOneBlock,\n\t\tfetchRequestOneBlockV4,\n\t\tfetchRequestOneBlockV11,\n\t} {\n\t\tf.Add(seed)\n\t}\n\tf.Fuzz(func(t *testing.T, in []byte) {\n\t\tfor i := int16(0); i < 11; i++ {\n\t\t\treq := &FetchRequest{}\n\t\t\terr := versionedDecode(in, req, i, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tout, err := encode(req, nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Logf(\"%v: encode: %v\", in, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif !bytes.Equal(in, out) {\n\t\t\t\tt.Logf(\"%v: not equal after round trip: %v\", in, out)\n\t\t\t}\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "end_txn_request.go",
    "content": "package sarama\n\ntype EndTxnRequest struct {\n\tVersion           int16\n\tTransactionalID   string\n\tProducerID        int64\n\tProducerEpoch     int16\n\tTransactionResult bool\n}\n\nfunc (a *EndTxnRequest) setVersion(v int16) {\n\ta.Version = v\n}\n\nfunc (a *EndTxnRequest) encode(pe packetEncoder) error {\n\tif err := pe.putString(a.TransactionalID); err != nil {\n\t\treturn err\n\t}\n\n\tpe.putInt64(a.ProducerID)\n\n\tpe.putInt16(a.ProducerEpoch)\n\n\tpe.putBool(a.TransactionResult)\n\n\treturn nil\n}\n\nfunc (a *EndTxnRequest) decode(pd packetDecoder, version int16) (err error) {\n\tif a.TransactionalID, err = pd.getString(); err != nil {\n\t\treturn err\n\t}\n\tif a.ProducerID, err = pd.getInt64(); err != nil {\n\t\treturn err\n\t}\n\tif a.ProducerEpoch, err = pd.getInt16(); err != nil {\n\t\treturn err\n\t}\n\tif a.TransactionResult, err = pd.getBool(); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (a *EndTxnRequest) key() int16 {\n\treturn apiKeyEndTxn\n}\n\nfunc (a *EndTxnRequest) version() int16 {\n\treturn a.Version\n}\n\nfunc (r *EndTxnRequest) headerVersion() int16 {\n\treturn 1\n}\n\nfunc (a *EndTxnRequest) isValidVersion() bool {\n\treturn a.Version >= 0 && a.Version <= 2\n}\n\nfunc (a *EndTxnRequest) requiredVersion() KafkaVersion {\n\tswitch a.Version {\n\tcase 2:\n\t\treturn V2_7_0_0\n\tcase 1:\n\t\treturn V2_0_0_0\n\tdefault:\n\t\treturn V0_11_0_0\n\t}\n}\n"
  },
  {
    "path": "end_txn_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nvar endTxnRequest = []byte{\n\t0, 3, 't', 'x', 'n',\n\t0, 0, 0, 0, 0, 0, 31, 64,\n\t0, 1,\n\t1,\n}\n\nfunc TestEndTxnRequest(t *testing.T) {\n\treq := &EndTxnRequest{\n\t\tTransactionalID:   \"txn\",\n\t\tProducerID:        8000,\n\t\tProducerEpoch:     1,\n\t\tTransactionResult: true,\n\t}\n\n\ttestRequest(t, \"\", req, endTxnRequest)\n}\n"
  },
  {
    "path": "end_txn_response.go",
    "content": "package sarama\n\nimport (\n\t\"time\"\n)\n\ntype EndTxnResponse struct {\n\tVersion      int16\n\tThrottleTime time.Duration\n\tErr          KError\n}\n\nfunc (e *EndTxnResponse) setVersion(v int16) {\n\te.Version = v\n}\n\nfunc (e *EndTxnResponse) encode(pe packetEncoder) error {\n\tpe.putDurationMs(e.ThrottleTime)\n\tpe.putKError(e.Err)\n\treturn nil\n}\n\nfunc (e *EndTxnResponse) decode(pd packetDecoder, version int16) (err error) {\n\tif e.ThrottleTime, err = pd.getDurationMs(); err != nil {\n\t\treturn err\n\t}\n\n\te.Err, err = pd.getKError()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (e *EndTxnResponse) key() int16 {\n\treturn apiKeyEndTxn\n}\n\nfunc (e *EndTxnResponse) version() int16 {\n\treturn e.Version\n}\n\nfunc (r *EndTxnResponse) headerVersion() int16 {\n\treturn 0\n}\n\nfunc (e *EndTxnResponse) isValidVersion() bool {\n\treturn e.Version >= 0 && e.Version <= 2\n}\n\nfunc (e *EndTxnResponse) requiredVersion() KafkaVersion {\n\tswitch e.Version {\n\tcase 2:\n\t\treturn V2_7_0_0\n\tcase 1:\n\t\treturn V2_0_0_0\n\tdefault:\n\t\treturn V0_11_0_0\n\t}\n}\n\nfunc (r *EndTxnResponse) throttleTime() time.Duration {\n\treturn r.ThrottleTime\n}\n"
  },
  {
    "path": "end_txn_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nvar endTxnResponse = []byte{\n\t0, 0, 0, 100,\n\t0, 49,\n}\n\nfunc TestEndTxnResponse(t *testing.T) {\n\tresp := &EndTxnResponse{\n\t\tThrottleTime: 100 * time.Millisecond,\n\t\tErr:          ErrInvalidProducerIDMapping,\n\t}\n\n\ttestResponse(t, \"\", resp, endTxnResponse)\n}\n"
  },
  {
    "path": "entrypoint.sh",
    "content": "#!/bin/bash\n\nset -eu\nset -o pipefail\n\nKAFKA_VERSION=\"${KAFKA_VERSION:-3.9.1}\"\nKAFKA_HOME=\"/opt/kafka-${KAFKA_VERSION}\"\n\nif [ ! -d \"${KAFKA_HOME}\" ]; then\n    echo 'Error: KAFKA_VERSION '$KAFKA_VERSION' not available in this image at '$KAFKA_HOME\n    exit 1\nfi\n\ncd \"${KAFKA_HOME}\" || exit 1\n\n# discard all empty/commented lines from default config and copy to /tmp\nsed -e '/^#/d' -e '/^$/d' config/server.properties >/tmp/server.properties\n\necho \"########################################################################\" >>/tmp/server.properties\n\n# emulate kafka_configure_from_environment_variables from bitnami/bitnami-docker-kafka\nfor var in \"${!KAFKA_CFG_@}\"; do\n    key=\"$(echo \"$var\" | sed -e 's/^KAFKA_CFG_//g' -e 's/_/\\./g' -e 's/.*/\\L&/')\"\n    sed -e '/^'$key'/d' -i\"\" /tmp/server.properties\n    value=\"${!var}\"\n    echo \"$key=$value\" >>/tmp/server.properties\ndone\n\n# use KRaft if KAFKA_VERSION is 4.0.0 or newer\nif printf \"%s\\n4.0.0\" \"$KAFKA_VERSION\" | sort -V | head -n1 | grep -q \"^4.0.0$\"; then\n  # remove zookeeper-only options from server.properties and setup storage\n  sed -e '/zookeeper.connect/d' \\\n      -i /tmp/server.properties\n  bin/kafka-storage.sh format -t \"$KAFKA_CFG_CLUSTER_ID\" -c /tmp/server.properties --ignore-formatted\nelse\n  # remove KRaft-only options from server.properties\n  sed -e '/process.roles/d' \\\n      -e '/controller.quorum.voters/d' \\\n      -e '/controller.listener.names/d' \\\n      -e '/cluster.id/d' \\\n      -i /tmp/server.properties\nfi\n\nsort /tmp/server.properties\n\nexec bin/kafka-server-start.sh /tmp/server.properties\n"
  },
  {
    "path": "errors.go",
    "content": "package sarama\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n)\n\n// ErrOutOfBrokers is the error returned when the client has run out of brokers to talk to because all of them errored\n// or otherwise failed to respond.\nvar ErrOutOfBrokers = errors.New(\"kafka: client has run out of available brokers to talk to\")\n\n// ErrBrokerNotFound is the error returned when there's no broker found for the requested ID.\nvar ErrBrokerNotFound = errors.New(\"kafka: broker for ID is not found\")\n\n// ErrClosedClient is the error returned when a method is called on a client that has been closed.\nvar ErrClosedClient = errors.New(\"kafka: tried to use a client that was closed\")\n\n// ErrIncompleteResponse is the error returned when the server returns a syntactically valid response, but it does\n// not contain the expected information.\nvar ErrIncompleteResponse = errors.New(\"kafka: response did not contain all the expected topic/partition blocks\")\n\n// ErrInvalidPartition is the error returned when a partitioner returns an invalid partition index\n// (meaning one outside of the range [0...numPartitions-1]).\nvar ErrInvalidPartition = errors.New(\"kafka: partitioner returned an invalid partition index\")\n\n// ErrAlreadyConnected is the error returned when calling Open() on a Broker that is already connected or connecting.\nvar ErrAlreadyConnected = errors.New(\"kafka: broker connection already initiated\")\n\n// ErrNotConnected is the error returned when trying to send or call Close() on a Broker that is not connected.\nvar ErrNotConnected = errors.New(\"kafka: broker not connected\")\n\n// ErrInsufficientData is returned when decoding and the packet is truncated. This can be expected\n// when requesting messages, since as an optimization the server is allowed to return a partial message at the end\n// of the message set.\nvar ErrInsufficientData = errors.New(\"kafka: insufficient data to decode packet, more bytes expected\")\n\n// ErrShuttingDown is returned when a producer receives a message during shutdown.\nvar ErrShuttingDown = errors.New(\"kafka: message received by producer in process of shutting down\")\n\n// ErrMessageTooLarge is returned when the next message to consume is larger than the configured Consumer.Fetch.Max\nvar ErrMessageTooLarge = errors.New(\"kafka: message is larger than Consumer.Fetch.Max\")\n\n// ErrConsumerOffsetNotAdvanced is returned when a partition consumer didn't advance its offset after parsing\n// a RecordBatch.\nvar ErrConsumerOffsetNotAdvanced = errors.New(\"kafka: consumer offset was not advanced after a RecordBatch\")\n\n// ErrControllerNotAvailable is returned when server didn't give correct controller id. May be kafka server's version\n// is lower than 0.10.0.0.\nvar ErrControllerNotAvailable = errors.New(\"kafka: controller is not available\")\n\n// ErrNoTopicsToUpdateMetadata is returned when Meta.Full is set to false but no specific topics were found to update\n// the metadata.\nvar ErrNoTopicsToUpdateMetadata = errors.New(\"kafka: no specific topics to update metadata\")\n\n// ErrUnknownScramMechanism is returned when user tries to AlterUserScramCredentials with unknown SCRAM mechanism\nvar ErrUnknownScramMechanism = errors.New(\"kafka: unknown SCRAM mechanism provided\")\n\n// ErrReassignPartitions is returned when altering partition assignments for a topic fails\nvar ErrReassignPartitions = errors.New(\"failed to reassign partitions for topic\")\n\n// ErrDeleteRecords is the type of error returned when fail to delete the required records\nvar ErrDeleteRecords = errors.New(\"kafka server: failed to delete records\")\n\n// ErrCreateACLs is the type of error returned when ACL creation failed\nvar ErrCreateACLs = errors.New(\"kafka server: failed to create one or more ACL rules\")\n\n// ErrAddPartitionsToTxn is returned when AddPartitionsToTxn failed multiple times\nvar ErrAddPartitionsToTxn = errors.New(\"transaction manager: failed to send partitions to transaction\")\n\n// ErrTxnOffsetCommit is returned when TxnOffsetCommit failed multiple times\nvar ErrTxnOffsetCommit = errors.New(\"transaction manager: failed to send offsets to transaction\")\n\n// ErrTransactionNotReady when transaction status is invalid for the current action.\nvar ErrTransactionNotReady = errors.New(\"transaction manager: transaction is not ready\")\n\n// ErrNonTransactedProducer when calling BeginTxn, CommitTxn or AbortTxn on a non transactional producer.\nvar ErrNonTransactedProducer = errors.New(\"transaction manager: you need to add TransactionalID to producer\")\n\n// ErrTransitionNotAllowed when txnmgr state transition is not valid.\nvar ErrTransitionNotAllowed = errors.New(\"transaction manager: invalid transition attempted\")\n\n// ErrCannotTransitionNilError when transition is attempted with an nil error.\nvar ErrCannotTransitionNilError = errors.New(\"transaction manager: cannot transition with a nil error\")\n\n// ErrTxnUnableToParseResponse when response is nil\nvar ErrTxnUnableToParseResponse = errors.New(\"transaction manager: unable to parse response\")\n\n// ErrUnknownMessage when the protocol message key is not recognized\nvar ErrUnknownMessage = errors.New(\"kafka: unknown protocol message key\")\n\n// MultiErrorFormat specifies the formatter applied to format multierrors.\n//\n// Deprecated: Please use [errors.Join] instead.\nfunc MultiErrorFormat(es []error) string {\n\tif len(es) == 1 {\n\t\treturn es[0].Error()\n\t}\n\n\tpoints := make([]string, len(es))\n\tfor i, err := range es {\n\t\tpoints[i] = fmt.Sprintf(\"* %s\", err)\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"%d errors occurred:\\n\\t%s\\n\",\n\t\tlen(es), strings.Join(points, \"\\n\\t\"))\n}\n\ntype sentinelError struct {\n\tsentinel error\n\twrapped  error\n}\n\nfunc (err sentinelError) Error() string {\n\tif err.wrapped != nil {\n\t\treturn fmt.Sprintf(\"%s: %v\", err.sentinel, err.wrapped)\n\t} else {\n\t\treturn fmt.Sprintf(\"%s\", err.sentinel)\n\t}\n}\n\nfunc (err sentinelError) Is(target error) bool {\n\treturn errors.Is(err.sentinel, target) || errors.Is(err.wrapped, target)\n}\n\nfunc (err sentinelError) Unwrap() error {\n\treturn err.wrapped\n}\n\nfunc Wrap(sentinel error, wrapped ...error) sentinelError {\n\treturn sentinelError{sentinel: sentinel, wrapped: errors.Join(wrapped...)}\n}\n\n// PacketEncodingError is returned from a failure while encoding a Kafka packet. This can happen, for example,\n// if you try to encode a string over 2^15 characters in length, since Kafka's encoding rules do not permit that.\ntype PacketEncodingError struct {\n\tInfo string\n}\n\nfunc (err PacketEncodingError) Error() string {\n\treturn fmt.Sprintf(\"kafka: error encoding packet: %s\", err.Info)\n}\n\n// PacketDecodingError is returned when there was an error (other than truncated data) decoding the Kafka broker's response.\n// This can be a bad CRC or length field, or any other invalid value.\ntype PacketDecodingError struct {\n\tInfo string\n}\n\nfunc (err PacketDecodingError) Error() string {\n\treturn fmt.Sprintf(\"kafka: error decoding packet: %s\", err.Info)\n}\n\n// ConfigurationError is the type of error returned from a constructor (e.g. NewClient, or NewConsumer)\n// when the specified configuration is invalid.\ntype ConfigurationError string\n\nfunc (err ConfigurationError) Error() string {\n\treturn \"kafka: invalid configuration (\" + string(err) + \")\"\n}\n\n// KError is the type of error that can be returned directly by the Kafka broker.\n// See https://cwiki.apache.org/confluence/display/KAFKA/A+Guide+To+The+Kafka+Protocol#AGuideToTheKafkaProtocol-ErrorCodes\ntype KError int16\n\n// Numeric error codes returned by the Kafka server.\nconst (\n\tErrUnknown                            KError = -1 // Errors.UNKNOWN_SERVER_ERROR\n\tErrNoError                            KError = 0  // Errors.NONE\n\tErrOffsetOutOfRange                   KError = 1  // Errors.OFFSET_OUT_OF_RANGE\n\tErrInvalidMessage                     KError = 2  // Errors.CORRUPT_MESSAGE\n\tErrUnknownTopicOrPartition            KError = 3  // Errors.UNKNOWN_TOPIC_OR_PARTITION\n\tErrInvalidMessageSize                 KError = 4  // Errors.INVALID_FETCH_SIZE\n\tErrLeaderNotAvailable                 KError = 5  // Errors.LEADER_NOT_AVAILABLE\n\tErrNotLeaderForPartition              KError = 6  // Errors.NOT_LEADER_OR_FOLLOWER\n\tErrRequestTimedOut                    KError = 7  // Errors.REQUEST_TIMED_OUT\n\tErrBrokerNotAvailable                 KError = 8  // Errors.BROKER_NOT_AVAILABLE\n\tErrReplicaNotAvailable                KError = 9  // Errors.REPLICA_NOT_AVAILABLE\n\tErrMessageSizeTooLarge                KError = 10 // Errors.MESSAGE_TOO_LARGE\n\tErrStaleControllerEpochCode           KError = 11 // Errors.STALE_CONTROLLER_EPOCH\n\tErrOffsetMetadataTooLarge             KError = 12 // Errors.OFFSET_METADATA_TOO_LARGE\n\tErrNetworkException                   KError = 13 // Errors.NETWORK_EXCEPTION\n\tErrOffsetsLoadInProgress              KError = 14 // Errors.COORDINATOR_LOAD_IN_PROGRESS\n\tErrConsumerCoordinatorNotAvailable    KError = 15 // Errors.COORDINATOR_NOT_AVAILABLE\n\tErrNotCoordinatorForConsumer          KError = 16 // Errors.NOT_COORDINATOR\n\tErrInvalidTopic                       KError = 17 // Errors.INVALID_TOPIC_EXCEPTION\n\tErrMessageSetSizeTooLarge             KError = 18 // Errors.RECORD_LIST_TOO_LARGE\n\tErrNotEnoughReplicas                  KError = 19 // Errors.NOT_ENOUGH_REPLICAS\n\tErrNotEnoughReplicasAfterAppend       KError = 20 // Errors.NOT_ENOUGH_REPLICAS_AFTER_APPEND\n\tErrInvalidRequiredAcks                KError = 21 // Errors.INVALID_REQUIRED_ACKS\n\tErrIllegalGeneration                  KError = 22 // Errors.ILLEGAL_GENERATION\n\tErrInconsistentGroupProtocol          KError = 23 // Errors.INCONSISTENT_GROUP_PROTOCOL\n\tErrInvalidGroupId                     KError = 24 // Errors.INVALID_GROUP_ID\n\tErrUnknownMemberId                    KError = 25 // Errors.UNKNOWN_MEMBER_ID\n\tErrInvalidSessionTimeout              KError = 26 // Errors.INVALID_SESSION_TIMEOUT\n\tErrRebalanceInProgress                KError = 27 // Errors.REBALANCE_IN_PROGRESS\n\tErrInvalidCommitOffsetSize            KError = 28 // Errors.INVALID_COMMIT_OFFSET_SIZE\n\tErrTopicAuthorizationFailed           KError = 29 // Errors.TOPIC_AUTHORIZATION_FAILED\n\tErrGroupAuthorizationFailed           KError = 30 // Errors.GROUP_AUTHORIZATION_FAILED\n\tErrClusterAuthorizationFailed         KError = 31 // Errors.CLUSTER_AUTHORIZATION_FAILED\n\tErrInvalidTimestamp                   KError = 32 // Errors.INVALID_TIMESTAMP\n\tErrUnsupportedSASLMechanism           KError = 33 // Errors.UNSUPPORTED_SASL_MECHANISM\n\tErrIllegalSASLState                   KError = 34 // Errors.ILLEGAL_SASL_STATE\n\tErrUnsupportedVersion                 KError = 35 // Errors.UNSUPPORTED_VERSION\n\tErrTopicAlreadyExists                 KError = 36 // Errors.TOPIC_ALREADY_EXISTS\n\tErrInvalidPartitions                  KError = 37 // Errors.INVALID_PARTITIONS\n\tErrInvalidReplicationFactor           KError = 38 // Errors.INVALID_REPLICATION_FACTOR\n\tErrInvalidReplicaAssignment           KError = 39 // Errors.INVALID_REPLICA_ASSIGNMENT\n\tErrInvalidConfig                      KError = 40 // Errors.INVALID_CONFIG\n\tErrNotController                      KError = 41 // Errors.NOT_CONTROLLER\n\tErrInvalidRequest                     KError = 42 // Errors.INVALID_REQUEST\n\tErrUnsupportedForMessageFormat        KError = 43 // Errors.UNSUPPORTED_FOR_MESSAGE_FORMAT\n\tErrPolicyViolation                    KError = 44 // Errors.POLICY_VIOLATION\n\tErrOutOfOrderSequenceNumber           KError = 45 // Errors.OUT_OF_ORDER_SEQUENCE_NUMBER\n\tErrDuplicateSequenceNumber            KError = 46 // Errors.DUPLICATE_SEQUENCE_NUMBER\n\tErrInvalidProducerEpoch               KError = 47 // Errors.INVALID_PRODUCER_EPOCH\n\tErrInvalidTxnState                    KError = 48 // Errors.INVALID_TXN_STATE\n\tErrInvalidProducerIDMapping           KError = 49 // Errors.INVALID_PRODUCER_ID_MAPPING\n\tErrInvalidTransactionTimeout          KError = 50 // Errors.INVALID_TRANSACTION_TIMEOUT\n\tErrConcurrentTransactions             KError = 51 // Errors.CONCURRENT_TRANSACTIONS\n\tErrTransactionCoordinatorFenced       KError = 52 // Errors.TRANSACTION_COORDINATOR_FENCED\n\tErrTransactionalIDAuthorizationFailed KError = 53 // Errors.TRANSACTIONAL_ID_AUTHORIZATION_FAILED\n\tErrSecurityDisabled                   KError = 54 // Errors.SECURITY_DISABLED\n\tErrOperationNotAttempted              KError = 55 // Errors.OPERATION_NOT_ATTEMPTED\n\tErrKafkaStorageError                  KError = 56 // Errors.KAFKA_STORAGE_ERROR\n\tErrLogDirNotFound                     KError = 57 // Errors.LOG_DIR_NOT_FOUND\n\tErrSASLAuthenticationFailed           KError = 58 // Errors.SASL_AUTHENTICATION_FAILED\n\tErrUnknownProducerID                  KError = 59 // Errors.UNKNOWN_PRODUCER_ID\n\tErrReassignmentInProgress             KError = 60 // Errors.REASSIGNMENT_IN_PROGRESS\n\tErrDelegationTokenAuthDisabled        KError = 61 // Errors.DELEGATION_TOKEN_AUTH_DISABLED\n\tErrDelegationTokenNotFound            KError = 62 // Errors.DELEGATION_TOKEN_NOT_FOUND\n\tErrDelegationTokenOwnerMismatch       KError = 63 // Errors.DELEGATION_TOKEN_OWNER_MISMATCH\n\tErrDelegationTokenRequestNotAllowed   KError = 64 // Errors.DELEGATION_TOKEN_REQUEST_NOT_ALLOWED\n\tErrDelegationTokenAuthorizationFailed KError = 65 // Errors.DELEGATION_TOKEN_AUTHORIZATION_FAILED\n\tErrDelegationTokenExpired             KError = 66 // Errors.DELEGATION_TOKEN_EXPIRED\n\tErrInvalidPrincipalType               KError = 67 // Errors.INVALID_PRINCIPAL_TYPE\n\tErrNonEmptyGroup                      KError = 68 // Errors.NON_EMPTY_GROUP\n\tErrGroupIDNotFound                    KError = 69 // Errors.GROUP_ID_NOT_FOUND\n\tErrFetchSessionIDNotFound             KError = 70 // Errors.FETCH_SESSION_ID_NOT_FOUND\n\tErrInvalidFetchSessionEpoch           KError = 71 // Errors.INVALID_FETCH_SESSION_EPOCH\n\tErrListenerNotFound                   KError = 72 // Errors.LISTENER_NOT_FOUND\n\tErrTopicDeletionDisabled              KError = 73 // Errors.TOPIC_DELETION_DISABLED\n\tErrFencedLeaderEpoch                  KError = 74 // Errors.FENCED_LEADER_EPOCH\n\tErrUnknownLeaderEpoch                 KError = 75 // Errors.UNKNOWN_LEADER_EPOCH\n\tErrUnsupportedCompressionType         KError = 76 // Errors.UNSUPPORTED_COMPRESSION_TYPE\n\tErrStaleBrokerEpoch                   KError = 77 // Errors.STALE_BROKER_EPOCH\n\tErrOffsetNotAvailable                 KError = 78 // Errors.OFFSET_NOT_AVAILABLE\n\tErrMemberIdRequired                   KError = 79 // Errors.MEMBER_ID_REQUIRED\n\tErrPreferredLeaderNotAvailable        KError = 80 // Errors.PREFERRED_LEADER_NOT_AVAILABLE\n\tErrGroupMaxSizeReached                KError = 81 // Errors.GROUP_MAX_SIZE_REACHED\n\tErrFencedInstancedId                  KError = 82 // Errors.FENCED_INSTANCE_ID\n\tErrEligibleLeadersNotAvailable        KError = 83 // Errors.ELIGIBLE_LEADERS_NOT_AVAILABLE\n\tErrElectionNotNeeded                  KError = 84 // Errors.ELECTION_NOT_NEEDED\n\tErrNoReassignmentInProgress           KError = 85 // Errors.NO_REASSIGNMENT_IN_PROGRESS\n\tErrGroupSubscribedToTopic             KError = 86 // Errors.GROUP_SUBSCRIBED_TO_TOPIC\n\tErrInvalidRecord                      KError = 87 // Errors.INVALID_RECORD\n\tErrUnstableOffsetCommit               KError = 88 // Errors.UNSTABLE_OFFSET_COMMIT\n\tErrThrottlingQuotaExceeded            KError = 89 // Errors.THROTTLING_QUOTA_EXCEEDED\n\tErrProducerFenced                     KError = 90 // Errors.PRODUCER_FENCED\n)\n\nfunc (err KError) Error() string {\n\t// Error messages stolen/adapted from\n\t// https://kafka.apache.org/protocol#protocol_error_codes\n\tswitch err {\n\tcase ErrNoError:\n\t\treturn \"kafka server: Not an error, why are you printing me?\"\n\tcase ErrUnknown:\n\t\treturn \"kafka server: Unexpected (unknown?) server error\"\n\tcase ErrOffsetOutOfRange:\n\t\treturn \"kafka server: The requested offset is outside the range of offsets maintained by the server for the given topic/partition\"\n\tcase ErrInvalidMessage:\n\t\treturn \"kafka server: Message contents does not match its CRC\"\n\tcase ErrUnknownTopicOrPartition:\n\t\treturn \"kafka server: Request was for a topic or partition that does not exist on this broker\"\n\tcase ErrInvalidMessageSize:\n\t\treturn \"kafka server: The message has a negative size\"\n\tcase ErrLeaderNotAvailable:\n\t\treturn \"kafka server: In the middle of a leadership election, there is currently no leader for this partition and hence it is unavailable for writes\"\n\tcase ErrNotLeaderForPartition:\n\t\treturn \"kafka server: Tried to send a message to a replica that is not the leader for some partition. Your metadata is out of date\"\n\tcase ErrRequestTimedOut:\n\t\treturn \"kafka server: Request exceeded the user-specified time limit in the request\"\n\tcase ErrBrokerNotAvailable:\n\t\treturn \"kafka server: Broker not available. Not a client facing error, we should never receive this!!!\"\n\tcase ErrReplicaNotAvailable:\n\t\treturn \"kafka server: Replica information not available, one or more brokers are down\"\n\tcase ErrMessageSizeTooLarge:\n\t\treturn \"kafka server: Message was too large, server rejected it to avoid allocation error\"\n\tcase ErrStaleControllerEpochCode:\n\t\treturn \"kafka server: StaleControllerEpochCode (internal error code for broker-to-broker communication)\"\n\tcase ErrOffsetMetadataTooLarge:\n\t\treturn \"kafka server: Specified a string larger than the configured maximum for offset metadata\"\n\tcase ErrNetworkException:\n\t\treturn \"kafka server: The server disconnected before a response was received\"\n\tcase ErrOffsetsLoadInProgress:\n\t\treturn \"kafka server: The coordinator is still loading offsets and cannot currently process requests\"\n\tcase ErrConsumerCoordinatorNotAvailable:\n\t\treturn \"kafka server: The coordinator is not available\"\n\tcase ErrNotCoordinatorForConsumer:\n\t\treturn \"kafka server: Request was for a consumer group that is not coordinated by this broker\"\n\tcase ErrInvalidTopic:\n\t\treturn \"kafka server: The request attempted to perform an operation on an invalid topic\"\n\tcase ErrMessageSetSizeTooLarge:\n\t\treturn \"kafka server: The request included message batch larger than the configured segment size on the server\"\n\tcase ErrNotEnoughReplicas:\n\t\treturn \"kafka server: Messages are rejected since there are fewer in-sync replicas than required\"\n\tcase ErrNotEnoughReplicasAfterAppend:\n\t\treturn \"kafka server: Messages are written to the log, but to fewer in-sync replicas than required\"\n\tcase ErrInvalidRequiredAcks:\n\t\treturn \"kafka server: The number of required acks is invalid (should be either -1, 0, or 1)\"\n\tcase ErrIllegalGeneration:\n\t\treturn \"kafka server: The provided generation id is not the current generation\"\n\tcase ErrInconsistentGroupProtocol:\n\t\treturn \"kafka server: The provider group protocol type is incompatible with the other members\"\n\tcase ErrInvalidGroupId:\n\t\treturn \"kafka server: The provided group id was empty\"\n\tcase ErrUnknownMemberId:\n\t\treturn \"kafka server: The provided member is not known in the current generation\"\n\tcase ErrInvalidSessionTimeout:\n\t\treturn \"kafka server: The provided session timeout is outside the allowed range\"\n\tcase ErrRebalanceInProgress:\n\t\treturn \"kafka server: A rebalance for the group is in progress. Please re-join the group\"\n\tcase ErrInvalidCommitOffsetSize:\n\t\treturn \"kafka server: The provided commit metadata was too large\"\n\tcase ErrTopicAuthorizationFailed:\n\t\treturn \"kafka server: The client is not authorized to access this topic\"\n\tcase ErrGroupAuthorizationFailed:\n\t\treturn \"kafka server: The client is not authorized to access this group\"\n\tcase ErrClusterAuthorizationFailed:\n\t\treturn \"kafka server: The client is not authorized to send this request type\"\n\tcase ErrInvalidTimestamp:\n\t\treturn \"kafka server: The timestamp of the message is out of acceptable range\"\n\tcase ErrUnsupportedSASLMechanism:\n\t\treturn \"kafka server: The broker does not support the requested SASL mechanism\"\n\tcase ErrIllegalSASLState:\n\t\treturn \"kafka server: Request is not valid given the current SASL state\"\n\tcase ErrUnsupportedVersion:\n\t\treturn \"kafka server: The version of API is not supported\"\n\tcase ErrTopicAlreadyExists:\n\t\treturn \"kafka server: Topic with this name already exists\"\n\tcase ErrInvalidPartitions:\n\t\treturn \"kafka server: Number of partitions is invalid\"\n\tcase ErrInvalidReplicationFactor:\n\t\treturn \"kafka server: Replication-factor is invalid\"\n\tcase ErrInvalidReplicaAssignment:\n\t\treturn \"kafka server: Replica assignment is invalid\"\n\tcase ErrInvalidConfig:\n\t\treturn \"kafka server: Configuration is invalid\"\n\tcase ErrNotController:\n\t\treturn \"kafka server: This is not the correct controller for this cluster\"\n\tcase ErrInvalidRequest:\n\t\treturn \"kafka server: This most likely occurs because of a request being malformed by the client library or the message was sent to an incompatible broker. See the broker logs for more details\"\n\tcase ErrUnsupportedForMessageFormat:\n\t\treturn \"kafka server: The requested operation is not supported by the message format version\"\n\tcase ErrPolicyViolation:\n\t\treturn \"kafka server: Request parameters do not satisfy the configured policy\"\n\tcase ErrOutOfOrderSequenceNumber:\n\t\treturn \"kafka server: The broker received an out of order sequence number\"\n\tcase ErrDuplicateSequenceNumber:\n\t\treturn \"kafka server: The broker received a duplicate sequence number\"\n\tcase ErrInvalidProducerEpoch:\n\t\treturn \"kafka server: Producer attempted an operation with an old epoch\"\n\tcase ErrInvalidTxnState:\n\t\treturn \"kafka server: The producer attempted a transactional operation in an invalid state\"\n\tcase ErrInvalidProducerIDMapping:\n\t\treturn \"kafka server: The producer attempted to use a producer id which is not currently assigned to its transactional id\"\n\tcase ErrInvalidTransactionTimeout:\n\t\treturn \"kafka server: The transaction timeout is larger than the maximum value allowed by the broker (as configured by max.transaction.timeout.ms)\"\n\tcase ErrConcurrentTransactions:\n\t\treturn \"kafka server: The producer attempted to update a transaction while another concurrent operation on the same transaction was ongoing\"\n\tcase ErrTransactionCoordinatorFenced:\n\t\treturn \"kafka server: The transaction coordinator sending a WriteTxnMarker is no longer the current coordinator for a given producer\"\n\tcase ErrTransactionalIDAuthorizationFailed:\n\t\treturn \"kafka server: Transactional ID authorization failed\"\n\tcase ErrSecurityDisabled:\n\t\treturn \"kafka server: Security features are disabled\"\n\tcase ErrOperationNotAttempted:\n\t\treturn \"kafka server: The broker did not attempt to execute this operation\"\n\tcase ErrKafkaStorageError:\n\t\treturn \"kafka server: Disk error when trying to access log file on the disk\"\n\tcase ErrLogDirNotFound:\n\t\treturn \"kafka server: The specified log directory is not found in the broker config\"\n\tcase ErrSASLAuthenticationFailed:\n\t\treturn \"kafka server: SASL Authentication failed\"\n\tcase ErrUnknownProducerID:\n\t\treturn \"kafka server: The broker could not locate the producer metadata associated with the Producer ID\"\n\tcase ErrReassignmentInProgress:\n\t\treturn \"kafka server: A partition reassignment is in progress\"\n\tcase ErrDelegationTokenAuthDisabled:\n\t\treturn \"kafka server: Delegation Token feature is not enabled\"\n\tcase ErrDelegationTokenNotFound:\n\t\treturn \"kafka server: Delegation Token is not found on server\"\n\tcase ErrDelegationTokenOwnerMismatch:\n\t\treturn \"kafka server: Specified Principal is not valid Owner/Renewer\"\n\tcase ErrDelegationTokenRequestNotAllowed:\n\t\treturn \"kafka server: Delegation Token requests are not allowed on PLAINTEXT/1-way SSL channels and on delegation token authenticated channels\"\n\tcase ErrDelegationTokenAuthorizationFailed:\n\t\treturn \"kafka server: Delegation Token authorization failed\"\n\tcase ErrDelegationTokenExpired:\n\t\treturn \"kafka server: Delegation Token is expired\"\n\tcase ErrInvalidPrincipalType:\n\t\treturn \"kafka server: Supplied principalType is not supported\"\n\tcase ErrNonEmptyGroup:\n\t\treturn \"kafka server: The group is not empty\"\n\tcase ErrGroupIDNotFound:\n\t\treturn \"kafka server: The group id does not exist\"\n\tcase ErrFetchSessionIDNotFound:\n\t\treturn \"kafka server: The fetch session ID was not found\"\n\tcase ErrInvalidFetchSessionEpoch:\n\t\treturn \"kafka server: The fetch session epoch is invalid\"\n\tcase ErrListenerNotFound:\n\t\treturn \"kafka server: There is no listener on the leader broker that matches the listener on which metadata request was processed\"\n\tcase ErrTopicDeletionDisabled:\n\t\treturn \"kafka server: Topic deletion is disabled\"\n\tcase ErrFencedLeaderEpoch:\n\t\treturn \"kafka server: The leader epoch in the request is older than the epoch on the broker\"\n\tcase ErrUnknownLeaderEpoch:\n\t\treturn \"kafka server: The leader epoch in the request is newer than the epoch on the broker\"\n\tcase ErrUnsupportedCompressionType:\n\t\treturn \"kafka server: The requesting client does not support the compression type of given partition\"\n\tcase ErrStaleBrokerEpoch:\n\t\treturn \"kafka server: Broker epoch has changed\"\n\tcase ErrOffsetNotAvailable:\n\t\treturn \"kafka server: The leader high watermark has not caught up from a recent leader election so the offsets cannot be guaranteed to be monotonically increasing\"\n\tcase ErrMemberIdRequired:\n\t\treturn \"kafka server: The group member needs to have a valid member id before actually entering a consumer group\"\n\tcase ErrPreferredLeaderNotAvailable:\n\t\treturn \"kafka server: The preferred leader was not available\"\n\tcase ErrGroupMaxSizeReached:\n\t\treturn \"kafka server: Consumer group The consumer group has reached its max size. already has the configured maximum number of members\"\n\tcase ErrFencedInstancedId:\n\t\treturn \"kafka server: The broker rejected this static consumer since another consumer with the same group.instance.id has registered with a different member.id\"\n\tcase ErrEligibleLeadersNotAvailable:\n\t\treturn \"kafka server: Eligible topic partition leaders are not available\"\n\tcase ErrElectionNotNeeded:\n\t\treturn \"kafka server: Leader election not needed for topic partition\"\n\tcase ErrNoReassignmentInProgress:\n\t\treturn \"kafka server: No partition reassignment is in progress\"\n\tcase ErrGroupSubscribedToTopic:\n\t\treturn \"kafka server: Deleting offsets of a topic is forbidden while the consumer group is actively subscribed to it\"\n\tcase ErrInvalidRecord:\n\t\treturn \"kafka server: This record has failed the validation on broker and hence will be rejected\"\n\tcase ErrUnstableOffsetCommit:\n\t\treturn \"kafka server: There are unstable offsets that need to be cleared\"\n\t}\n\n\treturn fmt.Sprintf(\"Unknown error, how did this happen? Error code = %d\", err)\n}\n"
  },
  {
    "path": "errors_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"testing\"\n)\n\nfunc TestSentinelWithSingleWrappedError(t *testing.T) {\n\tt.Parallel()\n\tmyNetError := &net.OpError{Op: \"mock\", Err: errors.New(\"op error\")}\n\terror := Wrap(ErrOutOfBrokers, myNetError)\n\n\texpected := fmt.Sprintf(\"%s: %s\", ErrOutOfBrokers, myNetError)\n\tactual := error.Error()\n\tif actual != expected {\n\t\tt.Errorf(\"unexpected value '%s' vs '%v'\", expected, actual)\n\t}\n\n\tif !errors.Is(error, ErrOutOfBrokers) {\n\t\tt.Error(\"errors.Is unexpected result\")\n\t}\n\n\tif !errors.Is(error, myNetError) {\n\t\tt.Error(\"errors.Is unexpected result\")\n\t}\n\n\tvar opError *net.OpError\n\tif !errors.As(error, &opError) {\n\t\tt.Error(\"errors.As unexpected result\")\n\t} else if opError != myNetError {\n\t\tt.Error(\"errors.As wrong value\")\n\t}\n\n\tunwrapped := errors.Unwrap(error)\n\tif errors.Is(unwrapped, ErrOutOfBrokers) || !errors.Is(unwrapped, myNetError) {\n\t\tt.Errorf(\"unexpected unwrapped value %v vs %vs\", error, unwrapped)\n\t}\n}\n\nfunc TestSentinelWithMultipleWrappedErrors(t *testing.T) {\n\tt.Parallel()\n\tmyNetError := &net.OpError{}\n\tmyAddrError := &net.AddrError{}\n\n\terror := Wrap(ErrOutOfBrokers, myNetError, myAddrError)\n\n\tif !errors.Is(error, ErrOutOfBrokers) {\n\t\tt.Error(\"errors.Is unexpected result\")\n\t}\n\n\tif !errors.Is(error, myNetError) {\n\t\tt.Error(\"errors.Is unexpected result\")\n\t}\n\n\tif !errors.Is(error, myAddrError) {\n\t\tt.Error(\"errors.Is unexpected result\")\n\t}\n\n\tunwrapped := errors.Unwrap(error)\n\tif errors.Is(unwrapped, ErrOutOfBrokers) || !errors.Is(unwrapped, myNetError) || !errors.Is(unwrapped, myAddrError) {\n\t\tt.Errorf(\"unwrapped value unexpected result\")\n\t}\n}\n"
  },
  {
    "path": "examples/README.md",
    "content": "# Sarama examples\n\nThis folder contains example applications to demonstrate the use of Sarama. For code snippet examples on how to use the different types in Sarama, see [Sarama's API documentation on pkg.go.dev](https://pkg.go.dev/github.com/IBM/sarama)\n\n#### HTTP server\n\n[http_server](./http_server) is a simple HTTP server uses both the sync producer to produce data as part of the request handling cycle, as well as the async producer to maintain an access log. It also uses the [mocks subpackage](https://pkg.go.dev/github.com/IBM/sarama/mocks) to test both.\n\n#### Interceptors\n\nBasic example to use a producer interceptor that produces [OpenTelemetry](https://github.com/open-telemetry/opentelemetry-go/) spans and add some headers for each intercepted message.\n\n#### Transactional Producer\n\n[txn_producer](./txn_producer) Basic example to use a transactional producer that produce on some topic within a Kafka transaction. To ensure transactional-id uniqueness it implement some **_ProducerProvider_** that build a producer appending an integer that grow when producer is created.\n\n#### Exacly-once transactional paradigm\n\n[exactly_once](./exactly_once) Basic example to use a transactional producer that produce consumed message from some topics within a Kafka transaction. To ensure transactional-id uniqueness it implement some **_ProducerProvider_** that build a producer using current message topic-partition.\n"
  },
  {
    "path": "examples/consumergroup/README.md",
    "content": "# Consumergroup example\n\nThis example shows you how to use the Sarama consumer group consumer. The example simply starts consuming the given Kafka topics and logs the consumed messages.\n\n```bash\n$ go run main.go -brokers=\"127.0.0.1:9092\" -topics=\"sarama\" -group=\"example\"\n```\n\nYou can also toggle (pause/resume) the consumption by sending SIGUSR1\n"
  },
  {
    "path": "examples/consumergroup/go.mod",
    "content": "module github.com/IBM/sarama/examples/consumer\n\ngo 1.25.0\n\nrequire github.com/IBM/sarama v1.46.3\n\nrequire (\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/eapache/go-resiliency v1.7.0 // indirect\n\tgithub.com/eapache/queue v1.1.0 // indirect\n\tgithub.com/hashicorp/go-uuid v1.0.3 // indirect\n\tgithub.com/jcmturner/aescts/v2 v2.0.0 // indirect\n\tgithub.com/jcmturner/dnsutils/v2 v2.0.0 // indirect\n\tgithub.com/jcmturner/gofork v1.7.6 // indirect\n\tgithub.com/jcmturner/gokrb5/v8 v8.4.4 // indirect\n\tgithub.com/jcmturner/rpc/v2 v2.0.3 // indirect\n\tgithub.com/klauspost/compress v1.18.3 // indirect\n\tgithub.com/pierrec/lz4/v4 v4.1.25 // indirect\n\tgithub.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect\n\tgolang.org/x/crypto v0.48.0 // indirect\n\tgolang.org/x/net v0.51.0 // indirect\n)\n\nreplace github.com/IBM/sarama => ../../\n"
  },
  {
    "path": "examples/consumergroup/go.sum",
    "content": "github.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/eapache/go-resiliency v1.7.0 h1:n3NRTnBn5N0Cbi/IeOHuQn9s2UwVUH7Ga0ZWcP+9JTA=\ngithub.com/eapache/go-resiliency v1.7.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho=\ngithub.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=\ngithub.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=\ngithub.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=\ngithub.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=\ngithub.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=\ngithub.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=\ngithub.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=\ngithub.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=\ngithub.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=\ngithub.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=\ngithub.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=\ngithub.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=\ngithub.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=\ngithub.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=\ngithub.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=\ngithub.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=\ngithub.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=\ngithub.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=\ngithub.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=\ngithub.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=\ngithub.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=\ngithub.com/pierrec/lz4/v4 v4.1.25 h1:kocOqRffaIbU5djlIBr7Wh+cx82C0vtFb0fOurZHqD0=\ngithub.com/pierrec/lz4/v4 v4.1.25/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=\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/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg=\ngithub.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=\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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\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/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\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/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=\ngolang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=\ngolang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=\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-20200114155413-6afb5195e5aa/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-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=\ngolang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=\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/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=\ngolang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/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-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.5.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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=\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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\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=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\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": "examples/consumergroup/main.go",
    "content": "package main\n\n// SIGUSR1 toggle the pause/resume consumption\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"flag\"\n\t\"log\"\n\t\"os\"\n\t\"os/signal\"\n\t\"strings\"\n\t\"sync\"\n\t\"syscall\"\n\n\t\"github.com/IBM/sarama\"\n)\n\n// Sarama configuration options\nvar (\n\tbrokers  = \"\"\n\tversion  = \"\"\n\tgroup    = \"\"\n\ttopics   = \"\"\n\tassignor = \"\"\n\toldest   = true\n\tverbose  = false\n)\n\nfunc init() {\n\tflag.StringVar(&brokers, \"brokers\", \"\", \"Kafka bootstrap brokers to connect to, as a comma separated list\")\n\tflag.StringVar(&group, \"group\", \"\", \"Kafka consumer group definition\")\n\tflag.StringVar(&version, \"version\", sarama.DefaultVersion.String(), \"Kafka cluster version\")\n\tflag.StringVar(&topics, \"topics\", \"\", \"Kafka topics to be consumed, as a comma separated list\")\n\tflag.StringVar(&assignor, \"assignor\", \"range\", \"Consumer group partition assignment strategy (range, roundrobin, sticky)\")\n\tflag.BoolVar(&oldest, \"oldest\", true, \"Kafka consumer consume initial offset from oldest\")\n\tflag.BoolVar(&verbose, \"verbose\", false, \"Sarama logging\")\n\tflag.Parse()\n\n\tif len(brokers) == 0 {\n\t\tpanic(\"no Kafka bootstrap brokers defined, please set the -brokers flag\")\n\t}\n\n\tif len(topics) == 0 {\n\t\tpanic(\"no topics given to be consumed, please set the -topics flag\")\n\t}\n\n\tif len(group) == 0 {\n\t\tpanic(\"no Kafka consumer group defined, please set the -group flag\")\n\t}\n}\n\nfunc main() {\n\tkeepRunning := true\n\tlog.Println(\"Starting a new Sarama consumer\")\n\n\tif verbose {\n\t\tsarama.Logger = log.New(os.Stdout, \"[sarama] \", log.LstdFlags)\n\t}\n\n\tversion, err := sarama.ParseKafkaVersion(version)\n\tif err != nil {\n\t\tlog.Panicf(\"Error parsing Kafka version: %v\", err)\n\t}\n\n\t/**\n\t * Construct a new Sarama configuration.\n\t * The Kafka cluster version has to be defined before the consumer/producer is initialized.\n\t */\n\tconfig := sarama.NewConfig()\n\tconfig.Version = version\n\n\tswitch assignor {\n\tcase \"sticky\":\n\t\tconfig.Consumer.Group.Rebalance.GroupStrategies = []sarama.BalanceStrategy{sarama.NewBalanceStrategySticky()}\n\tcase \"roundrobin\":\n\t\tconfig.Consumer.Group.Rebalance.GroupStrategies = []sarama.BalanceStrategy{sarama.NewBalanceStrategyRoundRobin()}\n\tcase \"range\":\n\t\tconfig.Consumer.Group.Rebalance.GroupStrategies = []sarama.BalanceStrategy{sarama.NewBalanceStrategyRange()}\n\tdefault:\n\t\tlog.Panicf(\"Unrecognized consumer group partition assignor: %s\", assignor)\n\t}\n\n\tif oldest {\n\t\tconfig.Consumer.Offsets.Initial = sarama.OffsetOldest\n\t}\n\n\t/**\n\t * Setup a new Sarama consumer group\n\t */\n\tconsumer := Consumer{\n\t\tready: make(chan bool),\n\t}\n\n\tctx, cancel := context.WithCancel(context.Background())\n\tclient, err := sarama.NewConsumerGroup(strings.Split(brokers, \",\"), group, config)\n\tif err != nil {\n\t\tlog.Panicf(\"Error creating consumer group client: %v\", err)\n\t}\n\n\tconsumptionIsPaused := false\n\twg := &sync.WaitGroup{}\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tfor {\n\t\t\t// `Consume` should be called inside an infinite loop, when a\n\t\t\t// server-side rebalance happens, the consumer session will need to be\n\t\t\t// recreated to get the new claims\n\t\t\tif err := client.Consume(ctx, strings.Split(topics, \",\"), &consumer); err != nil {\n\t\t\t\tif errors.Is(err, sarama.ErrClosedConsumerGroup) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tlog.Panicf(\"Error from consumer: %v\", err)\n\t\t\t}\n\t\t\t// check if context was cancelled, signaling that the consumer should stop\n\t\t\tif ctx.Err() != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconsumer.ready = make(chan bool)\n\t\t}\n\t}()\n\n\t<-consumer.ready // Await till the consumer has been set up\n\tlog.Println(\"Sarama consumer up and running!...\")\n\n\tsigusr1 := make(chan os.Signal, 1)\n\tsignal.Notify(sigusr1, syscall.SIGUSR1)\n\n\tsigterm := make(chan os.Signal, 1)\n\tsignal.Notify(sigterm, syscall.SIGINT, syscall.SIGTERM)\n\n\tfor keepRunning {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\tlog.Println(\"terminating: context cancelled\")\n\t\t\tkeepRunning = false\n\t\tcase <-sigterm:\n\t\t\tlog.Println(\"terminating: via signal\")\n\t\t\tkeepRunning = false\n\t\tcase <-sigusr1:\n\t\t\ttoggleConsumptionFlow(client, &consumptionIsPaused)\n\t\t}\n\t}\n\tcancel()\n\twg.Wait()\n\tif err = client.Close(); err != nil {\n\t\tlog.Panicf(\"Error closing client: %v\", err)\n\t}\n}\n\nfunc toggleConsumptionFlow(client sarama.ConsumerGroup, isPaused *bool) {\n\tif *isPaused {\n\t\tclient.ResumeAll()\n\t\tlog.Println(\"Resuming consumption\")\n\t} else {\n\t\tclient.PauseAll()\n\t\tlog.Println(\"Pausing consumption\")\n\t}\n\n\t*isPaused = !*isPaused\n}\n\n// Consumer represents a Sarama consumer group consumer\ntype Consumer struct {\n\tready chan bool\n}\n\n// Setup is run at the beginning of a new session, before ConsumeClaim\nfunc (consumer *Consumer) Setup(sarama.ConsumerGroupSession) error {\n\t// Mark the consumer as ready\n\tclose(consumer.ready)\n\treturn nil\n}\n\n// Cleanup is run at the end of a session, once all ConsumeClaim goroutines have exited\nfunc (consumer *Consumer) Cleanup(sarama.ConsumerGroupSession) error {\n\treturn nil\n}\n\n// ConsumeClaim must start a consumer loop of ConsumerGroupClaim's Messages().\n// Once the Messages() channel is closed, the Handler must finish its processing\n// loop and exit.\nfunc (consumer *Consumer) ConsumeClaim(session sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error {\n\t// NOTE:\n\t// Do not move the code below to a goroutine.\n\t// The `ConsumeClaim` itself is called within a goroutine, see:\n\t// https://github.com/IBM/sarama/blob/main/consumer_group.go#L27-L29\n\tfor {\n\t\tselect {\n\t\tcase message, ok := <-claim.Messages():\n\t\t\tif !ok {\n\t\t\t\tlog.Printf(\"message channel was closed\")\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tlog.Printf(\"Message claimed: value = %s, timestamp = %v, topic = %s\", string(message.Value), message.Timestamp, message.Topic)\n\t\t\tsession.MarkMessage(message, \"\")\n\t\t// Should return when `session.Context()` is done.\n\t\t// If not, will raise `ErrRebalanceInProgress` or `read tcp <ip>:<port>: i/o timeout` when kafka rebalance. see:\n\t\t// https://github.com/IBM/sarama/issues/1192\n\t\tcase <-session.Context().Done():\n\t\t\treturn nil\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "examples/interceptors/README.md",
    "content": "# Interceptors Example\n\nIt creates a *Producer* interceptor to produce some [OpenTelemetry](https://github.com/open-telemetry/opentelemetry-go/) spans and also modifies\nthe intercepted message to include some headers.\n\n``` go\nconf.Producer.Interceptors = []sarama.ProducerInterceptor{xxx}\n```\n\n## Run the example\n- `go run main.go trace_interceptor.go`.\n- or `go build and pass different parameters.\n``` sh\ngo build && ./interceptors --h\nUsage of ./interceptors:\n  -brokers string\n        The Kafka brokers to connect to, as a comma separated list (default \"localhost:9092\")\n  -topic string\n        The Kafka topic to use (default \"default_topic\")\n```\n\nApp will output OpenTelemetry spans for every intercepted message, i.e:\n\n```\ngo run main.go trace_interceptor.go\n[Sarama] 2020/08/11 11:35:56 Initializing new client\n[Sarama] 2020/08/11 11:35:56 ClientID is the default of 'sarama', you should consider setting it to something application-specific.\n[Sarama] 2020/08/11 11:35:56 ClientID is the default of 'sarama', you should consider setting it to something application-specific.\n[Sarama] 2020/08/11 11:35:56 client/metadata fetching metadata for all topics from broker localhost:9092\n[Sarama] 2020/08/11 11:35:56 Connected to broker at localhost:9092 (unregistered)\n[Sarama] 2020/08/11 11:35:56 client/brokers registered new broker #1 at localhost:9092\n[Sarama] 2020/08/11 11:35:56 Successfully initialized new client\nINFO[0000] Starting to produce 2 messages every 5s\nINFO[0005] producing 2 messages at 2020-08-11T11:36:01-07:00  topic=default_topic\n[\n  {\n    \"SpanContext\": {\n      \"TraceID\": \"2c4210c1d6c2ebe758eb41cbc95a0478\",\n      \"SpanID\": \"046bfc6d6db17ed7\",\n      \"TraceFlags\": 1\n    },\n    \"ParentSpanID\": \"0000000000000000\",\n    \"SpanKind\": 1,\n    \"Name\": \"default_topic\",\n    \"StartTime\": \"2020-08-11T11:36:01.57487-07:00\",\n    \"EndTime\": \"2020-08-11T11:36:01.574891849-07:00\",\n    \"Attributes\": [\n      {\n        \"Key\": \"messaging.destination_kind\",\n        \"Value\": { \"Type\": \"STRING\", \"Value\": \"topic\" }\n      },\n      {\n        \"Key\": \"span.otel.kind\",\n        \"Value\": { \"Type\": \"STRING\", \"Value\": \"PRODUCER\" }\n      },\n      {\n        \"Key\": \"messaging.system\",\n        \"Value\": { \"Type\": \"STRING\", \"Value\": \"kafka\" }\n      },\n      {\n        \"Key\": \"net.transport\",\n        \"Value\": { \"Type\": \"STRING\", \"Value\": \"IP.TCP\" }\n      },\n      {\n        \"Key\": \"messaging.url\",\n        \"Value\": { \"Type\": \"STRING\", \"Value\": \"localhost:9092\" }\n      },\n      {\n        \"Key\": \"messaging.destination\",\n        \"Value\": { \"Type\": \"STRING\", \"Value\": \"default_topic\" }\n      },\n      {\n        \"Key\": \"messaging.message_id\",\n        \"Value\": { \"Type\": \"STRING\", \"Value\": \"046bfc6d6db17ed7\" }\n      }\n    ],\n    \"MessageEvents\": null,\n    \"Links\": null,\n    \"StatusCode\": 0,\n    \"StatusMessage\": \"\",\n    \"HasRemoteParent\": false,\n    \"DroppedAttributeCount\": 0,\n    \"DroppedMessageEventCount\": 0,\n    \"DroppedLinkCount\": 0,\n    \"ChildSpanCount\": 0,\n    \"Resource\": null,\n    \"InstrumentationLibrary\": {\n      \"Name\": \"shopify.com/sarama/examples/interceptors\",\n      \"Version\": \"\"\n    }\n  }\n]\n[{\"SpanContext\":{\"TraceID\":\"b3922fbbaab23b16401c353b0ff9ce6b\",\"SpanID\":\"269f5133c0d0116e\",\"TraceFlags\":1},\"ParentSpanID\":\"0000000000000000\",\"SpanKind\":1,\"Name\":\"default_topic\",\"StartTime\":\"2020-08-11T11:36:01.575388-07:00\",\"EndTime\":\"2020-08-11T11:36:01.575399065-07:00\",\"Attributes\":[{\"Key\":\"messaging.destination_kind\",\"Value\":{\"Type\":\"STRING\",\"Value\":\"topic\"}},{\"Key\":\"span.otel.kind\",\"Value\":{\"Type\":\"STRING\",\"Value\":\"PRODUCER\"}},{\"Key\":\"messaging.system\",\"Value\":{\"Type\":\"STRING\",\"Value\":\"kafka\"}},{\"Key\":\"net.transport\",\"Value\":{\"Type\":\"STRING\",\"Value\":\"IP.TCP\"}},{\"Key\":\"messaging.url\",\"Value\":{\"Type\":\"STRING\",\"Value\":\"localhost:9092\"}},{\"Key\":\"messaging.destination\",\"Value\":{\"Type\":\"STRING\",\"Value\":\"default_topic\"}},{\"Key\":\"messaging.message_id\",\"Value\":{\"Type\":\"STRING\",\"Value\":\"269f5133c0d0116e\"}}],\"MessageEvents\":null,\"Links\":null,\"StatusCode\":0,\"StatusMessage\":\"\",\"HasRemoteParent\":false,\"DroppedAttributeCount\":0,\"DroppedMessageEventCount\":0,\"DroppedLinkCount\":0,\"ChildSpanCount\":0,\"Resource\":null,\"InstrumentationLibrary\":{\"Name\":\"shopify.com/sarama/examples/interceptors\",\"Version\":\"\"}}]\n[Sarama] 2020/08/11 11:36:01 ClientID is the default of 'sarama', you should consider setting it to something application-specific.\n[Sarama] 2020/08/11 11:36:01 producer/broker/1 starting up\n[Sarama] 2020/08/11 11:36:01 producer/broker/1 state change to [open] on default_topic/0\n[Sarama] 2020/08/11 11:36:01 Connected to broker at localhost:9092 (registered as #1)\n^CINFO[0005] terminating the program\nINFO[0005] Bye :)\n[Sarama] 2020/08/11 11:36:02 Producer shutting down.\n```\n\n## Check the produced intercepted messages\n\nCheck that messages have some headers added by the interceptor:\n``` sh\nkafkacat -Cb localhost:9092 -t default_topic -f '\\n- %s\\nheaders: %h'\n```\n\n```\nheaders: trace_id=235b3424775d8b2f9bf21e458496f447,span_id=50da1552c105e712,message_id=50da1552c105e712\n- test message 1/2 from kafka-client-go-test at 2020-08-11T11:36:01-07:00\nheaders: trace_id=2c4210c1d6c2ebe758eb41cbc95a0478,span_id=046bfc6d6db17ed7,message_id=046bfc6d6db17ed7\n- test message 2/2 from kafka-client-go-test at 2020-08-11T11:36:01-07:00\n% Reached end of topic default_topic [0] at offset 444\n```\n"
  },
  {
    "path": "examples/interceptors/go.mod",
    "content": "module github.com/IBM/sarama/examples/interceptors\n\ngo 1.25.0\n\nrequire (\n\tgithub.com/IBM/sarama v1.46.3\n\tgo.opentelemetry.io/otel v1.29.0\n\tgo.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0\n\tgo.opentelemetry.io/otel/sdk v1.29.0\n\tgo.opentelemetry.io/otel/trace v1.29.0\n)\n\nrequire (\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/eapache/go-resiliency v1.7.0 // indirect\n\tgithub.com/eapache/queue v1.1.0 // indirect\n\tgithub.com/go-logr/logr v1.4.2 // indirect\n\tgithub.com/go-logr/stdr v1.2.2 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/hashicorp/go-uuid v1.0.3 // indirect\n\tgithub.com/jcmturner/aescts/v2 v2.0.0 // indirect\n\tgithub.com/jcmturner/dnsutils/v2 v2.0.0 // indirect\n\tgithub.com/jcmturner/gofork v1.7.6 // indirect\n\tgithub.com/jcmturner/gokrb5/v8 v8.4.4 // indirect\n\tgithub.com/jcmturner/rpc/v2 v2.0.3 // indirect\n\tgithub.com/klauspost/compress v1.18.3 // indirect\n\tgithub.com/pierrec/lz4/v4 v4.1.25 // indirect\n\tgithub.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect\n\tgo.opentelemetry.io/otel/metric v1.29.0 // indirect\n\tgo.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect\n\tgolang.org/x/crypto v0.48.0 // indirect\n\tgolang.org/x/net v0.51.0 // indirect\n\tgolang.org/x/sys v0.41.0 // indirect\n)\n\nreplace github.com/IBM/sarama => ../../\n"
  },
  {
    "path": "examples/interceptors/go.sum",
    "content": "github.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/eapache/go-resiliency v1.7.0 h1:n3NRTnBn5N0Cbi/IeOHuQn9s2UwVUH7Ga0ZWcP+9JTA=\ngithub.com/eapache/go-resiliency v1.7.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho=\ngithub.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=\ngithub.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=\ngithub.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=\ngithub.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=\ngithub.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=\ngithub.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=\ngithub.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=\ngithub.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=\ngithub.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=\ngithub.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=\ngithub.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=\ngithub.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=\ngithub.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=\ngithub.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=\ngithub.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=\ngithub.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=\ngithub.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=\ngithub.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=\ngithub.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=\ngithub.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=\ngithub.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=\ngithub.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=\ngithub.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=\ngithub.com/pierrec/lz4/v4 v4.1.25 h1:kocOqRffaIbU5djlIBr7Wh+cx82C0vtFb0fOurZHqD0=\ngithub.com/pierrec/lz4/v4 v4.1.25/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=\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/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg=\ngithub.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=\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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\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/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngo.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=\ngo.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=\ngo.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc=\ngo.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I=\ngo.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc=\ngo.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=\ngo.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo=\ngo.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok=\ngo.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY=\ngo.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ=\ngo.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=\ngo.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=\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/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=\ngolang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=\ngolang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=\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-20200114155413-6afb5195e5aa/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-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=\ngolang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=\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/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=\ngolang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/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-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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=\ngolang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=\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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\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=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\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": "examples/interceptors/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"os/signal\"\n\t\"strings\"\n\t\"time\"\n\n\tstdout \"go.opentelemetry.io/otel/exporters/stdout/stdoutmetric\"\n\n\t\"github.com/IBM/sarama\"\n)\n\nvar (\n\tbrokers = flag.String(\"brokers\", \"localhost:9092\", \"The Kafka brokers to connect to, as a comma separated list\")\n\tversion = flag.String(\"version\", sarama.DefaultVersion.String(), \"Kafka cluster version\")\n\ttopic   = flag.String(\"topic\", \"default_topic\", \"The Kafka topic to use\")\n\tlogger  = log.New(os.Stdout, \"[OTelInterceptor] \", log.LstdFlags)\n)\n\nfunc main() {\n\tflag.Parse()\n\n\tif *brokers == \"\" {\n\t\tlogger.Fatalln(\"at least one broker is required\")\n\t}\n\tsplitBrokers := strings.Split(*brokers, \",\")\n\tsarama.Logger = log.New(os.Stdout, \"[Sarama] \", log.LstdFlags)\n\n\tversion, err := sarama.ParseKafkaVersion(*version)\n\tif err != nil {\n\t\tlog.Panicf(\"Error parsing Kafka version: %v\", err)\n\t}\n\n\t// oTel stdout example\n\tpusher, err := stdout.New()\n\tif err != nil {\n\t\tlogger.Fatalf(\"failed to initialize stdout export pipeline: %v\", err)\n\t}\n\tdefer pusher.Shutdown(context.Background())\n\n\t// simple sarama producer that adds a new producer interceptor\n\tconf := sarama.NewConfig()\n\tconf.Version = version\n\tconf.Producer.Interceptors = []sarama.ProducerInterceptor{NewOTelInterceptor(splitBrokers)}\n\n\tproducer, err := sarama.NewAsyncProducer(splitBrokers, conf)\n\tif err != nil {\n\t\tpanic(\"Couldn't create a Kafka producer\")\n\t}\n\tdefer producer.AsyncClose()\n\n\t// kill -2, trap SIGINT to trigger a shutdown\n\tsignals := make(chan os.Signal, 1)\n\tsignal.Notify(signals, os.Interrupt)\n\n\t// ticker\n\tbulkSize := 2\n\tduration := 5 * time.Second\n\tticker := time.NewTicker(duration)\n\tlogger.Printf(\"Starting to produce %v messages every %v\", bulkSize, duration)\n\tfor {\n\t\tselect {\n\t\tcase t := <-ticker.C:\n\t\t\tnow := t.Format(time.RFC3339)\n\t\t\tlogger.Printf(\"\\nproducing %v messages to topic %s at %s\", bulkSize, *topic, now)\n\t\t\tfor i := 0; i < bulkSize; i++ {\n\t\t\t\tproducer.Input() <- &sarama.ProducerMessage{\n\t\t\t\t\tTopic: *topic, Key: nil,\n\t\t\t\t\tValue: sarama.StringEncoder(fmt.Sprintf(\"test message %v/%v from kafka-client-go-test at %s\", i+1, bulkSize, now)),\n\t\t\t\t}\n\t\t\t}\n\t\tcase <-signals:\n\t\t\tlogger.Println(\"terminating the program\")\n\t\t\tlogger.Println(\"Bye :)\")\n\t\t\treturn\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "examples/interceptors/trace_interceptor.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\t\"go.opentelemetry.io/otel/attribute\"\n\tsdktrace \"go.opentelemetry.io/otel/sdk/trace\"\n\t\"go.opentelemetry.io/otel/trace\"\n\n\t\"github.com/IBM/sarama\"\n)\n\ntype OTelInterceptor struct {\n\ttracer     trace.Tracer\n\tfixedAttrs []attribute.KeyValue\n}\n\n// NewOTelInterceptor processes span for intercepted messages and add some\n// headers with the span data.\nfunc NewOTelInterceptor(brokers []string) *OTelInterceptor {\n\toi := OTelInterceptor{}\n\toi.tracer = sdktrace.NewTracerProvider().Tracer(\"github.com/IBM/sarama/examples/interceptors\")\n\n\t// These are based on the spec, which was reachable as of 2020-05-15\n\t// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/messaging.md\n\toi.fixedAttrs = []attribute.KeyValue{\n\t\tattribute.String(\"messaging.destination_kind\", \"topic\"),\n\t\tattribute.String(\"span.otel.kind\", \"PRODUCER\"),\n\t\tattribute.String(\"messaging.system\", \"kafka\"),\n\t\tattribute.String(\"net.transport\", \"IP.TCP\"),\n\t\tattribute.String(\"messaging.url\", strings.Join(brokers, \",\")),\n\t}\n\treturn &oi\n}\n\nconst (\n\tMessageIDHeaderName = \"message_id\"\n\tSpanHeaderName      = \"span_id\"\n\tTraceHeaderName     = \"trace_id\"\n)\n\nfunc shouldIgnoreMsg(msg *sarama.ProducerMessage) bool {\n\t// check message hasn't been here before (retries)\n\tvar traceFound, spanFound, msgIDFound bool\n\tfor _, h := range msg.Headers {\n\t\tif string(h.Key) == TraceHeaderName {\n\t\t\ttraceFound = true\n\t\t\tcontinue\n\t\t}\n\t\tif string(h.Key) == SpanHeaderName {\n\t\t\tspanFound = true\n\t\t\tcontinue\n\t\t}\n\t\tif string(h.Key) == MessageIDHeaderName {\n\t\t\tmsgIDFound = true\n\t\t}\n\t}\n\treturn traceFound && spanFound && msgIDFound\n}\n\nfunc (oi *OTelInterceptor) OnSend(msg *sarama.ProducerMessage) {\n\tif shouldIgnoreMsg(msg) {\n\t\treturn\n\t}\n\t_, span := oi.tracer.Start(context.TODO(), msg.Topic)\n\tdefer span.End()\n\tspanContext := span.SpanContext()\n\tattWithTopic := append(\n\t\toi.fixedAttrs,\n\t\tattribute.String(\"messaging.destination\", msg.Topic),\n\t\tattribute.String(\"messaging.message_id\", spanContext.SpanID().String()),\n\t)\n\tspan.SetAttributes(attWithTopic...)\n\n\t// remove existing partial tracing headers if exists\n\tnoTraceHeaders := msg.Headers[:0]\n\tfor _, h := range msg.Headers {\n\t\tkey := string(h.Key)\n\t\tif key != TraceHeaderName && key != SpanHeaderName && key != MessageIDHeaderName {\n\t\t\tnoTraceHeaders = append(noTraceHeaders, h)\n\t\t}\n\t}\n\ttraceHeaders := []sarama.RecordHeader{\n\t\t{Key: []byte(TraceHeaderName), Value: []byte(spanContext.TraceID().String())},\n\t\t{Key: []byte(SpanHeaderName), Value: []byte(spanContext.SpanID().String())},\n\t\t{Key: []byte(MessageIDHeaderName), Value: []byte(spanContext.SpanID().String())},\n\t}\n\tmsg.Headers = append(noTraceHeaders, traceHeaders...)\n}\n"
  },
  {
    "path": "fetch_request.go",
    "content": "package sarama\n\nimport \"fmt\"\n\ntype fetchRequestBlock struct {\n\tVersion int16\n\t// currentLeaderEpoch contains the current leader epoch of the partition.\n\tcurrentLeaderEpoch int32\n\t// fetchOffset contains the message offset.\n\tfetchOffset int64\n\t// logStartOffset contains the earliest available offset of the follower\n\t// replica.  The field is only used when the request is sent by the\n\t// follower.\n\tlogStartOffset int64\n\t// maxBytes contains the maximum bytes to fetch from this partition.  See\n\t// KIP-74 for cases where this limit may not be honored.\n\tmaxBytes int32\n}\n\nfunc (b *fetchRequestBlock) encode(pe packetEncoder, version int16) error {\n\tb.Version = version\n\tif b.Version >= 9 {\n\t\tpe.putInt32(b.currentLeaderEpoch)\n\t}\n\tpe.putInt64(b.fetchOffset)\n\tif b.Version >= 5 {\n\t\tpe.putInt64(b.logStartOffset)\n\t}\n\tpe.putInt32(b.maxBytes)\n\treturn nil\n}\n\nfunc (b *fetchRequestBlock) decode(pd packetDecoder, version int16) (err error) {\n\tb.Version = version\n\tif b.Version >= 9 {\n\t\tif b.currentLeaderEpoch, err = pd.getInt32(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif b.fetchOffset, err = pd.getInt64(); err != nil {\n\t\treturn err\n\t}\n\tif b.Version >= 5 {\n\t\tif b.logStartOffset, err = pd.getInt64(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif b.maxBytes, err = pd.getInt32(); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// FetchRequest (API key 1) will fetch Kafka messages. Version 3 introduced the MaxBytes field. See\n// https://issues.apache.org/jira/browse/KAFKA-2063 for a discussion of the issues leading up to that.  The KIP is at\n// https://cwiki.apache.org/confluence/display/KAFKA/KIP-74%3A+Add+Fetch+Response+Size+Limit+in+Bytes\ntype FetchRequest struct {\n\t// Version defines the protocol version to use for encode and decode\n\tVersion int16\n\t// ReplicaID contains the broker ID of the follower, of -1 if this request\n\t// is from a consumer.\n\t// ReplicaID int32\n\t// MaxWaitTime contains the maximum time in milliseconds to wait for the response.\n\tMaxWaitTime int32\n\t// MinBytes contains the minimum bytes to accumulate in the response.\n\tMinBytes int32\n\t// MaxBytes contains the maximum bytes to fetch.  See KIP-74 for cases\n\t// where this limit may not be honored.\n\tMaxBytes int32\n\t// Isolation contains a This setting controls the visibility of\n\t// transactional records. Using READ_UNCOMMITTED (isolation_level = 0)\n\t// makes all records visible. With READ_COMMITTED (isolation_level = 1),\n\t// non-transactional and COMMITTED transactional records are visible. To be\n\t// more concrete, READ_COMMITTED returns all data from offsets smaller than\n\t// the current LSO (last stable offset), and enables the inclusion of the\n\t// list of aborted transactions in the result, which allows consumers to\n\t// discard ABORTED transactional records\n\tIsolation IsolationLevel\n\t// SessionID contains the fetch session ID.\n\tSessionID int32\n\t// SessionEpoch contains the epoch of the partition leader as known to the\n\t// follower replica or a consumer.\n\tSessionEpoch int32\n\t// blocks contains the topics to fetch.\n\tblocks map[string]map[int32]*fetchRequestBlock\n\t// forgotten contains in an incremental fetch request, the partitions to remove.\n\tforgotten map[string][]int32\n\t// RackID contains a Rack ID of the consumer making this request\n\tRackID string\n}\n\nfunc (r *FetchRequest) setVersion(v int16) {\n\tr.Version = v\n}\n\ntype IsolationLevel int8\n\nconst (\n\tReadUncommitted IsolationLevel = iota\n\tReadCommitted\n)\n\nfunc (r *FetchRequest) encode(pe packetEncoder) (err error) {\n\tmetricRegistry := pe.metricRegistry()\n\n\tpe.putInt32(-1) // ReplicaID is always -1 for clients\n\tpe.putInt32(r.MaxWaitTime)\n\tpe.putInt32(r.MinBytes)\n\tif r.Version >= 3 {\n\t\tpe.putInt32(r.MaxBytes)\n\t}\n\tif r.Version >= 4 {\n\t\tpe.putInt8(int8(r.Isolation))\n\t}\n\tif r.Version >= 7 {\n\t\tpe.putInt32(r.SessionID)\n\t\tpe.putInt32(r.SessionEpoch)\n\t}\n\terr = pe.putArrayLength(len(r.blocks))\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor topic, blocks := range r.blocks {\n\t\terr = pe.putString(topic)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = pe.putArrayLength(len(blocks))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor partition, block := range blocks {\n\t\t\tpe.putInt32(partition)\n\t\t\terr = block.encode(pe, r.Version)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tgetOrRegisterTopicMeter(\"consumer-fetch-rate\", topic, metricRegistry).Mark(1)\n\t}\n\tif r.Version >= 7 {\n\t\terr = pe.putArrayLength(len(r.forgotten))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor topic, partitions := range r.forgotten {\n\t\t\terr = pe.putString(topic)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terr = pe.putArrayLength(len(partitions))\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tfor _, partition := range partitions {\n\t\t\t\tpe.putInt32(partition)\n\t\t\t}\n\t\t}\n\t}\n\tif r.Version >= 11 {\n\t\terr = pe.putString(r.RackID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (r *FetchRequest) decode(pd packetDecoder, version int16) (err error) {\n\tr.Version = version\n\n\tif _, err = pd.getInt32(); err != nil {\n\t\treturn err\n\t}\n\tif r.MaxWaitTime, err = pd.getInt32(); err != nil {\n\t\treturn err\n\t}\n\tif r.MinBytes, err = pd.getInt32(); err != nil {\n\t\treturn err\n\t}\n\tif r.Version >= 3 {\n\t\tif r.MaxBytes, err = pd.getInt32(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif r.Version >= 4 {\n\t\tisolation, err := pd.getInt8()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.Isolation = IsolationLevel(isolation)\n\t}\n\tif r.Version >= 7 {\n\t\tr.SessionID, err = pd.getInt32()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.SessionEpoch, err = pd.getInt32()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\ttopicCount, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif topicCount == 0 {\n\t\treturn nil\n\t}\n\tr.blocks = make(map[string]map[int32]*fetchRequestBlock)\n\tfor i := 0; i < topicCount; i++ {\n\t\ttopic, err := pd.getString()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tpartitionCount, err := pd.getArrayLength()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.blocks[topic] = make(map[int32]*fetchRequestBlock)\n\t\tfor j := 0; j < partitionCount; j++ {\n\t\t\tpartition, err := pd.getInt32()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tfetchBlock := &fetchRequestBlock{}\n\t\t\tif err = fetchBlock.decode(pd, r.Version); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tr.blocks[topic][partition] = fetchBlock\n\t\t}\n\t}\n\n\tif r.Version >= 7 {\n\t\tforgottenCount, err := pd.getArrayLength()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.forgotten = make(map[string][]int32)\n\t\tfor i := 0; i < forgottenCount; i++ {\n\t\t\ttopic, err := pd.getString()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tpartitionCount, err := pd.getArrayLength()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif partitionCount < 0 {\n\t\t\t\treturn fmt.Errorf(\"partitionCount %d is invalid\", partitionCount)\n\t\t\t}\n\t\t\tr.forgotten[topic] = make([]int32, partitionCount)\n\n\t\t\tfor j := 0; j < partitionCount; j++ {\n\t\t\t\tpartition, err := pd.getInt32()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tr.forgotten[topic][j] = partition\n\t\t\t}\n\t\t}\n\t}\n\n\tif r.Version >= 11 {\n\t\tr.RackID, err = pd.getString()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (r *FetchRequest) key() int16 {\n\treturn apiKeyFetch\n}\n\nfunc (r *FetchRequest) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *FetchRequest) headerVersion() int16 {\n\treturn 1\n}\n\nfunc (r *FetchRequest) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 11\n}\n\nfunc (r *FetchRequest) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 11:\n\t\treturn V2_3_0_0\n\tcase 9, 10:\n\t\treturn V2_1_0_0\n\tcase 8:\n\t\treturn V2_0_0_0\n\tcase 7:\n\t\treturn V1_1_0_0\n\tcase 6:\n\t\treturn V1_0_0_0\n\tcase 4, 5:\n\t\treturn V0_11_0_0\n\tcase 3:\n\t\treturn V0_10_1_0\n\tcase 2:\n\t\treturn V0_10_0_0\n\tcase 1:\n\t\treturn V0_9_0_0\n\tcase 0:\n\t\treturn V0_8_2_0\n\tdefault:\n\t\treturn V2_3_0_0\n\t}\n}\n\nfunc (r *FetchRequest) AddBlock(topic string, partitionID int32, fetchOffset int64, maxBytes int32, leaderEpoch int32) {\n\tif r.blocks == nil {\n\t\tr.blocks = make(map[string]map[int32]*fetchRequestBlock)\n\t}\n\n\tif r.Version >= 7 && r.forgotten == nil {\n\t\tr.forgotten = make(map[string][]int32)\n\t}\n\n\tif r.blocks[topic] == nil {\n\t\tr.blocks[topic] = make(map[int32]*fetchRequestBlock)\n\t}\n\n\ttmp := new(fetchRequestBlock)\n\ttmp.Version = r.Version\n\ttmp.maxBytes = maxBytes\n\ttmp.fetchOffset = fetchOffset\n\tif r.Version >= 9 {\n\t\ttmp.currentLeaderEpoch = leaderEpoch\n\t}\n\n\tr.blocks[topic][partitionID] = tmp\n}\n"
  },
  {
    "path": "fetch_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nvar (\n\tfetchRequestNoBlocks = []byte{\n\t\t0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x00,\n\t}\n\n\tfetchRequestWithProperties = []byte{\n\t\t0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0xEF,\n\t\t0x00, 0x00, 0x00, 0x00,\n\t}\n\n\tfetchRequestOneBlock = []byte{\n\t\t0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x05, 't', 'o', 'p', 'i', 'c',\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x56,\n\t}\n\n\tfetchRequestOneBlockV4 = []byte{\n\t\t0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0xFF,\n\t\t0x01,\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x05, 't', 'o', 'p', 'i', 'c',\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x56,\n\t}\n\n\tfetchRequestOneBlockV11 = []byte{\n\t\t0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0xFF,\n\t\t0x01,\n\t\t0x00, 0x00, 0x00, 0xAA, // sessionID\n\t\t0x00, 0x00, 0x00, 0xEE, // sessionEpoch\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x05, 't', 'o', 'p', 'i', 'c',\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x00, 0x00, 0x12, // partitionID\n\t\t0x00, 0x00, 0x00, 0x66, // currentLeaderEpoch\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, // fetchOffset\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // logStartOffset\n\t\t0x00, 0x00, 0x00, 0x56, // maxBytes\n\t\t0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x06, 'r', 'a', 'c', 'k', '0', '1', // rackID\n\t}\n)\n\nfunc TestFetchRequest(t *testing.T) {\n\tt.Run(\"no blocks\", func(t *testing.T) {\n\t\trequest := new(FetchRequest)\n\t\ttestRequest(t, \"no blocks\", request, fetchRequestNoBlocks)\n\t})\n\n\tt.Run(\"with properties\", func(t *testing.T) {\n\t\trequest := new(FetchRequest)\n\t\trequest.MaxWaitTime = 0x20\n\t\trequest.MinBytes = 0xEF\n\t\ttestRequest(t, \"with properties\", request, fetchRequestWithProperties)\n\t})\n\n\tt.Run(\"one block\", func(t *testing.T) {\n\t\trequest := new(FetchRequest)\n\t\trequest.MaxWaitTime = 0\n\t\trequest.MinBytes = 0\n\t\trequest.AddBlock(\"topic\", 0x12, 0x34, 0x56, -1)\n\t\ttestRequest(t, \"one block\", request, fetchRequestOneBlock)\n\t})\n\n\tt.Run(\"one block v4\", func(t *testing.T) {\n\t\trequest := new(FetchRequest)\n\t\trequest.Version = 4\n\t\trequest.MaxBytes = 0xFF\n\t\trequest.Isolation = ReadCommitted\n\t\trequest.AddBlock(\"topic\", 0x12, 0x34, 0x56, -1)\n\t\ttestRequest(t, \"one block v4\", request, fetchRequestOneBlockV4)\n\t})\n\n\tt.Run(\"one block v11 rackid and leader epoch\", func(t *testing.T) {\n\t\trequest := new(FetchRequest)\n\t\trequest.Version = 11\n\t\trequest.MaxBytes = 0xFF\n\t\trequest.Isolation = ReadCommitted\n\t\trequest.SessionID = 0xAA\n\t\trequest.SessionEpoch = 0xEE\n\t\trequest.AddBlock(\"topic\", 0x12, 0x34, 0x56, 0x66)\n\t\trequest.RackID = \"rack01\"\n\t\ttestRequest(t, \"one block v11 rackid\", request, fetchRequestOneBlockV11)\n\t})\n}\n"
  },
  {
    "path": "fetch_response.go",
    "content": "package sarama\n\nimport (\n\t\"errors\"\n\t\"sort\"\n\t\"time\"\n\n\t\"github.com/rcrowley/go-metrics\"\n)\n\nconst (\n\tinvalidLeaderEpoch        = -1\n\tinvalidPreferredReplicaID = -1\n)\n\ntype AbortedTransaction struct {\n\t// ProducerID contains the producer id associated with the aborted transaction.\n\tProducerID int64\n\t// FirstOffset contains the first offset in the aborted transaction.\n\tFirstOffset int64\n}\n\nfunc (t *AbortedTransaction) decode(pd packetDecoder) (err error) {\n\tif t.ProducerID, err = pd.getInt64(); err != nil {\n\t\treturn err\n\t}\n\n\tif t.FirstOffset, err = pd.getInt64(); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (t *AbortedTransaction) encode(pe packetEncoder) (err error) {\n\tpe.putInt64(t.ProducerID)\n\tpe.putInt64(t.FirstOffset)\n\n\treturn nil\n}\n\ntype FetchResponseBlock struct {\n\t// Err contains the error code, or 0 if there was no fetch error.\n\tErr KError\n\t// HighWatermarkOffset contains the current high water mark.\n\tHighWaterMarkOffset int64\n\t// LastStableOffset contains the last stable offset (or LSO) of the\n\t// partition. This is the last offset such that the state of all\n\t// transactional records prior to this offset have been decided (ABORTED or\n\t// COMMITTED)\n\tLastStableOffset int64\n\t// LogStartOffset contains the current log start offset.\n\tLogStartOffset int64\n\t// AbortedTransactions contains the aborted transactions.\n\tAbortedTransactions []*AbortedTransaction\n\t// PreferredReadReplica contains the preferred read replica for the\n\t// consumer to use on its next fetch request\n\tPreferredReadReplica int32\n\t// RecordsSet contains the record data.\n\tRecordsSet []*Records\n\n\tPartial bool\n\tRecords *Records // deprecated: use FetchResponseBlock.RecordsSet\n\n\t// recordsNextOffset contains the next consecutive offset following this response block.\n\t// This field is computed locally and is not part of the server's binary response.\n\trecordsNextOffset *int64\n}\n\nfunc (b *FetchResponseBlock) decode(pd packetDecoder, version int16) (err error) {\n\tmetricRegistry := pd.metricRegistry()\n\tvar sizeMetric metrics.Histogram\n\tif metricRegistry != nil {\n\t\tsizeMetric = getOrRegisterHistogram(\"consumer-fetch-response-size\", metricRegistry)\n\t}\n\n\tb.Err, err = pd.getKError()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tb.HighWaterMarkOffset, err = pd.getInt64()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif version >= 4 {\n\t\tb.LastStableOffset, err = pd.getInt64()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif version >= 5 {\n\t\t\tb.LogStartOffset, err = pd.getInt64()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\tnumTransact, err := pd.getArrayLength()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif numTransact >= 0 {\n\t\t\tb.AbortedTransactions = make([]*AbortedTransaction, numTransact)\n\t\t}\n\n\t\tfor i := 0; i < numTransact; i++ {\n\t\t\ttransact := new(AbortedTransaction)\n\t\t\tif err = transact.decode(pd); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tb.AbortedTransactions[i] = transact\n\t\t}\n\t}\n\n\tif version >= 11 {\n\t\tb.PreferredReadReplica, err = pd.getInt32()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\tb.PreferredReadReplica = -1\n\t}\n\n\trecordsSize, err := pd.getInt32()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif sizeMetric != nil {\n\t\tsizeMetric.Update(int64(recordsSize))\n\t}\n\n\trecordsDecoder, err := pd.getSubset(int(recordsSize))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tb.RecordsSet = []*Records{}\n\n\tfor recordsDecoder.remaining() > 0 {\n\t\trecords := &Records{}\n\t\tif err := records.decode(recordsDecoder); err != nil {\n\t\t\t// If we have at least one decoded records, this is not an error\n\t\t\tif errors.Is(err, ErrInsufficientData) {\n\t\t\t\tif len(b.RecordsSet) == 0 {\n\t\t\t\t\tb.Partial = true\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\n\t\tb.recordsNextOffset, err = records.nextOffset()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tpartial, err := records.isPartial()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tn, err := records.numRecords()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif n > 0 || (partial && len(b.RecordsSet) == 0) {\n\t\t\tb.RecordsSet = append(b.RecordsSet, records)\n\n\t\t\tif b.Records == nil {\n\t\t\t\tb.Records = records\n\t\t\t}\n\t\t}\n\n\t\toverflow, err := records.isOverflow()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif partial || overflow {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (b *FetchResponseBlock) numRecords() (int, error) {\n\tsum := 0\n\n\tfor _, records := range b.RecordsSet {\n\t\tcount, err := records.numRecords()\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\n\t\tsum += count\n\t}\n\n\treturn sum, nil\n}\n\nfunc (b *FetchResponseBlock) isPartial() (bool, error) {\n\tif b.Partial {\n\t\treturn true, nil\n\t}\n\n\tif len(b.RecordsSet) == 1 {\n\t\treturn b.RecordsSet[0].isPartial()\n\t}\n\n\treturn false, nil\n}\n\nfunc (b *FetchResponseBlock) encode(pe packetEncoder, version int16) (err error) {\n\tpe.putKError(b.Err)\n\n\tpe.putInt64(b.HighWaterMarkOffset)\n\n\tif version >= 4 {\n\t\tpe.putInt64(b.LastStableOffset)\n\n\t\tif version >= 5 {\n\t\t\tpe.putInt64(b.LogStartOffset)\n\t\t}\n\n\t\tif err = pe.putArrayLength(len(b.AbortedTransactions)); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor _, transact := range b.AbortedTransactions {\n\t\t\tif err = transact.encode(pe); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\tif version >= 11 {\n\t\tpe.putInt32(b.PreferredReadReplica)\n\t}\n\n\tpe.push(&lengthField{})\n\tfor _, records := range b.RecordsSet {\n\t\terr = records.encode(pe)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn pe.pop()\n}\n\nfunc (b *FetchResponseBlock) getAbortedTransactions() []*AbortedTransaction {\n\t// I can't find any doc that guarantee the field `fetchResponse.AbortedTransactions` is ordered\n\t// plus Java implementation use a PriorityQueue based on `FirstOffset`. I guess we have to order it ourself\n\tat := b.AbortedTransactions\n\tsort.Slice(\n\t\tat,\n\t\tfunc(i, j int) bool { return at[i].FirstOffset < at[j].FirstOffset },\n\t)\n\treturn at\n}\n\ntype FetchResponse struct {\n\t// Version defines the protocol version to use for encode and decode\n\tVersion int16\n\t// ThrottleTime contains the duration in milliseconds for which the request\n\t// was throttled due to a quota violation, or zero if the request did not\n\t// violate any quota.\n\tThrottleTime time.Duration\n\t// ErrorCode contains the top level response error code.\n\tErrorCode int16\n\t// SessionID contains the fetch session ID, or 0 if this is not part of a fetch session.\n\tSessionID int32\n\t// Blocks contains the response topics.\n\tBlocks map[string]map[int32]*FetchResponseBlock\n\n\tLogAppendTime bool\n\tTimestamp     time.Time\n}\n\nfunc (r *FetchResponse) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *FetchResponse) decode(pd packetDecoder, version int16) (err error) {\n\tr.Version = version\n\n\tif r.Version >= 1 {\n\t\tif r.ThrottleTime, err = pd.getDurationMs(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif r.Version >= 7 {\n\t\tr.ErrorCode, err = pd.getInt16()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.SessionID, err = pd.getInt32()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tnumTopics, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr.Blocks = make(map[string]map[int32]*FetchResponseBlock, numTopics)\n\tfor i := 0; i < numTopics; i++ {\n\t\tname, err := pd.getString()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tnumBlocks, err := pd.getArrayLength()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tr.Blocks[name] = make(map[int32]*FetchResponseBlock, numBlocks)\n\n\t\tfor j := 0; j < numBlocks; j++ {\n\t\t\tid, err := pd.getInt32()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tblock := new(FetchResponseBlock)\n\t\t\terr = block.decode(pd, version)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tr.Blocks[name][id] = block\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (r *FetchResponse) encode(pe packetEncoder) (err error) {\n\tif r.Version >= 1 {\n\t\tpe.putDurationMs(r.ThrottleTime)\n\t}\n\n\tif r.Version >= 7 {\n\t\tpe.putInt16(r.ErrorCode)\n\t\tpe.putInt32(r.SessionID)\n\t}\n\n\terr = pe.putArrayLength(len(r.Blocks))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor topic, partitions := range r.Blocks {\n\t\terr = pe.putString(topic)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\terr = pe.putArrayLength(len(partitions))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor id, block := range partitions {\n\t\t\tpe.putInt32(id)\n\t\t\terr = block.encode(pe, r.Version)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (r *FetchResponse) key() int16 {\n\treturn apiKeyFetch\n}\n\nfunc (r *FetchResponse) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *FetchResponse) headerVersion() int16 {\n\treturn 0\n}\n\nfunc (r *FetchResponse) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 11\n}\n\nfunc (r *FetchResponse) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 11:\n\t\treturn V2_3_0_0\n\tcase 9, 10:\n\t\treturn V2_1_0_0\n\tcase 8:\n\t\treturn V2_0_0_0\n\tcase 7:\n\t\treturn V1_1_0_0\n\tcase 6:\n\t\treturn V1_0_0_0\n\tcase 4, 5:\n\t\treturn V0_11_0_0\n\tcase 3:\n\t\treturn V0_10_1_0\n\tcase 2:\n\t\treturn V0_10_0_0\n\tcase 1:\n\t\treturn V0_9_0_0\n\tcase 0:\n\t\treturn V0_8_2_0\n\tdefault:\n\t\treturn V2_3_0_0\n\t}\n}\n\nfunc (r *FetchResponse) throttleTime() time.Duration {\n\treturn r.ThrottleTime\n}\n\nfunc (r *FetchResponse) GetBlock(topic string, partition int32) *FetchResponseBlock {\n\tif r.Blocks == nil {\n\t\treturn nil\n\t}\n\n\tif r.Blocks[topic] == nil {\n\t\treturn nil\n\t}\n\n\treturn r.Blocks[topic][partition]\n}\n\nfunc (r *FetchResponse) AddError(topic string, partition int32, err KError) {\n\tif r.Blocks == nil {\n\t\tr.Blocks = make(map[string]map[int32]*FetchResponseBlock)\n\t}\n\tpartitions, ok := r.Blocks[topic]\n\tif !ok {\n\t\tpartitions = make(map[int32]*FetchResponseBlock)\n\t\tr.Blocks[topic] = partitions\n\t}\n\tfrb, ok := partitions[partition]\n\tif !ok {\n\t\tfrb = new(FetchResponseBlock)\n\t\tpartitions[partition] = frb\n\t}\n\tfrb.Err = err\n}\n\nfunc (r *FetchResponse) getOrCreateBlock(topic string, partition int32) *FetchResponseBlock {\n\tif r.Blocks == nil {\n\t\tr.Blocks = make(map[string]map[int32]*FetchResponseBlock)\n\t}\n\tpartitions, ok := r.Blocks[topic]\n\tif !ok {\n\t\tpartitions = make(map[int32]*FetchResponseBlock)\n\t\tr.Blocks[topic] = partitions\n\t}\n\tfrb, ok := partitions[partition]\n\tif !ok {\n\t\tfrb = new(FetchResponseBlock)\n\t\tpartitions[partition] = frb\n\t}\n\n\treturn frb\n}\n\nfunc encodeKV(key, value Encoder) ([]byte, []byte) {\n\tvar kb []byte\n\tvar vb []byte\n\tif key != nil {\n\t\tkb, _ = key.Encode()\n\t}\n\tif value != nil {\n\t\tvb, _ = value.Encode()\n\t}\n\n\treturn kb, vb\n}\n\nfunc (r *FetchResponse) AddMessageWithTimestamp(topic string, partition int32, key, value Encoder, offset int64, timestamp time.Time, version int8) {\n\tfrb := r.getOrCreateBlock(topic, partition)\n\tkb, vb := encodeKV(key, value)\n\tif r.LogAppendTime {\n\t\ttimestamp = r.Timestamp\n\t}\n\tmsg := &Message{Key: kb, Value: vb, LogAppendTime: r.LogAppendTime, Timestamp: timestamp, Version: version}\n\tmsgBlock := &MessageBlock{Msg: msg, Offset: offset}\n\tif len(frb.RecordsSet) == 0 {\n\t\trecords := newLegacyRecords(&MessageSet{})\n\t\tfrb.RecordsSet = []*Records{&records}\n\t}\n\tset := frb.RecordsSet[0].MsgSet\n\tset.Messages = append(set.Messages, msgBlock)\n}\n\nfunc (r *FetchResponse) AddRecordWithTimestamp(topic string, partition int32, key, value Encoder, offset int64, timestamp time.Time) {\n\tfrb := r.getOrCreateBlock(topic, partition)\n\tkb, vb := encodeKV(key, value)\n\tif len(frb.RecordsSet) == 0 {\n\t\trecords := newDefaultRecords(&RecordBatch{Version: 2, LogAppendTime: r.LogAppendTime, FirstTimestamp: timestamp, MaxTimestamp: r.Timestamp})\n\t\tfrb.RecordsSet = []*Records{&records}\n\t}\n\tbatch := frb.RecordsSet[0].RecordBatch\n\trec := &Record{Key: kb, Value: vb, OffsetDelta: offset, TimestampDelta: timestamp.Sub(batch.FirstTimestamp)}\n\tbatch.addRecord(rec)\n}\n\n// AddRecordBatchWithTimestamp is similar to AddRecordWithTimestamp\n// But instead of appending 1 record to a batch, it append a new batch containing 1 record to the fetchResponse\n// Since transaction are handled on batch level (the whole batch is either committed or aborted), use this to test transactions\nfunc (r *FetchResponse) AddRecordBatchWithTimestamp(topic string, partition int32, key, value Encoder, offset int64, producerID int64, isTransactional bool, timestamp time.Time) {\n\tfrb := r.getOrCreateBlock(topic, partition)\n\tkb, vb := encodeKV(key, value)\n\n\trecords := newDefaultRecords(&RecordBatch{Version: 2, LogAppendTime: r.LogAppendTime, FirstTimestamp: timestamp, MaxTimestamp: r.Timestamp})\n\tbatch := &RecordBatch{\n\t\tVersion:         2,\n\t\tLogAppendTime:   r.LogAppendTime,\n\t\tFirstTimestamp:  timestamp,\n\t\tMaxTimestamp:    r.Timestamp,\n\t\tFirstOffset:     offset,\n\t\tLastOffsetDelta: 0,\n\t\tProducerID:      producerID,\n\t\tIsTransactional: isTransactional,\n\t}\n\trec := &Record{Key: kb, Value: vb, OffsetDelta: 0, TimestampDelta: timestamp.Sub(batch.FirstTimestamp)}\n\tbatch.addRecord(rec)\n\trecords.RecordBatch = batch\n\n\tfrb.RecordsSet = append(frb.RecordsSet, &records)\n}\n\nfunc (r *FetchResponse) AddControlRecordWithTimestamp(topic string, partition int32, offset int64, producerID int64, recordType ControlRecordType, timestamp time.Time) {\n\tfrb := r.getOrCreateBlock(topic, partition)\n\n\t// batch\n\tbatch := &RecordBatch{\n\t\tVersion:         2,\n\t\tLogAppendTime:   r.LogAppendTime,\n\t\tFirstTimestamp:  timestamp,\n\t\tMaxTimestamp:    r.Timestamp,\n\t\tFirstOffset:     offset,\n\t\tLastOffsetDelta: 0,\n\t\tProducerID:      producerID,\n\t\tIsTransactional: true,\n\t\tControl:         true,\n\t}\n\n\t// records\n\trecords := newDefaultRecords(nil)\n\trecords.RecordBatch = batch\n\n\t// record\n\tcrAbort := ControlRecord{\n\t\tVersion: 0,\n\t\tType:    recordType,\n\t}\n\tcrKey := &realEncoder{raw: make([]byte, 4)}\n\tcrValue := &realEncoder{raw: make([]byte, 6)}\n\tcrAbort.encode(crKey, crValue)\n\trec := &Record{Key: ByteEncoder(crKey.raw), Value: ByteEncoder(crValue.raw), OffsetDelta: 0, TimestampDelta: timestamp.Sub(batch.FirstTimestamp)}\n\tbatch.addRecord(rec)\n\n\tfrb.RecordsSet = append(frb.RecordsSet, &records)\n}\n\nfunc (r *FetchResponse) AddMessage(topic string, partition int32, key, value Encoder, offset int64) {\n\tr.AddMessageWithTimestamp(topic, partition, key, value, offset, time.Time{}, 0)\n}\n\nfunc (r *FetchResponse) AddRecord(topic string, partition int32, key, value Encoder, offset int64) {\n\tr.AddRecordWithTimestamp(topic, partition, key, value, offset, time.Time{})\n}\n\nfunc (r *FetchResponse) AddRecordBatch(topic string, partition int32, key, value Encoder, offset int64, producerID int64, isTransactional bool) {\n\tr.AddRecordBatchWithTimestamp(topic, partition, key, value, offset, producerID, isTransactional, time.Time{})\n}\n\nfunc (r *FetchResponse) AddControlRecord(topic string, partition int32, offset int64, producerID int64, recordType ControlRecordType) {\n\t// define controlRecord key and value\n\tr.AddControlRecordWithTimestamp(topic, partition, offset, producerID, recordType, time.Time{})\n}\n\nfunc (r *FetchResponse) SetLastOffsetDelta(topic string, partition int32, offset int32) {\n\tfrb := r.getOrCreateBlock(topic, partition)\n\tif len(frb.RecordsSet) == 0 {\n\t\trecords := newDefaultRecords(&RecordBatch{Version: 2})\n\t\tfrb.RecordsSet = []*Records{&records}\n\t}\n\tbatch := frb.RecordsSet[0].RecordBatch\n\tbatch.LastOffsetDelta = offset\n}\n\nfunc (r *FetchResponse) SetLastStableOffset(topic string, partition int32, offset int64) {\n\tfrb := r.getOrCreateBlock(topic, partition)\n\tfrb.LastStableOffset = offset\n}\n"
  },
  {
    "path": "fetch_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"testing\"\n)\n\nvar (\n\temptyFetchResponse = []byte{\n\t\t0x00, 0x00, 0x00, 0x00,\n\t}\n\n\toneMessageFetchResponse = []byte{\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x05, 't', 'o', 'p', 'i', 'c',\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x00, 0x00, 0x05,\n\t\t0x00, 0x01,\n\t\t0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10,\n\t\t0x00, 0x00, 0x00, 0x1C,\n\t\t// messageSet\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x10,\n\t\t// message\n\t\t0x23, 0x96, 0x4a, 0xf7, // CRC\n\t\t0x00,\n\t\t0x00,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0x00, 0x00, 0x00, 0x02, 0x00, 0xEE,\n\t}\n\n\toverflowMessageFetchResponse = []byte{\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x05, 't', 'o', 'p', 'i', 'c',\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x00, 0x00, 0x05,\n\t\t0x00, 0x01,\n\t\t0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10,\n\t\t0x00, 0x00, 0x00, 0x30,\n\t\t// messageSet\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x10,\n\t\t// message\n\t\t0x23, 0x96, 0x4a, 0xf7, // CRC\n\t\t0x00,\n\t\t0x00,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0x00, 0x00, 0x00, 0x02, 0x00, 0xEE,\n\t\t// overflow messageSet\n\t\t0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0x00, 0x00, 0x00, 0xFF,\n\t\t// overflow bytes\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t}\n\n\toneRecordFetchResponse = []byte{\n\t\t0x00, 0x00, 0x00, 0x00, // ThrottleTime\n\t\t0x00, 0x00, 0x00, 0x01, // Number of Topics\n\t\t0x00, 0x05, 't', 'o', 'p', 'i', 'c', // Topic\n\t\t0x00, 0x00, 0x00, 0x01, // Number of Partitions\n\t\t0x00, 0x00, 0x00, 0x05, // Partition\n\t\t0x00, 0x01, // Error\n\t\t0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, // High Watermark Offset\n\t\t0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, // Last Stable Offset\n\t\t0x00, 0x00, 0x00, 0x00, // Number of Aborted Transactions\n\t\t0x00, 0x00, 0x00, 0x52, // Records length\n\t\t// recordBatch\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x46,\n\t\t0x00, 0x00, 0x00, 0x00,\n\t\t0x02,\n\t\t0xDB, 0x47, 0x14, 0xC9,\n\t\t0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A,\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t// record\n\t\t0x28,\n\t\t0x00,\n\t\t0x0A,\n\t\t0x00,\n\t\t0x08, 0x01, 0x02, 0x03, 0x04,\n\t\t0x06, 0x05, 0x06, 0x07,\n\t\t0x02,\n\t\t0x06, 0x08, 0x09, 0x0A,\n\t\t0x04, 0x0B, 0x0C,\n\t}\n\n\tpartialFetchResponse = []byte{\n\t\t0x00, 0x00, 0x00, 0x00, // ThrottleTime\n\t\t0x00, 0x00, 0x00, 0x01, // Number of Topics\n\t\t0x00, 0x05, 't', 'o', 'p', 'i', 'c', // Topic\n\t\t0x00, 0x00, 0x00, 0x01, // Number of Partitions\n\t\t0x00, 0x00, 0x00, 0x05, // Partition\n\t\t0x00, 0x00, // Error\n\t\t0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, // High Watermark Offset\n\t\t0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, // Last Stable Offset\n\t\t0x00, 0x00, 0x00, 0x00, // Number of Aborted Transactions\n\t\t0x00, 0x00, 0x00, 0x40, // Records length\n\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x46,\n\t\t0x00, 0x00, 0x00, 0x00,\n\t\t0x02,\n\t\t0xDB, 0x47, 0x14, 0xC9,\n\t\t0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A,\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t// record\n\t\t0x28,\n\t\t0x00,\n\t\t0x00,\n\t}\n\n\temptyRecordsFetchResponsev11 = []byte{\n\t\t0x00, 0x00, 0x00, 0x00, // ThrottleTime\n\t\t0x00, 0x00, // Error\n\t\t0x00, 0x00, 0x00, 0x00, // Fetch session\n\t\t0x00, 0x00, 0x00, 0x01, // Num topic\n\t\t0x00, 0x05, 't', 'o', 'p', 'i', 'c', // Topic\n\t\t0x00, 0x00, 0x00, 0x01, // Num partition\n\t\t0x00, 0x00, 0x00, 0x05, // Partition\n\t\t0x00, 0x00, // Error\n\t\t0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, // High Watermark Offset\n\t\t0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, // Last Stable Offset\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Log start offset\n\t\t0x00, 0x00, 0x00, 0x00, // Number of Aborted Transactions\n\t\t0xff, 0xff, 0xff, 0xff, // Replica id\n\t\t0x00, 0x00, 0x00, 0x3D, // Batch size\n\t\t// recordBatch\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Offset\n\t\t0x00, 0x00, 0x00, 0x31, // Message size\n\t\t0x00, 0x00, 0x00, 0x00, // Leader epoch\n\t\t0x02,                   // Magic byte\n\t\t0x14, 0xE0, 0x7A, 0x62, // CRC\n\t\t0x00, 0x00, // Flags\n\t\t0x00, 0x00, 0x00, 0x00, // Last offset delta\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, // First timestamp\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, // Last timestamp\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // Producer id\n\t\t0x00, 0x00, // Producer epoch\n\t\t0x00, 0x00, 0x00, 0x3d, // Base sequence\n\t\t0x00, 0x00, 0x00, 0x00, // Records size\n\t}\n\n\toneMessageFetchResponseV4 = []byte{\n\t\t0x00, 0x00, 0x00, 0x00, // ThrottleTime\n\t\t0x00, 0x00, 0x00, 0x01, // Number of Topics\n\t\t0x00, 0x05, 't', 'o', 'p', 'i', 'c', // Topic\n\t\t0x00, 0x00, 0x00, 0x01, // Number of Partitions\n\t\t0x00, 0x00, 0x00, 0x05, // Partition\n\t\t0x00, 0x01, // Error\n\t\t0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, // High Watermark Offset\n\t\t0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, // Last Stable Offset\n\t\t0x00, 0x00, 0x00, 0x00, // Number of Aborted Transactions\n\t\t0x00, 0x00, 0x00, 0x1C,\n\t\t// messageSet\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x10,\n\t\t// message\n\t\t0x23, 0x96, 0x4a, 0xf7, // CRC\n\t\t0x00,\n\t\t0x00,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0x00, 0x00, 0x00, 0x02, 0x00, 0xEE,\n\t}\n\n\tpreferredReplicaFetchResponseV11 = []byte{\n\t\t0x00, 0x00, 0x00, 0x00, // ThrottleTime\n\t\t0x00, 0x02, // ErrorCode\n\t\t0x00, 0x00, 0x00, 0xAC, // SessionID\n\t\t0x00, 0x00, 0x00, 0x01, // Number of Topics\n\t\t0x00, 0x05, 't', 'o', 'p', 'i', 'c', // Topic\n\t\t0x00, 0x00, 0x00, 0x01, // Number of Partitions\n\t\t0x00, 0x00, 0x00, 0x05, // Partition\n\t\t0x00, 0x01, // Error\n\t\t0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, // High Watermark Offset\n\t\t0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x09, // Last Stable Offset\n\t\t0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, // Log Start Offset\n\t\t0x00, 0x00, 0x00, 0x00, // Number of Aborted Transactions\n\t\t0x00, 0x00, 0x00, 0x03, // Preferred Read Replica\n\t\t0x00, 0x00, 0x00, 0x1C,\n\t\t// messageSet\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x10,\n\t\t// message\n\t\t0x23, 0x96, 0x4a, 0xf7, // CRC\n\t\t0x00,\n\t\t0x00,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0x00, 0x00, 0x00, 0x02, 0x00, 0xEE,\n\t}\n)\n\nfunc TestEmptyFetchResponse(t *testing.T) {\n\tresponse := FetchResponse{}\n\ttestVersionDecodable(t, \"empty\", &response, emptyFetchResponse, 0)\n\n\tif len(response.Blocks) != 0 {\n\t\tt.Error(\"Decoding produced topic blocks where there were none.\")\n\t}\n}\n\nfunc TestOneMessageFetchResponse(t *testing.T) {\n\tresponse := FetchResponse{}\n\ttestVersionDecodable(t, \"one message\", &response, oneMessageFetchResponse, 0)\n\n\tif len(response.Blocks) != 1 {\n\t\tt.Fatal(\"Decoding produced incorrect number of topic blocks.\")\n\t}\n\n\tif len(response.Blocks[\"topic\"]) != 1 {\n\t\tt.Fatal(\"Decoding produced incorrect number of partition blocks for topic.\")\n\t}\n\n\tblock := response.GetBlock(\"topic\", 5)\n\tif block == nil {\n\t\tt.Fatal(\"GetBlock didn't return block.\")\n\t}\n\tif !errors.Is(block.Err, ErrOffsetOutOfRange) {\n\t\tt.Error(\"Decoding didn't produce correct error code.\")\n\t}\n\tif block.HighWaterMarkOffset != 0x10101010 {\n\t\tt.Error(\"Decoding didn't produce correct high water mark offset.\")\n\t}\n\tif block.PreferredReadReplica != -1 {\n\t\tt.Error(\"Decoding didn't produce correct preferred read replica.\")\n\t}\n\tpartial, err := block.isPartial()\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\tif partial {\n\t\tt.Error(\"Decoding detected a partial trailing message where there wasn't one.\")\n\t}\n\n\tn, err := block.numRecords()\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\tif n != 1 {\n\t\tt.Fatal(\"Decoding produced incorrect number of messages.\")\n\t}\n\tmsgBlock := block.RecordsSet[0].MsgSet.Messages[0]\n\tif msgBlock.Offset != 0x550000 {\n\t\tt.Error(\"Decoding produced incorrect message offset.\")\n\t}\n\tmsg := msgBlock.Msg\n\tif msg.Codec != CompressionNone {\n\t\tt.Error(\"Decoding produced incorrect message compression.\")\n\t}\n\tif msg.Key != nil {\n\t\tt.Error(\"Decoding produced message key where there was none.\")\n\t}\n\tif !bytes.Equal(msg.Value, []byte{0x00, 0xEE}) {\n\t\tt.Error(\"Decoding produced incorrect message value.\")\n\t}\n}\n\nfunc TestOverflowMessageFetchResponse(t *testing.T) {\n\tresponse := FetchResponse{}\n\ttestVersionDecodable(t, \"overflow message\", &response, overflowMessageFetchResponse, 0)\n\n\tif len(response.Blocks) != 1 {\n\t\tt.Fatal(\"Decoding produced incorrect number of topic blocks.\")\n\t}\n\n\tif len(response.Blocks[\"topic\"]) != 1 {\n\t\tt.Fatal(\"Decoding produced incorrect number of partition blocks for topic.\")\n\t}\n\n\tblock := response.GetBlock(\"topic\", 5)\n\tif block == nil {\n\t\tt.Fatal(\"GetBlock didn't return block.\")\n\t}\n\tif !errors.Is(block.Err, ErrOffsetOutOfRange) {\n\t\tt.Error(\"Decoding didn't produce correct error code.\")\n\t}\n\tif block.HighWaterMarkOffset != 0x10101010 {\n\t\tt.Error(\"Decoding didn't produce correct high water mark offset.\")\n\t}\n\tpartial, err := block.Records.isPartial()\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\tif partial {\n\t\tt.Error(\"Decoding detected a partial trailing message where there wasn't one.\")\n\t}\n\toverflow, err := block.Records.isOverflow()\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\tif !overflow {\n\t\tt.Error(\"Decoding detected a partial trailing message where there wasn't one.\")\n\t}\n\n\tn, err := block.Records.numRecords()\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\tif n != 1 {\n\t\tt.Fatal(\"Decoding produced incorrect number of messages.\")\n\t}\n\tmsgBlock := block.Records.MsgSet.Messages[0]\n\tif msgBlock.Offset != 0x550000 {\n\t\tt.Error(\"Decoding produced incorrect message offset.\")\n\t}\n\tmsg := msgBlock.Msg\n\tif msg.Codec != CompressionNone {\n\t\tt.Error(\"Decoding produced incorrect message compression.\")\n\t}\n\tif msg.Key != nil {\n\t\tt.Error(\"Decoding produced message key where there was none.\")\n\t}\n\tif !bytes.Equal(msg.Value, []byte{0x00, 0xEE}) {\n\t\tt.Error(\"Decoding produced incorrect message value.\")\n\t}\n}\n\nfunc TestOneRecordFetchResponse(t *testing.T) {\n\tresponse := FetchResponse{}\n\ttestVersionDecodable(t, \"one record\", &response, oneRecordFetchResponse, 4)\n\n\tif len(response.Blocks) != 1 {\n\t\tt.Fatal(\"Decoding produced incorrect number of topic blocks.\")\n\t}\n\n\tif len(response.Blocks[\"topic\"]) != 1 {\n\t\tt.Fatal(\"Decoding produced incorrect number of partition blocks for topic.\")\n\t}\n\n\tblock := response.GetBlock(\"topic\", 5)\n\tif block == nil {\n\t\tt.Fatal(\"GetBlock didn't return block.\")\n\t}\n\tif !errors.Is(block.Err, ErrOffsetOutOfRange) {\n\t\tt.Error(\"Decoding didn't produce correct error code.\")\n\t}\n\tif block.HighWaterMarkOffset != 0x10101010 {\n\t\tt.Error(\"Decoding didn't produce correct high water mark offset.\")\n\t}\n\tif block.PreferredReadReplica != -1 {\n\t\tt.Error(\"Decoding didn't produce correct preferred read replica.\")\n\t}\n\tpartial, err := block.isPartial()\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\tif partial {\n\t\tt.Error(\"Decoding detected a partial trailing record where there wasn't one.\")\n\t}\n\n\tn, err := block.numRecords()\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\tif n != 1 {\n\t\tt.Fatal(\"Decoding produced incorrect number of records.\")\n\t}\n\trec := block.RecordsSet[0].RecordBatch.Records[0]\n\tif !bytes.Equal(rec.Key, []byte{0x01, 0x02, 0x03, 0x04}) {\n\t\tt.Error(\"Decoding produced incorrect record key.\")\n\t}\n\tif !bytes.Equal(rec.Value, []byte{0x05, 0x06, 0x07}) {\n\t\tt.Error(\"Decoding produced incorrect record value.\")\n\t}\n}\n\nfunc TestPartailFetchResponse(t *testing.T) {\n\tresponse := FetchResponse{}\n\ttestVersionDecodable(t, \"partial record\", &response, partialFetchResponse, 4)\n\n\tif len(response.Blocks) != 1 {\n\t\tt.Fatal(\"Decoding produced incorrect number of topic blocks.\")\n\t}\n\n\tif len(response.Blocks[\"topic\"]) != 1 {\n\t\tt.Fatal(\"Decoding produced incorrect number of partition blocks for topic.\")\n\t}\n\n\tblock := response.GetBlock(\"topic\", 5)\n\tif block == nil {\n\t\tt.Fatal(\"GetBlock didn't return block.\")\n\t}\n\tif !errors.Is(block.Err, ErrNoError) {\n\t\tt.Error(\"Decoding didn't produce correct error code.\")\n\t}\n\tif block.HighWaterMarkOffset != 0x10101010 {\n\t\tt.Error(\"Decoding didn't produce correct high water mark offset.\")\n\t}\n\tif block.PreferredReadReplica != -1 {\n\t\tt.Error(\"Decoding didn't produce correct preferred read replica.\")\n\t}\n\tpartial, err := block.isPartial()\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\tif !partial {\n\t\tt.Error(\"Decoding not a partial trailing record\")\n\t}\n\n\tn, err := block.numRecords()\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\tif n != 0 {\n\t\tt.Fatal(\"Decoding produced incorrect number of records.\")\n\t}\n}\n\nfunc TestEmptyRecordsFetchResponse(t *testing.T) {\n\tresponse := FetchResponse{}\n\ttestVersionDecodable(t, \"empty record\", &response, emptyRecordsFetchResponsev11, 11)\n\n\tif len(response.Blocks) != 1 {\n\t\tt.Fatal(\"Decoding produced incorrect number of topic blocks.\")\n\t}\n\n\tif len(response.Blocks[\"topic\"]) != 1 {\n\t\tt.Fatal(\"Decoding produced incorrect number of partition blocks for topic.\")\n\t}\n\n\tblock := response.GetBlock(\"topic\", 5)\n\tif block == nil {\n\t\tt.Fatal(\"GetBlock didn't return block.\")\n\t}\n\tif !errors.Is(block.Err, ErrNoError) {\n\t\tt.Error(\"Decoding didn't produce correct error code.\")\n\t}\n\tif block.HighWaterMarkOffset != 0x10101010 {\n\t\tt.Error(\"Decoding didn't produce correct high water mark offset.\")\n\t}\n\tif block.PreferredReadReplica != -1 {\n\t\tt.Error(\"Decoding didn't produce correct preferred read replica.\")\n\t}\n\tpartial, err := block.isPartial()\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\tif partial {\n\t\tt.Error(\"Decoding a partial trailing record\")\n\t}\n\n\tn, err := block.numRecords()\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\tif n != 0 {\n\t\tt.Fatal(\"Decoding produced incorrect number of records.\")\n\t}\n\tif *block.recordsNextOffset != 1 {\n\t\tt.Fatal(\"Last records batch offset is incorrect.\")\n\t}\n}\n\nfunc TestOneMessageFetchResponseV4(t *testing.T) {\n\tresponse := FetchResponse{}\n\ttestVersionDecodable(t, \"one message v4\", &response, oneMessageFetchResponseV4, 4)\n\n\tif len(response.Blocks) != 1 {\n\t\tt.Fatal(\"Decoding produced incorrect number of topic blocks.\")\n\t}\n\n\tif len(response.Blocks[\"topic\"]) != 1 {\n\t\tt.Fatal(\"Decoding produced incorrect number of partition blocks for topic.\")\n\t}\n\n\tblock := response.GetBlock(\"topic\", 5)\n\tif block == nil {\n\t\tt.Fatal(\"GetBlock didn't return block.\")\n\t}\n\tif !errors.Is(block.Err, ErrOffsetOutOfRange) {\n\t\tt.Error(\"Decoding didn't produce correct error code.\")\n\t}\n\tif block.HighWaterMarkOffset != 0x10101010 {\n\t\tt.Error(\"Decoding didn't produce correct high water mark offset.\")\n\t}\n\tif block.PreferredReadReplica != -1 {\n\t\tt.Error(\"Decoding didn't produce correct preferred read replica.\")\n\t}\n\tpartial, err := block.isPartial()\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\tif partial {\n\t\tt.Error(\"Decoding detected a partial trailing record where there wasn't one.\")\n\t}\n\n\tn, err := block.numRecords()\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\tif n != 1 {\n\t\tt.Fatal(\"Decoding produced incorrect number of records.\")\n\t}\n\tmsgBlock := block.RecordsSet[0].MsgSet.Messages[0]\n\tif msgBlock.Offset != 0x550000 {\n\t\tt.Error(\"Decoding produced incorrect message offset.\")\n\t}\n\tmsg := msgBlock.Msg\n\tif msg.Codec != CompressionNone {\n\t\tt.Error(\"Decoding produced incorrect message compression.\")\n\t}\n\tif msg.Key != nil {\n\t\tt.Error(\"Decoding produced message key where there was none.\")\n\t}\n\tif !bytes.Equal(msg.Value, []byte{0x00, 0xEE}) {\n\t\tt.Error(\"Decoding produced incorrect message value.\")\n\t}\n}\n\nfunc TestPreferredReplicaFetchResponseV11(t *testing.T) {\n\tresponse := FetchResponse{}\n\ttestVersionDecodable(\n\t\tt, \"preferred replica fetch response v11\", &response,\n\t\tpreferredReplicaFetchResponseV11, 11)\n\n\tif response.ErrorCode != 0x0002 {\n\t\tt.Fatal(\"Decoding produced incorrect error code.\")\n\t}\n\n\tif response.SessionID != 0x000000AC {\n\t\tt.Fatal(\"Decoding produced incorrect session ID.\")\n\t}\n\n\tif len(response.Blocks) != 1 {\n\t\tt.Fatal(\"Decoding produced incorrect number of topic blocks.\")\n\t}\n\n\tif len(response.Blocks[\"topic\"]) != 1 {\n\t\tt.Fatal(\"Decoding produced incorrect number of partition blocks for topic.\")\n\t}\n\n\tblock := response.GetBlock(\"topic\", 5)\n\tif block == nil {\n\t\tt.Fatal(\"GetBlock didn't return block.\")\n\t}\n\tif !errors.Is(block.Err, ErrOffsetOutOfRange) {\n\t\tt.Error(\"Decoding didn't produce correct error code.\")\n\t}\n\tif block.HighWaterMarkOffset != 0x10101010 {\n\t\tt.Error(\"Decoding didn't produce correct high water mark offset.\")\n\t}\n\tif block.LastStableOffset != 0x10101009 {\n\t\tt.Error(\"Decoding didn't produce correct last stable offset.\")\n\t}\n\tif block.LogStartOffset != 0x01010101 {\n\t\tt.Error(\"Decoding didn't produce correct log start offset.\")\n\t}\n\tif block.PreferredReadReplica != 0x0003 {\n\t\tt.Error(\"Decoding didn't produce correct preferred read replica.\")\n\t}\n\tpartial, err := block.isPartial()\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\tif partial {\n\t\tt.Error(\"Decoding detected a partial trailing record where there wasn't one.\")\n\t}\n\n\tn, err := block.numRecords()\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\tif n != 1 {\n\t\tt.Fatal(\"Decoding produced incorrect number of records.\")\n\t}\n\tmsgBlock := block.RecordsSet[0].MsgSet.Messages[0]\n\tif msgBlock.Offset != 0x550000 {\n\t\tt.Error(\"Decoding produced incorrect message offset.\")\n\t}\n\tmsg := msgBlock.Msg\n\tif msg.Codec != CompressionNone {\n\t\tt.Error(\"Decoding produced incorrect message compression.\")\n\t}\n\tif msg.Key != nil {\n\t\tt.Error(\"Decoding produced message key where there was none.\")\n\t}\n\tif !bytes.Equal(msg.Value, []byte{0x00, 0xEE}) {\n\t\tt.Error(\"Decoding produced incorrect message value.\")\n\t}\n}\n"
  },
  {
    "path": "find_coordinator_request.go",
    "content": "package sarama\n\ntype CoordinatorType int8\n\nconst (\n\tCoordinatorGroup CoordinatorType = iota\n\tCoordinatorTransaction\n)\n\ntype FindCoordinatorRequest struct {\n\tVersion         int16\n\tCoordinatorKey  string\n\tCoordinatorType CoordinatorType\n}\n\nfunc (f *FindCoordinatorRequest) setVersion(v int16) {\n\tf.Version = v\n}\n\nfunc (f *FindCoordinatorRequest) encode(pe packetEncoder) error {\n\tif err := pe.putString(f.CoordinatorKey); err != nil {\n\t\treturn err\n\t}\n\n\tif f.Version >= 1 {\n\t\tpe.putInt8(int8(f.CoordinatorType))\n\t}\n\n\treturn nil\n}\n\nfunc (f *FindCoordinatorRequest) decode(pd packetDecoder, version int16) (err error) {\n\tif f.CoordinatorKey, err = pd.getString(); err != nil {\n\t\treturn err\n\t}\n\n\tif version >= 1 {\n\t\tf.Version = version\n\t\tcoordinatorType, err := pd.getInt8()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tf.CoordinatorType = CoordinatorType(coordinatorType)\n\t}\n\n\treturn nil\n}\n\nfunc (f *FindCoordinatorRequest) key() int16 {\n\treturn apiKeyFindCoordinator\n}\n\nfunc (f *FindCoordinatorRequest) version() int16 {\n\treturn f.Version\n}\n\nfunc (r *FindCoordinatorRequest) headerVersion() int16 {\n\treturn 1\n}\n\nfunc (f *FindCoordinatorRequest) isValidVersion() bool {\n\treturn f.Version >= 0 && f.Version <= 2\n}\n\nfunc (f *FindCoordinatorRequest) requiredVersion() KafkaVersion {\n\tswitch f.Version {\n\tcase 2:\n\t\treturn V2_0_0_0\n\tcase 1:\n\t\treturn V0_11_0_0\n\tdefault:\n\t\treturn V0_8_2_0\n\t}\n}\n"
  },
  {
    "path": "find_coordinator_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nvar (\n\tfindCoordinatorRequestConsumerGroup = []byte{\n\t\t0, 5, 'g', 'r', 'o', 'u', 'p',\n\t\t0,\n\t}\n\n\tfindCoordinatorRequestTransaction = []byte{\n\t\t0, 13, 't', 'r', 'a', 'n', 's', 'a', 'c', 't', 'i', 'o', 'n', 'i', 'd',\n\t\t1,\n\t}\n)\n\nfunc TestFindCoordinatorRequest(t *testing.T) {\n\treq := &FindCoordinatorRequest{\n\t\tVersion:         1,\n\t\tCoordinatorKey:  \"group\",\n\t\tCoordinatorType: CoordinatorGroup,\n\t}\n\n\ttestRequest(t, \"version 1 - group\", req, findCoordinatorRequestConsumerGroup)\n\n\treq = &FindCoordinatorRequest{\n\t\tVersion:         1,\n\t\tCoordinatorKey:  \"transactionid\",\n\t\tCoordinatorType: CoordinatorTransaction,\n\t}\n\n\ttestRequest(t, \"version 1 - transaction\", req, findCoordinatorRequestTransaction)\n}\n"
  },
  {
    "path": "find_coordinator_response.go",
    "content": "package sarama\n\nimport (\n\t\"time\"\n)\n\nvar NoNode = &Broker{id: -1, addr: \":-1\"}\n\ntype FindCoordinatorResponse struct {\n\tVersion      int16\n\tThrottleTime time.Duration\n\tErr          KError\n\tErrMsg       *string\n\tCoordinator  *Broker\n}\n\nfunc (f *FindCoordinatorResponse) setVersion(v int16) {\n\tf.Version = v\n}\n\nfunc (f *FindCoordinatorResponse) decode(pd packetDecoder, version int16) (err error) {\n\tif version >= 1 {\n\t\tf.Version = version\n\n\t\tif f.ThrottleTime, err = pd.getDurationMs(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tf.Err, err = pd.getKError()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif version >= 1 {\n\t\tif f.ErrMsg, err = pd.getNullableString(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tcoordinator := new(Broker)\n\t// The version is hardcoded to 0, as version 1 of the Broker-decode\n\t// contains the rack-field which is not present in the FindCoordinatorResponse.\n\tif err := coordinator.decode(pd, 0); err != nil {\n\t\treturn err\n\t}\n\tif coordinator.addr == \":0\" {\n\t\treturn nil\n\t}\n\tf.Coordinator = coordinator\n\n\treturn nil\n}\n\nfunc (f *FindCoordinatorResponse) encode(pe packetEncoder) error {\n\tif f.Version >= 1 {\n\t\tpe.putDurationMs(f.ThrottleTime)\n\t}\n\n\tpe.putKError(f.Err)\n\n\tif f.Version >= 1 {\n\t\tif err := pe.putNullableString(f.ErrMsg); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tcoordinator := f.Coordinator\n\tif coordinator == nil {\n\t\tcoordinator = NoNode\n\t}\n\tif err := coordinator.encode(pe, 0); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (f *FindCoordinatorResponse) key() int16 {\n\treturn apiKeyFindCoordinator\n}\n\nfunc (f *FindCoordinatorResponse) version() int16 {\n\treturn f.Version\n}\n\nfunc (r *FindCoordinatorResponse) headerVersion() int16 {\n\treturn 0\n}\n\nfunc (f *FindCoordinatorResponse) isValidVersion() bool {\n\treturn f.Version >= 0 && f.Version <= 2\n}\n\nfunc (f *FindCoordinatorResponse) requiredVersion() KafkaVersion {\n\tswitch f.Version {\n\tcase 2:\n\t\treturn V2_0_0_0\n\tcase 1:\n\t\treturn V0_11_0_0\n\tdefault:\n\t\treturn V0_8_2_0\n\t}\n}\n\nfunc (r *FindCoordinatorResponse) throttleTime() time.Duration {\n\treturn r.ThrottleTime\n}\n"
  },
  {
    "path": "find_coordinator_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestFindCoordinatorResponse(t *testing.T) {\n\terrMsg := \"kaboom\"\n\n\tfor _, tc := range []struct {\n\t\tdesc     string\n\t\tresponse *FindCoordinatorResponse\n\t\tencoded  []byte\n\t}{{\n\t\tdesc: \"version 0 - no error\",\n\t\tresponse: &FindCoordinatorResponse{\n\t\t\tVersion: 0,\n\t\t\tErr:     ErrNoError,\n\t\t\tCoordinator: &Broker{\n\t\t\t\tid:   7,\n\t\t\t\taddr: \"host:9092\",\n\t\t\t},\n\t\t},\n\t\tencoded: []byte{\n\t\t\t0, 0, // Err\n\t\t\t0, 0, 0, 7, // Coordinator.ID\n\t\t\t0, 4, 'h', 'o', 's', 't', // Coordinator.Host\n\t\t\t0, 0, 35, 132, // Coordinator.Port\n\t\t},\n\t}, {\n\t\tdesc: \"version 1 - no error\",\n\t\tresponse: &FindCoordinatorResponse{\n\t\t\tVersion:      1,\n\t\t\tThrottleTime: 100 * time.Millisecond,\n\t\t\tErr:          ErrNoError,\n\t\t\tCoordinator: &Broker{\n\t\t\t\tid:   7,\n\t\t\t\taddr: \"host:9092\",\n\t\t\t},\n\t\t},\n\t\tencoded: []byte{\n\t\t\t0, 0, 0, 100, // ThrottleTime\n\t\t\t0, 0, // Err\n\t\t\t255, 255, // ErrMsg: empty\n\t\t\t0, 0, 0, 7, // Coordinator.ID\n\t\t\t0, 4, 'h', 'o', 's', 't', // Coordinator.Host\n\t\t\t0, 0, 35, 132, // Coordinator.Port\n\t\t},\n\t}, {\n\t\tdesc: \"version 0 - error\",\n\t\tresponse: &FindCoordinatorResponse{\n\t\t\tVersion:     0,\n\t\t\tErr:         ErrConsumerCoordinatorNotAvailable,\n\t\t\tCoordinator: NoNode,\n\t\t},\n\t\tencoded: []byte{\n\t\t\t0, 15, // Err\n\t\t\t255, 255, 255, 255, // Coordinator.ID: -1\n\t\t\t0, 0, // Coordinator.Host: \"\"\n\t\t\t255, 255, 255, 255, // Coordinator.Port: -1\n\t\t},\n\t}, {\n\t\tdesc: \"version 1 - error\",\n\t\tresponse: &FindCoordinatorResponse{\n\t\t\tVersion:      1,\n\t\t\tThrottleTime: 100 * time.Millisecond,\n\t\t\tErr:          ErrConsumerCoordinatorNotAvailable,\n\t\t\tErrMsg:       &errMsg,\n\t\t\tCoordinator:  NoNode,\n\t\t},\n\t\tencoded: []byte{\n\t\t\t0, 0, 0, 100, // ThrottleTime\n\t\t\t0, 15, // Err\n\t\t\t0, 6, 'k', 'a', 'b', 'o', 'o', 'm', // ErrMsg\n\t\t\t255, 255, 255, 255, // Coordinator.ID: -1\n\t\t\t0, 0, // Coordinator.Host: \"\"\n\t\t\t255, 255, 255, 255, // Coordinator.Port: -1\n\t\t},\n\t}} {\n\t\ttestResponse(t, tc.desc, tc.response, tc.encoded)\n\t}\n}\n"
  },
  {
    "path": "functional_admin_test.go",
    "content": "//go:build functional\n\npackage sarama\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"maps\"\n\t\"slices\"\n\t\"strconv\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/davecgh/go-spew/spew\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestFuncAdminQuotas(t *testing.T) {\n\tconst (\n\t\twaitFor = 10 * time.Second\n\t\ttick    = 100 * time.Millisecond\n\t)\n\tcheckKafkaVersion(t, \"2.6.0.0\")\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tkafkaVersion, err := ParseKafkaVersion(FunctionalTestEnv.KafkaVersion)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tconfig := NewFunctionalTestConfig()\n\tconfig.Version = kafkaVersion\n\tadminClient, err := NewClusterAdmin(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer safeClose(t, adminClient)\n\n\t// Check that we can read the quotas, and that they are empty\n\tquotas, err := adminClient.DescribeClientQuotas(nil, false)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif len(quotas) != 0 {\n\t\tt.Fatalf(\"Expected quotas to be empty at start, found: %v\", quotas)\n\t}\n\n\t// Put a quota on default user\n\t// /config/users/<default>\n\tdefaultUser := []QuotaEntityComponent{{\n\t\tEntityType: QuotaEntityUser,\n\t\tMatchType:  QuotaMatchDefault,\n\t}}\n\tproduceOp := ClientQuotasOp{\n\t\tKey:   \"producer_byte_rate\",\n\t\tValue: 1024000,\n\t}\n\tif err = adminClient.AlterClientQuotas(defaultUser, produceOp, false); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Poll until we have the expected quota entry\n\tdefaultUserFilter := QuotaFilterComponent{\n\t\tEntityType: QuotaEntityUser,\n\t\tMatchType:  QuotaMatchDefault,\n\t}\n\trequire.EventuallyWithT(t, func(t *assert.CollectT) {\n\t\tquotas, err = adminClient.DescribeClientQuotas(\n\t\t\t[]QuotaFilterComponent{defaultUserFilter},\n\t\t\tfalse,\n\t\t)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, quotas, \"Expected not empty quotas for default user\")\n\t\trequire.Len(t, quotas, 1, \"Expected one quota entry for default user\")\n\t}, waitFor, tick, \"Quotas state has still not updated for default user\")\n\n\t// Put a quota on specific client-id for a specific user\n\t// /config/users/<user>/clients/<client-id>\n\tspecificUserClientID := []QuotaEntityComponent{\n\t\t{\n\t\t\tEntityType: QuotaEntityUser,\n\t\t\tMatchType:  QuotaMatchExact,\n\t\t\tName:       \"sarama\",\n\t\t},\n\t\t{\n\t\t\tEntityType: QuotaEntityClientID,\n\t\t\tMatchType:  QuotaMatchExact,\n\t\t\tName:       \"sarama-consumer\",\n\t\t},\n\t}\n\tconsumeOp := ClientQuotasOp{\n\t\tKey:   \"consumer_byte_rate\",\n\t\tValue: 2048000,\n\t}\n\tif err = adminClient.AlterClientQuotas(specificUserClientID, consumeOp, false); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Check that we can query a specific quota entry\n\tuserFilter := QuotaFilterComponent{\n\t\tEntityType: QuotaEntityUser,\n\t\tMatchType:  QuotaMatchExact,\n\t\tMatch:      \"sarama\",\n\t}\n\tclientFilter := QuotaFilterComponent{\n\t\tEntityType: QuotaEntityClientID,\n\t\tMatchType:  QuotaMatchExact,\n\t\tMatch:      \"sarama-consumer\",\n\t}\n\trequire.EventuallyWithT(t, func(t *assert.CollectT) {\n\t\tquotas, err = adminClient.DescribeClientQuotas(\n\t\t\t[]QuotaFilterComponent{userFilter, clientFilter},\n\t\t\ttrue,\n\t\t)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEmpty(t, quotas, \"Expected not empty quotas for specific clientID\")\n\t\trequire.Len(t, quotas, 1, \"Expected one quota entry for specific clientID\")\n\t\trequire.InDelta(\n\t\t\tt,\n\t\t\tquotas[0].Values[consumeOp.Key],\n\t\t\tconsumeOp.Value,\n\t\t\t0.01,\n\t\t\t\"Expected specific quota value to be %f, found: %v\",\n\t\t\tconsumeOp.Value,\n\t\t\tquotas[0].Values[consumeOp.Key],\n\t\t)\n\t}, waitFor, tick, \"Quotas state for specific clientID has still not updated\")\n\n\t// Remove quota entries\n\tdeleteProduceOp := ClientQuotasOp{\n\t\tKey:    produceOp.Key,\n\t\tRemove: true,\n\t}\n\tif err = adminClient.AlterClientQuotas(defaultUser, deleteProduceOp, false); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdeleteConsumeOp := ClientQuotasOp{\n\t\tKey:    consumeOp.Key,\n\t\tRemove: true,\n\t}\n\tif err = adminClient.AlterClientQuotas(specificUserClientID, deleteConsumeOp, false); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestFuncAdminDescribeGroups(t *testing.T) {\n\tcheckKafkaVersion(t, \"2.3.0.0\")\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tgroup1 := testFuncConsumerGroupID(t)\n\tgroup2 := testFuncConsumerGroupID(t)\n\n\tkafkaVersion, err := ParseKafkaVersion(FunctionalTestEnv.KafkaVersion)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tconfig := NewFunctionalTestConfig()\n\tconfig.Version = kafkaVersion\n\tadminClient, err := NewClusterAdmin(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer safeClose(t, adminClient)\n\n\tconfig1 := NewFunctionalTestConfig()\n\tconfig1.ClientID = \"M1\"\n\tconfig1.Version = V2_3_0_0\n\tconfig1.Consumer.Offsets.Initial = OffsetNewest\n\tm1 := runTestFuncConsumerGroupMemberWithConfig(t, config1, group1, 100, nil, \"test.4\")\n\tdefer m1.Close()\n\n\tconfig2 := NewFunctionalTestConfig()\n\tconfig2.ClientID = \"M2\"\n\tconfig2.Version = V2_3_0_0\n\tconfig2.Consumer.Offsets.Initial = OffsetNewest\n\tconfig2.Consumer.Group.InstanceId = \"Instance2\"\n\tm2 := runTestFuncConsumerGroupMemberWithConfig(t, config2, group2, 100, nil, \"test.4\")\n\tdefer m2.Close()\n\n\tm1.WaitForState(2)\n\tm2.WaitForState(2)\n\n\tres, err := adminClient.DescribeConsumerGroups([]string{group1, group2})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif len(res) != 2 {\n\t\tt.Errorf(\"group description should be 2, got %v\\n\", len(res))\n\t}\n\tif len(res[0].Members) != 1 {\n\t\tt.Errorf(\"should have 1 members in group , got %v\\n\", len(res[0].Members))\n\t}\n\tif len(res[1].Members) != 1 {\n\t\tt.Errorf(\"should have 1 members in group , got %v\\n\", len(res[1].Members))\n\t}\n\n\tm1.AssertCleanShutdown()\n\tm2.AssertCleanShutdown()\n}\n\nfunc TestFuncAdminListConsumerGroups(t *testing.T) {\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tgroup1 := testFuncConsumerGroupID(t)\n\tgroup2 := testFuncConsumerGroupID(t)\n\n\tconfig := NewFunctionalTestConfig()\n\tadminClient, err := NewClusterAdmin(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer safeClose(t, adminClient)\n\n\tconfig1 := NewFunctionalTestConfig()\n\tconfig1.ClientID = \"M1\"\n\tconfig1.Consumer.Offsets.Initial = OffsetNewest\n\tm1 := runTestFuncConsumerGroupMemberWithConfig(t, config1, group1, 100, nil, \"test.4\")\n\tdefer m1.Close()\n\n\tconfig2 := NewFunctionalTestConfig()\n\tconfig2.ClientID = \"M2\"\n\tconfig2.Consumer.Offsets.Initial = OffsetNewest\n\tconfig2.Consumer.Group.InstanceId = \"Instance2\"\n\tm2 := runTestFuncConsumerGroupMemberWithConfig(t, config2, group2, 100, nil, \"test.4\")\n\tdefer m2.Close()\n\n\tm1.WaitForState(2)\n\tm2.WaitForState(2)\n\n\tres, err := adminClient.ListConsumerGroups()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tassert.GreaterOrEqual(t, len(res), 2)\n\tassert.Contains(t, slices.Collect(maps.Keys(res)), group1)\n\tassert.Contains(t, slices.Collect(maps.Keys(res)), group2)\n\n\tm1.AssertCleanShutdown()\n\tm2.AssertCleanShutdown()\n}\n\nfunc TestFuncAdminListConsumerGroupOffsets(t *testing.T) {\n\tcheckKafkaVersion(t, \"0.8.2.0\")\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tconfig := NewFunctionalTestConfig()\n\tconfig.ClientID = t.Name()\n\tclient, err := NewClient(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\tdefer safeClose(t, client)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tgroup := testFuncConsumerGroupID(t)\n\tconsumerGroup, err := NewConsumerGroupFromClient(group, client)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer safeClose(t, consumerGroup)\n\n\toffsetMgr, _ := NewOffsetManagerFromClient(group, client)\n\tdefer safeClose(t, offsetMgr)\n\tmarkOffset(t, offsetMgr, \"test.4\", 0, 2)\n\toffsetMgr.Commit()\n\n\tcoordinator, err := client.Coordinator(group)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tt.Logf(\"coordinator broker %d\", coordinator.id)\n\n\tadminClient, err := NewClusterAdminFromClient(client)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t{\n\t\tresp, err := adminClient.ListConsumerGroupOffsets(group, map[string][]int32{\"test.4\": {0, 1, 2, 3}})\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tt.Log(spew.Sdump(resp))\n\t}\n\n\tbrokerID := coordinator.id\n\tif err := stopDockerTestBroker(context.Background(), brokerID); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tt.Cleanup(\n\t\tfunc() {\n\t\t\tif err := startDockerTestBroker(context.Background(), brokerID); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t},\n\t)\n\n\t{\n\t\tresp, err := adminClient.ListConsumerGroupOffsets(group, map[string][]int32{\"test.4\": {0, 1, 2, 3}})\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tt.Log(spew.Sdump(resp))\n\t}\n\n\tcoordinator, err = adminClient.Coordinator(group)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tt.Logf(\"coordinator broker %d\", coordinator.id)\n}\n\nfunc TestFuncAdminDescribeLogDirs(t *testing.T) {\n\tcheckKafkaVersion(t, \"2.0.0.0\")\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tkafkaVersion, err := ParseKafkaVersion(FunctionalTestEnv.KafkaVersion)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tconfig := NewFunctionalTestConfig()\n\tadminClient, err := NewClusterAdmin(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer safeClose(t, adminClient)\n\n\tbrokerIDs := make([]int32, len(FunctionalTestEnv.KafkaBrokerAddrs))\n\tfor i := range brokerIDs {\n\t\tbrokerIDs[i] = int32(i + 1)\n\t}\n\n\tres, err := adminClient.DescribeLogDirs(brokerIDs)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif len(res) != len(FunctionalTestEnv.KafkaBrokerAddrs) {\n\t\tt.Errorf(\"should have %d broker replies, got %v\\n\", len(FunctionalTestEnv.KafkaBrokerAddrs), len(res))\n\t}\n\n\tfor _, resp := range res {\n\t\tfor _, logDir := range resp {\n\t\t\tassert.Equal(t, logDir.ErrorCode, ErrNoError)\n\t\t\t// assert that total bytes and usable bytes were returned for kafka 3.3 and newer\n\t\t\tif kafkaVersion.IsAtLeast(V3_3_0_0) {\n\t\t\t\tassert.NotZero(t, logDir.TotalBytes)\n\t\t\t\tassert.NotZero(t, logDir.UsableBytes)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestFuncAdminDeleteGroup(t *testing.T) {\n\tcheckKafkaVersion(t, \"2.4.0\")\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\tconfig := NewFunctionalTestConfig()\n\tclient, err := NewClient(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\tdefer safeClose(t, client)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// create a consumer group\n\tgroupID := testFuncConsumerGroupID(t)\n\tconsumerGroup, err := NewConsumerGroupFromClient(groupID, client)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer safeClose(t, consumerGroup)\n\n\toffsetMgr, _ := NewOffsetManagerFromClient(groupID, client)\n\tdefer safeClose(t, offsetMgr)\n\tmarkOffset(t, offsetMgr, \"test.1\", 0, 1)\n\toffsetMgr.Commit()\n\n\tadmin, err := NewClusterAdminFromClient(client)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tgroups, err := admin.ListConsumerGroups()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif _, ok := groups[groupID]; !ok {\n\t\tt.Fatalf(\"Expected test group, %s, not found.\", groupID)\n\t}\n\n\terr = admin.DeleteConsumerGroup(groupID)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tgroups, err = admin.ListConsumerGroups()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif _, ok := groups[groupID]; ok {\n\t\tt.Fatalf(\"Expected test group, %s, found after delete.\", groupID)\n\t}\n}\n\nfunc TestFuncAdminDeleteTopic(t *testing.T) {\n\tcheckKafkaVersion(t, \"0.10.0.0\")\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tconfig := NewFunctionalTestConfig()\n\tadminClient, err := NewClusterAdmin(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer safeClose(t, adminClient)\n\n\terr = adminClient.CreateTopic(\"delete_topic_test\", &TopicDetail{NumPartitions: 1, ReplicationFactor: 1}, false)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\terr = adminClient.DeleteTopic(\"delete_topic_test\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\n// TestFuncAdminElectLeadersV1 covers low-version client compatibility against a\n// newer broker for the ElectLeaders V1 non-flexible path. See\n// https://github.com/IBM/sarama/pull/3312.\nfunc TestFuncAdminElectLeadersV1(t *testing.T) {\n\tcheckKafkaVersion(t, \"2.3.0.0\")\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tconfig := NewFunctionalTestConfig()\n\t// Force ElectLeaders onto request/response version 1 so the broker emits\n\t// the non-flexible wire format that previously regressed.\n\tconfig.Version = V2_3_0_0\n\n\tadminClient, err := NewClusterAdmin(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer safeClose(t, adminClient)\n\n\tresults, err := adminClient.ElectLeaders(PreferredElection, map[string][]int32{\"test.1\": {0}})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttopicResults, ok := results[\"test.1\"]\n\tif !ok {\n\t\tt.Fatalf(\"topic test.1 missing in response: %#v\", results)\n\t}\n\n\tpartitionResult, ok := topicResults[0]\n\tif !ok {\n\t\tt.Fatalf(\"partition 0 missing in response: %#v\", topicResults)\n\t}\n\n\tif partitionResult == nil {\n\t\tt.Fatal(\"partition 0 returned nil result\")\n\t}\n\n\tswitch partitionResult.ErrorCode {\n\tcase ErrNoError, ErrElectionNotNeeded:\n\tdefault:\n\t\tt.Fatalf(\"expected partition 0 to return a decodable ElectLeaders result, got %v (%v)\", partitionResult.ErrorCode, partitionResult.ErrorMessage)\n\t}\n}\n\nfunc TestFuncAdminIncrementalAlterConfigs(t *testing.T) {\n\tcheckKafkaVersion(t, \"2.3.0.0\")\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tconfig := NewFunctionalTestConfig()\n\tadminClient, err := NewClusterAdmin(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer safeClose(t, adminClient)\n\n\tbrokerIDs := make([]int32, len(FunctionalTestEnv.KafkaBrokerAddrs))\n\tfor i := range brokerIDs {\n\t\tbrokerIDs[i] = int32(i + 1)\n\t}\n\n\tgetConfigValue := func(config string) int {\n\t\tresource := ConfigResource{\n\t\t\tType:        BrokerResource,\n\t\t\tName:        \"1\",\n\t\t\tConfigNames: []string{config},\n\t\t}\n\t\tres, err := adminClient.DescribeConfig(resource)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif len(res) != 1 {\n\t\t\tt.Fatalf(\"expected 1 config in response but got %d\", len(res))\n\t\t}\n\t\tif res[0].Name != config {\n\t\t\tt.Fatalf(\"expected config in response name to be '%s' but got '%s'\", config, res[0].Name)\n\t\t}\n\t\tn, err := strconv.Atoi(res[0].Value)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"failed to parse config in response value '%s': %v\", res[0].Value, err)\n\t\t}\n\t\treturn n\n\t}\n\tconfigName := \"log.cleaner.backoff.ms\"\n\tn := getConfigValue(configName)\n\tn++\n\tvalue := fmt.Sprintf(\"%d\", n)\n\terr = adminClient.IncrementalAlterConfig(BrokerResource, \"1\",\n\t\tmap[string]IncrementalAlterConfigsEntry{\n\t\t\tconfigName: {\n\t\t\t\tOperation: IncrementalAlterConfigsOperationSet,\n\t\t\t\tValue:     &value,\n\t\t\t},\n\t\t}, false)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to alter config: %v\", err)\n\t}\n}\n"
  },
  {
    "path": "functional_client_test.go",
    "content": "//go:build functional\n\npackage sarama\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestFuncConnectionFailure(t *testing.T) {\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tproxy := proxyForBrokerID(t, 1)\n\tproxy.Enabled = false\n\tSaveProxy(t, \"kafka1\")\n\n\tconfig := NewFunctionalTestConfig()\n\tconfig.Metadata.Retry.Max = 1\n\n\t_, err := NewClient([]string{FunctionalTestEnv.KafkaBrokerAddrs[0]}, config)\n\tif !errors.Is(err, ErrOutOfBrokers) {\n\t\tt.Fatal(\"Expected returned error to be ErrOutOfBrokers, but was: \", err)\n\t}\n}\n\nfunc TestFuncAdminNetworkErrorClosesControllerConnection(t *testing.T) {\n\t// Goal (IBM/sarama#1162): verify controller reconnection semantics after a TCP reset.\n\t// Expected flow:\n\t// 1) First metadata request succeeds.\n\t// 2) Injected TCP reset makes the next metadata request fail.\n\t// 3) Explicit Open triggers automatic reconnection and the subsequent metadata request succeeds.\n\tcheckKafkaVersion(t, \"0.11.0.0\")\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tkafkaVersion, err := ParseKafkaVersion(FunctionalTestEnv.KafkaVersion)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tconfig := NewFunctionalTestConfig()\n\tconfig.Version = kafkaVersion\n\tadminClient, err := NewClusterAdmin(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer safeClose(t, adminClient)\n\n\tcontroller, err := adminClient.Controller()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif controller.ID() < 0 {\n\t\tt.Fatalf(\"expected controller broker ID to be resolved, got %d\", controller.ID())\n\t}\n\n\t// Warm up the connection so the proxy toxic applies to an established TCP session.\n\tmetadataReq := NewMetadataRequest(config.Version, nil)\n\tif _, err := controller.GetMetadata(metadataReq); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tproxy := proxyForBrokerID(t, controller.ID())\n\taddResetPeerToxic(t, proxy)\n\tdefer resetProxies(t)\n\n\tif _, err := controller.GetMetadata(metadataReq); err == nil {\n\t\tt.Fatal(\"expected metadata request to fail after injected network error\")\n\t}\n\t// Ensure the injected reset is one-shot; otherwise the proxy will continue\n\t// to reset new connections and make reconnection impossible.\n\tresetProxies(t)\n\n\t// Trigger a reconnect path and retry. It should succeed after the automatic reconnection.\n\t_ = controller.Open(config)\n\tif _, err := controller.GetMetadata(metadataReq); err != nil {\n\t\tt.Fatalf(\"expected metadata request to succeed after reopen, got %v\", err)\n\t}\n}\n\nfunc TestFuncClientMetadata(t *testing.T) {\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tconfig := NewFunctionalTestConfig()\n\tconfig.Metadata.Retry.Max = 1\n\tconfig.Metadata.Retry.Backoff = 10 * time.Millisecond\n\tclient, err := NewClient(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif err := client.RefreshMetadata(\"unknown_topic\"); !errors.Is(err, ErrUnknownTopicOrPartition) {\n\t\tt.Error(\"Expected ErrUnknownTopicOrPartition, got\", err)\n\t}\n\n\tif _, err := client.Leader(\"unknown_topic\", 0); !errors.Is(err, ErrUnknownTopicOrPartition) {\n\t\tt.Error(\"Expected ErrUnknownTopicOrPartition, got\", err)\n\t}\n\n\tif _, err := client.Replicas(\"invalid/topic\", 0); !errors.Is(err, ErrUnknownTopicOrPartition) && !errors.Is(err, ErrInvalidTopic) {\n\t\tt.Error(\"Expected ErrUnknownTopicOrPartition or ErrInvalidTopic, got\", err)\n\t}\n\n\tpartitions, err := client.Partitions(\"test.4\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tif len(partitions) != 4 {\n\t\tt.Errorf(\"Expected test.4 topic to have 4 partitions, found %v\", partitions)\n\t}\n\n\tpartitions, err = client.Partitions(\"test.1\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tif len(partitions) != 1 {\n\t\tt.Errorf(\"Expected test.1 topic to have 1 partitions, found %v\", partitions)\n\t}\n\n\tsafeClose(t, client)\n}\n\nfunc TestFuncClientCoordinator(t *testing.T) {\n\tcheckKafkaVersion(t, \"0.8.2\")\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tclient, err := NewClient(FunctionalTestEnv.KafkaBrokerAddrs, NewFunctionalTestConfig())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor i := 0; i < 10; i++ {\n\t\tbroker, err := client.Coordinator(fmt.Sprintf(\"another_new_consumer_group_%d\", i))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif connected, err := broker.Connected(); !connected || err != nil {\n\t\t\tt.Errorf(\"Expected to coordinator %s broker to be properly connected.\", broker.Addr())\n\t\t}\n\t}\n\n\tsafeClose(t, client)\n}\n"
  },
  {
    "path": "functional_consumer_follower_fetch_test.go",
    "content": "//go:build functional\n\npackage sarama\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestConsumerFetchFollowerFailover(t *testing.T) {\n\tconst (\n\t\ttopic  = \"test.1\"\n\t\tnumMsg = 1000\n\t)\n\n\tnewConfig := func() *Config {\n\t\tconfig := NewFunctionalTestConfig()\n\t\tconfig.ClientID = t.Name()\n\t\tconfig.Producer.Return.Successes = true\n\t\treturn config\n\t}\n\n\tconfig := newConfig()\n\n\t// pick a partition and find the ID for one of the follower brokers\n\tadmin, err := NewClusterAdmin(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer admin.Close()\n\n\tmetadata, err := admin.DescribeTopics([]string{topic})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tpartition := metadata[0].Partitions[0]\n\tleader := metadata[0].Partitions[0].Leader\n\tfollower := int32(-1)\n\tfor _, replica := range partition.Replicas {\n\t\tif replica == leader {\n\t\t\tcontinue\n\t\t}\n\t\tfollower = replica\n\t\tbreak\n\t}\n\n\tt.Logf(\"topic %s has leader kafka-%d and our chosen follower is kafka-%d\", topic, leader, follower)\n\n\t// match our clientID to the given broker so our requests should end up fetching from that follower\n\tconfig.RackID = strconv.FormatInt(int64(follower), 10)\n\n\tconsumer, err := NewConsumer(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tpc, err := consumer.ConsumePartition(topic, partition.ID, OffsetOldest)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer func() {\n\t\tpc.Close()\n\t\tconsumer.Close()\n\t}()\n\n\tproducer, err := NewSyncProducer(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer producer.Close()\n\n\tvar wg sync.WaitGroup\n\twg.Add(numMsg)\n\n\tgo func() {\n\t\tfor i := 0; i < numMsg; i++ {\n\t\t\tmsg := &ProducerMessage{\n\t\t\t\tTopic: topic, Key: nil, Value: StringEncoder(fmt.Sprintf(\"%s %-3d\", t.Name(), i)),\n\t\t\t}\n\t\t\tif _, offset, err := producer.SendMessage(msg); err != nil {\n\t\t\t\tt.Error(i, err)\n\t\t\t} else if offset%50 == 0 {\n\t\t\t\tt.Logf(\"sent: %d\\n\", offset)\n\t\t\t}\n\t\t\twg.Done()\n\t\t\ttime.Sleep(time.Millisecond * 25)\n\t\t}\n\t}()\n\n\ti := 0\n\n\tfor ; i < numMsg/8; i++ {\n\t\tmsg := <-pc.Messages()\n\t\tif msg.Offset%50 == 0 {\n\t\t\tt.Logf(\"recv: %d\\n\", msg.Offset)\n\t\t}\n\t}\n\n\tif err := stopDockerTestBroker(context.Background(), follower); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor ; i < numMsg/3; i++ {\n\t\tmsg := <-pc.Messages()\n\t\tif msg.Offset%50 == 0 {\n\t\t\tt.Logf(\"recv: %d\\n\", msg.Offset)\n\t\t}\n\t}\n\n\tif err := startDockerTestBroker(context.Background(), follower); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor ; i < numMsg; i++ {\n\t\tmsg := <-pc.Messages()\n\t\tif msg.Offset%50 == 0 {\n\t\t\tt.Logf(\"recv: %d\\n\", msg.Offset)\n\t\t}\n\t}\n\n\twg.Wait()\n}\n"
  },
  {
    "path": "functional_consumer_group_test.go",
    "content": "//go:build functional\n\npackage sarama\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestFuncConsumerGroupPartitioning(t *testing.T) {\n\tcheckKafkaVersion(t, \"0.10.2\")\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tgroupID := testFuncConsumerGroupID(t)\n\n\t// start M1\n\tm1 := runTestFuncConsumerGroupMember(t, groupID, \"M1\", 0, nil)\n\tdefer m1.Stop()\n\tm1.WaitForState(2)\n\tm1.WaitForClaims(map[string]int{\"test.4\": 4})\n\tm1.WaitForHandlers(4)\n\n\t// start M2\n\tm2 := runTestFuncConsumerGroupMember(t, groupID, \"M2\", 0, nil, \"test.1\", \"test.4\")\n\tdefer m2.Stop()\n\tm2.WaitForState(2)\n\n\t// assert that claims are shared among both members\n\tm1.WaitForClaims(map[string]int{\"test.4\": 2})\n\tm1.WaitForHandlers(2)\n\tm2.WaitForClaims(map[string]int{\"test.1\": 1, \"test.4\": 2})\n\tm2.WaitForHandlers(3)\n\n\t// shutdown M1, wait for M2 to take over\n\tm1.AssertCleanShutdown()\n\tm2.WaitForClaims(map[string]int{\"test.1\": 1, \"test.4\": 4})\n\tm2.WaitForHandlers(5)\n\n\t// shutdown M2\n\tm2.AssertCleanShutdown()\n}\n\nfunc TestFuncConsumerGroupPartitioningStateful(t *testing.T) {\n\tcheckKafkaVersion(t, \"0.10.2\")\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tgroupID := testFuncConsumerGroupID(t)\n\n\tm1s := newTestStatefulStrategy(t)\n\tconfig := defaultConfig(\"M1\")\n\tconfig.Consumer.Group.Rebalance.GroupStrategies = []BalanceStrategy{m1s}\n\tconfig.Consumer.Group.Member.UserData = []byte(config.ClientID)\n\n\t// start M1\n\tm1 := runTestFuncConsumerGroupMemberWithConfig(t, config, groupID, 0, nil)\n\tdefer m1.Stop()\n\tm1.WaitForState(2)\n\tm1.WaitForClaims(map[string]int{\"test.4\": 4})\n\tm1.WaitForHandlers(4)\n\tm1s.AssertInitialValues(1)\n\n\tm2s := newTestStatefulStrategy(t)\n\tconfig = defaultConfig(\"M2\")\n\tconfig.Consumer.Group.Rebalance.GroupStrategies = []BalanceStrategy{m2s}\n\tconfig.Consumer.Group.Member.UserData = []byte(config.ClientID)\n\n\t// start M2\n\tm2 := runTestFuncConsumerGroupMemberWithConfig(t, config, groupID, 0, nil, \"test.1\", \"test.4\")\n\tdefer m2.Stop()\n\tm2.WaitForState(2)\n\tm1s.AssertInitialValues(2)\n\tm2s.AssertNoInitialValues()\n\n\t// assert that claims are shared among both members\n\tm1.WaitForClaims(map[string]int{\"test.4\": 2})\n\tm1.WaitForHandlers(2)\n\tm2.WaitForClaims(map[string]int{\"test.1\": 1, \"test.4\": 2})\n\tm2.WaitForHandlers(3)\n\n\t// shutdown M1, wait for M2 to take over\n\tm1.AssertCleanShutdown()\n\tm2.WaitForClaims(map[string]int{\"test.1\": 1, \"test.4\": 4})\n\tm2.WaitForHandlers(5)\n\tm2s.AssertNoInitialValues()\n}\n\nfunc TestFuncConsumerGroupExcessConsumers(t *testing.T) {\n\tcheckKafkaVersion(t, \"0.10.2\")\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tgroupID := testFuncConsumerGroupID(t)\n\n\t// start members\n\tm1 := runTestFuncConsumerGroupMember(t, groupID, \"M1\", 0, nil)\n\tdefer m1.Stop()\n\tm2 := runTestFuncConsumerGroupMember(t, groupID, \"M2\", 0, nil)\n\tdefer m2.Stop()\n\tm3 := runTestFuncConsumerGroupMember(t, groupID, \"M3\", 0, nil)\n\tdefer m3.Stop()\n\tm4 := runTestFuncConsumerGroupMember(t, groupID, \"M4\", 0, nil)\n\tdefer m4.Stop()\n\n\tm1.WaitForClaims(map[string]int{\"test.4\": 1})\n\tm2.WaitForClaims(map[string]int{\"test.4\": 1})\n\tm3.WaitForClaims(map[string]int{\"test.4\": 1})\n\tm4.WaitForClaims(map[string]int{\"test.4\": 1})\n\n\t// start M5\n\tm5 := runTestFuncConsumerGroupMember(t, groupID, \"M5\", 0, nil)\n\tdefer m5.Stop()\n\tm5.WaitForState(1)\n\tm5.AssertNoErrs()\n\n\t// assert that claims are shared among both members\n\tm4.AssertCleanShutdown()\n\tm5.WaitForState(2)\n\tm5.WaitForClaims(map[string]int{\"test.4\": 1})\n\n\t// shutdown everything\n\tm1.AssertCleanShutdown()\n\tm2.AssertCleanShutdown()\n\tm3.AssertCleanShutdown()\n\tm5.AssertCleanShutdown()\n}\n\nfunc TestFuncConsumerGroupRebalanceAfterAddingPartitions(t *testing.T) {\n\tcheckKafkaVersion(t, \"0.10.2\")\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tconfig := NewFunctionalTestConfig()\n\tadmin, err := NewClusterAdmin(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer func() {\n\t\t_ = admin.Close()\n\t}()\n\n\tgroupID := testFuncConsumerGroupID(t)\n\n\t// start M1\n\tm1 := runTestFuncConsumerGroupMember(t, groupID, \"M1\", 0, nil, \"test.1\")\n\tdefer m1.Stop()\n\tm1.WaitForClaims(map[string]int{\"test.1\": 1})\n\tm1.WaitForHandlers(1)\n\n\t// start M2\n\tm2 := runTestFuncConsumerGroupMember(t, groupID, \"M2\", 0, nil, \"test.1_to_2\")\n\tdefer m2.Stop()\n\tm2.WaitForClaims(map[string]int{\"test.1_to_2\": 1})\n\tm1.WaitForHandlers(1)\n\n\t// add a new partition to topic \"test.1_to_2\"\n\terr = admin.CreatePartitions(\"test.1_to_2\", 2, nil, false)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// assert that claims are shared among both members\n\tm2.WaitForClaims(map[string]int{\"test.1_to_2\": 2})\n\tm2.WaitForHandlers(2)\n\tm1.WaitForClaims(map[string]int{\"test.1\": 1})\n\tm1.WaitForHandlers(1)\n\n\tm1.AssertCleanShutdown()\n\tm2.AssertCleanShutdown()\n}\n\nfunc TestFuncConsumerGroupFuzzy(t *testing.T) {\n\tcheckKafkaVersion(t, \"0.10.2\")\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tif err := testFuncConsumerGroupFuzzySeed(\"test.4\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tgroupID := testFuncConsumerGroupID(t)\n\tsink := &testFuncConsumerGroupSink{msgs: make(chan testFuncConsumerGroupMessage, 30000)}\n\twaitForMessages := func(t *testing.T, n int) {\n\t\tconst (\n\t\t\twaitFor = 60 * time.Second\n\t\t\ttick    = 100 * time.Millisecond\n\t\t)\n\t\tt.Helper()\n\t\trequire.EventuallyWithT(t, func(t *assert.CollectT) {\n\t\t\trequire.GreaterOrEqual(t, sink.Len(), n)\n\t\t}, waitFor, tick, \"expected to consume %d messages, but consumed %d\", n, sink.Len())\n\t}\n\n\tdefer runTestFuncConsumerGroupMember(t, groupID, \"M1\", 1500, sink).Stop()\n\tdefer runTestFuncConsumerGroupMember(t, groupID, \"M2\", 3000, sink).Stop()\n\tdefer runTestFuncConsumerGroupMember(t, groupID, \"M3\", 1500, sink).Stop()\n\tdefer runTestFuncConsumerGroupMember(t, groupID, \"M4\", 200, sink).Stop()\n\tdefer runTestFuncConsumerGroupMember(t, groupID, \"M5\", 100, sink).Stop()\n\twaitForMessages(t, 3000)\n\n\tdefer runTestFuncConsumerGroupMember(t, groupID, \"M6\", 300, sink).Stop()\n\tdefer runTestFuncConsumerGroupMember(t, groupID, \"M7\", 400, sink).Stop()\n\tdefer runTestFuncConsumerGroupMember(t, groupID, \"M8\", 500, sink).Stop()\n\tdefer runTestFuncConsumerGroupMember(t, groupID, \"M9\", 2000, sink).Stop()\n\twaitForMessages(t, 8000)\n\n\tdefer runTestFuncConsumerGroupMember(t, groupID, \"M10\", 1000, sink).Stop()\n\twaitForMessages(t, 10000)\n\n\tdefer runTestFuncConsumerGroupMember(t, groupID, \"M11\", 1000, sink).Stop()\n\tdefer runTestFuncConsumerGroupMember(t, groupID, \"M12\", 2500, sink).Stop()\n\twaitForMessages(t, 12000)\n\n\tdefer runTestFuncConsumerGroupMember(t, groupID, \"M13\", 1000, sink).Stop()\n\twaitForMessages(t, 15000)\n\n\tif umap := sink.Close(); len(umap) != 15000 {\n\t\tdupes := make(map[string][]string)\n\t\tfor k, v := range umap {\n\t\t\tif len(v) > 1 {\n\t\t\t\tdupes[k] = v\n\t\t\t}\n\t\t}\n\t\tt.Fatalf(\"expected %d unique messages to be consumed but got %d, including %d duplicates:\\n%v\", 15000, len(umap), len(dupes), dupes)\n\t}\n}\n\nfunc TestFuncConsumerGroupOffsetDeletion(t *testing.T) {\n\tcheckKafkaVersion(t, \"2.4.0\")\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\tconfig := NewFunctionalTestConfig()\n\tclient, err := NewClient(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\tdefer safeClose(t, client)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// create a consumer group with offsets on\n\t// - topic test.1 partition 0\n\t// - topic test.4 partition 0\n\tgroupID := testFuncConsumerGroupID(t)\n\tconsumerGroup, err := NewConsumerGroupFromClient(groupID, client)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer safeClose(t, consumerGroup)\n\n\toffsetMgr, _ := NewOffsetManagerFromClient(groupID, client)\n\tdefer safeClose(t, offsetMgr)\n\tmarkOffset(t, offsetMgr, \"test.1\", 0, 1)\n\tmarkOffset(t, offsetMgr, \"test.4\", 0, 2)\n\toffsetMgr.Commit()\n\n\tadmin, err := NewClusterAdminFromClient(client)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\toffsetFetch, err := admin.ListConsumerGroupOffsets(groupID, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif len(offsetFetch.Blocks) != 2 {\n\t\tt.Fatal(\"Expected offsets on two topics. Found offsets on \", len(offsetFetch.Blocks), \"topics.\")\n\t}\n\n\t// Delete offset for partition topic test.4 partition 0\n\terr = admin.DeleteConsumerGroupOffset(groupID, \"test.4\", 0)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\toffsetFetch, err = admin.ListConsumerGroupOffsets(groupID, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif len(offsetFetch.Blocks) != 1 {\n\t\tt.Fatal(\"Expected offsets on one topic. Found offsets on \", len(offsetFetch.Blocks), \"topics.\")\n\t}\n\tif offsetFetch.Blocks[\"test.4\"] != nil {\n\t\tt.Fatal(\"Offset still exists for topic 'topic.4'. It should have been deleted.\")\n\t}\n}\n\n// --------------------------------------------------------------------\n\nfunc testFuncConsumerGroupID(t *testing.T) string {\n\treturn fmt.Sprintf(\"sarama.%s%d\", t.Name(), time.Now().UnixNano())\n}\n\nfunc markOffset(t *testing.T, offsetMgr OffsetManager, topic string, partition int32, offset int64) {\n\tpartitionOffsetManager, err := offsetMgr.ManagePartition(topic, partition)\n\tdefer safeClose(t, partitionOffsetManager)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tpartitionOffsetManager.MarkOffset(offset, \"\")\n}\n\nfunc testFuncConsumerGroupFuzzySeed(topic string) error {\n\tclient, err := NewClient(FunctionalTestEnv.KafkaBrokerAddrs, NewFunctionalTestConfig())\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() { _ = client.Close() }()\n\n\ttotal := int64(0)\n\tfor pn := int32(0); pn < 4; pn++ {\n\t\tnewest, err := client.GetOffset(topic, pn, OffsetNewest)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\toldest, err := client.GetOffset(topic, pn, OffsetOldest)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ttotal = total + newest - oldest\n\t}\n\tif total >= 21000 {\n\t\treturn nil\n\t}\n\n\tproducer, err := NewAsyncProducerFromClient(client)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor i := total; i < 21000; i++ {\n\t\tproducer.Input() <- &ProducerMessage{Topic: topic, Value: ByteEncoder([]byte(\"testdata\"))}\n\t}\n\treturn producer.Close()\n}\n\ntype testFuncConsumerGroupMessage struct {\n\tClientID string\n\t*ConsumerMessage\n}\n\ntype testFuncConsumerGroupSink struct {\n\tmsgs  chan testFuncConsumerGroupMessage\n\tcount atomic.Int32\n}\n\nfunc (s *testFuncConsumerGroupSink) Len() int {\n\tif s == nil {\n\t\treturn -1\n\t}\n\treturn int(s.count.Load())\n}\n\nfunc (s *testFuncConsumerGroupSink) Push(clientID string, m *ConsumerMessage) {\n\tif s != nil {\n\t\ts.msgs <- testFuncConsumerGroupMessage{ClientID: clientID, ConsumerMessage: m}\n\t\ts.count.Add(1)\n\t}\n}\n\nfunc (s *testFuncConsumerGroupSink) Close() map[string][]string {\n\tclose(s.msgs)\n\n\tres := make(map[string][]string)\n\tfor msg := range s.msgs {\n\t\tkey := fmt.Sprintf(\"%s-%d:%d\", msg.Topic, msg.Partition, msg.Offset)\n\t\tres[key] = append(res[key], msg.ClientID)\n\t}\n\treturn res\n}\n\ntype testFuncConsumerGroupMember struct {\n\tConsumerGroup\n\tt        *testing.T\n\tclientID string\n\tisCapped bool\n\tsink     *testFuncConsumerGroupSink\n\n\tgenerationId atomic.Int32\n\tstate        atomic.Int32\n\thandlers     atomic.Int32\n\tmaxMessages  atomic.Int32\n\n\tmu     sync.RWMutex\n\tclaims map[string]int\n\terrs   []error\n}\n\nfunc defaultConfig(clientID string) *Config {\n\tconfig := NewFunctionalTestConfig()\n\tconfig.ClientID = clientID\n\tconfig.Consumer.Return.Errors = true\n\tconfig.Consumer.Offsets.Initial = OffsetOldest\n\tconfig.Consumer.Group.Rebalance.Timeout = 30 * time.Second\n\tconfig.Consumer.Group.Session.Timeout = 20 * time.Second\n\tconfig.Consumer.Group.Heartbeat.Interval = 5 * time.Second\n\tconfig.Metadata.Full = false\n\tconfig.Metadata.RefreshFrequency = 10 * time.Second\n\treturn config\n}\n\nfunc runTestFuncConsumerGroupMember(\n\tt *testing.T,\n\tgroupID string,\n\tclientID string,\n\tmaxMessages int32,\n\tsink *testFuncConsumerGroupSink,\n\ttopics ...string,\n) *testFuncConsumerGroupMember {\n\tt.Helper()\n\n\tconfig := defaultConfig(clientID)\n\treturn runTestFuncConsumerGroupMemberWithConfig(t, config, groupID, maxMessages, sink, topics...)\n}\n\nfunc runTestFuncConsumerGroupMemberWithConfig(\n\tt *testing.T,\n\tconfig *Config,\n\tgroupID string,\n\tmaxMessages int32,\n\tsink *testFuncConsumerGroupSink,\n\ttopics ...string,\n) *testFuncConsumerGroupMember {\n\tt.Helper()\n\n\tgroup, err := NewConsumerGroup(FunctionalTestEnv.KafkaBrokerAddrs, groupID, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn nil\n\t}\n\n\tif len(topics) == 0 {\n\t\ttopics = []string{\"test.4\"}\n\t}\n\n\tmember := &testFuncConsumerGroupMember{\n\t\tConsumerGroup: group,\n\t\tclientID:      config.ClientID,\n\t\tclaims:        make(map[string]int),\n\t\tisCapped:      maxMessages != 0,\n\t\tsink:          sink,\n\t\tt:             t,\n\t}\n\tmember.maxMessages.Store(maxMessages)\n\tgo member.loop(topics)\n\treturn member\n}\n\nfunc (m *testFuncConsumerGroupMember) AssertCleanShutdown() {\n\tm.t.Helper()\n\n\tif err := m.Close(); err != nil {\n\t\tm.t.Fatalf(\"unexpected error on Close(): %v\", err)\n\t}\n\tm.WaitForState(4)\n\tm.WaitForHandlers(0)\n\tm.AssertNoErrs()\n}\n\nfunc (m *testFuncConsumerGroupMember) AssertNoErrs() {\n\tm.t.Helper()\n\n\tvar errs []error\n\tm.mu.RLock()\n\terrs = append(errs, m.errs...)\n\tm.mu.RUnlock()\n\n\tif len(errs) != 0 {\n\t\tm.t.Fatalf(\"unexpected consumer errors: %v\", errs)\n\t}\n}\n\nfunc (m *testFuncConsumerGroupMember) WaitForState(expected int32) {\n\tm.t.Helper()\n\n\tm.waitFor(\"state\", expected, func() (interface{}, error) {\n\t\treturn m.state.Load(), nil\n\t})\n}\n\nfunc (m *testFuncConsumerGroupMember) WaitForHandlers(expected int) {\n\tm.t.Helper()\n\n\tm.waitFor(\"handlers\", expected, func() (interface{}, error) {\n\t\treturn int(m.handlers.Load()), nil\n\t})\n}\n\nfunc (m *testFuncConsumerGroupMember) WaitForClaims(expected map[string]int) {\n\tm.t.Helper()\n\n\tm.waitFor(\"claims\", expected, func() (interface{}, error) {\n\t\tm.mu.RLock()\n\t\tclaims := m.claims\n\t\tm.mu.RUnlock()\n\t\treturn claims, nil\n\t})\n}\n\nfunc (m *testFuncConsumerGroupMember) Stop() { _ = m.Close() }\n\nfunc (m *testFuncConsumerGroupMember) Setup(s ConsumerGroupSession) error {\n\tm.t.Logf(\"Consumer %s: new session started %s in generation %d\", m.clientID, s.MemberID(), s.GenerationID())\n\n\t// store claims\n\tclaims := make(map[string]int)\n\tfor topic, partitions := range s.Claims() {\n\t\tclaims[topic] = len(partitions)\n\t}\n\tm.mu.Lock()\n\tm.claims = claims\n\tm.mu.Unlock()\n\n\t// store generationID\n\tm.generationId.Store(s.GenerationID())\n\n\t// enter post-setup state\n\tm.state.Store(2)\n\treturn nil\n}\n\nfunc (m *testFuncConsumerGroupMember) Cleanup(s ConsumerGroupSession) error {\n\tm.t.Logf(\"Consumer %s: session ended %s in generation %d\", m.clientID, s.MemberID(), s.GenerationID())\n\t// enter post-cleanup state\n\tm.state.Store(3)\n\treturn nil\n}\n\nfunc (m *testFuncConsumerGroupMember) ConsumeClaim(s ConsumerGroupSession, c ConsumerGroupClaim) error {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tm.t.Errorf(\"panic in ConsumeClaim: %v\", r)\n\t\t}\n\t}()\n\tm.handlers.Add(1)\n\tdefer m.handlers.Add(-1)\n\n\tconsumed := 0\n\tfor {\n\t\tselect {\n\t\tcase msg, ok := <-c.Messages():\n\t\t\tif !ok {\n\t\t\t\tm.t.Logf(\"Consumer %s: message channel closed, consumed %d messages\", m.clientID, consumed)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif n := m.maxMessages.Add(-1); m.isCapped && n < 0 {\n\t\t\t\tm.t.Logf(\"Consumer %s: reached max messages, consumed %d messages\", m.clientID, consumed)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\ts.MarkMessage(msg, \"\")\n\t\t\tm.sink.Push(m.clientID, msg)\n\t\t\tconsumed++\n\t\tcase <-s.Context().Done():\n\t\t\tm.t.Logf(\"Consumer %s: session context done, consumed %d messages\", m.clientID, consumed)\n\t\t\treturn nil\n\t\t}\n\t}\n}\n\nfunc (m *testFuncConsumerGroupMember) waitFor(kind string, expected interface{}, factory func() (interface{}, error)) {\n\tm.t.Helper()\n\n\tdeadline := time.NewTimer(60 * time.Second)\n\tdefer deadline.Stop()\n\n\tticker := time.NewTicker(100 * time.Millisecond)\n\tdefer ticker.Stop()\n\n\tvar actual interface{}\n\tfor {\n\t\tvar err error\n\t\tif actual, err = factory(); err != nil {\n\t\t\tm.t.Errorf(\"failed retrieve value, expected %s %#v but received error %v\", kind, expected, err)\n\t\t}\n\n\t\tif reflect.DeepEqual(expected, actual) {\n\t\t\treturn\n\t\t}\n\n\t\tselect {\n\t\tcase <-deadline.C:\n\t\t\tm.t.Fatalf(\"ttl exceeded, expected %s %#v but got %#v\", kind, expected, actual)\n\t\t\treturn\n\t\tcase <-ticker.C:\n\t\t}\n\t}\n}\n\nfunc (m *testFuncConsumerGroupMember) loop(topics []string) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tm.t.Errorf(\"panic in loop for %s: %v\", m.clientID, r)\n\t\t}\n\t\tm.state.Store(4)\n\t}()\n\n\tgo func() {\n\t\tfor err := range m.Errors() {\n\t\t\tm.t.Logf(\"Consumer %s error: %v\", m.clientID, err)\n\t\t\t_ = m.Close()\n\n\t\t\tm.mu.Lock()\n\t\t\tm.errs = append(m.errs, err)\n\t\t\tm.mu.Unlock()\n\t\t}\n\t}()\n\n\tctx := context.Background()\n\tfor {\n\t\t// set state to pre-consume\n\t\tm.state.Store(1)\n\n\t\tif err := m.Consume(ctx, topics, m); errors.Is(err, ErrClosedConsumerGroup) {\n\t\t\tm.t.Logf(\"Consumer %s: closed consumer group\", m.clientID)\n\t\t\treturn\n\t\t} else if err != nil {\n\t\t\tm.t.Logf(\"Consumer %s: consume error: %v\", m.clientID, err)\n\t\t\tm.mu.Lock()\n\t\t\tm.errs = append(m.errs, err)\n\t\t\tm.mu.Unlock()\n\t\t\treturn\n\t\t}\n\n\t\t// check if context was canceled, signaling that the consumer should stop\n\t\tif ctx.Err() != nil {\n\t\t\tm.t.Logf(\"Consumer %s: context error: %v\", m.clientID, ctx.Err())\n\t\t\treturn\n\t\t}\n\n\t\t// return if capped\n\t\tif n := m.maxMessages.Load(); m.isCapped && n < 0 {\n\t\t\tm.t.Logf(\"Consumer %s: reached max messages, returning from loop\", m.clientID)\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc newTestStatefulStrategy(t *testing.T) *testStatefulStrategy {\n\treturn &testStatefulStrategy{\n\t\tBalanceStrategy: NewBalanceStrategyRange(),\n\t\tt:               t,\n\t}\n}\n\ntype testStatefulStrategy struct {\n\tBalanceStrategy\n\tt       *testing.T\n\tinitial atomic.Int32\n\tstate   sync.Map\n}\n\nfunc (h *testStatefulStrategy) Name() string {\n\treturn \"TestStatefulStrategy\"\n}\n\nfunc (h *testStatefulStrategy) Plan(members map[string]ConsumerGroupMemberMetadata, topics map[string][]int32) (BalanceStrategyPlan, error) {\n\th.state = sync.Map{}\n\tfor memberID, metadata := range members {\n\t\tif !strings.HasSuffix(string(metadata.UserData), \"-stateful\") {\n\t\t\tmetadata.UserData = []byte(string(metadata.UserData) + \"-stateful\")\n\t\t\th.initial.Add(1)\n\t\t}\n\t\th.state.Store(memberID, metadata.UserData)\n\t}\n\treturn h.BalanceStrategy.Plan(members, topics)\n}\n\nfunc (h *testStatefulStrategy) AssignmentData(memberID string, topics map[string][]int32, generationID int32) ([]byte, error) {\n\tif obj, ok := h.state.Load(memberID); ok {\n\t\treturn obj.([]byte), nil\n\t}\n\treturn nil, nil\n}\n\nfunc (h *testStatefulStrategy) AssertInitialValues(count int32) {\n\th.t.Helper()\n\tactual := h.initial.Load()\n\tif actual != count {\n\t\th.t.Fatalf(\"unexpected count of initial values: %d, expected: %d\", actual, count)\n\t}\n}\n\nfunc (h *testStatefulStrategy) AssertNoInitialValues() {\n\th.t.Helper()\n\th.AssertInitialValues(0)\n}\n"
  },
  {
    "path": "functional_consumer_staticmembership_test.go",
    "content": "//go:build functional\n\npackage sarama\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"math\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestFuncConsumerGroupStaticMembership_Basic(t *testing.T) {\n\tcheckKafkaVersion(t, \"2.3.0\")\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\tgroupID := testFuncConsumerGroupID(t)\n\n\tt.Helper()\n\n\tconfig1 := NewFunctionalTestConfig()\n\tconfig1.ClientID = \"M1\"\n\tconfig1.Consumer.Offsets.Initial = OffsetNewest\n\tconfig1.Consumer.Group.InstanceId = \"Instance1\"\n\tm1 := runTestFuncConsumerGroupMemberWithConfig(t, config1, groupID, 100, nil, \"test.4\")\n\tdefer m1.Close()\n\n\tconfig2 := NewFunctionalTestConfig()\n\tconfig2.ClientID = \"M2\"\n\tconfig2.Consumer.Offsets.Initial = OffsetNewest\n\tconfig2.Consumer.Group.InstanceId = \"Instance2\"\n\tm2 := runTestFuncConsumerGroupMemberWithConfig(t, config2, groupID, 100, nil, \"test.4\")\n\tdefer m2.Close()\n\n\tm1.WaitForState(2)\n\tm2.WaitForState(2)\n\n\terr := testFuncConsumerGroupProduceMessage(\"test.4\", 1000)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tadmin, err := NewClusterAdmin(FunctionalTestEnv.KafkaBrokerAddrs, config1)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer safeClose(t, admin)\n\n\tres, err := admin.DescribeConsumerGroups([]string{groupID})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif len(res) != 1 {\n\t\tt.Errorf(\"group description should be only 1, got %v\\n\", len(res))\n\t}\n\tif len(res[0].Members) != 2 {\n\t\tt.Errorf(\"should have 2 members in group , got %v\\n\", len(res[0].Members))\n\t}\n\n\tm1.WaitForState(4)\n\tm2.WaitForState(4)\n\n\tm1.AssertCleanShutdown()\n\tm2.AssertCleanShutdown()\n}\n\nfunc TestFuncConsumerGroupStaticMembership_RejoinAndLeave(t *testing.T) {\n\tcheckKafkaVersion(t, \"2.4.0\")\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\tgroupID := testFuncConsumerGroupID(t)\n\n\tt.Helper()\n\n\tconfig1 := NewFunctionalTestConfig()\n\tconfig1.ClientID = \"M1\"\n\tconfig1.Consumer.Offsets.Initial = OffsetNewest\n\tconfig1.Consumer.Group.InstanceId = \"Instance1\"\n\tm1 := runTestFuncConsumerGroupMemberWithConfig(t, config1, groupID, math.MaxInt32, nil, \"test.4\")\n\tdefer m1.Close()\n\n\tconfig2 := NewFunctionalTestConfig()\n\tconfig2.ClientID = \"M2\"\n\tconfig2.Consumer.Offsets.Initial = OffsetNewest\n\tconfig2.Consumer.Group.InstanceId = \"Instance2\"\n\tm2 := runTestFuncConsumerGroupMemberWithConfig(t, config2, groupID, math.MaxInt32, nil, \"test.4\")\n\tdefer m2.Close()\n\n\tm1.WaitForState(2)\n\tm2.WaitForState(2)\n\n\tadmin, err := NewClusterAdmin(FunctionalTestEnv.KafkaBrokerAddrs, config1)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer safeClose(t, admin)\n\n\tres1, err := admin.DescribeConsumerGroups([]string{groupID})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif len(res1) != 1 {\n\t\tt.Errorf(\"group description should be only 1, got %v\\n\", len(res1))\n\t}\n\tif len(res1[0].Members) != 2 {\n\t\tt.Errorf(\"should have 2 members in group , got %v\\n\", len(res1[0].Members))\n\t}\n\n\tgenerationId1 := m1.generationId.Load()\n\n\t// shut down m2, membership should not change (we didn't leave group when close)\n\tm2.AssertCleanShutdown()\n\n\tres2, err := admin.DescribeConsumerGroups([]string{groupID})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif !reflect.DeepEqual(res1, res2) {\n\t\tres1Bytes, _ := json.Marshal(res1)\n\t\tres2Bytes, _ := json.Marshal(res2)\n\t\tt.Errorf(\"group description be the same before %s, after %s\", res1Bytes, res2Bytes)\n\t}\n\n\tgenerationId2 := m1.generationId.Load()\n\tif generationId2 != generationId1 {\n\t\tt.Errorf(\"m1 generation should not increase expect %v, actual %v\", generationId1, generationId2)\n\t}\n\n\t// m2 rejoin, should generate a new memberId, no re-balance happens\n\tm2 = runTestFuncConsumerGroupMemberWithConfig(t, config2, groupID, math.MaxInt32, nil, \"test.4\")\n\tm2.WaitForState(2)\n\tm1.WaitForState(2)\n\n\tres3, err := admin.DescribeConsumerGroups([]string{groupID})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif len(res3) != 1 {\n\t\tt.Errorf(\"group description should be only 1, got %v\\n\", len(res3))\n\t}\n\tif len(res3[0].Members) != 2 {\n\t\tt.Errorf(\"should have 2 members in group , got %v\\n\", len(res3[0].Members))\n\t}\n\n\tgenerationId3 := m1.generationId.Load()\n\tif generationId3 != generationId1 {\n\t\tt.Errorf(\"m1 generation should not increase expect %v, actual %v\", generationId1, generationId3)\n\t}\n\n\tm2.AssertCleanShutdown()\n\tremoveResp, err := admin.RemoveMemberFromConsumerGroup(groupID, []string{config2.Consumer.Group.InstanceId})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif removeResp.Err != ErrNoError {\n\t\tt.Errorf(\"remove %s from consumer group failed %v\", config2.Consumer.Group.InstanceId, removeResp.Err)\n\t}\n\tm1.WaitForHandlers(4)\n\n\tgenerationId4 := m1.generationId.Load()\n\tif generationId4 == generationId1 {\n\t\tt.Errorf(\"m1 generation should increase expect %v, actual %v\", generationId1, generationId4)\n\t}\n}\n\nfunc TestFuncConsumerGroupStaticMembership_Fenced(t *testing.T) {\n\tcheckKafkaVersion(t, \"2.3.0\")\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\tgroupID := testFuncConsumerGroupID(t)\n\n\tt.Helper()\n\n\tconfig1 := NewFunctionalTestConfig()\n\tconfig1.ClientID = \"M1\"\n\tconfig1.Consumer.Offsets.Initial = OffsetNewest\n\tconfig1.Consumer.Group.InstanceId = \"Instance1\"\n\tm1 := runTestFuncConsumerGroupMemberWithConfig(t, config1, groupID, math.MaxInt32, nil, \"test.4\")\n\tdefer m1.Close()\n\n\tconfig2 := NewFunctionalTestConfig()\n\tconfig2.ClientID = \"M2\"\n\tconfig2.Consumer.Offsets.Initial = OffsetNewest\n\tconfig2.Consumer.Group.InstanceId = \"Instance2\"\n\tm2 := runTestFuncConsumerGroupMemberWithConfig(t, config2, groupID, math.MaxInt32, nil, \"test.4\")\n\tdefer m2.Close()\n\n\tm1.WaitForState(2)\n\tm2.WaitForState(2)\n\n\tconfig3 := NewFunctionalTestConfig()\n\tconfig3.ClientID = \"M3\"\n\tconfig3.Consumer.Offsets.Initial = OffsetNewest\n\tconfig3.Consumer.Group.InstanceId = \"Instance2\" // same instance id as config2\n\n\tm3 := runTestFuncConsumerGroupMemberWithConfig(t, config3, groupID, math.MaxInt32, nil, \"test.4\")\n\tdefer m3.Close()\n\n\tm3.WaitForState(2)\n\n\tm2.WaitForState(4)\n\tif len(m2.errs) < 1 {\n\t\tt.Errorf(\"expect m2 to be fenced by group instanced id, but got no err\")\n\t}\n\tif !errors.Is(m2.errs[0], ErrFencedInstancedId) {\n\t\tt.Errorf(\"expect m2 to be fenced by group instanced id, but got wrong err %v\", m2.errs[0])\n\t}\n\n\tm1.AssertCleanShutdown()\n\tm3.AssertCleanShutdown()\n}\n\n// --------------------------------------------------------------------\n\nfunc testFuncConsumerGroupProduceMessage(topic string, count int) error {\n\tclient, err := NewClient(FunctionalTestEnv.KafkaBrokerAddrs, NewFunctionalTestConfig())\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() { _ = client.Close() }()\n\n\tproducer, err := NewAsyncProducerFromClient(client)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor i := 0; i < count; i++ {\n\t\tproducer.Input() <- &ProducerMessage{Topic: topic, Value: ByteEncoder([]byte(\"testdata\"))}\n\t}\n\treturn producer.Close()\n}\n"
  },
  {
    "path": "functional_consumer_test.go",
    "content": "//go:build functional\n\npackage sarama\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"os\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"golang.org/x/sync/errgroup\"\n\n\t\"github.com/rcrowley/go-metrics\"\n\tassert \"github.com/stretchr/testify/require\"\n)\n\nfunc TestFuncConsumerOffsetOutOfRange(t *testing.T) {\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tconsumer, err := NewConsumer(FunctionalTestEnv.KafkaBrokerAddrs, NewFunctionalTestConfig())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif _, err := consumer.ConsumePartition(\"test.1\", 0, -10); !errors.Is(err, ErrOffsetOutOfRange) {\n\t\tt.Error(\"Expected ErrOffsetOutOfRange, got:\", err)\n\t}\n\n\tif _, err := consumer.ConsumePartition(\"test.1\", 0, math.MaxInt64); !errors.Is(err, ErrOffsetOutOfRange) {\n\t\tt.Error(\"Expected ErrOffsetOutOfRange, got:\", err)\n\t}\n\n\tsafeClose(t, consumer)\n}\n\nfunc TestConsumerHighWaterMarkOffset(t *testing.T) {\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tconfig := NewFunctionalTestConfig()\n\tconfig.Producer.Return.Successes = true\n\n\tp, err := NewSyncProducer(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer safeClose(t, p)\n\n\t_, offset, err := p.SendMessage(&ProducerMessage{Topic: \"test.1\", Value: StringEncoder(\"Test\")})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tc, err := NewConsumer(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer safeClose(t, c)\n\n\tpc, err := c.ConsumePartition(\"test.1\", 0, offset)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t<-pc.Messages()\n\n\tif hwmo := pc.HighWaterMarkOffset(); hwmo != offset+1 {\n\t\tt.Logf(\"Last produced offset %d; high water mark should be one higher but found %d.\", offset, hwmo)\n\t}\n\n\tsafeClose(t, pc)\n}\n\n// Makes sure that messages produced by all supported client versions/\n// compression codecs (except LZ4) combinations can be consumed by all\n// supported consumer versions. It relies on the KAFKA_VERSION environment\n// variable to provide the version of the test Kafka cluster.\n//\n// Note that LZ4 codec was introduced in v0.10.0.0 and therefore is excluded\n// from this test case. It has a similar version matrix test case below that\n// only checks versions from v0.10.0.0 until KAFKA_VERSION.\nfunc TestVersionMatrix(t *testing.T) {\n\tmetrics.UseNilMetrics = true // disable Sarama's go-metrics library\n\tt.Cleanup(func() {\n\t\tmetrics.UseNilMetrics = false\n\t})\n\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\t// Produce lot's of message with all possible combinations of supported\n\t// protocol versions and compressions for the except of LZ4.\n\ttestVersions := versionRange(V0_8_2_0)\n\tallCodecsButLZ4 := []CompressionCodec{CompressionNone, CompressionGZIP, CompressionSnappy}\n\tproducedMessages := produceMsgs(t, testVersions, allCodecsButLZ4, 17, 100, false)\n\n\t// When/Then\n\tconsumeMsgs(t, testVersions, producedMessages)\n}\n\n// Support for LZ4 codec was introduced in v0.10.0.0 so a version matrix to\n// test LZ4 should start with v0.10.0.0.\nfunc TestVersionMatrixLZ4(t *testing.T) {\n\tmetrics.UseNilMetrics = true // disable Sarama's go-metrics library\n\tt.Cleanup(func() {\n\t\tmetrics.UseNilMetrics = false\n\t})\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\t// Produce lot's of message with all possible combinations of supported\n\t// protocol versions starting with v0.10 (first where LZ4 was supported)\n\t// and all possible compressions.\n\ttestVersions := versionRange(V0_10_0_0)\n\tallCodecs := []CompressionCodec{CompressionNone, CompressionGZIP, CompressionSnappy, CompressionLZ4}\n\tproducedMessages := produceMsgs(t, testVersions, allCodecs, 17, 100, false)\n\n\t// When/Then\n\tconsumeMsgs(t, testVersions, producedMessages)\n}\n\n// Support for zstd codec was introduced in v2.1.0.0\nfunc TestVersionMatrixZstd(t *testing.T) {\n\tcheckKafkaVersion(t, \"2.1.0\")\n\tmetrics.UseNilMetrics = true // disable Sarama's go-metrics library\n\tt.Cleanup(func() {\n\t\tmetrics.UseNilMetrics = false\n\t})\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\t// Produce lot's of message with all possible combinations of supported\n\t// protocol versions starting with v2.1.0.0 (first where zstd was supported)\n\ttestVersions := versionRange(V2_1_0_0)\n\tallCodecs := []CompressionCodec{CompressionZSTD}\n\tproducedMessages := produceMsgs(t, testVersions, allCodecs, 17, 100, false)\n\n\t// When/Then\n\tconsumeMsgs(t, testVersions, producedMessages)\n}\n\nfunc TestVersionMatrixIdempotent(t *testing.T) {\n\tmetrics.UseNilMetrics = true // disable Sarama's go-metrics library\n\tt.Cleanup(func() {\n\t\tmetrics.UseNilMetrics = false\n\t})\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\t// Produce lot's of message with all possible combinations of supported\n\t// protocol versions starting with v0.11 (first where idempotent was supported)\n\ttestVersions := versionRange(V0_11_0_0)\n\tproducedMessages := produceMsgs(t, testVersions, []CompressionCodec{CompressionNone}, 17, 100, true)\n\n\t// When/Then\n\tconsumeMsgs(t, testVersions, producedMessages)\n}\n\nfunc TestReadOnlyAndAllCommittedMessages(t *testing.T) {\n\tt.Skip(\"TODO: TestReadOnlyAndAllCommittedMessages is periodically failing inexplicably.\")\n\tcheckKafkaVersion(t, \"0.11.0\")\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tconfig := NewFunctionalTestConfig()\n\tconfig.ClientID = t.Name()\n\tconfig.Net.MaxOpenRequests = 1\n\tconfig.Consumer.IsolationLevel = ReadCommitted\n\tconfig.Producer.Idempotent = true\n\tconfig.Producer.Return.Successes = true\n\tconfig.Producer.RequiredAcks = WaitForAll\n\n\tclient, err := NewClient(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer client.Close()\n\tcontroller, err := client.Controller()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer controller.Close()\n\n\ttransactionalID := strconv.FormatInt(time.Now().UnixNano()/(1<<22), 10)\n\n\tvar coordinator *Broker\n\n\t// find the transaction coordinator\n\tfor {\n\t\tcoordRes, err := controller.FindCoordinator(&FindCoordinatorRequest{\n\t\t\tVersion:         2,\n\t\t\tCoordinatorKey:  transactionalID,\n\t\t\tCoordinatorType: CoordinatorTransaction,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif coordRes.Err != ErrNoError {\n\t\t\tcontinue\n\t\t}\n\t\tif err := coordRes.Coordinator.Open(client.Config()); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tcoordinator = coordRes.Coordinator\n\t\tbreak\n\t}\n\n\t// produce some uncommitted messages to the topic\n\tpidRes, err := coordinator.InitProducerID(&InitProducerIDRequest{\n\t\tTransactionalID:    &transactionalID,\n\t\tTransactionTimeout: 10 * time.Second,\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t_, _ = coordinator.AddPartitionsToTxn(&AddPartitionsToTxnRequest{\n\t\tTransactionalID: transactionalID,\n\t\tProducerID:      pidRes.ProducerID,\n\t\tProducerEpoch:   pidRes.ProducerEpoch,\n\t\tTopicPartitions: map[string][]int32{\n\t\t\tuncommittedTopic: {0},\n\t\t},\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tps := &produceSet{\n\t\tmsgs: make(map[string]map[int32]*partitionSet),\n\t\tparent: &asyncProducer{\n\t\t\tconf:   config,\n\t\t\ttxnmgr: &transactionManager{},\n\t\t},\n\t\tproducerID:    pidRes.ProducerID,\n\t\tproducerEpoch: pidRes.ProducerEpoch,\n\t}\n\t_ = ps.add(&ProducerMessage{\n\t\tTopic:     uncommittedTopic,\n\t\tPartition: 0,\n\t\tValue:     StringEncoder(\"uncommitted message 1\"),\n\t})\n\t_ = ps.add(&ProducerMessage{\n\t\tTopic:     uncommittedTopic,\n\t\tPartition: 0,\n\t\tValue:     StringEncoder(\"uncommitted message 2\"),\n\t})\n\tproduceReq := ps.buildRequest()\n\tproduceReq.TransactionalID = &transactionalID\n\tif resp, err := coordinator.Produce(produceReq); err != nil {\n\t\tt.Fatal(err)\n\t} else {\n\t\tb := resp.GetBlock(uncommittedTopic, 0)\n\t\tif b != nil {\n\t\t\tt.Logf(\"uncommitted message 1 to %s-%d at offset %d\", uncommittedTopic, 0, b.Offset)\n\t\t\tt.Logf(\"uncommitted message 2 to %s-%d at offset %d\", uncommittedTopic, 0, b.Offset+1)\n\t\t}\n\t}\n\n\t// now produce some committed messages to the topic\n\tproducer, err := NewAsyncProducer(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer producer.Close()\n\n\tfor i := 1; i <= 6; i++ {\n\t\tproducer.Input() <- &ProducerMessage{\n\t\t\tTopic:     uncommittedTopic,\n\t\t\tPartition: 0,\n\t\t\tValue:     StringEncoder(fmt.Sprintf(\"Committed %v\", i)),\n\t\t}\n\t\tmsg := <-producer.Successes()\n\t\tt.Logf(\"Committed %v to %s-%d at offset %d\", i, msg.Topic, msg.Partition, msg.Offset)\n\t}\n\n\t// now abort the uncommitted transaction\n\tif _, err := coordinator.EndTxn(&EndTxnRequest{\n\t\tTransactionalID:   transactionalID,\n\t\tProducerID:        pidRes.ProducerID,\n\t\tProducerEpoch:     pidRes.ProducerEpoch,\n\t\tTransactionResult: false, // aborted\n\t}); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tconsumer, err := NewConsumer(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer consumer.Close()\n\n\tpc, err := consumer.ConsumePartition(uncommittedTopic, 0, OffsetOldest)\n\tassert.NoError(t, err)\n\n\tmsgChannel := pc.Messages()\n\tfor i := 1; i <= 6; i++ {\n\t\tmsg := <-msgChannel\n\t\tt.Logf(\"Received %s from %s-%d at offset %d\", msg.Value, msg.Topic, msg.Partition, msg.Offset)\n\t\tassert.Equal(t, fmt.Sprintf(\"Committed %v\", i), string(msg.Value))\n\t}\n}\n\nfunc TestConsumerGroupDeadlock(t *testing.T) {\n\tcheckKafkaVersion(t, \"1.1.0\")\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tconst topic = \"test_consumer_group_rebalance_test_topic\"\n\tconst msgQty = 50\n\tpartitionsQty := len(FunctionalTestEnv.KafkaBrokerAddrs) * 3\n\n\tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\n\tconfig := NewFunctionalTestConfig()\n\tconfig.Version = V1_1_0_0\n\tconfig.ClientID = t.Name()\n\tconfig.Producer.Return.Successes = true\n\tconfig.ChannelBufferSize = 2 * msgQty\n\n\tclient, err := NewClient(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\tassert.NoError(t, err)\n\n\tadmin, err := NewClusterAdminFromClient(client)\n\tassert.NoError(t, err)\n\n\tcgName := \"test_consumer_group_rebalance_consumer_group\"\n\n\terr = admin.DeleteConsumerGroup(cgName)\n\tif err != nil {\n\t\tt.Logf(\"failed to delete topic: %s\", err)\n\t}\n\n\terr = admin.DeleteTopic(topic)\n\tif err != nil {\n\t\tt.Logf(\"failed to delete topic: %s\", err)\n\t}\n\n\t// it takes time to delete topic, the API is not sync\n\tfor i := 0; i < 5; i++ {\n\t\terr = admin.CreateTopic(topic, &TopicDetail{NumPartitions: int32(partitionsQty), ReplicationFactor: 1}, false)\n\t\tif err == nil {\n\t\t\tbreak\n\t\t}\n\t\tif errors.Is(err, ErrTopicAlreadyExists) || strings.Contains(err.Error(), \"is marked for deletion\") {\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t\tcontinue\n\t\t}\n\t\tbreak\n\t}\n\tassert.NoError(t, err)\n\tdefer func() {\n\t\t_ = admin.DeleteTopic(topic)\n\t}()\n\n\tvar wg sync.WaitGroup\n\n\tconsumer, err := NewConsumerFromClient(client)\n\tassert.NoError(t, err)\n\n\tch := make(chan string, msgQty)\n\tfor i := 0; i < partitionsQty; i++ {\n\t\ttime.Sleep(250 * time.Millisecond) // ensure delays between the \"claims\"\n\t\twg.Add(1)\n\t\tgo func(i int) {\n\t\t\tdefer wg.Done()\n\n\t\t\tpConsumer, err := consumer.ConsumePartition(topic, int32(i), OffsetOldest)\n\t\t\tassert.NoError(t, err)\n\t\t\tdefer pConsumer.Close()\n\n\t\t\tfor {\n\t\t\t\tselect {\n\t\t\t\tcase <-ctx.Done():\n\t\t\t\t\treturn\n\t\t\t\tcase msg, ok := <-pConsumer.Messages():\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t// t.Logf(\"consumer-group %d consumed: %v from %s/%d/%d\", i, msg.Value, msg.Topic, msg.Partition, msg.Offset)\n\t\t\t\t\tch <- string(msg.Value)\n\t\t\t\t}\n\t\t\t}\n\t\t}(i)\n\t}\n\n\tproducer, err := NewSyncProducerFromClient(client)\n\tassert.NoError(t, err)\n\n\tfor i := 0; i < msgQty; i++ {\n\t\tmsg := &ProducerMessage{\n\t\t\tTopic: topic,\n\t\t\tValue: StringEncoder(strconv.FormatInt(int64(i), 10)),\n\t\t}\n\t\t_, _, err := producer.SendMessage(msg)\n\t\tassert.NoError(t, err)\n\t}\n\n\tvar received []string\n\tfunc() {\n\t\tfor len(received) < msgQty {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn\n\t\t\tcase msg := <-ch:\n\t\t\t\treceived = append(received, msg)\n\t\t\t\t// t.Logf(\"received: %s, count: %d\", msg, len(received))\n\t\t\t}\n\t\t}\n\t}()\n\n\tcancel()\n\n\tassert.Equal(t, msgQty, len(received))\n\n\terr = producer.Close()\n\tassert.NoError(t, err)\n\n\terr = consumer.Close()\n\tassert.NoError(t, err)\n\n\terr = client.Close()\n\tassert.NoError(t, err)\n\n\twg.Wait()\n}\n\nfunc prodMsg2Str(prodMsg *ProducerMessage) string {\n\treturn fmt.Sprintf(\"{offset: %d, value: %s}\", prodMsg.Offset, string(prodMsg.Value.(StringEncoder)))\n}\n\nfunc consMsg2Str(consMsg *ConsumerMessage) string {\n\treturn fmt.Sprintf(\"{offset: %d, value: %s}\", consMsg.Offset, string(consMsg.Value))\n}\n\nfunc versionRange(lower KafkaVersion) []KafkaVersion {\n\t// Get the test cluster version from the environment. If there is nothing\n\t// there then assume the highest.\n\tupper, err := ParseKafkaVersion(os.Getenv(\"KAFKA_VERSION\"))\n\tif err != nil {\n\t\tupper = MaxVersion\n\t}\n\n\t// KIP-896 dictates a minimum lower bound of 2.1 protocol for Kafka 4.0 onwards\n\tif upper.IsAtLeast(V4_0_0_0) {\n\t\tif !lower.IsAtLeast(V2_1_0_0) {\n\t\t\tlower = V2_1_0_0\n\t\t}\n\t}\n\n\tversions := make([]KafkaVersion, 0, len(fvtRangeVersions))\n\tfor _, v := range fvtRangeVersions {\n\t\tif !v.IsAtLeast(lower) {\n\t\t\tcontinue\n\t\t}\n\t\tif !upper.IsAtLeast(v) {\n\t\t\treturn versions\n\t\t}\n\t\tversions = append(versions, v)\n\t}\n\treturn versions\n}\n\nfunc produceMsgs(t *testing.T, clientVersions []KafkaVersion, codecs []CompressionCodec, flush int, countPerVerCodec int, idempotent bool) []*ProducerMessage {\n\tvar (\n\t\tproducers          []SyncProducer\n\t\tproducedMessagesMu sync.Mutex\n\t\tproducedMessages   []*ProducerMessage\n\t)\n\tg := errgroup.Group{}\n\tfor _, prodVer := range clientVersions {\n\t\tfor _, codec := range codecs {\n\t\t\tprodCfg := NewFunctionalTestConfig()\n\t\t\tprodCfg.ClientID = t.Name() + \"-Producer-\" + prodVer.String()\n\t\t\tif idempotent {\n\t\t\t\tprodCfg.ClientID += \"-idempotent\"\n\t\t\t}\n\t\t\tif codec > 0 {\n\t\t\t\tprodCfg.ClientID += \"-\" + codec.String()\n\t\t\t}\n\t\t\tprodCfg.Metadata.Full = false\n\t\t\tprodCfg.Version = prodVer\n\t\t\tprodCfg.Producer.Return.Successes = true\n\t\t\tprodCfg.Producer.Return.Errors = true\n\t\t\tprodCfg.Producer.Flush.MaxMessages = flush\n\t\t\tprodCfg.Producer.Compression = codec\n\t\t\tprodCfg.Producer.Idempotent = idempotent\n\t\t\tif idempotent {\n\t\t\t\tprodCfg.Producer.RequiredAcks = WaitForAll\n\t\t\t\tprodCfg.Net.MaxOpenRequests = 1\n\t\t\t}\n\n\t\t\tp, err := NewSyncProducer(FunctionalTestEnv.KafkaBrokerAddrs, prodCfg)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Failed to create producer: version=%s, compression=%s, err=%v\", prodVer, codec, err)\n\t\t\t}\n\t\t\tproducers = append(producers, p)\n\n\t\t\tg.Go(func() error {\n\t\t\t\tt.Logf(\"*** Producing with client version %s codec %s\\n\", prodVer, codec)\n\t\t\t\tvar wg sync.WaitGroup\n\t\t\t\tfor i := 0; i < countPerVerCodec; i++ {\n\t\t\t\t\tmsg := &ProducerMessage{\n\t\t\t\t\t\tTopic: \"test.1\",\n\t\t\t\t\t\tValue: StringEncoder(fmt.Sprintf(\"msg:%s:%s:%d\", prodVer, codec, i)),\n\t\t\t\t\t}\n\t\t\t\t\twg.Add(1)\n\t\t\t\t\tgo func() {\n\t\t\t\t\t\tdefer wg.Done()\n\t\t\t\t\t\t_, _, err := p.SendMessage(msg)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tt.Errorf(\"Failed to produce message: %s, err=%v\", msg.Value, err)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tproducedMessagesMu.Lock()\n\t\t\t\t\t\tproducedMessages = append(producedMessages, msg)\n\t\t\t\t\t\tproducedMessagesMu.Unlock()\n\t\t\t\t\t}()\n\t\t\t\t}\n\t\t\t\twg.Wait()\n\t\t\t\treturn nil\n\t\t\t})\n\t\t}\n\t}\n\tif err := g.Wait(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor _, p := range producers {\n\t\tsafeClose(t, p)\n\t}\n\n\t// Sort produced message in ascending offset order.\n\tsort.Slice(producedMessages, func(i, j int) bool {\n\t\treturn producedMessages[i].Offset < producedMessages[j].Offset\n\t})\n\tassert.NotEmpty(t, producedMessages, \"should have produced >0 messages\")\n\tt.Logf(\"*** Total produced %d, firstOffset=%d, lastOffset=%d\\n\",\n\t\tlen(producedMessages), producedMessages[0].Offset, producedMessages[len(producedMessages)-1].Offset)\n\treturn producedMessages\n}\n\nfunc consumeMsgs(t *testing.T, clientVersions []KafkaVersion, producedMessages []*ProducerMessage) {\n\t// Consume all produced messages with all client versions supported by the\n\t// cluster.\n\tg := errgroup.Group{}\n\tfor _, consVer := range clientVersions {\n\t\t// Create a partition consumer that should start from the first produced\n\t\t// message.\n\t\tconsCfg := NewFunctionalTestConfig()\n\t\tconsCfg.ClientID = t.Name() + \"-Consumer-\" + consVer.String()\n\t\tconsCfg.Consumer.MaxProcessingTime = time.Second\n\t\tconsCfg.Metadata.Full = false\n\t\tconsCfg.Version = consVer\n\t\tc, err := NewConsumer(FunctionalTestEnv.KafkaBrokerAddrs, consCfg)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer safeClose(t, c) //nolint: gocritic // the close intentionally happens outside the loop\n\t\tpc, err := c.ConsumePartition(\"test.1\", 0, producedMessages[0].Offset)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tdefer safeClose(t, pc) //nolint: gocritic // the close intentionally happens outside the loop\n\n\t\tvar wg sync.WaitGroup\n\t\twg.Add(1)\n\t\tg.Go(func() error {\n\t\t\t// Consume as many messages as there have been produced and make sure that\n\t\t\t// order is preserved.\n\t\t\tt.Logf(\"*** Consuming with client version %s\\n\", consVer)\n\t\t\tfor i, prodMsg := range producedMessages {\n\t\t\t\tselect {\n\t\t\t\tcase consMsg := <-pc.Messages():\n\t\t\t\t\tif consMsg.Offset != prodMsg.Offset {\n\t\t\t\t\t\tt.Fatalf(\"Consumed unexpected offset: version=%s, index=%d, want=%s, got=%s\",\n\t\t\t\t\t\t\tconsVer, i, prodMsg2Str(prodMsg), consMsg2Str(consMsg))\n\t\t\t\t\t}\n\t\t\t\t\tif string(consMsg.Value) != string(prodMsg.Value.(StringEncoder)) {\n\t\t\t\t\t\tt.Fatalf(\"Consumed unexpected msg: version=%s, index=%d, want=%s, got=%s\",\n\t\t\t\t\t\t\tconsVer, i, prodMsg2Str(prodMsg), consMsg2Str(consMsg))\n\t\t\t\t\t}\n\t\t\t\t\tif i == 0 {\n\t\t\t\t\t\tt.Logf(\"Consumed first msg: version=%s, index=%d, got=%s\",\n\t\t\t\t\t\t\tconsVer, i, consMsg2Str(consMsg))\n\t\t\t\t\t\twg.Done()\n\t\t\t\t\t}\n\t\t\t\t\tif i%1000 == 0 {\n\t\t\t\t\t\tt.Logf(\"Consumed messages: version=%s, index=%d, got=%s\",\n\t\t\t\t\t\t\tconsVer, i, consMsg2Str(consMsg))\n\t\t\t\t\t}\n\t\t\t\tcase <-time.After(15 * time.Second):\n\t\t\t\t\tt.Fatalf(\"Timeout %s waiting for: index=%d, offset=%d, msg=%s\",\n\t\t\t\t\t\tconsCfg.ClientID, i, prodMsg.Offset, prodMsg.Value)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t\twg.Wait() // wait for first message to be consumed before starting next consumer\n\t}\n\tif err := g.Wait(); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n"
  },
  {
    "path": "functional_java_interop_test.go",
    "content": "//go:build functional\n\npackage sarama\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"fmt\"\n\t\"os/exec\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nconst (\n\tbrokerContainer = \"kafka-1\"\n\tbrokerAddr      = \"kafka-1:9091\"\n\tzookeeperAddr   = \"zookeeper-1:2181,zookeeper-2:2181,zookeeper-3:2181\"\n)\n\nvar compressionTests = []struct {\n\tcodec    CompressionCodec\n\tminKafka string\n}{\n\t{CompressionNone, \"0.8.0\"},\n\t{CompressionGZIP, \"0.8.0\"},\n\t{CompressionSnappy, \"0.8.0\"},\n\t{CompressionLZ4, \"0.10.0\"},\n\t{CompressionZSTD, \"2.1.0\"},\n}\n\nfunc produceWithJava(t *testing.T, topic string, codec CompressionCodec, messages []string) {\n\tt.Helper()\n\tproducerPath := fmt.Sprintf(\"/opt/kafka-%s/bin/kafka-console-producer.sh\", FunctionalTestEnv.KafkaVersion)\n\targs := append(\n\t\t[]string{\"compose\", \"exec\", \"-T\", brokerContainer, producerPath},\n\t\tjavaProducerArgs(topic, codec)...,\n\t)\n\tcmd := exec.Command(\"docker\", args...)\n\n\tstdin, err := cmd.StdinPipe()\n\trequire.NoError(t, err)\n\n\tstderr, err := cmd.StderrPipe()\n\trequire.NoError(t, err)\n\n\tvar stderrOutput strings.Builder\n\tvar wg sync.WaitGroup\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\ts := bufio.NewScanner(stderr)\n\t\tfor s.Scan() {\n\t\t\tstderrOutput.WriteString(s.Text() + \"\\n\")\n\t\t}\n\t}()\n\n\trequire.NoError(t, cmd.Start())\n\n\tfor _, msg := range messages {\n\t\t_, err := fmt.Fprintln(stdin, msg)\n\t\tif err != nil {\n\t\t\tstdin.Close()\n\t\t\twaitErr := cmd.Wait()\n\t\t\twg.Wait()\n\t\t\tif waitErr != nil {\n\t\t\t\terr = fmt.Errorf(\"failed to write message: %w; Java producer failed: %w; stderr: %s\", err, waitErr, stderrOutput.String())\n\t\t\t}\n\t\t}\n\t\trequire.NoError(t, err)\n\t}\n\tstdin.Close()\n\n\terr = cmd.Wait()\n\twg.Wait()\n\tif err != nil {\n\t\tt.Logf(\"Java producer stderr: %s\", stderrOutput.String())\n\t\trequire.NoError(t, err, \"Java producer failed\")\n\t}\n}\n\nfunc consumeWithSarama(t *testing.T, topic string, startOffset int64, count int) []string {\n\tt.Helper()\n\tconfig := NewFunctionalTestConfig()\n\tconsumer, err := NewConsumer(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\trequire.NoError(t, err)\n\tdefer consumer.Close()\n\n\tpartitionConsumer, err := consumer.ConsumePartition(topic, 0, startOffset)\n\trequire.NoError(t, err)\n\tdefer partitionConsumer.Close()\n\n\tvar messages []string\n\tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\tdefer cancel()\n\n\tfor i := 0; i < count; i++ {\n\t\tselect {\n\t\tcase msg := <-partitionConsumer.Messages():\n\t\t\trequire.NotNil(t, msg)\n\t\t\tmessages = append(messages, string(msg.Value))\n\t\tcase err := <-partitionConsumer.Errors():\n\t\t\trequire.NoError(t, err)\n\t\tcase <-ctx.Done():\n\t\t\trequire.Fail(t, \"timeout waiting for messages\")\n\t\t}\n\t}\n\treturn messages\n}\n\nfunc produceWithSarama(t *testing.T, topic string, codec CompressionCodec, messages []string) {\n\tt.Helper()\n\tconfig := NewFunctionalTestConfig()\n\tconfig.Producer.Compression = codec\n\tconfig.Producer.Return.Successes = true\n\tconfig.Producer.RequiredAcks = WaitForAll\n\n\tproducer, err := NewSyncProducer(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\trequire.NoError(t, err)\n\tdefer producer.Close()\n\n\tfor _, msgText := range messages {\n\t\t_, _, err := producer.SendMessage(&ProducerMessage{\n\t\t\tTopic: topic,\n\t\t\tValue: StringEncoder(msgText),\n\t\t})\n\t\trequire.NoError(t, err)\n\t}\n}\n\nfunc consumeWithJava(t *testing.T, topic string, startOffset int64, count int) []string {\n\tt.Helper()\n\tconsumerPath := fmt.Sprintf(\"/opt/kafka-%s/bin/kafka-console-consumer.sh\", FunctionalTestEnv.KafkaVersion)\n\targs := append(\n\t\t[]string{\"compose\", \"exec\", \"-T\", brokerContainer, consumerPath},\n\t\tjavaConsumerArgs(topic, startOffset, count)...,\n\t)\n\tcmd := exec.Command(\"docker\", args...)\n\n\tstdout, err := cmd.StdoutPipe()\n\trequire.NoError(t, err)\n\n\tstderr, err := cmd.StderrPipe()\n\trequire.NoError(t, err)\n\n\trequire.NoError(t, cmd.Start())\n\n\tvar messages []string\n\tscanner := bufio.NewScanner(stdout)\n\tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\tdefer cancel()\n\n\tdone := make(chan struct{})\n\tstdoutErrCh := make(chan error, 1)\n\tstderrErrCh := make(chan error, 1)\n\tvar wg sync.WaitGroup\n\twg.Add(2)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tfor scanner.Scan() {\n\t\t\tif line := strings.TrimSpace(scanner.Text()); line != \"\" {\n\t\t\t\tmessages = append(messages, line)\n\t\t\t}\n\t\t\tif len(messages) >= count {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tstdoutErrCh <- scanner.Err()\n\t\tclose(done)\n\t}()\n\n\tvar stderrOutput strings.Builder\n\tgo func() {\n\t\tdefer wg.Done()\n\t\ts := bufio.NewScanner(stderr)\n\t\tfor s.Scan() {\n\t\t\tstderrOutput.WriteString(s.Text() + \"\\n\")\n\t\t}\n\t\tstderrErrCh <- s.Err()\n\t}()\n\n\tselect {\n\tcase <-done:\n\tcase <-ctx.Done():\n\t\trequire.Fail(t, \"timeout waiting for Java consumer\")\n\t\t_ = cmd.Process.Kill()\n\t}\n\n\tif err := cmd.Wait(); err != nil && len(messages) < count {\n\t\tt.Logf(\"stderr: %s\", stderrOutput.String())\n\t\trequire.NoError(t, err, \"Java consumer failed\")\n\t}\n\twg.Wait()\n\trequire.NoError(t, <-stdoutErrCh)\n\trequire.NoError(t, <-stderrErrCh)\n\treturn messages\n}\n\nfunc endOffsetForPartition(t *testing.T, topic string, partition int32) int64 {\n\tt.Helper()\n\tconfig := NewFunctionalTestConfig()\n\tclient, err := NewClient(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\trequire.NoError(t, err)\n\tdefer safeClose(t, client)\n\n\toffset, err := client.GetOffset(topic, partition, OffsetNewest)\n\trequire.NoError(t, err)\n\treturn offset\n}\n\n// TestJavaProducerCompressionRoundTrip tests that messages produced by Kafka's Java\n// console producer with various compression codecs can be correctly consumed and\n// decompressed by Sarama.\nfunc TestJavaProducerCompressionRoundTrip(t *testing.T) {\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tfor _, tc := range compressionTests {\n\t\tt.Run(tc.codec.String(), func(t *testing.T) {\n\t\t\tcheckKafkaVersion(t, tc.minKafka)\n\n\t\t\texpected := []string{\n\t\t\t\tfmt.Sprintf(\"Message 1 with %s compression\", tc.codec),\n\t\t\t\t\"Message 2\",\n\t\t\t\t\"Message 3\",\n\t\t\t}\n\n\t\t\tinitialOffset := endOffsetForPartition(t, \"test.1\", 0)\n\t\t\tproduceWithJava(t, \"test.1\", tc.codec, expected)\n\t\t\tactual := consumeWithSarama(t, \"test.1\", initialOffset, len(expected))\n\n\t\t\trequire.Equal(t, expected, actual)\n\t\t})\n\t}\n}\n\n// TestJavaConsumerCompressionRoundTrip tests that messages produced by Sarama\n// with various compression codecs can be correctly consumed and decompressed\n// by Kafka's Java console consumer.\nfunc TestJavaConsumerCompressionRoundTrip(t *testing.T) {\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tfor _, tc := range compressionTests {\n\t\tt.Run(tc.codec.String(), func(t *testing.T) {\n\t\t\tcheckKafkaVersion(t, tc.minKafka)\n\n\t\t\texpected := []string{\n\t\t\t\tfmt.Sprintf(\"Message 1 with %s compression\", tc.codec),\n\t\t\t\t\"Message 2\",\n\t\t\t\t\"Message 3\",\n\t\t\t}\n\n\t\t\tinitialOffset := endOffsetForPartition(t, \"test.1\", 0)\n\t\t\tproduceWithSarama(t, \"test.1\", tc.codec, expected)\n\t\t\tactual := consumeWithJava(t, \"test.1\", initialOffset, len(expected))\n\n\t\t\trequire.Equal(t, expected, actual)\n\t\t})\n\t}\n}\n\nfunc kafkaVersionAtLeast(requiredVersion string) bool {\n\tkafkaVersion := FunctionalTestEnv.KafkaVersion\n\tif kafkaVersion == \"\" {\n\t\treturn false\n\t}\n\treturn parseKafkaVersion(kafkaVersion).satisfies(parseKafkaVersion(requiredVersion))\n}\n\nfunc javaProducerArgs(topic string, codec CompressionCodec) []string {\n\targs := make([]string, 0, 8)\n\tif kafkaVersionAtLeast(\"2.5.0\") {\n\t\targs = append(args, \"--bootstrap-server\", brokerAddr)\n\t} else {\n\t\targs = append(args, \"--broker-list\", brokerAddr)\n\t}\n\targs = append(args, \"--topic\", topic)\n\treturn append(args, javaProducerCompressionArgs(codec)...)\n}\n\nfunc javaProducerCompressionArgs(codec CompressionCodec) []string {\n\tif kafkaVersionAtLeast(\"0.10.0\") {\n\t\treturn []string{\"--producer-property\", fmt.Sprintf(\"compression.type=%s\", codec.String())}\n\t}\n\treturn []string{\"--compression-codec\", codec.String()}\n}\n\nfunc javaConsumerArgs(topic string, startOffset int64, count int) []string {\n\targs := make([]string, 0, 12)\n\tif kafkaVersionAtLeast(\"0.10.0\") {\n\t\targs = append(args, \"--bootstrap-server\", brokerAddr)\n\t} else {\n\t\targs = append(args, \"--zookeeper\", zookeeperAddr)\n\t}\n\treturn append(args,\n\t\t\"--topic\", topic,\n\t\t\"--partition\", \"0\",\n\t\t\"--offset\", fmt.Sprint(startOffset),\n\t\t\"--max-messages\", fmt.Sprint(count),\n\t)\n}\n"
  },
  {
    "path": "functional_offset_manager_test.go",
    "content": "//go:build functional\n\npackage sarama\n\nimport (\n\t\"testing\"\n)\n\nfunc TestFuncOffsetManager(t *testing.T) {\n\tcheckKafkaVersion(t, \"0.8.2\")\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tclient, err := NewClient(FunctionalTestEnv.KafkaBrokerAddrs, NewFunctionalTestConfig())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\toffsetManager, err := NewOffsetManagerFromClient(\"sarama.TestFuncOffsetManager\", client)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tpom1, err := offsetManager.ManagePartition(\"test.1\", 0)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tpom1.MarkOffset(10, \"test metadata\")\n\tsafeClose(t, pom1)\n\n\t// Avoid flaky test: submit offset & let om cleanup removed poms\n\toffsetManager.Commit()\n\n\tpom2, err := offsetManager.ManagePartition(\"test.1\", 0)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\toffset, metadata := pom2.NextOffset()\n\n\tif offset != 10 {\n\t\tt.Errorf(\"Expected the next offset to be 10, found %d.\", offset)\n\t}\n\tif metadata != \"test metadata\" {\n\t\tt.Errorf(\"Expected metadata to be 'test metadata', found %s.\", metadata)\n\t}\n\n\tsafeClose(t, pom2)\n\tsafeClose(t, offsetManager)\n\tsafeClose(t, client)\n}\n"
  },
  {
    "path": "functional_producer_test.go",
    "content": "//go:build functional\n\npackage sarama\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/rcrowley/go-metrics\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/IBM/sarama/internal/toxiproxy\"\n)\n\nconst TestBatchSize = 1000\n\nfunc TestFuncProducing(t *testing.T) {\n\tconfig := NewFunctionalTestConfig()\n\ttestProducingMessages(t, config, MinVersion)\n}\n\nfunc TestFuncProducingGzip(t *testing.T) {\n\tconfig := NewFunctionalTestConfig()\n\tconfig.Producer.Compression = CompressionGZIP\n\ttestProducingMessages(t, config, MinVersion)\n}\n\nfunc TestFuncProducingSnappy(t *testing.T) {\n\tconfig := NewFunctionalTestConfig()\n\tconfig.Producer.Compression = CompressionSnappy\n\ttestProducingMessages(t, config, MinVersion)\n}\n\nfunc TestFuncProducingZstd(t *testing.T) {\n\tconfig := NewFunctionalTestConfig()\n\tconfig.Producer.Compression = CompressionZSTD\n\ttestProducingMessages(t, config, V2_1_0_0) // must be at least 2.1.0.0 for zstd\n}\n\nfunc TestFuncProducingNoResponse(t *testing.T) {\n\tconfig := NewFunctionalTestConfig()\n\tconfig.ApiVersionsRequest = false\n\tconfig.Producer.RequiredAcks = NoResponse\n\ttestProducingMessages(t, config, MinVersion)\n}\n\nfunc TestFuncProducingFlushing(t *testing.T) {\n\tconfig := NewFunctionalTestConfig()\n\tconfig.Producer.Flush.Messages = TestBatchSize / 8\n\tconfig.Producer.Flush.Frequency = 250 * time.Millisecond\n\ttestProducingMessages(t, config, MinVersion)\n}\n\nfunc TestFuncMultiPartitionProduce(t *testing.T) {\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tconfig := NewFunctionalTestConfig()\n\tconfig.ChannelBufferSize = 20\n\tconfig.Producer.Flush.Frequency = 50 * time.Millisecond\n\tconfig.Producer.Flush.Messages = 200\n\tconfig.Producer.Return.Successes = true\n\tproducer, err := NewSyncProducer(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tvar wg sync.WaitGroup\n\twg.Add(TestBatchSize)\n\n\tfor i := 1; i <= TestBatchSize; i++ {\n\t\tgo func(i int) {\n\t\t\tdefer wg.Done()\n\t\t\tmsg := &ProducerMessage{Topic: \"test.64\", Key: nil, Value: StringEncoder(fmt.Sprintf(\"hur %d\", i))}\n\t\t\tif _, _, err := producer.SendMessage(msg); err != nil {\n\t\t\t\tt.Error(i, err)\n\t\t\t}\n\t\t}(i)\n\t}\n\n\twg.Wait()\n\tif err := producer.Close(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestFuncTxnProduceNoBegin(t *testing.T) {\n\tcheckKafkaVersion(t, \"0.11.0.0\")\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tconfig := NewFunctionalTestConfig()\n\tconfig.ChannelBufferSize = 20\n\tconfig.Producer.Flush.Frequency = 50 * time.Millisecond\n\tconfig.Producer.Flush.Messages = 200\n\tconfig.Producer.Idempotent = true\n\tconfig.Producer.Transaction.ID = \"TestFuncTxnProduceNoBegin\"\n\tconfig.Producer.RequiredAcks = WaitForAll\n\tconfig.Producer.Retry.Max = 50\n\tconfig.Consumer.IsolationLevel = ReadCommitted\n\tconfig.Producer.Return.Errors = true\n\tconfig.Producer.Transaction.Retry.Max = 200\n\tconfig.Net.MaxOpenRequests = 1\n\tproducer, err := NewAsyncProducer(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\trequire.NoError(t, err)\n\tdefer producer.Close()\n\n\tproducer.Input() <- &ProducerMessage{Topic: \"test.1\", Key: nil, Value: StringEncoder(\"test\")}\n\tproducerError := <-producer.Errors()\n\trequire.Error(t, producerError)\n}\n\nfunc TestFuncTxnCommitNoMessages(t *testing.T) {\n\tcheckKafkaVersion(t, \"0.11.0.0\")\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tconfig := NewFunctionalTestConfig()\n\tconfig.ChannelBufferSize = 20\n\tconfig.Producer.Flush.Frequency = 50 * time.Millisecond\n\tconfig.Producer.Flush.Messages = 200\n\tconfig.Producer.Idempotent = true\n\tconfig.Producer.Transaction.ID = \"TestFuncTxnCommitNoMessages\"\n\tconfig.Producer.RequiredAcks = WaitForAll\n\tconfig.Producer.Retry.Max = 50\n\tconfig.Consumer.IsolationLevel = ReadCommitted\n\tconfig.Producer.Return.Errors = true\n\tconfig.Producer.Transaction.Retry.Max = 200\n\tconfig.Net.MaxOpenRequests = 1\n\tproducer, err := NewAsyncProducer(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\trequire.NoError(t, err)\n\tdefer producer.Close()\n\n\terr = producer.BeginTxn()\n\trequire.NoError(t, err)\n\n\terr = producer.AbortTxn()\n\trequire.NoError(t, err)\n\n\terr = producer.BeginTxn()\n\trequire.NoError(t, err)\n\n\terr = producer.CommitTxn()\n\trequire.NoError(t, err)\n}\n\nfunc TestFuncTxnProduce(t *testing.T) {\n\tcheckKafkaVersion(t, \"0.11.0.0\")\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tconfig := NewFunctionalTestConfig()\n\tconfig.ChannelBufferSize = 20\n\tconfig.Producer.Flush.Frequency = 50 * time.Millisecond\n\tconfig.Producer.Flush.Messages = 200\n\tconfig.Producer.Idempotent = true\n\tconfig.Producer.Transaction.ID = \"TestFuncTxnProduce\"\n\tconfig.Producer.RequiredAcks = WaitForAll\n\tconfig.Producer.Transaction.Retry.Max = 200\n\tconfig.Consumer.IsolationLevel = ReadCommitted\n\tconfig.Net.MaxOpenRequests = 1\n\n\tconsumer, err := NewConsumer(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\trequire.NoError(t, err)\n\tdefer consumer.Close()\n\n\tpc, err := consumer.ConsumePartition(\"test.1\", 0, OffsetNewest)\n\trequire.NoError(t, err)\n\tmsgChannel := pc.Messages()\n\tdefer pc.Close()\n\n\tnonTransactionalProducer, err := NewAsyncProducer(FunctionalTestEnv.KafkaBrokerAddrs, NewFunctionalTestConfig())\n\trequire.NoError(t, err)\n\tdefer nonTransactionalProducer.Close()\n\n\t// Ensure consumer is started\n\tnonTransactionalProducer.Input() <- &ProducerMessage{Topic: \"test.1\", Key: nil, Value: StringEncoder(\"test\")}\n\t<-msgChannel\n\n\tproducer, err := NewAsyncProducer(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\trequire.NoError(t, err)\n\tdefer producer.Close()\n\n\terr = producer.BeginTxn()\n\trequire.NoError(t, err)\n\n\tfor i := 0; i < 1; i++ {\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"test.1\", Key: nil, Value: StringEncoder(\"test\")}\n\t}\n\n\terr = producer.CommitTxn()\n\trequire.NoError(t, err)\n\n\tfor i := 0; i < 1; i++ {\n\t\tmsg := <-msgChannel\n\t\tt.Logf(\"Received %s from %s-%d at offset %d\", msg.Value, msg.Topic, msg.Partition, msg.Offset)\n\t}\n}\n\nfunc TestFuncTxnProduceWithBrokerFailure(t *testing.T) {\n\tcheckKafkaVersion(t, \"0.11.0.0\")\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tconfig := NewFunctionalTestConfig()\n\tconfig.ChannelBufferSize = 20\n\tconfig.Producer.Flush.Frequency = 50 * time.Millisecond\n\tconfig.Producer.Flush.Messages = 200\n\tconfig.Producer.Idempotent = true\n\tconfig.Producer.Transaction.ID = \"TestFuncTxnProduceWithBrokerFailure\"\n\tconfig.Producer.RequiredAcks = WaitForAll\n\tconfig.Producer.Transaction.Retry.Max = 200\n\tconfig.Consumer.IsolationLevel = ReadCommitted\n\tconfig.Net.MaxOpenRequests = 1\n\n\tconsumer, err := NewConsumer(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\trequire.NoError(t, err)\n\tdefer consumer.Close()\n\n\tpc, err := consumer.ConsumePartition(\"test.1\", 0, OffsetNewest)\n\trequire.NoError(t, err)\n\tmsgChannel := pc.Messages()\n\tdefer pc.Close()\n\n\tnonTransactionalProducer, err := NewAsyncProducer(FunctionalTestEnv.KafkaBrokerAddrs, NewFunctionalTestConfig())\n\trequire.NoError(t, err)\n\tdefer nonTransactionalProducer.Close()\n\n\t// Ensure consumer is started\n\tnonTransactionalProducer.Input() <- &ProducerMessage{Topic: \"test.1\", Key: nil, Value: StringEncoder(\"test\")}\n\t<-msgChannel\n\n\tproducer, err := NewAsyncProducer(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\trequire.NoError(t, err)\n\tdefer producer.Close()\n\n\ttxCoordinator, _ := producer.(*asyncProducer).client.TransactionCoordinator(config.Producer.Transaction.ID)\n\n\terr = producer.BeginTxn()\n\trequire.NoError(t, err)\n\n\tif err := stopDockerTestBroker(context.Background(), txCoordinator.id); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdefer func() {\n\t\tif err := startDockerTestBroker(context.Background(), txCoordinator.id); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tt.Logf(\"\\n\")\n\t}()\n\n\tfor i := 0; i < 1; i++ {\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"test.1\", Key: nil, Value: StringEncoder(\"test\")}\n\t}\n\n\terr = producer.CommitTxn()\n\trequire.NoError(t, err)\n\n\tfor i := 0; i < 1; i++ {\n\t\tmsg := <-msgChannel\n\t\tt.Logf(\"Received %s from %s-%d at offset %d\", msg.Value, msg.Topic, msg.Partition, msg.Offset)\n\t}\n}\n\nfunc TestFuncTxnProduceEpochBump(t *testing.T) {\n\tcheckKafkaVersion(t, \"2.6.0\")\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tconfig := NewFunctionalTestConfig()\n\tconfig.ChannelBufferSize = 20\n\tconfig.Producer.Flush.Frequency = 50 * time.Millisecond\n\tconfig.Producer.Flush.Messages = 200\n\tconfig.Producer.Idempotent = true\n\tconfig.Producer.Transaction.ID = \"TestFuncTxnProduceEpochBump\"\n\tconfig.Producer.RequiredAcks = WaitForAll\n\tconfig.Producer.Transaction.Retry.Max = 200\n\tconfig.Consumer.IsolationLevel = ReadCommitted\n\tconfig.Net.MaxOpenRequests = 1\n\n\tconsumer, err := NewConsumer(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\trequire.NoError(t, err)\n\tdefer consumer.Close()\n\n\tpc, err := consumer.ConsumePartition(\"test.1\", 0, OffsetNewest)\n\trequire.NoError(t, err)\n\tmsgChannel := pc.Messages()\n\tdefer pc.Close()\n\n\tnonTransactionalProducer, err := NewAsyncProducer(FunctionalTestEnv.KafkaBrokerAddrs, NewFunctionalTestConfig())\n\trequire.NoError(t, err)\n\tdefer nonTransactionalProducer.Close()\n\n\t// Ensure consumer is started\n\tnonTransactionalProducer.Input() <- &ProducerMessage{Topic: \"test.1\", Key: nil, Value: StringEncoder(\"test\")}\n\t<-msgChannel\n\n\tproducer, err := NewAsyncProducer(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\trequire.NoError(t, err)\n\tdefer producer.Close()\n\n\terr = producer.BeginTxn()\n\trequire.NoError(t, err)\n\n\tfor i := 0; i < 1; i++ {\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"test.1\", Key: nil, Value: StringEncoder(\"test\")}\n\t}\n\n\terr = producer.CommitTxn()\n\trequire.NoError(t, err)\n\n\tfor i := 0; i < 1; i++ {\n\t\tmsg := <-msgChannel\n\t\tt.Logf(\"Received %s from %s-%d at offset %d\", msg.Value, msg.Topic, msg.Partition, msg.Offset)\n\t}\n\n\terr = producer.BeginTxn()\n\trequire.NoError(t, err)\n\n\tfor i := 0; i < 1; i++ {\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"test.1\", Key: nil, Value: StringEncoder(\"test\")}\n\t}\n\n\terr = producer.CommitTxn()\n\trequire.NoError(t, err)\n\n\tfor i := 0; i < 1; i++ {\n\t\tmsg := <-msgChannel\n\t\tt.Logf(\"Received %s from %s-%d at offset %d\", msg.Value, msg.Topic, msg.Partition, msg.Offset)\n\t}\n}\n\nfunc TestFuncInitProducerId3(t *testing.T) {\n\tcheckKafkaVersion(t, \"2.6.0\")\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tconfig := NewFunctionalTestConfig()\n\tconfig.ChannelBufferSize = 20\n\tconfig.Producer.Flush.Frequency = 50 * time.Millisecond\n\tconfig.Producer.Flush.Messages = 200\n\tconfig.Producer.Idempotent = true\n\tconfig.Producer.Transaction.ID = \"TestFuncInitProducerId3\"\n\tconfig.Producer.RequiredAcks = WaitForAll\n\tconfig.Producer.Retry.Max = 50\n\tconfig.Consumer.IsolationLevel = ReadCommitted\n\tconfig.Net.MaxOpenRequests = 1\n\n\tproducer, err := NewAsyncProducer(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\trequire.NoError(t, err)\n\tdefer producer.Close()\n\n\trequire.Equal(t, true, producer.(*asyncProducer).txnmgr.coordinatorSupportsBumpingEpoch)\n}\n\ntype messageHandler struct {\n\t*testing.T\n\th       func(*ConsumerMessage)\n\tstarted sync.WaitGroup\n}\n\nfunc (h *messageHandler) Setup(s ConsumerGroupSession) error   { return nil }\nfunc (h *messageHandler) Cleanup(s ConsumerGroupSession) error { return nil }\nfunc (h *messageHandler) ConsumeClaim(sess ConsumerGroupSession, claim ConsumerGroupClaim) error {\n\th.started.Done()\n\n\tfor msg := range claim.Messages() {\n\t\th.Logf(\"consumed msg %v\", msg)\n\t\th.h(msg)\n\t}\n\treturn nil\n}\n\nfunc TestFuncTxnProduceAndCommitOffset(t *testing.T) {\n\tcheckKafkaVersion(t, \"0.11.0.0\")\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tconfig := NewFunctionalTestConfig()\n\tconfig.ChannelBufferSize = 20\n\tconfig.Producer.Flush.Frequency = 50 * time.Millisecond\n\tconfig.Producer.Flush.Messages = 200\n\tconfig.Producer.Idempotent = true\n\tconfig.Producer.Transaction.ID = \"TestFuncTxnProduceAndCommitOffset\"\n\tconfig.Producer.RequiredAcks = WaitForAll\n\tconfig.Producer.Transaction.Retry.Max = 200\n\tconfig.Consumer.IsolationLevel = ReadCommitted\n\tconfig.Consumer.Offsets.AutoCommit.Enable = false\n\tconfig.Net.MaxOpenRequests = 1\n\n\tclient, err := NewClient(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\trequire.NoError(t, err)\n\tdefer client.Close()\n\n\tadmin, err := NewClusterAdminFromClient(client)\n\trequire.NoError(t, err)\n\tdefer admin.Close()\n\n\tproducer, err := NewAsyncProducerFromClient(client)\n\trequire.NoError(t, err)\n\tdefer producer.Close()\n\n\tcg, err := NewConsumerGroupFromClient(\"test-produce\", client)\n\trequire.NoError(t, err)\n\tdefer cg.Close()\n\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\n\thandler := &messageHandler{}\n\thandler.T = t\n\thandler.h = func(msg *ConsumerMessage) {\n\t\terr := producer.BeginTxn()\n\t\trequire.NoError(t, err)\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"test.1\", Value: StringEncoder(\"test-prod\")}\n\t\terr = producer.AddMessageToTxn(msg, \"test-produce\", nil)\n\t\trequire.NoError(t, err)\n\t\terr = producer.CommitTxn()\n\t\trequire.NoError(t, err)\n\t}\n\n\thandler.started.Add(4)\n\tgo func() {\n\t\terr = cg.Consume(ctx, []string{\"test.4\"}, handler)\n\t\trequire.NoError(t, err)\n\t}()\n\n\thandler.started.Wait()\n\n\tnonTransactionalProducer, err := NewAsyncProducer(FunctionalTestEnv.KafkaBrokerAddrs, NewFunctionalTestConfig())\n\trequire.NoError(t, err)\n\tdefer nonTransactionalProducer.Close()\n\n\tconsumer, err := NewConsumerFromClient(client)\n\trequire.NoError(t, err)\n\tdefer consumer.Close()\n\n\tpc, err := consumer.ConsumePartition(\"test.1\", 0, OffsetNewest)\n\trequire.NoError(t, err)\n\tmsgChannel := pc.Messages()\n\tdefer pc.Close()\n\n\t// Ensure consumer is started\n\tnonTransactionalProducer.Input() <- &ProducerMessage{Topic: \"test.1\", Key: nil, Value: StringEncoder(\"test\")}\n\t<-msgChannel\n\n\tfor i := 0; i < 1; i++ {\n\t\tnonTransactionalProducer.Input() <- &ProducerMessage{Topic: \"test.4\", Key: nil, Value: StringEncoder(\"test\")}\n\t}\n\n\tfor i := 0; i < 1; i++ {\n\t\tmsg := <-msgChannel\n\t\tt.Logf(\"Received %s from %s-%d at offset %d\", msg.Value, msg.Topic, msg.Partition, msg.Offset)\n\t}\n\n\ttopicPartitions := make(map[string][]int32)\n\ttopicPartitions[\"test.4\"] = []int32{0, 1, 2, 3}\n\ttopicsDescription, err := admin.ListConsumerGroupOffsets(\"test-produce\", topicPartitions)\n\trequire.NoError(t, err)\n\n\tfor _, partition := range topicPartitions[\"test.4\"] {\n\t\tblock := topicsDescription.GetBlock(\"test.4\", partition)\n\t\t_ = client.RefreshMetadata(\"test.4\")\n\t\tlastOffset, err := client.GetOffset(\"test.4\", partition, OffsetNewest)\n\t\trequire.NoError(t, err)\n\t\tif block.Offset > -1 {\n\t\t\trequire.Equal(t, lastOffset, block.Offset)\n\t\t}\n\t}\n}\n\nfunc TestFuncTxnProduceMultiTxn(t *testing.T) {\n\tcheckKafkaVersion(t, \"0.11.0.0\")\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tconfig := NewFunctionalTestConfig()\n\tconfig.ChannelBufferSize = 20\n\tconfig.Producer.Flush.Frequency = 50 * time.Millisecond\n\tconfig.Producer.Flush.Messages = 200\n\tconfig.Producer.Idempotent = true\n\tconfig.Producer.Transaction.ID = \"TestFuncTxnProduceMultiTxn\"\n\tconfig.Producer.RequiredAcks = WaitForAll\n\tconfig.Producer.Transaction.Retry.Max = 200\n\tconfig.Consumer.IsolationLevel = ReadCommitted\n\tconfig.Net.MaxOpenRequests = 1\n\n\tconfigSecond := NewFunctionalTestConfig()\n\tconfigSecond.ChannelBufferSize = 20\n\tconfigSecond.Producer.Flush.Frequency = 50 * time.Millisecond\n\tconfigSecond.Producer.Flush.Messages = 200\n\tconfigSecond.Producer.Idempotent = true\n\tconfigSecond.Producer.Transaction.ID = \"TestFuncTxnProduceMultiTxn-second\"\n\tconfigSecond.Producer.RequiredAcks = WaitForAll\n\tconfigSecond.Producer.Retry.Max = 50\n\tconfigSecond.Consumer.IsolationLevel = ReadCommitted\n\tconfigSecond.Net.MaxOpenRequests = 1\n\n\tconsumer, err := NewConsumer(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\trequire.NoError(t, err)\n\tdefer consumer.Close()\n\n\tpc, err := consumer.ConsumePartition(\"test.1\", 0, OffsetNewest)\n\trequire.NoError(t, err)\n\tmsgChannel := pc.Messages()\n\tdefer pc.Close()\n\n\tnonTransactionalConfig := NewFunctionalTestConfig()\n\tnonTransactionalConfig.Producer.Return.Successes = true\n\tnonTransactionalConfig.Producer.Return.Errors = true\n\n\tnonTransactionalProducer, err := NewAsyncProducer(FunctionalTestEnv.KafkaBrokerAddrs, nonTransactionalConfig)\n\trequire.NoError(t, err)\n\tdefer nonTransactionalProducer.Close()\n\n\t// Ensure consumer is started\n\tnonTransactionalProducer.Input() <- &ProducerMessage{Topic: \"test.1\", Key: nil, Value: StringEncoder(\"test\")}\n\t<-msgChannel\n\n\tproducer, err := NewAsyncProducer(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\trequire.NoError(t, err)\n\tdefer producer.Close()\n\n\tproducerSecond, err := NewAsyncProducer(FunctionalTestEnv.KafkaBrokerAddrs, configSecond)\n\trequire.NoError(t, err)\n\tdefer producerSecond.Close()\n\n\terr = producer.BeginTxn()\n\trequire.NoError(t, err)\n\n\tfor i := 0; i < 2; i++ {\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"test.1\", Key: nil, Value: StringEncoder(\"test-committed\")}\n\t}\n\n\terr = producerSecond.BeginTxn()\n\trequire.NoError(t, err)\n\n\tfor i := 0; i < 2; i++ {\n\t\tproducerSecond.Input() <- &ProducerMessage{Topic: \"test.1\", Key: nil, Value: StringEncoder(\"test-aborted\")}\n\t}\n\n\terr = producer.CommitTxn()\n\trequire.NoError(t, err)\n\n\terr = producerSecond.AbortTxn()\n\trequire.NoError(t, err)\n\n\tfor i := 0; i < 2; i++ {\n\t\tmsg := <-msgChannel\n\t\tt.Logf(\"Received %s from %s-%d at offset %d\", msg.Value, msg.Topic, msg.Partition, msg.Offset)\n\t\trequire.Equal(t, \"test-committed\", string(msg.Value))\n\t}\n}\n\nfunc TestFuncTxnAbortedProduce(t *testing.T) {\n\tcheckKafkaVersion(t, \"0.11.0.0\")\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tconfig := NewFunctionalTestConfig()\n\tconfig.ChannelBufferSize = 20\n\tconfig.Producer.Flush.Frequency = 50 * time.Millisecond\n\tconfig.Producer.Flush.Messages = 200\n\tconfig.Producer.Idempotent = true\n\tconfig.Producer.Transaction.ID = \"TestFuncTxnAbortedProduce\"\n\tconfig.Producer.RequiredAcks = WaitForAll\n\tconfig.Producer.Return.Successes = true\n\tconfig.Producer.Transaction.Retry.Max = 200\n\tconfig.Consumer.IsolationLevel = ReadCommitted\n\tconfig.Net.MaxOpenRequests = 1\n\n\tclient, err := NewClient(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\trequire.NoError(t, err)\n\tdefer client.Close()\n\n\tconsumer, err := NewConsumerFromClient(client)\n\trequire.NoError(t, err)\n\tdefer consumer.Close()\n\n\tpc, err := consumer.ConsumePartition(\"test.1\", 0, OffsetNewest)\n\trequire.NoError(t, err)\n\tmsgChannel := pc.Messages()\n\tdefer pc.Close()\n\n\tnonTransactionalConfig := NewFunctionalTestConfig()\n\tnonTransactionalConfig.Producer.Return.Successes = true\n\tnonTransactionalConfig.Producer.Return.Errors = true\n\n\tnonTransactionalProducer, err := NewAsyncProducer(FunctionalTestEnv.KafkaBrokerAddrs, nonTransactionalConfig)\n\trequire.NoError(t, err)\n\tdefer nonTransactionalProducer.Close()\n\n\t// Ensure consumer is started\n\tnonTransactionalProducer.Input() <- &ProducerMessage{Topic: \"test.1\", Key: nil, Value: StringEncoder(\"test\")}\n\t<-msgChannel\n\n\tproducer, err := NewAsyncProducerFromClient(client)\n\trequire.NoError(t, err)\n\tdefer producer.Close()\n\n\terr = producer.BeginTxn()\n\trequire.NoError(t, err)\n\n\tfor i := 0; i < 2; i++ {\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"test.1\", Key: nil, Value: StringEncoder(\"transactional\")}\n\t}\n\n\tfor i := 0; i < 2; i++ {\n\t\t<-producer.Successes()\n\t}\n\n\terr = producer.AbortTxn()\n\trequire.NoError(t, err)\n\n\tfor i := 0; i < 2; i++ {\n\t\tnonTransactionalProducer.Input() <- &ProducerMessage{Topic: \"test.1\", Key: nil, Value: StringEncoder(\"non-transactional\")}\n\t\t<-nonTransactionalProducer.Successes()\n\t}\n\n\tfor i := 0; i < 2; i++ {\n\t\tmsg := <-msgChannel\n\t\tt.Logf(\"Received %s from %s-%d at offset %d\", msg.Value, msg.Topic, msg.Partition, msg.Offset)\n\t\trequire.Equal(t, \"non-transactional\", string(msg.Value))\n\t}\n}\n\nfunc TestFuncProducingToInvalidTopic(t *testing.T) {\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tconfig := NewFunctionalTestConfig()\n\tconfig.Producer.Return.Successes = true\n\tproducer, err := NewSyncProducer(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif _, _, err := producer.SendMessage(&ProducerMessage{Topic: \"in/valid\"}); !errors.Is(err, ErrUnknownTopicOrPartition) && !errors.Is(err, ErrInvalidTopic) {\n\t\tt.Error(\"Expected ErrUnknownTopicOrPartition, found\", err)\n\t}\n\n\tsafeClose(t, producer)\n}\n\nfunc TestFuncProducingIdempotentWithBrokerFailure(t *testing.T) {\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tconfig := NewFunctionalTestConfig()\n\tconfig.Producer.Flush.Frequency = 250 * time.Millisecond\n\tconfig.Producer.Idempotent = true\n\tconfig.Producer.Timeout = 500 * time.Millisecond\n\tconfig.Producer.Retry.Max = 1\n\tconfig.Producer.Retry.Backoff = 500 * time.Millisecond\n\tconfig.Producer.Return.Successes = true\n\tconfig.Producer.Return.Errors = true\n\tconfig.Producer.RequiredAcks = WaitForAll\n\tconfig.Net.MaxOpenRequests = 1\n\n\tproducer, err := NewSyncProducer(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer safeClose(t, producer)\n\n\t// Successfully publish a few messages\n\tfor i := 0; i < 10; i++ {\n\t\t_, _, err = producer.SendMessage(&ProducerMessage{\n\t\t\tTopic: \"test.1\",\n\t\t\tValue: StringEncoder(fmt.Sprintf(\"%d message\", i)),\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n\n\t// break the brokers.\n\tfor proxyName, proxy := range FunctionalTestEnv.Proxies {\n\t\tif !strings.Contains(proxyName, \"kafka\") {\n\t\t\tcontinue\n\t\t}\n\t\tif err := proxy.Disable(); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n\n\t// This should fail hard now\n\tfor i := 10; i < 20; i++ {\n\t\t_, _, err = producer.SendMessage(&ProducerMessage{\n\t\t\tTopic: \"test.1\",\n\t\t\tValue: StringEncoder(fmt.Sprintf(\"%d message\", i)),\n\t\t})\n\t\tif err == nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n\n\t// Now bring the proxy back up\n\tfor proxyName, proxy := range FunctionalTestEnv.Proxies {\n\t\tif !strings.Contains(proxyName, \"kafka\") {\n\t\t\tcontinue\n\t\t}\n\t\tif err := proxy.Enable(); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n\n\t// We should be able to publish again (once everything calms down)\n\t// (otherwise it times out)\n\tfor {\n\t\t_, _, err = producer.SendMessage(&ProducerMessage{\n\t\t\tTopic: \"test.1\",\n\t\t\tValue: StringEncoder(\"comeback message\"),\n\t\t})\n\t\tif err == nil {\n\t\t\tbreak\n\t\t}\n\t}\n}\n\nfunc TestFuncIdempotentBufferedSequence(t *testing.T) {\n\tcheckKafkaVersion(t, \"0.11.0.0\")\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tconst (\n\t\ttopic           = \"test.1\"\n\t\tpartition int32 = 0\n\t)\n\n\tcfg := NewFunctionalTestConfig()\n\tcfg.Net.MaxOpenRequests = 1\n\tcfg.Producer.Idempotent = true\n\tcfg.Producer.RequiredAcks = WaitForAll\n\tcfg.Producer.Return.Successes = true\n\tcfg.Producer.Return.Errors = true\n\tcfg.Producer.Retry.Max = 64\n\tcfg.Producer.Retry.Backoff = 250 * time.Millisecond\n\n\tstart := time.Now()\n\n\tproducer, err := NewAsyncProducer(FunctionalTestEnv.KafkaBrokerAddrs, cfg)\n\trequire.NoError(t, err)\n\tdefer producer.Close()\n\n\tasyncProd, ok := producer.(*asyncProducer)\n\trequire.True(t, ok)\n\n\twaitForMessages := func(count int) {\n\t\ttimeout := time.After(2 * time.Minute)\n\t\tfor count > 0 {\n\t\t\tselect {\n\t\t\tcase <-timeout:\n\t\t\t\tt.Fatalf(\"timed out waiting for %d messages\", count)\n\t\t\tcase perr := <-producer.Errors():\n\t\t\t\tif perr != nil {\n\t\t\t\t\tt.Logf(\"producer error: %v\", perr.Err)\n\t\t\t\t}\n\t\t\t\tcount--\n\t\t\tcase <-producer.Successes():\n\t\t\t\tcount--\n\t\t\t}\n\t\t}\n\t}\n\n\tfor i := 0; i < 5; i++ {\n\t\tproducer.Input() <- &ProducerMessage{\n\t\t\tTopic:     topic,\n\t\t\tPartition: partition,\n\t\t\tValue:     StringEncoder(fmt.Sprintf(\"warmup-%d\", i)),\n\t\t}\n\t}\n\twaitForMessages(5)\n\n\tleader, err := asyncProd.client.Leader(topic, partition)\n\trequire.NoError(t, err)\n\n\tbp := asyncProd.getBrokerProducer(leader)\n\tdefer asyncProd.unrefBrokerProducer(leader, bp)\n\n\tasyncProd.inFlight.Add(1)\n\tpp := &partitionProducer{\n\t\tparent:         asyncProd,\n\t\ttopic:          topic,\n\t\tpartition:      partition,\n\t\tbrokerProducer: bp,\n\t\tleader:         leader,\n\t\tretryState:     make([]partitionRetryState, asyncProd.conf.Producer.Retry.Max+1),\n\t\thighWatermark:  1,\n\t}\n\tpp.retryState[0].buf = []*ProducerMessage{{\n\t\tTopic:     topic,\n\t\tPartition: partition,\n\t\tValue:     StringEncoder(\"buffered\"),\n\t}}\n\tpp.flushRetryBuffers()\n\n\twaitForMessages(1)\n\n\tproducer.Input() <- &ProducerMessage{\n\t\tTopic:     topic,\n\t\tPartition: partition,\n\t\tValue:     StringEncoder(\"post-buffer\"),\n\t}\n\twaitForMessages(1)\n\n\tlogSince := start.UTC().Format(time.RFC3339)\n\tcmd := exec.Command(\n\t\t\"docker\",\n\t\t\"compose\",\n\t\t\"logs\",\n\t\t\"--since\",\n\t\tlogSince,\n\t\tfmt.Sprintf(\"kafka-%d\", leader.ID()),\n\t)\n\tcmd.Env = os.Environ()\n\tout, err := cmd.CombinedOutput()\n\trequire.NoErrorf(t, err, \"failed to read broker logs: %s\", out)\n\n\tlogs := string(out)\n\tt.Logf(\"kafka-%d logs since %s:\\n%s\", leader.ID(), logSince, logs)\n\trequire.NotContains(t, logs, \"OutOfOrderSequenceException\", \"leader logs contained out-of-order sequence errors:\\n%s\", logs)\n}\n\nfunc TestInterceptors(t *testing.T) {\n\tconfig := NewFunctionalTestConfig()\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tconfig.Producer.Return.Successes = true\n\tconfig.Consumer.Return.Errors = true\n\tconfig.Producer.Interceptors = []ProducerInterceptor{&appendInterceptor{i: 0}, &appendInterceptor{i: 100}}\n\tconfig.Consumer.Interceptors = []ConsumerInterceptor{&appendInterceptor{i: 20}}\n\n\tclient, err := NewClient(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer safeClose(t, client)\n\n\tinitialOffset, err := client.GetOffset(\"test.1\", 0, OffsetNewest)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tproducer, err := NewAsyncProducerFromClient(client)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor i := 0; i < 10; i++ {\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"test.1\", Key: nil, Value: StringEncoder(TestMessage)}\n\t}\n\n\tfor i := 0; i < 10; i++ {\n\t\tselect {\n\t\tcase msg := <-producer.Errors():\n\t\t\tt.Error(msg.Err)\n\t\tcase msg := <-producer.Successes():\n\t\t\tv, _ := msg.Value.Encode()\n\t\t\texpected := TestMessage + strconv.Itoa(i) + strconv.Itoa(i+100)\n\t\t\tif string(v) != expected {\n\t\t\t\tt.Errorf(\"Interceptor should have incremented the value, got %s, expected %s\", v, expected)\n\t\t\t}\n\t\t}\n\t}\n\tsafeClose(t, producer)\n\n\tmaster, err := NewConsumerFromClient(client)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tconsumer, err := master.ConsumePartition(\"test.1\", 0, initialOffset)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor i := 0; i < 10; i++ {\n\t\tselect {\n\t\tcase <-time.After(10 * time.Second):\n\t\t\tt.Fatal(\"Not received any more events in the last 10 seconds.\")\n\t\tcase err := <-consumer.Errors():\n\t\t\tt.Error(err)\n\t\tcase msg := <-consumer.Messages():\n\t\t\tprodInteExpectation := strconv.Itoa(i) + strconv.Itoa(i+100)\n\t\t\tconsInteExpectation := strconv.Itoa(i + 20)\n\t\t\texpected := TestMessage + prodInteExpectation + consInteExpectation\n\t\t\tv := string(msg.Value)\n\t\t\tif v != expected {\n\t\t\t\tt.Errorf(\"Interceptor should have incremented the value, got %s, expected %s\", v, expected)\n\t\t\t}\n\t\t}\n\t}\n\tsafeClose(t, consumer)\n}\n\nfunc testProducingMessages(t *testing.T, config *Config, minVersion KafkaVersion) {\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\t// Configure some latency in order to properly validate the request latency metric\n\tfor _, proxy := range FunctionalTestEnv.Proxies {\n\t\tif _, err := proxy.AddToxic(\"\", \"latency\", \"\", 1, toxiproxy.Attributes{\"latency\": 10}); err != nil {\n\t\t\tt.Fatal(\"Unable to configure latency toxicity\", err)\n\t\t}\n\t}\n\n\tconfig.Producer.Return.Successes = true\n\tconfig.Consumer.Return.Errors = true\n\n\tkafkaVersions := map[KafkaVersion]bool{}\n\tupper, err := ParseKafkaVersion(os.Getenv(\"KAFKA_VERSION\"))\n\tif err != nil {\n\t\tt.Logf(\"warning: failed to parse kafka version: %v\", err)\n\t}\n\tif upper.IsAtLeast(minVersion) {\n\t\tkafkaVersions[upper] = true\n\t\t// KIP-896 dictates a minimum lower bound of 2.1 protocol for Kafka 4.0 onwards\n\t\tif upper.IsAtLeast(V4_0_0_0) {\n\t\t\tif !minVersion.IsAtLeast(V2_1_0_0) {\n\t\t\t\tminVersion = V2_1_0_0\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, v := range []KafkaVersion{MinVersion, V0_10_0_0, V0_11_0_0, V1_0_0_0, V2_0_0_0, V2_1_0_0} {\n\t\tif v.IsAtLeast(minVersion) && upper.IsAtLeast(v) {\n\t\t\tkafkaVersions[v] = true\n\t\t}\n\t}\n\n\tfor version := range kafkaVersions {\n\t\tname := t.Name() + \"-v\" + version.String()\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\t// Clone the config to avoid data races between subtests\n\t\t\t// when background goroutines from a closing client still\n\t\t\t// reference the shared config object.\n\t\t\tcfg := *config\n\t\t\tcfg.ClientID = name\n\t\t\tcfg.MetricRegistry = metrics.NewRegistry()\n\t\t\tcheckKafkaVersion(t, version.String())\n\t\t\tcfg.Version = version\n\n\t\t\tproducerClient, err := NewClient(FunctionalTestEnv.KafkaBrokerAddrs, &cfg)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tdefer safeClose(t, producerClient)\n\n\t\t\t// Keep in mind the current offset (retry on transient leader election errors)\n\t\t\tvar initialOffset int64\n\t\t\tfor i := 0; i < 10; i++ {\n\t\t\t\tinitialOffset, err = producerClient.GetOffset(\"test.1\", 0, OffsetNewest)\n\t\t\t\tif err == nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tif errors.Is(err, ErrLeaderNotAvailable) || errors.Is(err, ErrOffsetNotAvailable) {\n\t\t\t\t\ttime.Sleep(100 * time.Millisecond)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"GetOffset failed after retries: %v\", err)\n\t\t\t}\n\n\t\t\tproducer, err := NewAsyncProducerFromClient(producerClient)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tdefer safeClose(t, producer)\n\n\t\t\texpectedResponses := TestBatchSize\n\t\t\tfor i := 1; i <= TestBatchSize; {\n\t\t\t\tmsg := &ProducerMessage{Topic: \"test.1\", Key: nil, Value: StringEncoder(fmt.Sprintf(\"testing %d\", i))}\n\t\t\t\tselect {\n\t\t\t\tcase producer.Input() <- msg:\n\t\t\t\t\ti++\n\t\t\t\tcase ret := <-producer.Errors():\n\t\t\t\t\tt.Fatal(ret.Err)\n\t\t\t\tcase <-producer.Successes():\n\t\t\t\t\texpectedResponses--\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor expectedResponses > 0 {\n\t\t\t\tselect {\n\t\t\t\tcase ret := <-producer.Errors():\n\t\t\t\t\tt.Fatal(ret.Err)\n\t\t\t\tcase <-producer.Successes():\n\t\t\t\t\texpectedResponses--\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Validate producer metrics before using the consumer minus the offset request\n\t\t\tvalidateProducerMetrics(t, producerClient)\n\n\t\t\tconsumerClient, err := NewClient(FunctionalTestEnv.KafkaBrokerAddrs, &cfg)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tdefer safeClose(t, consumerClient)\n\t\t\tconsumer, err := NewConsumerFromClient(consumerClient)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tdefer safeClose(t, consumer)\n\t\t\tpartitionConsumer, err := consumer.ConsumePartition(\"test.1\", 0, initialOffset)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tdefer safeClose(t, partitionConsumer)\n\n\t\t\tfor i := 1; i <= TestBatchSize; i++ {\n\t\t\t\tselect {\n\t\t\t\tcase <-time.After(10 * time.Second):\n\t\t\t\t\tt.Fatal(\"Not received any more events in the last 10 seconds.\")\n\n\t\t\t\tcase err := <-partitionConsumer.Errors():\n\t\t\t\t\tt.Error(err)\n\n\t\t\t\tcase message := <-partitionConsumer.Messages():\n\t\t\t\t\tif string(message.Value) != fmt.Sprintf(\"testing %d\", i) {\n\t\t\t\t\t\tt.Fatalf(\"Unexpected message with index %d: %s\", i, message.Value)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvalidateConsumerMetrics(t, consumerClient)\n\t\t})\n\t}\n}\n\n// TestAsyncProducerRemoteBrokerClosed ensures that the async producer can\n// cleanly recover if network connectivity to the remote brokers is lost and\n// then subsequently resumed.\n//\n// https://github.com/IBM/sarama/issues/2129\nfunc TestAsyncProducerRemoteBrokerClosed(t *testing.T) {\n\tsetupFunctionalTest(t)\n\tdefer teardownFunctionalTest(t)\n\n\tconfig := NewFunctionalTestConfig()\n\tconfig.ClientID = t.Name()\n\tconfig.Net.MaxOpenRequests = 1\n\tconfig.Producer.Flush.MaxMessages = 1\n\tconfig.Producer.Return.Successes = true\n\tconfig.Producer.Retry.Max = 1024\n\tconfig.Producer.Retry.Backoff = time.Millisecond * 50\n\n\tproducer, err := NewAsyncProducer(\n\t\tFunctionalTestEnv.KafkaBrokerAddrs,\n\t\tconfig,\n\t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// produce some more messages and ensure success\n\tfor i := 0; i < 10; i++ {\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"test.1\", Key: nil, Value: StringEncoder(TestMessage)}\n\t\t<-producer.Successes()\n\t}\n\n\t// shutdown all the active tcp connections\n\tfor _, proxy := range FunctionalTestEnv.Proxies {\n\t\t_ = proxy.Disable()\n\t}\n\n\t// produce some more messages\n\tfor i := 10; i < 20; i++ {\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"test.1\", Key: nil, Value: StringEncoder(TestMessage)}\n\t}\n\n\t// re-open the proxies\n\tfor _, proxy := range FunctionalTestEnv.Proxies {\n\t\t_ = proxy.Enable()\n\t}\n\n\t// ensure the previously produced messages succeed\n\tfor i := 10; i < 20; i++ {\n\t\t<-producer.Successes()\n\t}\n\n\t// produce some more messages and ensure success\n\tfor i := 20; i < 30; i++ {\n\t\tproducer.Input() <- &ProducerMessage{Topic: \"test.1\", Key: nil, Value: StringEncoder(TestMessage)}\n\t\t<-producer.Successes()\n\t}\n\n\tcloseProducer(t, producer)\n}\n\nfunc validateProducerMetrics(t *testing.T, client Client) {\n\t// Get the broker used by test1 topic\n\tvar broker *Broker\n\tif partitions, err := client.Partitions(\"test.1\"); err != nil {\n\t\tt.Error(err)\n\t} else {\n\t\tfor _, partition := range partitions {\n\t\t\tif b, err := client.Leader(\"test.1\", partition); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t} else {\n\t\t\t\tif broker != nil && b != broker {\n\t\t\t\t\tt.Fatal(\"Expected only one broker, got at least 2\")\n\t\t\t\t}\n\t\t\t\tbroker = b\n\t\t\t}\n\t\t}\n\t}\n\n\tmetricValidators := newMetricValidators()\n\tnoResponse := client.Config().Producer.RequiredAcks == NoResponse\n\tcompressionEnabled := client.Config().Producer.Compression != CompressionNone\n\n\t// We are adding 10ms of latency to all requests with toxiproxy\n\tminRequestLatencyInMs := 10\n\tif noResponse {\n\t\t// but when we do not wait for a response it can be less than 1ms\n\t\tminRequestLatencyInMs = 0\n\t}\n\n\t// We read at least 1 byte from the broker\n\tmetricValidators.registerForAllBrokers(broker, minCountMeterValidator(\"incoming-byte-rate\", 1))\n\t// in at least 3 global requests (1 for metadata request, 1 for offset request and N for produce request)\n\tmetricValidators.register(minCountMeterValidator(\"request-rate\", 3))\n\tmetricValidators.register(minCountHistogramValidator(\"request-size\", 3))\n\tmetricValidators.register(minValHistogramValidator(\"request-size\", 1))\n\t// and at least 2 requests to the registered broker (offset + produces)\n\tmetricValidators.registerForBroker(broker, minCountMeterValidator(\"request-rate\", 2))\n\tmetricValidators.registerForBroker(broker, minCountHistogramValidator(\"request-size\", 2))\n\tmetricValidators.registerForBroker(broker, minValHistogramValidator(\"request-size\", 1))\n\tmetricValidators.registerForBroker(broker, minValHistogramValidator(\"request-latency-in-ms\", minRequestLatencyInMs))\n\n\t// We send at least 1 batch\n\tmetricValidators.registerForGlobalAndTopic(\"test_1\", minCountHistogramValidator(\"batch-size\", 1))\n\tmetricValidators.registerForGlobalAndTopic(\"test_1\", minValHistogramValidator(\"batch-size\", 1))\n\tif compressionEnabled {\n\t\t// We record compression ratios between [0.50,-10.00] (50-1000 with a histogram) for at least one \"fake\" record\n\t\tmetricValidators.registerForGlobalAndTopic(\"test_1\", minCountHistogramValidator(\"compression-ratio\", 1))\n\t\tif client.Config().Version.IsAtLeast(V0_11_0_0) {\n\t\t\t// slightly better compression with batching\n\t\t\tmetricValidators.registerForGlobalAndTopic(\"test_1\", minValHistogramValidator(\"compression-ratio\", 30))\n\t\t} else {\n\t\t\tmetricValidators.registerForGlobalAndTopic(\"test_1\", minValHistogramValidator(\"compression-ratio\", 50))\n\t\t}\n\t\tmetricValidators.registerForGlobalAndTopic(\"test_1\", maxValHistogramValidator(\"compression-ratio\", 1000))\n\t} else {\n\t\t// We record compression ratios of 1.00 (100 with a histogram) for every TestBatchSize record\n\t\tif client.Config().Version.IsAtLeast(V0_11_0_0) {\n\t\t\t// records will be grouped in batchSet rather than msgSet\n\t\t\tmetricValidators.registerForGlobalAndTopic(\"test_1\", minCountHistogramValidator(\"compression-ratio\", 3))\n\t\t} else {\n\t\t\tmetricValidators.registerForGlobalAndTopic(\"test_1\", countHistogramValidator(\"compression-ratio\", TestBatchSize))\n\t\t}\n\t\tmetricValidators.registerForGlobalAndTopic(\"test_1\", minValHistogramValidator(\"compression-ratio\", 100))\n\t\tmetricValidators.registerForGlobalAndTopic(\"test_1\", maxValHistogramValidator(\"compression-ratio\", 100))\n\t}\n\n\t// We send exactly TestBatchSize messages\n\tmetricValidators.registerForGlobalAndTopic(\"test_1\", countMeterValidator(\"record-send-rate\", TestBatchSize))\n\t// We send at least one record per request\n\tmetricValidators.registerForGlobalAndTopic(\"test_1\", minCountHistogramValidator(\"records-per-request\", 1))\n\tmetricValidators.registerForGlobalAndTopic(\"test_1\", minValHistogramValidator(\"records-per-request\", 1))\n\n\t// We receive at least 1 byte from the broker\n\tmetricValidators.registerForAllBrokers(broker, minCountMeterValidator(\"outgoing-byte-rate\", 1))\n\tif noResponse {\n\t\t// in exactly 2 global responses (metadata + offset)\n\t\tmetricValidators.register(countMeterValidator(\"response-rate\", 2))\n\t\tmetricValidators.register(minCountHistogramValidator(\"response-size\", 2))\n\t\t// and exactly 1 offset response for the registered broker\n\t\tmetricValidators.registerForBroker(broker, countMeterValidator(\"response-rate\", 1))\n\t\tmetricValidators.registerForBroker(broker, minCountHistogramValidator(\"response-size\", 1))\n\t\tmetricValidators.registerForBroker(broker, minValHistogramValidator(\"response-size\", 1))\n\t} else {\n\t\t// in at least 3 global responses (metadata + offset + produces)\n\t\tmetricValidators.register(minCountMeterValidator(\"response-rate\", 3))\n\t\tmetricValidators.register(minCountHistogramValidator(\"response-size\", 3))\n\t\t// and at least 2 for the registered broker\n\t\tmetricValidators.registerForBroker(broker, minCountMeterValidator(\"response-rate\", 2))\n\t\tmetricValidators.registerForBroker(broker, minCountHistogramValidator(\"response-size\", 2))\n\t\tmetricValidators.registerForBroker(broker, minValHistogramValidator(\"response-size\", 1))\n\t}\n\n\t// There should be no requests in flight anymore\n\tmetricValidators.registerForAllBrokers(broker, counterValidator(\"requests-in-flight\", 0))\n\n\t// Run the validators\n\tmetricValidators.run(t, client.Config().MetricRegistry)\n}\n\nfunc validateConsumerMetrics(t *testing.T, client Client) {\n\t// Get the broker used by test1 topic\n\tvar broker *Broker\n\tif partitions, err := client.Partitions(\"test.1\"); err != nil {\n\t\tt.Error(err)\n\t} else {\n\t\tfor _, partition := range partitions {\n\t\t\tif b, err := client.Leader(\"test.1\", partition); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t} else {\n\t\t\t\tif broker != nil && b != broker {\n\t\t\t\t\tt.Fatal(\"Expected only one broker, got at least 2\")\n\t\t\t\t}\n\t\t\t\tbroker = b\n\t\t\t}\n\t\t}\n\t}\n\n\tmetricValidators := newMetricValidators()\n\n\t// at least 1 global fetch request for the given topic\n\tmetricValidators.registerForGlobalAndTopic(\"test_1\", minCountMeterValidator(\"consumer-fetch-rate\", 1))\n\n\t// and at least 1 fetch request to the lead broker\n\tmetricValidators.registerForBroker(broker, minCountMeterValidator(\"consumer-fetch-rate\", 1))\n\n\t// Run the validators\n\tmetricValidators.run(t, client.Config().MetricRegistry)\n}\n\n// Benchmarks\n\nfunc BenchmarkProducerSmall(b *testing.B) {\n\tbenchmarkProducer(b, nil, \"test.64\", ByteEncoder(make([]byte, 128)))\n}\n\nfunc BenchmarkProducerMedium(b *testing.B) {\n\tbenchmarkProducer(b, nil, \"test.64\", ByteEncoder(make([]byte, 1024)))\n}\n\nfunc BenchmarkProducerLarge(b *testing.B) {\n\tbenchmarkProducer(b, nil, \"test.64\", ByteEncoder(make([]byte, 8192)))\n}\n\nfunc BenchmarkProducerSmallSinglePartition(b *testing.B) {\n\tbenchmarkProducer(b, nil, \"test.1\", ByteEncoder(make([]byte, 128)))\n}\n\nfunc BenchmarkProducerMediumSnappy(b *testing.B) {\n\tconf := NewFunctionalTestConfig()\n\tconf.Producer.Compression = CompressionSnappy\n\tbenchmarkProducer(b, conf, \"test.1\", ByteEncoder(make([]byte, 1024)))\n}\n\nfunc benchmarkProducer(b *testing.B, conf *Config, topic string, value Encoder) {\n\tsetupFunctionalTest(b)\n\tdefer teardownFunctionalTest(b)\n\n\tmetricsDisable := os.Getenv(\"METRICS_DISABLE\")\n\tif metricsDisable != \"\" {\n\t\tpreviousUseNilMetrics := metrics.UseNilMetrics\n\t\tLogger.Println(\"Disabling metrics using no-op implementation\")\n\t\tmetrics.UseNilMetrics = true\n\t\t// Restore previous setting\n\t\tdefer func() {\n\t\t\tmetrics.UseNilMetrics = previousUseNilMetrics\n\t\t}()\n\t}\n\n\tproducer, err := NewAsyncProducer(FunctionalTestEnv.KafkaBrokerAddrs, conf)\n\tif err != nil {\n\t\tb.Fatal(err)\n\t}\n\n\tb.ResetTimer()\n\n\tfor i := 1; i <= b.N; {\n\t\tmsg := &ProducerMessage{Topic: topic, Key: StringEncoder(fmt.Sprintf(\"%d\", i)), Value: value}\n\t\tselect {\n\t\tcase producer.Input() <- msg:\n\t\t\ti++\n\t\tcase ret := <-producer.Errors():\n\t\t\tb.Fatal(ret.Err)\n\t\t}\n\t}\n\tsafeClose(b, producer)\n}\n"
  },
  {
    "path": "functional_test.go",
    "content": "//go:build functional\n\npackage sarama\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log\"\n\t\"net\"\n\t\"net/url\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/IBM/sarama/internal/toxiproxy\"\n)\n\nconst uncommittedTopic = \"uncommitted-topic-test-4\"\n\nvar (\n\ttestTopicDetails = map[string]*TopicDetail{\n\t\t\"test.1\": {\n\t\t\tNumPartitions:     1,\n\t\t\tReplicationFactor: 3,\n\t\t},\n\t\t\"test.4\": {\n\t\t\tNumPartitions:     4,\n\t\t\tReplicationFactor: 3,\n\t\t},\n\t\t\"test.64\": {\n\t\t\tNumPartitions:     64,\n\t\t\tReplicationFactor: 3,\n\t\t},\n\t\tuncommittedTopic: {\n\t\t\tNumPartitions:     1,\n\t\t\tReplicationFactor: 3,\n\t\t},\n\t\t\"test.1_to_2\": {\n\t\t\tNumPartitions:     1,\n\t\t\tReplicationFactor: 3,\n\t\t},\n\t}\n\n\tFunctionalTestEnv *testEnvironment\n)\n\nfunc TestMain(m *testing.M) {\n\t// Functional tests for Sarama\n\t//\n\t// You can either set TOXIPROXY_ADDR, which points at a toxiproxy address\n\t// already set up with 21801-21805 bound to zookeeper and 29091-29095\n\t// bound to kafka. Alternatively, if TOXIPROXY_ADDR is not set, we'll try\n\t// and use Docker to bring up a 5-node zookeeper cluster & 5-node kafka\n\t// cluster, with toxiproxy configured as above.\n\t//\n\t// In either case, the following topics will be deleted (if they exist) and\n\t// then created/pre-seeded with data for the functional test run:\n\t//     * uncommitted-topic-test-4\n\t//     * test.1\n\t//     * test.4\n\t//     * test.64\n\tos.Exit(testMain(m))\n}\n\nfunc testMain(m *testing.M) int {\n\tctx := context.Background()\n\tvar env testEnvironment\n\n\tif os.Getenv(\"DEBUG\") == \"true\" {\n\t\tLogger = log.New(os.Stderr, \"[DEBUG] \", log.Lmicroseconds|log.Ltime)\n\t}\n\n\tusingExisting, err := existingEnvironment(ctx, &env)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tif !usingExisting {\n\t\terr := prepareDockerTestEnvironment(ctx, &env)\n\t\tif err != nil {\n\t\t\t_ = tearDownDockerTestEnvironment(ctx, &env)\n\t\t\tpanic(err)\n\t\t}\n\t\tdefer tearDownDockerTestEnvironment(ctx, &env) // nolint:errcheck\n\t}\n\tif err := prepareTestTopics(ctx, &env); err != nil {\n\t\tpanic(err)\n\t}\n\tFunctionalTestEnv = &env\n\treturn m.Run()\n}\n\n// NewFunctionalTestConfig returns a config meant to be used by functional tests.\nfunc NewFunctionalTestConfig() *Config {\n\tconfig := NewConfig()\n\t// config.Consumer.Retry.Backoff = 0\n\t// config.Producer.Retry.Backoff = 0\n\n\t// Always use the maximum Sarama-supported API versions.\n\tconfig.Version = MaxVersion\n\t// Enable API versions negotiation with brokers. This will reduce the maximum\n\t// API versions Sarama uses to never exceed the broker's supported versions.\n\tconfig.ApiVersionsRequest = true\n\n\treturn config\n}\n\ntype testEnvironment struct {\n\tToxiproxyClient  *toxiproxy.Client\n\tProxies          map[string]*toxiproxy.Proxy\n\tKafkaBrokerAddrs []string\n\tKafkaVersion     string\n}\n\n// setupToxiProxies will configure the toxiproxy proxies with routes for the\n// kafka brokers if they don't already exist\nfunc setupToxiProxies(env *testEnvironment, endpoint string) error {\n\tenv.ToxiproxyClient = toxiproxy.NewClient(endpoint)\n\tenv.Proxies = map[string]*toxiproxy.Proxy{}\n\tenv.KafkaBrokerAddrs = nil\n\tfor i := 1; i <= 5; i++ {\n\t\tproxyName := fmt.Sprintf(\"kafka%d\", i)\n\t\tproxy, err := env.ToxiproxyClient.Proxy(proxyName)\n\t\tif err != nil {\n\t\t\tproxy, err = env.ToxiproxyClient.CreateProxy(\n\t\t\t\tproxyName,\n\t\t\t\tfmt.Sprintf(\"0.0.0.0:%d\", 29090+i),\n\t\t\t\tfmt.Sprintf(\"kafka-%d:%d\", i, 29090+i),\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to create toxiproxy: %w\", err)\n\t\t\t}\n\t\t}\n\t\tenv.Proxies[proxyName] = proxy\n\t\tenv.KafkaBrokerAddrs = append(env.KafkaBrokerAddrs, fmt.Sprintf(\"127.0.0.1:%d\", 29090+i))\n\t}\n\treturn nil\n}\n\nfunc prepareDockerTestEnvironment(ctx context.Context, env *testEnvironment) error {\n\tconst expectedBrokers = 5\n\n\tLogger.Println(\"bringing up docker-based test environment\")\n\n\t// Always (try to) tear down first.\n\tif err := tearDownDockerTestEnvironment(ctx, env); err != nil {\n\t\treturn fmt.Errorf(\"failed to tear down existing env: %w\", err)\n\t}\n\n\tif version, ok := os.LookupEnv(\"KAFKA_VERSION\"); ok {\n\t\tenv.KafkaVersion = version\n\t} else {\n\t\tenv.KafkaVersion = \"3.5.1\"\n\t}\n\t// docker compose v2.17.0 or newer required for `--wait-timeout` support\n\targs := []string{\"compose\", \"up\", \"-d\", \"--quiet-pull\", \"--timestamps\", \"--wait\", \"--wait-timeout\", \"600\"}\n\tv, _ := ParseKafkaVersion(env.KafkaVersion)\n\t// use zookeeper for kafka < 4\n\tif !v.IsAtLeast(V4_0_0_0) {\n\t\targs = append([]string{\"compose\", \"--profile\", \"zookeeper\"}, args[1:]...)\n\t}\n\tc := exec.Command(\"docker\", args...)\n\tc.Stdout = os.Stdout\n\tc.Stderr = os.Stderr\n\tc.Env = append(os.Environ(), fmt.Sprintf(\"KAFKA_VERSION=%s\", env.KafkaVersion))\n\terr := c.Run()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to run docker compose to start test environment: %w\", err)\n\t}\n\n\tif err := setupToxiProxies(env, \"http://localhost:8474\"); err != nil {\n\t\treturn fmt.Errorf(\"failed to setup toxiproxies: %w\", err)\n\t}\n\n\tdialCheck := func(addr string, timeout time.Duration) error {\n\t\tconn, err := net.DialTimeout(\"tcp\", addr, timeout)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn conn.Close()\n\t}\n\n\tconfig := NewFunctionalTestConfig()\n\tconfig.Net.DialTimeout = 1 * time.Second\n\tconfig.Net.ReadTimeout = 1 * time.Second\n\tconfig.Net.WriteTimeout = 1 * time.Second\n\tconfig.ClientID = \"sarama-tests\"\n\n\t// wait for the kafka brokers to come up\n\tallBrokersUp := false\n\n\tLogger.Printf(\"waiting for kafka %s brokers to come up...\\n\", env.KafkaVersion)\n\ttime.Sleep(10 * time.Second)\n\nmainLoop:\n\tfor i := 0; i < 30 && !allBrokersUp; i++ {\n\t\tif i > 0 {\n\t\t\tLogger.Printf(\"still waiting for kafka %s brokers to come up...\\n\", env.KafkaVersion)\n\t\t}\n\t\ttime.Sleep(3 * time.Second)\n\t\tbrokersOk := make([]bool, len(env.KafkaBrokerAddrs))\n\n\t\t// first check that all bootstrap brokers are TCP accessible\n\t\tfor _, addr := range env.KafkaBrokerAddrs {\n\t\t\tif err := dialCheck(addr, time.Second); err != nil {\n\t\t\t\tcontinue mainLoop\n\t\t\t}\n\t\t}\n\n\t\t// now check we can bootstrap metadata from the cluster and all brokers\n\t\t// are known and accessible at their advertised address\n\tretryLoop:\n\t\tfor j, addr := range env.KafkaBrokerAddrs {\n\t\t\tclient, err := NewClient([]string{addr}, config)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\terr = client.RefreshMetadata()\n\t\t\tif err != nil {\n\t\t\t\tclient.Close()\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbrokers := client.Brokers()\n\t\t\tif len(brokers) < expectedBrokers {\n\t\t\t\tclient.Close()\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfor _, broker := range brokers {\n\t\t\t\terr := broker.Open(client.Config())\n\t\t\t\tif err != nil && !errors.Is(err, ErrAlreadyConnected) {\n\t\t\t\t\tclient.Close()\n\t\t\t\t\tcontinue retryLoop\n\t\t\t\t}\n\t\t\t\tconnected, err := broker.Connected()\n\t\t\t\tif err != nil || !connected {\n\t\t\t\t\tbroker.Close()\n\t\t\t\t\tclient.Close()\n\t\t\t\t\tcontinue retryLoop\n\t\t\t\t}\n\t\t\t}\n\t\t\tclient.Close()\n\t\t\tbrokersOk[j] = true\n\t\t}\n\n\t\tallBrokersUp = true\n\t\tfor _, u := range brokersOk {\n\t\t\tallBrokersUp = allBrokersUp && u\n\t\t}\n\t}\n\n\tif !allBrokersUp {\n\t\tc := exec.Command(\"docker\", \"compose\", \"logs\", \"-t\", \"kafka-1\", \"kafka-2\", \"kafka-3\", \"kafka-4\", \"kafka-5\")\n\t\tc.Stdout = os.Stdout\n\t\tc.Stderr = os.Stderr\n\t\t_ = c.Run()\n\t\treturn fmt.Errorf(\"timed out waiting for one or more broker to come up\")\n\t}\n\n\treturn nil\n}\n\nfunc existingEnvironment(ctx context.Context, env *testEnvironment) (bool, error) {\n\ttoxiproxyAddr, ok := os.LookupEnv(\"TOXIPROXY_ADDR\")\n\tif !ok {\n\t\treturn false, nil\n\t}\n\ttoxiproxyURL, err := url.Parse(toxiproxyAddr)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"$TOXIPROXY_ADDR not parseable as url\")\n\t}\n\tif err := setupToxiProxies(env, toxiproxyURL.String()); err != nil {\n\t\treturn false, fmt.Errorf(\"failed to setup toxiproxies: %w\", err)\n\t}\n\n\tenv.KafkaVersion, ok = os.LookupEnv(\"KAFKA_VERSION\")\n\tif !ok {\n\t\treturn false, fmt.Errorf(\"KAFKA_VERSION needs to be provided with TOXIPROXY_ADDR\")\n\t}\n\treturn true, nil\n}\n\nfunc tearDownDockerTestEnvironment(ctx context.Context, env *testEnvironment) error {\n\targs := []string{\"compose\", \"down\", \"--volumes\"}\n\tv, _ := ParseKafkaVersion(env.KafkaVersion)\n\t// use zookeeper profile for kafka < 4 to ensure zookeeper containers are stopped\n\tif !v.IsAtLeast(V4_0_0_0) {\n\t\targs = append([]string{\"compose\", \"--profile\", \"zookeeper\"}, args[1:]...)\n\t}\n\tc := exec.Command(\"docker\", args...)\n\tc.Stdout = os.Stdout\n\tc.Stderr = os.Stderr\n\tdownErr := c.Run()\n\n\targs = []string{\"compose\", \"rm\", \"-v\", \"--force\", \"--stop\"}\n\tif !v.IsAtLeast(V4_0_0_0) {\n\t\targs = append([]string{\"compose\", \"--profile\", \"zookeeper\"}, args[1:]...)\n\t}\n\tc = exec.Command(\"docker\", args...)\n\tc.Stdout = os.Stdout\n\tc.Stderr = os.Stderr\n\trmErr := c.Run()\n\tif downErr != nil {\n\t\treturn fmt.Errorf(\"failed to run docker compose to stop test environment: %w\", downErr)\n\t}\n\tif rmErr != nil {\n\t\treturn fmt.Errorf(\"failed to run docker compose to rm test environment: %w\", rmErr)\n\t}\n\treturn nil\n}\n\nfunc startDockerTestBroker(ctx context.Context, brokerID int32) error {\n\tservice := fmt.Sprintf(\"kafka-%d\", brokerID)\n\tc := exec.Command(\"docker\", \"compose\", \"start\", service)\n\tc.Stdout = os.Stdout\n\tc.Stderr = os.Stderr\n\tif err := c.Run(); err != nil {\n\t\treturn fmt.Errorf(\"failed to run docker compose to start test broker kafka-%d: %w\", brokerID, err)\n\t}\n\treturn nil\n}\n\nfunc stopDockerTestBroker(ctx context.Context, brokerID int32) error {\n\tservice := fmt.Sprintf(\"kafka-%d\", brokerID)\n\tc := exec.Command(\"docker\", \"compose\", \"stop\", service)\n\tc.Stdout = os.Stdout\n\tc.Stderr = os.Stderr\n\tif err := c.Run(); err != nil {\n\t\treturn fmt.Errorf(\"failed to run docker compose to stop test broker kafka-%d: %w\", brokerID, err)\n\t}\n\treturn nil\n}\n\nfunc prepareTestTopics(ctx context.Context, env *testEnvironment) error {\n\tLogger.Println(\"creating test topics\")\n\tvar testTopicNames []string\n\tfor topic := range testTopicDetails {\n\t\ttestTopicNames = append(testTopicNames, topic)\n\t}\n\n\tLogger.Println(\"Creating topics\")\n\tconfig := NewFunctionalTestConfig()\n\tconfig.Metadata.Retry.Max = 5\n\tconfig.Metadata.Retry.Backoff = 10 * time.Second\n\tconfig.ClientID = \"sarama-prepareTestTopics\"\n\n\tclient, err := NewClient(env.KafkaBrokerAddrs, config)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to connect to kafka: %w\", err)\n\t}\n\tdefer client.Close()\n\n\tcontroller, err := client.Controller()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to connect to kafka controller: %w\", err)\n\t}\n\tdefer controller.Close()\n\n\t// Start by deleting the test topics (if they already exist)\n\t{\n\t\trequest := NewDeleteTopicsRequest(config.Version, testTopicNames, time.Minute)\n\t\tdeleteRes, err := controller.DeleteTopics(request)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to delete test topics: %w\", err)\n\t\t}\n\t\tfor topic, topicErr := range deleteRes.TopicErrorCodes {\n\t\t\tif !isTopicNotExistsErrorOrOk(topicErr) {\n\t\t\t\treturn fmt.Errorf(\"failed to delete topic %s: %w\", topic, topicErr)\n\t\t\t}\n\t\t}\n\t}\n\n\t// wait for the topics to _actually_ be gone - the delete is not guaranteed to be processed\n\t// synchronously\n\t{\n\t\tvar topicsOk bool\n\t\trequest := NewMetadataRequest(config.Version, testTopicNames)\n\t\tfor i := 0; i < 60 && !topicsOk; i++ {\n\t\t\ttime.Sleep(1 * time.Second)\n\t\t\tmd, err := controller.GetMetadata(request)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to get metadata for test topics: %w\", err)\n\t\t\t}\n\n\t\t\tif len(md.Topics) == len(testTopicNames) {\n\t\t\t\ttopicsOk = true\n\t\t\t\tfor _, topicsMd := range md.Topics {\n\t\t\t\t\tif !isTopicNotExistsErrorOrOk(topicsMd.Err) {\n\t\t\t\t\t\ttopicsOk = false\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif !topicsOk {\n\t\t\treturn fmt.Errorf(\"timed out waiting for test topics to be gone\")\n\t\t}\n\t}\n\n\t// now create the topics empty\n\t{\n\t\trequest := NewCreateTopicsRequest(config.Version, testTopicDetails, time.Minute, false)\n\t\tcreateRes, err := controller.CreateTopics(request)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to create test topics: %w\", err)\n\t\t}\n\t\tfor topic, topicErr := range createRes.TopicErrors {\n\t\t\tif !isTopicExistsErrorOrOk(topicErr.Err) {\n\t\t\t\treturn fmt.Errorf(\"failed to create test topic %s: %w\", topic, topicErr)\n\t\t\t}\n\t\t}\n\t}\n\n\t// wait for the topics to _actually_ exist - the creates are not guaranteed to be processed\n\t// synchronously\n\t{\n\t\tvar topicsOk bool\n\t\trequest := NewMetadataRequest(config.Version, testTopicNames)\n\t\tfor i := 0; i < 60 && !topicsOk; i++ {\n\t\t\ttime.Sleep(1 * time.Second)\n\t\t\tmd, err := controller.GetMetadata(request)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to get metadata for test topics: %w\", err)\n\t\t\t}\n\n\t\t\tif len(md.Topics) == len(testTopicNames) {\n\t\t\t\ttopicsOk = true\n\t\t\t\tfor _, topicsMd := range md.Topics {\n\t\t\t\t\tif topicsMd.Err != ErrNoError {\n\t\t\t\t\t\ttopicsOk = false\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif !topicsOk {\n\t\t\treturn fmt.Errorf(\"timed out waiting for test topics to be created\")\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc isTopicNotExistsErrorOrOk(err KError) bool {\n\treturn errors.Is(err, ErrUnknownTopicOrPartition) || errors.Is(err, ErrInvalidTopic) || errors.Is(err, ErrNoError)\n}\n\nfunc isTopicExistsErrorOrOk(err KError) bool {\n\treturn errors.Is(err, ErrTopicAlreadyExists) || errors.Is(err, ErrNoError)\n}\n\nfunc checkKafkaVersion(t testing.TB, requiredVersion string) {\n\tkafkaVersion := FunctionalTestEnv.KafkaVersion\n\tif kafkaVersion == \"\" {\n\t\tt.Skipf(\"No KAFKA_VERSION set. This test requires Kafka version %s or higher. Continuing...\", requiredVersion)\n\t} else {\n\t\tavailable := parseKafkaVersion(kafkaVersion)\n\t\trequired := parseKafkaVersion(requiredVersion)\n\t\tif !available.satisfies(required) {\n\t\t\tt.Skipf(\"Kafka version %s is required for this test; you have %s. Skipping...\", requiredVersion, kafkaVersion)\n\t\t}\n\t}\n}\n\nfunc resetProxies(t testing.TB) {\n\tif err := FunctionalTestEnv.ToxiproxyClient.ResetState(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc SaveProxy(t *testing.T, px string) {\n\tif _, err := FunctionalTestEnv.Proxies[px].Save(); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc proxyForBrokerID(t testing.TB, brokerID int32) *toxiproxy.Proxy {\n\tproxyName := fmt.Sprintf(\"kafka%d\", brokerID)\n\tproxy := FunctionalTestEnv.Proxies[proxyName]\n\tif proxy == nil {\n\t\tt.Fatalf(\"toxiproxy %s not found\", proxyName)\n\t}\n\treturn proxy\n}\n\nfunc addResetPeerToxic(t testing.TB, proxy *toxiproxy.Proxy) {\n\tif _, err := proxy.AddToxic(\"reset-peer\", \"reset_peer\", \"downstream\", 1, toxiproxy.Attributes{}); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc setupFunctionalTest(t testing.TB) {\n\tresetProxies(t)\n\tensureFullyReplicated(t, 60*time.Second, 5*time.Second)\n}\n\nfunc teardownFunctionalTest(t testing.TB) {\n\tresetProxies(t)\n}\n\nfunc ensureFullyReplicated(t testing.TB, timeout time.Duration, retry time.Duration) {\n\tconfig := NewFunctionalTestConfig()\n\tconfig.Metadata.Full = false\n\tconfig.Metadata.RefreshFrequency = 0\n\tconfig.Metadata.Retry.Max = 5\n\tconfig.Metadata.Retry.Backoff = 10 * time.Second\n\tconfig.ClientID = \"sarama-ensureFullyReplicated\"\n\n\tvar testTopicNames []string\n\tfor topic := range testTopicDetails {\n\t\ttestTopicNames = append(testTopicNames, topic)\n\t}\n\n\ttimer := time.NewTimer(timeout)\n\tdefer timer.Stop()\n\ttick := time.NewTicker(retry)\n\tdefer tick.Stop()\n\n\tfor {\n\t\tresp, err := func() (*MetadataResponse, error) {\n\t\t\tclient, err := NewClient(FunctionalTestEnv.KafkaBrokerAddrs, config)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"failed to connect to kafka: %w\", err)\n\t\t\t}\n\t\t\tdefer client.Close()\n\t\t\tbroker := client.LeastLoadedBroker()\n\t\t\tdefer broker.Close()\n\t\t\trequest := NewMetadataRequest(config.Version, testTopicNames)\n\t\t\treturn broker.GetMetadata(request)\n\t\t}()\n\t\tif err != nil {\n\t\t\tLogger.Printf(\"failed to get metadata during test setup: %v\\n\", err)\n\t\t} else {\n\t\t\tok := true\n\t\t\tfor _, topic := range resp.Topics {\n\t\t\t\tfor _, partition := range topic.Partitions {\n\t\t\t\t\tif len(partition.Isr) != 3 {\n\t\t\t\t\t\tok = false\n\t\t\t\t\t\tLogger.Printf(\"topic %s/%d is not fully-replicated Isr=%v Offline=%v\\n\", topic.Name, partition.ID, partition.Isr, partition.OfflineReplicas)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tselect {\n\t\tcase <-timer.C:\n\t\t\tt.Fatalf(\"timeout waiting for test topics to be fully replicated\")\n\t\tcase <-tick.C:\n\t\t}\n\t}\n}\n\ntype kafkaVersion []int\n\nfunc (kv kafkaVersion) satisfies(other kafkaVersion) bool {\n\tvar ov int\n\tfor index, v := range kv {\n\t\tif len(other) <= index {\n\t\t\tov = 0\n\t\t} else {\n\t\t\tov = other[index]\n\t\t}\n\n\t\tif v < ov {\n\t\t\treturn false\n\t\t} else if v > ov {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn true\n}\n\nfunc parseKafkaVersion(version string) kafkaVersion {\n\tnumbers := strings.Split(version, \".\")\n\tresult := make(kafkaVersion, 0, len(numbers))\n\tfor _, number := range numbers {\n\t\tnr, _ := strconv.Atoi(number)\n\t\tresult = append(result, nr)\n\t}\n\n\treturn result\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/IBM/sarama\n\ngo 1.25.0\n\nrequire (\n\tgithub.com/davecgh/go-spew v1.1.1\n\tgithub.com/eapache/go-resiliency v1.7.0\n\tgithub.com/eapache/queue v1.1.0\n\tgithub.com/fortytw2/leaktest v1.3.0\n\tgithub.com/jcmturner/gofork v1.7.6\n\tgithub.com/jcmturner/gokrb5/v8 v8.4.4\n\tgithub.com/klauspost/compress v1.18.5\n\tgithub.com/pierrec/lz4/v4 v4.1.26\n\tgithub.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9\n\tgithub.com/stretchr/testify v1.11.1\n\tgolang.org/x/net v0.52.0\n\tgolang.org/x/sync v0.20.0\n)\n\nrequire (\n\tgithub.com/hashicorp/go-uuid v1.0.3 // indirect\n\tgithub.com/jcmturner/aescts/v2 v2.0.0 // indirect\n\tgithub.com/jcmturner/dnsutils/v2 v2.0.0 // indirect\n\tgithub.com/jcmturner/rpc/v2 v2.0.3 // indirect\n\tgithub.com/kr/pretty v0.3.0 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgolang.org/x/crypto v0.49.0 // indirect\n\tgopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n\nretract (\n\tv1.32.0 // producer hangs on retry https://github.com/IBM/sarama/issues/2150\n\t[v1.31.0, v1.31.1] // producer deadlock https://github.com/IBM/sarama/issues/2129\n\t[v1.26.0, v1.26.1] // consumer fetch session allocation https://github.com/IBM/sarama/pull/1644\n\t[v1.24.1, v1.25.0] // consumer group metadata reqs https://github.com/IBM/sarama/issues/1544\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\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/eapache/go-resiliency v1.7.0 h1:n3NRTnBn5N0Cbi/IeOHuQn9s2UwVUH7Ga0ZWcP+9JTA=\ngithub.com/eapache/go-resiliency v1.7.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho=\ngithub.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=\ngithub.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=\ngithub.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=\ngithub.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=\ngithub.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=\ngithub.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=\ngithub.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=\ngithub.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=\ngithub.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=\ngithub.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=\ngithub.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=\ngithub.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=\ngithub.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=\ngithub.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=\ngithub.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=\ngithub.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=\ngithub.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=\ngithub.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=\ngithub.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=\ngithub.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=\ngithub.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=\ngithub.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=\ngithub.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=\ngithub.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY=\ngithub.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=\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/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg=\ngithub.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=\ngithub.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=\ngithub.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=\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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\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/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\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/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=\ngolang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=\ngolang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=\ngolang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=\ngolang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=\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-20200114155413-6afb5195e5aa/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-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=\ngolang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=\ngolang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=\ngolang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=\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/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=\ngolang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/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-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.5.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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=\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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\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=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\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": "gssapi_kerberos.go",
    "content": "package sarama\n\nimport (\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"math\"\n\t\"net\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/jcmturner/gofork/encoding/asn1\"\n\t\"github.com/jcmturner/gokrb5/v8/asn1tools\"\n\t\"github.com/jcmturner/gokrb5/v8/gssapi\"\n\t\"github.com/jcmturner/gokrb5/v8/iana/chksumtype\"\n\t\"github.com/jcmturner/gokrb5/v8/iana/keyusage\"\n\t\"github.com/jcmturner/gokrb5/v8/messages\"\n\t\"github.com/jcmturner/gokrb5/v8/types\"\n)\n\nconst (\n\tTOK_ID_KRB_AP_REQ   = 256\n\tGSS_API_GENERIC_TAG = 0x60\n\tKRB5_USER_AUTH      = 1\n\tKRB5_KEYTAB_AUTH    = 2\n\tKRB5_CCACHE_AUTH    = 3\n\tGSS_API_INITIAL     = 1\n\tGSS_API_VERIFY      = 2\n\tGSS_API_FINISH      = 3\n)\n\ntype GSSAPIConfig struct {\n\tAuthType           int\n\tKeyTabPath         string\n\tCCachePath         string\n\tKerberosConfigPath string\n\tServiceName        string\n\tUsername           string\n\tPassword           string // #nosec G117 -- required by GSSAPI auth config and intentionally user-provided.\n\tRealm              string\n\tDisablePAFXFAST    bool\n\tBuildSpn           BuildSpnFunc\n}\n\ntype GSSAPIKerberosAuth struct {\n\tConfig                *GSSAPIConfig\n\tticket                messages.Ticket\n\tencKey                types.EncryptionKey\n\tNewKerberosClientFunc func(config *GSSAPIConfig) (KerberosClient, error)\n\tstep                  int\n}\n\ntype KerberosClient interface {\n\tLogin() error\n\tGetServiceTicket(spn string) (messages.Ticket, types.EncryptionKey, error)\n\tDomain() string\n\tCName() types.PrincipalName\n\tDestroy()\n}\n\ntype BuildSpnFunc func(serviceName, host string) string\n\n// writePackage appends length in big endian before the payload, and sends it to kafka\nfunc (krbAuth *GSSAPIKerberosAuth) writePackage(broker *Broker, payload []byte) (int, error) {\n\tlength := uint64(len(payload))\n\tsize := length + 4 // 4 byte length header + payload\n\tif size > math.MaxInt32 {\n\t\treturn 0, errors.New(\"payload too large, will overflow int32\")\n\t}\n\tfinalPackage := make([]byte, size)\n\tcopy(finalPackage[4:], payload)\n\tbinary.BigEndian.PutUint32(finalPackage, uint32(length))\n\tbytes, err := broker.conn.Write(finalPackage)\n\tif err != nil {\n\t\treturn bytes, err\n\t}\n\treturn bytes, nil\n}\n\n// readPackage reads payload length (4 bytes) and then reads the payload into []byte\nfunc (krbAuth *GSSAPIKerberosAuth) readPackage(broker *Broker) ([]byte, int, error) {\n\tbytesRead := 0\n\tlengthInBytes := make([]byte, 4)\n\tbytes, err := io.ReadFull(broker.conn, lengthInBytes)\n\tif err != nil {\n\t\treturn nil, bytesRead, err\n\t}\n\tbytesRead += bytes\n\tpayloadLength := binary.BigEndian.Uint32(lengthInBytes)\n\tpayloadBytes := make([]byte, payloadLength)         // buffer for read..\n\tbytes, err = io.ReadFull(broker.conn, payloadBytes) // read bytes\n\tif err != nil {\n\t\treturn payloadBytes, bytesRead, err\n\t}\n\tbytesRead += bytes\n\treturn payloadBytes, bytesRead, nil\n}\n\nfunc (krbAuth *GSSAPIKerberosAuth) newAuthenticatorChecksum() []byte {\n\ta := make([]byte, 24)\n\tflags := []int{gssapi.ContextFlagInteg, gssapi.ContextFlagConf}\n\tbinary.LittleEndian.PutUint32(a[:4], 16)\n\tfor _, i := range flags {\n\t\tf := binary.LittleEndian.Uint32(a[20:24])\n\t\tf |= uint32(i)\n\t\tbinary.LittleEndian.PutUint32(a[20:24], f)\n\t}\n\treturn a\n}\n\n// Construct Kerberos AP_REQ package, conforming to RFC-4120\n// https://tools.ietf.org/html/rfc4120#page-84\nfunc (krbAuth *GSSAPIKerberosAuth) createKrb5Token(\n\tdomain string,\n\tcname types.PrincipalName,\n\tticket messages.Ticket,\n\tsessionKey types.EncryptionKey,\n) ([]byte, error) {\n\tauth, err := types.NewAuthenticator(domain, cname)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tauth.Cksum = types.Checksum{\n\t\tCksumType: chksumtype.GSSAPI,\n\t\tChecksum:  krbAuth.newAuthenticatorChecksum(),\n\t}\n\tAPReq, err := messages.NewAPReq(\n\t\tticket,\n\t\tsessionKey,\n\t\tauth,\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\taprBytes := make([]byte, 2)\n\tbinary.BigEndian.PutUint16(aprBytes, TOK_ID_KRB_AP_REQ)\n\ttb, err := APReq.Marshal()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\taprBytes = append(aprBytes, tb...)\n\treturn aprBytes, nil\n}\n\n// Append the GSS-API header to the payload, conforming to RFC-2743\n// Section 3.1, Mechanism-Independent Token Format\n//\n// https://tools.ietf.org/html/rfc2743#page-81\n//\n// GSSAPIHeader + <specific mechanism payload>\nfunc (krbAuth *GSSAPIKerberosAuth) appendGSSAPIHeader(payload []byte) ([]byte, error) {\n\toidBytes, err := asn1.Marshal(gssapi.OIDKRB5.OID())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttkoLengthBytes := asn1tools.MarshalLengthBytes(len(oidBytes) + len(payload))\n\tGSSHeader := append([]byte{GSS_API_GENERIC_TAG}, tkoLengthBytes...)\n\tGSSHeader = append(GSSHeader, oidBytes...)\n\tGSSPackage := append(GSSHeader, payload...)\n\treturn GSSPackage, nil\n}\n\nfunc (krbAuth *GSSAPIKerberosAuth) initSecContext(\n\tclient KerberosClient,\n\tbytes []byte,\n) ([]byte, error) {\n\tswitch krbAuth.step {\n\tcase GSS_API_INITIAL:\n\t\taprBytes, err := krbAuth.createKrb5Token(\n\t\t\tclient.Domain(),\n\t\t\tclient.CName(),\n\t\t\tkrbAuth.ticket,\n\t\t\tkrbAuth.encKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tkrbAuth.step = GSS_API_VERIFY\n\t\treturn krbAuth.appendGSSAPIHeader(aprBytes)\n\tcase GSS_API_VERIFY:\n\t\twrapTokenReq := gssapi.WrapToken{}\n\t\tif err := wrapTokenReq.Unmarshal(bytes, true); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\t// Validate response.\n\t\tisValid, err := wrapTokenReq.Verify(krbAuth.encKey, keyusage.GSSAPI_ACCEPTOR_SEAL)\n\t\tif !isValid {\n\t\t\treturn nil, err\n\t\t}\n\n\t\twrapTokenResponse, err := gssapi.NewInitiatorWrapToken(wrapTokenReq.Payload, krbAuth.encKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tkrbAuth.step = GSS_API_FINISH\n\t\treturn wrapTokenResponse.Marshal()\n\t}\n\treturn nil, nil\n}\n\nfunc (krbAuth *GSSAPIKerberosAuth) spn(broker *Broker) string {\n\thost, _, _ := net.SplitHostPort(broker.addr)\n\tvar spn string\n\tif krbAuth.Config.BuildSpn != nil {\n\t\tspn = krbAuth.Config.BuildSpn(broker.conf.Net.SASL.GSSAPI.ServiceName, host)\n\t} else {\n\t\tspn = fmt.Sprintf(\"%s/%s\", broker.conf.Net.SASL.GSSAPI.ServiceName, host)\n\t}\n\treturn spn\n}\n\n// Login will use the given KerberosClient to login and get a ticket for the given spn.\nfunc (krbAuth *GSSAPIKerberosAuth) Login(\n\tclient KerberosClient,\n\tspn string,\n) (*messages.Ticket, error) {\n\tif err := client.Login(); err != nil {\n\t\tLogger.Printf(\"Kerberos client login error: %s\", err)\n\t\treturn nil, err\n\t}\n\n\tticket, encKey, err := client.GetServiceTicket(spn)\n\tif err != nil {\n\t\tLogger.Printf(\"Kerberos service ticket error for %s: %s\", spn, err)\n\t\treturn nil, err\n\t}\n\tkrbAuth.ticket = ticket\n\tkrbAuth.encKey = encKey\n\tkrbAuth.step = GSS_API_INITIAL\n\n\treturn &ticket, nil\n}\n\n// Authorize performs the kerberos auth handshake for authorization\nfunc (krbAuth *GSSAPIKerberosAuth) Authorize(broker *Broker) error {\n\tclient, err := krbAuth.NewKerberosClientFunc(krbAuth.Config)\n\tif err != nil {\n\t\tLogger.Printf(\"Kerberos client initialization error: %s\", err)\n\t\treturn err\n\t}\n\tdefer client.Destroy()\n\n\tticket, err := krbAuth.Login(client, krbAuth.spn(broker))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tprincipal := strings.Join(ticket.SName.NameString, \"/\") + \"@\" + ticket.Realm\n\tvar receivedBytes []byte\n\n\tfor {\n\t\tpackBytes, err := krbAuth.initSecContext(client, receivedBytes)\n\t\tif err != nil {\n\t\t\tLogger.Printf(\"Kerberos init error as %s: %s\", principal, err)\n\t\t\treturn err\n\t\t}\n\n\t\trequestTime := time.Now()\n\t\tbytesWritten, err := krbAuth.writePackage(broker, packBytes)\n\t\tif err != nil {\n\t\t\tLogger.Printf(\"Kerberos write error as %s: %s\", principal, err)\n\t\t\treturn err\n\t\t}\n\t\tbroker.updateOutgoingCommunicationMetrics(bytesWritten)\n\n\t\tswitch krbAuth.step {\n\t\tcase GSS_API_VERIFY:\n\t\t\tvar bytesRead int\n\t\t\treceivedBytes, bytesRead, err = krbAuth.readPackage(broker)\n\t\t\trequestLatency := time.Since(requestTime)\n\t\t\tbroker.updateIncomingCommunicationMetrics(bytesRead, requestLatency)\n\t\t\tif err != nil {\n\t\t\t\tLogger.Printf(\"Kerberos read error as %s: %s\", principal, err)\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase GSS_API_FINISH:\n\t\t\treturn nil\n\t\t}\n\t}\n}\n\n// AuthorizeV2 performs the SASL v2 GSSAPI authentication with the Kafka broker.\nfunc (krbAuth *GSSAPIKerberosAuth) AuthorizeV2(\n\tbroker *Broker,\n\tauthSendReceiver func(authBytes []byte) (*SaslAuthenticateResponse, error),\n) error {\n\tclient, err := krbAuth.NewKerberosClientFunc(krbAuth.Config)\n\tif err != nil {\n\t\tLogger.Printf(\"Kerberos client initialization error: %s\", err)\n\t\treturn err\n\t}\n\tdefer client.Destroy()\n\n\tticket, err := krbAuth.Login(client, krbAuth.spn(broker))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tprincipal := strings.Join(ticket.SName.NameString, \"/\") + \"@\" + ticket.Realm\n\tvar receivedBytes []byte\n\n\tfor {\n\t\ttoken, err := krbAuth.initSecContext(client, receivedBytes)\n\t\tif err != nil {\n\t\t\tLogger.Printf(\"SASL Kerberos init error as %s: %s\", principal, err)\n\t\t\treturn err\n\t\t}\n\n\t\tauthResponse, err := authSendReceiver(token)\n\t\tif err != nil {\n\t\t\tLogger.Printf(\"SASL Kerberos authenticate error as %s: %s\", principal, err)\n\t\t\treturn err\n\t\t}\n\n\t\treceivedBytes = authResponse.SaslAuthBytes\n\n\t\tif krbAuth.step == GSS_API_FINISH {\n\t\t\treturn nil\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "heartbeat_request.go",
    "content": "package sarama\n\ntype HeartbeatRequest struct {\n\tVersion         int16\n\tGroupId         string\n\tGenerationId    int32\n\tMemberId        string\n\tGroupInstanceId *string\n}\n\nfunc (r *HeartbeatRequest) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *HeartbeatRequest) encode(pe packetEncoder) error {\n\tif err := pe.putString(r.GroupId); err != nil {\n\t\treturn err\n\t}\n\n\tpe.putInt32(r.GenerationId)\n\n\tif err := pe.putString(r.MemberId); err != nil {\n\t\treturn err\n\t}\n\n\tif r.Version >= 3 {\n\t\tif err := pe.putNullableString(r.GroupInstanceId); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (r *HeartbeatRequest) decode(pd packetDecoder, version int16) (err error) {\n\tr.Version = version\n\tif r.GroupId, err = pd.getString(); err != nil {\n\t\treturn\n\t}\n\tif r.GenerationId, err = pd.getInt32(); err != nil {\n\t\treturn\n\t}\n\tif r.MemberId, err = pd.getString(); err != nil {\n\t\treturn\n\t}\n\tif r.Version >= 3 {\n\t\tif r.GroupInstanceId, err = pd.getNullableString(); err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (r *HeartbeatRequest) key() int16 {\n\treturn apiKeyHeartbeat\n}\n\nfunc (r *HeartbeatRequest) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *HeartbeatRequest) headerVersion() int16 {\n\tif r.Version >= 4 {\n\t\treturn 2\n\t}\n\treturn 1\n}\n\nfunc (r *HeartbeatRequest) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 4\n}\n\nfunc (r *HeartbeatRequest) isFlexible() bool {\n\treturn r.isFlexibleVersion(r.Version)\n}\n\nfunc (r *HeartbeatRequest) isFlexibleVersion(version int16) bool {\n\treturn version >= 4\n}\n\nfunc (r *HeartbeatRequest) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 4:\n\t\treturn V2_4_0_0\n\tcase 3:\n\t\treturn V2_3_0_0\n\tcase 2:\n\t\treturn V2_0_0_0\n\tcase 1:\n\t\treturn V0_11_0_0\n\tcase 0:\n\t\treturn V0_8_2_0\n\tdefault:\n\t\treturn V2_3_0_0\n\t}\n}\n"
  },
  {
    "path": "heartbeat_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nvar (\n\tbasicHeartbeatRequestV0 = []byte{\n\t\t0, 3, 'f', 'o', 'o', // Group ID\n\t\t0x00, 0x01, 0x02, 0x03, // Generation ID\n\t\t0, 3, 'b', 'a', 'z', // Member ID\n\t}\n\n\tbasicHeartbeatRequestV3_GID = []byte{\n\t\t0, 3, 'f', 'o', 'o', // Group ID\n\t\t0x00, 0x01, 0x02, 0x03, // Generation ID\n\t\t0, 3, 'b', 'a', 'z', // Member ID\n\t\t0, 3, 'g', 'i', 'd', // Group Instance ID\n\t}\n\tbasicHeartbeatRequestV3_NOGID = []byte{\n\t\t0, 3, 'f', 'o', 'o', // Group ID\n\t\t0x00, 0x01, 0x02, 0x03, // Generation ID\n\t\t0, 3, 'b', 'a', 'z', // Member ID\n\t\t255, 255, // Group Instance ID\n\t}\n\tbasicHeartbeatRequestV4_GID = []byte{\n\t\t4, 'f', 'o', 'o', // Group ID\n\t\t0x00, 0x01, 0x02, 0x03, // Generation ID\n\t\t4, 'b', 'a', 'z', // Member ID\n\t\t4, 'g', 'i', 'd', // Group Instance ID\n\t\t0, // empty tagged fields\n\t}\n\tbasicHeartbeatRequestV4_NOGID = []byte{\n\t\t4, 'f', 'o', 'o', // Group ID\n\t\t0x00, 0x01, 0x02, 0x03, // Generation ID\n\t\t4, 'b', 'a', 'z', // Member ID\n\t\t0, // Group Instance ID\n\t\t0, // empty tagged fields\n\t}\n)\n\nfunc TestHeartbeatRequest(t *testing.T) {\n\tgroupInstanceId := \"gid\"\n\ttests := []struct {\n\t\tCaseName     string\n\t\tVersion      int16\n\t\tMessageBytes []byte\n\t\tMessage      *HeartbeatRequest\n\t}{\n\t\t{\n\t\t\t\"v0_basic\",\n\t\t\t0,\n\t\t\tbasicHeartbeatRequestV0,\n\t\t\t&HeartbeatRequest{\n\t\t\t\tVersion:      0,\n\t\t\t\tGroupId:      \"foo\",\n\t\t\t\tGenerationId: 0x00010203,\n\t\t\t\tMemberId:     \"baz\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"v3_basic\",\n\t\t\t3,\n\t\t\tbasicHeartbeatRequestV3_GID,\n\t\t\t&HeartbeatRequest{\n\t\t\t\tVersion:         3,\n\t\t\t\tGroupId:         \"foo\",\n\t\t\t\tGenerationId:    0x00010203,\n\t\t\t\tMemberId:        \"baz\",\n\t\t\t\tGroupInstanceId: &groupInstanceId,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"v3_basic\",\n\t\t\t3,\n\t\t\tbasicHeartbeatRequestV3_NOGID,\n\t\t\t&HeartbeatRequest{\n\t\t\t\tVersion:         3,\n\t\t\t\tGroupId:         \"foo\",\n\t\t\t\tGenerationId:    0x00010203,\n\t\t\t\tMemberId:        \"baz\",\n\t\t\t\tGroupInstanceId: nil,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"v4_basic\",\n\t\t\t4,\n\t\t\tbasicHeartbeatRequestV4_GID,\n\t\t\t&HeartbeatRequest{\n\t\t\t\tVersion:         4,\n\t\t\t\tGroupId:         \"foo\",\n\t\t\t\tGenerationId:    0x00010203,\n\t\t\t\tMemberId:        \"baz\",\n\t\t\t\tGroupInstanceId: &groupInstanceId,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"v4_basic\",\n\t\t\t4,\n\t\t\tbasicHeartbeatRequestV4_NOGID,\n\t\t\t&HeartbeatRequest{\n\t\t\t\tVersion:         4,\n\t\t\t\tGroupId:         \"foo\",\n\t\t\t\tGenerationId:    0x00010203,\n\t\t\t\tMemberId:        \"baz\",\n\t\t\t\tGroupInstanceId: nil,\n\t\t\t},\n\t\t},\n\t}\n\tfor _, c := range tests {\n\t\ttestEncodable(t, c.CaseName, c.Message, c.MessageBytes)\n\t\trequest := new(HeartbeatRequest)\n\t\ttestVersionDecodable(t, c.CaseName, request, c.MessageBytes, c.Version)\n\t\tif !reflect.DeepEqual(c.Message, request) {\n\t\t\tt.Errorf(\"case %s decode failed, expected:%+v got %+v\", c.CaseName, c.Message, request)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "heartbeat_response.go",
    "content": "package sarama\n\nimport \"time\"\n\ntype HeartbeatResponse struct {\n\tVersion      int16\n\tThrottleTime int32\n\tErr          KError\n}\n\nfunc (r *HeartbeatResponse) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *HeartbeatResponse) encode(pe packetEncoder) error {\n\tif r.Version >= 1 {\n\t\tpe.putInt32(r.ThrottleTime)\n\t}\n\tpe.putKError(r.Err)\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (r *HeartbeatResponse) decode(pd packetDecoder, version int16) error {\n\tvar err error\n\tr.Version = version\n\tif r.Version >= 1 {\n\t\tif r.ThrottleTime, err = pd.getInt32(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tr.Err, err = pd.getKError()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (r *HeartbeatResponse) key() int16 {\n\treturn apiKeyHeartbeat\n}\n\nfunc (r *HeartbeatResponse) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *HeartbeatResponse) headerVersion() int16 {\n\tif r.Version >= 4 {\n\t\treturn 1\n\t}\n\treturn 0\n}\n\nfunc (r *HeartbeatResponse) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 4\n}\n\nfunc (r *HeartbeatResponse) isFlexible() bool {\n\treturn r.isFlexibleVersion(r.Version)\n}\n\nfunc (r *HeartbeatResponse) isFlexibleVersion(version int16) bool {\n\treturn version >= 4\n}\n\nfunc (r *HeartbeatResponse) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 4:\n\t\treturn V2_4_0_0\n\tcase 3:\n\t\treturn V2_3_0_0\n\tcase 2:\n\t\treturn V2_0_0_0\n\tcase 1:\n\t\treturn V0_11_0_0\n\tcase 0:\n\t\treturn V0_8_2_0\n\tdefault:\n\t\treturn V2_3_0_0\n\t}\n}\n\nfunc (r *HeartbeatResponse) throttleTime() time.Duration {\n\treturn time.Duration(r.ThrottleTime) * time.Millisecond\n}\n"
  },
  {
    "path": "heartbeat_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nvar (\n\theartbeatResponseNoError_V0 = []byte{\n\t\t0x00, 0x00,\n\t}\n\theartbeatResponseNoError_V1 = []byte{\n\t\t0, 0, 0, 100,\n\t\t0, 0,\n\t}\n\theartbeatResponseError_V1 = []byte{\n\t\t0, 0, 0, 100,\n\t\t0, byte(ErrFencedInstancedId),\n\t}\n\theartbeatResponseNoError_V4 = []byte{\n\t\t0, 0, 0, 100,\n\t\t0, 0,\n\t\t0, // empty tagged fields\n\t}\n\theartbeatResponseError_V4 = []byte{\n\t\t0, 0, 0, 100,\n\t\t0, byte(ErrFencedInstancedId),\n\t\t0, // empty tagged fields\n\t}\n)\n\nfunc TestHeartbeatResponse(t *testing.T) {\n\ttests := []struct {\n\t\tCaseName     string\n\t\tVersion      int16\n\t\tMessageBytes []byte\n\t\tMessage      *HeartbeatResponse\n\t}{\n\t\t{\n\t\t\t\"v0_noErr\",\n\t\t\t0,\n\t\t\theartbeatResponseNoError_V0,\n\t\t\t&HeartbeatResponse{\n\t\t\t\tVersion: 0,\n\t\t\t\tErr:     ErrNoError,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"v1_noErr\",\n\t\t\t1,\n\t\t\theartbeatResponseNoError_V1,\n\t\t\t&HeartbeatResponse{\n\t\t\t\tVersion:      1,\n\t\t\t\tErr:          ErrNoError,\n\t\t\t\tThrottleTime: 100,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"v1_Err\",\n\t\t\t1,\n\t\t\theartbeatResponseError_V1,\n\t\t\t&HeartbeatResponse{\n\t\t\t\tVersion:      1,\n\t\t\t\tErr:          ErrFencedInstancedId,\n\t\t\t\tThrottleTime: 100,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"v4_noErr\",\n\t\t\t4,\n\t\t\theartbeatResponseNoError_V4,\n\t\t\t&HeartbeatResponse{\n\t\t\t\tVersion:      4,\n\t\t\t\tErr:          ErrNoError,\n\t\t\t\tThrottleTime: 100,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"v4_Err\",\n\t\t\t4,\n\t\t\theartbeatResponseError_V4,\n\t\t\t&HeartbeatResponse{\n\t\t\t\tVersion:      4,\n\t\t\t\tErr:          ErrFencedInstancedId,\n\t\t\t\tThrottleTime: 100,\n\t\t\t},\n\t\t},\n\t}\n\tfor _, c := range tests {\n\t\ttestEncodable(t, c.CaseName, c.Message, c.MessageBytes)\n\t\tresponse := new(HeartbeatResponse)\n\t\ttestVersionDecodable(t, c.CaseName, response, c.MessageBytes, c.Version)\n\t\tif !reflect.DeepEqual(c.Message, response) {\n\t\t\tt.Errorf(\"case %s decode failed, expected:%+v got %+v\", c.CaseName, c.Message, response)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "helpers_test.go",
    "content": "package sarama\n\nimport (\n\t\"io\"\n\t\"strconv\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc safeClose(t testing.TB, c io.Closer) {\n\tt.Helper()\n\terr := c.Close()\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc closeProducerWithTimeout(t *testing.T, p AsyncProducer, timeout time.Duration) {\n\tvar wg sync.WaitGroup\n\tp.AsyncClose()\n\n\tcloser := make(chan struct{})\n\ttimer := time.AfterFunc(timeout, func() {\n\t\tt.Error(\"timeout\")\n\t\tclose(closer)\n\t})\n\tdefer timer.Stop()\n\n\twg.Add(2)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-closer:\n\t\t\t\treturn\n\t\t\tcase _, ok := <-p.Successes():\n\t\t\t\tif !ok {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tt.Error(\"Unexpected message on Successes()\")\n\t\t\t}\n\t\t}\n\t}()\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-closer:\n\t\t\t\treturn\n\t\t\tcase msg, ok := <-p.Errors():\n\t\t\t\tif !ok {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tt.Error(msg.Err)\n\t\t\t}\n\t\t}\n\t}()\n\twg.Wait()\n}\n\nfunc closeProducer(t *testing.T, p AsyncProducer) {\n\tcloseProducerWithTimeout(t, p, 5*time.Minute)\n}\n\nconst TestMessage = \"ABC THE MESSAGE\"\n\ntype appendInterceptor struct {\n\ti int\n}\n\nfunc (b *appendInterceptor) OnSend(msg *ProducerMessage) {\n\tif b.i < 0 {\n\t\tpanic(\"hey, the interceptor has failed\")\n\t}\n\tv, _ := msg.Value.Encode()\n\tmsg.Value = StringEncoder(string(v) + strconv.Itoa(b.i))\n\tb.i++\n}\n\nfunc (b *appendInterceptor) OnConsume(msg *ConsumerMessage) {\n\tif b.i < 0 {\n\t\tpanic(\"hey, the interceptor has failed\")\n\t}\n\tmsg.Value = []byte(string(msg.Value) + strconv.Itoa(b.i))\n\tb.i++\n}\n"
  },
  {
    "path": "incremental_alter_configs_request.go",
    "content": "package sarama\n\ntype IncrementalAlterConfigsOperation int8\n\nconst (\n\tIncrementalAlterConfigsOperationSet IncrementalAlterConfigsOperation = iota\n\tIncrementalAlterConfigsOperationDelete\n\tIncrementalAlterConfigsOperationAppend\n\tIncrementalAlterConfigsOperationSubtract\n)\n\n// IncrementalAlterConfigsRequest is an incremental alter config request type\ntype IncrementalAlterConfigsRequest struct {\n\tVersion      int16\n\tResources    []*IncrementalAlterConfigsResource\n\tValidateOnly bool\n}\n\nfunc (a *IncrementalAlterConfigsRequest) setVersion(v int16) {\n\ta.Version = v\n}\n\ntype IncrementalAlterConfigsResource struct {\n\tType          ConfigResourceType\n\tName          string\n\tConfigEntries map[string]IncrementalAlterConfigsEntry\n}\n\ntype IncrementalAlterConfigsEntry struct {\n\tOperation IncrementalAlterConfigsOperation\n\tValue     *string\n}\n\nfunc (a *IncrementalAlterConfigsRequest) encode(pe packetEncoder) error {\n\tif err := pe.putArrayLength(len(a.Resources)); err != nil {\n\t\treturn err\n\t}\n\n\tfor _, r := range a.Resources {\n\t\tif err := r.encode(pe); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tpe.putBool(a.ValidateOnly)\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (a *IncrementalAlterConfigsRequest) decode(pd packetDecoder, version int16) error {\n\tresourceCount, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ta.Resources = make([]*IncrementalAlterConfigsResource, resourceCount)\n\tfor i := range a.Resources {\n\t\tr := &IncrementalAlterConfigsResource{}\n\t\terr = r.decode(pd, version)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Resources[i] = r\n\t}\n\n\tvalidateOnly, err := pd.getBool()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ta.ValidateOnly = validateOnly\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (a *IncrementalAlterConfigsResource) encode(pe packetEncoder) error {\n\tpe.putInt8(int8(a.Type))\n\n\tif err := pe.putString(a.Name); err != nil {\n\t\treturn err\n\t}\n\n\tif err := pe.putArrayLength(len(a.ConfigEntries)); err != nil {\n\t\treturn err\n\t}\n\n\tfor name, e := range a.ConfigEntries {\n\t\tif err := pe.putString(name); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := e.encode(pe); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (a *IncrementalAlterConfigsResource) decode(pd packetDecoder, version int16) error {\n\tt, err := pd.getInt8()\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.Type = ConfigResourceType(t)\n\n\tname, err := pd.getString()\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.Name = name\n\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif n > 0 {\n\t\ta.ConfigEntries = make(map[string]IncrementalAlterConfigsEntry, n)\n\t\tfor i := 0; i < n; i++ {\n\t\t\tname, err := pd.getString()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tvar v IncrementalAlterConfigsEntry\n\n\t\t\tif err := v.decode(pd, version); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\ta.ConfigEntries[name] = v\n\t\t}\n\t}\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (a *IncrementalAlterConfigsEntry) encode(pe packetEncoder) error {\n\tpe.putInt8(int8(a.Operation))\n\n\tif err := pe.putNullableString(a.Value); err != nil {\n\t\treturn err\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (a *IncrementalAlterConfigsEntry) decode(pd packetDecoder, version int16) error {\n\tt, err := pd.getInt8()\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.Operation = IncrementalAlterConfigsOperation(t)\n\n\ts, err := pd.getNullableString()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ta.Value = s\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (a *IncrementalAlterConfigsRequest) key() int16 {\n\treturn apiKeyIncrementalAlterConfigs\n}\n\nfunc (a *IncrementalAlterConfigsRequest) version() int16 {\n\treturn a.Version\n}\n\nfunc (a *IncrementalAlterConfigsRequest) headerVersion() int16 {\n\tif a.Version >= 1 {\n\t\treturn 2\n\t}\n\treturn 1\n}\n\nfunc (a *IncrementalAlterConfigsRequest) isValidVersion() bool {\n\treturn a.Version >= 0 && a.Version <= 1\n}\n\nfunc (a *IncrementalAlterConfigsRequest) isFlexible() bool {\n\treturn a.isFlexibleVersion(a.Version)\n}\n\nfunc (a *IncrementalAlterConfigsRequest) isFlexibleVersion(version int16) bool {\n\treturn version >= 1\n}\n\nfunc (a *IncrementalAlterConfigsRequest) requiredVersion() KafkaVersion {\n\tswitch a.Version {\n\tcase 1:\n\t\treturn V2_4_0_0\n\tdefault:\n\t\treturn V2_3_0_0\n\t}\n}\n"
  },
  {
    "path": "incremental_alter_configs_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nvar (\n\temptyIncrementalAlterConfigsRequest = []byte{\n\t\t0, 0, 0, 0, // 0 configs\n\t\t0, // don't Validate\n\t}\n\n\tsingleIncrementalAlterConfigsRequest = []byte{\n\t\t0, 0, 0, 1, // 1 config\n\t\t2,                   // a topic\n\t\t0, 3, 'f', 'o', 'o', // topic name: foo\n\t\t0, 0, 0, 1, // 1 config name\n\t\t0, 10, // 10 chars\n\t\t's', 'e', 'g', 'm', 'e', 'n', 't', '.', 'm', 's',\n\t\t0, // OperationSet\n\t\t0, 4,\n\t\t'1', '0', '0', '0',\n\t\t0, // don't validate\n\t}\n\n\tdoubleIncrementalAlterConfigsRequest = []byte{\n\t\t0, 0, 0, 2, // 2 config\n\t\t2,                   // a topic\n\t\t0, 3, 'f', 'o', 'o', // topic name: foo\n\t\t0, 0, 0, 1, // 1 config name\n\t\t0, 10, // 10 chars\n\t\t's', 'e', 'g', 'm', 'e', 'n', 't', '.', 'm', 's',\n\t\t0, // OperationSet\n\t\t0, 4,\n\t\t'1', '0', '0', '0',\n\t\t2,                   // a topic\n\t\t0, 3, 'b', 'a', 'r', // topic name: foo\n\t\t0, 0, 0, 1, // 2 config\n\t\t0, 12, // 12 chars\n\t\t'r', 'e', 't', 'e', 'n', 't', 'i', 'o', 'n', '.', 'm', 's',\n\t\t1, // OperationDelete\n\t\t0, 4,\n\t\t'1', '0', '0', '0',\n\t\t0, // don't validate\n\t}\n\n\temptyIncrementalAlterConfigsRequestV1 = []byte{\n\t\t1, // 0 configs\n\t\t0, // don't Validate\n\t\t0, // empty tagged fields\n\t}\n\n\tsingleIncrementalAlterConfigsRequestV1 = []byte{\n\t\t2,                // 1 config\n\t\t2,                // a topic\n\t\t4, 'f', 'o', 'o', // topic name: foo\n\t\t2,  // 1 config name\n\t\t11, // 10 chars\n\t\t's', 'e', 'g', 'm', 'e', 'n', 't', '.', 'm', 's',\n\t\t0, // OperationSet\n\t\t5,\n\t\t'1', '0', '0', '0',\n\t\t0, // empty tagged fields\n\t\t0, // empty tagged fields\n\t\t0, // don't validate\n\t\t0, // empty tagged fields\n\t}\n\n\tdoubleIncrementalAlterConfigsRequestV1 = []byte{\n\t\t3,                // 2 config\n\t\t2,                // a topic\n\t\t4, 'f', 'o', 'o', // topic name: foo\n\t\t2,  // 1 config name\n\t\t11, // 10 chars\n\t\t's', 'e', 'g', 'm', 'e', 'n', 't', '.', 'm', 's',\n\t\t0, // OperationSet\n\t\t5,\n\t\t'1', '0', '0', '0',\n\t\t0,                // empty tagged fields\n\t\t0,                // empty tagged fields\n\t\t2,                // a topic\n\t\t4, 'b', 'a', 'r', // topic name: foo\n\t\t2,  // 2 config\n\t\t13, // 12 chars\n\t\t'r', 'e', 't', 'e', 'n', 't', 'i', 'o', 'n', '.', 'm', 's',\n\t\t1, // OperationDelete\n\t\t5,\n\t\t'1', '0', '0', '0',\n\t\t0, // empty tagged fields\n\t\t0, // empty tagged fields\n\t\t0, // don't validate\n\t\t0, // empty tagged fields\n\t}\n)\n\nfunc TestIncrementalAlterConfigsRequest(t *testing.T) {\n\tvar request *IncrementalAlterConfigsRequest\n\n\trequest = &IncrementalAlterConfigsRequest{\n\t\tResources: []*IncrementalAlterConfigsResource{},\n\t}\n\ttestRequest(t, \"no requests\", request, emptyIncrementalAlterConfigsRequest)\n\n\tconfigValue := \"1000\"\n\trequest = &IncrementalAlterConfigsRequest{\n\t\tResources: []*IncrementalAlterConfigsResource{\n\t\t\t{\n\t\t\t\tType: TopicResource,\n\t\t\t\tName: \"foo\",\n\t\t\t\tConfigEntries: map[string]IncrementalAlterConfigsEntry{\n\t\t\t\t\t\"segment.ms\": {\n\t\t\t\t\t\tOperation: IncrementalAlterConfigsOperationSet,\n\t\t\t\t\t\tValue:     &configValue,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\ttestRequest(t, \"one config\", request, singleIncrementalAlterConfigsRequest)\n\n\trequest = &IncrementalAlterConfigsRequest{\n\t\tResources: []*IncrementalAlterConfigsResource{\n\t\t\t{\n\t\t\t\tType: TopicResource,\n\t\t\t\tName: \"foo\",\n\t\t\t\tConfigEntries: map[string]IncrementalAlterConfigsEntry{\n\t\t\t\t\t\"segment.ms\": {\n\t\t\t\t\t\tOperation: IncrementalAlterConfigsOperationSet,\n\t\t\t\t\t\tValue:     &configValue,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: TopicResource,\n\t\t\t\tName: \"bar\",\n\t\t\t\tConfigEntries: map[string]IncrementalAlterConfigsEntry{\n\t\t\t\t\t\"retention.ms\": {\n\t\t\t\t\t\tOperation: IncrementalAlterConfigsOperationDelete,\n\t\t\t\t\t\tValue:     &configValue,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\ttestRequest(t, \"two configs\", request, doubleIncrementalAlterConfigsRequest)\n}\n\nfunc TestIncrementalAlterConfigsRequestV1(t *testing.T) {\n\tvar request *IncrementalAlterConfigsRequest\n\n\trequest = &IncrementalAlterConfigsRequest{\n\t\tVersion:   1,\n\t\tResources: []*IncrementalAlterConfigsResource{},\n\t}\n\ttestRequest(t, \"no requests\", request, emptyIncrementalAlterConfigsRequestV1)\n\n\tconfigValue := \"1000\"\n\trequest = &IncrementalAlterConfigsRequest{\n\t\tVersion: 1,\n\t\tResources: []*IncrementalAlterConfigsResource{\n\t\t\t{\n\t\t\t\tType: TopicResource,\n\t\t\t\tName: \"foo\",\n\t\t\t\tConfigEntries: map[string]IncrementalAlterConfigsEntry{\n\t\t\t\t\t\"segment.ms\": {\n\t\t\t\t\t\tOperation: IncrementalAlterConfigsOperationSet,\n\t\t\t\t\t\tValue:     &configValue,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\ttestRequest(t, \"one config\", request, singleIncrementalAlterConfigsRequestV1)\n\n\trequest = &IncrementalAlterConfigsRequest{\n\t\tVersion: 1,\n\t\tResources: []*IncrementalAlterConfigsResource{\n\t\t\t{\n\t\t\t\tType: TopicResource,\n\t\t\t\tName: \"foo\",\n\t\t\t\tConfigEntries: map[string]IncrementalAlterConfigsEntry{\n\t\t\t\t\t\"segment.ms\": {\n\t\t\t\t\t\tOperation: IncrementalAlterConfigsOperationSet,\n\t\t\t\t\t\tValue:     &configValue,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: TopicResource,\n\t\t\t\tName: \"bar\",\n\t\t\t\tConfigEntries: map[string]IncrementalAlterConfigsEntry{\n\t\t\t\t\t\"retention.ms\": {\n\t\t\t\t\t\tOperation: IncrementalAlterConfigsOperationDelete,\n\t\t\t\t\t\tValue:     &configValue,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\ttestRequest(t, \"two configs\", request, doubleIncrementalAlterConfigsRequestV1)\n}\n"
  },
  {
    "path": "incremental_alter_configs_response.go",
    "content": "package sarama\n\nimport \"time\"\n\n// IncrementalAlterConfigsResponse is a response type for incremental alter config\ntype IncrementalAlterConfigsResponse struct {\n\tVersion      int16\n\tThrottleTime time.Duration\n\tResources    []*AlterConfigsResourceResponse\n}\n\nfunc (a *IncrementalAlterConfigsResponse) setVersion(v int16) {\n\ta.Version = v\n}\n\nfunc (a *IncrementalAlterConfigsResponse) encode(pe packetEncoder) error {\n\tpe.putDurationMs(a.ThrottleTime)\n\n\tif err := pe.putArrayLength(len(a.Resources)); err != nil {\n\t\treturn err\n\t}\n\n\tfor _, v := range a.Resources {\n\t\tif err := v.encode(pe); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (a *IncrementalAlterConfigsResponse) decode(pd packetDecoder, version int16) (err error) {\n\tif a.ThrottleTime, err = pd.getDurationMs(); err != nil {\n\t\treturn err\n\t}\n\n\tresponseCount, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ta.Resources = make([]*AlterConfigsResourceResponse, responseCount)\n\n\tfor i := range a.Resources {\n\t\ta.Resources[i] = new(AlterConfigsResourceResponse)\n\n\t\tif err := a.Resources[i].decode(pd, version); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (a *IncrementalAlterConfigsResponse) key() int16 {\n\treturn apiKeyIncrementalAlterConfigs\n}\n\nfunc (a *IncrementalAlterConfigsResponse) version() int16 {\n\treturn a.Version\n}\n\nfunc (a *IncrementalAlterConfigsResponse) headerVersion() int16 {\n\tif a.Version >= 1 {\n\t\treturn 1\n\t}\n\treturn 0\n}\n\nfunc (a *IncrementalAlterConfigsResponse) isFlexible() bool {\n\treturn a.isFlexibleVersion(a.Version)\n}\n\nfunc (a *IncrementalAlterConfigsResponse) isFlexibleVersion(version int16) bool {\n\treturn version >= 1\n}\n\nfunc (a *IncrementalAlterConfigsResponse) isValidVersion() bool {\n\treturn a.Version >= 0 && a.Version <= 1\n}\n\nfunc (a *IncrementalAlterConfigsResponse) requiredVersion() KafkaVersion {\n\tswitch a.Version {\n\tcase 1:\n\t\treturn V2_4_0_0\n\tdefault:\n\t\treturn V2_3_0_0\n\t}\n}\n\nfunc (r *IncrementalAlterConfigsResponse) throttleTime() time.Duration {\n\treturn r.ThrottleTime\n}\n"
  },
  {
    "path": "incremental_alter_configs_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"testing\"\n)\n\nvar (\n\tincrementalAlterResponseEmpty = []byte{\n\t\t0, 0, 0, 0, // throttle\n\t\t0, 0, 0, 0, // no configs\n\t}\n\n\tincrementalAlterResponsePopulated = []byte{\n\t\t0, 0, 0, 0, // throttle\n\t\t0, 0, 0, 1, // response\n\t\t0, 0, // errorcode\n\t\t0, 0, // string\n\t\t2, // topic\n\t\t0, 3, 'f', 'o', 'o',\n\t}\n\n\tincrementalAlterResponseEmptyV1 = []byte{\n\t\t0, 0, 0, 0, // throttle\n\t\t1, // no configs\n\t\t0, // empty tagged fields\n\t}\n\n\tincrementalAlterResponsePopulatedV1 = []byte{\n\t\t0, 0, 0, 0, // throttle\n\t\t2,    // response\n\t\t0, 0, // errorcode\n\t\t1, // empty string\n\t\t2, // topic\n\t\t4, 'f', 'o', 'o',\n\t\t0, // empty tagged fields\n\t\t0, // empty tagged fields\n\t}\n\n\tincrementalAlterConfigsResponseBrokerV1 = []byte{\n\t\t0, 0, 0, 0, // throttle time\n\t\t2,    // 1+1 response\n\t\t0, 0, // error code\n\t\t0,      // null string\n\t\t4,      // broker resource\n\t\t2, '1', // broker\n\t\t0, // empty tagged fields\n\t\t0, // empty tagged fields\n\t}\n)\n\nfunc TestIncrementalAlterConfigsResponse(t *testing.T) {\n\tvar response *IncrementalAlterConfigsResponse\n\n\tresponse = &IncrementalAlterConfigsResponse{\n\t\tResources: []*AlterConfigsResourceResponse{},\n\t}\n\ttestVersionDecodable(t, \"empty\", response, incrementalAlterResponseEmpty, 0)\n\tif len(response.Resources) != 0 {\n\t\tt.Error(\"Expected no groups\")\n\t}\n\n\tresponse = &IncrementalAlterConfigsResponse{\n\t\tResources: []*AlterConfigsResourceResponse{\n\t\t\t{\n\t\t\t\tErrorCode: 0,\n\t\t\t\tErrorMsg:  \"\",\n\t\t\t\tType:      TopicResource,\n\t\t\t\tName:      \"foo\",\n\t\t\t},\n\t\t},\n\t}\n\ttestResponse(t, \"response with error\", response, incrementalAlterResponsePopulated)\n}\n\nfunc TestIncrementalAlterConfigsResponseV1(t *testing.T) {\n\tvar response *IncrementalAlterConfigsResponse\n\n\tresponse = &IncrementalAlterConfigsResponse{\n\t\tResources: []*AlterConfigsResourceResponse{},\n\t}\n\ttestVersionDecodable(t, \"empty\", response, incrementalAlterResponseEmptyV1, 1)\n\tif len(response.Resources) != 0 {\n\t\tt.Error(\"Expected no groups\")\n\t}\n\n\tresponse = &IncrementalAlterConfigsResponse{\n\t\tResources: []*AlterConfigsResourceResponse{\n\t\t\t{\n\t\t\t\tErrorCode: 0,\n\t\t\t\tErrorMsg:  \"\",\n\t\t\t\tType:      TopicResource,\n\t\t\t\tName:      \"foo\",\n\t\t\t},\n\t\t},\n\t}\n\ttestVersionDecodable(t, \"response with error\", response, incrementalAlterResponsePopulatedV1, 1)\n\n\tresponse = &IncrementalAlterConfigsResponse{\n\t\tResources: []*AlterConfigsResourceResponse{\n\t\t\t{\n\t\t\t\tErrorCode: 0,\n\t\t\t\tErrorMsg:  \"\",\n\t\t\t\tType:      BrokerResource,\n\t\t\t\tName:      \"1\",\n\t\t\t},\n\t\t},\n\t}\n\ttestVersionDecodable(t, \"response with error\", response, incrementalAlterConfigsResponseBrokerV1, 1)\n}\n"
  },
  {
    "path": "init_producer_id_request.go",
    "content": "package sarama\n\nimport \"time\"\n\ntype InitProducerIDRequest struct {\n\tVersion            int16\n\tTransactionalID    *string\n\tTransactionTimeout time.Duration\n\tProducerID         int64\n\tProducerEpoch      int16\n}\n\nfunc (i *InitProducerIDRequest) setVersion(v int16) {\n\ti.Version = v\n}\n\nfunc (i *InitProducerIDRequest) encode(pe packetEncoder) error {\n\tif err := pe.putNullableString(i.TransactionalID); err != nil {\n\t\treturn err\n\t}\n\tpe.putInt32(int32(i.TransactionTimeout / time.Millisecond))\n\tif i.Version >= 3 {\n\t\tpe.putInt64(i.ProducerID)\n\t\tpe.putInt16(i.ProducerEpoch)\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (i *InitProducerIDRequest) decode(pd packetDecoder, version int16) (err error) {\n\ti.Version = version\n\tif i.TransactionalID, err = pd.getNullableString(); err != nil {\n\t\treturn err\n\t}\n\n\ttimeout, err := pd.getInt32()\n\tif err != nil {\n\t\treturn err\n\t}\n\ti.TransactionTimeout = time.Duration(timeout) * time.Millisecond\n\tif i.Version >= 3 {\n\t\tif i.ProducerID, err = pd.getInt64(); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif i.ProducerEpoch, err = pd.getInt16(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (i *InitProducerIDRequest) key() int16 {\n\treturn apiKeyInitProducerId\n}\n\nfunc (i *InitProducerIDRequest) version() int16 {\n\treturn i.Version\n}\n\nfunc (i *InitProducerIDRequest) headerVersion() int16 {\n\tif i.Version >= 2 {\n\t\treturn 2\n\t}\n\n\treturn 1\n}\n\nfunc (i *InitProducerIDRequest) isValidVersion() bool {\n\treturn i.Version >= 0 && i.Version <= 4\n}\n\nfunc (i *InitProducerIDRequest) isFlexible() bool {\n\treturn i.isFlexibleVersion(i.Version)\n}\n\nfunc (i *InitProducerIDRequest) isFlexibleVersion(version int16) bool {\n\treturn version >= 2\n}\n\nfunc (i *InitProducerIDRequest) requiredVersion() KafkaVersion {\n\tswitch i.Version {\n\tcase 4:\n\t\treturn V2_7_0_0\n\tcase 3:\n\t\treturn V2_5_0_0\n\tcase 2:\n\t\treturn V2_4_0_0\n\tcase 1:\n\t\treturn V2_0_0_0\n\tcase 0:\n\t\treturn V0_11_0_0\n\tdefault:\n\t\treturn V2_7_0_0\n\t}\n}\n"
  },
  {
    "path": "init_producer_id_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nvar (\n\tinitProducerIDRequestNull = []byte{\n\t\t255, 255,\n\t\t0, 0, 0, 100,\n\t}\n\n\tinitProducerIDRequest = []byte{\n\t\t0, 3, 't', 'x', 'n',\n\t\t0, 0, 0, 100,\n\t}\n\n\tinitProducerIDRequestTaggedFields = []byte{\n\t\t4, 116, 120, 110, // TransactionID in compact string\n\t\t0, 0, 0, 100, // TransactionTimeout\n\t\t0, // empty TaggedFields\n\t}\n\n\tinitProducerIDRequestProducerId = []byte{\n\t\t4, 116, 120, 110, // TransactionID in compact string\n\t\t0, 0, 0, 100, // TransactionTimeout\n\t\t0, 0, 0, 0, 0, 0, 0, 123, // ProducerID\n\t\t1, 65, // ProducerEpoch\n\t\t0, // empty TaggedFields\n\t}\n)\n\nfunc TestInitProducerIDRequest(t *testing.T) {\n\treq := &InitProducerIDRequest{\n\t\tTransactionTimeout: 100 * time.Millisecond,\n\t}\n\n\ttestRequest(t, \"null transaction id\", req, initProducerIDRequestNull)\n\n\ttransactionID := \"txn\"\n\treq.TransactionalID = &transactionID\n\n\ttestRequest(t, \"transaction id\", req, initProducerIDRequest)\n\n\treq.Version = 2\n\ttestRequest(t, \"tagged fields\", req, initProducerIDRequestTaggedFields)\n\n\treq.Version = 3\n\treq.ProducerID = 123\n\treq.ProducerEpoch = 321\n\n\ttestRequest(t, \"producer id\", req, initProducerIDRequestProducerId)\n}\n"
  },
  {
    "path": "init_producer_id_response.go",
    "content": "package sarama\n\nimport \"time\"\n\ntype InitProducerIDResponse struct {\n\tThrottleTime  time.Duration\n\tErr           KError\n\tVersion       int16\n\tProducerID    int64\n\tProducerEpoch int16\n}\n\nfunc (i *InitProducerIDResponse) setVersion(v int16) {\n\ti.Version = v\n}\n\nfunc (i *InitProducerIDResponse) encode(pe packetEncoder) error {\n\tpe.putDurationMs(i.ThrottleTime)\n\tpe.putKError(i.Err)\n\tpe.putInt64(i.ProducerID)\n\tpe.putInt16(i.ProducerEpoch)\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (i *InitProducerIDResponse) decode(pd packetDecoder, version int16) (err error) {\n\ti.Version = version\n\tif i.ThrottleTime, err = pd.getDurationMs(); err != nil {\n\t\treturn err\n\t}\n\n\ti.Err, err = pd.getKError()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif i.ProducerID, err = pd.getInt64(); err != nil {\n\t\treturn err\n\t}\n\n\tif i.ProducerEpoch, err = pd.getInt16(); err != nil {\n\t\treturn err\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (i *InitProducerIDResponse) key() int16 {\n\treturn apiKeyInitProducerId\n}\n\nfunc (i *InitProducerIDResponse) version() int16 {\n\treturn i.Version\n}\n\nfunc (i *InitProducerIDResponse) headerVersion() int16 {\n\tif i.Version >= 2 {\n\t\treturn 1\n\t}\n\treturn 0\n}\n\nfunc (i *InitProducerIDResponse) isValidVersion() bool {\n\treturn i.Version >= 0 && i.Version <= 4\n}\n\nfunc (i *InitProducerIDResponse) isFlexible() bool {\n\treturn i.isFlexibleVersion(i.Version)\n}\n\nfunc (i *InitProducerIDResponse) isFlexibleVersion(version int16) bool {\n\treturn version >= 2\n}\n\nfunc (i *InitProducerIDResponse) requiredVersion() KafkaVersion {\n\tswitch i.Version {\n\tcase 4:\n\t\treturn V2_7_0_0\n\tcase 3:\n\t\treturn V2_5_0_0\n\tcase 2:\n\t\treturn V2_4_0_0\n\tcase 1:\n\t\treturn V2_0_0_0\n\tdefault:\n\t\treturn V0_11_0_0\n\t}\n}\n\nfunc (r *InitProducerIDResponse) throttleTime() time.Duration {\n\treturn r.ThrottleTime\n}\n"
  },
  {
    "path": "init_producer_id_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nvar (\n\tinitProducerIDResponse = []byte{\n\t\t0, 0, 0, 100,\n\t\t0, 0,\n\t\t0, 0, 0, 0, 0, 0, 31, 64, // producerID = 8000\n\t\t0, 0, // epoch\n\t}\n\n\tinitProducerIDRequestError = []byte{\n\t\t0, 0, 0, 100,\n\t\t0, 51,\n\t\t255, 255, 255, 255, 255, 255, 255, 255,\n\t\t0, 0,\n\t}\n\n\tinitProducerIdResponseWithTaggedFields = []byte{\n\t\t0, 0, 0, 100,\n\t\t0, 51,\n\t\t255, 255, 255, 255, 255, 255, 255, 255,\n\t\t0, 0,\n\t\t0,\n\t}\n)\n\nfunc TestInitProducerIDResponse(t *testing.T) {\n\tresp := &InitProducerIDResponse{\n\t\tThrottleTime:  100 * time.Millisecond,\n\t\tProducerID:    8000,\n\t\tProducerEpoch: 0,\n\t}\n\n\ttestResponse(t, \"\", resp, initProducerIDResponse)\n\n\tresp.Err = ErrConcurrentTransactions\n\tresp.ProducerID = -1\n\n\ttestResponse(t, \"with error\", resp, initProducerIDRequestError)\n\n\tresp.Version = 2\n\ttestResponse(t, \"with tagged fields\", resp, initProducerIdResponseWithTaggedFields)\n}\n"
  },
  {
    "path": "interceptors.go",
    "content": "package sarama\n\n// ProducerInterceptor allows you to intercept (and possibly mutate) the records\n// received by the producer before they are published to the Kafka cluster.\n// https://cwiki.apache.org/confluence/display/KAFKA/KIP-42%3A+Add+Producer+and+Consumer+Interceptors#KIP42:AddProducerandConsumerInterceptors-Motivation\ntype ProducerInterceptor interface {\n\n\t// OnSend is called when the producer message is intercepted. Please avoid\n\t// modifying the message until it's safe to do so, as this is _not_ a copy\n\t// of the message.\n\tOnSend(*ProducerMessage)\n}\n\n// ConsumerInterceptor allows you to intercept (and possibly mutate) the records\n// received by the consumer before they are sent to the messages channel.\n// https://cwiki.apache.org/confluence/display/KAFKA/KIP-42%3A+Add+Producer+and+Consumer+Interceptors#KIP42:AddProducerandConsumerInterceptors-Motivation\ntype ConsumerInterceptor interface {\n\n\t// OnConsume is called when the consumed message is intercepted. Please\n\t// avoid modifying the message until it's safe to do so, as this is _not_ a\n\t// copy of the message.\n\tOnConsume(*ConsumerMessage)\n}\n\nfunc (msg *ProducerMessage) safelyApplyInterceptor(interceptor ProducerInterceptor) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tLogger.Printf(\"Error when calling producer interceptor: %v, %v\", interceptor, r)\n\t\t}\n\t}()\n\n\tinterceptor.OnSend(msg)\n}\n\nfunc (msg *ConsumerMessage) safelyApplyInterceptor(interceptor ConsumerInterceptor) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tLogger.Printf(\"Error when calling consumer interceptor: %v, %v\", interceptor, r)\n\t\t}\n\t}()\n\n\tinterceptor.OnConsume(msg)\n}\n"
  },
  {
    "path": "internal/toxiproxy/README.md",
    "content": "# toxiproxy\n\nA minimal client implementation to setup proxies and toxics in toxiproxy as used in the FV suite.\nWe have our own minimal client implementation to avoid having to pull in the toxiproxy repo which carries a number of transitive dependencies.\n"
  },
  {
    "path": "internal/toxiproxy/client.go",
    "content": "package toxiproxy\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"time\"\n)\n\ntype Client struct {\n\thttpClient *http.Client\n\tendpoint   string\n}\n\nfunc NewClient(endpoint string) *Client {\n\treturn &Client{\n\t\thttpClient: &http.Client{\n\t\t\tTransport: &http.Transport{\n\t\t\t\tProxy: http.ProxyFromEnvironment,\n\t\t\t\tDialContext: (&net.Dialer{\n\t\t\t\t\tTimeout:   30 * time.Second,\n\t\t\t\t\tKeepAlive: 30 * time.Second,\n\t\t\t\t}).DialContext,\n\t\t\t\tForceAttemptHTTP2:     true,\n\t\t\t\tMaxIdleConns:          -1,\n\t\t\t\tDisableKeepAlives:     true,\n\t\t\t\tIdleConnTimeout:       90 * time.Second,\n\t\t\t\tTLSHandshakeTimeout:   10 * time.Second,\n\t\t\t\tExpectContinueTimeout: 1 * time.Second,\n\t\t\t},\n\t\t},\n\t\tendpoint: endpoint,\n\t}\n}\n\nfunc (c *Client) CreateProxy(\n\tname string,\n\tlistenAddr string,\n\ttargetAddr string,\n) (*Proxy, error) {\n\tproxy := &Proxy{\n\t\tName:       name,\n\t\tListenAddr: listenAddr,\n\t\tTargetAddr: targetAddr,\n\t\tEnabled:    true,\n\t\tclient:     c,\n\t}\n\treturn proxy.Save()\n}\n\nfunc (c *Client) Proxy(name string) (*Proxy, error) {\n\treq, err := http.NewRequest(\"GET\", c.endpoint+\"/proxies/\"+name, nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to make proxy request: %w\", err)\n\t}\n\tresp, err := c.httpClient.Do(req) // #nosec G704 -- toxiproxy endpoint is controlled test infrastructure.\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to http get proxy: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != 200 {\n\t\tbody, _ := io.ReadAll(resp.Body)\n\t\treturn nil, fmt.Errorf(\"error getting proxy %s: %s %s\", name, resp.Status, body)\n\t}\n\n\tvar p Proxy\n\tif err := json.NewDecoder(resp.Body).Decode(&p); err != nil {\n\t\treturn nil, fmt.Errorf(\"error decoding json for proxy %s: %w\", name, err)\n\t}\n\tp.client = c\n\n\treturn &p, nil\n}\n\nfunc (c *Client) ResetState() error {\n\treq, err := http.NewRequest(\"POST\", c.endpoint+\"/reset\", http.NoBody)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to make reset request: %w\", err)\n\t}\n\tresp, err := c.httpClient.Do(req) // #nosec G704 -- toxiproxy endpoint is controlled test infrastructure.\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to http post reset: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != 204 {\n\t\tbody, _ := io.ReadAll(resp.Body)\n\t\treturn fmt.Errorf(\"error resetting proxies: %s %s\", resp.Status, body)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/toxiproxy/proxy.go",
    "content": "package toxiproxy\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n)\n\ntype Proxy struct {\n\tclient     *Client\n\tName       string `json:\"name\"`\n\tListenAddr string `json:\"listen\"`\n\tTargetAddr string `json:\"upstream\"`\n\tEnabled    bool   `json:\"enabled\"`\n}\n\ntype Attributes map[string]int\n\nfunc (p *Proxy) AddToxic(\n\tname string,\n\ttoxicType string,\n\tstream string,\n\ttoxicity float32,\n\tattributes Attributes,\n) (*Toxic, error) {\n\ttoxic := &Toxic{\n\t\tName:       name,\n\t\tType:       toxicType,\n\t\tStream:     stream,\n\t\tToxicity:   toxicity,\n\t\tAttributes: attributes,\n\t}\n\tvar b bytes.Buffer\n\tif err := json.NewEncoder(&b).Encode(&toxic); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to json encode toxic: %w\", err)\n\t}\n\tbody := bytes.NewReader(b.Bytes())\n\n\tc := p.client\n\treq, err := http.NewRequest(\"POST\", c.endpoint+\"/proxies/\"+p.Name+\"/toxics\", body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to make post toxic request: %w\", err)\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\tresp, err := c.httpClient.Do(req) // #nosec G704 -- toxiproxy endpoint is controlled test infrastructure.\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to http post toxic: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != 200 {\n\t\tbody, _ := io.ReadAll(resp.Body)\n\t\treturn nil, fmt.Errorf(\"error creating toxic %s: %s %s\", name, resp.Status, body)\n\t}\n\n\treturn toxic, nil\n}\n\nfunc (p *Proxy) Enable() error {\n\tp.Enabled = true\n\t_, err := p.Save()\n\treturn err\n}\n\nfunc (p *Proxy) Disable() error {\n\tp.Enabled = false\n\t_, err := p.Save()\n\treturn err\n}\n\nfunc (p *Proxy) Save() (*Proxy, error) {\n\tvar b bytes.Buffer\n\tif err := json.NewEncoder(&b).Encode(&p); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to json encode proxy: %w\", err)\n\t}\n\tbody := bytes.NewReader(b.Bytes())\n\n\tc := p.client\n\treq, err := http.NewRequest(\"POST\", c.endpoint+\"/proxies/\"+p.Name, body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to make post proxy request: %w\", err)\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\tresp, err := c.httpClient.Do(req) // #nosec G704 -- toxiproxy endpoint is controlled test infrastructure.\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to http post proxy: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode == 404 {\n\t\tif _, err := body.Seek(0, io.SeekStart); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to rewind post body: %w\", err)\n\t\t}\n\t\treq, err = http.NewRequest(\"POST\", c.endpoint+\"/proxies\", body)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to make post proxy request: %w\", err)\n\t\t}\n\t\treq.Header.Set(\"Content-Type\", \"application/json\")\n\t\tresp, err = c.httpClient.Do(req) // #nosec G704 -- toxiproxy endpoint is controlled test infrastructure.\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to http post proxy: %w\", err)\n\t\t}\n\t\tdefer resp.Body.Close()\n\t}\n\n\tif resp.StatusCode != 200 && resp.StatusCode != 201 {\n\t\tbody, _ := io.ReadAll(resp.Body)\n\t\treturn nil, fmt.Errorf(\"error saving proxy: %s %s\", resp.Status, body)\n\t}\n\n\treturn p, nil\n}\n"
  },
  {
    "path": "internal/toxiproxy/toxic.go",
    "content": "package toxiproxy\n\ntype Toxic struct {\n\tName       string     `json:\"name\"`\n\tType       string     `json:\"type\"`\n\tStream     string     `json:\"stream,omitempty\"`\n\tToxicity   float32    `json:\"toxicity\"`\n\tAttributes Attributes `json:\"attributes\"`\n}\n"
  },
  {
    "path": "join_group_request.go",
    "content": "package sarama\n\ntype GroupProtocol struct {\n\t// Name contains the protocol name.\n\tName string\n\t// Metadata contains the protocol metadata.\n\tMetadata []byte\n}\n\nfunc (p *GroupProtocol) decode(pd packetDecoder) (err error) {\n\tp.Name, err = pd.getString()\n\tif err != nil {\n\t\treturn err\n\t}\n\tp.Metadata, err = pd.getBytes()\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (p *GroupProtocol) encode(pe packetEncoder) (err error) {\n\tif err := pe.putString(p.Name); err != nil {\n\t\treturn err\n\t}\n\tif err := pe.putBytes(p.Metadata); err != nil {\n\t\treturn err\n\t}\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\ntype JoinGroupRequest struct {\n\t// Version defines the protocol version to use for encode and decode\n\tVersion int16\n\t// GroupId contains the group identifier.\n\tGroupId string\n\t// SessionTimeout specifies that the coordinator should consider the consumer\n\t// dead if it receives no heartbeat after this timeout in milliseconds.\n\tSessionTimeout int32\n\t// RebalanceTimeout contains the maximum time in milliseconds that the\n\t// coordinator will wait for each member to rejoin when rebalancing the\n\t// group.\n\tRebalanceTimeout int32\n\t// MemberId contains the member id assigned by the group coordinator.\n\tMemberId string\n\t// GroupInstanceId contains the unique identifier of the consumer instance\n\t// provided by end user.\n\tGroupInstanceId *string\n\t// ProtocolType contains the unique name the for class of protocols\n\t// implemented by the group we want to join.\n\tProtocolType string\n\t// GroupProtocols contains the list of protocols that the member supports.\n\t// deprecated; use OrderedGroupProtocols\n\tGroupProtocols map[string][]byte\n\t// OrderedGroupProtocols contains an ordered list of protocols that the member\n\t// supports.\n\tOrderedGroupProtocols []*GroupProtocol\n}\n\nfunc (r *JoinGroupRequest) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *JoinGroupRequest) encode(pe packetEncoder) error {\n\tif err := pe.putString(r.GroupId); err != nil {\n\t\treturn err\n\t}\n\tpe.putInt32(r.SessionTimeout)\n\tif r.Version >= 1 {\n\t\tpe.putInt32(r.RebalanceTimeout)\n\t}\n\tif err := pe.putString(r.MemberId); err != nil {\n\t\treturn err\n\t}\n\tif r.Version >= 5 {\n\t\tif err := pe.putNullableString(r.GroupInstanceId); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif err := pe.putString(r.ProtocolType); err != nil {\n\t\treturn err\n\t}\n\n\tif len(r.GroupProtocols) > 0 {\n\t\tif len(r.OrderedGroupProtocols) > 0 {\n\t\t\treturn PacketEncodingError{\"cannot specify both GroupProtocols and OrderedGroupProtocols on JoinGroupRequest\"}\n\t\t}\n\n\t\tif err := pe.putArrayLength(len(r.GroupProtocols)); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor name, metadata := range r.GroupProtocols {\n\t\t\tif err := pe.putString(name); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := pe.putBytes(metadata); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tpe.putEmptyTaggedFieldArray()\n\t\t}\n\t} else {\n\t\tif err := pe.putArrayLength(len(r.OrderedGroupProtocols)); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor _, protocol := range r.OrderedGroupProtocols {\n\t\t\tif err := protocol.encode(pe); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (r *JoinGroupRequest) decode(pd packetDecoder, version int16) (err error) {\n\tr.Version = version\n\n\tif r.GroupId, err = pd.getString(); err != nil {\n\t\treturn\n\t}\n\n\tif r.SessionTimeout, err = pd.getInt32(); err != nil {\n\t\treturn\n\t}\n\n\tif version >= 1 {\n\t\tif r.RebalanceTimeout, err = pd.getInt32(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif r.MemberId, err = pd.getString(); err != nil {\n\t\treturn\n\t}\n\n\tif version >= 5 {\n\t\tif r.GroupInstanceId, err = pd.getNullableString(); err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif r.ProtocolType, err = pd.getString(); err != nil {\n\t\treturn\n\t}\n\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif n == 0 {\n\t\treturn nil\n\t}\n\n\tr.GroupProtocols = make(map[string][]byte)\n\tfor i := 0; i < n; i++ {\n\t\tprotocol := &GroupProtocol{}\n\t\tif err := protocol.decode(pd); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.GroupProtocols[protocol.Name] = protocol.Metadata\n\t\tr.OrderedGroupProtocols = append(r.OrderedGroupProtocols, protocol)\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (r *JoinGroupRequest) key() int16 {\n\treturn apiKeyJoinGroup\n}\n\nfunc (r *JoinGroupRequest) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *JoinGroupRequest) headerVersion() int16 {\n\tif r.Version >= 6 {\n\t\treturn 2\n\t}\n\treturn 1\n}\n\nfunc (r *JoinGroupRequest) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 6\n}\n\nfunc (r *JoinGroupRequest) isFlexible() bool {\n\treturn r.isFlexibleVersion(r.Version)\n}\n\nfunc (r *JoinGroupRequest) isFlexibleVersion(version int16) bool {\n\treturn version >= 6\n}\n\nfunc (r *JoinGroupRequest) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 6:\n\t\treturn V2_4_0_0\n\tcase 5:\n\t\treturn V2_3_0_0\n\tcase 4:\n\t\treturn V2_2_0_0\n\tcase 3:\n\t\treturn V2_0_0_0\n\tcase 2:\n\t\treturn V0_11_0_0\n\tcase 1:\n\t\treturn V0_10_1_0\n\tcase 0:\n\t\treturn V0_10_0_0\n\tdefault:\n\t\treturn V2_3_0_0\n\t}\n}\n\nfunc (r *JoinGroupRequest) AddGroupProtocol(name string, metadata []byte) {\n\tr.OrderedGroupProtocols = append(r.OrderedGroupProtocols, &GroupProtocol{\n\t\tName:     name,\n\t\tMetadata: metadata,\n\t})\n}\n\nfunc (r *JoinGroupRequest) AddGroupProtocolMetadata(name string, metadata *ConsumerGroupMemberMetadata) error {\n\tbin, err := encode(metadata, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr.AddGroupProtocol(name, bin)\n\treturn nil\n}\n"
  },
  {
    "path": "join_group_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nvar (\n\tjoinGroupRequestV0_NoProtocols = []byte{\n\t\t0, 9, 'T', 'e', 's', 't', 'G', 'r', 'o', 'u', 'p', // Group ID\n\t\t0, 0, 0, 100, // Session timeout\n\t\t0, 0, // Member ID\n\t\t0, 8, 'c', 'o', 'n', 's', 'u', 'm', 'e', 'r', // Protocol Type\n\t\t0, 0, 0, 0, // 0 protocol groups\n\t}\n\n\tjoinGroupRequestV0_OneProtocol = []byte{\n\t\t0, 9, 'T', 'e', 's', 't', 'G', 'r', 'o', 'u', 'p', // Group ID\n\t\t0, 0, 0, 100, // Session timeout\n\t\t0, 11, 'O', 'n', 'e', 'P', 'r', 'o', 't', 'o', 'c', 'o', 'l', // Member ID\n\t\t0, 8, 'c', 'o', 'n', 's', 'u', 'm', 'e', 'r', // Protocol Type\n\t\t0, 0, 0, 1, // 1 group protocol\n\t\t0, 3, 'o', 'n', 'e', // Protocol name\n\t\t0, 0, 0, 3, 0x01, 0x02, 0x03, // protocol metadata\n\t}\n\n\tjoinGroupRequestV1 = []byte{\n\t\t0, 9, 'T', 'e', 's', 't', 'G', 'r', 'o', 'u', 'p', // Group ID\n\t\t0, 0, 0, 100, // Session timeout\n\t\t0, 0, 0, 200, // Rebalance timeout\n\t\t0, 11, 'O', 'n', 'e', 'P', 'r', 'o', 't', 'o', 'c', 'o', 'l', // Member ID\n\t\t0, 8, 'c', 'o', 'n', 's', 'u', 'm', 'e', 'r', // Protocol Type\n\t\t0, 0, 0, 1, // 1 group protocol\n\t\t0, 3, 'o', 'n', 'e', // Protocol name\n\t\t0, 0, 0, 3, 0x01, 0x02, 0x03, // protocol metadata\n\t}\n)\n\nfunc TestJoinGroupRequest(t *testing.T) {\n\trequest := new(JoinGroupRequest)\n\trequest.GroupId = \"TestGroup\"\n\trequest.SessionTimeout = 100\n\trequest.ProtocolType = \"consumer\"\n\ttestRequest(t, \"V0: no protocols\", request, joinGroupRequestV0_NoProtocols)\n}\n\nfunc TestJoinGroupRequestV0_OneProtocol(t *testing.T) {\n\trequest := new(JoinGroupRequest)\n\trequest.GroupId = \"TestGroup\"\n\trequest.SessionTimeout = 100\n\trequest.MemberId = \"OneProtocol\"\n\trequest.ProtocolType = \"consumer\"\n\trequest.AddGroupProtocol(\"one\", []byte{0x01, 0x02, 0x03})\n\tpacket := testRequestEncode(t, \"V0: one protocol\", request, joinGroupRequestV0_OneProtocol)\n\trequest.GroupProtocols = make(map[string][]byte)\n\trequest.GroupProtocols[\"one\"] = []byte{0x01, 0x02, 0x03}\n\ttestRequestDecode(t, \"V0: one protocol\", request, packet)\n}\n\nfunc TestJoinGroupRequestDeprecatedEncode(t *testing.T) {\n\trequest := new(JoinGroupRequest)\n\trequest.GroupId = \"TestGroup\"\n\trequest.SessionTimeout = 100\n\trequest.MemberId = \"OneProtocol\"\n\trequest.ProtocolType = \"consumer\"\n\trequest.GroupProtocols = make(map[string][]byte)\n\trequest.GroupProtocols[\"one\"] = []byte{0x01, 0x02, 0x03}\n\tpacket := testRequestEncode(t, \"V0: one protocol\", request, joinGroupRequestV0_OneProtocol)\n\trequest.AddGroupProtocol(\"one\", []byte{0x01, 0x02, 0x03})\n\ttestRequestDecode(t, \"V0: one protocol\", request, packet)\n}\n\nfunc TestJoinGroupRequestV1(t *testing.T) {\n\trequest := new(JoinGroupRequest)\n\trequest.Version = 1\n\trequest.GroupId = \"TestGroup\"\n\trequest.SessionTimeout = 100\n\trequest.RebalanceTimeout = 200\n\trequest.MemberId = \"OneProtocol\"\n\trequest.ProtocolType = \"consumer\"\n\trequest.AddGroupProtocol(\"one\", []byte{0x01, 0x02, 0x03})\n\tpacket := testRequestEncode(t, \"V1\", request, joinGroupRequestV1)\n\trequest.GroupProtocols = make(map[string][]byte)\n\trequest.GroupProtocols[\"one\"] = []byte{0x01, 0x02, 0x03}\n\ttestRequestDecode(t, \"V1\", request, packet)\n}\n\nvar (\n\tjoinGroupRequestV5 = []byte{\n\t\t0, 9, 'T', 'e', 's', 't', 'G', 'r', 'o', 'u', 'p', // Group ID\n\t\t0, 0, 0, 100, // Session timeout\n\t\t0, 0, 0, 200, // Rebalance timeout\n\t\t0, 11, 'O', 'n', 'e', 'P', 'r', 'o', 't', 'o', 'c', 'o', 'l', // Member ID\n\t\t0, 3, 'g', 'i', 'd', // GroupInstanceId\n\t\t0, 8, 'c', 'o', 'n', 's', 'u', 'm', 'e', 'r', // Protocol Type\n\t\t0, 0, 0, 1, // 1 group protocol\n\t\t0, 3, 'o', 'n', 'e', // Protocol name\n\t\t0, 0, 0, 3, 0x01, 0x02, 0x03, // protocol metadata\n\t}\n\n\tjoinGroupRequestV6 = []byte{\n\t\t10, 'T', 'e', 's', 't', 'G', 'r', 'o', 'u', 'p', // Group ID\n\t\t0, 0, 0, 100, // Session timeout\n\t\t0, 0, 0, 200, // Rebalance timeout\n\t\t12, 'O', 'n', 'e', 'P', 'r', 'o', 't', 'o', 'c', 'o', 'l', // Member ID\n\t\t4, 'g', 'i', 'd', // GroupInstanceId\n\t\t9, 'c', 'o', 'n', 's', 'u', 'm', 'e', 'r', // Protocol Type\n\t\t2,                // 1 group protocol\n\t\t4, 'o', 'n', 'e', // Protocol name\n\t\t4, 0x01, 0x02, 0x03, // protocol metadata\n\t\t0, // empty tagged fields\n\t\t0, // empty tagged fields\n\t}\n)\n\nfunc TestJoinGroupRequestV3plus(t *testing.T) {\n\tgroupInstanceId := \"gid\"\n\ttests := []struct {\n\t\tCaseName     string\n\t\tVersion      int16\n\t\tMessageBytes []byte\n\t\tMessage      *JoinGroupRequest\n\t}{\n\t\t{\n\t\t\t\"v5\",\n\t\t\t5,\n\t\t\tjoinGroupRequestV5,\n\t\t\t&JoinGroupRequest{\n\t\t\t\tVersion:          5,\n\t\t\t\tGroupId:          \"TestGroup\",\n\t\t\t\tSessionTimeout:   100,\n\t\t\t\tRebalanceTimeout: 200,\n\t\t\t\tMemberId:         \"OneProtocol\",\n\t\t\t\tGroupInstanceId:  &groupInstanceId,\n\t\t\t\tProtocolType:     \"consumer\",\n\t\t\t\tGroupProtocols: map[string][]byte{\n\t\t\t\t\t\"one\": {1, 2, 3},\n\t\t\t\t},\n\t\t\t\tOrderedGroupProtocols: []*GroupProtocol{\n\t\t\t\t\t{Name: \"one\", Metadata: []byte{1, 2, 3}},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"v6\",\n\t\t\t6,\n\t\t\tjoinGroupRequestV6,\n\t\t\t&JoinGroupRequest{\n\t\t\t\tVersion:          6,\n\t\t\t\tGroupId:          \"TestGroup\",\n\t\t\t\tSessionTimeout:   100,\n\t\t\t\tRebalanceTimeout: 200,\n\t\t\t\tMemberId:         \"OneProtocol\",\n\t\t\t\tGroupInstanceId:  &groupInstanceId,\n\t\t\t\tProtocolType:     \"consumer\",\n\t\t\t\tGroupProtocols: map[string][]byte{\n\t\t\t\t\t\"one\": {1, 2, 3},\n\t\t\t\t},\n\t\t\t\tOrderedGroupProtocols: []*GroupProtocol{\n\t\t\t\t\t{Name: \"one\", Metadata: []byte{1, 2, 3}},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tfor _, c := range tests {\n\t\trequest := new(JoinGroupRequest)\n\t\ttestVersionDecodable(t, c.CaseName, request, c.MessageBytes, c.Version)\n\t\tif !reflect.DeepEqual(c.Message, request) {\n\t\t\tt.Errorf(\"case %s decode failed, expected:%+v got %+v\", c.CaseName, c.Message, request)\n\t\t}\n\t\t// This is to avoid error check \"cannot specify both GroupProtocols and OrderedGroupProtocols on JoinGroupRequest\"\n\t\tc.Message.GroupProtocols = nil\n\t\ttestEncodable(t, c.CaseName, c.Message, c.MessageBytes)\n\t}\n}\n"
  },
  {
    "path": "join_group_response.go",
    "content": "package sarama\n\nimport \"time\"\n\ntype JoinGroupResponse struct {\n\t// Version defines the protocol version to use for encode and decode\n\tVersion int16\n\t// ThrottleTime contains the duration for which the request was throttled due\n\t// to a quota violation, or zero if the request did not violate any quota.\n\tThrottleTime int32\n\t// Err contains the error code, or 0 if there was no error.\n\tErr KError\n\t// GenerationId contains the generation ID of the group.\n\tGenerationId int32\n\t// GroupProtocol contains the group protocol selected by the coordinator.\n\tGroupProtocol string\n\t// LeaderId contains the leader of the group.\n\tLeaderId string\n\t// MemberId contains the member ID assigned by the group coordinator.\n\tMemberId string\n\t// Members contains the per-group-member information.\n\tMembers []GroupMember\n}\n\nfunc (r *JoinGroupResponse) setVersion(v int16) {\n\tr.Version = v\n}\n\ntype GroupMember struct {\n\t// MemberId contains the group member ID.\n\tMemberId string\n\t// GroupInstanceId contains the unique identifier of the consumer instance\n\t// provided by end user.\n\tGroupInstanceId *string\n\t// Metadata contains the group member metadata.\n\tMetadata []byte\n}\n\nfunc (r *JoinGroupResponse) GetMembers() (map[string]ConsumerGroupMemberMetadata, error) {\n\tmembers := make(map[string]ConsumerGroupMemberMetadata, len(r.Members))\n\tfor _, member := range r.Members {\n\t\tmeta := new(ConsumerGroupMemberMetadata)\n\t\tif err := decode(member.Metadata, meta, nil); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tmembers[member.MemberId] = *meta\n\t}\n\treturn members, nil\n}\n\nfunc (r *JoinGroupResponse) encode(pe packetEncoder) error {\n\tif r.Version >= 2 {\n\t\tpe.putInt32(r.ThrottleTime)\n\t}\n\tpe.putKError(r.Err)\n\tpe.putInt32(r.GenerationId)\n\n\tif err := pe.putString(r.GroupProtocol); err != nil {\n\t\treturn err\n\t}\n\tif err := pe.putString(r.LeaderId); err != nil {\n\t\treturn err\n\t}\n\tif err := pe.putString(r.MemberId); err != nil {\n\t\treturn err\n\t}\n\n\tif err := pe.putArrayLength(len(r.Members)); err != nil {\n\t\treturn err\n\t}\n\n\tfor _, member := range r.Members {\n\t\tif err := pe.putString(member.MemberId); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif r.Version >= 5 {\n\t\t\tif err := pe.putNullableString(member.GroupInstanceId); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif err := pe.putBytes(member.Metadata); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tpe.putEmptyTaggedFieldArray()\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (r *JoinGroupResponse) decode(pd packetDecoder, version int16) (err error) {\n\tr.Version = version\n\n\tif version >= 2 {\n\t\tif r.ThrottleTime, err = pd.getInt32(); err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tr.Err, err = pd.getKError()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif r.GenerationId, err = pd.getInt32(); err != nil {\n\t\treturn\n\t}\n\n\tif r.GroupProtocol, err = pd.getString(); err != nil {\n\t\treturn\n\t}\n\n\tif r.LeaderId, err = pd.getString(); err != nil {\n\t\treturn\n\t}\n\n\tif r.MemberId, err = pd.getString(); err != nil {\n\t\treturn\n\t}\n\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif n == 0 {\n\t\t_, err = pd.getEmptyTaggedFieldArray()\n\t\treturn err\n\t}\n\n\tr.Members = make([]GroupMember, n)\n\tfor i := 0; i < n; i++ {\n\t\tmemberId, err := pd.getString()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tvar groupInstanceId *string = nil\n\t\tif r.Version >= 5 {\n\t\t\tgroupInstanceId, err = pd.getNullableString()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\tmemberMetadata, err := pd.getBytes()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tr.Members[i] = GroupMember{MemberId: memberId, GroupInstanceId: groupInstanceId, Metadata: memberMetadata}\n\n\t\tif _, err := pd.getEmptyTaggedFieldArray(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (r *JoinGroupResponse) key() int16 {\n\treturn apiKeyJoinGroup\n}\n\nfunc (r *JoinGroupResponse) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *JoinGroupResponse) headerVersion() int16 {\n\tif r.Version >= 6 {\n\t\treturn 1\n\t}\n\treturn 0\n}\n\nfunc (r *JoinGroupResponse) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 6\n}\n\nfunc (r *JoinGroupResponse) isFlexible() bool {\n\treturn r.isFlexibleVersion(r.Version)\n}\n\nfunc (r *JoinGroupResponse) isFlexibleVersion(version int16) bool {\n\treturn version >= 6\n}\n\nfunc (r *JoinGroupResponse) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 6:\n\t\treturn V2_4_0_0\n\tcase 5:\n\t\treturn V2_3_0_0\n\tcase 4:\n\t\treturn V2_2_0_0\n\tcase 3:\n\t\treturn V2_0_0_0\n\tcase 2:\n\t\treturn V0_11_0_0\n\tcase 1:\n\t\treturn V0_10_1_0\n\tcase 0:\n\t\treturn V0_10_0_0\n\tdefault:\n\t\treturn V2_3_0_0\n\t}\n}\n\nfunc (r *JoinGroupResponse) throttleTime() time.Duration {\n\treturn time.Duration(r.ThrottleTime) * time.Millisecond\n}\n"
  },
  {
    "path": "join_group_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"errors\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nvar (\n\tjoinGroupResponseV0_NoError = []byte{\n\t\t0x00, 0x00, // No error\n\t\t0x00, 0x01, 0x02, 0x03, // Generation ID\n\t\t0, 8, 'p', 'r', 'o', 't', 'o', 'c', 'o', 'l', // Protocol name chosen\n\t\t0, 3, 'f', 'o', 'o', // Leader ID\n\t\t0, 3, 'b', 'a', 'r', // Member ID\n\t\t0, 0, 0, 0, // No member info\n\t}\n\n\tjoinGroupResponseV0_WithError = []byte{\n\t\t0, 23, // Error: inconsistent group protocol\n\t\t0x00, 0x00, 0x00, 0x00, // Generation ID\n\t\t0, 0, // Protocol name chosen\n\t\t0, 0, // Leader ID\n\t\t0, 0, // Member ID\n\t\t0, 0, 0, 0, // No member info\n\t}\n\n\tjoinGroupResponseV0_Leader = []byte{\n\t\t0x00, 0x00, // No error\n\t\t0x00, 0x01, 0x02, 0x03, // Generation ID\n\t\t0, 8, 'p', 'r', 'o', 't', 'o', 'c', 'o', 'l', // Protocol name chosen\n\t\t0, 3, 'f', 'o', 'o', // Leader ID\n\t\t0, 3, 'f', 'o', 'o', // Member ID == Leader ID\n\t\t0, 0, 0, 1, // 1 member\n\t\t0, 3, 'f', 'o', 'o', // Member ID\n\t\t0, 0, 0, 3, 0x01, 0x02, 0x03, // Member metadata\n\t}\n\n\tjoinGroupResponseV1 = []byte{\n\t\t0x00, 0x00, // No error\n\t\t0x00, 0x01, 0x02, 0x03, // Generation ID\n\t\t0, 8, 'p', 'r', 'o', 't', 'o', 'c', 'o', 'l', // Protocol name chosen\n\t\t0, 3, 'f', 'o', 'o', // Leader ID\n\t\t0, 3, 'b', 'a', 'r', // Member ID\n\t\t0, 0, 0, 0, // No member info\n\t}\n\n\tjoinGroupResponseV2 = []byte{\n\t\t0, 0, 0, 100,\n\t\t0x00, 0x00, // No error\n\t\t0x00, 0x01, 0x02, 0x03, // Generation ID\n\t\t0, 8, 'p', 'r', 'o', 't', 'o', 'c', 'o', 'l', // Protocol name chosen\n\t\t0, 3, 'f', 'o', 'o', // Leader ID\n\t\t0, 3, 'b', 'a', 'r', // Member ID\n\t\t0, 0, 0, 0, // No member info\n\t}\n)\n\nfunc TestJoinGroupResponseV0(t *testing.T) {\n\tvar response *JoinGroupResponse\n\n\tresponse = new(JoinGroupResponse)\n\ttestVersionDecodable(t, \"no error\", response, joinGroupResponseV0_NoError, 0)\n\tif !errors.Is(response.Err, ErrNoError) {\n\t\tt.Error(\"Decoding Err failed: no error expected but found\", response.Err)\n\t}\n\tif response.GenerationId != 66051 {\n\t\tt.Error(\"Decoding GenerationId failed, found:\", response.GenerationId)\n\t}\n\tif response.LeaderId != \"foo\" {\n\t\tt.Error(\"Decoding LeaderId failed, found:\", response.LeaderId)\n\t}\n\tif response.MemberId != \"bar\" {\n\t\tt.Error(\"Decoding MemberId failed, found:\", response.MemberId)\n\t}\n\tif len(response.Members) != 0 {\n\t\tt.Error(\"Decoding Members failed, found:\", response.Members)\n\t}\n\n\tresponse = new(JoinGroupResponse)\n\ttestVersionDecodable(t, \"with error\", response, joinGroupResponseV0_WithError, 0)\n\tif !errors.Is(response.Err, ErrInconsistentGroupProtocol) {\n\t\tt.Error(\"Decoding Err failed: ErrInconsistentGroupProtocol expected but found\", response.Err)\n\t}\n\tif response.GenerationId != 0 {\n\t\tt.Error(\"Decoding GenerationId failed, found:\", response.GenerationId)\n\t}\n\tif response.LeaderId != \"\" {\n\t\tt.Error(\"Decoding LeaderId failed, found:\", response.LeaderId)\n\t}\n\tif response.MemberId != \"\" {\n\t\tt.Error(\"Decoding MemberId failed, found:\", response.MemberId)\n\t}\n\tif len(response.Members) != 0 {\n\t\tt.Error(\"Decoding Members failed, found:\", response.Members)\n\t}\n\n\tresponse = new(JoinGroupResponse)\n\ttestVersionDecodable(t, \"with error\", response, joinGroupResponseV0_Leader, 0)\n\tif !errors.Is(response.Err, ErrNoError) {\n\t\tt.Error(\"Decoding Err failed: ErrNoError expected but found\", response.Err)\n\t}\n\tif response.GenerationId != 66051 {\n\t\tt.Error(\"Decoding GenerationId failed, found:\", response.GenerationId)\n\t}\n\tif response.LeaderId != \"foo\" {\n\t\tt.Error(\"Decoding LeaderId failed, found:\", response.LeaderId)\n\t}\n\tif response.MemberId != \"foo\" {\n\t\tt.Error(\"Decoding MemberId failed, found:\", response.MemberId)\n\t}\n\tif len(response.Members) != 1 {\n\t\tt.Error(\"Decoding Members failed, found:\", response.Members)\n\t}\n\tif response.Members[0].MemberId != \"foo\" {\n\t\tt.Error(\"Decoding MemberId failed, found:\", response.Members[0].MemberId)\n\t}\n\tif !reflect.DeepEqual(response.Members[0].Metadata, []byte{0x01, 0x02, 0x03}) {\n\t\tt.Error(\"Decoding foo member failed, found:\", response.Members[0].Metadata)\n\t}\n}\n\nfunc TestJoinGroupResponseV1(t *testing.T) {\n\tresponse := new(JoinGroupResponse)\n\ttestVersionDecodable(t, \"no error\", response, joinGroupResponseV1, 1)\n\tif !errors.Is(response.Err, ErrNoError) {\n\t\tt.Error(\"Decoding Err failed: no error expected but found\", response.Err)\n\t}\n\tif response.GenerationId != 66051 {\n\t\tt.Error(\"Decoding GenerationId failed, found:\", response.GenerationId)\n\t}\n\tif response.GroupProtocol != \"protocol\" {\n\t\tt.Error(\"Decoding GroupProtocol failed, found:\", response.GroupProtocol)\n\t}\n\tif response.LeaderId != \"foo\" {\n\t\tt.Error(\"Decoding LeaderId failed, found:\", response.LeaderId)\n\t}\n\tif response.MemberId != \"bar\" {\n\t\tt.Error(\"Decoding MemberId failed, found:\", response.MemberId)\n\t}\n\tif response.Version != 1 {\n\t\tt.Error(\"Decoding Version failed, found:\", response.Version)\n\t}\n\tif len(response.Members) != 0 {\n\t\tt.Error(\"Decoding Members failed, found:\", response.Members)\n\t}\n}\n\nfunc TestJoinGroupResponseV2(t *testing.T) {\n\tresponse := new(JoinGroupResponse)\n\ttestVersionDecodable(t, \"no error\", response, joinGroupResponseV2, 2)\n\tif response.ThrottleTime != 100 {\n\t\tt.Error(\"Decoding ThrottleTime failed, found:\", response.ThrottleTime)\n\t}\n\tif !errors.Is(response.Err, ErrNoError) {\n\t\tt.Error(\"Decoding Err failed: no error expected but found\", response.Err)\n\t}\n\tif response.GenerationId != 66051 {\n\t\tt.Error(\"Decoding GenerationId failed, found:\", response.GenerationId)\n\t}\n\tif response.GroupProtocol != \"protocol\" {\n\t\tt.Error(\"Decoding GroupProtocol failed, found:\", response.GroupProtocol)\n\t}\n\tif response.LeaderId != \"foo\" {\n\t\tt.Error(\"Decoding LeaderId failed, found:\", response.LeaderId)\n\t}\n\tif response.MemberId != \"bar\" {\n\t\tt.Error(\"Decoding MemberId failed, found:\", response.MemberId)\n\t}\n\tif response.Version != 2 {\n\t\tt.Error(\"Decoding Version failed, found:\", response.Version)\n\t}\n\tif len(response.Members) != 0 {\n\t\tt.Error(\"Decoding Members failed, found:\", response.Members)\n\t}\n}\n\nvar (\n\tjoinGroupResponseV5 = []byte{\n\t\t0, 0, 0, 100, // ThrottleTimeMs\n\t\t0x00, 0x00, // No error\n\t\t0x00, 0x01, 0x02, 0x03, // Generation ID\n\t\t0, 8, 'p', 'r', 'o', 't', 'o', 'c', 'o', 'l', // Protocol name chosen\n\t\t0, 3, 'f', 'o', 'o', // Leader ID\n\t\t0, 3, 'b', 'a', 'r', // Member ID\n\t\t0, 0, 0, 1, // One member info\n\t\t0, 3, 'm', 'i', 'd', // memberId\n\t\t0, 3, 'g', 'i', 'd', // GroupInstanceId\n\t\t0, 0, 0, 3, 1, 2, 3, // Metadata\n\t}\n\n\tjoinGroupResponseV6 = []byte{\n\t\t0, 0, 0, 100, // ThrottleTimeMs\n\t\t0x00, 0x00, // No error\n\t\t0x00, 0x01, 0x02, 0x03, // Generation ID\n\t\t9, 'p', 'r', 'o', 't', 'o', 'c', 'o', 'l', // Protocol name chosen\n\t\t4, 'f', 'o', 'o', // Leader ID\n\t\t4, 'b', 'a', 'r', // Member ID\n\t\t2,                // One member info\n\t\t4, 'm', 'i', 'd', // memberId\n\t\t4, 'g', 'i', 'd', // GroupInstanceId\n\t\t4, 1, 2, 3, // Metadata\n\t\t0, // empty tagged fields\n\t\t0, // empty tagged fields\n\t}\n\n\tjoinGroupResponseV6MemberIDRequired = []byte{\n\t\t0, 0, 0, 100, // throttle time\n\t\t0, 79, // member ID required error\n\t\t255, 255, 255, 255, // generation id\n\t\t1, // protocol name\n\t\t1, // leader id\n\t\t4, 'f', 'o', 'o',\n\t\t1, // no members\n\t\t0, // empty tagged fields\n\t}\n)\n\nfunc TestJoinGroupResponse3plus(t *testing.T) {\n\tgroupInstanceId := \"gid\"\n\ttests := []struct {\n\t\tCaseName     string\n\t\tVersion      int16\n\t\tMessageBytes []byte\n\t\tMessage      *JoinGroupResponse\n\t}{\n\t\t{\n\t\t\t\"v5\",\n\t\t\t5,\n\t\t\tjoinGroupResponseV5,\n\t\t\t&JoinGroupResponse{\n\t\t\t\tVersion:       5,\n\t\t\t\tThrottleTime:  100,\n\t\t\t\tErr:           ErrNoError,\n\t\t\t\tGenerationId:  0x00010203,\n\t\t\t\tGroupProtocol: \"protocol\",\n\t\t\t\tLeaderId:      \"foo\",\n\t\t\t\tMemberId:      \"bar\",\n\t\t\t\tMembers: []GroupMember{\n\t\t\t\t\t{\"mid\", &groupInstanceId, []byte{1, 2, 3}},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"v6\",\n\t\t\t6,\n\t\t\tjoinGroupResponseV6,\n\t\t\t&JoinGroupResponse{\n\t\t\t\tVersion:       6,\n\t\t\t\tThrottleTime:  100,\n\t\t\t\tErr:           ErrNoError,\n\t\t\t\tGenerationId:  0x00010203,\n\t\t\t\tGroupProtocol: \"protocol\",\n\t\t\t\tLeaderId:      \"foo\",\n\t\t\t\tMemberId:      \"bar\",\n\t\t\t\tMembers: []GroupMember{\n\t\t\t\t\t{\"mid\", &groupInstanceId, []byte{1, 2, 3}},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"v6 member id required\",\n\t\t\t6,\n\t\t\tjoinGroupResponseV6MemberIDRequired,\n\t\t\t&JoinGroupResponse{\n\t\t\t\tVersion:       6,\n\t\t\t\tThrottleTime:  100,\n\t\t\t\tErr:           ErrMemberIdRequired,\n\t\t\t\tGenerationId:  -1,\n\t\t\t\tGroupProtocol: \"\",\n\t\t\t\tLeaderId:      \"\",\n\t\t\t\tMemberId:      \"foo\",\n\t\t\t\tMembers:       nil,\n\t\t\t},\n\t\t},\n\t}\n\tfor _, c := range tests {\n\t\tresponse := new(JoinGroupResponse)\n\t\ttestVersionDecodable(t, c.CaseName, response, c.MessageBytes, c.Version)\n\t\tif !reflect.DeepEqual(c.Message, response) {\n\t\t\tt.Errorf(\"case %s decode failed, expected:%+v got %+v\", c.CaseName, c.Message, response)\n\t\t}\n\t\ttestEncodable(t, c.CaseName, c.Message, c.MessageBytes)\n\t}\n}\n"
  },
  {
    "path": "kerberos_client.go",
    "content": "package sarama\n\nimport (\n\tkrb5client \"github.com/jcmturner/gokrb5/v8/client\"\n\tkrb5config \"github.com/jcmturner/gokrb5/v8/config\"\n\t\"github.com/jcmturner/gokrb5/v8/credentials\"\n\t\"github.com/jcmturner/gokrb5/v8/keytab\"\n\t\"github.com/jcmturner/gokrb5/v8/types\"\n)\n\ntype KerberosGoKrb5Client struct {\n\tkrb5client.Client\n}\n\nfunc (c *KerberosGoKrb5Client) Domain() string {\n\treturn c.Credentials.Domain()\n}\n\nfunc (c *KerberosGoKrb5Client) CName() types.PrincipalName {\n\treturn c.Credentials.CName()\n}\n\n// NewKerberosClient creates kerberos client used to obtain TGT and TGS tokens.\n// It uses pure go Kerberos 5 solution (RFC-4121 and RFC-4120).\n// uses gokrb5 library underlying which is a pure go kerberos client with some GSS-API capabilities.\nfunc NewKerberosClient(config *GSSAPIConfig) (KerberosClient, error) {\n\tcfg, err := krb5config.Load(config.KerberosConfigPath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn createClient(config, cfg)\n}\n\nfunc createClient(config *GSSAPIConfig, cfg *krb5config.Config) (KerberosClient, error) {\n\tvar client *krb5client.Client\n\tswitch config.AuthType {\n\tcase KRB5_KEYTAB_AUTH:\n\t\tkt, err := keytab.Load(config.KeyTabPath)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tclient = krb5client.NewWithKeytab(config.Username, config.Realm, kt, cfg, krb5client.DisablePAFXFAST(config.DisablePAFXFAST))\n\tcase KRB5_CCACHE_AUTH:\n\t\tcc, err := credentials.LoadCCache(config.CCachePath)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tclient, err = krb5client.NewFromCCache(cc, cfg, krb5client.DisablePAFXFAST(config.DisablePAFXFAST))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\tdefault:\n\t\tclient = krb5client.NewWithPassword(config.Username,\n\t\t\tconfig.Realm, config.Password, cfg, krb5client.DisablePAFXFAST(config.DisablePAFXFAST))\n\t}\n\treturn &KerberosGoKrb5Client{*client}, nil\n}\n"
  },
  {
    "path": "kerberos_client_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\tkrbcfg \"github.com/jcmturner/gokrb5/v8/config\"\n)\n\n/*\n * Minimum requirement for client creation\n * we are not testing the client itself, we only test that the client is created\n * properly.\n *\n */\n\nconst (\n\tkrb5cfg = `[libdefaults]\n  default_realm = TEST.GOKRB5\n  dns_lookup_realm = false\n  dns_lookup_kdc = false\n  ticket_lifetime = 24h\n  forwardable = yes\n  default_tkt_enctypes = aes256-cts-hmac-sha1-96\n  default_tgs_enctypes = aes256-cts-hmac-sha1-96\n  noaddresses = false\n[realms]\n TEST.GOKRB5 = {\n  kdc = 127.0.0.1:88\n  admin_server = 127.0.0.1:749\n  default_domain = test.gokrb5\n }\n RESDOM.GOKRB5 = {\n  kdc = 10.80.88.88:188\n  admin_server = 127.0.0.1:749\n  default_domain = resdom.gokrb5\n }\n  USER.GOKRB5 = {\n  kdc = 192.168.88.100:88\n  admin_server = 192.168.88.100:464\n  default_domain = user.gokrb5\n }\n  RES.GOKRB5 = {\n  kdc = 192.168.88.101:88\n  admin_server = 192.168.88.101:464\n  default_domain = res.gokrb5\n }\n[domain_realm]\n .test.gokrb5 = TEST.GOKRB5\n test.gokrb5 = TEST.GOKRB5\n .resdom.gokrb5 = RESDOM.GOKRB5\n resdom.gokrb5 = RESDOM.GOKRB5\n  .user.gokrb5 = USER.GOKRB5\n user.gokrb5 = USER.GOKRB5\n  .res.gokrb5 = RES.GOKRB5\n res.gokrb5 = RES.GOKRB5\n`\n)\n\nfunc TestFaildToCreateKerberosConfig(t *testing.T) {\n\texpectedErr := errors.New(\"configuration file could not be opened: testdata/krb5.conf open testdata/krb5.conf: no such file or directory\")\n\tclientConfig := NewTestConfig()\n\tclientConfig.Net.SASL.Mechanism = SASLTypeGSSAPI\n\tclientConfig.Net.SASL.Enable = true\n\tclientConfig.Net.SASL.GSSAPI.ServiceName = \"kafka\"\n\tclientConfig.Net.SASL.GSSAPI.Realm = \"EXAMPLE.COM\"\n\tclientConfig.Net.SASL.GSSAPI.Username = \"client\"\n\tclientConfig.Net.SASL.GSSAPI.AuthType = KRB5_USER_AUTH\n\tclientConfig.Net.SASL.GSSAPI.Password = \"qwerty\"\n\tclientConfig.Net.SASL.GSSAPI.KerberosConfigPath = \"testdata/krb5.conf\"\n\t_, err := NewKerberosClient(&clientConfig.Net.SASL.GSSAPI)\n\t// Expect to create client with password\n\tif err.Error() != expectedErr.Error() {\n\t\tt.Errorf(\"Expected error:%s, got:%s.\", err, expectedErr)\n\t}\n}\n\nfunc TestCreateWithPassword(t *testing.T) {\n\tkerberosConfig, err := krbcfg.NewFromString(krb5cfg)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\texpectedDoman := \"EXAMPLE.COM\"\n\texpectedCName := \"client\"\n\n\tclientConfig := NewTestConfig()\n\tclientConfig.Net.SASL.Mechanism = SASLTypeGSSAPI\n\tclientConfig.Net.SASL.Enable = true\n\tclientConfig.Net.SASL.GSSAPI.ServiceName = \"kafka\"\n\tclientConfig.Net.SASL.GSSAPI.Realm = \"EXAMPLE.COM\"\n\tclientConfig.Net.SASL.GSSAPI.Username = \"client\"\n\tclientConfig.Net.SASL.GSSAPI.AuthType = KRB5_USER_AUTH\n\tclientConfig.Net.SASL.GSSAPI.Password = \"qwerty\"\n\tclientConfig.Net.SASL.GSSAPI.KerberosConfigPath = \"/etc/krb5.conf\"\n\tclient, _ := createClient(&clientConfig.Net.SASL.GSSAPI, kerberosConfig)\n\t// Expect to create client with password\n\tif client == nil {\n\t\tt.Errorf(\"Expected client not nil\")\n\t}\n\tif client.Domain() != expectedDoman {\n\t\tt.Errorf(\"Client domain: %s, got: %s\", expectedDoman, client.Domain())\n\t}\n\tif client.CName().NameString[0] != expectedCName {\n\t\tt.Errorf(\"Client domain:%s, got: %s\", expectedCName, client.CName().NameString[0])\n\t}\n}\n\nfunc TestCreateWithKeyTab(t *testing.T) {\n\tkerberosConfig, err := krbcfg.NewFromString(krb5cfg)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// Expect to try to create a client with keytab and fails with \"o such file or directory\" error\n\texpectedErr := errors.New(\"open nonexist.keytab: no such file or directory\")\n\tclientConfig := NewTestConfig()\n\tclientConfig.Net.SASL.Mechanism = SASLTypeGSSAPI\n\tclientConfig.Net.SASL.Enable = true\n\tclientConfig.Net.SASL.GSSAPI.ServiceName = \"kafka\"\n\tclientConfig.Net.SASL.GSSAPI.Realm = \"EXAMPLE.COM\"\n\tclientConfig.Net.SASL.GSSAPI.Username = \"client\"\n\tclientConfig.Net.SASL.GSSAPI.AuthType = KRB5_KEYTAB_AUTH\n\tclientConfig.Net.SASL.GSSAPI.KeyTabPath = \"nonexist.keytab\"\n\tclientConfig.Net.SASL.GSSAPI.KerberosConfigPath = \"/etc/krb5.conf\"\n\t_, err = createClient(&clientConfig.Net.SASL.GSSAPI, kerberosConfig)\n\tif err.Error() != expectedErr.Error() {\n\t\tt.Errorf(\"Expected error:%s, got:%s.\", err, expectedErr)\n\t}\n}\n\nfunc TestCreateWithCredentialsCache(t *testing.T) {\n\tkerberosConfig, err := krbcfg.NewFromString(krb5cfg)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// Expect to try to create a client with a credentials cache and fails with \"o such file or directory\" error\n\texpectedErr := errors.New(\"open nonexist.ccache: no such file or directory\")\n\tclientConfig := NewTestConfig()\n\tclientConfig.Net.SASL.Mechanism = SASLTypeGSSAPI\n\tclientConfig.Net.SASL.Enable = true\n\tclientConfig.Net.SASL.GSSAPI.ServiceName = \"kafka\"\n\tclientConfig.Net.SASL.GSSAPI.AuthType = KRB5_CCACHE_AUTH\n\tclientConfig.Net.SASL.GSSAPI.CCachePath = \"nonexist.ccache\"\n\tclientConfig.Net.SASL.GSSAPI.KerberosConfigPath = \"/etc/krb5.conf\"\n\t_, err = createClient(&clientConfig.Net.SASL.GSSAPI, kerberosConfig)\n\tif err.Error() != expectedErr.Error() {\n\t\tt.Errorf(\"Expected error:%s, got:%s.\", err, expectedErr)\n\t}\n}\n\nfunc TestCreateWithDisablePAFXFAST(t *testing.T) {\n\tkerberosConfig, err := krbcfg.NewFromString(krb5cfg)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// Expect to try to create a client with keytab and fails with \"o such file or directory\" error\n\texpectedErr := errors.New(\"open nonexist.keytab: no such file or directory\")\n\tclientConfig := NewTestConfig()\n\tclientConfig.Net.SASL.Mechanism = SASLTypeGSSAPI\n\tclientConfig.Net.SASL.Enable = true\n\tclientConfig.Net.SASL.GSSAPI.ServiceName = \"kafka\"\n\tclientConfig.Net.SASL.GSSAPI.Realm = \"EXAMPLE.COM\"\n\tclientConfig.Net.SASL.GSSAPI.Username = \"client\"\n\tclientConfig.Net.SASL.GSSAPI.AuthType = KRB5_KEYTAB_AUTH\n\tclientConfig.Net.SASL.GSSAPI.KeyTabPath = \"nonexist.keytab\"\n\tclientConfig.Net.SASL.GSSAPI.KerberosConfigPath = \"/etc/krb5.conf\"\n\tclientConfig.Net.SASL.GSSAPI.DisablePAFXFAST = true\n\n\t_, err = createClient(&clientConfig.Net.SASL.GSSAPI, kerberosConfig)\n\tif err.Error() != expectedErr.Error() {\n\t\tt.Errorf(\"Expected error:%s, got:%s.\", err, expectedErr)\n\t}\n}\n"
  },
  {
    "path": "leave_group_request.go",
    "content": "package sarama\n\ntype MemberIdentity struct {\n\tMemberId        string\n\tGroupInstanceId *string\n}\n\ntype LeaveGroupRequest struct {\n\tVersion  int16\n\tGroupId  string\n\tMemberId string           // Removed in Version 3\n\tMembers  []MemberIdentity // Added in Version 3\n}\n\nfunc (r *LeaveGroupRequest) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *LeaveGroupRequest) encode(pe packetEncoder) error {\n\tif err := pe.putString(r.GroupId); err != nil {\n\t\treturn err\n\t}\n\tif r.Version < 3 {\n\t\tif err := pe.putString(r.MemberId); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif r.Version >= 3 {\n\t\tif err := pe.putArrayLength(len(r.Members)); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor _, member := range r.Members {\n\t\t\tif err := pe.putString(member.MemberId); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := pe.putNullableString(member.GroupInstanceId); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tpe.putEmptyTaggedFieldArray()\n\t\t}\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (r *LeaveGroupRequest) decode(pd packetDecoder, version int16) (err error) {\n\tr.Version = version\n\tif r.GroupId, err = pd.getString(); err != nil {\n\t\treturn\n\t}\n\tif r.Version < 3 {\n\t\tif r.MemberId, err = pd.getString(); err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\tif r.Version >= 3 {\n\t\tmemberCount, err := pd.getArrayLength()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.Members = make([]MemberIdentity, memberCount)\n\t\tfor i := 0; i < memberCount; i++ {\n\t\t\tmemberIdentity := MemberIdentity{}\n\t\t\tif memberIdentity.MemberId, err = pd.getString(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif memberIdentity.GroupInstanceId, err = pd.getNullableString(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tr.Members[i] = memberIdentity\n\t\t\t_, err = pd.getEmptyTaggedFieldArray()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (r *LeaveGroupRequest) key() int16 {\n\treturn apiKeyLeaveGroup\n}\n\nfunc (r *LeaveGroupRequest) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *LeaveGroupRequest) headerVersion() int16 {\n\tif r.Version >= 4 {\n\t\treturn 2\n\t}\n\treturn 1\n}\n\nfunc (r *LeaveGroupRequest) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 4\n}\n\nfunc (r *LeaveGroupRequest) isFlexible() bool {\n\treturn r.isFlexibleVersion(r.Version)\n}\n\nfunc (r *LeaveGroupRequest) isFlexibleVersion(version int16) bool {\n\treturn version >= 4\n}\n\nfunc (r *LeaveGroupRequest) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 4:\n\t\treturn V2_4_0_0\n\tcase 3:\n\t\treturn V2_4_0_0\n\tcase 2:\n\t\treturn V2_0_0_0\n\tcase 1:\n\t\treturn V0_11_0_0\n\tcase 0:\n\t\treturn V0_9_0_0\n\tdefault:\n\t\treturn V2_4_0_0\n\t}\n}\n"
  },
  {
    "path": "leave_group_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nvar (\n\tbasicLeaveGroupRequestV0 = []byte{\n\t\t0, 3, 'f', 'o', 'o',\n\t\t0, 3, 'b', 'a', 'r',\n\t}\n\tbasicLeaveGroupRequestV3 = []byte{\n\t\t0, 3, 'f', 'o', 'o',\n\t\t0, 0, 0, 2, // Two Member\n\t\t0, 4, 'm', 'i', 'd', '1', // MemberId\n\t\t255, 255, // GroupInstanceId  nil\n\t\t0, 4, 'm', 'i', 'd', '2', // MemberId\n\t\t0, 3, 'g', 'i', 'd', // GroupInstanceId\n\t}\n\tbasicLeaveGroupRequestV4 = []byte{\n\t\t4, 'f', 'o', 'o',\n\t\t3,                     // Two Member\n\t\t5, 'm', 'i', 'd', '1', // MemberId\n\t\t0,                     // GroupInstanceId  nil\n\t\t0,                     // empty tagged fields\n\t\t5, 'm', 'i', 'd', '2', // MemberId\n\t\t4, 'g', 'i', 'd', // GroupInstanceId\n\t\t0, // empty tagged fields\n\t\t0, // empty tagged fields\n\t}\n)\n\nfunc TestLeaveGroupRequest(t *testing.T) {\n\tgroupInstanceId := \"gid\"\n\ttests := []struct {\n\t\tCaseName     string\n\t\tVersion      int16\n\t\tMessageBytes []byte\n\t\tMessage      *LeaveGroupRequest\n\t}{\n\t\t{\n\t\t\t\"v0\",\n\t\t\t0,\n\t\t\tbasicLeaveGroupRequestV0,\n\t\t\t&LeaveGroupRequest{\n\t\t\t\tVersion:  0,\n\t\t\t\tGroupId:  \"foo\",\n\t\t\t\tMemberId: \"bar\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"v3\",\n\t\t\t3,\n\t\t\tbasicLeaveGroupRequestV3,\n\t\t\t&LeaveGroupRequest{\n\t\t\t\tVersion: 3,\n\t\t\t\tGroupId: \"foo\",\n\t\t\t\tMembers: []MemberIdentity{\n\t\t\t\t\t{\"mid1\", nil},\n\t\t\t\t\t{\"mid2\", &groupInstanceId},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"v4\",\n\t\t\t4,\n\t\t\tbasicLeaveGroupRequestV4,\n\t\t\t&LeaveGroupRequest{\n\t\t\t\tVersion: 4,\n\t\t\t\tGroupId: \"foo\",\n\t\t\t\tMembers: []MemberIdentity{\n\t\t\t\t\t{\"mid1\", nil},\n\t\t\t\t\t{\"mid2\", &groupInstanceId},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tfor _, c := range tests {\n\t\trequest := new(LeaveGroupRequest)\n\t\ttestVersionDecodable(t, c.CaseName, request, c.MessageBytes, c.Version)\n\t\tif !reflect.DeepEqual(c.Message, request) {\n\t\t\tt.Errorf(\"case %s decode failed, expected:%+v got %+v\", c.CaseName, c.Message, request)\n\t\t}\n\t\ttestEncodable(t, c.CaseName, c.Message, c.MessageBytes)\n\t}\n}\n"
  },
  {
    "path": "leave_group_response.go",
    "content": "package sarama\n\nimport \"time\"\n\ntype MemberResponse struct {\n\tMemberId        string\n\tGroupInstanceId *string\n\tErr             KError\n}\ntype LeaveGroupResponse struct {\n\tVersion      int16\n\tThrottleTime int32\n\tErr          KError\n\tMembers      []MemberResponse\n}\n\nfunc (r *LeaveGroupResponse) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *LeaveGroupResponse) encode(pe packetEncoder) error {\n\tif r.Version >= 1 {\n\t\tpe.putInt32(r.ThrottleTime)\n\t}\n\tpe.putKError(r.Err)\n\tif r.Version >= 3 {\n\t\tif err := pe.putArrayLength(len(r.Members)); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor _, member := range r.Members {\n\t\t\tif err := pe.putString(member.MemberId); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := pe.putNullableString(member.GroupInstanceId); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tpe.putKError(member.Err)\n\t\t\tpe.putEmptyTaggedFieldArray()\n\t\t}\n\t}\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (r *LeaveGroupResponse) decode(pd packetDecoder, version int16) (err error) {\n\tr.Version = version\n\tif r.Version >= 1 {\n\t\tif r.ThrottleTime, err = pd.getInt32(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tr.Err, err = pd.getKError()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif r.Version >= 3 {\n\t\tmembersLen, err := pd.getArrayLength()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.Members = make([]MemberResponse, membersLen)\n\t\tfor i := 0; i < len(r.Members); i++ {\n\t\t\tif r.Members[i].MemberId, err = pd.getString(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif r.Members[i].GroupInstanceId, err = pd.getNullableString(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif r.Members[i].Err, err = pd.getKError(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif _, err := pd.getEmptyTaggedFieldArray(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (r *LeaveGroupResponse) key() int16 {\n\treturn apiKeyLeaveGroup\n}\n\nfunc (r *LeaveGroupResponse) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *LeaveGroupResponse) headerVersion() int16 {\n\tif r.Version >= 4 {\n\t\treturn 1\n\t}\n\treturn 0\n}\n\nfunc (r *LeaveGroupResponse) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 4\n}\n\nfunc (r *LeaveGroupResponse) isFlexible() bool {\n\treturn r.isFlexibleVersion(r.Version)\n}\n\nfunc (r *LeaveGroupResponse) isFlexibleVersion(version int16) bool {\n\treturn version >= 4\n}\n\nfunc (r *LeaveGroupResponse) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 4:\n\t\treturn V2_4_0_0\n\tcase 3:\n\t\treturn V2_4_0_0\n\tcase 2:\n\t\treturn V2_0_0_0\n\tcase 1:\n\t\treturn V0_11_0_0\n\tcase 0:\n\t\treturn V0_9_0_0\n\tdefault:\n\t\treturn V2_4_0_0\n\t}\n}\n\nfunc (r *LeaveGroupResponse) throttleTime() time.Duration {\n\treturn time.Duration(r.ThrottleTime) * time.Millisecond\n}\n"
  },
  {
    "path": "leave_group_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nvar (\n\tleaveGroupResponseV0NoError   = []byte{0x00, 0x00}\n\tleaveGroupResponseV0WithError = []byte{0, 25}\n\tleaveGroupResponseV1NoError   = []byte{\n\t\t0, 0, 0, 100, // ThrottleTime\n\t\t0x00, 0x00, // Err\n\t}\n\tleaveGroupResponseV3NoError = []byte{\n\t\t0, 0, 0, 100, // ThrottleTime\n\t\t0x00, 0x00, // Err\n\t\t0, 0, 0, 2, // Two Members\n\t\t0, 4, 'm', 'i', 'd', '1', // MemberId\n\t\t255, 255, // GroupInstanceId\n\t\t0, 0, // Err\n\t\t0, 4, 'm', 'i', 'd', '2', // MemberId\n\t\t0, 3, 'g', 'i', 'd', // GroupInstanceId\n\t\t0, 25, // Err\n\t}\n\tleaveGroupResponseV4NoError = []byte{\n\t\t0, 0, 0, 100, // ThrottleTime\n\t\t0x00, 0x00, // Err\n\t\t3,                     // Two Members\n\t\t5, 'm', 'i', 'd', '1', // MemberId\n\t\t0,    // GroupInstanceId\n\t\t0, 0, // Err\n\t\t0,                     // empty tagged fields\n\t\t5, 'm', 'i', 'd', '2', // MemberId\n\t\t4, 'g', 'i', 'd', // GroupInstanceId\n\t\t0, 25, // Err\n\t\t0, // empty tagged fields\n\t\t0, // empty tagged fields\n\t}\n)\n\nfunc TestLeaveGroupResponse(t *testing.T) {\n\tgroupInstanceId := \"gid\"\n\ttests := []struct {\n\t\tCaseName     string\n\t\tVersion      int16\n\t\tMessageBytes []byte\n\t\tMessage      *LeaveGroupResponse\n\t}{\n\t\t{\n\t\t\t\"v0-noErr\",\n\t\t\t0,\n\t\t\tleaveGroupResponseV0NoError,\n\t\t\t&LeaveGroupResponse{\n\t\t\t\tVersion: 0,\n\t\t\t\tErr:     ErrNoError,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"v0-Err\",\n\t\t\t0,\n\t\t\tleaveGroupResponseV0WithError,\n\t\t\t&LeaveGroupResponse{\n\t\t\t\tVersion: 0,\n\t\t\t\tErr:     ErrUnknownMemberId,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"v1-noErr\",\n\t\t\t1,\n\t\t\tleaveGroupResponseV1NoError,\n\t\t\t&LeaveGroupResponse{\n\t\t\t\tVersion:      1,\n\t\t\t\tThrottleTime: 100,\n\t\t\t\tErr:          ErrNoError,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"v3\",\n\t\t\t3,\n\t\t\tleaveGroupResponseV3NoError,\n\t\t\t&LeaveGroupResponse{\n\t\t\t\tVersion:      3,\n\t\t\t\tThrottleTime: 100,\n\t\t\t\tErr:          ErrNoError,\n\t\t\t\tMembers: []MemberResponse{\n\t\t\t\t\t{\"mid1\", nil, ErrNoError},\n\t\t\t\t\t{\"mid2\", &groupInstanceId, ErrUnknownMemberId},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"v4\",\n\t\t\t4,\n\t\t\tleaveGroupResponseV4NoError,\n\t\t\t&LeaveGroupResponse{\n\t\t\t\tVersion:      4,\n\t\t\t\tThrottleTime: 100,\n\t\t\t\tErr:          ErrNoError,\n\t\t\t\tMembers: []MemberResponse{\n\t\t\t\t\t{\"mid1\", nil, ErrNoError},\n\t\t\t\t\t{\"mid2\", &groupInstanceId, ErrUnknownMemberId},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tfor _, c := range tests {\n\t\tresponse := new(LeaveGroupResponse)\n\t\ttestVersionDecodable(t, c.CaseName, response, c.MessageBytes, c.Version)\n\t\tif !reflect.DeepEqual(c.Message, response) {\n\t\t\tt.Errorf(\"case %s decode failed, expected:%+v got %+v\", c.CaseName, c.Message, response)\n\t\t}\n\t\ttestEncodable(t, c.CaseName, c.Message, c.MessageBytes)\n\t}\n}\n"
  },
  {
    "path": "length_field.go",
    "content": "package sarama\n\nimport (\n\t\"encoding/binary\"\n\t\"sync\"\n)\n\n// LengthField implements the PushEncoder and PushDecoder interfaces for calculating 4-byte lengths.\ntype lengthField struct {\n\tstartOffset int\n\tlength      int32\n}\n\nvar lengthFieldPool = sync.Pool{}\n\nfunc acquireLengthField() *lengthField {\n\tval := lengthFieldPool.Get()\n\tif val != nil {\n\t\treturn val.(*lengthField)\n\t}\n\treturn &lengthField{}\n}\n\nfunc releaseLengthField(m *lengthField) {\n\tlengthFieldPool.Put(m)\n}\n\nfunc (l *lengthField) decode(pd packetDecoder) error {\n\tvar err error\n\tl.length, err = pd.getInt32()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif l.length > int32(pd.remaining()) {\n\t\treturn ErrInsufficientData\n\t}\n\treturn nil\n}\n\nfunc (l *lengthField) saveOffset(in int) {\n\tl.startOffset = in\n}\n\nfunc (l *lengthField) reserveLength() int {\n\treturn 4\n}\n\nfunc (l *lengthField) run(curOffset int, buf []byte) error {\n\tbinary.BigEndian.PutUint32(buf[l.startOffset:], uint32(curOffset-l.startOffset-4))\n\treturn nil\n}\n\nfunc (l *lengthField) check(curOffset int, buf []byte) error {\n\tif int32(curOffset-l.startOffset-4) != l.length {\n\t\treturn PacketDecodingError{\"length field invalid\"}\n\t}\n\n\treturn nil\n}\n\ntype varintLengthField struct {\n\tstartOffset int\n\tlength      int64\n}\n\nfunc (l *varintLengthField) decode(pd packetDecoder) error {\n\tvar err error\n\tl.length, err = pd.getVarint()\n\treturn err\n}\n\nfunc (l *varintLengthField) saveOffset(in int) {\n\tl.startOffset = in\n}\n\nfunc (l *varintLengthField) adjustLength(currOffset int) int {\n\toldFieldSize := l.reserveLength()\n\tl.length = int64(currOffset - l.startOffset - oldFieldSize)\n\n\treturn l.reserveLength() - oldFieldSize\n}\n\nfunc (l *varintLengthField) reserveLength() int {\n\tvar tmp [binary.MaxVarintLen64]byte\n\treturn binary.PutVarint(tmp[:], l.length)\n}\n\nfunc (l *varintLengthField) run(curOffset int, buf []byte) error {\n\tbinary.PutVarint(buf[l.startOffset:], l.length)\n\treturn nil\n}\n\nfunc (l *varintLengthField) check(curOffset int, buf []byte) error {\n\tif int64(curOffset-l.startOffset-l.reserveLength()) != l.length {\n\t\treturn PacketDecodingError{\"length field invalid\"}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "list_groups_request.go",
    "content": "package sarama\n\ntype ListGroupsRequest struct {\n\tVersion      int16\n\tStatesFilter []string // version 4 or later\n\tTypesFilter  []string // version 5 or later\n}\n\nfunc (r *ListGroupsRequest) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *ListGroupsRequest) encode(pe packetEncoder) error {\n\tif r.Version >= 4 {\n\t\tif err := pe.putArrayLength(len(r.StatesFilter)); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor _, filter := range r.StatesFilter {\n\t\t\terr := pe.putString(filter)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif r.Version >= 5 {\n\t\t\tif err := pe.putArrayLength(len(r.TypesFilter)); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tfor _, filter := range r.TypesFilter {\n\t\t\t\terr := pe.putString(filter)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (r *ListGroupsRequest) decode(pd packetDecoder, version int16) (err error) {\n\tr.Version = version\n\tif r.Version >= 4 {\n\t\tif r.StatesFilter, err = pd.getStringArray(); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif r.Version >= 5 {\n\t\t\tif r.TypesFilter, err = pd.getStringArray(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (r *ListGroupsRequest) key() int16 {\n\treturn apiKeyListGroups\n}\n\nfunc (r *ListGroupsRequest) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *ListGroupsRequest) headerVersion() int16 {\n\tif r.Version >= 3 {\n\t\treturn 2\n\t}\n\treturn 1\n}\n\nfunc (r *ListGroupsRequest) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 5\n}\n\nfunc (r *ListGroupsRequest) isFlexible() bool {\n\treturn r.isFlexibleVersion(r.Version)\n}\n\nfunc (r *ListGroupsRequest) isFlexibleVersion(version int16) bool {\n\treturn version >= 3\n}\n\nfunc (r *ListGroupsRequest) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 5:\n\t\treturn V3_8_0_0\n\tcase 4:\n\t\treturn V2_6_0_0\n\tcase 3:\n\t\treturn V2_4_0_0\n\tcase 2:\n\t\treturn V2_0_0_0\n\tcase 1:\n\t\treturn V0_11_0_0\n\tcase 0:\n\t\treturn V0_9_0_0\n\tdefault:\n\t\treturn V2_6_0_0\n\t}\n}\n"
  },
  {
    "path": "list_groups_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nfunc TestListGroupsRequest(t *testing.T) {\n\ttestRequest(t, \"ListGroupsRequest\", &ListGroupsRequest{}, []byte{})\n\n\ttestRequest(t, \"ListGroupsRequest\", &ListGroupsRequest{\n\t\tVersion: 1,\n\t}, []byte{})\n\n\ttestRequest(t, \"ListGroupsRequest\", &ListGroupsRequest{\n\t\tVersion: 2,\n\t}, []byte{})\n\n\ttestRequest(t, \"ListGroupsRequest\", &ListGroupsRequest{\n\t\tVersion: 3,\n\t}, []byte{\n\t\t0, //\t\t0, // empty tag buffer\n\t})\n\n\ttestRequest(t, \"ListGroupsRequest\", &ListGroupsRequest{\n\t\tVersion: 4,\n\t}, []byte{\n\t\t1, // compact array length (0)\n\t\t0, // empty tag buffer\n\t})\n\n\ttestRequest(t, \"ListGroupsRequest\", &ListGroupsRequest{\n\t\tVersion:      4,\n\t\tStatesFilter: []string{\"Empty\"},\n\t}, []byte{\n\t\t2,                          // compact array length (1)\n\t\t6, 'E', 'm', 'p', 't', 'y', // compact string\n\t\t0, // empty tag buffer\n\t})\n\n\ttestRequest(t, \"ListGroupsRequest\", &ListGroupsRequest{\n\t\tVersion:      5,\n\t\tStatesFilter: []string{\"Empty\"},\n\t\tTypesFilter:  []string{\"Classic\"},\n\t}, []byte{\n\t\t2,                          // compact array length (1)\n\t\t6, 'E', 'm', 'p', 't', 'y', // compact string\n\t\t2,                                    // compact array length (1)\n\t\t8, 'C', 'l', 'a', 's', 's', 'i', 'c', // compact string\n\t\t0, // empty tag buffer\n\t})\n}\n"
  },
  {
    "path": "list_groups_response.go",
    "content": "package sarama\n\ntype ListGroupsResponse struct {\n\tVersion      int16\n\tThrottleTime int32\n\tErr          KError\n\tGroups       map[string]string\n\tGroupsData   map[string]GroupData // version 4 or later\n}\n\nfunc (r *ListGroupsResponse) setVersion(v int16) {\n\tr.Version = v\n}\n\ntype GroupData struct {\n\tGroupState string // version 4 or later\n\tGroupType  string // version 5 or later\n}\n\nfunc (r *ListGroupsResponse) encode(pe packetEncoder) error {\n\tif r.Version >= 1 {\n\t\tpe.putInt32(r.ThrottleTime)\n\t}\n\n\tpe.putKError(r.Err)\n\n\tif err := pe.putArrayLength(len(r.Groups)); err != nil {\n\t\treturn err\n\t}\n\tfor groupId, protocolType := range r.Groups {\n\t\tif err := pe.putString(groupId); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := pe.putString(protocolType); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif r.Version >= 4 {\n\t\t\tgroupData := r.GroupsData[groupId]\n\t\t\tif err := pe.putString(groupData.GroupState); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\tif r.Version >= 5 {\n\t\t\tgroupData := r.GroupsData[groupId]\n\t\t\tif err := pe.putString(groupData.GroupType); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tpe.putEmptyTaggedFieldArray()\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (r *ListGroupsResponse) decode(pd packetDecoder, version int16) (err error) {\n\tr.Version = version\n\tif r.Version >= 1 {\n\t\tif r.ThrottleTime, err = pd.getInt32(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tr.Err, err = pd.getKError()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor i := 0; i < n; i++ {\n\t\tif i == 0 {\n\t\t\tr.Groups = make(map[string]string)\n\t\t\tif r.Version >= 4 {\n\t\t\t\tr.GroupsData = make(map[string]GroupData)\n\t\t\t}\n\t\t}\n\n\t\tvar groupId, protocolType string\n\t\tgroupId, err = pd.getString()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tprotocolType, err = pd.getString()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tr.Groups[groupId] = protocolType\n\n\t\tif r.Version >= 4 {\n\t\t\tvar groupData GroupData\n\t\t\tgroupState, err := pd.getString()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tgroupData.GroupState = groupState\n\t\t\tif r.Version >= 5 {\n\t\t\t\tgroupType, err := pd.getString()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tgroupData.GroupType = groupType\n\t\t\t}\n\t\t\tr.GroupsData[groupId] = groupData\n\t\t}\n\n\t\tif _, err = pd.getEmptyTaggedFieldArray(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (r *ListGroupsResponse) key() int16 {\n\treturn apiKeyListGroups\n}\n\nfunc (r *ListGroupsResponse) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *ListGroupsResponse) headerVersion() int16 {\n\tif r.Version >= 3 {\n\t\treturn 1\n\t}\n\treturn 0\n}\n\nfunc (r *ListGroupsResponse) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 5\n}\n\nfunc (r *ListGroupsResponse) isFlexible() bool {\n\treturn r.isFlexibleVersion(r.Version)\n}\n\nfunc (r *ListGroupsResponse) isFlexibleVersion(version int16) bool {\n\treturn version >= 3\n}\n\nfunc (r *ListGroupsResponse) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 5:\n\t\treturn V3_8_0_0\n\tcase 4:\n\t\treturn V2_6_0_0\n\tcase 3:\n\t\treturn V2_4_0_0\n\tcase 2:\n\t\treturn V2_0_0_0\n\tcase 1:\n\t\treturn V0_11_0_0\n\tcase 0:\n\t\treturn V0_9_0_0\n\tdefault:\n\t\treturn V2_6_0_0\n\t}\n}\n"
  },
  {
    "path": "list_groups_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"errors\"\n\t\"testing\"\n)\n\nvar (\n\tlistGroupsResponseEmpty = []byte{\n\t\t0, 0, // no error\n\t\t0, 0, 0, 0, // no groups\n\t}\n\n\tlistGroupsResponseError = []byte{\n\t\t0, 31, // no error\n\t\t0, 0, 0, 0, // ErrClusterAuthorizationFailed\n\t}\n\n\tlistGroupsResponseWithConsumer = []byte{\n\t\t0, 0, // no error\n\t\t0, 0, 0, 1, // 1 group\n\t\t0, 3, 'f', 'o', 'o', // group name\n\t\t0, 8, 'c', 'o', 'n', 's', 'u', 'm', 'e', 'r', // protocol type\n\t}\n\n\tlistGroupResponseV4 = []byte{\n\t\t0, 0, 0, 0, // no throttle time\n\t\t0, 0, // no error\n\t\t2,                // compact array length (1)\n\t\t4, 'f', 'o', 'o', // group name (compact string)\n\t\t9, 'c', 'o', 'n', 's', 'u', 'm', 'e', 'r', // protocol type (compact string)\n\t\t6, 'E', 'm', 'p', 't', 'y', // state (compact string)\n\t\t0, // Empty tag buffer\n\t\t0, // Empty tag buffer\n\t}\n\n\tlistGroupResponseV5 = []byte{\n\t\t0, 0, 0, 0, // no throttle time\n\t\t0, 0, // no error\n\t\t2,                // compact array length (1)\n\t\t4, 'f', 'o', 'o', // group name (compact string)\n\t\t9, 'c', 'o', 'n', 's', 'u', 'm', 'e', 'r', // protocol type (compact string)\n\t\t6, 'E', 'm', 'p', 't', 'y', // state (compact string)\n\t\t8, 'C', 'l', 'a', 's', 's', 'i', 'c', // type (compact string)\n\t\t0, // Empty tag buffer\n\t\t0, // Empty tag buffer\n\t}\n)\n\nfunc TestListGroupsResponse(t *testing.T) {\n\tvar response *ListGroupsResponse\n\n\tresponse = new(ListGroupsResponse)\n\ttestVersionDecodable(t, \"no error\", response, listGroupsResponseEmpty, 0)\n\tif !errors.Is(response.Err, ErrNoError) {\n\t\tt.Error(\"Expected no gerror, found:\", response.Err)\n\t}\n\tif len(response.Groups) != 0 {\n\t\tt.Error(\"Expected no groups\")\n\t}\n\n\tresponse = new(ListGroupsResponse)\n\ttestVersionDecodable(t, \"no error\", response, listGroupsResponseError, 0)\n\tif !errors.Is(response.Err, ErrClusterAuthorizationFailed) {\n\t\tt.Error(\"Expected no gerror, found:\", response.Err)\n\t}\n\tif len(response.Groups) != 0 {\n\t\tt.Error(\"Expected no groups\")\n\t}\n\n\tresponse = new(ListGroupsResponse)\n\ttestVersionDecodable(t, \"no error\", response, listGroupsResponseWithConsumer, 0)\n\tif !errors.Is(response.Err, ErrNoError) {\n\t\tt.Error(\"Expected no gerror, found:\", response.Err)\n\t}\n\tif len(response.Groups) != 1 {\n\t\tt.Error(\"Expected one group\")\n\t}\n\tif response.Groups[\"foo\"] != \"consumer\" {\n\t\tt.Error(\"Expected foo group to use consumer protocol\")\n\t}\n\n\tresponse = new(ListGroupsResponse)\n\ttestVersionDecodable(t, \"no error\", response, listGroupResponseV4, 4)\n\tif !errors.Is(response.Err, ErrNoError) {\n\t\tt.Error(\"Expected no gerror, found:\", response.Err)\n\t}\n\tif len(response.Groups) != 1 {\n\t\tt.Error(\"Expected one group\")\n\t}\n\tif response.Groups[\"foo\"] != \"consumer\" {\n\t\tt.Error(\"Expected foo group to use consumer protocol\")\n\t}\n\tif response.GroupsData[\"foo\"].GroupState != \"Empty\" {\n\t\tt.Error(\"Expected foo group to have empty state\")\n\t}\n\n\tresponse = new(ListGroupsResponse)\n\ttestVersionDecodable(t, \"no error\", response, listGroupResponseV5, 5)\n\tif !errors.Is(response.Err, ErrNoError) {\n\t\tt.Error(\"Expected no gerror, found:\", response.Err)\n\t}\n\tif len(response.Groups) != 1 {\n\t\tt.Error(\"Expected one group\")\n\t}\n\tif response.Groups[\"foo\"] != \"consumer\" {\n\t\tt.Error(\"Expected foo group to use consumer protocol\")\n\t}\n\tif response.GroupsData[\"foo\"].GroupState != \"Empty\" {\n\t\tt.Error(\"Expected foo group to have empty state\")\n\t}\n\tif response.GroupsData[\"foo\"].GroupType != \"Classic\" {\n\t\tt.Error(\"Expected foo group to have type 'Classic', found: \", response.GroupsData[\"foo\"].GroupType)\n\t}\n}\n"
  },
  {
    "path": "list_partition_reassignments_request.go",
    "content": "package sarama\n\ntype ListPartitionReassignmentsRequest struct {\n\tTimeoutMs int32\n\tblocks    map[string][]int32\n\tVersion   int16\n}\n\nfunc (r *ListPartitionReassignmentsRequest) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *ListPartitionReassignmentsRequest) encode(pe packetEncoder) error {\n\tpe.putInt32(r.TimeoutMs)\n\n\tif err := pe.putArrayLength(len(r.blocks)); err != nil {\n\t\treturn err\n\t}\n\n\tfor topic, partitions := range r.blocks {\n\t\tif err := pe.putString(topic); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := pe.putInt32Array(partitions); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tpe.putEmptyTaggedFieldArray()\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\n\treturn nil\n}\n\nfunc (r *ListPartitionReassignmentsRequest) decode(pd packetDecoder, version int16) (err error) {\n\tr.Version = version\n\n\tif r.TimeoutMs, err = pd.getInt32(); err != nil {\n\t\treturn err\n\t}\n\n\ttopicCount, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif topicCount > 0 {\n\t\tr.blocks = make(map[string][]int32)\n\t\tfor i := 0; i < topicCount; i++ {\n\t\t\ttopic, err := pd.getString()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tpartitionCount, err := pd.getArrayLength()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tr.blocks[topic] = make([]int32, partitionCount)\n\t\t\tfor j := 0; j < partitionCount; j++ {\n\t\t\t\tpartition, err := pd.getInt32()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tr.blocks[topic][j] = partition\n\t\t\t}\n\t\t\tif _, err := pd.getEmptyTaggedFieldArray(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (r *ListPartitionReassignmentsRequest) key() int16 {\n\treturn apiKeyListPartitionReassignments\n}\n\nfunc (r *ListPartitionReassignmentsRequest) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *ListPartitionReassignmentsRequest) headerVersion() int16 {\n\treturn 2\n}\n\nfunc (r *ListPartitionReassignmentsRequest) isValidVersion() bool {\n\treturn r.Version == 0\n}\n\nfunc (r *ListPartitionReassignmentsRequest) isFlexible() bool {\n\treturn r.isFlexibleVersion(r.Version)\n}\n\nfunc (r *ListPartitionReassignmentsRequest) isFlexibleVersion(version int16) bool {\n\treturn version >= 0\n}\n\nfunc (r *ListPartitionReassignmentsRequest) requiredVersion() KafkaVersion {\n\treturn V2_4_0_0\n}\n\nfunc (r *ListPartitionReassignmentsRequest) AddBlock(topic string, partitionIDs []int32) {\n\tif r.blocks == nil {\n\t\tr.blocks = make(map[string][]int32)\n\t}\n\n\tif r.blocks[topic] == nil {\n\t\tr.blocks[topic] = partitionIDs\n\t}\n}\n"
  },
  {
    "path": "list_partition_reassignments_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nvar listPartitionReassignmentsRequestOneBlock = []byte{\n\t0, 0, 39, 16, // timeout 10000\n\t2,                         // 2-1=1 block\n\t6, 116, 111, 112, 105, 99, // topic name \"topic\" as compact string\n\t2,          // 2-1=1 partitions\n\t0, 0, 0, 0, // partitionId\n\t0, 0, // empty tagged fields\n}\n\nfunc TestListPartitionReassignmentRequest(t *testing.T) {\n\tvar request *ListPartitionReassignmentsRequest = &ListPartitionReassignmentsRequest{\n\t\tTimeoutMs: int32(10000),\n\t\tVersion:   int16(0),\n\t}\n\n\trequest.AddBlock(\"topic\", []int32{0})\n\n\ttestRequest(t, \"one block\", request, listPartitionReassignmentsRequestOneBlock)\n\n\trequest.AddBlock(\"topic2\", []int32{1, 2})\n\n\ttestRequestWithoutByteComparison(t, \"two blocks\", request)\n}\n"
  },
  {
    "path": "list_partition_reassignments_response.go",
    "content": "package sarama\n\nimport \"time\"\n\ntype PartitionReplicaReassignmentsStatus struct {\n\tReplicas         []int32\n\tAddingReplicas   []int32\n\tRemovingReplicas []int32\n}\n\nfunc (b *PartitionReplicaReassignmentsStatus) encode(pe packetEncoder) error {\n\tif err := pe.putInt32Array(b.Replicas); err != nil {\n\t\treturn err\n\t}\n\tif err := pe.putInt32Array(b.AddingReplicas); err != nil {\n\t\treturn err\n\t}\n\tif err := pe.putInt32Array(b.RemovingReplicas); err != nil {\n\t\treturn err\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\n\treturn nil\n}\n\nfunc (b *PartitionReplicaReassignmentsStatus) decode(pd packetDecoder) (err error) {\n\tif b.Replicas, err = pd.getInt32Array(); err != nil {\n\t\treturn err\n\t}\n\n\tif b.AddingReplicas, err = pd.getInt32Array(); err != nil {\n\t\treturn err\n\t}\n\n\tif b.RemovingReplicas, err = pd.getInt32Array(); err != nil {\n\t\treturn err\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\ntype ListPartitionReassignmentsResponse struct {\n\tVersion        int16\n\tThrottleTimeMs int32\n\tErrorCode      KError\n\tErrorMessage   *string\n\tTopicStatus    map[string]map[int32]*PartitionReplicaReassignmentsStatus\n}\n\nfunc (r *ListPartitionReassignmentsResponse) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *ListPartitionReassignmentsResponse) AddBlock(topic string, partition int32, replicas, addingReplicas, removingReplicas []int32) {\n\tif r.TopicStatus == nil {\n\t\tr.TopicStatus = make(map[string]map[int32]*PartitionReplicaReassignmentsStatus)\n\t}\n\tpartitions := r.TopicStatus[topic]\n\tif partitions == nil {\n\t\tpartitions = make(map[int32]*PartitionReplicaReassignmentsStatus)\n\t\tr.TopicStatus[topic] = partitions\n\t}\n\n\tpartitions[partition] = &PartitionReplicaReassignmentsStatus{Replicas: replicas, AddingReplicas: addingReplicas, RemovingReplicas: removingReplicas}\n}\n\nfunc (r *ListPartitionReassignmentsResponse) encode(pe packetEncoder) error {\n\tpe.putInt32(r.ThrottleTimeMs)\n\tpe.putKError(r.ErrorCode)\n\tif err := pe.putNullableString(r.ErrorMessage); err != nil {\n\t\treturn err\n\t}\n\n\tif err := pe.putArrayLength(len(r.TopicStatus)); err != nil {\n\t\treturn err\n\t}\n\tfor topic, partitions := range r.TopicStatus {\n\t\tif err := pe.putString(topic); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := pe.putArrayLength(len(partitions)); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor partition, block := range partitions {\n\t\t\tpe.putInt32(partition)\n\n\t\t\tif err := block.encode(pe); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tpe.putEmptyTaggedFieldArray()\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\n\treturn nil\n}\n\nfunc (r *ListPartitionReassignmentsResponse) decode(pd packetDecoder, version int16) (err error) {\n\tr.Version = version\n\n\tif r.ThrottleTimeMs, err = pd.getInt32(); err != nil {\n\t\treturn err\n\t}\n\n\tr.ErrorCode, err = pd.getKError()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif r.ErrorMessage, err = pd.getNullableString(); err != nil {\n\t\treturn err\n\t}\n\n\tnumTopics, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr.TopicStatus = make(map[string]map[int32]*PartitionReplicaReassignmentsStatus, numTopics)\n\tfor i := 0; i < numTopics; i++ {\n\t\ttopic, err := pd.getString()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tongoingPartitionReassignments, err := pd.getArrayLength()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tr.TopicStatus[topic] = make(map[int32]*PartitionReplicaReassignmentsStatus, ongoingPartitionReassignments)\n\n\t\tfor j := 0; j < ongoingPartitionReassignments; j++ {\n\t\t\tpartition, err := pd.getInt32()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tblock := &PartitionReplicaReassignmentsStatus{}\n\t\t\tif err := block.decode(pd); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tr.TopicStatus[topic][partition] = block\n\t\t}\n\n\t\tif _, err := pd.getEmptyTaggedFieldArray(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (r *ListPartitionReassignmentsResponse) key() int16 {\n\treturn apiKeyListPartitionReassignments\n}\n\nfunc (r *ListPartitionReassignmentsResponse) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *ListPartitionReassignmentsResponse) headerVersion() int16 {\n\treturn 1\n}\n\nfunc (r *ListPartitionReassignmentsResponse) isValidVersion() bool {\n\treturn r.Version == 0\n}\n\nfunc (r *ListPartitionReassignmentsResponse) isFlexible() bool {\n\treturn r.isFlexibleVersion(r.Version)\n}\n\nfunc (r *ListPartitionReassignmentsResponse) isFlexibleVersion(version int16) bool {\n\treturn version >= 0\n}\n\nfunc (r *ListPartitionReassignmentsResponse) requiredVersion() KafkaVersion {\n\treturn V2_4_0_0\n}\n\nfunc (r *ListPartitionReassignmentsResponse) throttleTime() time.Duration {\n\treturn time.Duration(r.ThrottleTimeMs) * time.Millisecond\n}\n"
  },
  {
    "path": "list_partition_reassignments_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nvar listPartitionReassignmentsResponse = []byte{\n\t0, 0, 39, 16, // ThrottleTimeMs 10000\n\t0, 0, // errorcode\n\t0,                         // null string\n\t2,                         // block array length 1\n\t6, 116, 111, 112, 105, 99, // topic name \"topic\"\n\t2,          // partition array length 1\n\t0, 0, 0, 1, // partitionId\n\t3, 0, 0, 3, 232, 0, 0, 3, 233, // replicas [1000, 1001]\n\t3, 0, 0, 3, 234, 0, 0, 3, 235, // addingReplicas [1002, 1003]\n\t3, 0, 0, 3, 236, 0, 0, 3, 237, // addingReplicas [1004, 1005]\n\t0, 0, 0, // empty tagged fields\n}\n\nfunc TestListPartitionReassignmentResponse(t *testing.T) {\n\tvar response *ListPartitionReassignmentsResponse = &ListPartitionReassignmentsResponse{\n\t\tThrottleTimeMs: int32(10000),\n\t\tVersion:        int16(0),\n\t}\n\n\tresponse.AddBlock(\"topic\", 1, []int32{1000, 1001}, []int32{1002, 1003}, []int32{1004, 1005})\n\n\ttestResponse(t, \"one topic\", response, listPartitionReassignmentsResponse)\n}\n"
  },
  {
    "path": "logger_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\n// testLogger implements the StdLogger interface and records the text in the\n// logs of the given T passed from Test functions.\n// and records the text in the error log.\n//\n// nolint:unused\ntype testLogger struct {\n\tt *testing.T\n}\n\n// nolint:unused\nfunc (l *testLogger) Print(v ...interface{}) {\n\tif l.t != nil {\n\t\tl.t.Helper()\n\t\tl.t.Log(v...)\n\t}\n}\n\n// nolint:unused\nfunc (l *testLogger) Printf(format string, v ...interface{}) {\n\tif l.t != nil {\n\t\tl.t.Helper()\n\t\tl.t.Logf(format, v...)\n\t}\n}\n\n// nolint:unused\nfunc (l *testLogger) Println(v ...interface{}) {\n\tif l.t != nil {\n\t\tl.t.Helper()\n\t\tl.t.Log(v...)\n\t}\n}\n"
  },
  {
    "path": "message.go",
    "content": "package sarama\n\nimport (\n\t\"fmt\"\n\t\"time\"\n)\n\nconst (\n\t// CompressionNone no compression\n\tCompressionNone CompressionCodec = iota\n\t// CompressionGZIP compression using GZIP\n\tCompressionGZIP\n\t// CompressionSnappy compression using snappy\n\tCompressionSnappy\n\t// CompressionLZ4 compression using LZ4\n\tCompressionLZ4\n\t// CompressionZSTD compression using ZSTD\n\tCompressionZSTD\n\n\t// The lowest 3 bits contain the compression codec used for the message\n\tcompressionCodecMask int8 = 0x07\n\n\t// Bit 3 set for \"LogAppend\" timestamps\n\ttimestampTypeMask = 0x08\n\n\t// CompressionLevelDefault is the constant to use in CompressionLevel\n\t// to have the default compression level for any codec. The value is picked\n\t// that we don't use any existing compression levels.\n\tCompressionLevelDefault = -1000\n)\n\n// CompressionCodec represents the various compression codecs recognized by Kafka in messages.\ntype CompressionCodec int8\n\nfunc (cc CompressionCodec) String() string {\n\treturn []string{\n\t\t\"none\",\n\t\t\"gzip\",\n\t\t\"snappy\",\n\t\t\"lz4\",\n\t\t\"zstd\",\n\t}[int(cc)]\n}\n\n// UnmarshalText returns a CompressionCodec from its string representation.\nfunc (cc *CompressionCodec) UnmarshalText(text []byte) error {\n\tcodecs := map[string]CompressionCodec{\n\t\t\"none\":   CompressionNone,\n\t\t\"gzip\":   CompressionGZIP,\n\t\t\"snappy\": CompressionSnappy,\n\t\t\"lz4\":    CompressionLZ4,\n\t\t\"zstd\":   CompressionZSTD,\n\t}\n\tcodec, ok := codecs[string(text)]\n\tif !ok {\n\t\treturn fmt.Errorf(\"cannot parse %q as a compression codec\", string(text))\n\t}\n\t*cc = codec\n\treturn nil\n}\n\n// MarshalText transforms a CompressionCodec into its string representation.\nfunc (cc CompressionCodec) MarshalText() ([]byte, error) {\n\treturn []byte(cc.String()), nil\n}\n\n// Message is a kafka message type\ntype Message struct {\n\tCodec            CompressionCodec // codec used to compress the message contents\n\tCompressionLevel int              // compression level\n\tLogAppendTime    bool             // the used timestamp is LogAppendTime\n\tKey              []byte           // the message key, may be nil\n\tValue            []byte           // the message contents\n\tSet              *MessageSet      // the message set a message might wrap\n\tVersion          int8             // v1 requires Kafka 0.10\n\tTimestamp        time.Time        // the timestamp of the message (version 1+ only)\n\n\tcompressedCache []byte\n\tcompressedSize  int // used for computing the compression ratio metrics\n}\n\nfunc (m *Message) encode(pe packetEncoder) error {\n\tpe.push(newCRC32Field(crcIEEE))\n\n\tpe.putInt8(m.Version)\n\n\tattributes := int8(m.Codec) & compressionCodecMask\n\tif m.LogAppendTime {\n\t\tattributes |= timestampTypeMask\n\t}\n\tpe.putInt8(attributes)\n\n\tif m.Version >= 1 {\n\t\tif err := (Timestamp{&m.Timestamp}).encode(pe); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\terr := pe.putBytes(m.Key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar payload []byte\n\n\tif m.compressedCache != nil {\n\t\tpayload = m.compressedCache\n\t\tm.compressedCache = nil\n\t} else if m.Value != nil {\n\t\tpayload, err = compress(m.Codec, m.CompressionLevel, m.Value)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tm.compressedCache = payload\n\t\t// Keep in mind the compressed payload size for metric gathering\n\t\tm.compressedSize = len(payload)\n\t}\n\n\tif err = pe.putBytes(payload); err != nil {\n\t\treturn err\n\t}\n\n\treturn pe.pop()\n}\n\nfunc (m *Message) decode(pd packetDecoder) (err error) {\n\tcrc32Decoder := acquireCrc32Field(crcIEEE)\n\tdefer releaseCrc32Field(crc32Decoder)\n\n\terr = pd.push(crc32Decoder)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tm.Version, err = pd.getInt8()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif m.Version > 1 {\n\t\treturn PacketDecodingError{fmt.Sprintf(\"unknown magic byte (%v)\", m.Version)}\n\t}\n\n\tattribute, err := pd.getInt8()\n\tif err != nil {\n\t\treturn err\n\t}\n\tm.Codec = CompressionCodec(attribute & compressionCodecMask)\n\tm.LogAppendTime = attribute&timestampTypeMask == timestampTypeMask\n\n\tif m.Version == 1 {\n\t\tif err := (Timestamp{&m.Timestamp}).decode(pd); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tm.Key, err = pd.getBytes()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tm.Value, err = pd.getBytes()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Required for deep equal assertion during tests but might be useful\n\t// for future metrics about the compression ratio in fetch requests\n\tm.compressedSize = len(m.Value)\n\n\tif m.Value != nil && m.Codec != CompressionNone {\n\t\tm.Value, err = decompress(m.Codec, m.Value)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := m.decodeSet(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn pd.pop()\n}\n\n// decodes a message set from a previously encoded bulk-message\nfunc (m *Message) decodeSet() (err error) {\n\tpd := realDecoder{raw: m.Value}\n\tm.Set = &MessageSet{}\n\treturn m.Set.decode(&pd)\n}\n"
  },
  {
    "path": "message_set.go",
    "content": "package sarama\n\nimport \"errors\"\n\ntype MessageBlock struct {\n\tOffset int64\n\tMsg    *Message\n}\n\n// Messages convenience helper which returns either all the\n// messages that are wrapped in this block\nfunc (msb *MessageBlock) Messages() []*MessageBlock {\n\tif msb.Msg.Set != nil {\n\t\treturn msb.Msg.Set.Messages\n\t}\n\treturn []*MessageBlock{msb}\n}\n\nfunc (msb *MessageBlock) encode(pe packetEncoder) error {\n\tpe.putInt64(msb.Offset)\n\tpe.push(&lengthField{})\n\terr := msb.Msg.encode(pe)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn pe.pop()\n}\n\nfunc (msb *MessageBlock) decode(pd packetDecoder) (err error) {\n\tif msb.Offset, err = pd.getInt64(); err != nil {\n\t\treturn err\n\t}\n\n\tlengthDecoder := acquireLengthField()\n\tdefer releaseLengthField(lengthDecoder)\n\n\tif err = pd.push(lengthDecoder); err != nil {\n\t\treturn err\n\t}\n\n\tmsb.Msg = new(Message)\n\tif err = msb.Msg.decode(pd); err != nil {\n\t\treturn err\n\t}\n\n\tif err = pd.pop(); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\ntype MessageSet struct {\n\tPartialTrailingMessage bool // whether the set on the wire contained an incomplete trailing MessageBlock\n\tOverflowMessage        bool // whether the set on the wire contained an overflow message\n\tMessages               []*MessageBlock\n}\n\nfunc (ms *MessageSet) encode(pe packetEncoder) error {\n\tfor i := range ms.Messages {\n\t\terr := ms.Messages[i].encode(pe)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (ms *MessageSet) decode(pd packetDecoder) (err error) {\n\tms.Messages = nil\n\n\tfor pd.remaining() > 0 {\n\t\tmagic, err := magicValue(pd)\n\t\tif err != nil {\n\t\t\tif errors.Is(err, ErrInsufficientData) {\n\t\t\t\tms.PartialTrailingMessage = true\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\n\t\tif magic > 1 {\n\t\t\treturn nil\n\t\t}\n\n\t\tmsb := new(MessageBlock)\n\t\terr = msb.decode(pd)\n\t\tif err == nil {\n\t\t\tms.Messages = append(ms.Messages, msb)\n\t\t} else if errors.Is(err, ErrInsufficientData) {\n\t\t\t// As an optimization the server is allowed to return a partial message at the\n\t\t\t// end of the message set. Clients should handle this case. So we just ignore such things.\n\t\t\tif msb.Offset == -1 {\n\t\t\t\t// This is an overflow message caused by chunked down conversion\n\t\t\t\tms.OverflowMessage = true\n\t\t\t} else {\n\t\t\t\tms.PartialTrailingMessage = true\n\t\t\t}\n\t\t\treturn nil\n\t\t} else {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (ms *MessageSet) addMessage(msg *Message) {\n\tblock := new(MessageBlock)\n\tblock.Msg = msg\n\tms.Messages = append(ms.Messages, block)\n}\n"
  },
  {
    "path": "message_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nvar (\n\temptyMessage = []byte{\n\t\t167, 236, 104, 3, // CRC\n\t\t0x00,                   // magic version byte\n\t\t0x00,                   // attribute flags\n\t\t0xFF, 0xFF, 0xFF, 0xFF, // key\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t} // value\n\n\temptyV1Message = []byte{\n\t\t204, 47, 121, 217, // CRC\n\t\t0x01,                                           // magic version byte\n\t\t0x00,                                           // attribute flags\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // timestamp\n\t\t0xFF, 0xFF, 0xFF, 0xFF, // key\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t} // value\n\n\temptyV2Message = []byte{\n\t\t167, 236, 104, 3, // CRC\n\t\t0x02,                   // magic version byte\n\t\t0x00,                   // attribute flags\n\t\t0xFF, 0xFF, 0xFF, 0xFF, // key\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t} // value\n\n\temptyGzipMessage = []byte{\n\t\t196, 46, 92, 177, // CRC\n\t\t0x00,                   // magic version byte\n\t\t0x01,                   // attribute flags\n\t\t0xFF, 0xFF, 0xFF, 0xFF, // key\n\t\t// value\n\t\t0x00, 0x00, 0x00, 0x14,\n\t\t0x1f, 0x8b,\n\t\t0x08,\n\t\t0, 0, 9, 110, 136, 0, 255, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t}\n\n\temptyLZ4Message = []byte{\n\t\t136, 42, 245, 190, // CRC\n\t\t0x01,                          // version byte\n\t\t0x03,                          // attribute flags: lz4\n\t\t0, 0, 1, 88, 141, 205, 89, 56, // timestamp\n\t\t0xFF, 0xFF, 0xFF, 0xFF, // key\n\t\t0x00, 0x00, 0x00, 0x0f, // len\n\t\t0x04, 0x22, 0x4D, 0x18, // LZ4 magic number\n\t\t100,                 // LZ4 flags: version 01, block independent, content checksum\n\t\t64, 167, 0, 0, 0, 0, // LZ4 data\n\t\t5, 93, 204, 2, // LZ4 checksum\n\t}\n\n\temptyZSTDMessage = []byte{\n\t\t180, 172, 84, 179, // CRC\n\t\t0x01,                          // version byte\n\t\t0x04,                          // attribute flags: zstd\n\t\t0, 0, 1, 88, 141, 205, 89, 56, // timestamp\n\t\t0xFF, 0xFF, 0xFF, 0xFF, // key\n\t\t0x00, 0x00, 0x00, 0x09, // len\n\t\t// ZSTD data\n\t\t0x28, 0xb5, 0x2f, 0xfd, 0x20, 0x00, 0x01, 0x00, 0x00,\n\t}\n\n\temptyBulkSnappyMessage = []byte{\n\t\t180, 47, 53, 209, // CRC\n\t\t0x00,                   // magic version byte\n\t\t0x02,                   // attribute flags\n\t\t0xFF, 0xFF, 0xFF, 0xFF, // key\n\t\t0, 0, 0, 42,\n\t\t130, 83, 78, 65, 80, 80, 89, 0, // SNAPPY magic\n\t\t0, 0, 0, 1, // min version\n\t\t0, 0, 0, 1, // default version\n\t\t0, 0, 0, 22, 52, 0, 0, 25, 1, 16, 14, 227, 138, 104, 118, 25, 15, 13, 1, 8, 1, 0, 0, 62, 26, 0,\n\t}\n\n\temptyBulkGzipMessage = []byte{\n\t\t139, 160, 63, 141, // CRC\n\t\t0x00,                   // magic version byte\n\t\t0x01,                   // attribute flags\n\t\t0xFF, 0xFF, 0xFF, 0xFF, // key\n\t\t0x00, 0x00, 0x00, 0x27, // len\n\t\t0x1f, 0x8b, // Gzip Magic\n\t\t0x08, // deflate compressed\n\t\t0, 0, 0, 0, 0, 0, 0, 99, 96, 128, 3, 190, 202, 112, 143, 7, 12, 12, 255, 129, 0, 33, 200, 192, 136, 41, 3, 0, 199, 226, 155, 70, 52, 0, 0, 0,\n\t}\n\n\temptyBulkLZ4Message = []byte{\n\t\t246, 12, 188, 129, // CRC\n\t\t0x01,                                  // Version\n\t\t0x03,                                  // attribute flags (LZ4)\n\t\t255, 255, 249, 209, 212, 181, 73, 201, // timestamp\n\t\t0xFF, 0xFF, 0xFF, 0xFF, // key\n\t\t0x00, 0x00, 0x00, 0x47, // len\n\t\t0x04, 0x22, 0x4D, 0x18, // magic number lz4\n\t\t100, // lz4 flags 01100100\n\t\t// version: 01, block indep: 1, block checksum: 0, content size: 0, content checksum: 1, reserved: 00\n\t\t112, 185, 52, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 121, 87, 72, 224, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 14, 121, 87, 72, 224, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0,\n\t\t71, 129, 23, 111, // LZ4 checksum\n\t}\n\n\temptyBulkZSTDMessage = []byte{\n\t\t203, 151, 133, 28, // CRC\n\t\t0x01,                                  // Version\n\t\t0x04,                                  // attribute flags (ZSTD)\n\t\t255, 255, 249, 209, 212, 181, 73, 201, // timestamp\n\t\t0xFF, 0xFF, 0xFF, 0xFF, // key\n\t\t0x00, 0x00, 0x00, 0x26, // len\n\t\t// ZSTD data\n\t\t0x28, 0xb5, 0x2f, 0xfd, 0x24, 0x34, 0xcd, 0x0, 0x0, 0x78, 0x0, 0x0, 0xe, 0x79, 0x57, 0x48, 0xe0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0x0, 0x1, 0x3, 0x0, 0x3d, 0xbd, 0x0, 0x3b, 0x15, 0x0, 0xb, 0xd2, 0x34, 0xc1, 0x78,\n\t}\n)\n\nfunc TestMessageEncoding(t *testing.T) {\n\tmessage := Message{}\n\ttestEncodable(t, \"empty\", &message, emptyMessage)\n\n\tmessage.Value = []byte{}\n\tmessage.Codec = CompressionGZIP\n\ttestEncodable(t, \"empty gzip\", &message, emptyGzipMessage)\n\n\tmessage.Value = []byte{}\n\tmessage.Codec = CompressionLZ4\n\tmessage.Timestamp = time.Unix(1479847795, 0)\n\tmessage.Version = 1\n\ttestEncodable(t, \"empty lz4\", &message, emptyLZ4Message)\n\n\tmessage.Value = []byte{}\n\tmessage.Codec = CompressionZSTD\n\tmessage.Timestamp = time.Unix(1479847795, 0)\n\tmessage.Version = 1\n\ttestEncodable(t, \"empty zstd\", &message, emptyZSTDMessage)\n}\n\nfunc TestMessageDecoding(t *testing.T) {\n\tmessage := Message{}\n\ttestDecodable(t, \"empty\", &message, emptyMessage)\n\tif message.Codec != CompressionNone {\n\t\tt.Error(\"Decoding produced compression codec where there was none.\")\n\t}\n\tif message.Key != nil {\n\t\tt.Error(\"Decoding produced key where there was none.\")\n\t}\n\tif message.Value != nil {\n\t\tt.Error(\"Decoding produced value where there was none.\")\n\t}\n\tif message.Set != nil {\n\t\tt.Error(\"Decoding produced set where there was none.\")\n\t}\n\n\ttestDecodable(t, \"empty gzip\", &message, emptyGzipMessage)\n\tif message.Codec != CompressionGZIP {\n\t\tt.Error(\"Decoding produced incorrect compression codec (was gzip).\")\n\t}\n\tif message.Key != nil {\n\t\tt.Error(\"Decoding produced key where there was none.\")\n\t}\n\tif message.Value == nil || len(message.Value) != 0 {\n\t\tt.Error(\"Decoding produced nil or content-ful value where there was an empty array.\")\n\t}\n}\n\nfunc TestMessageDecodingBulkSnappy(t *testing.T) {\n\tmessage := Message{}\n\ttestDecodable(t, \"bulk snappy\", &message, emptyBulkSnappyMessage)\n\tif message.Codec != CompressionSnappy {\n\t\tt.Errorf(\"Decoding produced codec %d, but expected %d.\", message.Codec, CompressionSnappy)\n\t}\n\tif message.Key != nil {\n\t\tt.Errorf(\"Decoding produced key %+v, but none was expected.\", message.Key)\n\t}\n\tif message.Set == nil {\n\t\tt.Error(\"Decoding produced no set, but one was expected.\")\n\t} else if len(message.Set.Messages) != 2 {\n\t\tt.Errorf(\"Decoding produced a set with %d messages, but 2 were expected.\", len(message.Set.Messages))\n\t}\n}\n\nfunc TestMessageDecodingBulkGzip(t *testing.T) {\n\tmessage := Message{}\n\ttestDecodable(t, \"bulk gzip\", &message, emptyBulkGzipMessage)\n\tif message.Codec != CompressionGZIP {\n\t\tt.Errorf(\"Decoding produced codec %d, but expected %d.\", message.Codec, CompressionGZIP)\n\t}\n\tif message.Key != nil {\n\t\tt.Errorf(\"Decoding produced key %+v, but none was expected.\", message.Key)\n\t}\n\tif message.Set == nil {\n\t\tt.Error(\"Decoding produced no set, but one was expected.\")\n\t} else if len(message.Set.Messages) != 2 {\n\t\tt.Errorf(\"Decoding produced a set with %d messages, but 2 were expected.\", len(message.Set.Messages))\n\t}\n}\n\nfunc TestMessageDecodingBulkLZ4(t *testing.T) {\n\tmessage := Message{}\n\ttestDecodable(t, \"bulk lz4\", &message, emptyBulkLZ4Message)\n\tif message.Codec != CompressionLZ4 {\n\t\tt.Errorf(\"Decoding produced codec %d, but expected %d.\", message.Codec, CompressionLZ4)\n\t}\n\tif message.Key != nil {\n\t\tt.Errorf(\"Decoding produced key %+v, but none was expected.\", message.Key)\n\t}\n\tif message.Set == nil {\n\t\tt.Error(\"Decoding produced no set, but one was expected.\")\n\t} else if len(message.Set.Messages) != 2 {\n\t\tt.Errorf(\"Decoding produced a set with %d messages, but 2 were expected.\", len(message.Set.Messages))\n\t}\n}\n\nfunc TestMessageDecodingBulkZSTD(t *testing.T) {\n\tmessage := Message{}\n\ttestDecodable(t, \"bulk zstd\", &message, emptyBulkZSTDMessage)\n\tif message.Codec != CompressionZSTD {\n\t\tt.Errorf(\"Decoding produced codec %d, but expected %d.\", message.Codec, CompressionZSTD)\n\t}\n\tif message.Key != nil {\n\t\tt.Errorf(\"Decoding produced key %+v, but none was expected.\", message.Key)\n\t}\n\tif message.Set == nil {\n\t\tt.Error(\"Decoding produced no set, but one was expected.\")\n\t} else if len(message.Set.Messages) != 2 {\n\t\tt.Errorf(\"Decoding produced a set with %d messages, but 2 were expected.\", len(message.Set.Messages))\n\t}\n}\n\nfunc TestMessageDecodingVersion1(t *testing.T) {\n\tmessage := Message{Version: 1}\n\ttestDecodable(t, \"decoding empty v1 message\", &message, emptyV1Message)\n}\n\nfunc TestMessageDecodingUnknownVersions(t *testing.T) {\n\tmessage := Message{Version: 2}\n\terr := decode(emptyV2Message, &message, nil)\n\tif err == nil {\n\t\tt.Error(\"Decoding did not produce an error for an unknown magic byte\")\n\t}\n\tif err.Error() != \"kafka: error decoding packet: unknown magic byte (2)\" {\n\t\tt.Error(\"Decoding an unknown magic byte produced an unknown error \", err)\n\t}\n}\n\nfunc TestCompressionCodecUnmarshal(t *testing.T) {\n\tcases := []struct {\n\t\tInput         string\n\t\tExpected      CompressionCodec\n\t\tExpectedError bool\n\t}{\n\t\t{\"none\", CompressionNone, false},\n\t\t{\"zstd\", CompressionZSTD, false},\n\t\t{\"gzip\", CompressionGZIP, false},\n\t\t{\"unknown\", CompressionNone, true},\n\t}\n\tfor _, c := range cases {\n\t\tvar cc CompressionCodec\n\t\terr := cc.UnmarshalText([]byte(c.Input))\n\t\tif err != nil && !c.ExpectedError {\n\t\t\tt.Errorf(\"UnmarshalText(%q) error:\\n%+v\", c.Input, err)\n\t\t\tcontinue\n\t\t}\n\t\tif err == nil && c.ExpectedError {\n\t\t\tt.Errorf(\"UnmarshalText(%q) got %v but expected error\", c.Input, cc)\n\t\t\tcontinue\n\t\t}\n\t\tif cc != c.Expected {\n\t\t\tt.Errorf(\"UnmarshalText(%q) got %v but expected %v\", c.Input, cc, c.Expected)\n\t\t\tcontinue\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "metadata.go",
    "content": "package sarama\n\nimport (\n\t\"sync\"\n)\n\ntype metadataRefresh func(topics []string) error\n\n// currentRefresh makes sure sarama does not issue metadata requests\n// in parallel. If we need to refresh the metadata for a list of topics,\n// this struct will check if a refresh is already ongoing, and if so, it will\n// accumulate the list of topics to refresh in the next refresh.\n// When the current refresh is over, it will queue a new metadata refresh call\n// with the accumulated list of topics.\ntype currentRefresh struct {\n\t// This is the function that gets called when to refresh the metadata.\n\t// It is called with the list of all topics that need to be refreshed\n\t// or with nil if all topics need to be refreshed.\n\trefresh func(topics []string) error\n\n\tmu        sync.Mutex\n\tongoing   bool\n\ttopicsMap map[string]struct{}\n\ttopics    []string\n\tallTopics bool\n\tchans     []chan error\n}\n\n// addTopicsFrom adds topics from the next refresh to the current refresh.\n// You need to hold the lock to call this method.\nfunc (r *currentRefresh) addTopicsFrom(next *nextRefresh) {\n\tif next.allTopics {\n\t\tr.allTopics = true\n\t\treturn\n\t}\n\tif len(next.topics) > 0 {\n\t\tr.addTopics(next.topics)\n\t}\n}\n\n// nextRefresh holds the list of topics we will need\n// to refresh in the next refresh.\n// When a refresh is ongoing, calls to RefreshMetadata() are\n// accumulated in this struct, so that we can immediately issue another\n// refresh when the current refresh is over.\ntype nextRefresh struct {\n\tmu        sync.Mutex\n\ttopics    []string\n\tallTopics bool\n}\n\n// addTopics adds topics to the refresh.\n// You need to hold the lock to call this method.\nfunc (r *currentRefresh) addTopics(topics []string) {\n\tif len(topics) == 0 {\n\t\tr.allTopics = true\n\t\treturn\n\t}\n\tfor _, topic := range topics {\n\t\tif _, ok := r.topicsMap[topic]; ok {\n\t\t\tcontinue\n\t\t}\n\t\tr.topicsMap[topic] = struct{}{}\n\t\tr.topics = append(r.topics, topic)\n\t}\n}\n\nfunc (r *nextRefresh) addTopics(topics []string) {\n\tif len(topics) == 0 {\n\t\tr.allTopics = true\n\t\t// All topics are requested, so we can clear the topics\n\t\t// that were previously accumulated.\n\t\tr.topics = r.topics[:0]\n\t\treturn\n\t}\n\tr.topics = append(r.topics, topics...)\n}\n\nfunc (r *nextRefresh) clear() {\n\tr.topics = r.topics[:0]\n\tr.allTopics = false\n}\n\nfunc (r *currentRefresh) hasTopics(topics []string) bool {\n\tif len(topics) == 0 {\n\t\t// This means that the caller wants to know if the refresh is for all topics.\n\t\t// In this case, we return true if the refresh is for all topics, or false if it is not.\n\t\treturn r.allTopics\n\t}\n\tif r.allTopics {\n\t\treturn true\n\t}\n\tfor _, topic := range topics {\n\t\tif _, ok := r.topicsMap[topic]; !ok {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// start starts a new refresh.\n// The refresh is started in a new goroutine, and this function\n// returns a channel on which the caller can wait for the refresh\n// to complete.\n// You need to hold the lock to call this method.\nfunc (r *currentRefresh) start() chan error {\n\tr.ongoing = true\n\tch := r.wait()\n\ttopics := r.topics\n\tif r.allTopics {\n\t\ttopics = nil\n\t}\n\tgo func() {\n\t\terr := r.refresh(topics)\n\t\tr.mu.Lock()\n\t\tdefer r.mu.Unlock()\n\n\t\tr.ongoing = false\n\t\tfor _, ch := range r.chans {\n\t\t\tch <- err\n\t\t\tclose(ch)\n\t\t}\n\t\tr.clear()\n\t}()\n\treturn ch\n}\n\n// clear clears the refresh state.\n// You need to hold the lock to call this method.\nfunc (r *currentRefresh) clear() {\n\tr.topics = r.topics[:0]\n\tfor key := range r.topicsMap {\n\t\tdelete(r.topicsMap, key)\n\t}\n\tr.allTopics = false\n\tr.chans = r.chans[:0]\n}\n\n// wait returns the channel on which you can wait for the refresh\n// to complete.\n// You need to hold the lock to call this method.\nfunc (r *currentRefresh) wait() chan error {\n\tif !r.ongoing {\n\t\tpanic(\"waiting for a refresh that is not ongoing\")\n\t}\n\tch := make(chan error, 1)\n\tr.chans = append(r.chans, ch)\n\treturn ch\n}\n\n// singleFlightMetadataRefresher helps managing metadata refreshes.\n// It makes sure a sarama client never issues more than one metadata refresh\n// in parallel.\ntype singleFlightMetadataRefresher struct {\n\tcurrent *currentRefresh\n\tnext    *nextRefresh\n}\n\nfunc newSingleFlightRefresher(f func(topics []string) error) metadataRefresh {\n\treturn newMetadataRefresh(f).Refresh\n}\n\nfunc newMetadataRefresh(f func(topics []string) error) *singleFlightMetadataRefresher {\n\treturn &singleFlightMetadataRefresher{\n\t\tcurrent: &currentRefresh{\n\t\t\ttopicsMap: make(map[string]struct{}),\n\t\t\trefresh:   f,\n\t\t},\n\t\tnext: &nextRefresh{},\n\t}\n}\n\n// Refresh is the function that clients call when they want to refresh\n// the metadata. This function blocks until a refresh is issued, and its\n// result is received, for the list of topics the caller provided.\n// If a refresh was already ongoing for this list of topics, the function\n// waits on that refresh to complete, and returns its result.\n// If a refresh was already ongoing for a different list of topics, the function\n// accumulates the list of topics to refresh in the next refresh, and queues that refresh.\n// If no refresh is ongoing, it will start a new refresh, and return its result.\nfunc (m *singleFlightMetadataRefresher) Refresh(topics []string) error {\n\tfor {\n\t\tch, queued := m.refreshOrQueue(topics)\n\t\tif !queued {\n\t\t\treturn <-ch\n\t\t}\n\t\t<-ch\n\t}\n}\n\n// refreshOrQueue returns a channel the refresh needs to wait on, and a boolean\n// that indicates whether waiting on the channel will return the result of that refresh\n// or whether the refresh was \"queued\" and the caller needs to wait for the channel to\n// return, and then call refreshOrQueue again.\n// When calling refreshOrQueue, three things can happen:\n//  1. either no refresh is ongoing.\n//     In this case, a new refresh is started, and the channel that's returned will\n//     contain the result of that refresh, so it returns \"false\" as the second return value.\n//  2. a refresh is ongoing, and it contains the topics we need.\n//     In this case, the channel that's returned will contain the result of that refresh,\n//     so it returns \"false\" as the second return value.\n//     In this case, the channel that's returned will contain the result of that refresh,\n//     so it returns \"false\" as the second return value.\n//  3. a refresh is already ongoing, but doesn't contain the topics we need. In this case,\n//     the caller needs to wait for the refresh to finish, and then call refreshOrQueue again.\n//     The channel that's returned is for the current refresh (not the one the caller is\n//     interested in), so it returns \"true\" as the second return value. The caller needs to\n//     wait on the channel, disregard the value, and call refreshOrQueue again.\nfunc (m *singleFlightMetadataRefresher) refreshOrQueue(topics []string) (chan error, bool) {\n\tm.current.mu.Lock()\n\tdefer m.current.mu.Unlock()\n\tif !m.current.ongoing {\n\t\t// If no refresh is ongoing, we can start a new one, in which\n\t\t// we add the topics that have been accumulated in the next refresh\n\t\t// and the topics that have been provided by the caller.\n\t\tm.next.mu.Lock()\n\t\tm.current.addTopicsFrom(m.next)\n\t\tm.next.clear()\n\t\tm.next.mu.Unlock()\n\t\tm.current.addTopics(topics)\n\t\tch := m.current.start()\n\t\treturn ch, false\n\t}\n\tif m.current.hasTopics(topics) {\n\t\t// A refresh is ongoing, and we were lucky: it is refreshing the topics we need already:\n\t\t// we just have to wait for it to finish and return its results.\n\t\tch := m.current.wait()\n\t\treturn ch, false\n\t}\n\t// There is a refresh ongoing, but it is not refreshing the topics we need.\n\t// We need to wait for it to finish, and then start a new refresh.\n\tch := m.current.wait()\n\tm.next.mu.Lock()\n\tm.next.addTopics(topics)\n\tm.next.mu.Unlock()\n\t// This is where we wait for that refresh to finish, and the loop will take care\n\t// of starting the new one.\n\treturn ch, true\n}\n"
  },
  {
    "path": "metadata_request.go",
    "content": "package sarama\n\nimport \"encoding/base64\"\n\ntype Uuid [16]byte\n\nfunc (u Uuid) String() string {\n\treturn base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(u[:])\n}\n\nvar NullUUID = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}\n\ntype MetadataRequest struct {\n\t// Version defines the protocol version to use for encode and decode\n\tVersion int16\n\t// Topics contains the topics to fetch metadata for.\n\tTopics []string\n\t// AllowAutoTopicCreation contains a If this is true, the broker may auto-create topics that we requested which do not already exist, if it is configured to do so.\n\tAllowAutoTopicCreation             bool\n\tIncludeClusterAuthorizedOperations bool // version 8 and up\n\tIncludeTopicAuthorizedOperations   bool // version 8 and up\n}\n\nfunc (r *MetadataRequest) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc NewMetadataRequest(version KafkaVersion, topics []string) *MetadataRequest {\n\tm := &MetadataRequest{Topics: topics}\n\tif version.IsAtLeast(V2_8_0_0) {\n\t\tm.Version = 10\n\t} else if version.IsAtLeast(V2_4_0_0) {\n\t\tm.Version = 9\n\t} else if version.IsAtLeast(V2_4_0_0) {\n\t\tm.Version = 8\n\t} else if version.IsAtLeast(V2_1_0_0) {\n\t\tm.Version = 7\n\t} else if version.IsAtLeast(V2_0_0_0) {\n\t\tm.Version = 6\n\t} else if version.IsAtLeast(V1_0_0_0) {\n\t\tm.Version = 5\n\t} else if version.IsAtLeast(V0_11_0_0) {\n\t\tm.Version = 4\n\t} else if version.IsAtLeast(V0_10_1_0) {\n\t\tm.Version = 2\n\t} else if version.IsAtLeast(V0_10_0_0) {\n\t\tm.Version = 1\n\t}\n\treturn m\n}\n\nfunc (r *MetadataRequest) encode(pe packetEncoder) (err error) {\n\tif r.Version < 0 || r.Version > 10 {\n\t\treturn PacketEncodingError{\"invalid or unsupported MetadataRequest version field\"}\n\t}\n\tif r.Version == 0 || len(r.Topics) > 0 {\n\t\tif err := pe.putArrayLength(len(r.Topics)); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif r.Version <= 9 {\n\t\t\tfor _, topicName := range r.Topics {\n\t\t\t\tif err := pe.putString(topicName); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tpe.putEmptyTaggedFieldArray()\n\t\t\t}\n\t\t} else { // r.Version = 10\n\t\t\tfor _, topicName := range r.Topics {\n\t\t\t\tif err := pe.putRawBytes(NullUUID); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\t// Avoid implicit memory aliasing in for loop\n\t\t\t\ttn := topicName\n\t\t\t\tif err := pe.putNullableString(&tn); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tpe.putEmptyTaggedFieldArray()\n\t\t\t}\n\t\t}\n\t} else {\n\t\tif err := pe.putArrayLength(-1); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif r.Version > 3 {\n\t\tpe.putBool(r.AllowAutoTopicCreation)\n\t}\n\tif r.Version > 7 {\n\t\tpe.putBool(r.IncludeClusterAuthorizedOperations)\n\t\tpe.putBool(r.IncludeTopicAuthorizedOperations)\n\t}\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (r *MetadataRequest) decode(pd packetDecoder, version int16) (err error) {\n\tr.Version = version\n\tsize, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif size > 0 {\n\t\tr.Topics = make([]string, size)\n\t}\n\tif version <= 9 {\n\t\tfor i := range r.Topics {\n\t\t\ttopic, err := pd.getString()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tr.Topics[i] = topic\n\t\t\tif _, err := pd.getEmptyTaggedFieldArray(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t} else {\n\t\tfor i := range r.Topics {\n\t\t\tif _, err = pd.getRawBytes(16); err != nil { // skip UUID\n\t\t\t\treturn err\n\t\t\t}\n\t\t\ttopic, err := pd.getNullableString()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif topic != nil {\n\t\t\t\tr.Topics[i] = *topic\n\t\t\t}\n\n\t\t\tif _, err := pd.getEmptyTaggedFieldArray(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\tif r.Version >= 4 {\n\t\tif r.AllowAutoTopicCreation, err = pd.getBool(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif r.Version > 7 {\n\t\tincludeClusterAuthz, err := pd.getBool()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.IncludeClusterAuthorizedOperations = includeClusterAuthz\n\t\tincludeTopicAuthz, err := pd.getBool()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.IncludeTopicAuthorizedOperations = includeTopicAuthz\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (r *MetadataRequest) key() int16 {\n\treturn apiKeyMetadata\n}\n\nfunc (r *MetadataRequest) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *MetadataRequest) headerVersion() int16 {\n\tif r.Version >= 9 {\n\t\treturn 2\n\t}\n\treturn 1\n}\n\nfunc (r *MetadataRequest) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 10\n}\n\nfunc (r *MetadataRequest) isFlexible() bool {\n\treturn r.isFlexibleVersion(r.Version)\n}\n\nfunc (r *MetadataRequest) isFlexibleVersion(version int16) bool {\n\treturn version >= 9\n}\n\nfunc (r *MetadataRequest) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 10:\n\t\treturn V2_8_0_0\n\tcase 9:\n\t\treturn V2_4_0_0\n\tcase 8:\n\t\treturn V2_3_0_0\n\tcase 7:\n\t\treturn V2_1_0_0\n\tcase 6:\n\t\treturn V2_0_0_0\n\tcase 5:\n\t\treturn V1_0_0_0\n\tcase 3, 4:\n\t\treturn V0_11_0_0\n\tcase 2:\n\t\treturn V0_10_1_0\n\tcase 1:\n\t\treturn V0_10_0_0\n\tcase 0:\n\t\treturn V0_8_2_0\n\tdefault:\n\t\treturn V2_8_0_0\n\t}\n}\n"
  },
  {
    "path": "metadata_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nvar (\n\t// The v0 metadata request has a non-nullable array of topic names\n\t// to request metadata for. An empty array fetches metadata for all topics\n\n\tmetadataRequestNoTopicsV0 = []byte{\n\t\t0x00, 0x00, 0x00, 0x00,\n\t}\n\n\tmetadataRequestOneTopicV0 = []byte{\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x06, 't', 'o', 'p', 'i', 'c', '1',\n\t}\n\n\tmetadataRequestThreeTopicsV0 = []byte{\n\t\t0x00, 0x00, 0x00, 0x03,\n\t\t0x00, 0x03, 'f', 'o', 'o',\n\t\t0x00, 0x03, 'b', 'a', 'r',\n\t\t0x00, 0x03, 'b', 'a', 'z',\n\t}\n\n\t// The v1 metadata request is the same as v0 except that the array is now\n\t// nullable and should be explicitly null if all topics are required (an\n\t// empty list requests no topics)\n\n\tmetadataRequestNoTopicsV1 = []byte{\n\t\t0xff, 0xff, 0xff, 0xff,\n\t}\n\n\tmetadataRequestOneTopicV1    = metadataRequestOneTopicV0\n\tmetadataRequestThreeTopicsV1 = metadataRequestThreeTopicsV0\n\n\t// The v2 metadata request is the same as v1. An additional field for\n\t// cluster id has been added to the v2 metadata response\n\n\tmetadataRequestNoTopicsV2    = metadataRequestNoTopicsV1\n\tmetadataRequestOneTopicV2    = metadataRequestOneTopicV1\n\tmetadataRequestThreeTopicsV2 = metadataRequestThreeTopicsV1\n\n\t// The v3 metadata request is the same as v1 and v2. An additional field\n\t// for throttle time has been added to the v3 metadata response\n\n\tmetadataRequestNoTopicsV3    = metadataRequestNoTopicsV2\n\tmetadataRequestOneTopicV3    = metadataRequestOneTopicV2\n\tmetadataRequestThreeTopicsV3 = metadataRequestThreeTopicsV2\n\n\t// The v4 metadata request has an additional field for allowing auto topic\n\t// creation. The response is the same as v3.\n\n\tmetadataRequestNoTopicsV4     = append(metadataRequestNoTopicsV1, byte(0))\n\tmetadataRequestAutoCreateV4   = append(metadataRequestOneTopicV3, byte(1))\n\tmetadataRequestNoAutoCreateV4 = append(metadataRequestOneTopicV3, byte(0))\n\n\t// The v5 metadata request is the same as v4. An additional field for\n\t// offline_replicas has been added to the v5 metadata response\n\n\tmetadataRequestNoTopicsV5     = append(metadataRequestNoTopicsV1, byte(0))\n\tmetadataRequestAutoCreateV5   = append(metadataRequestOneTopicV3, byte(1))\n\tmetadataRequestNoAutoCreateV5 = append(metadataRequestOneTopicV3, byte(0))\n\n\t// The v6 metadata request and response are the same as v5. I know, right.\n\tmetadataRequestNoTopicsV6     = metadataRequestNoTopicsV5\n\tmetadataRequestAutoCreateV6   = metadataRequestAutoCreateV5\n\tmetadataRequestNoAutoCreateV6 = metadataRequestNoAutoCreateV5\n\n\t// The v7 metadata request is the same as v6. An additional field for\n\t// leader epoch has been added to the partition metadata in the v7 response.\n\tmetadataRequestNoTopicsV7     = metadataRequestNoTopicsV6\n\tmetadataRequestAutoCreateV7   = metadataRequestAutoCreateV6\n\tmetadataRequestNoAutoCreateV7 = metadataRequestNoAutoCreateV6\n\n\t// The v8 metadata request has additional fields for including cluster authorized operations\n\t// and including topic authorized operations. An additional field for cluster authorized operations\n\t// has been added to the v8 metadata response, and an additional field for topic authorized operations\n\t// has been added to the topic metadata in the v8 metadata response.\n\tmetadataRequestNoTopicsV8     = append(metadataRequestNoTopicsV7, []byte{0, 0}...)\n\tmetadataRequestAutoCreateV8   = append(metadataRequestAutoCreateV7, []byte{0, 0}...)\n\tmetadataRequestNoAutoCreateV8 = append(metadataRequestNoAutoCreateV7, []byte{0, 0}...)\n\t// Appending to an empty slice means we are creating a new backing array, rather than updating the backing array\n\t// for the slice metadataRequestAutoCreateV7\n\tmetadataRequestAutoCreateClusterAuthTopicAuthV8 = append(append([]byte{}, metadataRequestAutoCreateV7...), []byte{1, 1}...)\n\n\t// In v9 tag buffers have been added to the end of arrays, and various types have been replaced with compact types.\n\tmetadataRequestNoTopicsV9 = []byte{\n\t\t0x00, 0x00, 0x00, 0x00, 0x00,\n\t}\n\n\tmetadataRequestOneTopicV9 = []byte{\n\t\t2, 7, 't', 'o', 'p', 'i', 'c', '1', 0, 0, 0, 0, 0,\n\t}\n\n\tmetadataRequestOneTopicAutoCreateTopicV9 = []byte{\n\t\t2, 7, 't', 'o', 'p', 'i', 'c', '1', 0, 1, 0, 1, 0,\n\t}\n\n\t// v10 added topic UUIDs to the metadata request and responses, and made the topic name nullable in the request.\n\tmetadataRequestNoTopicsV10 = metadataRequestNoTopicsV9\n\n\tmetadataRequestTwoTopicsV10 = []byte{\n\t\t3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 't', 'o', 'p', 'i', 'c', '1',\n\t\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 't', 'o', 'p', 'i', 'c', '2', 0, 0, 0, 0, 0,\n\t}\n\n\tmetadataRequestAutoCreateClusterAuthTopicAuthV10 = []byte{\n\t\t3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 't', 'o', 'p', 'i', 'c', '1',\n\t\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 't', 'o', 'p', 'i', 'c', '2', 0, 1, 1, 1, 0,\n\t}\n)\n\nfunc TestMetadataRequestV0(t *testing.T) {\n\trequest := new(MetadataRequest)\n\ttestRequest(t, \"no topics\", request, metadataRequestNoTopicsV0)\n\n\trequest.Topics = []string{\"topic1\"}\n\ttestRequest(t, \"one topic\", request, metadataRequestOneTopicV0)\n\n\trequest.Topics = []string{\"foo\", \"bar\", \"baz\"}\n\ttestRequest(t, \"three topics\", request, metadataRequestThreeTopicsV0)\n}\n\nfunc TestMetadataRequestV1(t *testing.T) {\n\trequest := new(MetadataRequest)\n\trequest.Version = 1\n\ttestRequest(t, \"no topics\", request, metadataRequestNoTopicsV1)\n\n\trequest.Topics = []string{\"topic1\"}\n\ttestRequest(t, \"one topic\", request, metadataRequestOneTopicV1)\n\n\trequest.Topics = []string{\"foo\", \"bar\", \"baz\"}\n\ttestRequest(t, \"three topics\", request, metadataRequestThreeTopicsV1)\n}\n\nfunc TestMetadataRequestV2(t *testing.T) {\n\trequest := new(MetadataRequest)\n\trequest.Version = 2\n\ttestRequest(t, \"no topics\", request, metadataRequestNoTopicsV2)\n\n\trequest.Topics = []string{\"topic1\"}\n\ttestRequest(t, \"one topic\", request, metadataRequestOneTopicV2)\n\n\trequest.Topics = []string{\"foo\", \"bar\", \"baz\"}\n\ttestRequest(t, \"three topics\", request, metadataRequestThreeTopicsV2)\n}\n\nfunc TestMetadataRequestV3(t *testing.T) {\n\trequest := new(MetadataRequest)\n\trequest.Version = 3\n\ttestRequest(t, \"no topics\", request, metadataRequestNoTopicsV3)\n\n\trequest.Topics = []string{\"topic1\"}\n\ttestRequest(t, \"one topic\", request, metadataRequestOneTopicV3)\n\n\trequest.Topics = []string{\"foo\", \"bar\", \"baz\"}\n\ttestRequest(t, \"three topics\", request, metadataRequestThreeTopicsV3)\n}\n\nfunc TestMetadataRequestV4(t *testing.T) {\n\trequest := new(MetadataRequest)\n\trequest.Version = 4\n\ttestRequest(t, \"no topics\", request, metadataRequestNoTopicsV4)\n\n\trequest.Topics = []string{\"topic1\"}\n\n\trequest.AllowAutoTopicCreation = true\n\ttestRequest(t, \"one topic\", request, metadataRequestAutoCreateV4)\n\n\trequest.AllowAutoTopicCreation = false\n\ttestRequest(t, \"one topic\", request, metadataRequestNoAutoCreateV4)\n}\n\nfunc TestMetadataRequestV5(t *testing.T) {\n\trequest := new(MetadataRequest)\n\trequest.Version = 5\n\ttestRequest(t, \"no topics\", request, metadataRequestNoTopicsV5)\n\n\trequest.Topics = []string{\"topic1\"}\n\n\trequest.AllowAutoTopicCreation = true\n\ttestRequest(t, \"one topic\", request, metadataRequestAutoCreateV5)\n\n\trequest.AllowAutoTopicCreation = false\n\ttestRequest(t, \"one topic\", request, metadataRequestNoAutoCreateV5)\n}\n\nfunc TestMetadataRequestV6(t *testing.T) {\n\trequest := new(MetadataRequest)\n\trequest.Version = 6\n\ttestRequest(t, \"no topics\", request, metadataRequestNoTopicsV6)\n\n\trequest.Topics = []string{\"topic1\"}\n\n\trequest.AllowAutoTopicCreation = true\n\ttestRequest(t, \"one topic\", request, metadataRequestAutoCreateV6)\n\n\trequest.AllowAutoTopicCreation = false\n\ttestRequest(t, \"one topic\", request, metadataRequestNoAutoCreateV6)\n}\n\nfunc TestMetadataRequestV7(t *testing.T) {\n\trequest := new(MetadataRequest)\n\trequest.Version = 7\n\ttestRequest(t, \"no topics\", request, metadataRequestNoTopicsV7)\n\n\trequest.Topics = []string{\"topic1\"}\n\n\trequest.AllowAutoTopicCreation = true\n\ttestRequest(t, \"one topic\", request, metadataRequestAutoCreateV7)\n\n\trequest.AllowAutoTopicCreation = false\n\ttestRequest(t, \"one topic\", request, metadataRequestNoAutoCreateV7)\n}\n\nfunc TestMetadataRequestV8(t *testing.T) {\n\trequest := new(MetadataRequest)\n\trequest.Version = 8\n\ttestRequest(t, \"no topics\", request, metadataRequestNoTopicsV8)\n\n\trequest.Topics = []string{\"topic1\"}\n\n\trequest.AllowAutoTopicCreation = true\n\ttestRequest(t, \"one topic, auto create\", request, metadataRequestAutoCreateV8)\n\n\trequest.AllowAutoTopicCreation = false\n\ttestRequest(t, \"one topic, no auto create\", request, metadataRequestNoAutoCreateV8)\n\n\trequest.AllowAutoTopicCreation = true\n\trequest.IncludeClusterAuthorizedOperations = true\n\trequest.IncludeTopicAuthorizedOperations = true\n\ttestRequest(t, \"one topic, auto create, cluster auth, topic auth\", request, metadataRequestAutoCreateClusterAuthTopicAuthV8)\n}\n\nfunc TestMetadataRequestV9(t *testing.T) {\n\trequest := new(MetadataRequest)\n\trequest.Version = 9\n\ttestRequest(t, \"no topics\", request, metadataRequestNoTopicsV9)\n\n\trequest.Topics = []string{\"topic1\"}\n\ttestRequest(t, \"one topic\", request, metadataRequestOneTopicV9)\n\n\trequest.AllowAutoTopicCreation = true\n\trequest.IncludeTopicAuthorizedOperations = true\n\ttestRequest(t, \"one topic, auto create, no cluster auth, topic auth\", request, metadataRequestOneTopicAutoCreateTopicV9)\n}\n\nfunc TestMetadataRequestV10(t *testing.T) {\n\trequest := new(MetadataRequest)\n\trequest.Version = 10\n\ttestRequest(t, \"no topics\", request, metadataRequestNoTopicsV10)\n\n\trequest.Topics = []string{\"topic1\", \"topic2\"}\n\ttestRequest(t, \"one topic\", request, metadataRequestTwoTopicsV10)\n\n\trequest.AllowAutoTopicCreation = true\n\trequest.IncludeClusterAuthorizedOperations = true\n\trequest.IncludeTopicAuthorizedOperations = true\n\ttestRequest(t, \"one topic, auto create, cluster auth, topic auth\", request, metadataRequestAutoCreateClusterAuthTopicAuthV10)\n}\n"
  },
  {
    "path": "metadata_response.go",
    "content": "package sarama\n\nimport \"time\"\n\n// PartitionMetadata contains each partition in the topic.\ntype PartitionMetadata struct {\n\t// Version defines the protocol version to use for encode and decode\n\tVersion int16\n\t// Err contains the partition error, or 0 if there was no error.\n\tErr KError\n\t// ID contains the partition index.\n\tID int32\n\t// Leader contains the ID of the leader broker.\n\tLeader int32\n\t// LeaderEpoch contains the leader epoch of this partition.\n\tLeaderEpoch int32\n\t// Replicas contains the set of all nodes that host this partition.\n\tReplicas []int32\n\t// Isr contains the set of nodes that are in sync with the leader for this partition.\n\tIsr []int32\n\t// OfflineReplicas contains the set of offline replicas of this partition.\n\tOfflineReplicas []int32\n}\n\nfunc (p *PartitionMetadata) decode(pd packetDecoder, version int16) (err error) {\n\tp.Version = version\n\tp.Err, err = pd.getKError()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif p.ID, err = pd.getInt32(); err != nil {\n\t\treturn err\n\t}\n\n\tif p.Leader, err = pd.getInt32(); err != nil {\n\t\treturn err\n\t}\n\n\tif p.Version >= 7 {\n\t\tif p.LeaderEpoch, err = pd.getInt32(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tp.Replicas, err = pd.getInt32Array()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tp.Isr, err = pd.getInt32Array()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif p.Version >= 5 {\n\t\tp.OfflineReplicas, err = pd.getInt32Array()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (p *PartitionMetadata) encode(pe packetEncoder, version int16) (err error) {\n\tp.Version = version\n\tpe.putKError(p.Err)\n\n\tpe.putInt32(p.ID)\n\n\tpe.putInt32(p.Leader)\n\n\tif p.Version >= 7 {\n\t\tpe.putInt32(p.LeaderEpoch)\n\t}\n\n\terr = pe.putInt32Array(p.Replicas)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = pe.putInt32Array(p.Isr)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif p.Version >= 5 {\n\t\terr = pe.putInt32Array(p.OfflineReplicas)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\n// TopicMetadata contains each topic in the response.\ntype TopicMetadata struct {\n\t// Version defines the protocol version to use for encode and decode\n\tVersion int16\n\t// Err contains the topic error, or 0 if there was no error.\n\tErr KError\n\t// Name contains the topic name.\n\tName string\n\tUuid Uuid\n\t// IsInternal contains a True if the topic is internal.\n\tIsInternal bool\n\t// Partitions contains each partition in the topic.\n\tPartitions                []*PartitionMetadata\n\tTopicAuthorizedOperations int32 // Only valid for Version >= 8\n}\n\nfunc (t *TopicMetadata) decode(pd packetDecoder, version int16) (err error) {\n\tt.Version = version\n\tt.Err, err = pd.getKError()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tt.Name, err = pd.getString()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif t.Version >= 10 {\n\t\tuuid, err := pd.getRawBytes(16)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tt.Uuid = [16]byte{}\n\t\tfor i := 0; i < 16; i++ {\n\t\t\tt.Uuid[i] = uuid[i]\n\t\t}\n\t}\n\n\tif t.Version >= 1 {\n\t\tt.IsInternal, err = pd.getBool()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\tt.Partitions = make([]*PartitionMetadata, n)\n\tfor i := 0; i < n; i++ {\n\t\tblock := &PartitionMetadata{}\n\t\tif err := block.decode(pd, t.Version); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tt.Partitions[i] = block\n\t}\n\n\tif t.Version >= 8 {\n\t\tt.TopicAuthorizedOperations, err = pd.getInt32()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (t *TopicMetadata) encode(pe packetEncoder, version int16) (err error) {\n\tt.Version = version\n\tpe.putKError(t.Err)\n\n\terr = pe.putString(t.Name)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif t.Version >= 10 {\n\t\terr = pe.putRawBytes(t.Uuid[:])\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif t.Version >= 1 {\n\t\tpe.putBool(t.IsInternal)\n\t}\n\n\terr = pe.putArrayLength(len(t.Partitions))\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, block := range t.Partitions {\n\t\tif err := block.encode(pe, t.Version); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif t.Version >= 8 {\n\t\tpe.putInt32(t.TopicAuthorizedOperations)\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\ntype MetadataResponse struct {\n\t// Version defines the protocol version to use for encode and decode\n\tVersion int16\n\t// ThrottleTimeMs contains the duration in milliseconds for which the request was throttled due to a quota violation, or zero if the request did not violate any quota.\n\tThrottleTimeMs int32\n\t// Brokers contains each broker in the response.\n\tBrokers []*Broker\n\t// ClusterID contains the cluster ID that responding broker belongs to.\n\tClusterID *string\n\t// ControllerID contains the ID of the controller broker.\n\tControllerID int32\n\t// Topics contains each topic in the response.\n\tTopics                      []*TopicMetadata\n\tClusterAuthorizedOperations int32 // Only valid for Version >= 8\n}\n\nfunc (r *MetadataResponse) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *MetadataResponse) decode(pd packetDecoder, version int16) (err error) {\n\tr.Version = version\n\tif r.Version >= 3 {\n\t\tif r.ThrottleTimeMs, err = pd.getInt32(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tbrokerArrayLen, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr.Brokers = make([]*Broker, brokerArrayLen)\n\tfor i := 0; i < brokerArrayLen; i++ {\n\t\tr.Brokers[i] = new(Broker)\n\t\terr = r.Brokers[i].decode(pd, version)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif r.Version >= 2 {\n\t\tr.ClusterID, err = pd.getNullableString()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif r.Version >= 1 {\n\t\tif r.ControllerID, err = pd.getInt32(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\ttopicArrayLen, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr.Topics = make([]*TopicMetadata, topicArrayLen)\n\tfor i := 0; i < topicArrayLen; i++ {\n\t\tr.Topics[i] = new(TopicMetadata)\n\t\terr = r.Topics[i].decode(pd, version)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif r.Version >= 8 {\n\t\tr.ClusterAuthorizedOperations, err = pd.getInt32()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (r *MetadataResponse) encode(pe packetEncoder) (err error) {\n\tif r.Version >= 3 {\n\t\tpe.putInt32(r.ThrottleTimeMs)\n\t}\n\n\terr = pe.putArrayLength(len(r.Brokers))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, broker := range r.Brokers {\n\t\terr = broker.encode(pe, r.Version)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif r.Version >= 2 {\n\t\terr = pe.putNullableString(r.ClusterID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif r.Version >= 1 {\n\t\tpe.putInt32(r.ControllerID)\n\t}\n\n\terr = pe.putArrayLength(len(r.Topics))\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, block := range r.Topics {\n\t\tif err := block.encode(pe, r.Version); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif r.Version >= 8 {\n\t\tpe.putInt32(r.ClusterAuthorizedOperations)\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (r *MetadataResponse) key() int16 {\n\treturn apiKeyMetadata\n}\n\nfunc (r *MetadataResponse) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *MetadataResponse) headerVersion() int16 {\n\tif r.Version < 9 {\n\t\treturn 0\n\t} else {\n\t\treturn 1\n\t}\n}\n\nfunc (r *MetadataResponse) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 10\n}\n\nfunc (r *MetadataResponse) isFlexible() bool {\n\treturn r.isFlexibleVersion(r.Version)\n}\n\nfunc (r *MetadataResponse) isFlexibleVersion(version int16) bool {\n\treturn version >= 9\n}\n\nfunc (r *MetadataResponse) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 10:\n\t\treturn V2_8_0_0\n\tcase 9:\n\t\treturn V2_4_0_0\n\tcase 8:\n\t\treturn V2_3_0_0\n\tcase 7:\n\t\treturn V2_1_0_0\n\tcase 6:\n\t\treturn V2_0_0_0\n\tcase 5:\n\t\treturn V1_0_0_0\n\tcase 3, 4:\n\t\treturn V0_11_0_0\n\tcase 2:\n\t\treturn V0_10_1_0\n\tcase 1:\n\t\treturn V0_10_0_0\n\tcase 0:\n\t\treturn V0_8_2_0\n\tdefault:\n\t\treturn V2_8_0_0\n\t}\n}\n\nfunc (r *MetadataResponse) throttleTime() time.Duration {\n\treturn time.Duration(r.ThrottleTimeMs) * time.Millisecond\n}\n\n// testing API\n\nfunc (r *MetadataResponse) AddBroker(addr string, id int32) {\n\tr.Brokers = append(r.Brokers, &Broker{id: id, addr: addr})\n}\n\nfunc (r *MetadataResponse) AddTopic(topic string, err KError) *TopicMetadata {\n\tvar tmatch *TopicMetadata\n\n\tfor _, tm := range r.Topics {\n\t\tif tm.Name == topic {\n\t\t\ttmatch = tm\n\t\t\tgoto foundTopic\n\t\t}\n\t}\n\n\ttmatch = new(TopicMetadata)\n\ttmatch.Name = topic\n\tr.Topics = append(r.Topics, tmatch)\n\nfoundTopic:\n\n\ttmatch.Err = err\n\treturn tmatch\n}\n\nfunc (r *MetadataResponse) AddTopicPartition(topic string, partition, brokerID int32, replicas, isr []int32, offline []int32, err KError) {\n\ttmatch := r.AddTopic(topic, ErrNoError)\n\tvar pmatch *PartitionMetadata\n\n\tfor _, pm := range tmatch.Partitions {\n\t\tif pm.ID == partition {\n\t\t\tpmatch = pm\n\t\t\tgoto foundPartition\n\t\t}\n\t}\n\n\tpmatch = new(PartitionMetadata)\n\tpmatch.ID = partition\n\ttmatch.Partitions = append(tmatch.Partitions, pmatch)\n\nfoundPartition:\n\tpmatch.Leader = brokerID\n\tpmatch.Replicas = replicas\n\tif pmatch.Replicas == nil {\n\t\tpmatch.Replicas = []int32{}\n\t}\n\tpmatch.Isr = isr\n\tif pmatch.Isr == nil {\n\t\tpmatch.Isr = []int32{}\n\t}\n\tpmatch.OfflineReplicas = offline\n\tif pmatch.OfflineReplicas == nil {\n\t\tpmatch.OfflineReplicas = []int32{}\n\t}\n\tpmatch.Err = err\n}\n"
  },
  {
    "path": "metadata_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"errors\"\n\t\"testing\"\n)\n\nvar (\n\temptyMetadataResponseV0 = []byte{\n\t\t0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x00,\n\t}\n\n\tbrokersNoTopicsMetadataResponseV0 = []byte{\n\t\t0x00, 0x00, 0x00, 0x02,\n\n\t\t0x00, 0x00, 0xab, 0xff,\n\t\t0x00, 0x09, 'l', 'o', 'c', 'a', 'l', 'h', 'o', 's', 't',\n\t\t0x00, 0x00, 0x00, 0x33,\n\n\t\t0x00, 0x01, 0x02, 0x03,\n\t\t0x00, 0x0a, 'g', 'o', 'o', 'g', 'l', 'e', '.', 'c', 'o', 'm',\n\t\t0x00, 0x00, 0x01, 0x11,\n\n\t\t0x00, 0x00, 0x00, 0x00,\n\t}\n\n\ttopicsNoBrokersMetadataResponseV0 = []byte{\n\t\t0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x02,\n\n\t\t0x00, 0x00,\n\t\t0x00, 0x03, 'f', 'o', 'o',\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x04,\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x00, 0x00, 0x07,\n\t\t0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03,\n\t\t0x00, 0x00, 0x00, 0x00,\n\n\t\t0x00, 0x00,\n\t\t0x00, 0x03, 'b', 'a', 'r',\n\t\t0x00, 0x00, 0x00, 0x00,\n\t}\n\n\tbrokersNoTopicsMetadataResponseV1 = []byte{\n\t\t0x00, 0x00, 0x00, 0x02,\n\n\t\t0x00, 0x00, 0xab, 0xff,\n\t\t0x00, 0x09, 'l', 'o', 'c', 'a', 'l', 'h', 'o', 's', 't',\n\t\t0x00, 0x00, 0x00, 0x33,\n\t\t0x00, 0x05, 'r', 'a', 'c', 'k', '0',\n\n\t\t0x00, 0x01, 0x02, 0x03,\n\t\t0x00, 0x0a, 'g', 'o', 'o', 'g', 'l', 'e', '.', 'c', 'o', 'm',\n\t\t0x00, 0x00, 0x01, 0x11,\n\t\t0x00, 0x05, 'r', 'a', 'c', 'k', '1',\n\n\t\t0x00, 0x00, 0x00, 0x01,\n\n\t\t0x00, 0x00, 0x00, 0x00,\n\t}\n\n\ttopicsNoBrokersMetadataResponseV1 = []byte{\n\t\t0x00, 0x00, 0x00, 0x00,\n\n\t\t0x00, 0x00, 0x00, 0x04,\n\n\t\t0x00, 0x00, 0x00, 0x02,\n\n\t\t0x00, 0x00,\n\t\t0x00, 0x03, 'f', 'o', 'o',\n\t\t0x00,\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x04,\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x00, 0x00, 0x07,\n\t\t0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03,\n\t\t0x00, 0x00, 0x00, 0x00,\n\n\t\t0x00, 0x00,\n\t\t0x00, 0x03, 'b', 'a', 'r',\n\t\t0x01,\n\t\t0x00, 0x00, 0x00, 0x00,\n\t}\n\n\tnoBrokersNoTopicsWithThrottleTimeAndClusterIDV3 = []byte{\n\t\t0x00, 0x00, 0x00, 0x10,\n\t\t0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x09, 'c', 'l', 'u', 's', 't', 'e', 'r', 'I', 'd',\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x00, 0x00, 0x00,\n\t}\n\n\tnoBrokersOneTopicWithOfflineReplicasV5 = []byte{\n\t\t0x00, 0x00, 0x00, 0x05,\n\t\t0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x09, 'c', 'l', 'u', 's', 't', 'e', 'r', 'I', 'd',\n\t\t0x00, 0x00, 0x00, 0x02,\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x00,\n\t\t0x00, 0x03, 'f', 'o', 'o',\n\t\t0x00,\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x04,\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x00, 0x00, 0x07,\n\t\t0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03,\n\t\t0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02,\n\t\t0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03,\n\t}\n\n\tOneTopicV6 = []byte{\n\t\t0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 'h', 'o', 's',\n\t\t't', 0x00, 0x00, 0x23, 0x84, 0xff, 0xff, 0x00, 0x09, 'c', 'l', 'u', 's', 't', 'e', 'r',\n\t\t'I', 'd', 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 't', 'o',\n\t\t'n', 'y', 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n\t\t0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n\t\t0x02, 0x00, 0x00, 0x00, 0x00,\n\t}\n\n\tOneTopicV7 = []byte{\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 'h', 'o', 's',\n\t\t't', 0x00, 0x00, 0x23, 0x84, 0xff, 0xff, 0x00, 0x09, 'c', 'l', 'u', 's', 't', 'e', 'r',\n\t\t'I', 'd', 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 't', 'o',\n\t\t'n', 'y', 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x00, 0x7b, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,\n\t}\n\n\tOneTopicV8 = []byte{\n\t\t0x00, 0x00, 0x00, 0x00, // throttle ms\n\t\t0x00, 0x00, 0x00, 0x01, // length brokers\n\t\t0x00, 0x00, 0x00, 0x00, // broker[0].nodeid\n\t\t0x00, 0x04, // brokers[0].length(nodehost)\n\t\t'h', 'o', 's', 't', // broker[0].nodehost\n\t\t0x00, 0x00, 0x23, 0x84, // broker[0].port (9092)\n\t\t0xff, 0xff, // brokers[0].rack (null)\n\t\t0x00, 0x09, 'c', 'l', 'u', 's', 't', 'e', 'r',\n\t\t'I', 'd', 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 't', 'o',\n\t\t'n', 'y', 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x00, 0x7b, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 'Y', 0x00, 0x00, 0x00,\n\t\t0xea,\n\t}\n\n\tOneTopicV9 = []byte{\n\t\t0x00, 0x00, 0x00, 0x00, // throttle ms\n\t\t0x02,                   // length of brokers\n\t\t0x00, 0x00, 0x00, 0x00, // broker[0].nodeid\n\t\t0x05,               // length of brokers[0].nodehost\n\t\t'h', 'o', 's', 't', // brokers[0].nodehost\n\t\t0x00, 0x00, 0x23, 0x84, // brokers[0].port (9092)\n\t\t0x00,                                              // brokers[0].rack (null)\n\t\t0x00,                                              // empty tags\n\t\t0x0a, 'c', 'l', 'u', 's', 't', 'e', 'r', 'I', 'd', // cluster id\n\t\t0x00, 0x00, 0x00,\n\t\t0x01, 0x02, 0x00, 0x00, 0x05, 't', 'o', 'n', 'y', 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,\n\t\t0x00, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x01, 'Y', 0x00, 0x00, 0x00, 0x00, 0xea, 0x00,\n\t}\n\n\tOneTopicV10 = []byte{\n\t\t0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x05, 'h', 'o', 's', 't', 0x00, 0x00, 0x23,\n\t\t0x84, 0x00, 0x00, 0x0a, 'c', 'l', 'u', 's', 't', 'e', 'r', 'I', 'd', 0x00, 0x00, 0x00,\n\t\t0x01, 0x02, 0x00, 0x00, 0x05, 't', 'o', 'n', 'y', 0x84, 0xcd, 0xa7, 'U', 0x7e, 0x84, 'K',\n\t\t0xf9, 0xb7, 0xdc, 0xfc, 0x11, 0x82, 0x07, 'r', 'J', 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,\n\t\t0x00, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x01, 'Y', 0x00, 0x00, 0x00, 0x00, 0xea, 0x00,\n\t}\n)\n\nfunc TestEmptyMetadataResponseV0(t *testing.T) {\n\tresponse := MetadataResponse{}\n\n\ttestVersionDecodable(t, \"empty, V0\", &response, emptyMetadataResponseV0, 0)\n\tif len(response.Brokers) != 0 {\n\t\tt.Error(\"Decoding produced\", len(response.Brokers), \"brokers where there were none!\")\n\t}\n\tif len(response.Topics) != 0 {\n\t\tt.Error(\"Decoding produced\", len(response.Topics), \"topics where there were none!\")\n\t}\n}\n\nfunc TestMetadataResponseWithBrokersV0(t *testing.T) {\n\tresponse := MetadataResponse{}\n\n\ttestVersionDecodable(t, \"brokers, no topics, V0\", &response, brokersNoTopicsMetadataResponseV0, 0)\n\tif len(response.Brokers) != 2 {\n\t\tt.Fatal(\"Decoding produced\", len(response.Brokers), \"brokers where there were two!\")\n\t}\n\n\tif response.Brokers[0].id != 0xabff {\n\t\tt.Error(\"Decoding produced invalid broker 0 id.\")\n\t}\n\tif response.Brokers[0].addr != \"localhost:51\" {\n\t\tt.Error(\"Decoding produced invalid broker 0 address.\")\n\t}\n\tif response.Brokers[1].id != 0x010203 {\n\t\tt.Error(\"Decoding produced invalid broker 1 id.\")\n\t}\n\tif response.Brokers[1].addr != \"google.com:273\" {\n\t\tt.Error(\"Decoding produced invalid broker 1 address.\")\n\t}\n\n\tif len(response.Topics) != 0 {\n\t\tt.Error(\"Decoding produced\", len(response.Topics), \"topics where there were none!\")\n\t}\n}\n\nfunc TestMetadataResponseWithTopicsV0(t *testing.T) {\n\tresponse := MetadataResponse{}\n\n\ttestVersionDecodable(t, \"topics, no brokers, V0\", &response, topicsNoBrokersMetadataResponseV0, 0)\n\tif len(response.Brokers) != 0 {\n\t\tt.Error(\"Decoding produced\", len(response.Brokers), \"brokers where there were none!\")\n\t}\n\n\tif len(response.Topics) != 2 {\n\t\tt.Fatal(\"Decoding produced\", len(response.Topics), \"topics where there were two!\")\n\t}\n\n\tif !errors.Is(response.Topics[0].Err, ErrNoError) {\n\t\tt.Error(\"Decoding produced invalid topic 0 error.\")\n\t}\n\n\tif response.Topics[0].Name != \"foo\" {\n\t\tt.Error(\"Decoding produced invalid topic 0 name.\")\n\t}\n\n\tif len(response.Topics[0].Partitions) != 1 {\n\t\tt.Fatal(\"Decoding produced invalid partition count for topic 0.\")\n\t}\n\n\tif !errors.Is(response.Topics[0].Partitions[0].Err, ErrInvalidMessageSize) {\n\t\tt.Error(\"Decoding produced invalid topic 0 partition 0 error.\")\n\t}\n\n\tif response.Topics[0].Partitions[0].ID != 0x01 {\n\t\tt.Error(\"Decoding produced invalid topic 0 partition 0 id.\")\n\t}\n\n\tif response.Topics[0].Partitions[0].Leader != 0x07 {\n\t\tt.Error(\"Decoding produced invalid topic 0 partition 0 leader.\")\n\t}\n\n\tif len(response.Topics[0].Partitions[0].Replicas) != 3 {\n\t\tt.Fatal(\"Decoding produced invalid topic 0 partition 0 replicas.\")\n\t}\n\tfor i := 0; i < 3; i++ {\n\t\tif response.Topics[0].Partitions[0].Replicas[i] != int32(i+1) {\n\t\t\tt.Error(\"Decoding produced invalid topic 0 partition 0 replica\", i)\n\t\t}\n\t}\n\n\tif len(response.Topics[0].Partitions[0].Isr) != 0 {\n\t\tt.Error(\"Decoding produced invalid topic 0 partition 0 isr length.\")\n\t}\n\n\tif !errors.Is(response.Topics[1].Err, ErrNoError) {\n\t\tt.Error(\"Decoding produced invalid topic 1 error.\")\n\t}\n\n\tif response.Topics[1].Name != \"bar\" {\n\t\tt.Error(\"Decoding produced invalid topic 0 name.\")\n\t}\n\n\tif len(response.Topics[1].Partitions) != 0 {\n\t\tt.Error(\"Decoding produced invalid partition count for topic 1.\")\n\t}\n}\n\nfunc TestMetadataResponseWithBrokersV1(t *testing.T) {\n\tresponse := MetadataResponse{}\n\n\ttestVersionDecodable(t, \"topics, V1\", &response, brokersNoTopicsMetadataResponseV1, 1)\n\tif len(response.Brokers) != 2 {\n\t\tt.Error(\"Decoding produced\", len(response.Brokers), \"brokers where there were 2!\")\n\t}\n\tif response.Brokers[0].rack == nil || *response.Brokers[0].rack != \"rack0\" {\n\t\tt.Error(\"Decoding produced invalid broker 0 rack.\")\n\t}\n\tif response.Brokers[1].rack == nil || *response.Brokers[1].rack != \"rack1\" {\n\t\tt.Error(\"Decoding produced invalid broker 1 rack.\")\n\t}\n\tif response.ControllerID != 1 {\n\t\tt.Error(\"Decoding produced\", response.ControllerID, \"should have been 1!\")\n\t}\n\tif len(response.Topics) != 0 {\n\t\tt.Error(\"Decoding produced\", len(response.Brokers), \"brokers where there were none!\")\n\t}\n}\n\nfunc TestMetadataResponseWithTopicsV1(t *testing.T) {\n\tresponse := MetadataResponse{}\n\n\ttestVersionDecodable(t, \"topics, V1\", &response, topicsNoBrokersMetadataResponseV1, 1)\n\tif len(response.Brokers) != 0 {\n\t\tt.Error(\"Decoding produced\", len(response.Brokers), \"brokers where there were none!\")\n\t}\n\tif response.ControllerID != 4 {\n\t\tt.Error(\"Decoding produced\", response.ControllerID, \"should have been 4!\")\n\t}\n\tif len(response.Topics) != 2 {\n\t\tt.Error(\"Decoding produced\", len(response.Topics), \"topics where there were 2!\")\n\t}\n\tif response.Topics[0].IsInternal {\n\t\tt.Error(\"Decoding produced\", response.Topics[0], \"topic0 should have been false!\")\n\t}\n\tif !response.Topics[1].IsInternal {\n\t\tt.Error(\"Decoding produced\", response.Topics[1], \"topic1 should have been true!\")\n\t}\n}\n\nfunc TestMetadataResponseWithThrottleTime(t *testing.T) {\n\tresponse := MetadataResponse{}\n\n\ttestVersionDecodable(t, \"no topics, no brokers, throttle time and cluster Id V3\", &response, noBrokersNoTopicsWithThrottleTimeAndClusterIDV3, 3)\n\tif response.ThrottleTimeMs != int32(16) {\n\t\tt.Error(\"Decoding produced\", response.ThrottleTimeMs, \"should have been 16!\")\n\t}\n\tif len(response.Brokers) != 0 {\n\t\tt.Error(\"Decoding produced\", response.Brokers, \"should have been 0!\")\n\t}\n\tif response.ControllerID != int32(1) {\n\t\tt.Error(\"Decoding produced\", response.ControllerID, \"should have been 1!\")\n\t}\n\tif *response.ClusterID != \"clusterId\" {\n\t\tt.Error(\"Decoding produced\", response.ClusterID, \"should have been clusterId!\")\n\t}\n\tif len(response.Topics) != 0 {\n\t\tt.Error(\"Decoding produced\", len(response.Topics), \"should have been 0!\")\n\t}\n}\n\nfunc TestMetadataResponseWithOfflineReplicasV5(t *testing.T) {\n\tresponse := MetadataResponse{}\n\n\ttestVersionDecodable(t, \"no brokers, 1 topic with offline replica V5\", &response, noBrokersOneTopicWithOfflineReplicasV5, 5)\n\tif response.ThrottleTimeMs != int32(5) {\n\t\tt.Error(\"Decoding produced\", response.ThrottleTimeMs, \"should have been 5!\")\n\t}\n\tif len(response.Brokers) != 0 {\n\t\tt.Error(\"Decoding produced\", response.Brokers, \"should have been 0!\")\n\t}\n\tif response.ControllerID != int32(2) {\n\t\tt.Error(\"Decoding produced\", response.ControllerID, \"should have been 21!\")\n\t}\n\tif *response.ClusterID != \"clusterId\" {\n\t\tt.Error(\"Decoding produced\", response.ClusterID, \"should have been clusterId!\")\n\t}\n\tif len(response.Topics) != 1 {\n\t\tt.Error(\"Decoding produced\", len(response.Topics), \"should have been 1!\")\n\t}\n\tif len(response.Topics[0].Partitions[0].OfflineReplicas) != 1 {\n\t\tt.Error(\"Decoding produced\", len(response.Topics[0].Partitions[0].OfflineReplicas), \"should have been 1!\")\n\t}\n}\n\nfunc TestMetadataResponseV6(t *testing.T) {\n\tresponse := MetadataResponse{}\n\n\ttestVersionDecodable(t, \"no brokers, 1 topic with offline replica V5\", &response, OneTopicV6, 6)\n\tif response.ThrottleTimeMs != int32(7) {\n\t\tt.Error(\"Decoding produced\", response.ThrottleTimeMs, \"should have been 7!\")\n\t}\n\tif len(response.Brokers) != 1 {\n\t\tt.Error(\"Decoding produced\", response.Brokers, \"should have been 1!\")\n\t}\n\tif response.Brokers[0].addr != \"host:9092\" {\n\t\tt.Error(\"Decoding produced\", response.Brokers[0].addr, \"should have been host:9092!\")\n\t}\n\tif response.ControllerID != int32(1) {\n\t\tt.Error(\"Decoding produced\", response.ControllerID, \"should have been 1!\")\n\t}\n\tif *response.ClusterID != \"clusterId\" {\n\t\tt.Error(\"Decoding produced\", response.ClusterID, \"should have been clusterId!\")\n\t}\n\tif len(response.Topics) != 1 {\n\t\tt.Error(\"Decoding produced\", len(response.Topics), \"should have been 1!\")\n\t}\n\tif len(response.Topics[0].Partitions[0].OfflineReplicas) != 0 {\n\t\tt.Error(\"Decoding produced\", len(response.Topics[0].Partitions[0].OfflineReplicas), \"should have been 0!\")\n\t}\n}\n\nfunc TestMetadataResponseV7(t *testing.T) {\n\tresponse := MetadataResponse{}\n\n\ttestVersionDecodable(t, \"no brokers, 1 topic with offline replica V5\", &response, OneTopicV7, 7)\n\tif response.ThrottleTimeMs != int32(0) {\n\t\tt.Error(\"Decoding produced\", response.ThrottleTimeMs, \"should have been 0!\")\n\t}\n\tif len(response.Brokers) != 1 {\n\t\tt.Error(\"Decoding produced\", response.Brokers, \"should have been 1!\")\n\t}\n\tif response.Brokers[0].addr != \"host:9092\" {\n\t\tt.Error(\"Decoding produced\", response.Brokers[0].addr, \"should have been host:9092!\")\n\t}\n\tif response.ControllerID != int32(1) {\n\t\tt.Error(\"Decoding produced\", response.ControllerID, \"should have been 1!\")\n\t}\n\tif *response.ClusterID != \"clusterId\" {\n\t\tt.Error(\"Decoding produced\", response.ClusterID, \"should have been clusterId!\")\n\t}\n\tif len(response.Topics) != 1 {\n\t\tt.Error(\"Decoding produced\", len(response.Topics), \"should have been 1!\")\n\t}\n\tif len(response.Topics[0].Partitions[0].OfflineReplicas) != 0 {\n\t\tt.Error(\"Decoding produced\", len(response.Topics[0].Partitions[0].OfflineReplicas), \"should have been 0!\")\n\t}\n\tif response.Topics[0].Partitions[0].LeaderEpoch != 123 {\n\t\tt.Error(\"Decoding produced\", response.Topics[0].Partitions[0].LeaderEpoch, \"should have been 123!\")\n\t}\n}\n\nfunc TestMetadataResponseV8(t *testing.T) {\n\tresponse := MetadataResponse{}\n\n\ttestVersionDecodable(t, \"no brokers, 1 topic with offline replica V5\", &response, OneTopicV8, 8)\n\tif response.ThrottleTimeMs != int32(0) {\n\t\tt.Error(\"Decoding produced\", response.ThrottleTimeMs, \"should have been 0!\")\n\t}\n\tif len(response.Brokers) != 1 {\n\t\tt.Error(\"Decoding produced\", response.Brokers, \"should have been 1!\")\n\t}\n\tif response.Brokers[0].addr != \"host:9092\" {\n\t\tt.Error(\"Decoding produced\", response.Brokers[0].addr, \"should have been host:9092!\")\n\t}\n\tif response.ControllerID != int32(1) {\n\t\tt.Error(\"Decoding produced\", response.ControllerID, \"should have been 1!\")\n\t}\n\tif *response.ClusterID != \"clusterId\" {\n\t\tt.Error(\"Decoding produced\", response.ClusterID, \"should have been clusterId!\")\n\t}\n\tif response.ClusterAuthorizedOperations != 234 {\n\t\tt.Error(\"Decoding produced\", response.ClusterAuthorizedOperations, \"should have been 234!\")\n\t}\n\tif len(response.Topics) != 1 {\n\t\tt.Error(\"Decoding produced\", len(response.Topics), \"should have been 1!\")\n\t}\n\tif response.Topics[0].TopicAuthorizedOperations != 345 {\n\t\tt.Error(\"Decoding produced\", response.Topics[0].TopicAuthorizedOperations, \"should have been 345!\")\n\t}\n\tif len(response.Topics[0].Partitions[0].OfflineReplicas) != 0 {\n\t\tt.Error(\"Decoding produced\", len(response.Topics[0].Partitions[0].OfflineReplicas), \"should have been 0!\")\n\t}\n\tif response.Topics[0].Partitions[0].LeaderEpoch != 123 {\n\t\tt.Error(\"Decoding produced\", response.Topics[0].Partitions[0].LeaderEpoch, \"should have been 123!\")\n\t}\n}\n\nfunc TestMetadataResponseV9(t *testing.T) {\n\tresponse := MetadataResponse{}\n\n\ttestVersionDecodable(t, \"no brokers, 1 topic with offline replica V5\", &response, OneTopicV9, 9)\n\tif response.ThrottleTimeMs != int32(0) {\n\t\tt.Error(\"Decoding produced\", response.ThrottleTimeMs, \"should have been 0!\")\n\t}\n\tif len(response.Brokers) != 1 {\n\t\tt.Error(\"Decoding produced\", response.Brokers, \"should have been 1!\")\n\t}\n\tif response.Brokers[0].addr != \"host:9092\" {\n\t\tt.Error(\"Decoding produced\", response.Brokers[0].addr, \"should have been host:9092!\")\n\t}\n\tif response.ControllerID != int32(1) {\n\t\tt.Error(\"Decoding produced\", response.ControllerID, \"should have been 1!\")\n\t}\n\tif *response.ClusterID != \"clusterId\" {\n\t\tt.Error(\"Decoding produced\", response.ClusterID, \"should have been clusterId!\")\n\t}\n\tif response.ClusterAuthorizedOperations != 234 {\n\t\tt.Error(\"Decoding produced\", response.ClusterAuthorizedOperations, \"should have been 234!\")\n\t}\n\tif len(response.Topics) != 1 {\n\t\tt.Error(\"Decoding produced\", len(response.Topics), \"should have been 1!\")\n\t}\n\tif response.Topics[0].TopicAuthorizedOperations != 345 {\n\t\tt.Error(\"Decoding produced\", response.Topics[0].TopicAuthorizedOperations, \"should have been 345!\")\n\t}\n\tif len(response.Topics[0].Partitions[0].OfflineReplicas) != 0 {\n\t\tt.Error(\"Decoding produced\", len(response.Topics[0].Partitions[0].OfflineReplicas), \"should have been 0!\")\n\t}\n\tif response.Topics[0].Partitions[0].LeaderEpoch != 123 {\n\t\tt.Error(\"Decoding produced\", response.Topics[0].Partitions[0].LeaderEpoch, \"should have been 123!\")\n\t}\n}\n\nfunc TestMetadataResponseV10(t *testing.T) {\n\tresponse := MetadataResponse{}\n\n\ttestVersionDecodable(t, \"no brokers, 1 topic with offline replica V5\", &response, OneTopicV10, 10)\n\tif response.ThrottleTimeMs != int32(0) {\n\t\tt.Error(\"Decoding produced\", response.ThrottleTimeMs, \"should have been 0!\")\n\t}\n\tif len(response.Brokers) != 1 {\n\t\tt.Error(\"Decoding produced\", response.Brokers, \"should have been 1!\")\n\t}\n\tif response.Brokers[0].addr != \"host:9092\" {\n\t\tt.Error(\"Decoding produced\", response.Brokers[0].addr, \"should have been host:9092!\")\n\t}\n\tif response.ControllerID != int32(1) {\n\t\tt.Error(\"Decoding produced\", response.ControllerID, \"should have been 1!\")\n\t}\n\tif *response.ClusterID != \"clusterId\" {\n\t\tt.Error(\"Decoding produced\", response.ClusterID, \"should have been clusterId!\")\n\t}\n\tif response.ClusterAuthorizedOperations != 234 {\n\t\tt.Error(\"Decoding produced\", response.ClusterAuthorizedOperations, \"should have been 234!\")\n\t}\n\tif len(response.Topics) != 1 {\n\t\tt.Error(\"Decoding produced\", len(response.Topics), \"should have been 1!\")\n\t}\n\tif response.Topics[0].Uuid != [16]byte{\n\t\t0x84, 0xcd, 0xa7, 0x55, 0x7e, 0x84, 0x4b, 0xf9,\n\t\t0xb7, 0xdc, 0xfc, 0x11, 0x82, 0x07, 0x72, 0x4a,\n\t} {\n\t\tt.Error(\"Decoding produced\", response.Topics[0].Uuid, \"should have been different!\")\n\t}\n\tif response.Topics[0].TopicAuthorizedOperations != 345 {\n\t\tt.Error(\"Decoding produced\", response.Topics[0].TopicAuthorizedOperations, \"should have been 345!\")\n\t}\n\tif len(response.Topics[0].Partitions[0].OfflineReplicas) != 0 {\n\t\tt.Error(\"Decoding produced\", len(response.Topics[0].Partitions[0].OfflineReplicas), \"should have been 0!\")\n\t}\n\tif response.Topics[0].Partitions[0].LeaderEpoch != 123 {\n\t\tt.Error(\"Decoding produced\", response.Topics[0].Partitions[0].LeaderEpoch, \"should have been 123!\")\n\t}\n}\n"
  },
  {
    "path": "metadata_test.go",
    "content": "package sarama\n\nimport (\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestMetadataRefresh(t *testing.T) {\n\tstepRefresh := make(chan struct{})\n\trefresh := newMetadataRefresh(func(topics []string) error {\n\t\t<-stepRefresh\n\t\treturn nil\n\t})\n\n\tch, queued := refresh.refreshOrQueue([]string{\"topic1\"})\n\tif queued {\n\t\tt.Errorf(\"It's the first call, it should not be queued\")\n\t}\n\n\tch2, queued := refresh.refreshOrQueue([]string{\"topic2\", \"topic3\"})\n\tif !queued {\n\t\tt.Errorf(\"This one is requesting different topics, it should be queued\")\n\t}\n\n\tch3, queued := refresh.refreshOrQueue([]string{\"topic3\"})\n\tif !queued {\n\t\tt.Errorf(\"This one is requesting the same topics as the second one, it should be queued\")\n\t}\n\n\tch4, queued := refresh.refreshOrQueue([]string{\"topic4\"})\n\tif !queued {\n\t\tt.Errorf(\"This one is requesting different topics, it should be queued too\")\n\t}\n\n\tch5, queued := refresh.refreshOrQueue([]string{\"topic1\"})\n\tif queued {\n\t\tt.Errorf(\"Same topics as the first call, piggy backing on that call, so it's not queued\")\n\t}\n\n\tstepRefresh <- struct{}{}\n\trequire.NoError(t, <-ch)\n\trequire.NoError(t, <-ch5)\n\n\trequire.NoError(t, <-ch2)\n\trequire.NoError(t, <-ch3)\n\trequire.NoError(t, <-ch4)\n}\n\nfunc TestMetadataRefreshConcurrency(t *testing.T) {\n\tvar firstRefreshChans []chan error\n\tvar lock sync.Mutex\n\tstepRefresh := make(chan struct{})\n\trefresh := newMetadataRefresh(func(topics []string) error {\n\t\t<-stepRefresh\n\t\treturn nil\n\t})\n\n\tch, queued := refresh.refreshOrQueue([]string{\"topic1\"})\n\tfirstRefreshChans = append(firstRefreshChans, ch)\n\tif queued {\n\t\tt.Errorf(\"First call, should start a refresh\")\n\t}\n\n\tvar wg sync.WaitGroup\n\tfor i := 0; i < 1000; i++ {\n\t\twg.Add(1)\n\t\ttime.Sleep(time.Millisecond)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\n\t\t\tch, refreshQueued := refresh.refreshOrQueue([]string{\"topic1\"})\n\t\t\tif refreshQueued {\n\t\t\t\tt.Errorf(\"This one should not be queued: they are all requesting the topic that's already started\")\n\t\t\t}\n\t\t\tlock.Lock()\n\t\t\tfirstRefreshChans = append(firstRefreshChans, ch)\n\t\t\tlock.Unlock()\n\t\t}()\n\t}\n\twg.Wait()\n\t// We have now queued all the refreshes, and they're all blocked with the first one.\n\tstepRefresh <- struct{}{}\n\t// Now they are all finished, we can pull from the channels\n\tfor _, ch := range firstRefreshChans {\n\t\trequire.NoError(t, <-ch)\n\t}\n\n\tch, queued = refresh.refreshOrQueue([]string{\"topic2\", \"topic3\"})\n\tif queued {\n\t\tt.Errorf(\"This one should not be queued: no refresh is ongoing\")\n\t}\n\tch2, queued := refresh.refreshOrQueue([]string{\"topic3\", \"topic4\"})\n\tif !queued {\n\t\tt.Errorf(\"But now there is a refresh ongoing, so this one should be queued\")\n\t}\n\n\tstepRefresh <- struct{}{}\n\trequire.NoError(t, <-ch)\n\trequire.NoError(t, <-ch2)\n}\n"
  },
  {
    "path": "metrics.go",
    "content": "package sarama\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/rcrowley/go-metrics\"\n)\n\n// Use exponentially decaying reservoir for sampling histograms with the same defaults as the Java library:\n// 1028 elements, which offers a 99.9% confidence level with a 5% margin of error assuming a normal distribution,\n// and an alpha factor of 0.015, which heavily biases the reservoir to the past 5 minutes of measurements.\n// See https://github.com/dropwizard/metrics/blob/v3.1.0/metrics-core/src/main/java/com/codahale/metrics/ExponentiallyDecayingReservoir.java#L38\nconst (\n\tmetricsReservoirSize = 1028\n\tmetricsAlphaFactor   = 0.015\n)\n\nfunc getOrRegisterHistogram(name string, r metrics.Registry) metrics.Histogram {\n\treturn r.GetOrRegister(name, func() metrics.Histogram {\n\t\treturn metrics.NewHistogram(metrics.NewExpDecaySample(metricsReservoirSize, metricsAlphaFactor))\n\t}).(metrics.Histogram)\n}\n\nfunc getMetricNameForBroker(name string, broker *Broker) string {\n\t// Use broker id like the Java client as it does not contain '.' or ':' characters that\n\t// can be interpreted as special character by monitoring tool (e.g. Graphite)\n\treturn name + \"-for-broker-\" + strconv.FormatInt(int64(broker.ID()), 10)\n}\n\nfunc getMetricNameForTopic(name string, topic string) string {\n\t// Convert dot to _ since reporters like Graphite typically use dot to represent hierarchy\n\t// cf. KAFKA-1902 and KAFKA-2337\n\treturn name + \"-for-topic-\" + strings.ReplaceAll(topic, \".\", \"_\")\n}\n\nfunc getOrRegisterTopicMeter(name string, topic string, r metrics.Registry) metrics.Meter {\n\treturn metrics.GetOrRegisterMeter(getMetricNameForTopic(name, topic), r)\n}\n\nfunc getOrRegisterTopicHistogram(name string, topic string, r metrics.Registry) metrics.Histogram {\n\treturn getOrRegisterHistogram(getMetricNameForTopic(name, topic), r)\n}\n\n// cleanupRegistry is an implementation of metrics.Registry that allows\n// to unregister from the parent registry only those metrics\n// that have been registered in cleanupRegistry\ntype cleanupRegistry struct {\n\tparent  metrics.Registry\n\tmetrics map[string]struct{}\n\tmutex   sync.RWMutex\n}\n\nfunc newCleanupRegistry(parent metrics.Registry) metrics.Registry {\n\treturn &cleanupRegistry{\n\t\tparent:  parent,\n\t\tmetrics: map[string]struct{}{},\n\t}\n}\n\nfunc (r *cleanupRegistry) Each(fn func(string, interface{})) {\n\tr.mutex.RLock()\n\tdefer r.mutex.RUnlock()\n\twrappedFn := func(name string, iface interface{}) {\n\t\tif _, ok := r.metrics[name]; ok {\n\t\t\tfn(name, iface)\n\t\t}\n\t}\n\tr.parent.Each(wrappedFn)\n}\n\nfunc (r *cleanupRegistry) Get(name string) interface{} {\n\tr.mutex.RLock()\n\tdefer r.mutex.RUnlock()\n\tif _, ok := r.metrics[name]; ok {\n\t\treturn r.parent.Get(name)\n\t}\n\treturn nil\n}\n\nfunc (r *cleanupRegistry) GetOrRegister(name string, metric interface{}) interface{} {\n\tr.mutex.Lock()\n\tdefer r.mutex.Unlock()\n\tr.metrics[name] = struct{}{}\n\treturn r.parent.GetOrRegister(name, metric)\n}\n\nfunc (r *cleanupRegistry) Register(name string, metric interface{}) error {\n\tr.mutex.Lock()\n\tdefer r.mutex.Unlock()\n\tr.metrics[name] = struct{}{}\n\treturn r.parent.Register(name, metric)\n}\n\nfunc (r *cleanupRegistry) RunHealthchecks() {\n\tr.parent.RunHealthchecks()\n}\n\nfunc (r *cleanupRegistry) GetAll() map[string]map[string]interface{} {\n\treturn r.parent.GetAll()\n}\n\nfunc (r *cleanupRegistry) Unregister(name string) {\n\tr.mutex.Lock()\n\tdefer r.mutex.Unlock()\n\tif _, ok := r.metrics[name]; ok {\n\t\tdelete(r.metrics, name)\n\t\tr.parent.Unregister(name)\n\t}\n}\n\nfunc (r *cleanupRegistry) UnregisterAll() {\n\tr.mutex.Lock()\n\tdefer r.mutex.Unlock()\n\tfor name := range r.metrics {\n\t\tdelete(r.metrics, name)\n\t\tr.parent.Unregister(name)\n\t}\n}\n"
  },
  {
    "path": "metrics_helpers_test.go",
    "content": "package sarama\n\nimport (\n\t\"testing\"\n\n\t\"github.com/rcrowley/go-metrics\"\n)\n\n// Common type and functions for metric validation\ntype metricValidator struct {\n\tname      string\n\tvalidator func(*testing.T, interface{})\n}\n\ntype metricValidators []*metricValidator\n\nfunc newMetricValidators() metricValidators {\n\treturn make([]*metricValidator, 0, 32)\n}\n\nfunc (m *metricValidators) register(validator *metricValidator) {\n\t*m = append(*m, validator)\n}\n\nfunc (m *metricValidators) registerForBroker(broker *Broker, validator *metricValidator) {\n\tm.register(&metricValidator{getMetricNameForBroker(validator.name, broker), validator.validator})\n}\n\nfunc (m *metricValidators) registerForGlobalAndTopic(topic string, validator *metricValidator) {\n\tm.register(&metricValidator{validator.name, validator.validator})\n\tm.register(&metricValidator{getMetricNameForTopic(validator.name, topic), validator.validator})\n}\n\nfunc (m *metricValidators) registerForAllBrokers(broker *Broker, validator *metricValidator) {\n\tm.register(validator)\n\tm.registerForBroker(broker, validator)\n}\n\nfunc (m metricValidators) run(t *testing.T, r metrics.Registry) {\n\tt.Helper()\n\tfor _, metricValidator := range m {\n\t\tmetric := r.Get(metricValidator.name)\n\t\tif metric == nil {\n\t\t\tt.Error(\"No metric named\", metricValidator.name)\n\t\t} else {\n\t\t\tmetricValidator.validator(t, metric)\n\t\t}\n\t}\n}\n\nfunc meterValidator(name string, extraValidator func(*testing.T, metrics.Meter)) *metricValidator {\n\treturn &metricValidator{\n\t\tname: name,\n\t\tvalidator: func(t *testing.T, metric interface{}) {\n\t\t\tt.Helper()\n\t\t\tif meter, ok := metric.(metrics.Meter); !ok {\n\t\t\t\tt.Errorf(\"Expected meter metric for '%s', got %T\", name, metric)\n\t\t\t} else {\n\t\t\t\textraValidator(t, meter)\n\t\t\t}\n\t\t},\n\t}\n}\n\nfunc countMeterValidator(name string, expectedCount int) *metricValidator {\n\treturn meterValidator(name, func(t *testing.T, meter metrics.Meter) {\n\t\tt.Helper()\n\t\tcount := meter.Count()\n\t\tif count != int64(expectedCount) {\n\t\t\tt.Errorf(\"Expected meter metric '%s' count = %d, got %d\", name, expectedCount, count)\n\t\t}\n\t})\n}\n\nfunc minCountMeterValidator(name string, minCount int) *metricValidator {\n\treturn meterValidator(name, func(t *testing.T, meter metrics.Meter) {\n\t\tt.Helper()\n\t\tcount := meter.Count()\n\t\tif count < int64(minCount) {\n\t\t\tt.Errorf(\"Expected meter metric '%s' count >= %d, got %d\", name, minCount, count)\n\t\t}\n\t})\n}\n\nfunc histogramValidator(name string, extraValidator func(*testing.T, metrics.Histogram)) *metricValidator {\n\treturn &metricValidator{\n\t\tname: name,\n\t\tvalidator: func(t *testing.T, metric interface{}) {\n\t\t\tt.Helper()\n\t\t\tif histogram, ok := metric.(metrics.Histogram); !ok {\n\t\t\t\tt.Errorf(\"Expected histogram metric for '%s', got %T\", name, metric)\n\t\t\t} else {\n\t\t\t\textraValidator(t, histogram)\n\t\t\t}\n\t\t},\n\t}\n}\n\nfunc countHistogramValidator(name string, expectedCount int) *metricValidator {\n\treturn histogramValidator(name, func(t *testing.T, histogram metrics.Histogram) {\n\t\tt.Helper()\n\t\tcount := histogram.Count()\n\t\tif count != int64(expectedCount) {\n\t\t\tt.Errorf(\"Expected histogram metric '%s' count = %d, got %d\", name, expectedCount, count)\n\t\t}\n\t})\n}\n\nfunc minCountHistogramValidator(name string, minCount int) *metricValidator {\n\treturn histogramValidator(name, func(t *testing.T, histogram metrics.Histogram) {\n\t\tt.Helper()\n\t\tcount := histogram.Count()\n\t\tif count < int64(minCount) {\n\t\t\tt.Errorf(\"Expected histogram metric '%s' count >= %d, got %d\", name, minCount, count)\n\t\t}\n\t})\n}\n\n//lint:ignore U1000 // this is used but only in unittests which are excluded by the integration build tag\nfunc minMaxHistogramValidator(name string, expectedMin int, expectedMax int) *metricValidator {\n\treturn histogramValidator(name, func(t *testing.T, histogram metrics.Histogram) {\n\t\tt.Helper()\n\t\tmin := int(histogram.Min())\n\t\tif min != expectedMin {\n\t\t\tt.Errorf(\"Expected histogram metric '%s' min = %d, got %d\", name, expectedMin, min)\n\t\t}\n\t\tmax := int(histogram.Max())\n\t\tif max != expectedMax {\n\t\t\tt.Errorf(\"Expected histogram metric '%s' max = %d, got %d\", name, expectedMax, max)\n\t\t}\n\t})\n}\n\nfunc minValHistogramValidator(name string, minMin int) *metricValidator {\n\treturn histogramValidator(name, func(t *testing.T, histogram metrics.Histogram) {\n\t\tt.Helper()\n\t\tmin := int(histogram.Min())\n\t\tif min < minMin {\n\t\t\tt.Errorf(\"Expected histogram metric '%s' min >= %d, got %d\", name, minMin, min)\n\t\t}\n\t})\n}\n\nfunc maxValHistogramValidator(name string, maxMax int) *metricValidator {\n\treturn histogramValidator(name, func(t *testing.T, histogram metrics.Histogram) {\n\t\tt.Helper()\n\t\tmax := int(histogram.Max())\n\t\tif max > maxMax {\n\t\t\tt.Errorf(\"Expected histogram metric '%s' max <= %d, got %d\", name, maxMax, max)\n\t\t}\n\t})\n}\n\nfunc counterValidator(name string, expectedCount int) *metricValidator {\n\treturn &metricValidator{\n\t\tname: name,\n\t\tvalidator: func(t *testing.T, metric interface{}) {\n\t\t\tt.Helper()\n\t\t\tif counter, ok := metric.(metrics.Counter); !ok {\n\t\t\t\tt.Errorf(\"Expected counter metric for '%s', got %T\", name, metric)\n\t\t\t} else {\n\t\t\t\tcount := counter.Count()\n\t\t\t\tif count != int64(expectedCount) {\n\t\t\t\t\tt.Errorf(\"Expected counter metric '%s' count = %d, got %d\", name, expectedCount, count)\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "metrics_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"testing\"\n\n\t\"github.com/rcrowley/go-metrics\"\n)\n\nfunc TestGetOrRegisterHistogram(t *testing.T) {\n\tmetricRegistry := metrics.NewRegistry()\n\thistogram := getOrRegisterHistogram(\"name\", metricRegistry)\n\n\tif histogram == nil {\n\t\tt.Error(\"Unexpected nil histogram\")\n\t}\n\n\t// Fetch the metric\n\tfoundHistogram := metricRegistry.Get(\"name\")\n\n\tif foundHistogram != histogram {\n\t\tt.Error(\"Unexpected different histogram\", foundHistogram, histogram)\n\t}\n\n\t// Try to register the metric again\n\tsameHistogram := getOrRegisterHistogram(\"name\", metricRegistry)\n\n\tif sameHistogram != histogram {\n\t\tt.Error(\"Unexpected different histogram\", sameHistogram, histogram)\n\t}\n}\n\nfunc TestGetMetricNameForBroker(t *testing.T) {\n\tmetricName := getMetricNameForBroker(\"name\", &Broker{id: 1})\n\n\tif metricName != \"name-for-broker-1\" {\n\t\tt.Error(\"Unexpected metric name\", metricName)\n\t}\n}\n\nfunc Benchmark_getMetricNameForTopic(b *testing.B) {\n\tb.ReportAllocs()\n\n\tfor b.Loop() {\n\t\tname := getMetricNameForTopic(\"sarama\", \"says.hello\")\n\t\tif name != \"sarama-for-topic-says_hello\" {\n\t\t\tb.Fail()\n\t\t}\n\t}\n}\n\nfunc Benchmark_getMetricNameForBroker(b *testing.B) {\n\tbroker := &Broker{id: 1965}\n\n\tb.ReportAllocs()\n\n\tfor b.Loop() {\n\t\tname := getMetricNameForBroker(\"summer\", broker)\n\t\tif name != \"summer-for-broker-1965\" {\n\t\t\tb.Fail()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "mockbroker.go",
    "content": "package sarama\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"maps\"\n\t\"net\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"sync\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/davecgh/go-spew/spew\"\n)\n\nconst (\n\texpectationTimeout = 500 * time.Millisecond\n)\n\ntype GSSApiHandlerFunc func([]byte) []byte\n\ntype requestHandlerFunc func(req *request) (res encoderWithHeader)\n\n// RequestNotifierFunc is invoked when a mock broker processes a request successfully\n// and will provides the number of bytes read and written.\ntype RequestNotifierFunc func(bytesRead, bytesWritten int)\n\n// MockBroker is a mock Kafka broker that is used in unit tests. It is exposed\n// to facilitate testing of higher level or specialized consumers and producers\n// built on top of Sarama. Note that it does not 'mimic' the Kafka API protocol,\n// but rather provides a facility to do that. It takes care of the TCP\n// transport, request unmarshalling, response marshaling, and makes it the test\n// writer responsibility to program correct according to the Kafka API protocol\n// MockBroker behavior.\n//\n// MockBroker is implemented as a TCP server listening on a kernel-selected\n// localhost port that can accept many connections. It reads Kafka requests\n// from that connection and returns responses programmed by the SetHandlerByMap\n// function. If a MockBroker receives a request that it has no programmed\n// response for, then it returns nothing and the request times out.\n//\n// A set of MockRequest builders to define mappings used by MockBroker is\n// provided by Sarama. But users can develop MockRequests of their own and use\n// them along with or instead of the standard ones.\n//\n// When running tests with MockBroker it is strongly recommended to specify\n// a timeout to `go test` so that if the broker hangs waiting for a response,\n// the test panics.\n//\n// It is not necessary to prefix message length or correlation ID to your\n// response bytes, the server does that automatically as a convenience.\ntype MockBroker struct {\n\tbrokerID      int32\n\tport          int32\n\tclosing       chan none\n\tstopper       chan none\n\texpectations  chan encoderWithHeader\n\tlistener      net.Listener\n\tt             TestReporter\n\tlatency       time.Duration\n\thandler       requestHandlerFunc\n\tnotifier      RequestNotifierFunc\n\thistory       []RequestResponse\n\tlock          sync.Mutex\n\tgssApiHandler GSSApiHandlerFunc\n}\n\n// RequestResponse represents a Request/Response pair processed by MockBroker.\ntype RequestResponse struct {\n\tRequest  protocolBody\n\tResponse encoder\n}\n\n// SetLatency makes broker pause for the specified period every time before\n// replying.\nfunc (b *MockBroker) SetLatency(latency time.Duration) {\n\tb.latency = latency\n}\n\n// SetHandlerByMap defines mapping of Request types to MockResponses. When a\n// request is received by the broker, it looks up the request type in the map\n// and uses the found MockResponse instance to generate an appropriate reply.\n// If the request type is not found in the map then nothing is sent.\nfunc (b *MockBroker) SetHandlerByMap(handlerMap map[string]MockResponse) {\n\tfnMap := maps.Clone(handlerMap)\n\tb.setHandler(func(req *request) (res encoderWithHeader) {\n\t\treqTypeName := reflect.TypeOf(req.body).Elem().Name()\n\t\tmockResponse := fnMap[reqTypeName]\n\t\tif mockResponse == nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn mockResponse.For(req.body)\n\t})\n}\n\n// SetHandlerFuncByMap defines mapping of Request types to RequestHandlerFunc. When a\n// request is received by the broker, it looks up the request type in the map\n// and invoke the found RequestHandlerFunc instance to generate an appropriate reply.\nfunc (b *MockBroker) SetHandlerFuncByMap(handlerMap map[string]requestHandlerFunc) {\n\tfnMap := maps.Clone(handlerMap)\n\tb.setHandler(func(req *request) (res encoderWithHeader) {\n\t\treqTypeName := reflect.TypeOf(req.body).Elem().Name()\n\t\treturn fnMap[reqTypeName](req)\n\t})\n}\n\n// SetNotifier set a function that will get invoked whenever a request has been\n// processed successfully and will provide the number of bytes read and written\nfunc (b *MockBroker) SetNotifier(notifier RequestNotifierFunc) {\n\tb.lock.Lock()\n\tb.notifier = notifier\n\tb.lock.Unlock()\n}\n\n// BrokerID returns broker ID assigned to the broker.\nfunc (b *MockBroker) BrokerID() int32 {\n\treturn b.brokerID\n}\n\n// History returns a slice of RequestResponse pairs in the order they were\n// processed by the broker. Note that in case of multiple connections to the\n// broker the order expected by a test can be different from the order recorded\n// in the history, unless some synchronization is implemented in the test.\nfunc (b *MockBroker) History() []RequestResponse {\n\tb.lock.Lock()\n\thistory := make([]RequestResponse, len(b.history))\n\tcopy(history, b.history)\n\tb.lock.Unlock()\n\treturn history\n}\n\n// Port returns the TCP port number the broker is listening for requests on.\nfunc (b *MockBroker) Port() int32 {\n\treturn b.port\n}\n\n// Addr returns the broker connection string in the form \"<address>:<port>\".\nfunc (b *MockBroker) Addr() string {\n\treturn b.listener.Addr().String()\n}\n\n// Close terminates the broker blocking until it stops internal goroutines and\n// releases all resources.\nfunc (b *MockBroker) Close() {\n\tclose(b.expectations)\n\tif len(b.expectations) > 0 {\n\t\tbuf := bytes.NewBufferString(fmt.Sprintf(\"mockbroker/%d: not all expectations were satisfied! Still waiting on:\\n\", b.BrokerID()))\n\t\tfor e := range b.expectations {\n\t\t\t_, _ = buf.WriteString(spew.Sdump(e))\n\t\t}\n\t\tb.t.Error(buf.String())\n\t}\n\tclose(b.closing)\n\t<-b.stopper\n}\n\n// setHandler sets the specified function as the request handler. Whenever\n// a mock broker reads a request from the wire it passes the request to the\n// function and sends back whatever the handler function returns.\nfunc (b *MockBroker) setHandler(handler requestHandlerFunc) {\n\tb.lock.Lock()\n\tb.handler = handler\n\tb.lock.Unlock()\n}\n\nfunc (b *MockBroker) serverLoop() {\n\tdefer close(b.stopper)\n\tvar err error\n\tvar conn net.Conn\n\n\tgo func() {\n\t\t<-b.closing\n\t\terr := b.listener.Close()\n\t\tif err != nil {\n\t\t\tb.t.Error(err)\n\t\t}\n\t}()\n\n\twg := &sync.WaitGroup{}\n\ti := 0\n\tfor conn, err = b.listener.Accept(); err == nil; conn, err = b.listener.Accept() {\n\t\twg.Add(1)\n\t\tgo b.handleRequests(conn, i, wg)\n\t\ti++\n\t}\n\twg.Wait()\n\tif !isConnectionClosedError(err) {\n\t\tLogger.Printf(\"*** mockbroker/%d: listener closed, err=%v\", b.BrokerID(), err)\n\t}\n}\n\nfunc (b *MockBroker) SetGSSAPIHandler(handler GSSApiHandlerFunc) {\n\tb.gssApiHandler = handler\n}\n\nfunc (b *MockBroker) readToBytes(r io.Reader) ([]byte, error) {\n\tvar (\n\t\tbytesRead   int\n\t\tlengthBytes = make([]byte, 4)\n\t)\n\n\tif _, err := io.ReadFull(r, lengthBytes); err != nil {\n\t\treturn nil, err\n\t}\n\n\tbytesRead += len(lengthBytes)\n\tlength := int32(binary.BigEndian.Uint32(lengthBytes))\n\n\tif length <= 4 || length > MaxRequestSize {\n\t\treturn nil, PacketDecodingError{fmt.Sprintf(\"message of length %d too large or too small\", length)}\n\t}\n\n\tencodedReq := make([]byte, length)\n\tif _, err := io.ReadFull(r, encodedReq); err != nil {\n\t\treturn nil, err\n\t}\n\n\tbytesRead += len(encodedReq)\n\n\tfullBytes := append(lengthBytes, encodedReq...)\n\n\treturn fullBytes, nil\n}\n\nfunc (b *MockBroker) isGSSAPI(buffer []byte) bool {\n\treturn buffer[4] == 0x60 || bytes.Equal(buffer[4:6], []byte{0x05, 0x04})\n}\n\nfunc (b *MockBroker) handleRequests(conn io.ReadWriteCloser, idx int, wg *sync.WaitGroup) {\n\tdefer wg.Done()\n\tdefer func() {\n\t\t_ = conn.Close()\n\t}()\n\ts := spew.NewDefaultConfig()\n\ts.MaxDepth = 1\n\tLogger.Printf(\"*** mockbroker/%d/%d: connection opened\", b.BrokerID(), idx)\n\tvar err error\n\n\tabort := make(chan none)\n\tdefer close(abort)\n\tgo func() {\n\t\tselect {\n\t\tcase <-b.closing:\n\t\t\t_ = conn.Close()\n\t\tcase <-abort:\n\t\t}\n\t}()\n\n\tvar bytesWritten int\n\tvar bytesRead int\n\tfor {\n\t\tbuffer, err := b.readToBytes(conn)\n\t\tif err != nil {\n\t\t\tif !isConnectionClosedError(err) {\n\t\t\t\tLogger.Printf(\"*** mockbroker/%d/%d: invalid request: err=%+v, %+v\", b.brokerID, idx, err, spew.Sdump(buffer))\n\t\t\t\tb.serverError(err)\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\n\t\tbytesWritten = 0\n\t\tif !b.isGSSAPI(buffer) {\n\t\t\treq, br, err := decodeRequest(bytes.NewReader(buffer))\n\t\t\tbytesRead = br\n\t\t\tif err != nil {\n\t\t\t\tif !isConnectionClosedError(err) {\n\t\t\t\t\tLogger.Printf(\"*** mockbroker/%d/%d: invalid request: err=%+v, %+v\", b.brokerID, idx, err, spew.Sdump(req))\n\t\t\t\t\tb.serverError(err)\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tif b.latency > 0 {\n\t\t\t\ttime.Sleep(b.latency)\n\t\t\t}\n\n\t\t\tb.lock.Lock()\n\t\t\tres := b.handler(req)\n\t\t\tb.history = append(b.history, RequestResponse{req.body, res})\n\t\t\tb.lock.Unlock()\n\n\t\t\tif res == nil {\n\t\t\t\tLogger.Printf(\"*** mockbroker/%d/%d: ignored %v\", b.brokerID, idx, spew.Sdump(req))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tLogger.Printf(\n\t\t\t\t\"*** mockbroker/%d/%d: replied to %T with %T\\n-> %s\\n-> %s\",\n\t\t\t\tb.brokerID, idx, req.body, res,\n\t\t\t\ts.Sprintf(\"%#v\", req.body),\n\t\t\t\ts.Sprintf(\"%#v\", res),\n\t\t\t)\n\n\t\t\tencodedRes, err := encode(res, nil)\n\t\t\tif err != nil {\n\t\t\t\tb.serverError(fmt.Errorf(\"failed to encode %T - %w\", res, err))\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif len(encodedRes) == 0 {\n\t\t\t\tb.lock.Lock()\n\t\t\t\tif b.notifier != nil {\n\t\t\t\t\tb.notifier(bytesRead, 0)\n\t\t\t\t}\n\t\t\t\tb.lock.Unlock()\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tresHeader := b.encodeHeader(res.headerVersion(), req.correlationID, uint32(len(encodedRes)))\n\t\t\tif _, err = conn.Write(resHeader); err != nil {\n\t\t\t\tb.serverError(err)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif _, err = conn.Write(encodedRes); err != nil {\n\t\t\t\tb.serverError(err)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tbytesWritten = len(resHeader) + len(encodedRes)\n\t\t} else {\n\t\t\t// GSSAPI is not part of kafka protocol, but is supported for authentication proposes.\n\t\t\t// Don't support history for this kind of request as is only used for test GSSAPI authentication mechanism\n\t\t\tb.lock.Lock()\n\t\t\tres := b.gssApiHandler(buffer)\n\t\t\tb.lock.Unlock()\n\t\t\tif res == nil {\n\t\t\t\tLogger.Printf(\"*** mockbroker/%d/%d: ignored %v\", b.brokerID, idx, spew.Sdump(buffer))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, err = conn.Write(res); err != nil {\n\t\t\t\tb.serverError(err)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tbytesWritten = len(res)\n\t\t}\n\n\t\tb.lock.Lock()\n\t\tif b.notifier != nil {\n\t\t\tb.notifier(bytesRead, bytesWritten)\n\t\t}\n\t\tb.lock.Unlock()\n\t}\n\tLogger.Printf(\"*** mockbroker/%d/%d: connection closed, err=%v\", b.BrokerID(), idx, err)\n}\n\nfunc (b *MockBroker) encodeHeader(headerVersion int16, correlationId int32, payloadLength uint32) []byte {\n\theaderLength := uint32(8)\n\n\tif headerVersion >= 1 {\n\t\theaderLength = 9\n\t}\n\n\tresHeader := make([]byte, headerLength)\n\tbinary.BigEndian.PutUint32(resHeader, payloadLength+headerLength-4)\n\tbinary.BigEndian.PutUint32(resHeader[4:], uint32(correlationId))\n\n\tif headerVersion >= 1 {\n\t\tbinary.PutUvarint(resHeader[8:], 0)\n\t}\n\n\treturn resHeader\n}\n\nfunc (b *MockBroker) defaultRequestHandler(req *request) (res encoderWithHeader) {\n\tselect {\n\tcase res, ok := <-b.expectations:\n\t\tif !ok {\n\t\t\treturn nil\n\t\t}\n\t\treturn res\n\tcase <-time.After(expectationTimeout):\n\t\treturn nil\n\t}\n}\n\nfunc isConnectionClosedError(err error) bool {\n\tvar result bool\n\topError := &net.OpError{}\n\tif errors.As(err, &opError) {\n\t\tresult = true\n\t} else if errors.Is(err, io.EOF) {\n\t\tresult = true\n\t} else if err.Error() == \"use of closed network connection\" {\n\t\tresult = true\n\t}\n\n\treturn result\n}\n\nfunc (b *MockBroker) serverError(err error) {\n\tb.t.Helper()\n\tif isConnectionClosedError(err) {\n\t\treturn\n\t}\n\tb.t.Errorf(err.Error())\n}\n\n// NewMockBroker launches a fake Kafka broker. It takes a TestReporter as provided by the\n// test framework and a channel of responses to use.  If an error occurs it is\n// simply logged to the TestReporter and the broker exits.\nfunc NewMockBroker(t TestReporter, brokerID int32) *MockBroker {\n\treturn NewMockBrokerAddr(t, brokerID, \"localhost:0\")\n}\n\n// NewMockBrokerAddr behaves like newMockBroker but listens on the address you give\n// it rather than just some ephemeral port.\nfunc NewMockBrokerAddr(t TestReporter, brokerID int32, addr string) *MockBroker {\n\tvar (\n\t\tlistener net.Listener\n\t\terr      error\n\t)\n\n\t// retry up to 20 times if address already in use (e.g., if replacing broker which hasn't cleanly shutdown)\n\tfor i := 0; i < 20; i++ {\n\t\tlistener, err = net.Listen(\"tcp\", addr)\n\t\tif err != nil {\n\t\t\tif errors.Is(err, syscall.EADDRINUSE) {\n\t\t\t\tLogger.Printf(\"*** mockbroker/%d waiting for %s (address already in use)\", brokerID, addr)\n\t\t\t\ttime.Sleep(time.Millisecond * 100)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tbreak\n\t}\n\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn NewMockBrokerListener(t, brokerID, listener)\n}\n\n// NewMockBrokerListener behaves like newMockBrokerAddr but accepts connections on the listener specified.\nfunc NewMockBrokerListener(t TestReporter, brokerID int32, listener net.Listener) *MockBroker {\n\tvar err error\n\n\tbroker := &MockBroker{\n\t\tclosing:      make(chan none),\n\t\tstopper:      make(chan none),\n\t\tt:            t,\n\t\tbrokerID:     brokerID,\n\t\texpectations: make(chan encoderWithHeader, 512),\n\t\tlistener:     listener,\n\t}\n\tbroker.handler = broker.defaultRequestHandler\n\n\tLogger.Printf(\"*** mockbroker/%d listening on %s\\n\", brokerID, broker.listener.Addr().String())\n\t_, portStr, err := net.SplitHostPort(broker.listener.Addr().String())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\ttmp, err := strconv.ParseInt(portStr, 10, 32)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tbroker.port = int32(tmp)\n\n\tgo broker.serverLoop()\n\n\treturn broker\n}\n\nfunc (b *MockBroker) Returns(e encoderWithHeader) {\n\tb.expectations <- e\n}\n"
  },
  {
    "path": "mockkerberos.go",
    "content": "package sarama\n\nimport (\n\t\"encoding/binary\"\n\t\"encoding/hex\"\n\n\t\"github.com/jcmturner/gokrb5/v8/credentials\"\n\t\"github.com/jcmturner/gokrb5/v8/gssapi\"\n\t\"github.com/jcmturner/gokrb5/v8/iana/keyusage\"\n\t\"github.com/jcmturner/gokrb5/v8/messages\"\n\t\"github.com/jcmturner/gokrb5/v8/types\"\n)\n\ntype KafkaGSSAPIHandler struct {\n\tclient         *MockKerberosClient\n\tbadResponse    bool\n\tbadKeyChecksum bool\n}\n\nfunc (h *KafkaGSSAPIHandler) MockKafkaGSSAPI(buffer []byte) []byte {\n\t// Default payload used for verify\n\terr := h.client.Login() // Mock client construct keys when login\n\tif err != nil {\n\t\treturn nil\n\t}\n\tif h.badResponse { // Returns trash\n\t\treturn []byte{0x00, 0x00, 0x00, 0x01, 0xAD}\n\t}\n\n\tpack := gssapi.WrapToken{\n\t\tFlags:     KRB5_USER_AUTH,\n\t\tEC:        12,\n\t\tRRC:       0,\n\t\tSndSeqNum: 3398292281,\n\t\tPayload:   []byte{0x11, 0x00}, // 1100\n\t}\n\t// Compute checksum\n\tif h.badKeyChecksum {\n\t\tpack.CheckSum = []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}\n\t} else {\n\t\terr = pack.SetCheckSum(h.client.ASRep.DecryptedEncPart.Key, keyusage.GSSAPI_ACCEPTOR_SEAL)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tpackBytes, err := pack.Marshal()\n\tif err != nil {\n\t\treturn nil\n\t}\n\tlenBytes := len(packBytes)\n\tresponse := make([]byte, lenBytes+4)\n\tcopy(response[4:], packBytes)\n\tbinary.BigEndian.PutUint32(response, uint32(lenBytes))\n\treturn response\n}\n\ntype MockKerberosClient struct {\n\tasRepBytes  string\n\tASRep       messages.ASRep\n\tcredentials *credentials.Credentials\n\tmockError   error\n\terrorStage  string\n}\n\nfunc (c *MockKerberosClient) Login() error {\n\tif c.errorStage == \"login\" && c.mockError != nil {\n\t\treturn c.mockError\n\t}\n\tc.asRepBytes = \"6b8202e9308202e5a003020105a10302010ba22b30293027a103020113a220041e301c301aa003020112a1131b114\" +\n\t\t\"558414d504c452e434f4d636c69656e74a30d1b0b4558414d504c452e434f4da4133011a003020101a10a30081b06636c69656e7\" +\n\t\t\"4a5820156618201523082014ea003020105a10d1b0b4558414d504c452e434f4da220301ea003020102a11730151b066b7262746\" +\n\t\t\"7741b0b4558414d504c452e434f4da382011430820110a003020112a103020101a28201020481ffdb9891175d106818e61008c51\" +\n\t\t\"d0b3462bca92f3bf9d4cfa82de4c4d7aff9994ec87c573e3a3d54dcb2bb79618c76f2bf4a3d006f90d5bdbd049bc18f48be39203\" +\n\t\t\"549ca02acaf63f292b12404f9b74c34b83687119d8f56552ccc0c50ebee2a53bb114c1b4619bb1d5d31f0f49b4d40a08a9b4c046\" +\n\t\t\"2e1398d0b648be1c0e50c552ad16e1d8d8e74263dd0bf0ec591e4797dfd40a9a1be4ae830d03a306e053fd7586fef84ffc5e4a83\" +\n\t\t\"7c3122bf3e6a40fe87e84019f6283634461b955712b44a5f7386c278bff94ec2c2dc0403247e29c2450e853471ceababf9b8911f\" +\n\t\t\"997f2e3010b046d2c49eb438afb0f4c210821e80d4ffa4c9521eb895dcd68610b3feaa682012c30820128a003020112a282011f0\" +\n\t\t\"482011bce73cbce3f1dd17661c412005f0f2257c756fe8e98ff97e6ec24b7bab66e5fd3a3827aeeae4757af0c6e892948122d8b2\" +\n\t\t\"03c8df48df0ef5d142d0e416d688f11daa0fcd63d96bdd431d02b8e951c664eeff286a2be62383d274a04016d5f0e141da58cb86\" +\n\t\t\"331de64063062f4f885e8e9ce5b181ca2fdc67897c5995e0ae1ae0c171a64493ff7bd91bc6d89cd4fce1e2b3ea0a10e34b0d5eda\" +\n\t\t\"aa38ee727b50c5632ed1d2f2b457908e616178d0d80b72af209fb8ac9dbaa1768fa45931392b36b6d8c12400f8ded2efaa0654d0\" +\n\t\t\"da1db966e8b5aab4706c800f95d559664646041fdb38b411c62fc0fbe0d25083a28562b0e1c8df16e62e9d5626b0addee489835f\" +\n\t\t\"eedb0f26c05baa596b69b17f47920aa64b29dc77cfcc97ba47885\"\n\tapRepBytes, err := hex.DecodeString(c.asRepBytes)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = c.ASRep.Unmarshal(apRepBytes)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc.credentials = credentials.New(\"client\", \"EXAMPLE.COM\").WithPassword(\"qwerty\")\n\t_, err = c.ASRep.DecryptEncPart(c.credentials)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (c *MockKerberosClient) GetServiceTicket(spn string) (messages.Ticket, types.EncryptionKey, error) {\n\tif c.errorStage == \"service_ticket\" && c.mockError != nil {\n\t\treturn messages.Ticket{}, types.EncryptionKey{}, c.mockError\n\t}\n\treturn c.ASRep.Ticket, c.ASRep.DecryptedEncPart.Key, nil\n}\n\nfunc (c *MockKerberosClient) Domain() string {\n\treturn \"EXAMPLE.COM\"\n}\n\nfunc (c *MockKerberosClient) CName() types.PrincipalName {\n\tp := types.PrincipalName{\n\t\tNameType: KRB5_USER_AUTH,\n\t\tNameString: []string{\n\t\t\t\"kafka\",\n\t\t\t\"kafka\",\n\t\t},\n\t}\n\treturn p\n}\n\nfunc (c *MockKerberosClient) Destroy() {\n\t// Do nothing.\n}\n"
  },
  {
    "path": "mockresponses.go",
    "content": "package sarama\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"sync\"\n)\n\n// TestReporter has methods matching go's testing.T to avoid importing\n// `testing` in the main part of the library.\ntype TestReporter interface {\n\tError(...interface{})\n\tErrorf(string, ...interface{})\n\tFatal(...interface{})\n\tFatalf(string, ...interface{})\n\tHelper()\n}\n\n// MockResponse is a response builder interface it defines one method that\n// allows generating a response based on a request body. MockResponses are used\n// to program behavior of MockBroker in tests.\ntype MockResponse interface {\n\tFor(reqBody versionedDecoder) (res encoderWithHeader)\n}\n\n// MockWrapper is a mock response builder that returns a particular concrete\n// response regardless of the actual request passed to the `For` method.\ntype MockWrapper struct {\n\tres encoderWithHeader\n}\n\nfunc (mw *MockWrapper) For(reqBody versionedDecoder) (res encoderWithHeader) {\n\treturn mw.res\n}\n\nfunc NewMockWrapper(res encoderWithHeader) *MockWrapper {\n\treturn &MockWrapper{res: res}\n}\n\n// MockSequence is a mock response builder that is created from a sequence of\n// concrete responses. Every time when a `MockBroker` calls its `For` method\n// the next response from the sequence is returned. When the end of the\n// sequence is reached the last element from the sequence is returned.\ntype MockSequence struct {\n\tresponses []MockResponse\n}\n\nfunc NewMockSequence(responses ...interface{}) *MockSequence {\n\tms := &MockSequence{}\n\tms.responses = make([]MockResponse, len(responses))\n\tfor i, res := range responses {\n\t\tswitch res := res.(type) {\n\t\tcase MockResponse:\n\t\t\tms.responses[i] = res\n\t\tcase encoderWithHeader:\n\t\t\tms.responses[i] = NewMockWrapper(res)\n\t\tdefault:\n\t\t\tpanic(fmt.Sprintf(\"Unexpected response type: %T\", res))\n\t\t}\n\t}\n\treturn ms\n}\n\nfunc (mc *MockSequence) For(reqBody versionedDecoder) (res encoderWithHeader) {\n\tres = mc.responses[0].For(reqBody)\n\tif len(mc.responses) > 1 {\n\t\tmc.responses = mc.responses[1:]\n\t}\n\treturn res\n}\n\ntype MockListGroupsResponse struct {\n\tgroups map[string]string\n\tt      TestReporter\n}\n\nfunc NewMockListGroupsResponse(t TestReporter) *MockListGroupsResponse {\n\treturn &MockListGroupsResponse{\n\t\tgroups: make(map[string]string),\n\t\tt:      t,\n\t}\n}\n\nfunc (m *MockListGroupsResponse) For(reqBody versionedDecoder) encoderWithHeader {\n\trequest := reqBody.(*ListGroupsRequest)\n\tresponse := &ListGroupsResponse{\n\t\tVersion: request.Version,\n\t\tGroups:  m.groups,\n\t}\n\treturn response\n}\n\nfunc (m *MockListGroupsResponse) AddGroup(groupID, protocolType string) *MockListGroupsResponse {\n\tm.groups[groupID] = protocolType\n\treturn m\n}\n\ntype MockDescribeGroupsResponse struct {\n\tgroups map[string]*GroupDescription\n\tt      TestReporter\n}\n\nfunc NewMockDescribeGroupsResponse(t TestReporter) *MockDescribeGroupsResponse {\n\treturn &MockDescribeGroupsResponse{\n\t\tt:      t,\n\t\tgroups: make(map[string]*GroupDescription),\n\t}\n}\n\nfunc (m *MockDescribeGroupsResponse) AddGroupDescription(groupID string, description *GroupDescription) *MockDescribeGroupsResponse {\n\tm.groups[groupID] = description\n\treturn m\n}\n\nfunc (m *MockDescribeGroupsResponse) For(reqBody versionedDecoder) encoderWithHeader {\n\trequest := reqBody.(*DescribeGroupsRequest)\n\n\tresponse := &DescribeGroupsResponse{Version: request.version()}\n\tfor _, requestedGroup := range request.Groups {\n\t\tif group, ok := m.groups[requestedGroup]; ok {\n\t\t\tresponse.Groups = append(response.Groups, group)\n\t\t} else {\n\t\t\t// Mimic real kafka - if a group doesn't exist, return\n\t\t\t// an entry with state \"Dead\"\n\t\t\tresponse.Groups = append(response.Groups, &GroupDescription{\n\t\t\t\tGroupId: requestedGroup,\n\t\t\t\tState:   \"Dead\",\n\t\t\t})\n\t\t}\n\t}\n\n\treturn response\n}\n\n// MockMetadataResponse is a `MetadataResponse` builder.\ntype MockMetadataResponse struct {\n\tcontrollerID int32\n\terrors       map[string]KError\n\tleaders      map[string]map[int32]int32\n\tbrokers      map[string]int32\n\tt            TestReporter\n}\n\nfunc NewMockMetadataResponse(t TestReporter) *MockMetadataResponse {\n\treturn &MockMetadataResponse{\n\t\terrors:  make(map[string]KError),\n\t\tleaders: make(map[string]map[int32]int32),\n\t\tbrokers: make(map[string]int32),\n\t\tt:       t,\n\t}\n}\n\nfunc (mmr *MockMetadataResponse) SetError(topic string, kerror KError) *MockMetadataResponse {\n\tmmr.errors[topic] = kerror\n\treturn mmr\n}\n\nfunc (mmr *MockMetadataResponse) SetLeader(topic string, partition, brokerID int32) *MockMetadataResponse {\n\tpartitions := mmr.leaders[topic]\n\tif partitions == nil {\n\t\tpartitions = make(map[int32]int32)\n\t\tmmr.leaders[topic] = partitions\n\t}\n\tpartitions[partition] = brokerID\n\treturn mmr\n}\n\nfunc (mmr *MockMetadataResponse) SetBroker(addr string, brokerID int32) *MockMetadataResponse {\n\tmmr.brokers[addr] = brokerID\n\treturn mmr\n}\n\nfunc (mmr *MockMetadataResponse) SetController(brokerID int32) *MockMetadataResponse {\n\tmmr.controllerID = brokerID\n\treturn mmr\n}\n\nfunc (mmr *MockMetadataResponse) For(reqBody versionedDecoder) encoderWithHeader {\n\tmetadataRequest := reqBody.(*MetadataRequest)\n\tmetadataResponse := &MetadataResponse{\n\t\tVersion:      metadataRequest.version(),\n\t\tControllerID: mmr.controllerID,\n\t}\n\tfor addr, brokerID := range mmr.brokers {\n\t\tmetadataResponse.AddBroker(addr, brokerID)\n\t}\n\n\t// Generate set of replicas\n\tvar replicas []int32\n\tvar offlineReplicas []int32\n\tfor _, brokerID := range mmr.brokers {\n\t\treplicas = append(replicas, brokerID)\n\t}\n\n\tif len(metadataRequest.Topics) == 0 {\n\t\tfor topic, partitions := range mmr.leaders {\n\t\t\tfor partition, brokerID := range partitions {\n\t\t\t\tmetadataResponse.AddTopicPartition(topic, partition, brokerID, replicas, replicas, offlineReplicas, ErrNoError)\n\t\t\t}\n\t\t}\n\t\tfor topic, err := range mmr.errors {\n\t\t\tmetadataResponse.AddTopic(topic, err)\n\t\t}\n\t\treturn metadataResponse\n\t}\n\tfor _, topic := range metadataRequest.Topics {\n\t\tleaders, ok := mmr.leaders[topic]\n\t\tif !ok {\n\t\t\tif err, ok := mmr.errors[topic]; ok {\n\t\t\t\tmetadataResponse.AddTopic(topic, err)\n\t\t\t} else {\n\t\t\t\tmetadataResponse.AddTopic(topic, ErrUnknownTopicOrPartition)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tfor partition, brokerID := range leaders {\n\t\t\tmetadataResponse.AddTopicPartition(topic, partition, brokerID, replicas, replicas, offlineReplicas, ErrNoError)\n\t\t}\n\t}\n\treturn metadataResponse\n}\n\n// MockOffsetResponse is an `OffsetResponse` builder.\ntype MockOffsetResponse struct {\n\toffsets map[string]map[int32]map[int64]int64\n\tt       TestReporter\n}\n\nfunc NewMockOffsetResponse(t TestReporter) *MockOffsetResponse {\n\treturn &MockOffsetResponse{\n\t\toffsets: make(map[string]map[int32]map[int64]int64),\n\t\tt:       t,\n\t}\n}\n\nfunc (mor *MockOffsetResponse) SetOffset(topic string, partition int32, time, offset int64) *MockOffsetResponse {\n\tpartitions := mor.offsets[topic]\n\tif partitions == nil {\n\t\tpartitions = make(map[int32]map[int64]int64)\n\t\tmor.offsets[topic] = partitions\n\t}\n\ttimes := partitions[partition]\n\tif times == nil {\n\t\ttimes = make(map[int64]int64)\n\t\tpartitions[partition] = times\n\t}\n\ttimes[time] = offset\n\treturn mor\n}\n\nfunc (mor *MockOffsetResponse) For(reqBody versionedDecoder) encoderWithHeader {\n\toffsetRequest := reqBody.(*OffsetRequest)\n\toffsetResponse := &OffsetResponse{Version: offsetRequest.Version}\n\tfor topic, partitions := range offsetRequest.blocks {\n\t\tfor partition, block := range partitions {\n\t\t\toffset := mor.getOffset(topic, partition, block.timestamp)\n\t\t\toffsetResponse.AddTopicPartition(topic, partition, offset)\n\t\t}\n\t}\n\treturn offsetResponse\n}\n\nfunc (mor *MockOffsetResponse) getOffset(topic string, partition int32, time int64) int64 {\n\tpartitions := mor.offsets[topic]\n\tif partitions == nil {\n\t\tmor.t.Errorf(\"missing topic: %s\", topic)\n\t}\n\ttimes := partitions[partition]\n\tif times == nil {\n\t\tmor.t.Errorf(\"missing partition: %d\", partition)\n\t}\n\toffset, ok := times[time]\n\tif !ok {\n\t\tmor.t.Errorf(\"missing time: %d\", time)\n\t}\n\treturn offset\n}\n\n// mockMessage is a message that used to be mocked for `FetchResponse`\ntype mockMessage struct {\n\tkey Encoder\n\tmsg Encoder\n}\n\nfunc newMockMessage(key, msg Encoder) *mockMessage {\n\treturn &mockMessage{\n\t\tkey: key,\n\t\tmsg: msg,\n\t}\n}\n\n// MockFetchResponse is a `FetchResponse` builder.\ntype MockFetchResponse struct {\n\tmessages       map[string]map[int32]map[int64]*mockMessage\n\tmessagesLock   *sync.RWMutex\n\thighWaterMarks map[string]map[int32]int64\n\tt              TestReporter\n\tbatchSize      int\n}\n\nfunc NewMockFetchResponse(t TestReporter, batchSize int) *MockFetchResponse {\n\treturn &MockFetchResponse{\n\t\tmessages:       make(map[string]map[int32]map[int64]*mockMessage),\n\t\tmessagesLock:   &sync.RWMutex{},\n\t\thighWaterMarks: make(map[string]map[int32]int64),\n\t\tt:              t,\n\t\tbatchSize:      batchSize,\n\t}\n}\n\nfunc (mfr *MockFetchResponse) SetMessage(topic string, partition int32, offset int64, msg Encoder) *MockFetchResponse {\n\treturn mfr.SetMessageWithKey(topic, partition, offset, nil, msg)\n}\n\nfunc (mfr *MockFetchResponse) SetMessageWithKey(topic string, partition int32, offset int64, key, msg Encoder) *MockFetchResponse {\n\tmfr.messagesLock.Lock()\n\tdefer mfr.messagesLock.Unlock()\n\tpartitions := mfr.messages[topic]\n\tif partitions == nil {\n\t\tpartitions = make(map[int32]map[int64]*mockMessage)\n\t\tmfr.messages[topic] = partitions\n\t}\n\tmessages := partitions[partition]\n\tif messages == nil {\n\t\tmessages = make(map[int64]*mockMessage)\n\t\tpartitions[partition] = messages\n\t}\n\tmessages[offset] = newMockMessage(key, msg)\n\treturn mfr\n}\n\nfunc (mfr *MockFetchResponse) SetHighWaterMark(topic string, partition int32, offset int64) *MockFetchResponse {\n\tpartitions := mfr.highWaterMarks[topic]\n\tif partitions == nil {\n\t\tpartitions = make(map[int32]int64)\n\t\tmfr.highWaterMarks[topic] = partitions\n\t}\n\tpartitions[partition] = offset\n\treturn mfr\n}\n\nfunc (mfr *MockFetchResponse) For(reqBody versionedDecoder) encoderWithHeader {\n\tfetchRequest := reqBody.(*FetchRequest)\n\tres := &FetchResponse{\n\t\tVersion: fetchRequest.Version,\n\t}\n\tfor topic, partitions := range fetchRequest.blocks {\n\t\tfor partition, block := range partitions {\n\t\t\tinitialOffset := block.fetchOffset\n\t\t\toffset := initialOffset\n\t\t\tmaxOffset := initialOffset + int64(mfr.getMessageCount(topic, partition))\n\t\t\tfor i := 0; i < mfr.batchSize && offset < maxOffset; {\n\t\t\t\tmsg := mfr.getMessage(topic, partition, offset)\n\t\t\t\tif msg != nil {\n\t\t\t\t\tres.AddMessage(topic, partition, msg.key, msg.msg, offset)\n\t\t\t\t\ti++\n\t\t\t\t}\n\t\t\t\toffset++\n\t\t\t}\n\t\t\tfb := res.GetBlock(topic, partition)\n\t\t\tif fb == nil {\n\t\t\t\tres.AddError(topic, partition, ErrNoError)\n\t\t\t\tfb = res.GetBlock(topic, partition)\n\t\t\t}\n\t\t\tfb.HighWaterMarkOffset = mfr.getHighWaterMark(topic, partition)\n\t\t}\n\t}\n\treturn res\n}\n\nfunc (mfr *MockFetchResponse) getMessage(topic string, partition int32, offset int64) *mockMessage {\n\tmfr.messagesLock.RLock()\n\tdefer mfr.messagesLock.RUnlock()\n\tpartitions := mfr.messages[topic]\n\tif partitions == nil {\n\t\treturn nil\n\t}\n\tmessages := partitions[partition]\n\tif messages == nil {\n\t\treturn nil\n\t}\n\treturn messages[offset]\n}\n\nfunc (mfr *MockFetchResponse) getMessageCount(topic string, partition int32) int {\n\tmfr.messagesLock.RLock()\n\tdefer mfr.messagesLock.RUnlock()\n\tpartitions := mfr.messages[topic]\n\tif partitions == nil {\n\t\treturn 0\n\t}\n\tmessages := partitions[partition]\n\tif messages == nil {\n\t\treturn 0\n\t}\n\treturn len(messages)\n}\n\nfunc (mfr *MockFetchResponse) getHighWaterMark(topic string, partition int32) int64 {\n\tpartitions := mfr.highWaterMarks[topic]\n\tif partitions == nil {\n\t\treturn 0\n\t}\n\treturn partitions[partition]\n}\n\n// MockConsumerMetadataResponse is a `ConsumerMetadataResponse` builder.\ntype MockConsumerMetadataResponse struct {\n\tcoordinators map[string]interface{}\n\tt            TestReporter\n}\n\nfunc NewMockConsumerMetadataResponse(t TestReporter) *MockConsumerMetadataResponse {\n\treturn &MockConsumerMetadataResponse{\n\t\tcoordinators: make(map[string]interface{}),\n\t\tt:            t,\n\t}\n}\n\nfunc (mr *MockConsumerMetadataResponse) SetCoordinator(group string, broker *MockBroker) *MockConsumerMetadataResponse {\n\tmr.coordinators[group] = broker\n\treturn mr\n}\n\nfunc (mr *MockConsumerMetadataResponse) SetError(group string, kerror KError) *MockConsumerMetadataResponse {\n\tmr.coordinators[group] = kerror\n\treturn mr\n}\n\nfunc (mr *MockConsumerMetadataResponse) For(reqBody versionedDecoder) encoderWithHeader {\n\treq := reqBody.(*ConsumerMetadataRequest)\n\tgroup := req.ConsumerGroup\n\tres := &ConsumerMetadataResponse{Version: req.version()}\n\tv := mr.coordinators[group]\n\tswitch v := v.(type) {\n\tcase *MockBroker:\n\t\tres.Coordinator = &Broker{id: v.BrokerID(), addr: v.Addr()}\n\tcase KError:\n\t\tres.Err = v\n\t}\n\treturn res\n}\n\n// MockFindCoordinatorResponse is a `FindCoordinatorResponse` builder.\ntype MockFindCoordinatorResponse struct {\n\tgroupCoordinators map[string]interface{}\n\ttransCoordinators map[string]interface{}\n\tt                 TestReporter\n}\n\nfunc NewMockFindCoordinatorResponse(t TestReporter) *MockFindCoordinatorResponse {\n\treturn &MockFindCoordinatorResponse{\n\t\tgroupCoordinators: make(map[string]interface{}),\n\t\ttransCoordinators: make(map[string]interface{}),\n\t\tt:                 t,\n\t}\n}\n\nfunc (mr *MockFindCoordinatorResponse) SetCoordinator(coordinatorType CoordinatorType, group string, broker *MockBroker) *MockFindCoordinatorResponse {\n\tswitch coordinatorType {\n\tcase CoordinatorGroup:\n\t\tmr.groupCoordinators[group] = broker\n\tcase CoordinatorTransaction:\n\t\tmr.transCoordinators[group] = broker\n\t}\n\treturn mr\n}\n\nfunc (mr *MockFindCoordinatorResponse) SetError(coordinatorType CoordinatorType, group string, kerror KError) *MockFindCoordinatorResponse {\n\tswitch coordinatorType {\n\tcase CoordinatorGroup:\n\t\tmr.groupCoordinators[group] = kerror\n\tcase CoordinatorTransaction:\n\t\tmr.transCoordinators[group] = kerror\n\t}\n\treturn mr\n}\n\nfunc (mr *MockFindCoordinatorResponse) For(reqBody versionedDecoder) encoderWithHeader {\n\treq := reqBody.(*FindCoordinatorRequest)\n\tres := &FindCoordinatorResponse{Version: req.version()}\n\tvar v interface{}\n\tswitch req.CoordinatorType {\n\tcase CoordinatorGroup:\n\t\tv = mr.groupCoordinators[req.CoordinatorKey]\n\tcase CoordinatorTransaction:\n\t\tv = mr.transCoordinators[req.CoordinatorKey]\n\t}\n\tswitch v := v.(type) {\n\tcase *MockBroker:\n\t\tres.Coordinator = &Broker{id: v.BrokerID(), addr: v.Addr()}\n\tcase KError:\n\t\tres.Err = v\n\t}\n\treturn res\n}\n\n// MockOffsetCommitResponse is a `OffsetCommitResponse` builder.\ntype MockOffsetCommitResponse struct {\n\terrors map[string]map[string]map[int32]KError\n\tt      TestReporter\n}\n\nfunc NewMockOffsetCommitResponse(t TestReporter) *MockOffsetCommitResponse {\n\treturn &MockOffsetCommitResponse{t: t}\n}\n\nfunc (mr *MockOffsetCommitResponse) SetError(group, topic string, partition int32, kerror KError) *MockOffsetCommitResponse {\n\tif mr.errors == nil {\n\t\tmr.errors = make(map[string]map[string]map[int32]KError)\n\t}\n\ttopics := mr.errors[group]\n\tif topics == nil {\n\t\ttopics = make(map[string]map[int32]KError)\n\t\tmr.errors[group] = topics\n\t}\n\tpartitions := topics[topic]\n\tif partitions == nil {\n\t\tpartitions = make(map[int32]KError)\n\t\ttopics[topic] = partitions\n\t}\n\tpartitions[partition] = kerror\n\treturn mr\n}\n\nfunc (mr *MockOffsetCommitResponse) For(reqBody versionedDecoder) encoderWithHeader {\n\treq := reqBody.(*OffsetCommitRequest)\n\tgroup := req.ConsumerGroup\n\tres := &OffsetCommitResponse{Version: req.version()}\n\tfor topic, partitions := range req.blocks {\n\t\tfor partition := range partitions {\n\t\t\tres.AddError(topic, partition, mr.getError(group, topic, partition))\n\t\t}\n\t}\n\treturn res\n}\n\nfunc (mr *MockOffsetCommitResponse) getError(group, topic string, partition int32) KError {\n\ttopics := mr.errors[group]\n\tif topics == nil {\n\t\treturn ErrNoError\n\t}\n\tpartitions := topics[topic]\n\tif partitions == nil {\n\t\treturn ErrNoError\n\t}\n\tkerror, ok := partitions[partition]\n\tif !ok {\n\t\treturn ErrNoError\n\t}\n\treturn kerror\n}\n\n// MockProduceResponse is a `ProduceResponse` builder.\ntype MockProduceResponse struct {\n\tversion int16\n\terrors  map[string]map[int32]KError\n\tt       TestReporter\n}\n\nfunc NewMockProduceResponse(t TestReporter) *MockProduceResponse {\n\treturn &MockProduceResponse{t: t}\n}\n\nfunc (mr *MockProduceResponse) SetVersion(version int16) *MockProduceResponse {\n\tmr.version = version\n\treturn mr\n}\n\nfunc (mr *MockProduceResponse) SetError(topic string, partition int32, kerror KError) *MockProduceResponse {\n\tif mr.errors == nil {\n\t\tmr.errors = make(map[string]map[int32]KError)\n\t}\n\tpartitions := mr.errors[topic]\n\tif partitions == nil {\n\t\tpartitions = make(map[int32]KError)\n\t\tmr.errors[topic] = partitions\n\t}\n\tpartitions[partition] = kerror\n\treturn mr\n}\n\nfunc (mr *MockProduceResponse) For(reqBody versionedDecoder) encoderWithHeader {\n\treq := reqBody.(*ProduceRequest)\n\tres := &ProduceResponse{\n\t\tVersion: req.version(),\n\t}\n\tif mr.version > 0 {\n\t\tres.Version = mr.version\n\t}\n\tfor topic, partitions := range req.records {\n\t\tfor partition := range partitions {\n\t\t\tres.AddTopicPartition(topic, partition, mr.getError(topic, partition))\n\t\t}\n\t}\n\treturn res\n}\n\nfunc (mr *MockProduceResponse) getError(topic string, partition int32) KError {\n\tpartitions := mr.errors[topic]\n\tif partitions == nil {\n\t\treturn ErrNoError\n\t}\n\tkerror, ok := partitions[partition]\n\tif !ok {\n\t\treturn ErrNoError\n\t}\n\treturn kerror\n}\n\n// MockOffsetFetchResponse is a `OffsetFetchResponse` builder.\ntype MockOffsetFetchResponse struct {\n\toffsets map[string]map[string]map[int32]*OffsetFetchResponseBlock\n\terror   KError\n\tt       TestReporter\n}\n\nfunc NewMockOffsetFetchResponse(t TestReporter) *MockOffsetFetchResponse {\n\treturn &MockOffsetFetchResponse{t: t}\n}\n\nfunc (mr *MockOffsetFetchResponse) SetOffset(group, topic string, partition int32, offset int64, metadata string, kerror KError) *MockOffsetFetchResponse {\n\tif mr.offsets == nil {\n\t\tmr.offsets = make(map[string]map[string]map[int32]*OffsetFetchResponseBlock)\n\t}\n\ttopics := mr.offsets[group]\n\tif topics == nil {\n\t\ttopics = make(map[string]map[int32]*OffsetFetchResponseBlock)\n\t\tmr.offsets[group] = topics\n\t}\n\tpartitions := topics[topic]\n\tif partitions == nil {\n\t\tpartitions = make(map[int32]*OffsetFetchResponseBlock)\n\t\ttopics[topic] = partitions\n\t}\n\tpartitions[partition] = &OffsetFetchResponseBlock{offset, 0, metadata, kerror}\n\treturn mr\n}\n\nfunc (mr *MockOffsetFetchResponse) SetError(kerror KError) *MockOffsetFetchResponse {\n\tmr.error = kerror\n\treturn mr\n}\n\nfunc (mr *MockOffsetFetchResponse) For(reqBody versionedDecoder) encoderWithHeader {\n\treq := reqBody.(*OffsetFetchRequest)\n\tgroup := req.ConsumerGroup\n\tres := &OffsetFetchResponse{Version: req.Version}\n\n\tfor topic, partitions := range mr.offsets[group] {\n\t\tfor partition, block := range partitions {\n\t\t\tres.AddBlock(topic, partition, block)\n\t\t}\n\t}\n\n\tif res.Version >= 2 {\n\t\tres.Err = mr.error\n\t}\n\treturn res\n}\n\ntype MockCreateTopicsResponse struct {\n\tt TestReporter\n}\n\nfunc NewMockCreateTopicsResponse(t TestReporter) *MockCreateTopicsResponse {\n\treturn &MockCreateTopicsResponse{t: t}\n}\n\nfunc (mr *MockCreateTopicsResponse) For(reqBody versionedDecoder) encoderWithHeader {\n\treq := reqBody.(*CreateTopicsRequest)\n\tres := &CreateTopicsResponse{\n\t\tVersion: req.Version,\n\t}\n\tres.TopicErrors = make(map[string]*TopicError)\n\n\tfor topic := range req.TopicDetails {\n\t\tif res.Version >= 1 && strings.HasPrefix(topic, \"_\") {\n\t\t\tmsg := \"insufficient permissions to create topic with reserved prefix\"\n\t\t\tres.TopicErrors[topic] = &TopicError{\n\t\t\t\tErr:    ErrTopicAuthorizationFailed,\n\t\t\t\tErrMsg: &msg,\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tres.TopicErrors[topic] = &TopicError{Err: ErrNoError}\n\t}\n\treturn res\n}\n\ntype MockDeleteTopicsResponse struct {\n\tt     TestReporter\n\terror KError\n}\n\nfunc NewMockDeleteTopicsResponse(t TestReporter) *MockDeleteTopicsResponse {\n\treturn &MockDeleteTopicsResponse{t: t}\n}\n\nfunc (mr *MockDeleteTopicsResponse) For(reqBody versionedDecoder) encoderWithHeader {\n\treq := reqBody.(*DeleteTopicsRequest)\n\tres := &DeleteTopicsResponse{Version: req.version()}\n\tres.TopicErrorCodes = make(map[string]KError)\n\n\tfor _, topic := range req.Topics {\n\t\tres.TopicErrorCodes[topic] = mr.error\n\t}\n\tres.Version = req.Version\n\treturn res\n}\n\nfunc (mr *MockDeleteTopicsResponse) SetError(kerror KError) *MockDeleteTopicsResponse {\n\tmr.error = kerror\n\treturn mr\n}\n\ntype MockCreatePartitionsResponse struct {\n\tt TestReporter\n}\n\nfunc NewMockCreatePartitionsResponse(t TestReporter) *MockCreatePartitionsResponse {\n\treturn &MockCreatePartitionsResponse{t: t}\n}\n\nfunc (mr *MockCreatePartitionsResponse) For(reqBody versionedDecoder) encoderWithHeader {\n\treq := reqBody.(*CreatePartitionsRequest)\n\tres := &CreatePartitionsResponse{Version: req.version()}\n\tres.TopicPartitionErrors = make(map[string]*TopicPartitionError)\n\n\tfor topic := range req.TopicPartitions {\n\t\tif strings.HasPrefix(topic, \"_\") {\n\t\t\tmsg := \"insufficient permissions to create partition on topic with reserved prefix\"\n\t\t\tres.TopicPartitionErrors[topic] = &TopicPartitionError{\n\t\t\t\tErr:    ErrTopicAuthorizationFailed,\n\t\t\t\tErrMsg: &msg,\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tres.TopicPartitionErrors[topic] = &TopicPartitionError{Err: ErrNoError}\n\t}\n\treturn res\n}\n\ntype MockAlterPartitionReassignmentsResponse struct {\n\tt TestReporter\n}\n\nfunc NewMockAlterPartitionReassignmentsResponse(t TestReporter) *MockAlterPartitionReassignmentsResponse {\n\treturn &MockAlterPartitionReassignmentsResponse{t: t}\n}\n\nfunc (mr *MockAlterPartitionReassignmentsResponse) For(reqBody versionedDecoder) encoderWithHeader {\n\treq := reqBody.(*AlterPartitionReassignmentsRequest)\n\t_ = req\n\tres := &AlterPartitionReassignmentsResponse{Version: req.version()}\n\treturn res\n}\n\ntype MockListPartitionReassignmentsResponse struct {\n\tt TestReporter\n}\n\nfunc NewMockListPartitionReassignmentsResponse(t TestReporter) *MockListPartitionReassignmentsResponse {\n\treturn &MockListPartitionReassignmentsResponse{t: t}\n}\n\nfunc (mr *MockListPartitionReassignmentsResponse) For(reqBody versionedDecoder) encoderWithHeader {\n\treq := reqBody.(*ListPartitionReassignmentsRequest)\n\t_ = req\n\tres := &ListPartitionReassignmentsResponse{Version: req.version()}\n\n\tfor topic, partitions := range req.blocks {\n\t\tfor _, partition := range partitions {\n\t\t\tres.AddBlock(topic, partition, []int32{0}, []int32{1}, []int32{2})\n\t\t}\n\t}\n\n\treturn res\n}\n\ntype MockElectLeadersResponse struct {\n\tt TestReporter\n}\n\nfunc NewMockElectLeadersResponse(t TestReporter) *MockElectLeadersResponse {\n\treturn &MockElectLeadersResponse{t: t}\n}\n\nfunc (mr *MockElectLeadersResponse) For(reqBody versionedDecoder) encoderWithHeader {\n\treq := reqBody.(*ElectLeadersRequest)\n\tres := &ElectLeadersResponse{Version: req.version(), ReplicaElectionResults: map[string]map[int32]*PartitionResult{}}\n\n\tfor topic, partitions := range req.TopicPartitions {\n\t\tfor _, partition := range partitions {\n\t\t\tres.ReplicaElectionResults[topic] = map[int32]*PartitionResult{\n\t\t\t\tpartition: {ErrorCode: ErrNoError},\n\t\t\t}\n\t\t}\n\t}\n\treturn res\n}\n\ntype MockDeleteRecordsResponse struct {\n\tt TestReporter\n}\n\nfunc NewMockDeleteRecordsResponse(t TestReporter) *MockDeleteRecordsResponse {\n\treturn &MockDeleteRecordsResponse{t: t}\n}\n\nfunc (mr *MockDeleteRecordsResponse) For(reqBody versionedDecoder) encoderWithHeader {\n\treq := reqBody.(*DeleteRecordsRequest)\n\tres := &DeleteRecordsResponse{Version: req.version()}\n\tres.Topics = make(map[string]*DeleteRecordsResponseTopic)\n\n\tfor topic, deleteRecordRequestTopic := range req.Topics {\n\t\tpartitions := make(map[int32]*DeleteRecordsResponsePartition)\n\t\tfor partition := range deleteRecordRequestTopic.PartitionOffsets {\n\t\t\tpartitions[partition] = &DeleteRecordsResponsePartition{Err: ErrNoError}\n\t\t}\n\t\tres.Topics[topic] = &DeleteRecordsResponseTopic{Partitions: partitions}\n\t}\n\treturn res\n}\n\ntype MockDescribeConfigsResponse struct {\n\tt TestReporter\n}\n\nfunc NewMockDescribeConfigsResponse(t TestReporter) *MockDescribeConfigsResponse {\n\treturn &MockDescribeConfigsResponse{t: t}\n}\n\nfunc (mr *MockDescribeConfigsResponse) For(reqBody versionedDecoder) encoderWithHeader {\n\treq := reqBody.(*DescribeConfigsRequest)\n\tres := &DescribeConfigsResponse{\n\t\tVersion: req.Version,\n\t}\n\n\tincludeSynonyms := req.Version > 0\n\tincludeSource := req.Version > 0\n\n\tfor _, r := range req.Resources {\n\t\tvar configEntries []*ConfigEntry\n\t\tswitch r.Type {\n\t\tcase BrokerResource:\n\t\t\tconfigEntries = append(configEntries,\n\t\t\t\t&ConfigEntry{\n\t\t\t\t\tName:     \"min.insync.replicas\",\n\t\t\t\t\tValue:    \"2\",\n\t\t\t\t\tReadOnly: false,\n\t\t\t\t\tDefault:  false,\n\t\t\t\t},\n\t\t\t)\n\t\t\tres.Resources = append(res.Resources, &ResourceResponse{\n\t\t\t\tName:    r.Name,\n\t\t\t\tConfigs: configEntries,\n\t\t\t})\n\t\tcase BrokerLoggerResource:\n\t\t\tconfigEntries = append(configEntries,\n\t\t\t\t&ConfigEntry{\n\t\t\t\t\tName:     \"kafka.controller.KafkaController\",\n\t\t\t\t\tValue:    \"DEBUG\",\n\t\t\t\t\tReadOnly: false,\n\t\t\t\t\tDefault:  false,\n\t\t\t\t},\n\t\t\t)\n\t\t\tres.Resources = append(res.Resources, &ResourceResponse{\n\t\t\t\tName:    r.Name,\n\t\t\t\tConfigs: configEntries,\n\t\t\t})\n\t\tcase TopicResource:\n\t\t\tmaxMessageBytes := &ConfigEntry{\n\t\t\t\tName:      \"max.message.bytes\",\n\t\t\t\tValue:     \"1000000\",\n\t\t\t\tReadOnly:  false,\n\t\t\t\tDefault:   !includeSource,\n\t\t\t\tSensitive: false,\n\t\t\t}\n\t\t\tif includeSource {\n\t\t\t\tmaxMessageBytes.Source = SourceDefault\n\t\t\t}\n\t\t\tif includeSynonyms {\n\t\t\t\tmaxMessageBytes.Synonyms = []*ConfigSynonym{\n\t\t\t\t\t{\n\t\t\t\t\t\tConfigName:  \"max.message.bytes\",\n\t\t\t\t\t\tConfigValue: \"500000\",\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t}\n\t\t\tretentionMs := &ConfigEntry{\n\t\t\t\tName:      \"retention.ms\",\n\t\t\t\tValue:     \"5000\",\n\t\t\t\tReadOnly:  false,\n\t\t\t\tDefault:   false,\n\t\t\t\tSensitive: false,\n\t\t\t}\n\t\t\tif includeSynonyms {\n\t\t\t\tretentionMs.Synonyms = []*ConfigSynonym{\n\t\t\t\t\t{\n\t\t\t\t\t\tConfigName:  \"log.retention.ms\",\n\t\t\t\t\t\tConfigValue: \"2500\",\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t}\n\t\t\tpassword := &ConfigEntry{\n\t\t\t\tName:      \"password\",\n\t\t\t\tValue:     \"12345\",\n\t\t\t\tReadOnly:  false,\n\t\t\t\tDefault:   false,\n\t\t\t\tSensitive: true,\n\t\t\t}\n\t\t\tconfigEntries = append(\n\t\t\t\tconfigEntries, maxMessageBytes, retentionMs, password)\n\t\t\tres.Resources = append(res.Resources, &ResourceResponse{\n\t\t\t\tName:    r.Name,\n\t\t\t\tConfigs: configEntries,\n\t\t\t})\n\t\t}\n\t}\n\treturn res\n}\n\ntype MockDescribeConfigsResponseWithErrorCode struct {\n\tt TestReporter\n}\n\nfunc NewMockDescribeConfigsResponseWithErrorCode(t TestReporter) *MockDescribeConfigsResponseWithErrorCode {\n\treturn &MockDescribeConfigsResponseWithErrorCode{t: t}\n}\n\nfunc (mr *MockDescribeConfigsResponseWithErrorCode) For(reqBody versionedDecoder) encoderWithHeader {\n\treq := reqBody.(*DescribeConfigsRequest)\n\tres := &DescribeConfigsResponse{\n\t\tVersion: req.Version,\n\t}\n\n\tfor _, r := range req.Resources {\n\t\tres.Resources = append(res.Resources, &ResourceResponse{\n\t\t\tName:      r.Name,\n\t\t\tType:      r.Type,\n\t\t\tErrorCode: 83,\n\t\t\tErrorMsg:  \"\",\n\t\t})\n\t}\n\treturn res\n}\n\ntype MockAlterConfigsResponse struct {\n\tt TestReporter\n}\n\nfunc NewMockAlterConfigsResponse(t TestReporter) *MockAlterConfigsResponse {\n\treturn &MockAlterConfigsResponse{t: t}\n}\n\nfunc (mr *MockAlterConfigsResponse) For(reqBody versionedDecoder) encoderWithHeader {\n\treq := reqBody.(*AlterConfigsRequest)\n\tres := &AlterConfigsResponse{Version: req.version()}\n\n\tfor _, r := range req.Resources {\n\t\tres.Resources = append(res.Resources, &AlterConfigsResourceResponse{\n\t\t\tName:     r.Name,\n\t\t\tType:     r.Type,\n\t\t\tErrorMsg: \"\",\n\t\t})\n\t}\n\treturn res\n}\n\ntype MockAlterConfigsResponseWithErrorCode struct {\n\tt TestReporter\n}\n\nfunc NewMockAlterConfigsResponseWithErrorCode(t TestReporter) *MockAlterConfigsResponseWithErrorCode {\n\treturn &MockAlterConfigsResponseWithErrorCode{t: t}\n}\n\nfunc (mr *MockAlterConfigsResponseWithErrorCode) For(reqBody versionedDecoder) encoderWithHeader {\n\treq := reqBody.(*AlterConfigsRequest)\n\tres := &AlterConfigsResponse{Version: req.version()}\n\n\tfor _, r := range req.Resources {\n\t\tres.Resources = append(res.Resources, &AlterConfigsResourceResponse{\n\t\t\tName:      r.Name,\n\t\t\tType:      r.Type,\n\t\t\tErrorCode: 83,\n\t\t\tErrorMsg:  \"\",\n\t\t})\n\t}\n\treturn res\n}\n\ntype MockIncrementalAlterConfigsResponse struct {\n\tt TestReporter\n}\n\nfunc NewMockIncrementalAlterConfigsResponse(t TestReporter) *MockIncrementalAlterConfigsResponse {\n\treturn &MockIncrementalAlterConfigsResponse{t: t}\n}\n\nfunc (mr *MockIncrementalAlterConfigsResponse) For(reqBody versionedDecoder) encoderWithHeader {\n\treq := reqBody.(*IncrementalAlterConfigsRequest)\n\tres := &IncrementalAlterConfigsResponse{Version: req.version()}\n\n\tfor _, r := range req.Resources {\n\t\tres.Resources = append(res.Resources, &AlterConfigsResourceResponse{\n\t\t\tName:     r.Name,\n\t\t\tType:     r.Type,\n\t\t\tErrorMsg: \"\",\n\t\t})\n\t}\n\treturn res\n}\n\ntype MockIncrementalAlterConfigsResponseWithErrorCode struct {\n\tt TestReporter\n}\n\nfunc NewMockIncrementalAlterConfigsResponseWithErrorCode(t TestReporter) *MockIncrementalAlterConfigsResponseWithErrorCode {\n\treturn &MockIncrementalAlterConfigsResponseWithErrorCode{t: t}\n}\n\nfunc (mr *MockIncrementalAlterConfigsResponseWithErrorCode) For(reqBody versionedDecoder) encoderWithHeader {\n\treq := reqBody.(*IncrementalAlterConfigsRequest)\n\tres := &IncrementalAlterConfigsResponse{Version: req.version()}\n\n\tfor _, r := range req.Resources {\n\t\tres.Resources = append(res.Resources, &AlterConfigsResourceResponse{\n\t\t\tName:      r.Name,\n\t\t\tType:      r.Type,\n\t\t\tErrorCode: int16(ErrInvalidConfig),\n\t\t\tErrorMsg:  \"Invalid value xyz for configuration retention.ms: Not a number of type LONG\",\n\t\t})\n\t}\n\treturn res\n}\n\ntype MockCreateAclsResponse struct {\n\tt TestReporter\n}\n\nfunc NewMockCreateAclsResponse(t TestReporter) *MockCreateAclsResponse {\n\treturn &MockCreateAclsResponse{t: t}\n}\n\nfunc (mr *MockCreateAclsResponse) For(reqBody versionedDecoder) encoderWithHeader {\n\treq := reqBody.(*CreateAclsRequest)\n\tres := &CreateAclsResponse{Version: req.version()}\n\n\tfor range req.AclCreations {\n\t\tres.AclCreationResponses = append(res.AclCreationResponses, &AclCreationResponse{Err: ErrNoError})\n\t}\n\treturn res\n}\n\ntype MockCreateAclsResponseError struct {\n\tt TestReporter\n}\n\nfunc NewMockCreateAclsResponseWithError(t TestReporter) *MockCreateAclsResponseError {\n\treturn &MockCreateAclsResponseError{t: t}\n}\n\nfunc (mr *MockCreateAclsResponseError) For(reqBody versionedDecoder) encoderWithHeader {\n\treq := reqBody.(*CreateAclsRequest)\n\tres := &CreateAclsResponse{Version: req.version()}\n\n\tfor range req.AclCreations {\n\t\tres.AclCreationResponses = append(res.AclCreationResponses, &AclCreationResponse{Err: ErrInvalidRequest})\n\t}\n\treturn res\n}\n\ntype MockListAclsResponse struct {\n\tt TestReporter\n}\n\nfunc NewMockListAclsResponse(t TestReporter) *MockListAclsResponse {\n\treturn &MockListAclsResponse{t: t}\n}\n\nfunc (mr *MockListAclsResponse) For(reqBody versionedDecoder) encoderWithHeader {\n\treq := reqBody.(*DescribeAclsRequest)\n\tres := &DescribeAclsResponse{Version: req.version()}\n\tres.Err = ErrNoError\n\tacl := &ResourceAcls{}\n\tif req.ResourceName != nil {\n\t\tacl.Resource.ResourceName = *req.ResourceName\n\t}\n\tacl.Resource.ResourcePatternType = req.ResourcePatternTypeFilter\n\tacl.Resource.ResourceType = req.ResourceType\n\n\thost := \"*\"\n\tif req.Host != nil {\n\t\thost = *req.Host\n\t}\n\n\tprincipal := \"User:test\"\n\tif req.Principal != nil {\n\t\tprincipal = *req.Principal\n\t}\n\n\tpermissionType := req.PermissionType\n\tif permissionType == AclPermissionAny {\n\t\tpermissionType = AclPermissionAllow\n\t}\n\n\tacl.Acls = append(acl.Acls, &Acl{Operation: req.Operation, PermissionType: permissionType, Host: host, Principal: principal})\n\tres.ResourceAcls = append(res.ResourceAcls, acl)\n\tres.Version = int16(req.Version)\n\treturn res\n}\n\ntype MockSaslAuthenticateResponse struct {\n\tt                 TestReporter\n\tkerror            KError\n\tsaslAuthBytes     []byte\n\tsessionLifetimeMs int64\n}\n\nfunc NewMockSaslAuthenticateResponse(t TestReporter) *MockSaslAuthenticateResponse {\n\treturn &MockSaslAuthenticateResponse{t: t}\n}\n\nfunc (msar *MockSaslAuthenticateResponse) For(reqBody versionedDecoder) encoderWithHeader {\n\treq := reqBody.(*SaslAuthenticateRequest)\n\tres := &SaslAuthenticateResponse{\n\t\tVersion:           req.version(),\n\t\tErr:               msar.kerror,\n\t\tSaslAuthBytes:     msar.saslAuthBytes,\n\t\tSessionLifetimeMs: msar.sessionLifetimeMs,\n\t}\n\treturn res\n}\n\nfunc (msar *MockSaslAuthenticateResponse) SetError(kerror KError) *MockSaslAuthenticateResponse {\n\tmsar.kerror = kerror\n\treturn msar\n}\n\nfunc (msar *MockSaslAuthenticateResponse) SetAuthBytes(saslAuthBytes []byte) *MockSaslAuthenticateResponse {\n\tmsar.saslAuthBytes = saslAuthBytes\n\treturn msar\n}\n\nfunc (msar *MockSaslAuthenticateResponse) SetSessionLifetimeMs(sessionLifetimeMs int64) *MockSaslAuthenticateResponse {\n\tmsar.sessionLifetimeMs = sessionLifetimeMs\n\treturn msar\n}\n\ntype MockDeleteAclsResponse struct {\n\tt TestReporter\n}\n\ntype MockSaslHandshakeResponse struct {\n\tenabledMechanisms []string\n\tkerror            KError\n\tt                 TestReporter\n}\n\nfunc NewMockSaslHandshakeResponse(t TestReporter) *MockSaslHandshakeResponse {\n\treturn &MockSaslHandshakeResponse{t: t}\n}\n\nfunc (mshr *MockSaslHandshakeResponse) For(reqBody versionedDecoder) encoderWithHeader {\n\treq := reqBody.(*SaslHandshakeRequest)\n\tres := &SaslHandshakeResponse{Version: req.version()}\n\tres.Err = mshr.kerror\n\tres.EnabledMechanisms = mshr.enabledMechanisms\n\treturn res\n}\n\nfunc (mshr *MockSaslHandshakeResponse) SetError(kerror KError) *MockSaslHandshakeResponse {\n\tmshr.kerror = kerror\n\treturn mshr\n}\n\nfunc (mshr *MockSaslHandshakeResponse) SetEnabledMechanisms(enabledMechanisms []string) *MockSaslHandshakeResponse {\n\tmshr.enabledMechanisms = enabledMechanisms\n\treturn mshr\n}\n\nfunc NewMockDeleteAclsResponse(t TestReporter) *MockDeleteAclsResponse {\n\treturn &MockDeleteAclsResponse{t: t}\n}\n\nfunc (mr *MockDeleteAclsResponse) For(reqBody versionedDecoder) encoderWithHeader {\n\treq := reqBody.(*DeleteAclsRequest)\n\tres := &DeleteAclsResponse{Version: req.version()}\n\n\tfor range req.Filters {\n\t\tresponse := &FilterResponse{Err: ErrNoError}\n\t\tresponse.MatchingAcls = append(response.MatchingAcls, &MatchingAcl{Err: ErrNoError})\n\t\tres.FilterResponses = append(res.FilterResponses, response)\n\t}\n\tres.Version = int16(req.Version)\n\treturn res\n}\n\ntype MockDeleteGroupsResponse struct {\n\tdeletedGroups []string\n}\n\nfunc NewMockDeleteGroupsRequest(t TestReporter) *MockDeleteGroupsResponse {\n\treturn &MockDeleteGroupsResponse{}\n}\n\nfunc (m *MockDeleteGroupsResponse) SetDeletedGroups(groups []string) *MockDeleteGroupsResponse {\n\tm.deletedGroups = groups\n\treturn m\n}\n\nfunc (m *MockDeleteGroupsResponse) For(reqBody versionedDecoder) encoderWithHeader {\n\treq := reqBody.(*DeleteGroupsRequest)\n\tresp := &DeleteGroupsResponse{\n\t\tVersion:         req.version(),\n\t\tGroupErrorCodes: map[string]KError{},\n\t}\n\tfor _, group := range m.deletedGroups {\n\t\tresp.GroupErrorCodes[group] = ErrNoError\n\t}\n\treturn resp\n}\n\ntype MockDeleteOffsetResponse struct {\n\terrorCode      KError\n\ttopic          string\n\tpartition      int32\n\terrorPartition KError\n}\n\nfunc NewMockDeleteOffsetRequest(t TestReporter) *MockDeleteOffsetResponse {\n\treturn &MockDeleteOffsetResponse{}\n}\n\nfunc (m *MockDeleteOffsetResponse) SetDeletedOffset(errorCode KError, topic string, partition int32, errorPartition KError) *MockDeleteOffsetResponse {\n\tm.errorCode = errorCode\n\tm.topic = topic\n\tm.partition = partition\n\tm.errorPartition = errorPartition\n\treturn m\n}\n\nfunc (m *MockDeleteOffsetResponse) For(reqBody versionedDecoder) encoderWithHeader {\n\treq := reqBody.(*DeleteOffsetsRequest)\n\tresp := &DeleteOffsetsResponse{\n\t\tVersion:   req.version(),\n\t\tErrorCode: m.errorCode,\n\t\tErrors: map[string]map[int32]KError{\n\t\t\tm.topic: {m.partition: m.errorPartition},\n\t\t},\n\t}\n\treturn resp\n}\n\ntype MockJoinGroupResponse struct {\n\tt TestReporter\n\n\tThrottleTime  int32\n\tErr           KError\n\tGenerationId  int32\n\tGroupProtocol string\n\tLeaderId      string\n\tMemberId      string\n\tMembers       []GroupMember\n}\n\nfunc NewMockJoinGroupResponse(t TestReporter) *MockJoinGroupResponse {\n\treturn &MockJoinGroupResponse{\n\t\tt:       t,\n\t\tMembers: make([]GroupMember, 0),\n\t}\n}\n\nfunc (m *MockJoinGroupResponse) For(reqBody versionedDecoder) encoderWithHeader {\n\treq := reqBody.(*JoinGroupRequest)\n\tresp := &JoinGroupResponse{\n\t\tVersion:       req.Version,\n\t\tThrottleTime:  m.ThrottleTime,\n\t\tErr:           m.Err,\n\t\tGenerationId:  m.GenerationId,\n\t\tGroupProtocol: m.GroupProtocol,\n\t\tLeaderId:      m.LeaderId,\n\t\tMemberId:      m.MemberId,\n\t\tMembers:       m.Members,\n\t}\n\treturn resp\n}\n\nfunc (m *MockJoinGroupResponse) SetThrottleTime(t int32) *MockJoinGroupResponse {\n\tm.ThrottleTime = t\n\treturn m\n}\n\nfunc (m *MockJoinGroupResponse) SetError(kerr KError) *MockJoinGroupResponse {\n\tm.Err = kerr\n\treturn m\n}\n\nfunc (m *MockJoinGroupResponse) SetGenerationId(id int32) *MockJoinGroupResponse {\n\tm.GenerationId = id\n\treturn m\n}\n\nfunc (m *MockJoinGroupResponse) SetGroupProtocol(proto string) *MockJoinGroupResponse {\n\tm.GroupProtocol = proto\n\treturn m\n}\n\nfunc (m *MockJoinGroupResponse) SetLeaderId(id string) *MockJoinGroupResponse {\n\tm.LeaderId = id\n\treturn m\n}\n\nfunc (m *MockJoinGroupResponse) SetMemberId(id string) *MockJoinGroupResponse {\n\tm.MemberId = id\n\treturn m\n}\n\nfunc (m *MockJoinGroupResponse) SetMember(id string, meta *ConsumerGroupMemberMetadata) *MockJoinGroupResponse {\n\tbin, err := encode(meta, nil)\n\tif err != nil {\n\t\tpanic(fmt.Sprintf(\"error encoding member metadata: %v\", err))\n\t}\n\tm.Members = append(m.Members, GroupMember{MemberId: id, Metadata: bin})\n\treturn m\n}\n\ntype MockLeaveGroupResponse struct {\n\tt TestReporter\n\n\tErr KError\n}\n\nfunc NewMockLeaveGroupResponse(t TestReporter) *MockLeaveGroupResponse {\n\treturn &MockLeaveGroupResponse{t: t}\n}\n\nfunc (m *MockLeaveGroupResponse) For(reqBody versionedDecoder) encoderWithHeader {\n\treq := reqBody.(*LeaveGroupRequest)\n\tresp := &LeaveGroupResponse{\n\t\tVersion: req.version(),\n\t\tErr:     m.Err,\n\t}\n\treturn resp\n}\n\nfunc (m *MockLeaveGroupResponse) SetError(kerr KError) *MockLeaveGroupResponse {\n\tm.Err = kerr\n\treturn m\n}\n\ntype MockSyncGroupResponse struct {\n\tt TestReporter\n\n\tErr              KError\n\tMemberAssignment []byte\n}\n\nfunc NewMockSyncGroupResponse(t TestReporter) *MockSyncGroupResponse {\n\treturn &MockSyncGroupResponse{t: t}\n}\n\nfunc (m *MockSyncGroupResponse) For(reqBody versionedDecoder) encoderWithHeader {\n\treq := reqBody.(*SyncGroupRequest)\n\tresp := &SyncGroupResponse{\n\t\tVersion:          req.version(),\n\t\tErr:              m.Err,\n\t\tMemberAssignment: m.MemberAssignment,\n\t}\n\treturn resp\n}\n\nfunc (m *MockSyncGroupResponse) SetError(kerr KError) *MockSyncGroupResponse {\n\tm.Err = kerr\n\treturn m\n}\n\nfunc (m *MockSyncGroupResponse) SetMemberAssignment(assignment *ConsumerGroupMemberAssignment) *MockSyncGroupResponse {\n\tbin, err := encode(assignment, nil)\n\tif err != nil {\n\t\tpanic(fmt.Sprintf(\"error encoding member assignment: %v\", err))\n\t}\n\tm.MemberAssignment = bin\n\treturn m\n}\n\ntype MockHeartbeatResponse struct {\n\tt TestReporter\n\n\tErr KError\n}\n\nfunc NewMockHeartbeatResponse(t TestReporter) *MockHeartbeatResponse {\n\treturn &MockHeartbeatResponse{t: t}\n}\n\nfunc (m *MockHeartbeatResponse) For(reqBody versionedDecoder) encoderWithHeader {\n\treq := reqBody.(*HeartbeatRequest)\n\tresp := &HeartbeatResponse{\n\t\tVersion: req.version(),\n\t}\n\treturn resp\n}\n\nfunc (m *MockHeartbeatResponse) SetError(kerr KError) *MockHeartbeatResponse {\n\tm.Err = kerr\n\treturn m\n}\n\ntype MockDescribeLogDirsResponse struct {\n\tt       TestReporter\n\tlogDirs []DescribeLogDirsResponseDirMetadata\n}\n\nfunc NewMockDescribeLogDirsResponse(t TestReporter) *MockDescribeLogDirsResponse {\n\treturn &MockDescribeLogDirsResponse{t: t}\n}\n\nfunc (m *MockDescribeLogDirsResponse) SetLogDirs(logDirPath string, topicPartitions map[string]int) *MockDescribeLogDirsResponse {\n\tvar topics []DescribeLogDirsResponseTopic\n\tfor topic := range topicPartitions {\n\t\tvar partitions []DescribeLogDirsResponsePartition\n\t\tfor i := 0; i < topicPartitions[topic]; i++ {\n\t\t\tpartitions = append(partitions, DescribeLogDirsResponsePartition{\n\t\t\t\tPartitionID: int32(i),\n\t\t\t\tIsTemporary: false,\n\t\t\t\tOffsetLag:   int64(0),\n\t\t\t\tSize:        int64(1234),\n\t\t\t})\n\t\t}\n\t\ttopics = append(topics, DescribeLogDirsResponseTopic{\n\t\t\tTopic:      topic,\n\t\t\tPartitions: partitions,\n\t\t})\n\t}\n\tlogDir := DescribeLogDirsResponseDirMetadata{\n\t\tErrorCode: ErrNoError,\n\t\tPath:      logDirPath,\n\t\tTopics:    topics,\n\t}\n\tm.logDirs = []DescribeLogDirsResponseDirMetadata{logDir}\n\treturn m\n}\n\nfunc (m *MockDescribeLogDirsResponse) For(reqBody versionedDecoder) encoderWithHeader {\n\treq := reqBody.(*DescribeLogDirsRequest)\n\tresp := &DescribeLogDirsResponse{\n\t\tVersion: req.version(),\n\t\tLogDirs: m.logDirs,\n\t}\n\treturn resp\n}\n\ntype MockApiVersionsResponse struct {\n\tt       TestReporter\n\tapiKeys []ApiVersionsResponseKey\n}\n\nfunc NewMockApiVersionsResponse(t TestReporter) *MockApiVersionsResponse {\n\treturn &MockApiVersionsResponse{\n\t\tt: t,\n\t\tapiKeys: []ApiVersionsResponseKey{\n\t\t\t{\n\t\t\t\tApiKey:     0,\n\t\t\t\tMinVersion: 5,\n\t\t\t\tMaxVersion: 8,\n\t\t\t},\n\t\t\t{\n\t\t\t\tApiKey:     1,\n\t\t\t\tMinVersion: 7,\n\t\t\t\tMaxVersion: 11,\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc (m *MockApiVersionsResponse) SetApiKeys(apiKeys []ApiVersionsResponseKey) *MockApiVersionsResponse {\n\tm.apiKeys = apiKeys\n\treturn m\n}\n\nfunc (m *MockApiVersionsResponse) For(reqBody versionedDecoder) encoderWithHeader {\n\treq := reqBody.(*ApiVersionsRequest)\n\tres := &ApiVersionsResponse{\n\t\tVersion: req.Version,\n\t\tApiKeys: m.apiKeys,\n\t}\n\treturn res\n}\n\n// MockInitProducerIDResponse is an `InitPorducerIDResponse` builder.\ntype MockInitProducerIDResponse struct {\n\tproducerID    int64\n\tproducerEpoch int16\n\terr           KError\n\tt             TestReporter\n}\n\nfunc NewMockInitProducerIDResponse(t TestReporter) *MockInitProducerIDResponse {\n\treturn &MockInitProducerIDResponse{\n\t\tt: t,\n\t}\n}\n\nfunc (m *MockInitProducerIDResponse) SetProducerID(id int) *MockInitProducerIDResponse {\n\tm.producerID = int64(id)\n\treturn m\n}\n\nfunc (m *MockInitProducerIDResponse) SetProducerEpoch(epoch int) *MockInitProducerIDResponse {\n\tm.producerEpoch = int16(epoch)\n\treturn m\n}\n\nfunc (m *MockInitProducerIDResponse) SetError(err KError) *MockInitProducerIDResponse {\n\tm.err = err\n\treturn m\n}\n\nfunc (m *MockInitProducerIDResponse) For(reqBody versionedDecoder) encoderWithHeader {\n\treq := reqBody.(*InitProducerIDRequest)\n\tres := &InitProducerIDResponse{\n\t\tVersion:       req.Version,\n\t\tErr:           m.err,\n\t\tProducerID:    m.producerID,\n\t\tProducerEpoch: m.producerEpoch,\n\t}\n\treturn res\n}\n"
  },
  {
    "path": "mocks/README.md",
    "content": "# sarama/mocks\n\nThe `mocks` subpackage includes mock implementations that implement the interfaces of the major sarama types.\nYou can use them to test your sarama applications using dependency injection.\n\nThe following mock objects are available:\n\n- [Consumer](https://pkg.go.dev/github.com/IBM/sarama/mocks#Consumer), which will create [PartitionConsumer](https://pkg.go.dev/github.com/IBM/sarama/mocks#PartitionConsumer) mocks.\n- [AsyncProducer](https://pkg.go.dev/github.com/IBM/sarama/mocks#AsyncProducer)\n- [SyncProducer](https://pkg.go.dev/github.com/IBM/sarama/mocks#SyncProducer)\n\nThe mocks allow you to set expectations on them. When you close the mocks, the expectations will be verified,\nand the results will be reported to the `*testing.T` object you provided when creating the mock.\n"
  },
  {
    "path": "mocks/async_producer.go",
    "content": "package mocks\n\nimport (\n\t\"errors\"\n\t\"sync\"\n\n\t\"github.com/IBM/sarama\"\n)\n\n// AsyncProducer implements sarama's Producer interface for testing purposes.\n// Before you can send messages to it's Input channel, you have to set expectations\n// so it knows how to handle the input; it returns an error if the number of messages\n// received is bigger then the number of expectations set. You can also set a\n// function in each expectation so that the message is checked by this function and\n// an error is returned if the match fails.\ntype AsyncProducer struct {\n\tl               sync.Mutex\n\tt               ErrorReporter\n\texpectations    []*producerExpectation\n\tclosed          chan struct{}\n\tinput           chan *sarama.ProducerMessage\n\tsuccesses       chan *sarama.ProducerMessage\n\terrors          chan *sarama.ProducerError\n\tisTransactional bool\n\ttxnLock         sync.Mutex\n\ttxnStatus       sarama.ProducerTxnStatusFlag\n\tlastOffset      int64\n\t*TopicConfig\n}\n\n// NewAsyncProducer instantiates a new Producer mock. The t argument should\n// be the *testing.T instance of your test method. An error will be written to it if\n// an expectation is violated. The config argument is validated and used to determine\n// whether it should ack successes on the Successes channel and handle partitioning.\nfunc NewAsyncProducer(t ErrorReporter, config *sarama.Config) *AsyncProducer {\n\tif config == nil {\n\t\tconfig = sarama.NewConfig()\n\t}\n\tif err := config.Validate(); err != nil {\n\t\tt.Errorf(\"Invalid mock configuration provided: %s\", err.Error())\n\t}\n\tmp := &AsyncProducer{\n\t\tt:               t,\n\t\tclosed:          make(chan struct{}),\n\t\texpectations:    make([]*producerExpectation, 0),\n\t\tinput:           make(chan *sarama.ProducerMessage, config.ChannelBufferSize),\n\t\tsuccesses:       make(chan *sarama.ProducerMessage, config.ChannelBufferSize),\n\t\terrors:          make(chan *sarama.ProducerError, config.ChannelBufferSize),\n\t\tisTransactional: config.Producer.Transaction.ID != \"\",\n\t\ttxnStatus:       sarama.ProducerTxnFlagReady,\n\t\tTopicConfig:     NewTopicConfig(),\n\t}\n\n\tgo func() {\n\t\tdefer func() {\n\t\t\tclose(mp.successes)\n\t\t\tclose(mp.errors)\n\t\t\tclose(mp.closed)\n\t\t}()\n\n\t\tpartitioners := make(map[string]sarama.Partitioner, 1)\n\n\t\tfor msg := range mp.input {\n\t\t\tmp.txnLock.Lock()\n\t\t\tif mp.IsTransactional() && mp.txnStatus&sarama.ProducerTxnFlagInTransaction == 0 {\n\t\t\t\tmp.t.Errorf(\"attempt to send message when transaction is not started or is in ending state.\")\n\t\t\t\tmp.errors <- &sarama.ProducerError{Err: errors.New(\"attempt to send message when transaction is not started or is in ending state\"), Msg: msg}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tmp.txnLock.Unlock()\n\t\t\tpartitioner := partitioners[msg.Topic]\n\t\t\tif partitioner == nil {\n\t\t\t\tpartitioner = config.Producer.Partitioner(msg.Topic)\n\t\t\t\tpartitioners[msg.Topic] = partitioner\n\t\t\t}\n\t\t\tmp.l.Lock()\n\t\t\tif len(mp.expectations) == 0 {\n\t\t\t\tmp.expectations = nil\n\t\t\t\tmp.t.Errorf(\"No more expectation set on this mock producer to handle the input message.\")\n\t\t\t} else {\n\t\t\t\texpectation := mp.expectations[0]\n\t\t\t\tmp.expectations = mp.expectations[1:]\n\n\t\t\t\tpartition, err := partitioner.Partition(msg, mp.partitions(msg.Topic))\n\t\t\t\tif err != nil {\n\t\t\t\t\tmp.t.Errorf(\"Partitioner returned an error: %s\", err.Error())\n\t\t\t\t\tmp.errors <- &sarama.ProducerError{Err: err, Msg: msg}\n\t\t\t\t} else {\n\t\t\t\t\tmsg.Partition = partition\n\t\t\t\t\tif expectation.CheckFunction != nil {\n\t\t\t\t\t\terr := expectation.CheckFunction(msg)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tmp.t.Errorf(\"Check function returned an error: %s\", err.Error())\n\t\t\t\t\t\t\tmp.errors <- &sarama.ProducerError{Err: err, Msg: msg}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif errors.Is(expectation.Result, errProduceSuccess) {\n\t\t\t\t\t\tmp.lastOffset++\n\t\t\t\t\t\tif config.Producer.Return.Successes {\n\t\t\t\t\t\t\tmsg.Offset = mp.lastOffset\n\t\t\t\t\t\t\tmp.successes <- msg\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if config.Producer.Return.Errors {\n\t\t\t\t\t\tmp.errors <- &sarama.ProducerError{Err: expectation.Result, Msg: msg}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tmp.l.Unlock()\n\t\t}\n\n\t\tmp.l.Lock()\n\t\tif len(mp.expectations) > 0 {\n\t\t\tmp.t.Errorf(\"Expected to exhaust all expectations, but %d are left.\", len(mp.expectations))\n\t\t}\n\t\tmp.l.Unlock()\n\t}()\n\n\treturn mp\n}\n\n////////////////////////////////////////////////\n// Implement Producer interface\n////////////////////////////////////////////////\n\n// AsyncClose corresponds with the AsyncClose method of sarama's Producer implementation.\n// By closing a mock producer, you also tell it that no more input will be provided, so it will\n// write an error to the test state if there's any remaining expectations.\nfunc (mp *AsyncProducer) AsyncClose() {\n\tclose(mp.input)\n}\n\n// Close corresponds with the Close method of sarama's Producer implementation.\n// By closing a mock producer, you also tell it that no more input will be provided, so it will\n// write an error to the test state if there's any remaining expectations.\nfunc (mp *AsyncProducer) Close() error {\n\tmp.AsyncClose()\n\t<-mp.closed\n\treturn nil\n}\n\n// Input corresponds with the Input method of sarama's Producer implementation.\n// You have to set expectations on the mock producer before writing messages to the Input\n// channel, so it knows how to handle them. If there is no more remaining expectations and\n// a messages is written to the Input channel, the mock producer will write an error to the test\n// state object.\nfunc (mp *AsyncProducer) Input() chan<- *sarama.ProducerMessage {\n\treturn mp.input\n}\n\n// Successes corresponds with the Successes method of sarama's Producer implementation.\nfunc (mp *AsyncProducer) Successes() <-chan *sarama.ProducerMessage {\n\treturn mp.successes\n}\n\n// Errors corresponds with the Errors method of sarama's Producer implementation.\nfunc (mp *AsyncProducer) Errors() <-chan *sarama.ProducerError {\n\treturn mp.errors\n}\n\nfunc (mp *AsyncProducer) IsTransactional() bool {\n\treturn mp.isTransactional\n}\n\nfunc (mp *AsyncProducer) BeginTxn() error {\n\tmp.txnLock.Lock()\n\tdefer mp.txnLock.Unlock()\n\n\tmp.txnStatus = sarama.ProducerTxnFlagInTransaction\n\treturn nil\n}\n\nfunc (mp *AsyncProducer) CommitTxn() error {\n\tmp.txnLock.Lock()\n\tdefer mp.txnLock.Unlock()\n\n\tmp.txnStatus = sarama.ProducerTxnFlagReady\n\treturn nil\n}\n\nfunc (mp *AsyncProducer) AbortTxn() error {\n\tmp.txnLock.Lock()\n\tdefer mp.txnLock.Unlock()\n\n\tmp.txnStatus = sarama.ProducerTxnFlagReady\n\treturn nil\n}\n\nfunc (mp *AsyncProducer) TxnStatus() sarama.ProducerTxnStatusFlag {\n\tmp.txnLock.Lock()\n\tdefer mp.txnLock.Unlock()\n\n\treturn mp.txnStatus\n}\n\nfunc (mp *AsyncProducer) AddOffsetsToTxn(offsets map[string][]*sarama.PartitionOffsetMetadata, groupId string) error {\n\treturn nil\n}\n\nfunc (mp *AsyncProducer) AddMessageToTxn(msg *sarama.ConsumerMessage, groupId string, metadata *string) error {\n\treturn nil\n}\n\n////////////////////////////////////////////////\n// Setting expectations\n////////////////////////////////////////////////\n\n// ExpectInputWithMessageCheckerFunctionAndSucceed sets an expectation on the mock producer that a\n// message will be provided on the input channel. The mock producer will call the given function to\n// check the message. If an error is returned it will be made available on the Errors channel\n// otherwise the mock will handle the message as if it produced successfully, i.e. it will make it\n// available on the Successes channel if the Producer.Return.Successes setting is set to true.\nfunc (mp *AsyncProducer) ExpectInputWithMessageCheckerFunctionAndSucceed(cf MessageChecker) *AsyncProducer {\n\tmp.l.Lock()\n\tdefer mp.l.Unlock()\n\tmp.expectations = append(mp.expectations, &producerExpectation{Result: errProduceSuccess, CheckFunction: cf})\n\n\treturn mp\n}\n\n// ExpectInputWithMessageCheckerFunctionAndFail sets an expectation on the mock producer that a\n// message will be provided on the input channel. The mock producer will first call the given\n// function to check the message. If an error is returned it will be made available on the Errors\n// channel otherwise the mock will handle the message as if it failed to produce successfully. This\n// means it will make a ProducerError available on the Errors channel.\nfunc (mp *AsyncProducer) ExpectInputWithMessageCheckerFunctionAndFail(cf MessageChecker, err error) *AsyncProducer {\n\tmp.l.Lock()\n\tdefer mp.l.Unlock()\n\tmp.expectations = append(mp.expectations, &producerExpectation{Result: err, CheckFunction: cf})\n\n\treturn mp\n}\n\n// ExpectInputWithCheckerFunctionAndSucceed sets an expectation on the mock producer that a message\n// will be provided on the input channel. The mock producer will call the given function to check\n// the message value. If an error is returned it will be made available on the Errors channel\n// otherwise the mock will handle the message as if it produced successfully, i.e. it will make\n// it available on the Successes channel if the Producer.Return.Successes setting is set to true.\nfunc (mp *AsyncProducer) ExpectInputWithCheckerFunctionAndSucceed(cf ValueChecker) *AsyncProducer {\n\tmp.ExpectInputWithMessageCheckerFunctionAndSucceed(messageValueChecker(cf))\n\n\treturn mp\n}\n\n// ExpectInputWithCheckerFunctionAndFail sets an expectation on the mock producer that a message\n// will be provided on the input channel. The mock producer will first call the given function to\n// check the message value. If an error is returned it will be made available on the Errors channel\n// otherwise the mock will handle the message as if it failed to produce successfully. This means\n// it will make a ProducerError available on the Errors channel.\nfunc (mp *AsyncProducer) ExpectInputWithCheckerFunctionAndFail(cf ValueChecker, err error) *AsyncProducer {\n\tmp.ExpectInputWithMessageCheckerFunctionAndFail(messageValueChecker(cf), err)\n\n\treturn mp\n}\n\n// ExpectInputAndSucceed sets an expectation on the mock producer that a message will be provided\n// on the input channel. The mock producer will handle the message as if it is produced successfully,\n// i.e. it will make it available on the Successes channel if the Producer.Return.Successes setting\n// is set to true.\nfunc (mp *AsyncProducer) ExpectInputAndSucceed() *AsyncProducer {\n\tmp.ExpectInputWithMessageCheckerFunctionAndSucceed(nil)\n\n\treturn mp\n}\n\n// ExpectInputAndFail sets an expectation on the mock producer that a message will be provided\n// on the input channel. The mock producer will handle the message as if it failed to produce\n// successfully. This means it will make a ProducerError available on the Errors channel.\nfunc (mp *AsyncProducer) ExpectInputAndFail(err error) *AsyncProducer {\n\tmp.ExpectInputWithMessageCheckerFunctionAndFail(nil, err)\n\n\treturn mp\n}\n"
  },
  {
    "path": "mocks/async_producer_test.go",
    "content": "//go:build !functional\n\npackage mocks\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/IBM/sarama\"\n)\n\nfunc generateRegexpChecker(re string) func([]byte) error {\n\treturn func(val []byte) error {\n\t\tmatched, err := regexp.MatchString(re, string(val))\n\t\tif err != nil {\n\t\t\treturn errors.New(\"Error while trying to match the input message with the expected pattern: \" + err.Error())\n\t\t}\n\t\tif !matched {\n\t\t\treturn fmt.Errorf(\"No match between input value \\\"%s\\\" and expected pattern \\\"%s\\\"\", val, re)\n\t\t}\n\t\treturn nil\n\t}\n}\n\ntype testReporterMock struct {\n\terrors []string\n}\n\nfunc newTestReporterMock() *testReporterMock {\n\treturn &testReporterMock{errors: make([]string, 0)}\n}\n\nfunc (trm *testReporterMock) Errorf(format string, args ...interface{}) {\n\ttrm.errors = append(trm.errors, fmt.Sprintf(format, args...))\n}\n\nfunc TestMockAsyncProducerImplementsAsyncProducerInterface(t *testing.T) {\n\tvar mp interface{} = &AsyncProducer{}\n\tif _, ok := mp.(sarama.AsyncProducer); !ok {\n\t\tt.Error(\"The mock producer should implement the sarama.Producer interface.\")\n\t}\n}\n\nfunc TestProducerReturnsExpectationsToChannels(t *testing.T) {\n\tconfig := NewTestConfig()\n\tconfig.Producer.Return.Successes = true\n\tmp := NewAsyncProducer(t, config).\n\t\tExpectInputAndSucceed().\n\t\tExpectInputAndSucceed().\n\t\tExpectInputAndFail(sarama.ErrOutOfBrokers)\n\n\tmp.Input() <- &sarama.ProducerMessage{Topic: \"test 1\"}\n\tmp.Input() <- &sarama.ProducerMessage{Topic: \"test 2\"}\n\tmp.Input() <- &sarama.ProducerMessage{Topic: \"test 3\"}\n\n\tmsg1 := <-mp.Successes()\n\tmsg2 := <-mp.Successes()\n\terr1 := <-mp.Errors()\n\n\tif msg1.Topic != \"test 1\" {\n\t\tt.Error(\"Expected message 1 to be returned first\")\n\t}\n\n\tif msg2.Topic != \"test 2\" {\n\t\tt.Error(\"Expected message 2 to be returned second\")\n\t}\n\n\tif err1.Msg.Topic != \"test 3\" || !errors.Is(err1, sarama.ErrOutOfBrokers) {\n\t\tt.Error(\"Expected message 3 to be returned as error\")\n\t}\n\n\tif err := mp.Close(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestProducerWithTooFewExpectations(t *testing.T) {\n\ttrm := newTestReporterMock()\n\tmp := NewAsyncProducer(trm, nil)\n\tmp.ExpectInputAndSucceed()\n\n\tmp.Input() <- &sarama.ProducerMessage{Topic: \"test\"}\n\tmp.Input() <- &sarama.ProducerMessage{Topic: \"test\"}\n\n\tif err := mp.Close(); err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif len(trm.errors) != 1 {\n\t\tt.Error(\"Expected to report an error\")\n\t}\n}\n\nfunc TestProducerWithTooManyExpectations(t *testing.T) {\n\ttrm := newTestReporterMock()\n\tmp := NewAsyncProducer(trm, nil).\n\t\tExpectInputAndSucceed().\n\t\tExpectInputAndFail(sarama.ErrOutOfBrokers)\n\n\tmp.Input() <- &sarama.ProducerMessage{Topic: \"test\"}\n\tif err := mp.Close(); err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif len(trm.errors) != 1 {\n\t\tt.Error(\"Expected to report an error\")\n\t}\n}\n\nfunc TestProducerFailTxn(t *testing.T) {\n\tconfig := NewTestConfig()\n\tconfig.Producer.Transaction.ID = \"test\"\n\tconfig.Producer.RequiredAcks = sarama.WaitForAll\n\tconfig.Producer.Retry.Backoff = 0\n\tconfig.Producer.Idempotent = true\n\tconfig.Net.MaxOpenRequests = 1\n\tconfig.Version = sarama.V0_11_0_0\n\n\ttrm := newTestReporterMock()\n\tmp := NewAsyncProducer(trm, config)\n\n\tmp.Input() <- &sarama.ProducerMessage{Topic: \"test\"}\n\n\t_ = mp.Close()\n\n\tif len(trm.errors) != 1 {\n\t\tt.Error(\"must have fail with txn begin error\")\n\t}\n}\n\nfunc TestProducerWithTxn(t *testing.T) {\n\tconfig := NewTestConfig()\n\tconfig.Producer.Transaction.ID = \"test\"\n\tconfig.Producer.RequiredAcks = sarama.WaitForAll\n\tconfig.Producer.Retry.Backoff = 0\n\tconfig.Producer.Idempotent = true\n\tconfig.Net.MaxOpenRequests = 1\n\tconfig.Version = sarama.V0_11_0_0\n\n\ttrm := newTestReporterMock()\n\tmp := NewAsyncProducer(trm, config).\n\t\tExpectInputAndSucceed()\n\n\tif !mp.IsTransactional() {\n\t\tt.Error(\"producer must be transactional\")\n\t}\n\n\tif err := mp.BeginTxn(); err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif mp.TxnStatus()&sarama.ProducerTxnFlagInTransaction == 0 {\n\t\tt.Error(\"transaction must be started\")\n\t}\n\n\tmp.Input() <- &sarama.ProducerMessage{Topic: \"test\"}\n\n\tif err := mp.AddMessageToTxn(&sarama.ConsumerMessage{\n\t\tTopic:     \"original-topic\",\n\t\tPartition: 0,\n\t\tOffset:    123,\n\t}, \"test-group\", nil); err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif err := mp.AddOffsetsToTxn(map[string][]*sarama.PartitionOffsetMetadata{\n\t\t\"original-topic\": {\n\t\t\t{\n\t\t\t\tPartition: 1,\n\t\t\t\tOffset:    321,\n\t\t\t},\n\t\t},\n\t}, \"test-group\"); err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif err := mp.CommitTxn(); err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif err := mp.Close(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestProducerWithCheckerFunction(t *testing.T) {\n\ttrm := newTestReporterMock()\n\tmp := NewAsyncProducer(trm, nil).\n\t\tExpectInputWithCheckerFunctionAndSucceed(generateRegexpChecker(\"^tes\")).\n\t\tExpectInputWithCheckerFunctionAndSucceed(generateRegexpChecker(\"^tes$\"))\n\n\tmp.Input() <- &sarama.ProducerMessage{Topic: \"test\", Value: sarama.StringEncoder(\"test\")}\n\tmp.Input() <- &sarama.ProducerMessage{Topic: \"test\", Value: sarama.StringEncoder(\"test\")}\n\tif err := mp.Close(); err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif len(mp.Errors()) != 1 {\n\t\tt.Error(\"Expected to report an error\")\n\t}\n\n\terr1 := <-mp.Errors()\n\tif !strings.HasPrefix(err1.Err.Error(), \"No match\") {\n\t\tt.Error(\"Expected to report a value check error, found: \", err1.Err)\n\t}\n}\n\nfunc TestProducerWithBrokenPartitioner(t *testing.T) {\n\ttrm := newTestReporterMock()\n\tconfig := NewTestConfig()\n\tconfig.Producer.Partitioner = func(string) sarama.Partitioner {\n\t\treturn brokePartitioner{}\n\t}\n\tmp := NewAsyncProducer(trm, config)\n\tmp.ExpectInputWithMessageCheckerFunctionAndSucceed(func(msg *sarama.ProducerMessage) error {\n\t\tif msg.Partition != 15 {\n\t\t\tt.Error(\"Expected partition 15, found: \", msg.Partition)\n\t\t}\n\t\tif msg.Topic != \"test\" {\n\t\t\tt.Errorf(`Expected topic \"test\", found: %q`, msg.Topic)\n\t\t}\n\t\treturn nil\n\t})\n\tmp.ExpectInputAndSucceed() // should actually fail in partitioning\n\n\tmp.Input() <- &sarama.ProducerMessage{Topic: \"test\"}\n\tmp.Input() <- &sarama.ProducerMessage{Topic: \"not-test\"}\n\tif err := mp.Close(); err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif len(trm.errors) != 1 || !strings.Contains(trm.errors[0], \"partitioning unavailable\") {\n\t\tt.Error(\"Expected to report partitioning unavailable, found\", trm.errors)\n\t}\n}\n\n// brokeProducer refuses to partition anything not on the “test” topic, and sends everything on\n// that topic to partition 15.\ntype brokePartitioner struct{}\n\nfunc (brokePartitioner) Partition(msg *sarama.ProducerMessage, n int32) (int32, error) {\n\tif msg.Topic == \"test\" {\n\t\treturn 15, nil\n\t}\n\treturn 0, errors.New(\"partitioning unavailable\")\n}\n\nfunc (brokePartitioner) RequiresConsistency() bool { return false }\n\nfunc TestProducerWithInvalidConfiguration(t *testing.T) {\n\ttrm := newTestReporterMock()\n\tconfig := NewTestConfig()\n\tconfig.Version = sarama.V0_11_0_2\n\tconfig.ClientID = \"not a valid producer ID\"\n\tmp := NewAsyncProducer(trm, config)\n\tif err := mp.Close(); err != nil {\n\t\tt.Error(err)\n\t}\n\tif len(trm.errors) != 1 {\n\t\tt.Error(\"Expected to report a single error\")\n\t} else if !strings.Contains(trm.errors[0], `ClientID value \"not a valid producer ID\" is not valid for Kafka versions before 1.0.0`) {\n\t\tt.Errorf(\"Unexpected error: %s\", trm.errors[0])\n\t}\n}\n"
  },
  {
    "path": "mocks/consumer.go",
    "content": "package mocks\n\nimport (\n\t\"sync\"\n\t\"sync/atomic\"\n\n\t\"github.com/IBM/sarama\"\n)\n\n// Consumer implements sarama's Consumer interface for testing purposes.\n// Before you can start consuming from this consumer, you have to register\n// topic/partitions using ExpectConsumePartition, and set expectations on them.\ntype Consumer struct {\n\tl                  sync.Mutex\n\tt                  ErrorReporter\n\tconfig             *sarama.Config\n\tpartitionConsumers map[string]map[int32]*PartitionConsumer\n\tmetadata           map[string][]int32\n}\n\n// NewConsumer returns a new mock Consumer instance. The t argument should\n// be the *testing.T instance of your test method. An error will be written to it if\n// an expectation is violated. The config argument can be set to nil; if it is\n// non-nil it is validated.\nfunc NewConsumer(t ErrorReporter, config *sarama.Config) *Consumer {\n\tif config == nil {\n\t\tconfig = sarama.NewConfig()\n\t}\n\tif err := config.Validate(); err != nil {\n\t\tt.Errorf(\"Invalid mock configuration provided: %s\", err.Error())\n\t}\n\n\tc := &Consumer{\n\t\tt:                  t,\n\t\tconfig:             config,\n\t\tpartitionConsumers: make(map[string]map[int32]*PartitionConsumer),\n\t}\n\treturn c\n}\n\n///////////////////////////////////////////////////\n// Consumer interface implementation\n///////////////////////////////////////////////////\n\n// ConsumePartition implements the ConsumePartition method from the sarama.Consumer interface.\n// Before you can start consuming a partition, you have to set expectations on it using\n// ExpectConsumePartition. You can only consume a partition once per consumer.\nfunc (c *Consumer) ConsumePartition(topic string, partition int32, offset int64) (sarama.PartitionConsumer, error) {\n\tc.l.Lock()\n\tdefer c.l.Unlock()\n\n\tif c.partitionConsumers[topic] == nil || c.partitionConsumers[topic][partition] == nil {\n\t\tc.t.Errorf(\"No expectations set for %s/%d\", topic, partition)\n\t\treturn nil, errOutOfExpectations\n\t}\n\n\tpc := c.partitionConsumers[topic][partition]\n\tif pc.consumed {\n\t\treturn nil, sarama.ConfigurationError(\"The topic/partition is already being consumed\")\n\t}\n\n\tif pc.offset != AnyOffset && pc.offset != offset {\n\t\tc.t.Errorf(\"Unexpected offset when calling ConsumePartition for %s/%d. Expected %d, got %d.\", topic, partition, pc.offset, offset)\n\t}\n\n\tpc.consumed = true\n\treturn pc, nil\n}\n\n// Topics returns a list of topics, as registered with SetTopicMetadata\nfunc (c *Consumer) Topics() ([]string, error) {\n\tc.l.Lock()\n\tdefer c.l.Unlock()\n\n\tif c.metadata == nil {\n\t\tc.t.Errorf(\"Unexpected call to Topics. Initialize the mock's topic metadata with SetTopicMetadata.\")\n\t\treturn nil, sarama.ErrOutOfBrokers\n\t}\n\n\tvar result []string\n\tfor topic := range c.metadata {\n\t\tresult = append(result, topic)\n\t}\n\treturn result, nil\n}\n\n// Partitions returns the list of parititons for the given topic, as registered with SetTopicMetadata\nfunc (c *Consumer) Partitions(topic string) ([]int32, error) {\n\tc.l.Lock()\n\tdefer c.l.Unlock()\n\n\tif c.metadata == nil {\n\t\tc.t.Errorf(\"Unexpected call to Partitions. Initialize the mock's topic metadata with SetTopicMetadata.\")\n\t\treturn nil, sarama.ErrOutOfBrokers\n\t}\n\tif c.metadata[topic] == nil {\n\t\treturn nil, sarama.ErrUnknownTopicOrPartition\n\t}\n\n\treturn c.metadata[topic], nil\n}\n\nfunc (c *Consumer) HighWaterMarks() map[string]map[int32]int64 {\n\tc.l.Lock()\n\tdefer c.l.Unlock()\n\n\thwms := make(map[string]map[int32]int64, len(c.partitionConsumers))\n\tfor topic, partitionConsumers := range c.partitionConsumers {\n\t\thwm := make(map[int32]int64, len(partitionConsumers))\n\t\tfor partition, pc := range partitionConsumers {\n\t\t\thwm[partition] = pc.HighWaterMarkOffset()\n\t\t}\n\t\thwms[topic] = hwm\n\t}\n\n\treturn hwms\n}\n\n// Close implements the Close method from the sarama.Consumer interface. It will close\n// all registered PartitionConsumer instances.\nfunc (c *Consumer) Close() error {\n\tc.l.Lock()\n\tdefer c.l.Unlock()\n\n\tfor _, partitions := range c.partitionConsumers {\n\t\tfor _, partitionConsumer := range partitions {\n\t\t\t_ = partitionConsumer.Close()\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Pause implements Consumer.\nfunc (c *Consumer) Pause(topicPartitions map[string][]int32) {\n\tc.l.Lock()\n\tdefer c.l.Unlock()\n\n\tfor topic, partitions := range topicPartitions {\n\t\tfor _, partition := range partitions {\n\t\t\tif topicConsumers, ok := c.partitionConsumers[topic]; ok {\n\t\t\t\tif partitionConsumer, ok := topicConsumers[partition]; ok {\n\t\t\t\t\tpartitionConsumer.Pause()\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Resume implements Consumer.\nfunc (c *Consumer) Resume(topicPartitions map[string][]int32) {\n\tc.l.Lock()\n\tdefer c.l.Unlock()\n\n\tfor topic, partitions := range topicPartitions {\n\t\tfor _, partition := range partitions {\n\t\t\tif topicConsumers, ok := c.partitionConsumers[topic]; ok {\n\t\t\t\tif partitionConsumer, ok := topicConsumers[partition]; ok {\n\t\t\t\t\tpartitionConsumer.Resume()\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n// PauseAll implements Consumer.\nfunc (c *Consumer) PauseAll() {\n\tc.l.Lock()\n\tdefer c.l.Unlock()\n\n\tfor _, partitions := range c.partitionConsumers {\n\t\tfor _, partitionConsumer := range partitions {\n\t\t\tpartitionConsumer.Pause()\n\t\t}\n\t}\n}\n\n// ResumeAll implements Consumer.\nfunc (c *Consumer) ResumeAll() {\n\tc.l.Lock()\n\tdefer c.l.Unlock()\n\n\tfor _, partitions := range c.partitionConsumers {\n\t\tfor _, partitionConsumer := range partitions {\n\t\t\tpartitionConsumer.Resume()\n\t\t}\n\t}\n}\n\n///////////////////////////////////////////////////\n// Expectation API\n///////////////////////////////////////////////////\n\n// SetTopicMetadata sets the clusters topic/partition metadata,\n// which will be returned by Topics() and Partitions().\nfunc (c *Consumer) SetTopicMetadata(metadata map[string][]int32) {\n\tc.l.Lock()\n\tdefer c.l.Unlock()\n\n\tc.metadata = metadata\n}\n\n// ExpectConsumePartition will register a topic/partition, so you can set expectations on it.\n// The registered PartitionConsumer will be returned, so you can set expectations\n// on it using method chaining. Once a topic/partition is registered, you are\n// expected to start consuming it using ConsumePartition. If that doesn't happen,\n// an error will be written to the error reporter once the mock consumer is closed. It also expects\n// that the message and error channels be written with YieldMessage and YieldError accordingly,\n// and be fully consumed once the mock consumer is closed if ExpectMessagesDrainedOnClose or\n// ExpectErrorsDrainedOnClose have been called.\nfunc (c *Consumer) ExpectConsumePartition(topic string, partition int32, offset int64) *PartitionConsumer {\n\tc.l.Lock()\n\tdefer c.l.Unlock()\n\n\tif c.partitionConsumers[topic] == nil {\n\t\tc.partitionConsumers[topic] = make(map[int32]*PartitionConsumer)\n\t}\n\n\tif c.partitionConsumers[topic][partition] == nil {\n\t\thighWatermarkOffset := offset\n\t\tif offset == sarama.OffsetOldest {\n\t\t\thighWatermarkOffset = 0\n\t\t}\n\n\t\tconsumer := &PartitionConsumer{\n\t\t\tt:                  c.t,\n\t\t\ttopic:              topic,\n\t\t\tpartition:          partition,\n\t\t\toffset:             offset,\n\t\t\tmessages:           make(chan *sarama.ConsumerMessage, c.config.ChannelBufferSize),\n\t\t\tsuppressedMessages: make(chan *sarama.ConsumerMessage, c.config.ChannelBufferSize),\n\t\t\terrors:             make(chan *sarama.ConsumerError, c.config.ChannelBufferSize),\n\t\t}\n\t\tconsumer.highWaterMarkOffset.Store(highWatermarkOffset)\n\t\tc.partitionConsumers[topic][partition] = consumer\n\t}\n\n\treturn c.partitionConsumers[topic][partition]\n}\n\n///////////////////////////////////////////////////\n// PartitionConsumer mock type\n///////////////////////////////////////////////////\n\n// PartitionConsumer implements sarama's PartitionConsumer interface for testing purposes.\n// It is returned by the mock Consumers ConsumePartitionMethod, but only if it is\n// registered first using the Consumer's ExpectConsumePartition method. Before consuming the\n// Errors and Messages channel, you should specify what values will be provided on these\n// channels using YieldMessage and YieldError.\ntype PartitionConsumer struct {\n\thighWaterMarkOffset           atomic.Int64 // must be at the top of the struct because https://golang.org/pkg/sync/atomic/#pkg-note-BUG\n\tsuppressedHighWaterMarkOffset int64\n\tl                             sync.Mutex\n\tt                             ErrorReporter\n\ttopic                         string\n\tpartition                     int32\n\toffset                        int64\n\tmessages                      chan *sarama.ConsumerMessage\n\tsuppressedMessages            chan *sarama.ConsumerMessage\n\terrors                        chan *sarama.ConsumerError\n\tsingleClose                   sync.Once\n\tconsumed                      bool\n\terrorsShouldBeDrained         bool\n\tmessagesShouldBeDrained       bool\n\tpaused                        bool\n}\n\n///////////////////////////////////////////////////\n// PartitionConsumer interface implementation\n///////////////////////////////////////////////////\n\n// AsyncClose implements the AsyncClose method from the sarama.PartitionConsumer interface.\nfunc (pc *PartitionConsumer) AsyncClose() {\n\tpc.singleClose.Do(func() {\n\t\tclose(pc.suppressedMessages)\n\t\tclose(pc.messages)\n\t\tclose(pc.errors)\n\t})\n}\n\n// Close implements the Close method from the sarama.PartitionConsumer interface. It will\n// verify whether the partition consumer was actually started.\nfunc (pc *PartitionConsumer) Close() error {\n\tif !pc.consumed {\n\t\tpc.t.Errorf(\"Expectations set on %s/%d, but no partition consumer was started.\", pc.topic, pc.partition)\n\t\treturn errPartitionConsumerNotStarted\n\t}\n\n\tif pc.errorsShouldBeDrained && len(pc.errors) > 0 {\n\t\tpc.t.Errorf(\"Expected the errors channel for %s/%d to be drained on close, but found %d errors.\", pc.topic, pc.partition, len(pc.errors))\n\t}\n\n\tif pc.messagesShouldBeDrained && len(pc.messages) > 0 {\n\t\tpc.t.Errorf(\"Expected the messages channel for %s/%d to be drained on close, but found %d messages.\", pc.topic, pc.partition, len(pc.messages))\n\t}\n\n\tpc.AsyncClose()\n\n\tvar (\n\t\tcloseErr error\n\t\twg       sync.WaitGroup\n\t)\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\terrs := make(sarama.ConsumerErrors, 0)\n\t\tfor err := range pc.errors {\n\t\t\terrs = append(errs, err)\n\t\t}\n\n\t\tif len(errs) > 0 {\n\t\t\tcloseErr = errs\n\t\t}\n\t}()\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tfor range pc.messages {\n\t\t\t// drain\n\t\t}\n\t}()\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tfor range pc.suppressedMessages {\n\t\t\t// drain\n\t\t}\n\t}()\n\n\twg.Wait()\n\treturn closeErr\n}\n\n// Errors implements the Errors method from the sarama.PartitionConsumer interface.\nfunc (pc *PartitionConsumer) Errors() <-chan *sarama.ConsumerError {\n\treturn pc.errors\n}\n\n// Messages implements the Messages method from the sarama.PartitionConsumer interface.\nfunc (pc *PartitionConsumer) Messages() <-chan *sarama.ConsumerMessage {\n\treturn pc.messages\n}\n\nfunc (pc *PartitionConsumer) HighWaterMarkOffset() int64 {\n\treturn pc.highWaterMarkOffset.Load()\n}\n\n// Pause implements the Pause method from the sarama.PartitionConsumer interface.\nfunc (pc *PartitionConsumer) Pause() {\n\tpc.l.Lock()\n\tdefer pc.l.Unlock()\n\n\tpc.suppressedHighWaterMarkOffset = pc.highWaterMarkOffset.Load()\n\n\tpc.paused = true\n}\n\n// Resume implements the Resume method from the sarama.PartitionConsumer interface.\nfunc (pc *PartitionConsumer) Resume() {\n\tpc.l.Lock()\n\tdefer pc.l.Unlock()\n\n\tpc.highWaterMarkOffset.Store(pc.suppressedHighWaterMarkOffset)\n\tfor len(pc.suppressedMessages) > 0 {\n\t\tmsg := <-pc.suppressedMessages\n\t\tpc.messages <- msg\n\t}\n\n\tpc.paused = false\n}\n\n// IsPaused implements the IsPaused method from the sarama.PartitionConsumer interface.\nfunc (pc *PartitionConsumer) IsPaused() bool {\n\tpc.l.Lock()\n\tdefer pc.l.Unlock()\n\n\treturn pc.paused\n}\n\n///////////////////////////////////////////////////\n// Expectation API\n///////////////////////////////////////////////////\n\n// YieldMessage will yield a messages Messages channel of this partition consumer\n// when it is consumed. By default, the mock consumer will not verify whether this\n// message was consumed from the Messages channel, because there are legitimate\n// reasons forthis not to happen. ou can call ExpectMessagesDrainedOnClose so it will\n// verify that the channel is empty on close.\nfunc (pc *PartitionConsumer) YieldMessage(msg *sarama.ConsumerMessage) *PartitionConsumer {\n\tpc.l.Lock()\n\tdefer pc.l.Unlock()\n\n\tmsg.Topic = pc.topic\n\tmsg.Partition = pc.partition\n\n\tif pc.paused {\n\t\tmsg.Offset = pc.suppressedHighWaterMarkOffset\n\t\tpc.suppressedHighWaterMarkOffset++\n\t\tpc.suppressedMessages <- msg\n\t} else {\n\t\tmsg.Offset = pc.highWaterMarkOffset.Add(1) - 1\n\t\tpc.messages <- msg\n\t}\n\n\treturn pc\n}\n\n// YieldError will yield an error on the Errors channel of this partition consumer\n// when it is consumed. By default, the mock consumer will not verify whether this error was\n// consumed from the Errors channel, because there are legitimate reasons for this\n// not to happen. You can call ExpectErrorsDrainedOnClose so it will verify that\n// the channel is empty on close.\nfunc (pc *PartitionConsumer) YieldError(err error) *PartitionConsumer {\n\tpc.errors <- &sarama.ConsumerError{\n\t\tTopic:     pc.topic,\n\t\tPartition: pc.partition,\n\t\tErr:       err,\n\t}\n\n\treturn pc\n}\n\n// ExpectMessagesDrainedOnClose sets an expectation on the partition consumer\n// that the messages channel will be fully drained when Close is called. If this\n// expectation is not met, an error is reported to the error reporter.\nfunc (pc *PartitionConsumer) ExpectMessagesDrainedOnClose() *PartitionConsumer {\n\tpc.messagesShouldBeDrained = true\n\n\treturn pc\n}\n\n// ExpectErrorsDrainedOnClose sets an expectation on the partition consumer\n// that the errors channel will be fully drained when Close is called. If this\n// expectation is not met, an error is reported to the error reporter.\nfunc (pc *PartitionConsumer) ExpectErrorsDrainedOnClose() *PartitionConsumer {\n\tpc.errorsShouldBeDrained = true\n\n\treturn pc\n}\n"
  },
  {
    "path": "mocks/consumer_test.go",
    "content": "//go:build !functional\n\npackage mocks\n\nimport (\n\t\"errors\"\n\t\"sort\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/IBM/sarama\"\n)\n\nfunc TestMockConsumerImplementsConsumerInterface(t *testing.T) {\n\tvar c interface{} = &Consumer{}\n\tif _, ok := c.(sarama.Consumer); !ok {\n\t\tt.Error(\"The mock consumer should implement the sarama.Consumer interface.\")\n\t}\n\n\tvar pc interface{} = &PartitionConsumer{}\n\tif _, ok := pc.(sarama.PartitionConsumer); !ok {\n\t\tt.Error(\"The mock partitionconsumer should implement the sarama.PartitionConsumer interface.\")\n\t}\n}\n\nfunc TestConsumerHandlesExpectations(t *testing.T) {\n\tconsumer := NewConsumer(t, NewTestConfig())\n\tdefer func() {\n\t\tif err := consumer.Close(); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}()\n\n\tconsumer.ExpectConsumePartition(\"test\", 0, sarama.OffsetOldest).YieldMessage(&sarama.ConsumerMessage{Value: []byte(\"hello world\")})\n\tconsumer.ExpectConsumePartition(\"test\", 0, sarama.OffsetOldest).YieldError(sarama.ErrOutOfBrokers)\n\tconsumer.ExpectConsumePartition(\"test\", 1, sarama.OffsetOldest).YieldMessage(&sarama.ConsumerMessage{Value: []byte(\"hello world again\")})\n\tconsumer.ExpectConsumePartition(\"other\", 0, AnyOffset).YieldMessage(&sarama.ConsumerMessage{Value: []byte(\"hello other\")})\n\n\tpc_test0, err := consumer.ConsumePartition(\"test\", 0, sarama.OffsetOldest)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\ttest0_msg := <-pc_test0.Messages()\n\tif test0_msg.Topic != \"test\" || test0_msg.Partition != 0 || string(test0_msg.Value) != \"hello world\" {\n\t\tt.Error(\"Message was not as expected:\", test0_msg)\n\t}\n\ttest0_err := <-pc_test0.Errors()\n\tif !errors.Is(test0_err, sarama.ErrOutOfBrokers) {\n\t\tt.Error(\"Expected sarama.ErrOutOfBrokers, found:\", test0_err.Err)\n\t}\n\n\tpc_test1, err := consumer.ConsumePartition(\"test\", 1, sarama.OffsetOldest)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\ttest1_msg := <-pc_test1.Messages()\n\tif test1_msg.Topic != \"test\" || test1_msg.Partition != 1 || string(test1_msg.Value) != \"hello world again\" {\n\t\tt.Error(\"Message was not as expected:\", test1_msg)\n\t}\n\n\tpc_other0, err := consumer.ConsumePartition(\"other\", 0, sarama.OffsetNewest)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tother0_msg := <-pc_other0.Messages()\n\tif other0_msg.Topic != \"other\" || other0_msg.Partition != 0 || string(other0_msg.Value) != \"hello other\" {\n\t\tt.Error(\"Message was not as expected:\", other0_msg)\n\t}\n}\n\nfunc TestConsumerHandlesExpectationsPausingResuming(t *testing.T) {\n\tconsumer := NewConsumer(t, NewTestConfig())\n\tdefer func() {\n\t\tif err := consumer.Close(); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}()\n\n\tconsumePartitionT0P0 := consumer.ExpectConsumePartition(\"test\", 0, sarama.OffsetOldest)\n\tconsumePartitionT0P1 := consumer.ExpectConsumePartition(\"test\", 1, sarama.OffsetOldest)\n\tconsumePartitionT1P0 := consumer.ExpectConsumePartition(\"other\", 0, AnyOffset)\n\n\tconsumePartitionT0P0.Pause()\n\tconsumePartitionT0P0.YieldMessage(&sarama.ConsumerMessage{Value: []byte(\"hello world\")})\n\tconsumePartitionT0P0.YieldMessage(&sarama.ConsumerMessage{Value: []byte(\"hello world x\")})\n\tconsumePartitionT0P0.YieldError(sarama.ErrOutOfBrokers)\n\n\tconsumePartitionT0P1.YieldMessage(&sarama.ConsumerMessage{Value: []byte(\"hello world again\")})\n\n\tconsumePartitionT1P0.YieldMessage(&sarama.ConsumerMessage{Value: []byte(\"hello other\")})\n\n\tpc_test0, err := consumer.ConsumePartition(\"test\", 0, sarama.OffsetOldest)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif len(pc_test0.Messages()) > 0 {\n\t\tt.Error(\"Problem to pause consumption\")\n\t}\n\ttest0_err := <-pc_test0.Errors()\n\tif !errors.Is(test0_err, sarama.ErrOutOfBrokers) {\n\t\tt.Error(\"Expected sarama.ErrOutOfBrokers, found:\", test0_err.Err)\n\t}\n\n\tif pc_test0.HighWaterMarkOffset() != 0 {\n\t\tt.Error(\"High water mark offset with value different from the expected: \", pc_test0.HighWaterMarkOffset())\n\t}\n\n\tpc_test1, err := consumer.ConsumePartition(\"test\", 1, sarama.OffsetOldest)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\ttest1_msg := <-pc_test1.Messages()\n\tif test1_msg.Topic != \"test\" || test1_msg.Partition != 1 || string(test1_msg.Value) != \"hello world again\" {\n\t\tt.Error(\"Message was not as expected:\", test1_msg)\n\t}\n\n\tif pc_test1.HighWaterMarkOffset() != 1 {\n\t\tt.Error(\"High water mark offset with value different from the expected: \", pc_test1.HighWaterMarkOffset())\n\t}\n\n\tpc_other0, err := consumer.ConsumePartition(\"other\", 0, sarama.OffsetNewest)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tother0_msg := <-pc_other0.Messages()\n\tif other0_msg.Topic != \"other\" || other0_msg.Partition != 0 || string(other0_msg.Value) != \"hello other\" {\n\t\tt.Error(\"Message was not as expected:\", other0_msg)\n\t}\n\n\tif pc_other0.HighWaterMarkOffset() != AnyOffset+1 {\n\t\tt.Error(\"High water mark offset with value different from the expected: \", pc_other0.HighWaterMarkOffset())\n\t}\n\n\tpc_test0.Resume()\n\ttest0_msg1 := <-pc_test0.Messages()\n\tif test0_msg1.Topic != \"test\" || test0_msg1.Partition != 0 || string(test0_msg1.Value) != \"hello world\" || test0_msg1.Offset != 0 {\n\t\tt.Error(\"Message was not as expected:\", test0_msg1)\n\t}\n\n\ttest0_msg2 := <-pc_test0.Messages()\n\tif test0_msg2.Topic != \"test\" || test0_msg2.Partition != 0 || string(test0_msg2.Value) != \"hello world x\" || test0_msg2.Offset != 1 {\n\t\tt.Error(\"Message was not as expected:\", test0_msg2)\n\t}\n\n\tif pc_test0.HighWaterMarkOffset() != 2 {\n\t\tt.Error(\"High water mark offset with value different from the expected: \", pc_test0.HighWaterMarkOffset())\n\t}\n}\n\nfunc TestConsumerReturnsNonconsumedErrorsOnClose(t *testing.T) {\n\tconsumer := NewConsumer(t, NewTestConfig())\n\tconsumer.ExpectConsumePartition(\"test\", 0, sarama.OffsetOldest).YieldError(sarama.ErrOutOfBrokers)\n\tconsumer.ExpectConsumePartition(\"test\", 0, sarama.OffsetOldest).YieldError(sarama.ErrOutOfBrokers)\n\n\tpc, err := consumer.ConsumePartition(\"test\", 0, sarama.OffsetOldest)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tselect {\n\tcase <-pc.Messages():\n\t\tt.Error(\"Did not expect a message on the messages channel.\")\n\tcase err := <-pc.Errors():\n\t\tif !errors.Is(err, sarama.ErrOutOfBrokers) {\n\t\t\tt.Error(\"Expected sarama.ErrOutOfBrokers, found\", err)\n\t\t}\n\t}\n\n\tvar errs sarama.ConsumerErrors\n\tif !errors.As(pc.Close(), &errs) {\n\t\tt.Error(\"Expected Close to return ConsumerErrors\")\n\t}\n\tif len(errs) != 1 && !errors.Is(errs[0], sarama.ErrOutOfBrokers) {\n\t\tt.Error(\"Expected Close to return the remaining sarama.ErrOutOfBrokers\")\n\t}\n}\n\nfunc TestConsumerWithoutExpectationsOnPartition(t *testing.T) {\n\ttrm := newTestReporterMock()\n\tconsumer := NewConsumer(trm, NewTestConfig())\n\n\t_, err := consumer.ConsumePartition(\"test\", 1, sarama.OffsetOldest)\n\tif !errors.Is(err, errOutOfExpectations) {\n\t\tt.Error(\"Expected ConsumePartition to return errOutOfExpectations\")\n\t}\n\n\tif err := consumer.Close(); err != nil {\n\t\tt.Error(\"No error expected on close, but found:\", err)\n\t}\n\n\tif len(trm.errors) != 1 {\n\t\tt.Errorf(\"Expected an expectation failure to be set on the error reporter.\")\n\t}\n}\n\nfunc TestConsumerWithExpectationsOnUnconsumedPartition(t *testing.T) {\n\ttrm := newTestReporterMock()\n\tconsumer := NewConsumer(trm, NewTestConfig())\n\tconsumer.ExpectConsumePartition(\"test\", 0, sarama.OffsetOldest).YieldMessage(&sarama.ConsumerMessage{Value: []byte(\"hello world\")})\n\n\tif err := consumer.Close(); err != nil {\n\t\tt.Error(\"No error expected on close, but found:\", err)\n\t}\n\n\tif len(trm.errors) != 1 {\n\t\tt.Errorf(\"Expected an expectation failure to be set on the error reporter.\")\n\t}\n}\n\nfunc TestConsumerWithWrongOffsetExpectation(t *testing.T) {\n\ttrm := newTestReporterMock()\n\tconsumer := NewConsumer(trm, NewTestConfig())\n\tconsumer.ExpectConsumePartition(\"test\", 0, sarama.OffsetOldest)\n\n\t_, err := consumer.ConsumePartition(\"test\", 0, sarama.OffsetNewest)\n\tif err != nil {\n\t\tt.Error(\"Did not expect error, found:\", err)\n\t}\n\n\tif len(trm.errors) != 1 {\n\t\tt.Errorf(\"Expected an expectation failure to be set on the error reporter.\")\n\t}\n\n\tif err := consumer.Close(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestConsumerViolatesMessagesDrainedExpectation(t *testing.T) {\n\ttrm := newTestReporterMock()\n\tconsumer := NewConsumer(trm, NewTestConfig())\n\tconsumer.ExpectConsumePartition(\"test\", 0, sarama.OffsetOldest).\n\t\tYieldMessage(&sarama.ConsumerMessage{Value: []byte(\"hello\")}).\n\t\tYieldMessage(&sarama.ConsumerMessage{Value: []byte(\"hello\")}).\n\t\tExpectMessagesDrainedOnClose()\n\n\tpc, err := consumer.ConsumePartition(\"test\", 0, sarama.OffsetOldest)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\t// consume first message, not second one\n\t<-pc.Messages()\n\n\tif err := consumer.Close(); err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif len(trm.errors) != 1 {\n\t\tt.Errorf(\"Expected an expectation failure to be set on the error reporter.\")\n\t}\n}\n\nfunc TestConsumerMeetsErrorsDrainedExpectation(t *testing.T) {\n\ttrm := newTestReporterMock()\n\tconsumer := NewConsumer(trm, NewTestConfig())\n\n\tconsumer.ExpectConsumePartition(\"test\", 0, sarama.OffsetOldest).\n\t\tYieldError(sarama.ErrInvalidMessage).\n\t\tYieldError(sarama.ErrInvalidMessage).\n\t\tExpectErrorsDrainedOnClose()\n\n\tpc, err := consumer.ConsumePartition(\"test\", 0, sarama.OffsetOldest)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\t// consume first and second error,\n\t<-pc.Errors()\n\t<-pc.Errors()\n\n\tif err := consumer.Close(); err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif len(trm.errors) != 0 {\n\t\tt.Errorf(\"Expected no expectation failures to be set on the error reporter.\")\n\t}\n}\n\nfunc TestConsumerTopicMetadata(t *testing.T) {\n\ttrm := newTestReporterMock()\n\tconsumer := NewConsumer(trm, NewTestConfig())\n\n\tconsumer.SetTopicMetadata(map[string][]int32{\n\t\t\"test1\": {0, 1, 2, 3},\n\t\t\"test2\": {0, 1, 2, 3, 4, 5, 6, 7},\n\t})\n\n\ttopics, err := consumer.Topics()\n\tif err != nil {\n\t\tt.Error(t)\n\t}\n\n\tsortedTopics := sort.StringSlice(topics)\n\tsortedTopics.Sort()\n\tif len(sortedTopics) != 2 || sortedTopics[0] != \"test1\" || sortedTopics[1] != \"test2\" {\n\t\tt.Error(\"Unexpected topics returned:\", sortedTopics)\n\t}\n\n\tpartitions1, err := consumer.Partitions(\"test1\")\n\tif err != nil {\n\t\tt.Error(t)\n\t}\n\n\tif len(partitions1) != 4 {\n\t\tt.Error(\"Unexpected partitions returned:\", len(partitions1))\n\t}\n\n\tpartitions2, err := consumer.Partitions(\"test2\")\n\tif err != nil {\n\t\tt.Error(t)\n\t}\n\n\tif len(partitions2) != 8 {\n\t\tt.Error(\"Unexpected partitions returned:\", len(partitions2))\n\t}\n\n\tif len(trm.errors) != 0 {\n\t\tt.Errorf(\"Expected no expectation failures to be set on the error reporter.\")\n\t}\n}\n\nfunc TestConsumerUnexpectedTopicMetadata(t *testing.T) {\n\ttrm := newTestReporterMock()\n\tconsumer := NewConsumer(trm, NewTestConfig())\n\n\tif _, err := consumer.Topics(); !errors.Is(err, sarama.ErrOutOfBrokers) {\n\t\tt.Error(\"Expected sarama.ErrOutOfBrokers, found\", err)\n\t}\n\n\tif len(trm.errors) != 1 {\n\t\tt.Errorf(\"Expected an expectation failure to be set on the error reporter.\")\n\t}\n}\n\nfunc TestConsumerOffsetsAreManagedCorrectlyWithOffsetOldest(t *testing.T) {\n\ttrm := newTestReporterMock()\n\tconsumer := NewConsumer(trm, NewTestConfig())\n\tpcmock := consumer.ExpectConsumePartition(\"test\", 0, sarama.OffsetOldest)\n\tpcmock.YieldMessage(&sarama.ConsumerMessage{Value: []byte(\"hello\")})\n\tpcmock.YieldMessage(&sarama.ConsumerMessage{Value: []byte(\"hello\")})\n\tpcmock.ExpectMessagesDrainedOnClose()\n\n\tpc, err := consumer.ConsumePartition(\"test\", 0, sarama.OffsetOldest)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tmessage1 := <-pc.Messages()\n\tif message1.Offset != 0 {\n\t\tt.Errorf(\"Expected offset of first message in the partition to be 0, got %d\", message1.Offset)\n\t}\n\n\tmessage2 := <-pc.Messages()\n\tif message2.Offset != 1 {\n\t\tt.Errorf(\"Expected offset of second message in the partition to be 1, got %d\", message2.Offset)\n\t}\n\n\tif err := consumer.Close(); err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif len(trm.errors) != 0 {\n\t\tt.Errorf(\"Expected to not report any errors, found: %v\", trm.errors)\n\t}\n}\n\nfunc TestConsumerOffsetsAreManagedCorrectlyWithSpecifiedOffset(t *testing.T) {\n\tstartingOffset := int64(123)\n\ttrm := newTestReporterMock()\n\tconsumer := NewConsumer(trm, NewTestConfig())\n\tpcmock := consumer.ExpectConsumePartition(\"test\", 0, startingOffset)\n\tpcmock.YieldMessage(&sarama.ConsumerMessage{Value: []byte(\"hello\")})\n\tpcmock.YieldMessage(&sarama.ConsumerMessage{Value: []byte(\"hello\")})\n\tpcmock.ExpectMessagesDrainedOnClose()\n\n\tpc, err := consumer.ConsumePartition(\"test\", 0, startingOffset)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tmessage1 := <-pc.Messages()\n\tif message1.Offset != startingOffset {\n\t\tt.Errorf(\"Expected offset of first message to be %d, got %d\", startingOffset, message1.Offset)\n\t}\n\n\tmessage2 := <-pc.Messages()\n\tif message2.Offset != startingOffset+1 {\n\t\tt.Errorf(\"Expected offset of second message to be %d, got %d\", startingOffset+1, message2.Offset)\n\t}\n\n\tif err := consumer.Close(); err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif len(trm.errors) != 0 {\n\t\tt.Errorf(\"Expected to not report any errors, found: %v\", trm.errors)\n\t}\n\n\tif pc.HighWaterMarkOffset() != message2.Offset+1 {\n\t\tdiff := pc.HighWaterMarkOffset() - message2.Offset\n\t\tt.Errorf(\"Difference between highwatermarkoffset and last message offset greater than 1, got: %v\", diff)\n\t}\n}\n\nfunc TestConsumerInvalidConfiguration(t *testing.T) {\n\ttrm := newTestReporterMock()\n\tconfig := NewTestConfig()\n\tconfig.Version = sarama.V0_11_0_2\n\tconfig.ClientID = \"not a valid consumer ID\"\n\tconsumer := NewConsumer(trm, config)\n\tif err := consumer.Close(); err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif len(trm.errors) != 1 {\n\t\tt.Error(\"Expected to report a single error\")\n\t} else if !strings.Contains(trm.errors[0], `ClientID value \"not a valid consumer ID\" is not valid for Kafka versions before 1.0.0`) {\n\t\tt.Errorf(\"Unexpected error: %s\", trm.errors[0])\n\t}\n}\n"
  },
  {
    "path": "mocks/mocks.go",
    "content": "/*\nPackage mocks provides mocks that can be used for testing applications\nthat use Sarama. The mock types provided by this package implement the\ninterfaces Sarama exports, so you can use them for dependency injection\nin your tests.\n\nAll mock instances require you to set expectations on them before you\ncan use them. It will determine how the mock will behave. If an\nexpectation is not met, it will make your test fail.\n\nNOTE: this package currently does not fall under the API stability\nguarantee of Sarama as it is still considered experimental.\n*/\npackage mocks\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"maps\"\n\n\t\"github.com/IBM/sarama\"\n)\n\n// ErrorReporter is a simple interface that includes the testing.T methods we use to report\n// expectation violations when using the mock objects.\ntype ErrorReporter interface {\n\tErrorf(string, ...interface{})\n}\n\n// ValueChecker is a function type to be set in each expectation of the producer mocks\n// to check the value passed.\ntype ValueChecker func(val []byte) error\n\n// MessageChecker is a function type to be set in each expectation of the producer mocks\n// to check the message passed.\ntype MessageChecker func(*sarama.ProducerMessage) error\n\n// messageValueChecker wraps a ValueChecker into a MessageChecker.\n// Failure to encode the message value will return an error and not call\n// the wrapped ValueChecker.\nfunc messageValueChecker(f ValueChecker) MessageChecker {\n\tif f == nil {\n\t\treturn nil\n\t}\n\treturn func(msg *sarama.ProducerMessage) error {\n\t\tval, err := msg.Value.Encode()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"Input message encoding failed: %w\", err)\n\t\t}\n\t\treturn f(val)\n\t}\n}\n\nvar (\n\terrProduceSuccess              error = nil\n\terrOutOfExpectations                 = errors.New(\"no more expectations set on mock\")\n\terrPartitionConsumerNotStarted       = errors.New(\"the partition consumer was never started\")\n)\n\nconst AnyOffset int64 = -1000\n\ntype producerExpectation struct {\n\tResult        error\n\tCheckFunction MessageChecker\n}\n\n// TopicConfig describes a mock topic structure for the mock producers’ partitioning needs.\ntype TopicConfig struct {\n\toverridePartitions map[string]int32\n\tdefaultPartitions  int32\n}\n\n// NewTopicConfig makes a configuration which defaults to 32 partitions for every topic.\nfunc NewTopicConfig() *TopicConfig {\n\treturn &TopicConfig{\n\t\toverridePartitions: make(map[string]int32, 0),\n\t\tdefaultPartitions:  32,\n\t}\n}\n\n// SetDefaultPartitions sets the number of partitions any topic not explicitly configured otherwise\n// (by SetPartitions) will have from the perspective of created partitioners.\nfunc (pc *TopicConfig) SetDefaultPartitions(n int32) {\n\tpc.defaultPartitions = n\n}\n\n// SetPartitions sets the number of partitions the partitioners will see for specific topics. This\n// only applies to messages produced after setting them.\nfunc (pc *TopicConfig) SetPartitions(partitions map[string]int32) {\n\tmaps.Copy(pc.overridePartitions, partitions)\n}\n\nfunc (pc *TopicConfig) partitions(topic string) int32 {\n\tif n, found := pc.overridePartitions[topic]; found {\n\t\treturn n\n\t}\n\treturn pc.defaultPartitions\n}\n\n// NewTestConfig returns a config meant to be used by tests.\n// Due to inconsistencies with the request versions the clients send using the default Kafka version\n// and the response versions our mocks use, we default to the minimum Kafka version in most tests\nfunc NewTestConfig() *sarama.Config {\n\tconfig := sarama.NewConfig()\n\tconfig.Consumer.Retry.Backoff = 0\n\tconfig.Producer.Retry.Backoff = 0\n\tconfig.Version = sarama.MinVersion\n\treturn config\n}\n"
  },
  {
    "path": "mocks/sync_producer.go",
    "content": "package mocks\n\nimport (\n\t\"errors\"\n\t\"sync\"\n\n\t\"github.com/IBM/sarama\"\n)\n\n// SyncProducer implements sarama's SyncProducer interface for testing purposes.\n// Before you can use it, you have to set expectations on the mock SyncProducer\n// to tell it how to handle calls to SendMessage, so you can easily test success\n// and failure scenarios.\ntype SyncProducer struct {\n\tl            sync.Mutex\n\tt            ErrorReporter\n\texpectations []*producerExpectation\n\tlastOffset   int64\n\n\t*TopicConfig\n\tnewPartitioner sarama.PartitionerConstructor\n\tpartitioners   map[string]sarama.Partitioner\n\n\tisTransactional bool\n\ttxnLock         sync.Mutex\n\ttxnStatus       sarama.ProducerTxnStatusFlag\n}\n\n// NewSyncProducer instantiates a new SyncProducer mock. The t argument should\n// be the *testing.T instance of your test method. An error will be written to it if\n// an expectation is violated. The config argument is validated and used to handle\n// partitioning.\nfunc NewSyncProducer(t ErrorReporter, config *sarama.Config) *SyncProducer {\n\tif config == nil {\n\t\tconfig = sarama.NewConfig()\n\t}\n\tif err := config.Validate(); err != nil {\n\t\tt.Errorf(\"Invalid mock configuration provided: %s\", err.Error())\n\t}\n\treturn &SyncProducer{\n\t\tt:               t,\n\t\texpectations:    make([]*producerExpectation, 0),\n\t\tTopicConfig:     NewTopicConfig(),\n\t\tnewPartitioner:  config.Producer.Partitioner,\n\t\tpartitioners:    make(map[string]sarama.Partitioner, 1),\n\t\tisTransactional: config.Producer.Transaction.ID != \"\",\n\t\ttxnStatus:       sarama.ProducerTxnFlagReady,\n\t}\n}\n\n////////////////////////////////////////////////\n// Implement SyncProducer interface\n////////////////////////////////////////////////\n\n// SendMessage corresponds with the SendMessage method of sarama's SyncProducer implementation.\n// You have to set expectations on the mock producer before calling SendMessage, so it knows\n// how to handle them. You can set a function in each expectation so that the message value\n// checked by this function and an error is returned if the match fails.\n// If there is no more remaining expectation when SendMessage is called,\n// the mock producer will write an error to the test state object.\nfunc (sp *SyncProducer) SendMessage(msg *sarama.ProducerMessage) (partition int32, offset int64, err error) {\n\tsp.l.Lock()\n\tdefer sp.l.Unlock()\n\n\tif sp.IsTransactional() && sp.txnStatus&sarama.ProducerTxnFlagInTransaction == 0 {\n\t\tsp.t.Errorf(\"attempt to send message when transaction is not started or is in ending state.\")\n\t\treturn -1, -1, errors.New(\"attempt to send message when transaction is not started or is in ending state\")\n\t}\n\n\tif len(sp.expectations) > 0 {\n\t\texpectation := sp.expectations[0]\n\t\tsp.expectations = sp.expectations[1:]\n\t\ttopic := msg.Topic\n\t\tpartition, err := sp.partitioner(topic).Partition(msg, sp.partitions(topic))\n\t\tif err != nil {\n\t\t\tsp.t.Errorf(\"Partitioner returned an error: %s\", err.Error())\n\t\t\treturn -1, -1, err\n\t\t}\n\t\tmsg.Partition = partition\n\t\tif expectation.CheckFunction != nil {\n\t\t\terrCheck := expectation.CheckFunction(msg)\n\t\t\tif errCheck != nil {\n\t\t\t\tsp.t.Errorf(\"Check function returned an error: %s\", errCheck.Error())\n\t\t\t\treturn -1, -1, errCheck\n\t\t\t}\n\t\t}\n\t\tif errors.Is(expectation.Result, errProduceSuccess) {\n\t\t\tsp.lastOffset++\n\t\t\tmsg.Offset = sp.lastOffset\n\t\t\treturn msg.Partition, msg.Offset, nil\n\t\t}\n\t\treturn -1, -1, expectation.Result\n\t}\n\tsp.t.Errorf(\"No more expectation set on this mock producer to handle the input message.\")\n\treturn -1, -1, errOutOfExpectations\n}\n\n// SendMessages corresponds with the SendMessages method of sarama's SyncProducer implementation.\n// You have to set expectations on the mock producer before calling SendMessages, so it knows\n// how to handle them. If there is no more remaining expectations when SendMessages is called,\n// the mock producer will write an error to the test state object.\nfunc (sp *SyncProducer) SendMessages(msgs []*sarama.ProducerMessage) error {\n\tsp.l.Lock()\n\tdefer sp.l.Unlock()\n\n\tif len(sp.expectations) >= len(msgs) {\n\t\texpectations := sp.expectations[0:len(msgs)]\n\t\tsp.expectations = sp.expectations[len(msgs):]\n\n\t\tfor i, expectation := range expectations {\n\t\t\ttopic := msgs[i].Topic\n\t\t\tpartition, err := sp.partitioner(topic).Partition(msgs[i], sp.partitions(topic))\n\t\t\tif err != nil {\n\t\t\t\tsp.t.Errorf(\"Partitioner returned an error: %s\", err.Error())\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tmsgs[i].Partition = partition\n\t\t\tif expectation.CheckFunction != nil {\n\t\t\t\terrCheck := expectation.CheckFunction(msgs[i])\n\t\t\t\tif errCheck != nil {\n\t\t\t\t\tsp.t.Errorf(\"Check function returned an error: %s\", errCheck.Error())\n\t\t\t\t\treturn errCheck\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !errors.Is(expectation.Result, errProduceSuccess) {\n\t\t\t\treturn expectation.Result\n\t\t\t}\n\t\t\tsp.lastOffset++\n\t\t\tmsgs[i].Offset = sp.lastOffset\n\t\t}\n\t\treturn nil\n\t}\n\tsp.t.Errorf(\"Insufficient expectations set on this mock producer to handle the input messages.\")\n\treturn errOutOfExpectations\n}\n\nfunc (sp *SyncProducer) partitioner(topic string) sarama.Partitioner {\n\tpartitioner := sp.partitioners[topic]\n\tif partitioner == nil {\n\t\tpartitioner = sp.newPartitioner(topic)\n\t\tsp.partitioners[topic] = partitioner\n\t}\n\treturn partitioner\n}\n\n// Close corresponds with the Close method of sarama's SyncProducer implementation.\n// By closing a mock syncproducer, you also tell it that no more SendMessage calls will follow,\n// so it will write an error to the test state if there's any remaining expectations.\nfunc (sp *SyncProducer) Close() error {\n\tsp.l.Lock()\n\tdefer sp.l.Unlock()\n\n\tif len(sp.expectations) > 0 {\n\t\tsp.t.Errorf(\"Expected to exhaust all expectations, but %d are left.\", len(sp.expectations))\n\t}\n\n\treturn nil\n}\n\n////////////////////////////////////////////////\n// Setting expectations\n////////////////////////////////////////////////\n\n// ExpectSendMessageWithMessageCheckerFunctionAndSucceed sets an expectation on the mock producer\n// that SendMessage will be called. The mock producer will first call the given function to check\n// the message. It will cascade the error of the function, if any, or handle the message as if it\n// produced successfully, i.e. by returning a valid partition, and offset, and a nil error.\nfunc (sp *SyncProducer) ExpectSendMessageWithMessageCheckerFunctionAndSucceed(cf MessageChecker) *SyncProducer {\n\tsp.l.Lock()\n\tdefer sp.l.Unlock()\n\tsp.expectations = append(sp.expectations, &producerExpectation{Result: errProduceSuccess, CheckFunction: cf})\n\n\treturn sp\n}\n\n// ExpectSendMessageWithMessageCheckerFunctionAndFail sets an expectation on the mock producer that\n// SendMessage will be called. The mock producer will first call the given function to check the\n// message. It will cascade the error of the function, if any, or handle the message as if it\n// failed to produce successfully, i.e. by returning the provided error.\nfunc (sp *SyncProducer) ExpectSendMessageWithMessageCheckerFunctionAndFail(cf MessageChecker, err error) *SyncProducer {\n\tsp.l.Lock()\n\tdefer sp.l.Unlock()\n\tsp.expectations = append(sp.expectations, &producerExpectation{Result: err, CheckFunction: cf})\n\n\treturn sp\n}\n\n// ExpectSendMessageWithCheckerFunctionAndSucceed sets an expectation on the mock producer that SendMessage\n// will be called. The mock producer will first call the given function to check the message value.\n// It will cascade the error of the function, if any, or handle the message as if it produced\n// successfully, i.e. by returning a valid partition, and offset, and a nil error.\nfunc (sp *SyncProducer) ExpectSendMessageWithCheckerFunctionAndSucceed(cf ValueChecker) *SyncProducer {\n\tsp.ExpectSendMessageWithMessageCheckerFunctionAndSucceed(messageValueChecker(cf))\n\n\treturn sp\n}\n\n// ExpectSendMessageWithCheckerFunctionAndFail sets an expectation on the mock producer that SendMessage will be\n// called. The mock producer will first call the given function to check the message value.\n// It will cascade the error of the function, if any, or handle the message as if it failed\n// to produce successfully, i.e. by returning the provided error.\nfunc (sp *SyncProducer) ExpectSendMessageWithCheckerFunctionAndFail(cf ValueChecker, err error) *SyncProducer {\n\tsp.ExpectSendMessageWithMessageCheckerFunctionAndFail(messageValueChecker(cf), err)\n\n\treturn sp\n}\n\n// ExpectSendMessageAndSucceed sets an expectation on the mock producer that SendMessage will be\n// called. The mock producer will handle the message as if it produced successfully, i.e. by\n// returning a valid partition, and offset, and a nil error.\nfunc (sp *SyncProducer) ExpectSendMessageAndSucceed() *SyncProducer {\n\tsp.ExpectSendMessageWithMessageCheckerFunctionAndSucceed(nil)\n\n\treturn sp\n}\n\n// ExpectSendMessageAndFail sets an expectation on the mock producer that SendMessage will be\n// called. The mock producer will handle the message as if it failed to produce\n// successfully, i.e. by returning the provided error.\nfunc (sp *SyncProducer) ExpectSendMessageAndFail(err error) *SyncProducer {\n\tsp.ExpectSendMessageWithMessageCheckerFunctionAndFail(nil, err)\n\n\treturn sp\n}\n\nfunc (sp *SyncProducer) IsTransactional() bool {\n\treturn sp.isTransactional\n}\n\nfunc (sp *SyncProducer) BeginTxn() error {\n\tsp.txnLock.Lock()\n\tdefer sp.txnLock.Unlock()\n\n\tsp.txnStatus = sarama.ProducerTxnFlagInTransaction\n\treturn nil\n}\n\nfunc (sp *SyncProducer) CommitTxn() error {\n\tsp.txnLock.Lock()\n\tdefer sp.txnLock.Unlock()\n\n\tsp.txnStatus = sarama.ProducerTxnFlagReady\n\treturn nil\n}\n\nfunc (sp *SyncProducer) AbortTxn() error {\n\tsp.txnLock.Lock()\n\tdefer sp.txnLock.Unlock()\n\n\tsp.txnStatus = sarama.ProducerTxnFlagReady\n\treturn nil\n}\n\nfunc (sp *SyncProducer) TxnStatus() sarama.ProducerTxnStatusFlag {\n\treturn sp.txnStatus\n}\n\nfunc (sp *SyncProducer) AddOffsetsToTxn(offsets map[string][]*sarama.PartitionOffsetMetadata, groupId string) error {\n\treturn nil\n}\n\nfunc (sp *SyncProducer) AddMessageToTxn(msg *sarama.ConsumerMessage, groupId string, metadata *string) error {\n\treturn nil\n}\n"
  },
  {
    "path": "mocks/sync_producer_test.go",
    "content": "//go:build !functional\n\npackage mocks\n\nimport (\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/IBM/sarama\"\n)\n\nfunc TestMockSyncProducerImplementsSyncProducerInterface(t *testing.T) {\n\tvar mp interface{} = &SyncProducer{}\n\tif _, ok := mp.(sarama.SyncProducer); !ok {\n\t\tt.Error(\"The mock async producer should implement the sarama.SyncProducer interface.\")\n\t}\n}\n\nfunc TestSyncProducerReturnsExpectationsToSendMessage(t *testing.T) {\n\tsp := NewSyncProducer(t, nil)\n\tdefer func() {\n\t\tif err := sp.Close(); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}()\n\n\tsp.ExpectSendMessageAndSucceed()\n\tsp.ExpectSendMessageAndSucceed()\n\tsp.ExpectSendMessageAndFail(sarama.ErrOutOfBrokers)\n\n\tmsg := &sarama.ProducerMessage{Topic: \"test\", Value: sarama.StringEncoder(\"test\")}\n\n\t_, offset, err := sp.SendMessage(msg)\n\tif err != nil {\n\t\tt.Errorf(\"The first message should have been produced successfully, but got %s\", err)\n\t}\n\tif offset != 1 || offset != msg.Offset {\n\t\tt.Errorf(\"The first message should have been assigned offset 1, but got %d\", msg.Offset)\n\t}\n\n\t_, offset, err = sp.SendMessage(msg)\n\tif err != nil {\n\t\tt.Errorf(\"The second message should have been produced successfully, but got %s\", err)\n\t}\n\tif offset != 2 || offset != msg.Offset {\n\t\tt.Errorf(\"The second message should have been assigned offset 2, but got %d\", offset)\n\t}\n\n\t_, _, err = sp.SendMessage(msg)\n\tif !errors.Is(err, sarama.ErrOutOfBrokers) {\n\t\tt.Errorf(\"The third message should not have been produced successfully\")\n\t}\n\n\tif err := sp.Close(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestSyncProducerFailTxn(t *testing.T) {\n\tconfig := NewTestConfig()\n\tconfig.Producer.Transaction.ID = \"test\"\n\tconfig.Producer.RequiredAcks = sarama.WaitForAll\n\tconfig.Producer.Retry.Backoff = 0\n\tconfig.Producer.Idempotent = true\n\tconfig.Net.MaxOpenRequests = 1\n\tconfig.Version = sarama.V0_11_0_0\n\n\ttfm := newTestReporterMock()\n\n\tsp := NewSyncProducer(tfm, config)\n\tdefer func() {\n\t\tif err := sp.Close(); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}()\n\n\tmsg := &sarama.ProducerMessage{Topic: \"test\", Value: sarama.StringEncoder(\"test\")}\n\n\t_, _, err := sp.SendMessage(msg)\n\tif err == nil {\n\t\tt.Errorf(\"must have failed with txn begin error\")\n\t}\n\n\tif len(tfm.errors) != 1 {\n\t\tt.Errorf(\"must have failed with txn begin error\")\n\t}\n}\n\nfunc TestSyncProducerUseTxn(t *testing.T) {\n\tconfig := NewTestConfig()\n\tconfig.Producer.Transaction.ID = \"test\"\n\tconfig.Producer.RequiredAcks = sarama.WaitForAll\n\tconfig.Producer.Retry.Backoff = 0\n\tconfig.Producer.Idempotent = true\n\tconfig.Net.MaxOpenRequests = 1\n\tconfig.Version = sarama.V0_11_0_0\n\n\tsp := NewSyncProducer(t, config)\n\tdefer func() {\n\t\tif err := sp.Close(); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}()\n\n\tif !sp.IsTransactional() {\n\t\tt.Error(\"producer must be transactional\")\n\t}\n\n\tsp.ExpectSendMessageAndSucceed()\n\n\tmsg := &sarama.ProducerMessage{Topic: \"test\", Value: sarama.StringEncoder(\"test\")}\n\n\terr := sp.BeginTxn()\n\tif err != nil {\n\t\tt.Errorf(\"txn can't be started, got %s\", err)\n\t}\n\tif sp.TxnStatus()&sarama.ProducerTxnFlagInTransaction == 0 {\n\t\tt.Error(\"transaction must be started\")\n\t}\n\t_, offset, err := sp.SendMessage(msg)\n\tif err != nil {\n\t\tt.Errorf(\"The first message should have been produced successfully, but got %s\", err)\n\t}\n\tif offset != 1 || offset != msg.Offset {\n\t\tt.Errorf(\"The first message should have been assigned offset 1, but got %d\", msg.Offset)\n\t}\n\n\tif err := sp.AddMessageToTxn(&sarama.ConsumerMessage{\n\t\tTopic:     \"original-topic\",\n\t\tPartition: 0,\n\t\tOffset:    123,\n\t}, \"test-group\", nil); err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif err := sp.AddOffsetsToTxn(map[string][]*sarama.PartitionOffsetMetadata{\n\t\t\"original-topic\": {\n\t\t\t{\n\t\t\t\tPartition: 1,\n\t\t\t\tOffset:    321,\n\t\t\t},\n\t\t},\n\t}, \"test-group\"); err != nil {\n\t\tt.Error(err)\n\t}\n\n\terr = sp.CommitTxn()\n\tif err != nil {\n\t\tt.Errorf(\"txn can't be committed, got %s\", err)\n\t}\n\n\tif err := sp.Close(); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestSyncProducerWithTooManyExpectations(t *testing.T) {\n\ttrm := newTestReporterMock()\n\n\tsp := NewSyncProducer(trm, nil).\n\t\tExpectSendMessageAndSucceed().\n\t\tExpectSendMessageAndFail(sarama.ErrOutOfBrokers)\n\n\tmsg := &sarama.ProducerMessage{Topic: \"test\", Value: sarama.StringEncoder(\"test\")}\n\tif _, _, err := sp.SendMessage(msg); err != nil {\n\t\tt.Error(\"No error expected on first SendMessage call\", err)\n\t}\n\n\tif err := sp.Close(); err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif len(trm.errors) != 1 {\n\t\tt.Error(\"Expected to report an error\")\n\t}\n}\n\nfunc TestSyncProducerWithTooFewExpectations(t *testing.T) {\n\ttrm := newTestReporterMock()\n\n\tsp := NewSyncProducer(trm, nil).ExpectSendMessageAndSucceed()\n\n\tmsg := &sarama.ProducerMessage{Topic: \"test\", Value: sarama.StringEncoder(\"test\")}\n\tif _, _, err := sp.SendMessage(msg); err != nil {\n\t\tt.Error(\"No error expected on first SendMessage call\", err)\n\t}\n\tif _, _, err := sp.SendMessage(msg); !errors.Is(err, errOutOfExpectations) {\n\t\tt.Error(\"errOutOfExpectations expected on second SendMessage call, found:\", err)\n\t}\n\n\tif err := sp.Close(); err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif len(trm.errors) != 1 {\n\t\tt.Error(\"Expected to report an error\")\n\t}\n}\n\nfunc TestSyncProducerWithCheckerFunction(t *testing.T) {\n\ttrm := newTestReporterMock()\n\n\tsp := NewSyncProducer(trm, nil).\n\t\tExpectSendMessageWithCheckerFunctionAndSucceed(generateRegexpChecker(\"^tes\")).\n\t\tExpectSendMessageWithCheckerFunctionAndSucceed(generateRegexpChecker(\"^tes$\"))\n\n\tmsg := &sarama.ProducerMessage{Topic: \"test\", Value: sarama.StringEncoder(\"test\")}\n\tif _, _, err := sp.SendMessage(msg); err != nil {\n\t\tt.Error(\"No error expected on first SendMessage call, found: \", err)\n\t}\n\tmsg = &sarama.ProducerMessage{Topic: \"test\", Value: sarama.StringEncoder(\"test\")}\n\tif _, _, err := sp.SendMessage(msg); err == nil || !strings.HasPrefix(err.Error(), \"No match\") {\n\t\tt.Error(\"Error during value check expected on second SendMessage call, found:\", err)\n\t}\n\n\tif err := sp.Close(); err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif len(trm.errors) != 1 {\n\t\tt.Error(\"Expected to report an error\")\n\t}\n}\n\nfunc TestSyncProducerWithCheckerFunctionForSendMessagesWithError(t *testing.T) {\n\ttrm := newTestReporterMock()\n\n\tsp := NewSyncProducer(trm, nil).\n\t\tExpectSendMessageWithCheckerFunctionAndSucceed(generateRegexpChecker(\"^tes\")).\n\t\tExpectSendMessageWithCheckerFunctionAndSucceed(generateRegexpChecker(\"^tes$\"))\n\n\tmsg1 := &sarama.ProducerMessage{Topic: \"test\", Value: sarama.StringEncoder(\"test\")}\n\tmsg2 := &sarama.ProducerMessage{Topic: \"test\", Value: sarama.StringEncoder(\"test\")}\n\tmsgs := []*sarama.ProducerMessage{msg1, msg2}\n\n\tif err := sp.SendMessages(msgs); err == nil || !strings.HasPrefix(err.Error(), \"No match\") {\n\t\tt.Error(\"Error during value check expected on second message, found: \", err)\n\t}\n\n\tif err := sp.Close(); err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif len(trm.errors) != 1 {\n\t\tt.Error(\"Expected to report an error\")\n\t}\n}\n\nfunc TestSyncProducerWithCheckerFunctionForSendMessagesWithoutError(t *testing.T) {\n\ttrm := newTestReporterMock()\n\n\tsp := NewSyncProducer(trm, nil).\n\t\tExpectSendMessageWithCheckerFunctionAndSucceed(generateRegexpChecker(\"^tes\"))\n\n\tmsg1 := &sarama.ProducerMessage{Topic: \"test\", Value: sarama.StringEncoder(\"test\")}\n\tmsgs := []*sarama.ProducerMessage{msg1}\n\n\tif err := sp.SendMessages(msgs); err != nil {\n\t\tt.Error(\"No error expected on SendMessages call, found: \", err)\n\t}\n\n\tfor i, msg := range msgs {\n\t\toffset := int64(i + 1)\n\t\tif offset != msg.Offset {\n\t\t\tt.Errorf(\"The message should have been assigned offset %d, but got %d\", offset, msg.Offset)\n\t\t}\n\t}\n\n\tif err := sp.Close(); err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif len(trm.errors) != 0 {\n\t\tt.Errorf(\"Expected to not report any errors, found: %v\", trm.errors)\n\t}\n}\n\nfunc TestSyncProducerSendMessagesExpectationsMismatchTooFew(t *testing.T) {\n\ttrm := newTestReporterMock()\n\n\tsp := NewSyncProducer(trm, nil).\n\t\tExpectSendMessageWithCheckerFunctionAndSucceed(generateRegexpChecker(\"^tes\"))\n\n\tmsg1 := &sarama.ProducerMessage{Topic: \"test\", Value: sarama.StringEncoder(\"test\")}\n\tmsg2 := &sarama.ProducerMessage{Topic: \"test\", Value: sarama.StringEncoder(\"test\")}\n\n\tmsgs := []*sarama.ProducerMessage{msg1, msg2}\n\n\tif err := sp.SendMessages(msgs); err == nil {\n\t\tt.Error(\"Error during value check expected on second message, found: \", err)\n\t}\n\n\tif err := sp.Close(); err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif len(trm.errors) != 2 {\n\t\tt.Error(\"Expected to report 2 errors\")\n\t}\n}\n\nfunc TestSyncProducerSendMessagesExpectationsMismatchTooMany(t *testing.T) {\n\ttrm := newTestReporterMock()\n\n\tsp := NewSyncProducer(trm, nil).\n\t\tExpectSendMessageWithCheckerFunctionAndSucceed(generateRegexpChecker(\"^tes\")).\n\t\tExpectSendMessageWithCheckerFunctionAndSucceed(generateRegexpChecker(\"^tes\"))\n\n\tmsg1 := &sarama.ProducerMessage{Topic: \"test\", Value: sarama.StringEncoder(\"test\")}\n\tmsgs := []*sarama.ProducerMessage{msg1}\n\n\tif err := sp.SendMessages(msgs); err != nil {\n\t\tt.Error(\"No error expected on SendMessages call, found: \", err)\n\t}\n\n\tif err := sp.Close(); err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif len(trm.errors) != 1 {\n\t\tt.Error(\"Expected to report 1 errors\")\n\t}\n}\n\nfunc TestSyncProducerSendMessagesFaultyEncoder(t *testing.T) {\n\ttrm := newTestReporterMock()\n\n\tsp := NewSyncProducer(trm, nil).\n\t\tExpectSendMessageWithCheckerFunctionAndSucceed(generateRegexpChecker(\"^tes\"))\n\n\tmsg1 := &sarama.ProducerMessage{Topic: \"test\", Value: faultyEncoder(\"123\")}\n\tmsgs := []*sarama.ProducerMessage{msg1}\n\n\tif err := sp.SendMessages(msgs); err == nil || !strings.Contains(err.Error(), \"encode error\") {\n\t\tt.Error(\"Encoding error expected, found: \", err)\n\t}\n\n\tif err := sp.Close(); err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif len(trm.errors) != 1 {\n\t\tt.Error(\"Expected to report 1 errors\")\n\t}\n}\n\ntype faultyEncoder []byte\n\nfunc (f faultyEncoder) Encode() ([]byte, error) {\n\treturn nil, errors.New(\"encode error\")\n}\n\nfunc (f faultyEncoder) Length() int {\n\treturn len(f)\n}\n\nfunc TestSyncProducerInvalidConfiguration(t *testing.T) {\n\ttrm := newTestReporterMock()\n\tconfig := NewTestConfig()\n\tconfig.Version = sarama.V0_11_0_2\n\tconfig.ClientID = \"not a valid producer ID\"\n\tmp := NewSyncProducer(trm, config)\n\tif err := mp.Close(); err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif len(trm.errors) != 1 {\n\t\tt.Error(\"Expected to report a single error\")\n\t} else if !strings.Contains(trm.errors[0], `ClientID value \"not a valid producer ID\" is not valid for Kafka versions before 1.0.0`) {\n\t\tt.Errorf(\"Unexpected error: %s\", trm.errors[0])\n\t}\n}\n"
  },
  {
    "path": "offset_commit_request.go",
    "content": "package sarama\n\nimport \"errors\"\n\n// ReceiveTime is a special value for the timestamp field of Offset Commit Requests which\n// tells the broker to set the timestamp to the time at which the request was received.\n// The timestamp is only used if message version 1 is used, which requires kafka 0.8.2.\nconst ReceiveTime int64 = -1\n\n// GroupGenerationUndefined is a special value for the group generation field of\n// Offset Commit Requests that should be used when a consumer group does not rely\n// on Kafka for partition management.\nconst GroupGenerationUndefined = -1\n\ntype offsetCommitRequestBlock struct {\n\toffset               int64\n\ttimestamp            int64\n\tcommittedLeaderEpoch int32\n\tmetadata             string\n}\n\nfunc (b *offsetCommitRequestBlock) encode(pe packetEncoder, version int16) error {\n\tpe.putInt64(b.offset)\n\tif version == 1 {\n\t\tpe.putInt64(b.timestamp)\n\t} else if b.timestamp != 0 {\n\t\tLogger.Println(\"Non-zero timestamp specified for OffsetCommitRequest not v1, it will be ignored\")\n\t}\n\tif version >= 6 {\n\t\tpe.putInt32(b.committedLeaderEpoch)\n\t}\n\n\treturn pe.putString(b.metadata)\n}\n\nfunc (b *offsetCommitRequestBlock) decode(pd packetDecoder, version int16) (err error) {\n\tif b.offset, err = pd.getInt64(); err != nil {\n\t\treturn err\n\t}\n\tif version == 1 {\n\t\tif b.timestamp, err = pd.getInt64(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif version >= 6 {\n\t\tif b.committedLeaderEpoch, err = pd.getInt32(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tb.metadata, err = pd.getString()\n\treturn err\n}\n\ntype OffsetCommitRequest struct {\n\tConsumerGroup           string\n\tConsumerGroupGeneration int32   // v1 or later\n\tConsumerID              string  // v1 or later\n\tGroupInstanceId         *string // v7 or later\n\tRetentionTime           int64   // v2 or later\n\n\t// Version can be:\n\t// - 0 (kafka 0.8.1 and later)\n\t// - 1 (kafka 0.8.2 and later)\n\t// - 2 (kafka 0.9.0 and later)\n\t// - 3 (kafka 0.11.0 and later)\n\t// - 4 (kafka 2.0.0 and later)\n\t// - 5&6 (kafka 2.1.0 and later)\n\t// - 7 (kafka 2.3.0 and later)\n\tVersion int16\n\tblocks  map[string]map[int32]*offsetCommitRequestBlock\n}\n\nfunc (r *OffsetCommitRequest) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *OffsetCommitRequest) encode(pe packetEncoder) error {\n\tif r.Version < 0 || r.Version > 7 {\n\t\treturn PacketEncodingError{\"invalid or unsupported OffsetCommitRequest version field\"}\n\t}\n\n\tif err := pe.putString(r.ConsumerGroup); err != nil {\n\t\treturn err\n\t}\n\n\tif r.Version >= 1 {\n\t\tpe.putInt32(r.ConsumerGroupGeneration)\n\t\tif err := pe.putString(r.ConsumerID); err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\tif r.ConsumerGroupGeneration != 0 {\n\t\t\tLogger.Println(\"Non-zero ConsumerGroupGeneration specified for OffsetCommitRequest v0, it will be ignored\")\n\t\t}\n\t\tif r.ConsumerID != \"\" {\n\t\t\tLogger.Println(\"Non-empty ConsumerID specified for OffsetCommitRequest v0, it will be ignored\")\n\t\t}\n\t}\n\n\t// Version 5 removes RetentionTime, which is now controlled only by a broker configuration.\n\tif r.Version >= 2 && r.Version <= 4 {\n\t\tpe.putInt64(r.RetentionTime)\n\t} else if r.RetentionTime != 0 {\n\t\tLogger.Println(\"Non-zero RetentionTime specified for OffsetCommitRequest version <2, it will be ignored\")\n\t}\n\n\tif r.Version >= 7 {\n\t\tif err := pe.putNullableString(r.GroupInstanceId); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif err := pe.putArrayLength(len(r.blocks)); err != nil {\n\t\treturn err\n\t}\n\tfor topic, partitions := range r.blocks {\n\t\tif err := pe.putString(topic); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := pe.putArrayLength(len(partitions)); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor partition, block := range partitions {\n\t\t\tpe.putInt32(partition)\n\t\t\tif err := block.encode(pe, r.Version); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (r *OffsetCommitRequest) decode(pd packetDecoder, version int16) (err error) {\n\tr.Version = version\n\n\tif r.ConsumerGroup, err = pd.getString(); err != nil {\n\t\treturn err\n\t}\n\n\tif r.Version >= 1 {\n\t\tif r.ConsumerGroupGeneration, err = pd.getInt32(); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif r.ConsumerID, err = pd.getString(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Version 5 removes RetentionTime, which is now controlled only by a broker configuration.\n\tif r.Version >= 2 && r.Version <= 4 {\n\t\tif r.RetentionTime, err = pd.getInt64(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif r.Version >= 7 {\n\t\tif r.GroupInstanceId, err = pd.getNullableString(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\ttopicCount, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif topicCount == 0 {\n\t\treturn nil\n\t}\n\tr.blocks = make(map[string]map[int32]*offsetCommitRequestBlock)\n\tfor i := 0; i < topicCount; i++ {\n\t\ttopic, err := pd.getString()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tpartitionCount, err := pd.getArrayLength()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.blocks[topic] = make(map[int32]*offsetCommitRequestBlock)\n\t\tfor j := 0; j < partitionCount; j++ {\n\t\t\tpartition, err := pd.getInt32()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tblock := &offsetCommitRequestBlock{}\n\t\t\tif err := block.decode(pd, r.Version); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tr.blocks[topic][partition] = block\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (r *OffsetCommitRequest) key() int16 {\n\treturn apiKeyOffsetCommit\n}\n\nfunc (r *OffsetCommitRequest) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *OffsetCommitRequest) headerVersion() int16 {\n\treturn 1\n}\n\nfunc (r *OffsetCommitRequest) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 7\n}\n\nfunc (r *OffsetCommitRequest) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 7:\n\t\treturn V2_3_0_0\n\tcase 5, 6:\n\t\treturn V2_1_0_0\n\tcase 4:\n\t\treturn V2_0_0_0\n\tcase 3:\n\t\treturn V0_11_0_0\n\tcase 2:\n\t\treturn V0_9_0_0\n\tcase 0, 1:\n\t\treturn V0_8_2_0\n\tdefault:\n\t\treturn V2_4_0_0\n\t}\n}\n\nfunc (r *OffsetCommitRequest) AddBlock(topic string, partitionID int32, offset int64, timestamp int64, metadata string) {\n\tr.AddBlockWithLeaderEpoch(topic, partitionID, offset, 0, timestamp, metadata)\n}\n\nfunc (r *OffsetCommitRequest) AddBlockWithLeaderEpoch(topic string, partitionID int32, offset int64, leaderEpoch int32, timestamp int64, metadata string) {\n\tif r.blocks == nil {\n\t\tr.blocks = make(map[string]map[int32]*offsetCommitRequestBlock)\n\t}\n\n\tif r.blocks[topic] == nil {\n\t\tr.blocks[topic] = make(map[int32]*offsetCommitRequestBlock)\n\t}\n\n\tr.blocks[topic][partitionID] = &offsetCommitRequestBlock{offset, timestamp, leaderEpoch, metadata}\n}\n\nfunc (r *OffsetCommitRequest) Offset(topic string, partitionID int32) (int64, string, error) {\n\tpartitions := r.blocks[topic]\n\tif partitions == nil {\n\t\treturn 0, \"\", errors.New(\"no such offset\")\n\t}\n\tblock := partitions[partitionID]\n\tif block == nil {\n\t\treturn 0, \"\", errors.New(\"no such offset\")\n\t}\n\treturn block.offset, block.metadata, nil\n}\n"
  },
  {
    "path": "offset_commit_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nvar (\n\toffsetCommitRequestNoBlocksV0 = []byte{\n\t\t0x00, 0x06, 'f', 'o', 'o', 'b', 'a', 'r',\n\t\t0x00, 0x00, 0x00, 0x00,\n\t}\n\n\toffsetCommitRequestNoBlocksV1 = []byte{\n\t\t0x00, 0x06, 'f', 'o', 'o', 'b', 'a', 'r',\n\t\t0x00, 0x00, 0x11, 0x22,\n\t\t0x00, 0x04, 'c', 'o', 'n', 's',\n\t\t0x00, 0x00, 0x00, 0x00,\n\t}\n\n\toffsetCommitRequestNoBlocksV2 = []byte{\n\t\t0x00, 0x06, 'f', 'o', 'o', 'b', 'a', 'r',\n\t\t0x00, 0x00, 0x11, 0x22,\n\t\t0x00, 0x04, 'c', 'o', 'n', 's',\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x33,\n\t\t0x00, 0x00, 0x00, 0x00,\n\t}\n\n\toffsetCommitRequestOneBlockV0 = []byte{\n\t\t0x00, 0x06, 'f', 'o', 'o', 'b', 'a', 'r',\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x05, 't', 'o', 'p', 'i', 'c',\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x00, 0x52, 0x21,\n\t\t0x00, 0x00, 0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF,\n\t\t0x00, 0x08, 'm', 'e', 't', 'a', 'd', 'a', 't', 'a',\n\t}\n\n\toffsetCommitRequestOneBlockV1 = []byte{\n\t\t0x00, 0x06, 'f', 'o', 'o', 'b', 'a', 'r',\n\t\t0x00, 0x00, 0x11, 0x22,\n\t\t0x00, 0x04, 'c', 'o', 'n', 's',\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x05, 't', 'o', 'p', 'i', 'c',\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x00, 0x52, 0x21,\n\t\t0x00, 0x00, 0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF,\n\t\t0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0x00, 0x08, 'm', 'e', 't', 'a', 'd', 'a', 't', 'a',\n\t}\n\n\toffsetCommitRequestOneBlockV2 = []byte{\n\t\t0x00, 0x06, 'f', 'o', 'o', 'b', 'a', 'r',\n\t\t0x00, 0x00, 0x11, 0x22,\n\t\t0x00, 0x04, 'c', 'o', 'n', 's',\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x33,\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x05, 't', 'o', 'p', 'i', 'c',\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x00, 0x52, 0x21,\n\t\t0x00, 0x00, 0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF,\n\t\t0x00, 0x08, 'm', 'e', 't', 'a', 'd', 'a', 't', 'a',\n\t}\n)\n\nfunc TestOffsetCommitRequestV0(t *testing.T) {\n\trequest := new(OffsetCommitRequest)\n\trequest.Version = 0\n\trequest.ConsumerGroup = \"foobar\"\n\ttestRequest(t, \"no blocks v0\", request, offsetCommitRequestNoBlocksV0)\n\n\trequest.AddBlock(\"topic\", 0x5221, 0xDEADBEEF, 0, \"metadata\")\n\ttestRequest(t, \"one block v0\", request, offsetCommitRequestOneBlockV0)\n}\n\nfunc TestOffsetCommitRequestV1(t *testing.T) {\n\trequest := new(OffsetCommitRequest)\n\trequest.ConsumerGroup = \"foobar\"\n\trequest.ConsumerID = \"cons\"\n\trequest.ConsumerGroupGeneration = 0x1122\n\trequest.Version = 1\n\ttestRequest(t, \"no blocks v1\", request, offsetCommitRequestNoBlocksV1)\n\n\trequest.AddBlock(\"topic\", 0x5221, 0xDEADBEEF, ReceiveTime, \"metadata\")\n\ttestRequest(t, \"one block v1\", request, offsetCommitRequestOneBlockV1)\n}\n\nfunc TestOffsetCommitRequestV2ToV4(t *testing.T) {\n\tfor version := 2; version <= 4; version++ {\n\t\trequest := new(OffsetCommitRequest)\n\t\trequest.ConsumerGroup = \"foobar\"\n\t\trequest.ConsumerID = \"cons\"\n\t\trequest.ConsumerGroupGeneration = 0x1122\n\t\trequest.RetentionTime = 0x4433\n\t\trequest.Version = int16(version)\n\t\ttestRequest(t, fmt.Sprintf(\"no blocks v%d\", version), request, offsetCommitRequestNoBlocksV2)\n\n\t\trequest.AddBlock(\"topic\", 0x5221, 0xDEADBEEF, 0, \"metadata\")\n\t\ttestRequest(t, fmt.Sprintf(\"one block v%d\", version), request, offsetCommitRequestOneBlockV2)\n\t}\n}\n\nvar (\n\toffsetCommitRequestOneBlockV5 = []byte{\n\t\t0, 3, 'f', 'o', 'o', // GroupId\n\t\t0x00, 0x00, 0x00, 0x01, // GenerationId\n\t\t0, 3, 'm', 'i', 'd', // MemberId\n\t\t0, 0, 0, 1, // One Topic\n\t\t0, 5, 't', 'o', 'p', 'i', 'c', // Name\n\t\t0, 0, 0, 1, // One Partition\n\t\t0, 0, 0, 1, // PartitionIndex\n\t\t0, 0, 0, 0, 0, 0, 0, 2, // CommittedOffset\n\t\t0, 4, 'm', 'e', 't', 'a', // CommittedMetadata\n\t}\n\toffsetCommitRequestOneBlockV6 = []byte{\n\t\t0, 3, 'f', 'o', 'o', // GroupId\n\t\t0x00, 0x00, 0x00, 0x01, // GenerationId\n\t\t0, 3, 'm', 'i', 'd', // MemberId\n\t\t0, 0, 0, 1, // One Topic\n\t\t0, 5, 't', 'o', 'p', 'i', 'c', // Name\n\t\t0, 0, 0, 1, // One Partition\n\t\t0, 0, 0, 1, // PartitionIndex\n\t\t0, 0, 0, 0, 0, 0, 0, 2, // CommittedOffset\n\t\t0, 0, 0, 3, // CommittedEpoch\n\t\t0, 4, 'm', 'e', 't', 'a', // CommittedMetadata\n\t}\n\toffsetCommitRequestOneBlockV7 = []byte{\n\t\t0, 3, 'f', 'o', 'o', // GroupId\n\t\t0x00, 0x00, 0x00, 0x01, // GenerationId\n\t\t0, 3, 'm', 'i', 'd', // MemberId\n\t\t0, 3, 'g', 'i', 'd', // MemberId\n\t\t0, 0, 0, 1, // One Topic\n\t\t0, 5, 't', 'o', 'p', 'i', 'c', // Name\n\t\t0, 0, 0, 1, // One Partition\n\t\t0, 0, 0, 1, // PartitionIndex\n\t\t0, 0, 0, 0, 0, 0, 0, 2, // CommittedOffset\n\t\t0, 0, 0, 3, // CommittedEpoch\n\t\t0, 4, 'm', 'e', 't', 'a', // CommittedMetadata\n\t}\n)\n\nfunc TestOffsetCommitRequestV5AndPlus(t *testing.T) {\n\tgroupInstanceId := \"gid\"\n\ttests := []struct {\n\t\tCaseName     string\n\t\tVersion      int16\n\t\tMessageBytes []byte\n\t\tMessage      *OffsetCommitRequest\n\t}{\n\t\t{\n\t\t\t\"v5\",\n\t\t\t5,\n\t\t\toffsetCommitRequestOneBlockV5,\n\t\t\t&OffsetCommitRequest{\n\t\t\t\tVersion:                 5,\n\t\t\t\tConsumerGroup:           \"foo\",\n\t\t\t\tConsumerGroupGeneration: 1,\n\t\t\t\tConsumerID:              \"mid\",\n\t\t\t\tblocks: map[string]map[int32]*offsetCommitRequestBlock{\n\t\t\t\t\t\"topic\": {\n\t\t\t\t\t\t1: &offsetCommitRequestBlock{offset: 2, metadata: \"meta\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"v6\",\n\t\t\t6,\n\t\t\toffsetCommitRequestOneBlockV6,\n\t\t\t&OffsetCommitRequest{\n\t\t\t\tVersion:                 6,\n\t\t\t\tConsumerGroup:           \"foo\",\n\t\t\t\tConsumerGroupGeneration: 1,\n\t\t\t\tConsumerID:              \"mid\",\n\t\t\t\tblocks: map[string]map[int32]*offsetCommitRequestBlock{\n\t\t\t\t\t\"topic\": {\n\t\t\t\t\t\t1: &offsetCommitRequestBlock{offset: 2, metadata: \"meta\", committedLeaderEpoch: 3},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"v7\",\n\t\t\t7,\n\t\t\toffsetCommitRequestOneBlockV7,\n\t\t\t&OffsetCommitRequest{\n\t\t\t\tVersion:                 7,\n\t\t\t\tConsumerGroup:           \"foo\",\n\t\t\t\tConsumerGroupGeneration: 1,\n\t\t\t\tConsumerID:              \"mid\",\n\t\t\t\tGroupInstanceId:         &groupInstanceId,\n\t\t\t\tblocks: map[string]map[int32]*offsetCommitRequestBlock{\n\t\t\t\t\t\"topic\": {\n\t\t\t\t\t\t1: &offsetCommitRequestBlock{offset: 2, metadata: \"meta\", committedLeaderEpoch: 3},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tfor _, c := range tests {\n\t\trequest := new(OffsetCommitRequest)\n\t\ttestVersionDecodable(t, c.CaseName, request, c.MessageBytes, c.Version)\n\t\tif !reflect.DeepEqual(c.Message, request) {\n\t\t\tt.Errorf(\"case %s decode failed, expected:%+v got %+v\", c.CaseName, c.Message, request)\n\t\t}\n\t\ttestEncodable(t, c.CaseName, c.Message, c.MessageBytes)\n\t}\n}\n"
  },
  {
    "path": "offset_commit_response.go",
    "content": "package sarama\n\nimport \"time\"\n\ntype OffsetCommitResponse struct {\n\tVersion        int16\n\tThrottleTimeMs int32\n\tErrors         map[string]map[int32]KError\n}\n\nfunc (r *OffsetCommitResponse) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *OffsetCommitResponse) AddError(topic string, partition int32, kerror KError) {\n\tif r.Errors == nil {\n\t\tr.Errors = make(map[string]map[int32]KError)\n\t}\n\tpartitions := r.Errors[topic]\n\tif partitions == nil {\n\t\tpartitions = make(map[int32]KError)\n\t\tr.Errors[topic] = partitions\n\t}\n\tpartitions[partition] = kerror\n}\n\nfunc (r *OffsetCommitResponse) encode(pe packetEncoder) error {\n\tif r.Version >= 3 {\n\t\tpe.putInt32(r.ThrottleTimeMs)\n\t}\n\tif err := pe.putArrayLength(len(r.Errors)); err != nil {\n\t\treturn err\n\t}\n\tfor topic, partitions := range r.Errors {\n\t\tif err := pe.putString(topic); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := pe.putArrayLength(len(partitions)); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor partition, kerror := range partitions {\n\t\t\tpe.putInt32(partition)\n\t\t\tpe.putKError(kerror)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (r *OffsetCommitResponse) decode(pd packetDecoder, version int16) (err error) {\n\tr.Version = version\n\n\tif version >= 3 {\n\t\tr.ThrottleTimeMs, err = pd.getInt32()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tnumTopics, err := pd.getArrayLength()\n\tif err != nil || numTopics == 0 {\n\t\treturn err\n\t}\n\n\tr.Errors = make(map[string]map[int32]KError, numTopics)\n\tfor i := 0; i < numTopics; i++ {\n\t\tname, err := pd.getString()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tnumErrors, err := pd.getArrayLength()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tr.Errors[name] = make(map[int32]KError, numErrors)\n\n\t\tfor j := 0; j < numErrors; j++ {\n\t\t\tid, err := pd.getInt32()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tr.Errors[name][id], err = pd.getKError()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (r *OffsetCommitResponse) key() int16 {\n\treturn apiKeyOffsetCommit\n}\n\nfunc (r *OffsetCommitResponse) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *OffsetCommitResponse) headerVersion() int16 {\n\treturn 0\n}\n\nfunc (r *OffsetCommitResponse) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 7\n}\n\nfunc (r *OffsetCommitResponse) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 7:\n\t\treturn V2_3_0_0\n\tcase 5, 6:\n\t\treturn V2_1_0_0\n\tcase 4:\n\t\treturn V2_0_0_0\n\tcase 3:\n\t\treturn V0_11_0_0\n\tcase 2:\n\t\treturn V0_9_0_0\n\tcase 0, 1:\n\t\treturn V0_8_2_0\n\tdefault:\n\t\treturn V2_4_0_0\n\t}\n}\n\nfunc (r *OffsetCommitResponse) throttleTime() time.Duration {\n\treturn time.Duration(r.ThrottleTimeMs) * time.Millisecond\n}\n"
  },
  {
    "path": "offset_commit_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nvar (\n\temptyOffsetCommitResponseV0 = []byte{\n\t\t0x00, 0x00, 0x00, 0x00, // Empty topic\n\t}\n\tnoEmptyOffsetCommitResponseV0 = []byte{\n\t\t0, 0, 0, 1, // Topic Len\n\t\t0, 5, 't', 'o', 'p', 'i', 'c', // Name\n\t\t0, 0, 0, 1, // Partition Len\n\t\t0, 0, 0, 3, // PartitionIndex\n\t\t0, 0, // ErrorCode\n\t}\n\tnoEmptyOffsetCommitResponseV3 = []byte{\n\t\t0, 0, 0, 100, // ThrottleTimeMs\n\t\t0, 0, 0, 1, // Topic Len\n\t\t0, 5, 't', 'o', 'p', 'i', 'c', // Name\n\t\t0, 0, 0, 1, // Partition Len\n\t\t0, 0, 0, 3, // PartitionIndex\n\t\t0, 0, // ErrorCode\n\t}\n)\n\nfunc TestEmptyOffsetCommitResponse(t *testing.T) {\n\t// groupInstanceId := \"gid\"\n\ttests := []struct {\n\t\tCaseName     string\n\t\tVersion      int16\n\t\tMessageBytes []byte\n\t\tMessage      *OffsetCommitResponse\n\t}{\n\t\t{\n\t\t\t\"v0-empty\",\n\t\t\t0,\n\t\t\temptyOffsetCommitResponseV0,\n\t\t\t&OffsetCommitResponse{\n\t\t\t\tVersion: 0,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"v0-two-partition\",\n\t\t\t0,\n\t\t\tnoEmptyOffsetCommitResponseV0,\n\t\t\t&OffsetCommitResponse{\n\t\t\t\tVersion: 0,\n\t\t\t\tErrors: map[string]map[int32]KError{\n\t\t\t\t\t\"topic\": {\n\t\t\t\t\t\t3: ErrNoError,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"v3\",\n\t\t\t3,\n\t\t\tnoEmptyOffsetCommitResponseV3,\n\t\t\t&OffsetCommitResponse{\n\t\t\t\tThrottleTimeMs: 100,\n\t\t\t\tVersion:        3,\n\t\t\t\tErrors: map[string]map[int32]KError{\n\t\t\t\t\t\"topic\": {\n\t\t\t\t\t\t3: ErrNoError,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tfor _, c := range tests {\n\t\tresponse := new(OffsetCommitResponse)\n\t\ttestVersionDecodable(t, c.CaseName, response, c.MessageBytes, c.Version)\n\t\tif !reflect.DeepEqual(c.Message, response) {\n\t\t\tt.Errorf(\"case %s decode failed, expected:%+v got %+v\", c.CaseName, c.Message, response)\n\t\t}\n\t\ttestEncodable(t, c.CaseName, c.Message, c.MessageBytes)\n\t}\n}\n\nfunc TestNormalOffsetCommitResponse(t *testing.T) {\n\tresponse := OffsetCommitResponse{}\n\tresponse.AddError(\"t\", 0, ErrNotLeaderForPartition)\n\tresponse.Errors[\"m\"] = make(map[int32]KError)\n\t// The response encoded form cannot be checked for it varies due to\n\t// unpredictable map traversal order.\n\ttestResponse(t, \"normal\", &response, nil)\n}\n\nfunc TestOffsetCommitResponseWithThrottleTime(t *testing.T) {\n\tfor version := 3; version <= 4; version++ {\n\t\tresponse := OffsetCommitResponse{\n\t\t\tVersion:        int16(version),\n\t\t\tThrottleTimeMs: 123,\n\t\t}\n\t\tresponse.AddError(\"t\", 0, ErrNotLeaderForPartition)\n\t\tresponse.Errors[\"m\"] = make(map[int32]KError)\n\t\t// The response encoded form cannot be checked for it varies due to\n\t\t// unpredictable map traversal order.\n\t\ttestResponse(t, fmt.Sprintf(\"v%d with throttle time\", version), &response, nil)\n\t}\n}\n"
  },
  {
    "path": "offset_fetch_request.go",
    "content": "package sarama\n\ntype OffsetFetchRequest struct {\n\tVersion       int16\n\tConsumerGroup string\n\tRequireStable bool // requires v7+\n\tpartitions    map[string][]int32\n}\n\nfunc (r *OffsetFetchRequest) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc NewOffsetFetchRequest(\n\tversion KafkaVersion,\n\tgroup string,\n\tpartitions map[string][]int32,\n) *OffsetFetchRequest {\n\trequest := &OffsetFetchRequest{\n\t\tConsumerGroup: group,\n\t\tpartitions:    partitions,\n\t}\n\tif version.IsAtLeast(V2_5_0_0) {\n\t\t// Version 7 is adding the require stable flag.\n\t\trequest.Version = 7\n\t} else if version.IsAtLeast(V2_4_0_0) {\n\t\t// Version 6 is the first flexible version.\n\t\trequest.Version = 6\n\t} else if version.IsAtLeast(V2_1_0_0) {\n\t\t// Version 3, 4, and 5 are the same as version 2.\n\t\trequest.Version = 5\n\t} else if version.IsAtLeast(V2_0_0_0) {\n\t\trequest.Version = 4\n\t} else if version.IsAtLeast(V0_11_0_0) {\n\t\trequest.Version = 3\n\t} else if version.IsAtLeast(V0_10_2_0) {\n\t\t// Starting in version 2, the request can contain a null topics array to indicate that offsets\n\t\t// for all topics should be fetched. It also returns a top level error code\n\t\t// for group or coordinator level errors.\n\t\trequest.Version = 2\n\t} else if version.IsAtLeast(V0_8_2_0) {\n\t\t// In version 0, the request read offsets from ZK.\n\t\t//\n\t\t// Starting in version 1, the broker supports fetching offsets from the internal __consumer_offsets topic.\n\t\trequest.Version = 1\n\t}\n\n\treturn request\n}\n\nfunc (r *OffsetFetchRequest) encode(pe packetEncoder) (err error) {\n\tif r.Version < 0 || r.Version > 7 {\n\t\treturn PacketEncodingError{\"invalid or unsupported OffsetFetchRequest version field\"}\n\t}\n\n\terr = pe.putString(r.ConsumerGroup)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif r.partitions == nil && r.Version >= 2 {\n\t\tif err := pe.putArrayLength(-1); err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\tif err = pe.putArrayLength(len(r.partitions)); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tfor topic, partitions := range r.partitions {\n\t\terr = pe.putString(topic)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\terr = pe.putInt32Array(partitions)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tpe.putEmptyTaggedFieldArray()\n\t}\n\n\tif r.RequireStable && r.Version < 7 {\n\t\treturn PacketEncodingError{\"requireStable is not supported. use version 7 or later\"}\n\t}\n\n\tif r.Version >= 7 {\n\t\tpe.putBool(r.RequireStable)\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (r *OffsetFetchRequest) decode(pd packetDecoder, version int16) (err error) {\n\tr.Version = version\n\tr.ConsumerGroup, err = pd.getString()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tpartitionCount, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif (partitionCount == 0 && version < 2) || partitionCount < 0 {\n\t\treturn nil\n\t}\n\n\tr.partitions = make(map[string][]int32, partitionCount)\n\tfor i := 0; i < partitionCount; i++ {\n\t\ttopic, err := pd.getString()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tpartitions, err := pd.getInt32Array()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, err = pd.getEmptyTaggedFieldArray()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tr.partitions[topic] = partitions\n\t}\n\n\tif r.Version >= 7 {\n\t\tr.RequireStable, err = pd.getBool()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (r *OffsetFetchRequest) key() int16 {\n\treturn apiKeyOffsetFetch\n}\n\nfunc (r *OffsetFetchRequest) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *OffsetFetchRequest) headerVersion() int16 {\n\tif r.Version >= 6 {\n\t\treturn 2\n\t}\n\n\treturn 1\n}\n\nfunc (r *OffsetFetchRequest) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 7\n}\n\nfunc (r *OffsetFetchRequest) isFlexible() bool {\n\treturn r.isFlexibleVersion(r.Version)\n}\n\nfunc (r *OffsetFetchRequest) isFlexibleVersion(version int16) bool {\n\treturn version >= 6\n}\n\nfunc (r *OffsetFetchRequest) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 7:\n\t\treturn V2_5_0_0\n\tcase 6:\n\t\treturn V2_4_0_0\n\tcase 5:\n\t\treturn V2_1_0_0\n\tcase 4:\n\t\treturn V2_0_0_0\n\tcase 3:\n\t\treturn V0_11_0_0\n\tcase 2:\n\t\treturn V0_10_2_0\n\tcase 1:\n\t\treturn V0_8_2_0\n\tcase 0:\n\t\treturn V0_8_2_0\n\tdefault:\n\t\treturn V2_5_0_0\n\t}\n}\n\nfunc (r *OffsetFetchRequest) ZeroPartitions() {\n\tif r.partitions == nil && r.Version >= 2 {\n\t\tr.partitions = make(map[string][]int32)\n\t}\n}\n\nfunc (r *OffsetFetchRequest) AddPartition(topic string, partitionID int32) {\n\tif r.partitions == nil {\n\t\tr.partitions = make(map[string][]int32)\n\t}\n\n\tr.partitions[topic] = append(r.partitions[topic], partitionID)\n}\n"
  },
  {
    "path": "offset_fetch_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n)\n\nvar (\n\toffsetFetchRequestNoGroupNoPartitions = []byte{\n\t\t0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x00,\n\t}\n\n\toffsetFetchRequestNoPartitionsV6 = []byte{\n\t\t0x05, 'b', 'l', 'a', 'h', 0x01, 0x00,\n\t}\n\n\toffsetFetchRequestNoPartitionsV7 = []byte{\n\t\t0x05, 'b', 'l', 'a', 'h', 0x01, 0x01, 0x00,\n\t}\n\n\toffsetFetchRequestNoPartitions = []byte{\n\t\t0x00, 0x04, 'b', 'l', 'a', 'h',\n\t\t0x00, 0x00, 0x00, 0x00,\n\t}\n\n\toffsetFetchRequestOnePartition = []byte{\n\t\t0x00, 0x04, 'b', 'l', 'a', 'h',\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x0D, 't', 'o', 'p', 'i', 'c', 'T', 'h', 'e', 'F', 'i', 'r', 's', 't',\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x4F, 0x4F, 0x4F, 0x4F,\n\t}\n\n\toffsetFetchRequestOnePartitionV6 = []byte{\n\t\t0x05, 'b', 'l', 'a', 'h',\n\t\t0x02, 0x0E, 't', 'o', 'p', 'i', 'c', 'T', 'h', 'e', 'F', 'i', 'r', 's', 't',\n\t\t0x02,\n\t\t0x4F, 0x4F, 0x4F, 0x4F,\n\t\t0x00, 0x00,\n\t}\n\n\toffsetFetchRequestOnePartitionV7 = []byte{\n\t\t0x05, 'b', 'l', 'a', 'h',\n\t\t0x02, 0x0E, 't', 'o', 'p', 'i', 'c', 'T', 'h', 'e', 'F', 'i', 'r', 's', 't',\n\t\t0x02,\n\t\t0x4F, 0x4F, 0x4F, 0x4F,\n\t\t0x00, 0x00, 0x00,\n\t}\n\n\toffsetFetchRequestAllPartitions = []byte{\n\t\t0x00, 0x04, 'b', 'l', 'a', 'h',\n\t\t0xff, 0xff, 0xff, 0xff,\n\t}\n)\n\nfunc TestOffsetFetchRequestNoPartitions(t *testing.T) {\n\tfor version := 0; version <= 5; version++ {\n\t\trequest := new(OffsetFetchRequest)\n\t\trequest.Version = int16(version)\n\t\trequest.ZeroPartitions()\n\t\ttestRequest(t, fmt.Sprintf(\"no group, no partitions %d\", version), request, offsetFetchRequestNoGroupNoPartitions)\n\n\t\trequest.ConsumerGroup = \"blah\"\n\t\ttestRequest(t, fmt.Sprintf(\"no partitions %d\", version), request, offsetFetchRequestNoPartitions)\n\t}\n\n\t{ // v6\n\t\tversion := 6\n\t\trequest := new(OffsetFetchRequest)\n\t\trequest.Version = int16(version)\n\t\trequest.ConsumerGroup = \"blah\"\n\t\trequest.ZeroPartitions()\n\n\t\ttestRequest(t, fmt.Sprintf(\"no partitions %d\", version), request, offsetFetchRequestNoPartitionsV6)\n\t}\n\n\t{ // v7\n\t\tversion := 7\n\t\trequest := new(OffsetFetchRequest)\n\t\trequest.Version = int16(version)\n\t\trequest.ConsumerGroup = \"blah\"\n\t\trequest.RequireStable = true\n\t\trequest.ZeroPartitions()\n\n\t\ttestRequest(t, fmt.Sprintf(\"no partitions %d\", version), request, offsetFetchRequestNoPartitionsV7)\n\t}\n}\n\nfunc TestOffsetFetchRequest(t *testing.T) {\n\tfor version := 0; version <= 5; version++ {\n\t\trequest := new(OffsetFetchRequest)\n\t\trequest.Version = int16(version)\n\t\trequest.ConsumerGroup = \"blah\"\n\t\trequest.AddPartition(\"topicTheFirst\", 0x4F4F4F4F)\n\t\ttestRequest(t, fmt.Sprintf(\"one partition %d\", version), request, offsetFetchRequestOnePartition)\n\t}\n\n\t{ // v6\n\t\tversion := 6\n\t\trequest := new(OffsetFetchRequest)\n\t\trequest.Version = int16(version)\n\t\trequest.ConsumerGroup = \"blah\"\n\t\trequest.AddPartition(\"topicTheFirst\", 0x4F4F4F4F)\n\t\ttestRequest(t, fmt.Sprintf(\"one partition %d\", version), request, offsetFetchRequestOnePartitionV6)\n\t}\n\n\t{ // v7\n\t\tversion := 7\n\t\trequest := new(OffsetFetchRequest)\n\t\trequest.Version = int16(version)\n\t\trequest.ConsumerGroup = \"blah\"\n\t\trequest.AddPartition(\"topicTheFirst\", 0x4F4F4F4F)\n\t\ttestRequest(t, fmt.Sprintf(\"one partition %d\", version), request, offsetFetchRequestOnePartitionV7)\n\t}\n}\n\nfunc TestOffsetFetchRequestAllPartitions(t *testing.T) {\n\tfor version := 2; version <= 5; version++ {\n\t\trequest := &OffsetFetchRequest{Version: int16(version), ConsumerGroup: \"blah\"}\n\t\ttestRequest(t, fmt.Sprintf(\"all partitions %d\", version), request, offsetFetchRequestAllPartitions)\n\t}\n}\n"
  },
  {
    "path": "offset_fetch_response.go",
    "content": "package sarama\n\nimport \"time\"\n\ntype OffsetFetchResponseBlock struct {\n\tOffset      int64\n\tLeaderEpoch int32\n\tMetadata    string\n\tErr         KError\n}\n\nfunc (b *OffsetFetchResponseBlock) decode(pd packetDecoder, version int16) (err error) {\n\tb.Offset, err = pd.getInt64()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif version >= 5 {\n\t\tb.LeaderEpoch, err = pd.getInt32()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\tb.LeaderEpoch = -1\n\t}\n\n\tmetadata, err := pd.getNullableString()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif metadata != nil {\n\t\tb.Metadata = *metadata\n\t}\n\n\tb.Err, err = pd.getKError()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (b *OffsetFetchResponseBlock) encode(pe packetEncoder, version int16) (err error) {\n\tpe.putInt64(b.Offset)\n\n\tif version >= 5 {\n\t\tpe.putInt32(b.LeaderEpoch)\n\t}\n\terr = pe.putNullableString(&b.Metadata)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tpe.putKError(b.Err)\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\ntype OffsetFetchResponse struct {\n\tVersion        int16\n\tThrottleTimeMs int32\n\tBlocks         map[string]map[int32]*OffsetFetchResponseBlock\n\tErr            KError\n}\n\nfunc (r *OffsetFetchResponse) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *OffsetFetchResponse) encode(pe packetEncoder) (err error) {\n\tif r.Version >= 3 {\n\t\tpe.putInt32(r.ThrottleTimeMs)\n\t}\n\terr = pe.putArrayLength(len(r.Blocks))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor topic, partitions := range r.Blocks {\n\t\terr = pe.putString(topic)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\terr = pe.putArrayLength(len(partitions))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor partition, block := range partitions {\n\t\t\tpe.putInt32(partition)\n\t\t\tif err := block.encode(pe, r.Version); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tpe.putEmptyTaggedFieldArray()\n\t}\n\tif r.Version >= 2 {\n\t\tpe.putKError(r.Err)\n\t}\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (r *OffsetFetchResponse) decode(pd packetDecoder, version int16) (err error) {\n\tr.Version = version\n\n\tif version >= 3 {\n\t\tr.ThrottleTimeMs, err = pd.getInt32()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tnumTopics, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif numTopics > 0 {\n\t\tr.Blocks = make(map[string]map[int32]*OffsetFetchResponseBlock, numTopics)\n\t\tfor i := 0; i < numTopics; i++ {\n\t\t\tname, err := pd.getString()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tnumBlocks, err := pd.getArrayLength()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tr.Blocks[name] = nil\n\t\t\tif numBlocks > 0 {\n\t\t\t\tr.Blocks[name] = make(map[int32]*OffsetFetchResponseBlock, numBlocks)\n\t\t\t}\n\t\t\tfor j := 0; j < numBlocks; j++ {\n\t\t\t\tid, err := pd.getInt32()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\tblock := new(OffsetFetchResponseBlock)\n\t\t\t\terr = block.decode(pd, version)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\tr.Blocks[name][id] = block\n\t\t\t}\n\n\t\t\tif _, err := pd.getEmptyTaggedFieldArray(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\tif version >= 2 {\n\t\tr.Err, err = pd.getKError()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (r *OffsetFetchResponse) key() int16 {\n\treturn apiKeyOffsetFetch\n}\n\nfunc (r *OffsetFetchResponse) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *OffsetFetchResponse) headerVersion() int16 {\n\tif r.Version >= 6 {\n\t\treturn 1\n\t}\n\n\treturn 0\n}\n\nfunc (r *OffsetFetchResponse) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 7\n}\n\nfunc (r *OffsetFetchResponse) isFlexible() bool {\n\treturn r.isFlexibleVersion(r.Version)\n}\n\nfunc (r *OffsetFetchResponse) isFlexibleVersion(version int16) bool {\n\treturn version >= 6\n}\n\nfunc (r *OffsetFetchResponse) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 7:\n\t\treturn V2_5_0_0\n\tcase 6:\n\t\treturn V2_4_0_0\n\tcase 5:\n\t\treturn V2_1_0_0\n\tcase 4:\n\t\treturn V2_0_0_0\n\tcase 3:\n\t\treturn V0_11_0_0\n\tcase 2:\n\t\treturn V0_10_2_0\n\tcase 1:\n\t\treturn V0_8_2_0\n\tcase 0:\n\t\treturn V0_8_2_0\n\tdefault:\n\t\treturn V2_5_0_0\n\t}\n}\n\nfunc (r *OffsetFetchResponse) throttleTime() time.Duration {\n\treturn time.Duration(r.ThrottleTimeMs) * time.Millisecond\n}\n\nfunc (r *OffsetFetchResponse) GetBlock(topic string, partition int32) *OffsetFetchResponseBlock {\n\tif r.Blocks == nil {\n\t\treturn nil\n\t}\n\n\tif r.Blocks[topic] == nil {\n\t\treturn nil\n\t}\n\n\treturn r.Blocks[topic][partition]\n}\n\nfunc (r *OffsetFetchResponse) AddBlock(topic string, partition int32, block *OffsetFetchResponseBlock) {\n\tif r.Blocks == nil {\n\t\tr.Blocks = make(map[string]map[int32]*OffsetFetchResponseBlock)\n\t}\n\tpartitions := r.Blocks[topic]\n\tif partitions == nil {\n\t\tpartitions = make(map[int32]*OffsetFetchResponseBlock)\n\t\tr.Blocks[topic] = partitions\n\t}\n\tpartitions[partition] = block\n}\n"
  },
  {
    "path": "offset_fetch_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nvar (\n\t// version 0 response with one topic \"t\", one partition 0, offset 0,\n\t// null metadata (length -1), and error code 7 (ErrRequestTimedOut)\n\toffsetFetchResponseV0NullMetadata = []byte{\n\t\t0x00, 0x00, 0x00, 0x01, // num topics = 1\n\t\t0x00, 0x01, 't', // topic name = \"t\"\n\t\t0x00, 0x00, 0x00, 0x01, // num partitions = 1\n\t\t0x00, 0x00, 0x00, 0x00, // partition = 0\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // offset = 0\n\t\t0xFF, 0xFF, // metadata = null (length -1)\n\t\t0x00, 0x07, // error code = 7\n\t}\n\n\temptyOffsetFetchResponse = []byte{\n\t\t0x00, 0x00, 0x00, 0x00,\n\t}\n\n\temptyOffsetFetchResponseV2 = []byte{\n\t\t0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x2A,\n\t}\n\n\temptyOffsetFetchResponseV3 = []byte{\n\t\t0x00, 0x00, 0x00, 0x09,\n\t\t0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x2A,\n\t}\n)\n\nfunc TestEmptyOffsetFetchResponse(t *testing.T) {\n\tfor version := 0; version <= 1; version++ {\n\t\tresponse := OffsetFetchResponse{Version: int16(version)}\n\t\ttestResponse(t, fmt.Sprintf(\"empty v%d\", version), &response, emptyOffsetFetchResponse)\n\t}\n\n\tresponseV2 := OffsetFetchResponse{Version: 2, Err: ErrInvalidRequest}\n\ttestResponse(t, \"empty V2\", &responseV2, emptyOffsetFetchResponseV2)\n\n\tfor version := 3; version <= 5; version++ {\n\t\tresponseV3 := OffsetFetchResponse{Version: int16(version), Err: ErrInvalidRequest, ThrottleTimeMs: 9}\n\t\ttestResponse(t, fmt.Sprintf(\"empty v%d\", version), &responseV3, emptyOffsetFetchResponseV3)\n\t}\n}\n\nfunc TestOffsetFetchResponseNullMetadata(t *testing.T) {\n\tresponse := &OffsetFetchResponse{}\n\terr := versionedDecode(offsetFetchResponseV0NullMetadata, response, 0, nil)\n\trequire.NoError(t, err)\n\n\tblock := response.GetBlock(\"t\", 0)\n\trequire.NotNil(t, block)\n\tassert.Empty(t, block.Metadata)\n\tassert.Equal(t, ErrRequestTimedOut, block.Err)\n}\n\nfunc TestNormalOffsetFetchResponse(t *testing.T) {\n\t// The response encoded form cannot be checked for it varies due to\n\t// unpredictable map traversal order.\n\t// Hence the 'nil' as byte[] parameter in the 'testResponse(..)' calls\n\n\tfor version := 0; version <= 1; version++ {\n\t\tresponse := OffsetFetchResponse{Version: int16(version)}\n\t\tresponse.AddBlock(\"t\", 0, &OffsetFetchResponseBlock{0, -1, \"md\", ErrRequestTimedOut})\n\t\tresponse.Blocks[\"m\"] = nil\n\t\ttestResponse(t, fmt.Sprintf(\"Normal v%d\", version), &response, nil)\n\t}\n\n\tresponseV2 := OffsetFetchResponse{Version: 2, Err: ErrInvalidRequest}\n\tresponseV2.AddBlock(\"t\", 0, &OffsetFetchResponseBlock{0, -1, \"md\", ErrRequestTimedOut})\n\tresponseV2.Blocks[\"m\"] = nil\n\ttestResponse(t, \"normal V2\", &responseV2, nil)\n\n\tfor version := 3; version <= 4; version++ {\n\t\tresponseV3 := OffsetFetchResponse{Version: int16(version), Err: ErrInvalidRequest, ThrottleTimeMs: 9}\n\t\tresponseV3.AddBlock(\"t\", 0, &OffsetFetchResponseBlock{0, -1, \"md\", ErrRequestTimedOut})\n\t\tresponseV3.Blocks[\"m\"] = nil\n\t\ttestResponse(t, fmt.Sprintf(\"Normal v%d\", version), &responseV3, nil)\n\t}\n\n\tresponseV5 := OffsetFetchResponse{Version: 5, Err: ErrInvalidRequest, ThrottleTimeMs: 9}\n\tresponseV5.AddBlock(\"t\", 0, &OffsetFetchResponseBlock{Offset: 10, LeaderEpoch: 100, Metadata: \"md\", Err: ErrRequestTimedOut})\n\tresponseV5.Blocks[\"m\"] = nil\n\ttestResponse(t, \"normal V5\", &responseV5, nil)\n}\n"
  },
  {
    "path": "offset_manager.go",
    "content": "package sarama\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\n// Offset Manager\n\n// OffsetManager uses Kafka to store and fetch consumed partition offsets.\ntype OffsetManager interface {\n\t// ManagePartition creates a PartitionOffsetManager on the given topic/partition.\n\t// It will return an error if this OffsetManager is already managing the given\n\t// topic/partition.\n\tManagePartition(topic string, partition int32) (PartitionOffsetManager, error)\n\n\t// Close stops the OffsetManager from managing offsets. It is required to call\n\t// this function before an OffsetManager object passes out of scope, as it\n\t// will otherwise leak memory. You must call this after all the\n\t// PartitionOffsetManagers are closed.\n\tClose() error\n\n\t// Commit commits the offsets. This method can be used if AutoCommit.Enable is\n\t// set to false.\n\tCommit()\n}\n\ntype offsetManager struct {\n\tclient          Client\n\tconf            *Config\n\tgroup           string\n\tticker          *time.Ticker\n\tsessionCanceler func()\n\n\tmemberID        string\n\tgroupInstanceId *string\n\tgeneration      int32\n\n\tbroker     *Broker\n\tbrokerLock sync.RWMutex\n\n\tpoms     map[string]map[int32]*partitionOffsetManager\n\tpomsLock sync.RWMutex\n\n\tcloseOnce sync.Once\n\tclosing   chan none\n\tclosed    chan none\n}\n\n// NewOffsetManagerFromClient creates a new OffsetManager from the given client.\n// It is still necessary to call Close() on the underlying client when finished with the partition manager.\nfunc NewOffsetManagerFromClient(group string, client Client) (OffsetManager, error) {\n\treturn newOffsetManagerFromClient(group, \"\", GroupGenerationUndefined, client, nil)\n}\n\nfunc newOffsetManagerFromClient(group, memberID string, generation int32, client Client, sessionCanceler func()) (*offsetManager, error) {\n\t// Check that we are not dealing with a closed Client before processing any other arguments\n\tif client.Closed() {\n\t\treturn nil, ErrClosedClient\n\t}\n\n\tconf := client.Config()\n\tom := &offsetManager{\n\t\tclient:          client,\n\t\tconf:            conf,\n\t\tgroup:           group,\n\t\tpoms:            make(map[string]map[int32]*partitionOffsetManager),\n\t\tsessionCanceler: sessionCanceler,\n\n\t\tmemberID:   memberID,\n\t\tgeneration: generation,\n\n\t\tclosing: make(chan none),\n\t\tclosed:  make(chan none),\n\t}\n\tif conf.Consumer.Group.InstanceId != \"\" {\n\t\tom.groupInstanceId = &conf.Consumer.Group.InstanceId\n\t}\n\tif conf.Consumer.Offsets.AutoCommit.Enable {\n\t\tom.ticker = time.NewTicker(conf.Consumer.Offsets.AutoCommit.Interval)\n\t\tgo withRecover(om.mainLoop)\n\t}\n\n\treturn om, nil\n}\n\nfunc (om *offsetManager) ManagePartition(topic string, partition int32) (PartitionOffsetManager, error) {\n\tpom, err := om.newPartitionOffsetManager(topic, partition)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tom.pomsLock.Lock()\n\tdefer om.pomsLock.Unlock()\n\n\ttopicManagers := om.poms[topic]\n\tif topicManagers == nil {\n\t\ttopicManagers = make(map[int32]*partitionOffsetManager)\n\t\tom.poms[topic] = topicManagers\n\t}\n\n\tif topicManagers[partition] != nil {\n\t\treturn nil, ConfigurationError(\"That topic/partition is already being managed\")\n\t}\n\n\ttopicManagers[partition] = pom\n\treturn pom, nil\n}\n\nfunc (om *offsetManager) Close() error {\n\tom.closeOnce.Do(func() {\n\t\t// exit the mainLoop\n\t\tclose(om.closing)\n\t\tif om.conf.Consumer.Offsets.AutoCommit.Enable {\n\t\t\t<-om.closed\n\t\t}\n\n\t\t// mark all POMs as closed\n\t\tom.asyncClosePOMs()\n\n\t\t// flush one last time\n\t\tif om.conf.Consumer.Offsets.AutoCommit.Enable {\n\t\t\tfor attempt := 0; attempt <= om.conf.Consumer.Offsets.Retry.Max; attempt++ {\n\t\t\t\tom.flushToBroker()\n\t\t\t\tif om.releasePOMs(false) == 0 {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tom.releasePOMs(true)\n\t\tom.brokerLock.Lock()\n\t\tom.broker = nil\n\t\tom.brokerLock.Unlock()\n\t})\n\treturn nil\n}\n\nfunc (om *offsetManager) computeBackoff(retries int) time.Duration {\n\tif om.conf.Metadata.Retry.BackoffFunc != nil {\n\t\treturn om.conf.Metadata.Retry.BackoffFunc(retries, om.conf.Metadata.Retry.Max)\n\t} else {\n\t\treturn om.conf.Metadata.Retry.Backoff\n\t}\n}\n\nfunc (om *offsetManager) fetchInitialOffset(topic string, partition int32, retries int) (int64, int32, string, error) {\n\tbroker, err := om.coordinator()\n\tif err != nil {\n\t\tif retries <= 0 {\n\t\t\treturn 0, 0, \"\", err\n\t\t}\n\t\treturn om.fetchInitialOffset(topic, partition, retries-1)\n\t}\n\n\tpartitions := map[string][]int32{topic: {partition}}\n\treq := NewOffsetFetchRequest(om.conf.Version, om.group, partitions)\n\tresp, err := broker.FetchOffset(req)\n\tif err != nil {\n\t\tif retries <= 0 {\n\t\t\treturn 0, 0, \"\", err\n\t\t}\n\t\tom.releaseCoordinator(broker)\n\t\treturn om.fetchInitialOffset(topic, partition, retries-1)\n\t}\n\n\tblock := resp.GetBlock(topic, partition)\n\tif block == nil {\n\t\treturn 0, 0, \"\", ErrIncompleteResponse\n\t}\n\n\tswitch block.Err {\n\tcase ErrNoError:\n\t\treturn block.Offset, block.LeaderEpoch, block.Metadata, nil\n\tcase ErrNotCoordinatorForConsumer:\n\t\tif retries <= 0 {\n\t\t\treturn 0, 0, \"\", block.Err\n\t\t}\n\t\tom.releaseCoordinator(broker)\n\t\treturn om.fetchInitialOffset(topic, partition, retries-1)\n\tcase ErrOffsetsLoadInProgress:\n\t\tif retries <= 0 {\n\t\t\treturn 0, 0, \"\", block.Err\n\t\t}\n\t\tbackoff := om.computeBackoff(retries)\n\t\tselect {\n\t\tcase <-om.closing:\n\t\t\treturn 0, 0, \"\", block.Err\n\t\tcase <-time.After(backoff):\n\t\t}\n\t\treturn om.fetchInitialOffset(topic, partition, retries-1)\n\tdefault:\n\t\treturn 0, 0, \"\", block.Err\n\t}\n}\n\nfunc (om *offsetManager) coordinator() (*Broker, error) {\n\tom.brokerLock.RLock()\n\tbroker := om.broker\n\tom.brokerLock.RUnlock()\n\n\tif broker != nil {\n\t\treturn broker, nil\n\t}\n\n\tom.brokerLock.Lock()\n\tdefer om.brokerLock.Unlock()\n\n\tif broker := om.broker; broker != nil {\n\t\treturn broker, nil\n\t}\n\n\tif err := om.client.RefreshCoordinator(om.group); err != nil {\n\t\treturn nil, err\n\t}\n\n\tbroker, err := om.client.Coordinator(om.group)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tom.broker = broker\n\treturn broker, nil\n}\n\nfunc (om *offsetManager) releaseCoordinator(b *Broker) {\n\tom.brokerLock.Lock()\n\tif om.broker == b {\n\t\tom.broker = nil\n\t}\n\tom.brokerLock.Unlock()\n}\n\nfunc (om *offsetManager) mainLoop() {\n\tdefer om.ticker.Stop()\n\tdefer close(om.closed)\n\n\tfor {\n\t\tselect {\n\t\tcase <-om.ticker.C:\n\t\t\tom.Commit()\n\t\tcase <-om.closing:\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (om *offsetManager) Commit() {\n\tom.flushToBroker()\n\tom.releasePOMs(false)\n}\n\nfunc (om *offsetManager) flushToBroker() {\n\tbroker, err := om.coordinator()\n\tif err != nil {\n\t\tom.handleError(err)\n\t\treturn\n\t}\n\n\t// Care needs to be taken to unlock this. Don't want to defer the unlock as this would\n\t// cause the lock to be held while waiting for the broker to reply.\n\tbroker.lock.Lock()\n\treq := om.constructRequest()\n\tif req == nil {\n\t\tbroker.lock.Unlock()\n\t\treturn\n\t}\n\tresp, rp, err := sendOffsetCommit(broker, req)\n\tbroker.lock.Unlock()\n\n\tif err != nil {\n\t\tom.handleError(err)\n\t\tom.releaseCoordinator(broker)\n\t\t_ = broker.Close()\n\t\treturn\n\t}\n\n\terr = handleResponsePromise(req, resp, rp, nil)\n\tif err != nil {\n\t\tom.handleError(err)\n\t\tom.releaseCoordinator(broker)\n\t\t_ = broker.Close()\n\t\treturn\n\t}\n\n\tbroker.handleThrottledResponse(resp)\n\tom.handleResponse(broker, req, resp)\n}\n\nfunc sendOffsetCommit(coordinator *Broker, req *OffsetCommitRequest) (*OffsetCommitResponse, *responsePromise, error) {\n\tresp := new(OffsetCommitResponse)\n\n\tpromise, err := coordinator.send(req, resp)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\treturn resp, promise, nil\n}\n\nfunc (om *offsetManager) constructRequest() *OffsetCommitRequest {\n\tr := &OffsetCommitRequest{\n\t\tVersion:                 1,\n\t\tConsumerGroup:           om.group,\n\t\tConsumerID:              om.memberID,\n\t\tConsumerGroupGeneration: om.generation,\n\t}\n\t// Version 1 adds timestamp and group membership information, as well as the commit timestamp.\n\t//\n\t// Version 2 adds retention time.  It removes the commit timestamp added in version 1.\n\tif om.conf.Version.IsAtLeast(V0_9_0_0) {\n\t\tr.Version = 2\n\t}\n\t// Version 3 and 4 are the same as version 2.\n\tif om.conf.Version.IsAtLeast(V0_11_0_0) {\n\t\tr.Version = 3\n\t}\n\tif om.conf.Version.IsAtLeast(V2_0_0_0) {\n\t\tr.Version = 4\n\t}\n\t// Version 5 removes the retention time, which is now controlled only by a broker configuration.\n\t//\n\t// Version 6 adds the leader epoch for fencing.\n\tif om.conf.Version.IsAtLeast(V2_1_0_0) {\n\t\tr.Version = 6\n\t}\n\t// version 7 adds a new field called groupInstanceId to indicate member identity across restarts.\n\tif om.conf.Version.IsAtLeast(V2_3_0_0) {\n\t\tr.Version = 7\n\t\tr.GroupInstanceId = om.groupInstanceId\n\t}\n\n\t// commit timestamp was only briefly supported in V1 where we set it to\n\t// ReceiveTime (-1) to tell the broker to set it to the time when the commit\n\t// request was received\n\tvar commitTimestamp int64\n\tif r.Version == 1 {\n\t\tcommitTimestamp = ReceiveTime\n\t}\n\n\t// request controlled retention was only supported from V2-V4 (it became\n\t// broker-only after that) so if the user has set the config options then\n\t// flow those through as retention time on the commit request.\n\tif r.Version >= 2 && r.Version < 5 {\n\t\t// Map Sarama's default of 0 to Kafka's default of -1\n\t\tr.RetentionTime = -1\n\t\tif om.conf.Consumer.Offsets.Retention > 0 {\n\t\t\tr.RetentionTime = int64(om.conf.Consumer.Offsets.Retention / time.Millisecond)\n\t\t}\n\t}\n\n\tom.pomsLock.RLock()\n\tdefer om.pomsLock.RUnlock()\n\n\tfor _, topicManagers := range om.poms {\n\t\tfor _, pom := range topicManagers {\n\t\t\tpom.lock.Lock()\n\t\t\tif pom.dirty {\n\t\t\t\tr.AddBlockWithLeaderEpoch(pom.topic, pom.partition, pom.offset, pom.leaderEpoch, commitTimestamp, pom.metadata)\n\t\t\t}\n\t\t\tpom.lock.Unlock()\n\t\t}\n\t}\n\n\tif len(r.blocks) > 0 {\n\t\treturn r\n\t}\n\n\treturn nil\n}\n\nfunc (om *offsetManager) handleResponse(broker *Broker, req *OffsetCommitRequest, resp *OffsetCommitResponse) {\n\tom.pomsLock.RLock()\n\tdefer om.pomsLock.RUnlock()\n\n\tfor _, topicManagers := range om.poms {\n\t\tfor _, pom := range topicManagers {\n\t\t\tif req.blocks[pom.topic] == nil || req.blocks[pom.topic][pom.partition] == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tvar err KError\n\t\t\tvar ok bool\n\n\t\t\tif resp.Errors[pom.topic] == nil {\n\t\t\t\tpom.handleError(ErrIncompleteResponse)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif err, ok = resp.Errors[pom.topic][pom.partition]; !ok {\n\t\t\t\tpom.handleError(ErrIncompleteResponse)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tswitch err {\n\t\t\tcase ErrNoError:\n\t\t\t\tblock := req.blocks[pom.topic][pom.partition]\n\t\t\t\tpom.updateCommitted(block.offset, block.metadata)\n\t\t\tcase ErrNotLeaderForPartition, ErrLeaderNotAvailable,\n\t\t\t\tErrConsumerCoordinatorNotAvailable, ErrNotCoordinatorForConsumer:\n\t\t\t\t// not a critical error, we just need to redispatch\n\t\t\t\tom.releaseCoordinator(broker)\n\t\t\tcase ErrOffsetMetadataTooLarge, ErrInvalidCommitOffsetSize:\n\t\t\t\t// nothing we can do about this, just tell the user and carry on\n\t\t\t\tpom.handleError(err)\n\t\t\tcase ErrOffsetsLoadInProgress:\n\t\t\t\t// nothing wrong but we didn't commit, we'll get it next time round\n\t\t\tcase ErrFencedInstancedId:\n\t\t\t\tpom.handleError(err)\n\t\t\t\t// TODO close the whole consumer for instance fenced....\n\t\t\t\tom.tryCancelSession()\n\t\t\tcase ErrUnknownTopicOrPartition:\n\t\t\t\t// let the user know *and* try redispatching - if topic-auto-create is\n\t\t\t\t// enabled, redispatching should trigger a metadata req and create the\n\t\t\t\t// topic; if not then re-dispatching won't help, but we've let the user\n\t\t\t\t// know and it shouldn't hurt either (see https://github.com/IBM/sarama/issues/706)\n\t\t\t\tfallthrough\n\t\t\tdefault:\n\t\t\t\t// dunno, tell the user and try redispatching\n\t\t\t\tpom.handleError(err)\n\t\t\t\tom.releaseCoordinator(broker)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (om *offsetManager) handleError(err error) {\n\tom.pomsLock.RLock()\n\tdefer om.pomsLock.RUnlock()\n\n\tfor _, topicManagers := range om.poms {\n\t\tfor _, pom := range topicManagers {\n\t\t\tpom.handleError(err)\n\t\t}\n\t}\n}\n\nfunc (om *offsetManager) asyncClosePOMs() {\n\tom.pomsLock.RLock()\n\tdefer om.pomsLock.RUnlock()\n\n\tfor _, topicManagers := range om.poms {\n\t\tfor _, pom := range topicManagers {\n\t\t\tpom.AsyncClose()\n\t\t}\n\t}\n}\n\n// Releases/removes closed POMs once they are clean (or when forced)\nfunc (om *offsetManager) releasePOMs(force bool) (remaining int) {\n\tom.pomsLock.Lock()\n\tdefer om.pomsLock.Unlock()\n\n\tfor topic, topicManagers := range om.poms {\n\t\tfor partition, pom := range topicManagers {\n\t\t\tpom.lock.Lock()\n\t\t\treleaseDue := pom.done && (force || !pom.dirty)\n\t\t\tpom.lock.Unlock()\n\n\t\t\tif releaseDue {\n\t\t\t\tpom.release()\n\n\t\t\t\tdelete(om.poms[topic], partition)\n\t\t\t\tif len(om.poms[topic]) == 0 {\n\t\t\t\t\tdelete(om.poms, topic)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tremaining += len(om.poms[topic])\n\t}\n\treturn\n}\n\nfunc (om *offsetManager) findPOM(topic string, partition int32) *partitionOffsetManager {\n\tom.pomsLock.RLock()\n\tdefer om.pomsLock.RUnlock()\n\n\tif partitions, ok := om.poms[topic]; ok {\n\t\tif pom, ok := partitions[partition]; ok {\n\t\t\treturn pom\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (om *offsetManager) tryCancelSession() {\n\tif om.sessionCanceler != nil {\n\t\tom.sessionCanceler()\n\t}\n}\n\n// Partition Offset Manager\n\n// PartitionOffsetManager uses Kafka to store and fetch consumed partition offsets. You MUST call Close()\n// on a partition offset manager to avoid leaks, it will not be garbage-collected automatically when it passes\n// out of scope.\ntype PartitionOffsetManager interface {\n\t// NextOffset returns the next offset that should be consumed for the managed\n\t// partition, accompanied by metadata which can be used to reconstruct the state\n\t// of the partition consumer when it resumes. NextOffset() will return\n\t// `config.Consumer.Offsets.Initial` and an empty metadata string if no offset\n\t// was committed for this partition yet.\n\tNextOffset() (int64, string)\n\n\t// MarkOffset marks the provided offset, alongside a metadata string\n\t// that represents the state of the partition consumer at that point in time. The\n\t// metadata string can be used by another consumer to restore that state, so it\n\t// can resume consumption.\n\t//\n\t// To follow upstream conventions, you are expected to mark the offset of the\n\t// next message to read, not the last message read. Thus, when calling `MarkOffset`\n\t// you should typically add one to the offset of the last consumed message.\n\t//\n\t// Note: calling MarkOffset does not necessarily commit the offset to the backend\n\t// store immediately for efficiency reasons, and it may never be committed if\n\t// your application crashes. This means that you may end up processing the same\n\t// message twice, and your processing should ideally be idempotent.\n\tMarkOffset(offset int64, metadata string)\n\n\t// ResetOffset resets to the provided offset, alongside a metadata string that\n\t// represents the state of the partition consumer at that point in time. Reset\n\t// acts as a counterpart to MarkOffset, the difference being that it allows to\n\t// reset an offset to an earlier or smaller value, where MarkOffset only\n\t// allows incrementing the offset. cf MarkOffset for more details.\n\tResetOffset(offset int64, metadata string)\n\n\t// Errors returns a read channel of errors that occur during offset management, if\n\t// enabled. By default, errors are logged and not returned over this channel. If\n\t// you want to implement any custom error handling, set your config's\n\t// Consumer.Return.Errors setting to true, and read from this channel.\n\tErrors() <-chan *ConsumerError\n\n\t// AsyncClose initiates a shutdown of the PartitionOffsetManager. This method will\n\t// return immediately, after which you should wait until the 'errors' channel has\n\t// been drained and closed. It is required to call this function, or Close before\n\t// a consumer object passes out of scope, as it will otherwise leak memory. You\n\t// must call this before calling Close on the underlying client.\n\tAsyncClose()\n\n\t// Close stops the PartitionOffsetManager from managing offsets. It is required to\n\t// call this function (or AsyncClose) before a PartitionOffsetManager object\n\t// passes out of scope, as it will otherwise leak memory. You must call this\n\t// before calling Close on the underlying client.\n\tClose() error\n}\n\ntype partitionOffsetManager struct {\n\tparent      *offsetManager\n\ttopic       string\n\tpartition   int32\n\tleaderEpoch int32\n\n\tlock     sync.Mutex\n\toffset   int64\n\tmetadata string\n\tdirty    bool\n\tdone     bool\n\n\treleaseOnce sync.Once\n\terrors      chan *ConsumerError\n}\n\nfunc (om *offsetManager) newPartitionOffsetManager(topic string, partition int32) (*partitionOffsetManager, error) {\n\toffset, leaderEpoch, metadata, err := om.fetchInitialOffset(topic, partition, om.conf.Metadata.Retry.Max)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &partitionOffsetManager{\n\t\tparent:      om,\n\t\ttopic:       topic,\n\t\tpartition:   partition,\n\t\tleaderEpoch: leaderEpoch,\n\t\terrors:      make(chan *ConsumerError, om.conf.ChannelBufferSize),\n\t\toffset:      offset,\n\t\tmetadata:    metadata,\n\t}, nil\n}\n\nfunc (pom *partitionOffsetManager) Errors() <-chan *ConsumerError {\n\treturn pom.errors\n}\n\nfunc (pom *partitionOffsetManager) MarkOffset(offset int64, metadata string) {\n\tpom.lock.Lock()\n\tdefer pom.lock.Unlock()\n\n\tif offset > pom.offset {\n\t\tpom.offset = offset\n\t\tpom.metadata = metadata\n\t\tpom.dirty = true\n\t}\n}\n\nfunc (pom *partitionOffsetManager) ResetOffset(offset int64, metadata string) {\n\tpom.lock.Lock()\n\tdefer pom.lock.Unlock()\n\n\tif offset <= pom.offset {\n\t\tpom.offset = offset\n\t\tpom.metadata = metadata\n\t\tpom.dirty = true\n\t}\n}\n\nfunc (pom *partitionOffsetManager) updateCommitted(offset int64, metadata string) {\n\tpom.lock.Lock()\n\tdefer pom.lock.Unlock()\n\n\tif pom.offset == offset && pom.metadata == metadata {\n\t\tpom.dirty = false\n\t}\n}\n\nfunc (pom *partitionOffsetManager) NextOffset() (int64, string) {\n\tpom.lock.Lock()\n\tdefer pom.lock.Unlock()\n\n\tif pom.offset >= 0 {\n\t\treturn pom.offset, pom.metadata\n\t}\n\n\treturn pom.parent.conf.Consumer.Offsets.Initial, \"\"\n}\n\nfunc (pom *partitionOffsetManager) AsyncClose() {\n\tpom.lock.Lock()\n\tpom.done = true\n\tpom.lock.Unlock()\n}\n\nfunc (pom *partitionOffsetManager) Close() error {\n\tpom.AsyncClose()\n\n\tvar errors ConsumerErrors\n\tfor err := range pom.errors {\n\t\terrors = append(errors, err)\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn errors\n\t}\n\treturn nil\n}\n\nfunc (pom *partitionOffsetManager) handleError(err error) {\n\tcErr := &ConsumerError{\n\t\tTopic:     pom.topic,\n\t\tPartition: pom.partition,\n\t\tErr:       err,\n\t}\n\n\tif pom.parent.conf.Consumer.Return.Errors {\n\t\tpom.errors <- cErr\n\t} else {\n\t\tLogger.Println(cErr)\n\t}\n}\n\nfunc (pom *partitionOffsetManager) release() {\n\tpom.releaseOnce.Do(func() {\n\t\tclose(pom.errors)\n\t})\n}\n"
  },
  {
    "path": "offset_manager_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"runtime\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc initOffsetManagerWithBackoffFunc(\n\tt *testing.T,\n\tretention time.Duration,\n\tbackoffFunc func(retries, maxRetries int) time.Duration, config *Config,\n) (om OffsetManager, testClient Client, broker, coordinator *MockBroker) {\n\tconfig.Metadata.Retry.Max = 1\n\tif backoffFunc != nil {\n\t\tconfig.Metadata.Retry.BackoffFunc = backoffFunc\n\t}\n\tconfig.Consumer.Offsets.AutoCommit.Interval = 1 * time.Millisecond\n\tconfig.Version = V0_9_0_0\n\tif retention > 0 {\n\t\tconfig.Consumer.Offsets.Retention = retention\n\t}\n\n\tbroker = NewMockBroker(t, 1)\n\tcoordinator = NewMockBroker(t, 2)\n\n\tseedMeta := new(MetadataResponse)\n\tseedMeta.AddBroker(coordinator.Addr(), coordinator.BrokerID())\n\tseedMeta.AddTopicPartition(\"my_topic\", 0, 1, []int32{}, []int32{}, []int32{}, ErrNoError)\n\tseedMeta.AddTopicPartition(\"my_topic\", 1, 1, []int32{}, []int32{}, []int32{}, ErrNoError)\n\tbroker.Returns(seedMeta)\n\n\tvar err error\n\ttestClient, err = NewClient([]string{broker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tcoordinator.Returns(&ConsumerMetadataResponse{\n\t\tCoordinatorID:   coordinator.BrokerID(),\n\t\tCoordinatorHost: \"127.0.0.1\",\n\t\tCoordinatorPort: coordinator.Port(),\n\t})\n\n\tom, err = NewOffsetManagerFromClient(\"group\", testClient)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn om, testClient, broker, coordinator\n}\n\nfunc initOffsetManager(t *testing.T, retention time.Duration) (om OffsetManager,\n\ttestClient Client, broker, coordinator *MockBroker,\n) {\n\treturn initOffsetManagerWithBackoffFunc(t, retention, nil, NewTestConfig())\n}\n\nfunc initPartitionOffsetManager(t *testing.T, om OffsetManager,\n\tcoordinator *MockBroker, initialOffset int64, metadata string,\n) PartitionOffsetManager {\n\tfetchResponse := new(OffsetFetchResponse)\n\tfetchResponse.AddBlock(\"my_topic\", 0, &OffsetFetchResponseBlock{\n\t\tErr:      ErrNoError,\n\t\tOffset:   initialOffset,\n\t\tMetadata: metadata,\n\t})\n\tcoordinator.Returns(fetchResponse)\n\n\tpom, err := om.ManagePartition(\"my_topic\", 0)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn pom\n}\n\nfunc TestNewOffsetManager(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tmetadataResponse := new(MetadataResponse)\n\tmetadataResponse.AddBroker(seedBroker.Addr(), seedBroker.BrokerID())\n\tseedBroker.Returns(metadataResponse)\n\tfindCoordResponse := new(FindCoordinatorResponse)\n\tfindCoordResponse.Coordinator = &Broker{id: seedBroker.brokerID, addr: seedBroker.Addr()}\n\tseedBroker.Returns(findCoordResponse)\n\tdefer seedBroker.Close()\n\n\ttestClient, err := NewClient([]string{seedBroker.Addr()}, NewTestConfig())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tom, err := NewOffsetManagerFromClient(\"group\", testClient)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tsafeClose(t, om)\n\tsafeClose(t, testClient)\n\n\t_, err = NewOffsetManagerFromClient(\"group\", testClient)\n\tif !errors.Is(err, ErrClosedClient) {\n\t\tt.Errorf(\"Error expected for closed client; actual value: %v\", err)\n\t}\n}\n\n// Test that the correct sequence of offset commit messages is sent to a broker when\n// multiple goroutines for a group are committing offsets at the same time\nfunc TestOffsetManagerCommitSequence(t *testing.T) {\n\tlastOffset := make(map[int32]int64)\n\tvar outOfOrder atomic.Pointer[string]\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\tseedBroker.SetHandlerFuncByMap(map[string]requestHandlerFunc{\n\t\t\"MetadataRequest\": func(req *request) encoderWithHeader {\n\t\t\tresp := new(MetadataResponse)\n\t\t\tresp.AddBroker(seedBroker.Addr(), seedBroker.BrokerID())\n\t\t\treturn resp\n\t\t},\n\t\t\"FindCoordinatorRequest\": func(req *request) encoderWithHeader {\n\t\t\tresp := new(FindCoordinatorResponse)\n\t\t\tresp.Coordinator = &Broker{id: seedBroker.brokerID, addr: seedBroker.Addr()}\n\t\t\treturn resp\n\t\t},\n\t\t\"OffsetFetchRequest\": func(r *request) encoderWithHeader {\n\t\t\treq := r.body.(*OffsetFetchRequest)\n\t\t\tresp := new(OffsetFetchResponse)\n\t\t\tresp.Blocks = map[string]map[int32]*OffsetFetchResponseBlock{}\n\t\t\tfor topic, partitions := range req.partitions {\n\t\t\t\tfor _, partition := range partitions {\n\t\t\t\t\tif _, ok := resp.Blocks[topic]; !ok {\n\t\t\t\t\t\tresp.Blocks[topic] = map[int32]*OffsetFetchResponseBlock{}\n\t\t\t\t\t}\n\t\t\t\t\tresp.Blocks[topic][partition] = &OffsetFetchResponseBlock{\n\t\t\t\t\t\tOffset: 0,\n\t\t\t\t\t\tErr:    ErrNoError,\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn resp\n\t\t},\n\t\t\"OffsetCommitRequest\": func(r *request) encoderWithHeader {\n\t\t\treq := r.body.(*OffsetCommitRequest)\n\t\t\tif outOfOrder.Load() == nil {\n\t\t\t\tfor partition, offset := range req.blocks[\"topic\"] {\n\t\t\t\t\tlast := lastOffset[partition]\n\t\t\t\t\tif last > offset.offset {\n\t\t\t\t\t\tmsg := fmt.Sprintf(\"out of order commit to partition %d, current committed offset: %d, offset in request: %d\",\n\t\t\t\t\t\t\tpartition, last, offset.offset)\n\t\t\t\t\t\toutOfOrder.Store(&msg)\n\t\t\t\t\t}\n\t\t\t\t\tlastOffset[partition] = offset.offset\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Potentially yield, to try and avoid each Go routine running sequentially to completion\n\t\t\truntime.Gosched()\n\n\t\t\tresp := new(OffsetCommitResponse)\n\t\t\tresp.Errors = map[string]map[int32]KError{}\n\t\t\tresp.Errors[\"topic\"] = map[int32]KError{}\n\t\t\tfor partition := range req.blocks[\"topic\"] {\n\t\t\t\tresp.Errors[\"topic\"][partition] = ErrNoError\n\t\t\t}\n\t\t\treturn resp\n\t\t},\n\t})\n\ttestClient, err := NewClient([]string{seedBroker.Addr()}, NewTestConfig())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer safeClose(t, testClient)\n\tom, err := NewOffsetManagerFromClient(\"group\", testClient)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer safeClose(t, om)\n\n\tconst numPartitions = 10\n\tconst commitsPerPartition = 1000\n\n\tvar wg sync.WaitGroup\n\tfor p := 0; p < numPartitions; p++ {\n\t\tpom, err := om.ManagePartition(\"topic\", int32(p))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tfor c := 0; c < commitsPerPartition; c++ {\n\t\t\t\tpom.MarkOffset(int64(c+1), \"\")\n\t\t\t\tom.Commit()\n\t\t\t}\n\t\t\twg.Done()\n\t\t}()\n\t}\n\n\twg.Wait()\n\terrMsg := outOfOrder.Load()\n\tif errMsg != nil {\n\t\tt.Error(*errMsg)\n\t}\n}\n\nvar offsetsautocommitTestTable = []struct {\n\tname   string\n\tset    bool // if given will override default configuration for Consumer.Offsets.AutoCommit.Enable\n\tenable bool\n}{\n\t{\n\t\t\"AutoCommit (default)\",\n\t\tfalse, // use default\n\t\ttrue,\n\t},\n\t{\n\t\t\"AutoCommit Enabled\",\n\t\ttrue,\n\t\ttrue,\n\t},\n\t{\n\t\t\"AutoCommit Disabled\",\n\t\ttrue,\n\t\tfalse,\n\t},\n}\n\nfunc TestNewOffsetManagerOffsetsAutoCommit(t *testing.T) {\n\t// Tests to validate configuration of `Consumer.Offsets.AutoCommit.Enable`\n\tfor _, tt := range offsetsautocommitTestTable {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tconfig := NewTestConfig()\n\t\t\tif tt.set {\n\t\t\t\tconfig.Consumer.Offsets.AutoCommit.Enable = tt.enable\n\t\t\t}\n\t\t\tom, testClient, broker, coordinator := initOffsetManagerWithBackoffFunc(t, 0, nil, config)\n\t\t\tdefer broker.Close()\n\t\t\tdefer coordinator.Close()\n\t\t\tpom := initPartitionOffsetManager(t, om, coordinator, 5, \"original_meta\")\n\n\t\t\t// Wait long enough for the test not to fail..\n\t\t\ttimeout := 50 * config.Consumer.Offsets.AutoCommit.Interval\n\n\t\t\tcalled := make(chan none)\n\n\t\t\tocResponse := new(OffsetCommitResponse)\n\t\t\tocResponse.AddError(\"my_topic\", 0, ErrNoError)\n\t\t\thandler := func(req *request) (res encoderWithHeader) {\n\t\t\t\tclose(called)\n\t\t\t\treturn ocResponse\n\t\t\t}\n\t\t\tcoordinator.setHandler(handler)\n\n\t\t\t// Should force an offset commit, if auto-commit is enabled.\n\t\t\texpected := int64(1)\n\t\t\tpom.ResetOffset(expected, \"modified_meta\")\n\t\t\t_, _ = pom.NextOffset()\n\n\t\t\tselect {\n\t\t\tcase <-called:\n\t\t\t\t// OffsetManager called on the wire.\n\t\t\t\tif !config.Consumer.Offsets.AutoCommit.Enable {\n\t\t\t\t\tt.Errorf(\"Received request for: %s when AutoCommit is disabled\", tt.name)\n\t\t\t\t}\n\t\t\tcase <-time.After(timeout):\n\t\t\t\t// Timeout waiting for OffsetManager to call on the wire.\n\t\t\t\tif config.Consumer.Offsets.AutoCommit.Enable {\n\t\t\t\t\tt.Errorf(\"No request received for: %s after waiting for %v\", tt.name, timeout)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// !! om must be closed before the pom so pom.release() is called before pom.Close()\n\t\t\tsafeClose(t, om)\n\t\t\tsafeClose(t, pom)\n\t\t\tsafeClose(t, testClient)\n\t\t})\n\t}\n}\n\nfunc TestNewOffsetManagerOffsetsManualCommit(t *testing.T) {\n\t// Tests to validate configuration when `Consumer.Offsets.AutoCommit.Enable` is false\n\tconfig := NewTestConfig()\n\tconfig.Consumer.Offsets.AutoCommit.Enable = false\n\n\tom, testClient, broker, coordinator := initOffsetManagerWithBackoffFunc(t, 0, nil, config)\n\tdefer broker.Close()\n\tdefer coordinator.Close()\n\tpom := initPartitionOffsetManager(t, om, coordinator, 5, \"original_meta\")\n\n\t// Wait long enough for the test not to fail..\n\ttimeout := 50 * config.Consumer.Offsets.AutoCommit.Interval\n\n\tocResponse := new(OffsetCommitResponse)\n\tocResponse.AddError(\"my_topic\", 0, ErrNoError)\n\tcalled := make(chan none)\n\thandler := func(req *request) (res encoderWithHeader) {\n\t\tclose(called)\n\t\treturn ocResponse\n\t}\n\tcoordinator.setHandler(handler)\n\n\t// Should not trigger an auto-commit\n\texpected := int64(1)\n\tpom.ResetOffset(expected, \"modified_meta\")\n\t_, _ = pom.NextOffset()\n\n\tselect {\n\tcase <-called:\n\t\t// OffsetManager called on the wire.\n\t\tt.Errorf(\"Received request when AutoCommit is disabled\")\n\tcase <-time.After(timeout):\n\t\t// Timeout waiting for OffsetManager to call on the wire.\n\t\t// OK\n\t}\n\n\t// Setup again to test manual commit\n\tcalled = make(chan none)\n\n\tom.Commit()\n\n\tselect {\n\tcase <-called:\n\t\t// OffsetManager called on the wire.\n\t\t// OK\n\tcase <-time.After(timeout):\n\t\t// Timeout waiting for OffsetManager to call on the wire.\n\t\tt.Errorf(\"No request received for after waiting for %v\", timeout)\n\t}\n\n\t// !! om must be closed before the pom so pom.release() is called before pom.Close()\n\tsafeClose(t, om)\n\tsafeClose(t, pom)\n\tsafeClose(t, testClient)\n}\n\n// Test recovery from ErrNotCoordinatorForConsumer\n// on first fetchInitialOffset call\nfunc TestOffsetManagerFetchInitialFail(t *testing.T) {\n\tom, testClient, broker, coordinator := initOffsetManager(t, 0)\n\tdefer broker.Close()\n\tdefer coordinator.Close()\n\n\t// Error on first fetchInitialOffset call\n\tresponseBlock := OffsetFetchResponseBlock{\n\t\tErr:      ErrNotCoordinatorForConsumer,\n\t\tOffset:   5,\n\t\tMetadata: \"test_meta\",\n\t}\n\n\tfetchResponse := new(OffsetFetchResponse)\n\tfetchResponse.AddBlock(\"my_topic\", 0, &responseBlock)\n\tcoordinator.Returns(fetchResponse)\n\n\t// Refresh coordinator\n\tnewCoordinator := NewMockBroker(t, 3)\n\tdefer newCoordinator.Close()\n\tcoordinator.Returns(&ConsumerMetadataResponse{\n\t\tCoordinatorID:   newCoordinator.BrokerID(),\n\t\tCoordinatorHost: \"127.0.0.1\",\n\t\tCoordinatorPort: newCoordinator.Port(),\n\t})\n\n\t// Second fetchInitialOffset call is fine\n\tfetchResponse2 := new(OffsetFetchResponse)\n\tresponseBlock2 := responseBlock\n\tresponseBlock2.Err = ErrNoError\n\tfetchResponse2.AddBlock(\"my_topic\", 0, &responseBlock2)\n\tnewCoordinator.Returns(fetchResponse2)\n\n\tpom, err := om.ManagePartition(\"my_topic\", 0)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tsafeClose(t, pom)\n\tsafeClose(t, om)\n\tsafeClose(t, testClient)\n}\n\n// Test fetchInitialOffset retry on ErrOffsetsLoadInProgress\nfunc TestOffsetManagerFetchInitialLoadInProgress(t *testing.T) {\n\tvar retryCount atomic.Int32\n\tbackoff := func(retries, maxRetries int) time.Duration {\n\t\tretryCount.Add(1)\n\t\treturn 0\n\t}\n\tom, testClient, broker, coordinator := initOffsetManagerWithBackoffFunc(t, 0, backoff, NewTestConfig())\n\tdefer broker.Close()\n\tdefer coordinator.Close()\n\n\t// Error on first fetchInitialOffset call\n\tresponseBlock := OffsetFetchResponseBlock{\n\t\tErr:      ErrOffsetsLoadInProgress,\n\t\tOffset:   5,\n\t\tMetadata: \"test_meta\",\n\t}\n\n\tfetchResponse := new(OffsetFetchResponse)\n\tfetchResponse.AddBlock(\"my_topic\", 0, &responseBlock)\n\tcoordinator.Returns(fetchResponse)\n\n\t// Second fetchInitialOffset call is fine\n\tfetchResponse2 := new(OffsetFetchResponse)\n\tresponseBlock2 := responseBlock\n\tresponseBlock2.Err = ErrNoError\n\tfetchResponse2.AddBlock(\"my_topic\", 0, &responseBlock2)\n\tcoordinator.Returns(fetchResponse2)\n\n\tpom, err := om.ManagePartition(\"my_topic\", 0)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tsafeClose(t, pom)\n\tsafeClose(t, om)\n\tsafeClose(t, testClient)\n\n\tif retryCount.Load() == 0 {\n\t\tt.Fatal(\"Expected at least one retry\")\n\t}\n}\n\nfunc TestPartitionOffsetManagerInitialOffset(t *testing.T) {\n\tom, testClient, broker, coordinator := initOffsetManager(t, 0)\n\tdefer broker.Close()\n\tdefer coordinator.Close()\n\ttestClient.Config().Consumer.Offsets.Initial = OffsetOldest\n\n\t// Kafka returns -1 if no offset has been stored for this partition yet.\n\tpom := initPartitionOffsetManager(t, om, coordinator, -1, \"\")\n\n\toffset, meta := pom.NextOffset()\n\tif offset != OffsetOldest {\n\t\tt.Errorf(\"Expected offset 5. Actual: %v\", offset)\n\t}\n\tif meta != \"\" {\n\t\tt.Errorf(\"Expected metadata to be empty. Actual: %q\", meta)\n\t}\n\n\tsafeClose(t, pom)\n\tsafeClose(t, om)\n\tsafeClose(t, testClient)\n}\n\nfunc TestPartitionOffsetManagerNextOffset(t *testing.T) {\n\tom, testClient, broker, coordinator := initOffsetManager(t, 0)\n\tdefer broker.Close()\n\tdefer coordinator.Close()\n\tpom := initPartitionOffsetManager(t, om, coordinator, 5, \"test_meta\")\n\n\toffset, meta := pom.NextOffset()\n\tif offset != 5 {\n\t\tt.Errorf(\"Expected offset 5. Actual: %v\", offset)\n\t}\n\tif meta != \"test_meta\" {\n\t\tt.Errorf(\"Expected metadata \\\"test_meta\\\". Actual: %q\", meta)\n\t}\n\n\tsafeClose(t, pom)\n\tsafeClose(t, om)\n\tsafeClose(t, testClient)\n}\n\nfunc TestPartitionOffsetManagerResetOffset(t *testing.T) {\n\tom, testClient, broker, coordinator := initOffsetManager(t, 0)\n\tdefer broker.Close()\n\tdefer coordinator.Close()\n\tpom := initPartitionOffsetManager(t, om, coordinator, 5, \"original_meta\")\n\n\tocResponse := new(OffsetCommitResponse)\n\tocResponse.AddError(\"my_topic\", 0, ErrNoError)\n\tcoordinator.Returns(ocResponse)\n\n\texpected := int64(1)\n\tpom.ResetOffset(expected, \"modified_meta\")\n\tactual, meta := pom.NextOffset()\n\n\tif actual != expected {\n\t\tt.Errorf(\"Expected offset %v. Actual: %v\", expected, actual)\n\t}\n\tif meta != \"modified_meta\" {\n\t\tt.Errorf(\"Expected metadata \\\"modified_meta\\\". Actual: %q\", meta)\n\t}\n\n\tsafeClose(t, pom)\n\tsafeClose(t, om)\n\tsafeClose(t, testClient)\n}\n\nfunc TestPartitionOffsetManagerResetOffsetWithRetention(t *testing.T) {\n\tom, testClient, broker, coordinator := initOffsetManager(t, time.Hour)\n\tdefer broker.Close()\n\tdefer coordinator.Close()\n\tpom := initPartitionOffsetManager(t, om, coordinator, 5, \"original_meta\")\n\n\tocResponse := new(OffsetCommitResponse)\n\tocResponse.AddError(\"my_topic\", 0, ErrNoError)\n\thandler := func(req *request) (res encoderWithHeader) {\n\t\tif req.body.version() != 2 {\n\t\t\tt.Errorf(\"Expected to be using version 2. Actual: %v\", req.body.version())\n\t\t}\n\t\toffsetCommitRequest := req.body.(*OffsetCommitRequest)\n\t\tif offsetCommitRequest.RetentionTime != (60 * 60 * 1000) {\n\t\t\tt.Errorf(\"Expected an hour retention time. Actual: %v\", offsetCommitRequest.RetentionTime)\n\t\t}\n\t\treturn ocResponse\n\t}\n\tcoordinator.setHandler(handler)\n\n\texpected := int64(1)\n\tpom.ResetOffset(expected, \"modified_meta\")\n\tactual, meta := pom.NextOffset()\n\n\tif actual != expected {\n\t\tt.Errorf(\"Expected offset %v. Actual: %v\", expected, actual)\n\t}\n\tif meta != \"modified_meta\" {\n\t\tt.Errorf(\"Expected metadata \\\"modified_meta\\\". Actual: %q\", meta)\n\t}\n\n\tsafeClose(t, pom)\n\tsafeClose(t, om)\n\tsafeClose(t, testClient)\n}\n\nfunc TestPartitionOffsetManagerMarkOffset(t *testing.T) {\n\tom, testClient, broker, coordinator := initOffsetManager(t, 0)\n\tdefer broker.Close()\n\tdefer coordinator.Close()\n\tpom := initPartitionOffsetManager(t, om, coordinator, 5, \"original_meta\")\n\n\tocResponse := new(OffsetCommitResponse)\n\tocResponse.AddError(\"my_topic\", 0, ErrNoError)\n\tcoordinator.Returns(ocResponse)\n\n\tpom.MarkOffset(100, \"modified_meta\")\n\toffset, meta := pom.NextOffset()\n\n\tif offset != 100 {\n\t\tt.Errorf(\"Expected offset 100. Actual: %v\", offset)\n\t}\n\tif meta != \"modified_meta\" {\n\t\tt.Errorf(\"Expected metadata \\\"modified_meta\\\". Actual: %q\", meta)\n\t}\n\n\tsafeClose(t, pom)\n\tsafeClose(t, om)\n\tsafeClose(t, testClient)\n}\n\nfunc TestPartitionOffsetManagerMarkOffsetWithRetention(t *testing.T) {\n\tom, testClient, broker, coordinator := initOffsetManager(t, time.Hour)\n\tdefer broker.Close()\n\tdefer coordinator.Close()\n\tpom := initPartitionOffsetManager(t, om, coordinator, 5, \"original_meta\")\n\n\tocResponse := new(OffsetCommitResponse)\n\tocResponse.AddError(\"my_topic\", 0, ErrNoError)\n\thandler := func(req *request) (res encoderWithHeader) {\n\t\tif req.body.version() != 2 {\n\t\t\tt.Errorf(\"Expected to be using version 2. Actual: %v\", req.body.version())\n\t\t}\n\t\toffsetCommitRequest := req.body.(*OffsetCommitRequest)\n\t\tif offsetCommitRequest.RetentionTime != (60 * 60 * 1000) {\n\t\t\tt.Errorf(\"Expected an hour retention time. Actual: %v\", offsetCommitRequest.RetentionTime)\n\t\t}\n\t\treturn ocResponse\n\t}\n\tcoordinator.setHandler(handler)\n\n\tpom.MarkOffset(100, \"modified_meta\")\n\toffset, meta := pom.NextOffset()\n\n\tif offset != 100 {\n\t\tt.Errorf(\"Expected offset 100. Actual: %v\", offset)\n\t}\n\tif meta != \"modified_meta\" {\n\t\tt.Errorf(\"Expected metadata \\\"modified_meta\\\". Actual: %q\", meta)\n\t}\n\n\tsafeClose(t, pom)\n\tsafeClose(t, om)\n\tsafeClose(t, testClient)\n}\n\nfunc TestPartitionOffsetManagerCommitErr(t *testing.T) {\n\tom, testClient, broker, coordinator := initOffsetManager(t, 0)\n\tdefer broker.Close()\n\tdefer coordinator.Close()\n\tpom := initPartitionOffsetManager(t, om, coordinator, 5, \"meta\")\n\n\t// Error on one partition\n\tocResponse := new(OffsetCommitResponse)\n\tocResponse.AddError(\"my_topic\", 0, ErrOffsetOutOfRange)\n\tocResponse.AddError(\"my_topic\", 1, ErrNoError)\n\tcoordinator.Returns(ocResponse)\n\n\t// For RefreshCoordinator()\n\tcoordinator.Returns(&ConsumerMetadataResponse{\n\t\tCoordinatorID:   coordinator.BrokerID(),\n\t\tCoordinatorHost: \"127.0.0.1\",\n\t\tCoordinatorPort: coordinator.Port(),\n\t})\n\n\t// Nothing in response.Errors at all\n\tocResponse2 := new(OffsetCommitResponse)\n\tcoordinator.Returns(ocResponse2)\n\n\t// No error, no need to refresh coordinator\n\n\t// Error on the wrong partition for this pom\n\tocResponse3 := new(OffsetCommitResponse)\n\tocResponse3.AddError(\"my_topic\", 1, ErrNoError)\n\tcoordinator.Returns(ocResponse3)\n\n\t// ErrUnknownTopicOrPartition/ErrNotLeaderForPartition/ErrLeaderNotAvailable block\n\tocResponse4 := new(OffsetCommitResponse)\n\tocResponse4.AddError(\"my_topic\", 0, ErrUnknownTopicOrPartition)\n\tcoordinator.Returns(ocResponse4)\n\n\tnewCoordinator := NewMockBroker(t, 3)\n\tdefer newCoordinator.Close()\n\n\t// For RefreshCoordinator()\n\tcoordinator.Returns(&ConsumerMetadataResponse{\n\t\tCoordinatorID:   newCoordinator.BrokerID(),\n\t\tCoordinatorHost: \"127.0.0.1\",\n\t\tCoordinatorPort: newCoordinator.Port(),\n\t})\n\n\t// Normal error response\n\tocResponse5 := new(OffsetCommitResponse)\n\tocResponse5.AddError(\"my_topic\", 0, ErrNoError)\n\tnewCoordinator.Returns(ocResponse5)\n\n\tpom.MarkOffset(100, \"modified_meta\")\n\n\terr := pom.Close()\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tsafeClose(t, om)\n\tsafeClose(t, testClient)\n}\n\n// Test of recovery from abort\nfunc TestAbortPartitionOffsetManager(t *testing.T) {\n\tom, testClient, broker, coordinator := initOffsetManager(t, 0)\n\tdefer broker.Close()\n\tpom := initPartitionOffsetManager(t, om, coordinator, 5, \"meta\")\n\n\t// this triggers an error in the CommitOffset request,\n\t// which leads to the abort call\n\tcoordinator.Close()\n\n\t// Response to refresh coordinator request\n\tnewCoordinator := NewMockBroker(t, 3)\n\tdefer newCoordinator.Close()\n\tbroker.Returns(&ConsumerMetadataResponse{\n\t\tCoordinatorID:   newCoordinator.BrokerID(),\n\t\tCoordinatorHost: \"127.0.0.1\",\n\t\tCoordinatorPort: newCoordinator.Port(),\n\t})\n\n\tocResponse := new(OffsetCommitResponse)\n\tocResponse.AddError(\"my_topic\", 0, ErrNoError)\n\tnewCoordinator.Returns(ocResponse)\n\n\tpom.MarkOffset(100, \"modified_meta\")\n\n\tsafeClose(t, pom)\n\tsafeClose(t, om)\n\tsafeClose(t, testClient)\n}\n\n// Validate that the constructRequest() method correctly maps Sarama's default for\n// Config.Consumer.Offsets.Retention to the equivalent Kafka value.\nfunc TestConstructRequestRetentionTime(t *testing.T) {\n\texpectedRetention := func(version KafkaVersion, retention time.Duration) int64 {\n\t\tswitch {\n\t\tcase version.IsAtLeast(V2_1_0_0):\n\t\t\t// version >= 2.1.0: Client specified retention time isn't supported in the\n\t\t\t// offset commit request anymore, thus the retention time field set in the\n\t\t\t// OffsetCommitRequest struct should be 0.\n\t\t\treturn 0\n\t\tcase version.IsAtLeast(V0_9_0_0):\n\t\t\t// 0.9.0 <= version < 2.1.0: Retention time *is* supported in the offset commit\n\t\t\t// request. Sarama's default retention times (0) must be mapped to the Kafka\n\t\t\t// default (-1). Non-zero Sarama times are converted from time.Duration to\n\t\t\t// an int64 millisecond value.\n\t\t\tif retention > 0 {\n\t\t\t\treturn int64(retention / time.Millisecond)\n\t\t\t} else {\n\t\t\t\treturn -1\n\t\t\t}\n\t\tdefault:\n\t\t\t// version < 0.9.0: Client specified retention time is not supported in the offset\n\t\t\t// commit request, thus the retention time field set in the OffsetCommitRequest\n\t\t\t// struct should be 0.\n\t\t\treturn 0\n\t\t}\n\t}\n\n\tfor _, version := range SupportedVersions {\n\t\tfor _, retention := range []time.Duration{0, time.Millisecond} {\n\t\t\tname := fmt.Sprintf(\"version %s retention: %s\", version, retention)\n\t\t\tt.Run(name, func(t *testing.T) {\n\t\t\t\t// Perform necessary setup for calling the constructRequest() method. This\n\t\t\t\t// test-case only cares about the code path that sets the retention time\n\t\t\t\t// field in the returned request struct.\n\t\t\t\tconf := NewTestConfig()\n\t\t\t\tconf.Version = version\n\t\t\t\tconf.Consumer.Offsets.Retention = retention\n\t\t\t\tom := &offsetManager{\n\t\t\t\t\tconf: conf,\n\t\t\t\t\tpoms: map[string]map[int32]*partitionOffsetManager{\n\t\t\t\t\t\t\"topic\": {\n\t\t\t\t\t\t\t0: {\n\t\t\t\t\t\t\t\tdirty: true,\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\n\t\t\t\treq := om.constructRequest()\n\n\t\t\t\texpectedRetention := expectedRetention(version, retention)\n\t\t\t\tif req.RetentionTime != expectedRetention {\n\t\t\t\t\tt.Errorf(\"expected retention time %d, got: %d\", expectedRetention, req.RetentionTime)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "offset_request.go",
    "content": "package sarama\n\ntype offsetRequestBlock struct {\n\t// currentLeaderEpoch contains the current leader epoch (used in version 4+).\n\tcurrentLeaderEpoch int32\n\t// timestamp contains the current timestamp.\n\ttimestamp int64\n\t// maxNumOffsets contains the maximum number of offsets to report.\n\tmaxNumOffsets int32 // Only used in version 0\n}\n\nfunc (b *offsetRequestBlock) encode(pe packetEncoder, version int16) error {\n\tif version >= 4 {\n\t\tpe.putInt32(b.currentLeaderEpoch)\n\t}\n\n\tpe.putInt64(b.timestamp)\n\n\tif version == 0 {\n\t\tpe.putInt32(b.maxNumOffsets)\n\t}\n\n\treturn nil\n}\n\nfunc (b *offsetRequestBlock) decode(pd packetDecoder, version int16) (err error) {\n\tb.currentLeaderEpoch = -1\n\tif version >= 4 {\n\t\tif b.currentLeaderEpoch, err = pd.getInt32(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif b.timestamp, err = pd.getInt64(); err != nil {\n\t\treturn err\n\t}\n\n\tif version == 0 {\n\t\tif b.maxNumOffsets, err = pd.getInt32(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\ntype OffsetRequest struct {\n\tVersion        int16\n\tIsolationLevel IsolationLevel\n\treplicaID      int32\n\tisReplicaIDSet bool\n\tblocks         map[string]map[int32]*offsetRequestBlock\n}\n\nfunc NewOffsetRequest(version KafkaVersion) *OffsetRequest {\n\trequest := &OffsetRequest{}\n\tif version.IsAtLeast(V2_2_0_0) {\n\t\t// Version 5 adds a new error code, OFFSET_NOT_AVAILABLE.\n\t\trequest.Version = 5\n\t} else if version.IsAtLeast(V2_1_0_0) {\n\t\t// Version 4 adds the current leader epoch, which is used for fencing.\n\t\trequest.Version = 4\n\t} else if version.IsAtLeast(V2_0_0_0) {\n\t\t// Version 3 is the same as version 2.\n\t\trequest.Version = 3\n\t} else if version.IsAtLeast(V0_11_0_0) {\n\t\t// Version 2 adds the isolation level, which is used for transactional reads.\n\t\trequest.Version = 2\n\t} else if version.IsAtLeast(V0_10_1_0) {\n\t\t// Version 1 removes MaxNumOffsets.  From this version forward, only a single\n\t\t// offset can be returned.\n\t\trequest.Version = 1\n\t}\n\treturn request\n}\n\nfunc (r *OffsetRequest) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *OffsetRequest) encode(pe packetEncoder) error {\n\tif r.isReplicaIDSet {\n\t\tpe.putInt32(r.replicaID)\n\t} else {\n\t\t// default replica ID is always -1 for clients\n\t\tpe.putInt32(-1)\n\t}\n\n\tif r.Version >= 2 {\n\t\tpe.putBool(r.IsolationLevel == ReadCommitted)\n\t}\n\n\terr := pe.putArrayLength(len(r.blocks))\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor topic, partitions := range r.blocks {\n\t\terr = pe.putString(topic)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = pe.putArrayLength(len(partitions))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor partition, block := range partitions {\n\t\t\tpe.putInt32(partition)\n\t\t\tif err = block.encode(pe, r.Version); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (r *OffsetRequest) decode(pd packetDecoder, version int16) error {\n\tr.Version = version\n\n\treplicaID, err := pd.getInt32()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif replicaID >= 0 {\n\t\tr.SetReplicaID(replicaID)\n\t}\n\n\tif r.Version >= 2 {\n\t\ttmp, err := pd.getBool()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tr.IsolationLevel = ReadUncommitted\n\t\tif tmp {\n\t\t\tr.IsolationLevel = ReadCommitted\n\t\t}\n\t}\n\n\tblockCount, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif blockCount == 0 {\n\t\treturn nil\n\t}\n\tr.blocks = make(map[string]map[int32]*offsetRequestBlock)\n\tfor i := 0; i < blockCount; i++ {\n\t\ttopic, err := pd.getString()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tpartitionCount, err := pd.getArrayLength()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.blocks[topic] = make(map[int32]*offsetRequestBlock)\n\t\tfor j := 0; j < partitionCount; j++ {\n\t\t\tpartition, err := pd.getInt32()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tblock := &offsetRequestBlock{}\n\t\t\tif err := block.decode(pd, version); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tr.blocks[topic][partition] = block\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (r *OffsetRequest) key() int16 {\n\treturn apiKeyListOffsets\n}\n\nfunc (r *OffsetRequest) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *OffsetRequest) headerVersion() int16 {\n\treturn 1\n}\n\nfunc (r *OffsetRequest) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 5\n}\n\nfunc (r *OffsetRequest) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 5:\n\t\treturn V2_2_0_0\n\tcase 4:\n\t\treturn V2_1_0_0\n\tcase 3:\n\t\treturn V2_0_0_0\n\tcase 2:\n\t\treturn V0_11_0_0\n\tcase 1:\n\t\treturn V0_10_1_0\n\tcase 0:\n\t\treturn V0_8_2_0\n\tdefault:\n\t\treturn V2_0_0_0\n\t}\n}\n\nfunc (r *OffsetRequest) SetReplicaID(id int32) {\n\tr.replicaID = id\n\tr.isReplicaIDSet = true\n}\n\nfunc (r *OffsetRequest) ReplicaID() int32 {\n\tif r.isReplicaIDSet {\n\t\treturn r.replicaID\n\t}\n\treturn -1\n}\n\nfunc (r *OffsetRequest) AddBlock(topic string, partitionID int32, timestamp int64, maxOffsets int32) {\n\tif r.blocks == nil {\n\t\tr.blocks = make(map[string]map[int32]*offsetRequestBlock)\n\t}\n\n\tif r.blocks[topic] == nil {\n\t\tr.blocks[topic] = make(map[int32]*offsetRequestBlock)\n\t}\n\n\ttmp := new(offsetRequestBlock)\n\ttmp.currentLeaderEpoch = -1\n\ttmp.timestamp = timestamp\n\tif r.Version == 0 {\n\t\ttmp.maxNumOffsets = maxOffsets\n\t}\n\n\tr.blocks[topic][partitionID] = tmp\n}\n"
  },
  {
    "path": "offset_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nvar (\n\toffsetRequestNoBlocksV1 = []byte{\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0x00, 0x00, 0x00, 0x00,\n\t}\n\n\toffsetRequestNoBlocksV2 = []byte{\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0x00, 0x00, 0x00, 0x00,\n\t\t0x00,\n\t}\n\n\toffsetRequestOneBlock = []byte{\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x03, 'f', 'o', 'o',\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x00, 0x00, 0x04,\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x00, 0x00, 0x02,\n\t}\n\n\toffsetRequestOneBlockV1 = []byte{\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x03, 'b', 'a', 'r',\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x00, 0x00, 0x04,\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,\n\t}\n\n\toffsetRequestOneBlockReadCommittedV2 = []byte{\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0x01, 0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x03, 'b', 'a', 'r',\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x00, 0x00, 0x04,\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,\n\t}\n\n\toffsetRequestReplicaID = []byte{\n\t\t0x00, 0x00, 0x00, 0x2a,\n\t\t0x00, 0x00, 0x00, 0x00,\n\t}\n\n\toffsetRequestV4 = []byte{\n\t\t0xff, 0xff, 0xff, 0xff, // replicaID\n\t\t0x01, // IsolationLevel\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x04,\n\t\t0x64, 0x6e, 0x77, 0x65, // topic name\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x00, 0x00, 0x09, // partitionID\n\t\t0xff, 0xff, 0xff, 0xff, // leader epoch\n\t\t0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // timestamp\n\t}\n)\n\nfunc TestOffsetRequest(t *testing.T) {\n\trequest := new(OffsetRequest)\n\ttestRequest(t, \"no blocks\", request, offsetRequestNoBlocksV1)\n\n\trequest.AddBlock(\"foo\", 4, 1, 2)\n\ttestRequest(t, \"one block\", request, offsetRequestOneBlock)\n}\n\nfunc TestOffsetRequestV1(t *testing.T) {\n\trequest := new(OffsetRequest)\n\trequest.Version = 1\n\ttestRequest(t, \"no blocks\", request, offsetRequestNoBlocksV1)\n\n\trequest.AddBlock(\"bar\", 4, 1, 2) // Last argument is ignored for V1\n\ttestRequest(t, \"one block\", request, offsetRequestOneBlockV1)\n}\n\nfunc TestOffsetRequestV2(t *testing.T) {\n\trequest := new(OffsetRequest)\n\trequest.Version = 2\n\ttestRequest(t, \"no blocks\", request, offsetRequestNoBlocksV2)\n\n\trequest.IsolationLevel = ReadCommitted\n\trequest.AddBlock(\"bar\", 4, 1, 2) // Last argument is ignored for V1\n\ttestRequest(t, \"one block\", request, offsetRequestOneBlockReadCommittedV2)\n}\n\nfunc TestOffsetRequestReplicaID(t *testing.T) {\n\trequest := new(OffsetRequest)\n\treplicaID := int32(42)\n\trequest.SetReplicaID(replicaID)\n\n\tif found := request.ReplicaID(); found != replicaID {\n\t\tt.Errorf(\"replicaID: expected %v, found %v\", replicaID, found)\n\t}\n\n\ttestRequest(t, \"with replica ID\", request, offsetRequestReplicaID)\n}\n\nfunc TestOffsetRequestV4(t *testing.T) {\n\trequest := new(OffsetRequest)\n\trequest.Version = 4\n\trequest.IsolationLevel = ReadCommitted\n\trequest.AddBlock(\"dnwe\", 9, -1, -1)\n\ttestRequest(t, \"V4\", request, offsetRequestV4)\n}\n"
  },
  {
    "path": "offset_response.go",
    "content": "package sarama\n\nimport \"time\"\n\ntype OffsetResponseBlock struct {\n\tErr KError\n\t// Offsets contains the result offsets (for V0/V1 compatibility)\n\tOffsets []int64 // Version 0\n\t// Timestamp contains the timestamp associated with the returned offset.\n\tTimestamp int64 // Version 1\n\t// Offset contains the returned offset.\n\tOffset int64 // Version 1\n\t// LeaderEpoch contains the current leader epoch of the partition.\n\tLeaderEpoch int32\n}\n\nfunc (b *OffsetResponseBlock) decode(pd packetDecoder, version int16) (err error) {\n\tb.Err, err = pd.getKError()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif version == 0 {\n\t\tb.Offsets, err = pd.getInt64Array()\n\t\treturn err\n\t}\n\n\tif version >= 1 {\n\t\tb.Timestamp, err = pd.getInt64()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tb.Offset, err = pd.getInt64()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// For backwards compatibility put the offset in the offsets array too\n\t\tb.Offsets = []int64{b.Offset}\n\t}\n\n\tif version >= 4 {\n\t\tif b.LeaderEpoch, err = pd.getInt32(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (b *OffsetResponseBlock) encode(pe packetEncoder, version int16) (err error) {\n\tpe.putKError(b.Err)\n\n\tif version == 0 {\n\t\treturn pe.putInt64Array(b.Offsets)\n\t}\n\n\tif version >= 1 {\n\t\tpe.putInt64(b.Timestamp)\n\t\tpe.putInt64(b.Offset)\n\t}\n\n\tif version >= 4 {\n\t\tpe.putInt32(b.LeaderEpoch)\n\t}\n\n\treturn nil\n}\n\ntype OffsetResponse struct {\n\tVersion        int16\n\tThrottleTimeMs int32\n\tBlocks         map[string]map[int32]*OffsetResponseBlock\n}\n\nfunc (r *OffsetResponse) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *OffsetResponse) decode(pd packetDecoder, version int16) (err error) {\n\tif version >= 2 {\n\t\tr.ThrottleTimeMs, err = pd.getInt32()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tnumTopics, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr.Blocks = make(map[string]map[int32]*OffsetResponseBlock, numTopics)\n\tfor i := 0; i < numTopics; i++ {\n\t\tname, err := pd.getString()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tnumBlocks, err := pd.getArrayLength()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tr.Blocks[name] = make(map[int32]*OffsetResponseBlock, numBlocks)\n\n\t\tfor j := 0; j < numBlocks; j++ {\n\t\t\tid, err := pd.getInt32()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tblock := new(OffsetResponseBlock)\n\t\t\terr = block.decode(pd, version)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tr.Blocks[name][id] = block\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (r *OffsetResponse) GetBlock(topic string, partition int32) *OffsetResponseBlock {\n\tif r.Blocks == nil {\n\t\treturn nil\n\t}\n\n\tif r.Blocks[topic] == nil {\n\t\treturn nil\n\t}\n\n\treturn r.Blocks[topic][partition]\n}\n\n/*\n// [0 0 0 1 ntopics\n0 8 109 121 95 116 111 112 105 99 topic\n0 0 0 1 npartitions\n0 0 0 0 id\n0 0\n\n0 0 0 1 0 0 0 0\n0 1 1 1 0 0 0 1\n0 8 109 121 95 116 111 112\n105 99 0 0 0 1 0 0\n0 0 0 0 0 0 0 1\n0 0 0 0 0 1 1 1] <nil>\n*/\nfunc (r *OffsetResponse) encode(pe packetEncoder) (err error) {\n\tif r.Version >= 2 {\n\t\tpe.putInt32(r.ThrottleTimeMs)\n\t}\n\n\tif err = pe.putArrayLength(len(r.Blocks)); err != nil {\n\t\treturn err\n\t}\n\n\tfor topic, partitions := range r.Blocks {\n\t\tif err = pe.putString(topic); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err = pe.putArrayLength(len(partitions)); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor partition, block := range partitions {\n\t\t\tpe.putInt32(partition)\n\t\t\tif err = block.encode(pe, r.version()); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (r *OffsetResponse) key() int16 {\n\treturn apiKeyListOffsets\n}\n\nfunc (r *OffsetResponse) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *OffsetResponse) headerVersion() int16 {\n\treturn 0\n}\n\nfunc (r *OffsetResponse) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 5\n}\n\nfunc (r *OffsetResponse) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 5:\n\t\treturn V2_2_0_0\n\tcase 4:\n\t\treturn V2_1_0_0\n\tcase 3:\n\t\treturn V2_0_0_0\n\tcase 2:\n\t\treturn V0_11_0_0\n\tcase 1:\n\t\treturn V0_10_1_0\n\tcase 0:\n\t\treturn V0_8_2_0\n\tdefault:\n\t\treturn V2_0_0_0\n\t}\n}\n\nfunc (r *OffsetResponse) throttleTime() time.Duration {\n\treturn time.Duration(r.ThrottleTimeMs) * time.Millisecond\n}\n\n// testing API\n\nfunc (r *OffsetResponse) AddTopicPartition(topic string, partition int32, offset int64) {\n\tif r.Blocks == nil {\n\t\tr.Blocks = make(map[string]map[int32]*OffsetResponseBlock)\n\t}\n\tbyTopic, ok := r.Blocks[topic]\n\tif !ok {\n\t\tbyTopic = make(map[int32]*OffsetResponseBlock)\n\t\tr.Blocks[topic] = byTopic\n\t}\n\tbyTopic[partition] = &OffsetResponseBlock{Offsets: []int64{offset}, Offset: offset}\n}\n"
  },
  {
    "path": "offset_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"errors\"\n\t\"testing\"\n)\n\nvar (\n\temptyOffsetResponse = []byte{\n\t\t0x00, 0x00, 0x00, 0x00,\n\t}\n\n\tnormalOffsetResponse = []byte{\n\t\t0x00, 0x00, 0x00, 0x02,\n\n\t\t0x00, 0x01, 'a',\n\t\t0x00, 0x00, 0x00, 0x00,\n\n\t\t0x00, 0x01, 'z',\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x00, 0x00, 0x02,\n\t\t0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x02,\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06,\n\t}\n\n\tnormalOffsetResponseV1 = []byte{\n\t\t0x00, 0x00, 0x00, 0x02,\n\n\t\t0x00, 0x01, 'a',\n\t\t0x00, 0x00, 0x00, 0x00,\n\n\t\t0x00, 0x01, 'z',\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x00, 0x00, 0x02,\n\t\t0x00, 0x00,\n\t\t0x00, 0x00, 0x01, 0x58, 0x1A, 0xE6, 0x48, 0x86,\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06,\n\t}\n\n\toffsetResponseV4 = []byte{\n\t\t0x00, 0x00, 0x00, 0x00, // throttle time\n\t\t0x00, 0x00, 0x00, 0x01, // length of topics\n\t\t0x00, 0x04, 0x64, 0x6e, 0x77, 0x65, // topic name\n\t\t0x00, 0x00, 0x00, 0x01, // length of partitions\n\t\t0x00, 0x00, 0x00, 0x09, // partitionID\n\t\t0x00, 0x00, // err\n\t\t0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // timestamp\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // offset\n\t\t0xff, 0xff, 0xff, 0xff, // leaderEpoch\n\t}\n)\n\nfunc TestEmptyOffsetResponse(t *testing.T) {\n\tresponse := OffsetResponse{}\n\n\ttestVersionDecodable(t, \"empty\", &response, emptyOffsetResponse, 0)\n\tif len(response.Blocks) != 0 {\n\t\tt.Error(\"Decoding produced\", len(response.Blocks), \"topics where there were none.\")\n\t}\n\n\tresponse = OffsetResponse{}\n\n\ttestVersionDecodable(t, \"empty\", &response, emptyOffsetResponse, 1)\n\tif len(response.Blocks) != 0 {\n\t\tt.Error(\"Decoding produced\", len(response.Blocks), \"topics where there were none.\")\n\t}\n}\n\nfunc TestNormalOffsetResponse(t *testing.T) {\n\tresponse := OffsetResponse{}\n\n\ttestVersionDecodable(t, \"normal\", &response, normalOffsetResponse, 0)\n\n\tif len(response.Blocks) != 2 {\n\t\tt.Fatal(\"Decoding produced\", len(response.Blocks), \"topics where there were two.\")\n\t}\n\n\tif len(response.Blocks[\"a\"]) != 0 {\n\t\tt.Fatal(\"Decoding produced\", len(response.Blocks[\"a\"]), \"partitions for topic 'a' where there were none.\")\n\t}\n\n\tif len(response.Blocks[\"z\"]) != 1 {\n\t\tt.Fatal(\"Decoding produced\", len(response.Blocks[\"z\"]), \"partitions for topic 'z' where there was one.\")\n\t}\n\n\tif !errors.Is(response.Blocks[\"z\"][2].Err, ErrNoError) {\n\t\tt.Fatal(\"Decoding produced invalid error for topic z partition 2.\")\n\t}\n\n\tif len(response.Blocks[\"z\"][2].Offsets) != 2 {\n\t\tt.Fatal(\"Decoding produced invalid number of offsets for topic z partition 2.\")\n\t}\n\n\tif response.Blocks[\"z\"][2].Offsets[0] != 5 || response.Blocks[\"z\"][2].Offsets[1] != 6 {\n\t\tt.Fatal(\"Decoding produced invalid offsets for topic z partition 2.\")\n\t}\n}\n\nfunc TestNormalOffsetResponseV1(t *testing.T) {\n\tresponse := OffsetResponse{}\n\n\ttestVersionDecodable(t, \"normal\", &response, normalOffsetResponseV1, 1)\n\n\tif len(response.Blocks) != 2 {\n\t\tt.Fatal(\"Decoding produced\", len(response.Blocks), \"topics where there were two.\")\n\t}\n\n\tif len(response.Blocks[\"a\"]) != 0 {\n\t\tt.Fatal(\"Decoding produced\", len(response.Blocks[\"a\"]), \"partitions for topic 'a' where there were none.\")\n\t}\n\n\tif len(response.Blocks[\"z\"]) != 1 {\n\t\tt.Fatal(\"Decoding produced\", len(response.Blocks[\"z\"]), \"partitions for topic 'z' where there was one.\")\n\t}\n\n\tif !errors.Is(response.Blocks[\"z\"][2].Err, ErrNoError) {\n\t\tt.Fatal(\"Decoding produced invalid error for topic z partition 2.\")\n\t}\n\n\tif response.Blocks[\"z\"][2].Timestamp != 1477920049286 {\n\t\tt.Fatal(\"Decoding produced invalid timestamp for topic z partition 2.\", response.Blocks[\"z\"][2].Timestamp)\n\t}\n\n\tif response.Blocks[\"z\"][2].Offset != 6 {\n\t\tt.Fatal(\"Decoding produced invalid offsets for topic z partition 2.\")\n\t}\n}\n\nfunc TestOffsetResponseV4(t *testing.T) {\n\tresponse := OffsetResponse{}\n\n\ttestVersionDecodable(t, \"v4\", &response, offsetResponseV4, 4)\n}\n"
  },
  {
    "path": "packet_decoder.go",
    "content": "package sarama\n\nimport (\n\t\"time\"\n\n\t\"github.com/rcrowley/go-metrics\"\n)\n\ntype taggedFieldDecoderFunc func(pd packetDecoder) error\ntype taggedFieldDecoders map[uint64]taggedFieldDecoderFunc\n\n// PacketDecoder is the interface providing helpers for reading with Kafka's encoding rules.\n// Types implementing Decoder only need to worry about calling methods like GetString,\n// not about how a string is represented in Kafka.\ntype packetDecoder interface {\n\t// Primitives\n\tgetInt8() (int8, error)\n\tgetInt16() (int16, error)\n\tgetInt32() (int32, error)\n\tgetInt64() (int64, error)\n\tgetVarint() (int64, error)\n\tgetUVarint() (uint64, error)\n\tgetFloat64() (float64, error)\n\tgetArrayLength() (int, error)\n\tgetBool() (bool, error)\n\tgetKError() (KError, error)\n\tgetDurationMs() (time.Duration, error)\n\tgetEmptyTaggedFieldArray() (int, error)\n\tgetTaggedFieldArray(taggedFieldDecoders) error\n\n\t// Collections\n\tgetBytes() ([]byte, error)\n\tgetVarintBytes() ([]byte, error)\n\tgetRawBytes(length int) ([]byte, error)\n\tgetString() (string, error)\n\tgetNullableString() (*string, error)\n\tgetInt32Array() ([]int32, error)\n\tgetInt64Array() ([]int64, error)\n\tgetStringArray() ([]string, error)\n\n\t// Subsets\n\tremaining() int\n\tgetSubset(length int) (packetDecoder, error)\n\tpeek(offset, length int) (packetDecoder, error) // similar to getSubset, but it doesn't advance the offset\n\tpeekInt8(offset int) (int8, error)              // similar to peek, but just one byte\n\n\t// Stacks, see PushDecoder\n\tpush(in pushDecoder) error\n\tpop() error\n\n\t// To record metrics when provided\n\tmetricRegistry() metrics.Registry\n}\n\n// PushDecoder is the interface for decoding fields like CRCs and lengths where the validity\n// of the field depends on what is after it in the packet. Start them with PacketDecoder.Push() where\n// the actual value is located in the packet, then PacketDecoder.Pop() them when all the bytes they\n// depend upon have been decoded.\ntype pushDecoder interface {\n\t// Saves the offset into the input buffer as the location to actually read the calculated value when able.\n\tsaveOffset(in int)\n\n\t// Returns the length of data to reserve for the input of this encoder (e.g. 4 bytes for a CRC32).\n\treserveLength() int\n\n\t// Indicates that all required data is now available to calculate and check the field.\n\t// SaveOffset is guaranteed to have been called first. The implementation should read ReserveLength() bytes\n\t// of data from the saved offset, and verify it based on the data between the saved offset and curOffset.\n\tcheck(curOffset int, buf []byte) error\n}\n\n// dynamicPushDecoder extends the interface of pushDecoder for uses cases where the length of the\n// fields itself is unknown until its value was decoded (for instance varint encoded length\n// fields).\n// During push, dynamicPushDecoder.decode() method will be called instead of reserveLength()\ntype dynamicPushDecoder interface {\n\tpushDecoder\n\tdecoder\n}\n"
  },
  {
    "path": "packet_encoder.go",
    "content": "package sarama\n\nimport (\n\t\"time\"\n\n\t\"github.com/rcrowley/go-metrics\"\n)\n\n// PacketEncoder is the interface providing helpers for writing with Kafka's encoding rules.\n// Types implementing Encoder only need to worry about calling methods like PutString,\n// not about how a string is represented in Kafka.\ntype packetEncoder interface {\n\t// Primitives\n\tputInt8(in int8)\n\tputInt16(in int16)\n\tputInt32(in int32)\n\tputInt64(in int64)\n\tputVarint(in int64)\n\tputUVarint(in uint64)\n\tputFloat64(in float64)\n\tputArrayLength(in int) error\n\tputBool(in bool)\n\tputKError(in KError)\n\tputDurationMs(in time.Duration)\n\n\t// Collections\n\tputBytes(in []byte) error\n\tputVarintBytes(in []byte) error\n\tputRawBytes(in []byte) error\n\tputString(in string) error\n\tputNullableString(in *string) error\n\tputStringArray(in []string) error\n\tputInt32Array(in []int32) error\n\tputInt64Array(in []int64) error\n\tputNullableInt32Array(in []int32) error\n\tputEmptyTaggedFieldArray()\n\n\t// Provide the current offset to record the batch size metric\n\toffset() int\n\n\t// Stacks, see PushEncoder\n\tpush(in pushEncoder)\n\tpop() error\n\n\t// To record metrics when provided\n\tmetricRegistry() metrics.Registry\n}\n\n// PushEncoder is the interface for encoding fields like CRCs and lengths where the value\n// of the field depends on what is encoded after it in the packet. Start them with PacketEncoder.Push() where\n// the actual value is located in the packet, then PacketEncoder.Pop() them when all the bytes they\n// depend upon have been written.\ntype pushEncoder interface {\n\t// Saves the offset into the input buffer as the location to actually write the calculated value when able.\n\tsaveOffset(in int)\n\n\t// Returns the length of data to reserve for the output of this encoder (eg 4 bytes for a CRC32).\n\treserveLength() int\n\n\t// Indicates that all required data is now available to calculate and write the field.\n\t// SaveOffset is guaranteed to have been called first. The implementation should write ReserveLength() bytes\n\t// of data to the saved offset, based on the data between the saved offset and curOffset.\n\trun(curOffset int, buf []byte) error\n}\n\n// dynamicPushEncoder extends the interface of pushEncoder for uses cases where the length of the\n// fields itself is unknown until its value was computed (for instance varint encoded length\n// fields).\ntype dynamicPushEncoder interface {\n\tpushEncoder\n\n\t// Called during pop() to adjust the length of the field.\n\t// It should return the difference in bytes between the last computed length and current length.\n\tadjustLength(currOffset int) int\n}\n"
  },
  {
    "path": "partitioner.go",
    "content": "package sarama\n\nimport (\n\t\"hash\"\n\t\"hash/crc32\"\n\t\"hash/fnv\"\n\t\"math/rand\"\n\t\"time\"\n)\n\n// Partitioner is anything that, given a Kafka message and a number of partitions indexed [0...numPartitions-1],\n// decides to which partition to send the message. RandomPartitioner, RoundRobinPartitioner and HashPartitioner are provided\n// as simple default implementations.\ntype Partitioner interface {\n\t// Partition takes a message and partition count and chooses a partition\n\tPartition(message *ProducerMessage, numPartitions int32) (int32, error)\n\n\t// RequiresConsistency indicates to the user of the partitioner whether the\n\t// mapping of key->partition is consistent or not. Specifically, if a\n\t// partitioner requires consistency then it must be allowed to choose from all\n\t// partitions (even ones known to be unavailable), and its choice must be\n\t// respected by the caller. The obvious example is the HashPartitioner.\n\tRequiresConsistency() bool\n}\n\n// DynamicConsistencyPartitioner can optionally be implemented by Partitioners\n// in order to allow more flexibility than is originally allowed by the\n// RequiresConsistency method in the Partitioner interface. This allows\n// partitioners to require consistency sometimes, but not all times. It's useful\n// for, e.g., the HashPartitioner, which does not require consistency if the\n// message key is nil.\ntype DynamicConsistencyPartitioner interface {\n\tPartitioner\n\n\t// MessageRequiresConsistency is similar to Partitioner.RequiresConsistency,\n\t// but takes in the message being partitioned so that the partitioner can\n\t// make a per-message determination.\n\tMessageRequiresConsistency(message *ProducerMessage) bool\n}\n\n// PartitionerConstructor is the type for a function capable of constructing new Partitioners.\ntype PartitionerConstructor func(topic string) Partitioner\n\ntype manualPartitioner struct{}\n\n// HashPartitionerOption lets you modify default values of the partitioner\ntype HashPartitionerOption func(*hashPartitioner)\n\n// WithAbsFirst means that the partitioner handles absolute values\n// in the same way as the reference Java implementation\nfunc WithAbsFirst() HashPartitionerOption {\n\treturn func(hp *hashPartitioner) {\n\t\thp.referenceAbs = true\n\t}\n}\n\n// WithHashUnsigned means the partitioner treats the hashed value as unsigned when\n// partitioning.  This is intended to be combined with the crc32 hash algorithm to\n// be compatible with librdkafka's implementation\nfunc WithHashUnsigned() HashPartitionerOption {\n\treturn func(hp *hashPartitioner) {\n\t\thp.hashUnsigned = true\n\t}\n}\n\n// WithCustomHashFunction lets you specify what hash function to use for the partitioning\nfunc WithCustomHashFunction(hasher func() hash.Hash32) HashPartitionerOption {\n\treturn func(hp *hashPartitioner) {\n\t\thp.hasher = hasher()\n\t}\n}\n\n// WithCustomFallbackPartitioner lets you specify what HashPartitioner should be used in case a Distribution Key is empty\nfunc WithCustomFallbackPartitioner(randomHP Partitioner) HashPartitionerOption {\n\treturn func(hp *hashPartitioner) {\n\t\thp.random = randomHP\n\t}\n}\n\n// NewManualPartitioner returns a Partitioner which uses the partition manually set in the provided\n// ProducerMessage's Partition field as the partition to produce to.\nfunc NewManualPartitioner(topic string) Partitioner {\n\treturn new(manualPartitioner)\n}\n\nfunc (p *manualPartitioner) Partition(message *ProducerMessage, numPartitions int32) (int32, error) {\n\treturn message.Partition, nil\n}\n\nfunc (p *manualPartitioner) RequiresConsistency() bool {\n\treturn true\n}\n\ntype randomPartitioner struct {\n\tgenerator *rand.Rand\n}\n\n// NewRandomPartitioner returns a Partitioner which chooses a random partition each time.\nfunc NewRandomPartitioner(topic string) Partitioner {\n\tp := new(randomPartitioner)\n\tp.generator = rand.New(rand.NewSource(time.Now().UTC().UnixNano()))\n\treturn p\n}\n\nfunc (p *randomPartitioner) Partition(message *ProducerMessage, numPartitions int32) (int32, error) {\n\treturn int32(p.generator.Intn(int(numPartitions))), nil\n}\n\nfunc (p *randomPartitioner) RequiresConsistency() bool {\n\treturn false\n}\n\ntype roundRobinPartitioner struct {\n\tpartition int32\n}\n\n// NewRoundRobinPartitioner returns a Partitioner which walks through the available partitions one at a time.\nfunc NewRoundRobinPartitioner(topic string) Partitioner {\n\treturn &roundRobinPartitioner{}\n}\n\nfunc (p *roundRobinPartitioner) Partition(message *ProducerMessage, numPartitions int32) (int32, error) {\n\tif p.partition >= numPartitions {\n\t\tp.partition = 0\n\t}\n\tret := p.partition\n\tp.partition++\n\treturn ret, nil\n}\n\nfunc (p *roundRobinPartitioner) RequiresConsistency() bool {\n\treturn false\n}\n\ntype hashPartitioner struct {\n\trandom       Partitioner\n\thasher       hash.Hash32\n\treferenceAbs bool\n\thashUnsigned bool\n}\n\n// NewCustomHashPartitioner is a wrapper around NewHashPartitioner, allowing the use of custom hasher.\n// The argument is a function providing the instance, implementing the hash.Hash32 interface. This is to ensure that\n// each partition dispatcher gets its own hasher, to avoid concurrency issues by sharing an instance.\nfunc NewCustomHashPartitioner(hasher func() hash.Hash32) PartitionerConstructor {\n\treturn func(topic string) Partitioner {\n\t\tp := new(hashPartitioner)\n\t\tp.random = NewRandomPartitioner(topic)\n\t\tp.hasher = hasher()\n\t\tp.referenceAbs = false\n\t\tp.hashUnsigned = false\n\t\treturn p\n\t}\n}\n\n// NewCustomPartitioner creates a default Partitioner but lets you specify the behavior of each component via options\nfunc NewCustomPartitioner(options ...HashPartitionerOption) PartitionerConstructor {\n\treturn func(topic string) Partitioner {\n\t\tp := new(hashPartitioner)\n\t\tp.random = NewRandomPartitioner(topic)\n\t\tp.hasher = fnv.New32a()\n\t\tp.referenceAbs = false\n\t\tp.hashUnsigned = false\n\t\tfor _, option := range options {\n\t\t\toption(p)\n\t\t}\n\t\treturn p\n\t}\n}\n\n// NewHashPartitioner returns a Partitioner which behaves as follows. If the message's key is nil then a\n// random partition is chosen. Otherwise the FNV-1a hash of the encoded bytes of the message key is used,\n// modulus the number of partitions. This ensures that messages with the same key always end up on the\n// same partition.\nfunc NewHashPartitioner(topic string) Partitioner {\n\tp := new(hashPartitioner)\n\tp.random = NewRandomPartitioner(topic)\n\tp.hasher = fnv.New32a()\n\tp.referenceAbs = false\n\tp.hashUnsigned = false\n\treturn p\n}\n\n// NewReferenceHashPartitioner is like NewHashPartitioner except that it handles absolute values\n// in the same way as the reference Java implementation. NewHashPartitioner was supposed to do\n// that but it had a mistake and now there are people depending on both behaviors. This will\n// all go away on the next major version bump.\nfunc NewReferenceHashPartitioner(topic string) Partitioner {\n\tp := new(hashPartitioner)\n\tp.random = NewRandomPartitioner(topic)\n\tp.hasher = fnv.New32a()\n\tp.referenceAbs = true\n\tp.hashUnsigned = false\n\treturn p\n}\n\n// NewConsistentCRCHashPartitioner is like NewHashPartitioner execpt that it uses the *unsigned* crc32 hash\n// of the encoded bytes of the message key modulus the number of partitions.  This is compatible with\n// librdkafka's `consistent_random` partitioner\nfunc NewConsistentCRCHashPartitioner(topic string) Partitioner {\n\tp := new(hashPartitioner)\n\tp.random = NewRandomPartitioner(topic)\n\tp.hasher = crc32.NewIEEE()\n\tp.referenceAbs = false\n\tp.hashUnsigned = true\n\treturn p\n}\n\nfunc (p *hashPartitioner) Partition(message *ProducerMessage, numPartitions int32) (int32, error) {\n\tif message.Key == nil {\n\t\treturn p.random.Partition(message, numPartitions)\n\t}\n\tbytes, err := message.Key.Encode()\n\tif err != nil {\n\t\treturn -1, err\n\t}\n\tp.hasher.Reset()\n\t_, err = p.hasher.Write(bytes)\n\tif err != nil {\n\t\treturn -1, err\n\t}\n\tvar partition int32\n\t// Turns out we were doing our absolute value in a subtly different way from the upstream\n\t// implementation, but now we need to maintain backwards compat for people who started using\n\t// the old version; if referenceAbs is set we are compatible with the reference java client\n\t// but not past Sarama versions\n\tif p.referenceAbs {\n\t\tpartition = (int32(p.hasher.Sum32()) & 0x7fffffff) % numPartitions\n\t} else if p.hashUnsigned {\n\t\t// librdkafka treats the hashed value as unsigned.  If `hashUnsigned` is set we are compatible\n\t\t// with librdkafka's `consistent` partitioning but not past Sarama versions\n\t\tpartition = int32(p.hasher.Sum32() % uint32(numPartitions))\n\t} else {\n\t\tpartition = int32(p.hasher.Sum32()) % numPartitions\n\t\tif partition < 0 {\n\t\t\tpartition = -partition\n\t\t}\n\t}\n\treturn partition, nil\n}\n\nfunc (p *hashPartitioner) RequiresConsistency() bool {\n\treturn true\n}\n\nfunc (p *hashPartitioner) MessageRequiresConsistency(message *ProducerMessage) bool {\n\treturn message.Key != nil\n}\n"
  },
  {
    "path": "partitioner_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"crypto/rand\"\n\t\"hash/crc32\"\n\t\"hash/fnv\"\n\t\"log\"\n\t\"testing\"\n)\n\nfunc assertPartitioningConsistent(t *testing.T, partitioner Partitioner, message *ProducerMessage, numPartitions int32) {\n\tchoice, err := partitioner.Partition(message, numPartitions)\n\tif err != nil {\n\t\tt.Error(partitioner, err)\n\t}\n\tif choice < 0 || choice >= numPartitions {\n\t\tt.Error(partitioner, \"returned partition\", choice, \"outside of range for\", message)\n\t}\n\tfor i := 1; i < 50; i++ {\n\t\tnewChoice, err := partitioner.Partition(message, numPartitions)\n\t\tif err != nil {\n\t\t\tt.Error(partitioner, err)\n\t\t}\n\t\tif newChoice != choice {\n\t\t\tt.Error(partitioner, \"returned partition\", newChoice, \"inconsistent with\", choice, \".\")\n\t\t}\n\t}\n}\n\ntype partitionerTestCase struct {\n\tkey               string\n\texpectedPartition int32\n}\n\nfunc partitionAndAssert(t *testing.T, partitioner Partitioner, numPartitions int32, testCase partitionerTestCase) {\n\tt.Run(\"partitionAndAssert \"+testCase.key, func(t *testing.T) {\n\t\tmsg := &ProducerMessage{\n\t\t\tKey: StringEncoder(testCase.key),\n\t\t}\n\n\t\tpartition, err := partitioner.Partition(msg, numPartitions)\n\n\t\tif err != nil {\n\t\t\tt.Error(partitioner, err)\n\t\t}\n\t\tif partition != testCase.expectedPartition {\n\t\t\tt.Error(partitioner, \"partitioning\", testCase.key, \"returned partition\", partition, \"but expected\", testCase.expectedPartition, \".\")\n\t\t}\n\t})\n}\n\nfunc TestRandomPartitioner(t *testing.T) {\n\tpartitioner := NewRandomPartitioner(\"mytopic\")\n\n\tchoice, err := partitioner.Partition(nil, 1)\n\tif err != nil {\n\t\tt.Error(partitioner, err)\n\t}\n\tif choice != 0 {\n\t\tt.Error(\"Returned non-zero partition when only one available.\")\n\t}\n\n\tfor i := 1; i < 50; i++ {\n\t\tchoice, err := partitioner.Partition(nil, 50)\n\t\tif err != nil {\n\t\t\tt.Error(partitioner, err)\n\t\t}\n\t\tif choice < 0 || choice >= 50 {\n\t\t\tt.Error(\"Returned partition\", choice, \"outside of range.\")\n\t\t}\n\t}\n}\n\nfunc TestRoundRobinPartitioner(t *testing.T) {\n\tpartitioner := NewRoundRobinPartitioner(\"mytopic\")\n\n\tchoice, err := partitioner.Partition(nil, 1)\n\tif err != nil {\n\t\tt.Error(partitioner, err)\n\t}\n\tif choice != 0 {\n\t\tt.Error(\"Returned non-zero partition when only one available.\")\n\t}\n\n\tvar i int32\n\tfor i = 1; i < 50; i++ {\n\t\tchoice, err := partitioner.Partition(nil, 7)\n\t\tif err != nil {\n\t\t\tt.Error(partitioner, err)\n\t\t}\n\t\tif choice != i%7 {\n\t\t\tt.Error(\"Returned partition\", choice, \"expecting\", i%7)\n\t\t}\n\t}\n}\n\nfunc TestNewHashPartitionerWithHasher(t *testing.T) {\n\t// use the current default hasher fnv.New32a()\n\tpartitioner := NewCustomHashPartitioner(fnv.New32a)(\"mytopic\")\n\n\tchoice, err := partitioner.Partition(&ProducerMessage{}, 1)\n\tif err != nil {\n\t\tt.Error(partitioner, err)\n\t}\n\tif choice != 0 {\n\t\tt.Error(\"Returned non-zero partition when only one available.\")\n\t}\n\n\tfor i := 1; i < 50; i++ {\n\t\tchoice, err := partitioner.Partition(&ProducerMessage{}, 50)\n\t\tif err != nil {\n\t\t\tt.Error(partitioner, err)\n\t\t}\n\t\tif choice < 0 || choice >= 50 {\n\t\t\tt.Error(\"Returned partition\", choice, \"outside of range for nil key.\")\n\t\t}\n\t}\n\n\tbuf := make([]byte, 256)\n\tfor i := 1; i < 50; i++ {\n\t\tif _, err := rand.Read(buf); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t\tassertPartitioningConsistent(t, partitioner, &ProducerMessage{Key: ByteEncoder(buf)}, 50)\n\t}\n}\n\nfunc TestHashPartitionerWithHasherMinInt32(t *testing.T) {\n\t// use the current default hasher fnv.New32a()\n\tpartitioner := NewCustomHashPartitioner(fnv.New32a)(\"mytopic\")\n\n\tmsg := ProducerMessage{}\n\t// \"1468509572224\" generates 2147483648 (uint32) result from Sum32 function\n\t// which is -2147483648 or int32's min value\n\tmsg.Key = StringEncoder(\"1468509572224\")\n\n\tchoice, err := partitioner.Partition(&msg, 50)\n\tif err != nil {\n\t\tt.Error(partitioner, err)\n\t}\n\tif choice < 0 || choice >= 50 {\n\t\tt.Error(\"Returned partition\", choice, \"outside of range for nil key.\")\n\t}\n}\n\nfunc TestHashPartitioner(t *testing.T) {\n\tpartitioner := NewHashPartitioner(\"mytopic\")\n\n\tchoice, err := partitioner.Partition(&ProducerMessage{}, 1)\n\tif err != nil {\n\t\tt.Error(partitioner, err)\n\t}\n\tif choice != 0 {\n\t\tt.Error(\"Returned non-zero partition when only one available.\")\n\t}\n\n\tfor i := 1; i < 50; i++ {\n\t\tchoice, err := partitioner.Partition(&ProducerMessage{}, 50)\n\t\tif err != nil {\n\t\t\tt.Error(partitioner, err)\n\t\t}\n\t\tif choice < 0 || choice >= 50 {\n\t\t\tt.Error(\"Returned partition\", choice, \"outside of range for nil key.\")\n\t\t}\n\t}\n\n\tbuf := make([]byte, 256)\n\tfor i := 1; i < 50; i++ {\n\t\tif _, err := rand.Read(buf); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t\tassertPartitioningConsistent(t, partitioner, &ProducerMessage{Key: ByteEncoder(buf)}, 50)\n\t}\n}\n\nfunc TestHashPartitionerConsistency(t *testing.T) {\n\tpartitioner := NewHashPartitioner(\"mytopic\")\n\tep, ok := partitioner.(DynamicConsistencyPartitioner)\n\n\tif !ok {\n\t\tt.Error(\"Hash partitioner does not implement DynamicConsistencyPartitioner\")\n\t}\n\n\tconsistency := ep.MessageRequiresConsistency(&ProducerMessage{Key: StringEncoder(\"hi\")})\n\tif !consistency {\n\t\tt.Error(\"Messages with keys should require consistency\")\n\t}\n\tconsistency = ep.MessageRequiresConsistency(&ProducerMessage{})\n\tif consistency {\n\t\tt.Error(\"Messages without keys should require consistency\")\n\t}\n}\n\nfunc TestHashPartitionerMinInt32(t *testing.T) {\n\tpartitioner := NewHashPartitioner(\"mytopic\")\n\n\tmsg := ProducerMessage{}\n\t// \"1468509572224\" generates 2147483648 (uint32) result from Sum32 function\n\t// which is -2147483648 or int32's min value\n\tmsg.Key = StringEncoder(\"1468509572224\")\n\n\tchoice, err := partitioner.Partition(&msg, 50)\n\tif err != nil {\n\t\tt.Error(partitioner, err)\n\t}\n\tif choice < 0 || choice >= 50 {\n\t\tt.Error(\"Returned partition\", choice, \"outside of range for nil key.\")\n\t}\n}\n\nfunc TestConsistentCRCHashPartitioner(t *testing.T) {\n\tnumPartitions := int32(100)\n\tpartitioner := NewConsistentCRCHashPartitioner(\"mytopic\")\n\n\ttestCases := []partitionerTestCase{\n\t\t{\n\t\t\tkey:               \"abc123def456\",\n\t\t\texpectedPartition: 57,\n\t\t},\n\t\t{\n\t\t\t// `SheetJS` has a crc32 hash value of 2647669026 (which is -1647298270 as a signed int32)\n\t\t\t// Modding the signed value will give a partition of 70.  Modding the unsigned value will give 26\n\t\t\tkey:               \"SheetJS\",\n\t\t\texpectedPartition: 26,\n\t\t},\n\t\t{\n\t\t\tkey:               \"9e8c7f4cf45857cfff7645d6\",\n\t\t\texpectedPartition: 24,\n\t\t},\n\t\t{\n\t\t\tkey:               \"3900446192ff85a5f67da10c\",\n\t\t\texpectedPartition: 75,\n\t\t},\n\t\t{\n\t\t\tkey:               \"0f4407b7a67d6d27de372198\",\n\t\t\texpectedPartition: 50,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tpartitionAndAssert(t, partitioner, numPartitions, tc)\n\t}\n}\n\nfunc TestCustomPartitionerWithConsistentHashing(t *testing.T) {\n\t// Setting both `hashUnsigned` and the hash function to `crc32.NewIEEE` is equivalent to using `NewConsistentCRCHashPartitioner`\n\tpartitioner := NewCustomPartitioner(\n\t\tWithHashUnsigned(),\n\t\tWithCustomHashFunction(crc32.NewIEEE),\n\t)(\"mytopic\")\n\n\t// See above re: why `SheetJS`\n\tmsg := ProducerMessage{\n\t\tKey: StringEncoder(\"SheetJS\"),\n\t}\n\n\tchoice, err := partitioner.Partition(&msg, 100)\n\tif err != nil {\n\t\tt.Error(partitioner, err)\n\t}\n\texpectedPartition := int32(26)\n\tif choice != expectedPartition {\n\t\tt.Error(partitioner, \"returned partition\", choice, \"but expected\", expectedPartition, \".\")\n\t}\n}\n\nfunc TestManualPartitioner(t *testing.T) {\n\tpartitioner := NewManualPartitioner(\"mytopic\")\n\n\tchoice, err := partitioner.Partition(&ProducerMessage{}, 1)\n\tif err != nil {\n\t\tt.Error(partitioner, err)\n\t}\n\tif choice != 0 {\n\t\tt.Error(\"Returned non-zero partition when only one available.\")\n\t}\n\n\tfor i := int32(1); i < 50; i++ {\n\t\tchoice, err := partitioner.Partition(&ProducerMessage{Partition: i}, 50)\n\t\tif err != nil {\n\t\t\tt.Error(partitioner, err)\n\t\t}\n\t\tif choice != i {\n\t\t\tt.Error(\"Returned partition not the same as the input partition\")\n\t\t}\n\t}\n}\n\nfunc TestWithCustomFallbackPartitioner(t *testing.T) {\n\ttopic := \"mytopic\"\n\n\tpartitioner := NewCustomPartitioner(\n\t\t// override default random partitioner with round robin\n\t\tWithCustomFallbackPartitioner(NewRoundRobinPartitioner(topic)),\n\t)(topic)\n\n\t// Should use round robin implementation if there is no key\n\tvar i int32\n\tfor i = 0; i < 50; i++ {\n\t\tchoice, err := partitioner.Partition(&ProducerMessage{Key: nil}, 7)\n\t\tif err != nil {\n\t\t\tt.Error(partitioner, err)\n\t\t}\n\t\tif choice != i%7 {\n\t\t\tt.Error(\"Returned partition\", choice, \"expecting\", i%7)\n\t\t}\n\t}\n\n\t// should use hash partitioner if key is specified\n\tbuf := make([]byte, 256)\n\tfor i := 0; i < 50; i++ {\n\t\tif _, err := rand.Read(buf); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t\tassertPartitioningConsistent(t, partitioner, &ProducerMessage{Key: ByteEncoder(buf)}, 50)\n\t}\n}\n\n// By default, Sarama uses the message's key to consistently assign a partition to\n// a message using hashing. If no key is set, a random partition will be chosen.\n// This example shows how you can partition messages randomly, even when a key is set,\n// by overriding Config.Producer.Partitioner.\nfunc ExamplePartitioner_random() {\n\tconfig := NewTestConfig()\n\tconfig.Producer.Partitioner = NewRandomPartitioner\n\n\tproducer, err := NewSyncProducer([]string{\"localhost:9092\"}, config)\n\tif err != nil {\n\t\tlog.Println(err)\n\t\treturn\n\t}\n\tdefer func() {\n\t\tif err := producer.Close(); err != nil {\n\t\t\tlog.Println(\"Failed to close producer:\", err)\n\t\t}\n\t}()\n\n\tmsg := &ProducerMessage{Topic: \"test\", Key: StringEncoder(\"key is set\"), Value: StringEncoder(\"test\")}\n\tpartition, offset, err := producer.SendMessage(msg)\n\tif err != nil {\n\t\tlog.Println(\"Failed to produce message to kafka cluster.\")\n\t\treturn\n\t}\n\n\tlog.Printf(\"Produced message to partition %d with offset %d\", partition, offset)\n}\n\n// This example shows how to assign partitions to your messages manually.\nfunc ExamplePartitioner_manual() {\n\tconfig := NewTestConfig()\n\n\t// First, we tell the producer that we are going to partition ourselves.\n\tconfig.Producer.Partitioner = NewManualPartitioner\n\n\tproducer, err := NewSyncProducer([]string{\"localhost:9092\"}, config)\n\tif err != nil {\n\t\tlog.Println(err)\n\t\treturn\n\t}\n\tdefer func() {\n\t\tif err := producer.Close(); err != nil {\n\t\t\tlog.Println(\"Failed to close producer:\", err)\n\t\t}\n\t}()\n\n\t// Now, we set the Partition field of the ProducerMessage struct.\n\tmsg := &ProducerMessage{Topic: \"test\", Partition: 6, Value: StringEncoder(\"test\")}\n\n\tpartition, offset, err := producer.SendMessage(msg)\n\tif err != nil {\n\t\tlog.Println(\"Failed to produce message to kafka cluster.\")\n\t\treturn\n\t}\n\n\tif partition != 6 {\n\t\tlog.Println(\"Message should have been produced to partition 6!\")\n\t\treturn\n\t}\n\n\tlog.Printf(\"Produced message to partition %d with offset %d\", partition, offset)\n}\n\n// This example shows how to set a different partitioner depending on the topic.\nfunc ExamplePartitioner_per_topic() {\n\tconfig := NewTestConfig()\n\tconfig.Producer.Partitioner = func(topic string) Partitioner {\n\t\tswitch topic {\n\t\tcase \"access_log\", \"error_log\":\n\t\t\treturn NewRandomPartitioner(topic)\n\n\t\tdefault:\n\t\t\treturn NewHashPartitioner(topic)\n\t\t}\n\t}\n\n\t// ...\n}\n"
  },
  {
    "path": "prep_encoder.go",
    "content": "package sarama\n\nimport (\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"time\"\n\n\t\"github.com/rcrowley/go-metrics\"\n)\n\ntype prepEncoder struct {\n\tstack  []pushEncoder\n\tlength int\n}\n\ntype prepFlexibleEncoder struct {\n\t*prepEncoder\n}\n\n// primitives\n\nfunc (pe *prepEncoder) putInt8(in int8) {\n\tpe.length++\n}\n\nfunc (pe *prepEncoder) putInt16(in int16) {\n\tpe.length += 2\n}\n\nfunc (pe *prepEncoder) putInt32(in int32) {\n\tpe.length += 4\n}\n\nfunc (pe *prepEncoder) putInt64(in int64) {\n\tpe.length += 8\n}\n\nfunc (pe *prepEncoder) putVarint(in int64) {\n\tvar buf [binary.MaxVarintLen64]byte\n\tpe.length += binary.PutVarint(buf[:], in)\n}\n\nfunc (pe *prepEncoder) putUVarint(in uint64) {\n\tvar buf [binary.MaxVarintLen64]byte\n\tpe.length += binary.PutUvarint(buf[:], in)\n}\n\nfunc (pe *prepEncoder) putFloat64(in float64) {\n\tpe.length += 8\n}\n\nfunc (pe *prepEncoder) putArrayLength(in int) error {\n\tif in > math.MaxInt32 {\n\t\treturn PacketEncodingError{fmt.Sprintf(\"array too long (%d)\", in)}\n\t}\n\tpe.length += 4\n\treturn nil\n}\n\nfunc (pe *prepEncoder) putBool(in bool) {\n\tpe.length++\n}\n\nfunc (pe *prepEncoder) putKError(in KError) {\n\tpe.length += 2\n}\n\nfunc (pe *prepEncoder) putDurationMs(in time.Duration) {\n\tpe.length += 4\n}\n\n// arrays\n\nfunc (pe *prepEncoder) putBytes(in []byte) error {\n\tpe.length += 4\n\tif in == nil {\n\t\treturn nil\n\t}\n\treturn pe.putRawBytes(in)\n}\n\nfunc (pe *prepEncoder) putVarintBytes(in []byte) error {\n\tif in == nil {\n\t\tpe.putVarint(-1)\n\t\treturn nil\n\t}\n\tpe.putVarint(int64(len(in)))\n\treturn pe.putRawBytes(in)\n}\n\nfunc (pe *prepEncoder) putRawBytes(in []byte) error {\n\tif len(in) > math.MaxInt32 {\n\t\treturn PacketEncodingError{fmt.Sprintf(\"byteslice too long (%d)\", len(in))}\n\t}\n\tpe.length += len(in)\n\treturn nil\n}\n\nfunc (pe *prepEncoder) putNullableString(in *string) error {\n\tif in == nil {\n\t\tpe.length += 2\n\t\treturn nil\n\t}\n\treturn pe.putString(*in)\n}\n\nfunc (pe *prepEncoder) putString(in string) error {\n\tpe.length += 2\n\tif len(in) > math.MaxInt16 {\n\t\treturn PacketEncodingError{fmt.Sprintf(\"string too long (%d)\", len(in))}\n\t}\n\tpe.length += len(in)\n\treturn nil\n}\n\nfunc (pe *prepEncoder) putStringArray(in []string) error {\n\terr := pe.putArrayLength(len(in))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, str := range in {\n\t\tif err := pe.putString(str); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (pe *prepEncoder) putInt32Array(in []int32) error {\n\terr := pe.putArrayLength(len(in))\n\tif err != nil {\n\t\treturn err\n\t}\n\tpe.length += 4 * len(in)\n\treturn nil\n}\n\nfunc (pe *prepEncoder) putNullableInt32Array(in []int32) error {\n\tif in == nil {\n\t\tpe.length += 4\n\t\treturn nil\n\t}\n\terr := pe.putArrayLength(len(in))\n\tif err != nil {\n\t\treturn err\n\t}\n\tpe.length += 4 * len(in)\n\treturn nil\n}\n\nfunc (pe *prepEncoder) putInt64Array(in []int64) error {\n\terr := pe.putArrayLength(len(in))\n\tif err != nil {\n\t\treturn err\n\t}\n\tpe.length += 8 * len(in)\n\treturn nil\n}\n\nfunc (pe *prepEncoder) putEmptyTaggedFieldArray() {\n}\n\nfunc (pe *prepEncoder) offset() int {\n\treturn pe.length\n}\n\n// stackable\n\nfunc (pe *prepEncoder) push(in pushEncoder) {\n\tin.saveOffset(pe.length)\n\tpe.length += in.reserveLength()\n\tpe.stack = append(pe.stack, in)\n}\n\nfunc (pe *prepEncoder) pop() error {\n\tin := pe.stack[len(pe.stack)-1]\n\tpe.stack = pe.stack[:len(pe.stack)-1]\n\tif dpe, ok := in.(dynamicPushEncoder); ok {\n\t\tpe.length += dpe.adjustLength(pe.length)\n\t}\n\n\treturn nil\n}\n\n// we do not record metrics during the prep encoder pass\nfunc (pe *prepEncoder) metricRegistry() metrics.Registry {\n\treturn nil\n}\n\nfunc (pe *prepFlexibleEncoder) putArrayLength(in int) error {\n\tpe.putUVarint(uint64(in + 1))\n\treturn nil\n}\n\nfunc (pe *prepFlexibleEncoder) putBytes(in []byte) error {\n\tpe.putUVarint(uint64(len(in) + 1))\n\treturn pe.putRawBytes(in)\n}\n\nfunc (pe *prepFlexibleEncoder) putString(in string) error {\n\tif err := pe.putArrayLength(len(in)); err != nil {\n\t\treturn err\n\t}\n\treturn pe.putRawBytes([]byte(in))\n}\n\nfunc (pe *prepFlexibleEncoder) putNullableString(in *string) error {\n\tif in == nil {\n\t\tpe.putUVarint(0)\n\t\treturn nil\n\t} else {\n\t\treturn pe.putString(*in)\n\t}\n}\n\nfunc (pe *prepFlexibleEncoder) putStringArray(in []string) error {\n\terr := pe.putArrayLength(len(in))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, str := range in {\n\t\tif err := pe.putString(str); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (pe *prepFlexibleEncoder) putInt32Array(in []int32) error {\n\tif in == nil {\n\t\treturn errors.New(\"expected int32 array to be non null\")\n\t}\n\n\tpe.putUVarint(uint64(len(in)) + 1)\n\tpe.length += 4 * len(in)\n\treturn nil\n}\n\nfunc (pe *prepFlexibleEncoder) putNullableInt32Array(in []int32) error {\n\tif in == nil {\n\t\tpe.putUVarint(0)\n\t\treturn nil\n\t}\n\n\tpe.putUVarint(uint64(len(in)) + 1)\n\tpe.length += 4 * len(in)\n\treturn nil\n}\n\nfunc (pe *prepFlexibleEncoder) putEmptyTaggedFieldArray() {\n\tpe.putUVarint(0)\n}\n"
  },
  {
    "path": "produce_request.go",
    "content": "package sarama\n\nimport \"github.com/rcrowley/go-metrics\"\n\n// RequiredAcks is used in Produce Requests to tell the broker how many replica acknowledgements\n// it must see before responding. Any of the constants defined here are valid. On broker versions\n// prior to 0.8.2.0 any other positive int16 is also valid (the broker will wait for that many\n// acknowledgements) but in 0.8.2.0 and later this will raise an exception (it has been replaced\n// by setting the `min.isr` value in the brokers configuration).\ntype RequiredAcks int16\n\nconst (\n\t// NoResponse doesn't send any response, the TCP ACK is all you get.\n\tNoResponse RequiredAcks = 0\n\t// WaitForLocal waits for only the local commit to succeed before responding.\n\tWaitForLocal RequiredAcks = 1\n\t// WaitForAll waits for all in-sync replicas to commit before responding.\n\t// The minimum number of in-sync replicas is configured on the broker via\n\t// the `min.insync.replicas` configuration key.\n\tWaitForAll RequiredAcks = -1\n)\n\ntype ProduceRequest struct {\n\tTransactionalID *string\n\tRequiredAcks    RequiredAcks\n\tTimeout         int32\n\tVersion         int16 // v1 requires Kafka 0.9, v2 requires Kafka 0.10, v3 requires Kafka 0.11\n\trecords         map[string]map[int32]Records\n}\n\nfunc (r *ProduceRequest) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc updateMsgSetMetrics(msgSet *MessageSet, compressionRatioMetric metrics.Histogram,\n\ttopicCompressionRatioMetric metrics.Histogram,\n) int64 {\n\tvar topicRecordCount int64\n\tfor _, messageBlock := range msgSet.Messages {\n\t\t// Is this a fake \"message\" wrapping real messages?\n\t\tif messageBlock.Msg.Set != nil {\n\t\t\ttopicRecordCount += int64(len(messageBlock.Msg.Set.Messages))\n\t\t} else {\n\t\t\t// A single uncompressed message\n\t\t\ttopicRecordCount++\n\t\t}\n\t\t// Better be safe than sorry when computing the compression ratio\n\t\tif messageBlock.Msg.compressedSize != 0 {\n\t\t\tcompressionRatio := float64(len(messageBlock.Msg.Value)) /\n\t\t\t\tfloat64(messageBlock.Msg.compressedSize)\n\t\t\t// Histogram do not support decimal values, let's multiple it by 100 for better precision\n\t\t\tintCompressionRatio := int64(100 * compressionRatio)\n\t\t\tcompressionRatioMetric.Update(intCompressionRatio)\n\t\t\ttopicCompressionRatioMetric.Update(intCompressionRatio)\n\t\t}\n\t}\n\treturn topicRecordCount\n}\n\nfunc updateBatchMetrics(recordBatch *RecordBatch, compressionRatioMetric metrics.Histogram,\n\ttopicCompressionRatioMetric metrics.Histogram,\n) int64 {\n\tif recordBatch.compressedRecords != nil {\n\t\tcompressionRatio := int64(float64(recordBatch.recordsLen) / float64(len(recordBatch.compressedRecords)) * 100)\n\t\tcompressionRatioMetric.Update(compressionRatio)\n\t\ttopicCompressionRatioMetric.Update(compressionRatio)\n\t}\n\n\treturn int64(len(recordBatch.Records))\n}\n\nfunc (r *ProduceRequest) encode(pe packetEncoder) error {\n\tif r.Version >= 3 {\n\t\tif err := pe.putNullableString(r.TransactionalID); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tpe.putInt16(int16(r.RequiredAcks))\n\tpe.putInt32(r.Timeout)\n\tmetricRegistry := pe.metricRegistry()\n\tvar batchSizeMetric metrics.Histogram\n\tvar compressionRatioMetric metrics.Histogram\n\tif metricRegistry != nil {\n\t\tbatchSizeMetric = getOrRegisterHistogram(\"batch-size\", metricRegistry)\n\t\tcompressionRatioMetric = getOrRegisterHistogram(\"compression-ratio\", metricRegistry)\n\t}\n\ttotalRecordCount := int64(0)\n\n\terr := pe.putArrayLength(len(r.records))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor topic, partitions := range r.records {\n\t\terr = pe.putString(topic)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = pe.putArrayLength(len(partitions))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ttopicRecordCount := int64(0)\n\t\tvar topicCompressionRatioMetric metrics.Histogram\n\t\tif metricRegistry != nil {\n\t\t\ttopicCompressionRatioMetric = getOrRegisterTopicHistogram(\"compression-ratio\", topic, metricRegistry)\n\t\t}\n\t\tfor id, records := range partitions {\n\t\t\tstartOffset := pe.offset()\n\t\t\tpe.putInt32(id)\n\t\t\tpe.push(&lengthField{})\n\t\t\terr = records.encode(pe)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terr = pe.pop()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif metricRegistry != nil {\n\t\t\t\tif r.Version >= 3 {\n\t\t\t\t\ttopicRecordCount += updateBatchMetrics(records.RecordBatch, compressionRatioMetric, topicCompressionRatioMetric)\n\t\t\t\t} else {\n\t\t\t\t\ttopicRecordCount += updateMsgSetMetrics(records.MsgSet, compressionRatioMetric, topicCompressionRatioMetric)\n\t\t\t\t}\n\t\t\t\tbatchSize := int64(pe.offset() - startOffset)\n\t\t\t\tbatchSizeMetric.Update(batchSize)\n\t\t\t\tgetOrRegisterTopicHistogram(\"batch-size\", topic, metricRegistry).Update(batchSize)\n\t\t\t}\n\t\t}\n\t\tif topicRecordCount > 0 {\n\t\t\tgetOrRegisterTopicMeter(\"record-send-rate\", topic, metricRegistry).Mark(topicRecordCount)\n\t\t\tgetOrRegisterTopicHistogram(\"records-per-request\", topic, metricRegistry).Update(topicRecordCount)\n\t\t\ttotalRecordCount += topicRecordCount\n\t\t}\n\t}\n\tif totalRecordCount > 0 {\n\t\tmetrics.GetOrRegisterMeter(\"record-send-rate\", metricRegistry).Mark(totalRecordCount)\n\t\tgetOrRegisterHistogram(\"records-per-request\", metricRegistry).Update(totalRecordCount)\n\t}\n\n\treturn nil\n}\n\nfunc (r *ProduceRequest) decode(pd packetDecoder, version int16) error {\n\tr.Version = version\n\n\tif version >= 3 {\n\t\tid, err := pd.getNullableString()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.TransactionalID = id\n\t}\n\trequiredAcks, err := pd.getInt16()\n\tif err != nil {\n\t\treturn err\n\t}\n\tr.RequiredAcks = RequiredAcks(requiredAcks)\n\tif r.Timeout, err = pd.getInt32(); err != nil {\n\t\treturn err\n\t}\n\ttopicCount, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif topicCount == 0 {\n\t\treturn nil\n\t}\n\n\tr.records = make(map[string]map[int32]Records)\n\tfor i := 0; i < topicCount; i++ {\n\t\ttopic, err := pd.getString()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tpartitionCount, err := pd.getArrayLength()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.records[topic] = make(map[int32]Records)\n\n\t\tfor j := 0; j < partitionCount; j++ {\n\t\t\tpartition, err := pd.getInt32()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tsize, err := pd.getInt32()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\trecordsDecoder, err := pd.getSubset(int(size))\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tvar records Records\n\t\t\tif err := records.decode(recordsDecoder); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tr.records[topic][partition] = records\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (r *ProduceRequest) key() int16 {\n\treturn apiKeyProduce\n}\n\nfunc (r *ProduceRequest) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *ProduceRequest) headerVersion() int16 {\n\treturn 1\n}\n\nfunc (r *ProduceRequest) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 7\n}\n\nfunc (r *ProduceRequest) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 7:\n\t\treturn V2_1_0_0\n\tcase 6:\n\t\treturn V2_0_0_0\n\tcase 4, 5:\n\t\treturn V1_0_0_0\n\tcase 3:\n\t\treturn V0_11_0_0\n\tcase 2:\n\t\treturn V0_10_0_0\n\tcase 1:\n\t\treturn V0_9_0_0\n\tcase 0:\n\t\treturn V0_8_2_0\n\tdefault:\n\t\treturn V2_1_0_0\n\t}\n}\n\nfunc (r *ProduceRequest) ensureRecords(topic string, partition int32) {\n\tif r.records == nil {\n\t\tr.records = make(map[string]map[int32]Records)\n\t}\n\n\tif r.records[topic] == nil {\n\t\tr.records[topic] = make(map[int32]Records)\n\t}\n}\n\nfunc (r *ProduceRequest) AddMessage(topic string, partition int32, msg *Message) {\n\tr.ensureRecords(topic, partition)\n\tset := r.records[topic][partition].MsgSet\n\n\tif set == nil {\n\t\tset = new(MessageSet)\n\t\tr.records[topic][partition] = newLegacyRecords(set)\n\t}\n\n\tset.addMessage(msg)\n}\n\nfunc (r *ProduceRequest) AddSet(topic string, partition int32, set *MessageSet) {\n\tr.ensureRecords(topic, partition)\n\tr.records[topic][partition] = newLegacyRecords(set)\n}\n\nfunc (r *ProduceRequest) AddBatch(topic string, partition int32, batch *RecordBatch) {\n\tr.ensureRecords(topic, partition)\n\tr.records[topic][partition] = newDefaultRecords(batch)\n}\n"
  },
  {
    "path": "produce_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nvar (\n\tproduceRequestEmpty = []byte{\n\t\t0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x00,\n\t}\n\n\tproduceRequestHeader = []byte{\n\t\t0x01, 0x23,\n\t\t0x00, 0x00, 0x04, 0x44,\n\t\t0x00, 0x00, 0x00, 0x00,\n\t}\n\n\tproduceRequestOneMessage = []byte{\n\t\t0x01, 0x23,\n\t\t0x00, 0x00, 0x04, 0x44,\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x05, 't', 'o', 'p', 'i', 'c',\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x00, 0x00, 0xAD,\n\t\t0x00, 0x00, 0x00, 0x1C,\n\t\t// messageSet\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x10,\n\t\t// message\n\t\t0x23, 0x96, 0x4a, 0xf7, // CRC\n\t\t0x00,\n\t\t0x00,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0x00, 0x00, 0x00, 0x02, 0x00, 0xEE,\n\t}\n\n\tproduceRequestOneRecord = []byte{\n\t\t0xFF, 0xFF, // Transaction ID\n\t\t0x01, 0x23, // Required Acks\n\t\t0x00, 0x00, 0x04, 0x44, // Timeout\n\t\t0x00, 0x00, 0x00, 0x01, // Number of Topics\n\t\t0x00, 0x05, 't', 'o', 'p', 'i', 'c', // Topic\n\t\t0x00, 0x00, 0x00, 0x01, // Number of Partitions\n\t\t0x00, 0x00, 0x00, 0xAD, // Partition\n\t\t0x00, 0x00, 0x00, 0x52, // Records length\n\t\t// recordBatch\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x46,\n\t\t0x00, 0x00, 0x00, 0x00,\n\t\t0x02,\n\t\t0xCA, 0x33, 0xBC, 0x05,\n\t\t0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t0x00, 0x00, 0x01, 0x58, 0x8D, 0xCD, 0x59, 0x38,\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x00,\n\t\t0x00, 0x00, 0x00, 0x01,\n\t\t// record\n\t\t0x28,\n\t\t0x00,\n\t\t0x0A,\n\t\t0x00,\n\t\t0x08, 0x01, 0x02, 0x03, 0x04,\n\t\t0x06, 0x05, 0x06, 0x07,\n\t\t0x02,\n\t\t0x06, 0x08, 0x09, 0x0A,\n\t\t0x04, 0x0B, 0x0C,\n\t}\n)\n\nfunc TestProduceRequest(t *testing.T) {\n\trequest := new(ProduceRequest)\n\ttestRequest(t, \"empty\", request, produceRequestEmpty)\n\n\trequest.RequiredAcks = 0x123\n\trequest.Timeout = 0x444\n\ttestRequest(t, \"header\", request, produceRequestHeader)\n\n\trequest.AddMessage(\"topic\", 0xAD, &Message{Codec: CompressionNone, Key: nil, Value: []byte{0x00, 0xEE}})\n\ttestRequest(t, \"one message\", request, produceRequestOneMessage)\n\n\trequest.Version = 3\n\tbatch := &RecordBatch{\n\t\tLastOffsetDelta: 1,\n\t\tVersion:         2,\n\t\tFirstTimestamp:  time.Unix(1479847795, 0),\n\t\tMaxTimestamp:    time.Unix(0, 0),\n\t\tRecords: []*Record{{\n\t\t\tTimestampDelta: 5 * time.Millisecond,\n\t\t\tKey:            []byte{0x01, 0x02, 0x03, 0x04},\n\t\t\tValue:          []byte{0x05, 0x06, 0x07},\n\t\t\tHeaders: []*RecordHeader{{\n\t\t\t\tKey:   []byte{0x08, 0x09, 0x0A},\n\t\t\t\tValue: []byte{0x0B, 0x0C},\n\t\t\t}},\n\t\t}},\n\t}\n\trequest.AddBatch(\"topic\", 0xAD, batch)\n\tpacket := testRequestEncode(t, \"one record\", request, produceRequestOneRecord)\n\t// compressRecords field is not populated on decoding because consumers\n\t// are only interested in decoded records.\n\tbatch.compressedRecords = nil\n\ttestRequestDecode(t, \"one record\", request, packet)\n}\n"
  },
  {
    "path": "produce_response.go",
    "content": "package sarama\n\nimport (\n\t\"fmt\"\n\t\"time\"\n)\n\n// Protocol, http://kafka.apache.org/protocol.html\n// v1\n// v2 = v3 = v4\n// v5 = v6 = v7\n// Produce Response (Version: 7) => [responses] throttle_time_ms\n//   responses => topic [partition_responses]\n//     topic => STRING\n//     partition_responses => partition error_code base_offset log_append_time log_start_offset\n//       partition => INT32\n//       error_code => INT16\n//       base_offset => INT64\n//       log_append_time => INT64\n//       log_start_offset => INT64\n//   throttle_time_ms => INT32\n\n// partition_responses in protocol\ntype ProduceResponseBlock struct {\n\tErr         KError    // v0, error_code\n\tOffset      int64     // v0, base_offset\n\tTimestamp   time.Time // v2, log_append_time, and the broker is configured with `LogAppendTime`\n\tStartOffset int64     // v5, log_start_offset\n}\n\nfunc (b *ProduceResponseBlock) decode(pd packetDecoder, version int16) (err error) {\n\tb.Err, err = pd.getKError()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tb.Offset, err = pd.getInt64()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif version >= 2 {\n\t\tif millis, err := pd.getInt64(); err != nil {\n\t\t\treturn err\n\t\t} else if millis != -1 {\n\t\t\tb.Timestamp = time.Unix(millis/1000, (millis%1000)*int64(time.Millisecond))\n\t\t}\n\t}\n\n\tif version >= 5 {\n\t\tb.StartOffset, err = pd.getInt64()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (b *ProduceResponseBlock) encode(pe packetEncoder, version int16) (err error) {\n\tpe.putKError(b.Err)\n\tpe.putInt64(b.Offset)\n\n\tif version >= 2 {\n\t\ttimestamp := int64(-1)\n\t\tif !b.Timestamp.Before(time.Unix(0, 0)) {\n\t\t\ttimestamp = b.Timestamp.UnixNano() / int64(time.Millisecond)\n\t\t} else if !b.Timestamp.IsZero() {\n\t\t\treturn PacketEncodingError{fmt.Sprintf(\"invalid timestamp (%v)\", b.Timestamp)}\n\t\t}\n\t\tpe.putInt64(timestamp)\n\t}\n\n\tif version >= 5 {\n\t\tpe.putInt64(b.StartOffset)\n\t}\n\n\treturn nil\n}\n\ntype ProduceResponse struct {\n\tBlocks       map[string]map[int32]*ProduceResponseBlock // v0, responses\n\tVersion      int16\n\tThrottleTime time.Duration // v1, throttle_time_ms\n}\n\nfunc (r *ProduceResponse) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *ProduceResponse) decode(pd packetDecoder, version int16) (err error) {\n\tr.Version = version\n\n\tnumTopics, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr.Blocks = make(map[string]map[int32]*ProduceResponseBlock, numTopics)\n\tfor i := 0; i < numTopics; i++ {\n\t\tname, err := pd.getString()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tnumBlocks, err := pd.getArrayLength()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tr.Blocks[name] = make(map[int32]*ProduceResponseBlock, numBlocks)\n\n\t\tfor j := 0; j < numBlocks; j++ {\n\t\t\tid, err := pd.getInt32()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tblock := new(ProduceResponseBlock)\n\t\t\terr = block.decode(pd, version)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tr.Blocks[name][id] = block\n\t\t}\n\t}\n\n\tif r.Version >= 1 {\n\t\tif r.ThrottleTime, err = pd.getDurationMs(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (r *ProduceResponse) encode(pe packetEncoder) error {\n\terr := pe.putArrayLength(len(r.Blocks))\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor topic, partitions := range r.Blocks {\n\t\terr = pe.putString(topic)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = pe.putArrayLength(len(partitions))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor id, prb := range partitions {\n\t\t\tpe.putInt32(id)\n\t\t\terr = prb.encode(pe, r.Version)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\tif r.Version >= 1 {\n\t\tpe.putDurationMs(r.ThrottleTime)\n\t}\n\treturn nil\n}\n\nfunc (r *ProduceResponse) key() int16 {\n\treturn apiKeyProduce\n}\n\nfunc (r *ProduceResponse) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *ProduceResponse) headerVersion() int16 {\n\treturn 0\n}\n\nfunc (r *ProduceResponse) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 7\n}\n\nfunc (r *ProduceResponse) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 7:\n\t\treturn V2_1_0_0\n\tcase 6:\n\t\treturn V2_0_0_0\n\tcase 4, 5:\n\t\treturn V1_0_0_0\n\tcase 3:\n\t\treturn V0_11_0_0\n\tcase 2:\n\t\treturn V0_10_0_0\n\tcase 1:\n\t\treturn V0_9_0_0\n\tcase 0:\n\t\treturn V0_8_2_0\n\tdefault:\n\t\treturn V2_1_0_0\n\t}\n}\n\nfunc (r *ProduceResponse) throttleTime() time.Duration {\n\treturn r.ThrottleTime\n}\n\nfunc (r *ProduceResponse) GetBlock(topic string, partition int32) *ProduceResponseBlock {\n\tif r.Blocks == nil {\n\t\treturn nil\n\t}\n\n\tif r.Blocks[topic] == nil {\n\t\treturn nil\n\t}\n\n\treturn r.Blocks[topic][partition]\n}\n\n// Testing API\n\nfunc (r *ProduceResponse) AddTopicPartition(topic string, partition int32, err KError) {\n\tif r.Blocks == nil {\n\t\tr.Blocks = make(map[string]map[int32]*ProduceResponseBlock)\n\t}\n\tbyTopic, ok := r.Blocks[topic]\n\tif !ok {\n\t\tbyTopic = make(map[int32]*ProduceResponseBlock)\n\t\tr.Blocks[topic] = byTopic\n\t}\n\tblock := &ProduceResponseBlock{\n\t\tErr: err,\n\t}\n\tif r.Version >= 2 {\n\t\tblock.Timestamp = time.Now()\n\t}\n\tbyTopic[partition] = block\n}\n"
  },
  {
    "path": "produce_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n)\n\nvar (\n\tproduceResponseNoBlocksV0 = []byte{\n\t\t0x00, 0x00, 0x00, 0x00,\n\t}\n\n\tproduceResponseManyBlocksVersions = map[int][]byte{\n\t\t0: {\n\t\t\t0x00, 0x00, 0x00, 0x01,\n\n\t\t\t0x00, 0x03, 'f', 'o', 'o',\n\t\t\t0x00, 0x00, 0x00, 0x01,\n\n\t\t\t0x00, 0x00, 0x00, 0x01, // Partition 1\n\t\t\t0x00, 0x02, // ErrInvalidMessage\n\t\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, // Offset 255\n\t\t},\n\n\t\t1: {\n\t\t\t0x00, 0x00, 0x00, 0x01,\n\n\t\t\t0x00, 0x03, 'f', 'o', 'o',\n\t\t\t0x00, 0x00, 0x00, 0x01,\n\n\t\t\t0x00, 0x00, 0x00, 0x01, // Partition 1\n\t\t\t0x00, 0x02, // ErrInvalidMessage\n\t\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, // Offset 255\n\n\t\t\t0x00, 0x00, 0x00, 0x64, // 100 ms throttle time\n\t\t},\n\t\t2: {\n\t\t\t0x00, 0x00, 0x00, 0x01,\n\n\t\t\t0x00, 0x03, 'f', 'o', 'o',\n\t\t\t0x00, 0x00, 0x00, 0x01,\n\n\t\t\t0x00, 0x00, 0x00, 0x01, // Partition 1\n\t\t\t0x00, 0x02, // ErrInvalidMessage\n\t\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, // Offset 255\n\t\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xE8, // Timestamp January 1st 0001 at 00:00:01,000 UTC (LogAppendTime was used)\n\n\t\t\t0x00, 0x00, 0x00, 0x64, // 100 ms throttle time\n\t\t},\n\t\t7: { // version 7 adds StartOffset\n\t\t\t0x00, 0x00, 0x00, 0x01,\n\n\t\t\t0x00, 0x03, 'f', 'o', 'o',\n\t\t\t0x00, 0x00, 0x00, 0x01,\n\n\t\t\t0x00, 0x00, 0x00, 0x01, // Partition 1\n\t\t\t0x00, 0x02, // ErrInvalidMessage\n\t\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, // Offset 255\n\t\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xE8, // Timestamp January 1st 0001 at 00:00:01,000 UTC (LogAppendTime was used)\n\t\t\t0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, // StartOffset 50\n\n\t\t\t0x00, 0x00, 0x00, 0x64, // 100 ms throttle time\n\t\t},\n\t}\n)\n\nfunc TestProduceResponseDecode(t *testing.T) {\n\tresponse := ProduceResponse{}\n\n\ttestVersionDecodable(t, \"no blocks\", &response, produceResponseNoBlocksV0, 0)\n\tif len(response.Blocks) != 0 {\n\t\tt.Error(\"Decoding produced\", len(response.Blocks), \"topics where there were none\")\n\t}\n\n\tfor v, produceResponseManyBlocks := range produceResponseManyBlocksVersions {\n\t\tt.Logf(\"Decoding produceResponseManyBlocks version %d\", v)\n\t\ttestVersionDecodable(t, \"many blocks\", &response, produceResponseManyBlocks, int16(v))\n\t\tif len(response.Blocks) != 1 {\n\t\t\tt.Error(\"Decoding produced\", len(response.Blocks), \"topics where there was 1\")\n\t\t}\n\t\tif len(response.Blocks[\"foo\"]) != 1 {\n\t\t\tt.Error(\"Decoding produced\", len(response.Blocks[\"foo\"]), \"partitions for 'foo' where there was one\")\n\t\t}\n\t\tblock := response.GetBlock(\"foo\", 1)\n\t\tif block == nil {\n\t\t\tt.Error(\"Decoding did not produce a block for foo/1\")\n\t\t} else {\n\t\t\tif !errors.Is(block.Err, ErrInvalidMessage) {\n\t\t\t\tt.Error(\"Decoding failed for foo/1/Err, got:\", int16(block.Err))\n\t\t\t}\n\t\t\tif block.Offset != 255 {\n\t\t\t\tt.Error(\"Decoding failed for foo/1/Offset, got:\", block.Offset)\n\t\t\t}\n\t\t\tif v >= 2 {\n\t\t\t\tif !block.Timestamp.Equal(time.Unix(1, 0)) {\n\t\t\t\t\tt.Error(\"Decoding failed for foo/1/Timestamp, got:\", block.Timestamp)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif v >= 7 {\n\t\t\t\tif block.StartOffset != 50 {\n\t\t\t\t\tt.Error(\"Decoding failed for foo/1/StartOffset, got:\", block.StartOffset)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif v >= 1 {\n\t\t\tif expected := 100 * time.Millisecond; response.ThrottleTime != expected {\n\t\t\t\tt.Error(\"Failed decoding produced throttle time, expected:\", expected, \", got:\", response.ThrottleTime)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestProduceResponseEncode(t *testing.T) {\n\tresponse := ProduceResponse{}\n\tresponse.Blocks = make(map[string]map[int32]*ProduceResponseBlock)\n\ttestEncodable(t, \"empty\", &response, produceResponseNoBlocksV0)\n\n\tresponse.Blocks[\"foo\"] = make(map[int32]*ProduceResponseBlock)\n\tresponse.Blocks[\"foo\"][1] = &ProduceResponseBlock{\n\t\tErr:         ErrInvalidMessage,\n\t\tOffset:      255,\n\t\tTimestamp:   time.Unix(1, 0),\n\t\tStartOffset: 50,\n\t}\n\tresponse.ThrottleTime = 100 * time.Millisecond\n\tfor v, produceResponseManyBlocks := range produceResponseManyBlocksVersions {\n\t\tresponse.Version = int16(v)\n\t\ttestEncodable(t, fmt.Sprintf(\"many blocks version %d\", v), &response, produceResponseManyBlocks)\n\t}\n}\n\nfunc TestProduceResponseEncodeInvalidTimestamp(t *testing.T) {\n\tresponse := ProduceResponse{}\n\tresponse.Version = 2\n\tresponse.Blocks = make(map[string]map[int32]*ProduceResponseBlock)\n\tresponse.Blocks[\"t\"] = make(map[int32]*ProduceResponseBlock)\n\tresponse.Blocks[\"t\"][0] = &ProduceResponseBlock{\n\t\tErr:    ErrNoError,\n\t\tOffset: 0,\n\t\t// Use a timestamp before Unix time\n\t\tTimestamp: time.Unix(0, 0).Add(-1 * time.Millisecond),\n\t}\n\tresponse.ThrottleTime = 100 * time.Millisecond\n\t_, err := encode(&response, nil)\n\tif err == nil {\n\t\tt.Error(\"Expecting error, got nil\")\n\t}\n\ttarget := PacketEncodingError{}\n\tif !errors.As(err, &target) {\n\t\tt.Error(\"Expecting PacketEncodingError, got:\", err)\n\t}\n}\n"
  },
  {
    "path": "produce_set.go",
    "content": "package sarama\n\nimport (\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"time\"\n)\n\ntype partitionSet struct {\n\tmsgs          []*ProducerMessage\n\trecordsToSend Records\n\tbufferBytes   int\n}\n\ntype produceSet struct {\n\tparent        *asyncProducer\n\tmsgs          map[string]map[int32]*partitionSet\n\tproducerID    int64\n\tproducerEpoch int16\n\n\tbufferBytes int\n\tbufferCount int\n}\n\nfunc newProduceSet(parent *asyncProducer) *produceSet {\n\tpid, epoch := parent.txnmgr.getProducerID()\n\treturn newProduceSetWithMeta(parent, pid, epoch)\n}\n\nfunc newProduceSetWithMeta(parent *asyncProducer, producerID int64, producerEpoch int16) *produceSet {\n\treturn &produceSet{\n\t\tmsgs:          make(map[string]map[int32]*partitionSet),\n\t\tparent:        parent,\n\t\tproducerID:    producerID,\n\t\tproducerEpoch: producerEpoch,\n\t}\n}\n\nfunc (ps *produceSet) add(msg *ProducerMessage) error {\n\tvar err error\n\tvar key, val []byte\n\n\tif msg.Key != nil {\n\t\tif key, err = msg.Key.Encode(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif msg.Value != nil {\n\t\tif val, err = msg.Value.Encode(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\ttimestamp := msg.Timestamp\n\tif timestamp.IsZero() {\n\t\ttimestamp = time.Now()\n\t}\n\ttimestamp = timestamp.Truncate(time.Millisecond)\n\n\tpartitions := ps.msgs[msg.Topic]\n\tif partitions == nil {\n\t\tpartitions = make(map[int32]*partitionSet)\n\t\tps.msgs[msg.Topic] = partitions\n\t}\n\n\tvar size int\n\n\tset := partitions[msg.Partition]\n\tif set == nil {\n\t\tif ps.parent.conf.Version.IsAtLeast(V0_11_0_0) {\n\t\t\tbatch := &RecordBatch{\n\t\t\t\tFirstTimestamp:   timestamp,\n\t\t\t\tVersion:          2,\n\t\t\t\tCodec:            ps.parent.conf.Producer.Compression,\n\t\t\t\tCompressionLevel: ps.parent.conf.Producer.CompressionLevel,\n\t\t\t\tProducerID:       ps.producerID,\n\t\t\t\tProducerEpoch:    ps.producerEpoch,\n\t\t\t}\n\t\t\tif ps.parent.conf.Producer.Idempotent {\n\t\t\t\tbatch.FirstSequence = msg.sequenceNumber\n\t\t\t}\n\t\t\tset = &partitionSet{recordsToSend: newDefaultRecords(batch)}\n\t\t\tsize = recordBatchOverhead\n\t\t} else {\n\t\t\tset = &partitionSet{recordsToSend: newLegacyRecords(new(MessageSet))}\n\t\t}\n\t\tpartitions[msg.Partition] = set\n\t}\n\n\tif ps.parent.conf.Version.IsAtLeast(V0_11_0_0) {\n\t\tif ps.parent.conf.Producer.Idempotent && msg.sequenceNumber < set.recordsToSend.RecordBatch.FirstSequence {\n\t\t\treturn errors.New(\"assertion failed: message out of sequence added to a batch\")\n\t\t}\n\t}\n\n\t// Past this point we can't return an error, because we've already added the message to the set.\n\tset.msgs = append(set.msgs, msg)\n\n\tif ps.parent.conf.Version.IsAtLeast(V0_11_0_0) {\n\t\t// We are being conservative here to avoid having to prep encode the record\n\t\tsize += maximumRecordOverhead\n\t\trec := &Record{\n\t\t\tKey:            key,\n\t\t\tValue:          val,\n\t\t\tTimestampDelta: timestamp.Sub(set.recordsToSend.RecordBatch.FirstTimestamp),\n\t\t}\n\t\tsize += len(key) + len(val)\n\t\tif len(msg.Headers) > 0 {\n\t\t\trec.Headers = make([]*RecordHeader, len(msg.Headers))\n\t\t\tfor i := range msg.Headers {\n\t\t\t\trec.Headers[i] = &msg.Headers[i]\n\t\t\t\tsize += len(rec.Headers[i].Key) + len(rec.Headers[i].Value) + 2*binary.MaxVarintLen32\n\t\t\t}\n\t\t}\n\t\tset.recordsToSend.RecordBatch.addRecord(rec)\n\t} else {\n\t\tmsgToSend := &Message{Codec: CompressionNone, Key: key, Value: val}\n\t\tif ps.parent.conf.Version.IsAtLeast(V0_10_0_0) {\n\t\t\tmsgToSend.Timestamp = timestamp\n\t\t\tmsgToSend.Version = 1\n\t\t}\n\t\tset.recordsToSend.MsgSet.addMessage(msgToSend)\n\t\tsize = producerMessageOverhead + len(key) + len(val)\n\t}\n\n\tset.bufferBytes += size\n\tps.bufferBytes += size\n\tps.bufferCount++\n\n\treturn nil\n}\n\nfunc (ps *produceSet) takePartitions(predicate func(topic string, partition int32) bool) *produceSet {\n\tif ps.empty() {\n\t\treturn nil\n\t}\n\tout := newProduceSetWithMeta(ps.parent, ps.producerID, ps.producerEpoch)\n\tfor topic, partitions := range ps.msgs {\n\t\tfor partition, set := range partitions {\n\t\t\tif !predicate(topic, partition) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif out.msgs[topic] == nil {\n\t\t\t\tout.msgs[topic] = make(map[int32]*partitionSet)\n\t\t\t}\n\t\t\tout.msgs[topic][partition] = set\n\t\t\tout.bufferBytes += set.bufferBytes\n\t\t\tout.bufferCount += len(set.msgs)\n\t\t\tps.bufferBytes -= set.bufferBytes\n\t\t\tps.bufferCount -= len(set.msgs)\n\t\t\tdelete(partitions, partition)\n\t\t}\n\t\tif len(partitions) == 0 {\n\t\t\tdelete(ps.msgs, topic)\n\t\t}\n\t}\n\tif out.empty() {\n\t\treturn nil\n\t}\n\treturn out\n}\n\nfunc (ps *produceSet) copyFunc(predicate func(topic string, partition int32) bool) *produceSet {\n\tout := newProduceSetWithMeta(ps.parent, ps.producerID, ps.producerEpoch)\n\tfor topic, partitions := range ps.msgs {\n\t\tfor partition, set := range partitions {\n\t\t\tif !predicate(topic, partition) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif out.msgs[topic] == nil {\n\t\t\t\tout.msgs[topic] = make(map[int32]*partitionSet)\n\t\t\t}\n\t\t\tout.msgs[topic][partition] = set\n\t\t}\n\t}\n\treturn out\n}\n\nfunc (ps *produceSet) buildRequest() *ProduceRequest {\n\treq := &ProduceRequest{\n\t\tRequiredAcks: ps.parent.conf.Producer.RequiredAcks,\n\t\tTimeout:      int32(ps.parent.conf.Producer.Timeout / time.Millisecond),\n\t}\n\tif ps.parent.conf.Version.IsAtLeast(V0_10_0_0) {\n\t\treq.Version = 2\n\t}\n\tif ps.parent.conf.Version.IsAtLeast(V0_11_0_0) {\n\t\treq.Version = 3\n\t\tif ps.parent.IsTransactional() {\n\t\t\treq.TransactionalID = &ps.parent.conf.Producer.Transaction.ID\n\t\t}\n\t}\n\tif ps.parent.conf.Version.IsAtLeast(V1_0_0_0) {\n\t\treq.Version = 5\n\t}\n\tif ps.parent.conf.Version.IsAtLeast(V2_0_0_0) {\n\t\treq.Version = 6\n\t}\n\tif ps.parent.conf.Version.IsAtLeast(V2_1_0_0) {\n\t\treq.Version = 7\n\t}\n\n\tfor topic, partitionSets := range ps.msgs {\n\t\tfor partition, set := range partitionSets {\n\t\t\tif req.Version >= 3 {\n\t\t\t\t// If the API version we're hitting is 3 or greater, we need to calculate\n\t\t\t\t// offsets for each record in the batch relative to FirstOffset.\n\t\t\t\t// Additionally, we must set LastOffsetDelta to the value of the last offset\n\t\t\t\t// in the batch. Since the OffsetDelta of the first record is 0, we know that the\n\t\t\t\t// final record of any batch will have an offset of (# of records in batch) - 1.\n\t\t\t\t// (See https://cwiki.apache.org/confluence/display/KAFKA/A+Guide+To+The+Kafka+Protocol#AGuideToTheKafkaProtocol-Messagesets\n\t\t\t\t//  under the RecordBatch section for details.)\n\t\t\t\trb := set.recordsToSend.RecordBatch\n\t\t\t\tif len(rb.Records) > 0 {\n\t\t\t\t\trb.LastOffsetDelta = int32(len(rb.Records) - 1)\n\t\t\t\t\tvar maxTimestampDelta time.Duration\n\t\t\t\t\tfor i, record := range rb.Records {\n\t\t\t\t\t\trecord.OffsetDelta = int64(i)\n\t\t\t\t\t\tmaxTimestampDelta = max(maxTimestampDelta, record.TimestampDelta)\n\t\t\t\t\t}\n\t\t\t\t\t// Also set the MaxTimestamp similar to other clients.\n\t\t\t\t\trb.MaxTimestamp = rb.FirstTimestamp.Add(maxTimestampDelta)\n\t\t\t\t}\n\n\t\t\t\t// Set the batch as transactional when a transactionalID is set\n\t\t\t\trb.IsTransactional = ps.parent.IsTransactional()\n\n\t\t\t\treq.AddBatch(topic, partition, rb)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif ps.parent.conf.Producer.Compression == CompressionNone {\n\t\t\t\treq.AddSet(topic, partition, set.recordsToSend.MsgSet)\n\t\t\t} else {\n\t\t\t\t// When compression is enabled, the entire set for each partition is compressed\n\t\t\t\t// and sent as the payload of a single fake \"message\" with the appropriate codec\n\t\t\t\t// set and no key. When the server sees a message with a compression codec, it\n\t\t\t\t// decompresses the payload and treats the result as its message set.\n\n\t\t\t\tif ps.parent.conf.Version.IsAtLeast(V0_10_0_0) {\n\t\t\t\t\t// If our version is 0.10 or later, assign relative offsets\n\t\t\t\t\t// to the inner messages. This lets the broker avoid\n\t\t\t\t\t// recompressing the message set.\n\t\t\t\t\t// (See https://cwiki.apache.org/confluence/display/KAFKA/KIP-31+-+Move+to+relative+offsets+in+compressed+message+sets\n\t\t\t\t\t// for details on relative offsets.)\n\t\t\t\t\tfor i, msg := range set.recordsToSend.MsgSet.Messages {\n\t\t\t\t\t\tmsg.Offset = int64(i)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tpayload, err := encode(set.recordsToSend.MsgSet, ps.parent.metricsRegistry)\n\t\t\t\tif err != nil {\n\t\t\t\t\tLogger.Println(err) // if this happens, it's basically our fault.\n\t\t\t\t\tpanic(err)\n\t\t\t\t}\n\t\t\t\tcompMsg := &Message{\n\t\t\t\t\tCodec:            ps.parent.conf.Producer.Compression,\n\t\t\t\t\tCompressionLevel: ps.parent.conf.Producer.CompressionLevel,\n\t\t\t\t\tKey:              nil,\n\t\t\t\t\tValue:            payload,\n\t\t\t\t\tSet:              set.recordsToSend.MsgSet, // Provide the underlying message set for accurate metrics\n\t\t\t\t}\n\t\t\t\tif ps.parent.conf.Version.IsAtLeast(V0_10_0_0) {\n\t\t\t\t\tcompMsg.Version = 1\n\t\t\t\t\tcompMsg.Timestamp = set.recordsToSend.MsgSet.Messages[0].Msg.Timestamp\n\t\t\t\t}\n\t\t\t\treq.AddMessage(topic, partition, compMsg)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn req\n}\n\nfunc (ps *produceSet) eachPartition(cb func(topic string, partition int32, pSet *partitionSet)) {\n\tfor topic, partitionSet := range ps.msgs {\n\t\tfor partition, set := range partitionSet {\n\t\t\tcb(topic, partition, set)\n\t\t}\n\t}\n}\n\nfunc (ps *produceSet) anyPartition(predicate func(topic string, partition int32, pSet *partitionSet) bool) bool {\n\tfor topic, partitionSet := range ps.msgs {\n\t\tfor partition, set := range partitionSet {\n\t\t\tif predicate(topic, partition, set) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (ps *produceSet) dropPartition(topic string, partition int32) []*ProducerMessage {\n\tif ps.msgs[topic] == nil {\n\t\treturn nil\n\t}\n\tset := ps.msgs[topic][partition]\n\tif set == nil {\n\t\treturn nil\n\t}\n\tps.bufferBytes -= set.bufferBytes\n\tps.bufferCount -= len(set.msgs)\n\tdelete(ps.msgs[topic], partition)\n\treturn set.msgs\n}\n\nfunc (ps *produceSet) wouldOverflow(msg *ProducerMessage) bool {\n\tversion := 1\n\tif ps.parent.conf.Version.IsAtLeast(V0_11_0_0) {\n\t\tversion = 2\n\t}\n\n\tswitch {\n\t// Would we overflow our maximum possible size-on-the-wire? 10KiB is arbitrary overhead for safety.\n\tcase ps.bufferBytes+msg.ByteSize(version) >= int(MaxRequestSize-(10*1024)):\n\t\treturn true\n\t// Would we overflow the size-limit of a message-batch for this partition?\n\tcase ps.msgs[msg.Topic] != nil && ps.msgs[msg.Topic][msg.Partition] != nil &&\n\t\tps.msgs[msg.Topic][msg.Partition].bufferBytes+msg.ByteSize(version) >= ps.parent.conf.Producer.MaxMessageBytes:\n\t\treturn true\n\t// Would we overflow simply in number of messages?\n\tcase ps.parent.conf.Producer.Flush.MaxMessages > 0 && ps.bufferCount >= ps.parent.conf.Producer.Flush.MaxMessages:\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc (ps *produceSet) readyToFlush() bool {\n\tswitch {\n\t// If we don't have any messages, nothing else matters\n\tcase ps.empty():\n\t\treturn false\n\t// If all three config values are 0, we always flush as-fast-as-possible\n\tcase ps.parent.conf.Producer.Flush.Frequency == 0 && ps.parent.conf.Producer.Flush.Bytes == 0 && ps.parent.conf.Producer.Flush.Messages == 0:\n\t\treturn true\n\t// If we've passed the message trigger-point\n\tcase ps.parent.conf.Producer.Flush.Messages > 0 && ps.bufferCount >= ps.parent.conf.Producer.Flush.Messages:\n\t\treturn true\n\t// If we've passed the byte trigger-point\n\tcase ps.parent.conf.Producer.Flush.Bytes > 0 && ps.bufferBytes >= ps.parent.conf.Producer.Flush.Bytes:\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc (ps *produceSet) empty() bool {\n\treturn ps.bufferCount == 0\n}\n"
  },
  {
    "path": "produce_set_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc makeProduceSet() (*asyncProducer, *produceSet) {\n\tconf := NewTestConfig()\n\ttxnmgr, _ := newTransactionManager(conf, nil)\n\tparent := &asyncProducer{\n\t\tconf:   conf,\n\t\tmuter:  newPartitionMuter(),\n\t\ttxnmgr: txnmgr,\n\t}\n\treturn parent, newProduceSet(parent)\n}\n\nfunc safeAddMessage(t *testing.T, ps *produceSet, msg *ProducerMessage) {\n\tif err := ps.add(msg); err != nil {\n\t\tt.Error(err)\n\t}\n}\n\nfunc TestProduceSetInitial(t *testing.T) {\n\t_, ps := makeProduceSet()\n\n\tif !ps.empty() {\n\t\tt.Error(\"New produceSet should be empty\")\n\t}\n\n\tif ps.readyToFlush() {\n\t\tt.Error(\"Empty produceSet must never be ready to flush\")\n\t}\n}\n\nfunc TestProduceSetAddingMessages(t *testing.T) {\n\t_, ps := makeProduceSet()\n\tmsg := &ProducerMessage{Key: StringEncoder(TestMessage), Value: StringEncoder(TestMessage)}\n\n\tsafeAddMessage(t, ps, msg)\n\n\tif ps.empty() {\n\t\tt.Error(\"set shouldn't be empty when a message is added\")\n\t}\n\n\tif !ps.readyToFlush() {\n\t\tt.Error(\"by default set should be ready to flush when any message is in place\")\n\t}\n}\n\nfunc TestProduceSetAddingMessagesOverflowMessagesLimit(t *testing.T) {\n\tparent, ps := makeProduceSet()\n\tparent.conf.Producer.Flush.MaxMessages = 1000\n\n\tmsg := &ProducerMessage{Key: StringEncoder(TestMessage), Value: StringEncoder(TestMessage)}\n\n\tfor i := 0; i < 1000; i++ {\n\t\tif ps.wouldOverflow(msg) {\n\t\t\tt.Error(\"set shouldn't fill up after only\", i+1, \"messages\")\n\t\t}\n\t\tsafeAddMessage(t, ps, msg)\n\t}\n\n\tif !ps.wouldOverflow(msg) {\n\t\tt.Error(\"set should be full after 1000 messages\")\n\t}\n}\n\nfunc TestProduceSetAddingMessagesOverflowBytesLimit(t *testing.T) {\n\tparent, ps := makeProduceSet()\n\tparent.conf.Producer.MaxMessageBytes = 1000\n\n\tmsg := &ProducerMessage{Key: StringEncoder(TestMessage), Value: StringEncoder(TestMessage)}\n\n\tfor ps.bufferBytes+msg.ByteSize(2) < parent.conf.Producer.MaxMessageBytes {\n\t\tif ps.wouldOverflow(msg) {\n\t\t\tt.Error(\"set shouldn't fill up before 1000 bytes\")\n\t\t}\n\t\tsafeAddMessage(t, ps, msg)\n\t}\n\n\tif !ps.wouldOverflow(msg) {\n\t\tt.Error(\"set should be full after 1000 bytes\")\n\t}\n}\n\nfunc TestProduceSetPartitionTracking(t *testing.T) {\n\t_, ps := makeProduceSet()\n\n\tm1 := &ProducerMessage{Topic: \"t1\", Partition: 0}\n\tm2 := &ProducerMessage{Topic: \"t1\", Partition: 1}\n\tm3 := &ProducerMessage{Topic: \"t2\", Partition: 0}\n\tsafeAddMessage(t, ps, m1)\n\tsafeAddMessage(t, ps, m2)\n\tsafeAddMessage(t, ps, m3)\n\n\tseenT1P0 := false\n\tseenT1P1 := false\n\tseenT2P0 := false\n\n\tps.eachPartition(func(topic string, partition int32, pSet *partitionSet) {\n\t\tif len(pSet.msgs) != 1 {\n\t\t\tt.Error(\"Wrong message count\")\n\t\t}\n\n\t\tif topic == \"t1\" && partition == 0 {\n\t\t\tseenT1P0 = true\n\t\t} else if topic == \"t1\" && partition == 1 {\n\t\t\tseenT1P1 = true\n\t\t} else if topic == \"t2\" && partition == 0 {\n\t\t\tseenT2P0 = true\n\t\t}\n\t})\n\n\tif !seenT1P0 {\n\t\tt.Error(\"Didn't see t1p0\")\n\t}\n\tif !seenT1P1 {\n\t\tt.Error(\"Didn't see t1p1\")\n\t}\n\tif !seenT2P0 {\n\t\tt.Error(\"Didn't see t2p0\")\n\t}\n\n\tif len(ps.dropPartition(\"t1\", 1)) != 1 {\n\t\tt.Error(\"Got wrong messages back from dropping partition\")\n\t}\n\n\tif ps.bufferCount != 2 {\n\t\tt.Error(\"Incorrect buffer count after dropping partition\")\n\t}\n}\n\nfunc TestProduceSetRequestBuilding(t *testing.T) {\n\tparent, ps := makeProduceSet()\n\tparent.conf.Producer.RequiredAcks = WaitForAll\n\tparent.conf.Producer.Timeout = 10 * time.Second\n\n\tmsg := &ProducerMessage{\n\t\tTopic:     \"t1\",\n\t\tPartition: 0,\n\t\tKey:       StringEncoder(TestMessage),\n\t\tValue:     StringEncoder(TestMessage),\n\t}\n\tfor i := 0; i < 10; i++ {\n\t\tsafeAddMessage(t, ps, msg)\n\t}\n\tmsg.Partition = 1\n\tfor i := 0; i < 10; i++ {\n\t\tsafeAddMessage(t, ps, msg)\n\t}\n\tmsg.Topic = \"t2\"\n\tfor i := 0; i < 10; i++ {\n\t\tsafeAddMessage(t, ps, msg)\n\t}\n\n\treq := ps.buildRequest()\n\n\tif req.RequiredAcks != WaitForAll {\n\t\tt.Error(\"RequiredAcks not set properly\")\n\t}\n\n\tif req.Timeout != 10000 {\n\t\tt.Error(\"Timeout not set properly\")\n\t}\n\n\tif len(req.records) != 2 {\n\t\tt.Error(\"Wrong number of topics in request\")\n\t}\n}\n\nfunc TestProduceSetCompressedRequestBuilding(t *testing.T) {\n\tparent, ps := makeProduceSet()\n\tparent.conf.Producer.RequiredAcks = WaitForAll\n\tparent.conf.Producer.Timeout = 10 * time.Second\n\tparent.conf.Producer.Compression = CompressionGZIP\n\tparent.conf.Version = V0_10_0_0\n\n\tmsg := &ProducerMessage{\n\t\tTopic:     \"t1\",\n\t\tPartition: 0,\n\t\tKey:       StringEncoder(TestMessage),\n\t\tValue:     StringEncoder(TestMessage),\n\t\tTimestamp: time.Now(),\n\t}\n\tfor i := 0; i < 10; i++ {\n\t\tsafeAddMessage(t, ps, msg)\n\t}\n\n\treq := ps.buildRequest()\n\n\tif req.Version != 2 {\n\t\tt.Error(\"Wrong request version\")\n\t}\n\n\tfor _, msgBlock := range req.records[\"t1\"][0].MsgSet.Messages {\n\t\tmsg := msgBlock.Msg\n\t\terr := msg.decodeSet()\n\t\tif err != nil {\n\t\t\tt.Error(\"Failed to decode set from payload\")\n\t\t}\n\t\tfor i, compMsgBlock := range msg.Set.Messages {\n\t\t\tcompMsg := compMsgBlock.Msg\n\t\t\tif compMsg.Version != 1 {\n\t\t\t\tt.Error(\"Wrong compressed message version\")\n\t\t\t}\n\t\t\tif compMsgBlock.Offset != int64(i) {\n\t\t\t\tt.Errorf(\"Wrong relative inner offset, expected %d, got %d\", i, compMsgBlock.Offset)\n\t\t\t}\n\t\t}\n\t\tif msg.Version != 1 {\n\t\t\tt.Error(\"Wrong compressed parent message version\")\n\t\t}\n\t}\n}\n\nfunc TestProduceSetV3RequestBuilding(t *testing.T) {\n\tparent, ps := makeProduceSet()\n\tparent.conf.Producer.RequiredAcks = WaitForAll\n\tparent.conf.Producer.Timeout = 10 * time.Second\n\tparent.conf.Version = V0_11_0_0\n\n\tnow := time.Now()\n\tmsg := &ProducerMessage{\n\t\tTopic:     \"t1\",\n\t\tPartition: 0,\n\t\tKey:       StringEncoder(TestMessage),\n\t\tValue:     StringEncoder(TestMessage),\n\t\tHeaders: []RecordHeader{\n\t\t\t{\n\t\t\t\tKey:   []byte(\"header-1\"),\n\t\t\t\tValue: []byte(\"value-1\"),\n\t\t\t},\n\t\t\t{\n\t\t\t\tKey:   []byte(\"header-2\"),\n\t\t\t\tValue: []byte(\"value-2\"),\n\t\t\t},\n\t\t\t{\n\t\t\t\tKey:   []byte(\"header-3\"),\n\t\t\t\tValue: []byte(\"value-3\"),\n\t\t\t},\n\t\t},\n\t\tTimestamp: now,\n\t}\n\tfor i := 0; i < 10; i++ {\n\t\tsafeAddMessage(t, ps, msg)\n\t\tmsg.Timestamp = msg.Timestamp.Add(time.Second)\n\t}\n\n\treq := ps.buildRequest()\n\n\tif req.Version != 3 {\n\t\tt.Error(\"Wrong request version\")\n\t}\n\n\tbatch := req.records[\"t1\"][0].RecordBatch\n\tif !batch.FirstTimestamp.Equal(now.Truncate(time.Millisecond)) {\n\t\tt.Errorf(\"Wrong first timestamp: %v\", batch.FirstTimestamp)\n\t}\n\tif !batch.MaxTimestamp.Equal(now.Add(9 * time.Second).Truncate(time.Millisecond)) {\n\t\tt.Errorf(\"Wrong max timestamp: %v\", batch.MaxTimestamp)\n\t}\n\tfor i := 0; i < 10; i++ {\n\t\trec := batch.Records[i]\n\t\tif rec.TimestampDelta != time.Duration(i)*time.Second {\n\t\t\tt.Errorf(\"Wrong timestamp delta: %v\", rec.TimestampDelta)\n\t\t}\n\n\t\tif rec.OffsetDelta != int64(i) {\n\t\t\tt.Errorf(\"Wrong relative inner offset, expected %d, got %d\", i, rec.OffsetDelta)\n\t\t}\n\n\t\tfor j, h := range batch.Records[i].Headers {\n\t\t\texp := fmt.Sprintf(\"header-%d\", j+1)\n\t\t\tif string(h.Key) != exp {\n\t\t\t\tt.Errorf(\"Wrong header key, expected %v, got %v\", exp, h.Key)\n\t\t\t}\n\t\t\texp = fmt.Sprintf(\"value-%d\", j+1)\n\t\t\tif string(h.Value) != exp {\n\t\t\t\tt.Errorf(\"Wrong header value, expected %v, got %v\", exp, h.Value)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestProduceSetIdempotentRequestBuilding(t *testing.T) {\n\tconst pID = 1000\n\tconst pEpoch = 1234\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.RequiredAcks = WaitForAll\n\tconfig.Producer.Idempotent = true\n\tconfig.Version = V0_11_0_0\n\n\tparent := &asyncProducer{\n\t\tconf: config,\n\t\ttxnmgr: &transactionManager{\n\t\t\tproducerID:    pID,\n\t\t\tproducerEpoch: pEpoch,\n\t\t},\n\t}\n\tps := newProduceSet(parent)\n\n\tnow := time.Now()\n\tmsg := &ProducerMessage{\n\t\tTopic:     \"t1\",\n\t\tPartition: 0,\n\t\tKey:       StringEncoder(TestMessage),\n\t\tValue:     StringEncoder(TestMessage),\n\t\tHeaders: []RecordHeader{\n\t\t\t{\n\t\t\t\tKey:   []byte(\"header-1\"),\n\t\t\t\tValue: []byte(\"value-1\"),\n\t\t\t},\n\t\t\t{\n\t\t\t\tKey:   []byte(\"header-2\"),\n\t\t\t\tValue: []byte(\"value-2\"),\n\t\t\t},\n\t\t\t{\n\t\t\t\tKey:   []byte(\"header-3\"),\n\t\t\t\tValue: []byte(\"value-3\"),\n\t\t\t},\n\t\t},\n\t\tTimestamp:      now,\n\t\tsequenceNumber: 123,\n\t}\n\tfor i := 0; i < 10; i++ {\n\t\tsafeAddMessage(t, ps, msg)\n\t\tmsg.Timestamp = msg.Timestamp.Add(time.Second)\n\t}\n\n\treq := ps.buildRequest()\n\n\tif req.Version != 3 {\n\t\tt.Error(\"Wrong request version\")\n\t}\n\n\tbatch := req.records[\"t1\"][0].RecordBatch\n\tif !batch.FirstTimestamp.Equal(now.Truncate(time.Millisecond)) {\n\t\tt.Errorf(\"Wrong first timestamp: %v\", batch.FirstTimestamp)\n\t}\n\tif batch.ProducerID != pID {\n\t\tt.Errorf(\"Wrong producerID: %v\", batch.ProducerID)\n\t}\n\tif batch.ProducerEpoch != pEpoch {\n\t\tt.Errorf(\"Wrong producerEpoch: %v\", batch.ProducerEpoch)\n\t}\n\tif batch.FirstSequence != 123 {\n\t\tt.Errorf(\"Wrong first sequence: %v\", batch.FirstSequence)\n\t}\n\tfor i := 0; i < 10; i++ {\n\t\trec := batch.Records[i]\n\t\tif rec.TimestampDelta != time.Duration(i)*time.Second {\n\t\t\tt.Errorf(\"Wrong timestamp delta: %v\", rec.TimestampDelta)\n\t\t}\n\n\t\tif rec.OffsetDelta != int64(i) {\n\t\t\tt.Errorf(\"Wrong relative inner offset, expected %d, got %d\", i, rec.OffsetDelta)\n\t\t}\n\n\t\tfor j, h := range batch.Records[i].Headers {\n\t\t\texp := fmt.Sprintf(\"header-%d\", j+1)\n\t\t\tif string(h.Key) != exp {\n\t\t\t\tt.Errorf(\"Wrong header key, expected %v, got %v\", exp, h.Key)\n\t\t\t}\n\t\t\texp = fmt.Sprintf(\"value-%d\", j+1)\n\t\t\tif string(h.Value) != exp {\n\t\t\t\tt.Errorf(\"Wrong header value, expected %v, got %v\", exp, h.Value)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestProduceSetConsistentTimestamps(t *testing.T) {\n\tparent, ps1 := makeProduceSet()\n\tps2 := newProduceSet(parent)\n\tparent.conf.Producer.RequiredAcks = WaitForAll\n\tparent.conf.Producer.Timeout = 10 * time.Second\n\tparent.conf.Version = V0_11_0_0\n\n\tmsg1 := &ProducerMessage{\n\t\tTopic:          \"t1\",\n\t\tPartition:      0,\n\t\tKey:            StringEncoder(TestMessage),\n\t\tValue:          StringEncoder(TestMessage),\n\t\tTimestamp:      time.Unix(1555718400, 500000000),\n\t\tsequenceNumber: 123,\n\t}\n\tmsg2 := &ProducerMessage{\n\t\tTopic:          \"t1\",\n\t\tPartition:      0,\n\t\tKey:            StringEncoder(TestMessage),\n\t\tValue:          StringEncoder(TestMessage),\n\t\tTimestamp:      time.Unix(1555718400, 500900000),\n\t\tsequenceNumber: 123,\n\t}\n\tmsg3 := &ProducerMessage{\n\t\tTopic:          \"t1\",\n\t\tPartition:      0,\n\t\tKey:            StringEncoder(TestMessage),\n\t\tValue:          StringEncoder(TestMessage),\n\t\tTimestamp:      time.Unix(1555718400, 600000000),\n\t\tsequenceNumber: 123,\n\t}\n\n\tsafeAddMessage(t, ps1, msg1)\n\tsafeAddMessage(t, ps1, msg3)\n\treq1 := ps1.buildRequest()\n\tif req1.Version != 3 {\n\t\tt.Error(\"Wrong request version\")\n\t}\n\tbatch1 := req1.records[\"t1\"][0].RecordBatch\n\tft1 := batch1.FirstTimestamp.Unix()*1000 + int64(batch1.FirstTimestamp.Nanosecond()/1000000)\n\ttime1 := ft1 + int64(batch1.Records[1].TimestampDelta/time.Millisecond)\n\n\tsafeAddMessage(t, ps2, msg2)\n\tsafeAddMessage(t, ps2, msg3)\n\treq2 := ps2.buildRequest()\n\tif req2.Version != 3 {\n\t\tt.Error(\"Wrong request version\")\n\t}\n\tbatch2 := req2.records[\"t1\"][0].RecordBatch\n\tft2 := batch2.FirstTimestamp.Unix()*1000 + int64(batch2.FirstTimestamp.Nanosecond()/1000000)\n\ttime2 := ft2 + int64(batch2.Records[1].TimestampDelta/time.Millisecond)\n\n\tif time1 != time2 {\n\t\tt.Errorf(\"Message timestamps do not match: %v, %v\", time1, time2)\n\t}\n}\n"
  },
  {
    "path": "quota_types.go",
    "content": "package sarama\n\ntype (\n\tQuotaEntityType string\n\n\tQuotaMatchType int\n)\n\n// ref: https://github.com/apache/kafka/blob/trunk/clients/src/main/java/org/apache/kafka/common/quota/ClientQuotaEntity.java\nconst (\n\tQuotaEntityUser     QuotaEntityType = \"user\"\n\tQuotaEntityClientID QuotaEntityType = \"client-id\"\n\tQuotaEntityIP       QuotaEntityType = \"ip\"\n)\n\n// ref: https://github.com/apache/kafka/blob/trunk/clients/src/main/java/org/apache/kafka/common/requests/DescribeClientQuotasRequest.java\nconst (\n\tQuotaMatchExact QuotaMatchType = iota\n\tQuotaMatchDefault\n\tQuotaMatchAny\n)\n"
  },
  {
    "path": "real_decoder.go",
    "content": "package sarama\n\nimport (\n\t\"encoding/binary\"\n\t\"math\"\n\t\"time\"\n\n\t\"github.com/rcrowley/go-metrics\"\n)\n\nvar (\n\terrInvalidArrayLength     = PacketDecodingError{\"invalid array length\"}\n\terrInvalidByteSliceLength = PacketDecodingError{\"invalid byteslice length\"}\n\terrInvalidStringLength    = PacketDecodingError{\"invalid string length\"}\n\terrVarintOverflow         = PacketDecodingError{\"varint overflow\"}\n\terrUVarintOverflow        = PacketDecodingError{\"uvarint overflow\"}\n\terrInvalidBool            = PacketDecodingError{\"invalid bool\"}\n)\n\ntype realDecoder struct {\n\traw      []byte\n\toff      int\n\tstack    []pushDecoder\n\tregistry metrics.Registry\n}\n\ntype realFlexibleDecoder struct {\n\t*realDecoder\n}\n\n// primitives\n\nfunc (rd *realDecoder) getInt8() (int8, error) {\n\tif rd.remaining() < 1 {\n\t\trd.off = len(rd.raw)\n\t\treturn -1, ErrInsufficientData\n\t}\n\ttmp := int8(rd.raw[rd.off])\n\trd.off++\n\treturn tmp, nil\n}\n\nfunc (rd *realDecoder) getInt16() (int16, error) {\n\tif rd.remaining() < 2 {\n\t\trd.off = len(rd.raw)\n\t\treturn -1, ErrInsufficientData\n\t}\n\ttmp := int16(binary.BigEndian.Uint16(rd.raw[rd.off:]))\n\trd.off += 2\n\treturn tmp, nil\n}\n\nfunc (rd *realDecoder) getInt32() (int32, error) {\n\tif rd.remaining() < 4 {\n\t\trd.off = len(rd.raw)\n\t\treturn -1, ErrInsufficientData\n\t}\n\ttmp := int32(binary.BigEndian.Uint32(rd.raw[rd.off:]))\n\trd.off += 4\n\treturn tmp, nil\n}\n\nfunc (rd *realDecoder) getInt64() (int64, error) {\n\tif rd.remaining() < 8 {\n\t\trd.off = len(rd.raw)\n\t\treturn -1, ErrInsufficientData\n\t}\n\ttmp := int64(binary.BigEndian.Uint64(rd.raw[rd.off:]))\n\trd.off += 8\n\treturn tmp, nil\n}\n\nfunc (rd *realDecoder) getVarint() (int64, error) {\n\ttmp, n := binary.Varint(rd.raw[rd.off:])\n\tif n == 0 {\n\t\trd.off = len(rd.raw)\n\t\treturn -1, ErrInsufficientData\n\t}\n\tif n < 0 {\n\t\trd.off -= n\n\t\treturn -1, errVarintOverflow\n\t}\n\trd.off += n\n\treturn tmp, nil\n}\n\nfunc (rd *realDecoder) getUVarint() (uint64, error) {\n\ttmp, n := binary.Uvarint(rd.raw[rd.off:])\n\tif n == 0 {\n\t\trd.off = len(rd.raw)\n\t\treturn 0, ErrInsufficientData\n\t}\n\n\tif n < 0 {\n\t\trd.off -= n\n\t\treturn 0, errUVarintOverflow\n\t}\n\n\trd.off += n\n\treturn tmp, nil\n}\n\nfunc (rd *realDecoder) getFloat64() (float64, error) {\n\tif rd.remaining() < 8 {\n\t\trd.off = len(rd.raw)\n\t\treturn -1, ErrInsufficientData\n\t}\n\ttmp := math.Float64frombits(binary.BigEndian.Uint64(rd.raw[rd.off:]))\n\trd.off += 8\n\treturn tmp, nil\n}\n\nfunc (rd *realDecoder) getArrayLength() (int, error) {\n\tif rd.remaining() < 4 {\n\t\trd.off = len(rd.raw)\n\t\treturn -1, ErrInsufficientData\n\t}\n\t// cast to int32 first to get correct signedness for length before then\n\t// casting to int for ease of interop\n\ttmp := int(int32(binary.BigEndian.Uint32(rd.raw[rd.off:])))\n\trd.off += 4\n\tif tmp > rd.remaining() {\n\t\trd.off = len(rd.raw)\n\t\treturn -1, ErrInsufficientData\n\t} else if tmp > int(MaxResponseSize) {\n\t\treturn -1, errInvalidArrayLength\n\t}\n\treturn tmp, nil\n}\n\nfunc (rd *realDecoder) getBool() (bool, error) {\n\tb, err := rd.getInt8()\n\tif err != nil || b == 0 {\n\t\treturn false, err\n\t}\n\tif b != 1 {\n\t\treturn false, errInvalidBool\n\t}\n\treturn true, nil\n}\n\nfunc (rd *realDecoder) getKError() (KError, error) {\n\ti, err := rd.getInt16()\n\treturn KError(i), err\n}\n\nfunc (rd *realDecoder) getDurationMs() (time.Duration, error) {\n\tt, err := rd.getInt32()\n\tif err != nil {\n\t\treturn time.Duration(0), err\n\t}\n\treturn time.Duration(t) * time.Millisecond, nil\n}\n\nfunc (rd *realDecoder) getTaggedFieldArray(decoders taggedFieldDecoders) error {\n\treturn PacketDecodingError{\"tagged fields used in non-flexible context\"}\n}\n\nfunc (rd *realDecoder) getEmptyTaggedFieldArray() (int, error) {\n\treturn 0, nil\n}\n\n// collections\n\nfunc (rd *realDecoder) getBytes() ([]byte, error) {\n\ttmp, err := rd.getInt32()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif tmp == -1 {\n\t\treturn nil, nil\n\t}\n\n\treturn rd.getRawBytes(int(tmp))\n}\n\nfunc (rd *realDecoder) getVarintBytes() ([]byte, error) {\n\ttmp, err := rd.getVarint()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif tmp == -1 {\n\t\treturn nil, nil\n\t}\n\n\treturn rd.getRawBytes(int(tmp))\n}\n\nfunc (rd *realDecoder) getStringLength() (int, error) {\n\tlength, err := rd.getInt16()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tn := int(length)\n\n\tswitch {\n\tcase n < -1:\n\t\treturn 0, errInvalidStringLength\n\tcase n > rd.remaining():\n\t\trd.off = len(rd.raw)\n\t\treturn 0, ErrInsufficientData\n\t}\n\n\treturn n, nil\n}\n\nfunc (rd *realDecoder) getString() (string, error) {\n\tn, err := rd.getStringLength()\n\tif err != nil || n == -1 {\n\t\treturn \"\", err\n\t}\n\n\ttmpStr := string(rd.raw[rd.off : rd.off+n])\n\trd.off += n\n\treturn tmpStr, nil\n}\n\nfunc (rd *realDecoder) getNullableString() (*string, error) {\n\tn, err := rd.getStringLength()\n\tif err != nil || n == -1 {\n\t\treturn nil, err\n\t}\n\n\ttmpStr := string(rd.raw[rd.off : rd.off+n])\n\trd.off += n\n\treturn &tmpStr, err\n}\n\nfunc (rd *realDecoder) getInt32Array() ([]int32, error) {\n\tn, err := rd.getArrayLength()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif n <= 0 {\n\t\treturn nil, nil\n\t}\n\n\tif rd.remaining() < 4*n {\n\t\trd.off = len(rd.raw)\n\t\treturn nil, ErrInsufficientData\n\t}\n\n\tret := make([]int32, n)\n\tfor i := range ret {\n\t\tret[i] = int32(binary.BigEndian.Uint32(rd.raw[rd.off:]))\n\t\trd.off += 4\n\t}\n\treturn ret, nil\n}\n\nfunc (rd *realDecoder) getInt64Array() ([]int64, error) {\n\tn, err := rd.getArrayLength()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif n <= 0 {\n\t\treturn nil, nil\n\t}\n\n\tif rd.remaining() < 8*n {\n\t\trd.off = len(rd.raw)\n\t\treturn nil, ErrInsufficientData\n\t}\n\n\tret := make([]int64, n)\n\tfor i := range ret {\n\t\tret[i] = int64(binary.BigEndian.Uint64(rd.raw[rd.off:]))\n\t\trd.off += 8\n\t}\n\treturn ret, nil\n}\n\nfunc (rd *realDecoder) getStringArray() ([]string, error) {\n\tn, err := rd.getArrayLength()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif n <= 0 {\n\t\treturn nil, nil\n\t}\n\n\tret := make([]string, n)\n\tfor i := range ret {\n\t\tstr, err := rd.getString()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tret[i] = str\n\t}\n\treturn ret, nil\n}\n\n// subsets\n\nfunc (rd *realDecoder) remaining() int {\n\treturn len(rd.raw) - rd.off\n}\n\nfunc (rd *realDecoder) getSubset(length int) (packetDecoder, error) {\n\tbuf, err := rd.getRawBytes(length)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &realDecoder{raw: buf}, nil\n}\n\nfunc (rd *realDecoder) getRawBytes(length int) ([]byte, error) {\n\tif length < 0 {\n\t\treturn nil, errInvalidByteSliceLength\n\t} else if length > rd.remaining() {\n\t\trd.off = len(rd.raw)\n\t\treturn nil, ErrInsufficientData\n\t}\n\n\tstart := rd.off\n\trd.off += length\n\treturn rd.raw[start:rd.off], nil\n}\n\nfunc (rd *realDecoder) peek(offset, length int) (packetDecoder, error) {\n\tif rd.remaining() < offset+length {\n\t\treturn nil, ErrInsufficientData\n\t}\n\toff := rd.off + offset\n\treturn &realDecoder{raw: rd.raw[off : off+length]}, nil\n}\n\nfunc (rd *realDecoder) peekInt8(offset int) (int8, error) {\n\tconst byteLen = 1\n\tif rd.remaining() < offset+byteLen {\n\t\treturn -1, ErrInsufficientData\n\t}\n\treturn int8(rd.raw[rd.off+offset]), nil\n}\n\n// stacks\n\nfunc (rd *realDecoder) push(in pushDecoder) error {\n\tin.saveOffset(rd.off)\n\n\tvar reserve int\n\tif dpd, ok := in.(dynamicPushDecoder); ok {\n\t\tif err := dpd.decode(rd); err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\treserve = in.reserveLength()\n\t\tif rd.remaining() < reserve {\n\t\t\trd.off = len(rd.raw)\n\t\t\treturn ErrInsufficientData\n\t\t}\n\t}\n\n\trd.stack = append(rd.stack, in)\n\n\trd.off += reserve\n\n\treturn nil\n}\n\nfunc (rd *realDecoder) pop() error {\n\t// this is go's ugly pop pattern (the inverse of append)\n\tin := rd.stack[len(rd.stack)-1]\n\trd.stack = rd.stack[:len(rd.stack)-1]\n\n\treturn in.check(rd.off, rd.raw)\n}\n\nfunc (rd *realDecoder) metricRegistry() metrics.Registry {\n\treturn rd.registry\n}\n\nfunc (rd *realFlexibleDecoder) getArrayLength() (int, error) {\n\tn, err := rd.getUVarint()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tif n == 0 {\n\t\treturn 0, nil\n\t}\n\n\treturn int(n) - 1, nil\n}\n\nfunc (rd *realFlexibleDecoder) getEmptyTaggedFieldArray() (int, error) {\n\ttagCount, err := rd.getUVarint()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\t// skip over any tagged fields without deserializing them\n\t// as we don't currently support doing anything with them\n\tfor i := uint64(0); i < tagCount; i++ {\n\t\t// fetch and ignore tag identifier\n\t\t_, err := rd.getUVarint()\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\tlength, err := rd.getUVarint()\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\tif _, err := rd.getRawBytes(int(length)); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t}\n\n\treturn 0, nil\n}\n\nfunc (rd *realFlexibleDecoder) getTaggedFieldArray(decoders taggedFieldDecoders) error {\n\t// if we have no decoders just skip over the tagged fields\n\tif decoders == nil {\n\t\t_, err := rd.getEmptyTaggedFieldArray()\n\t\treturn err\n\t}\n\n\ttagCount, err := rd.getUVarint()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor i := uint64(0); i < tagCount; i++ {\n\t\t// fetch and ignore tag identifier\n\t\tid, err := rd.getUVarint()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tlength, err := rd.getUVarint()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tbytes, err := rd.getRawBytes(int(length))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdecoder, ok := decoders[id]\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif err := decoder(&realFlexibleDecoder{&realDecoder{raw: bytes}}); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (rd *realFlexibleDecoder) getBytes() ([]byte, error) {\n\tn, err := rd.getUVarint()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlength := int(n - 1)\n\treturn rd.getRawBytes(length)\n}\n\nfunc (rd *realFlexibleDecoder) getStringLength() (int, error) {\n\tlength, err := rd.getUVarint()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tn := int(length - 1)\n\n\tswitch {\n\tcase n < -1:\n\t\treturn 0, errInvalidStringLength\n\tcase n > rd.remaining():\n\t\trd.off = len(rd.raw)\n\t\treturn 0, ErrInsufficientData\n\t}\n\n\treturn n, nil\n}\n\nfunc (rd *realFlexibleDecoder) getString() (string, error) {\n\tlength, err := rd.getStringLength()\n\tif err != nil || length == -1 {\n\t\treturn \"\", err\n\t}\n\n\tif length < 0 {\n\t\treturn \"\", errInvalidStringLength\n\t}\n\ttmpStr := string(rd.raw[rd.off : rd.off+length])\n\trd.off += length\n\treturn tmpStr, nil\n}\n\nfunc (rd *realFlexibleDecoder) getNullableString() (*string, error) {\n\tlength, err := rd.getStringLength()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif length < 0 {\n\t\treturn nil, err\n\t}\n\n\ttmpStr := string(rd.raw[rd.off : rd.off+length])\n\trd.off += length\n\treturn &tmpStr, err\n}\n\nfunc (rd *realFlexibleDecoder) getInt32Array() ([]int32, error) {\n\tn, err := rd.getUVarint()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif n == 0 {\n\t\treturn nil, nil\n\t}\n\n\tarrayLength := int(n) - 1\n\n\tret := make([]int32, arrayLength)\n\n\tfor i := range ret {\n\t\tret[i] = int32(binary.BigEndian.Uint32(rd.raw[rd.off:]))\n\t\trd.off += 4\n\t}\n\treturn ret, nil\n}\n\nfunc (rd *realFlexibleDecoder) getStringArray() ([]string, error) {\n\tn, err := rd.getArrayLength()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif n <= 0 {\n\t\treturn nil, nil\n\t}\n\n\tret := make([]string, n)\n\tfor i := range ret {\n\t\tstr, err := rd.getString()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tret[i] = str\n\t}\n\treturn ret, nil\n}\n"
  },
  {
    "path": "real_decoder_test.go",
    "content": "package sarama\n\nimport (\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"testing\"\n)\n\nfunc TestRealDecoder_getArrayLength(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\tinput   []byte\n\t\twantLen int\n\t\twantErr error\n\t}{\n\t\t{\n\t\t\tname:    \"null array (-1)\",\n\t\t\tinput:   []byte{0xFF, 0xFF, 0xFF, 0xFF},\n\t\t\twantLen: -1,\n\t\t\twantErr: nil,\n\t\t},\n\t\t{\n\t\t\tname:    \"valid array length 64\",\n\t\t\tinput:   makeInput(64),\n\t\t\twantLen: 64,\n\t\t\twantErr: nil,\n\t\t},\n\t\t{\n\t\t\tname:    \"valid array up to MaxResponseSize\",\n\t\t\tinput:   makeInput(int(MaxResponseSize)),\n\t\t\twantLen: int(MaxResponseSize),\n\t\t\twantErr: nil,\n\t\t},\n\t\t{\n\t\t\tname:    \"insufficient data\",\n\t\t\tinput:   []byte{0x00, 0x00, 0x00}, // fewer than 4 bytes\n\t\t\twantLen: -1,\n\t\t\twantErr: ErrInsufficientData,\n\t\t},\n\t\t{\n\t\t\tname:    \"length exceeds remaining\",\n\t\t\tinput:   []byte{0x00, 0x00, 0x00, 0x05, 0x00}, // length of 5, but only 1 byte remains\n\t\t\twantLen: -1,\n\t\t\twantErr: ErrInsufficientData,\n\t\t},\n\t\t{\n\t\t\tname:    \"length exceeds MaxResponseSize\",\n\t\t\tinput:   makeInput(int(MaxResponseSize + 1)),\n\t\t\twantLen: -1,\n\t\t\twantErr: errInvalidArrayLength,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\trd := &realDecoder{\n\t\t\t\traw: tt.input,\n\t\t\t}\n\t\t\tgotLen, gotErr := rd.getArrayLength()\n\t\t\tif gotLen != tt.wantLen {\n\t\t\t\tt.Errorf(\"getArrayLength() gotLen = %v, want %v\", gotLen, tt.wantLen)\n\t\t\t}\n\t\t\tif !errors.Is(gotErr, tt.wantErr) {\n\t\t\t\tt.Errorf(\"getArrayLength() gotErr = %v, want %v\", gotErr, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc makeInput(length int) []byte {\n\tinput := make([]byte, 4+length)\n\tbinary.BigEndian.PutUint32(input, uint32(length)) // #nosec G115 - not going to exceed uint32\n\treturn input\n}\n"
  },
  {
    "path": "real_encoder.go",
    "content": "package sarama\n\nimport (\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"math\"\n\t\"time\"\n\n\t\"github.com/rcrowley/go-metrics\"\n)\n\ntype realEncoder struct {\n\traw      []byte\n\toff      int\n\tstack    []pushEncoder\n\tregistry metrics.Registry\n}\n\ntype realFlexibleEncoder struct {\n\t*realEncoder\n}\n\n// primitives\n\nfunc (re *realEncoder) putInt8(in int8) {\n\tre.raw[re.off] = byte(in)\n\tre.off++\n}\n\nfunc (re *realEncoder) putInt16(in int16) {\n\tbinary.BigEndian.PutUint16(re.raw[re.off:], uint16(in))\n\tre.off += 2\n}\n\nfunc (re *realEncoder) putInt32(in int32) {\n\tbinary.BigEndian.PutUint32(re.raw[re.off:], uint32(in))\n\tre.off += 4\n}\n\nfunc (re *realEncoder) putInt64(in int64) {\n\tbinary.BigEndian.PutUint64(re.raw[re.off:], uint64(in))\n\tre.off += 8\n}\n\nfunc (re *realEncoder) putVarint(in int64) {\n\tre.off += binary.PutVarint(re.raw[re.off:], in)\n}\n\nfunc (re *realEncoder) putUVarint(in uint64) {\n\tre.off += binary.PutUvarint(re.raw[re.off:], in)\n}\n\nfunc (re *realEncoder) putFloat64(in float64) {\n\tbinary.BigEndian.PutUint64(re.raw[re.off:], math.Float64bits(in))\n\tre.off += 8\n}\n\nfunc (re *realEncoder) putArrayLength(in int) error {\n\tre.putInt32(int32(in))\n\treturn nil\n}\n\nfunc (re *realEncoder) putBool(in bool) {\n\tif in {\n\t\tre.putInt8(1)\n\t\treturn\n\t}\n\tre.putInt8(0)\n}\n\nfunc (re *realEncoder) putKError(in KError) {\n\tre.putInt16(int16(in))\n}\n\nfunc (re *realEncoder) putDurationMs(in time.Duration) {\n\tre.putInt32(int32(in / time.Millisecond))\n}\n\n// collection\n\nfunc (re *realEncoder) putRawBytes(in []byte) error {\n\tcopy(re.raw[re.off:], in)\n\tre.off += len(in)\n\treturn nil\n}\n\nfunc (re *realEncoder) putBytes(in []byte) error {\n\tif in == nil {\n\t\tre.putInt32(-1)\n\t\treturn nil\n\t}\n\tre.putInt32(int32(len(in)))\n\treturn re.putRawBytes(in)\n}\n\nfunc (re *realEncoder) putVarintBytes(in []byte) error {\n\tif in == nil {\n\t\tre.putVarint(-1)\n\t\treturn nil\n\t}\n\tre.putVarint(int64(len(in)))\n\treturn re.putRawBytes(in)\n}\n\nfunc (re *realEncoder) putString(in string) error {\n\tre.putInt16(int16(len(in)))\n\tcopy(re.raw[re.off:], in)\n\tre.off += len(in)\n\treturn nil\n}\n\nfunc (re *realEncoder) putNullableString(in *string) error {\n\tif in == nil {\n\t\tre.putInt16(-1)\n\t\treturn nil\n\t}\n\treturn re.putString(*in)\n}\n\nfunc (re *realEncoder) putStringArray(in []string) error {\n\terr := re.putArrayLength(len(in))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, val := range in {\n\t\tif err := re.putString(val); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (re *realEncoder) putInt32Array(in []int32) error {\n\terr := re.putArrayLength(len(in))\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, val := range in {\n\t\tre.putInt32(val)\n\t}\n\treturn nil\n}\n\nfunc (re *realEncoder) putNullableInt32Array(in []int32) error {\n\tif in == nil {\n\t\tre.putInt32(-1)\n\t\treturn nil\n\t}\n\terr := re.putArrayLength(len(in))\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, val := range in {\n\t\tre.putInt32(val)\n\t}\n\treturn nil\n}\n\nfunc (re *realEncoder) putInt64Array(in []int64) error {\n\terr := re.putArrayLength(len(in))\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, val := range in {\n\t\tre.putInt64(val)\n\t}\n\treturn nil\n}\n\nfunc (re *realEncoder) putEmptyTaggedFieldArray() {\n}\n\nfunc (re *realEncoder) offset() int {\n\treturn re.off\n}\n\n// stacks\n\nfunc (re *realEncoder) push(in pushEncoder) {\n\tin.saveOffset(re.off)\n\tre.off += in.reserveLength()\n\tre.stack = append(re.stack, in)\n}\n\nfunc (re *realEncoder) pop() error {\n\t// this is go's ugly pop pattern (the inverse of append)\n\tin := re.stack[len(re.stack)-1]\n\tre.stack = re.stack[:len(re.stack)-1]\n\n\treturn in.run(re.off, re.raw)\n}\n\n// we do record metrics during the real encoder pass\nfunc (re *realEncoder) metricRegistry() metrics.Registry {\n\treturn re.registry\n}\n\nfunc (re *realFlexibleEncoder) putArrayLength(in int) error {\n\t// 0 represents a null array, so +1 has to be added\n\tre.putUVarint(uint64(in + 1))\n\treturn nil\n}\n\nfunc (re *realFlexibleEncoder) putBytes(in []byte) error {\n\tre.putUVarint(uint64(len(in) + 1))\n\treturn re.putRawBytes(in)\n}\n\nfunc (re *realFlexibleEncoder) putString(in string) error {\n\tif err := re.putArrayLength(len(in)); err != nil {\n\t\treturn err\n\t}\n\treturn re.putRawBytes([]byte(in))\n}\n\nfunc (re *realFlexibleEncoder) putNullableString(in *string) error {\n\tif in == nil {\n\t\tre.putInt8(0)\n\t\treturn nil\n\t}\n\treturn re.putString(*in)\n}\n\nfunc (re *realFlexibleEncoder) putStringArray(in []string) error {\n\terr := re.putArrayLength(len(in))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, val := range in {\n\t\tif err := re.putString(val); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (re *realFlexibleEncoder) putInt32Array(in []int32) error {\n\tif in == nil {\n\t\treturn errors.New(\"expected int32 array to be non null\")\n\t}\n\t// 0 represents a null array, so +1 has to be added\n\tre.putUVarint(uint64(len(in)) + 1)\n\tfor _, val := range in {\n\t\tre.putInt32(val)\n\t}\n\treturn nil\n}\n\nfunc (re *realFlexibleEncoder) putNullableInt32Array(in []int32) error {\n\tif in == nil {\n\t\tre.putUVarint(0)\n\t\treturn nil\n\t}\n\t// 0 represents a null array, so +1 has to be added\n\tre.putUVarint(uint64(len(in)) + 1)\n\tfor _, val := range in {\n\t\tre.putInt32(val)\n\t}\n\treturn nil\n}\n\nfunc (re *realFlexibleEncoder) putEmptyTaggedFieldArray() {\n\tre.putUVarint(0)\n}\n"
  },
  {
    "path": "record.go",
    "content": "package sarama\n\nimport (\n\t\"encoding/binary\"\n\t\"time\"\n)\n\nconst (\n\tisTransactionalMask   = 0x10\n\tcontrolMask           = 0x20\n\tmaximumRecordOverhead = 5*binary.MaxVarintLen32 + binary.MaxVarintLen64 + 1\n)\n\n// RecordHeader stores key and value for a record header\ntype RecordHeader struct {\n\tKey   []byte\n\tValue []byte\n}\n\nfunc (h *RecordHeader) encode(pe packetEncoder) error {\n\tif err := pe.putVarintBytes(h.Key); err != nil {\n\t\treturn err\n\t}\n\treturn pe.putVarintBytes(h.Value)\n}\n\nfunc (h *RecordHeader) decode(pd packetDecoder) (err error) {\n\tif h.Key, err = pd.getVarintBytes(); err != nil {\n\t\treturn err\n\t}\n\n\tif h.Value, err = pd.getVarintBytes(); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// Record is kafka record type\ntype Record struct {\n\tHeaders []*RecordHeader\n\n\tAttributes     int8\n\tTimestampDelta time.Duration\n\tOffsetDelta    int64\n\tKey            []byte\n\tValue          []byte\n\tlength         varintLengthField\n}\n\nfunc (r *Record) encode(pe packetEncoder) error {\n\tpe.push(&r.length)\n\tpe.putInt8(r.Attributes)\n\tpe.putVarint(int64(r.TimestampDelta / time.Millisecond))\n\tpe.putVarint(r.OffsetDelta)\n\tif err := pe.putVarintBytes(r.Key); err != nil {\n\t\treturn err\n\t}\n\tif err := pe.putVarintBytes(r.Value); err != nil {\n\t\treturn err\n\t}\n\tpe.putVarint(int64(len(r.Headers)))\n\n\tfor _, h := range r.Headers {\n\t\tif err := h.encode(pe); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn pe.pop()\n}\n\nfunc (r *Record) decode(pd packetDecoder) (err error) {\n\tif err = pd.push(&r.length); err != nil {\n\t\treturn err\n\t}\n\n\tif r.Attributes, err = pd.getInt8(); err != nil {\n\t\treturn err\n\t}\n\n\ttimestamp, err := pd.getVarint()\n\tif err != nil {\n\t\treturn err\n\t}\n\tr.TimestampDelta = time.Duration(timestamp) * time.Millisecond\n\n\tif r.OffsetDelta, err = pd.getVarint(); err != nil {\n\t\treturn err\n\t}\n\n\tif r.Key, err = pd.getVarintBytes(); err != nil {\n\t\treturn err\n\t}\n\n\tif r.Value, err = pd.getVarintBytes(); err != nil {\n\t\treturn err\n\t}\n\n\tnumHeaders, err := pd.getVarint()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif numHeaders >= 0 {\n\t\tr.Headers = make([]*RecordHeader, numHeaders)\n\t}\n\tfor i := int64(0); i < numHeaders; i++ {\n\t\thdr := new(RecordHeader)\n\t\tif err := hdr.decode(pd); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.Headers[i] = hdr\n\t}\n\n\treturn pd.pop()\n}\n"
  },
  {
    "path": "record_batch.go",
    "content": "package sarama\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n)\n\nconst recordBatchOverhead = 49\n\ntype recordsArray []*Record\n\nfunc (e recordsArray) encode(pe packetEncoder) error {\n\tfor _, r := range e {\n\t\tif err := r.encode(pe); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (e recordsArray) decode(pd packetDecoder) error {\n\trecords := make([]Record, len(e))\n\tfor i := range e {\n\t\tif err := records[i].decode(pd); err != nil {\n\t\t\treturn err\n\t\t}\n\t\te[i] = &records[i]\n\t}\n\treturn nil\n}\n\ntype RecordBatch struct {\n\tFirstOffset           int64\n\tPartitionLeaderEpoch  int32\n\tVersion               int8\n\tCodec                 CompressionCodec\n\tCompressionLevel      int\n\tControl               bool\n\tLogAppendTime         bool\n\tLastOffsetDelta       int32\n\tFirstTimestamp        time.Time\n\tMaxTimestamp          time.Time\n\tProducerID            int64\n\tProducerEpoch         int16\n\tFirstSequence         int32\n\tRecords               []*Record\n\tPartialTrailingRecord bool\n\tIsTransactional       bool\n\n\tcompressedRecords []byte\n\trecordsLen        int // uncompressed records size\n}\n\nfunc (b *RecordBatch) LastOffset() int64 {\n\treturn b.FirstOffset + int64(b.LastOffsetDelta)\n}\n\nfunc (b *RecordBatch) encode(pe packetEncoder) error {\n\tif b.Version != 2 {\n\t\treturn PacketEncodingError{fmt.Sprintf(\"unsupported record batch version (%d)\", b.Version)}\n\t}\n\tpe.putInt64(b.FirstOffset)\n\tpe.push(&lengthField{})\n\tpe.putInt32(b.PartitionLeaderEpoch)\n\tpe.putInt8(b.Version)\n\tpe.push(newCRC32Field(crcCastagnoli))\n\tpe.putInt16(b.computeAttributes())\n\tpe.putInt32(b.LastOffsetDelta)\n\n\tif err := (Timestamp{&b.FirstTimestamp}).encode(pe); err != nil {\n\t\treturn err\n\t}\n\n\tif err := (Timestamp{&b.MaxTimestamp}).encode(pe); err != nil {\n\t\treturn err\n\t}\n\n\tpe.putInt64(b.ProducerID)\n\tpe.putInt16(b.ProducerEpoch)\n\tpe.putInt32(b.FirstSequence)\n\n\tif err := pe.putArrayLength(len(b.Records)); err != nil {\n\t\treturn err\n\t}\n\n\tif b.compressedRecords == nil {\n\t\tif err := b.encodeRecords(pe); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif err := pe.putRawBytes(b.compressedRecords); err != nil {\n\t\treturn err\n\t}\n\n\tif err := pe.pop(); err != nil {\n\t\treturn err\n\t}\n\treturn pe.pop()\n}\n\nfunc (b *RecordBatch) decode(pd packetDecoder) (err error) {\n\tif b.FirstOffset, err = pd.getInt64(); err != nil {\n\t\treturn err\n\t}\n\n\tbatchLen, err := pd.getInt32()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif b.PartitionLeaderEpoch, err = pd.getInt32(); err != nil {\n\t\treturn err\n\t}\n\n\tif b.Version, err = pd.getInt8(); err != nil {\n\t\treturn err\n\t}\n\n\tcrc32Decoder := acquireCrc32Field(crcCastagnoli)\n\tdefer releaseCrc32Field(crc32Decoder)\n\n\tif err = pd.push(crc32Decoder); err != nil {\n\t\treturn err\n\t}\n\n\tattributes, err := pd.getInt16()\n\tif err != nil {\n\t\treturn err\n\t}\n\tb.Codec = CompressionCodec(int8(attributes) & compressionCodecMask)\n\tb.Control = attributes&controlMask == controlMask\n\tb.LogAppendTime = attributes&timestampTypeMask == timestampTypeMask\n\tb.IsTransactional = attributes&isTransactionalMask == isTransactionalMask\n\n\tif b.LastOffsetDelta, err = pd.getInt32(); err != nil {\n\t\treturn err\n\t}\n\n\tif err = (Timestamp{&b.FirstTimestamp}).decode(pd); err != nil {\n\t\treturn err\n\t}\n\n\tif err = (Timestamp{&b.MaxTimestamp}).decode(pd); err != nil {\n\t\treturn err\n\t}\n\n\tif b.ProducerID, err = pd.getInt64(); err != nil {\n\t\treturn err\n\t}\n\n\tif b.ProducerEpoch, err = pd.getInt16(); err != nil {\n\t\treturn err\n\t}\n\n\tif b.FirstSequence, err = pd.getInt32(); err != nil {\n\t\treturn err\n\t}\n\n\tnumRecs, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif numRecs >= 0 {\n\t\tb.Records = make([]*Record, numRecs)\n\t}\n\n\tbufSize := int(batchLen) - recordBatchOverhead\n\trecBuffer, err := pd.getRawBytes(bufSize)\n\tif err != nil {\n\t\tif errors.Is(err, ErrInsufficientData) {\n\t\t\tb.PartialTrailingRecord = true\n\t\t\tb.Records = nil\n\t\t\treturn nil\n\t\t}\n\t\treturn err\n\t}\n\n\tif err = pd.pop(); err != nil {\n\t\treturn err\n\t}\n\n\trecBuffer, err = decompress(b.Codec, recBuffer)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tb.recordsLen = len(recBuffer)\n\terr = decode(recBuffer, recordsArray(b.Records), nil)\n\tif errors.Is(err, ErrInsufficientData) {\n\t\tb.PartialTrailingRecord = true\n\t\tb.Records = nil\n\t\treturn nil\n\t}\n\treturn err\n}\n\nfunc (b *RecordBatch) encodeRecords(pe packetEncoder) error {\n\tvar raw []byte\n\tvar err error\n\tif raw, err = encode(recordsArray(b.Records), pe.metricRegistry()); err != nil {\n\t\treturn err\n\t}\n\tb.recordsLen = len(raw)\n\n\tb.compressedRecords, err = compress(b.Codec, b.CompressionLevel, raw)\n\treturn err\n}\n\nfunc (b *RecordBatch) computeAttributes() int16 {\n\tattr := int16(b.Codec) & int16(compressionCodecMask)\n\tif b.Control {\n\t\tattr |= controlMask\n\t}\n\tif b.LogAppendTime {\n\t\tattr |= timestampTypeMask\n\t}\n\tif b.IsTransactional {\n\t\tattr |= isTransactionalMask\n\t}\n\treturn attr\n}\n\nfunc (b *RecordBatch) addRecord(r *Record) {\n\tb.Records = append(b.Records, r)\n}\n"
  },
  {
    "path": "record_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"encoding/binary\"\n\t\"math\"\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/davecgh/go-spew/spew\"\n)\n\nfunc recordBatchTestCases() []struct {\n\tname         string\n\tbatch        RecordBatch\n\tencoded      []byte\n\toldGoEncoded []byte\n} {\n\treturn []struct {\n\t\tname         string\n\t\tbatch        RecordBatch\n\t\tencoded      []byte\n\t\toldGoEncoded []byte // used in case of gzipped content for go versions prior to 1.8\n\t}{\n\t\t{\n\t\t\tname: \"empty record\",\n\t\t\tbatch: RecordBatch{\n\t\t\t\tVersion:        2,\n\t\t\t\tFirstTimestamp: time.Unix(0, 0),\n\t\t\t\tMaxTimestamp:   time.Unix(0, 0),\n\t\t\t\tRecords:        []*Record{},\n\t\t\t},\n\t\t\tencoded: []byte{\n\t\t\t\t0, 0, 0, 0, 0, 0, 0, 0, // First Offset\n\t\t\t\t0, 0, 0, 49, // Length\n\t\t\t\t0, 0, 0, 0, // Partition Leader Epoch\n\t\t\t\t2,                // Version\n\t\t\t\t89, 95, 183, 221, // CRC\n\t\t\t\t0, 0, // Attributes\n\t\t\t\t0, 0, 0, 0, // Last Offset Delta\n\t\t\t\t0, 0, 0, 0, 0, 0, 0, 0, // First Timestamp\n\t\t\t\t0, 0, 0, 0, 0, 0, 0, 0, // Max Timestamp\n\t\t\t\t0, 0, 0, 0, 0, 0, 0, 0, // Producer ID\n\t\t\t\t0, 0, // Producer Epoch\n\t\t\t\t0, 0, 0, 0, // First Sequence\n\t\t\t\t0, 0, 0, 0, // Number of Records\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"control batch\",\n\t\t\tbatch: RecordBatch{\n\t\t\t\tVersion:        2,\n\t\t\t\tControl:        true,\n\t\t\t\tFirstTimestamp: time.Unix(0, 0),\n\t\t\t\tMaxTimestamp:   time.Unix(0, 0),\n\t\t\t\tRecords:        []*Record{},\n\t\t\t},\n\t\t\tencoded: []byte{\n\t\t\t\t0, 0, 0, 0, 0, 0, 0, 0, // First Offset\n\t\t\t\t0, 0, 0, 49, // Length\n\t\t\t\t0, 0, 0, 0, // Partition Leader Epoch\n\t\t\t\t2,               // Version\n\t\t\t\t81, 46, 67, 217, // CRC\n\t\t\t\t0, 32, // Attributes\n\t\t\t\t0, 0, 0, 0, // Last Offset Delta\n\t\t\t\t0, 0, 0, 0, 0, 0, 0, 0, // First Timestamp\n\t\t\t\t0, 0, 0, 0, 0, 0, 0, 0, // Max Timestamp\n\t\t\t\t0, 0, 0, 0, 0, 0, 0, 0, // Producer ID\n\t\t\t\t0, 0, // Producer Epoch\n\t\t\t\t0, 0, 0, 0, // First Sequence\n\t\t\t\t0, 0, 0, 0, // Number of Records\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"uncompressed record\",\n\t\t\tbatch: RecordBatch{\n\t\t\t\tVersion:         2,\n\t\t\t\tFirstTimestamp:  time.Unix(1479847795, 0),\n\t\t\t\tMaxTimestamp:    time.Unix(0, 0),\n\t\t\t\tLastOffsetDelta: 0,\n\t\t\t\tRecords: []*Record{{\n\t\t\t\t\tTimestampDelta: 5 * time.Millisecond,\n\t\t\t\t\tKey:            []byte{1, 2, 3, 4},\n\t\t\t\t\tValue:          []byte{5, 6, 7},\n\t\t\t\t\tHeaders: []*RecordHeader{{\n\t\t\t\t\t\tKey:   []byte{8, 9, 10},\n\t\t\t\t\t\tValue: []byte{11, 12},\n\t\t\t\t\t}},\n\t\t\t\t}},\n\t\t\t\trecordsLen: 21,\n\t\t\t},\n\t\t\tencoded: []byte{\n\t\t\t\t0, 0, 0, 0, 0, 0, 0, 0, // First Offset\n\t\t\t\t0, 0, 0, 70, // Length\n\t\t\t\t0, 0, 0, 0, // Partition Leader Epoch\n\t\t\t\t2,                // Version\n\t\t\t\t84, 121, 97, 253, // CRC\n\t\t\t\t0, 0, // Attributes\n\t\t\t\t0, 0, 0, 0, // Last Offset Delta\n\t\t\t\t0, 0, 1, 88, 141, 205, 89, 56, // First Timestamp\n\t\t\t\t0, 0, 0, 0, 0, 0, 0, 0, // Max Timestamp\n\t\t\t\t0, 0, 0, 0, 0, 0, 0, 0, // Producer ID\n\t\t\t\t0, 0, // Producer Epoch\n\t\t\t\t0, 0, 0, 0, // First Sequence\n\t\t\t\t0, 0, 0, 1, // Number of Records\n\t\t\t\t40, // Record Length\n\t\t\t\t0,  // Attributes\n\t\t\t\t10, // Timestamp Delta\n\t\t\t\t0,  // Offset Delta\n\t\t\t\t8,  // Key Length\n\t\t\t\t1, 2, 3, 4,\n\t\t\t\t6, // Value Length\n\t\t\t\t5, 6, 7,\n\t\t\t\t2,        // Number of Headers\n\t\t\t\t6,        // Header Key Length\n\t\t\t\t8, 9, 10, // Header Key\n\t\t\t\t4,      // Header Value Length\n\t\t\t\t11, 12, // Header Value\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"gzipped record\",\n\t\t\tbatch: RecordBatch{\n\t\t\t\tVersion:          2,\n\t\t\t\tCodec:            CompressionGZIP,\n\t\t\t\tCompressionLevel: CompressionLevelDefault,\n\t\t\t\tFirstTimestamp:   time.Unix(1479847795, 0),\n\t\t\t\tMaxTimestamp:     time.Unix(0, 0),\n\t\t\t\tLastOffsetDelta:  0,\n\t\t\t\tRecords: []*Record{{\n\t\t\t\t\tTimestampDelta: 5 * time.Millisecond,\n\t\t\t\t\tKey:            []byte{1, 2, 3, 4},\n\t\t\t\t\tValue:          []byte{5, 6, 7},\n\t\t\t\t\tHeaders: []*RecordHeader{{\n\t\t\t\t\t\tKey:   []byte{8, 9, 10},\n\t\t\t\t\t\tValue: []byte{11, 12},\n\t\t\t\t\t}},\n\t\t\t\t}},\n\t\t\t\trecordsLen: 21,\n\t\t\t},\n\t\t\tencoded: []byte{\n\t\t\t\t0, 0, 0, 0, 0, 0, 0, 0, // First Offset\n\t\t\t\t0, 0, 0, 95, // Length\n\t\t\t\t0, 0, 0, 0, // Partition Leader Epoch\n\t\t\t\t2,                 // Version\n\t\t\t\t231, 74, 206, 165, // CRC\n\t\t\t\t0, 1, // Attributes\n\t\t\t\t0, 0, 0, 0, // Last Offset Delta\n\t\t\t\t0, 0, 1, 88, 141, 205, 89, 56, // First Timestamp\n\t\t\t\t0, 0, 0, 0, 0, 0, 0, 0, // Max Timestamp\n\t\t\t\t0, 0, 0, 0, 0, 0, 0, 0, // Producer ID\n\t\t\t\t0, 0, // Producer Epoch\n\t\t\t\t0, 0, 0, 0, // First Sequence\n\t\t\t\t0, 0, 0, 1, // Number of Records\n\t\t\t\t31, 139, 8, 0, 0, 9, 110, 136, 0, 255, 0, 21, 0, 234, 255, 40, 0, 10, 0, 8, 1, 2,\n\t\t\t\t3, 4, 6, 5, 6, 7, 2, 6, 8, 9, 10, 4, 11, 12, 3, 0, 173, 201, 88, 103, 21, 0, 0, 0,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"snappy compressed record\",\n\t\t\tbatch: RecordBatch{\n\t\t\t\tVersion:         2,\n\t\t\t\tCodec:           CompressionSnappy,\n\t\t\t\tFirstTimestamp:  time.Unix(1479847795, 0),\n\t\t\t\tMaxTimestamp:    time.Unix(0, 0),\n\t\t\t\tLastOffsetDelta: 0,\n\t\t\t\tRecords: []*Record{{\n\t\t\t\t\tTimestampDelta: 5 * time.Millisecond,\n\t\t\t\t\tKey:            []byte{1, 2, 3, 4},\n\t\t\t\t\tValue:          []byte{5, 6, 7},\n\t\t\t\t\tHeaders: []*RecordHeader{{\n\t\t\t\t\t\tKey:   []byte{8, 9, 10},\n\t\t\t\t\t\tValue: []byte{11, 12},\n\t\t\t\t\t}},\n\t\t\t\t}},\n\t\t\t\trecordsLen: 21,\n\t\t\t},\n\t\t\tencoded: []byte{\n\t\t\t\t0, 0, 0, 0, 0, 0, 0, 0, // First Offset\n\t\t\t\t0, 0, 0, 92, // Length\n\t\t\t\t0, 0, 0, 0, // Partition Leader Epoch\n\t\t\t\t2,                 // Version\n\t\t\t\t133, 100, 178, 36, // CRC\n\t\t\t\t0, 2, // Attributes\n\t\t\t\t0, 0, 0, 0, // Last Offset Delta\n\t\t\t\t0, 0, 1, 88, 141, 205, 89, 56, // First Timestamp\n\t\t\t\t0, 0, 0, 0, 0, 0, 0, 0, // Max Timestamp\n\t\t\t\t0, 0, 0, 0, 0, 0, 0, 0, // Producer ID\n\t\t\t\t0, 0, // Producer Epoch\n\t\t\t\t0, 0, 0, 0, // First Sequence\n\t\t\t\t0, 0, 0, 1, // Number of Records\n\t\t\t\t// Xerial framing header + compressed data\n\t\t\t\t130, 83, 78, 65, 80, 80, 89, 0, // SNAPPY magic\n\t\t\t\t0, 0, 0, 1, // min version\n\t\t\t\t0, 0, 0, 1, // default version\n\t\t\t\t0, 0, 0, 23, // compressed length\n\t\t\t\t21, 80, 40, 0, 10, 0, 8, 1, 2, 3, 4, 6, 5, 6, 7, 2, 6, 8, 9, 10, 4, 11, 12, // compressed data\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"lz4 compressed record\",\n\t\t\tbatch: RecordBatch{\n\t\t\t\tVersion:         2,\n\t\t\t\tCodec:           CompressionLZ4,\n\t\t\t\tFirstTimestamp:  time.Unix(1479847795, 0),\n\t\t\t\tMaxTimestamp:    time.Unix(0, 0),\n\t\t\t\tLastOffsetDelta: 0,\n\t\t\t\tRecords: []*Record{{\n\t\t\t\t\tTimestampDelta: 5 * time.Millisecond,\n\t\t\t\t\tKey:            []byte{1, 2, 3, 4},\n\t\t\t\t\tValue:          []byte{5, 6, 7},\n\t\t\t\t\tHeaders: []*RecordHeader{{\n\t\t\t\t\t\tKey:   []byte{8, 9, 10},\n\t\t\t\t\t\tValue: []byte{11, 12},\n\t\t\t\t\t}},\n\t\t\t\t}},\n\t\t\t\trecordsLen: 21,\n\t\t\t},\n\t\t\tencoded: []byte{\n\t\t\t\t0, 0, 0, 0, 0, 0, 0, 0, // First Offset\n\t\t\t\t0, 0, 0, 89, // Length\n\t\t\t\t0, 0, 0, 0, // Partition Leader Epoch\n\t\t\t\t2,                 // Version\n\t\t\t\t157, 18, 145, 248, // CRC\n\t\t\t\t0, 3, // Attributes\n\t\t\t\t0, 0, 0, 0, // Last Offset Delta\n\t\t\t\t0, 0, 1, 88, 141, 205, 89, 56, // First Timestamp\n\t\t\t\t0, 0, 0, 0, 0, 0, 0, 0, // Max Timestamp\n\t\t\t\t0, 0, 0, 0, 0, 0, 0, 0, // Producer ID\n\t\t\t\t0, 0, // Producer Epoch\n\t\t\t\t0, 0, 0, 0, // First Sequence\n\t\t\t\t0, 0, 0, 1, // Number of Records\n\t\t\t\t4, 34, 77, 24, 100, 64, 167, 21, 0, 0, 128, 40, 0, 10, 0, 8, 1, 2, 3, 4, 6, 5, 6, 7, 2,\n\t\t\t\t6, 8, 9, 10, 4, 11, 12, 0, 0, 0, 0, 12, 59, 239, 146,\n\t\t\t},\n\t\t},\n\t\t//\n\t}\n}\n\nfunc TestRecordBatchEncoding(t *testing.T) {\n\tfor _, tc := range recordBatchTestCases() {\n\t\ttestEncodable(t, tc.name, &tc.batch, tc.encoded)\n\t}\n}\n\nfunc TestRecordBatchDecoding(t *testing.T) {\n\tfor _, tc := range recordBatchTestCases() {\n\t\tbatch := RecordBatch{}\n\t\ttestDecodable(t, tc.name, &batch, tc.encoded)\n\t\tfor _, r := range batch.Records {\n\t\t\tr.length = varintLengthField{}\n\t\t}\n\t\t// The compression level is not restored on decoding. It is not needed\n\t\t// anyway. We only set it here to ensure that comparison succeeds.\n\t\tbatch.CompressionLevel = tc.batch.CompressionLevel\n\t\tif !reflect.DeepEqual(batch, tc.batch) {\n\t\t\tt.Error(spew.Sprintf(\"invalid decode of %s\\ngot %+v\\nwanted %+v\", tc.name, batch, tc.batch))\n\t\t}\n\t}\n}\n\nfunc TestRecordBatchLargeNumRecords(t *testing.T) {\n\tnumOfRecords := 10 + (2 * math.MaxUint16)\n\tnumofRecordsBytes := make([]byte, 4)\n\tbinary.BigEndian.PutUint32(numofRecordsBytes, uint32(numOfRecords))\n\n\tencodedBatch := []byte{\n\t\t0, 0, 0, 0, 0, 0, 0, 0, // First Offset\n\t\t0, 42, 0, 250, // Length\n\t\t0, 0, 0, 0, // Partition Leader Epoch\n\t\t2,                 // Version\n\t\t103, 68, 166, 213, // CRC\n\t\t0, 0, // Attributes\n\t\t0, 0, 0, 0, // Last Offset Delta\n\t\t0, 0, 1, 88, 141, 205, 89, 56, // First Timestamp\n\t\t0, 0, 0, 0, 0, 0, 0, 0, // Max Timestamp\n\t\t0, 0, 0, 0, 0, 0, 0, 0, // Producer ID\n\t\t0, 0, // Producer Epoch\n\t\t0, 0, 0, 0, // First Sequence\n\t}\n\n\tencodedBatch = append(encodedBatch, numofRecordsBytes...)\n\n\tfor range numOfRecords {\n\t\tencodedBatch = append(encodedBatch, []byte{\n\t\t\t40, // Record Length\n\t\t\t0,  // Attributes\n\t\t\t10, // Timestamp Delta\n\t\t\t0,  // Offset Delta\n\t\t\t8,  // Key Length\n\t\t\t1, 2, 3, 4,\n\t\t\t6, // Value Length\n\t\t\t5, 6, 7,\n\t\t\t2,        // Number of Headers\n\t\t\t6,        // Header Key Length\n\t\t\t8, 9, 10, // Header Key\n\t\t\t4,      // Header Value Length\n\t\t\t11, 12, // Header Value\n\t\t}...)\n\t}\n\n\tvar batch RecordBatch\n\terr := decode(encodedBatch, &batch, nil)\n\tif err != nil {\n\t\tt.Fatal(\"received error while decoding record batch\", err)\n\t}\n}\n"
  },
  {
    "path": "records.go",
    "content": "package sarama\n\nimport \"fmt\"\n\nconst (\n\tunknownRecords = iota\n\tlegacyRecords\n\tdefaultRecords\n\n\tmagicOffset = 16\n)\n\n// Records implements a union type containing either a RecordBatch or a legacy MessageSet.\ntype Records struct {\n\trecordsType int\n\tMsgSet      *MessageSet\n\tRecordBatch *RecordBatch\n}\n\nfunc newLegacyRecords(msgSet *MessageSet) Records {\n\treturn Records{recordsType: legacyRecords, MsgSet: msgSet}\n}\n\nfunc newDefaultRecords(batch *RecordBatch) Records {\n\treturn Records{recordsType: defaultRecords, RecordBatch: batch}\n}\n\n// setTypeFromFields sets type of Records depending on which of MsgSet or RecordBatch is not nil.\n// The first return value indicates whether both fields are nil (and the type is not set).\n// If both fields are not nil, it returns an error.\nfunc (r *Records) setTypeFromFields() (bool, error) {\n\tif r.MsgSet == nil && r.RecordBatch == nil {\n\t\treturn true, nil\n\t}\n\tif r.MsgSet != nil && r.RecordBatch != nil {\n\t\treturn false, fmt.Errorf(\"both MsgSet and RecordBatch are set, but record type is unknown\")\n\t}\n\tr.recordsType = defaultRecords\n\tif r.MsgSet != nil {\n\t\tr.recordsType = legacyRecords\n\t}\n\treturn false, nil\n}\n\nfunc (r *Records) encode(pe packetEncoder) error {\n\tif r.recordsType == unknownRecords {\n\t\tif empty, err := r.setTypeFromFields(); err != nil || empty {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tswitch r.recordsType {\n\tcase legacyRecords:\n\t\tif r.MsgSet == nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn r.MsgSet.encode(pe)\n\tcase defaultRecords:\n\t\tif r.RecordBatch == nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn r.RecordBatch.encode(pe)\n\t}\n\n\treturn fmt.Errorf(\"unknown records type: %v\", r.recordsType)\n}\n\nfunc (r *Records) setTypeFromMagic(pd packetDecoder) error {\n\tmagic, err := magicValue(pd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr.recordsType = defaultRecords\n\tif magic < 2 {\n\t\tr.recordsType = legacyRecords\n\t}\n\n\treturn nil\n}\n\nfunc (r *Records) decode(pd packetDecoder) error {\n\tif r.recordsType == unknownRecords {\n\t\tif err := r.setTypeFromMagic(pd); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tswitch r.recordsType {\n\tcase legacyRecords:\n\t\tr.MsgSet = &MessageSet{}\n\t\treturn r.MsgSet.decode(pd)\n\tcase defaultRecords:\n\t\tr.RecordBatch = &RecordBatch{}\n\t\treturn r.RecordBatch.decode(pd)\n\t}\n\treturn fmt.Errorf(\"unknown records type: %v\", r.recordsType)\n}\n\nfunc (r *Records) numRecords() (int, error) {\n\tif r.recordsType == unknownRecords {\n\t\tif empty, err := r.setTypeFromFields(); err != nil || empty {\n\t\t\treturn 0, err\n\t\t}\n\t}\n\n\tswitch r.recordsType {\n\tcase legacyRecords:\n\t\tif r.MsgSet == nil {\n\t\t\treturn 0, nil\n\t\t}\n\t\treturn len(r.MsgSet.Messages), nil\n\tcase defaultRecords:\n\t\tif r.RecordBatch == nil {\n\t\t\treturn 0, nil\n\t\t}\n\t\treturn len(r.RecordBatch.Records), nil\n\t}\n\treturn 0, fmt.Errorf(\"unknown records type: %v\", r.recordsType)\n}\n\nfunc (r *Records) isPartial() (bool, error) {\n\tif r.recordsType == unknownRecords {\n\t\tif empty, err := r.setTypeFromFields(); err != nil || empty {\n\t\t\treturn false, err\n\t\t}\n\t}\n\n\tswitch r.recordsType {\n\tcase unknownRecords:\n\t\treturn false, nil\n\tcase legacyRecords:\n\t\tif r.MsgSet == nil {\n\t\t\treturn false, nil\n\t\t}\n\t\treturn r.MsgSet.PartialTrailingMessage, nil\n\tcase defaultRecords:\n\t\tif r.RecordBatch == nil {\n\t\t\treturn false, nil\n\t\t}\n\t\treturn r.RecordBatch.PartialTrailingRecord, nil\n\t}\n\treturn false, fmt.Errorf(\"unknown records type: %v\", r.recordsType)\n}\n\nfunc (r *Records) isControl() (bool, error) {\n\tif r.recordsType == unknownRecords {\n\t\tif empty, err := r.setTypeFromFields(); err != nil || empty {\n\t\t\treturn false, err\n\t\t}\n\t}\n\n\tswitch r.recordsType {\n\tcase legacyRecords:\n\t\treturn false, nil\n\tcase defaultRecords:\n\t\tif r.RecordBatch == nil {\n\t\t\treturn false, nil\n\t\t}\n\t\treturn r.RecordBatch.Control, nil\n\t}\n\treturn false, fmt.Errorf(\"unknown records type: %v\", r.recordsType)\n}\n\nfunc (r *Records) isOverflow() (bool, error) {\n\tif r.recordsType == unknownRecords {\n\t\tif empty, err := r.setTypeFromFields(); err != nil || empty {\n\t\t\treturn false, err\n\t\t}\n\t}\n\n\tswitch r.recordsType {\n\tcase unknownRecords:\n\t\treturn false, nil\n\tcase legacyRecords:\n\t\tif r.MsgSet == nil {\n\t\t\treturn false, nil\n\t\t}\n\t\treturn r.MsgSet.OverflowMessage, nil\n\tcase defaultRecords:\n\t\treturn false, nil\n\t}\n\treturn false, fmt.Errorf(\"unknown records type: %v\", r.recordsType)\n}\n\nfunc (r *Records) nextOffset() (*int64, error) {\n\tswitch r.recordsType {\n\tcase unknownRecords:\n\t\treturn nil, nil\n\tcase legacyRecords:\n\t\treturn nil, nil\n\tcase defaultRecords:\n\t\tif r.RecordBatch == nil {\n\t\t\treturn nil, nil\n\t\t}\n\t\tnextOffset := r.RecordBatch.LastOffset() + 1\n\t\treturn &nextOffset, nil\n\t}\n\treturn nil, fmt.Errorf(\"unknown records type: %v\", r.recordsType)\n}\n\nfunc magicValue(pd packetDecoder) (int8, error) {\n\treturn pd.peekInt8(magicOffset)\n}\n\nfunc (r *Records) getControlRecord() (ControlRecord, error) {\n\tif r.RecordBatch == nil || len(r.RecordBatch.Records) == 0 {\n\t\treturn ControlRecord{}, fmt.Errorf(\"cannot get control record, record batch is empty\")\n\t}\n\n\tfirstRecord := r.RecordBatch.Records[0]\n\tcontrolRecord := ControlRecord{}\n\terr := controlRecord.decode(&realDecoder{raw: firstRecord.Key}, &realDecoder{raw: firstRecord.Value})\n\tif err != nil {\n\t\treturn ControlRecord{}, err\n\t}\n\n\treturn controlRecord, nil\n}\n"
  },
  {
    "path": "records_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"bytes\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestLegacyRecords(t *testing.T) {\n\tset := &MessageSet{\n\t\tMessages: []*MessageBlock{\n\t\t\t{\n\t\t\t\tMsg: &Message{\n\t\t\t\t\tVersion: 1,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tr := newLegacyRecords(set)\n\n\texp, err := encode(set, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tbuf, err := encode(&r, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif !bytes.Equal(buf, exp) {\n\t\tt.Errorf(\"Wrong encoding for legacy records, wanted %v, got %v\", exp, buf)\n\t}\n\n\tset = &MessageSet{}\n\tr = Records{}\n\n\terr = decode(exp, set, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\terr = decode(buf, &r, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif r.recordsType != legacyRecords {\n\t\tt.Fatalf(\"Wrong records type %v, expected %v\", r.recordsType, legacyRecords)\n\t}\n\tif !reflect.DeepEqual(set, r.MsgSet) {\n\t\tt.Errorf(\"Wrong decoding for legacy records, wanted %#+v, got %#+v\", set, r.MsgSet)\n\t}\n\n\tn, err := r.numRecords()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif n != 1 {\n\t\tt.Errorf(\"Wrong number of records, wanted 1, got %d\", n)\n\t}\n\n\tp, err := r.isPartial()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif p {\n\t\tt.Errorf(\"MessageSet shouldn't have a partial trailing message\")\n\t}\n\n\tc, err := r.isControl()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif c {\n\t\tt.Errorf(\"MessageSet can't be a control batch\")\n\t}\n\tf, err := r.nextOffset()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif f != nil {\n\t\tt.Errorf(\"RecordBatch record offset is invalid\")\n\t}\n}\n\nfunc TestDefaultRecords(t *testing.T) {\n\tbatch := &RecordBatch{\n\t\tIsTransactional: true,\n\t\tVersion:         2,\n\t\tFirstOffset:     1,\n\t\tRecords: []*Record{\n\t\t\t{\n\t\t\t\tValue: []byte{1},\n\t\t\t},\n\t\t},\n\t}\n\n\tr := newDefaultRecords(batch)\n\n\texp, err := encode(batch, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tbuf, err := encode(&r, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif !bytes.Equal(buf, exp) {\n\t\tt.Errorf(\"Wrong encoding for default records, wanted %v, got %v\", exp, buf)\n\t}\n\n\tbatch = &RecordBatch{}\n\tr = Records{}\n\n\terr = decode(exp, batch, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\terr = decode(buf, &r, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif r.recordsType != defaultRecords {\n\t\tt.Fatalf(\"Wrong records type %v, expected %v\", r.recordsType, defaultRecords)\n\t}\n\tif !reflect.DeepEqual(batch, r.RecordBatch) {\n\t\tt.Errorf(\"Wrong decoding for default records, wanted %#+v, got %#+v\", batch, r.RecordBatch)\n\t}\n\n\tn, err := r.numRecords()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif n != 1 {\n\t\tt.Errorf(\"Wrong number of records, wanted 1, got %d\", n)\n\t}\n\n\tp, err := r.isPartial()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif p {\n\t\tt.Errorf(\"RecordBatch shouldn't have a partial trailing record\")\n\t}\n\n\tc, err := r.isControl()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif c {\n\t\tt.Errorf(\"RecordBatch shouldn't be a control batch\")\n\t}\n\tf, err := r.nextOffset()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif f == nil || *f != 2 {\n\t\tt.Errorf(\"RecordBatch record offset is invalid\")\n\t}\n}\n"
  },
  {
    "path": "request.go",
    "content": "package sarama\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"io\"\n)\n\ntype protocolBody interface {\n\tencoder\n\tversionedDecoder\n\tkey() int16\n\tversion() int16\n\tsetVersion(int16)\n\theaderVersion() int16\n\tisValidVersion() bool\n\trequiredVersion() KafkaVersion\n}\n\ntype request struct {\n\tcorrelationID int32\n\tclientID      string\n\tbody          protocolBody\n}\n\nfunc (r *request) encode(pe packetEncoder) error {\n\tpe.push(&lengthField{})\n\tpe.putInt16(r.body.key())\n\tpe.putInt16(r.body.version())\n\tpe.putInt32(r.correlationID)\n\n\tif r.body.headerVersion() >= 1 {\n\t\terr := pe.putString(r.clientID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif r.body.headerVersion() >= 2 {\n\t\t// we don't use tag headers at the moment so we just put an array length of 0\n\t\tpe.putUVarint(0)\n\t}\n\tpe = prepareFlexibleEncoder(pe, r.body)\n\n\terr := r.body.encode(pe)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn pe.pop()\n}\n\nfunc (r *request) decode(pd packetDecoder) (err error) {\n\tkey, err := pd.getInt16()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tversion, err := pd.getInt16()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr.correlationID, err = pd.getInt32()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr.clientID, err = pd.getString()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr.body = allocateBody(key, version)\n\tif r.body == nil {\n\t\treturn PacketDecodingError{fmt.Sprintf(\"unknown request key (%d)\", key)}\n\t}\n\n\tif r.body.headerVersion() >= 2 {\n\t\t// tagged field\n\t\t_, err = pd.getUVarint()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif decoder, ok := pd.(*realDecoder); ok {\n\t\tpd = prepareFlexibleDecoder(decoder, r.body, version)\n\t}\n\treturn r.body.decode(pd, version)\n}\n\nfunc decodeRequest(r io.Reader) (*request, int, error) {\n\tvar (\n\t\tbytesRead   int\n\t\tlengthBytes = make([]byte, 4)\n\t)\n\n\tif n, err := io.ReadFull(r, lengthBytes); err != nil {\n\t\treturn nil, n, err\n\t}\n\n\tbytesRead += len(lengthBytes)\n\tlength := int32(binary.BigEndian.Uint32(lengthBytes))\n\n\tif length <= 4 || length > MaxRequestSize {\n\t\treturn nil, bytesRead, PacketDecodingError{fmt.Sprintf(\"message of length %d too large or too small\", length)}\n\t}\n\n\tencodedReq := make([]byte, length)\n\tif n, err := io.ReadFull(r, encodedReq); err != nil {\n\t\treturn nil, bytesRead + n, err\n\t}\n\n\tbytesRead += len(encodedReq)\n\n\treq := &request{}\n\tif err := decode(encodedReq, req, nil); err != nil {\n\t\treturn nil, bytesRead, err\n\t}\n\n\treturn req, bytesRead, nil\n}\n\nfunc allocateBody(key, version int16) protocolBody {\n\tswitch key {\n\tcase apiKeyProduce:\n\t\treturn &ProduceRequest{Version: version}\n\tcase apiKeyFetch:\n\t\treturn &FetchRequest{Version: version}\n\tcase apiKeyListOffsets:\n\t\treturn &OffsetRequest{Version: version}\n\tcase apiKeyMetadata:\n\t\treturn &MetadataRequest{Version: version}\n\t// 4: LeaderAndIsrRequest\n\t// 5: StopReplicaRequest\n\t// 6: UpdateMetadataRequest\n\t// 7: ControlledShutdownRequest\n\tcase apiKeyOffsetCommit:\n\t\treturn &OffsetCommitRequest{Version: version}\n\tcase apiKeyOffsetFetch:\n\t\treturn &OffsetFetchRequest{Version: version}\n\tcase apiKeyFindCoordinator:\n\t\treturn &FindCoordinatorRequest{Version: version}\n\tcase apiKeyJoinGroup:\n\t\treturn &JoinGroupRequest{Version: version}\n\tcase apiKeyHeartbeat:\n\t\treturn &HeartbeatRequest{Version: version}\n\tcase apiKeyLeaveGroup:\n\t\treturn &LeaveGroupRequest{Version: version}\n\tcase apiKeySyncGroup:\n\t\treturn &SyncGroupRequest{Version: version}\n\tcase apiKeyDescribeGroups:\n\t\treturn &DescribeGroupsRequest{Version: version}\n\tcase apiKeyListGroups:\n\t\treturn &ListGroupsRequest{Version: version}\n\tcase apiKeySaslHandshake:\n\t\treturn &SaslHandshakeRequest{Version: version}\n\tcase apiKeyApiVersions:\n\t\treturn &ApiVersionsRequest{Version: version}\n\tcase apiKeyCreateTopics:\n\t\treturn &CreateTopicsRequest{Version: version}\n\tcase apiKeyDeleteTopics:\n\t\treturn &DeleteTopicsRequest{Version: version}\n\tcase apiKeyDeleteRecords:\n\t\treturn &DeleteRecordsRequest{Version: version}\n\tcase apiKeyInitProducerId:\n\t\treturn &InitProducerIDRequest{Version: version}\n\t// 23: OffsetForLeaderEpochRequest\n\tcase apiKeyAddPartitionsToTxn:\n\t\treturn &AddPartitionsToTxnRequest{Version: version}\n\tcase apiKeyAddOffsetsToTxn:\n\t\treturn &AddOffsetsToTxnRequest{Version: version}\n\tcase apiKeyEndTxn:\n\t\treturn &EndTxnRequest{Version: version}\n\t// 27: WriteTxnMarkersRequest\n\tcase apiKeyTxnOffsetCommit:\n\t\treturn &TxnOffsetCommitRequest{Version: version}\n\tcase apiKeyDescribeAcls:\n\t\treturn &DescribeAclsRequest{Version: int(version)}\n\tcase apiKeyCreateAcls:\n\t\treturn &CreateAclsRequest{Version: version}\n\tcase apiKeyDeleteAcls:\n\t\treturn &DeleteAclsRequest{Version: int(version)}\n\tcase apiKeyDescribeConfigs:\n\t\treturn &DescribeConfigsRequest{Version: version}\n\tcase apiKeyAlterConfigs:\n\t\treturn &AlterConfigsRequest{Version: version}\n\t// 34: AlterReplicaLogDirsRequest\n\tcase apiKeyDescribeLogDirs:\n\t\treturn &DescribeLogDirsRequest{Version: version}\n\tcase apiKeySASLAuth:\n\t\treturn &SaslAuthenticateRequest{Version: version}\n\tcase apiKeyCreatePartitions:\n\t\treturn &CreatePartitionsRequest{Version: version}\n\t// 38: CreateDelegationTokenRequest\n\t// 39: RenewDelegationTokenRequest\n\t// 40: ExpireDelegationTokenRequest\n\t// 41: DescribeDelegationTokenRequest\n\tcase apiKeyDeleteGroups:\n\t\treturn &DeleteGroupsRequest{Version: version}\n\tcase apiKeyElectLeaders:\n\t\treturn &ElectLeadersRequest{Version: version}\n\tcase apiKeyIncrementalAlterConfigs:\n\t\treturn &IncrementalAlterConfigsRequest{Version: version}\n\tcase apiKeyAlterPartitionReassignments:\n\t\treturn &AlterPartitionReassignmentsRequest{Version: version}\n\tcase apiKeyListPartitionReassignments:\n\t\treturn &ListPartitionReassignmentsRequest{Version: version}\n\tcase apiKeyOffsetDelete:\n\t\treturn &DeleteOffsetsRequest{Version: version}\n\tcase apiKeyDescribeClientQuotas:\n\t\treturn &DescribeClientQuotasRequest{Version: version}\n\tcase apiKeyAlterClientQuotas:\n\t\treturn &AlterClientQuotasRequest{Version: version}\n\tcase apiKeyDescribeUserScramCredentials:\n\t\treturn &DescribeUserScramCredentialsRequest{Version: version}\n\tcase apiKeyAlterUserScramCredentials:\n\t\treturn &AlterUserScramCredentialsRequest{Version: version}\n\tcase apiKeyDescribeCluster:\n\t\treturn &DescribeClusterRequest{Version: version}\n\t\t// 52: VoteRequest\n\t\t// 53: BeginQuorumEpochRequest\n\t\t// 54: EndQuorumEpochRequest\n\t\t// 55: DescribeQuorumRequest\n\t\t// 56: AlterPartitionRequest\n\t\t// 57: UpdateFeaturesRequest\n\t\t// 58: EnvelopeRequest\n\t\t// 59: FetchSnapshotRequest\n\t\t// 60: DescribeClusterRequest\n\t\t// 61: DescribeProducersRequest\n\t\t// 62: BrokerRegistrationRequest\n\t\t// 63: BrokerHeartbeatRequest\n\t\t// 64: UnregisterBrokerRequest\n\t\t// 65: DescribeTransactionsRequest\n\t\t// 66: ListTransactionsRequest\n\t\t// 67: AllocateProducerIdsRequest\n\t\t// 68: ConsumerGroupHeartbeatRequest\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/davecgh/go-spew/spew\"\n\tassert \"github.com/stretchr/testify/require\"\n)\n\nvar names = map[int16]string{\n\tapiKeyProduce:                      \"ProduceRequest\",\n\tapiKeyFetch:                        \"FetchRequest\",\n\tapiKeyListOffsets:                  \"ListOffsetsRequest\",\n\tapiKeyMetadata:                     \"MetadataRequest\",\n\tapiKeyLeaderAndIsr:                 \"LeaderAndIsrRequest\",\n\tapiKeyStopReplica:                  \"StopReplicaRequest\",\n\tapiKeyUpdateMetadata:               \"UpdateMetadataRequest\",\n\tapiKeyControlledShutdown:           \"ControlledShutdownRequest\",\n\tapiKeyOffsetCommit:                 \"OffsetCommitRequest\",\n\tapiKeyOffsetFetch:                  \"OffsetFetchRequest\",\n\tapiKeyFindCoordinator:              \"FindCoordinatorRequest\",\n\tapiKeyJoinGroup:                    \"JoinGroupRequest\",\n\tapiKeyHeartbeat:                    \"HeartbeatRequest\",\n\tapiKeyLeaveGroup:                   \"LeaveGroupRequest\",\n\tapiKeySyncGroup:                    \"SyncGroupRequest\",\n\tapiKeyDescribeGroups:               \"DescribeGroupsRequest\",\n\tapiKeyListGroups:                   \"ListGroupsRequest\",\n\tapiKeySaslHandshake:                \"SaslHandshakeRequest\",\n\tapiKeyApiVersions:                  \"ApiVersionsRequest\",\n\tapiKeyCreateTopics:                 \"CreateTopicsRequest\",\n\tapiKeyDeleteTopics:                 \"DeleteTopicsRequest\",\n\tapiKeyDeleteRecords:                \"DeleteRecordsRequest\",\n\tapiKeyInitProducerId:               \"InitProducerIdRequest\",\n\tapiKeyOffsetForLeaderEpoch:         \"OffsetForLeaderEpochRequest\",\n\tapiKeyAddPartitionsToTxn:           \"AddPartitionsToTxnRequest\",\n\tapiKeyAddOffsetsToTxn:              \"AddOffsetsToTxnRequest\",\n\tapiKeyEndTxn:                       \"EndTxnRequest\",\n\tapiKeyWriteTxnMarkers:              \"WriteTxnMarkersRequest\",\n\tapiKeyTxnOffsetCommit:              \"TxnOffsetCommitRequest\",\n\tapiKeyDescribeAcls:                 \"DescribeAclsRequest\",\n\tapiKeyCreateAcls:                   \"CreateAclsRequest\",\n\tapiKeyDeleteAcls:                   \"DeleteAclsRequest\",\n\tapiKeyDescribeConfigs:              \"DescribeConfigsRequest\",\n\tapiKeyAlterConfigs:                 \"AlterConfigsRequest\",\n\tapiKeyAlterReplicaLogDirs:          \"AlterReplicaLogDirsRequest\",\n\tapiKeyDescribeLogDirs:              \"DescribeLogDirsRequest\",\n\tapiKeySASLAuth:                     \"SaslAuthenticateRequest\",\n\tapiKeyCreatePartitions:             \"CreatePartitionsRequest\",\n\tapiKeyCreateDelegationToken:        \"CreateDelegationTokenRequest\",\n\tapiKeyRenewDelegationToken:         \"RenewDelegationTokenRequest\",\n\tapiKeyExpireDelegationToken:        \"ExpireDelegationTokenRequest\",\n\tapiKeyDescribeDelegationToken:      \"DescribeDelegationTokenRequest\",\n\tapiKeyDeleteGroups:                 \"DeleteGroupsRequest\",\n\tapiKeyElectLeaders:                 \"ElectLeadersRequest\",\n\tapiKeyIncrementalAlterConfigs:      \"IncrementalAlterConfigsRequest\",\n\tapiKeyAlterPartitionReassignments:  \"AlterPartitionReassignmentsRequest\",\n\tapiKeyListPartitionReassignments:   \"ListPartitionReassignmentsRequest\",\n\tapiKeyOffsetDelete:                 \"OffsetDeleteRequest\",\n\tapiKeyDescribeClientQuotas:         \"DescribeClientQuotasRequest\",\n\tapiKeyAlterClientQuotas:            \"AlterClientQuotasRequest\",\n\tapiKeyDescribeUserScramCredentials: \"DescribeUserScramCredentialsRequest\",\n\tapiKeyAlterUserScramCredentials:    \"AlterUserScramCredentialsRequest\",\n\t52:                                 \"VoteRequest\",\n\t53:                                 \"BeginQuorumEpochRequest\",\n\t54:                                 \"EndQuorumEpochRequest\",\n\t55:                                 \"DescribeQuorumRequest\",\n\t56:                                 \"AlterPartitionRequest\",\n\t57:                                 \"UpdateFeaturesRequest\",\n\t58:                                 \"EnvelopeRequest\",\n\t59:                                 \"FetchSnapshotRequest\",\n\tapiKeyDescribeCluster:              \"DescribeClusterRequest\",\n\t61:                                 \"DescribeProducersRequest\",\n\t62:                                 \"BrokerRegistrationRequest\",\n\t63:                                 \"BrokerHeartbeatRequest\",\n\t64:                                 \"UnregisterBrokerRequest\",\n\t65:                                 \"DescribeTransactionsRequest\",\n\t66:                                 \"ListTransactionsRequest\",\n\t67:                                 \"AllocateProducerIdsRequest\",\n\t68:                                 \"ConsumerGroupHeartbeatRequest\",\n}\n\n// allocateResponseBody is a test-only clone of allocateBody. There's no\n// central registry of types, so we can't do this using reflection for Response\n// types and assuming that the struct is identically named, just with Response\n// instead of Request.\nfunc allocateResponseBody(req protocolBody) protocolBody {\n\tkey := req.key()\n\tversion := req.version()\n\tswitch key {\n\tcase apiKeyProduce:\n\t\treturn &ProduceResponse{Version: version}\n\tcase apiKeyFetch:\n\t\treturn &FetchResponse{Version: version}\n\tcase apiKeyListOffsets:\n\t\treturn &OffsetResponse{Version: version}\n\tcase apiKeyMetadata:\n\t\treturn &MetadataResponse{Version: version}\n\tcase apiKeyOffsetCommit:\n\t\treturn &OffsetCommitResponse{Version: version}\n\tcase apiKeyOffsetFetch:\n\t\treturn &OffsetFetchResponse{Version: version}\n\tcase apiKeyFindCoordinator:\n\t\treturn &FindCoordinatorResponse{Version: version}\n\tcase apiKeyJoinGroup:\n\t\treturn &JoinGroupResponse{Version: version}\n\tcase apiKeyHeartbeat:\n\t\treturn &HeartbeatResponse{Version: version}\n\tcase apiKeyLeaveGroup:\n\t\treturn &LeaveGroupResponse{Version: version}\n\tcase apiKeySyncGroup:\n\t\treturn &SyncGroupResponse{Version: version}\n\tcase apiKeyDescribeGroups:\n\t\treturn &DescribeGroupsResponse{Version: version}\n\tcase apiKeyListGroups:\n\t\treturn &ListGroupsResponse{Version: version}\n\tcase apiKeySaslHandshake:\n\t\treturn &SaslHandshakeResponse{Version: version}\n\tcase apiKeyApiVersions:\n\t\treturn &ApiVersionsResponse{Version: version}\n\tcase apiKeyCreateTopics:\n\t\treturn &CreateTopicsResponse{Version: version}\n\tcase apiKeyDeleteTopics:\n\t\treturn &DeleteTopicsResponse{Version: version}\n\tcase apiKeyDeleteRecords:\n\t\treturn &DeleteRecordsResponse{Version: version}\n\tcase apiKeyInitProducerId:\n\t\treturn &InitProducerIDResponse{Version: version}\n\tcase apiKeyAddPartitionsToTxn:\n\t\treturn &AddPartitionsToTxnResponse{Version: version}\n\tcase apiKeyAddOffsetsToTxn:\n\t\treturn &AddOffsetsToTxnResponse{Version: version}\n\tcase apiKeyEndTxn:\n\t\treturn &EndTxnResponse{Version: version}\n\tcase apiKeyTxnOffsetCommit:\n\t\treturn &TxnOffsetCommitResponse{Version: version}\n\tcase apiKeyDescribeAcls:\n\t\treturn &DescribeAclsResponse{Version: version}\n\tcase apiKeyCreateAcls:\n\t\treturn &CreateAclsResponse{Version: version}\n\tcase apiKeyDeleteAcls:\n\t\treturn &DeleteAclsResponse{Version: version}\n\tcase apiKeyDescribeConfigs:\n\t\treturn &DescribeConfigsResponse{Version: version}\n\tcase apiKeyAlterConfigs:\n\t\treturn &AlterConfigsResponse{Version: version}\n\tcase apiKeyDescribeLogDirs:\n\t\treturn &DescribeLogDirsResponse{Version: version}\n\tcase apiKeySASLAuth:\n\t\treturn &SaslAuthenticateResponse{Version: version}\n\tcase apiKeyCreatePartitions:\n\t\treturn &CreatePartitionsResponse{Version: version}\n\tcase apiKeyDeleteGroups:\n\t\treturn &DeleteGroupsResponse{Version: version}\n\tcase apiKeyElectLeaders:\n\t\treturn &ElectLeadersResponse{Version: version}\n\tcase apiKeyIncrementalAlterConfigs:\n\t\treturn &IncrementalAlterConfigsResponse{Version: version}\n\tcase apiKeyAlterPartitionReassignments:\n\t\treturn &AlterPartitionReassignmentsResponse{Version: version}\n\tcase apiKeyListPartitionReassignments:\n\t\treturn &ListPartitionReassignmentsResponse{Version: version}\n\tcase apiKeyOffsetDelete:\n\t\treturn &DeleteOffsetsResponse{Version: version}\n\tcase apiKeyDescribeClientQuotas:\n\t\treturn &DescribeClientQuotasResponse{Version: version}\n\tcase apiKeyAlterClientQuotas:\n\t\treturn &AlterClientQuotasResponse{Version: version}\n\tcase apiKeyDescribeUserScramCredentials:\n\t\treturn &DescribeUserScramCredentialsResponse{Version: version}\n\tcase apiKeyAlterUserScramCredentials:\n\t\treturn &AlterUserScramCredentialsResponse{Version: version}\n\tcase apiKeyDescribeCluster:\n\t\treturn &DescribeClusterResponse{Version: version}\n\t}\n\treturn nil\n}\n\nfunc TestAllocateBodyProtocolVersions(t *testing.T) {\n\ttype test struct {\n\t\tversion     KafkaVersion\n\t\tapiVersions map[int16]int16\n\t}\n\n\tsaramaMaxVersions := newKafkaVersion(999, 999, 999, 999)\n\tmaxVersion := func(pb protocolBody) int16 {\n\t\tvar (\n\t\t\ti int16\n\t\t\tv int16\n\t\t)\n\t\tfor i = 0; ; i++ {\n\t\t\tpb.setVersion(i)\n\t\t\tif !pb.isValidVersion() {\n\t\t\t\treturn v\n\t\t\t}\n\t\t\tv = i\n\t\t}\n\t}\n\n\ttests := []test{\n\t\t{\n\t\t\tV1_1_0_0,\n\t\t\tmap[int16]int16{\n\t\t\t\tapiKeyProduce:                 5,\n\t\t\t\tapiKeyFetch:                   7,\n\t\t\t\tapiKeyListOffsets:             2,\n\t\t\t\tapiKeyMetadata:                5,\n\t\t\t\tapiKeyLeaderAndIsr:            1,\n\t\t\t\tapiKeyStopReplica:             0,\n\t\t\t\tapiKeyUpdateMetadata:          4,\n\t\t\t\tapiKeyControlledShutdown:      1,\n\t\t\t\tapiKeyOffsetCommit:            3,\n\t\t\t\tapiKeyOffsetFetch:             3,\n\t\t\t\tapiKeyFindCoordinator:         1,\n\t\t\t\tapiKeyJoinGroup:               2,\n\t\t\t\tapiKeyHeartbeat:               1,\n\t\t\t\tapiKeyLeaveGroup:              1,\n\t\t\t\tapiKeySyncGroup:               1,\n\t\t\t\tapiKeyDescribeGroups:          1,\n\t\t\t\tapiKeyListGroups:              1,\n\t\t\t\tapiKeySaslHandshake:           1,\n\t\t\t\tapiKeyApiVersions:             1,\n\t\t\t\tapiKeyCreateTopics:            2,\n\t\t\t\tapiKeyDeleteTopics:            1,\n\t\t\t\tapiKeyDeleteRecords:           0,\n\t\t\t\tapiKeyInitProducerId:          0,\n\t\t\t\tapiKeyOffsetForLeaderEpoch:    0,\n\t\t\t\tapiKeyAddPartitionsToTxn:      0,\n\t\t\t\tapiKeyAddOffsetsToTxn:         0,\n\t\t\t\tapiKeyEndTxn:                  0,\n\t\t\t\tapiKeyWriteTxnMarkers:         0,\n\t\t\t\tapiKeyTxnOffsetCommit:         0,\n\t\t\t\tapiKeyDescribeAcls:            0,\n\t\t\t\tapiKeyCreateAcls:              0,\n\t\t\t\tapiKeyDeleteAcls:              0,\n\t\t\t\tapiKeyDescribeConfigs:         1,\n\t\t\t\tapiKeyAlterConfigs:            0,\n\t\t\t\tapiKeyAlterReplicaLogDirs:     0,\n\t\t\t\tapiKeyDescribeLogDirs:         0,\n\t\t\t\tapiKeySASLAuth:                0,\n\t\t\t\tapiKeyCreatePartitions:        0,\n\t\t\t\tapiKeyCreateDelegationToken:   0,\n\t\t\t\tapiKeyRenewDelegationToken:    0,\n\t\t\t\tapiKeyExpireDelegationToken:   0,\n\t\t\t\tapiKeyDescribeDelegationToken: 0,\n\t\t\t\tapiKeyDeleteGroups:            0,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tV2_0_0_0,\n\t\t\tmap[int16]int16{\n\t\t\t\tapiKeyProduce:                 6, // up from 5\n\t\t\t\tapiKeyFetch:                   8, // up from 7\n\t\t\t\tapiKeyListOffsets:             3, // up from 2\n\t\t\t\tapiKeyMetadata:                6, // up from 5\n\t\t\t\tapiKeyOffsetCommit:            4, // up from 3\n\t\t\t\tapiKeyOffsetFetch:             4, // up from 3\n\t\t\t\tapiKeyFindCoordinator:         2, // up from 1\n\t\t\t\tapiKeyJoinGroup:               3, // up from 2\n\t\t\t\tapiKeyHeartbeat:               2, // up from 1\n\t\t\t\tapiKeyLeaveGroup:              2, // up from 1\n\t\t\t\tapiKeySyncGroup:               2, // up from 1\n\t\t\t\tapiKeyDescribeGroups:          2, // up from 1\n\t\t\t\tapiKeyListGroups:              2, // up from 1\n\t\t\t\tapiKeyApiVersions:             2, // up from 1\n\t\t\t\tapiKeyCreateTopics:            3, // up from 2\n\t\t\t\tapiKeyDeleteTopics:            2, // up from 1\n\t\t\t\tapiKeyDeleteRecords:           1, // up from 0\n\t\t\t\tapiKeyInitProducerId:          1, // up from 0\n\t\t\t\tapiKeyOffsetForLeaderEpoch:    1, // up from 0\n\t\t\t\tapiKeyAddPartitionsToTxn:      1, // up from 0\n\t\t\t\tapiKeyAddOffsetsToTxn:         1, // up from 0\n\t\t\t\tapiKeyEndTxn:                  1, // up from 0\n\t\t\t\tapiKeyTxnOffsetCommit:         1, // up from 0\n\t\t\t\tapiKeyDescribeAcls:            1, // up from 0\n\t\t\t\tapiKeyCreateAcls:              1, // up from 0\n\t\t\t\tapiKeyDeleteAcls:              1, // up from 0\n\t\t\t\tapiKeyDescribeConfigs:         2, // up from 1\n\t\t\t\tapiKeyAlterConfigs:            1, // up from 0\n\t\t\t\tapiKeyAlterReplicaLogDirs:     1, // up from 0\n\t\t\t\tapiKeyDescribeLogDirs:         1, // up from 0\n\t\t\t\tapiKeyCreatePartitions:        1, // up from 0\n\t\t\t\tapiKeyCreateDelegationToken:   1, // up from 0\n\t\t\t\tapiKeyRenewDelegationToken:    1, // up from 0\n\t\t\t\tapiKeyExpireDelegationToken:   1, // up from 0\n\t\t\t\tapiKeyDescribeDelegationToken: 1, // up from 0\n\t\t\t\tapiKeyDeleteGroups:            1, // up from 0\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tV2_1_0_0,\n\t\t\tmap[int16]int16{\n\t\t\t\tapiKeyProduce:              7,  // up from 6\n\t\t\t\tapiKeyFetch:                10, // up from 8\n\t\t\t\tapiKeyListOffsets:          4,  // up from 3\n\t\t\t\tapiKeyMetadata:             7,  // up from 6\n\t\t\t\tapiKeyOffsetCommit:         6,  // up from 4\n\t\t\t\tapiKeyOffsetFetch:          5,  // up from 4\n\t\t\t\tapiKeyOffsetForLeaderEpoch: 2,  // up from 1\n\t\t\t\tapiKeyTxnOffsetCommit:      2,  // up from 1\n\t\t\t\tapiKeyDeleteTopics:         3,  // up from 2\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tV2_2_0_0,\n\t\t\tmap[int16]int16{\n\t\t\t\tapiKeyListOffsets:        5, // up from 4\n\t\t\t\tapiKeyLeaderAndIsr:       2, // up from 1\n\t\t\t\tapiKeyStopReplica:        1, // up from 0\n\t\t\t\tapiKeyUpdateMetadata:     5, // up from 4\n\t\t\t\tapiKeyControlledShutdown: 2, // up from 1\n\t\t\t\tapiKeyJoinGroup:          4, // up from 3\n\t\t\t\tapiKeySASLAuth:           1, // up from 0\n\t\t\t\tapiKeyElectLeaders:       0, // new in 2.2\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tV2_3_0_0,\n\t\t\tmap[int16]int16{\n\t\t\t\tapiKeyFetch:                   11, // up from 10\n\t\t\t\tapiKeyMetadata:                8,  // up from 7\n\t\t\t\tapiKeyOffsetCommit:            7,  // up from 6\n\t\t\t\tapiKeyJoinGroup:               5,  // up from 4\n\t\t\t\tapiKeyHeartbeat:               3,  // up from 2\n\t\t\t\tapiKeySyncGroup:               3,  // up from 2\n\t\t\t\tapiKeyDescribeGroups:          3,  // up from 2\n\t\t\t\tapiKeyIncrementalAlterConfigs: 0,  // new in 2.3\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tV2_4_0_0,\n\t\t\tmap[int16]int16{\n\t\t\t\t// TODO: ProduceRequest v8 is not supported, but expected for KafkaVersion 2.4.0\n\t\t\t\t// apiKeyProduce:                     8, // up from 7\n\t\t\t\tapiKeyMetadata:           9, // up from 8\n\t\t\t\tapiKeyLeaderAndIsr:       4, // up from 2\n\t\t\t\tapiKeyStopReplica:        2, // up from 1\n\t\t\t\tapiKeyUpdateMetadata:     6, // up from 5\n\t\t\t\tapiKeyControlledShutdown: 3, // up from 2\n\t\t\t\t// TODO: OffsetCommitRequest v8 is not supported, but expected for KafkaVersion 2.4.0\n\t\t\t\t// apiKeyOffsetCommit:                8, // up from 7\n\t\t\t\tapiKeyOffsetFetch: 6, // up from 5\n\t\t\t\t// TODO: FindCoordinatorRequest v3 is not supported, but expected for KafkaVersion 2.4.0\n\t\t\t\t// apiKeyFindCoordinator:             3, // up from 2\n\t\t\t\tapiKeyJoinGroup:                   6, // up from 5\n\t\t\t\tapiKeyHeartbeat:                   4, // up from 3\n\t\t\t\tapiKeyLeaveGroup:                  4, // up from 2\n\t\t\t\tapiKeySyncGroup:                   4, // up from 3\n\t\t\t\tapiKeyDescribeGroups:              5, // up from 3\n\t\t\t\tapiKeyListGroups:                  3, // up from 2\n\t\t\t\tapiKeyApiVersions:                 3, // up from 2\n\t\t\t\tapiKeyCreateTopics:                5, // up from 3\n\t\t\t\tapiKeyDeleteTopics:                4, // up from 3\n\t\t\t\tapiKeyInitProducerId:              2, // up from 1\n\t\t\t\tapiKeyDeleteGroups:                2, // up from 1\n\t\t\t\tapiKeyElectLeaders:                2, // up from 0\n\t\t\t\tapiKeyIncrementalAlterConfigs:     1, // up from 0\n\t\t\t\tapiKeyAlterPartitionReassignments: 0, // new in 2.4\n\t\t\t\tapiKeyListPartitionReassignments:  0, // new in 2.4\n\t\t\t\tapiKeyOffsetDelete:                0, // new in 2.4\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tsaramaMaxVersions, // placeholder version for current maximums implemented by Sarama\n\t\t\tmap[int16]int16{\n\t\t\t\tapiKeyProduce:            maxVersion(&ProduceRequest{}),\n\t\t\t\tapiKeyFetch:              maxVersion(&FetchRequest{}),\n\t\t\t\tapiKeyListOffsets:        maxVersion(&OffsetRequest{}),\n\t\t\t\tapiKeyMetadata:           maxVersion(&MetadataRequest{}),\n\t\t\t\tapiKeyOffsetCommit:       maxVersion(&OffsetCommitRequest{}),\n\t\t\t\tapiKeyOffsetFetch:        maxVersion(&OffsetFetchRequest{}),\n\t\t\t\tapiKeyFindCoordinator:    maxVersion(&FindCoordinatorRequest{}),\n\t\t\t\tapiKeyJoinGroup:          maxVersion(&JoinGroupRequest{}),\n\t\t\t\tapiKeyHeartbeat:          maxVersion(&HeartbeatRequest{}),\n\t\t\t\tapiKeyLeaveGroup:         maxVersion(&LeaveGroupRequest{}),\n\t\t\t\tapiKeySyncGroup:          maxVersion(&SyncGroupRequest{}),\n\t\t\t\tapiKeyDescribeGroups:     maxVersion(&DescribeGroupsRequest{}),\n\t\t\t\tapiKeyListGroups:         maxVersion(&ListGroupsRequest{}),\n\t\t\t\tapiKeySaslHandshake:      maxVersion(&SaslHandshakeRequest{}),\n\t\t\t\tapiKeyApiVersions:        maxVersion(&ApiVersionsRequest{}),\n\t\t\t\tapiKeyCreateTopics:       maxVersion(&CreateTopicsRequest{}),\n\t\t\t\tapiKeyDeleteTopics:       maxVersion(&DeleteTopicsRequest{}),\n\t\t\t\tapiKeyDeleteRecords:      maxVersion(&DeleteRecordsRequest{}),\n\t\t\t\tapiKeyInitProducerId:     maxVersion(&InitProducerIDRequest{}),\n\t\t\t\tapiKeyAddPartitionsToTxn: maxVersion(&AddPartitionsToTxnRequest{}),\n\t\t\t\tapiKeyAddOffsetsToTxn:    maxVersion(&AddOffsetsToTxnRequest{}),\n\t\t\t\tapiKeyEndTxn:             maxVersion(&EndTxnRequest{}),\n\t\t\t\tapiKeyTxnOffsetCommit:    maxVersion(&TxnOffsetCommitRequest{}),\n\t\t\t\tapiKeyDescribeAcls:       maxVersion(&DescribeAclsRequest{}),\n\t\t\t\tapiKeyCreateAcls:         maxVersion(&CreateAclsRequest{}),\n\t\t\t\tapiKeyDeleteAcls:         maxVersion(&DeleteAclsRequest{}),\n\t\t\t\tapiKeyDescribeConfigs:    maxVersion(&DescribeConfigsRequest{}),\n\t\t\t\tapiKeyAlterConfigs:       maxVersion(&AlterConfigsRequest{}),\n\t\t\t\tapiKeyDescribeLogDirs:    maxVersion(&DescribeLogDirsRequest{}),\n\t\t\t\tapiKeySASLAuth:           maxVersion(&SaslAuthenticateRequest{}),\n\t\t\t\tapiKeyCreatePartitions:   maxVersion(&CreatePartitionsRequest{}),\n\t\t\t\tapiKeyDeleteGroups:       maxVersion(&DeleteGroupsRequest{}),\n\t\t\t\tapiKeyElectLeaders:       maxVersion(&ElectLeadersRequest{}),\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tfor key, version := range tt.apiVersions {\n\t\t\tt.Run(fmt.Sprintf(\"%s-%s\", tt.version.String(), names[key]), func(t *testing.T) {\n\t\t\t\treq := allocateBody(key, version)\n\t\t\t\tif req == nil {\n\t\t\t\t\tt.Skipf(\"apikey %d is not implemented\", key)\n\t\t\t\t}\n\t\t\t\tt.Logf(\"Testing %s V%d\", reflect.TypeOf(req), version)\n\t\t\t\tresp := allocateResponseBody(req)\n\t\t\t\tassert.NotNil(t, resp, fmt.Sprintf(\"%s has no matching response type in allocateResponseBody\", reflect.TypeOf(req)))\n\t\t\t\tassert.Equal(t, req.isValidVersion(), resp.isValidVersion(), fmt.Sprintf(\"%s isValidVersion should match %s\", reflect.TypeOf(req), reflect.TypeOf(resp)))\n\t\t\t\tassert.Equal(t, req.requiredVersion(), resp.requiredVersion(), fmt.Sprintf(\"%s requiredVersion should match %s\", reflect.TypeOf(req), reflect.TypeOf(resp)))\n\t\t\t\tfor _, body := range []protocolBody{req, resp} {\n\t\t\t\t\tassert.Equal(t, key, body.key())\n\t\t\t\t\tassert.Equal(t, version, body.version())\n\t\t\t\t\tassert.True(t, body.isValidVersion(), fmt.Sprintf(\"%s v%d is not supported, but expected for KafkaVersion %s\", reflect.TypeOf(body), version, tt.version))\n\t\t\t\t\tassert.True(t, tt.version.IsAtLeast(body.requiredVersion()), fmt.Sprintf(\"KafkaVersion %s should be enough for %s v%d\", tt.version, reflect.TypeOf(body), version))\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t}\n}\n\n// not specific to request tests, just helper functions for testing structures that\n// implement the encoder or decoder interfaces that needed somewhere to live\n\nfunc testEncodable(t *testing.T, name string, in encoder, expect []byte) {\n\tt.Helper()\n\tpacket, err := encode(in, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t} else if !bytes.Equal(packet, expect) {\n\t\tt.Error(\"Encoding\", name, \"failed\\ngot \", packet, \"\\nwant\", expect)\n\t}\n}\n\nfunc testDecodable(t *testing.T, name string, out decoder, in []byte) {\n\tt.Helper()\n\terr := decode(in, out, nil)\n\tif err != nil {\n\t\tt.Error(\"Decoding\", name, \"failed:\", err)\n\t}\n}\n\nfunc testVersionDecodable(t *testing.T, name string, out versionedDecoder, in []byte, version int16) {\n\tt.Helper()\n\terr := versionedDecode(in, out, version, nil)\n\tif err != nil {\n\t\tt.Error(\"Decoding\", name, \"version\", version, \"failed:\", err)\n\t}\n}\n\nfunc testRequest(t *testing.T, name string, rb protocolBody, expected []byte) {\n\tt.Helper()\n\tif !rb.requiredVersion().IsAtLeast(MinVersion) {\n\t\tt.Errorf(\"Request %s has invalid required version\", name)\n\t}\n\tpacket := testRequestEncode(t, name, rb, expected)\n\ttestRequestDecode(t, name, rb, packet)\n}\n\nfunc testRequestWithoutByteComparison(t *testing.T, name string, rb protocolBody) {\n\tif !rb.requiredVersion().IsAtLeast(MinVersion) {\n\t\tt.Errorf(\"Request %s has invalid required version\", name)\n\t}\n\tpacket := testRequestEncode(t, name, rb, nil)\n\ttestRequestDecode(t, name, rb, packet)\n}\n\nfunc testRequestEncode(t *testing.T, name string, rb protocolBody, expected []byte) []byte {\n\treq := &request{correlationID: 123, clientID: \"foo\", body: rb}\n\tpacket, err := encode(req, nil)\n\n\theaderSize := 0\n\n\tswitch rb.headerVersion() {\n\tcase 1:\n\t\theaderSize = 14 + len(\"foo\")\n\tcase 2:\n\t\theaderSize = 14 + len(\"foo\") + 1\n\tdefault:\n\t\tt.Error(\"Encoding\", name, \"failed\\nheaderVersion\", rb.headerVersion(), \"not implemented\")\n\t}\n\n\tif err != nil {\n\t\tt.Error(err)\n\t} else if expected != nil && !bytes.Equal(packet[headerSize:], expected) {\n\t\tt.Error(\"Encoding\", name, \"failed\\ngot \", packet[headerSize:], \"\\nwant\", expected)\n\t}\n\treturn packet\n}\n\nfunc testRequestDecode(t *testing.T, name string, rb protocolBody, packet []byte) {\n\tt.Helper()\n\tdecoded, n, err := decodeRequest(bytes.NewReader(packet))\n\tif err != nil {\n\t\tt.Error(\"Failed to decode request\", err)\n\t} else if decoded.correlationID != 123 || decoded.clientID != \"foo\" {\n\t\tt.Errorf(\"Decoded header %q is not valid: %+v\", name, decoded)\n\t} else if !reflect.DeepEqual(rb, decoded.body) {\n\t\tt.Error(spew.Sprintf(\"Decoded request %q does not match the encoded one\\nencoded: %+v\\ndecoded: %+v\", name, rb, decoded.body))\n\t} else if n != len(packet) {\n\t\tt.Errorf(\"Decoded request %q bytes: %d does not match the encoded one: %d\\n\", name, n, len(packet))\n\t} else if rb.version() != decoded.body.version() {\n\t\tt.Errorf(\"Decoded request %q version: %d does not match the encoded one: %d\\n\", name, decoded.body.version(), rb.version())\n\t}\n}\n\nfunc testResponse(t *testing.T, name string, res protocolBody, expected []byte) {\n\tencoded, err := encode(res, nil)\n\tif err != nil {\n\t\tt.Error(err)\n\t} else if expected != nil && !bytes.Equal(encoded, expected) {\n\t\tt.Error(\"Encoding\", name, \"failed\\ngot \", encoded, \"\\nwant\", expected)\n\t}\n\n\tdecoded := reflect.New(reflect.TypeOf(res).Elem()).Interface().(versionedDecoder)\n\tif err := versionedDecode(encoded, decoded, res.version(), nil); err != nil {\n\t\tt.Error(\"Decoding\", name, \"failed:\", err)\n\t}\n\n\tif !reflect.DeepEqual(decoded, res) {\n\t\tt.Errorf(\"Decoded response does not match the encoded one\\nencoded: %#v\\ndecoded: %#v\", res, decoded)\n\t}\n}\n\nfunc TestDecodeRequestErrorReturns(t *testing.T) {\n\t_, bytesRead, err := decodeRequest(bytes.NewReader([]byte{0, 0, 0}))\n\tif err == nil {\n\t\tt.Error(\"Decode of short request should give error but was nil\")\n\t}\n\tif bytesRead != 3 {\n\t\tt.Errorf(\"Decode of short request should read 3 bytes but was %d\", bytesRead)\n\t}\n\t_, bytesRead, err = decodeRequest(bytes.NewReader([]byte{0, 0, 0, 8, 0, 0, 0}))\n\tif err == nil {\n\t\tt.Error(\"Decode of short request should give error but was nil\")\n\t}\n\tif bytesRead != 7 {\n\t\tt.Errorf(\"Decode of short request should read 7 bytes but was %d\", bytesRead)\n\t}\n}\n\nfunc nullString(s string) *string { return &s }\n"
  },
  {
    "path": "response_header.go",
    "content": "package sarama\n\nimport \"fmt\"\n\ntype responseHeader struct {\n\tlength        int32\n\tcorrelationID int32\n}\n\nfunc (r *responseHeader) decode(pd packetDecoder, version int16) (err error) {\n\tif version >= 1 {\n\t\tif decoder, ok := pd.(*realDecoder); ok {\n\t\t\tpd = &realFlexibleDecoder{decoder}\n\t\t} else {\n\t\t\treturn PacketDecodingError{\"failed to instantiate flexible decoder\"}\n\t\t}\n\t}\n\tr.length, err = pd.getInt32()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif r.length <= 4 || r.length > MaxResponseSize {\n\t\treturn PacketDecodingError{fmt.Sprintf(\"message of length %d too large or too small\", r.length)}\n\t}\n\n\tr.correlationID, err = pd.getInt32()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n"
  },
  {
    "path": "response_header_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nvar (\n\tresponseHeaderBytesV0 = []byte{\n\t\t0x00, 0x00, 0x0f, 0x00,\n\t\t0x0a, 0xbb, 0xcc, 0xff,\n\t}\n\n\tresponseHeaderBytesV1 = []byte{\n\t\t0x00, 0x00, 0x0f, 0x00,\n\t\t0x0a, 0xbb, 0xcc, 0xff, 0x00,\n\t}\n)\n\nfunc TestResponseHeaderV0(t *testing.T) {\n\theader := responseHeader{}\n\n\ttestVersionDecodable(t, \"response header\", &header, responseHeaderBytesV0, 0)\n\tif header.length != 0xf00 {\n\t\tt.Error(\"Decoding header length failed, got\", header.length)\n\t}\n\tif header.correlationID != 0x0abbccff {\n\t\tt.Error(\"Decoding header correlation id failed, got\", header.correlationID)\n\t}\n}\n\nfunc TestResponseHeaderV1(t *testing.T) {\n\theader := responseHeader{}\n\n\ttestVersionDecodable(t, \"response header\", &header, responseHeaderBytesV1, 1)\n\tif header.length != 0xf00 {\n\t\tt.Error(\"Decoding header length failed, got\", header.length)\n\t}\n\tif header.correlationID != 0x0abbccff {\n\t\tt.Error(\"Decoding header correlation id failed, got\", header.correlationID)\n\t}\n}\n"
  },
  {
    "path": "sarama.go",
    "content": "/*\nPackage sarama is a pure Go client library for dealing with Apache Kafka (versions 0.8 and later). It includes a high-level\nAPI for easily producing and consuming messages, and a low-level API for controlling bytes on the wire when the high-level\nAPI is insufficient. Usage examples for the high-level APIs are provided inline with their full documentation.\n\nTo produce messages, use either the AsyncProducer or the SyncProducer. The AsyncProducer accepts messages on a channel\nand produces them asynchronously in the background as efficiently as possible; it is preferred in most cases.\nThe SyncProducer provides a method which will block until Kafka acknowledges the message as produced. This can be\nuseful but comes with two caveats: it will generally be less efficient, and the actual durability guarantees\ndepend on the configured value of `Producer.RequiredAcks`. There are configurations where a message acknowledged by the\nSyncProducer can still sometimes be lost.\n\nTo consume messages, use Consumer or Consumer-Group API.\n\nFor lower-level needs, the Broker and Request/Response objects permit precise control over each connection\nand message sent on the wire; the Client provides higher-level metadata management that is shared between\nthe producers and the consumer. The Request/Response objects and properties are mostly undocumented, as they line up\nexactly with the protocol fields documented by Kafka at\nhttps://cwiki.apache.org/confluence/display/KAFKA/A+Guide+To+The+Kafka+Protocol\n\nMetrics are exposed through https://github.com/rcrowley/go-metrics library in a local registry.\n\nBroker related metrics:\n\n\t+---------------------------------------------------------+------------+---------------------------------------------------------------+\n\t| Name                                                    | Type       | Description                                                   |\n\t+---------------------------------------------------------+------------+---------------------------------------------------------------+\n\t| incoming-byte-rate                                      | meter      | Bytes/second read off all brokers                             |\n\t| incoming-byte-rate-for-broker-<broker-id>               | meter      | Bytes/second read off a given broker                          |\n\t| outgoing-byte-rate                                      | meter      | Bytes/second written off all brokers                          |\n\t| outgoing-byte-rate-for-broker-<broker-id>               | meter      | Bytes/second written off a given broker                       |\n\t| request-rate                                            | meter      | Requests/second sent to all brokers                           |\n\t| request-rate-for-broker-<broker-id>                     | meter      | Requests/second sent to a given broker                        |\n\t| request-size                                            | histogram  | Distribution of the request size in bytes for all brokers     |\n\t| request-size-for-broker-<broker-id>                     | histogram  | Distribution of the request size in bytes for a given broker  |\n\t| request-latency-in-ms                                   | histogram  | Distribution of the request latency in ms for all brokers     |\n\t| request-latency-in-ms-for-broker-<broker-id>            | histogram  | Distribution of the request latency in ms for a given broker  |\n\t| response-rate                                           | meter      | Responses/second received from all brokers                    |\n\t| response-rate-for-broker-<broker-id>                    | meter      | Responses/second received from a given broker                 |\n\t| response-size                                           | histogram  | Distribution of the response size in bytes for all brokers    |\n\t| response-size-for-broker-<broker-id>                    | histogram  | Distribution of the response size in bytes for a given broker |\n\t| requests-in-flight                                      | counter    | The current number of in-flight requests awaiting a response  |\n\t|                                                         |            | for all brokers                                               |\n\t| requests-in-flight-for-broker-<broker-id>               | counter    | The current number of in-flight requests awaiting a response  |\n\t|                                                         |            | for a given broker                                            |\n\t| protocol-requests-rate-<api-key>          \t          | meter      | Number of api requests sent to the brokers for all brokers    |\n\t|                                                         |            | https://kafka.apache.org/protocol.html#protocol_api_keys      |                                        |\n\t| protocol-requests-rate-<api-key>-for-broker-<broker-id> | meter      | Number of packets sent to the brokers by api-key for a given  |\n\t|                                                         |            | broker                                                        |\n\t+---------------------------------------------------------+------------+---------------------------------------------------------------+\n\nNote that we do not gather specific metrics for seed brokers but they are part of the \"all brokers\" metrics.\n\nProducer related metrics:\n\n\t+-------------------------------------------+------------+--------------------------------------------------------------------------------------+\n\t| Name                                      | Type       | Description                                                                          |\n\t+-------------------------------------------+------------+--------------------------------------------------------------------------------------+\n\t| batch-size                                | histogram  | Distribution of the number of bytes sent per partition per request for all topics    |\n\t| batch-size-for-topic-<topic>              | histogram  | Distribution of the number of bytes sent per partition per request for a given topic |\n\t| record-send-rate                          | meter      | Records/second sent to all topics                                                    |\n\t| record-send-rate-for-topic-<topic>        | meter      | Records/second sent to a given topic                                                 |\n\t| records-per-request                       | histogram  | Distribution of the number of records sent per request for all topics                |\n\t| records-per-request-for-topic-<topic>     | histogram  | Distribution of the number of records sent per request for a given topic             |\n\t| compression-ratio                         | histogram  | Distribution of the compression ratio times 100 of record batches for all topics     |\n\t| compression-ratio-for-topic-<topic>       | histogram  | Distribution of the compression ratio times 100 of record batches for a given topic  |\n\t+-------------------------------------------+------------+--------------------------------------------------------------------------------------+\n\nConsumer related metrics:\n\n\t+-------------------------------------------+------------+--------------------------------------------------------------------------------------+\n\t| Name                                      | Type       | Description                                                                          |\n\t+-------------------------------------------+------------+--------------------------------------------------------------------------------------+\n\t| consumer-batch-size                       | histogram  | Distribution of the number of messages in a batch                                    |\n\t| consumer-fetch-rate                       | meter      | Fetch requests/second sent to all brokers                                            |\n\t| consumer-fetch-rate-for-broker-<broker>   | meter      | Fetch requests/second sent to a given broker                                         |\n\t| consumer-fetch-rate-for-topic-<topic>     | meter      | Fetch requests/second sent for a given topic                                         |\n\t| consumer-fetch-response-size              | histogram  | Distribution of the fetch response size in bytes                                     |\n\t| consumer-group-join-total-<GroupID>       | counter    | Total count of consumer group join attempts                                          |\n\t| consumer-group-join-failed-<GroupID>      | counter    | Total count of consumer group join failures                                          |\n\t| consumer-group-sync-total-<GroupID>       | counter    | Total count of consumer group sync attempts                                          |\n\t| consumer-group-sync-failed-<GroupID>      | counter    | Total count of consumer group sync failures                                          |\n\t+-------------------------------------------+------------+--------------------------------------------------------------------------------------+\n*/\npackage sarama\n\nimport (\n\t\"io\"\n\t\"log\"\n)\n\nvar (\n\t// Logger is the instance of a StdLogger interface that Sarama writes connection\n\t// management events to. By default it is set to discard all log messages via io.Discard,\n\t// but you can set it to redirect wherever you want.\n\tLogger StdLogger = log.New(io.Discard, \"[Sarama] \", log.LstdFlags)\n\n\t// PanicHandler is called for recovering from panics spawned internally to the library (and thus\n\t// not recoverable by the caller's goroutine). Defaults to nil, which means panics are not recovered.\n\tPanicHandler func(interface{})\n\n\t// MaxRequestSize is the maximum size (in bytes) of any request that Sarama will attempt to send. Trying\n\t// to send a request larger than this will result in an PacketEncodingError. The default of 100 MiB is aligned\n\t// with Kafka's default `socket.request.max.bytes`, which is the largest request the broker will attempt\n\t// to process.\n\tMaxRequestSize int32 = 100 * 1024 * 1024\n\n\t// MaxResponseSize is the maximum size (in bytes) of any response that Sarama will attempt to parse. If\n\t// a broker returns a response message larger than this value, Sarama will return a PacketDecodingError to\n\t// protect the client from running out of memory. Please note that brokers do not have any natural limit on\n\t// the size of responses they send. In particular, they can send arbitrarily large fetch responses to consumers\n\t// (see https://issues.apache.org/jira/browse/KAFKA-2063).\n\tMaxResponseSize int32 = 100 * 1024 * 1024\n)\n\n// StdLogger is used to log error messages.\ntype StdLogger interface {\n\tPrint(v ...interface{})\n\tPrintf(format string, v ...interface{})\n\tPrintln(v ...interface{})\n}\n\ntype debugLogger struct{}\n\nfunc (d *debugLogger) Print(v ...interface{}) {\n\tLogger.Print(v...)\n}\nfunc (d *debugLogger) Printf(format string, v ...interface{}) {\n\tLogger.Printf(format, v...)\n}\nfunc (d *debugLogger) Println(v ...interface{}) {\n\tLogger.Println(v...)\n}\n\n// DebugLogger is the instance of a StdLogger that Sarama writes more verbose\n// debug information to. By default it is set to redirect all debug to the\n// default Logger above, but you can optionally set it to another StdLogger\n// instance to (e.g.,) discard debug information\nvar DebugLogger StdLogger = &debugLogger{}\n"
  },
  {
    "path": "sarama_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"flag\"\n\t\"log\"\n\t\"os\"\n\t\"testing\"\n)\n\nfunc TestMain(m *testing.M) {\n\tflag.Parse()\n\tif f := flag.Lookup(\"test.v\"); f != nil && f.Value.String() == \"true\" {\n\t\tLogger = log.New(os.Stderr, \"[DEBUG] \", log.Lmicroseconds|log.Ltime)\n\t}\n\tos.Exit(m.Run())\n}\n"
  },
  {
    "path": "sasl_authenticate_request.go",
    "content": "package sarama\n\ntype SaslAuthenticateRequest struct {\n\t// Version defines the protocol version to use for encode and decode\n\tVersion       int16\n\tSaslAuthBytes []byte\n}\n\nfunc (r *SaslAuthenticateRequest) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *SaslAuthenticateRequest) encode(pe packetEncoder) error {\n\treturn pe.putBytes(r.SaslAuthBytes)\n}\n\nfunc (r *SaslAuthenticateRequest) decode(pd packetDecoder, version int16) (err error) {\n\tr.Version = version\n\tr.SaslAuthBytes, err = pd.getBytes()\n\treturn err\n}\n\nfunc (r *SaslAuthenticateRequest) key() int16 {\n\treturn apiKeySASLAuth\n}\n\nfunc (r *SaslAuthenticateRequest) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *SaslAuthenticateRequest) headerVersion() int16 {\n\treturn 1\n}\n\nfunc (r *SaslAuthenticateRequest) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 1\n}\n\nfunc (r *SaslAuthenticateRequest) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 1:\n\t\treturn V2_2_0_0\n\tdefault:\n\t\treturn V1_0_0_0\n\t}\n}\n"
  },
  {
    "path": "sasl_authenticate_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nvar saslAuthenticateRequest = []byte{\n\t0, 0, 0, 3, 'f', 'o', 'o',\n}\n\nfunc TestSaslAuthenticateRequest(t *testing.T) {\n\trequest := new(SaslAuthenticateRequest)\n\trequest.SaslAuthBytes = []byte(`foo`)\n\ttestRequest(t, \"basic\", request, saslAuthenticateRequest)\n}\n\nfunc TestSaslAuthenticateRequestV1(t *testing.T) {\n\trequest := new(SaslAuthenticateRequest)\n\trequest.Version = 1\n\trequest.SaslAuthBytes = []byte(`foo`)\n\ttestRequest(t, \"basic\", request, saslAuthenticateRequest)\n}\n"
  },
  {
    "path": "sasl_authenticate_response.go",
    "content": "package sarama\n\ntype SaslAuthenticateResponse struct {\n\t// Version defines the protocol version to use for encode and decode\n\tVersion           int16\n\tErr               KError\n\tErrorMessage      *string\n\tSaslAuthBytes     []byte\n\tSessionLifetimeMs int64\n}\n\nfunc (r *SaslAuthenticateResponse) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *SaslAuthenticateResponse) encode(pe packetEncoder) error {\n\tpe.putKError(r.Err)\n\tif err := pe.putNullableString(r.ErrorMessage); err != nil {\n\t\treturn err\n\t}\n\tif err := pe.putBytes(r.SaslAuthBytes); err != nil {\n\t\treturn err\n\t}\n\tif r.Version > 0 {\n\t\tpe.putInt64(r.SessionLifetimeMs)\n\t}\n\treturn nil\n}\n\nfunc (r *SaslAuthenticateResponse) decode(pd packetDecoder, version int16) (err error) {\n\tr.Version = version\n\tr.Err, err = pd.getKError()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif r.ErrorMessage, err = pd.getNullableString(); err != nil {\n\t\treturn err\n\t}\n\n\tif r.SaslAuthBytes, err = pd.getBytes(); err != nil {\n\t\treturn err\n\t}\n\n\tif version > 0 {\n\t\tr.SessionLifetimeMs, err = pd.getInt64()\n\t}\n\n\treturn err\n}\n\nfunc (r *SaslAuthenticateResponse) key() int16 {\n\treturn apiKeySASLAuth\n}\n\nfunc (r *SaslAuthenticateResponse) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *SaslAuthenticateResponse) headerVersion() int16 {\n\treturn 0\n}\n\nfunc (r *SaslAuthenticateResponse) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 1\n}\n\nfunc (r *SaslAuthenticateResponse) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 1:\n\t\treturn V2_2_0_0\n\tdefault:\n\t\treturn V1_0_0_0\n\t}\n}\n"
  },
  {
    "path": "sasl_authenticate_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nvar (\n\tsaslAuthenticateResponseErr = []byte{\n\t\t0, 58,\n\t\t0, 3, 'e', 'r', 'r',\n\t\t0, 0, 0, 3, 'm', 's', 'g',\n\t}\n\tsaslAuthenticateResponseErrV1 = []byte{\n\t\t0, 58,\n\t\t0, 3, 'e', 'r', 'r',\n\t\t0, 0, 0, 3, 'm', 's', 'g',\n\t\t0, 0, 0, 0, 0, 0, 0, 1,\n\t}\n)\n\nfunc TestSaslAuthenticateResponse(t *testing.T) {\n\tresponse := new(SaslAuthenticateResponse)\n\tresponse.Err = ErrSASLAuthenticationFailed\n\tmsg := \"err\"\n\tresponse.ErrorMessage = &msg\n\tresponse.SaslAuthBytes = []byte(`msg`)\n\n\ttestResponse(t, \"authenticate response\", response, saslAuthenticateResponseErr)\n}\n\nfunc TestSaslAuthenticateResponseV1(t *testing.T) {\n\tresponse := new(SaslAuthenticateResponse)\n\tresponse.Err = ErrSASLAuthenticationFailed\n\tmsg := \"err\"\n\tresponse.Version = 1\n\tresponse.ErrorMessage = &msg\n\tresponse.SaslAuthBytes = []byte(`msg`)\n\tresponse.SessionLifetimeMs = 1\n\n\ttestResponse(t, \"authenticate response\", response, saslAuthenticateResponseErrV1)\n}\n"
  },
  {
    "path": "sasl_handshake_request.go",
    "content": "package sarama\n\ntype SaslHandshakeRequest struct {\n\tMechanism string\n\tVersion   int16\n}\n\nfunc (r *SaslHandshakeRequest) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *SaslHandshakeRequest) encode(pe packetEncoder) error {\n\tif err := pe.putString(r.Mechanism); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (r *SaslHandshakeRequest) decode(pd packetDecoder, version int16) (err error) {\n\tif r.Mechanism, err = pd.getString(); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (r *SaslHandshakeRequest) key() int16 {\n\treturn apiKeySaslHandshake\n}\n\nfunc (r *SaslHandshakeRequest) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *SaslHandshakeRequest) headerVersion() int16 {\n\treturn 1\n}\n\nfunc (r *SaslHandshakeRequest) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 1\n}\n\nfunc (r *SaslHandshakeRequest) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 1:\n\t\treturn V1_0_0_0\n\tdefault:\n\t\treturn V0_10_0_0\n\t}\n}\n"
  },
  {
    "path": "sasl_handshake_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nvar baseSaslRequest = []byte{\n\t0, 3, 'f', 'o', 'o', // Mechanism\n}\n\nfunc TestSaslHandshakeRequest(t *testing.T) {\n\trequest := new(SaslHandshakeRequest)\n\trequest.Mechanism = \"foo\"\n\ttestRequest(t, \"basic\", request, baseSaslRequest)\n}\n"
  },
  {
    "path": "sasl_handshake_response.go",
    "content": "package sarama\n\ntype SaslHandshakeResponse struct {\n\tVersion           int16\n\tErr               KError\n\tEnabledMechanisms []string\n}\n\nfunc (r *SaslHandshakeResponse) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *SaslHandshakeResponse) encode(pe packetEncoder) error {\n\tpe.putKError(r.Err)\n\treturn pe.putStringArray(r.EnabledMechanisms)\n}\n\nfunc (r *SaslHandshakeResponse) decode(pd packetDecoder, version int16) (err error) {\n\tr.Err, err = pd.getKError()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif r.EnabledMechanisms, err = pd.getStringArray(); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (r *SaslHandshakeResponse) key() int16 {\n\treturn apiKeySaslHandshake\n}\n\nfunc (r *SaslHandshakeResponse) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *SaslHandshakeResponse) headerVersion() int16 {\n\treturn 0\n}\n\nfunc (r *SaslHandshakeResponse) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 1\n}\n\nfunc (r *SaslHandshakeResponse) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 1:\n\t\treturn V1_0_0_0\n\tdefault:\n\t\treturn V0_10_0_0\n\t}\n}\n"
  },
  {
    "path": "sasl_handshake_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"errors\"\n\t\"testing\"\n)\n\nvar saslHandshakeResponse = []byte{\n\t0x00, 0x00,\n\t0x00, 0x00, 0x00, 0x01,\n\t0x00, 0x03, 'f', 'o', 'o',\n}\n\nfunc TestSaslHandshakeResponse(t *testing.T) {\n\tresponse := new(SaslHandshakeResponse)\n\ttestVersionDecodable(t, \"no error\", response, saslHandshakeResponse, 0)\n\tif !errors.Is(response.Err, ErrNoError) {\n\t\tt.Error(\"Decoding error failed: no error expected but found\", response.Err)\n\t}\n\tif response.EnabledMechanisms[0] != \"foo\" {\n\t\tt.Error(\"Decoding error failed: expected 'foo' but found\", response.EnabledMechanisms)\n\t}\n}\n"
  },
  {
    "path": "scram_formatter.go",
    "content": "package sarama\n\nimport (\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"crypto/sha512\"\n\t\"hash\"\n)\n\n// ScramFormatter implementation\n// @see: https://github.com/apache/kafka/blob/99b9b3e84f4e98c3f07714e1de6a139a004cbc5b/clients/src/main/java/org/apache/kafka/common/security/scram/internals/ScramFormatter.java#L93\ntype scramFormatter struct {\n\tmechanism ScramMechanismType\n}\n\nfunc (s scramFormatter) mac(key []byte) (hash.Hash, error) {\n\tvar m hash.Hash\n\n\tswitch s.mechanism {\n\tcase SCRAM_MECHANISM_SHA_256:\n\t\tm = hmac.New(sha256.New, key)\n\n\tcase SCRAM_MECHANISM_SHA_512:\n\t\tm = hmac.New(sha512.New, key)\n\tdefault:\n\t\treturn nil, ErrUnknownScramMechanism\n\t}\n\n\treturn m, nil\n}\n\nfunc (s scramFormatter) hmac(key []byte, extra []byte) ([]byte, error) {\n\tmac, err := s.mac(key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif _, err := mac.Write(extra); err != nil {\n\t\treturn nil, err\n\t}\n\treturn mac.Sum(nil), nil\n}\n\nfunc (s scramFormatter) xor(result []byte, second []byte) {\n\tfor i := 0; i < len(result); i++ {\n\t\tresult[i] = result[i] ^ second[i]\n\t}\n}\n\nfunc (s scramFormatter) saltedPassword(password []byte, salt []byte, iterations int) ([]byte, error) {\n\tmac, err := s.mac(password)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif _, err := mac.Write(salt); err != nil {\n\t\treturn nil, err\n\t}\n\tif _, err := mac.Write([]byte{0, 0, 0, 1}); err != nil {\n\t\treturn nil, err\n\t}\n\n\tu1 := mac.Sum(nil)\n\tprev := u1\n\tresult := u1\n\n\tfor i := 2; i <= iterations; i++ {\n\t\tui, err := s.hmac(password, prev)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\ts.xor(result, ui)\n\t\tprev = ui\n\t}\n\n\treturn result, nil\n}\n"
  },
  {
    "path": "scram_formatter_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n)\n\n/*\nFollowing code can be used to validate saltedPassword implementation:\n\n<pre>\nimport org.apache.kafka.common.security.scram.internals.ScramFormatter;\nimport org.apache.kafka.common.security.scram.internals.ScramMechanism;\nimport java.nio.charset.StandardCharsets;\n\npublic class App {\n\n    public static String bytesToHex(byte[] in) {\n        final StringBuilder builder = new StringBuilder();\n        for(byte b : in) {\n            builder.append(String.format(\"0x%02x, \", b));\n        }\n        return builder.toString();\n    }\n\n\tpublic static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException {\n\t   int digestIterations = 4096;\n\t   String password = \"hello\";\n\t   byte[] salt = \"world\".getBytes(StandardCharsets.UTF_8);\n\t   byte[] saltedPassword = new ScramFormatter(ScramMechanism.SCRAM_SHA_256)\n\t\t\t   .saltedPassword(password, salt, digestIterations);\n\t   System.out.println(bytesToHex(saltedPassword));\n\t}\n}\n</pre>\n*/\n\nfunc TestScramSaltedPasswordSha512(t *testing.T) {\n\tpassword := []byte(\"hello\")\n\tsalt := []byte(\"world\")\n\n\tformatter := scramFormatter{mechanism: SCRAM_MECHANISM_SHA_512}\n\tresult, _ := formatter.saltedPassword(password, salt, 4096)\n\n\t// calculated using ScramFormatter (see comment above)\n\texpected := []byte{\n\t\t0x35, 0x0c, 0x77, 0x84, 0x8a, 0x63, 0x06, 0x92, 0x00,\n\t\t0x6e, 0xc6, 0x6a, 0x0c, 0x39, 0xeb, 0xb0, 0x00, 0xd3,\n\t\t0xf8, 0x8a, 0x94, 0xae, 0x7f, 0x8c, 0xcd, 0x1d, 0x92,\n\t\t0x52, 0x6c, 0x5b, 0x16, 0x15, 0x86, 0x3b, 0xde, 0xa1,\n\t\t0x6c, 0x12, 0x9a, 0x7b, 0x09, 0xed, 0x0e, 0x38, 0xf2,\n\t\t0x07, 0x4d, 0x2f, 0xe2, 0x9f, 0x0f, 0x41, 0xe1, 0xfb,\n\t\t0x00, 0xc1, 0xd3, 0xbd, 0xd3, 0xfd, 0x51, 0x0b, 0xa9,\n\t\t0x8f,\n\t}\n\n\tif !bytes.Equal(result, expected) {\n\t\tt.Errorf(\"saltedPassword SHA-512 failed, expected: %v, result: %v\", expected, result)\n\t}\n}\n\nfunc TestScramSaltedPasswordSha256(t *testing.T) {\n\tpassword := []byte(\"hello\")\n\tsalt := []byte(\"world\")\n\n\tformatter := scramFormatter{mechanism: SCRAM_MECHANISM_SHA_256}\n\tresult, _ := formatter.saltedPassword(password, salt, 4096)\n\n\t// calculated using ScramFormatter (see comment above)\n\texpected := []byte{\n\t\t0xc1, 0x55, 0x53, 0x03, 0xda, 0x30, 0x9f, 0x6b, 0x7d,\n\t\t0x1e, 0x8f, 0xe4, 0x56, 0x36, 0xbf, 0xdd, 0xdc, 0x4b,\n\t\t0xf5, 0x64, 0x05, 0xe7, 0xe9, 0x4e, 0x9d, 0x15, 0xf0,\n\t\t0xe7, 0xb9, 0xcb, 0xd3, 0x80,\n\t}\n\n\tif !bytes.Equal(result, expected) {\n\t\tt.Errorf(\"saltedPassword SHA-256 failed, expected: %v, result: %v\", expected, result)\n\t}\n}\n"
  },
  {
    "path": "server.properties",
    "content": "# Licensed to the Apache Software Foundation (ASF) under one or more\n# contributor license agreements.  See the NOTICE file distributed with\n# this work for additional information regarding copyright ownership.\n# The ASF licenses this file to You under the Apache License, Version 2.0\n# (the \"License\"); you may not use this file except in compliance with\n# the License.  You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n#\n# This configuration file is intended for use in ZK-based mode, where Apache ZooKeeper is required.\n# See kafka.server.KafkaConfig for additional details and defaults\n#\n\n############################# Server Basics #############################\n\n# The id of the broker. This must be set to a unique integer for each broker.\nbroker.id=0\n\n############################# Socket Server Settings #############################\n\n# The address the socket server listens on. If not configured, the host name will be equal to the value of\n# java.net.InetAddress.getCanonicalHostName(), with PLAINTEXT listener name, and port 9092.\n#   FORMAT:\n#     listeners = listener_name://host_name:port\n#   EXAMPLE:\n#     listeners = PLAINTEXT://your.host.name:9092\n#listeners=PLAINTEXT://:9092\n\n# Listener name, hostname and port the broker will advertise to clients.\n# If not set, it uses the value for \"listeners\".\n#advertised.listeners=PLAINTEXT://your.host.name:9092\n\n# Maps listener names to security protocols, the default is for them to be the same. See the config documentation for more details\n#listener.security.protocol.map=PLAINTEXT:PLAINTEXT,SSL:SSL,SASL_PLAINTEXT:SASL_PLAINTEXT,SASL_SSL:SASL_SSL\n\n# The number of threads that the server uses for receiving requests from the network and sending responses to the network\nnum.network.threads=3\n\n# The number of threads that the server uses for processing requests, which may include disk I/O\nnum.io.threads=8\n\n# The send buffer (SO_SNDBUF) used by the socket server\nsocket.send.buffer.bytes=102400\n\n# The receive buffer (SO_RCVBUF) used by the socket server\nsocket.receive.buffer.bytes=102400\n\n# The maximum size of a request that the socket server will accept (protection against OOM)\nsocket.request.max.bytes=104857600\n\n\n############################# Log Basics #############################\n\n# A comma separated list of directories under which to store log files\nlog.dirs=/tmp/kafka-logs\n\n# The default number of log partitions per topic. More partitions allow greater\n# parallelism for consumption, but this will also result in more files across\n# the brokers.\nnum.partitions=1\n\n# The number of threads per data directory to be used for log recovery at startup and flushing at shutdown.\n# This value is recommended to be increased for installations with data dirs located in RAID array.\nnum.recovery.threads.per.data.dir=1\n\n############################# Internal Topic Settings  #############################\n# The replication factor for the group metadata internal topics \"__consumer_offsets\" and \"__transaction_state\"\n# For anything other than development testing, a value greater than 1 is recommended to ensure availability such as 3.\noffsets.topic.replication.factor=1\ntransaction.state.log.replication.factor=1\ntransaction.state.log.min.isr=1\n\n############################# Log Flush Policy #############################\n\n# Messages are immediately written to the filesystem but by default we only fsync() to sync\n# the OS cache lazily. The following configurations control the flush of data to disk.\n# There are a few important trade-offs here:\n#    1. Durability: Unflushed data may be lost if you are not using replication.\n#    2. Latency: Very large flush intervals may lead to latency spikes when the flush does occur as there will be a lot of data to flush.\n#    3. Throughput: The flush is generally the most expensive operation, and a small flush interval may lead to excessive seeks.\n# The settings below allow one to configure the flush policy to flush data after a period of time or\n# every N messages (or both). This can be done globally and overridden on a per-topic basis.\n\n# The number of messages to accept before forcing a flush of data to disk\n#log.flush.interval.messages=10000\n\n# The maximum amount of time a message can sit in a log before we force a flush\n#log.flush.interval.ms=1000\n\n############################# Log Retention Policy #############################\n\n# The following configurations control the disposal of log segments. The policy can\n# be set to delete segments after a period of time, or after a given size has accumulated.\n# A segment will be deleted whenever *either* of these criteria are met. Deletion always happens\n# from the end of the log.\n\n# The minimum age of a log file to be eligible for deletion due to age\nlog.retention.hours=168\n\n# A size-based retention policy for logs. Segments are pruned from the log unless the remaining\n# segments drop below log.retention.bytes. Functions independently of log.retention.hours.\n#log.retention.bytes=1073741824\n\n# The maximum size of a log segment file. When this size is reached a new log segment will be created.\n#log.segment.bytes=1073741824\n\n# The interval at which log segments are checked to see if they can be deleted according\n# to the retention policies\nlog.retention.check.interval.ms=300000\n\n############################# Zookeeper #############################\n\n# Zookeeper connection string (see zookeeper docs for details).\n# This is a comma separated host:port pairs, each corresponding to a zk\n# server. e.g. \"127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002\".\n# You can also append an optional chroot string to the urls to specify the\n# root directory for all kafka znodes.\nzookeeper.connect=localhost:2181\n\n# Timeout in ms for connecting to zookeeper\nzookeeper.connection.timeout.ms=18000\n\n\n############################# Group Coordinator Settings #############################\n\n# The following configuration specifies the time, in milliseconds, that the GroupCoordinator will delay the initial consumer rebalance.\n# The rebalance will be further delayed by the value of group.initial.rebalance.delay.ms as new members join the group, up to a maximum of max.poll.interval.ms.\n# The default value for this is 3 seconds.\n# We override this to 0 here as it makes for a better out-of-the-box experience for development and testing.\n# However, in production environments the default value of 3 seconds is more suitable as this will help to avoid unnecessary, and potentially expensive, rebalances during application startup.\ngroup.initial.rebalance.delay.ms=0\n"
  },
  {
    "path": "sticky_assignor_user_data.go",
    "content": "package sarama\n\ntype topicPartitionAssignment struct {\n\tTopic     string\n\tPartition int32\n}\n\ntype StickyAssignorUserData interface {\n\tpartitions() []topicPartitionAssignment\n\thasGeneration() bool\n\tgeneration() int\n}\n\n// StickyAssignorUserDataV0 holds topic partition information for an assignment\ntype StickyAssignorUserDataV0 struct {\n\tTopics map[string][]int32\n\n\ttopicPartitions []topicPartitionAssignment\n}\n\nfunc (m *StickyAssignorUserDataV0) encode(pe packetEncoder) error {\n\tif err := pe.putArrayLength(len(m.Topics)); err != nil {\n\t\treturn err\n\t}\n\n\tfor topic, partitions := range m.Topics {\n\t\tif err := pe.putString(topic); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := pe.putInt32Array(partitions); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (m *StickyAssignorUserDataV0) decode(pd packetDecoder) (err error) {\n\tvar topicLen int\n\tif topicLen, err = pd.getArrayLength(); err != nil {\n\t\treturn\n\t}\n\n\tm.Topics = make(map[string][]int32, topicLen)\n\tfor i := 0; i < topicLen; i++ {\n\t\tvar topic string\n\t\tif topic, err = pd.getString(); err != nil {\n\t\t\treturn\n\t\t}\n\t\tif m.Topics[topic], err = pd.getInt32Array(); err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\tm.topicPartitions = populateTopicPartitions(m.Topics)\n\treturn nil\n}\n\nfunc (m *StickyAssignorUserDataV0) partitions() []topicPartitionAssignment { return m.topicPartitions }\nfunc (m *StickyAssignorUserDataV0) hasGeneration() bool                    { return false }\nfunc (m *StickyAssignorUserDataV0) generation() int                        { return defaultGeneration }\n\n// StickyAssignorUserDataV1 holds topic partition information for an assignment\ntype StickyAssignorUserDataV1 struct {\n\tTopics     map[string][]int32\n\tGeneration int32\n\n\ttopicPartitions []topicPartitionAssignment\n}\n\nfunc (m *StickyAssignorUserDataV1) encode(pe packetEncoder) error {\n\tif err := pe.putArrayLength(len(m.Topics)); err != nil {\n\t\treturn err\n\t}\n\n\tfor topic, partitions := range m.Topics {\n\t\tif err := pe.putString(topic); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := pe.putInt32Array(partitions); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tpe.putInt32(m.Generation)\n\treturn nil\n}\n\nfunc (m *StickyAssignorUserDataV1) decode(pd packetDecoder) (err error) {\n\tvar topicLen int\n\tif topicLen, err = pd.getArrayLength(); err != nil {\n\t\treturn\n\t}\n\n\tm.Topics = make(map[string][]int32, topicLen)\n\tfor i := 0; i < topicLen; i++ {\n\t\tvar topic string\n\t\tif topic, err = pd.getString(); err != nil {\n\t\t\treturn\n\t\t}\n\t\tif m.Topics[topic], err = pd.getInt32Array(); err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tm.Generation, err = pd.getInt32()\n\tif err != nil {\n\t\treturn err\n\t}\n\tm.topicPartitions = populateTopicPartitions(m.Topics)\n\treturn nil\n}\n\nfunc (m *StickyAssignorUserDataV1) partitions() []topicPartitionAssignment { return m.topicPartitions }\nfunc (m *StickyAssignorUserDataV1) hasGeneration() bool                    { return true }\nfunc (m *StickyAssignorUserDataV1) generation() int                        { return int(m.Generation) }\n\nfunc populateTopicPartitions(topics map[string][]int32) []topicPartitionAssignment {\n\ttopicPartitions := make([]topicPartitionAssignment, 0)\n\tfor topic, partitions := range topics {\n\t\tfor _, partition := range partitions {\n\t\t\ttopicPartitions = append(topicPartitions, topicPartitionAssignment{Topic: topic, Partition: partition})\n\t\t}\n\t}\n\treturn topicPartitions\n}\n"
  },
  {
    "path": "sticky_assignor_user_data_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"encoding/base64\"\n\t\"testing\"\n)\n\nfunc TestStickyAssignorUserDataV0(t *testing.T) {\n\t// Single topic with deterministic ordering across encode-decode\n\treq := &StickyAssignorUserDataV0{}\n\tdata := decodeUserDataBytes(t, \"AAAAAQADdDAzAAAAAQAAAAU=\")\n\ttestDecodable(t, \"\", req, data)\n\ttestEncodable(t, \"\", req, data)\n\n\t// Multiple partitions\n\treq = &StickyAssignorUserDataV0{}\n\tdata = decodeUserDataBytes(t, \"AAAAAQADdDE4AAAAEgAAAAAAAAABAAAAAgAAAAMAAAAEAAAABQAAAAYAAAAHAAAACAAAAAkAAAAKAAAACwAAAAwAAAANAAAADgAAAA8AAAAQAAAAEQ==\")\n\ttestDecodable(t, \"\", req, data)\n\n\t// Multiple topics and partitions\n\treq = &StickyAssignorUserDataV0{}\n\tdata = decodeUserDataBytes(t, \"AAAABQADdDEyAAAAAgAAAAIAAAAKAAN0MTEAAAABAAAABAADdDE0AAAAAQAAAAgAA3QxMwAAAAEAAAANAAN0MDkAAAABAAAABQ==\")\n\ttestDecodable(t, \"\", req, data)\n}\n\nfunc TestStickyAssignorUserDataV1(t *testing.T) {\n\t// Single topic with deterministic ordering across encode-decode\n\treq := &StickyAssignorUserDataV1{}\n\tdata := decodeUserDataBytes(t, \"AAAAAQADdDA2AAAAAgAAAAAAAAAE/////w==\")\n\ttestDecodable(t, \"\", req, data)\n\ttestEncodable(t, \"\", req, data)\n\n\t// Multiple topics and partitions\n\treq = &StickyAssignorUserDataV1{}\n\tdata = decodeUserDataBytes(t, \"AAAABgADdDEwAAAAAgAAAAIAAAAJAAN0MTIAAAACAAAAAwAAAAsAA3QxNAAAAAEAAAAEAAN0MTMAAAABAAAACwADdDE1AAAAAQAAAAwAA3QwOQAAAAEAAAAG/////w==\")\n\ttestDecodable(t, \"\", req, data)\n\n\t// Generation is populated\n\treq = &StickyAssignorUserDataV1{}\n\tdata = decodeUserDataBytes(t, \"AAAAAQAHdG9waWMwMQAAAAMAAAAAAAAAAQAAAAIAAAAB\")\n\ttestDecodable(t, \"\", req, data)\n}\n\nfunc decodeUserDataBytes(t *testing.T, base64Data string) []byte {\n\tdata, err := base64.StdEncoding.DecodeString(base64Data)\n\tif err != nil {\n\t\tt.Errorf(\"Error decoding data: %v\", err)\n\t\tt.FailNow()\n\t}\n\treturn data\n}\n"
  },
  {
    "path": "sync_group_request.go",
    "content": "package sarama\n\ntype SyncGroupRequestAssignment struct {\n\t// MemberId contains the ID of the member to assign.\n\tMemberId string\n\t// Assignment contains the member assignment.\n\tAssignment []byte\n}\n\nfunc (a *SyncGroupRequestAssignment) encode(pe packetEncoder, version int16) (err error) {\n\tif err := pe.putString(a.MemberId); err != nil {\n\t\treturn err\n\t}\n\n\tif err := pe.putBytes(a.Assignment); err != nil {\n\t\treturn err\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (a *SyncGroupRequestAssignment) decode(pd packetDecoder, version int16) (err error) {\n\tif a.MemberId, err = pd.getString(); err != nil {\n\t\treturn err\n\t}\n\n\tif a.Assignment, err = pd.getBytes(); err != nil {\n\t\treturn err\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\ntype SyncGroupRequest struct {\n\t// Version defines the protocol version to use for encode and decode\n\tVersion int16\n\t// GroupId contains the unique group identifier.\n\tGroupId string\n\t// GenerationId contains the generation of the group.\n\tGenerationId int32\n\t// MemberId contains the member ID assigned by the group.\n\tMemberId string\n\t// GroupInstanceId contains the unique identifier of the consumer instance provided by end user.\n\tGroupInstanceId *string\n\t// GroupAssignments contains each assignment.\n\tGroupAssignments []SyncGroupRequestAssignment\n}\n\nfunc (s *SyncGroupRequest) setVersion(v int16) {\n\ts.Version = v\n}\n\nfunc (s *SyncGroupRequest) encode(pe packetEncoder) (err error) {\n\tif err := pe.putString(s.GroupId); err != nil {\n\t\treturn err\n\t}\n\n\tpe.putInt32(s.GenerationId)\n\n\tif err := pe.putString(s.MemberId); err != nil {\n\t\treturn err\n\t}\n\n\tif s.Version >= 3 {\n\t\tif err := pe.putNullableString(s.GroupInstanceId); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif err := pe.putArrayLength(len(s.GroupAssignments)); err != nil {\n\t\treturn err\n\t}\n\tfor _, block := range s.GroupAssignments {\n\t\tif err := block.encode(pe, s.Version); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (s *SyncGroupRequest) decode(pd packetDecoder, version int16) (err error) {\n\ts.Version = version\n\tif s.GroupId, err = pd.getString(); err != nil {\n\t\treturn err\n\t}\n\n\tif s.GenerationId, err = pd.getInt32(); err != nil {\n\t\treturn err\n\t}\n\n\tif s.MemberId, err = pd.getString(); err != nil {\n\t\treturn err\n\t}\n\n\tif s.Version >= 3 {\n\t\tif s.GroupInstanceId, err = pd.getNullableString(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif numAssignments, err := pd.getArrayLength(); err != nil {\n\t\treturn err\n\t} else if numAssignments > 0 {\n\t\ts.GroupAssignments = make([]SyncGroupRequestAssignment, numAssignments)\n\t\tfor i := 0; i < numAssignments; i++ {\n\t\t\tvar block SyncGroupRequestAssignment\n\t\t\tif err := block.decode(pd, s.Version); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\ts.GroupAssignments[i] = block\n\t\t}\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (r *SyncGroupRequest) key() int16 {\n\treturn apiKeySyncGroup\n}\n\nfunc (r *SyncGroupRequest) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *SyncGroupRequest) headerVersion() int16 {\n\tif r.Version >= 4 {\n\t\treturn 2\n\t}\n\treturn 1\n}\n\nfunc (r *SyncGroupRequest) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 4\n}\n\nfunc (r *SyncGroupRequest) isFlexible() bool {\n\treturn r.isFlexibleVersion(r.Version)\n}\n\nfunc (r *SyncGroupRequest) isFlexibleVersion(version int16) bool {\n\treturn version >= 4\n}\n\nfunc (r *SyncGroupRequest) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 4:\n\t\treturn V2_4_0_0\n\tcase 3:\n\t\treturn V2_3_0_0\n\tcase 2:\n\t\treturn V2_0_0_0\n\tcase 1:\n\t\treturn V0_11_0_0\n\tcase 0:\n\t\treturn V0_9_0_0\n\tdefault:\n\t\treturn V2_3_0_0\n\t}\n}\n\nfunc (r *SyncGroupRequest) AddGroupAssignment(memberId string, memberAssignment []byte) {\n\tr.GroupAssignments = append(r.GroupAssignments, SyncGroupRequestAssignment{\n\t\tMemberId:   memberId,\n\t\tAssignment: memberAssignment,\n\t})\n}\n\nfunc (r *SyncGroupRequest) AddGroupAssignmentMember(\n\tmemberId string,\n\tmemberAssignment *ConsumerGroupMemberAssignment,\n) error {\n\tbin, err := encode(memberAssignment, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr.AddGroupAssignment(memberId, bin)\n\treturn nil\n}\n"
  },
  {
    "path": "sync_group_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nvar (\n\temptySyncGroupRequest = []byte{\n\t\t0, 3, 'f', 'o', 'o', // Group ID\n\t\t0x00, 0x01, 0x02, 0x03, // Generation ID\n\t\t0, 3, 'b', 'a', 'z', // Member ID\n\t\t0, 0, 0, 0, // no assignments\n\t}\n\n\tpopulatedSyncGroupRequest = []byte{\n\t\t0, 3, 'f', 'o', 'o', // Group ID\n\t\t0x00, 0x01, 0x02, 0x03, // Generation ID\n\t\t0, 3, 'b', 'a', 'z', // Member ID\n\t\t0, 0, 0, 1, // one assignment\n\t\t0, 3, 'b', 'a', 'z', // Member ID\n\t\t0, 0, 0, 3, 'f', 'o', 'o', // Member assignment\n\t}\n)\n\nfunc TestSyncGroupRequest(t *testing.T) {\n\tvar request *SyncGroupRequest\n\n\trequest = new(SyncGroupRequest)\n\trequest.GroupId = \"foo\"\n\trequest.GenerationId = 66051\n\trequest.MemberId = \"baz\"\n\ttestRequest(t, \"empty\", request, emptySyncGroupRequest)\n\n\trequest = new(SyncGroupRequest)\n\trequest.GroupId = \"foo\"\n\trequest.GenerationId = 66051\n\trequest.MemberId = \"baz\"\n\trequest.AddGroupAssignment(\"baz\", []byte(\"foo\"))\n\ttestRequest(t, \"populated\", request, populatedSyncGroupRequest)\n}\n\nvar (\n\tpopulatedSyncGroupRequestV3 = []byte{\n\t\t0, 3, 'f', 'o', 'o', // Group ID\n\t\t0x00, 0x01, 0x02, 0x03, // Generation ID\n\t\t0, 3, 'b', 'a', 'z', // Member ID\n\t\t0, 3, 'g', 'i', 'd', // GroupInstance ID\n\t\t0, 0, 0, 1, // one assignment\n\t\t0, 3, 'b', 'a', 'z', // Member ID\n\t\t0, 0, 0, 3, 'f', 'o', 'o', // Member assignment\n\t}\n\tpopulatedSyncGroupRequestV4 = []byte{\n\t\t4, 'f', 'o', 'o', // Group ID\n\t\t0x00, 0x01, 0x02, 0x03, // Generation ID\n\t\t4, 'b', 'a', 'z', // Member ID\n\t\t4, 'g', 'i', 'd', // GroupInstance ID\n\t\t2,                // 1 + one assignment\n\t\t4, 'b', 'a', 'z', // Member ID\n\t\t4, 'f', 'o', 'o', // Member assignment\n\t\t0, // empty tagged fields\n\t\t0, // empty tagged fields\n\t}\n)\n\nfunc TestSyncGroupRequestV3AndPlus(t *testing.T) {\n\tgroupInstanceId := \"gid\"\n\ttests := []struct {\n\t\tCaseName     string\n\t\tVersion      int16\n\t\tMessageBytes []byte\n\t\tMessage      *SyncGroupRequest\n\t}{\n\t\t{\n\t\t\t\"v3\",\n\t\t\t3,\n\t\t\tpopulatedSyncGroupRequestV3,\n\t\t\t&SyncGroupRequest{\n\t\t\t\tVersion:         3,\n\t\t\t\tGroupId:         \"foo\",\n\t\t\t\tGenerationId:    0x00010203,\n\t\t\t\tMemberId:        \"baz\",\n\t\t\t\tGroupInstanceId: &groupInstanceId,\n\t\t\t\tGroupAssignments: []SyncGroupRequestAssignment{\n\t\t\t\t\t{\n\t\t\t\t\t\tMemberId:   \"baz\",\n\t\t\t\t\t\tAssignment: []byte(\"foo\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"v4\",\n\t\t\t4,\n\t\t\tpopulatedSyncGroupRequestV4,\n\t\t\t&SyncGroupRequest{\n\t\t\t\tVersion:         4,\n\t\t\t\tGroupId:         \"foo\",\n\t\t\t\tGenerationId:    0x00010203,\n\t\t\t\tMemberId:        \"baz\",\n\t\t\t\tGroupInstanceId: &groupInstanceId,\n\t\t\t\tGroupAssignments: []SyncGroupRequestAssignment{\n\t\t\t\t\t{\n\t\t\t\t\t\tMemberId:   \"baz\",\n\t\t\t\t\t\tAssignment: []byte(\"foo\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tfor _, c := range tests {\n\t\trequest := new(SyncGroupRequest)\n\t\ttestVersionDecodable(t, c.CaseName, request, c.MessageBytes, c.Version)\n\t\tif !reflect.DeepEqual(c.Message, request) {\n\t\t\tt.Errorf(\"case %s decode failed, expected:%+v got %+v\", c.CaseName, c.Message, request)\n\t\t}\n\t\ttestEncodable(t, c.CaseName, c.Message, c.MessageBytes)\n\t}\n}\n"
  },
  {
    "path": "sync_group_response.go",
    "content": "package sarama\n\nimport \"time\"\n\ntype SyncGroupResponse struct {\n\t// Version defines the protocol version to use for encode and decode\n\tVersion int16\n\t// ThrottleTime contains the duration in milliseconds for which the\n\t// request was throttled due to a quota violation, or zero if the request\n\t// did not violate any quota.\n\tThrottleTime int32\n\t// Err contains the error code, or 0 if there was no error.\n\tErr KError\n\t// MemberAssignment contains the member assignment.\n\tMemberAssignment []byte\n}\n\nfunc (r *SyncGroupResponse) setVersion(v int16) {\n\tr.Version = v\n}\n\nfunc (r *SyncGroupResponse) GetMemberAssignment() (*ConsumerGroupMemberAssignment, error) {\n\tassignment := new(ConsumerGroupMemberAssignment)\n\terr := decode(r.MemberAssignment, assignment, nil)\n\treturn assignment, err\n}\n\nfunc (r *SyncGroupResponse) encode(pe packetEncoder) error {\n\tif r.Version >= 1 {\n\t\tpe.putInt32(r.ThrottleTime)\n\t}\n\tpe.putKError(r.Err)\n\tif err := pe.putBytes(r.MemberAssignment); err != nil {\n\t\treturn err\n\t}\n\n\tpe.putEmptyTaggedFieldArray()\n\treturn nil\n}\n\nfunc (r *SyncGroupResponse) decode(pd packetDecoder, version int16) (err error) {\n\tr.Version = version\n\tif r.Version >= 1 {\n\t\tif r.ThrottleTime, err = pd.getInt32(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tr.Err, err = pd.getKError()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr.MemberAssignment, err = pd.getBytes()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = pd.getEmptyTaggedFieldArray()\n\treturn err\n}\n\nfunc (r *SyncGroupResponse) key() int16 {\n\treturn apiKeySyncGroup\n}\n\nfunc (r *SyncGroupResponse) version() int16 {\n\treturn r.Version\n}\n\nfunc (r *SyncGroupResponse) headerVersion() int16 {\n\tif r.Version >= 4 {\n\t\treturn 1\n\t}\n\treturn 0\n}\n\nfunc (r *SyncGroupResponse) isValidVersion() bool {\n\treturn r.Version >= 0 && r.Version <= 4\n}\n\nfunc (r *SyncGroupResponse) isFlexible() bool {\n\treturn r.isFlexibleVersion(r.Version)\n}\n\nfunc (r *SyncGroupResponse) isFlexibleVersion(version int16) bool {\n\treturn version >= 4\n}\n\nfunc (r *SyncGroupResponse) requiredVersion() KafkaVersion {\n\tswitch r.Version {\n\tcase 4:\n\t\treturn V2_4_0_0\n\tcase 3:\n\t\treturn V2_3_0_0\n\tcase 2:\n\t\treturn V2_0_0_0\n\tcase 1:\n\t\treturn V0_11_0_0\n\tcase 0:\n\t\treturn V0_9_0_0\n\tdefault:\n\t\treturn V2_3_0_0\n\t}\n}\n\nfunc (r *SyncGroupResponse) throttleTime() time.Duration {\n\treturn time.Duration(r.ThrottleTime) * time.Millisecond\n}\n"
  },
  {
    "path": "sync_group_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nvar (\n\tsyncGroupResponseV0NoError = []byte{\n\t\t0x00, 0x00, // No error\n\t\t0, 0, 0, 3, 0x01, 0x02, 0x03, // Member assignment data\n\t}\n\n\tsyncGroupResponseV0WithError = []byte{\n\t\t0, 27, // ErrRebalanceInProgress\n\t\t0, 0, 0, 0, // No member assignment data\n\t}\n\n\tsyncGroupResponseV1NoError = []byte{\n\t\t0, 0, 0, 100, // ThrottleTimeMs\n\t\t0x00, 0x00, // No error\n\t\t0, 0, 0, 3, 0x01, 0x02, 0x03, // Member assignment data\n\t}\n\n\tsyncGroupResponseV4NoError = []byte{\n\t\t0, 0, 0, 100, // ThrottleTimeMs\n\t\t0x00, 0x00, // No error\n\t\t4, 0x01, 0x02, 0x03, // Member assignment data\n\t\t0, // empty tagged fields\n\t}\n)\n\nfunc TestSyncGroupResponse(t *testing.T) {\n\ttests := []struct {\n\t\tCaseName     string\n\t\tVersion      int16\n\t\tMessageBytes []byte\n\t\tMessage      *SyncGroupResponse\n\t}{\n\t\t{\n\t\t\t\"v0-noErr\",\n\t\t\t0,\n\t\t\tsyncGroupResponseV0NoError,\n\t\t\t&SyncGroupResponse{\n\t\t\t\tVersion:          0,\n\t\t\t\tErr:              ErrNoError,\n\t\t\t\tMemberAssignment: []byte{1, 2, 3},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"v0-Err\",\n\t\t\t0,\n\t\t\tsyncGroupResponseV0WithError,\n\t\t\t&SyncGroupResponse{\n\t\t\t\tVersion:          0,\n\t\t\t\tErr:              ErrRebalanceInProgress,\n\t\t\t\tMemberAssignment: []byte{},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"v1-noErr\",\n\t\t\t1,\n\t\t\tsyncGroupResponseV1NoError,\n\t\t\t&SyncGroupResponse{\n\t\t\t\tThrottleTime:     100,\n\t\t\t\tVersion:          1,\n\t\t\t\tErr:              ErrNoError,\n\t\t\t\tMemberAssignment: []byte{1, 2, 3},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"v4-noErr\",\n\t\t\t4,\n\t\t\tsyncGroupResponseV4NoError,\n\t\t\t&SyncGroupResponse{\n\t\t\t\tThrottleTime:     100,\n\t\t\t\tVersion:          4,\n\t\t\t\tErr:              ErrNoError,\n\t\t\t\tMemberAssignment: []byte{1, 2, 3},\n\t\t\t},\n\t\t},\n\t}\n\tfor _, c := range tests {\n\t\tresponse := new(SyncGroupResponse)\n\t\ttestVersionDecodable(t, c.CaseName, response, c.MessageBytes, c.Version)\n\t\tif !reflect.DeepEqual(c.Message, response) {\n\t\t\tt.Errorf(\"case %s decode failed, expected:%+v got %+v\", c.CaseName, c.Message, response)\n\t\t}\n\t\ttestEncodable(t, c.CaseName, c.Message, c.MessageBytes)\n\t}\n}\n"
  },
  {
    "path": "sync_producer.go",
    "content": "package sarama\n\nimport \"sync\"\n\nvar expectationsPool = sync.Pool{\n\tNew: func() interface{} {\n\t\treturn make(chan *ProducerError, 1)\n\t},\n}\n\n// SyncProducer publishes Kafka messages, blocking until they have been acknowledged. It routes messages to the correct\n// broker, refreshing metadata as appropriate, and parses responses for errors. You must call Close() on a producer\n// to avoid leaks, it may not be garbage-collected automatically when it passes out of scope.\n//\n// The SyncProducer comes with two caveats: it will generally be less efficient than the AsyncProducer, and the actual\n// durability guarantee provided when a message is acknowledged depend on the configured value of `Producer.RequiredAcks`.\n// There are configurations where a message acknowledged by the SyncProducer can still sometimes be lost.\n//\n// For implementation reasons, the SyncProducer requires `Producer.Return.Errors` and `Producer.Return.Successes` to\n// be set to true in its configuration.\ntype SyncProducer interface {\n\n\t// SendMessage produces a given message, and returns only when it either has\n\t// succeeded or failed to produce. It will return the partition and the offset\n\t// of the produced message, or an error if the message failed to produce.\n\tSendMessage(msg *ProducerMessage) (partition int32, offset int64, err error)\n\n\t// SendMessages produces a given set of messages, and returns only when all\n\t// messages in the set have either succeeded or failed. Note that messages\n\t// can succeed and fail individually; if some succeed and some fail,\n\t// SendMessages will return an error.\n\tSendMessages(msgs []*ProducerMessage) error\n\n\t// Close shuts down the producer; you must call this function before a producer\n\t// object passes out of scope, as it may otherwise leak memory.\n\t// You must call this before calling Close on the underlying client.\n\tClose() error\n\n\t// TxnStatus return current producer transaction status.\n\tTxnStatus() ProducerTxnStatusFlag\n\n\t// IsTransactional return true when current producer is transactional.\n\tIsTransactional() bool\n\n\t// BeginTxn mark current transaction as ready.\n\tBeginTxn() error\n\n\t// CommitTxn commit current transaction.\n\tCommitTxn() error\n\n\t// AbortTxn abort current transaction.\n\tAbortTxn() error\n\n\t// AddOffsetsToTxn add associated offsets to current transaction.\n\tAddOffsetsToTxn(offsets map[string][]*PartitionOffsetMetadata, groupId string) error\n\n\t// AddMessageToTxn add message offsets to current transaction.\n\tAddMessageToTxn(msg *ConsumerMessage, groupId string, metadata *string) error\n}\n\ntype syncProducer struct {\n\tproducer *asyncProducer\n\twg       sync.WaitGroup\n}\n\n// NewSyncProducer creates a new SyncProducer using the given broker addresses and configuration.\nfunc NewSyncProducer(addrs []string, config *Config) (SyncProducer, error) {\n\tif config == nil {\n\t\tconfig = NewConfig()\n\t\tconfig.Producer.Return.Successes = true\n\t}\n\n\tif err := verifyProducerConfig(config); err != nil {\n\t\treturn nil, err\n\t}\n\n\tp, err := NewAsyncProducer(addrs, config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn newSyncProducerFromAsyncProducer(p.(*asyncProducer)), nil\n}\n\n// NewSyncProducerFromClient creates a new SyncProducer using the given client. It is still\n// necessary to call Close() on the underlying client when shutting down this producer.\nfunc NewSyncProducerFromClient(client Client) (SyncProducer, error) {\n\tif err := verifyProducerConfig(client.Config()); err != nil {\n\t\treturn nil, err\n\t}\n\n\tp, err := NewAsyncProducerFromClient(client)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn newSyncProducerFromAsyncProducer(p.(*asyncProducer)), nil\n}\n\nfunc newSyncProducerFromAsyncProducer(p *asyncProducer) *syncProducer {\n\tsp := &syncProducer{producer: p}\n\n\tsp.wg.Add(2)\n\tgo withRecover(sp.handleSuccesses)\n\tgo withRecover(sp.handleErrors)\n\n\treturn sp\n}\n\nfunc verifyProducerConfig(config *Config) error {\n\tif !config.Producer.Return.Errors {\n\t\treturn ConfigurationError(\"Producer.Return.Errors must be true to be used in a SyncProducer\")\n\t}\n\tif !config.Producer.Return.Successes {\n\t\treturn ConfigurationError(\"Producer.Return.Successes must be true to be used in a SyncProducer\")\n\t}\n\treturn nil\n}\n\nfunc (sp *syncProducer) SendMessage(msg *ProducerMessage) (partition int32, offset int64, err error) {\n\texpectation := expectationsPool.Get().(chan *ProducerError)\n\tmsg.expectation = expectation\n\tsp.producer.Input() <- msg\n\tpErr := <-expectation\n\tmsg.expectation = nil\n\texpectationsPool.Put(expectation)\n\tif pErr != nil {\n\t\treturn -1, -1, pErr.Err\n\t}\n\n\treturn msg.Partition, msg.Offset, nil\n}\n\nfunc (sp *syncProducer) SendMessages(msgs []*ProducerMessage) error {\n\tindices := make(chan int, len(msgs))\n\tgo func() {\n\t\tfor i, msg := range msgs {\n\t\t\texpectation := expectationsPool.Get().(chan *ProducerError)\n\t\t\tmsg.expectation = expectation\n\t\t\tsp.producer.Input() <- msg\n\t\t\tindices <- i\n\t\t}\n\t\tclose(indices)\n\t}()\n\n\tvar errors ProducerErrors\n\tfor i := range indices {\n\t\texpectation := msgs[i].expectation\n\t\tpErr := <-expectation\n\t\tmsgs[i].expectation = nil\n\t\texpectationsPool.Put(expectation)\n\t\tif pErr != nil {\n\t\t\terrors = append(errors, pErr)\n\t\t}\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn errors\n\t}\n\treturn nil\n}\n\nfunc (sp *syncProducer) handleSuccesses() {\n\tdefer sp.wg.Done()\n\tfor msg := range sp.producer.Successes() {\n\t\texpectation := msg.expectation\n\t\texpectation <- nil\n\t}\n}\n\nfunc (sp *syncProducer) handleErrors() {\n\tdefer sp.wg.Done()\n\tfor err := range sp.producer.Errors() {\n\t\texpectation := err.Msg.expectation\n\t\texpectation <- err\n\t}\n}\n\nfunc (sp *syncProducer) Close() error {\n\tsp.producer.AsyncClose()\n\tsp.wg.Wait()\n\treturn nil\n}\n\nfunc (sp *syncProducer) IsTransactional() bool {\n\treturn sp.producer.IsTransactional()\n}\n\nfunc (sp *syncProducer) BeginTxn() error {\n\treturn sp.producer.BeginTxn()\n}\n\nfunc (sp *syncProducer) CommitTxn() error {\n\treturn sp.producer.CommitTxn()\n}\n\nfunc (sp *syncProducer) AbortTxn() error {\n\treturn sp.producer.AbortTxn()\n}\n\nfunc (sp *syncProducer) AddOffsetsToTxn(offsets map[string][]*PartitionOffsetMetadata, groupId string) error {\n\treturn sp.producer.AddOffsetsToTxn(offsets, groupId)\n}\n\nfunc (sp *syncProducer) AddMessageToTxn(msg *ConsumerMessage, groupId string, metadata *string) error {\n\treturn sp.producer.AddMessageToTxn(msg, groupId, metadata)\n}\n\nfunc (p *syncProducer) TxnStatus() ProducerTxnStatusFlag {\n\treturn p.producer.TxnStatus()\n}\n"
  },
  {
    "path": "sync_producer_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"errors\"\n\t\"log\"\n\t\"sync\"\n\t\"testing\"\n)\n\nfunc TestSyncProducer(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tleader := NewMockBroker(t, 2)\n\n\tmetadataResponse := new(MetadataResponse)\n\tmetadataResponse.AddBroker(leader.Addr(), leader.BrokerID())\n\tmetadataResponse.AddTopicPartition(\"my_topic\", 0, leader.BrokerID(), nil, nil, nil, ErrNoError)\n\tseedBroker.Returns(metadataResponse)\n\n\tprodSuccess := new(ProduceResponse)\n\tprodSuccess.AddTopicPartition(\"my_topic\", 0, ErrNoError)\n\tfor i := 0; i < 10; i++ {\n\t\tleader.Returns(prodSuccess)\n\t}\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Return.Successes = true\n\tproducer, err := NewSyncProducer([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tfor i := 0; i < 10; i++ {\n\t\tmsg := &ProducerMessage{\n\t\t\tTopic:    \"my_topic\",\n\t\t\tValue:    StringEncoder(TestMessage),\n\t\t\tMetadata: \"test\",\n\t\t}\n\n\t\tpartition, offset, err := producer.SendMessage(msg)\n\n\t\tif partition != 0 || msg.Partition != partition {\n\t\t\tt.Error(\"Unexpected partition\")\n\t\t}\n\t\tif offset != 0 || msg.Offset != offset {\n\t\t\tt.Error(\"Unexpected offset\")\n\t\t}\n\t\tif str, ok := msg.Metadata.(string); !ok || str != \"test\" {\n\t\t\tt.Error(\"Unexpected metadata\")\n\t\t}\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}\n\n\tsafeClose(t, producer)\n\tleader.Close()\n\tseedBroker.Close()\n}\n\nfunc TestSyncProducerTransactional(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tdefer seedBroker.Close()\n\tleader := NewMockBroker(t, 2)\n\tdefer leader.Close()\n\n\tconfig := NewTestConfig()\n\tconfig.Version = V0_11_0_0\n\tconfig.Producer.RequiredAcks = WaitForAll\n\tconfig.Producer.Return.Successes = true\n\tconfig.Producer.Transaction.ID = \"test\"\n\tconfig.Producer.Idempotent = true\n\tconfig.Producer.Retry.Max = 5\n\tconfig.Net.MaxOpenRequests = 1\n\n\tmetadataResponse := new(MetadataResponse)\n\tmetadataResponse.Version = 4\n\tmetadataResponse.ControllerID = leader.BrokerID()\n\tmetadataResponse.AddBroker(leader.Addr(), leader.BrokerID())\n\tmetadataResponse.AddTopic(\"my_topic\", ErrNoError)\n\tmetadataResponse.AddTopicPartition(\"my_topic\", 0, leader.BrokerID(), nil, nil, nil, ErrNoError)\n\tseedBroker.Returns(metadataResponse)\n\n\tclient, err := NewClient([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer safeClose(t, client)\n\n\tfindCoordinatorResponse := new(FindCoordinatorResponse)\n\tfindCoordinatorResponse.Coordinator = client.Brokers()[0]\n\tfindCoordinatorResponse.Version = 1\n\tleader.Returns(findCoordinatorResponse)\n\n\tinitProducerIdResponse := new(InitProducerIDResponse)\n\tleader.Returns(initProducerIdResponse)\n\n\taddPartitionToTxn := new(AddPartitionsToTxnResponse)\n\taddPartitionToTxn.Errors = map[string][]*PartitionError{\n\t\t\"my_topic\": {\n\t\t\t{\n\t\t\t\tPartition: 0,\n\t\t\t},\n\t\t},\n\t}\n\tleader.Returns(addPartitionToTxn)\n\n\tprodSuccess := new(ProduceResponse)\n\tprodSuccess.Version = 3\n\tprodSuccess.AddTopicPartition(\"my_topic\", 0, ErrNoError)\n\tfor i := 0; i < 10; i++ {\n\t\tleader.Returns(prodSuccess)\n\t}\n\n\tendTxnResponse := &EndTxnResponse{}\n\tleader.Returns(endTxnResponse)\n\n\tproducer, err := NewSyncProducerFromClient(client)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif !producer.IsTransactional() {\n\t\tt.Error(\"producer is not transactional\")\n\t}\n\n\terr = producer.BeginTxn()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif producer.TxnStatus()&ProducerTxnFlagInTransaction == 0 {\n\t\tt.Error(\"transaction must started\")\n\t}\n\n\tfor i := 0; i < 10; i++ {\n\t\tmsg := &ProducerMessage{\n\t\t\tTopic:    \"my_topic\",\n\t\t\tValue:    StringEncoder(TestMessage),\n\t\t\tMetadata: \"test\",\n\t\t}\n\n\t\tpartition, offset, err := producer.SendMessage(msg)\n\n\t\tif partition != 0 || msg.Partition != partition {\n\t\t\tt.Error(\"Unexpected partition\")\n\t\t}\n\t\tif offset != 0 || msg.Offset != offset {\n\t\t\tt.Error(\"Unexpected offset\")\n\t\t}\n\t\tif str, ok := msg.Metadata.(string); !ok || str != \"test\" {\n\t\t\tt.Error(\"Unexpected metadata\")\n\t\t}\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}\n\terr = producer.CommitTxn()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tsafeClose(t, producer)\n}\n\nfunc TestSyncProducerBatch(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tleader := NewMockBroker(t, 2)\n\n\tmetadataResponse := new(MetadataResponse)\n\tmetadataResponse.AddBroker(leader.Addr(), leader.BrokerID())\n\tmetadataResponse.AddTopicPartition(\"my_topic\", 0, leader.BrokerID(), nil, nil, nil, ErrNoError)\n\tseedBroker.Returns(metadataResponse)\n\n\tprodSuccess := new(ProduceResponse)\n\tprodSuccess.AddTopicPartition(\"my_topic\", 0, ErrNoError)\n\tleader.Returns(prodSuccess)\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Flush.Messages = 3\n\tconfig.Producer.Return.Successes = true\n\tproducer, err := NewSyncProducer([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = producer.SendMessages([]*ProducerMessage{\n\t\t{\n\t\t\tTopic:    \"my_topic\",\n\t\t\tValue:    StringEncoder(TestMessage),\n\t\t\tMetadata: \"test\",\n\t\t},\n\t\t{\n\t\t\tTopic:    \"my_topic\",\n\t\t\tValue:    StringEncoder(TestMessage),\n\t\t\tMetadata: \"test\",\n\t\t},\n\t\t{\n\t\t\tTopic:    \"my_topic\",\n\t\t\tValue:    StringEncoder(TestMessage),\n\t\t\tMetadata: \"test\",\n\t\t},\n\t})\n\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tsafeClose(t, producer)\n\tleader.Close()\n\tseedBroker.Close()\n}\n\nfunc TestConcurrentSyncProducer(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tleader := NewMockBroker(t, 2)\n\n\tmetadataResponse := new(MetadataResponse)\n\tmetadataResponse.AddBroker(leader.Addr(), leader.BrokerID())\n\tmetadataResponse.AddTopicPartition(\"my_topic\", 0, leader.BrokerID(), nil, nil, nil, ErrNoError)\n\tseedBroker.Returns(metadataResponse)\n\n\tprodSuccess := new(ProduceResponse)\n\tprodSuccess.AddTopicPartition(\"my_topic\", 0, ErrNoError)\n\tleader.Returns(prodSuccess)\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Flush.Messages = 100\n\tconfig.Producer.Return.Successes = true\n\tproducer, err := NewSyncProducer([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\twg := sync.WaitGroup{}\n\n\tfor i := 0; i < 100; i++ {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tmsg := &ProducerMessage{Topic: \"my_topic\", Value: StringEncoder(TestMessage)}\n\t\t\tpartition, _, err := producer.SendMessage(msg)\n\t\t\tif partition != 0 {\n\t\t\t\tt.Error(\"Unexpected partition\")\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\twg.Done()\n\t\t}()\n\t}\n\twg.Wait()\n\n\tsafeClose(t, producer)\n\tleader.Close()\n\tseedBroker.Close()\n}\n\nfunc TestSyncProducerToNonExistingTopic(t *testing.T) {\n\tbroker := NewMockBroker(t, 1)\n\n\tmetadataResponse := new(MetadataResponse)\n\tmetadataResponse.AddBroker(broker.Addr(), broker.BrokerID())\n\tmetadataResponse.AddTopicPartition(\"my_topic\", 0, broker.BrokerID(), nil, nil, nil, ErrNoError)\n\tbroker.Returns(metadataResponse)\n\n\tconfig := NewTestConfig()\n\tconfig.Metadata.Retry.Max = 0\n\tconfig.Producer.Retry.Max = 0\n\tconfig.Producer.Return.Successes = true\n\n\tproducer, err := NewSyncProducer([]string{broker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tmetadataResponse = new(MetadataResponse)\n\tmetadataResponse.AddBroker(broker.Addr(), broker.BrokerID())\n\tmetadataResponse.AddTopic(\"unknown\", ErrUnknownTopicOrPartition)\n\tbroker.Returns(metadataResponse)\n\n\t_, _, err = producer.SendMessage(&ProducerMessage{Topic: \"unknown\"})\n\tif !errors.Is(err, ErrUnknownTopicOrPartition) {\n\t\tt.Error(\"Uxpected ErrUnknownTopicOrPartition, found:\", err)\n\t}\n\n\tsafeClose(t, producer)\n\tbroker.Close()\n}\n\nfunc TestSyncProducerRecoveryWithRetriesDisabled(t *testing.T) {\n\tseedBroker := NewMockBroker(t, 1)\n\tleader1 := NewMockBroker(t, 2)\n\tleader2 := NewMockBroker(t, 3)\n\n\tmetadataLeader1 := new(MetadataResponse)\n\tmetadataLeader1.AddBroker(leader1.Addr(), leader1.BrokerID())\n\tmetadataLeader1.AddTopicPartition(\"my_topic\", 0, leader1.BrokerID(), nil, nil, nil, ErrNoError)\n\tseedBroker.Returns(metadataLeader1)\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Retry.Max = 0 // disable!\n\tconfig.Producer.Retry.Backoff = 0\n\tconfig.Producer.Return.Successes = true\n\tproducer, err := NewSyncProducer([]string{seedBroker.Addr()}, config)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tseedBroker.Close()\n\n\tprodNotLeader := new(ProduceResponse)\n\tprodNotLeader.AddTopicPartition(\"my_topic\", 0, ErrNotLeaderForPartition)\n\tleader1.Returns(prodNotLeader)\n\t_, _, err = producer.SendMessage(&ProducerMessage{Topic: \"my_topic\", Value: StringEncoder(TestMessage)})\n\tif !errors.Is(err, ErrNotLeaderForPartition) {\n\t\tt.Fatal(err)\n\t}\n\n\tmetadataLeader2 := new(MetadataResponse)\n\tmetadataLeader2.AddBroker(leader2.Addr(), leader2.BrokerID())\n\tmetadataLeader2.AddTopicPartition(\"my_topic\", 0, leader2.BrokerID(), nil, nil, nil, ErrNoError)\n\tleader1.Returns(metadataLeader2)\n\tprodSuccess := new(ProduceResponse)\n\tprodSuccess.AddTopicPartition(\"my_topic\", 0, ErrNoError)\n\tleader2.Returns(prodSuccess)\n\t_, _, err = producer.SendMessage(&ProducerMessage{Topic: \"my_topic\", Value: StringEncoder(TestMessage)})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tleader1.Close()\n\tleader2.Close()\n\tsafeClose(t, producer)\n}\n\n// This example shows the basic usage pattern of the SyncProducer.\nfunc ExampleSyncProducer() {\n\tproducer, err := NewSyncProducer([]string{\"localhost:9092\"}, nil)\n\tif err != nil {\n\t\tlog.Fatalln(err)\n\t}\n\tdefer func() {\n\t\tif err := producer.Close(); err != nil {\n\t\t\tlog.Fatalln(err)\n\t\t}\n\t}()\n\n\tmsg := &ProducerMessage{Topic: \"my_topic\", Value: StringEncoder(\"testing 123\")}\n\tpartition, offset, err := producer.SendMessage(msg)\n\tif err != nil {\n\t\tlog.Printf(\"FAILED to send message: %s\\n\", err)\n\t} else {\n\t\tlog.Printf(\"> message sent to partition %d at offset %d\\n\", partition, offset)\n\t}\n}\n"
  },
  {
    "path": "timestamp.go",
    "content": "package sarama\n\nimport (\n\t\"fmt\"\n\t\"time\"\n)\n\ntype Timestamp struct {\n\t*time.Time\n}\n\nfunc (t Timestamp) encode(pe packetEncoder) error {\n\ttimestamp := int64(-1)\n\n\tif !t.Before(time.Unix(0, 0)) {\n\t\ttimestamp = t.UnixNano() / int64(time.Millisecond)\n\t} else if !t.IsZero() {\n\t\treturn PacketEncodingError{fmt.Sprintf(\"invalid timestamp (%v)\", t)}\n\t}\n\n\tpe.putInt64(timestamp)\n\treturn nil\n}\n\nfunc (t Timestamp) decode(pd packetDecoder) error {\n\tmillis, err := pd.getInt64()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// negative timestamps are invalid, in these cases we should return\n\t// a zero time\n\ttimestamp := time.Time{}\n\tif millis >= 0 {\n\t\ttimestamp = time.Unix(millis/1000, (millis%1000)*int64(time.Millisecond))\n\t}\n\n\t*t.Time = timestamp\n\treturn nil\n}\n"
  },
  {
    "path": "tools/README.md",
    "content": "# Sarama tools\n\nThis folder contains applications that are useful for exploration of your Kafka cluster, or instrumentation.\nSome of these tools mirror tools that ship with Kafka, but these tools won't require installing the JVM to function.\n\n- [kafka-console-producer](./kafka-console-producer): a command line tool to produce a single message to your Kafka custer.\n- [kafka-console-partitionconsumer](./kafka-console-partitionconsumer): (deprecated) a command line tool to consume a single partition of a topic on your Kafka cluster.\n- [kafka-console-consumer](./kafka-console-consumer): a command line tool to consume arbitrary partitions of a topic on your Kafka cluster.\n- [kafka-producer-performance](./kafka-producer-performance): a command line tool to performance test producers (sync and async) on your Kafka cluster.\n\nTo install all tools, run `go install github.com/IBM/sarama/tools/...@latest`\n"
  },
  {
    "path": "tools/kafka-producer-performance/README.md",
    "content": "# kafka-producer-performance\n\nA command line tool to test producer performance.\n\n### Installation\n\n    go get github.com/IBM/sarama/tools/kafka-producer-performance\n\n\n### Usage\n\n    # Display all command line options\n    kafka-producer-performance -help\n\n\t# Minimum invocation\n    kafka-producer-performance \\\n\t\t-brokers=kafka:9092 \\\n\t\t-message-load=50000 \\\n\t\t-message-size=100 \\\n\t\t-topic=producer_test\n"
  },
  {
    "path": "tools/kafka-producer-performance/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"crypto/x509\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\tgosync \"sync\"\n\t\"time\"\n\n\t\"github.com/rcrowley/go-metrics\"\n\n\t\"github.com/IBM/sarama\"\n\t\"github.com/IBM/sarama/tools/tls\"\n)\n\nvar (\n\tsync = flag.Bool(\n\t\t\"sync\",\n\t\tfalse,\n\t\t\"Use a synchronous producer.\",\n\t)\n\tmessageLoad = flag.Int(\n\t\t\"message-load\",\n\t\t0,\n\t\t\"REQUIRED: The number of messages to produce to -topic.\",\n\t)\n\tmessageSize = flag.Int(\n\t\t\"message-size\",\n\t\t0,\n\t\t\"REQUIRED: The approximate size (in bytes) of each message to produce to -topic.\",\n\t)\n\tbrokers = flag.String(\n\t\t\"brokers\",\n\t\t\"\",\n\t\t\"REQUIRED: A comma separated list of broker addresses.\",\n\t)\n\tsecurityProtocol = flag.String(\n\t\t\"security-protocol\",\n\t\t\"PLAINTEXT\",\n\t\t\"The name of the security protocol to talk to Kafka (PLAINTEXT, SSL).\",\n\t)\n\ttlsRootCACerts = flag.String(\n\t\t\"tls-ca-certs\",\n\t\t\"\",\n\t\t\"The path to a file that contains a set of root certificate authorities in PEM format \"+\n\t\t\t\"to trust when verifying broker certificates when -security-protocol=SSL \"+\n\t\t\t\"(leave empty to use the host's root CA set).\",\n\t)\n\ttlsClientCert = flag.String(\n\t\t\"tls-client-cert\",\n\t\t\"\",\n\t\t\"The path to a file that contains the client certificate to send to the broker \"+\n\t\t\t\"in PEM format if client authentication is required when -security-protocol=SSL \"+\n\t\t\t\"(leave empty to disable client authentication).\",\n\t)\n\ttlsClientKey = flag.String(\n\t\t\"tls-client-key\",\n\t\t\"\",\n\t\t\"The path to a file that contains the client private key linked to the client certificate \"+\n\t\t\t\"in PEM format when -security-protocol=SSL (REQUIRED if tls-client-cert is provided).\",\n\t)\n\ttopic = flag.String(\n\t\t\"topic\",\n\t\t\"\",\n\t\t\"REQUIRED: The topic to run the performance test on.\",\n\t)\n\tpartition = flag.Int(\n\t\t\"partition\",\n\t\t-1,\n\t\t\"The partition of -topic to run the performance test on.\",\n\t)\n\tthroughput = flag.Int(\n\t\t\"throughput\",\n\t\t0,\n\t\t\"The maximum number of messages to send per second (0 for no limit).\",\n\t)\n\tmaxOpenRequests = flag.Int(\n\t\t\"max-open-requests\",\n\t\t5,\n\t\t\"The maximum number of unacknowledged requests the client will send on a single connection before blocking.\",\n\t)\n\tmaxMessageBytes = flag.Int(\n\t\t\"max-message-bytes\",\n\t\t1000000,\n\t\t\"The max permitted size of a message.\",\n\t)\n\trequiredAcks = flag.Int(\n\t\t\"required-acks\",\n\t\t1,\n\t\t\"The required number of acks needed from the broker (-1: all, 0: none, 1: local).\",\n\t)\n\ttimeout = flag.Duration(\n\t\t\"timeout\",\n\t\t10*time.Second,\n\t\t\"The duration the producer will wait to receive -required-acks.\",\n\t)\n\tpartitioner = flag.String(\n\t\t\"partitioner\",\n\t\t\"roundrobin\",\n\t\t\"The partitioning scheme to use (hash, manual, random, roundrobin).\",\n\t)\n\tcompression = flag.String(\n\t\t\"compression\",\n\t\t\"none\",\n\t\t\"The compression method to use (none, gzip, snappy, lz4).\",\n\t)\n\tflushFrequency = flag.Duration(\n\t\t\"flush-frequency\",\n\t\t0,\n\t\t\"The best-effort frequency of flushes.\",\n\t)\n\tflushBytes = flag.Int(\n\t\t\"flush-bytes\",\n\t\t0,\n\t\t\"The best-effort number of bytes needed to trigger a flush.\",\n\t)\n\tflushMessages = flag.Int(\n\t\t\"flush-messages\",\n\t\t0,\n\t\t\"The best-effort number of messages needed to trigger a flush.\",\n\t)\n\tflushMaxMessages = flag.Int(\n\t\t\"flush-max-messages\",\n\t\t0,\n\t\t\"The maximum number of messages the producer will send in a single request.\",\n\t)\n\tclientID = flag.String(\n\t\t\"client-id\",\n\t\t\"sarama\",\n\t\t\"The client ID sent with every request to the brokers.\",\n\t)\n\tchannelBufferSize = flag.Int(\n\t\t\"channel-buffer-size\",\n\t\t256,\n\t\t\"The number of events to buffer in internal and external channels.\",\n\t)\n\troutines = flag.Int(\n\t\t\"routines\",\n\t\t1,\n\t\t\"The number of routines to send the messages from (-sync only).\",\n\t)\n\tversion = flag.String(\n\t\t\"version\",\n\t\t\"0.8.2.0\",\n\t\t\"The assumed version of Kafka.\",\n\t)\n\tverbose = flag.Bool(\n\t\t\"verbose\",\n\t\tfalse,\n\t\t\"Turn on sarama logging to stderr\",\n\t)\n)\n\nfunc parseCompression(scheme string) sarama.CompressionCodec {\n\tswitch scheme {\n\tcase \"none\":\n\t\treturn sarama.CompressionNone\n\tcase \"gzip\":\n\t\treturn sarama.CompressionGZIP\n\tcase \"snappy\":\n\t\treturn sarama.CompressionSnappy\n\tcase \"lz4\":\n\t\treturn sarama.CompressionLZ4\n\tdefault:\n\t\tprintUsageErrorAndExit(fmt.Sprintf(\"Unknown -compression: %s\", scheme))\n\t}\n\tpanic(\"should not happen\")\n}\n\nfunc parsePartitioner(scheme string, partition int) sarama.PartitionerConstructor {\n\tif partition < 0 && scheme == \"manual\" {\n\t\tprintUsageErrorAndExit(\"-partition must not be -1 for -partitioning=manual\")\n\t}\n\tswitch scheme {\n\tcase \"manual\":\n\t\treturn sarama.NewManualPartitioner\n\tcase \"hash\":\n\t\treturn sarama.NewHashPartitioner\n\tcase \"random\":\n\t\treturn sarama.NewRandomPartitioner\n\tcase \"roundrobin\":\n\t\treturn sarama.NewRoundRobinPartitioner\n\tdefault:\n\t\tprintUsageErrorAndExit(fmt.Sprintf(\"Unknown -partitioning: %s\", scheme))\n\t}\n\tpanic(\"should not happen\")\n}\n\nfunc parseVersion(version string) sarama.KafkaVersion {\n\tresult, err := sarama.ParseKafkaVersion(version)\n\tif err != nil {\n\t\tprintUsageErrorAndExit(fmt.Sprintf(\"unknown -version: %s\", version))\n\t}\n\treturn result\n}\n\nfunc generateMessages(topic string, partition, messageLoad, messageSize int) []*sarama.ProducerMessage {\n\tmessages := make([]*sarama.ProducerMessage, messageLoad)\n\tfor i := 0; i < messageLoad; i++ {\n\t\tpayload := make([]byte, messageSize)\n\t\tif _, err := rand.Read(payload); err != nil {\n\t\t\tprintErrorAndExit(69, \"Failed to generate message payload: %s\", err)\n\t\t}\n\t\tmessages[i] = &sarama.ProducerMessage{\n\t\t\tTopic:     topic,\n\t\t\tPartition: int32(partition),\n\t\t\tValue:     sarama.ByteEncoder(payload),\n\t\t}\n\t}\n\treturn messages\n}\n\nfunc main() {\n\tflag.Parse()\n\n\tif *brokers == \"\" {\n\t\tprintUsageErrorAndExit(\"-brokers is required\")\n\t}\n\tif *topic == \"\" {\n\t\tprintUsageErrorAndExit(\"-topic is required\")\n\t}\n\tif *messageLoad <= 0 {\n\t\tprintUsageErrorAndExit(\"-message-load must be greater than 0\")\n\t}\n\tif *messageSize <= 0 {\n\t\tprintUsageErrorAndExit(\"-message-size must be greater than 0\")\n\t}\n\tif *routines < 1 || *routines > *messageLoad {\n\t\tprintUsageErrorAndExit(\"-routines must be greater than 0 and less than or equal to -message-load\")\n\t}\n\tif *securityProtocol != \"PLAINTEXT\" && *securityProtocol != \"SSL\" {\n\t\tprintUsageErrorAndExit(fmt.Sprintf(\"-security-protocol %q is not supported\", *securityProtocol))\n\t}\n\tif *verbose {\n\t\tsarama.Logger = log.New(os.Stderr, \"\", log.LstdFlags)\n\t}\n\n\tconfig := sarama.NewConfig()\n\n\tconfig.Net.MaxOpenRequests = *maxOpenRequests\n\tconfig.Producer.MaxMessageBytes = *maxMessageBytes\n\tconfig.Producer.RequiredAcks = sarama.RequiredAcks(*requiredAcks)\n\tconfig.Producer.Timeout = *timeout\n\tconfig.Producer.Partitioner = parsePartitioner(*partitioner, *partition)\n\tconfig.Producer.Compression = parseCompression(*compression)\n\tconfig.Producer.Flush.Frequency = *flushFrequency\n\tconfig.Producer.Flush.Bytes = *flushBytes\n\tconfig.Producer.Flush.Messages = *flushMessages\n\tconfig.Producer.Flush.MaxMessages = *flushMaxMessages\n\tconfig.Producer.Return.Successes = true\n\tconfig.ClientID = *clientID\n\tconfig.ChannelBufferSize = *channelBufferSize\n\tconfig.Version = parseVersion(*version)\n\n\tif *securityProtocol == \"SSL\" {\n\t\ttlsConfig, err := tls.NewConfig(*tlsClientCert, *tlsClientKey)\n\t\tif err != nil {\n\t\t\tprintErrorAndExit(69, \"failed to load client certificate from: %s and private key from: %s: %v\",\n\t\t\t\t*tlsClientCert, *tlsClientKey, err)\n\t\t}\n\n\t\tif *tlsRootCACerts != \"\" {\n\t\t\trootCAsBytes, err := os.ReadFile(*tlsRootCACerts)\n\t\t\tif err != nil {\n\t\t\t\tprintErrorAndExit(69, \"failed to read root CA certificates: %v\", err)\n\t\t\t}\n\t\t\tcertPool := x509.NewCertPool()\n\t\t\tif !certPool.AppendCertsFromPEM(rootCAsBytes) {\n\t\t\t\tprintErrorAndExit(69, \"failed to load root CA certificates from file: %s\", *tlsRootCACerts)\n\t\t\t}\n\t\t\t// Use specific root CA set vs the host's set\n\t\t\ttlsConfig.RootCAs = certPool\n\t\t}\n\n\t\tconfig.Net.TLS.Enable = true\n\t\tconfig.Net.TLS.Config = tlsConfig\n\t}\n\n\tif err := config.Validate(); err != nil {\n\t\tprintErrorAndExit(69, \"Invalid configuration: %s\", err)\n\t}\n\n\t// Print out metrics periodically.\n\tdone := make(chan struct{})\n\tctx, cancel := context.WithCancel(context.Background())\n\tgo func(ctx context.Context) {\n\t\tdefer close(done)\n\t\tt := time.Tick(5 * time.Second)\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-t:\n\t\t\t\tprintMetrics(os.Stdout, config.MetricRegistry)\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}(ctx)\n\n\tbrokers := strings.Split(*brokers, \",\")\n\tif *sync {\n\t\trunSyncProducer(*topic, *partition, *messageLoad, *messageSize, *routines,\n\t\t\tconfig, brokers, *throughput)\n\t} else {\n\t\trunAsyncProducer(*topic, *partition, *messageLoad, *messageSize,\n\t\t\tconfig, brokers, *throughput)\n\t}\n\n\tcancel()\n\t<-done\n}\n\nfunc runAsyncProducer(topic string, partition, messageLoad, messageSize int,\n\tconfig *sarama.Config, brokers []string, throughput int) {\n\tproducer, err := sarama.NewAsyncProducer(brokers, config)\n\tif err != nil {\n\t\tprintErrorAndExit(69, \"Failed to create producer: %s\", err)\n\t}\n\tdefer func() {\n\t\t// Print final metrics.\n\t\tprintMetrics(os.Stdout, config.MetricRegistry)\n\t\tif err := producer.Close(); err != nil {\n\t\t\tprintErrorAndExit(69, \"Failed to close producer: %s\", err)\n\t\t}\n\t}()\n\n\tmessages := generateMessages(topic, partition, messageLoad, messageSize)\n\n\tmessagesDone := make(chan struct{})\n\tgo func() {\n\t\tfor i := 0; i < messageLoad; i++ {\n\t\t\tselect {\n\t\t\tcase <-producer.Successes():\n\t\t\tcase err = <-producer.Errors():\n\t\t\t\tprintErrorAndExit(69, \"%s\", err)\n\t\t\t}\n\t\t}\n\t\tmessagesDone <- struct{}{}\n\t}()\n\n\tif throughput > 0 {\n\t\tticker := time.NewTicker(time.Second)\n\t\tfor idx, message := range messages {\n\t\t\tproducer.Input() <- message\n\t\t\tif (idx+1)%throughput == 0 {\n\t\t\t\t<-ticker.C\n\t\t\t}\n\t\t}\n\t\tticker.Stop()\n\t} else {\n\t\tfor _, message := range messages {\n\t\t\tproducer.Input() <- message\n\t\t}\n\t}\n\n\t<-messagesDone\n\tclose(messagesDone)\n}\n\nfunc runSyncProducer(topic string, partition, messageLoad, messageSize, routines int,\n\tconfig *sarama.Config, brokers []string, throughput int) {\n\tproducer, err := sarama.NewSyncProducer(brokers, config)\n\tif err != nil {\n\t\tprintErrorAndExit(69, \"Failed to create producer: %s\", err)\n\t}\n\tdefer func() {\n\t\t// Print final metrics.\n\t\tprintMetrics(os.Stdout, config.MetricRegistry)\n\t\tif err := producer.Close(); err != nil {\n\t\t\tprintErrorAndExit(69, \"Failed to close producer: %s\", err)\n\t\t}\n\t}()\n\n\tmessages := make([][]*sarama.ProducerMessage, routines)\n\tfor i := 0; i < routines; i++ {\n\t\tif i == routines-1 {\n\t\t\tmessages[i] = generateMessages(topic, partition, messageLoad/routines+messageLoad%routines, messageSize)\n\t\t} else {\n\t\t\tmessages[i] = generateMessages(topic, partition, messageLoad/routines, messageSize)\n\t\t}\n\t}\n\n\tvar wg gosync.WaitGroup\n\tif throughput > 0 {\n\t\tfor _, messages := range messages {\n\t\t\twg.Add(1)\n\t\t\tgo func() {\n\t\t\t\tticker := time.NewTicker(time.Second)\n\t\t\t\tfor _, message := range messages {\n\t\t\t\t\tfor i := 0; i < throughput; i++ {\n\t\t\t\t\t\t_, _, err = producer.SendMessage(message)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tprintErrorAndExit(69, \"Failed to send message: %s\", err)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t<-ticker.C\n\t\t\t\t}\n\t\t\t\tticker.Stop()\n\t\t\t\twg.Done()\n\t\t\t}()\n\t\t}\n\t} else {\n\t\tfor _, messages := range messages {\n\t\t\twg.Add(1)\n\t\t\tgo func() {\n\t\t\t\tfor _, message := range messages {\n\t\t\t\t\t_, _, err = producer.SendMessage(message)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tprintErrorAndExit(69, \"Failed to send message: %s\", err)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\twg.Done()\n\t\t\t}()\n\t\t}\n\t}\n\twg.Wait()\n}\n\nfunc printMetrics(w io.Writer, r metrics.Registry) {\n\trecordSendRateMetric := r.Get(\"record-send-rate\")\n\trequestLatencyMetric := r.Get(\"request-latency-in-ms\")\n\toutgoingByteRateMetric := r.Get(\"outgoing-byte-rate\")\n\trequestsInFlightMetric := r.Get(\"requests-in-flight\")\n\n\tif recordSendRateMetric == nil || requestLatencyMetric == nil || outgoingByteRateMetric == nil ||\n\t\trequestsInFlightMetric == nil {\n\t\treturn\n\t}\n\trecordSendRate := recordSendRateMetric.(metrics.Meter).Snapshot()\n\trequestLatency := requestLatencyMetric.(metrics.Histogram).Snapshot()\n\trequestLatencyPercentiles := requestLatency.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999})\n\toutgoingByteRate := outgoingByteRateMetric.(metrics.Meter).Snapshot()\n\trequestsInFlight := requestsInFlightMetric.(metrics.Counter).Count()\n\tfmt.Fprintf(w, \"%d records sent, %.1f records/sec (%.2f MiB/sec ingress, %.2f MiB/sec egress), \"+\n\t\t\"%.1f ms avg latency, %.1f ms stddev, %.1f ms 50th, %.1f ms 75th, \"+\n\t\t\"%.1f ms 95th, %.1f ms 99th, %.1f ms 99.9th, %d total req. in flight\\n\",\n\t\trecordSendRate.Count(),\n\t\trecordSendRate.RateMean(),\n\t\trecordSendRate.RateMean()*float64(*messageSize)/1024/1024,\n\t\toutgoingByteRate.RateMean()/1024/1024,\n\t\trequestLatency.Mean(),\n\t\trequestLatency.StdDev(),\n\t\trequestLatencyPercentiles[0],\n\t\trequestLatencyPercentiles[1],\n\t\trequestLatencyPercentiles[2],\n\t\trequestLatencyPercentiles[3],\n\t\trequestLatencyPercentiles[4],\n\t\trequestsInFlight,\n\t)\n}\n\nfunc printUsageErrorAndExit(message string) {\n\tfmt.Fprintln(os.Stderr, \"ERROR:\", message)\n\tfmt.Fprintln(os.Stderr)\n\tfmt.Fprintln(os.Stderr, \"Available command line options:\")\n\tflag.PrintDefaults()\n\tos.Exit(64)\n}\n\nfunc printErrorAndExit(code int, format string, values ...interface{}) {\n\tfmt.Fprintf(os.Stderr, \"ERROR: %s\\n\", fmt.Sprintf(format, values...))\n\tfmt.Fprintln(os.Stderr)\n\tos.Exit(code)\n}\n"
  },
  {
    "path": "tools/tls/config.go",
    "content": "package tls\n\nimport \"crypto/tls\"\n\nfunc NewConfig(clientCert, clientKey string) (*tls.Config, error) {\n\ttlsConfig := tls.Config{\n\t\tMinVersion: tls.VersionTLS12,\n\t}\n\n\tif clientCert != \"\" && clientKey != \"\" {\n\t\tcert, err := tls.LoadX509KeyPair(clientCert, clientKey)\n\t\tif err != nil {\n\t\t\treturn &tlsConfig, err\n\t\t}\n\t\ttlsConfig.Certificates = []tls.Certificate{cert}\n\t}\n\n\treturn &tlsConfig, nil\n}\n"
  },
  {
    "path": "transaction_manager.go",
    "content": "package sarama\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n)\n\n// ProducerTxnStatusFlag mark current transaction status.\ntype ProducerTxnStatusFlag int16\n\nconst (\n\t// ProducerTxnFlagUninitialized when txnmgr is created\n\tProducerTxnFlagUninitialized ProducerTxnStatusFlag = 1 << iota\n\t// ProducerTxnFlagInitializing when txnmgr is initializing\n\tProducerTxnFlagInitializing\n\t// ProducerTxnFlagReady when is ready to receive transaction\n\tProducerTxnFlagReady\n\t// ProducerTxnFlagInTransaction when transaction is started\n\tProducerTxnFlagInTransaction\n\t// ProducerTxnFlagEndTransaction when transaction will be committed\n\tProducerTxnFlagEndTransaction\n\t// ProducerTxnFlagInError when having abortable or fatal error\n\tProducerTxnFlagInError\n\t// ProducerTxnFlagCommittingTransaction when committing txn\n\tProducerTxnFlagCommittingTransaction\n\t// ProducerTxnFlagAbortingTransaction when committing txn\n\tProducerTxnFlagAbortingTransaction\n\t// ProducerTxnFlagAbortableError when producer encounter an abortable error\n\t// Must call AbortTxn in this case.\n\tProducerTxnFlagAbortableError\n\t// ProducerTxnFlagFatalError when producer encounter an fatal error\n\t// Must Close an recreate it.\n\tProducerTxnFlagFatalError\n)\n\nfunc (s ProducerTxnStatusFlag) String() string {\n\tstatus := make([]string, 0)\n\tif s&ProducerTxnFlagUninitialized != 0 {\n\t\tstatus = append(status, \"ProducerTxnStateUninitialized\")\n\t}\n\tif s&ProducerTxnFlagInitializing != 0 {\n\t\tstatus = append(status, \"ProducerTxnStateInitializing\")\n\t}\n\tif s&ProducerTxnFlagReady != 0 {\n\t\tstatus = append(status, \"ProducerTxnStateReady\")\n\t}\n\tif s&ProducerTxnFlagInTransaction != 0 {\n\t\tstatus = append(status, \"ProducerTxnStateInTransaction\")\n\t}\n\tif s&ProducerTxnFlagEndTransaction != 0 {\n\t\tstatus = append(status, \"ProducerTxnStateEndTransaction\")\n\t}\n\tif s&ProducerTxnFlagInError != 0 {\n\t\tstatus = append(status, \"ProducerTxnStateInError\")\n\t}\n\tif s&ProducerTxnFlagCommittingTransaction != 0 {\n\t\tstatus = append(status, \"ProducerTxnStateCommittingTransaction\")\n\t}\n\tif s&ProducerTxnFlagAbortingTransaction != 0 {\n\t\tstatus = append(status, \"ProducerTxnStateAbortingTransaction\")\n\t}\n\tif s&ProducerTxnFlagAbortableError != 0 {\n\t\tstatus = append(status, \"ProducerTxnStateAbortableError\")\n\t}\n\tif s&ProducerTxnFlagFatalError != 0 {\n\t\tstatus = append(status, \"ProducerTxnStateFatalError\")\n\t}\n\treturn strings.Join(status, \"|\")\n}\n\n// transactionManager keeps the state necessary to ensure idempotent production\ntype transactionManager struct {\n\tproducerID         int64\n\tproducerEpoch      int16\n\tsequenceNumbers    map[string]int32\n\tmutex              sync.Mutex\n\ttransactionalID    string\n\ttransactionTimeout time.Duration\n\tclient             Client\n\n\t// when kafka cluster is at least 2.5.0.\n\t// used to recover when producer failed.\n\tcoordinatorSupportsBumpingEpoch bool\n\n\t// When producer need to bump it's epoch.\n\tepochBumpRequired bool\n\t// Record last seen error.\n\tlastError error\n\n\t// Ensure that status is never accessed with a race-condition.\n\tstatusLock sync.RWMutex\n\tstatus     ProducerTxnStatusFlag\n\n\t// Ensure that only one goroutine will update partitions in current transaction.\n\tpartitionInTxnLock            sync.Mutex\n\tpendingPartitionsInCurrentTxn topicPartitionSet\n\tpartitionsInCurrentTxn        topicPartitionSet\n\n\t// Offsets to add to transaction.\n\toffsetsInCurrentTxn map[string]topicPartitionOffsets\n}\n\nconst (\n\tnoProducerID    = -1\n\tnoProducerEpoch = -1\n\n\t// see publishTxnPartitions comment.\n\taddPartitionsRetryBackoff = 20 * time.Millisecond\n)\n\n// txnmngr allowed transitions.\nvar producerTxnTransitions = map[ProducerTxnStatusFlag][]ProducerTxnStatusFlag{\n\tProducerTxnFlagUninitialized: {\n\t\tProducerTxnFlagReady,\n\t\tProducerTxnFlagInError,\n\t},\n\t// When we need are initializing\n\tProducerTxnFlagInitializing: {\n\t\tProducerTxnFlagInitializing,\n\t\tProducerTxnFlagReady,\n\t\tProducerTxnFlagInError,\n\t},\n\t// When we have initialized transactional producer\n\tProducerTxnFlagReady: {\n\t\tProducerTxnFlagInTransaction,\n\t},\n\t// When beginTxn has been called\n\tProducerTxnFlagInTransaction: {\n\t\t// When calling commit or abort\n\t\tProducerTxnFlagEndTransaction,\n\t\t// When got an error\n\t\tProducerTxnFlagInError,\n\t},\n\tProducerTxnFlagEndTransaction: {\n\t\t// When epoch bump\n\t\tProducerTxnFlagInitializing,\n\t\t// When commit is good\n\t\tProducerTxnFlagReady,\n\t\t// When got an error\n\t\tProducerTxnFlagInError,\n\t},\n\t// Need to abort transaction\n\tProducerTxnFlagAbortableError: {\n\t\t// Call AbortTxn\n\t\tProducerTxnFlagAbortingTransaction,\n\t\t// When got an error\n\t\tProducerTxnFlagInError,\n\t},\n\t// Need to close producer\n\tProducerTxnFlagFatalError: {\n\t\tProducerTxnFlagFatalError,\n\t},\n}\n\ntype topicPartition struct {\n\ttopic     string\n\tpartition int32\n}\n\n// to ensure that we don't do a full scan every time a partition or an offset is added.\ntype (\n\ttopicPartitionSet     map[topicPartition]struct{}\n\ttopicPartitionOffsets map[topicPartition]*PartitionOffsetMetadata\n)\n\nfunc (s topicPartitionSet) mapToRequest() map[string][]int32 {\n\tresult := make(map[string][]int32, len(s))\n\tfor tp := range s {\n\t\tresult[tp.topic] = append(result[tp.topic], tp.partition)\n\t}\n\treturn result\n}\n\nfunc (s topicPartitionOffsets) mapToRequest() map[string][]*PartitionOffsetMetadata {\n\tresult := make(map[string][]*PartitionOffsetMetadata, len(s))\n\tfor tp, offset := range s {\n\t\tresult[tp.topic] = append(result[tp.topic], offset)\n\t}\n\treturn result\n}\n\n// Return true if current transition is allowed.\nfunc (t *transactionManager) isTransitionValid(target ProducerTxnStatusFlag) bool {\n\tfor status, allowedTransitions := range producerTxnTransitions {\n\t\tif status&t.status != 0 {\n\t\t\tfor _, allowedTransition := range allowedTransitions {\n\t\t\t\tif allowedTransition&target != 0 {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\n// Get current transaction status.\nfunc (t *transactionManager) currentTxnStatus() ProducerTxnStatusFlag {\n\tt.statusLock.RLock()\n\tdefer t.statusLock.RUnlock()\n\n\treturn t.status\n}\n\n// Try to transition to a valid status and return an error otherwise.\nfunc (t *transactionManager) transitionTo(target ProducerTxnStatusFlag, err error) error {\n\tt.statusLock.Lock()\n\tdefer t.statusLock.Unlock()\n\n\tif !t.isTransitionValid(target) {\n\t\treturn ErrTransitionNotAllowed\n\t}\n\n\tif target&ProducerTxnFlagInError != 0 {\n\t\tif err == nil {\n\t\t\treturn ErrCannotTransitionNilError\n\t\t}\n\t\tt.lastError = err\n\t} else {\n\t\tt.lastError = nil\n\t}\n\n\tDebugLogger.Printf(\"txnmgr/transition [%s] transition from %s to %s\\n\", t.transactionalID, t.status, target)\n\n\tt.status = target\n\treturn err\n}\n\nfunc (t *transactionManager) getAndIncrementSequenceNumber(topic string, partition int32) (int32, int16) {\n\tkey := fmt.Sprintf(\"%s-%d\", topic, partition)\n\tt.mutex.Lock()\n\tdefer t.mutex.Unlock()\n\tsequence := t.sequenceNumbers[key]\n\tt.sequenceNumbers[key] = sequence + 1\n\treturn sequence, t.producerEpoch\n}\n\nfunc (t *transactionManager) bumpEpoch() {\n\tt.mutex.Lock()\n\tdefer t.mutex.Unlock()\n\tt.producerEpoch++\n\tfor k := range t.sequenceNumbers {\n\t\tt.sequenceNumbers[k] = 0\n\t}\n}\n\nfunc (t *transactionManager) getProducerID() (int64, int16) {\n\tt.mutex.Lock()\n\tdefer t.mutex.Unlock()\n\treturn t.producerID, t.producerEpoch\n}\n\n// Compute retry backoff considered current attempts.\nfunc (t *transactionManager) computeBackoff(attemptsRemaining int) time.Duration {\n\tif t.client.Config().Producer.Transaction.Retry.BackoffFunc != nil {\n\t\tmaxRetries := t.client.Config().Producer.Transaction.Retry.Max\n\t\tretries := maxRetries - attemptsRemaining\n\t\treturn t.client.Config().Producer.Transaction.Retry.BackoffFunc(retries, maxRetries)\n\t}\n\treturn t.client.Config().Producer.Transaction.Retry.Backoff\n}\n\n// return true is txnmngr is transactinal.\nfunc (t *transactionManager) isTransactional() bool {\n\treturn t.transactionalID != \"\"\n}\n\n// add specified offsets to current transaction.\nfunc (t *transactionManager) addOffsetsToTxn(offsetsToAdd map[string][]*PartitionOffsetMetadata, groupId string) error {\n\tt.mutex.Lock()\n\tdefer t.mutex.Unlock()\n\n\tif t.currentTxnStatus()&ProducerTxnFlagInTransaction == 0 {\n\t\treturn ErrTransactionNotReady\n\t}\n\n\tif t.currentTxnStatus()&ProducerTxnFlagFatalError != 0 {\n\t\treturn t.lastError\n\t}\n\n\tif _, ok := t.offsetsInCurrentTxn[groupId]; !ok {\n\t\tt.offsetsInCurrentTxn[groupId] = topicPartitionOffsets{}\n\t}\n\n\tfor topic, offsets := range offsetsToAdd {\n\t\tfor _, offset := range offsets {\n\t\t\ttp := topicPartition{topic: topic, partition: offset.Partition}\n\t\t\tt.offsetsInCurrentTxn[groupId][tp] = offset\n\t\t}\n\t}\n\treturn nil\n}\n\n// send txnmgnr save offsets to transaction coordinator.\nfunc (t *transactionManager) publishOffsetsToTxn(offsets topicPartitionOffsets, groupId string) (topicPartitionOffsets, error) {\n\t// First AddOffsetsToTxn\n\tattemptsRemaining := t.client.Config().Producer.Transaction.Retry.Max\n\texec := func(run func() (bool, error), err error) error {\n\t\tfor attemptsRemaining >= 0 {\n\t\t\tvar retry bool\n\t\t\tretry, err = run()\n\t\t\tif !retry {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tbackoff := t.computeBackoff(attemptsRemaining)\n\t\t\tLogger.Printf(\"txnmgr/add-offset-to-txn [%s] retrying after %dms... (%d attempts remaining) (%s)\\n\",\n\t\t\t\tt.transactionalID, backoff/time.Millisecond, attemptsRemaining, err)\n\t\t\ttime.Sleep(backoff)\n\t\t\tattemptsRemaining--\n\t\t}\n\t\treturn err\n\t}\n\tlastError := exec(func() (bool, error) {\n\t\tcoordinator, err := t.client.TransactionCoordinator(t.transactionalID)\n\t\tif err != nil {\n\t\t\treturn true, err\n\t\t}\n\t\trequest := &AddOffsetsToTxnRequest{\n\t\t\tTransactionalID: t.transactionalID,\n\t\t\tProducerEpoch:   t.producerEpoch,\n\t\t\tProducerID:      t.producerID,\n\t\t\tGroupID:         groupId,\n\t\t}\n\t\tif t.client.Config().Version.IsAtLeast(V2_7_0_0) {\n\t\t\t// Version 2 adds the support for new error code PRODUCER_FENCED.\n\t\t\trequest.Version = 2\n\t\t} else if t.client.Config().Version.IsAtLeast(V2_0_0_0) {\n\t\t\t// Version 1 is the same as version 0.\n\t\t\trequest.Version = 1\n\t\t}\n\t\tresponse, err := coordinator.AddOffsetsToTxn(request)\n\t\tif err != nil {\n\t\t\t// If an error occurred try to refresh current transaction coordinator.\n\t\t\t_ = coordinator.Close()\n\t\t\t_ = t.client.RefreshTransactionCoordinator(t.transactionalID)\n\t\t\treturn true, err\n\t\t}\n\t\tif response == nil {\n\t\t\t// If no response is returned just retry.\n\t\t\treturn true, ErrTxnUnableToParseResponse\n\t\t}\n\t\tif response.Err == ErrNoError {\n\t\t\tDebugLogger.Printf(\"txnmgr/add-offset-to-txn [%s] successful add-offset-to-txn with group %s %+v\\n\",\n\t\t\t\tt.transactionalID, groupId, response)\n\t\t\t// If no error, just exit.\n\t\t\treturn false, nil\n\t\t}\n\t\tswitch response.Err {\n\t\tcase ErrConsumerCoordinatorNotAvailable:\n\t\t\tfallthrough\n\t\tcase ErrNotCoordinatorForConsumer:\n\t\t\t_ = coordinator.Close()\n\t\t\t_ = t.client.RefreshTransactionCoordinator(t.transactionalID)\n\t\t\tfallthrough\n\t\tcase ErrOffsetsLoadInProgress:\n\t\t\tfallthrough\n\t\tcase ErrConcurrentTransactions:\n\t\t\t// Retry\n\t\tcase ErrUnknownProducerID:\n\t\t\tfallthrough\n\t\tcase ErrInvalidProducerIDMapping:\n\t\t\treturn false, t.abortableErrorIfPossible(response.Err)\n\t\tcase ErrGroupAuthorizationFailed:\n\t\t\treturn false, t.transitionTo(ProducerTxnFlagInError|ProducerTxnFlagAbortableError, response.Err)\n\t\tdefault:\n\t\t\t// Others are fatal\n\t\t\treturn false, t.transitionTo(ProducerTxnFlagInError|ProducerTxnFlagFatalError, response.Err)\n\t\t}\n\t\treturn true, response.Err\n\t}, nil)\n\n\tif lastError != nil {\n\t\treturn offsets, lastError\n\t}\n\n\tresultOffsets := offsets\n\t// Then TxnOffsetCommit\n\t// note the result is not completed until the TxnOffsetCommit returns\n\tattemptsRemaining = t.client.Config().Producer.Transaction.Retry.Max\n\texecTxnOffsetCommit := func(run func() (topicPartitionOffsets, bool, error), err error) (topicPartitionOffsets, error) {\n\t\tvar r topicPartitionOffsets\n\t\tfor attemptsRemaining >= 0 {\n\t\t\tvar retry bool\n\t\t\tr, retry, err = run()\n\t\t\tif !retry {\n\t\t\t\treturn r, err\n\t\t\t}\n\t\t\tbackoff := t.computeBackoff(attemptsRemaining)\n\t\t\tLogger.Printf(\"txnmgr/txn-offset-commit [%s] retrying after %dms... (%d attempts remaining) (%s)\\n\",\n\t\t\t\tt.transactionalID, backoff/time.Millisecond, attemptsRemaining, err)\n\t\t\ttime.Sleep(backoff)\n\t\t\tattemptsRemaining--\n\t\t}\n\t\treturn r, err\n\t}\n\treturn execTxnOffsetCommit(func() (topicPartitionOffsets, bool, error) {\n\t\tconsumerGroupCoordinator, err := t.client.Coordinator(groupId)\n\t\tif err != nil {\n\t\t\treturn resultOffsets, true, err\n\t\t}\n\t\trequest := &TxnOffsetCommitRequest{\n\t\t\tTransactionalID: t.transactionalID,\n\t\t\tProducerEpoch:   t.producerEpoch,\n\t\t\tProducerID:      t.producerID,\n\t\t\tGroupID:         groupId,\n\t\t\tTopics:          offsets.mapToRequest(),\n\t\t}\n\t\tif t.client.Config().Version.IsAtLeast(V2_1_0_0) {\n\t\t\t// Version 2 adds the committed leader epoch.\n\t\t\trequest.Version = 2\n\t\t} else if t.client.Config().Version.IsAtLeast(V2_0_0_0) {\n\t\t\t// Version 1 is the same as version 0.\n\t\t\trequest.Version = 1\n\t\t}\n\t\tresponses, err := consumerGroupCoordinator.TxnOffsetCommit(request)\n\t\tif err != nil {\n\t\t\t_ = consumerGroupCoordinator.Close()\n\t\t\t_ = t.client.RefreshCoordinator(groupId)\n\t\t\treturn resultOffsets, true, err\n\t\t}\n\n\t\tif responses == nil {\n\t\t\treturn resultOffsets, true, ErrTxnUnableToParseResponse\n\t\t}\n\n\t\tvar responseErrors []error\n\t\tfailedTxn := topicPartitionOffsets{}\n\t\tfor topic, partitionErrors := range responses.Topics {\n\t\t\tfor _, partitionError := range partitionErrors {\n\t\t\t\tswitch partitionError.Err {\n\t\t\t\tcase ErrNoError:\n\t\t\t\t\tcontinue\n\t\t\t\t// If the topic is unknown or the coordinator is loading, retry with the current coordinator\n\t\t\t\tcase ErrRequestTimedOut:\n\t\t\t\t\tfallthrough\n\t\t\t\tcase ErrConsumerCoordinatorNotAvailable:\n\t\t\t\t\tfallthrough\n\t\t\t\tcase ErrNotCoordinatorForConsumer:\n\t\t\t\t\t_ = consumerGroupCoordinator.Close()\n\t\t\t\t\t_ = t.client.RefreshCoordinator(groupId)\n\t\t\t\t\tfallthrough\n\t\t\t\tcase ErrUnknownTopicOrPartition:\n\t\t\t\t\tfallthrough\n\t\t\t\tcase ErrOffsetsLoadInProgress:\n\t\t\t\t\t// Do nothing just retry\n\t\t\t\tcase ErrIllegalGeneration:\n\t\t\t\t\tfallthrough\n\t\t\t\tcase ErrUnknownMemberId:\n\t\t\t\t\tfallthrough\n\t\t\t\tcase ErrFencedInstancedId:\n\t\t\t\t\tfallthrough\n\t\t\t\tcase ErrGroupAuthorizationFailed:\n\t\t\t\t\treturn resultOffsets, false, t.transitionTo(ProducerTxnFlagInError|ProducerTxnFlagAbortableError, partitionError.Err)\n\t\t\t\tdefault:\n\t\t\t\t\t// Others are fatal\n\t\t\t\t\treturn resultOffsets, false, t.transitionTo(ProducerTxnFlagInError|ProducerTxnFlagFatalError, partitionError.Err)\n\t\t\t\t}\n\t\t\t\ttp := topicPartition{topic: topic, partition: partitionError.Partition}\n\t\t\t\tfailedTxn[tp] = offsets[tp]\n\t\t\t\tresponseErrors = append(responseErrors, partitionError.Err)\n\t\t\t}\n\t\t}\n\n\t\tresultOffsets = failedTxn\n\n\t\tif len(resultOffsets) == 0 {\n\t\t\tDebugLogger.Printf(\"txnmgr/txn-offset-commit [%s] successful txn-offset-commit with group %s\\n\",\n\t\t\t\tt.transactionalID, groupId)\n\t\t\treturn resultOffsets, false, nil\n\t\t}\n\t\treturn resultOffsets, true, Wrap(ErrTxnOffsetCommit, responseErrors...)\n\t}, nil)\n}\n\nfunc (t *transactionManager) initProducerId() (int64, int16, error) {\n\tisEpochBump := false\n\n\treq := &InitProducerIDRequest{}\n\tif t.isTransactional() {\n\t\treq.TransactionalID = &t.transactionalID\n\t\treq.TransactionTimeout = t.transactionTimeout\n\t}\n\n\tif t.client.Config().Version.IsAtLeast(V2_5_0_0) {\n\t\tif t.client.Config().Version.IsAtLeast(V2_7_0_0) {\n\t\t\t// Version 4 adds the support for new error code PRODUCER_FENCED.\n\t\t\treq.Version = 4\n\t\t} else {\n\t\t\t// Version 3 adds ProducerId and ProducerEpoch, allowing producers to try\n\t\t\t// to resume after an INVALID_PRODUCER_EPOCH error\n\t\t\treq.Version = 3\n\t\t}\n\t\tisEpochBump = t.producerID != noProducerID && t.producerEpoch != noProducerEpoch\n\t\tt.coordinatorSupportsBumpingEpoch = true\n\t\treq.ProducerID = t.producerID\n\t\treq.ProducerEpoch = t.producerEpoch\n\t} else if t.client.Config().Version.IsAtLeast(V2_4_0_0) {\n\t\t// Version 2 is the first flexible version.\n\t\treq.Version = 2\n\t} else if t.client.Config().Version.IsAtLeast(V2_0_0_0) {\n\t\t// Version 1 is the same as version 0.\n\t\treq.Version = 1\n\t}\n\n\tif isEpochBump {\n\t\terr := t.transitionTo(ProducerTxnFlagInitializing, nil)\n\t\tif err != nil {\n\t\t\treturn -1, -1, err\n\t\t}\n\t\tDebugLogger.Printf(\"txnmgr/init-producer-id [%s] invoking InitProducerId for the first time in order to acquire a producer ID\\n\",\n\t\t\tt.transactionalID)\n\t} else {\n\t\tDebugLogger.Printf(\"txnmgr/init-producer-id [%s] invoking InitProducerId with current producer ID %d and epoch %d in order to bump the epoch\\n\",\n\t\t\tt.transactionalID, t.producerID, t.producerEpoch)\n\t}\n\n\tattemptsRemaining := t.client.Config().Producer.Transaction.Retry.Max\n\texec := func(run func() (int64, int16, bool, error), err error) (int64, int16, error) {\n\t\tpid := int64(-1)\n\t\tpepoch := int16(-1)\n\t\tfor attemptsRemaining >= 0 {\n\t\t\tvar retry bool\n\t\t\tpid, pepoch, retry, err = run()\n\t\t\tif !retry {\n\t\t\t\treturn pid, pepoch, err\n\t\t\t}\n\t\t\tbackoff := t.computeBackoff(attemptsRemaining)\n\t\t\tLogger.Printf(\"txnmgr/init-producer-id [%s] retrying after %dms... (%d attempts remaining) (%s)\\n\",\n\t\t\t\tt.transactionalID, backoff/time.Millisecond, attemptsRemaining, err)\n\t\t\ttime.Sleep(backoff)\n\t\t\tattemptsRemaining--\n\t\t}\n\t\treturn -1, -1, err\n\t}\n\treturn exec(func() (int64, int16, bool, error) {\n\t\tvar err error\n\t\tvar coordinator *Broker\n\t\tif t.isTransactional() {\n\t\t\tcoordinator, err = t.client.TransactionCoordinator(t.transactionalID)\n\t\t} else {\n\t\t\tcoordinator = t.client.LeastLoadedBroker()\n\t\t}\n\t\tif err != nil {\n\t\t\treturn -1, -1, true, err\n\t\t}\n\t\tresponse, err := coordinator.InitProducerID(req)\n\t\tif err != nil {\n\t\t\tif t.isTransactional() {\n\t\t\t\t_ = coordinator.Close()\n\t\t\t\t_ = t.client.RefreshTransactionCoordinator(t.transactionalID)\n\t\t\t}\n\t\t\treturn -1, -1, true, err\n\t\t}\n\t\tif response == nil {\n\t\t\treturn -1, -1, true, ErrTxnUnableToParseResponse\n\t\t}\n\t\tif response.Err == ErrNoError {\n\t\t\tif isEpochBump {\n\t\t\t\tt.sequenceNumbers = make(map[string]int32)\n\t\t\t}\n\t\t\terr := t.transitionTo(ProducerTxnFlagReady, nil)\n\t\t\tif err != nil {\n\t\t\t\treturn -1, -1, true, err\n\t\t\t}\n\t\t\tDebugLogger.Printf(\"txnmgr/init-producer-id [%s] successful init producer id %+v\\n\",\n\t\t\t\tt.transactionalID, response)\n\t\t\treturn response.ProducerID, response.ProducerEpoch, false, nil\n\t\t}\n\t\tswitch response.Err {\n\t\t// Retriable errors\n\t\tcase ErrConsumerCoordinatorNotAvailable, ErrNotCoordinatorForConsumer, ErrOffsetsLoadInProgress:\n\t\t\tif t.isTransactional() {\n\t\t\t\t_ = coordinator.Close()\n\t\t\t\t_ = t.client.RefreshTransactionCoordinator(t.transactionalID)\n\t\t\t}\n\t\t// Fatal errors\n\t\tdefault:\n\t\t\treturn -1, -1, false, t.transitionTo(ProducerTxnFlagInError|ProducerTxnFlagFatalError, response.Err)\n\t\t}\n\t\treturn -1, -1, true, response.Err\n\t}, nil)\n}\n\n// if kafka cluster is at least 2.5.0 mark txnmngr to bump epoch else mark it as fatal.\nfunc (t *transactionManager) abortableErrorIfPossible(err error) error {\n\tif t.coordinatorSupportsBumpingEpoch {\n\t\tt.epochBumpRequired = true\n\t\treturn t.transitionTo(ProducerTxnFlagInError|ProducerTxnFlagAbortableError, err)\n\t}\n\treturn t.transitionTo(ProducerTxnFlagInError|ProducerTxnFlagFatalError, err)\n}\n\n// End current transaction.\nfunc (t *transactionManager) completeTransaction() error {\n\tif t.epochBumpRequired {\n\t\terr := t.transitionTo(ProducerTxnFlagInitializing, nil)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\terr := t.transitionTo(ProducerTxnFlagReady, nil)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tt.lastError = nil\n\tt.epochBumpRequired = false\n\tt.partitionsInCurrentTxn = topicPartitionSet{}\n\tt.pendingPartitionsInCurrentTxn = topicPartitionSet{}\n\tt.offsetsInCurrentTxn = map[string]topicPartitionOffsets{}\n\n\treturn nil\n}\n\n// send EndTxn request with commit flag. (true when committing false otherwise)\nfunc (t *transactionManager) endTxn(commit bool) error {\n\tattemptsRemaining := t.client.Config().Producer.Transaction.Retry.Max\n\texec := func(run func() (bool, error), err error) error {\n\t\tfor attemptsRemaining >= 0 {\n\t\t\tvar retry bool\n\t\t\tretry, err = run()\n\t\t\tif !retry {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tbackoff := t.computeBackoff(attemptsRemaining)\n\t\t\tLogger.Printf(\"txnmgr/endtxn [%s] retrying after %dms... (%d attempts remaining) (%s)\\n\",\n\t\t\t\tt.transactionalID, backoff/time.Millisecond, attemptsRemaining, err)\n\t\t\ttime.Sleep(backoff)\n\t\t\tattemptsRemaining--\n\t\t}\n\t\treturn err\n\t}\n\treturn exec(func() (bool, error) {\n\t\tcoordinator, err := t.client.TransactionCoordinator(t.transactionalID)\n\t\tif err != nil {\n\t\t\treturn true, err\n\t\t}\n\t\trequest := &EndTxnRequest{\n\t\t\tTransactionalID:   t.transactionalID,\n\t\t\tProducerEpoch:     t.producerEpoch,\n\t\t\tProducerID:        t.producerID,\n\t\t\tTransactionResult: commit,\n\t\t}\n\t\tif t.client.Config().Version.IsAtLeast(V2_7_0_0) {\n\t\t\t// Version 2 adds the support for new error code PRODUCER_FENCED.\n\t\t\trequest.Version = 2\n\t\t} else if t.client.Config().Version.IsAtLeast(V2_0_0_0) {\n\t\t\t// Version 1 is the same as version 0.\n\t\t\trequest.Version = 1\n\t\t}\n\t\tresponse, err := coordinator.EndTxn(request)\n\t\tif err != nil {\n\t\t\t// Always retry on network error\n\t\t\t_ = coordinator.Close()\n\t\t\t_ = t.client.RefreshTransactionCoordinator(t.transactionalID)\n\t\t\treturn true, err\n\t\t}\n\t\tif response == nil {\n\t\t\treturn true, ErrTxnUnableToParseResponse\n\t\t}\n\t\tif response.Err == ErrNoError {\n\t\t\tDebugLogger.Printf(\"txnmgr/endtxn [%s] successful to end txn %+v\\n\",\n\t\t\t\tt.transactionalID, response)\n\t\t\treturn false, t.completeTransaction()\n\t\t}\n\t\tswitch response.Err {\n\t\t// Need to refresh coordinator\n\t\tcase ErrConsumerCoordinatorNotAvailable:\n\t\t\tfallthrough\n\t\tcase ErrNotCoordinatorForConsumer:\n\t\t\t_ = coordinator.Close()\n\t\t\t_ = t.client.RefreshTransactionCoordinator(t.transactionalID)\n\t\t\tfallthrough\n\t\tcase ErrOffsetsLoadInProgress:\n\t\t\tfallthrough\n\t\tcase ErrConcurrentTransactions:\n\t\t\t// Just retry\n\t\tcase ErrUnknownProducerID:\n\t\t\tfallthrough\n\t\tcase ErrInvalidProducerIDMapping:\n\t\t\treturn false, t.abortableErrorIfPossible(response.Err)\n\t\t// Fatal errors\n\t\tdefault:\n\t\t\treturn false, t.transitionTo(ProducerTxnFlagInError|ProducerTxnFlagFatalError, response.Err)\n\t\t}\n\t\treturn true, response.Err\n\t}, nil)\n}\n\n// We will try to publish associated offsets for each groups\n// then send endtxn request to mark transaction as finished.\nfunc (t *transactionManager) finishTransaction(commit bool) error {\n\tt.mutex.Lock()\n\tdefer t.mutex.Unlock()\n\n\t// Ensure no error when committing or aborting\n\tif commit && t.currentTxnStatus()&ProducerTxnFlagInError != 0 {\n\t\treturn t.lastError\n\t} else if !commit && t.currentTxnStatus()&ProducerTxnFlagFatalError != 0 {\n\t\treturn t.lastError\n\t}\n\n\t// if no records has been sent don't do anything.\n\tif len(t.partitionsInCurrentTxn) == 0 {\n\t\treturn t.completeTransaction()\n\t}\n\n\tepochBump := t.epochBumpRequired\n\t// If we're aborting the transaction, so there should be no need to add offsets.\n\tif commit && len(t.offsetsInCurrentTxn) > 0 {\n\t\tfor group, offsets := range t.offsetsInCurrentTxn {\n\t\t\tnewOffsets, err := t.publishOffsetsToTxn(offsets, group)\n\t\t\tif err != nil {\n\t\t\t\tt.offsetsInCurrentTxn[group] = newOffsets\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdelete(t.offsetsInCurrentTxn, group)\n\t\t}\n\t}\n\n\tif t.currentTxnStatus()&ProducerTxnFlagFatalError != 0 {\n\t\treturn t.lastError\n\t}\n\n\tif !errors.Is(t.lastError, ErrInvalidProducerIDMapping) {\n\t\terr := t.endTxn(commit)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !epochBump {\n\t\t\treturn nil\n\t\t}\n\t}\n\t// reset pid and epoch if needed.\n\treturn t.initializeTransactions()\n}\n\n// called before sending any transactional record\n// won't do anything if current topic-partition is already added to transaction.\nfunc (t *transactionManager) maybeAddPartitionToCurrentTxn(topic string, partition int32) {\n\tif t.currentTxnStatus()&ProducerTxnFlagInError != 0 {\n\t\treturn\n\t}\n\n\ttp := topicPartition{topic: topic, partition: partition}\n\n\tt.partitionInTxnLock.Lock()\n\tdefer t.partitionInTxnLock.Unlock()\n\tif _, ok := t.partitionsInCurrentTxn[tp]; ok {\n\t\t// partition is already added\n\t\treturn\n\t}\n\n\tt.pendingPartitionsInCurrentTxn[tp] = struct{}{}\n}\n\n// Makes a request to kafka to add a list of partitions ot the current transaction.\nfunc (t *transactionManager) publishTxnPartitions() error {\n\tt.partitionInTxnLock.Lock()\n\tdefer t.partitionInTxnLock.Unlock()\n\n\tif t.currentTxnStatus()&ProducerTxnFlagInError != 0 {\n\t\treturn t.lastError\n\t}\n\n\tif len(t.pendingPartitionsInCurrentTxn) == 0 {\n\t\treturn nil\n\t}\n\n\t// Remove the partitions from the pending set regardless of the result. We use the presence\n\t// of partitions in the pending set to know when it is not safe to send batches. However, if\n\t// the partitions failed to be added and we enter an error state, we expect the batches to be\n\t// aborted anyway. In this case, we must be able to continue sending the batches which are in\n\t// retry for partitions that were successfully added.\n\tremoveAllPartitionsOnFatalOrAbortedError := func() {\n\t\tt.pendingPartitionsInCurrentTxn = topicPartitionSet{}\n\t}\n\n\t// We only want to reduce the backoff when retrying the first AddPartition which errored out due to a\n\t// CONCURRENT_TRANSACTIONS error since this means that the previous transaction is still completing and\n\t// we don't want to wait too long before trying to start the new one.\n\t//\n\t// This is only a temporary fix, the long term solution is being tracked in\n\t// https://issues.apache.org/jira/browse/KAFKA-5482\n\tretryBackoff := t.client.Config().Producer.Transaction.Retry.Backoff\n\tcomputeBackoff := func(attemptsRemaining int) time.Duration {\n\t\tif t.client.Config().Producer.Transaction.Retry.BackoffFunc != nil {\n\t\t\tmaxRetries := t.client.Config().Producer.Transaction.Retry.Max\n\t\t\tretries := maxRetries - attemptsRemaining\n\t\t\treturn t.client.Config().Producer.Transaction.Retry.BackoffFunc(retries, maxRetries)\n\t\t}\n\t\treturn retryBackoff\n\t}\n\tattemptsRemaining := t.client.Config().Producer.Transaction.Retry.Max\n\n\texec := func(run func() (bool, error), err error) error {\n\t\tfor attemptsRemaining >= 0 {\n\t\t\tvar retry bool\n\t\t\tretry, err = run()\n\t\t\tif !retry {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tbackoff := computeBackoff(attemptsRemaining)\n\t\t\tLogger.Printf(\"txnmgr/add-partition-to-txn retrying after %dms... (%d attempts remaining) (%s)\\n\", backoff/time.Millisecond, attemptsRemaining, err)\n\t\t\ttime.Sleep(backoff)\n\t\t\tattemptsRemaining--\n\t\t}\n\t\treturn err\n\t}\n\treturn exec(func() (bool, error) {\n\t\tcoordinator, err := t.client.TransactionCoordinator(t.transactionalID)\n\t\tif err != nil {\n\t\t\treturn true, err\n\t\t}\n\t\trequest := &AddPartitionsToTxnRequest{\n\t\t\tTransactionalID: t.transactionalID,\n\t\t\tProducerID:      t.producerID,\n\t\t\tProducerEpoch:   t.producerEpoch,\n\t\t\tTopicPartitions: t.pendingPartitionsInCurrentTxn.mapToRequest(),\n\t\t}\n\t\tif t.client.Config().Version.IsAtLeast(V2_7_0_0) {\n\t\t\t// Version 2 adds the support for new error code PRODUCER_FENCED.\n\t\t\trequest.Version = 2\n\t\t} else if t.client.Config().Version.IsAtLeast(V2_0_0_0) {\n\t\t\t// Version 1 is the same as version 0.\n\t\t\trequest.Version = 1\n\t\t}\n\t\taddPartResponse, err := coordinator.AddPartitionsToTxn(request)\n\t\tif err != nil {\n\t\t\t_ = coordinator.Close()\n\t\t\t_ = t.client.RefreshTransactionCoordinator(t.transactionalID)\n\t\t\treturn true, err\n\t\t}\n\n\t\tif addPartResponse == nil {\n\t\t\treturn true, ErrTxnUnableToParseResponse\n\t\t}\n\n\t\t// remove from the list partitions that have been successfully updated\n\t\tvar responseErrors []error\n\t\tfor topic, results := range addPartResponse.Errors {\n\t\t\tfor _, response := range results {\n\t\t\t\ttp := topicPartition{topic: topic, partition: response.Partition}\n\t\t\t\tswitch response.Err {\n\t\t\t\tcase ErrNoError:\n\t\t\t\t\t// Mark partition as added to transaction\n\t\t\t\t\tt.partitionsInCurrentTxn[tp] = struct{}{}\n\t\t\t\t\tdelete(t.pendingPartitionsInCurrentTxn, tp)\n\t\t\t\t\tcontinue\n\t\t\t\tcase ErrConsumerCoordinatorNotAvailable:\n\t\t\t\t\tfallthrough\n\t\t\t\tcase ErrNotCoordinatorForConsumer:\n\t\t\t\t\t_ = coordinator.Close()\n\t\t\t\t\t_ = t.client.RefreshTransactionCoordinator(t.transactionalID)\n\t\t\t\t\tfallthrough\n\t\t\t\tcase ErrUnknownTopicOrPartition:\n\t\t\t\t\tfallthrough\n\t\t\t\tcase ErrOffsetsLoadInProgress:\n\t\t\t\t\t// Retry topicPartition\n\t\t\t\tcase ErrConcurrentTransactions:\n\t\t\t\t\tif len(t.partitionsInCurrentTxn) == 0 && retryBackoff > addPartitionsRetryBackoff {\n\t\t\t\t\t\tretryBackoff = addPartitionsRetryBackoff\n\t\t\t\t\t}\n\t\t\t\tcase ErrOperationNotAttempted:\n\t\t\t\t\tfallthrough\n\t\t\t\tcase ErrTopicAuthorizationFailed:\n\t\t\t\t\tremoveAllPartitionsOnFatalOrAbortedError()\n\t\t\t\t\treturn false, t.transitionTo(ProducerTxnFlagInError|ProducerTxnFlagAbortableError, response.Err)\n\t\t\t\tcase ErrUnknownProducerID:\n\t\t\t\t\tfallthrough\n\t\t\t\tcase ErrInvalidProducerIDMapping:\n\t\t\t\t\tremoveAllPartitionsOnFatalOrAbortedError()\n\t\t\t\t\treturn false, t.abortableErrorIfPossible(response.Err)\n\t\t\t\t// Fatal errors\n\t\t\t\tdefault:\n\t\t\t\t\tremoveAllPartitionsOnFatalOrAbortedError()\n\t\t\t\t\treturn false, t.transitionTo(ProducerTxnFlagInError|ProducerTxnFlagFatalError, response.Err)\n\t\t\t\t}\n\t\t\t\tresponseErrors = append(responseErrors, response.Err)\n\t\t\t}\n\t\t}\n\n\t\t// handle end\n\t\tif len(t.pendingPartitionsInCurrentTxn) == 0 {\n\t\t\tDebugLogger.Printf(\"txnmgr/add-partition-to-txn [%s] successful to add partitions txn %+v\\n\",\n\t\t\t\tt.transactionalID, addPartResponse)\n\t\t\treturn false, nil\n\t\t}\n\t\treturn true, Wrap(ErrAddPartitionsToTxn, responseErrors...)\n\t}, nil)\n}\n\n// Build a new transaction manager sharing producer client.\nfunc newTransactionManager(conf *Config, client Client) (*transactionManager, error) {\n\ttxnmgr := &transactionManager{\n\t\tproducerID:                    noProducerID,\n\t\tproducerEpoch:                 noProducerEpoch,\n\t\tclient:                        client,\n\t\tpendingPartitionsInCurrentTxn: topicPartitionSet{},\n\t\tpartitionsInCurrentTxn:        topicPartitionSet{},\n\t\toffsetsInCurrentTxn:           make(map[string]topicPartitionOffsets),\n\t\tstatus:                        ProducerTxnFlagUninitialized,\n\t}\n\n\tif conf.Producer.Idempotent {\n\t\ttxnmgr.transactionalID = conf.Producer.Transaction.ID\n\t\ttxnmgr.transactionTimeout = conf.Producer.Transaction.Timeout\n\t\ttxnmgr.sequenceNumbers = make(map[string]int32)\n\t\ttxnmgr.mutex = sync.Mutex{}\n\n\t\tvar err error\n\t\ttxnmgr.producerID, txnmgr.producerEpoch, err = txnmgr.initProducerId()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tLogger.Printf(\"txnmgr/init-producer-id [%s] obtained a ProducerId: %d and ProducerEpoch: %d\\n\",\n\t\t\ttxnmgr.transactionalID, txnmgr.producerID, txnmgr.producerEpoch)\n\t}\n\n\treturn txnmgr, nil\n}\n\n// re-init producer-id and producer-epoch if needed.\nfunc (t *transactionManager) initializeTransactions() (err error) {\n\tt.producerID, t.producerEpoch, err = t.initProducerId()\n\treturn\n}\n"
  },
  {
    "path": "transaction_manager_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestTransitions(t *testing.T) {\n\ttestError := errors.New(\"test\")\n\ttype testCase struct {\n\t\ttransitions   []ProducerTxnStatusFlag\n\t\texpectedError error\n\t}\n\ttestCases := []testCase{\n\t\t{\n\t\t\ttransitions: []ProducerTxnStatusFlag{\n\t\t\t\tProducerTxnFlagUninitialized,\n\t\t\t\tProducerTxnFlagReady,\n\t\t\t\tProducerTxnFlagInTransaction,\n\t\t\t\tProducerTxnFlagEndTransaction | ProducerTxnFlagCommittingTransaction,\n\t\t\t\tProducerTxnFlagReady,\n\t\t\t},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\ttransitions: []ProducerTxnStatusFlag{\n\t\t\t\tProducerTxnFlagUninitialized,\n\t\t\t\tProducerTxnFlagReady,\n\t\t\t\tProducerTxnFlagInTransaction,\n\t\t\t\tProducerTxnFlagEndTransaction | ProducerTxnFlagAbortingTransaction,\n\t\t\t\tProducerTxnFlagReady,\n\t\t\t},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\ttransitions: []ProducerTxnStatusFlag{\n\t\t\t\tProducerTxnFlagUninitialized,\n\t\t\t\tProducerTxnFlagReady,\n\t\t\t\tProducerTxnFlagInTransaction,\n\t\t\t\tProducerTxnFlagEndTransaction,\n\t\t\t\tProducerTxnFlagInError | ProducerTxnFlagAbortableError,\n\t\t\t},\n\t\t\texpectedError: testError,\n\t\t},\n\t\t{\n\t\t\ttransitions: []ProducerTxnStatusFlag{\n\t\t\t\tProducerTxnFlagInError | ProducerTxnFlagAbortableError,\n\t\t\t\tProducerTxnFlagEndTransaction | ProducerTxnFlagAbortingTransaction,\n\t\t\t\tProducerTxnFlagReady,\n\t\t\t},\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\ttransitions: []ProducerTxnStatusFlag{\n\t\t\t\tProducerTxnFlagInError | ProducerTxnFlagAbortableError,\n\t\t\t\tProducerTxnFlagEndTransaction | ProducerTxnFlagCommittingTransaction,\n\t\t\t},\n\t\t\texpectedError: ErrTransitionNotAllowed,\n\t\t},\n\t\t{\n\t\t\ttransitions: []ProducerTxnStatusFlag{\n\t\t\t\tProducerTxnFlagInError | ProducerTxnFlagFatalError,\n\t\t\t\tProducerTxnFlagEndTransaction | ProducerTxnFlagAbortingTransaction,\n\t\t\t},\n\t\t\texpectedError: ErrTransitionNotAllowed,\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\ttxnmgr := transactionManager{}\n\t\ttxnmgr.status = tc.transitions[0]\n\t\tvar lastError error\n\t\tfor i := 1; i < len(tc.transitions); i++ {\n\t\t\tvar baseErr error\n\t\t\tif tc.transitions[i]&ProducerTxnFlagInError != 0 {\n\t\t\t\tbaseErr = testError\n\t\t\t}\n\t\t\tlastError = txnmgr.transitionTo(tc.transitions[i], baseErr)\n\t\t}\n\t\trequire.Equal(t, tc.expectedError, lastError, tc)\n\t}\n}\n\nfunc TestTxnmgrInitProducerIdTxn(t *testing.T) {\n\tbroker := NewMockBroker(t, 1)\n\tdefer broker.Close()\n\n\tmetadataLeader := new(MetadataResponse)\n\tmetadataLeader.Version = 4\n\tmetadataLeader.ControllerID = broker.brokerID\n\tmetadataLeader.AddBroker(broker.Addr(), broker.BrokerID())\n\tbroker.Returns(metadataLeader)\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Idempotent = true\n\tconfig.Producer.Transaction.ID = \"test\"\n\tconfig.Version = V0_11_0_0\n\tconfig.Producer.RequiredAcks = WaitForAll\n\tconfig.Net.MaxOpenRequests = 1\n\n\tclient, err := NewClient([]string{broker.Addr()}, config)\n\trequire.NoError(t, err)\n\tdefer client.Close()\n\n\tfindCoordinatorResponse := FindCoordinatorResponse{\n\t\tCoordinator: client.Brokers()[0],\n\t\tErr:         ErrNoError,\n\t\tVersion:     1,\n\t}\n\tbroker.Returns(&findCoordinatorResponse)\n\n\tproducerIdResponse := &InitProducerIDResponse{\n\t\tErr:           ErrNoError,\n\t\tProducerID:    1,\n\t\tProducerEpoch: 0,\n\t}\n\tbroker.Returns(producerIdResponse)\n\n\ttxmng, err := newTransactionManager(config, client)\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, int64(1), txmng.producerID)\n\trequire.Equal(t, int16(0), txmng.producerEpoch)\n\trequire.Equal(t, ProducerTxnFlagReady, txmng.status)\n}\n\n// TestTxnmgrInitProducerIdTxnCoordinatorLoading ensure we retry initProducerId when either FindCoordinator or InitProducerID returns ErrOffsetsLoadInProgress\nfunc TestTxnmgrInitProducerIdTxnCoordinatorLoading(t *testing.T) {\n\tconfig := NewTestConfig()\n\tconfig.Producer.Idempotent = true\n\tconfig.Producer.Transaction.ID = \"txid-group\"\n\tconfig.Version = V0_11_0_0\n\tconfig.Producer.RequiredAcks = WaitForAll\n\tconfig.Net.MaxOpenRequests = 1\n\n\tbroker := NewMockBroker(t, 1)\n\tdefer broker.Close()\n\n\tbroker.SetHandlerByMap(map[string]MockResponse{\n\t\t\"MetadataRequest\": NewMockMetadataResponse(t).\n\t\t\tSetController(broker.BrokerID()).\n\t\t\tSetBroker(broker.Addr(), broker.BrokerID()),\n\t\t\"FindCoordinatorRequest\": NewMockSequence(\n\t\t\tNewMockFindCoordinatorResponse(t).\n\t\t\t\tSetError(CoordinatorTransaction, \"txid-group\", ErrOffsetsLoadInProgress),\n\t\t\tNewMockFindCoordinatorResponse(t).\n\t\t\t\tSetError(CoordinatorTransaction, \"txid-group\", ErrOffsetsLoadInProgress),\n\t\t\tNewMockFindCoordinatorResponse(t).\n\t\t\t\tSetCoordinator(CoordinatorTransaction, \"txid-group\", broker),\n\t\t),\n\t\t\"InitProducerIDRequest\": NewMockSequence(\n\t\t\tNewMockInitProducerIDResponse(t).\n\t\t\t\tSetError(ErrOffsetsLoadInProgress),\n\t\t\tNewMockInitProducerIDResponse(t).\n\t\t\t\tSetError(ErrOffsetsLoadInProgress),\n\t\t\tNewMockInitProducerIDResponse(t).\n\t\t\t\tSetProducerID(1).\n\t\t\t\tSetProducerEpoch(0),\n\t\t),\n\t})\n\n\tclient, err := NewClient([]string{broker.Addr()}, config)\n\trequire.NoError(t, err)\n\tdefer client.Close()\n\n\ttxmng, err := newTransactionManager(config, client)\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, int64(1), txmng.producerID)\n\trequire.Equal(t, int16(0), txmng.producerEpoch)\n\trequire.Equal(t, ProducerTxnFlagReady, txmng.status)\n}\n\nfunc TestMaybeAddPartitionToCurrentTxn(t *testing.T) {\n\ttype testCase struct {\n\t\tinitialFlags                         ProducerTxnStatusFlag\n\t\tinitialPartitionsInCurrentTxn        topicPartitionSet\n\t\tinitialPendingPartitionsInCurrentTxn topicPartitionSet\n\n\t\ttpToAdd map[string][]int32\n\n\t\texpectedPendingPartitions topicPartitionSet\n\t\texpectedPartitionsInTxn   topicPartitionSet\n\t}\n\ttestCases := []testCase{\n\t\t{\n\t\t\tinitialFlags: ProducerTxnFlagInTransaction,\n\t\t\tinitialPartitionsInCurrentTxn: topicPartitionSet{\n\t\t\t\t{topic: \"test-topic\", partition: 0}: struct{}{},\n\t\t\t},\n\t\t\tinitialPendingPartitionsInCurrentTxn: topicPartitionSet{},\n\t\t\ttpToAdd: map[string][]int32{\n\t\t\t\t\"test-topic\": {\n\t\t\t\t\t0,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedPendingPartitions: topicPartitionSet{},\n\t\t\texpectedPartitionsInTxn: topicPartitionSet{\n\t\t\t\t{topic: \"test-topic\", partition: 0}: struct{}{},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinitialFlags:                         ProducerTxnFlagInTransaction,\n\t\t\tinitialPartitionsInCurrentTxn:        topicPartitionSet{},\n\t\t\tinitialPendingPartitionsInCurrentTxn: topicPartitionSet{},\n\t\t\ttpToAdd: map[string][]int32{\n\t\t\t\t\"test-topic\": {\n\t\t\t\t\t0,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedPendingPartitions: topicPartitionSet{\n\t\t\t\t{topic: \"test-topic\", partition: 0}: struct{}{},\n\t\t\t},\n\t\t\texpectedPartitionsInTxn: topicPartitionSet{},\n\t\t},\n\t\t{\n\t\t\tinitialFlags: ProducerTxnFlagInTransaction,\n\t\t\tinitialPartitionsInCurrentTxn: topicPartitionSet{\n\t\t\t\t{topic: \"test-topic\", partition: 0}: struct{}{},\n\t\t\t},\n\t\t\tinitialPendingPartitionsInCurrentTxn: topicPartitionSet{},\n\t\t\ttpToAdd: map[string][]int32{\n\t\t\t\t\"test-topic\": {\n\t\t\t\t\t0,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedPendingPartitions: topicPartitionSet{},\n\t\t\texpectedPartitionsInTxn: topicPartitionSet{\n\t\t\t\t{topic: \"test-topic\", partition: 0}: struct{}{},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tinitialFlags:                  ProducerTxnFlagInTransaction,\n\t\t\tinitialPartitionsInCurrentTxn: topicPartitionSet{},\n\t\t\tinitialPendingPartitionsInCurrentTxn: topicPartitionSet{\n\t\t\t\t{topic: \"test-topic\", partition: 0}: struct{}{},\n\t\t\t},\n\t\t\ttpToAdd: map[string][]int32{\n\t\t\t\t\"test-topic\": {\n\t\t\t\t\t0,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedPendingPartitions: topicPartitionSet{\n\t\t\t\t{topic: \"test-topic\", partition: 0}: struct{}{},\n\t\t\t},\n\t\t\texpectedPartitionsInTxn: topicPartitionSet{},\n\t\t},\n\t\t{\n\t\t\tinitialFlags:                         ProducerTxnFlagInError,\n\t\t\tinitialPartitionsInCurrentTxn:        topicPartitionSet{},\n\t\t\tinitialPendingPartitionsInCurrentTxn: topicPartitionSet{},\n\t\t\ttpToAdd: map[string][]int32{\n\t\t\t\t\"test-topic\": {\n\t\t\t\t\t0,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedPendingPartitions: topicPartitionSet{},\n\t\t\texpectedPartitionsInTxn:   topicPartitionSet{},\n\t\t},\n\t}\n\n\tbroker := NewMockBroker(t, 1)\n\tdefer broker.Close()\n\n\tmetadataLeader := new(MetadataResponse)\n\tmetadataLeader.Version = 4\n\tmetadataLeader.ControllerID = broker.brokerID\n\tmetadataLeader.AddBroker(broker.Addr(), broker.BrokerID())\n\tmetadataLeader.AddTopic(\"test-topic\", ErrNoError)\n\tmetadataLeader.AddTopicPartition(\"test-topic\", 0, broker.BrokerID(), nil, nil, nil, ErrNoError)\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Idempotent = true\n\tconfig.Producer.Transaction.ID = \"test\"\n\tconfig.Version = V0_11_0_0\n\tconfig.Producer.RequiredAcks = WaitForAll\n\tconfig.Net.MaxOpenRequests = 1\n\tconfig.Producer.Transaction.Retry.Max = 0\n\tconfig.Producer.Transaction.Retry.Backoff = 0\n\n\tfor _, tc := range testCases {\n\t\tfunc() {\n\t\t\tbroker.Returns(metadataLeader)\n\n\t\t\tclient, err := NewClient([]string{broker.Addr()}, config)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer client.Close()\n\n\t\t\tfindCoordinatorResponse := FindCoordinatorResponse{\n\t\t\t\tCoordinator: client.Brokers()[0],\n\t\t\t\tErr:         ErrNoError,\n\t\t\t\tVersion:     1,\n\t\t\t}\n\t\t\tbroker.Returns(&findCoordinatorResponse)\n\n\t\t\tproducerIdResponse := &InitProducerIDResponse{\n\t\t\t\tErr:           ErrNoError,\n\t\t\t\tProducerID:    1,\n\t\t\t\tProducerEpoch: 0,\n\t\t\t}\n\t\t\tbroker.Returns(producerIdResponse)\n\n\t\t\ttxmng, err := newTransactionManager(config, client)\n\t\t\trequire.NoError(t, err)\n\n\t\t\ttxmng.partitionsInCurrentTxn = tc.initialPartitionsInCurrentTxn\n\t\t\ttxmng.pendingPartitionsInCurrentTxn = tc.initialPendingPartitionsInCurrentTxn\n\t\t\ttxmng.status = tc.initialFlags\n\n\t\t\tfor topic, partitions := range tc.tpToAdd {\n\t\t\t\tfor _, partition := range partitions {\n\t\t\t\t\ttxmng.maybeAddPartitionToCurrentTxn(topic, partition)\n\t\t\t\t}\n\t\t\t}\n\t\t\trequire.Equal(t, tc.expectedPartitionsInTxn, txmng.partitionsInCurrentTxn, tc)\n\t\t\trequire.Equal(t, tc.expectedPendingPartitions, txmng.pendingPartitionsInCurrentTxn, tc)\n\t\t}()\n\t}\n}\n\nfunc TestAddOffsetsToTxn(t *testing.T) {\n\ttype testCase struct {\n\t\tbrokerErr     KError\n\t\tinitialFlags  ProducerTxnStatusFlag\n\t\texpectedFlags ProducerTxnStatusFlag\n\t\texpectedError error\n\t\tnewOffsets    topicPartitionOffsets\n\t}\n\n\toriginalOffsets := topicPartitionOffsets{\n\t\ttopicPartition{topic: \"test-topic\", partition: 0}: {\n\t\t\tPartition: 0,\n\t\t\tOffset:    0,\n\t\t},\n\t}\n\n\ttestCases := []testCase{\n\t\t{\n\t\t\tbrokerErr:     ErrNoError,\n\t\t\tinitialFlags:  ProducerTxnFlagInTransaction,\n\t\t\texpectedFlags: ProducerTxnFlagInTransaction,\n\t\t\texpectedError: nil,\n\t\t\tnewOffsets:    topicPartitionOffsets{},\n\t\t},\n\t\t{\n\t\t\tbrokerErr:     ErrConsumerCoordinatorNotAvailable,\n\t\t\tinitialFlags:  ProducerTxnFlagInTransaction,\n\t\t\texpectedFlags: ProducerTxnFlagInTransaction,\n\t\t\texpectedError: ErrConsumerCoordinatorNotAvailable,\n\t\t\tnewOffsets:    originalOffsets,\n\t\t},\n\t\t{\n\t\t\tbrokerErr:     ErrNotCoordinatorForConsumer,\n\t\t\tinitialFlags:  ProducerTxnFlagInTransaction,\n\t\t\texpectedFlags: ProducerTxnFlagInTransaction,\n\t\t\texpectedError: ErrNotCoordinatorForConsumer,\n\t\t\tnewOffsets:    originalOffsets,\n\t\t},\n\t\t{\n\t\t\tbrokerErr:     ErrOffsetsLoadInProgress,\n\t\t\tinitialFlags:  ProducerTxnFlagInTransaction,\n\t\t\texpectedFlags: ProducerTxnFlagInTransaction,\n\t\t\texpectedError: ErrOffsetsLoadInProgress,\n\t\t\tnewOffsets:    originalOffsets,\n\t\t},\n\t\t{\n\t\t\tbrokerErr:     ErrConcurrentTransactions,\n\t\t\tinitialFlags:  ProducerTxnFlagInTransaction,\n\t\t\texpectedFlags: ProducerTxnFlagInTransaction,\n\t\t\texpectedError: ErrConcurrentTransactions,\n\t\t\tnewOffsets:    originalOffsets,\n\t\t},\n\t\t{\n\t\t\tbrokerErr:     ErrUnknownProducerID,\n\t\t\tinitialFlags:  ProducerTxnFlagInTransaction,\n\t\t\texpectedFlags: ProducerTxnFlagFatalError,\n\t\t\texpectedError: ErrUnknownProducerID,\n\t\t\tnewOffsets:    originalOffsets,\n\t\t},\n\t\t{\n\t\t\tbrokerErr:     ErrInvalidProducerIDMapping,\n\t\t\tinitialFlags:  ProducerTxnFlagInTransaction,\n\t\t\texpectedFlags: ProducerTxnFlagFatalError,\n\t\t\texpectedError: ErrInvalidProducerIDMapping,\n\t\t\tnewOffsets:    originalOffsets,\n\t\t},\n\t\t{\n\t\t\tbrokerErr:     ErrGroupAuthorizationFailed,\n\t\t\tinitialFlags:  ProducerTxnFlagInTransaction,\n\t\t\texpectedFlags: ProducerTxnFlagAbortableError,\n\t\t\texpectedError: ErrGroupAuthorizationFailed,\n\t\t\tnewOffsets:    originalOffsets,\n\t\t},\n\t}\n\n\tbroker := NewMockBroker(t, 1)\n\tdefer broker.Close()\n\n\tmetadataLeader := new(MetadataResponse)\n\tmetadataLeader.Version = 4\n\tmetadataLeader.ControllerID = broker.brokerID\n\tmetadataLeader.AddBroker(broker.Addr(), broker.BrokerID())\n\tmetadataLeader.AddTopic(\"test-topic\", ErrNoError)\n\tmetadataLeader.AddTopicPartition(\"test-topic\", 0, broker.BrokerID(), nil, nil, nil, ErrNoError)\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Idempotent = true\n\tconfig.Producer.Transaction.ID = \"test\"\n\tconfig.Version = V0_11_0_0\n\tconfig.Producer.RequiredAcks = WaitForAll\n\tconfig.Net.MaxOpenRequests = 1\n\tconfig.Producer.Transaction.Retry.Max = 0\n\tconfig.Producer.Transaction.Retry.Backoff = 0\n\n\toffsets := topicPartitionOffsets{\n\t\ttopicPartition{topic: \"test-topic\", partition: 0}: {\n\t\t\tPartition: 0,\n\t\t\tOffset:    0,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tfunc() {\n\t\t\tbroker.Returns(metadataLeader)\n\n\t\t\tclient, err := NewClient([]string{broker.Addr()}, config)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tdefer client.Close()\n\n\t\t\tfindCoordinatorResponse := FindCoordinatorResponse{\n\t\t\t\tCoordinator: client.Brokers()[0],\n\t\t\t\tErr:         ErrNoError,\n\t\t\t\tVersion:     1,\n\t\t\t}\n\t\t\tbroker.Returns(&findCoordinatorResponse)\n\n\t\t\tproducerIdResponse := &InitProducerIDResponse{\n\t\t\t\tErr:           ErrNoError,\n\t\t\t\tProducerID:    1,\n\t\t\t\tProducerEpoch: 0,\n\t\t\t}\n\t\t\tbroker.Returns(producerIdResponse)\n\n\t\t\ttxmng, err := newTransactionManager(config, client)\n\t\t\trequire.NoError(t, err)\n\n\t\t\ttxmng.status = tc.initialFlags\n\n\t\t\tbroker.Returns(&AddOffsetsToTxnResponse{\n\t\t\t\tErr: tc.brokerErr,\n\t\t\t})\n\n\t\t\tif errors.Is(tc.brokerErr, ErrRequestTimedOut) ||\n\t\t\t\terrors.Is(tc.brokerErr, ErrConsumerCoordinatorNotAvailable) ||\n\t\t\t\terrors.Is(tc.brokerErr, ErrNotCoordinatorForConsumer) {\n\t\t\t\tbroker.Returns(&FindCoordinatorResponse{\n\t\t\t\t\tCoordinator: client.Brokers()[0],\n\t\t\t\t\tErr:         ErrNoError,\n\t\t\t\t\tVersion:     1,\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tif tc.brokerErr == ErrNoError {\n\t\t\t\tbroker.Returns(&FindCoordinatorResponse{\n\t\t\t\t\tCoordinator: client.Brokers()[0],\n\t\t\t\t\tErr:         ErrNoError,\n\t\t\t\t\tVersion:     1,\n\t\t\t\t})\n\t\t\t\tbroker.Returns(&TxnOffsetCommitResponse{\n\t\t\t\t\tTopics: map[string][]*PartitionError{\n\t\t\t\t\t\t\"test-topic\": {\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tPartition: 0,\n\t\t\t\t\t\t\t\tErr:       ErrNoError,\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\n\t\t\tnewOffsets, err := txmng.publishOffsetsToTxn(offsets, \"test-group\")\n\t\t\tif tc.expectedError != nil {\n\t\t\t\trequire.Equal(t, tc.expectedError.Error(), err.Error())\n\t\t\t} else {\n\t\t\t\trequire.Equal(t, tc.expectedError, err)\n\t\t\t}\n\t\t\trequire.Equal(t, tc.newOffsets, newOffsets)\n\t\t\trequire.True(t, tc.expectedFlags&txmng.status != 0)\n\t\t}()\n\t}\n}\n\nfunc TestTxnOffsetsCommit(t *testing.T) {\n\ttype testCase struct {\n\t\tbrokerErr       KError\n\t\tinitialFlags    ProducerTxnStatusFlag\n\t\tinitialOffsets  topicPartitionOffsets\n\t\texpectedFlags   ProducerTxnStatusFlag\n\t\texpectedError   error\n\t\texpectedOffsets topicPartitionOffsets\n\t}\n\n\toriginalOffsets := topicPartitionOffsets{\n\t\ttopicPartition{topic: \"test-topic\", partition: 0}: {\n\t\t\tPartition: 0,\n\t\t\tOffset:    0,\n\t\t},\n\t}\n\n\ttestCases := []testCase{\n\t\t{\n\t\t\tbrokerErr:    ErrConsumerCoordinatorNotAvailable,\n\t\t\tinitialFlags: ProducerTxnFlagInTransaction,\n\t\t\tinitialOffsets: topicPartitionOffsets{\n\t\t\t\ttopicPartition{topic: \"test-topic\", partition: 0}: {\n\t\t\t\t\tPartition: 0,\n\t\t\t\t\tOffset:    0,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedFlags:   ProducerTxnFlagInTransaction,\n\t\t\texpectedError:   Wrap(ErrTxnOffsetCommit, ErrConsumerCoordinatorNotAvailable),\n\t\t\texpectedOffsets: originalOffsets,\n\t\t},\n\t\t{\n\t\t\tbrokerErr:    ErrNotCoordinatorForConsumer,\n\t\t\tinitialFlags: ProducerTxnFlagInTransaction,\n\t\t\tinitialOffsets: topicPartitionOffsets{\n\t\t\t\ttopicPartition{topic: \"test-topic\", partition: 0}: {\n\t\t\t\t\tPartition: 0,\n\t\t\t\t\tOffset:    0,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedFlags:   ProducerTxnFlagInTransaction,\n\t\t\texpectedError:   Wrap(ErrTxnOffsetCommit, ErrNotCoordinatorForConsumer),\n\t\t\texpectedOffsets: originalOffsets,\n\t\t},\n\t\t{\n\t\t\tbrokerErr:    ErrNoError,\n\t\t\tinitialFlags: ProducerTxnFlagInTransaction,\n\t\t\tinitialOffsets: topicPartitionOffsets{\n\t\t\t\ttopicPartition{topic: \"test-topic\", partition: 0}: {\n\t\t\t\t\tPartition: 0,\n\t\t\t\t\tOffset:    0,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedFlags:   ProducerTxnFlagInTransaction,\n\t\t\texpectedError:   nil,\n\t\t\texpectedOffsets: topicPartitionOffsets{},\n\t\t},\n\t\t{\n\t\t\tbrokerErr:    ErrUnknownTopicOrPartition,\n\t\t\tinitialFlags: ProducerTxnFlagInTransaction,\n\t\t\tinitialOffsets: topicPartitionOffsets{\n\t\t\t\ttopicPartition{topic: \"test-topic\", partition: 0}: {\n\t\t\t\t\tPartition: 0,\n\t\t\t\t\tOffset:    0,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedFlags:   ProducerTxnFlagInTransaction,\n\t\t\texpectedError:   Wrap(ErrTxnOffsetCommit, ErrUnknownTopicOrPartition),\n\t\t\texpectedOffsets: originalOffsets,\n\t\t},\n\t\t{\n\t\t\tbrokerErr:    ErrOffsetsLoadInProgress,\n\t\t\tinitialFlags: ProducerTxnFlagInTransaction,\n\t\t\tinitialOffsets: topicPartitionOffsets{\n\t\t\t\ttopicPartition{topic: \"test-topic\", partition: 0}: {\n\t\t\t\t\tPartition: 0,\n\t\t\t\t\tOffset:    0,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedFlags:   ProducerTxnFlagInTransaction,\n\t\t\texpectedError:   Wrap(ErrTxnOffsetCommit, ErrOffsetsLoadInProgress),\n\t\t\texpectedOffsets: originalOffsets,\n\t\t},\n\t\t{\n\t\t\tbrokerErr:    ErrIllegalGeneration,\n\t\t\tinitialFlags: ProducerTxnFlagInTransaction,\n\t\t\tinitialOffsets: topicPartitionOffsets{\n\t\t\t\ttopicPartition{topic: \"test-topic\", partition: 0}: {\n\t\t\t\t\tPartition: 0,\n\t\t\t\t\tOffset:    0,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedFlags:   ProducerTxnFlagAbortableError,\n\t\t\texpectedError:   ErrIllegalGeneration,\n\t\t\texpectedOffsets: originalOffsets,\n\t\t},\n\t\t{\n\t\t\tbrokerErr:    ErrUnknownMemberId,\n\t\t\tinitialFlags: ProducerTxnFlagInTransaction,\n\t\t\tinitialOffsets: topicPartitionOffsets{\n\t\t\t\ttopicPartition{topic: \"test-topic\", partition: 0}: {\n\t\t\t\t\tPartition: 0,\n\t\t\t\t\tOffset:    0,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedFlags:   ProducerTxnFlagAbortableError,\n\t\t\texpectedError:   ErrUnknownMemberId,\n\t\t\texpectedOffsets: originalOffsets,\n\t\t},\n\t\t{\n\t\t\tbrokerErr:    ErrFencedInstancedId,\n\t\t\tinitialFlags: ProducerTxnFlagInTransaction,\n\t\t\tinitialOffsets: topicPartitionOffsets{\n\t\t\t\ttopicPartition{topic: \"test-topic\", partition: 0}: {\n\t\t\t\t\tPartition: 0,\n\t\t\t\t\tOffset:    0,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedFlags:   ProducerTxnFlagAbortableError,\n\t\t\texpectedError:   ErrFencedInstancedId,\n\t\t\texpectedOffsets: originalOffsets,\n\t\t},\n\t\t{\n\t\t\tbrokerErr:    ErrGroupAuthorizationFailed,\n\t\t\tinitialFlags: ProducerTxnFlagInTransaction,\n\t\t\tinitialOffsets: topicPartitionOffsets{\n\t\t\t\ttopicPartition{topic: \"test-topic\", partition: 0}: {\n\t\t\t\t\tPartition: 0,\n\t\t\t\t\tOffset:    0,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedFlags:   ProducerTxnFlagAbortableError,\n\t\t\texpectedError:   ErrGroupAuthorizationFailed,\n\t\t\texpectedOffsets: originalOffsets,\n\t\t},\n\t\t{\n\t\t\tbrokerErr:    ErrKafkaStorageError,\n\t\t\tinitialFlags: ProducerTxnFlagInTransaction,\n\t\t\tinitialOffsets: topicPartitionOffsets{\n\t\t\t\ttopicPartition{topic: \"test-topic\", partition: 0}: {\n\t\t\t\t\tPartition: 0,\n\t\t\t\t\tOffset:    0,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedFlags:   ProducerTxnFlagFatalError,\n\t\t\texpectedError:   ErrKafkaStorageError,\n\t\t\texpectedOffsets: originalOffsets,\n\t\t},\n\t}\n\n\tbroker := NewMockBroker(t, 1)\n\tdefer broker.Close()\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Idempotent = true\n\tconfig.Producer.Transaction.ID = \"test\"\n\tconfig.Version = V0_11_0_0\n\tconfig.Producer.RequiredAcks = WaitForAll\n\tconfig.Net.MaxOpenRequests = 1\n\tconfig.Producer.Transaction.Retry.Max = 0\n\tconfig.Producer.Transaction.Retry.Backoff = 0\n\n\tmetadataLeader := new(MetadataResponse)\n\tmetadataLeader.Version = 4\n\tmetadataLeader.ControllerID = broker.brokerID\n\tmetadataLeader.AddBroker(broker.Addr(), broker.BrokerID())\n\tmetadataLeader.AddTopic(\"test-topic\", ErrNoError)\n\tmetadataLeader.AddTopicPartition(\"test-topic\", 0, broker.BrokerID(), nil, nil, nil, ErrNoError)\n\n\tfor _, tc := range testCases {\n\t\tfunc() {\n\t\t\tbroker.Returns(metadataLeader)\n\n\t\t\tclient, err := NewClient([]string{broker.Addr()}, config)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer client.Close()\n\n\t\t\tfindCoordinatorResponse := FindCoordinatorResponse{\n\t\t\t\tCoordinator: client.Brokers()[0],\n\t\t\t\tErr:         ErrNoError,\n\t\t\t\tVersion:     1,\n\t\t\t}\n\t\t\tbroker.Returns(&findCoordinatorResponse)\n\n\t\t\tproducerIdResponse := &InitProducerIDResponse{\n\t\t\t\tErr:           ErrNoError,\n\t\t\t\tProducerID:    1,\n\t\t\t\tProducerEpoch: 0,\n\t\t\t}\n\t\t\tbroker.Returns(producerIdResponse)\n\n\t\t\ttxmng, err := newTransactionManager(config, client)\n\t\t\trequire.NoError(t, err)\n\n\t\t\ttxmng.status = tc.initialFlags\n\n\t\t\tbroker.Returns(&AddOffsetsToTxnResponse{\n\t\t\t\tErr: ErrNoError,\n\t\t\t})\n\n\t\t\tbroker.Returns(&FindCoordinatorResponse{\n\t\t\t\tCoordinator: client.Brokers()[0],\n\t\t\t\tErr:         ErrNoError,\n\t\t\t\tVersion:     1,\n\t\t\t})\n\t\t\tbroker.Returns(&TxnOffsetCommitResponse{\n\t\t\t\tTopics: map[string][]*PartitionError{\n\t\t\t\t\t\"test-topic\": {\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tPartition: 0,\n\t\t\t\t\t\t\tErr:       tc.brokerErr,\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\tif errors.Is(tc.brokerErr, ErrRequestTimedOut) ||\n\t\t\t\terrors.Is(tc.brokerErr, ErrConsumerCoordinatorNotAvailable) ||\n\t\t\t\terrors.Is(tc.brokerErr, ErrNotCoordinatorForConsumer) {\n\t\t\t\tbroker.Returns(&FindCoordinatorResponse{\n\t\t\t\t\tCoordinator: client.Brokers()[0],\n\t\t\t\t\tErr:         ErrNoError,\n\t\t\t\t\tVersion:     1,\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tnewOffsets, err := txmng.publishOffsetsToTxn(tc.initialOffsets, \"test-group\")\n\t\t\tif tc.expectedError != nil {\n\t\t\t\trequire.Equal(t, tc.expectedError.Error(), err.Error())\n\t\t\t} else {\n\t\t\t\trequire.Equal(t, tc.expectedError, err)\n\t\t\t}\n\t\t\trequire.Equal(t, tc.expectedOffsets, newOffsets)\n\t\t\trequire.True(t, tc.expectedFlags&txmng.status != 0)\n\t\t}()\n\t}\n}\n\nfunc TestEndTxn(t *testing.T) {\n\ttype testCase struct {\n\t\tbrokerErr     KError\n\t\tcommit        bool\n\t\texpectedFlags ProducerTxnStatusFlag\n\t\texpectedError error\n\t}\n\n\ttestCases := []testCase{\n\t\t{\n\t\t\tbrokerErr:     ErrNoError,\n\t\t\tcommit:        true,\n\t\t\texpectedFlags: ProducerTxnFlagReady,\n\t\t\texpectedError: nil,\n\t\t},\n\t\t{\n\t\t\tbrokerErr:     ErrConsumerCoordinatorNotAvailable,\n\t\t\tcommit:        true,\n\t\t\texpectedFlags: ProducerTxnFlagEndTransaction,\n\t\t\texpectedError: ErrConsumerCoordinatorNotAvailable,\n\t\t},\n\t\t{\n\t\t\tbrokerErr:     ErrNotCoordinatorForConsumer,\n\t\t\tcommit:        true,\n\t\t\texpectedFlags: ProducerTxnFlagEndTransaction,\n\t\t\texpectedError: ErrNotCoordinatorForConsumer,\n\t\t},\n\t\t{\n\t\t\tbrokerErr:     ErrOffsetsLoadInProgress,\n\t\t\tcommit:        true,\n\t\t\texpectedFlags: ProducerTxnFlagEndTransaction,\n\t\t\texpectedError: ErrOffsetsLoadInProgress,\n\t\t},\n\t\t{\n\t\t\tbrokerErr:     ErrConcurrentTransactions,\n\t\t\tcommit:        true,\n\t\t\texpectedFlags: ProducerTxnFlagEndTransaction,\n\t\t\texpectedError: ErrConcurrentTransactions,\n\t\t},\n\t\t{\n\t\t\tbrokerErr:     ErrUnknownProducerID,\n\t\t\tcommit:        true,\n\t\t\texpectedFlags: ProducerTxnFlagFatalError,\n\t\t\texpectedError: ErrUnknownProducerID,\n\t\t},\n\t\t{\n\t\t\tbrokerErr:     ErrInvalidProducerIDMapping,\n\t\t\tcommit:        true,\n\t\t\texpectedFlags: ProducerTxnFlagFatalError,\n\t\t\texpectedError: ErrInvalidProducerIDMapping,\n\t\t},\n\t}\n\n\tbroker := NewMockBroker(t, 1)\n\tdefer broker.Close()\n\n\tmetadataLeader := new(MetadataResponse)\n\tmetadataLeader.Version = 4\n\tmetadataLeader.ControllerID = broker.brokerID\n\tmetadataLeader.AddBroker(broker.Addr(), broker.BrokerID())\n\tmetadataLeader.AddTopic(\"test-topic\", ErrNoError)\n\tmetadataLeader.AddTopicPartition(\"test-topic\", 0, broker.BrokerID(), nil, nil, nil, ErrNoError)\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Idempotent = true\n\tconfig.Producer.Transaction.ID = \"test\"\n\tconfig.Version = V0_11_0_0\n\tconfig.Producer.RequiredAcks = WaitForAll\n\tconfig.Net.MaxOpenRequests = 1\n\tconfig.Producer.Transaction.Retry.Max = 0\n\tconfig.Producer.Transaction.Retry.Backoff = 0\n\n\tfor _, tc := range testCases {\n\t\tfunc() {\n\t\t\tbroker.Returns(metadataLeader)\n\n\t\t\tclient, err := NewClient([]string{broker.Addr()}, config)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer client.Close()\n\n\t\t\tfindCoordinatorResponse := FindCoordinatorResponse{\n\t\t\t\tCoordinator: client.Brokers()[0],\n\t\t\t\tErr:         ErrNoError,\n\t\t\t\tVersion:     1,\n\t\t\t}\n\t\t\tbroker.Returns(&findCoordinatorResponse)\n\n\t\t\tproducerIdResponse := &InitProducerIDResponse{\n\t\t\t\tErr:           ErrNoError,\n\t\t\t\tProducerID:    1,\n\t\t\t\tProducerEpoch: 0,\n\t\t\t}\n\t\t\tbroker.Returns(producerIdResponse)\n\n\t\t\ttxmng, err := newTransactionManager(config, client)\n\t\t\trequire.NoError(t, err)\n\n\t\t\ttxmng.status = ProducerTxnFlagEndTransaction\n\n\t\t\tendTxnResponse := &EndTxnResponse{\n\t\t\t\tErr:          tc.brokerErr,\n\t\t\t\tThrottleTime: 0,\n\t\t\t}\n\t\t\tbroker.Returns(endTxnResponse)\n\n\t\t\tif errors.Is(tc.brokerErr, ErrRequestTimedOut) ||\n\t\t\t\terrors.Is(tc.brokerErr, ErrConsumerCoordinatorNotAvailable) ||\n\t\t\t\terrors.Is(tc.brokerErr, ErrNotCoordinatorForConsumer) {\n\t\t\t\tbroker.Returns(&FindCoordinatorResponse{\n\t\t\t\t\tCoordinator: client.Brokers()[0],\n\t\t\t\t\tErr:         ErrNoError,\n\t\t\t\t\tVersion:     1,\n\t\t\t\t})\n\t\t\t}\n\n\t\t\terr = txmng.endTxn(tc.commit)\n\t\t\trequire.Equal(t, tc.expectedError, err)\n\t\t\trequire.True(t, txmng.currentTxnStatus()&tc.expectedFlags != 0)\n\t\t}()\n\t}\n}\n\nfunc TestPublishPartitionToTxn(t *testing.T) {\n\ttype testCase struct {\n\t\tbrokerErr                 KError\n\t\texpectedFlags             ProducerTxnStatusFlag\n\t\texpectedError             error\n\t\texpectedPendingPartitions topicPartitionSet\n\t\texpectedPartitionsInTxn   topicPartitionSet\n\t}\n\n\tinitialPendingTopicPartitionSet := topicPartitionSet{\n\t\t{\n\t\t\ttopic:     \"test-topic\",\n\t\t\tpartition: 0,\n\t\t}: struct{}{},\n\t}\n\n\ttestCases := []testCase{\n\t\t{\n\t\t\tbrokerErr:                 ErrNoError,\n\t\t\texpectedFlags:             ProducerTxnFlagInTransaction,\n\t\t\texpectedError:             nil,\n\t\t\texpectedPendingPartitions: topicPartitionSet{},\n\t\t\texpectedPartitionsInTxn:   initialPendingTopicPartitionSet,\n\t\t},\n\t\t{\n\t\t\tbrokerErr:                 ErrConsumerCoordinatorNotAvailable,\n\t\t\texpectedFlags:             ProducerTxnFlagInTransaction,\n\t\t\texpectedError:             Wrap(ErrAddPartitionsToTxn, ErrConsumerCoordinatorNotAvailable),\n\t\t\texpectedPartitionsInTxn:   topicPartitionSet{},\n\t\t\texpectedPendingPartitions: initialPendingTopicPartitionSet,\n\t\t},\n\t\t{\n\t\t\tbrokerErr:                 ErrNotCoordinatorForConsumer,\n\t\t\texpectedFlags:             ProducerTxnFlagInTransaction,\n\t\t\texpectedError:             Wrap(ErrAddPartitionsToTxn, ErrNotCoordinatorForConsumer),\n\t\t\texpectedPartitionsInTxn:   topicPartitionSet{},\n\t\t\texpectedPendingPartitions: initialPendingTopicPartitionSet,\n\t\t},\n\t\t{\n\t\t\tbrokerErr:                 ErrUnknownTopicOrPartition,\n\t\t\texpectedFlags:             ProducerTxnFlagInTransaction,\n\t\t\texpectedError:             Wrap(ErrAddPartitionsToTxn, ErrUnknownTopicOrPartition),\n\t\t\texpectedPartitionsInTxn:   topicPartitionSet{},\n\t\t\texpectedPendingPartitions: initialPendingTopicPartitionSet,\n\t\t},\n\t\t{\n\t\t\tbrokerErr:                 ErrOffsetsLoadInProgress,\n\t\t\texpectedFlags:             ProducerTxnFlagInTransaction,\n\t\t\texpectedError:             Wrap(ErrAddPartitionsToTxn, ErrOffsetsLoadInProgress),\n\t\t\texpectedPartitionsInTxn:   topicPartitionSet{},\n\t\t\texpectedPendingPartitions: initialPendingTopicPartitionSet,\n\t\t},\n\t\t{\n\t\t\tbrokerErr:                 ErrConcurrentTransactions,\n\t\t\texpectedFlags:             ProducerTxnFlagInTransaction,\n\t\t\texpectedError:             Wrap(ErrAddPartitionsToTxn, ErrConcurrentTransactions),\n\t\t\texpectedPartitionsInTxn:   topicPartitionSet{},\n\t\t\texpectedPendingPartitions: initialPendingTopicPartitionSet,\n\t\t},\n\t\t{\n\t\t\tbrokerErr:                 ErrOperationNotAttempted,\n\t\t\texpectedFlags:             ProducerTxnFlagAbortableError,\n\t\t\texpectedError:             ErrOperationNotAttempted,\n\t\t\texpectedPartitionsInTxn:   topicPartitionSet{},\n\t\t\texpectedPendingPartitions: topicPartitionSet{},\n\t\t},\n\t\t{\n\t\t\tbrokerErr:                 ErrTopicAuthorizationFailed,\n\t\t\texpectedFlags:             ProducerTxnFlagAbortableError,\n\t\t\texpectedError:             ErrTopicAuthorizationFailed,\n\t\t\texpectedPartitionsInTxn:   topicPartitionSet{},\n\t\t\texpectedPendingPartitions: topicPartitionSet{},\n\t\t},\n\t\t{\n\t\t\tbrokerErr:                 ErrUnknownProducerID,\n\t\t\texpectedFlags:             ProducerTxnFlagFatalError,\n\t\t\texpectedError:             ErrUnknownProducerID,\n\t\t\texpectedPartitionsInTxn:   topicPartitionSet{},\n\t\t\texpectedPendingPartitions: topicPartitionSet{},\n\t\t},\n\t\t{\n\t\t\tbrokerErr:                 ErrInvalidProducerIDMapping,\n\t\t\texpectedFlags:             ProducerTxnFlagFatalError,\n\t\t\texpectedError:             ErrInvalidProducerIDMapping,\n\t\t\texpectedPartitionsInTxn:   topicPartitionSet{},\n\t\t\texpectedPendingPartitions: topicPartitionSet{},\n\t\t},\n\t\t{\n\t\t\tbrokerErr:                 ErrKafkaStorageError,\n\t\t\texpectedFlags:             ProducerTxnFlagFatalError,\n\t\t\texpectedError:             ErrKafkaStorageError,\n\t\t\texpectedPartitionsInTxn:   topicPartitionSet{},\n\t\t\texpectedPendingPartitions: topicPartitionSet{},\n\t\t},\n\t}\n\n\tbroker := NewMockBroker(t, 1)\n\tdefer broker.Close()\n\n\tmetadataLeader := new(MetadataResponse)\n\tmetadataLeader.Version = 4\n\tmetadataLeader.ControllerID = broker.brokerID\n\tmetadataLeader.AddBroker(broker.Addr(), broker.BrokerID())\n\tmetadataLeader.AddTopic(\"test-topic\", ErrNoError)\n\tmetadataLeader.AddTopicPartition(\"test-topic\", 0, broker.BrokerID(), nil, nil, nil, ErrNoError)\n\n\tconfig := NewTestConfig()\n\tconfig.Producer.Idempotent = true\n\tconfig.Producer.Transaction.ID = \"test\"\n\tconfig.Version = V0_11_0_0\n\tconfig.Producer.RequiredAcks = WaitForAll\n\tconfig.Net.MaxOpenRequests = 1\n\tconfig.Producer.Transaction.Retry.Max = 0\n\tconfig.Producer.Transaction.Retry.Backoff = 0\n\n\tfor _, tc := range testCases {\n\t\tfunc() {\n\t\t\tbroker.Returns(metadataLeader)\n\n\t\t\tclient, err := NewClient([]string{broker.Addr()}, config)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer client.Close()\n\n\t\t\tfindCoordinatorResponse := FindCoordinatorResponse{\n\t\t\t\tCoordinator: client.Brokers()[0],\n\t\t\t\tErr:         ErrNoError,\n\t\t\t\tVersion:     1,\n\t\t\t}\n\t\t\tbroker.Returns(&findCoordinatorResponse)\n\n\t\t\tproducerIdResponse := &InitProducerIDResponse{\n\t\t\t\tErr:           ErrNoError,\n\t\t\t\tProducerID:    1,\n\t\t\t\tProducerEpoch: 0,\n\t\t\t}\n\t\t\tbroker.Returns(producerIdResponse)\n\n\t\t\ttxmng, err := newTransactionManager(config, client)\n\t\t\trequire.NoError(t, err)\n\n\t\t\ttxmng.status = ProducerTxnFlagInTransaction\n\t\t\ttxmng.pendingPartitionsInCurrentTxn = topicPartitionSet{\n\t\t\t\t{\n\t\t\t\t\ttopic:     \"test-topic\",\n\t\t\t\t\tpartition: 0,\n\t\t\t\t}: struct{}{},\n\t\t\t}\n\t\t\tbroker.Returns(&AddPartitionsToTxnResponse{\n\t\t\t\tErrors: map[string][]*PartitionError{\n\t\t\t\t\t\"test-topic\": {\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tPartition: 0,\n\t\t\t\t\t\t\tErr:       tc.brokerErr,\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\tif errors.Is(tc.brokerErr, ErrRequestTimedOut) ||\n\t\t\t\terrors.Is(tc.brokerErr, ErrConsumerCoordinatorNotAvailable) ||\n\t\t\t\terrors.Is(tc.brokerErr, ErrNotCoordinatorForConsumer) {\n\t\t\t\tbroker.Returns(&FindCoordinatorResponse{\n\t\t\t\t\tCoordinator: client.Brokers()[0],\n\t\t\t\t\tErr:         ErrNoError,\n\t\t\t\t\tVersion:     1,\n\t\t\t\t})\n\t\t\t}\n\t\t\terr = txmng.publishTxnPartitions()\n\t\t\tif tc.expectedError != nil {\n\t\t\t\trequire.Equal(t, tc.expectedError.Error(), err.Error(), tc)\n\t\t\t} else {\n\t\t\t\trequire.Equal(t, tc.expectedError, err, tc)\n\t\t\t}\n\n\t\t\trequire.True(t, txmng.status&tc.expectedFlags != 0, tc)\n\t\t\trequire.Equal(t, tc.expectedPartitionsInTxn, txmng.partitionsInCurrentTxn, tc)\n\t\t\trequire.Equal(t, tc.expectedPendingPartitions, txmng.pendingPartitionsInCurrentTxn, tc)\n\t\t}()\n\t}\n}\n"
  },
  {
    "path": "txn_offset_commit_request.go",
    "content": "package sarama\n\ntype TxnOffsetCommitRequest struct {\n\tVersion         int16\n\tTransactionalID string\n\tGroupID         string\n\tProducerID      int64\n\tProducerEpoch   int16\n\tTopics          map[string][]*PartitionOffsetMetadata\n}\n\nfunc (t *TxnOffsetCommitRequest) setVersion(v int16) {\n\tt.Version = v\n}\n\nfunc (t *TxnOffsetCommitRequest) encode(pe packetEncoder) error {\n\tif err := pe.putString(t.TransactionalID); err != nil {\n\t\treturn err\n\t}\n\tif err := pe.putString(t.GroupID); err != nil {\n\t\treturn err\n\t}\n\tpe.putInt64(t.ProducerID)\n\tpe.putInt16(t.ProducerEpoch)\n\n\tif err := pe.putArrayLength(len(t.Topics)); err != nil {\n\t\treturn err\n\t}\n\tfor topic, partitions := range t.Topics {\n\t\tif err := pe.putString(topic); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := pe.putArrayLength(len(partitions)); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor _, partition := range partitions {\n\t\t\tif err := partition.encode(pe, t.Version); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (t *TxnOffsetCommitRequest) decode(pd packetDecoder, version int16) (err error) {\n\tt.Version = version\n\tif t.TransactionalID, err = pd.getString(); err != nil {\n\t\treturn err\n\t}\n\tif t.GroupID, err = pd.getString(); err != nil {\n\t\treturn err\n\t}\n\tif t.ProducerID, err = pd.getInt64(); err != nil {\n\t\treturn err\n\t}\n\tif t.ProducerEpoch, err = pd.getInt16(); err != nil {\n\t\treturn err\n\t}\n\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tt.Topics = make(map[string][]*PartitionOffsetMetadata)\n\tfor i := 0; i < n; i++ {\n\t\ttopic, err := pd.getString()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tm, err := pd.getArrayLength()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tt.Topics[topic] = make([]*PartitionOffsetMetadata, m)\n\n\t\tfor j := 0; j < m; j++ {\n\t\t\tpartitionOffsetMetadata := new(PartitionOffsetMetadata)\n\t\t\tif err := partitionOffsetMetadata.decode(pd, version); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tt.Topics[topic][j] = partitionOffsetMetadata\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (a *TxnOffsetCommitRequest) key() int16 {\n\treturn apiKeyTxnOffsetCommit\n}\n\nfunc (a *TxnOffsetCommitRequest) version() int16 {\n\treturn a.Version\n}\n\nfunc (a *TxnOffsetCommitRequest) headerVersion() int16 {\n\treturn 1\n}\n\nfunc (a *TxnOffsetCommitRequest) isValidVersion() bool {\n\treturn a.Version >= 0 && a.Version <= 2\n}\n\nfunc (a *TxnOffsetCommitRequest) requiredVersion() KafkaVersion {\n\tswitch a.Version {\n\tcase 2:\n\t\treturn V2_1_0_0\n\tcase 1:\n\t\treturn V2_0_0_0\n\tcase 0:\n\t\treturn V0_11_0_0\n\tdefault:\n\t\treturn V2_1_0_0\n\t}\n}\n\ntype PartitionOffsetMetadata struct {\n\t// Partition contains the index of the partition within the topic.\n\tPartition int32\n\t// Offset contains the message offset to be committed.\n\tOffset int64\n\t// LeaderEpoch contains the leader epoch of the last consumed record.\n\tLeaderEpoch int32\n\t// Metadata contains any associated metadata the client wants to keep.\n\tMetadata *string\n}\n\nfunc (p *PartitionOffsetMetadata) encode(pe packetEncoder, version int16) error {\n\tpe.putInt32(p.Partition)\n\tpe.putInt64(p.Offset)\n\n\tif version >= 2 {\n\t\tpe.putInt32(p.LeaderEpoch)\n\t}\n\n\tif err := pe.putNullableString(p.Metadata); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (p *PartitionOffsetMetadata) decode(pd packetDecoder, version int16) (err error) {\n\tif p.Partition, err = pd.getInt32(); err != nil {\n\t\treturn err\n\t}\n\tif p.Offset, err = pd.getInt64(); err != nil {\n\t\treturn err\n\t}\n\n\tif version >= 2 {\n\t\tif p.LeaderEpoch, err = pd.getInt32(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif p.Metadata, err = pd.getNullableString(); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "txn_offset_commit_request_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport \"testing\"\n\nvar (\n\ttxnOffsetCommitRequest = []byte{\n\t\t0, 3, 't', 'x', 'n',\n\t\t0, 7, 'g', 'r', 'o', 'u', 'p', 'i', 'd',\n\t\t0, 0, 0, 0, 0, 0, 31, 64, // producer ID\n\t\t0, 1, // producer epoch\n\t\t0, 0, 0, 1, // 1 topic\n\t\t0, 5, 't', 'o', 'p', 'i', 'c',\n\t\t0, 0, 0, 1, // 1 partition\n\t\t0, 0, 0, 2, // partition no 2\n\t\t0, 0, 0, 0, 0, 0, 0, 123,\n\t\t255, 255, // no meta data\n\t}\n\n\ttxnOffsetCommitRequestV2 = []byte{\n\t\t0, 3, 't', 'x', 'n',\n\t\t0, 7, 'g', 'r', 'o', 'u', 'p', 'i', 'd',\n\t\t0, 0, 0, 0, 0, 0, 31, 64, // producer ID\n\t\t0, 1, // producer epoch\n\t\t0, 0, 0, 1, // 1 topic\n\t\t0, 5, 't', 'o', 'p', 'i', 'c',\n\t\t0, 0, 0, 1, // 1 partition\n\t\t0, 0, 0, 2, // partition no 2\n\t\t0, 0, 0, 0, 0, 0, 0, 123,\n\t\t0, 0, 0, 9, // leader epoch\n\t\t255, 255, // no meta data\n\t}\n)\n\nfunc TestTxnOffsetCommitRequest(t *testing.T) {\n\treq := &TxnOffsetCommitRequest{\n\t\tTransactionalID: \"txn\",\n\t\tGroupID:         \"groupid\",\n\t\tProducerID:      8000,\n\t\tProducerEpoch:   1,\n\t\tTopics: map[string][]*PartitionOffsetMetadata{\n\t\t\t\"topic\": {{\n\t\t\t\tOffset:    123,\n\t\t\t\tPartition: 2,\n\t\t\t}},\n\t\t},\n\t}\n\n\ttestRequest(t, \"V0\", req, txnOffsetCommitRequest)\n}\n\nfunc TestTxnOffsetCommitRequestV2(t *testing.T) {\n\treq := &TxnOffsetCommitRequest{\n\t\tVersion:         2,\n\t\tTransactionalID: \"txn\",\n\t\tGroupID:         \"groupid\",\n\t\tProducerID:      8000,\n\t\tProducerEpoch:   1,\n\t\tTopics: map[string][]*PartitionOffsetMetadata{\n\t\t\t\"topic\": {{\n\t\t\t\tOffset:      123,\n\t\t\t\tPartition:   2,\n\t\t\t\tLeaderEpoch: 9,\n\t\t\t}},\n\t\t},\n\t}\n\n\ttestRequest(t, \"V2\", req, txnOffsetCommitRequestV2)\n}\n"
  },
  {
    "path": "txn_offset_commit_response.go",
    "content": "package sarama\n\nimport (\n\t\"time\"\n)\n\ntype TxnOffsetCommitResponse struct {\n\tVersion      int16\n\tThrottleTime time.Duration\n\tTopics       map[string][]*PartitionError\n}\n\nfunc (t *TxnOffsetCommitResponse) setVersion(v int16) {\n\tt.Version = v\n}\n\nfunc (t *TxnOffsetCommitResponse) encode(pe packetEncoder) error {\n\tpe.putDurationMs(t.ThrottleTime)\n\tif err := pe.putArrayLength(len(t.Topics)); err != nil {\n\t\treturn err\n\t}\n\n\tfor topic, e := range t.Topics {\n\t\tif err := pe.putString(topic); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := pe.putArrayLength(len(e)); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor _, partitionError := range e {\n\t\t\tif err := partitionError.encode(pe); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (t *TxnOffsetCommitResponse) decode(pd packetDecoder, version int16) (err error) {\n\tt.Version = version\n\tthrottleTime, err := pd.getInt32()\n\tif err != nil {\n\t\treturn err\n\t}\n\tt.ThrottleTime = time.Duration(throttleTime) * time.Millisecond\n\n\tn, err := pd.getArrayLength()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tt.Topics = make(map[string][]*PartitionError)\n\n\tfor i := 0; i < n; i++ {\n\t\ttopic, err := pd.getString()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tm, err := pd.getArrayLength()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tt.Topics[topic] = make([]*PartitionError, m)\n\n\t\tfor j := 0; j < m; j++ {\n\t\t\tt.Topics[topic][j] = new(PartitionError)\n\t\t\tif err := t.Topics[topic][j].decode(pd, version); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (a *TxnOffsetCommitResponse) key() int16 {\n\treturn apiKeyTxnOffsetCommit\n}\n\nfunc (a *TxnOffsetCommitResponse) version() int16 {\n\treturn a.Version\n}\n\nfunc (a *TxnOffsetCommitResponse) headerVersion() int16 {\n\treturn 0\n}\n\nfunc (a *TxnOffsetCommitResponse) isValidVersion() bool {\n\treturn a.Version >= 0 && a.Version <= 2\n}\n\nfunc (a *TxnOffsetCommitResponse) requiredVersion() KafkaVersion {\n\tswitch a.Version {\n\tcase 2:\n\t\treturn V2_1_0_0\n\tcase 1:\n\t\treturn V2_0_0_0\n\tcase 0:\n\t\treturn V0_11_0_0\n\tdefault:\n\t\treturn V2_1_0_0\n\t}\n}\n\nfunc (r *TxnOffsetCommitResponse) throttleTime() time.Duration {\n\treturn r.ThrottleTime\n}\n"
  },
  {
    "path": "txn_offset_commit_response_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nvar txnOffsetCommitResponse = []byte{\n\t0, 0, 0, 100,\n\t0, 0, 0, 1, // 1 topic\n\t0, 5, 't', 'o', 'p', 'i', 'c',\n\t0, 0, 0, 1, // 1 partition response\n\t0, 0, 0, 2, // partition number 2\n\t0, 47, // err\n}\n\nfunc TestTxnOffsetCommitResponse(t *testing.T) {\n\tresp := &TxnOffsetCommitResponse{\n\t\tThrottleTime: 100 * time.Millisecond,\n\t\tTopics: map[string][]*PartitionError{\n\t\t\t\"topic\": {{\n\t\t\t\tPartition: 2,\n\t\t\t\tErr:       ErrInvalidProducerEpoch,\n\t\t\t}},\n\t\t},\n\t}\n\n\ttestResponse(t, \"\", resp, txnOffsetCommitResponse)\n}\n"
  },
  {
    "path": "utils.go",
    "content": "package sarama\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"net\"\n\t\"regexp\"\n\t\"time\"\n)\n\nconst (\n\tdefaultRetryBackoff    = 100 * time.Millisecond\n\tdefaultRetryMaxBackoff = 1000 * time.Millisecond\n)\n\ntype none struct{}\n\n// make []int32 sortable so we can sort partition numbers\ntype int32Slice []int32\n\nfunc (slice int32Slice) Len() int {\n\treturn len(slice)\n}\n\nfunc (slice int32Slice) Less(i, j int) bool {\n\treturn slice[i] < slice[j]\n}\n\nfunc (slice int32Slice) Swap(i, j int) {\n\tslice[i], slice[j] = slice[j], slice[i]\n}\n\nfunc dupInt32Slice(input []int32) []int32 {\n\tret := make([]int32, 0, len(input))\n\tret = append(ret, input...)\n\treturn ret\n}\n\nfunc withRecover(fn func()) {\n\tdefer func() {\n\t\thandler := PanicHandler\n\t\tif handler != nil {\n\t\t\tif err := recover(); err != nil {\n\t\t\t\thandler(err)\n\t\t\t}\n\t\t}\n\t}()\n\n\tfn()\n}\n\nfunc safeAsyncClose(b *Broker) {\n\tgo withRecover(func() {\n\t\tif connected, _ := b.Connected(); connected {\n\t\t\tif err := b.Close(); err != nil {\n\t\t\t\tLogger.Println(\"Error closing broker\", b.ID(), \":\", err)\n\t\t\t}\n\t\t}\n\t})\n}\n\n// Encoder is a simple interface for any type that can be encoded as an array of bytes\n// in order to be sent as the key or value of a Kafka message. Length() is provided as an\n// optimization, and must return the same as len() on the result of Encode().\ntype Encoder interface {\n\tEncode() ([]byte, error)\n\tLength() int\n}\n\n// make strings and byte slices encodable for convenience so they can be used as keys\n// and/or values in kafka messages\n\n// StringEncoder implements the Encoder interface for Go strings so that they can be used\n// as the Key or Value in a ProducerMessage.\ntype StringEncoder string\n\nfunc (s StringEncoder) Encode() ([]byte, error) {\n\treturn []byte(s), nil\n}\n\nfunc (s StringEncoder) Length() int {\n\treturn len(s)\n}\n\n// ByteEncoder implements the Encoder interface for Go byte slices so that they can be used\n// as the Key or Value in a ProducerMessage.\ntype ByteEncoder []byte\n\nfunc (b ByteEncoder) Encode() ([]byte, error) {\n\treturn b, nil\n}\n\nfunc (b ByteEncoder) Length() int {\n\treturn len(b)\n}\n\n// bufConn wraps a net.Conn with a buffer for reads to reduce the number of\n// reads that trigger syscalls.\ntype bufConn struct {\n\tnet.Conn\n\tbuf *bufio.Reader\n}\n\nfunc newBufConn(conn net.Conn) *bufConn {\n\treturn &bufConn{\n\t\tConn: conn,\n\t\tbuf:  bufio.NewReader(conn),\n\t}\n}\n\nfunc (bc *bufConn) Read(b []byte) (n int, err error) {\n\treturn bc.buf.Read(b)\n}\n\n// KafkaVersion instances represent versions of the upstream Kafka broker.\ntype KafkaVersion struct {\n\t// it's a struct rather than just typing the array directly to make it opaque and stop people\n\t// generating their own arbitrary versions\n\tversion [4]uint\n}\n\nfunc newKafkaVersion(major, minor, veryMinor, patch uint) KafkaVersion {\n\treturn KafkaVersion{\n\t\tversion: [4]uint{major, minor, veryMinor, patch},\n\t}\n}\n\n// IsAtLeast return true if and only if the version it is called on is\n// greater than or equal to the version passed in:\n//\n//\tV1.IsAtLeast(V2) // false\n//\tV2.IsAtLeast(V1) // true\nfunc (v KafkaVersion) IsAtLeast(other KafkaVersion) bool {\n\tfor i := range v.version {\n\t\tif v.version[i] > other.version[i] {\n\t\t\treturn true\n\t\t} else if v.version[i] < other.version[i] {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// Effective constants defining the supported kafka versions.\nvar (\n\tV0_8_2_0  = newKafkaVersion(0, 8, 2, 0)\n\tV0_8_2_1  = newKafkaVersion(0, 8, 2, 1)\n\tV0_8_2_2  = newKafkaVersion(0, 8, 2, 2)\n\tV0_9_0_0  = newKafkaVersion(0, 9, 0, 0)\n\tV0_9_0_1  = newKafkaVersion(0, 9, 0, 1)\n\tV0_10_0_0 = newKafkaVersion(0, 10, 0, 0)\n\tV0_10_0_1 = newKafkaVersion(0, 10, 0, 1)\n\tV0_10_1_0 = newKafkaVersion(0, 10, 1, 0)\n\tV0_10_1_1 = newKafkaVersion(0, 10, 1, 1)\n\tV0_10_2_0 = newKafkaVersion(0, 10, 2, 0)\n\tV0_10_2_1 = newKafkaVersion(0, 10, 2, 1)\n\tV0_10_2_2 = newKafkaVersion(0, 10, 2, 2)\n\tV0_11_0_0 = newKafkaVersion(0, 11, 0, 0)\n\tV0_11_0_1 = newKafkaVersion(0, 11, 0, 1)\n\tV0_11_0_2 = newKafkaVersion(0, 11, 0, 2)\n\tV1_0_0_0  = newKafkaVersion(1, 0, 0, 0)\n\tV1_0_1_0  = newKafkaVersion(1, 0, 1, 0)\n\tV1_0_2_0  = newKafkaVersion(1, 0, 2, 0)\n\tV1_1_0_0  = newKafkaVersion(1, 1, 0, 0)\n\tV1_1_1_0  = newKafkaVersion(1, 1, 1, 0)\n\tV2_0_0_0  = newKafkaVersion(2, 0, 0, 0)\n\tV2_0_1_0  = newKafkaVersion(2, 0, 1, 0)\n\tV2_1_0_0  = newKafkaVersion(2, 1, 0, 0)\n\tV2_1_1_0  = newKafkaVersion(2, 1, 1, 0)\n\tV2_2_0_0  = newKafkaVersion(2, 2, 0, 0)\n\tV2_2_1_0  = newKafkaVersion(2, 2, 1, 0)\n\tV2_2_2_0  = newKafkaVersion(2, 2, 2, 0)\n\tV2_3_0_0  = newKafkaVersion(2, 3, 0, 0)\n\tV2_3_1_0  = newKafkaVersion(2, 3, 1, 0)\n\tV2_4_0_0  = newKafkaVersion(2, 4, 0, 0)\n\tV2_4_1_0  = newKafkaVersion(2, 4, 1, 0)\n\tV2_5_0_0  = newKafkaVersion(2, 5, 0, 0)\n\tV2_5_1_0  = newKafkaVersion(2, 5, 1, 0)\n\tV2_6_0_0  = newKafkaVersion(2, 6, 0, 0)\n\tV2_6_1_0  = newKafkaVersion(2, 6, 1, 0)\n\tV2_6_2_0  = newKafkaVersion(2, 6, 2, 0)\n\tV2_6_3_0  = newKafkaVersion(2, 6, 3, 0)\n\tV2_7_0_0  = newKafkaVersion(2, 7, 0, 0)\n\tV2_7_1_0  = newKafkaVersion(2, 7, 1, 0)\n\tV2_7_2_0  = newKafkaVersion(2, 7, 2, 0)\n\tV2_8_0_0  = newKafkaVersion(2, 8, 0, 0)\n\tV2_8_1_0  = newKafkaVersion(2, 8, 1, 0)\n\tV2_8_2_0  = newKafkaVersion(2, 8, 2, 0)\n\tV3_0_0_0  = newKafkaVersion(3, 0, 0, 0)\n\tV3_0_1_0  = newKafkaVersion(3, 0, 1, 0)\n\tV3_0_2_0  = newKafkaVersion(3, 0, 2, 0)\n\tV3_1_0_0  = newKafkaVersion(3, 1, 0, 0)\n\tV3_1_1_0  = newKafkaVersion(3, 1, 1, 0)\n\tV3_1_2_0  = newKafkaVersion(3, 1, 2, 0)\n\tV3_2_0_0  = newKafkaVersion(3, 2, 0, 0)\n\tV3_2_1_0  = newKafkaVersion(3, 2, 1, 0)\n\tV3_2_2_0  = newKafkaVersion(3, 2, 2, 0)\n\tV3_2_3_0  = newKafkaVersion(3, 2, 3, 0)\n\tV3_3_0_0  = newKafkaVersion(3, 3, 0, 0)\n\tV3_3_1_0  = newKafkaVersion(3, 3, 1, 0)\n\tV3_3_2_0  = newKafkaVersion(3, 3, 2, 0)\n\tV3_4_0_0  = newKafkaVersion(3, 4, 0, 0)\n\tV3_4_1_0  = newKafkaVersion(3, 4, 1, 0)\n\tV3_5_0_0  = newKafkaVersion(3, 5, 0, 0)\n\tV3_5_1_0  = newKafkaVersion(3, 5, 1, 0)\n\tV3_5_2_0  = newKafkaVersion(3, 5, 2, 0)\n\tV3_6_0_0  = newKafkaVersion(3, 6, 0, 0)\n\tV3_6_1_0  = newKafkaVersion(3, 6, 1, 0)\n\tV3_6_2_0  = newKafkaVersion(3, 6, 2, 0)\n\tV3_7_0_0  = newKafkaVersion(3, 7, 0, 0)\n\tV3_7_1_0  = newKafkaVersion(3, 7, 1, 0)\n\tV3_7_2_0  = newKafkaVersion(3, 7, 2, 0)\n\tV3_8_0_0  = newKafkaVersion(3, 8, 0, 0)\n\tV3_8_1_0  = newKafkaVersion(3, 8, 1, 0)\n\tV3_9_0_0  = newKafkaVersion(3, 9, 0, 0)\n\tV3_9_1_0  = newKafkaVersion(3, 9, 1, 0)\n\tV3_9_2_0  = newKafkaVersion(3, 9, 2, 0)\n\tV4_0_0_0  = newKafkaVersion(4, 0, 0, 0)\n\tV4_0_1_0  = newKafkaVersion(4, 0, 1, 0)\n\tV4_1_0_0  = newKafkaVersion(4, 1, 0, 0)\n\tV4_1_1_0  = newKafkaVersion(4, 1, 1, 0)\n\tV4_2_0_0  = newKafkaVersion(4, 2, 0, 0)\n\n\tSupportedVersions = []KafkaVersion{\n\t\tV0_8_2_0,\n\t\tV0_8_2_1,\n\t\tV0_8_2_2,\n\t\tV0_9_0_0,\n\t\tV0_9_0_1,\n\t\tV0_10_0_0,\n\t\tV0_10_0_1,\n\t\tV0_10_1_0,\n\t\tV0_10_1_1,\n\t\tV0_10_2_0,\n\t\tV0_10_2_1,\n\t\tV0_10_2_2,\n\t\tV0_11_0_0,\n\t\tV0_11_0_1,\n\t\tV0_11_0_2,\n\t\tV1_0_0_0,\n\t\tV1_0_1_0,\n\t\tV1_0_2_0,\n\t\tV1_1_0_0,\n\t\tV1_1_1_0,\n\t\tV2_0_0_0,\n\t\tV2_0_1_0,\n\t\tV2_1_0_0,\n\t\tV2_1_1_0,\n\t\tV2_2_0_0,\n\t\tV2_2_1_0,\n\t\tV2_2_2_0,\n\t\tV2_3_0_0,\n\t\tV2_3_1_0,\n\t\tV2_4_0_0,\n\t\tV2_4_1_0,\n\t\tV2_5_0_0,\n\t\tV2_5_1_0,\n\t\tV2_6_0_0,\n\t\tV2_6_1_0,\n\t\tV2_6_2_0,\n\t\tV2_6_3_0,\n\t\tV2_7_0_0,\n\t\tV2_7_1_0,\n\t\tV2_7_2_0,\n\t\tV2_8_0_0,\n\t\tV2_8_1_0,\n\t\tV2_8_2_0,\n\t\tV3_0_0_0,\n\t\tV3_0_1_0,\n\t\tV3_0_2_0,\n\t\tV3_1_0_0,\n\t\tV3_1_1_0,\n\t\tV3_1_2_0,\n\t\tV3_2_0_0,\n\t\tV3_2_1_0,\n\t\tV3_2_2_0,\n\t\tV3_2_3_0,\n\t\tV3_3_0_0,\n\t\tV3_3_1_0,\n\t\tV3_3_2_0,\n\t\tV3_4_0_0,\n\t\tV3_4_1_0,\n\t\tV3_5_0_0,\n\t\tV3_5_1_0,\n\t\tV3_5_2_0,\n\t\tV3_6_0_0,\n\t\tV3_6_1_0,\n\t\tV3_6_2_0,\n\t\tV3_7_0_0,\n\t\tV3_7_1_0,\n\t\tV3_7_2_0,\n\t\tV3_8_0_0,\n\t\tV3_8_1_0,\n\t\tV3_9_0_0,\n\t\tV3_9_1_0,\n\t\tV3_9_2_0,\n\t\tV4_0_0_0,\n\t\tV4_0_1_0,\n\t\tV4_1_0_0,\n\t\tV4_1_1_0,\n\t\tV4_2_0_0,\n\t}\n\tMinVersion     = V0_8_2_0\n\tMaxVersion     = V4_2_0_0\n\tDefaultVersion = V2_1_0_0\n\n\t// reduced set of protocol versions to matrix test\n\tfvtRangeVersions = []KafkaVersion{\n\t\tV0_8_2_2,\n\t\tV0_10_2_2,\n\t\tV1_0_2_0,\n\t\tV1_1_1_0,\n\t\tV2_0_1_0,\n\t\tV2_2_2_0,\n\t\tV2_4_1_0,\n\t\tV2_6_3_0,\n\t\tV2_8_2_0,\n\t\tV3_1_2_0,\n\t\tV3_3_2_0,\n\t\tV3_6_2_0,\n\t}\n)\n\nvar (\n\t// This regex validates that a string complies with the pre kafka 1.0.0 format for version strings, for example 0.11.0.3\n\tvalidPreKafka1Version = regexp.MustCompile(`^0\\.\\d+\\.\\d+\\.\\d+$`)\n\n\t// This regex validates that a string complies with the post Kafka 1.0.0 format, for example 1.0.0\n\tvalidPostKafka1Version = regexp.MustCompile(`^\\d+\\.\\d+\\.\\d+$`)\n)\n\n// ParseKafkaVersion parses and returns kafka version or error from a string\nfunc ParseKafkaVersion(s string) (KafkaVersion, error) {\n\tif len(s) < 5 {\n\t\treturn DefaultVersion, fmt.Errorf(\"invalid version `%s`\", s)\n\t}\n\tvar major, minor, veryMinor, patch uint\n\tvar err error\n\tif s[0] == '0' {\n\t\terr = scanKafkaVersion(s, validPreKafka1Version, \"0.%d.%d.%d\", [3]*uint{&minor, &veryMinor, &patch})\n\t} else {\n\t\terr = scanKafkaVersion(s, validPostKafka1Version, \"%d.%d.%d\", [3]*uint{&major, &minor, &veryMinor})\n\t}\n\tif err != nil {\n\t\treturn DefaultVersion, err\n\t}\n\treturn newKafkaVersion(major, minor, veryMinor, patch), nil\n}\n\nfunc scanKafkaVersion(s string, pattern *regexp.Regexp, format string, v [3]*uint) error {\n\tif !pattern.MatchString(s) {\n\t\treturn fmt.Errorf(\"invalid version `%s`\", s)\n\t}\n\t_, err := fmt.Sscanf(s, format, v[0], v[1], v[2])\n\treturn err\n}\n\nfunc (v KafkaVersion) String() string {\n\tif v.version[0] == 0 {\n\t\treturn fmt.Sprintf(\"0.%d.%d.%d\", v.version[1], v.version[2], v.version[3])\n\t}\n\n\treturn fmt.Sprintf(\"%d.%d.%d\", v.version[0], v.version[1], v.version[2])\n}\n\n// NewExponentialBackoff returns a function that implements an exponential backoff strategy with jitter.\n// It follows KIP-580, implementing the formula:\n// MIN(retry.backoff.max.ms, (retry.backoff.ms * 2**(failures - 1)) * random(0.8, 1.2))\n// This ensures retries start with `backoff` and exponentially increase until `maxBackoff`, with added jitter.\n// The behavior when `failures = 0` is not explicitly defined in KIP-580 and is left to implementation discretion.\n//\n// Example usage:\n//\n//\tbackoffFunc := sarama.NewExponentialBackoff(config.Producer.Retry.Backoff, 2*time.Second)\n//\tconfig.Producer.Retry.BackoffFunc = backoffFunc\nfunc NewExponentialBackoff(backoff time.Duration, maxBackoff time.Duration) func(retries, maxRetries int) time.Duration {\n\tif backoff <= 0 {\n\t\tbackoff = defaultRetryBackoff\n\t}\n\tif maxBackoff <= 0 {\n\t\tmaxBackoff = defaultRetryMaxBackoff\n\t}\n\n\tif backoff > maxBackoff {\n\t\tLogger.Println(\"Warning: backoff is greater than maxBackoff, using maxBackoff instead.\")\n\t\tbackoff = maxBackoff\n\t}\n\n\treturn func(retries, maxRetries int) time.Duration {\n\t\tif retries <= 0 {\n\t\t\treturn backoff\n\t\t}\n\n\t\tcalculatedBackoff := backoff * time.Duration(1<<(retries-1))\n\t\tjitter := 0.8 + 0.4*rand.Float64()\n\t\tcalculatedBackoff = time.Duration(float64(calculatedBackoff) * jitter)\n\n\t\treturn min(calculatedBackoff, maxBackoff)\n\t}\n}\n"
  },
  {
    "path": "utils_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestVersionCompare(t *testing.T) {\n\tif V0_8_2_0.IsAtLeast(V0_8_2_1) {\n\t\tt.Error(\"0.8.2.0 >= 0.8.2.1\")\n\t}\n\tif !V0_8_2_1.IsAtLeast(V0_8_2_0) {\n\t\tt.Error(\"! 0.8.2.1 >= 0.8.2.0\")\n\t}\n\tif !V0_8_2_0.IsAtLeast(V0_8_2_0) {\n\t\tt.Error(\"! 0.8.2.0 >= 0.8.2.0\")\n\t}\n\tif !V0_9_0_0.IsAtLeast(V0_8_2_1) {\n\t\tt.Error(\"! 0.9.0.0 >= 0.8.2.1\")\n\t}\n\tif V0_8_2_1.IsAtLeast(V0_10_0_0) {\n\t\tt.Error(\"0.8.2.1 >= 0.10.0.0\")\n\t}\n\tif !V1_0_0_0.IsAtLeast(V0_9_0_0) {\n\t\tt.Error(\"! 1.0.0.0 >= 0.9.0.0\")\n\t}\n\tif V0_9_0_0.IsAtLeast(V1_0_0_0) {\n\t\tt.Error(\"0.9.0.0 >= 1.0.0.0\")\n\t}\n}\n\nfunc TestVersionParsing(t *testing.T) {\n\tvalidVersions := []string{\n\t\t\"0.8.2.0\",\n\t\t\"0.8.2.1\",\n\t\t\"0.8.2.2\",\n\t\t\"0.9.0.0\",\n\t\t\"0.9.0.1\",\n\t\t\"0.10.0.0\",\n\t\t\"0.10.0.1\",\n\t\t\"0.10.1.0\",\n\t\t\"0.10.1.1\",\n\t\t\"0.10.2.0\",\n\t\t\"0.10.2.1\",\n\t\t\"0.10.2.2\",\n\t\t\"0.11.0.0\",\n\t\t\"0.11.0.1\",\n\t\t\"0.11.0.2\",\n\t\t\"1.0.0\",\n\t\t\"1.0.1\",\n\t\t\"1.0.2\",\n\t\t\"1.1.0\",\n\t\t\"1.1.1\",\n\t\t\"2.0.0\",\n\t\t\"2.0.1\",\n\t\t\"2.1.0\",\n\t\t\"2.1.1\",\n\t\t\"2.2.0\",\n\t\t\"2.2.1\",\n\t\t\"2.2.2\",\n\t\t\"2.3.0\",\n\t\t\"2.3.1\",\n\t\t\"2.4.0\",\n\t\t\"2.4.1\",\n\t\t\"2.5.0\",\n\t\t\"2.5.1\",\n\t\t\"2.6.0\",\n\t\t\"2.6.1\",\n\t\t\"2.6.2\",\n\t\t\"2.6.3\",\n\t\t\"2.7.0\",\n\t\t\"2.7.1\",\n\t\t\"2.7.2\",\n\t\t\"2.8.0\",\n\t\t\"2.8.1\",\n\t\t\"3.0.0\",\n\t\t\"3.0.1\",\n\t\t\"3.1.0\",\n\t\t\"3.1.1\",\n\t\t\"3.2.0\",\n\t}\n\tfor _, s := range validVersions {\n\t\tv, err := ParseKafkaVersion(s)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"could not parse valid version %s: %s\", s, err)\n\t\t}\n\t\tif v.String() != s {\n\t\t\tt.Errorf(\"version %s != %s\", v.String(), s)\n\t\t}\n\t}\n\n\tinvalidVersions := []string{\"0.8.2-4\", \"0.8.20\", \"1.19.0.0\", \"1.0.x\"}\n\tfor _, s := range invalidVersions {\n\t\tif _, err := ParseKafkaVersion(s); err == nil {\n\t\t\tt.Errorf(\"invalid version %s parsed without error\", s)\n\t\t}\n\t}\n}\n\nfunc TestExponentialBackoffValidCases(t *testing.T) {\n\ttestCases := []struct {\n\t\tretries            int\n\t\tmaxRetries         int\n\t\tminBackoff         time.Duration\n\t\tmaxBackoffExpected time.Duration\n\t}{\n\t\t{1, 5, 80 * time.Millisecond, 120 * time.Millisecond},\n\t\t{3, 5, 320 * time.Millisecond, 480 * time.Millisecond},\n\t\t{5, 5, 1280 * time.Millisecond, 1920 * time.Millisecond},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tbackoffFunc := NewExponentialBackoff(100*time.Millisecond, 2*time.Second)\n\t\tbackoff := backoffFunc(tc.retries, tc.maxRetries)\n\t\tif backoff < tc.minBackoff || backoff > tc.maxBackoffExpected {\n\t\t\tt.Errorf(\"backoff(%d, %d): expected between %v and %v, got %v\", tc.retries, tc.maxRetries, tc.minBackoff, tc.maxBackoffExpected, backoff)\n\t\t}\n\t}\n}\n\nfunc TestExponentialBackoffDefaults(t *testing.T) {\n\ttestCases := []struct {\n\t\tbackoff    time.Duration\n\t\tmaxBackoff time.Duration\n\t}{\n\t\t{-100 * time.Millisecond, 2 * time.Second},\n\t\t{100 * time.Millisecond, -2 * time.Second},\n\t\t{-100 * time.Millisecond, -2 * time.Second},\n\t\t{0 * time.Millisecond, 2 * time.Second},\n\t\t{100 * time.Millisecond, 0 * time.Second},\n\t\t{0 * time.Millisecond, 0 * time.Second},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tbackoffFunc := NewExponentialBackoff(tc.backoff, tc.maxBackoff)\n\t\tbackoff := backoffFunc(2, 5)\n\t\tif backoff < defaultRetryBackoff || backoff > defaultRetryMaxBackoff {\n\t\t\tt.Errorf(\"backoff(%v, %v): expected between %v and %v, got %v\",\n\t\t\t\ttc.backoff, tc.maxBackoff, defaultRetryBackoff, defaultRetryMaxBackoff, backoff)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "version.go",
    "content": "package sarama\n\nimport (\n\t\"reflect\"\n\t\"runtime/debug\"\n\t\"sync\"\n)\n\nvar (\n\tv     string\n\tvOnce sync.Once\n)\n\nfunc version() string {\n\tvOnce.Do(func() {\n\t\t// Determine our package name without hardcoding a string\n\t\ttype getPackageName struct{}\n\t\tthisPackagePath := reflect.TypeFor[getPackageName]().PkgPath()\n\n\t\tbi, ok := debug.ReadBuildInfo()\n\t\tif ok {\n\t\t\tfor _, dep := range bi.Deps {\n\t\t\t\tif dep.Path == thisPackagePath {\n\t\t\t\t\tv = dep.Version\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif v == \"\" || v == \"(devel)\" {\n\t\t\t// if we can't read a go module version then they're using a git\n\t\t\t// clone or vendored module so all we can do is report \"dev\" for\n\t\t\t// the version to make a valid ApiVersions request\n\t\t\tv = \"dev\"\n\t\t}\n\t})\n\treturn v\n}\n"
  },
  {
    "path": "zstd.go",
    "content": "package sarama\n\nimport (\n\t\"sync\"\n\n\t\"github.com/klauspost/compress/zstd\"\n)\n\n// zstdMaxBufferedEncoders maximum number of not-in-use zstd encoders\n// If the pool of encoders is exhausted then new encoders will be created on the fly\nconst zstdMaxBufferedEncoders = 1\n\ntype ZstdEncoderParams struct {\n\tLevel int\n}\ntype ZstdDecoderParams struct {\n}\n\nvar zstdDecMap sync.Map\n\nvar zstdAvailableEncoders sync.Map\n\nfunc getZstdEncoderChannel(params ZstdEncoderParams) chan *zstd.Encoder {\n\tif c, ok := zstdAvailableEncoders.Load(params); ok {\n\t\treturn c.(chan *zstd.Encoder)\n\t}\n\tc, _ := zstdAvailableEncoders.LoadOrStore(params, make(chan *zstd.Encoder, zstdMaxBufferedEncoders))\n\treturn c.(chan *zstd.Encoder)\n}\n\nfunc getZstdEncoder(params ZstdEncoderParams) *zstd.Encoder {\n\tselect {\n\tcase enc := <-getZstdEncoderChannel(params):\n\t\treturn enc\n\tdefault:\n\t\tencoderLevel := zstd.SpeedDefault\n\t\tif params.Level != CompressionLevelDefault {\n\t\t\tencoderLevel = zstd.EncoderLevelFromZstd(params.Level)\n\t\t}\n\t\tzstdEnc, _ := zstd.NewWriter(nil, zstd.WithZeroFrames(true),\n\t\t\tzstd.WithEncoderLevel(encoderLevel),\n\t\t\tzstd.WithEncoderConcurrency(1))\n\t\treturn zstdEnc\n\t}\n}\n\nfunc releaseEncoder(params ZstdEncoderParams, enc *zstd.Encoder) {\n\tselect {\n\tcase getZstdEncoderChannel(params) <- enc:\n\tdefault:\n\t}\n}\n\nfunc getDecoder(params ZstdDecoderParams) *zstd.Decoder {\n\tif ret, ok := zstdDecMap.Load(params); ok {\n\t\treturn ret.(*zstd.Decoder)\n\t}\n\t// It's possible to race and create multiple new readers.\n\t// Only one will survive GC after use.\n\tzstdDec, _ := zstd.NewReader(nil, zstd.WithDecoderConcurrency(0))\n\tzstdDecMap.Store(params, zstdDec)\n\treturn zstdDec\n}\n\nfunc zstdDecompress(params ZstdDecoderParams, dst, src []byte) ([]byte, error) {\n\treturn getDecoder(params).DecodeAll(src, dst)\n}\n\nfunc zstdCompress(params ZstdEncoderParams, dst, src []byte) ([]byte, error) {\n\tenc := getZstdEncoder(params)\n\tout := enc.EncodeAll(src, dst)\n\treleaseEncoder(params, enc)\n\treturn out, nil\n}\n"
  },
  {
    "path": "zstd_test.go",
    "content": "//go:build !functional\n\npackage sarama\n\nimport (\n\t\"runtime\"\n\t\"testing\"\n)\n\nfunc BenchmarkZstdMemoryConsumption(b *testing.B) {\n\tparams := ZstdEncoderParams{Level: 9}\n\tbuf := make([]byte, 1024*1024)\n\tfor i := 0; i < len(buf); i++ {\n\t\tbuf[i] = byte((i / 256) + (i * 257))\n\t}\n\n\tcpus := 96\n\n\tgomaxprocsBackup := runtime.GOMAXPROCS(cpus)\n\tb.ReportAllocs()\n\tfor b.Loop() {\n\t\tfor j := 0; j < 2*cpus; j++ {\n\t\t\t_, _ = zstdCompress(params, nil, buf)\n\t\t}\n\t\t// drain the buffered encoder\n\t\tgetZstdEncoder(params)\n\t\t// previously this would be achieved with\n\t\t// zstdEncMap.Delete(params)\n\t}\n\truntime.GOMAXPROCS(gomaxprocsBackup)\n}\n"
  }
]