From 239fff7b350f70d7d2883860dcb29aae35deaac6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20=C5=9Awi=C4=99cki?= Date: Fri, 31 Oct 2025 23:38:19 +0100 Subject: [PATCH 01/10] Cleanup repository leaving only the datastore and common parts. Things that were removed: * Docker images * Executables * Blenc code * CinodeFS code * Datatore factory * Filesystem-based datastore * Web-based datastore * Multisource datastore --- .dockerignore | 3 - .github/workflows/images.yml | 235 ----- .github/workflows/tests.yml | 2 - README.md | 8 +- build/docker/Dockerfile.public_node | 42 - .../Dockerfile.static_datastore_builder | 43 - build/docker/Dockerfile.web_proxy | 42 - cmd/public_node/main.go | 30 - cmd/static_datastore_builder/main.go | 29 - cmd/web_proxy/main.go | 30 - go.mod | 192 +--- go.sum | 978 +----------------- pkg/blenc/datastore.go | 108 -- pkg/blenc/datastore_dynamic_link.go | 141 --- pkg/blenc/datastore_dynamic_link_test.go | 182 ---- pkg/blenc/datastore_static.go | 152 --- pkg/blenc/datastore_static_test.go | 309 ------ pkg/blenc/interface.go | 89 -- pkg/blenc/interface_test.go | 341 ------ pkg/blobtypes/list.go | 4 +- pkg/blobtypes/list_test.go | 4 +- pkg/cinodefs/cinodefs_interface.go | 472 --------- pkg/cinodefs/cinodefs_interface_bb_test.go | 949 ----------------- pkg/cinodefs/cinodefs_options.go | 168 --- pkg/cinodefs/cinodefs_options_bb_test.go | 114 -- pkg/cinodefs/cinodefs_traverse.go | 72 -- pkg/cinodefs/context.go | 164 --- pkg/cinodefs/entrypoint.go | 138 --- pkg/cinodefs/entrypoint_bb_test.go | 77 -- pkg/cinodefs/entrypoint_options.go | 43 - pkg/cinodefs/httphandler/http.go | 131 --- pkg/cinodefs/httphandler/http_test.go | 305 ------ pkg/cinodefs/node.go | 81 -- pkg/cinodefs/node_directory.go | 238 ----- pkg/cinodefs/node_file.go | 60 -- pkg/cinodefs/node_link.go | 127 --- pkg/cinodefs/node_unloaded.go | 135 --- pkg/cinodefs/protobuf/protobuf.pb.go | 485 --------- pkg/cinodefs/protobuf/protobuf.proto | 48 - pkg/cinodefs/uploader/directory.go | 219 ---- pkg/cinodefs/uploader/directory_test.go | 318 ------ pkg/cinodefs/uploader/templates/dir.html | 111 -- pkg/cinodefs/writerinfo.go | 83 -- pkg/cinodefs/writerinfo_bb_test.go | 42 - pkg/cmd/cinodewebproxy/integration_test.go | 160 --- pkg/cmd/cinodewebproxy/root.go | 176 ---- pkg/cmd/cinodewebproxy/root_test.go | 310 ------ pkg/cmd/publicnode/root.go | 192 ---- pkg/cmd/publicnode/root_test.go | 213 ---- pkg/cmd/staticdatastore/compile.go | 248 ----- pkg/cmd/staticdatastore/root.go | 46 - .../staticdatastore/static_datastore_test.go | 409 -------- pkg/datastore/datastore.go | 32 +- pkg/datastore/datastore_dynamic_link.go | 6 +- pkg/datastore/datastore_static.go | 8 +- pkg/datastore/datastore_test.go | 19 +- pkg/datastore/factory.go | 68 -- pkg/datastore/factory_test.go | 77 -- pkg/datastore/interface.go | 4 +- pkg/datastore/interface_test.go | 55 - pkg/datastore/interface_testsuite.go | 8 +- pkg/datastore/multisource/multi_source.go | 245 ----- .../multisource/multi_source_options.go | 50 - .../multisource/multi_source_options_test.go | 50 - .../multisource/multi_source_test.go | 289 ------ pkg/datastore/storage.go | 4 +- pkg/datastore/storage_filesystem.go | 168 --- pkg/datastore/storage_filesystem_test.go | 129 --- pkg/datastore/storage_memory.go | 6 +- pkg/datastore/storage_memory_test.go | 7 + pkg/datastore/storage_raw_filesystem.go | 117 --- pkg/datastore/storage_test.go | 8 +- pkg/datastore/testutils/generate/main.go | 8 +- .../testutils/generate/tesblobs.go.tpl | 4 +- pkg/datastore/testutils/testblobs.go | 4 +- pkg/datastore/testutils/testutils.go | 6 +- pkg/datastore/webcommon.go | 59 -- pkg/datastore/webconnector.go | 249 ----- pkg/datastore/webconnector_test.go | 170 --- pkg/datastore/webinterface.go | 261 ----- pkg/datastore/webinterface_test.go | 247 ----- pkg/internal/blobtypes/dynamiclink/public.go | 8 +- .../blobtypes/dynamiclink/public_test.go | 6 +- .../blobtypes/dynamiclink/publisher.go | 6 +- .../blobtypes/dynamiclink/publisher_test.go | 2 +- .../blobtypes/dynamiclink/vectors_test.go | 2 +- .../utilities/cipherfactory/cipher_factory.go | 2 +- .../cipherfactory/cipher_factory_test.go | 2 +- .../utilities/cipherfactory/generator.go | 4 +- .../utilities/cipherfactory/generator_test.go | 4 +- .../utilities/securefifo/secure_fifo.go | 165 --- .../utilities/securefifo/secure_fifo_test.go | 113 -- .../hashvalidatingreader_test.go | 2 +- .../validatingreader/oneofcheckreader_test.go | 2 +- pkg/utilities/golang/test_utils.go | 28 - pkg/utilities/golang/test_utils_test.go | 33 - pkg/utilities/httpserver/httpserver.go | 119 --- .../httpserver/httpserver_nosignal_test.go | 26 - .../httpserver/httpserver_signal_test.go | 30 - pkg/utilities/httpserver/httpserver_test.go | 209 ---- testvectors/testblobs/base.go | 106 -- testvectors/testblobs/dynamiclink.go | 56 - 102 files changed, 88 insertions(+), 12493 deletions(-) delete mode 100644 .dockerignore delete mode 100644 .github/workflows/images.yml delete mode 100644 build/docker/Dockerfile.public_node delete mode 100644 build/docker/Dockerfile.static_datastore_builder delete mode 100644 build/docker/Dockerfile.web_proxy delete mode 100644 cmd/public_node/main.go delete mode 100644 cmd/static_datastore_builder/main.go delete mode 100644 cmd/web_proxy/main.go delete mode 100644 pkg/blenc/datastore.go delete mode 100644 pkg/blenc/datastore_dynamic_link.go delete mode 100644 pkg/blenc/datastore_dynamic_link_test.go delete mode 100644 pkg/blenc/datastore_static.go delete mode 100644 pkg/blenc/datastore_static_test.go delete mode 100644 pkg/blenc/interface.go delete mode 100644 pkg/blenc/interface_test.go delete mode 100644 pkg/cinodefs/cinodefs_interface.go delete mode 100644 pkg/cinodefs/cinodefs_interface_bb_test.go delete mode 100644 pkg/cinodefs/cinodefs_options.go delete mode 100644 pkg/cinodefs/cinodefs_options_bb_test.go delete mode 100644 pkg/cinodefs/cinodefs_traverse.go delete mode 100644 pkg/cinodefs/context.go delete mode 100644 pkg/cinodefs/entrypoint.go delete mode 100644 pkg/cinodefs/entrypoint_bb_test.go delete mode 100644 pkg/cinodefs/entrypoint_options.go delete mode 100644 pkg/cinodefs/httphandler/http.go delete mode 100644 pkg/cinodefs/httphandler/http_test.go delete mode 100644 pkg/cinodefs/node.go delete mode 100644 pkg/cinodefs/node_directory.go delete mode 100644 pkg/cinodefs/node_file.go delete mode 100644 pkg/cinodefs/node_link.go delete mode 100644 pkg/cinodefs/node_unloaded.go delete mode 100644 pkg/cinodefs/protobuf/protobuf.pb.go delete mode 100644 pkg/cinodefs/protobuf/protobuf.proto delete mode 100644 pkg/cinodefs/uploader/directory.go delete mode 100644 pkg/cinodefs/uploader/directory_test.go delete mode 100644 pkg/cinodefs/uploader/templates/dir.html delete mode 100644 pkg/cinodefs/writerinfo.go delete mode 100644 pkg/cinodefs/writerinfo_bb_test.go delete mode 100644 pkg/cmd/cinodewebproxy/integration_test.go delete mode 100644 pkg/cmd/cinodewebproxy/root.go delete mode 100644 pkg/cmd/cinodewebproxy/root_test.go delete mode 100644 pkg/cmd/publicnode/root.go delete mode 100644 pkg/cmd/publicnode/root_test.go delete mode 100644 pkg/cmd/staticdatastore/compile.go delete mode 100644 pkg/cmd/staticdatastore/root.go delete mode 100644 pkg/cmd/staticdatastore/static_datastore_test.go delete mode 100644 pkg/datastore/factory.go delete mode 100644 pkg/datastore/factory_test.go delete mode 100644 pkg/datastore/interface_test.go delete mode 100644 pkg/datastore/multisource/multi_source.go delete mode 100644 pkg/datastore/multisource/multi_source_options.go delete mode 100644 pkg/datastore/multisource/multi_source_options_test.go delete mode 100644 pkg/datastore/multisource/multi_source_test.go delete mode 100644 pkg/datastore/storage_filesystem.go delete mode 100644 pkg/datastore/storage_filesystem_test.go delete mode 100644 pkg/datastore/storage_raw_filesystem.go delete mode 100644 pkg/datastore/webcommon.go delete mode 100644 pkg/datastore/webconnector.go delete mode 100644 pkg/datastore/webconnector_test.go delete mode 100644 pkg/datastore/webinterface.go delete mode 100644 pkg/datastore/webinterface_test.go delete mode 100644 pkg/internal/utilities/securefifo/secure_fifo.go delete mode 100644 pkg/internal/utilities/securefifo/secure_fifo_test.go delete mode 100644 pkg/utilities/golang/test_utils.go delete mode 100644 pkg/utilities/golang/test_utils_test.go delete mode 100644 pkg/utilities/httpserver/httpserver.go delete mode 100644 pkg/utilities/httpserver/httpserver_nosignal_test.go delete mode 100644 pkg/utilities/httpserver/httpserver_signal_test.go delete mode 100644 pkg/utilities/httpserver/httpserver_test.go delete mode 100644 testvectors/testblobs/base.go delete mode 100644 testvectors/testblobs/dynamiclink.go diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index e75e718..0000000 --- a/.dockerignore +++ /dev/null @@ -1,3 +0,0 @@ -./build -./.vscode -./.github diff --git a/.github/workflows/images.yml b/.github/workflows/images.yml deleted file mode 100644 index 818c850..0000000 --- a/.github/workflows/images.yml +++ /dev/null @@ -1,235 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -# GitHub recommends pinning actions to a commit SHA. -# To get a newer version, you will need to update the SHA. -# You can also reference a tag or branch, but the action may change without warning. - -name: Create and publish a Docker images - -on: - push: {} - -env: - REGISTRY: ghcr.io - IMAGE_BASE_NAME: ${{ github.repository_owner }} - -jobs: - build-and-push-image: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - strategy: - matrix: - app: - - name: web_proxy - - name: static_datastore_builder - - name: public_node - env: - OUTPUTS_DIR: /tmp/outputs - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Log in to the Container registry - uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - id: buildx - uses: docker/setup-buildx-action@v3 - - - name: Docker meta - id: meta - uses: docker/metadata-action@v5 - with: - images: | - "${{ env.REGISTRY }}/${{ env.IMAGE_BASE_NAME }}/${{ matrix.app.name }}" - tags: | - type=ref,event=branch,prefix=branch- - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - - - name: Build and push - id: build_and_push - uses: docker/build-push-action@v6 - with: - context: . - file: build/docker/Dockerfile.${{ matrix.app.name }} - platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/riscv64 - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - - - name: Prepare artifacts - run: | - mkdir -p "$OUTPUTS_DIR" - jq -c '{ - "${{ matrix.app.name }}": ( - "${{ env.REGISTRY }}/${{ env.IMAGE_BASE_NAME }}/${{ matrix.app.name }}@" + .["containerimage.digest"] - ) - }' <> "$OUTPUTS_DIR/image" - ${{ steps.build_and_push.outputs.metadata }} - EOF - - - name: Upload artifacts - uses: actions/upload-artifact@v4 - with: - name: image_${{ matrix.app.name }} - path: ${{ env.OUTPUTS_DIR }}/image - - images: - runs-on: ubuntu-latest - permissions: - contents: read - needs: build-and-push-image - env: - OUTPUTS_DIR: /tmp/outputs - outputs: - images: ${{ steps.images.outputs.images }} - - steps: - - uses: actions/download-artifact@v4 - with: - path: /tmp/outputs - pattern: image_* - - run: ls /tmp/outputs || true - - run: cat /tmp/outputs/*/image || true - - id: images - run: echo "images=$( cat /tmp/outputs/*/image | jq -sc add )" >> "$GITHUB_OUTPUT" - - test-docker-images: - runs-on: ubuntu-latest - permissions: - contents: read - packages: read - needs: images - - strategy: - matrix: - arch: - - linux/amd64 - - linux/arm/v6 - - linux/arm/v7 - - linux/arm64 - - linux/riscv64 - - env: - IMG_STATIC_DATASTORE_BUILDER: ${{ fromJSON(needs.images.outputs.images).static_datastore_builder }} - IMG_PUBLIC_NODE: ${{ fromJSON(needs.images.outputs.images).public_node }} - IMG_WEB_PROXY: ${{ fromJSON(needs.images.outputs.images).web_proxy }} - NETWORK_NAME: "cinode/${{ matrix.arch }}" - - steps: - - name: Dump docker images info - run: | - jq . < /tmp/cinode/compile_result.json - - - name: Create docker network - run: | - docker network create "$NETWORK_NAME" - - - name: Run public datastore node - run: | - docker run \ - --platform "${{ matrix.arch }}" \ - --detach \ - --read-only \ - --network "$NETWORK_NAME" \ - --volume "/tmp/cinode/encrypted:/data:ro" \ - --env CINODE_MAIN_DATASTORE=/data \ - --name datastore \ - "$IMG_PUBLIC_NODE" - - - name: Run web proxy - run: | - docker run \ - --platform "${{ matrix.arch }}" \ - --detach \ - --read-only \ - --network "$NETWORK_NAME" \ - --env CINODE_ENTRYPOINT="$( cat /tmp/cinode/compile_result.json | jq -r .entrypoint )" \ - --env CINODE_ADDITIONAL_DATASTORE_1="http://datastore:8080/" \ - --name web_proxy \ - "$IMG_WEB_PROXY" - - - name: Inspect docker environment - run: docker ps - - - name: Check if can fetch files from cinode proxy - run: | - docker run \ - --rm \ - --interactive \ - --network "$NETWORK_NAME" \ - --read-only \ - --mount type=tmpfs,destination=/tmp \ - --volume "$(pwd)/testvectors:/data:ro" \ - --user $(id -u ${USER}):$(id -g ${USER}) \ - curlimages/curl \ - sh \ - -c ' - set -euo pipefail - cd /data - while read f; do - FILE_HASH="$( cat "$f" | sha256sum )" - WEB_HASH="$( curl -s "http://web_proxy:8080/${f}" | sha256sum )" - echo "${FILE_HASH} ${f}" - diff <( echo "$FILE_HASH" ) <( echo "$WEB_HASH" ) - done < <( find . -type f -print ) - ' - - - name: Dump datastore logs - if: always() - run: docker logs datastore - - - name: Dump web_proxy logs - if: always() - run: docker logs web_proxy - - - name: Inspect docker environment - if: always() - run: docker ps diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0e6fa12..8dfb0bb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -41,9 +41,7 @@ jobs: ${{ matrix.env.coverage && '-coverprofile=profile.cov' }} \ $( go list ./... \ - | grep -v "${PREFIX}/cmd" \ | grep -v "${PREFIX}/testvectors" \ - | grep -v "${PREFIX}/pkg/cinodefs/protobuf" \ | grep -v "${PREFIX}/pkg/datastore/testutils/generate" \ ) continue-on-error: ${{ matrix.env['continue-on-error'] }} diff --git a/README.md b/README.md index c6b6773..23436f7 100644 --- a/README.md +++ b/README.md @@ -1 +1,7 @@ -Cinode - prototype implementation in [go](https://golang.org/) language. +# Cinode - prototype implementation in [go](https://golang.org/) language + +Core datastore module: + +- Definition of datastore interfaces +- In-memory datastore implementation +- Test suite for datastore implementation conformance test diff --git a/build/docker/Dockerfile.public_node b/build/docker/Dockerfile.public_node deleted file mode 100644 index 98834e8..0000000 --- a/build/docker/Dockerfile.public_node +++ /dev/null @@ -1,42 +0,0 @@ -# syntax=docker/dockerfile:1 -ARG GO_VERSION=1.25 - -# Base image with go compiler and tested source code -FROM --platform=$BUILDPLATFORM docker.io/library/golang:${GO_VERSION} AS build - -# Compile and test with non-root user -RUN useradd -ms /bin/bash go -USER go -RUN git config --global --add safe.directory /home/go/app - -# Fetch and verify dependencies -WORKDIR /home/go/app -COPY go.mod go.sum ./ -RUN go mod download -RUN go mod verify - -# Bring in and test the source code -COPY . . -RUN go vet -v ./... -RUN go test -v ./... - -# Build binary -ENV CGO_ENABLED=0 -ARG TARGETOS TARGETARCH -RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} GOARM=${TARGETVARIANT#v} \ - go build \ - -v \ - -ldflags="-w -s" \ - -o "/home/go/public_node" \ - "./cmd/public_node" - -# Force distroless base to use current platform (most likely linux/amd64) -# which is needed since distroless/static is not available for linux/arm/v6 -FROM --platform=$BUILDPLATFORM gcr.io/distroless/static AS distroless - -FROM scratch -COPY --from=distroless / / -COPY --from=build /home/go/public_node /usr/sbin/public_node -USER nonroot:nonroot -EXPOSE 8080 -ENTRYPOINT [ "/usr/sbin/public_node" ] diff --git a/build/docker/Dockerfile.static_datastore_builder b/build/docker/Dockerfile.static_datastore_builder deleted file mode 100644 index dc9b4e4..0000000 --- a/build/docker/Dockerfile.static_datastore_builder +++ /dev/null @@ -1,43 +0,0 @@ -# syntax=docker/dockerfile:1 -ARG GO_VERSION=1.25 - -# Base image with go compiler and tested source code -FROM --platform=$BUILDPLATFORM docker.io/library/golang:${GO_VERSION} AS build - -# Compile and test with non-root user -RUN useradd -ms /bin/bash go -USER go -RUN git config --global --add safe.directory /home/go/app - -# Fetch and verify dependencies -WORKDIR /home/go/app -COPY go.mod go.sum ./ -RUN go mod download -RUN go mod verify - -# Bring in and test the source code -COPY . . -RUN go vet -v ./... -RUN go test -v ./... - -# Build binary -ENV CGO_ENABLED=0 -ARG TARGETOS TARGETARCH -RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} GOARM=${TARGETVARIANT#v} \ - go build \ - -v \ - -ldflags="-w -s" \ - -o "/home/go/static_datastore_builder" \ - "./cmd/static_datastore_builder" - -FROM alpine AS base-amd64 -FROM alpine AS base-arm -FROM alpine AS base-arm64 -FROM riscv64/alpine AS base-riscv64 - -FROM base-${TARGETARCH} -RUN adduser --disabled-password --gecos "" --home="/cinode" cinode -WORKDIR /cinode -USER cinode:cinode -COPY --from=build /home/go/static_datastore_builder /usr/sbin/static_datastore_builder -ENTRYPOINT [ "/usr/sbin/static_datastore_builder" ] diff --git a/build/docker/Dockerfile.web_proxy b/build/docker/Dockerfile.web_proxy deleted file mode 100644 index fc5da19..0000000 --- a/build/docker/Dockerfile.web_proxy +++ /dev/null @@ -1,42 +0,0 @@ -# syntax=docker/dockerfile:1 -ARG GO_VERSION=1.25 - -# Base image with go compiler and tested source code -FROM --platform=$BUILDPLATFORM docker.io/library/golang:${GO_VERSION} AS build - -# Compile and test with non-root user -RUN useradd -ms /bin/bash go -USER go -RUN git config --global --add safe.directory /home/go/app - -# Fetch and verify dependencies -WORKDIR /home/go/app -COPY go.mod go.sum ./ -RUN go mod download -RUN go mod verify - -# Bring in and test the source code -COPY . . -RUN go vet -v ./... -RUN go test -v ./... - -# Build binary -ENV CGO_ENABLED=0 -ARG TARGETOS TARGETARCH -RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} GOARM=${TARGETVARIANT#v} \ - go build \ - -v \ - -ldflags="-w -s" \ - -o "/home/go/web_proxy" \ - "./cmd/web_proxy" - -# Force distroless base to use current platform (most likely linux/amd64) -# which is needed since distroless/static is not available for linux/arm/v6 -FROM --platform=$BUILDPLATFORM gcr.io/distroless/static AS distroless - -FROM scratch -COPY --from=distroless / / -COPY --from=build /home/go/web_proxy /usr/sbin/web_proxy -USER nonroot:nonroot -EXPOSE 8080 -ENTRYPOINT [ "/usr/sbin/web_proxy" ] diff --git a/cmd/public_node/main.go b/cmd/public_node/main.go deleted file mode 100644 index 40f1483..0000000 --- a/cmd/public_node/main.go +++ /dev/null @@ -1,30 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package main - -import ( - "context" - "log" - - "github.com/cinode/go/pkg/cmd/publicnode" -) - -func main() { - if err := publicnode.Execute(context.Background()); err != nil { - log.Fatal(err) - } -} diff --git a/cmd/static_datastore_builder/main.go b/cmd/static_datastore_builder/main.go deleted file mode 100644 index b639b64..0000000 --- a/cmd/static_datastore_builder/main.go +++ /dev/null @@ -1,29 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package main - -import ( - "log" - - "github.com/cinode/go/pkg/cmd/staticdatastore" -) - -func main() { - if err := staticdatastore.RootCmd().Execute(); err != nil { - log.Fatal(err.Error()) - } -} diff --git a/cmd/web_proxy/main.go b/cmd/web_proxy/main.go deleted file mode 100644 index bec39ad..0000000 --- a/cmd/web_proxy/main.go +++ /dev/null @@ -1,30 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package main - -import ( - "context" - "log" - - "github.com/cinode/go/pkg/cmd/cinodewebproxy" -) - -func main() { - if err := cinodewebproxy.Execute(context.Background()); err != nil { - log.Fatal(err) - } -} diff --git a/go.mod b/go.mod index 69739cd..337b9cb 100644 --- a/go.mod +++ b/go.mod @@ -1,200 +1,16 @@ -module github.com/cinode/go +module github.com/cinode/go-datastore -go 1.25 +go 1.25.3 require ( github.com/jbenet/go-base58 v0.0.0-20150317085156-6237cf65f3a6 - github.com/spf13/cobra v1.9.1 github.com/stretchr/testify v1.11.1 - golang.org/x/crypto v0.41.0 - google.golang.org/protobuf v1.36.8 + golang.org/x/crypto v0.43.0 ) require ( - 4d63.com/gocheckcompilerdirectives v1.3.0 // indirect - 4d63.com/gochecknoglobals v0.2.2 // indirect - github.com/4meepo/tagalign v1.4.2 // indirect - github.com/Abirdcfly/dupword v0.1.3 // indirect - github.com/Antonboom/errname v1.0.0 // indirect - github.com/Antonboom/nilnil v1.0.1 // indirect - github.com/Antonboom/testifylint v1.5.2 // indirect - github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect - github.com/Crocmagnon/fatcontext v0.7.1 // indirect - github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect - github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1 // indirect - github.com/Masterminds/semver/v3 v3.3.0 // indirect - github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect - github.com/alecthomas/go-check-sumtype v0.3.1 // indirect - github.com/alexkohler/nakedret/v2 v2.0.5 // indirect - github.com/alexkohler/prealloc v1.0.0 // indirect - github.com/alingse/asasalint v0.0.11 // indirect - github.com/alingse/nilnesserr v0.1.2 // indirect - github.com/ashanbrown/forbidigo v1.6.0 // indirect - github.com/ashanbrown/makezero v1.2.0 // indirect - github.com/beorn7/perks v1.0.1 // indirect - github.com/bkielbasa/cyclop v1.2.3 // indirect - github.com/blizzy78/varnamelen v0.8.0 // indirect - github.com/bombsimon/wsl/v4 v4.5.0 // indirect - github.com/breml/bidichk v0.3.2 // indirect - github.com/breml/errchkjson v0.4.0 // indirect - github.com/butuzov/ireturn v0.3.1 // indirect - github.com/butuzov/mirror v1.3.0 // indirect - github.com/catenacyber/perfsprint v0.8.2 // indirect - github.com/ccojocar/zxcvbn-go v1.0.2 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/charithe/durationcheck v0.0.10 // indirect - github.com/chavacava/garif v0.1.0 // indirect - github.com/ckaznocha/intrange v0.3.0 // indirect - github.com/curioswitch/go-reassign v0.3.0 // indirect - github.com/daixiang0/gci v0.13.5 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/denis-tingaikin/go-header v0.5.0 // indirect - github.com/ettle/strcase v0.2.0 // indirect - github.com/fatih/color v1.18.0 // indirect - github.com/fatih/structtag v1.2.0 // indirect - github.com/firefart/nonamedreturns v1.0.5 // indirect - github.com/fsnotify/fsnotify v1.5.4 // indirect - github.com/fzipp/gocyclo v0.6.0 // indirect - github.com/ghostiam/protogetter v0.3.9 // indirect - github.com/go-critic/go-critic v0.12.0 // indirect - github.com/go-toolsmith/astcast v1.1.0 // indirect - github.com/go-toolsmith/astcopy v1.1.0 // indirect - github.com/go-toolsmith/astequal v1.2.0 // indirect - github.com/go-toolsmith/astfmt v1.1.0 // indirect - github.com/go-toolsmith/astp v1.1.0 // indirect - github.com/go-toolsmith/strparse v1.1.0 // indirect - github.com/go-toolsmith/typep v1.1.0 // indirect - github.com/go-viper/mapstructure/v2 v2.4.0 // indirect - github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect - github.com/gobwas/glob v0.2.3 // indirect - github.com/gofrs/flock v0.12.1 // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect - github.com/golangci/go-printf-func-name v0.1.0 // indirect - github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect - github.com/golangci/golangci-lint v1.64.8 // indirect - github.com/golangci/misspell v0.6.0 // indirect - github.com/golangci/plugin-module-register v0.1.1 // indirect - github.com/golangci/revgrep v0.8.0 // indirect - github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect - github.com/google/go-cmp v0.7.0 // indirect - github.com/gordonklaus/ineffassign v0.1.0 // indirect - github.com/gostaticanalysis/analysisutil v0.7.1 // indirect - github.com/gostaticanalysis/comment v1.5.0 // indirect - github.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect - github.com/gostaticanalysis/nilerr v0.1.1 // indirect - github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect - github.com/hashicorp/go-version v1.7.0 // indirect - github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect - github.com/hexops/gotextdiff v1.0.3 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jgautheron/goconst v1.7.1 // indirect - github.com/jingyugao/rowserrcheck v1.1.1 // indirect - github.com/jjti/go-spancheck v0.6.4 // indirect - github.com/julz/importas v0.2.0 // indirect - github.com/karamaru-alpha/copyloopvar v1.2.1 // indirect - github.com/kisielk/errcheck v1.9.0 // indirect - github.com/kkHAIKE/contextcheck v1.1.6 // indirect - github.com/kulti/thelper v0.6.3 // indirect - github.com/kunwardeep/paralleltest v1.0.10 // indirect - github.com/lasiar/canonicalheader v1.1.2 // indirect - github.com/ldez/exptostd v0.4.2 // indirect - github.com/ldez/gomoddirectives v0.6.1 // indirect - github.com/ldez/grignotin v0.9.0 // indirect - github.com/ldez/tagliatelle v0.7.1 // indirect - github.com/ldez/usetesting v0.4.2 // indirect - github.com/leonklingele/grouper v1.1.2 // indirect - github.com/macabu/inamedparam v0.1.3 // indirect - github.com/magiconair/properties v1.8.6 // indirect - github.com/maratori/testableexamples v1.0.0 // indirect - github.com/maratori/testpackage v1.1.1 // indirect - github.com/matoous/godox v1.1.0 // indirect - github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.16 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/mgechev/revive v1.7.0 // indirect - github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/moricho/tparallel v0.3.2 // indirect - github.com/nakabonne/nestif v0.3.1 // indirect - github.com/nishanths/exhaustive v0.12.0 // indirect - github.com/nishanths/predeclared v0.2.2 // indirect - github.com/nunnatsa/ginkgolinter v0.19.1 // indirect - github.com/olekukonko/tablewriter v0.0.5 // indirect - github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/polyfloyd/go-errorlint v1.7.1 // indirect - github.com/prometheus/client_golang v1.12.1 // indirect - github.com/prometheus/client_model v0.2.0 // indirect - github.com/prometheus/common v0.32.1 // indirect - github.com/prometheus/procfs v0.7.3 // indirect - github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 // indirect - github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect - github.com/quasilyte/gogrep v0.5.0 // indirect - github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect - github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect - github.com/raeperd/recvcheck v0.2.0 // indirect - github.com/rivo/uniseg v0.4.7 // indirect - github.com/rogpeppe/go-internal v1.14.1 // indirect - github.com/ryancurrah/gomodguard v1.3.5 // indirect - github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect - github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect - github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect - github.com/sashamelentyev/interfacebloat v1.1.0 // indirect - github.com/sashamelentyev/usestdlibvars v1.28.0 // indirect - github.com/securego/gosec/v2 v2.22.2 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect - github.com/sivchari/containedctx v1.0.3 // indirect - github.com/sivchari/tenv v1.12.1 // indirect - github.com/sonatard/noctx v0.1.0 // indirect - github.com/sourcegraph/go-diff v0.7.0 // indirect - github.com/spf13/afero v1.12.0 // indirect - github.com/spf13/cast v1.5.0 // indirect - github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/pflag v1.0.7 // indirect - github.com/spf13/viper v1.12.0 // indirect - github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect - github.com/stbenjam/no-sprintf-host-port v0.2.0 // indirect - github.com/stretchr/objx v0.5.2 // indirect - github.com/subosito/gotenv v1.4.1 // indirect - github.com/tdakkota/asciicheck v0.4.1 // indirect - github.com/tetafro/godot v1.5.0 // indirect - github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3 // indirect - github.com/timonwong/loggercheck v0.10.1 // indirect - github.com/tomarrell/wrapcheck/v2 v2.10.0 // indirect - github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect - github.com/ultraware/funlen v0.2.0 // indirect - github.com/ultraware/whitespace v0.2.0 // indirect - github.com/uudashr/gocognit v1.2.0 // indirect - github.com/uudashr/iface v1.3.1 // indirect - github.com/xen0n/gosmopolitan v1.2.2 // indirect - github.com/yagipy/maintidx v1.0.0 // indirect - github.com/yeya24/promlinter v0.3.0 // indirect - github.com/ykadowak/zerologlint v0.1.5 // indirect - gitlab.com/bosi/decorder v0.4.2 // indirect - go-simpler.org/musttag v0.13.0 // indirect - go-simpler.org/sloglint v0.9.0 // indirect - go.uber.org/atomic v1.7.0 // indirect - go.uber.org/automaxprocs v1.6.0 // indirect - go.uber.org/multierr v1.6.0 // indirect - go.uber.org/zap v1.24.0 // indirect - golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect - golang.org/x/mod v0.26.0 // indirect - golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.35.0 // indirect - golang.org/x/text v0.28.0 // indirect - golang.org/x/tools v0.35.0 // indirect - golang.org/x/tools/go/expect v0.1.1-deprecated // indirect - golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect - gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect + golang.org/x/sys v0.37.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - honnef.co/go/tools v0.6.1 // indirect - mvdan.cc/gofumpt v0.7.0 // indirect - mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f // indirect ) - -tool github.com/golangci/golangci-lint/cmd/golangci-lint diff --git a/go.sum b/go.sum index 2dc64db..5c44e89 100644 --- a/go.sum +++ b/go.sum @@ -1,984 +1,16 @@ -4d63.com/gocheckcompilerdirectives v1.3.0 h1:Ew5y5CtcAAQeTVKUVFrE7EwHMrTO6BggtEj8BZSjZ3A= -4d63.com/gocheckcompilerdirectives v1.3.0/go.mod h1:ofsJ4zx2QAuIP/NO/NAh1ig6R1Fb18/GI7RVMwz7kAY= -4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU= -4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0= -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/4meepo/tagalign v1.4.2 h1:0hcLHPGMjDyM1gHG58cS73aQF8J4TdVR96TZViorO9E= -github.com/4meepo/tagalign v1.4.2/go.mod h1:+p4aMyFM+ra7nb41CnFG6aSDXqRxU/w1VQqScKqDARI= -github.com/Abirdcfly/dupword v0.1.3 h1:9Pa1NuAsZvpFPi9Pqkd93I7LIYRURj+A//dFd5tgBeE= -github.com/Abirdcfly/dupword v0.1.3/go.mod h1:8VbB2t7e10KRNdwTVoxdBaxla6avbhGzb8sCTygUMhw= -github.com/Antonboom/errname v1.0.0 h1:oJOOWR07vS1kRusl6YRSlat7HFnb3mSfMl6sDMRoTBA= -github.com/Antonboom/errname v1.0.0/go.mod h1:gMOBFzK/vrTiXN9Oh+HFs+e6Ndl0eTFbtsRTSRdXyGI= -github.com/Antonboom/nilnil v1.0.1 h1:C3Tkm0KUxgfO4Duk3PM+ztPncTFlOf0b2qadmS0s4xs= -github.com/Antonboom/nilnil v1.0.1/go.mod h1:CH7pW2JsRNFgEh8B2UaPZTEPhCMuFowP/e8Udp9Nnb0= -github.com/Antonboom/testifylint v1.5.2 h1:4s3Xhuv5AvdIgbd8wOOEeo0uZG7PbDKQyKY5lGoQazk= -github.com/Antonboom/testifylint v1.5.2/go.mod h1:vxy8VJ0bc6NavlYqjZfmp6EfqXMtBgQ4+mhCojwC1P8= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= -github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Crocmagnon/fatcontext v0.7.1 h1:SC/VIbRRZQeQWj/TcQBS6JmrXcfA+BU4OGSVUt54PjM= -github.com/Crocmagnon/fatcontext v0.7.1/go.mod h1:1wMvv3NXEBJucFGfwOJBxSVWcoIO6emV215SMkW9MFU= -github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= -github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= -github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1 h1:Sz1JIXEcSfhz7fUi7xHnhpIE0thVASYjvosApmHuD2k= -github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1/go.mod h1:n/LSCXNuIYqVfBlVXyHfMQkZDdp1/mmxfSjADd3z1Zg= -github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= -github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= -github.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsuj3piCMx4= -github.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo= -github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= -github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= -github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsrxJb4Aq31NLkU= -github.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E= -github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= -github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/alexkohler/nakedret/v2 v2.0.5 h1:fP5qLgtwbx9EJE8dGEERT02YwS8En4r9nnZ71RK+EVU= -github.com/alexkohler/nakedret/v2 v2.0.5/go.mod h1:bF5i0zF2Wo2o4X4USt9ntUWve6JbFv02Ff4vlkmS/VU= -github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw= -github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= -github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= -github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= -github.com/alingse/nilnesserr v0.1.2 h1:Yf8Iwm3z2hUUrP4muWfW83DF4nE3r1xZ26fGWUKCZlo= -github.com/alingse/nilnesserr v0.1.2/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg= -github.com/ashanbrown/forbidigo v1.6.0 h1:D3aewfM37Yb3pxHujIPSpTf6oQk9sc9WZi8gerOIVIY= -github.com/ashanbrown/forbidigo v1.6.0/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU= -github.com/ashanbrown/makezero v1.2.0 h1:/2Lp1bypdmK9wDIq7uWBlDF1iMUpIIS4A+pF6C9IEUU= -github.com/ashanbrown/makezero v1.2.0/go.mod h1:dxlPhHbDMC6N6xICzFBSK+4njQDdK8euNO0qjQMtGY4= -github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bkielbasa/cyclop v1.2.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5w= -github.com/bkielbasa/cyclop v1.2.3/go.mod h1:kHTwA9Q0uZqOADdupvcFJQtp/ksSnytRMe8ztxG8Fuo= -github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M= -github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= -github.com/bombsimon/wsl/v4 v4.5.0 h1:iZRsEvDdyhd2La0FVi5k6tYehpOR/R7qIUjmKk7N74A= -github.com/bombsimon/wsl/v4 v4.5.0/go.mod h1:NOQ3aLF4nD7N5YPXMruR6ZXDOAqLoM0GEpLwTdvmOSc= -github.com/breml/bidichk v0.3.2 h1:xV4flJ9V5xWTqxL+/PMFF6dtJPvZLPsyixAoPe8BGJs= -github.com/breml/bidichk v0.3.2/go.mod h1:VzFLBxuYtT23z5+iVkamXO386OB+/sVwZOpIj6zXGos= -github.com/breml/errchkjson v0.4.0 h1:gftf6uWZMtIa/Is3XJgibewBm2ksAQSY/kABDNFTAdk= -github.com/breml/errchkjson v0.4.0/go.mod h1:AuBOSTHyLSaaAFlWsRSuRBIroCh3eh7ZHh5YeelDIk8= -github.com/butuzov/ireturn v0.3.1 h1:mFgbEI6m+9W8oP/oDdfA34dLisRFCj2G6o/yiI1yZrY= -github.com/butuzov/ireturn v0.3.1/go.mod h1:ZfRp+E7eJLC0NQmk1Nrm1LOrn/gQlOykv+cVPdiXH5M= -github.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc= -github.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI= -github.com/catenacyber/perfsprint v0.8.2 h1:+o9zVmCSVa7M4MvabsWvESEhpsMkhfE7k0sHNGL95yw= -github.com/catenacyber/perfsprint v0.8.2/go.mod h1:q//VWC2fWbcdSLEY1R3l8n0zQCDPdE4IjZwyY1HMunM= -github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg= -github.com/ccojocar/zxcvbn-go v1.0.2/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iyoHGPf5w4= -github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoGOY1C1L6uouGNreQ= -github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc= -github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/ckaznocha/intrange v0.3.0 h1:VqnxtK32pxgkhJgYQEeOArVidIPg+ahLP7WBOXZd5ZY= -github.com/ckaznocha/intrange v0.3.0/go.mod h1:+I/o2d2A1FBHgGELbGxzIcyd3/9l9DuwjM8FsbSS3Lo= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs= -github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88= -github.com/daixiang0/gci v0.13.5 h1:kThgmH1yBmZSBCh1EJVxQ7JsHpm5Oms0AMed/0LaH4c= -github.com/daixiang0/gci v0.13.5/go.mod h1:12etP2OniiIdP4q+kjUGrC/rUagga7ODbqsom5Eo5Yk= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8= -github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= -github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= -github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q= -github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A= -github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= -github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= -github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= -github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= -github.com/firefart/nonamedreturns v1.0.5 h1:tM+Me2ZaXs8tfdDw3X6DOX++wMCOqzYUho6tUTYIdRA= -github.com/firefart/nonamedreturns v1.0.5/go.mod h1:gHJjDqhGM4WyPt639SOZs+G89Ko7QKH5R5BhnO6xJhw= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= -github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= -github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= -github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= -github.com/ghostiam/protogetter v0.3.9 h1:j+zlLLWzqLay22Cz/aYwTHKQ88GE2DQ6GkWSYFOI4lQ= -github.com/ghostiam/protogetter v0.3.9/go.mod h1:WZ0nw9pfzsgxuRsPOFQomgDVSWtDLJRfQJEhsGbmQMA= -github.com/go-critic/go-critic v0.12.0 h1:iLosHZuye812wnkEz1Xu3aBwn5ocCPfc9yqmFG9pa6w= -github.com/go-critic/go-critic v0.12.0/go.mod h1:DpE0P6OVc6JzVYzmM5gq5jMU31zLr4am5mB/VfFK64w= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= -github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= -github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8= -github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU= -github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s= -github.com/go-toolsmith/astcopy v1.1.0/go.mod h1:hXM6gan18VA1T/daUEHCFcYiW8Ai1tIwIzHY6srfEAw= -github.com/go-toolsmith/astequal v1.0.3/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4= -github.com/go-toolsmith/astequal v1.1.0/go.mod h1:sedf7VIdCL22LD8qIvv7Nn9MuWJruQA/ysswh64lffQ= -github.com/go-toolsmith/astequal v1.2.0 h1:3Fs3CYZ1k9Vo4FzFhwwewC3CHISHDnVUPC4x0bI2+Cw= -github.com/go-toolsmith/astequal v1.2.0/go.mod h1:c8NZ3+kSFtFY/8lPso4v8LuJjdJiUFVnSuU3s0qrrDY= -github.com/go-toolsmith/astfmt v1.1.0 h1:iJVPDPp6/7AaeLJEruMsBUlOYCmvg0MoCfJprsOmcco= -github.com/go-toolsmith/astfmt v1.1.0/go.mod h1:OrcLlRwu0CuiIBp/8b5PYF9ktGVZUjlNMV634mhwuQ4= -github.com/go-toolsmith/astp v1.1.0 h1:dXPuCl6u2llURjdPLLDxJeZInAeZ0/eZwFJmqZMnpQA= -github.com/go-toolsmith/astp v1.1.0/go.mod h1:0T1xFGz9hicKs8Z5MfAqSUitoUYS30pDMsRVIDHs8CA= -github.com/go-toolsmith/pkgload v1.2.2 h1:0CtmHq/02QhxcF7E9N5LIFcYFsMR5rdovfqTtRKkgIk= -github.com/go-toolsmith/pkgload v1.2.2/go.mod h1:R2hxLNRKuAsiXCo2i5J6ZQPhnPMOVtU+f0arbFPWCus= -github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= -github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQiyP2Bvw= -github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= -github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus= -github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= -github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= -github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY= -github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= -github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= -github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= -github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 h1:WUvBfQL6EW/40l6OmeSBYQJNSif4O11+bmWEz+C7FYw= -github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32/go.mod h1:NUw9Zr2Sy7+HxzdjIULge71wI6yEg1lWQr7Evcu8K0E= -github.com/golangci/go-printf-func-name v0.1.0 h1:dVokQP+NMTO7jwO4bwsRwLWeudOVUPPyAKJuzv8pEJU= -github.com/golangci/go-printf-func-name v0.1.0/go.mod h1:wqhWFH5mUdJQhweRnldEywnR5021wTdZSNgwYceV14s= -github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE= -github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY= -github.com/golangci/golangci-lint v1.64.8 h1:y5TdeVidMtBGG32zgSC7ZXTFNHrsJkDnpO4ItB3Am+I= -github.com/golangci/golangci-lint v1.64.8/go.mod h1:5cEsUQBSr6zi8XI8OjmcY2Xmliqc4iYL7YoPrL+zLJ4= -github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= -github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= -github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c= -github.com/golangci/plugin-module-register v0.1.1/go.mod h1:TTpqoB6KkwOJMV8u7+NyXMrkwwESJLOkfl9TxR1DGFc= -github.com/golangci/revgrep v0.8.0 h1:EZBctwbVd0aMeRnNUsFogoyayvKHyxlV3CdUA46FX2s= -github.com/golangci/revgrep v0.8.0/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k= -github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed h1:IURFTjxeTfNFP0hTEi1YKjB/ub8zkpaOqFFMApi2EAs= -github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed/go.mod h1:XLXN8bNw4CGRPaqgl3bv/lhz7bsGPh4/xSaMTbo2vkQ= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= -github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s= -github.com/gordonklaus/ineffassign v0.1.0/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= -github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= -github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= -github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado= -github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= -github.com/gostaticanalysis/comment v1.5.0 h1:X82FLl+TswsUMpMh17srGRuKaaXprTaytmEpgnKIDu8= -github.com/gostaticanalysis/comment v1.5.0/go.mod h1:V6eb3gpCv9GNVqb6amXzEUX3jXLVK/AdA+IrAMSqvEc= -github.com/gostaticanalysis/forcetypeassert v0.2.0 h1:uSnWrrUEYDr86OCxWa4/Tp2jeYDlogZiZHzGkWFefTk= -github.com/gostaticanalysis/forcetypeassert v0.2.0/go.mod h1:M5iPavzE9pPqWyeiVXSFghQjljW1+l/Uke3PXHS6ILY= -github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3Uqrmrcpk= -github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= -github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= -github.com/gostaticanalysis/testutil v0.5.0 h1:Dq4wT1DdTwTGCQQv3rl3IvD5Ld0E6HiY+3Zh0sUGqw8= -github.com/gostaticanalysis/testutil v0.5.0/go.mod h1:OLQSbuM6zw2EvCcXTz1lVq5unyoNft372msDY0nY5Hs= -github.com/hashicorp/go-immutable-radix/v2 v2.1.0 h1:CUW5RYIcysz+D3B+l1mDeXrQ7fUvGGCwJfdASSzbrfo= -github.com/hashicorp/go-immutable-radix/v2 v2.1.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw= -github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= -github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= -github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= -github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= -github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jbenet/go-base58 v0.0.0-20150317085156-6237cf65f3a6 h1:4zOlv2my+vf98jT1nQt4bT/yKWUImevYPJ2H344CloE= github.com/jbenet/go-base58 v0.0.0-20150317085156-6237cf65f3a6/go.mod h1:r/8JmuR0qjuCiEhAolkfvdZgmPiHTnJaG0UXCSeR1Zo= -github.com/jgautheron/goconst v1.7.1 h1:VpdAG7Ca7yvvJk5n8dMwQhfEZJh95kl/Hl9S1OI5Jkk= -github.com/jgautheron/goconst v1.7.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= -github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= -github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= -github.com/jjti/go-spancheck v0.6.4 h1:Tl7gQpYf4/TMU7AT84MN83/6PutY21Nb9fuQjFTpRRc= -github.com/jjti/go-spancheck v0.6.4/go.mod h1:yAEYdKJ2lRkDA8g7X+oKUHXOWVAXSBJRv04OhF+QUjk= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ= -github.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY= -github.com/karamaru-alpha/copyloopvar v1.2.1 h1:wmZaZYIjnJ0b5UoKDjUHrikcV0zuPyyxI4SVplLd2CI= -github.com/karamaru-alpha/copyloopvar v1.2.1/go.mod h1:nFmMlFNlClC2BPvNaHMdkirmTJxVCY0lhxBtlfOypMM= -github.com/kisielk/errcheck v1.9.0 h1:9xt1zI9EBfcYBvdU1nVrzMzzUPUtPKs9bVSIM3TAb3M= -github.com/kisielk/errcheck v1.9.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE= -github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kulti/thelper v0.6.3 h1:ElhKf+AlItIu+xGnI990no4cE2+XaSu1ULymV2Yulxs= -github.com/kulti/thelper v0.6.3/go.mod h1:DsqKShOvP40epevkFrvIwkCMNYxMeTNjdWL4dqWHZ6I= -github.com/kunwardeep/paralleltest v1.0.10 h1:wrodoaKYzS2mdNVnc4/w31YaXFtsc21PCTdvWJ/lDDs= -github.com/kunwardeep/paralleltest v1.0.10/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY= -github.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4= -github.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI= -github.com/ldez/exptostd v0.4.2 h1:l5pOzHBz8mFOlbcifTxzfyYbgEmoUqjxLFHZkjlbHXs= -github.com/ldez/exptostd v0.4.2/go.mod h1:iZBRYaUmcW5jwCR3KROEZ1KivQQp6PHXbDPk9hqJKCQ= -github.com/ldez/gomoddirectives v0.6.1 h1:Z+PxGAY+217f/bSGjNZr/b2KTXcyYLgiWI6geMBN2Qc= -github.com/ldez/gomoddirectives v0.6.1/go.mod h1:cVBiu3AHR9V31em9u2kwfMKD43ayN5/XDgr+cdaFaKs= -github.com/ldez/grignotin v0.9.0 h1:MgOEmjZIVNn6p5wPaGp/0OKWyvq42KnzAt/DAb8O4Ow= -github.com/ldez/grignotin v0.9.0/go.mod h1:uaVTr0SoZ1KBii33c47O1M8Jp3OP3YDwhZCmzT9GHEk= -github.com/ldez/tagliatelle v0.7.1 h1:bTgKjjc2sQcsgPiT902+aadvMjCeMHrY7ly2XKFORIk= -github.com/ldez/tagliatelle v0.7.1/go.mod h1:3zjxUpsNB2aEZScWiZTHrAXOl1x25t3cRmzfK1mlo2I= -github.com/ldez/usetesting v0.4.2 h1:J2WwbrFGk3wx4cZwSMiCQQ00kjGR0+tuuyW0Lqm4lwA= -github.com/ldez/usetesting v0.4.2/go.mod h1:eEs46T3PpQ+9RgN9VjpY6qWdiw2/QmfiDeWmdZdrjIQ= -github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= -github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= -github.com/macabu/inamedparam v0.1.3 h1:2tk/phHkMlEL/1GNe/Yf6kkR/hkcUdAEY3L0hjYV1Mk= -github.com/macabu/inamedparam v0.1.3/go.mod h1:93FLICAIk/quk7eaPPQvbzihUdn/QkGDwIZEoLtpH6I= -github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= -github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI= -github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE= -github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04= -github.com/maratori/testpackage v1.1.1/go.mod h1:s4gRK/ym6AMrqpOa/kEbQTV4Q4jb7WeLZzVhVVVOQMc= -github.com/matoous/godox v1.1.0 h1:W5mqwbyWrwZv6OQ5Z1a/DHGMOvXYCBP3+Ht7KMoJhq4= -github.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs= -github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= -github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= -github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= -github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= -github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mgechev/revive v1.7.0 h1:JyeQ4yO5K8aZhIKf5rec56u0376h8AlKNQEmjfkjKlY= -github.com/mgechev/revive v1.7.0/go.mod h1:qZnwcNhoguE58dfi96IJeSTPeZQejNeoMQLUZGi4SW4= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI= -github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= -github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= -github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg= -github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= -github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= -github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= -github.com/nunnatsa/ginkgolinter v0.19.1 h1:mjwbOlDQxZi9Cal+KfbEJTCz327OLNfwNvoZ70NJ+c4= -github.com/nunnatsa/ginkgolinter v0.19.1/go.mod h1:jkQ3naZDmxaZMXPWaS9rblH+i+GWXQCaS/JFIWcOH2s= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= -github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= -github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= -github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= -github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= -github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= -github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= -github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= -github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= -github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= -github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= -github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= -github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= -github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/polyfloyd/go-errorlint v1.7.1 h1:RyLVXIbosq1gBdk/pChWA8zWYLsq9UEw7a1L5TVMCnA= -github.com/polyfloyd/go-errorlint v1.7.1/go.mod h1:aXjNb1x2TNhoLsk26iv1yl7a+zTnXPhwEMtEXukiLR8= -github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= -github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 h1:+Wl/0aFp0hpuHM3H//KMft64WQ1yX9LdJY64Qm/gFCo= -github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1/go.mod h1:GJLgqsLeo4qgavUoL8JeGFNS7qcisx3awV/w9eWTmNI= -github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE= -github.com/quasilyte/go-ruleguard/dsl v0.3.22/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= -github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo= -github.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng= -github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU= -github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= -github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs= -github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= -github.com/raeperd/recvcheck v0.2.0 h1:GnU+NsbiCqdC2XX5+vMZzP+jAJC5fht7rcVTAhX74UI= -github.com/raeperd/recvcheck v0.2.0/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtzBnWNocnYU= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= -github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryancurrah/gomodguard v1.3.5 h1:cShyguSwUEeC0jS7ylOiG/idnd1TpJ1LfHGpV3oJmPU= -github.com/ryancurrah/gomodguard v1.3.5/go.mod h1:MXlEPQRxgfPQa62O8wzK3Ozbkv9Rkqr+wKjSxTdsNJE= -github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU= -github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= -github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0= -github.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= -github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw= -github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= -github.com/sashamelentyev/usestdlibvars v1.28.0 h1:jZnudE2zKCtYlGzLVreNp5pmCdOxXUzwsMDBkR21cyQ= -github.com/sashamelentyev/usestdlibvars v1.28.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= -github.com/securego/gosec/v2 v2.22.2 h1:IXbuI7cJninj0nRpZSLCUlotsj8jGusohfONMrHoF6g= -github.com/securego/gosec/v2 v2.22.2/go.mod h1:UEBGA+dSKb+VqM6TdehR7lnQtIIMorYJ4/9CW1KVQBE= -github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= -github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= -github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= -github.com/sivchari/tenv v1.12.1 h1:+E0QzjktdnExv/wwsnnyk4oqZBUfuh89YMQT1cyuvSY= -github.com/sivchari/tenv v1.12.1/go.mod h1:1LjSOUCc25snIr5n3DtGGrENhX3LuWefcplwVGC24mw= -github.com/sonatard/noctx v0.1.0 h1:JjqOc2WN16ISWAjAk8M5ej0RfExEXtkEyExl2hLW+OM= -github.com/sonatard/noctx v0.1.0/go.mod h1:0RvBxqY8D4j9cTTTWE8ylt2vqj2EPI8fHmrxHdsaZ2c= -github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= -github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= -github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= -github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= -github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= -github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= -github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= -github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= -github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= -github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= -github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= -github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= -github.com/stbenjam/no-sprintf-host-port v0.2.0 h1:i8pxvGrt1+4G0czLr/WnmyH7zbZ8Bg8etvARQ1rpyl4= -github.com/stbenjam/no-sprintf-host-port v0.2.0/go.mod h1:eL0bQ9PasS0hsyTyfTjjG+E80QIyPnBVQbYZyv20Jfk= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= -github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/tdakkota/asciicheck v0.4.1 h1:bm0tbcmi0jezRA2b5kg4ozmMuGAFotKI3RZfrhfovg8= -github.com/tdakkota/asciicheck v0.4.1/go.mod h1:0k7M3rCfRXb0Z6bwgvkEIMleKH3kXNz9UqJ9Xuqopr8= -github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= -github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= -github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= -github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= -github.com/tetafro/godot v1.5.0 h1:aNwfVI4I3+gdxjMgYPus9eHmoBeJIbnajOyqZYStzuw= -github.com/tetafro/godot v1.5.0/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= -github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3 h1:y4mJRFlM6fUyPhoXuFg/Yu02fg/nIPFMOY8tOqppoFg= -github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460= -github.com/timonwong/loggercheck v0.10.1 h1:uVZYClxQFpw55eh+PIoqM7uAOHMrhVcDoWDery9R8Lg= -github.com/timonwong/loggercheck v0.10.1/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8= -github.com/tomarrell/wrapcheck/v2 v2.10.0 h1:SzRCryzy4IrAH7bVGG4cK40tNUhmVmMDuJujy4XwYDg= -github.com/tomarrell/wrapcheck/v2 v2.10.0/go.mod h1:g9vNIyhb5/9TQgumxQyOEqDHsmGYcGsVMOx/xGkqdMo= -github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= -github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= -github.com/ultraware/funlen v0.2.0 h1:gCHmCn+d2/1SemTdYMiKLAHFYxTYz7z9VIDRaTGyLkI= -github.com/ultraware/funlen v0.2.0/go.mod h1:ZE0q4TsJ8T1SQcjmkhN/w+MceuatI6pBFSxxyteHIJA= -github.com/ultraware/whitespace v0.2.0 h1:TYowo2m9Nfj1baEQBjuHzvMRbp19i+RCcRYrSWoFa+g= -github.com/ultraware/whitespace v0.2.0/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= -github.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYRA= -github.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU= -github.com/uudashr/iface v1.3.1 h1:bA51vmVx1UIhiIsQFSNq6GZ6VPTk3WNMZgRiCe9R29U= -github.com/uudashr/iface v1.3.1/go.mod h1:4QvspiRd3JLPAEXBQ9AiZpLbJlrWWgRChOKDJEuQTdg= -github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU= -github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg= -github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= -github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= -github.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs= -github.com/yeya24/promlinter v0.3.0/go.mod h1:cDfJQQYv9uYciW60QT0eeHlFodotkYZlL+YcPQN+mW4= -github.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw= -github.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo= -gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8= -go-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ= -go-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= -go-simpler.org/musttag v0.13.0 h1:Q/YAW0AHvaoaIbsPj3bvEI5/QFP7w696IMUpnKXQfCE= -go-simpler.org/musttag v0.13.0/go.mod h1:FTzIGeK6OkKlUDVpj0iQUXZLUO1Js9+mvykDQy9C5yM= -go-simpler.org/sloglint v0.9.0 h1:/40NQtjRx9txvsB/RN022KsUJU+zaaSb/9q9BSefSrE= -go-simpler.org/sloglint v0.9.0/go.mod h1:G/OrAF6uxj48sHahCzrbarVMptL2kjWTaUeC8+fOGww= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= -go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= -go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= -go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= -golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= -golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac h1:TSSpLIG4v+p0rPv1pNOQtl1I8knsO4S9trOxNMOLVP4= -golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= -golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= -golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= -golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= -golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= -golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= -golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= -golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= -golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= -golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= -golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= -google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI= -honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4= -mvdan.cc/gofumpt v0.7.0 h1:bg91ttqXmi9y2xawvkuMXyvAA/1ZGJqYAEGjXuP0JXU= -mvdan.cc/gofumpt v0.7.0/go.mod h1:txVFJy/Sc/mvaycET54pV8SW8gWxTlUuGHVEcncmNUo= -mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f h1:lMpcwN6GxNbWtbpI1+xzFLSW8XzX0u72NttUGVFjO3U= -mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f/go.mod h1:RSLa7mKKCNeTTMHBw5Hsy2rfJmd6O2ivt9Dw9ZqCQpQ= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/pkg/blenc/datastore.go b/pkg/blenc/datastore.go deleted file mode 100644 index 421f6cf..0000000 --- a/pkg/blenc/datastore.go +++ /dev/null @@ -1,108 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package blenc - -import ( - "context" - "crypto/rand" - "io" - "math" - "time" - - "github.com/cinode/go/pkg/blobtypes" - "github.com/cinode/go/pkg/common" - "github.com/cinode/go/pkg/datastore" - "github.com/cinode/go/pkg/internal/utilities/securefifo" -) - -// FromDatastore creates Blob Encoder using given datastore implementation as -// the storage layer -func FromDatastore(ds datastore.DS) BE { - return &beDatastore{ - ds: ds, - rand: rand.Reader, - generateVersion: func() uint64 { - // nolint:gosec // UnixMicro should never return negative value - return uint64(time.Now().UnixMicro() & math.MaxInt64) - }, - newSecureFifo: securefifo.New, - } -} - -type versionSource func() uint64 - -type secureFifoGenerator func() (securefifo.Writer, error) - -type beDatastore struct { - ds datastore.DS - rand io.Reader - generateVersion versionSource - newSecureFifo secureFifoGenerator -} - -func (be *beDatastore) Open(ctx context.Context, name *common.BlobName, key *common.BlobKey) (io.ReadCloser, error) { - switch name.Type() { - case blobtypes.Static: - return be.openStatic(ctx, name, key) - case blobtypes.DynamicLink: - return be.openDynamicLink(ctx, name, key) - } - return nil, blobtypes.ErrUnknownBlobType -} - -func (be *beDatastore) Create( - ctx context.Context, - blobType common.BlobType, - r io.Reader, -) ( - *common.BlobName, - *common.BlobKey, - *common.AuthInfo, - error, -) { - switch blobType { - case blobtypes.Static: - return be.createStatic(ctx, r) - case blobtypes.DynamicLink: - return be.createDynamicLink(ctx, r) - } - return nil, nil, nil, blobtypes.ErrUnknownBlobType -} - -func (be *beDatastore) Update( - ctx context.Context, - name *common.BlobName, - authInfo *common.AuthInfo, - key *common.BlobKey, - r io.Reader, -) error { - switch name.Type() { - case blobtypes.Static: - return be.updateStatic(ctx, name, authInfo, key, r) - case blobtypes.DynamicLink: - return be.updateDynamicLink(ctx, name, authInfo, key, r) - } - return blobtypes.ErrUnknownBlobType -} - -func (be *beDatastore) Exists(ctx context.Context, name *common.BlobName) (bool, error) { - return be.ds.Exists(ctx, name) -} - -func (be *beDatastore) Delete(ctx context.Context, name *common.BlobName) error { - return be.ds.Delete(ctx, name) -} diff --git a/pkg/blenc/datastore_dynamic_link.go b/pkg/blenc/datastore_dynamic_link.go deleted file mode 100644 index 7973071..0000000 --- a/pkg/blenc/datastore_dynamic_link.go +++ /dev/null @@ -1,141 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package blenc - -import ( - "context" - "errors" - "fmt" - "io" - - "github.com/cinode/go/pkg/common" - "github.com/cinode/go/pkg/internal/blobtypes/dynamiclink" -) - -var ( - ErrDynamicLinkUpdateFailed = errors.New("could not prepare dynamic link update") - ErrDynamicLinkUpdateFailedWriterInfo = fmt.Errorf("%w: invalid writer info", ErrDynamicLinkUpdateFailed) - ErrDynamicLinkUpdateFailedWrongKey = fmt.Errorf("%w: encryption key mismatch", ErrDynamicLinkUpdateFailed) - ErrDynamicLinkUpdateFailedWrongName = fmt.Errorf("%w: blob name mismatch", ErrDynamicLinkUpdateFailed) -) - -func (be *beDatastore) openDynamicLink( - ctx context.Context, - name *common.BlobName, - key *common.BlobKey, -) ( - io.ReadCloser, - error, -) { - // TODO: Protect against long links - there should be max size limit and maybe some streaming involved? - - rc, err := be.ds.Open(ctx, name) - if err != nil { - return nil, err - } - - dl, err := dynamiclink.FromPublicData(name, rc) - if err != nil { - rc.Close() - return nil, err - } - - linkReader, err := dl.GetLinkDataReader(key) - if err != nil { - rc.Close() - return nil, err - } - - // Decrypt link data - return struct { - io.Reader - io.Closer - }{ - Reader: linkReader, - Closer: rc, - }, nil -} - -func (be *beDatastore) createDynamicLink( - ctx context.Context, - r io.Reader, -) ( - *common.BlobName, - *common.BlobKey, - *common.AuthInfo, - error, -) { - version := be.generateVersion() - - dl, err := dynamiclink.Create(be.rand) - if err != nil { - return nil, nil, nil, err - } - - pr, encryptionKey, err := dl.UpdateLinkData(r, version) - if err != nil { - return nil, nil, nil, err - } - - // Send update packet - bn := dl.BlobName() - err = be.ds.Update(ctx, bn, pr.GetPublicDataReader()) - if err != nil { - return nil, nil, nil, err - } - - return bn, - encryptionKey, - dl.AuthInfo(), - nil -} - -func (be *beDatastore) updateDynamicLink( - ctx context.Context, - name *common.BlobName, - authInfo *common.AuthInfo, - key *common.BlobKey, - r io.Reader, -) error { - newVersion := be.generateVersion() - - dl, err := dynamiclink.FromAuthInfo(authInfo) - if err != nil { - return err - } - - pr, encryptionKey, err := dl.UpdateLinkData(r, newVersion) - if err != nil { - return err - } - - // Sanity checks - if !encryptionKey.Equal(key) { - return ErrDynamicLinkUpdateFailedWrongKey - } - if !name.Equal(dl.BlobName()) { - return ErrDynamicLinkUpdateFailedWrongName - } - - // Send update packet - err = be.ds.Update(ctx, name, pr.GetPublicDataReader()) - if err != nil { - return err - } - - return nil -} diff --git a/pkg/blenc/datastore_dynamic_link_test.go b/pkg/blenc/datastore_dynamic_link_test.go deleted file mode 100644 index f88dae6..0000000 --- a/pkg/blenc/datastore_dynamic_link_test.go +++ /dev/null @@ -1,182 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package blenc - -import ( - "bytes" - "context" - "crypto/rand" - "errors" - "fmt" - "io" - "strings" - "testing" - "testing/iotest" - - "github.com/cinode/go/pkg/blobtypes" - "github.com/cinode/go/pkg/common" - "github.com/cinode/go/pkg/datastore" - "github.com/stretchr/testify/require" -) - -type dsWrapper struct { - datastore.DS - openFn func(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) - updateFn func(ctx context.Context, name *common.BlobName, r io.Reader) error -} - -func (w *dsWrapper) Open(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) { - if w.openFn != nil { - return w.openFn(ctx, name) - } - return w.DS.Open(ctx, name) -} - -func (w *dsWrapper) Update(ctx context.Context, name *common.BlobName, r io.Reader) error { - if w.updateFn != nil { - return w.updateFn(ctx, name, r) - } - return w.DS.Update(ctx, name, r) -} - -type closeFunc struct { - io.Reader - closeFn func() error -} - -func (c *closeFunc) Close() error { return c.closeFn() } - -func TestDynamicLinkErrors(t *testing.T) { - dsw := dsWrapper{DS: datastore.InMemory()} - be := FromDatastore(&dsw) - - bn, key, _, err := be.Create( - t.Context(), - blobtypes.DynamicLink, - strings.NewReader("Hello world!"), - ) - require.NoError(t, err) - - t.Run("handle error while opening blob", func(t *testing.T) { - injectedErr := errors.New("test") - dsw.openFn = func(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) { return nil, injectedErr } - - rc, err := be.Open(t.Context(), bn, key) - require.ErrorIs(t, err, injectedErr) - require.Nil(t, rc) - }) - - t.Run("handle blob read errors", func(t *testing.T) { - injectedErr := errors.New("test") - - for i := 0; ; i++ { - closed := false - dataLen := 0 - - t.Run(fmt.Sprintf("error at byte %d", i), func(t *testing.T) { - dsw.openFn = func(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) { - origRC, err := dsw.DS.Open(ctx, name) - require.NoError(t, err) - - data, err := io.ReadAll(origRC) - require.NoError(t, err) - - dataLen = len(data) // store to figure out when to break - - err = origRC.Close() - require.NoError(t, err) - - return &closeFunc{ - Reader: io.MultiReader(bytes.NewReader(data[:i]), iotest.ErrReader(injectedErr)), - closeFn: func() error { closed = true; return nil }, - }, nil - } - - rc, err := be.Open(t.Context(), bn, key) - require.ErrorIs(t, err, injectedErr) - require.Nil(t, rc) - require.True(t, closed) - }) - - if i >= dataLen { - break - } - } - - dsw.openFn = nil - }) - - t.Run("fail to create dynamic link key pair", func(t *testing.T) { - injectedErr := errors.New("test") - - be.(*beDatastore).rand = iotest.ErrReader(injectedErr) - - bn, key, ai, err := be.Create( - t.Context(), - blobtypes.DynamicLink, - bytes.NewReader(nil), - ) - require.ErrorIs(t, err, injectedErr) - require.Empty(t, bn) - require.Empty(t, key) - require.Empty(t, ai) - - be.(*beDatastore).rand = rand.Reader - }) - - t.Run("fail to store new dynamic link blob", func(t *testing.T) { - injectedErr := errors.New("test") - - dsw.updateFn = func(ctx context.Context, name *common.BlobName, r io.Reader) error { return injectedErr } - - bn, key, ai, err := be.Create( - t.Context(), - blobtypes.DynamicLink, - bytes.NewReader(nil), - ) - require.ErrorIs(t, err, injectedErr) - require.Empty(t, bn) - require.Empty(t, key) - require.Empty(t, ai) - - dsw.updateFn = nil - }) - - t.Run("fail to update new dynamic link blob", func(t *testing.T) { - injectedErr := errors.New("test") - - bn, key, ai, err := be.Create( - t.Context(), - blobtypes.DynamicLink, - bytes.NewReader(nil), - ) - require.NoError(t, err) - - dsw.updateFn = func(ctx context.Context, name *common.BlobName, r io.Reader) error { return injectedErr } - - err = be.Update( - t.Context(), - bn, - ai, - key, - bytes.NewReader(nil), - ) - require.ErrorIs(t, err, injectedErr) - - dsw.updateFn = nil - }) -} diff --git a/pkg/blenc/datastore_static.go b/pkg/blenc/datastore_static.go deleted file mode 100644 index dc22afe..0000000 --- a/pkg/blenc/datastore_static.go +++ /dev/null @@ -1,152 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package blenc - -import ( - "context" - "crypto/sha256" - "errors" - "io" - - "github.com/cinode/go/pkg/blobtypes" - "github.com/cinode/go/pkg/common" - "github.com/cinode/go/pkg/internal/utilities/cipherfactory" - "github.com/cinode/go/pkg/internal/utilities/validatingreader" -) - -var ( - ErrCanNotUpdateStaticBlob = errors.New("blob update is not supported for static blobs") -) - -func (be *beDatastore) openStatic( - ctx context.Context, - name *common.BlobName, - key *common.BlobKey, -) (io.ReadCloser, error) { - rc, err := be.ds.Open(ctx, name) - if err != nil { - return nil, err - } - - scr, err := cipherfactory.StreamCipherReader(key, cipherfactory.DefaultIV(key), rc) - if err != nil { - return nil, err - } - - keyGenerator := cipherfactory.NewKeyGenerator(blobtypes.Static) - - return &struct { - io.Reader - io.Closer - }{ - Reader: validatingreader.CheckOnEOF( - io.TeeReader(scr, keyGenerator), - func() error { - if !key.Equal(keyGenerator.Generate()) { - return blobtypes.ErrValidationFailed - } - return nil - }, - ), - Closer: rc, - }, nil -} - -func (be *beDatastore) createStatic( - ctx context.Context, - r io.Reader, -) ( - *common.BlobName, - *common.BlobKey, - *common.AuthInfo, - error, -) { - tempWriteBufferPlain, err := be.newSecureFifo() - if err != nil { - return nil, nil, nil, err - } - defer tempWriteBufferPlain.Close() - - tempWriteBufferEncrypted, err := be.newSecureFifo() - if err != nil { - return nil, nil, nil, err - } - defer tempWriteBufferEncrypted.Close() - - keyGenerator := cipherfactory.NewKeyGenerator(blobtypes.Static) - _, err = io.Copy(tempWriteBufferPlain, io.TeeReader(r, keyGenerator)) - if err != nil { - return nil, nil, nil, err - } - - key := keyGenerator.Generate() - iv := cipherfactory.DefaultIV(key) // We can use this since each blob will have different key - - rClone, err := tempWriteBufferPlain.Done() // rClone will allow re-reading the source data - if err != nil { - return nil, nil, nil, err - } - defer rClone.Close() - - // Encrypt data with calculated key, hash encrypted data to generate blob name - blobNameHasher := sha256.New() - encWriter, err := cipherfactory.StreamCipherWriter( - key, iv, - io.MultiWriter( - tempWriteBufferEncrypted, // Stream out encrypted data to temporary fifo - blobNameHasher, // Also hash the output to avoid re-reading the fifo again to build blob name - ), - ) - if err != nil { - return nil, nil, nil, err - } - - _, err = io.Copy(encWriter, rClone) - if err != nil { - return nil, nil, nil, err - } - - encReader, err := tempWriteBufferEncrypted.Done() - if err != nil { - return nil, nil, nil, err - } - defer encReader.Close() - - // Generate blob name from the encrypted data - name, err := common.BlobNameFromHashAndType(blobNameHasher.Sum(nil), blobtypes.Static) - if err != nil { - return nil, nil, nil, err - } - - // Send encrypted blob into the datastore - err = be.ds.Update(ctx, name, encReader) - if err != nil { - return nil, nil, nil, err - } - - return name, key, nil, nil -} - -func (be *beDatastore) updateStatic( - ctx context.Context, - name *common.BlobName, - authInfo *common.AuthInfo, - key *common.BlobKey, - r io.Reader, -) error { - return ErrCanNotUpdateStaticBlob -} diff --git a/pkg/blenc/datastore_static_test.go b/pkg/blenc/datastore_static_test.go deleted file mode 100644 index f9dcc3b..0000000 --- a/pkg/blenc/datastore_static_test.go +++ /dev/null @@ -1,309 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package blenc - -import ( - "bytes" - "context" - "errors" - "fmt" - "io" - "testing" - "testing/iotest" - - "github.com/cinode/go/pkg/blobtypes" - "github.com/cinode/go/pkg/common" - "github.com/cinode/go/pkg/datastore" - "github.com/cinode/go/pkg/internal/utilities/securefifo" - "github.com/stretchr/testify/require" -) - -type sfwWrapper struct { - w securefifo.Writer - writeFn func([]byte) (int, error) - closeFn func() error - doneFn func() (securefifo.Reader, error) -} - -func (w *sfwWrapper) Write(b []byte) (int, error) { - if w.writeFn != nil { - return w.writeFn(b) - } - return w.w.Write(b) -} - -func (w *sfwWrapper) Close() error { - if w.closeFn != nil { - return w.closeFn() - } - return w.w.Close() -} - -func (w *sfwWrapper) Done() (securefifo.Reader, error) { - if w.doneFn != nil { - return w.doneFn() - } - return w.w.Done() -} - -func TestStaticErrorTruncatedDatastore(t *testing.T) { - dsw := dsWrapper{DS: datastore.InMemory()} - be := FromDatastore(&dsw) - - bn, key, _, err := be.Create( - t.Context(), - blobtypes.Static, - bytes.NewReader([]byte("Hello world!")), - ) - require.NoError(t, err) - - t.Run("handle error while opening blob", func(t *testing.T) { - injectedErr := errors.New("test") - dsw.openFn = func(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) { return nil, injectedErr } - - rc, err := be.Open(t.Context(), bn, key) - require.ErrorIs(t, err, injectedErr) - require.Nil(t, rc) - - dsw.openFn = nil - }) - - t.Run("handle error while opening blob", func(t *testing.T) { - injectedErr := errors.New("test") - dsw.openFn = func(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) { return nil, injectedErr } - - rc, err := be.Open(t.Context(), bn, key) - require.ErrorIs(t, err, injectedErr) - require.Nil(t, rc) - - dsw.openFn = nil - }) - - t.Run("handle failures to create secure fifo", func(t *testing.T) { - t.Run("first securefifo", func(t *testing.T) { - be := FromDatastore(datastore.InMemory()) - injectedErr := errors.New("test") - be.(*beDatastore).newSecureFifo = func() (securefifo.Writer, error) { return nil, injectedErr } - - bn, key, ai, err := be.Create( - t.Context(), - blobtypes.Static, - bytes.NewReader(nil), - ) - require.ErrorIs(t, err, injectedErr) - require.Empty(t, bn) - require.Empty(t, key) - require.Empty(t, ai) - }) - - t.Run("second securefifo", func(t *testing.T) { - be := FromDatastore(datastore.InMemory()) - injectedErr := errors.New("test") - firstSecureFifoCreated := false - firstSecureFifoClosed := false - be.(*beDatastore).newSecureFifo = func() (securefifo.Writer, error) { - if firstSecureFifoCreated { - return nil, injectedErr - } - - firstSecureFifoCreated = true - w, err := securefifo.New() - require.NoError(t, err) - - return &sfwWrapper{ - w: w, - closeFn: func() error { - firstSecureFifoClosed = true - return w.Close() - }, - }, nil - } - - bn, key, ai, err := be.Create( - t.Context(), - blobtypes.Static, - bytes.NewReader(nil), - ) - require.ErrorIs(t, err, injectedErr) - require.Empty(t, bn) - require.Empty(t, key) - require.Empty(t, ai) - require.True(t, firstSecureFifoCreated) - require.True(t, firstSecureFifoClosed) - }) - }) - - t.Run("fail to call Done on secure fifos", func(t *testing.T) { - for i := 0; i < 2; i++ { - t.Run(fmt.Sprint(i), func(t *testing.T) { - be := FromDatastore(datastore.InMemory()) - injectedErr := errors.New("test") - secureFifosCreated := 0 - secureFifosClosed := 0 - - be.(*beDatastore).newSecureFifo = func() (securefifo.Writer, error) { - shouldReturnError := secureFifosCreated == i // Inject error on Done for i'th secure fifo - secureFifosCreated++ - sf, err := securefifo.New() - require.NoError(t, err) - return &sfwWrapper{ - w: sf, - closeFn: func() error { - secureFifosClosed++ - return sf.Close() - }, - doneFn: func() (securefifo.Reader, error) { - if shouldReturnError { - return nil, injectedErr - } - return sf.Done() - }, - }, nil - } - - bn, key, ai, err := be.Create( - t.Context(), - blobtypes.Static, - bytes.NewReader(nil), - ) - require.ErrorIs(t, err, injectedErr) - require.Empty(t, bn) - require.Empty(t, key) - require.Empty(t, ai) - require.Equal(t, 2, secureFifosCreated) - require.Equal(t, secureFifosCreated, secureFifosClosed) - }) - } - }) - - t.Run("fail to call write to secure fifos", func(t *testing.T) { - for i := 0; i < 2; i++ { - t.Run(fmt.Sprint(i), func(t *testing.T) { - be := FromDatastore(datastore.InMemory()) - injectedErr := errors.New("test") - secureFifosCreated := 0 - secureFifosClosed := 0 - - be.(*beDatastore).newSecureFifo = func() (securefifo.Writer, error) { - shouldReturnError := secureFifosCreated == i // Inject error on Done for i'th secure fifo - secureFifosCreated++ - sf, err := securefifo.New() - require.NoError(t, err) - return &sfwWrapper{ - w: sf, - closeFn: func() error { - secureFifosClosed++ - return sf.Close() - }, - writeFn: func(b []byte) (int, error) { - if shouldReturnError { - return 0, injectedErr - } - return sf.Write(b) - }, - }, nil - } - - bn, key, ai, err := be.Create( - t.Context(), - blobtypes.Static, - bytes.NewReader([]byte("Hello world")), - ) - require.ErrorIs(t, err, injectedErr) - require.Empty(t, bn) - require.Empty(t, key) - require.Empty(t, ai) - require.Equal(t, 2, secureFifosCreated) - require.Equal(t, secureFifosCreated, secureFifosClosed) - }) - } - }) - - t.Run("fail to read data", func(t *testing.T) { - be := FromDatastore(datastore.InMemory()) - injectedErr := errors.New("test") - secureFifosCreated := 0 - secureFifosClosed := 0 - - // To check if secure fifos are closed correctly - be.(*beDatastore).newSecureFifo = func() (securefifo.Writer, error) { - secureFifosCreated++ - w, err := securefifo.New() - require.NoError(t, err) - - return &sfwWrapper{ - w: w, - closeFn: func() error { - secureFifosClosed++ - return w.Close() - }, - }, nil - } - - bn, key, ai, err := be.Create( - t.Context(), - blobtypes.Static, - iotest.ErrReader(injectedErr), - ) - require.ErrorIs(t, err, injectedErr) - require.Empty(t, bn) - require.Empty(t, key) - require.Empty(t, ai) - require.Equal(t, 2, secureFifosCreated) - require.Equal(t, secureFifosCreated, secureFifosClosed) - }) - - t.Run("fail to store blob", func(t *testing.T) { - injectedErr := errors.New("test") - - dsw := dsWrapper{DS: datastore.InMemory()} - be := FromDatastore(&dsw) - - secureFifosCreated := 0 - secureFifosClosed := 0 - - // To check if secure fifos are closed correctly - be.(*beDatastore).newSecureFifo = func() (securefifo.Writer, error) { - secureFifosCreated++ - w, err := securefifo.New() - require.NoError(t, err) - - return &sfwWrapper{ - w: w, - closeFn: func() error { - secureFifosClosed++ - return w.Close() - }, - }, nil - } - - dsw.updateFn = func(ctx context.Context, name *common.BlobName, r io.Reader) error { return injectedErr } - - bn, key, ai, err := be.Create( - t.Context(), - blobtypes.Static, - bytes.NewReader(nil), - ) - require.ErrorIs(t, err, injectedErr) - require.Empty(t, bn) - require.Empty(t, key) - require.Empty(t, ai) - - require.Equal(t, 2, secureFifosCreated) - require.Equal(t, secureFifosCreated, secureFifosClosed) - }) -} diff --git a/pkg/blenc/interface.go b/pkg/blenc/interface.go deleted file mode 100644 index 5128f35..0000000 --- a/pkg/blenc/interface.go +++ /dev/null @@ -1,89 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package blenc - -import ( - "context" - "io" - - "github.com/cinode/go/pkg/common" - "github.com/cinode/go/pkg/datastore" -) - -// AuthInfo is an opaque data that is necessary to perform update of a blob with the same name -type AuthInfo = []byte - -var ( - ErrNotFound = datastore.ErrNotFound -) - -// BE interface describes functionality exposed by Blob Encryption layer -// implementation -type BE interface { - - // Open opens given blob data for reading. - // - // If returned error is not nil, the reader must be nil. Otherwise it is required to - // close the reader once done working with it. - Open( - ctx context.Context, - name *common.BlobName, - key *common.BlobKey, - ) ( - io.ReadCloser, - error, - ) - - // Create completely new blob with given dataset, as a result, the blob name and optional - // AuthInfo that allows blob's update is returned - Create( - ctx context.Context, - blobType common.BlobType, - r io.Reader, - ) ( - *common.BlobName, - *common.BlobKey, - *common.AuthInfo, - error, - ) - - // Update updates given blob type with new data, - // The update must happen within a single blob name (i.e. it can not end up with blob with different name) - // and may not be available for certain blob types such as static blobs. - // A valid auth info is necessary to ensure a correct new content can be created - Update( - ctx context.Context, - name *common.BlobName, - ai *common.AuthInfo, - key *common.BlobKey, - r io.Reader, - ) error - - // Exists does check whether blob of given name exists. It forwards the call - // to underlying datastore. - Exists( - ctx context.Context, - name *common.BlobName, - ) (bool, error) - - // Delete tries to remove blob with given name. It forwards the call to - // underlying datastore. - Delete( - ctx context.Context, - name *common.BlobName, - ) error -} diff --git a/pkg/blenc/interface_test.go b/pkg/blenc/interface_test.go deleted file mode 100644 index eafaa94..0000000 --- a/pkg/blenc/interface_test.go +++ /dev/null @@ -1,341 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package blenc - -import ( - "bytes" - "crypto/sha256" - "errors" - "io" - "testing" - "testing/iotest" - - "github.com/cinode/go/pkg/blobtypes" - "github.com/cinode/go/pkg/common" - "github.com/cinode/go/pkg/datastore" - "github.com/cinode/go/pkg/internal/blobtypes/dynamiclink" - "github.com/cinode/go/pkg/internal/utilities/cipherfactory" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" -) - -type BlencTestSuite struct { - suite.Suite - be BE -} - -func TestBlencTestSuite(t *testing.T) { - suite.Run(t, &BlencTestSuite{ - be: FromDatastore(datastore.InMemory()), - }) -} - -func (s *BlencTestSuite) TestStaticBlobs() { - t := s.T() - data := []byte("Hello world!!!") - - bn, key, ai, err := s.be.Create( - t.Context(), - blobtypes.Static, - bytes.NewReader(data), - ) - require.NoError(t, err) - require.Equal(t, blobtypes.Static, bn.Type()) - require.Len(t, bn.Hash(), sha256.Size) - require.Nil(t, ai) // Static blobs don't generate auth info - - t.Run("check successful operations on a static blob", func(t *testing.T) { - t.Run("blob must be reported as existing", func(t *testing.T) { - exists, err := s.be.Exists(t.Context(), bn) - require.NoError(t, err) - require.True(t, exists) - }) - - t.Run("must correctly read blob's content", func(t *testing.T) { - rc, err := s.be.Open(t.Context(), bn, key) - require.NoError(t, err) - - readData, err := io.ReadAll(rc) - require.NoError(t, err) - require.Equal(t, data, readData) - - err = rc.Close() - require.NoError(t, err) - }) - - t.Run("must correctly delete blob", func(t *testing.T) { - err := s.be.Delete(t.Context(), bn) - require.NoError(t, err) - - exists, err := s.be.Exists(t.Context(), bn) - require.NoError(t, err) - require.False(t, exists) - }) - }) - - t.Run("work with second static blob", func(t *testing.T) { - data2 := []byte("Hello Cinode!") - - bn2, key2, ai2, err := s.be.Create( - t.Context(), - blobtypes.Static, - bytes.NewReader(data2), - ) - require.NoError(t, err) - require.NotEqual(t, bn, bn2) - require.Nil(t, ai2) - - t.Run("new static blob must be different from the first one", func(t *testing.T) { - require.NoError(t, err) - require.NotEqual(t, key, key2) - require.Len(t, key2.Bytes(), len(key.Bytes())) - }) - - t.Run("must fail to update static blob", func(t *testing.T) { - data3 := []byte("Hello Universe!") - - err := s.be.Update( - t.Context(), - bn2, - ai2, - key2, - bytes.NewReader(data3), - ) - require.ErrorIs(t, err, ErrCanNotUpdateStaticBlob) - }) - - t.Run("must fail to open static blob with wrong key", func(t *testing.T) { - err := func() error { - rc, err := s.be.Open(t.Context(), bn2, key) - if err != nil { - return err - } - - _, err = io.ReadAll(rc) - if err != nil { - return err - } - - return rc.Close() - }() - require.ErrorIs(t, err, blobtypes.ErrValidationFailed) - }) - - t.Run("must fail to open static blob with invalid key", func(t *testing.T) { - brokenKey := common.BlobKeyFromBytes(key2.Bytes()[1:]) - rc, err := s.be.Open(t.Context(), bn2, brokenKey) - require.ErrorIs(t, err, cipherfactory.ErrInvalidEncryptionConfig) - require.Nil(t, rc) - }) - }) -} - -func (s *BlencTestSuite) TestDynamicLinkSuccessPath() { - t := s.T() - - data := []byte("Hello world!!!") - - bn, key, ai, err := s.be.Create( - t.Context(), - blobtypes.DynamicLink, - bytes.NewReader(data), - ) - require.NoError(t, err) - require.Equal(t, blobtypes.DynamicLink, bn.Type()) - require.Len(t, bn.Hash(), sha256.Size) - require.NotNil(t, ai) - - t.Run("check successful operations on a dynamic link", func(t *testing.T) { - t.Run("blob must be reported as existing", func(t *testing.T) { - exists, err := s.be.Exists(t.Context(), bn) - require.NoError(t, err) - require.True(t, exists) - }) - - t.Run("must correctly read blob's content", func(t *testing.T) { - rc, err := s.be.Open(t.Context(), bn, key) - require.NoError(t, err) - - readData, err := io.ReadAll(rc) - require.NoError(t, err) - require.Equal(t, data, readData) - - err = rc.Close() - require.NoError(t, err) - }) - - t.Run("must correctly delete blob", func(t *testing.T) { - err := s.be.Delete(t.Context(), bn) - require.NoError(t, err) - - exists, err := s.be.Exists(t.Context(), bn) - require.NoError(t, err) - require.False(t, exists) - }) - }) - - t.Run("work with second dynamic link", func(t *testing.T) { - data2 := []byte("Hello Cinode!") - - bn2, key2, ai2, err := s.be.Create( - t.Context(), - blobtypes.DynamicLink, - bytes.NewReader(data2), - ) - require.NoError(t, err) - require.NotEqual(t, bn, bn2) - require.NotNil(t, ai2) - - t.Run("new dynamic link must be different from the first one", func(t *testing.T) { - require.NoError(t, err) - require.NotEqual(t, key, key2) - require.Len(t, key2.Bytes(), len(key.Bytes())) - }) - - t.Run("must correctly read blob's content", func(t *testing.T) { - rc, err := s.be.Open(t.Context(), bn2, key2) - require.NoError(t, err) - - readData, err := io.ReadAll(rc) - require.NoError(t, err) - require.Equal(t, data2, readData) - - err = rc.Close() - require.NoError(t, err) - }) - - t.Run("must correctly update dynamic link", func(t *testing.T) { - data3 := []byte("Hello Universe!") - - err = s.be.Update(t.Context(), bn2, ai2, key2, bytes.NewReader(data3)) - require.NoError(t, err) - - rc, err := s.be.Open(t.Context(), bn2, key2) - require.NoError(t, err) - - readData, err := io.ReadAll(rc) - require.NoError(t, err) - require.Equal(t, data3, readData) - - err = rc.Close() - require.NoError(t, err) - }) - - t.Run("must fail to update if encryption key is invalid", func(t *testing.T) { - err := s.be.Update( - t.Context(), - bn2, - ai2, - key, - bytes.NewReader(nil), - ) - require.ErrorIs(t, err, ErrDynamicLinkUpdateFailed) - require.ErrorIs(t, err, ErrDynamicLinkUpdateFailedWrongKey) - }) - - t.Run("must fail to update if blob name is invalid", func(t *testing.T) { - err := s.be.Update( - t.Context(), - bn, - ai2, - key2, - bytes.NewReader(nil), - ) - require.ErrorIs(t, err, ErrDynamicLinkUpdateFailed) - require.ErrorIs(t, err, ErrDynamicLinkUpdateFailedWrongName) - }) - - t.Run("must fail to update if auth info is invalid", func(t *testing.T) { - brokenAI2 := common.AuthInfoFromBytes(ai2.Bytes()[1:]) - err := s.be.Update( - t.Context(), - bn, - brokenAI2, - key2, - bytes.NewReader(nil), - ) - require.ErrorIs(t, err, dynamiclink.ErrInvalidDynamicLinkAuthInfo) - }) - - t.Run("must fail to update link on read errors", func(t *testing.T) { - injectedErr := errors.New("test") - - err := s.be.Update( - t.Context(), - bn, - ai2, - key2, - iotest.ErrReader(injectedErr), - ) - require.ErrorIs(t, err, injectedErr) - }) - }) - - t.Run("must fail to create link on read errors", func(t *testing.T) { - injectedErr := errors.New("test") - - bn, key, ai, err := s.be.Create( - t.Context(), - blobtypes.DynamicLink, - iotest.ErrReader(injectedErr), - ) - require.ErrorIs(t, err, injectedErr) - require.Empty(t, bn) - require.Empty(t, key) - require.Empty(t, ai) - }) -} - -func (s *BlencTestSuite) TestInvalidBlobTypes() { - t := s.T() - - invalidBlobName, err := common.BlobNameFromHashAndType(sha256.New().Sum(nil), blobtypes.Invalid) - require.NoError(t, err) - - t.Run("must fail to create blob of invalid type", func(t *testing.T) { - bn, key, ai, err := s.be.Create( - t.Context(), - blobtypes.Invalid, - bytes.NewReader(nil), - ) - require.ErrorIs(t, err, blobtypes.ErrUnknownBlobType) - require.Empty(t, bn) - require.Empty(t, key) - require.Empty(t, ai) - }) - - t.Run("must fail to open blob of invalid type", func(t *testing.T) { - rc, err := s.be.Open( - t.Context(), - invalidBlobName, - nil, - ) - require.ErrorIs(t, err, blobtypes.ErrUnknownBlobType) - require.Nil(t, rc) - }) - - t.Run("must fail to update blob of invalid type", func(t *testing.T) { - err = s.be.Update( - t.Context(), - invalidBlobName, - nil, - nil, - bytes.NewReader(nil), - ) - require.ErrorIs(t, err, blobtypes.ErrUnknownBlobType) - }) -} diff --git a/pkg/blobtypes/list.go b/pkg/blobtypes/list.go index e9f5202..9d81c64 100644 --- a/pkg/blobtypes/list.go +++ b/pkg/blobtypes/list.go @@ -1,5 +1,5 @@ /* -Copyright © 2023 Bartłomiej Święcki (byo) +Copyright © 2025 Bartłomiej Święcki (byo) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ package blobtypes import ( "fmt" - "github.com/cinode/go/pkg/common" + "github.com/cinode/go-datastore/pkg/common" ) var ( diff --git a/pkg/blobtypes/list_test.go b/pkg/blobtypes/list_test.go index 7e38bee..25c95e4 100644 --- a/pkg/blobtypes/list_test.go +++ b/pkg/blobtypes/list_test.go @@ -1,5 +1,5 @@ /* -Copyright © 2023 Bartłomiej Święcki (byo) +Copyright © 2025 Bartłomiej Święcki (byo) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ package blobtypes import ( "testing" - "github.com/cinode/go/pkg/common" + "github.com/cinode/go-datastore/pkg/common" "github.com/stretchr/testify/require" ) diff --git a/pkg/cinodefs/cinodefs_interface.go b/pkg/cinodefs/cinodefs_interface.go deleted file mode 100644 index eee0fd2..0000000 --- a/pkg/cinodefs/cinodefs_interface.go +++ /dev/null @@ -1,472 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package cinodefs - -import ( - "context" - "crypto/rand" - "errors" - "fmt" - "io" - "mime" - "net/http" - "path/filepath" - "time" - - "github.com/cinode/go/pkg/blenc" - "github.com/cinode/go/pkg/blobtypes" - "github.com/cinode/go/pkg/common" - "github.com/cinode/go/pkg/internal/blobtypes/dynamiclink" - "github.com/cinode/go/pkg/internal/utilities/headwriter" - "github.com/cinode/go/pkg/utilities/golang" -) - -var ( - ErrInvalidBE = errors.New("invalid BE argument") - ErrCantOpenDir = errors.New("can not open directory") - ErrCantOpenDirDuplicateEntry = fmt.Errorf("%w: duplicate entry", ErrCantOpenDir) - ErrCantOpenLink = errors.New("can not open link") - ErrTooManyRedirects = errors.New("too many link redirects") - ErrCantComputeBlobKey = errors.New("can not compute blob keys") - ErrModifiedDirectory = errors.New("can not get entrypoint for a directory, unsaved content") - ErrCantDeleteRoot = errors.New("can not delete root object") - ErrNotADirectory = errors.New("entry is not a directory") - ErrNotALink = errors.New("entry is not a link") - ErrNilEntrypoint = errors.New("nil entrypoint") - ErrEmptyName = errors.New("entry name can not be empty") - ErrEntryNotFound = errors.New("entry not found") - ErrIsADirectory = errors.New("entry is a directory") - ErrInvalidDirectoryData = errors.New("invalid directory data") - ErrCantWriteDirectory = errors.New("can not write directory") - ErrMissingRootInfo = errors.New("root info not specified") -) - -const ( - CinodeDirMimeType = "application/cinode-dir" -) - -type FS interface { - SetEntryFile( - ctx context.Context, - path []string, - data io.Reader, - opts ...EntrypointOption, - ) (*Entrypoint, error) - - CreateFileEntrypoint( - ctx context.Context, - data io.Reader, - opts ...EntrypointOption, - ) (*Entrypoint, error) - - SetEntry( - ctx context.Context, - path []string, - ep *Entrypoint, - ) error - - ResetDir( - ctx context.Context, - path []string, - ) error - - Flush( - ctx context.Context, - ) error - - FindEntry( - ctx context.Context, - path []string, - ) (*Entrypoint, error) - - DeleteEntry( - ctx context.Context, - path []string, - ) error - - InjectDynamicLink( - ctx context.Context, - path []string, - ) ( - *WriterInfo, - error, - ) - - OpenEntryData( - ctx context.Context, - path []string, - ) (io.ReadCloser, error) - - OpenEntrypointData( - ctx context.Context, - ep *Entrypoint, - ) (io.ReadCloser, error) - - RootEntrypoint() (*Entrypoint, error) - - EntrypointWriterInfo( - ctx context.Context, - ep *Entrypoint, - ) (*WriterInfo, error) - - RootWriterInfo( - ctx context.Context, - ) (*WriterInfo, error) -} - -type cinodeFS struct { - c graphContext - randSource io.Reader - rootEP node - timeFunc func() time.Time - maxLinkRedirects int -} - -func New( - ctx context.Context, - be blenc.BE, - options ...Option, -) (FS, error) { - if be == nil { - return nil, ErrInvalidBE - } - - ret := cinodeFS{ - maxLinkRedirects: DefaultMaxLinksRedirects, - timeFunc: time.Now, - randSource: rand.Reader, - c: graphContext{ - be: be, - authInfos: map[string]*common.AuthInfo{}, - }, - } - - for _, opt := range options { - err := opt.apply(ctx, &ret) - if err != nil { - return nil, err - } - } - - if ret.rootEP == nil { - return nil, ErrMissingRootInfo - } - - return &ret, nil -} - -func (fs *cinodeFS) SetEntryFile( - ctx context.Context, - path []string, - data io.Reader, - opts ...EntrypointOption, -) (*Entrypoint, error) { - ep := entrypointFromOptions(ctx, opts...) - if ep.ep.MimeType == "" && len(path) > 0 { - // Try detecting mime type from filename extension - ep.ep.MimeType = mime.TypeByExtension(filepath.Ext(path[len(path)-1])) - } - - ep, err := fs.createFileEntrypoint(ctx, data, ep) - if err != nil { - return nil, err - } - - err = fs.SetEntry(ctx, path, ep) - if err != nil { - return nil, err - } - - return ep, nil -} - -func (fs *cinodeFS) CreateFileEntrypoint( - ctx context.Context, - data io.Reader, - opts ...EntrypointOption, -) (*Entrypoint, error) { - ep := entrypointFromOptions(ctx, opts...) - return fs.createFileEntrypoint(ctx, data, ep) -} - -func (fs *cinodeFS) createFileEntrypoint( - ctx context.Context, - data io.Reader, - ep *Entrypoint, -) (*Entrypoint, error) { - var hw headwriter.Writer - - if ep.ep.MimeType == "" { - // detect mimetype from the content - hw = headwriter.New(512) - data = io.TeeReader(data, &hw) - } - - bn, key, _, err := fs.c.be.Create(ctx, blobtypes.Static, data) - if err != nil { - return nil, err - } - - if ep.ep.MimeType == "" { - ep.ep.MimeType = http.DetectContentType(hw.Head()) - } - - return setEntrypointBlobNameAndKey(bn, key, ep), nil -} - -func (fs *cinodeFS) SetEntry( - ctx context.Context, - path []string, - ep *Entrypoint, -) error { - whenReached := func( - ctx context.Context, - current node, - isWriteable bool, - ) (node, dirtyState, error) { - if !isWriteable { - return nil, 0, ErrMissingWriterInfo - } - return &nodeUnloaded{ep: ep}, dsDirty, nil - } - - return fs.traverseGraph( - ctx, - path, - traverseOptions{ - createNodes: true, - maxLinkRedirects: fs.maxLinkRedirects, - }, - whenReached, - ) -} - -func (fs *cinodeFS) ResetDir(ctx context.Context, path []string) error { - whenReached := func( - ctx context.Context, - current node, - isWriteable bool, - ) (node, dirtyState, error) { - if !isWriteable { - return nil, 0, ErrMissingWriterInfo - } - return &nodeDirectory{ - entries: map[string]node{}, - dState: dsDirty, - }, dsDirty, nil - } - - return fs.traverseGraph( - ctx, - path, - traverseOptions{ - createNodes: true, - maxLinkRedirects: fs.maxLinkRedirects, - }, - whenReached, - ) -} - -func (fs *cinodeFS) Flush(ctx context.Context) error { - _, newRootEP, err := fs.rootEP.flush(ctx, &fs.c) - if err != nil { - return err - } - - fs.rootEP = &nodeUnloaded{ep: newRootEP} - return nil -} - -func (fs *cinodeFS) FindEntry(ctx context.Context, path []string) (*Entrypoint, error) { - var ret *Entrypoint - err := fs.traverseGraph( - ctx, - path, - traverseOptions{ - doNotCache: true, - }, - func(_ context.Context, ep node, _ bool) (node, dirtyState, error) { - var subErr error - ret, subErr = ep.entrypoint() - return ep, dsClean, subErr - }, - ) - if err != nil { - return nil, err - } - return ret, nil -} - -func (fs *cinodeFS) DeleteEntry(ctx context.Context, path []string) error { - // Entry removal is done on the parent level, we find the parent directory - // and remove the entry from its list - if len(path) == 0 { - return ErrCantDeleteRoot - } - - return fs.traverseGraph( - ctx, - path[:len(path)-1], - traverseOptions{createNodes: true}, - func(_ context.Context, reachedEntrypoint node, isWriteable bool) (node, dirtyState, error) { - if !isWriteable { - return nil, 0, ErrMissingWriterInfo - } - - dir, isDir := reachedEntrypoint.(*nodeDirectory) - if !isDir { - return nil, 0, ErrNotADirectory - } - - if !dir.deleteEntry(path[len(path)-1]) { - return nil, 0, ErrEntryNotFound - } - - return dir, dsDirty, nil - }, - ) -} - -func (fs *cinodeFS) InjectDynamicLink( - ctx context.Context, - path []string, -) ( - *WriterInfo, - error, -) { - var retWi *WriterInfo - - whenReached := func( - ctx context.Context, - current node, - isWriteable bool, - ) (node, dirtyState, error) { - if !isWriteable { - return nil, 0, ErrMissingWriterInfo - } - - ep, ai, err := fs.generateNewDynamicLinkEntrypoint() - if err != nil { - return nil, 0, err - } - - key, err := fs.c.keyFromEntrypoint(ctx, ep) - if err != nil { - return nil, 0, err - } - - retWi = writerInfoFromBlobNameKeyAndAuthInfo(ep.BlobName(), key, ai) - return &nodeLink{ - ep: ep, - target: current, - // Link itself must be marked as dirty - even if the content is clean, - // the link itself must be persisted - dState: dsSubDirty, - }, - // Parent node becomes dirty - new link is a new blob - dsDirty, - nil - } - - err := fs.traverseGraph( - ctx, - path, - traverseOptions{ - createNodes: true, - maxLinkRedirects: fs.maxLinkRedirects, - }, - whenReached, - ) - if err != nil { - return nil, err - } - - return retWi, nil -} - -func (fs *cinodeFS) generateNewDynamicLinkEntrypoint() (*Entrypoint, *common.AuthInfo, error) { - // Generate new entrypoint link data but do not yet store it in datastore - link, err := dynamiclink.Create(fs.randSource) - if err != nil { - return nil, nil, err - } - - bn := link.BlobName() - key := link.EncryptionKey() - ai := link.AuthInfo() - - fs.c.authInfos[bn.String()] = ai - - return EntrypointFromBlobNameAndKey(bn, key), ai, nil -} - -func (fs *cinodeFS) OpenEntryData(ctx context.Context, path []string) (io.ReadCloser, error) { - ep, err := fs.FindEntry(ctx, path) - if err != nil { - return nil, err - } - if ep.IsDir() { - return nil, ErrIsADirectory - } - golang.Assert( - !ep.IsLink(), - "assumed that fs.FindEntry does not return a link", - ) - - return fs.OpenEntrypointData(ctx, ep) -} - -func (fs *cinodeFS) OpenEntrypointData(ctx context.Context, ep *Entrypoint) (io.ReadCloser, error) { - if ep == nil { - return nil, ErrNilEntrypoint - } - - return fs.c.getDataReader(ctx, ep) -} - -func (fs *cinodeFS) RootEntrypoint() (*Entrypoint, error) { - return fs.rootEP.entrypoint() -} - -func (fs *cinodeFS) EntrypointWriterInfo(ctx context.Context, ep *Entrypoint) (*WriterInfo, error) { - if !ep.IsLink() { - return nil, ErrNotALink - } - - bn := ep.BlobName() - - key, err := fs.c.keyFromEntrypoint(ctx, ep) - if err != nil { - return nil, err - } - - authInfo, found := fs.c.authInfos[bn.String()] - if !found { - return nil, ErrMissingWriterInfo - } - - return writerInfoFromBlobNameKeyAndAuthInfo(bn, key, authInfo), nil -} - -func (fs *cinodeFS) RootWriterInfo(ctx context.Context) (*WriterInfo, error) { - rootEP, err := fs.RootEntrypoint() - if err != nil { - return nil, err - } - - return fs.EntrypointWriterInfo(ctx, rootEP) -} diff --git a/pkg/cinodefs/cinodefs_interface_bb_test.go b/pkg/cinodefs/cinodefs_interface_bb_test.go deleted file mode 100644 index 15c2c7e..0000000 --- a/pkg/cinodefs/cinodefs_interface_bb_test.go +++ /dev/null @@ -1,949 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package cinodefs_test - -import ( - "bytes" - "context" - "crypto/rand" - "errors" - "fmt" - "io" - "slices" - "strings" - "testing" - "testing/iotest" - "time" - - "github.com/cinode/go/pkg/blenc" - "github.com/cinode/go/pkg/cinodefs" - "github.com/cinode/go/pkg/cinodefs/protobuf" - "github.com/cinode/go/pkg/common" - "github.com/cinode/go/pkg/datastore" - "github.com/cinode/go/pkg/internal/blobtypes/dynamiclink" - "github.com/cinode/go/pkg/utilities/golang" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" - "google.golang.org/protobuf/proto" -) - -func TestCinodeFSSingleFileScenario(t *testing.T) { - fs, err := cinodefs.New(t.Context(), - blenc.FromDatastore(datastore.InMemory()), - cinodefs.NewRootDynamicLink(), - ) - require.NoError(t, err) - require.NotNil(t, fs) - - { // Check single file write operation - path1 := []string{"dir", "subdir", "file.txt"} - - ep1, err := fs.SetEntryFile(t.Context(), - path1, - strings.NewReader("Hello world!"), - ) - require.NoError(t, err) - require.NotNil(t, ep1) - - ep2, err := fs.FindEntry( - t.Context(), - path1, - ) - require.NoError(t, err) - require.NotNil(t, ep2) - - require.Equal(t, ep1.String(), ep2.String()) - - // Directories are modified, not yet flushed - for i := range path1 { - ep3, err := fs.FindEntry(t.Context(), path1[:i]) - require.ErrorIs(t, err, cinodefs.ErrModifiedDirectory) - require.Nil(t, ep3) - } - - err = fs.Flush(t.Context()) - require.NoError(t, err) - } -} - -type testBEWrapper struct { - blenc.BE - - createFunc func( - ctx context.Context, blobType common.BlobType, r io.Reader, - ) (*common.BlobName, *common.BlobKey, *common.AuthInfo, error) - - updateFunc func( - ctx context.Context, name *common.BlobName, ai *common.AuthInfo, - key *common.BlobKey, r io.Reader, - ) error -} - -func (w *testBEWrapper) Create( - ctx context.Context, blobType common.BlobType, r io.Reader, -) (*common.BlobName, *common.BlobKey, *common.AuthInfo, error) { - if w.createFunc != nil { - return w.createFunc(ctx, blobType, r) - } - return w.BE.Create(ctx, blobType, r) -} - -func (w *testBEWrapper) Update( - ctx context.Context, name *common.BlobName, ai *common.AuthInfo, - key *common.BlobKey, r io.Reader, -) error { - if w.updateFunc != nil { - return w.updateFunc(ctx, name, ai, key, r) - } - return w.BE.Update(ctx, name, ai, key, r) -} - -type testFileEntry struct { - content string - mimeType string - path []string -} - -type CinodeFSMultiFileTestSuite struct { - suite.Suite - be testBEWrapper - ds datastore.DS - fs cinodefs.FS - randSource io.Reader - timeFunc func() time.Time - contentMap []testFileEntry - maxLinkRedirects int -} - -type randReaderForCinodeFSMultiFileTestSuite CinodeFSMultiFileTestSuite - -func (r *randReaderForCinodeFSMultiFileTestSuite) Read(b []byte) (int, error) { - return r.randSource.Read(b) -} - -func TestCinodeFSMultiFileTestSuite(t *testing.T) { - suite.Run(t, &CinodeFSMultiFileTestSuite{ - maxLinkRedirects: 5, - }) -} - -func (c *CinodeFSMultiFileTestSuite) SetupTest() { - t := c.T() - - c.timeFunc = time.Now - c.randSource = rand.Reader - c.ds = datastore.InMemory() - c.be = testBEWrapper{ - BE: blenc.FromDatastore(c.ds), - } - fs, err := cinodefs.New(t.Context(), - &c.be, - cinodefs.NewRootDynamicLink(), - cinodefs.MaxLinkRedirects(c.maxLinkRedirects), - cinodefs.TimeFunc(func() time.Time { return c.timeFunc() }), - cinodefs.RandSource((*randReaderForCinodeFSMultiFileTestSuite)(c)), - ) - require.NoError(t, err) - require.NotNil(t, fs) - c.fs = fs - - const testFilesCount = 10 - const dirsCount = 3 - const subDirsCount = 2 - - c.contentMap = make([]testFileEntry, testFilesCount) - for i := 0; i < testFilesCount; i++ { - c.contentMap[i].path = []string{ - fmt.Sprintf("dir%d", i%dirsCount), - fmt.Sprintf("subdir%d", i%subDirsCount), - fmt.Sprintf("file%d.txt", i), - } - c.contentMap[i].content = fmt.Sprintf("Hello world! from file %d!", i) - c.contentMap[i].mimeType = "text/plain" - } - - for _, file := range c.contentMap { - _, err := c.fs.SetEntryFile(t.Context(), - file.path, - strings.NewReader(file.content), - ) - require.NoError(t, err) - } - - err = c.fs.Flush(t.Context()) - require.NoError(t, err) -} - -func (c *CinodeFSMultiFileTestSuite) checkContentMap(t *testing.T, fs cinodefs.FS) { - for _, file := range c.contentMap { - t.Run(strings.Join(file.path, "/"), func(t *testing.T) { - ep, err := fs.FindEntry(t.Context(), file.path) - require.NoError(t, err) - require.Contains(t, ep.MimeType(), file.mimeType) - - rc, err := fs.OpenEntrypointData(t.Context(), ep) - require.NoError(t, err) - defer rc.Close() - - data, err := io.ReadAll(rc) - require.NoError(t, err) - - require.Equal(t, file.content, string(data)) - }) - } -} - -func (c *CinodeFSMultiFileTestSuite) TestReopeningInReadOnlyMode() { - t := c.T() - - rootEP, err := c.fs.RootEntrypoint() - require.NoError(t, err) - - fs2, err := cinodefs.New( - t.Context(), - blenc.FromDatastore(c.ds), - cinodefs.RootEntrypointString(rootEP.String()), - ) - require.NoError(t, err) - require.NotNil(t, fs2) - - c.checkContentMap(t, fs2) - - _, err = c.fs.SetEntryFile(t.Context(), - c.contentMap[0].path, - strings.NewReader("modified content"), - ) - require.NoError(t, err) - - // Data in fs was not yet flushed to the datastore, fs2 should still refer to the old content - c.checkContentMap(t, fs2) - - err = c.fs.Flush(t.Context()) - require.NoError(t, err) - - // reopen fs2 to avoid any caching issues - fs2, err = cinodefs.New( - t.Context(), - blenc.FromDatastore(c.ds), - cinodefs.RootEntrypoint(rootEP), - ) - require.NoError(t, err) - - // Check with modified content map - c.contentMap[0].content = "modified content" - c.checkContentMap(t, fs2) - - // We should not be allowed to modify fs2 without writer info - ep, err := fs2.SetEntryFile( - t.Context(), - c.contentMap[0].path, - strings.NewReader("should fail"), - ) - require.ErrorIs(t, err, cinodefs.ErrMissingWriterInfo) - require.Nil(t, ep) - c.checkContentMap(t, c.fs) - c.checkContentMap(t, fs2) -} - -func (c *CinodeFSMultiFileTestSuite) TestReopeningInReadWriteMode() { - t := c.T() - - rootWriterInfo, err := c.fs.RootWriterInfo(t.Context()) - require.NoError(t, err) - require.NotNil(t, rootWriterInfo) - - fs3, err := cinodefs.New( - t.Context(), - blenc.FromDatastore(c.ds), - cinodefs.RootWriterInfoString(rootWriterInfo.String()), - ) - require.NoError(t, err) - require.NotNil(t, fs3) - - c.checkContentMap(t, fs3) - - // With a proper auth info we can modify files in the root path - ep, err := fs3.SetEntryFile( - t.Context(), - c.contentMap[0].path, - strings.NewReader("modified through fs3"), - ) - require.NoError(t, err) - require.NotNil(t, ep) - - c.contentMap[0].content = "modified through fs3" - c.checkContentMap(t, fs3) -} - -func (c *CinodeFSMultiFileTestSuite) TestRemovalOfAFile() { - t := c.T() - - err := c.fs.DeleteEntry(t.Context(), c.contentMap[0].path) - require.NoError(t, err) - - c.contentMap = c.contentMap[1:] - c.checkContentMap(t, c.fs) -} - -func (c *CinodeFSMultiFileTestSuite) TestRemovalOfADirectory() { - t := c.T() - - removedPath := c.contentMap[0].path[:2] - - err := c.fs.DeleteEntry(t.Context(), removedPath) - require.NoError(t, err) - - filteredEntries := []testFileEntry{} - removed := 0 - for _, e := range c.contentMap { - if e.path[0] == removedPath[0] && e.path[1] == removedPath[1] { - continue - } - - filteredEntries = append(filteredEntries, e) - removed++ - } - c.contentMap = filteredEntries - require.NotZero(t, removed) - - c.checkContentMap(t, c.fs) - - err = c.fs.DeleteEntry(t.Context(), removedPath) - require.ErrorIs(t, err, cinodefs.ErrEntryNotFound) - - c.checkContentMap(t, c.fs) - - ep, err := c.fs.FindEntry(t.Context(), removedPath) - require.ErrorIs(t, err, cinodefs.ErrEntryNotFound) - require.Nil(t, ep) - - err = c.fs.DeleteEntry(t.Context(), []string{}) - require.ErrorIs(t, err, cinodefs.ErrCantDeleteRoot) -} - -func (c *CinodeFSMultiFileTestSuite) TestDeleteTreatFileAsDirectory() { - t := c.T() - - path := slices.Concat(c.contentMap[0].path, []string{"sub-file"}) - err := c.fs.DeleteEntry(t.Context(), path) - require.ErrorIs(t, err, cinodefs.ErrNotADirectory) -} - -func (c *CinodeFSMultiFileTestSuite) TestResetDir() { - t := c.T() - - removedPath := c.contentMap[0].path[:2] - - err := c.fs.ResetDir(t.Context(), removedPath) - require.NoError(t, err) - - filteredEntries := []testFileEntry{} - removed := 0 - for _, e := range c.contentMap { - if e.path[0] == removedPath[0] && e.path[1] == removedPath[1] { - continue - } - - filteredEntries = append(filteredEntries, e) - removed++ - } - c.contentMap = filteredEntries - require.NotZero(t, removed) - - c.checkContentMap(t, c.fs) - - err = c.fs.ResetDir(t.Context(), removedPath) - require.NoError(t, err) - - c.checkContentMap(t, c.fs) - - ep, err := c.fs.FindEntry(t.Context(), removedPath) - require.ErrorIs(t, err, cinodefs.ErrModifiedDirectory) - require.Nil(t, ep) -} - -func (c *CinodeFSMultiFileTestSuite) TestSettingEntry() { - t := c.T() - - t.Run("prevent treating file as directory", func(t *testing.T) { - path := slices.Concat(c.contentMap[0].path, []string{"sub-file"}) - _, err := c.fs.SetEntryFile(t.Context(), path, strings.NewReader("should not happen")) - require.ErrorIs(t, err, cinodefs.ErrNotADirectory) - }) - - t.Run("prevent setting empty path segment", func(t *testing.T) { - for _, path := range [][]string{ - {"", "subdir", "file.txt"}, - {"dir", "", "file.txt"}, - {"dir", "subdir", ""}, - } { - t.Run(strings.Join(path, "::"), func(t *testing.T) { - _, err := c.fs.SetEntryFile(t.Context(), path, strings.NewReader("should not succeed")) - require.ErrorIs(t, err, cinodefs.ErrEmptyName) - }) - } - }) - - t.Run("test root entrypoint on dirty filesystem", func(t *testing.T) { - ep1, err := c.fs.RootEntrypoint() - require.NoError(t, err) - - _, err = c.fs.SetEntryFile(t.Context(), c.contentMap[0].path, strings.NewReader("hello")) - require.NoError(t, err) - c.contentMap[0].content = "hello" - - ep2, err := c.fs.RootEntrypoint() - require.NoError(t, err) - - // Even though dirty, entrypoint won't change it's content - require.Equal(t, ep1.String(), ep2.String()) - - err = c.fs.Flush(t.Context()) - require.NoError(t, err) - - ep3, err := c.fs.RootEntrypoint() - require.NoError(t, err) - - require.Equal(t, ep1.String(), ep3.String()) - }) - - t.Run("test crete file entrypoint", func(t *testing.T) { - ep, err := c.fs.CreateFileEntrypoint(t.Context(), strings.NewReader("new file")) - require.NoError(t, err) - require.NotNil(t, ep) - - err = c.fs.SetEntry(t.Context(), []string{"new-file.txt"}, ep) - require.NoError(t, err) - - c.contentMap = append(c.contentMap, testFileEntry{ - path: []string{"new-file.txt"}, - content: "new file", - mimeType: ep.MimeType(), - }) - - c.checkContentMap(t, c.fs) - }) -} - -func (c *CinodeFSMultiFileTestSuite) TestRootEPDirectoryOnDirtyFS() { - t := c.T() - - rootDir, err := c.fs.FindEntry(t.Context(), []string{}) - require.NoError(t, err) - - fs2, err := cinodefs.New( - t.Context(), - blenc.FromDatastore(c.ds), - cinodefs.RootEntrypoint(rootDir), - ) - require.NoError(t, err) - - ep1, err := fs2.RootEntrypoint() - require.NoError(t, err) - require.Equal(t, rootDir.String(), ep1.String()) - - _, err = fs2.SetEntryFile(t.Context(), c.contentMap[0].path, strings.NewReader("hello")) - require.NoError(t, err) - - ep2, err := fs2.RootEntrypoint() - require.ErrorIs(t, err, cinodefs.ErrModifiedDirectory) - require.Nil(t, ep2) - - err = fs2.Flush(t.Context()) - require.NoError(t, err) - - ep3, err := c.fs.RootEntrypoint() - require.NoError(t, err) - - require.NotEqual(t, ep1.String(), ep3.String()) -} - -func (c *CinodeFSMultiFileTestSuite) TestOpeningData() { - t := c.T() - - _, err := c.fs.OpenEntrypointData(t.Context(), nil) - require.ErrorIs(t, err, cinodefs.ErrNilEntrypoint) - - _, err = c.fs.OpenEntryData(t.Context(), []string{"a", "b", "c"}) - require.ErrorIs(t, err, cinodefs.ErrEntryNotFound) - - _, err = c.fs.OpenEntryData(t.Context(), []string{}) - require.ErrorIs(t, err, cinodefs.ErrIsADirectory) - - contentReader, err := c.fs.OpenEntryData(t.Context(), c.contentMap[0].path) - require.NoError(t, err) - content, err := io.ReadAll(contentReader) - require.NoError(t, err) - require.Equal(t, c.contentMap[0].content, string(content)) -} - -func (c *CinodeFSMultiFileTestSuite) TestSubLinksAndWriteOnlyPath() { - t := c.T() - - path := append([]string{}, c.contentMap[0].path...) - path = append(path[:len(path)-1], "linked", "sub", "directory", "linked-file.txt") - linkPath := path[:len(path)-2] - - // Create normal file - ep, err := c.fs.SetEntryFile(t.Context(), path, strings.NewReader("linked-file")) - require.NoError(t, err) - c.contentMap = append(c.contentMap, testFileEntry{ - path: path, - content: "linked-file", - mimeType: ep.MimeType(), - }) - c.checkContentMap(t, c.fs) - - // Convert path to the file to a dynamic link - wi, err := c.fs.InjectDynamicLink(t.Context(), linkPath) - require.NoError(t, err) - require.NotNil(t, wi) - c.checkContentMap(t, c.fs) - - // Ensure flushing through the dynamic link works - err = c.fs.Flush(t.Context()) - require.NoError(t, err) - c.checkContentMap(t, c.fs) - - // Ensure the content can still be changed - corresponding auth info - // is still kept in the concept - _, err = c.fs.SetEntryFile(t.Context(), path, strings.NewReader("updated-linked-file")) - require.NoError(t, err) - c.contentMap[len(c.contentMap)-1].content = "updated-linked-file" - c.checkContentMap(t, c.fs) - - // Ensure flushing works after the change behind the link - err = c.fs.Flush(t.Context()) - require.NoError(t, err) - c.checkContentMap(t, c.fs) - - rootWriterInfo, err := c.fs.RootWriterInfo(t.Context()) - require.NoError(t, err) - - // Reopen the filesystem, but only with the root writer info - fs2, err := cinodefs.New( - t.Context(), - blenc.FromDatastore(c.ds), - cinodefs.RootWriterInfoString(rootWriterInfo.String()), - ) - require.NoError(t, err) - c.checkContentMap(t, fs2) - - // Can not do any operation below the split point - ep, err = fs2.SetEntryFile( - t.Context(), - path, - strings.NewReader("won't work"), - ) - require.ErrorIs(t, err, cinodefs.ErrMissingWriterInfo) - require.Nil(t, ep) - - ep, err = fs2.SetEntryFile( - t.Context(), - slices.Concat( - path[:len(path)-1], - []string{"other", "directory", "path"}, - ), - strings.NewReader("won't work"), - ) - require.ErrorIs(t, err, cinodefs.ErrMissingWriterInfo) - require.Nil(t, ep) - - err = fs2.ResetDir(t.Context(), path[:len(path)-1]) - require.ErrorIs(t, err, cinodefs.ErrMissingWriterInfo) - - err = fs2.DeleteEntry(t.Context(), path) - require.ErrorIs(t, err, cinodefs.ErrMissingWriterInfo) - - _, err = fs2.InjectDynamicLink(t.Context(), path) - require.ErrorIs(t, err, cinodefs.ErrMissingWriterInfo) -} - -func (c *CinodeFSMultiFileTestSuite) TestMaxLinksRedirects() { - t := c.T() - - entryPath := c.contentMap[0].path - linkPath := entryPath[:len(entryPath)-1] - - // Up to max links redirects, lookup must be allowed - for i := 0; i < c.maxLinkRedirects; i++ { - _, err := c.fs.InjectDynamicLink(t.Context(), linkPath) - require.NoError(t, err) - - _, err = c.fs.FindEntry(t.Context(), entryPath) - require.NoError(t, err) - } - - // Cross the max redirects count, next lookup should fail - _, err := c.fs.InjectDynamicLink(t.Context(), linkPath) - require.NoError(t, err) - - _, err = c.fs.FindEntry(t.Context(), entryPath) - require.ErrorIs(t, err, cinodefs.ErrTooManyRedirects) -} - -func (c *CinodeFSMultiFileTestSuite) TestExplicitMimeType() { - t := c.T() - - entryPath := c.contentMap[0].path - const newMimeType = "forced-mime-type" - - _, err := c.fs.SetEntryFile( - t.Context(), - entryPath, - strings.NewReader("modified content"), - cinodefs.SetMimeType(newMimeType), - ) - require.NoError(t, err) - - entry, err := c.fs.FindEntry(t.Context(), entryPath) - require.NoError(t, err) - require.Equal(t, newMimeType, entry.MimeType()) -} - -func (c *CinodeFSMultiFileTestSuite) TestMalformedDirectory() { - t := c.T() - - var ep protobuf.Entrypoint - err := proto.Unmarshal( - golang.Must(c.fs.FindEntry(t.Context(), c.contentMap[0].path)).Bytes(), - &ep, - ) - require.NoError(t, err) - - var brokenEP protobuf.Entrypoint - proto.Merge(&brokenEP, &ep) - brokenEP.BlobName = []byte{} - - for _, d := range []struct { - err error - n string - d []byte - }{ - { - n: "malformed data", - d: []byte{23, 45, 67, 89, 12, 34, 56, 78, 90}, // Some malformed message - err: cinodefs.ErrCantOpenDir, - }, - { - n: "entry with empty name", - d: golang.Must(proto.Marshal(&protobuf.Directory{ - Entries: []*protobuf.Directory_Entry{{ - Name: "", - }}, - })), - err: cinodefs.ErrEmptyName, - }, - { - n: "two entries with the same name", - d: golang.Must(proto.Marshal(&protobuf.Directory{ - Entries: []*protobuf.Directory_Entry{ - {Name: "entry", Ep: &ep}, - {Name: "entry", Ep: &ep}, - }, - })), - err: cinodefs.ErrCantOpenDirDuplicateEntry, - }, - { - n: "missing entrypoint", - d: golang.Must(proto.Marshal(&protobuf.Directory{ - Entries: []*protobuf.Directory_Entry{ - {Name: "entry"}, - }, - })), - err: cinodefs.ErrInvalidEntrypointDataNil, - }, - { - n: "missing blob name", - d: golang.Must(proto.Marshal(&protobuf.Directory{ - Entries: []*protobuf.Directory_Entry{ - {Name: "entry", Ep: &brokenEP}, - }, - })), - err: common.ErrInvalidBlobName, - }, - } { - t.Run(d.n, func(t *testing.T) { - _, err := c.fs.SetEntryFile( - t.Context(), - []string{"dir"}, - bytes.NewReader(d.d), - cinodefs.SetMimeType(cinodefs.CinodeDirMimeType), - ) - require.NoError(t, err) - - _, err = c.fs.FindEntry(t.Context(), []string{"dir", "entry"}) - require.ErrorIs(t, err, cinodefs.ErrCantOpenDir) - require.ErrorIs(t, err, d.err) - - // TODO: We should be able to set new entry even if the underlying object is broken - err = c.fs.DeleteEntry(t.Context(), []string{"dir"}) - require.NoError(t, err) - }) - } -} - -func (c *CinodeFSMultiFileTestSuite) TestMalformedLink() { - t := c.T() - - var ep protobuf.Entrypoint - err := proto.Unmarshal( - golang.Must(c.fs.FindEntry(t.Context(), c.contentMap[0].path)).Bytes(), - &ep, - ) - require.NoError(t, err) - - var brokenEP protobuf.Entrypoint - proto.Merge(&brokenEP, &ep) - brokenEP.BlobName = []byte{} - - _, err = c.fs.SetEntryFile(t.Context(), []string{"link", "file"}, strings.NewReader("test")) - require.NoError(t, err) - - linkWI, err := c.fs.InjectDynamicLink(t.Context(), []string{"link"}) - require.NoError(t, err) - - // Flush is needed so that we can update entrypoint data and the fs cache won't get into our way - err = c.fs.Flush(t.Context()) - require.NoError(t, err) - - for _, d := range []struct { - err error - n string - d []byte - }{ - { - n: "malformed data", - d: []byte{23, 45, 67, 89, 12, 34, 56, 78, 90}, // Some malformed message - err: cinodefs.ErrCantOpenLink, - }, - { - n: "missing target blob name", - d: golang.Must(proto.Marshal(&brokenEP)), - err: common.ErrInvalidBlobName, - }, - } { - t.Run(d.n, func(t *testing.T) { - var linkWIParsed protobuf.WriterInfo - err = proto.Unmarshal(linkWI.Bytes(), &linkWIParsed) - require.NoError(t, err) - linkBlobName := golang.Must(common.BlobNameFromBytes(linkWIParsed.BlobName)) - linkAuthInfo := common.AuthInfoFromBytes(linkWIParsed.AuthInfo) - linkKey := common.BlobKeyFromBytes(linkWIParsed.Key) - - err = c.be.Update(t.Context(), - linkBlobName, linkAuthInfo, linkKey, bytes.NewReader(d.d), - ) - require.NoError(t, err) - - _, err = c.fs.FindEntry(t.Context(), []string{"link", "file"}) - require.ErrorIs(t, err, cinodefs.ErrCantOpenLink) - require.ErrorIs(t, err, d.err) - }) - } -} - -func (c *CinodeFSMultiFileTestSuite) TestPathWithMultipleLinks() { - t := c.T() - - path := []string{ - "multi", - "level", - "path", - "with", - "more", - "than", - "one", - "link", - } - - // Create test entry - const initialContent = "initial content" - ep, err := c.fs.SetEntryFile(t.Context(), path, strings.NewReader(initialContent)) - require.NoError(t, err) - - // Inject few links among the path to the entry - for _, splitPoint := range []int{2, 6, 4} { - _, err = c.fs.InjectDynamicLink(t.Context(), path[:splitPoint]) - require.NoError(t, err) - - err = c.fs.Flush(t.Context()) - require.NoError(t, err) - } - - // Create parallel filesystem - rootEP, err := c.fs.RootEntrypoint() - require.NoError(t, err) - - fs2, err := cinodefs.New(t.Context(), - blenc.FromDatastore(c.ds), - cinodefs.RootEntrypointString(rootEP.String()), - ) - require.NoError(t, err) - - c.contentMap = append(c.contentMap, testFileEntry{ - path: path, - content: initialContent, - mimeType: ep.MimeType(), - }) - c.checkContentMap(t, c.fs) - - // Modify the content of the file in the original filesystem, not yet flushed - const modifiedContent1 = "modified content 1" - _, err = c.fs.SetEntryFile(t.Context(), path, strings.NewReader(modifiedContent1)) - require.NoError(t, err) - - // Change not yet observed through the second filesystem due to no flush - c.checkContentMap(t, fs2) - - err = c.fs.Flush(t.Context()) - require.NoError(t, err) - - // Change must now be observed through the second filesystem - c.contentMap[len(c.contentMap)-1].content = modifiedContent1 - c.checkContentMap(t, c.fs) - c.checkContentMap(t, fs2) -} - -func (c *CinodeFSMultiFileTestSuite) TestBlobWriteErrorWhenCreatingFile() { - t := c.T() - - injectedErr := errors.New("entry file create error") - c.be.createFunc = func(ctx context.Context, blobType common.BlobType, r io.Reader, - ) (*common.BlobName, *common.BlobKey, *common.AuthInfo, error) { - return nil, nil, nil, injectedErr - } - - _, err := c.fs.SetEntryFile(t.Context(), []string{"file"}, strings.NewReader("test")) - require.ErrorIs(t, err, injectedErr) -} - -func (c *CinodeFSMultiFileTestSuite) TestBlobWriteErrorWhenFlushing() { - t := c.T() - - _, err := c.fs.SetEntryFile(t.Context(), []string{"file"}, strings.NewReader("test")) - require.NoError(t, err) - - injectedErr := errors.New("flush error") - c.be.createFunc = func(ctx context.Context, blobType common.BlobType, r io.Reader, - ) (*common.BlobName, *common.BlobKey, *common.AuthInfo, error) { - return nil, nil, nil, injectedErr - } - - err = c.fs.Flush(t.Context()) - require.ErrorIs(t, err, injectedErr) -} - -func (c *CinodeFSMultiFileTestSuite) TestLinkGenerationError() { - t := c.T() - - injectedErr := errors.New("rand data read error") - - c.randSource = iotest.ErrReader(injectedErr) - - _, err := c.fs.InjectDynamicLink( - t.Context(), - c.contentMap[0].path[:2], - ) - require.ErrorIs(t, err, injectedErr) -} - -func (c *CinodeFSMultiFileTestSuite) TestBlobWriteWhenCreatingLink() { - t := c.T() - - injectedErr := errors.New("link creation error") - c.be.updateFunc = func( - ctx context.Context, - name *common.BlobName, - ai *common.AuthInfo, - key *common.BlobKey, - r io.Reader, - ) error { - return injectedErr - } - - _, err := c.fs.InjectDynamicLink(t.Context(), c.contentMap[0].path[:2]) - require.NoError(t, err) - - err = c.fs.Flush(t.Context()) - require.ErrorIs(t, err, injectedErr) -} - -func (c *CinodeFSMultiFileTestSuite) TestReadFailureMissingKey() { - t := c.T() - - var epProto protobuf.Entrypoint - err := proto.Unmarshal( - golang.Must(c.fs.FindEntry(t.Context(), c.contentMap[0].path)).Bytes(), - &epProto, - ) - require.NoError(t, err) - - // Generate derived EP without key - epProto.KeyInfo.Key = nil - ep := golang.Must(cinodefs.EntrypointFromBytes( - golang.Must(proto.Marshal(&epProto)), - )) - - // Replace current entrypoint with one without the key - err = c.fs.SetEntry(t.Context(), c.contentMap[0].path, ep) - require.NoError(t, err) - - r, err := c.fs.OpenEntryData(t.Context(), c.contentMap[0].path) - require.ErrorIs(t, err, cinodefs.ErrMissingKeyInfo) - require.Nil(t, r) -} - -func TestFetchingWriterInfo(t *testing.T) { - t.Run("not a dynamic link", func(t *testing.T) { - fs, err := cinodefs.New( - t.Context(), - blenc.FromDatastore(datastore.InMemory()), - cinodefs.NewRootStaticDirectory(), - ) - require.NoError(t, err) - - wi, err := fs.RootWriterInfo(t.Context()) - require.ErrorIs(t, err, cinodefs.ErrModifiedDirectory) - require.Nil(t, wi) - - err = fs.Flush(t.Context()) - require.NoError(t, err) - - wi, err = fs.RootWriterInfo(t.Context()) - require.ErrorIs(t, err, cinodefs.ErrNotALink) - require.Nil(t, wi) - }) - - t.Run("dynamic link without writer info", func(t *testing.T) { - link, err := dynamiclink.Create(rand.Reader) - require.NoError(t, err) - ep := cinodefs.EntrypointFromBlobNameAndKey(link.BlobName(), link.EncryptionKey()) - - fs, err := cinodefs.New( - t.Context(), - blenc.FromDatastore(datastore.InMemory()), - // Set entrypoint without auth info - cinodefs.RootEntrypoint(ep), - ) - require.NoError(t, err) - - wi, err := fs.RootWriterInfo(t.Context()) - require.ErrorIs(t, err, cinodefs.ErrMissingWriterInfo) - require.Nil(t, wi) - }) -} diff --git a/pkg/cinodefs/cinodefs_options.go b/pkg/cinodefs/cinodefs_options.go deleted file mode 100644 index 79238fe..0000000 --- a/pkg/cinodefs/cinodefs_options.go +++ /dev/null @@ -1,168 +0,0 @@ -/* -Copyright © 2023 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package cinodefs - -import ( - "context" - "errors" - "fmt" - "io" - "time" - - "github.com/cinode/go/pkg/common" -) - -const ( - DefaultMaxLinksRedirects = 10 -) - -var ( - ErrNegativeMaxLinksRedirects = errors.New("negative value of maximum links redirects") - ErrInvalidNilTimeFunc = errors.New("nil time function") - ErrInvalidNilRandSource = errors.New("nil random source") -) - -type Option interface { - apply(ctx context.Context, fs *cinodeFS) error -} - -type errOption struct{ err error } - -func (e errOption) apply(ctx context.Context, fs *cinodeFS) error { return e.err } - -type optionFunc func(ctx context.Context, fs *cinodeFS) error - -func (f optionFunc) apply(ctx context.Context, fs *cinodeFS) error { - return f(ctx, fs) -} - -func MaxLinkRedirects(maxLinkRedirects int) Option { - if maxLinkRedirects < 0 { - return errOption{ErrNegativeMaxLinksRedirects} - } - return optionFunc(func(ctx context.Context, fs *cinodeFS) error { - fs.maxLinkRedirects = maxLinkRedirects - return nil - }) -} - -func RootEntrypoint(ep *Entrypoint) Option { - return optionFunc(func(ctx context.Context, fs *cinodeFS) error { - fs.rootEP = &nodeUnloaded{ep: ep} - return nil - }) -} - -func RootEntrypointString(eps string) Option { - ep, err := EntrypointFromString(eps) - if err != nil { - return errOption{err} - } - return RootEntrypoint(ep) -} - -func RootWriterInfo(wi *WriterInfo) Option { - if wi == nil { - return errOption{fmt.Errorf( - "%w: nil", - ErrInvalidWriterInfoData, - )} - } - bn, err := common.BlobNameFromBytes(wi.wi.BlobName) - if err != nil { - return errOption{fmt.Errorf( - "%w: %w", - ErrInvalidWriterInfoData, - err, - )} - } - - key := common.BlobKeyFromBytes(wi.wi.Key) - ep := EntrypointFromBlobNameAndKey(bn, key) - - return optionFunc(func(ctx context.Context, fs *cinodeFS) error { - fs.rootEP = &nodeUnloaded{ep: ep} - fs.c.authInfos[bn.String()] = common.AuthInfoFromBytes(wi.wi.AuthInfo) - return nil - }) -} - -func RootWriterInfoString(wis string) Option { - wi, err := WriterInfoFromString(wis) - if err != nil { - return errOption{err} - } - - return RootWriterInfo(wi) -} - -func TimeFunc(f func() time.Time) Option { - if f == nil { - return errOption{ErrInvalidNilTimeFunc} - } - return optionFunc(func(ctx context.Context, fs *cinodeFS) error { - fs.timeFunc = f - return nil - }) -} - -func RandSource(r io.Reader) Option { - if r == nil { - return errOption{ErrInvalidNilRandSource} - } - return optionFunc(func(ctx context.Context, fs *cinodeFS) error { - fs.randSource = r - return nil - }) -} - -// NewRootDynamicLink option can be used to create completely new, random -// dynamic link as the root -func NewRootDynamicLink() Option { - return optionFunc(func(ctx context.Context, fs *cinodeFS) error { - newLinkEntrypoint, _, err := fs.generateNewDynamicLinkEntrypoint() - if err != nil { - return err - } - - // Generate a simple dummy structure consisting of a root link - // and an empty directory, all the entries are in-memory upon - // creation and have to be flushed first to generate any - // blobs - fs.rootEP = &nodeLink{ - ep: newLinkEntrypoint, - dState: dsSubDirty, - target: &nodeDirectory{ - entries: map[string]node{}, - dState: dsDirty, - }, - } - return nil - }) -} - -// NewRootDynamicLink option can be used to create completely new, random -// dynamic link as the root -func NewRootStaticDirectory() Option { - return optionFunc(func(ctx context.Context, fs *cinodeFS) error { - fs.rootEP = &nodeDirectory{ - entries: map[string]node{}, - dState: dsDirty, - } - return nil - }) -} diff --git a/pkg/cinodefs/cinodefs_options_bb_test.go b/pkg/cinodefs/cinodefs_options_bb_test.go deleted file mode 100644 index 456fc5c..0000000 --- a/pkg/cinodefs/cinodefs_options_bb_test.go +++ /dev/null @@ -1,114 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package cinodefs_test - -import ( - "errors" - "testing" - "testing/iotest" - - "github.com/cinode/go/pkg/blenc" - "github.com/cinode/go/pkg/cinodefs" - "github.com/cinode/go/pkg/datastore" - "github.com/stretchr/testify/require" -) - -func TestInvalidCinodeFSOptions(t *testing.T) { - t.Run("no blenc", func(t *testing.T) { - cfs, err := cinodefs.New(t.Context(), nil) - require.ErrorIs(t, err, cinodefs.ErrInvalidBE) - require.Nil(t, cfs) - }) - - be := blenc.FromDatastore(datastore.InMemory()) - - t.Run("no root info", func(t *testing.T) { - cfs, err := cinodefs.New(t.Context(), be) - require.ErrorIs(t, err, cinodefs.ErrMissingRootInfo) - require.Nil(t, cfs) - }) - - t.Run("negative max links redirects", func(t *testing.T) { - cfs, err := cinodefs.New(t.Context(), be, - cinodefs.NewRootStaticDirectory(), - cinodefs.MaxLinkRedirects(-1), - ) - require.ErrorIs(t, err, cinodefs.ErrNegativeMaxLinksRedirects) - require.Nil(t, cfs) - }) - - t.Run("invalid entrypoint string", func(t *testing.T) { - cfs, err := cinodefs.New(t.Context(), be, - cinodefs.RootEntrypointString(""), - ) - require.ErrorIs(t, err, cinodefs.ErrInvalidEntrypointData) - require.Nil(t, cfs) - }) - - t.Run("invalid writer info string", func(t *testing.T) { - cfs, err := cinodefs.New(t.Context(), be, - cinodefs.RootWriterInfoString(""), - ) - require.ErrorIs(t, err, cinodefs.ErrInvalidWriterInfoData) - require.Nil(t, cfs) - }) - - t.Run("invalid nil writer info", func(t *testing.T) { - cfs, err := cinodefs.New(t.Context(), be, - cinodefs.RootWriterInfo(nil), - ) - require.ErrorIs(t, err, cinodefs.ErrInvalidWriterInfoData) - require.Nil(t, cfs) - }) - - t.Run("invalid writer info", func(t *testing.T) { - cfs, err := cinodefs.New(t.Context(), be, - cinodefs.RootWriterInfo(&cinodefs.WriterInfo{}), - ) - require.ErrorIs(t, err, cinodefs.ErrInvalidWriterInfoData) - require.Nil(t, cfs) - }) - - t.Run("invalid time func", func(t *testing.T) { - cfs, err := cinodefs.New(t.Context(), be, - cinodefs.TimeFunc(nil), - ) - require.ErrorIs(t, err, cinodefs.ErrInvalidNilTimeFunc) - require.Nil(t, cfs) - }) - - t.Run("invalid nil random source", func(t *testing.T) { - cfs, err := cinodefs.New(t.Context(), be, - cinodefs.RandSource(nil), - ) - require.ErrorIs(t, err, cinodefs.ErrInvalidNilRandSource) - require.Nil(t, cfs) - }) - - t.Run("invalid random source", func(t *testing.T) { - // Error will manifest itself while random data source - // is needed which only takes place when new random - // dynamic link is requested - injectedErr := errors.New("random source error") - cfs, err := cinodefs.New(t.Context(), be, - cinodefs.RandSource(iotest.ErrReader(injectedErr)), - cinodefs.NewRootDynamicLink(), - ) - require.ErrorIs(t, err, injectedErr) - require.Nil(t, cfs) - }) -} diff --git a/pkg/cinodefs/cinodefs_traverse.go b/pkg/cinodefs/cinodefs_traverse.go deleted file mode 100644 index 9c54074..0000000 --- a/pkg/cinodefs/cinodefs_traverse.go +++ /dev/null @@ -1,72 +0,0 @@ -/* -Copyright © 2023 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package cinodefs - -import ( - "context" -) - -type traverseGoalFunc func( - ctx context.Context, - reachedEntrypoint node, - isWriteable bool, -) ( - replacementEntrypoint node, - changeResult dirtyState, - err error, -) - -type traverseOptions struct { - createNodes bool - doNotCache bool - maxLinkRedirects int -} - -// Generic graph traversal function, it follows given path, once the endpoint -// is reached, it executed given callback function. -func (fs *cinodeFS) traverseGraph( - ctx context.Context, - path []string, - opts traverseOptions, - whenReached traverseGoalFunc, -) error { - for _, p := range path { - if p == "" { - return ErrEmptyName - } - } - - opts.maxLinkRedirects = fs.maxLinkRedirects - - changedEntrypoint, _, err := fs.rootEP.traverse( - ctx, // context - &fs.c, // graph context - path, // path - 0, // pathPosition - start at the beginning - 0, // linkDepth - we don't come from any link - true, // isWritable - root is always writable - opts, // traverseOptions - whenReached, // callback - ) - if err != nil { - return err - } - if !opts.doNotCache { - fs.rootEP = changedEntrypoint - } - return nil -} diff --git a/pkg/cinodefs/context.go b/pkg/cinodefs/context.go deleted file mode 100644 index bf445fc..0000000 --- a/pkg/cinodefs/context.go +++ /dev/null @@ -1,164 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package cinodefs - -import ( - "bytes" - "context" - "errors" - "fmt" - "io" - - "github.com/cinode/go/pkg/blenc" - "github.com/cinode/go/pkg/cinodefs/protobuf" - "github.com/cinode/go/pkg/common" - "google.golang.org/protobuf/proto" -) - -var ( - ErrMissingKeyInfo = errors.New("missing key info") - ErrMissingWriterInfo = errors.New("missing writer info") -) - -type graphContext struct { - // blenc layer used in the graph - be blenc.BE - - // known writer info data - authInfos map[string]*common.AuthInfo -} - -// Get symmetric encryption key for given entrypoint. -// -// Note: Currently the key will be stored inside entrypoint data, -// but more advanced methods of obtaining the key may be added -// through this function in the future. -func (c *graphContext) keyFromEntrypoint( - _ context.Context, - ep *Entrypoint, -) (*common.BlobKey, error) { - if ep.ep.KeyInfo == nil || - ep.ep.KeyInfo.Key == nil { - return nil, ErrMissingKeyInfo - } - return common.BlobKeyFromBytes(ep.ep.GetKeyInfo().GetKey()), nil -} - -// open io.ReadCloser for data behind given entrypoint -func (c *graphContext) getDataReader( - ctx context.Context, - ep *Entrypoint, -) ( - io.ReadCloser, - error, -) { - key, err := c.keyFromEntrypoint(ctx, ep) - if err != nil { - return nil, err - } - rc, err := c.be.Open(ctx, ep.BlobName(), key) - if err != nil { - return nil, fmt.Errorf("failed to open blob: %w", err) - } - return rc, nil -} - -// return data behind entrypoint -func (c *graphContext) readProtobufMessage( - ctx context.Context, - ep *Entrypoint, - msg proto.Message, -) error { - rc, err := c.getDataReader(ctx, ep) - if err != nil { - return err - } - defer rc.Close() - - data, err := io.ReadAll(rc) - if err != nil { - return fmt.Errorf("failed to read blob: %w", err) - } - - err = proto.Unmarshal(data, msg) - if err != nil { - return fmt.Errorf("malformed data: %w", err) - } - - return nil -} - -func (c *graphContext) createProtobufMessage( - ctx context.Context, - blobType common.BlobType, - msg proto.Message, -) ( - *Entrypoint, - error, -) { - data, err := proto.Marshal(msg) - if err != nil { - return nil, fmt.Errorf("serialization failed: %w", err) - } - - bn, key, ai, err := c.be.Create(ctx, blobType, bytes.NewReader(data)) - if err != nil { - return nil, fmt.Errorf("write failed: %w", err) - } - - if ai != nil { - c.authInfos[bn.String()] = ai - } - - return &Entrypoint{ - bn: bn, - ep: protobuf.Entrypoint{ - BlobName: bn.Bytes(), - KeyInfo: &protobuf.KeyInfo{ - Key: key.Bytes(), - }, - }, - }, nil -} - -func (c *graphContext) updateProtobufMessage( - ctx context.Context, - ep *Entrypoint, - msg proto.Message, -) error { - wi, found := c.authInfos[ep.BlobName().String()] - if !found { - return ErrMissingWriterInfo - } - - key, err := c.keyFromEntrypoint(ctx, ep) - if err != nil { - return err - } - - data, err := proto.Marshal(msg) - if err != nil { - return fmt.Errorf("serialization failed: %w", err) - } - - err = c.be.Update(ctx, ep.BlobName(), wi, key, bytes.NewReader(data)) - if err != nil { - return fmt.Errorf("write failed: %w", err) - } - - return nil -} diff --git a/pkg/cinodefs/entrypoint.go b/pkg/cinodefs/entrypoint.go deleted file mode 100644 index 299a351..0000000 --- a/pkg/cinodefs/entrypoint.go +++ /dev/null @@ -1,138 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package cinodefs - -import ( - "errors" - "fmt" - - "github.com/cinode/go/pkg/blobtypes" - "github.com/cinode/go/pkg/cinodefs/protobuf" - "github.com/cinode/go/pkg/common" - "github.com/cinode/go/pkg/utilities/golang" - "github.com/jbenet/go-base58" - "google.golang.org/protobuf/proto" -) - -var ( - ErrInvalidEntrypointData = errors.New("invalid entrypoint data") - ErrInvalidEntrypointDataParse = fmt.Errorf("%w: protobuf parse error", ErrInvalidEntrypointData) - ErrInvalidEntrypointDataLinkMimetype = fmt.Errorf("%w: link can not have mimetype set", ErrInvalidEntrypointData) - ErrInvalidEntrypointDataNil = fmt.Errorf("%w: nil data", ErrInvalidEntrypointData) - ErrInvalidEntrypointTime = errors.New("time validation failed") - ErrExpired = fmt.Errorf("%w: entry expired", ErrInvalidEntrypointTime) - ErrNotYetValid = fmt.Errorf("%w: entry not yet valid", ErrInvalidEntrypointTime) -) - -type Entrypoint struct { - bn *common.BlobName - ep protobuf.Entrypoint -} - -func EntrypointFromString(s string) (*Entrypoint, error) { - if s == "" { - return nil, fmt.Errorf("%w: empty string", ErrInvalidEntrypointData) - } - - b := base58.Decode(s) - if len(b) == 0 { - return nil, fmt.Errorf("%w: not a base58 string", ErrInvalidEntrypointData) - } - - return EntrypointFromBytes(b) -} - -func EntrypointFromBytes(b []byte) (*Entrypoint, error) { - ep := &Entrypoint{} - - err := proto.Unmarshal(b, &ep.ep) - if err != nil { - return nil, fmt.Errorf("%w: %s", ErrInvalidEntrypointDataParse, err) - } - - err = expandEntrypointProto(ep) - if err != nil { - return nil, err - } - - return ep, nil -} - -func entrypointFromProtobuf(data *protobuf.Entrypoint) (*Entrypoint, error) { - if data == nil { - return nil, ErrInvalidEntrypointDataNil - } - - ep := &Entrypoint{} - proto.Merge(&ep.ep, data) - err := expandEntrypointProto(ep) - if err != nil { - return nil, err - } - return ep, nil -} - -func expandEntrypointProto(ep *Entrypoint) error { - // Extract blob name from entrypoint - bn, err := common.BlobNameFromBytes(ep.ep.BlobName) - if err != nil { - return fmt.Errorf("%w: %w", ErrInvalidEntrypointData, err) - } - ep.bn = bn - - // Links must not have mimetype set - if ep.IsLink() && ep.ep.MimeType != "" { - return ErrInvalidEntrypointDataLinkMimetype - } - - return nil -} - -func EntrypointFromBlobNameAndKey(bn *common.BlobName, key *common.BlobKey) *Entrypoint { - return setEntrypointBlobNameAndKey(bn, key, &Entrypoint{}) -} - -func setEntrypointBlobNameAndKey(bn *common.BlobName, key *common.BlobKey, ep *Entrypoint) *Entrypoint { - ep.bn = bn - ep.ep.BlobName = bn.Bytes() - ep.ep.KeyInfo = &protobuf.KeyInfo{Key: key.Bytes()} - return ep -} - -func (e *Entrypoint) String() string { - return base58.Encode(e.Bytes()) -} - -func (e *Entrypoint) Bytes() []byte { - return golang.Must(proto.Marshal(&e.ep)) -} - -func (e *Entrypoint) BlobName() *common.BlobName { - return e.bn -} - -func (e *Entrypoint) IsLink() bool { - return e.bn.Type() == blobtypes.DynamicLink -} - -func (e *Entrypoint) IsDir() bool { - return e.ep.MimeType == CinodeDirMimeType -} - -func (e *Entrypoint) MimeType() string { - return e.ep.MimeType -} diff --git a/pkg/cinodefs/entrypoint_bb_test.go b/pkg/cinodefs/entrypoint_bb_test.go deleted file mode 100644 index 59e03b9..0000000 --- a/pkg/cinodefs/entrypoint_bb_test.go +++ /dev/null @@ -1,77 +0,0 @@ -/* -Copyright © 2023 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package cinodefs_test - -import ( - "testing" - - "github.com/cinode/go/pkg/cinodefs" - "github.com/cinode/go/pkg/cinodefs/protobuf" - "github.com/cinode/go/testvectors/testblobs" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/proto" -) - -func TestEntrypointFromStringFailures(t *testing.T) { - for _, d := range []struct { - s string - errContains string - }{ - {"", "empty string"}, - {"not-a-base64-string!!!!!!!!", "not a base58 string"}, - {"aaaaaaaa", "protobuf parse error"}, - } { - t.Run(d.s, func(t *testing.T) { - wi, err := cinodefs.EntrypointFromString(d.s) - require.ErrorIs(t, err, cinodefs.ErrInvalidEntrypointData) - require.ErrorContains(t, err, d.errContains) - require.Nil(t, wi) - }) - } -} - -func TestInvalidEntrypointData(t *testing.T) { - for _, d := range []struct { - n string - p *protobuf.Entrypoint - errContains string - }{ - { - "invalid blob name", - &protobuf.Entrypoint{}, - "invalid blob name", - }, - { - "mime type set for link", - &protobuf.Entrypoint{ - BlobName: testblobs.DynamicLink.BlobName.Bytes(), - MimeType: "test-mimetype", - }, - "link can not have mimetype set", - }, - } { - t.Run(d.n, func(t *testing.T) { - bytes, err := proto.Marshal(d.p) - require.NoError(t, err) - - ep, err := cinodefs.EntrypointFromBytes(bytes) - require.ErrorIs(t, err, cinodefs.ErrInvalidEntrypointData) - require.ErrorContains(t, err, d.errContains) - require.Nil(t, ep) - }) - } -} diff --git a/pkg/cinodefs/entrypoint_options.go b/pkg/cinodefs/entrypoint_options.go deleted file mode 100644 index be6d24f..0000000 --- a/pkg/cinodefs/entrypoint_options.go +++ /dev/null @@ -1,43 +0,0 @@ -/* -Copyright © 2023 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package cinodefs - -import ( - "context" -) - -type EntrypointOption interface { - apply(ctx context.Context, ep *Entrypoint) -} - -type entrypointOptionBasicFunc func(ep *Entrypoint) - -func (f entrypointOptionBasicFunc) apply(ctx context.Context, ep *Entrypoint) { f(ep) } - -func SetMimeType(mimeType string) EntrypointOption { - return entrypointOptionBasicFunc(func(ep *Entrypoint) { - ep.ep.MimeType = mimeType - }) -} - -func entrypointFromOptions(ctx context.Context, opts ...EntrypointOption) *Entrypoint { - ep := &Entrypoint{} - for _, o := range opts { - o.apply(ctx, ep) - } - return ep -} diff --git a/pkg/cinodefs/httphandler/http.go b/pkg/cinodefs/httphandler/http.go deleted file mode 100644 index 5262a2b..0000000 --- a/pkg/cinodefs/httphandler/http.go +++ /dev/null @@ -1,131 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package httphandler - -import ( - "crypto/sha256" - "errors" - "fmt" - "io" - "log/slog" - "net/http" - "strings" - - "github.com/cinode/go/pkg/cinodefs" -) - -type Handler struct { - Log *slog.Logger - FS cinodefs.FS - IndexFile string -} - -func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - log := h.Log.With( - slog.String("RemoteAddr", r.RemoteAddr), - slog.String("URL", r.URL.String()), - slog.String("Method", r.Method), - ) - - switch r.Method { - case "GET": - h.serveGet(w, r, log) - return - default: - log.Error("Method not allowed") - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } -} - -// sanitizeRedirectPath ensures redirect targets do not lead to open redirects. -func sanitizeRedirectPath(p string) string { - if len(p) > 1 && p[0] == '/' && p[1] != '/' && p[1] != '\\' { - return p - } - return "/" -} - -func (h *Handler) serveGet(w http.ResponseWriter, r *http.Request, log *slog.Logger) { - path := r.URL.Path - if strings.HasSuffix(path, "/") { - path += h.IndexFile - } - - pathList := strings.Split(strings.TrimPrefix(path, "/"), "/") - fileEP, err := h.FS.FindEntry(r.Context(), pathList) - switch { - case errors.Is(err, cinodefs.ErrEntryNotFound), - errors.Is(err, cinodefs.ErrNotADirectory): - log.Warn("Not found") - http.NotFound(w, r) - return - case errors.Is(err, cinodefs.ErrModifiedDirectory): - // Can't get the entrypoint, but since it's a directory - // (only with unsaved changes), redirect to the directory itself - // that will in the end load the index file if present. - http.Redirect(w, r, sanitizeRedirectPath(r.URL.Path+"/"), http.StatusTemporaryRedirect) - return - case h.handleHTTPError(err, w, log, "Error finding entrypoint"): - return - } - - if fileEP.IsDir() { - http.Redirect(w, r, sanitizeRedirectPath(r.URL.Path+"/"), http.StatusTemporaryRedirect) - return - } - - if h.handleEtag(w, r, fileEP, log) { - // Client ETag matches, can optimize out the data - return - } - - rc, err := h.FS.OpenEntrypointData(r.Context(), fileEP) - if h.handleHTTPError(err, w, log, "Error opening file") { - return - } - defer rc.Close() - - w.Header().Set("Content-Type", fileEP.MimeType()) - _, err = io.Copy(w, rc) - h.handleHTTPError(err, w, log, "Error sending file") -} - -func (h *Handler) handleHTTPError(err error, w http.ResponseWriter, log *slog.Logger, logMsg string) bool { - if err != nil { - log.Error(logMsg, "err", err) - http.Error(w, - fmt.Sprintf("%s: %v", http.StatusText(http.StatusInternalServerError), err), - http.StatusInternalServerError, - ) - return true - } - return false -} - -func (h *Handler) handleEtag(w http.ResponseWriter, r *http.Request, ep *cinodefs.Entrypoint, log *slog.Logger) bool { - currentEtag := fmt.Sprintf("\"%X\"", sha256.Sum256(ep.Bytes())) - - if strings.Contains(r.Header.Get("If-None-Match"), currentEtag) { - log.Debug("Valid ETag found, sending 304 Not Modified") - w.WriteHeader(http.StatusNotModified) - return true - } - - w.Header().Set("ETag", currentEtag) - return false -} diff --git a/pkg/cinodefs/httphandler/http_test.go b/pkg/cinodefs/httphandler/http_test.go deleted file mode 100644 index 65274d2..0000000 --- a/pkg/cinodefs/httphandler/http_test.go +++ /dev/null @@ -1,305 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package httphandler - -import ( - "bytes" - "context" - "errors" - "io" - "log/slog" - "net/http" - "net/http/httptest" - "strings" - "testing" - "testing/iotest" - - "github.com/cinode/go/pkg/blenc" - "github.com/cinode/go/pkg/cinodefs" - "github.com/cinode/go/pkg/common" - "github.com/cinode/go/pkg/datastore" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" -) - -type mockDatastore struct { - datastore.DS - openFunc func(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) -} - -func (m *mockDatastore) Open(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) { - if m.openFunc != nil { - return m.openFunc(ctx, name) - } - return m.DS.Open(ctx, name) -} - -type HandlerTestSuite struct { - suite.Suite - - ds mockDatastore - fs cinodefs.FS - handler *Handler - server *httptest.Server - logData *bytes.Buffer -} - -func TestHandlerTestSuite(t *testing.T) { - suite.Run(t, &HandlerTestSuite{}) -} - -func (s *HandlerTestSuite) SetupTest() { - t := s.T() - - s.ds = mockDatastore{DS: datastore.InMemory()} - fs, err := cinodefs.New( - t.Context(), - blenc.FromDatastore(&s.ds), - cinodefs.NewRootStaticDirectory(), - ) - require.NoError(t, err) - s.fs = fs - - s.logData = bytes.NewBuffer(nil) - log := slog.New(slog.NewJSONHandler( - s.logData, - &slog.HandlerOptions{Level: slog.LevelDebug}, - )) - - s.handler = &Handler{ - FS: fs, - IndexFile: "index.html", - Log: log, - } - s.server = httptest.NewServer(s.handler) - s.T().Cleanup(s.server.Close) -} - -func (s *HandlerTestSuite) setEntry(t *testing.T, data string, path ...string) { - _, err := s.fs.SetEntryFile( - t.Context(), - path, - strings.NewReader(data), - ) - require.NoError(t, err) -} - -func (s *HandlerTestSuite) getEntryETag( - t *testing.T, - path string, - etag string, -) ( - data string, - contentType string, - respEtag string, - code int, -) { - req, err := http.NewRequest(http.MethodGet, s.server.URL+path, http.NoBody) - require.NoError(t, err) - - if etag != "" { - req.Header.Set("If-None-Match", etag) - } - - resp, err := http.DefaultClient.Do(req) - require.NoError(t, err) - defer resp.Body.Close() - - bodyData, err := io.ReadAll(resp.Body) - require.NoError(t, err) - - return string(bodyData), resp.Header.Get("content-type"), resp.Header.Get("ETag"), resp.StatusCode -} - -func (s *HandlerTestSuite) getEntry(path string) ( - data string, - contentType string, - code int, -) { - data, contentType, _, code = s.getEntryETag(s.T(), path, "") - return data, contentType, code -} - -func (s *HandlerTestSuite) getData(t *testing.T, path string) string { - data, _, code := s.getEntry(path) - require.Equal(t, http.StatusOK, code) - return data -} - -func (s *HandlerTestSuite) TestSuccessfulFileDownload() { - t := s.T() - - s.setEntry(t, "hello", "file.txt") - readBack := s.getData(t, "/file.txt") - require.Equal(t, "hello", readBack) -} - -func (s *HandlerTestSuite) TestEtag() { - t := s.T() - - s.setEntry(t, "hello", "file.txt") - - readBack, _, etag, code := s.getEntryETag(t, "/file.txt", "") - require.NotEmpty(t, etag) - require.Greater(t, len(etag), 10) - require.Equal(t, http.StatusOK, code) - require.Equal(t, "hello", readBack) - - readBack, _, _, code = s.getEntryETag(t, "/file.txt", etag) - require.Equal(t, http.StatusNotModified, code) - require.Empty(t, readBack) - - s.setEntry(t, "updated", "file.txt") - - readBack, _, etag2, code := s.getEntryETag(t, "/file.txt", etag) - require.Equal(t, http.StatusOK, code) - require.Greater(t, len(etag2), 10) - require.NotEqual(t, etag, etag2) - require.Equal(t, "updated", readBack) -} - -func (s *HandlerTestSuite) TestNonGetRequest() { - t := s.T() - - resp, err := http.Post(s.server.URL, "text/plain", strings.NewReader("Hello world!")) - require.NoError(t, err) - defer resp.Body.Close() - require.Equal(t, http.StatusMethodNotAllowed, resp.StatusCode) -} - -func (s *HandlerTestSuite) TestNotFound() { - t := s.T() - - _, err := s.fs.SetEntryFile(t.Context(), []string{"hello.txt"}, strings.NewReader("hello")) - require.NoError(t, err) - - _, _, code := s.getEntry("/no-hello.txt") - require.Equal(t, http.StatusNotFound, code) - - _, _, code = s.getEntry("/hello.txt/world") - require.Equal(t, http.StatusNotFound, code) -} - -func (s *HandlerTestSuite) TestReadIndexFile() { - t := s.T() - - s.setEntry(t, "hello", "dir", "index.html") - - // Repeat twice, once before and once after flush - for i := 0; i < 2; i++ { - readBack := s.getData(t, "/dir") - require.Equal(t, "hello", readBack) - - err := s.fs.Flush(t.Context()) - require.NoError(t, err) - } -} - -func (s *HandlerTestSuite) TestReadErrors() { - t := s.T() - - // Strictly controlled list of blob ids accessed, if at any time blob names - // would change, that would mean change in blob hashing algorithm - const bNameDir = "KAJgH9GYbmHxp4MUZvLswDh4t2TjTfVECAMmmv7MAzSZF" - const bNameFile = "pKFmwKyCeLeHjFRiwhGaajuhupPg5tS61tcL6F7sjBHRW" - - s.setEntry(t, "hello", "file.txt") - - err := s.fs.Flush(t.Context()) - require.NoError(t, err) - - t.Run("dir read error", func(t *testing.T) { - mockErr := errors.New("mock error dir") - s.ds.openFunc = func(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) { - switch n := name.String(); n { - case bNameDir: - return nil, mockErr - case bNameFile: - return s.ds.DS.Open(ctx, name) - default: - panic("Unrecognized blob: " + n) - } - } - defer func() { s.ds.openFunc = nil }() - - _, _, code := s.getEntry("/file.txt") - require.Equal(t, http.StatusInternalServerError, code) - require.Contains(t, s.logData.String(), mockErr.Error()) - }) - - t.Run("file open error", func(t *testing.T) { - mockErr := errors.New("mock error file open") - s.ds.openFunc = func(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) { - switch n := name.String(); n { - case bNameDir: - return s.ds.DS.Open(ctx, name) - case bNameFile: - return nil, mockErr - default: - panic("Unrecognized blob: " + n) - } - } - defer func() { s.ds.openFunc = nil }() - - _, _, code := s.getEntry("/file.txt") - require.Equal(t, http.StatusInternalServerError, code) - require.Contains(t, s.logData.String(), mockErr.Error()) - }) - - t.Run("file read error with error header", func(t *testing.T) { - mockErr := errors.New("mock error file read with headers") - s.ds.openFunc = func(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) { - switch n := name.String(); n { - case bNameDir: - return s.ds.DS.Open(ctx, name) - case bNameFile: - return io.NopCloser(iotest.ErrReader(mockErr)), nil - default: - panic("Unrecognized blob: " + n) - } - } - defer func() { s.ds.openFunc = nil }() - - _, _, code := s.getEntry("/file.txt") - require.Equal(t, http.StatusInternalServerError, code) - require.Contains(t, s.logData.String(), mockErr.Error()) - }) - - t.Run("file read error with partially sent data", func(t *testing.T) { - mockErr := errors.New("mock error file read without headers") - s.ds.openFunc = func(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) { - switch n := name.String(); n { - case bNameDir: - return s.ds.DS.Open(ctx, name) - case bNameFile: - return io.NopCloser(io.MultiReader( - strings.NewReader("hello world!"), - iotest.ErrReader(mockErr), - )), nil - default: - panic("Unrecognized blob: " + n) - } - } - defer func() { s.ds.openFunc = nil }() - - content, _, _ := s.getEntry("/file.txt") - // Since headers were already sent, there's no way to report back an error, - // we can only check if logs contain some error information - require.Contains(t, s.logData.String(), mockErr.Error()) - require.Contains(t, content, http.StatusText(http.StatusInternalServerError)) - }) -} diff --git a/pkg/cinodefs/node.go b/pkg/cinodefs/node.go deleted file mode 100644 index 6bbe9b2..0000000 --- a/pkg/cinodefs/node.go +++ /dev/null @@ -1,81 +0,0 @@ -/* -Copyright © 2023 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package cinodefs - -// -// cached entries: -// * unloaded entry - we only have entrypoint data -// * directory - either clean (with existing entrypoint) or dirty (modified entries, not yet flushed) -// * link - either clean (with tarted stored) or dirty (target changed but not yet flushed) -// * file - entrypoint to static blob -// -// node states: -// * if unloaded entry - contains entrypoint to the element, from entrypoint it can be deduced if this -// is a dynamic link (from blob name) or directory (from mime type), this node does not need flushing -// * node is dirty directly - the node was modified, its entrypoint can not be deduced before the node -// is flushed, some modifications are kept in memory and can still be lost -// * sub-nodes are dirty - the node itself is not dirty but some sub-nodes are. The node itself can have -// entrypoint deduced because it will not change, but some sub-nodes will need flushing to persist the -// data. Such situation is caused by dynamic links - the target can require flushing but the link itself -// will preserve its entrypoint. -// - -import ( - "context" -) - -type dirtyState byte - -const ( - // node and its sub-nodes are all clear, this sub-graph does not require flushing and is fully persisted - dsClean dirtyState = 0 - - // node is dirty, requires flushing to persist data - dsDirty dirtyState = 1 - - // node is itself clean, but some sub-nodes are dirty, flushing will be forwarded to sub-nodes - dsSubDirty dirtyState = 2 -) - -// node is a base interface required by all cached entries -type node interface { - // returns dirty state of this entrypoint - dirty() dirtyState - - // flush this entrypoint - flush(ctx context.Context, gc *graphContext) (node, *Entrypoint, error) - - // traverse node - traverse( - ctx context.Context, - gc *graphContext, - path []string, - pathPosition int, - linkDepth int, - isWritable bool, - opts traverseOptions, - whenReached traverseGoalFunc, - ) ( - replacementNode node, - state dirtyState, - err error, - ) - - // get current entrypoint value, do not flush before, if node is not flushed - // it must return appropriate error - entrypoint() (*Entrypoint, error) -} diff --git a/pkg/cinodefs/node_directory.go b/pkg/cinodefs/node_directory.go deleted file mode 100644 index bb8c7d9..0000000 --- a/pkg/cinodefs/node_directory.go +++ /dev/null @@ -1,238 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package cinodefs - -import ( - "context" - "sort" - - "github.com/cinode/go/pkg/blobtypes" - "github.com/cinode/go/pkg/cinodefs/protobuf" - "github.com/cinode/go/pkg/utilities/golang" -) - -// nodeDirectory holds a directory entry loaded into memory -type nodeDirectory struct { - entries map[string]node - stored *Entrypoint // current entrypoint, will be nil if directory was modified - dState dirtyState // true if any subtree is dirty -} - -func (d *nodeDirectory) dirty() dirtyState { - return d.dState -} - -func (d *nodeDirectory) flush(ctx context.Context, gc *graphContext) (node, *Entrypoint, error) { - if d.dState == dsClean { - // all clear, nothing to flush here or in sub-trees - return d, d.stored, nil - } - - if d.dState == dsSubDirty { - // Some sub-nodes are dirty, need to propagate flush to - flushedEntries := make(map[string]node, len(d.entries)) - for name, entry := range d.entries { - target, _, err := entry.flush(ctx, gc) - if err != nil { - return nil, nil, err - } - - flushedEntries[name] = target - } - - // directory itself was not modified and does not need flush, don't bother - // saving it to datastore - return &nodeDirectory{ - entries: flushedEntries, - stored: d.stored, - dState: dsClean, - }, d.stored, nil - } - - golang.Assert(d.dState == dsDirty, "ensure correct dirtiness state") - - // Directory has changed, have to recalculate its blob and save it in data store - dir := protobuf.Directory{ - Entries: make([]*protobuf.Directory_Entry, 0, len(d.entries)), - } - flushedEntries := make(map[string]node, len(d.entries)) - for name, entry := range d.entries { - target, targetEP, err := entry.flush(ctx, gc) - if err != nil { - return nil, nil, err - } - - flushedEntries[name] = target - dir.Entries = append(dir.Entries, &protobuf.Directory_Entry{ - Name: name, - Ep: &targetEP.ep, - }) - } - - // Sort by name - that way we gain deterministic order during - // serialization od the directory - sort.Slice(dir.Entries, func(i, j int) bool { - return dir.Entries[i].Name < dir.Entries[j].Name - }) - - ep, err := gc.createProtobufMessage(ctx, blobtypes.Static, &dir) - if err != nil { - return nil, nil, err - } - ep.ep.MimeType = CinodeDirMimeType - - return &nodeDirectory{ - entries: flushedEntries, - stored: ep, - dState: dsClean, - }, ep, nil -} - -func (d *nodeDirectory) traverse( - ctx context.Context, - gc *graphContext, - path []string, - pathPosition int, - linkDepth int, - isWritable bool, - opts traverseOptions, - whenReached traverseGoalFunc, -) ( - node, - dirtyState, - error, -) { - if pathPosition == len(path) { - return whenReached(ctx, d, isWritable) - } - - subNode, found := d.entries[path[pathPosition]] - if !found { - if !opts.createNodes { - return nil, 0, ErrEntryNotFound - } - if !isWritable { - return nil, 0, ErrMissingWriterInfo - } - // create new sub-path - newNode, err := d.traverseRecursiveNewPath( - ctx, - path, - pathPosition+1, - whenReached, - ) - if err != nil { - return nil, 0, err - } - d.entries[path[pathPosition]] = newNode - d.dState = dsDirty - return d, dsDirty, nil - } - - // found path entry, descend to sub-node - replacement, replacementState, err := subNode.traverse( - ctx, - gc, - path, - pathPosition+1, - 0, - isWritable, - opts, - whenReached, - ) - if err != nil { - return nil, 0, err - } - if opts.doNotCache { - return d, dsClean, nil - } - - d.entries[path[pathPosition]] = replacement - if replacementState == dsDirty { - // child is dirty, this propagates down to the current node - d.dState = dsDirty - return d, dsDirty, nil - } - - if replacementState == dsSubDirty { - // child itself is not dirty, but some sub-node is, sub-dirtiness - // propagates to the current node, but if the directory is - // already directly dirty (stronger dirtiness), keep it as it is - if d.dState != dsDirty { - d.dState = dsSubDirty - } - return d, dsSubDirty, nil - } - - golang.Assert(replacementState == dsClean, "ensure correct dirtiness state") - // leave current state as it is - return d, dsClean, nil -} - -func (d *nodeDirectory) traverseRecursiveNewPath( - ctx context.Context, - path []string, - pathPosition int, - whenReached traverseGoalFunc, -) ( - node, - error, -) { - if len(path) == pathPosition { - replacement, _, err := whenReached(ctx, nil, true) - return replacement, err - } - - sub, err := d.traverseRecursiveNewPath( - ctx, - path, - pathPosition+1, - whenReached, - ) - if err != nil { - return nil, err - } - - return &nodeDirectory{ - entries: map[string]node{ - path[pathPosition]: sub, - }, - dState: dsDirty, - }, nil -} - -func (d *nodeDirectory) entrypoint() (*Entrypoint, error) { - if d.dState == dsDirty { - return nil, ErrModifiedDirectory - } - - golang.Assert( - d.dState == dsClean || d.dState == dsSubDirty, - "ensure dirtiness state is valid", - ) - - return d.stored, nil -} - -func (d *nodeDirectory) deleteEntry(name string) bool { - if _, hasEntry := d.entries[name]; !hasEntry { - return false - } - delete(d.entries, name) - d.dState = dsDirty - return true -} diff --git a/pkg/cinodefs/node_file.go b/pkg/cinodefs/node_file.go deleted file mode 100644 index 77347e5..0000000 --- a/pkg/cinodefs/node_file.go +++ /dev/null @@ -1,60 +0,0 @@ -/* -Copyright © 2023 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package cinodefs - -import ( - "context" -) - -// Entry is a file with its entrypoint -type nodeFile struct { - ep *Entrypoint -} - -func (c *nodeFile) dirty() dirtyState { - return dsClean -} - -func (c *nodeFile) flush(ctx context.Context, gc *graphContext) (node, *Entrypoint, error) { - return c, c.ep, nil -} - -func (c *nodeFile) traverse( - ctx context.Context, - gc *graphContext, - path []string, - pathPosition int, - linkDepth int, - isWritable bool, - opts traverseOptions, - whenReached traverseGoalFunc, -) ( - node, - dirtyState, - error, -) { - if pathPosition == len(path) { - return whenReached(ctx, c, isWritable) - } - - // We're supposed to traverse into sub-path but it's not a directory - return nil, 0, ErrNotADirectory -} - -func (c *nodeFile) entrypoint() (*Entrypoint, error) { - return c.ep, nil -} diff --git a/pkg/cinodefs/node_link.go b/pkg/cinodefs/node_link.go deleted file mode 100644 index 140228b..0000000 --- a/pkg/cinodefs/node_link.go +++ /dev/null @@ -1,127 +0,0 @@ -/* -Copyright © 2023 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package cinodefs - -import ( - "context" - - "github.com/cinode/go/pkg/utilities/golang" -) - -// Entry is a link loaded into memory -type nodeLink struct { - ep *Entrypoint // entrypoint of the link itself - target node // target for the link - dState dirtyState -} - -func (c *nodeLink) dirty() dirtyState { - return c.dState -} - -func (c *nodeLink) flush(ctx context.Context, gc *graphContext) (node, *Entrypoint, error) { - if c.dState == dsClean { - // all clear - return c, c.ep, nil - } - - golang.Assert(c.dState == dsSubDirty, "link can be clean or sub-dirty") - target, targetEP, err := c.target.flush(ctx, gc) - if err != nil { - return nil, nil, err - } - - err = gc.updateProtobufMessage(ctx, c.ep, &targetEP.ep) - if err != nil { - return nil, nil, err - } - - ret := &nodeLink{ - ep: c.ep, - target: target, - dState: dsClean, - } - - return ret, ret.ep, nil -} - -func (c *nodeLink) traverse( - ctx context.Context, - gc *graphContext, - path []string, - pathPosition int, - linkDepth int, - isWritable bool, - opts traverseOptions, - whenReached traverseGoalFunc, -) ( - node, - dirtyState, - error, -) { - if linkDepth >= opts.maxLinkRedirects { - return nil, 0, ErrTooManyRedirects - } - - // Note: we don't stop here even if we've reached the end of - // traverse path, delegate traversal to target node instead - - // crossing link border, whether sub-graph is writeable is determined - // by availability of corresponding writer info - _, hasAuthInfo := gc.authInfos[c.ep.bn.String()] - - newTarget, targetState, err := c.target.traverse( - ctx, - gc, - path, - pathPosition, - linkDepth+1, - hasAuthInfo, - opts, - whenReached, - ) - if err != nil { - return nil, 0, err - } - - if opts.doNotCache { - return c, dsClean, nil - } - - c.target = newTarget - if targetState == dsClean { - // Nothing to do - // - // Note: this path will happen once we keep clean nodes - // in the memory for caching purposes - return c, dsClean, nil - } - - golang.Assert( - targetState == dsDirty || targetState == dsSubDirty, - "ensure correct dirtiness state", - ) - - // sub-dirty propagates normally, dirty becomes sub-dirty - // because link's entrypoint never changes - c.dState = dsSubDirty - return c, dsSubDirty, nil -} - -func (c *nodeLink) entrypoint() (*Entrypoint, error) { - return c.ep, nil -} diff --git a/pkg/cinodefs/node_unloaded.go b/pkg/cinodefs/node_unloaded.go deleted file mode 100644 index 6ddd088..0000000 --- a/pkg/cinodefs/node_unloaded.go +++ /dev/null @@ -1,135 +0,0 @@ -/* -Copyright © 2023 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package cinodefs - -import ( - "context" - "fmt" - - "github.com/cinode/go/pkg/cinodefs/protobuf" -) - -type nodeUnloaded struct { - ep *Entrypoint -} - -func (c *nodeUnloaded) dirty() dirtyState { - return dsClean -} - -func (c *nodeUnloaded) flush(ctx context.Context, gc *graphContext) (node, *Entrypoint, error) { - return c, c.ep, nil -} - -func (c *nodeUnloaded) traverse( - ctx context.Context, - gc *graphContext, - path []string, - pathPosition int, - linkDepth int, - isWritable bool, - opts traverseOptions, - whenReached traverseGoalFunc, -) ( - node, - dirtyState, - error, -) { - loaded, err := c.load(ctx, gc) - if err != nil { - return nil, 0, err - } - - return loaded.traverse( - ctx, - gc, - path, - pathPosition, - linkDepth, - isWritable, - opts, - whenReached, - ) -} - -func (c *nodeUnloaded) load(ctx context.Context, gc *graphContext) (node, error) { - // Data is behind some entrypoint, try to load it - if c.ep.IsLink() { - return c.loadEntrypointLink(ctx, gc) - } - - if c.ep.IsDir() { - return c.loadEntrypointDir(ctx, gc) - } - - return &nodeFile{ep: c.ep}, nil -} - -func (c *nodeUnloaded) loadEntrypointLink(ctx context.Context, gc *graphContext) (node, error) { - targetEP := &Entrypoint{} - err := gc.readProtobufMessage(ctx, c.ep, &targetEP.ep) - if err != nil { - return nil, fmt.Errorf("%w: %w", ErrCantOpenLink, err) - } - - err = expandEntrypointProto(targetEP) - if err != nil { - return nil, fmt.Errorf("%w: %w", ErrCantOpenLink, err) - } - - return &nodeLink{ - ep: c.ep, - target: &nodeUnloaded{ep: targetEP}, - dState: dsClean, - }, nil -} - -func (c *nodeUnloaded) loadEntrypointDir(ctx context.Context, gc *graphContext) (node, error) { - msg := &protobuf.Directory{} - err := gc.readProtobufMessage(ctx, c.ep, msg) - if err != nil { - return nil, fmt.Errorf("%w: %w", ErrCantOpenDir, err) - } - - dir := make(map[string]node, len(msg.Entries)) - - for _, entry := range msg.Entries { - if entry.Name == "" { - return nil, fmt.Errorf("%w: %w", ErrCantOpenDir, ErrEmptyName) - } - if _, exists := dir[entry.Name]; exists { - return nil, fmt.Errorf("%w: %s", ErrCantOpenDirDuplicateEntry, entry.Name) - } - - ep, err := entrypointFromProtobuf(entry.Ep) - if err != nil { - return nil, fmt.Errorf("%w: %w", ErrCantOpenDir, err) - } - - dir[entry.Name] = &nodeUnloaded{ep: ep} - } - - return &nodeDirectory{ - stored: c.ep, - entries: dir, - dState: dsClean, - }, nil -} - -func (c *nodeUnloaded) entrypoint() (*Entrypoint, error) { - return c.ep, nil -} diff --git a/pkg/cinodefs/protobuf/protobuf.pb.go b/pkg/cinodefs/protobuf/protobuf.pb.go deleted file mode 100644 index a5c1dcd..0000000 --- a/pkg/cinodefs/protobuf/protobuf.pb.go +++ /dev/null @@ -1,485 +0,0 @@ -// Copyright © 2023 Bartłomiej Święcki (byo) -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.28.1 -// protoc v3.12.4 -// source: protobuf.proto - -package protobuf - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -// KeyInfo represents encryption key information -type KeyInfo struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` -} - -func (x *KeyInfo) Reset() { - *x = KeyInfo{} - if protoimpl.UnsafeEnabled { - mi := &file_protobuf_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *KeyInfo) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*KeyInfo) ProtoMessage() {} - -func (x *KeyInfo) ProtoReflect() protoreflect.Message { - mi := &file_protobuf_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use KeyInfo.ProtoReflect.Descriptor instead. -func (*KeyInfo) Descriptor() ([]byte, []int) { - return file_protobuf_proto_rawDescGZIP(), []int{0} -} - -func (x *KeyInfo) GetKey() []byte { - if x != nil { - return x.Key - } - return nil -} - -// Entry represents a single entry of a directory -type Entrypoint struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - BlobName []byte `protobuf:"bytes,1,opt,name=blobName,proto3" json:"blobName,omitempty"` - KeyInfo *KeyInfo `protobuf:"bytes,2,opt,name=keyInfo,proto3" json:"keyInfo,omitempty"` - MimeType string `protobuf:"bytes,3,opt,name=mimeType,proto3" json:"mimeType,omitempty"` - NotValidBeforeUnixMicro int64 `protobuf:"varint,4,opt,name=notValidBeforeUnixMicro,proto3" json:"notValidBeforeUnixMicro,omitempty"` - NotValidAfterUnixMicro int64 `protobuf:"varint,5,opt,name=notValidAfterUnixMicro,proto3" json:"notValidAfterUnixMicro,omitempty"` -} - -func (x *Entrypoint) Reset() { - *x = Entrypoint{} - if protoimpl.UnsafeEnabled { - mi := &file_protobuf_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Entrypoint) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Entrypoint) ProtoMessage() {} - -func (x *Entrypoint) ProtoReflect() protoreflect.Message { - mi := &file_protobuf_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Entrypoint.ProtoReflect.Descriptor instead. -func (*Entrypoint) Descriptor() ([]byte, []int) { - return file_protobuf_proto_rawDescGZIP(), []int{1} -} - -func (x *Entrypoint) GetBlobName() []byte { - if x != nil { - return x.BlobName - } - return nil -} - -func (x *Entrypoint) GetKeyInfo() *KeyInfo { - if x != nil { - return x.KeyInfo - } - return nil -} - -func (x *Entrypoint) GetMimeType() string { - if x != nil { - return x.MimeType - } - return "" -} - -func (x *Entrypoint) GetNotValidBeforeUnixMicro() int64 { - if x != nil { - return x.NotValidBeforeUnixMicro - } - return 0 -} - -func (x *Entrypoint) GetNotValidAfterUnixMicro() int64 { - if x != nil { - return x.NotValidAfterUnixMicro - } - return 0 -} - -// Directory represents a content of a static directory -type Directory struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // List of directory entries, shall be sorted by the name (sorting topologically by the utf-8 byte representation of the name) - Entries []*Directory_Entry `protobuf:"bytes,1,rep,name=entries,proto3" json:"entries,omitempty"` -} - -func (x *Directory) Reset() { - *x = Directory{} - if protoimpl.UnsafeEnabled { - mi := &file_protobuf_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Directory) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Directory) ProtoMessage() {} - -func (x *Directory) ProtoReflect() protoreflect.Message { - mi := &file_protobuf_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Directory.ProtoReflect.Descriptor instead. -func (*Directory) Descriptor() ([]byte, []int) { - return file_protobuf_proto_rawDescGZIP(), []int{2} -} - -func (x *Directory) GetEntries() []*Directory_Entry { - if x != nil { - return x.Entries - } - return nil -} - -// WriterInfo contains information that allows updating given blob -type WriterInfo struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - BlobName []byte `protobuf:"bytes,1,opt,name=blobName,proto3" json:"blobName,omitempty"` - Key []byte `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` - AuthInfo []byte `protobuf:"bytes,3,opt,name=authInfo,proto3" json:"authInfo,omitempty"` -} - -func (x *WriterInfo) Reset() { - *x = WriterInfo{} - if protoimpl.UnsafeEnabled { - mi := &file_protobuf_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *WriterInfo) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*WriterInfo) ProtoMessage() {} - -func (x *WriterInfo) ProtoReflect() protoreflect.Message { - mi := &file_protobuf_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use WriterInfo.ProtoReflect.Descriptor instead. -func (*WriterInfo) Descriptor() ([]byte, []int) { - return file_protobuf_proto_rawDescGZIP(), []int{3} -} - -func (x *WriterInfo) GetBlobName() []byte { - if x != nil { - return x.BlobName - } - return nil -} - -func (x *WriterInfo) GetKey() []byte { - if x != nil { - return x.Key - } - return nil -} - -func (x *WriterInfo) GetAuthInfo() []byte { - if x != nil { - return x.AuthInfo - } - return nil -} - -type Directory_Entry struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Ep *Entrypoint `protobuf:"bytes,2,opt,name=ep,proto3" json:"ep,omitempty"` -} - -func (x *Directory_Entry) Reset() { - *x = Directory_Entry{} - if protoimpl.UnsafeEnabled { - mi := &file_protobuf_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Directory_Entry) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Directory_Entry) ProtoMessage() {} - -func (x *Directory_Entry) ProtoReflect() protoreflect.Message { - mi := &file_protobuf_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Directory_Entry.ProtoReflect.Descriptor instead. -func (*Directory_Entry) Descriptor() ([]byte, []int) { - return file_protobuf_proto_rawDescGZIP(), []int{2, 0} -} - -func (x *Directory_Entry) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -func (x *Directory_Entry) GetEp() *Entrypoint { - if x != nil { - return x.Ep - } - return nil -} - -var File_protobuf_proto protoreflect.FileDescriptor - -var file_protobuf_proto_rawDesc = []byte{ - 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x22, 0x1b, 0x0a, 0x07, 0x4b, 0x65, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x10, 0x0a, 0x03, 0x6b, - 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0xda, 0x01, - 0x0a, 0x0a, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, - 0x62, 0x6c, 0x6f, 0x62, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, - 0x62, 0x6c, 0x6f, 0x62, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x07, 0x6b, 0x65, 0x79, 0x49, - 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x4b, 0x65, 0x79, 0x49, - 0x6e, 0x66, 0x6f, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1a, 0x0a, 0x08, - 0x6d, 0x69, 0x6d, 0x65, 0x54, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x6d, 0x69, 0x6d, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x38, 0x0a, 0x17, 0x6e, 0x6f, 0x74, 0x56, - 0x61, 0x6c, 0x69, 0x64, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x55, 0x6e, 0x69, 0x78, 0x4d, 0x69, - 0x63, 0x72, 0x6f, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x17, 0x6e, 0x6f, 0x74, 0x56, 0x61, - 0x6c, 0x69, 0x64, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x55, 0x6e, 0x69, 0x78, 0x4d, 0x69, 0x63, - 0x72, 0x6f, 0x12, 0x36, 0x0a, 0x16, 0x6e, 0x6f, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x41, 0x66, - 0x74, 0x65, 0x72, 0x55, 0x6e, 0x69, 0x78, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x16, 0x6e, 0x6f, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x41, 0x66, 0x74, 0x65, - 0x72, 0x55, 0x6e, 0x69, 0x78, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x22, 0x71, 0x0a, 0x09, 0x44, 0x69, - 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x2a, 0x0a, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, - 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x44, 0x69, 0x72, 0x65, 0x63, - 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x65, 0x6e, 0x74, 0x72, - 0x69, 0x65, 0x73, 0x1a, 0x38, 0x0a, 0x05, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x12, 0x0a, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x12, 0x1b, 0x0a, 0x02, 0x65, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x02, 0x65, 0x70, 0x22, 0x56, 0x0a, - 0x0a, 0x57, 0x72, 0x69, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1a, 0x0a, 0x08, 0x62, - 0x6c, 0x6f, 0x62, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x62, - 0x6c, 0x6f, 0x62, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x75, 0x74, - 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x61, 0x75, 0x74, - 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x0c, 0x5a, 0x0a, 0x2e, 0x3b, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} - -var ( - file_protobuf_proto_rawDescOnce sync.Once - file_protobuf_proto_rawDescData = file_protobuf_proto_rawDesc -) - -func file_protobuf_proto_rawDescGZIP() []byte { - file_protobuf_proto_rawDescOnce.Do(func() { - file_protobuf_proto_rawDescData = protoimpl.X.CompressGZIP(file_protobuf_proto_rawDescData) - }) - return file_protobuf_proto_rawDescData -} - -var file_protobuf_proto_msgTypes = make([]protoimpl.MessageInfo, 5) -var file_protobuf_proto_goTypes = []interface{}{ - (*KeyInfo)(nil), // 0: KeyInfo - (*Entrypoint)(nil), // 1: Entrypoint - (*Directory)(nil), // 2: Directory - (*WriterInfo)(nil), // 3: WriterInfo - (*Directory_Entry)(nil), // 4: Directory.Entry -} -var file_protobuf_proto_depIdxs = []int32{ - 0, // 0: Entrypoint.keyInfo:type_name -> KeyInfo - 4, // 1: Directory.entries:type_name -> Directory.Entry - 1, // 2: Directory.Entry.ep:type_name -> Entrypoint - 3, // [3:3] is the sub-list for method output_type - 3, // [3:3] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name -} - -func init() { file_protobuf_proto_init() } -func file_protobuf_proto_init() { - if File_protobuf_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_protobuf_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*KeyInfo); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_protobuf_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Entrypoint); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_protobuf_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Directory); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_protobuf_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WriterInfo); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_protobuf_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Directory_Entry); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_protobuf_proto_rawDesc, - NumEnums: 0, - NumMessages: 5, - NumExtensions: 0, - NumServices: 0, - }, - GoTypes: file_protobuf_proto_goTypes, - DependencyIndexes: file_protobuf_proto_depIdxs, - MessageInfos: file_protobuf_proto_msgTypes, - }.Build() - File_protobuf_proto = out.File - file_protobuf_proto_rawDesc = nil - file_protobuf_proto_goTypes = nil - file_protobuf_proto_depIdxs = nil -} diff --git a/pkg/cinodefs/protobuf/protobuf.proto b/pkg/cinodefs/protobuf/protobuf.proto deleted file mode 100644 index 9d8d89d..0000000 --- a/pkg/cinodefs/protobuf/protobuf.proto +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright © 2023 Bartłomiej Święcki (byo) -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -syntax = "proto3"; - -option go_package = ".;protobuf"; - -// KeyInfo represents encryption key information -message KeyInfo { - bytes key = 1; -} - -// Entry represents a single entry of a directory -message Entrypoint { - bytes blobName = 1; - KeyInfo keyInfo = 2; - string mimeType = 3; - int64 notValidBeforeUnixMicro = 4; - int64 notValidAfterUnixMicro = 5; -} - -// Directory represents a content of a static directory -message Directory { - message Entry { - string name = 1; - Entrypoint ep = 2; - } - // List of directory entries, shall be sorted by the name (sorting topologically by the utf-8 byte representation of the name) - repeated Entry entries = 1; -} - -// WriterInfo contains information that allows updating given blob -message WriterInfo { - bytes blobName = 1; - bytes key = 2; - bytes authInfo = 3; -} diff --git a/pkg/cinodefs/uploader/directory.go b/pkg/cinodefs/uploader/directory.go deleted file mode 100644 index c314df1..0000000 --- a/pkg/cinodefs/uploader/directory.go +++ /dev/null @@ -1,219 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package uploader - -import ( - "bytes" - "context" - "errors" - "fmt" - "html/template" - "io/fs" - "log/slog" - "path" - - _ "embed" - - "github.com/cinode/go/pkg/blenc" - "github.com/cinode/go/pkg/cinodefs" - "github.com/cinode/go/pkg/utilities/golang" -) - -const ( - CinodeDirMimeType = "application/cinode-dir" -) - -var ( - ErrNotFound = blenc.ErrNotFound - ErrNotADirectory = errors.New("entry is not a directory") - ErrNotAFile = errors.New("entry is not a file") - ErrNotADirectoryOrAFile = errors.New("entry is neither a directory nor a regular file") -) - -func UploadStaticDirectory( - ctx context.Context, - fsys fs.FS, - cfs cinodefs.FS, - opts ...Option, -) error { - c := dirCompiler{ - ctx: ctx, - fsys: fsys, - cfs: cfs, - log: slog.Default(), - } - for _, opt := range opts { - opt(&c) - } - - _, err := c.compilePath(ctx, ".", c.basePath) - if err != nil { - return err - } - - return nil -} - -type Option func(d *dirCompiler) - -func BasePath(path ...string) Option { - return Option(func(d *dirCompiler) { - d.basePath = path - }) -} - -func CreateIndexFile(indexFile string) Option { - return Option(func(d *dirCompiler) { - d.createIndexFile = true - d.indexFileName = indexFile - }) -} - -type dirCompiler struct { - ctx context.Context - fsys fs.FS - cfs cinodefs.FS - log *slog.Logger - indexFileName string - basePath []string - createIndexFile bool -} - -type dirEntry struct { - Name string - MimeType string - IsDir bool - Size int64 -} - -func (d *dirCompiler) compilePath( - ctx context.Context, - srcPath string, - destPath []string, -) (*dirEntry, error) { - st, err := fs.Stat(d.fsys, srcPath) - if err != nil { - d.log.ErrorContext(ctx, "failed to stat path", "path", srcPath, "err", err) - return nil, fmt.Errorf("couldn't check path: %w", err) - } - - var name string - if len(destPath) > 0 { - name = destPath[len(destPath)-1] - } - - if st.IsDir() { - size, err := d.compileDir(ctx, srcPath, destPath) - if err != nil { - return nil, err - } - return &dirEntry{ - Name: name, - MimeType: cinodefs.CinodeDirMimeType, - IsDir: true, - Size: int64(size), - }, nil - } - - if st.Mode().IsRegular() { - mime, err := d.compileFile(ctx, srcPath, destPath) - if err != nil { - return nil, err - } - return &dirEntry{ - Name: name, - MimeType: mime, - IsDir: false, - Size: st.Size(), - }, nil - } - - d.log.ErrorContext(ctx, "path is neither dir nor a regular file", "path", srcPath) - return nil, fmt.Errorf("%w: %v", ErrNotADirectoryOrAFile, srcPath) -} - -func (d *dirCompiler) compileFile(ctx context.Context, srcPath string, dstPath []string) (string, error) { - d.log.InfoContext(ctx, "compiling file", "path", srcPath) - fl, err := d.fsys.Open(srcPath) - if err != nil { - d.log.ErrorContext(ctx, "failed to open file", "path", srcPath, "err", err) - return "", fmt.Errorf("couldn't open file %v: %w", srcPath, err) - } - defer fl.Close() - - ep, err := d.cfs.SetEntryFile(ctx, dstPath, fl) - if err != nil { - return "", fmt.Errorf("failed to upload file %v: %w", srcPath, err) - } - - return ep.MimeType(), nil -} - -func (d *dirCompiler) compileDir(ctx context.Context, srcPath string, dstPath []string) (int, error) { - fileList, err := fs.ReadDir(d.fsys, srcPath) - if err != nil { - d.log.ErrorContext(ctx, "couldn't read contents of dir", "path", srcPath, "err", err) - return 0, fmt.Errorf("couldn't read contents of dir %v: %w", srcPath, err) - } - - entries := make([]*dirEntry, 0, len(fileList)) - hasIndex := false - - for _, e := range fileList { - entry, err := d.compilePath( - ctx, - path.Join(srcPath, e.Name()), - append(dstPath, e.Name()), - ) - if err != nil { - return 0, err - } - - if entry.Name == d.indexFileName { - hasIndex = true - } else { - entries = append(entries, entry) - } - } - - if d.createIndexFile && !hasIndex { - buf := bytes.NewBuffer(nil) - err = dirIndexTemplate.Execute(buf, map[string]any{ - "entries": entries, - "indexName": d.indexFileName, - }) - golang.Assert(err == nil, "template execution must not fail") - - _, err = d.cfs.SetEntryFile(ctx, - append(dstPath, d.indexFileName), - bytes.NewReader(buf.Bytes()), - ) - if err != nil { - return 0, err - } - } - - return len(fileList), nil -} - -//go:embed templates/dir.html -var _dirIndexTemplateStr string -var dirIndexTemplate = golang.Must( - template. - New("dir"). - Parse(_dirIndexTemplateStr), -) diff --git a/pkg/cinodefs/uploader/directory_test.go b/pkg/cinodefs/uploader/directory_test.go deleted file mode 100644 index 98a6bf6..0000000 --- a/pkg/cinodefs/uploader/directory_test.go +++ /dev/null @@ -1,318 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package uploader_test - -import ( - "context" - "errors" - "io" - "io/fs" - "strings" - "testing" - "testing/fstest" - - "github.com/cinode/go/pkg/blenc" - "github.com/cinode/go/pkg/cinodefs" - "github.com/cinode/go/pkg/cinodefs/uploader" - "github.com/cinode/go/pkg/datastore" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" -) - -type DirectoryTestSuite struct { - suite.Suite - - cfs cinodefs.FS -} - -func TestDirectoryTestSuite(t *testing.T) { - suite.Run(t, &DirectoryTestSuite{}) -} - -func (s *DirectoryTestSuite) SetupTest() { - t := s.T() - - cfs, err := cinodefs.New( - t.Context(), - blenc.FromDatastore(datastore.InMemory()), - cinodefs.NewRootStaticDirectory(), - ) - require.NoError(t, err) - s.cfs = cfs -} - -func (s *DirectoryTestSuite) singleFileFs() fstest.MapFS { - return fstest.MapFS{ - "file.txt": &fstest.MapFile{Data: []byte("hello")}, - } -} - -type wrapFS struct { - fs.FS - - openFunc func(path string) (fs.File, error) - statFunc func(name string) (fs.FileInfo, error) - readDirFunc func(name string) ([]fs.DirEntry, error) -} - -func (w *wrapFS) Open(path string) (fs.File, error) { - if w.openFunc != nil { - return w.openFunc(path) - } - return w.FS.Open(path) -} - -func (w *wrapFS) Stat(name string) (fs.FileInfo, error) { - if w.statFunc != nil { - return w.statFunc(name) - } - return fs.Stat(w.FS, name) -} - -func (w *wrapFS) ReadDir(name string) ([]fs.DirEntry, error) { - if w.readDirFunc != nil { - return w.readDirFunc(name) - } - return fs.ReadDir(w.FS, name) -} - -func (s *DirectoryTestSuite) uploadFS(t *testing.T, fs fs.FS, opts ...uploader.Option) { - err := uploader.UploadStaticDirectory( - t.Context(), - fs, - s.cfs, - opts..., - ) - require.NoError(t, err) -} - -func (s *DirectoryTestSuite) readContent(t *testing.T, path ...string) (string, error) { - rc, err := s.cfs.OpenEntryData(t.Context(), path) - if err != nil { - return "", err - } - defer rc.Close() - data, err := io.ReadAll(rc) - return string(data), err -} - -func (s *DirectoryTestSuite) TestSingleFileUploadDefaultOptions() { - t := s.T() - - s.uploadFS(t, s.singleFileFs()) - - readBack, err := s.readContent(t, "file.txt") - require.NoError(t, err) - require.Equal(t, "hello", readBack) -} - -func (s *DirectoryTestSuite) TestSingleFileUploadBasePath() { - t := s.T() - - s.uploadFS(t, s.singleFileFs(), uploader.BasePath("sub", "dir")) - - readBack, err := s.readContent(t, "sub", "dir", "file.txt") - require.NoError(t, err) - require.Equal(t, "hello", readBack) - - _, err = s.readContent(t, "file.txt") - require.ErrorIs(t, err, cinodefs.ErrEntryNotFound) -} - -func (s *DirectoryTestSuite) TestSingleFileUploadWithIndexFile() { - t := s.T() - - s.uploadFS(t, s.singleFileFs(), uploader.CreateIndexFile("index.html")) - - readBack, err := s.readContent(t, "index.html") - require.NoError(t, err) - require.True(t, strings.HasPrefix(readBack, " - - - - Directory Listing - - - - -

Directory Listing

- - - - - - - - {{- if eq (len .entries) 0 }} - - - - {{- else }} - {{- range .entries }}{{- if .IsDir }} - - - - - - - {{- end }}{{- end }} - {{- range .entries }}{{- if not .IsDir }} - - - - - - - {{- end }}{{- end }} - {{- end }} -
NameSizeMimeType
— Empty —
[DIR]{{ .Name }}{{ .Size }} entries{{ .MimeType }}
{{ .Name }}{{ .Size }} bytes{{ .MimeType }}
- - - diff --git a/pkg/cinodefs/writerinfo.go b/pkg/cinodefs/writerinfo.go deleted file mode 100644 index 4aeb90b..0000000 --- a/pkg/cinodefs/writerinfo.go +++ /dev/null @@ -1,83 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package cinodefs - -import ( - "errors" - "fmt" - - "github.com/cinode/go/pkg/cinodefs/protobuf" - "github.com/cinode/go/pkg/common" - "github.com/cinode/go/pkg/utilities/golang" - "github.com/jbenet/go-base58" - "google.golang.org/protobuf/proto" -) - -var ( - ErrInvalidWriterInfoData = errors.New("invalid writer info data") - ErrInvalidWriterInfoDataParse = fmt.Errorf("%w: protobuf parse error", ErrInvalidWriterInfoData) -) - -type WriterInfo struct { - wi protobuf.WriterInfo -} - -func (wi *WriterInfo) Bytes() []byte { - return golang.Must(proto.Marshal(&wi.wi)) -} - -func (wi *WriterInfo) String() string { - return base58.Encode(wi.Bytes()) -} - -func WriterInfoFromString(s string) (*WriterInfo, error) { - if s == "" { - return nil, fmt.Errorf("%w: empty string", ErrInvalidWriterInfoData) - } - - b := base58.Decode(s) - if len(b) == 0 { - return nil, fmt.Errorf("%w: not a base58 string", ErrInvalidWriterInfoData) - } - - return WriterInfoFromBytes(b) -} - -func WriterInfoFromBytes(b []byte) (*WriterInfo, error) { - wi := WriterInfo{} - - err := proto.Unmarshal(b, &wi.wi) - if err != nil { - return nil, fmt.Errorf("%w: %s", ErrInvalidWriterInfoDataParse, err) - } - - return &wi, nil -} - -func writerInfoFromBlobNameKeyAndAuthInfo( - bn *common.BlobName, - key *common.BlobKey, - authInfo *common.AuthInfo, -) *WriterInfo { - return &WriterInfo{ - wi: protobuf.WriterInfo{ - BlobName: bn.Bytes(), - Key: key.Bytes(), - AuthInfo: authInfo.Bytes(), - }, - } -} diff --git a/pkg/cinodefs/writerinfo_bb_test.go b/pkg/cinodefs/writerinfo_bb_test.go deleted file mode 100644 index 72d2b34..0000000 --- a/pkg/cinodefs/writerinfo_bb_test.go +++ /dev/null @@ -1,42 +0,0 @@ -/* -Copyright © 2023 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package cinodefs_test - -import ( - "testing" - - "github.com/cinode/go/pkg/cinodefs" - "github.com/stretchr/testify/require" -) - -func TestWriterInfoFromStringFailures(t *testing.T) { - for _, d := range []struct { - s string - errContains string - }{ - {"", "empty string"}, - {"not-a-base64-string!!!!!!!!", "not a base58 string"}, - {"aaaaaaaa", "protobuf parse error"}, - } { - t.Run(d.s, func(t *testing.T) { - wi, err := cinodefs.WriterInfoFromString(d.s) - require.ErrorIs(t, err, cinodefs.ErrInvalidWriterInfoData) - require.ErrorContains(t, err, d.errContains) - require.Nil(t, wi) - }) - } -} diff --git a/pkg/cmd/cinodewebproxy/integration_test.go b/pkg/cmd/cinodewebproxy/integration_test.go deleted file mode 100644 index 7b7535e..0000000 --- a/pkg/cmd/cinodewebproxy/integration_test.go +++ /dev/null @@ -1,160 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package cinodewebproxy_test - -import ( - "context" - "fmt" - "io" - "net/http" - "os" - "path/filepath" - "sync" - "testing" - "testing/fstest" - "time" - - "github.com/cinode/go/pkg/blenc" - "github.com/cinode/go/pkg/cinodefs" - "github.com/cinode/go/pkg/cinodefs/uploader" - "github.com/cinode/go/pkg/cmd/cinodewebproxy" - "github.com/cinode/go/pkg/datastore" - "github.com/stretchr/testify/require" -) - -func TestIntegration(t *testing.T) { - os.Clearenv() - - // Prepare test filesystem - testFS := fstest.MapFS{ - "index.html": &fstest.MapFile{ - Data: []byte("Hello world!"), - }, - "test.txt": &fstest.MapFile{ - Data: []byte("test.txt"), - }, - "internal/folder/file.txt": &fstest.MapFile{ - Data: []byte("internal folder file"), - }, - } - - for i := 0; i < 20; i++ { - testFS[fmt.Sprintf("batch/file_%d", i)] = &fstest.MapFile{ - Data: []byte(fmt.Sprintf("data_%d", i)), - } - } - - // Compile encrypted datastore - - dir := t.TempDir() - ds, err := datastore.InRawFileSystem(dir) - require.NoError(t, err) - - cfs, err := cinodefs.New( - t.Context(), - blenc.FromDatastore(ds), - cinodefs.NewRootStaticDirectory(), - ) - require.NoError(t, err) - - err = uploader.UploadStaticDirectory( - t.Context(), - testFS, - cfs, - ) - require.NoError(t, err) - - err = cfs.Flush(t.Context()) - require.NoError(t, err) - - ep, err := cfs.RootEntrypoint() - require.NoError(t, err) - - t.Setenv("CINODE_ENTRYPOINT", ep.String()) - - runAndValidateCinodeProxy := func() { - ctx, cancel := context.WithCancel(t.Context()) - - // Run the server in the background - wg := sync.WaitGroup{} - wg.Add(1) - go func() { - defer wg.Done() - cinodewebproxy.Execute(ctx) - }() - time.Sleep(time.Millisecond) // Wait for the server, TODO: This is ugly way to do this - - // Ensure we clean up cleanly - defer func() { - cancel() - wg.Wait() - }() - - // Validate content of all files - for name, file := range testFS { - t.Run(name, func(t *testing.T) { - resp, err := http.Get("http://localhost:8080/" + name) - require.NoError(t, err) - defer resp.Body.Close() - - data, err := io.ReadAll(resp.Body) - require.NoError(t, err) - require.Equal(t, file.Data, data) - }) - } - } - - t.Run("main datastore from compiled files", func(t *testing.T) { - t.Setenv("CINODE_MAIN_DATASTORE", "file-raw://"+dir) - runAndValidateCinodeProxy() - }) - - t.Run("additional datastore from compiled files", func(t *testing.T) { - t.Setenv("CINODE_ADDITIONAL_DATASTORE", "file-raw://"+dir) - runAndValidateCinodeProxy() - }) - - t.Run("use multiple datastores", func(t *testing.T) { - // the blobstore is split to smaller folders, each one of them containing - // some subset of blobs - partialDirs := []string{ - t.TempDir(), - t.TempDir(), - t.TempDir(), - } - - files, err := os.ReadDir(dir) - require.NoError(t, err) - for i, fl := range files { - partialDir := partialDirs[i%len(partialDirs)] - - require.False(t, fl.IsDir()) - require.True(t, fl.Type().IsRegular()) - - data, err := os.ReadFile(filepath.Join(dir, fl.Name())) - require.NoError(t, err) - - err = os.WriteFile(filepath.Join(partialDir, fl.Name()), data, 0o666) - require.NoError(t, err) - } - - for i, partialDir := range partialDirs { - t.Setenv(fmt.Sprintf("CINODE_ADDITIONAL_DATASTORE_%d", i), "file-raw://"+partialDir) - } - runAndValidateCinodeProxy() - }) -} diff --git a/pkg/cmd/cinodewebproxy/root.go b/pkg/cmd/cinodewebproxy/root.go deleted file mode 100644 index 25a186a..0000000 --- a/pkg/cmd/cinodewebproxy/root.go +++ /dev/null @@ -1,176 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package cinodewebproxy - -import ( - "bytes" - "context" - "errors" - "fmt" - "log/slog" - "net/http" - "os" - "runtime" - "sort" - "strconv" - "strings" - - "github.com/cinode/go/pkg/blenc" - "github.com/cinode/go/pkg/cinodefs" - "github.com/cinode/go/pkg/cinodefs/httphandler" - "github.com/cinode/go/pkg/datastore" - "github.com/cinode/go/pkg/datastore/multisource" - "github.com/cinode/go/pkg/utilities/golang" - "github.com/cinode/go/pkg/utilities/httpserver" -) - -func Execute(ctx context.Context) error { - cfg, err := getConfig() - if err != nil { - return err - } - return executeWithConfig(ctx, cfg) -} - -func executeWithConfig(ctx context.Context, cfg *config) error { - mainDS, err := datastore.FromLocation(cfg.mainDSLocation) - if err != nil { - return fmt.Errorf("could not create main datastore: %w", err) - } - - additionalDSs := []datastore.DS{} - for _, loc := range cfg.additionalDSLocations { - ds, err := datastore.FromLocation(loc) - if err != nil { - return fmt.Errorf("could not create additional datastores: %w", err) - } - additionalDSs = append(additionalDSs, ds) - } - - entrypoint, err := cinodefs.EntrypointFromString(cfg.entrypoint) - if err != nil { - return fmt.Errorf("could not parse entrypoint data: %w", err) - } - - log := slog.Default() - - log.Info("Server listening for connections", - "address", fmt.Sprintf("http://localhost:%d", cfg.port), - ) - log.Info("Main datastore", "addr", cfg.mainDSLocation) - log.Info("Additional datastores", "addrs", cfg.additionalDSLocations) - - log.Info("System info", - "goos", runtime.GOOS, - "goarch", runtime.GOARCH, - "compiler", runtime.Compiler, - "cpus", runtime.NumCPU(), - ) - - handler := setupCinodeProxy(ctx, mainDS, additionalDSs, entrypoint) - - return httpserver.RunGracefully(ctx, - handler, - httpserver.ListenPort(cfg.port), - httpserver.Logger(log), - ) -} - -func setupCinodeProxy( - ctx context.Context, - mainDS datastore.DS, - additionalDSs []datastore.DS, - entrypoint *cinodefs.Entrypoint, -) http.Handler { - fs := golang.Must(cinodefs.New( - ctx, - blenc.FromDatastore( - multisource.New( - mainDS, - multisource.WithAdditionalDatastores(additionalDSs...), - ), - ), - cinodefs.RootEntrypoint(entrypoint), - cinodefs.MaxLinkRedirects(10), - )) - - return &httphandler.Handler{ - FS: fs, - IndexFile: "index.html", - Log: slog.Default(), - } -} - -type config struct { - entrypoint string - mainDSLocation string - additionalDSLocations []string - port int -} - -func getConfig() (*config, error) { - cfg := config{} - - entrypoint, found := os.LookupEnv("CINODE_ENTRYPOINT") - if !found { - entrypointFile, found := os.LookupEnv("CINODE_ENTRYPOINT_FILE") - if !found { - return nil, errors.New("missing CINODE_ENTRYPOINT or CINODE_ENTRYPOINT_FILE env var") - } - entrypointFileData, err := os.ReadFile(entrypointFile) - if err != nil { - return nil, fmt.Errorf("could not read entrypoint file at '%s': %w", entrypointFile, err) - } - entrypoint = string(bytes.TrimSpace(entrypointFileData)) - } - cfg.entrypoint = entrypoint - - cfg.mainDSLocation = os.Getenv("CINODE_MAIN_DATASTORE") - if cfg.mainDSLocation == "" { - cfg.mainDSLocation = "memory://" - } - - additionalDSEnvNames := []string{} - for _, e := range os.Environ() { - if strings.HasPrefix(e, "CINODE_ADDITIONAL_DATASTORE") { - split := strings.SplitN(e, "=", 2) - additionalDSEnvNames = append(additionalDSEnvNames, split[0]) - } - } - sort.Strings(additionalDSEnvNames) - - for _, envName := range additionalDSEnvNames { - location := os.Getenv(envName) - cfg.additionalDSLocations = append(cfg.additionalDSLocations, location) - } - - port := os.Getenv("CINODE_LISTEN_PORT") - if port == "" { - cfg.port = 8080 - } else { - portNum, err := strconv.Atoi(port) - if err == nil && (portNum < 0 || portNum > 65535) { - err = fmt.Errorf("not in range 0..65535") - } - if err != nil { - return nil, fmt.Errorf("invalid listen port %s: %w", port, err) - } - cfg.port = portNum - } - - return &cfg, nil -} diff --git a/pkg/cmd/cinodewebproxy/root_test.go b/pkg/cmd/cinodewebproxy/root_test.go deleted file mode 100644 index cc59596..0000000 --- a/pkg/cmd/cinodewebproxy/root_test.go +++ /dev/null @@ -1,310 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package cinodewebproxy - -import ( - "bytes" - "context" - "crypto/sha256" - "io" - "net/http" - "net/http/httptest" - "os" - "path/filepath" - "testing" - "time" - - "github.com/cinode/go/pkg/blenc" - "github.com/cinode/go/pkg/blobtypes" - "github.com/cinode/go/pkg/cinodefs" - "github.com/cinode/go/pkg/cinodefs/uploader" - "github.com/cinode/go/pkg/common" - "github.com/cinode/go/pkg/datastore" - "github.com/cinode/go/pkg/internal/utilities/cipherfactory" - "github.com/cinode/go/testvectors/testblobs" - "github.com/jbenet/go-base58" - "github.com/stretchr/testify/require" -) - -func TestGetConfig(t *testing.T) { - os.Clearenv() - - t.Run("default config", func(t *testing.T) { - cfg, err := getConfig() - require.ErrorContains(t, err, "ENTRYPOINT") - require.Nil(t, cfg) - }) - - t.Run("default config with entrypoint", func(t *testing.T) { - t.Setenv("CINODE_ENTRYPOINT", "12345") - cfg, err := getConfig() - require.NoError(t, err) - require.Equal(t, "12345", cfg.entrypoint) - require.Equal(t, "memory://", cfg.mainDSLocation) - require.Empty(t, cfg.additionalDSLocations) - require.Equal(t, 8080, cfg.port) - }) - - t.Run("entrypoint file", func(t *testing.T) { - t.Run("valid", func(t *testing.T) { - entrypointFile := filepath.Join(t.TempDir(), "ep.txt") - err := os.WriteFile(entrypointFile, []byte("54321"), 0o666) - require.NoError(t, err) - - t.Setenv("CINODE_ENTRYPOINT_FILE", entrypointFile) - cfg, err := getConfig() - require.NoError(t, err) - require.Equal(t, "54321", cfg.entrypoint) - }) - t.Run("invalid", func(t *testing.T) { - entrypointFile := filepath.Join(t.TempDir(), "ep.txt") - t.Setenv("CINODE_ENTRYPOINT_FILE", entrypointFile) - cfg, err := getConfig() - require.ErrorContains(t, err, "read") - require.Nil(t, cfg) - }) - }) - - t.Setenv("CINODE_ENTRYPOINT", "000000") - - t.Run("set main datastore", func(t *testing.T) { - t.Setenv("CINODE_MAIN_DATASTORE", "testdatastore") - cfg, err := getConfig() - require.NoError(t, err) - require.Equal(t, cfg.mainDSLocation, "testdatastore") - }) - - t.Run("set additional datastores", func(t *testing.T) { - t.Setenv("CINODE_ADDITIONAL_DATASTORE", "additional") - t.Setenv("CINODE_ADDITIONAL_DATASTORE_3", "additional3") - t.Setenv("CINODE_ADDITIONAL_DATASTORE_2", "additional2") - t.Setenv("CINODE_ADDITIONAL_DATASTORE_1", "additional1") - - cfg, err := getConfig() - require.NoError(t, err) - require.Equal(t, cfg.additionalDSLocations, []string{ - "additional", - "additional1", - "additional2", - "additional3", - }) - }) - - t.Run("set listen port", func(t *testing.T) { - t.Setenv("CINODE_LISTEN_PORT", "12345") - cfg, err := getConfig() - require.NoError(t, err) - require.Equal(t, 12345, cfg.port) - }) - - t.Run("invalid port - not a number", func(t *testing.T) { - t.Setenv("CINODE_LISTEN_PORT", "123-45") - _, err := getConfig() - require.ErrorContains(t, err, "invalid listen port") - }) - - t.Run("invalid port - outside range", func(t *testing.T) { - t.Setenv("CINODE_LISTEN_PORT", "-1") - _, err := getConfig() - require.ErrorContains(t, err, "invalid listen port") - }) -} - -func TestWebProxyHandlerInvalidEntrypoint(t *testing.T) { - n, err := common.BlobNameFromHashAndType( - make([]byte, sha256.Size), - blobtypes.Static, - ) - require.NoError(t, err) - - key := cipherfactory.NewKeyGenerator(blobtypes.Static).Generate() - - handler := setupCinodeProxy( - t.Context(), - datastore.InMemory(), - []datastore.DS{}, - cinodefs.EntrypointFromBlobNameAndKey(n, key), - ) - - server := httptest.NewServer(handler) - defer server.Close() - - t.Run("query invalid entrypoint", func(t *testing.T) { - resp, err := http.Get(server.URL) - require.NoError(t, err) - defer resp.Body.Close() - - require.Equal(t, http.StatusNotFound, resp.StatusCode) - }) - - t.Run("invalid method", func(t *testing.T) { - resp, err := http.Post(server.URL, "application/octet-stream", bytes.NewReader(nil)) - require.NoError(t, err) - defer resp.Body.Close() - - require.Equal(t, http.StatusMethodNotAllowed, resp.StatusCode) - }) -} - -func TestWebProxyHandlerSimplePage(t *testing.T) { - ds := datastore.InMemory() - be := blenc.FromDatastore(ds) - - ep := func() *cinodefs.Entrypoint { - dir := t.TempDir() - - for name, content := range map[string]string{ - "index.html": "index", - "sub/index.html": "sub-index", - } { - err := os.MkdirAll(filepath.Dir(filepath.Join(dir, name)), 0o755) - require.NoError(t, err) - - err = os.WriteFile(filepath.Join(dir, name), []byte(content), 0o644) - require.NoError(t, err) - } - - fs, err := cinodefs.New(t.Context(), be, cinodefs.NewRootDynamicLink()) - require.NoError(t, err) - - err = uploader.UploadStaticDirectory( - t.Context(), - os.DirFS(dir), - fs, - ) - require.NoError(t, err) - - err = fs.Flush(t.Context()) - require.NoError(t, err) - - ep, err := fs.RootEntrypoint() - require.NoError(t, err) - return ep - }() - - handler := setupCinodeProxy(t.Context(), ds, []datastore.DS{}, ep) - - server := httptest.NewServer(handler) - defer server.Close() - - t.Run("get index", func(t *testing.T) { - resp, err := http.Get(server.URL) - require.NoError(t, err) - defer resp.Body.Close() - require.Equal(t, resp.StatusCode, http.StatusOK) - data, err := io.ReadAll(resp.Body) - require.NoError(t, err) - require.Equal(t, "index", string(data)) - }) - - t.Run("get sub-index", func(t *testing.T) { - resp, err := http.Get(server.URL + "/sub") - require.NoError(t, err) - defer resp.Body.Close() - require.Equal(t, resp.StatusCode, http.StatusOK) - data, err := io.ReadAll(resp.Body) - require.NoError(t, err) - require.Equal(t, "sub-index", string(data)) - }) - - t.Run("get sub-index directly", func(t *testing.T) { - resp, err := http.Get(server.URL + "/sub/index.html") - require.NoError(t, err) - defer resp.Body.Close() - require.Equal(t, resp.StatusCode, http.StatusOK) - data, err := io.ReadAll(resp.Body) - require.NoError(t, err) - require.Equal(t, "sub-index", string(data)) - }) -} - -func TestExecuteWithConfig(t *testing.T) { - t.Run("invalid main datastore", func(t *testing.T) { - err := executeWithConfig(t.Context(), &config{ - mainDSLocation: "memory://invalid", - }) - require.ErrorContains(t, err, "main datastore") - }) - - t.Run("invalid additional datastore", func(t *testing.T) { - err := executeWithConfig(t.Context(), &config{ - mainDSLocation: "memory://", - additionalDSLocations: []string{"memory://", "memory://invalid"}, - }) - require.ErrorContains(t, err, "additional datastores") - }) - - t.Run("invalid entrypoint", func(t *testing.T) { - err := executeWithConfig(t.Context(), &config{ - mainDSLocation: "memory://", - entrypoint: "!@#$", - }) - require.ErrorContains(t, err, "could not parse") - }) - - t.Run("invalid entrypoint bytes", func(t *testing.T) { - err := executeWithConfig(t.Context(), &config{ - mainDSLocation: "memory://", - entrypoint: base58.Encode([]byte("1234567890")), - }) - require.ErrorContains(t, err, "could not parse") - }) - - t.Run("successful run", func(t *testing.T) { - ep := testblobs.DynamicLink.Entrypoint() - - ctx, cancel := context.WithCancel(t.Context()) - go func() { - time.Sleep(10 * time.Millisecond) - cancel() - }() - - err := executeWithConfig(ctx, &config{ - mainDSLocation: "memory://", - entrypoint: ep.String(), - }) - require.NoError(t, err) - }) -} - -func TestExecute(t *testing.T) { - os.Clearenv() - - t.Run("valid configuration", func(t *testing.T) { - ep := testblobs.DynamicLink.Entrypoint() - - t.Setenv("CINODE_ENTRYPOINT", ep.String()) - t.Setenv("CINODE_LISTEN_PORT", "0") - ctx, cancel := context.WithCancel(t.Context()) - go func() { - time.Sleep(10 * time.Millisecond) - cancel() - }() - err := Execute(ctx) - require.NoError(t, err) - }) - - t.Run("invalid configuration", func(t *testing.T) { - ctx, cancel := context.WithCancel(t.Context()) - go func() { - time.Sleep(10 * time.Millisecond) - cancel() - }() - err := Execute(ctx) - require.ErrorContains(t, err, "CINODE_ENTRYPOINT") - }) -} diff --git a/pkg/cmd/publicnode/root.go b/pkg/cmd/publicnode/root.go deleted file mode 100644 index aed6382..0000000 --- a/pkg/cmd/publicnode/root.go +++ /dev/null @@ -1,192 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package publicnode - -import ( - "context" - "crypto/sha256" - "crypto/subtle" - "fmt" - "log/slog" - "net/http" - "os" - "runtime" - "sort" - "strconv" - "strings" - - "github.com/cinode/go/pkg/datastore" - "github.com/cinode/go/pkg/datastore/multisource" - "github.com/cinode/go/pkg/utilities/httpserver" -) - -func Execute(ctx context.Context) error { - cfg, err := getConfig() - if err != nil { - return err - } - return executeWithConfig(ctx, cfg) -} - -func executeWithConfig(ctx context.Context, cfg *config) error { - handler, err := buildHTTPHandler(cfg) - if err != nil { - return err - } - - cfg.log.Info("Server listening for connections", - "address", fmt.Sprintf("http://localhost:%d", cfg.port), - ) - - cfg.log.Info("System info", - "goos", runtime.GOOS, - "goarch", runtime.GOARCH, - "compiler", runtime.Compiler, - "cpus", runtime.NumCPU(), - ) - - return httpserver.RunGracefully(ctx, - handler, - httpserver.ListenPort(cfg.port), - ) -} - -func buildHTTPHandler(cfg *config) (http.Handler, error) { - mainDS, err := datastore.FromLocation(cfg.mainDSLocation) - if err != nil { - return nil, fmt.Errorf("could not create main datastore: %w", err) - } - - additionalDSs := []datastore.DS{} - for _, loc := range cfg.additionalDSLocations { - ds, err := datastore.FromLocation(loc) - if err != nil { - return nil, fmt.Errorf("could not create additional datastores: %w", err) - } - additionalDSs = append(additionalDSs, ds) - } - - ds := multisource.New( - mainDS, - multisource.WithAdditionalDatastores(additionalDSs...), - ) - handler := datastore.WebInterface( - ds, - datastore.WebInterfaceOptionLogger(cfg.log), - ) - - if cfg.uploadUsername != "" || cfg.uploadPassword != "" { - origHandler := handler - expectedUsernameHash := sha256.Sum256([]byte(cfg.uploadUsername)) - expectedPasswordHash := sha256.Sum256([]byte(cfg.uploadPassword)) - handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - switch r.Method { - case http.MethodGet, http.MethodHead: - // Auth not required, continue without auth check - default: - // Every other method requires token, this is preventive - // since not all methods will be uploads, but it comes from the - // secure-by-default approach. - // - // Also we're comparing hashes instead of their values. - // This, due to properties of a hashing function, reduces attacks - // based on side-channel information, including the length of the - // token. The subtle.ConstantTimeCompare is not really needed here - // but it does not do any harm. - username, password, ok := r.BasicAuth() - - var validAuth = 0 - if ok { - validAuth = 1 - } - - usernameHash := sha256.Sum256([]byte(username)) - validAuth &= subtle.ConstantTimeCompare( - expectedUsernameHash[:], - usernameHash[:], - ) - - passwordHash := sha256.Sum256([]byte(password)) - validAuth &= subtle.ConstantTimeCompare( - expectedPasswordHash[:], - passwordHash[:], - ) - - if validAuth != 1 { - w.WriteHeader(http.StatusForbidden) - return - } - } - origHandler.ServeHTTP(w, r) - }) - } - - return handler, nil -} - -type config struct { - log *slog.Logger - mainDSLocation string - uploadUsername string - uploadPassword string - additionalDSLocations []string - port int -} - -func getConfig() (*config, error) { - cfg := config{ - log: slog.Default(), - } - - cfg.mainDSLocation = os.Getenv("CINODE_MAIN_DATASTORE") - if cfg.mainDSLocation == "" { - cfg.mainDSLocation = "memory://" - } - - additionalDSEnvNames := []string{} - for _, e := range os.Environ() { - if strings.HasPrefix(e, "CINODE_ADDITIONAL_DATASTORE") { - split := strings.SplitN(e, "=", 2) - additionalDSEnvNames = append(additionalDSEnvNames, split[0]) - } - } - sort.Strings(additionalDSEnvNames) - - for _, envName := range additionalDSEnvNames { - location := os.Getenv(envName) - cfg.additionalDSLocations = append(cfg.additionalDSLocations, location) - } - - port := os.Getenv("CINODE_LISTEN_PORT") - if port == "" { - cfg.port = 8080 - } else { - portNum, err := strconv.Atoi(port) - if err == nil && (portNum < 0 || portNum > 65535) { - err = fmt.Errorf("not in range 0..65535") - } - if err != nil { - return nil, fmt.Errorf("invalid listen port %s: %w", port, err) - } - cfg.port = portNum - } - - cfg.uploadUsername = os.Getenv("CINODE_UPLOAD_USERNAME") - cfg.uploadPassword = os.Getenv("CINODE_UPLOAD_PASSWORD") - - return &cfg, nil -} diff --git a/pkg/cmd/publicnode/root_test.go b/pkg/cmd/publicnode/root_test.go deleted file mode 100644 index 02cbdb8..0000000 --- a/pkg/cmd/publicnode/root_test.go +++ /dev/null @@ -1,213 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package publicnode - -import ( - "context" - "log/slog" - "net/http/httptest" - "os" - "testing" - "time" - - "github.com/cinode/go/testvectors/testblobs" - "github.com/stretchr/testify/require" -) - -func TestGetConfig(t *testing.T) { - os.Clearenv() - - t.Run("default config", func(t *testing.T) { - cfg, err := getConfig() - require.NoError(t, err) - require.Equal(t, "memory://", cfg.mainDSLocation) - require.Empty(t, cfg.additionalDSLocations) - require.Equal(t, 8080, cfg.port) - }) - - t.Run("set main datastore", func(t *testing.T) { - t.Setenv("CINODE_MAIN_DATASTORE", "testdatastore") - cfg, err := getConfig() - require.NoError(t, err) - require.Equal(t, cfg.mainDSLocation, "testdatastore") - }) - - t.Run("set additional datastores", func(t *testing.T) { - t.Setenv("CINODE_ADDITIONAL_DATASTORE", "additional") - t.Setenv("CINODE_ADDITIONAL_DATASTORE_3", "additional3") - t.Setenv("CINODE_ADDITIONAL_DATASTORE_2", "additional2") - t.Setenv("CINODE_ADDITIONAL_DATASTORE_1", "additional1") - - cfg, err := getConfig() - require.NoError(t, err) - require.Equal(t, cfg.additionalDSLocations, []string{ - "additional", - "additional1", - "additional2", - "additional3", - }) - }) - - t.Run("set listen port", func(t *testing.T) { - t.Setenv("CINODE_LISTEN_PORT", "12345") - cfg, err := getConfig() - require.NoError(t, err) - require.Equal(t, 12345, cfg.port) - }) - - t.Run("invalid port - not a number", func(t *testing.T) { - t.Setenv("CINODE_LISTEN_PORT", "123-45") - _, err := getConfig() - require.ErrorContains(t, err, "invalid listen port") - }) - - t.Run("invalid port - outside range", func(t *testing.T) { - t.Setenv("CINODE_LISTEN_PORT", "-1") - _, err := getConfig() - require.ErrorContains(t, err, "invalid listen port") - }) -} - -func TestBuildHttpHandler(t *testing.T) { - t.Run("Successfully created handler", func(t *testing.T) { - h, err := buildHTTPHandler(&config{ - mainDSLocation: t.TempDir(), - additionalDSLocations: []string{ - t.TempDir(), - t.TempDir(), - t.TempDir(), - }, - }) - require.NoError(t, err) - require.NotNil(t, h) - - t.Run("check the server", func(t *testing.T) { - server := httptest.NewServer(h) - defer server.Close() - - err := testblobs.DynamicLink.Put(server.URL) - require.NoError(t, err) - - _, err = testblobs.DynamicLink.Get(server.URL) - require.NoError(t, err) - }) - }) - - t.Run("Upload auth", func(t *testing.T) { - const ValidUserName = "Alice" - const InvalidUserName = "Bob" - const ValidPassword = "secret" - const InvalidPassword = "plaintext" - - h, err := buildHTTPHandler(&config{ - mainDSLocation: t.TempDir(), - additionalDSLocations: []string{ - t.TempDir(), - t.TempDir(), - t.TempDir(), - }, - uploadUsername: ValidUserName, - uploadPassword: ValidPassword, - }) - require.NoError(t, err) - require.NotNil(t, h) - - server := httptest.NewServer(h) - defer server.Close() - - err = testblobs.DynamicLink.Put(server.URL) - require.ErrorContains(t, err, "403") - - err = testblobs.DynamicLink.PutWithAuth(server.URL, ValidUserName, ValidPassword) - require.NoError(t, err) - - err = testblobs.DynamicLink.PutWithAuth(server.URL, ValidUserName, InvalidPassword) - require.ErrorContains(t, err, "403") - - err = testblobs.DynamicLink.PutWithAuth(server.URL, InvalidUserName, ValidPassword) - require.ErrorContains(t, err, "403") - - err = testblobs.DynamicLink.PutWithAuth(server.URL, InvalidUserName, InvalidPassword) - require.ErrorContains(t, err, "403") - - _, err = testblobs.DynamicLink.Get(server.URL) - require.NoError(t, err) - }) - - t.Run("invalid main datastore", func(t *testing.T) { - h, err := buildHTTPHandler(&config{ - mainDSLocation: "", - }) - require.ErrorContains(t, err, "could not create main datastore") - require.Nil(t, h) - }) - - t.Run("invalid additional datastore", func(t *testing.T) { - h, err := buildHTTPHandler(&config{ - mainDSLocation: "memory://", - additionalDSLocations: []string{""}, - }) - require.ErrorContains(t, err, "could not create additional datastore") - require.Nil(t, h) - }) -} - -func TestExecuteWithConfig(t *testing.T) { - t.Run("successful run", func(t *testing.T) { - ctx, cancel := context.WithCancel(t.Context()) - go func() { - time.Sleep(10 * time.Millisecond) - cancel() - }() - err := executeWithConfig(ctx, &config{ - mainDSLocation: "memory://", - log: slog.Default(), - }) - require.NoError(t, err) - }) - - t.Run("invalid configuration", func(t *testing.T) { - err := executeWithConfig(t.Context(), &config{}) - require.ErrorContains(t, err, "datastore") - }) -} - -func TestExecute(t *testing.T) { - t.Run("valid configuration", func(t *testing.T) { - t.Setenv("CINODE_LISTEN_PORT", "0") - - ctx, cancel := context.WithCancel(t.Context()) - go func() { - time.Sleep(10 * time.Millisecond) - cancel() - }() - err := Execute(ctx) - require.NoError(t, err) - }) - - t.Run("invalid configuration", func(t *testing.T) { - t.Setenv("CINODE_MAIN_DATASTORE", "memory://invalid") - err := Execute(t.Context()) - require.ErrorContains(t, err, "datastore") - }) - - t.Run("invalid configuration - port", func(t *testing.T) { - t.Setenv("CINODE_LISTEN_PORT", "-1") - err := Execute(t.Context()) - require.ErrorContains(t, err, "listen port") - }) -} diff --git a/pkg/cmd/staticdatastore/compile.go b/pkg/cmd/staticdatastore/compile.go deleted file mode 100644 index 4df2460..0000000 --- a/pkg/cmd/staticdatastore/compile.go +++ /dev/null @@ -1,248 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package staticdatastore - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "log" - "os" - "strings" - - "github.com/cinode/go/pkg/blenc" - "github.com/cinode/go/pkg/cinodefs" - "github.com/cinode/go/pkg/cinodefs/uploader" - "github.com/cinode/go/pkg/datastore" - "github.com/spf13/cobra" -) - -func compileCmd() *cobra.Command { - var o compileFSOptions - var rootWriterInfoStr string - var rootWriterInfoFile string - var useRawFilesystem bool - - cmd := &cobra.Command{ - Use: "compile --source --destination ", - Short: "Compile datastore from static files", - Long: strings.Join([]string{ - "The compile command can be used to create an encrypted datastore from", - "a content with static files that can then be used to serve through a", - "simple http server.", - }, "\n"), - RunE: func(cmd *cobra.Command, args []string) error { - if o.srcDir == "" || o.dstLocation == "" { - return cmd.Help() - } - - enc := json.NewEncoder(cmd.OutOrStdout()) - enc.SetIndent("", " ") - - fatalResult := func(format string, args ...interface{}) error { - msg := fmt.Sprintf(format, args...) - - _ = enc.Encode(map[string]string{ - "result": "ERROR", - "msg": msg, - }) - - cmd.SilenceUsage = true - cmd.SilenceErrors = true - return errors.New(msg) - } - - if rootWriterInfoFile != "" { - data, err := os.ReadFile(rootWriterInfoFile) - if err != nil { - return fatalResult("Couldn't read data from the writer info file at '%s': %v", rootWriterInfoFile, err) - } - if len(data) == 0 { - return fatalResult("Writer info file at '%s' is empty", rootWriterInfoFile) - } - rootWriterInfoStr = string(data) - } - if rootWriterInfoStr != "" { - wi, err := cinodefs.WriterInfoFromString(rootWriterInfoStr) - if err != nil { - return fatalResult("Couldn't parse writer info: %v", err) - } - o.writerInfo = wi - } - - if useRawFilesystem { - // Note: workaround for https://github.com/spf13/cobra/issues/1708 - fmt.Fprintln( - cmd.ErrOrStderr(), - "Flag --raw-filesystem has been deprecated, use file-raw:// destination prefix instead", - ) - // For backwards compatibility - o.dstLocation = "file-raw://" + o.dstLocation - } - - ep, wi, err := compileFS(cmd.Context(), o) - if err != nil { - return fatalResult("%s", err) - } - - result := map[string]string{ - "result": "OK", - "entrypoint": ep.String(), - } - if wi != nil { - result["writer-info"] = wi.String() - } - - _ = enc.Encode(result) - - log.Println("DONE") - return nil - }, - } - - cmd.Flags().StringVarP( - &o.srcDir, "source", "s", "", - "Source directory with content to compile", - ) - cmd.Flags().StringVarP( - &o.dstLocation, "destination", "d", "", - "location of destination datastore for blobs, can be a directory "+ - "or an url prefixed with file://, file-raw://, http://, https://", - ) - cmd.Flags().BoolVarP( - &o.static, "static", "t", false, - "if set to true, compile only the static dataset, do not create or update dynamic link", - ) - cmd.Flags().BoolVarP( - &useRawFilesystem, "raw-filesystem", "r", false, - "if set to true, use raw filesystem instead of the optimized one, "+ - "can be used to create dataset for a standard http server", - ) - _ = cmd.Flags().MarkHidden("raw-filesystem") - cmd.Flags().StringVarP( - &rootWriterInfoStr, "writer-info", "w", "", - "writer info for the root dynamic link, if neither writer info nor writer info file is specified, "+ - "a random writer info will be generated and printed out", - ) - cmd.Flags().StringVarP( - &rootWriterInfoFile, "writer-info-file", "f", "", - "name of the file containing writer info for the root dynamic link, "+ - "if neither writer info nor writer info file is specified, "+ - "a random writer info will be generated and printed out", - ) - cmd.Flags().StringVar( - &o.indexFile, "index-file", "index.html", - "name of the index file", - ) - cmd.Flags().BoolVar( - &o.generateIndexFiles, "generate-index-files", false, - "automatically generate index html files with directory listing if index file is not present", - ) - cmd.Flags().BoolVar( - &o.append, "append", false, - "append file in existing datastore leaving existing unchanged files as is", - ) - - return cmd -} - -type compileFSOptions struct { - writerInfo *cinodefs.WriterInfo - srcDir string - dstLocation string - indexFile string - static bool - generateIndexFiles bool - append bool -} - -func compileFS( - ctx context.Context, - o compileFSOptions, -) ( - *cinodefs.Entrypoint, - *cinodefs.WriterInfo, - error, -) { - ds, err := datastore.FromLocation(o.dstLocation) - if err != nil { - return nil, nil, fmt.Errorf("could not open datastore: %w", err) - } - - opts := []cinodefs.Option{} - - switch { - case o.static: - opts = append(opts, cinodefs.NewRootStaticDirectory()) - case o.writerInfo == nil: - opts = append(opts, cinodefs.NewRootDynamicLink()) - default: - opts = append(opts, cinodefs.RootWriterInfo(o.writerInfo)) - } - - fs, err := cinodefs.New( - ctx, - blenc.FromDatastore(ds), - opts..., - ) - if err != nil { - return nil, nil, fmt.Errorf("couldn't create cinode filesystem instance: %w", err) - } - - if !o.append { - err = fs.ResetDir(ctx, []string{}) - if err != nil { - return nil, nil, fmt.Errorf("failed to reset the root directory: %w", err) - } - } - - var genOpts []uploader.Option - if o.generateIndexFiles { - genOpts = append(genOpts, uploader.CreateIndexFile(o.indexFile)) - } - - err = uploader.UploadStaticDirectory( - ctx, - os.DirFS(o.srcDir), - fs, - genOpts..., - ) - if err != nil { - return nil, nil, fmt.Errorf("couldn't upload directory content: %w", err) - } - - err = fs.Flush(ctx) - if err != nil { - return nil, nil, fmt.Errorf("couldn't flush after directory upload: %w", err) - } - - ep, err := fs.RootEntrypoint() - if err != nil { - return nil, nil, fmt.Errorf("couldn't get root entrypoint from cinodefs instance: %w", err) - } - - wi, err := fs.RootWriterInfo(ctx) - if errors.Is(err, cinodefs.ErrNotALink) { - return ep, nil, nil - } - if err != nil { - return nil, nil, fmt.Errorf("couldn't get root writer info from cinodefs instance: %w", err) - } - - return ep, wi, nil -} diff --git a/pkg/cmd/staticdatastore/root.go b/pkg/cmd/staticdatastore/root.go deleted file mode 100644 index e2eb4ac..0000000 --- a/pkg/cmd/staticdatastore/root.go +++ /dev/null @@ -1,46 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package staticdatastore - -import ( - "github.com/spf13/cobra" -) - -// RootCmd represents the base command when called without any subcommands -func RootCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "static_datastore", - Short: "Sample application to operate on static datastore", - Long: `static_datastore can be used to create a simple http server serving -content from datastore served from encrypted datastore layer. - -The first step is to generate datastore content with the 'compile' -command which can then be served using the 'server' command. - -Note that this tool is supposed to be used for testing purposes only. -It does not guarantee secrecy since the encryption key for the root -node is stored in a plaintext in a file called 'entrypoint.txt'. -`, - RunE: func(cmd *cobra.Command, args []string) error { - return cmd.Help() - }, - } - - cmd.AddCommand(compileCmd()) - - return cmd -} diff --git a/pkg/cmd/staticdatastore/static_datastore_test.go b/pkg/cmd/staticdatastore/static_datastore_test.go deleted file mode 100644 index bd2fec7..0000000 --- a/pkg/cmd/staticdatastore/static_datastore_test.go +++ /dev/null @@ -1,409 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package staticdatastore - -import ( - "bytes" - "encoding/json" - "io" - "log/slog" - "net/http" - "net/http/httptest" - "os" - "path/filepath" - "testing" - - "github.com/cinode/go/pkg/blenc" - "github.com/cinode/go/pkg/cinodefs" - "github.com/cinode/go/pkg/cinodefs/httphandler" - "github.com/cinode/go/pkg/datastore" - "github.com/cinode/go/pkg/utilities/golang" - "github.com/spf13/cobra" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" -) - -type datasetFile struct { - fName string - contents string -} -type CompileAndReadTestSuite struct { - suite.Suite - - initialTestDataset []datasetFile - updatedTestDataset []datasetFile -} - -func TestCompileAndReadTestSuite(t *testing.T) { - s := &CompileAndReadTestSuite{ - initialTestDataset: []datasetFile{ - { - "/homefile.txt", - "Hello home dir", - }, - { - "/subpath/file.txt", - "File in subpath", - }, - { - "/subpath/file2.txt", - "Another file in subpath", - }, - { - "/some/other/nested/path/file.txt", - "Deeply nested file", - }, - { - "/index.html", - "Index", - }, - }, - - updatedTestDataset: []datasetFile{ - { - "/homefile.txt", - "Hello home dir - updated", - }, - { - "/subpath/file.txt", - "File in subpath", - }, - { - "/subpath/file2.txt", - "Another file in subpath", - }, - { - "/some/other/nested/path/file.txt", - "Deeply nested file", - }, - { - "/index.html", - "Index", - }, - }, - } - - suite.Run(t, s) -} - -type testOutputParser struct { - Result string `json:"result"` - Msg string `json:"msg"` - WI string `json:"writer-info"` - EP string `json:"entrypoint"` -} - -func (s *CompileAndReadTestSuite) uploadDatasetToDatastore( - t *testing.T, - dataset []datasetFile, - datastoreDir string, - extraArgs ...string, -) (wi *cinodefs.WriterInfo, ep *cinodefs.Entrypoint) { - dir := t.TempDir() - - for _, td := range dataset { - err := os.MkdirAll(filepath.Join(dir, filepath.Dir(td.fName)), 0o777) - require.NoError(t, err) - - err = os.WriteFile(filepath.Join(dir, td.fName), []byte(td.contents), 0o600) - require.NoError(t, err) - } - - buf := bytes.NewBuffer(nil) - - args := []string{ - "compile", - "-s", dir, - "-d", datastoreDir, - } - args = append(args, extraArgs...) - - cmd := RootCmd() - cmd.SetArgs(args) - cmd.SetOut(buf) - - err := cmd.Execute() - require.NoError(t, err) - - output := testOutputParser{} - - err = json.Unmarshal(buf.Bytes(), &output) - require.NoErrorf(t, err, "output: %s", buf.String()) - require.Equal(t, "OK", output.Result) - - if output.WI != "" { - wi = golang.Must(cinodefs.WriterInfoFromString(output.WI)) - } - ep = golang.Must(cinodefs.EntrypointFromString(output.EP)) - return wi, ep -} - -func (s *CompileAndReadTestSuite) validateDataset( - dataset []datasetFile, - ep *cinodefs.Entrypoint, - datastoreDir string, -) { - t := s.T() - - ds, err := datastore.InFileSystem(datastoreDir) - require.NoError(t, err) - - s.validateDatasetInDatastore(t, dataset, ep, ds) -} - -func (s *CompileAndReadTestSuite) validateDatasetInDatastore( - t *testing.T, - dataset []datasetFile, - ep *cinodefs.Entrypoint, - ds datastore.DS, -) { - fs, err := cinodefs.New( - t.Context(), - blenc.FromDatastore(ds), - cinodefs.RootEntrypoint(ep), - cinodefs.MaxLinkRedirects(10), - ) - require.NoError(t, err) - - testServer := httptest.NewServer(&httphandler.Handler{ - FS: fs, - IndexFile: "index.html", - Log: slog.Default(), - }) - defer testServer.Close() - - for _, td := range dataset { - t.Run(td.fName, func(t *testing.T) { - res, err := http.Get(testServer.URL + td.fName) - require.NoError(t, err) - defer res.Body.Close() - - data, err := io.ReadAll(res.Body) - require.NoError(t, err) - require.Equal(t, []byte(td.contents), data) - - res, err = http.Post(testServer.URL+td.fName, "plain/text", bytes.NewReader([]byte("test"))) - require.NoError(t, err) - defer res.Body.Close() - - require.Equal(t, http.StatusMethodNotAllowed, res.StatusCode) - - res, err = http.Get(testServer.URL + td.fName + ".notfound") - require.NoError(t, err) - defer res.Body.Close() - - require.Equal(t, http.StatusNotFound, res.StatusCode) - }) - } - - t.Run("Default to index.html", func(t *testing.T) { - res, err := http.Get(testServer.URL + "/") - require.NoError(t, err) - defer res.Body.Close() - - data, err := io.ReadAll(res.Body) - require.NoError(t, err) - - require.Equal(t, []byte("Index"), data) - }) -} - -func (s *CompileAndReadTestSuite) TestCompileAndRead() { - t := s.T() - - datastoreAddress := t.TempDir() - - // Create and test initial dataset - wi, ep := s.uploadDatasetToDatastore(t, s.initialTestDataset, datastoreAddress) - s.validateDataset(s.initialTestDataset, ep, datastoreAddress) - - t.Run("Re-upload same dataset", func(t *testing.T) { - s.uploadDatasetToDatastore(t, s.initialTestDataset, datastoreAddress, - "--writer-info", wi.String(), - ) - s.validateDataset(s.initialTestDataset, ep, datastoreAddress) - }) - - t.Run("Upload modified dataset but for different root link", func(t *testing.T) { - _, updatedEP := s.uploadDatasetToDatastore(t, s.updatedTestDataset, datastoreAddress) - s.validateDataset(s.updatedTestDataset, updatedEP, datastoreAddress) - require.NotEqual(t, ep, updatedEP) - - // After restoring the original entrypoint dataset should be back to the initial one - s.validateDataset(s.initialTestDataset, ep, datastoreAddress) - }) - - t.Run("Update the original entrypoint with the new dataset", func(t *testing.T) { - _, epOrigWriterInfo := s.uploadDatasetToDatastore(t, s.updatedTestDataset, datastoreAddress, - "--writer-info", wi.String(), - ) - s.validateDataset(s.updatedTestDataset, epOrigWriterInfo, datastoreAddress) - - // Entrypoint must stay the same - require.EqualValues(t, ep, epOrigWriterInfo) - }) - - t.Run("Upload data with static entrypoint", func(t *testing.T) { - wiStatic, epStatic := s.uploadDatasetToDatastore(t, s.initialTestDataset, datastoreAddress, - "--static", - ) - s.validateDataset(s.initialTestDataset, epStatic, datastoreAddress) - require.Nil(t, wiStatic) - }) - - t.Run("Read writer info from file", func(t *testing.T) { - wiFile := filepath.Join(t.TempDir(), "epfile") - require.NoError(t, os.WriteFile(wiFile, []byte(wi.String()), 0o777)) - - _, ep := s.uploadDatasetToDatastore(t, s.initialTestDataset, datastoreAddress, - "--writer-info-file", wiFile, - ) - s.validateDataset(s.initialTestDataset, ep, datastoreAddress) - }) - - t.Run("Generate index file", func(t *testing.T) { - dir := t.TempDir() - _, ep := s.uploadDatasetToDatastore(t, s.initialTestDataset, dir, - "--generate-index-files", - "--index-file", "homefile.txt", - ) - s.validateDataset(s.initialTestDataset, ep, dir) - - ds, err := datastore.InFileSystem(dir) - require.NoError(t, err) - - fs, err := cinodefs.New( - t.Context(), - blenc.FromDatastore(ds), - cinodefs.RootEntrypoint(ep), - ) - require.NoError(t, err) - - rc, err := fs.OpenEntryData(t.Context(), []string{"subpath", "homefile.txt"}) - require.NoError(t, err) - defer rc.Close() - - data, err := io.ReadAll(rc) - require.NoError(t, err) - - dataStr := string(data) - require.Contains(t, dataStr, "file.txt") - require.Contains(t, dataStr, "file2.txt") - }) -} - -func (s *CompileAndReadTestSuite) TestBackwardsCompatibilityForRawFileSystem() { - t := s.T() - - dir := t.TempDir() - - _, ep := s.uploadDatasetToDatastore(t, s.initialTestDataset, dir, "--raw-filesystem") - - dsRaw, err := datastore.InRawFileSystem(dir) - require.NoError(t, err) - s.validateDatasetInDatastore(t, s.initialTestDataset, ep, dsRaw) -} - -func testExecCommand(cmd *cobra.Command, args []string) (output, stderr []byte, err error) { - outputBuff := bytes.NewBuffer(nil) - stderrBuff := bytes.NewBuffer(nil) - cmd.SetOut(outputBuff) - cmd.SetErr(stderrBuff) - cmd.SetArgs(args) - err = cmd.Execute() - return outputBuff.Bytes(), stderrBuff.Bytes(), err -} - -func testExec(args []string) (output, stderr []byte, err error) { - return testExecCommand(RootCmd(), args) -} - -func TestHelpCalls(t *testing.T) { - for _, d := range []struct { - name string - args []string - }{ - {"no args", []string{}}, - {"not enough compile args", []string{"compile"}}, - } { - t.Run(d.name, func(t *testing.T) { - cmd := RootCmd() - helpCalled := false - cmd.SetHelpFunc(func(c *cobra.Command, s []string) { helpCalled = true }) - cmd.SetArgs(d.args) - err := cmd.Execute() - require.NoError(t, err) - require.True(t, helpCalled) - }) - } -} - -func TestInvalidOptions(t *testing.T) { - tempDir := t.TempDir() - emptyFile := filepath.Join(tempDir, "empty") - - err := os.WriteFile(emptyFile, []byte{}, 0o777) - require.NoError(t, err) - - for _, d := range []struct { - name string - errorContains string - args []string - }{ - { - name: "invalid root writer info", - args: []string{ - "compile", - "--source", t.TempDir(), - "--destination", t.TempDir(), - "--writer-info", "not-a-valid-writer-info", - }, - errorContains: "Couldn't parse writer info:", - }, - { - name: "invalid root writer info file", - args: []string{ - "compile", - "--source", t.TempDir(), - "--destination", t.TempDir(), - "--writer-info-file", "/invalid/file/name/with/writer/info", - }, - errorContains: "no such file or directory", - }, - { - name: "empty root writer info file", - args: []string{ - "compile", - "--source", t.TempDir(), - "--destination", t.TempDir(), - "--writer-info-file", emptyFile, - }, - errorContains: "is empty", - }, - } { - t.Run(d.name, func(t *testing.T) { - output, _, err := testExec(d.args) - require.ErrorContains(t, err, d.errorContains) - - parsedOutput := testOutputParser{} - err = json.Unmarshal(output, &parsedOutput) - require.NoError(t, err) - require.Equal(t, "ERROR", parsedOutput.Result) - require.Contains(t, parsedOutput.Msg, d.errorContains) - }) - } -} diff --git a/pkg/datastore/datastore.go b/pkg/datastore/datastore.go index 5767ff3..d172788 100644 --- a/pkg/datastore/datastore.go +++ b/pkg/datastore/datastore.go @@ -1,5 +1,5 @@ /* -Copyright © 2023 Bartłomiej Święcki (byo) +Copyright © 2025 Bartłomiej Święcki (byo) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,8 +20,8 @@ import ( "context" "io" - "github.com/cinode/go/pkg/blobtypes" - "github.com/cinode/go/pkg/common" + "github.com/cinode/go-datastore/pkg/blobtypes" + "github.com/cinode/go-datastore/pkg/common" ) type datastore struct { @@ -75,29 +75,3 @@ func (ds *datastore) Delete(ctx context.Context, name *common.BlobName) error { func InMemory() DS { return &datastore{s: newStorageMemory()} } - -// InFileSystem constructs a datastore using filesystem as a storage layer. -// -// Contrary to InRawFileSystem, this datastore is optimized for large datastores -// and concurrent use. -func InFileSystem(path string) (DS, error) { - s, err := newStorageFilesystem(path) - if err != nil { - return nil, err - } - return &datastore{s: s}, nil -} - -// InRawFilesystem is a simplified storage that uses filesystem as a storage layer. -// -// Datastore files are stored directly under base58-encoded blob names. -// This datastore should not be used for highly concurrent or highly modified -// cases. The main purpose is to dump files to a disk in a form that can -// be lated used in a classic web server and used as a static web source. -func InRawFileSystem(path string) (DS, error) { - s, err := newStorageRawFilesystem(path) - if err != nil { - return nil, err - } - return &datastore{s: s}, nil -} diff --git a/pkg/datastore/datastore_dynamic_link.go b/pkg/datastore/datastore_dynamic_link.go index 7ed3bbb..4cd6320 100644 --- a/pkg/datastore/datastore_dynamic_link.go +++ b/pkg/datastore/datastore_dynamic_link.go @@ -1,5 +1,5 @@ /* -Copyright © 2023 Bartłomiej Święcki (byo) +Copyright © 2025 Bartłomiej Święcki (byo) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -21,8 +21,8 @@ import ( "errors" "io" - "github.com/cinode/go/pkg/common" - "github.com/cinode/go/pkg/internal/blobtypes/dynamiclink" + "github.com/cinode/go-datastore/pkg/common" + "github.com/cinode/go-datastore/pkg/internal/blobtypes/dynamiclink" ) func (ds *datastore) openDynamicLink(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) { diff --git a/pkg/datastore/datastore_static.go b/pkg/datastore/datastore_static.go index 05bb5bb..a7100d2 100644 --- a/pkg/datastore/datastore_static.go +++ b/pkg/datastore/datastore_static.go @@ -1,5 +1,5 @@ /* -Copyright © 2023 Bartłomiej Święcki (byo) +Copyright © 2025 Bartłomiej Święcki (byo) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -22,9 +22,9 @@ import ( "crypto/sha256" "io" - "github.com/cinode/go/pkg/blobtypes" - "github.com/cinode/go/pkg/common" - "github.com/cinode/go/pkg/internal/utilities/validatingreader" + "github.com/cinode/go-datastore/pkg/blobtypes" + "github.com/cinode/go-datastore/pkg/common" + "github.com/cinode/go-datastore/pkg/internal/utilities/validatingreader" ) func (ds *datastore) openStatic(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) { diff --git a/pkg/datastore/datastore_test.go b/pkg/datastore/datastore_test.go index a844d7f..3f38e54 100644 --- a/pkg/datastore/datastore_test.go +++ b/pkg/datastore/datastore_test.go @@ -21,12 +21,11 @@ import ( "context" "errors" "io" - "io/fs" "testing" - "github.com/cinode/go/pkg/blobtypes" - "github.com/cinode/go/pkg/common" - "github.com/cinode/go/pkg/datastore/testutils" + "github.com/cinode/go-datastore/pkg/blobtypes" + "github.com/cinode/go-datastore/pkg/common" + "github.com/cinode/go-datastore/pkg/datastore/testutils" "github.com/stretchr/testify/require" ) @@ -140,15 +139,3 @@ func TestDatastoreDetectCorruptedRead(t *testing.T) { err = r.Close() require.NoError(t, err) } - -func TestInvalidInFileSystemParameters(t *testing.T) { - ds, err := InFileSystem("/some:invalid;path?*") - require.IsType(t, &fs.PathError{}, err) - require.Nil(t, ds) -} - -func TestInvalidInRawFileSystemParameters(t *testing.T) { - ds, err := InRawFileSystem("/some:invalid;path?*") - require.IsType(t, &fs.PathError{}, err) - require.Nil(t, ds) -} diff --git a/pkg/datastore/factory.go b/pkg/datastore/factory.go deleted file mode 100644 index c920033..0000000 --- a/pkg/datastore/factory.go +++ /dev/null @@ -1,68 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package datastore - -import ( - "fmt" - "strings" -) - -const ( - filePrefix = "file://" - rawFilePrefix = "file-raw://" - webPrefixHTTP = "http://" - webPrefixHTTPS = "https://" - memoryPrefix = "memory://" -) - -var ( - ErrInvalidMemoryLocation = fmt.Errorf("memory datastore must not use any parameters, use only `%s`", memoryPrefix) -) - -// FromLocation creates new instance of the datastore from location string. -// -// The string may be of the following form: -// - file:// - create datastore using local filesystem's path (optimized) as the storage, -// see InFileSystem for more details -// - file-raw:// - create datastore using local filesystem's path (simplified) as the storage, -// see InRawFileSystem for more details -// - http://
or https://
- connects to datastore exposed through a http protocol, -// see FromWeb for more details -// - memory:// - creates a local in-process datastore without persistent storage -// - - equivalent to file:// -func FromLocation(location string) (DS, error) { - switch { - case strings.HasPrefix(location, filePrefix): - return InFileSystem(location[len(filePrefix):]) - - case strings.HasPrefix(location, rawFilePrefix): - return InRawFileSystem(location[len(rawFilePrefix):]) - - case strings.HasPrefix(location, webPrefixHTTP), - strings.HasPrefix(location, webPrefixHTTPS): - return FromWeb(location) - - case strings.HasPrefix(location, memoryPrefix): - if location != memoryPrefix { - return nil, ErrInvalidMemoryLocation - } - return InMemory(), nil - - default: - return InFileSystem(location) - } -} diff --git a/pkg/datastore/factory_test.go b/pkg/datastore/factory_test.go deleted file mode 100644 index 954719f..0000000 --- a/pkg/datastore/factory_test.go +++ /dev/null @@ -1,77 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package datastore - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestNewFromLocation(t *testing.T) { - for _, d := range []struct { - expectedMainType any - expectedStorageType any - location string - }{ - { - location: "file://" + t.TempDir(), - expectedMainType: &datastore{}, - expectedStorageType: &fileSystem{}, - }, - { - location: "file-raw://" + t.TempDir(), - expectedMainType: &datastore{}, - expectedStorageType: &rawFileSystem{}, - }, - { - location: "memory://", - expectedMainType: &datastore{}, - expectedStorageType: &memory{}, - }, - { - location: "http://some.domain.com", - expectedMainType: &webConnector{}, - expectedStorageType: nil, - }, - { - location: "https://some.domain.com", - expectedMainType: &webConnector{}, - expectedStorageType: nil, - }, - { - location: t.TempDir(), - expectedMainType: &datastore{}, - expectedStorageType: &fileSystem{}, - }, - } { - t.Run(d.location, func(t *testing.T) { - ds, err := FromLocation(d.location) - require.NoError(t, err) - require.IsType(t, d.expectedMainType, ds) - if d.expectedStorageType != nil { - require.IsType(t, d.expectedStorageType, ds.(*datastore).s) - } - }) - } - - t.Run("invalid memory location", func(t *testing.T) { - ds, err := FromLocation("memory://some-invalid-parameter") - require.ErrorIs(t, err, ErrInvalidMemoryLocation) - require.Nil(t, ds) - }) -} diff --git a/pkg/datastore/interface.go b/pkg/datastore/interface.go index 06a7e61..9e52faa 100644 --- a/pkg/datastore/interface.go +++ b/pkg/datastore/interface.go @@ -1,5 +1,5 @@ /* -Copyright © 2023 Bartłomiej Święcki (byo) +Copyright © 2025 Bartłomiej Święcki (byo) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import ( "errors" "io" - "github.com/cinode/go/pkg/common" + "github.com/cinode/go-datastore/pkg/common" ) var ( diff --git a/pkg/datastore/interface_test.go b/pkg/datastore/interface_test.go deleted file mode 100644 index da4d539..0000000 --- a/pkg/datastore/interface_test.go +++ /dev/null @@ -1,55 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package datastore - -import ( - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/suite" -) - -func TestDatastoreTestSuite(t *testing.T) { - t.Run("InMemory", func(t *testing.T) { - suite.Run(t, &TestSuite{ - CreateDS: func() (DS, error) { return InMemory(), nil }, - }) - }) - - t.Run("InFileSystem", func(t *testing.T) { - suite.Run(t, &TestSuite{ - CreateDS: func() (DS, error) { return InFileSystem(t.TempDir()) }, - }) - }) - - t.Run("InRawFileSystem", func(t *testing.T) { - suite.Run(t, &TestSuite{ - CreateDS: func() (DS, error) { return InRawFileSystem(t.TempDir()) }, - }) - }) - - t.Run("FromWeb", func(t *testing.T) { - suite.Run(t, &TestSuite{ - CreateDS: func() (DS, error) { - server := httptest.NewServer(WebInterface(InMemory())) - t.Cleanup(func() { server.Close() }) - - return FromWeb(server.URL + "/") - }, - }) - }) -} diff --git a/pkg/datastore/interface_testsuite.go b/pkg/datastore/interface_testsuite.go index 0b70e62..7790a0f 100644 --- a/pkg/datastore/interface_testsuite.go +++ b/pkg/datastore/interface_testsuite.go @@ -25,10 +25,10 @@ import ( "io" "sync" - "github.com/cinode/go/pkg/blobtypes" - "github.com/cinode/go/pkg/common" - "github.com/cinode/go/pkg/datastore/testutils" - "github.com/cinode/go/pkg/internal/blobtypes/dynamiclink" + "github.com/cinode/go-datastore/pkg/blobtypes" + "github.com/cinode/go-datastore/pkg/common" + "github.com/cinode/go-datastore/pkg/datastore/testutils" + "github.com/cinode/go-datastore/pkg/internal/blobtypes/dynamiclink" "github.com/stretchr/testify/suite" ) diff --git a/pkg/datastore/multisource/multi_source.go b/pkg/datastore/multisource/multi_source.go deleted file mode 100644 index 33c7af5..0000000 --- a/pkg/datastore/multisource/multi_source.go +++ /dev/null @@ -1,245 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package multisource - -import ( - "context" - "io" - "log/slog" - "sync" - "time" - - "github.com/cinode/go/pkg/blobtypes" - "github.com/cinode/go/pkg/common" - "github.com/cinode/go/pkg/datastore" -) - -const ( - defaultDynamicDataRefreshTime = time.Minute - defaultNotFoundRecheckTime = time.Minute -) - -type multiSourceDatastoreBlobState struct { - downloadingFinishedCChan chan struct{} - lastUpdateTime time.Time - downloading bool - notFound bool -} - -type multiSourceDatastore struct { - // Logger output - log *slog.Logger - - // Main datastore - main datastore.DS - - // Last update time for blobs, either for dynamic content or last result of not found for - // static ones - blobStates map[string]multiSourceDatastoreBlobState - - // Additional sources that will be queried whenever the main source - // does not contain the data or contains outdated content - additional []datastore.DS - - // Average time between dynamic content refreshes - dynamicDataRefreshTime time.Duration - - // Time between re-checking blob existence in additional datastores - notFoundRecheckTime time.Duration - - // Guard additional sources and update time map - m sync.Mutex -} - -func New(main datastore.DS, options ...Option) datastore.DS { - ds := &multiSourceDatastore{ - main: main, - blobStates: map[string]multiSourceDatastoreBlobState{}, - additional: nil, - dynamicDataRefreshTime: defaultDynamicDataRefreshTime, - notFoundRecheckTime: defaultNotFoundRecheckTime, - log: slog.Default(), - } - - for _, option := range options { - option(ds) - } - - return ds -} - -var _ datastore.DS = (*multiSourceDatastore)(nil) - -func (m *multiSourceDatastore) Kind() string { - return "MultiSource" -} - -func (m *multiSourceDatastore) Address() string { - return "multi-source://" -} - -func (m *multiSourceDatastore) Open(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) { - m.fetch(ctx, name) - return m.main.Open(ctx, name) -} - -func (m *multiSourceDatastore) Update(ctx context.Context, name *common.BlobName, r io.Reader) error { - return m.main.Update(ctx, name, r) -} - -func (m *multiSourceDatastore) Exists(ctx context.Context, name *common.BlobName) (bool, error) { - m.fetch(ctx, name) - return m.main.Exists(ctx, name) -} - -func (m *multiSourceDatastore) Delete(ctx context.Context, name *common.BlobName) error { - return m.main.Delete(ctx, name) -} - -func (m *multiSourceDatastore) fetch(ctx context.Context, name *common.BlobName) { - // TODO: - // if not found locally, go over all additional sources and check if exists, - // for dynamic content, perform merge operation if found in more than one, - // initially in the background - // also if dynamic content is queried and it was not updated in enough time, - // do an update process - - for { - waitChan, startDownload := m.determineBlobState(name) - - switch { - case startDownload: - // Start the download routine - m.startBlobDownload(ctx, name, waitChan) - - case waitChan == nil: - // No ned to do anything, blob ready - return - - default: - // Wait for the current in-progress download - <-waitChan - } - } -} - -func (m *multiSourceDatastore) determineBlobState(name *common.BlobName) ( - finishedDownloadingChannel chan struct{}, - needsDownload bool, -) { - m.m.Lock() - defer m.m.Unlock() - - state, found := m.blobStates[name.String()] - - switch { - case !found: - // Seen for the first time - needsDownload = true - - case state.downloading: - // Blob currently being downloaded - return state.downloadingFinishedCChan, false - - case m.needsDownload(state, name, time.Now()): - // We should update the blob - needsDownload = true - } - - if !needsDownload { - return nil, false - } - - // State not found, request new download - ch := make(chan struct{}) - m.blobStates[name.String()] = multiSourceDatastoreBlobState{ - downloading: true, - downloadingFinishedCChan: ch, - } - - return ch, true -} - -// needsDownload checks if the blob needs to be downloaded based on the state and the current time. -func (m *multiSourceDatastore) needsDownload( - state multiSourceDatastoreBlobState, - name *common.BlobName, - now time.Time, -) bool { - switch { - case state.notFound: - return now.After(state.lastUpdateTime.Add(m.notFoundRecheckTime)) - - case name.Type() == blobtypes.Static: - return false - - default: - return now.After(state.lastUpdateTime.Add(m.dynamicDataRefreshTime)) - } -} - -func (m *multiSourceDatastore) startBlobDownload( - ctx context.Context, - name *common.BlobName, - waitChan chan struct{}, -) { - m.log.Info("Starting download", - "blob", name.String(), - ) - - wasFound := false - - for i, ds := range m.additional { - r, err := ds.Open(ctx, name) - if err != nil { - m.log.Debug("Failed to fetch blob from additional datastore", - "blob", name.String(), - "datastore", ds.Address(), - "err", err, - ) - continue - } - - m.log.Info("Blob found in additional datastore", - "blob", name.String(), - "datastore-num", i+1, - ) - err = m.main.Update(ctx, name, r) - r.Close() - if err != nil { - m.log.Error("Failed to store blob in local datastore", - slog.Any("err", err), - slog.String("blob", name.String()), - ) - } - wasFound = true - } - if !wasFound { - m.log.Warn("Did not find blob in any datastore", - "blob", name.String(), - ) - } - defer close(waitChan) - - m.m.Lock() - defer m.m.Unlock() - - m.blobStates[name.String()] = multiSourceDatastoreBlobState{ - lastUpdateTime: time.Now(), - notFound: !wasFound, - } -} diff --git a/pkg/datastore/multisource/multi_source_options.go b/pkg/datastore/multisource/multi_source_options.go deleted file mode 100644 index b6d5235..0000000 --- a/pkg/datastore/multisource/multi_source_options.go +++ /dev/null @@ -1,50 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package multisource - -import ( - "log/slog" - "time" - - "github.com/cinode/go/pkg/datastore" -) - -type Option func(*multiSourceDatastore) - -func WithDynamicDataRefreshTime(refreshTime time.Duration) Option { - return func(m *multiSourceDatastore) { - m.dynamicDataRefreshTime = refreshTime - } -} - -func WithLogger(log *slog.Logger) Option { - return func(m *multiSourceDatastore) { - m.log = log - } -} - -func WithAdditionalDatastores(additional ...datastore.DS) Option { - return func(m *multiSourceDatastore) { - m.additional = append(m.additional, additional...) - } -} - -func WithNotFoundRecheckTime(notFoundRecheckTime time.Duration) Option { - return func(m *multiSourceDatastore) { - m.notFoundRecheckTime = notFoundRecheckTime - } -} diff --git a/pkg/datastore/multisource/multi_source_options_test.go b/pkg/datastore/multisource/multi_source_options_test.go deleted file mode 100644 index d7de6a6..0000000 --- a/pkg/datastore/multisource/multi_source_options_test.go +++ /dev/null @@ -1,50 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package multisource - -import ( - "log/slog" - "os" - "testing" - "time" - - "github.com/cinode/go/pkg/datastore" - "github.com/stretchr/testify/require" -) - -func TestOptions(t *testing.T) { - log := slog.New(slog.NewTextHandler(os.Stdout, nil)) - - additionalDatastores := []datastore.DS{ - datastore.InMemory(), - datastore.InMemory(), - datastore.InMemory(), - } - - ds := New( - datastore.InMemory(), - WithDynamicDataRefreshTime(7*time.Second), - WithNotFoundRecheckTime(13*time.Second), - WithLogger(log), - WithAdditionalDatastores(additionalDatastores...), - ).(*multiSourceDatastore) - - require.Equal(t, 7*time.Second, ds.dynamicDataRefreshTime) - require.Equal(t, 13*time.Second, ds.notFoundRecheckTime) - require.Equal(t, log, ds.log) - require.Equal(t, additionalDatastores, ds.additional) -} diff --git a/pkg/datastore/multisource/multi_source_test.go b/pkg/datastore/multisource/multi_source_test.go deleted file mode 100644 index 8c90af2..0000000 --- a/pkg/datastore/multisource/multi_source_test.go +++ /dev/null @@ -1,289 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package multisource - -import ( - "bytes" - "context" - "crypto/rand" - "crypto/sha256" - "io" - "sync" - "sync/atomic" - "testing" - "time" - - "github.com/cinode/go/pkg/blobtypes" - "github.com/cinode/go/pkg/common" - "github.com/cinode/go/pkg/datastore" - "github.com/cinode/go/pkg/internal/blobtypes/dynamiclink" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" -) - -func TestInterface(t *testing.T) { - suite.Run(t, &datastore.TestSuite{ - CreateDS: func() (datastore.DS, error) { - return New(datastore.InMemory()), nil - }, - }) -} - -type MultiSourceDatastoreTestSuite struct { - suite.Suite -} - -func TestMultiSourceDatastore(t *testing.T) { - suite.Run(t, &MultiSourceDatastoreTestSuite{}) -} - -func (s *MultiSourceDatastoreTestSuite) addStaticBlob(ds datastore.DS, c string) *common.BlobName { - t := s.T() - - hash := sha256.Sum256([]byte(c)) - name, err := common.BlobNameFromHashAndType(hash[:], blobtypes.Static) - require.NoError(t, err) - err = ds.Update(t.Context(), name, bytes.NewReader([]byte(c))) - require.NoError(t, err) - return name -} - -func (s *MultiSourceDatastoreTestSuite) fetchBlob(ds datastore.DS, n *common.BlobName) string { - t := s.T() - - rc, err := ds.Open(t.Context(), n) - require.NoError(t, err) - - data, err := io.ReadAll(rc) - require.NoError(t, err) - - err = rc.Close() - require.NoError(t, err) - - return string(data) -} - -func (s *MultiSourceDatastoreTestSuite) ensureNotFound(ds datastore.DS, n *common.BlobName) { - t := s.T() - - _, err := ds.Open(t.Context(), n) - require.ErrorIs(t, err, datastore.ErrNotFound) -} - -func (s *MultiSourceDatastoreTestSuite) TestStaticBlobPropagation() { - t := s.T() - - main := datastore.InMemory() - add1 := datastore.InMemory() - add2 := datastore.InMemory() - - ds := New( - main, - WithDynamicDataRefreshTime(time.Hour), - WithAdditionalDatastores(add1, add2), - ).(*multiSourceDatastore) - - bn1 := s.addStaticBlob(add1, "Hello world 1") - bn2 := s.addStaticBlob(add2, "Hello world 2") - - require.EqualValues(t, "Hello world 1", s.fetchBlob(ds, bn1)) - require.EqualValues(t, "Hello world 2", s.fetchBlob(ds, bn2)) - - // Blobs should already be in the cache - ds.additional = []datastore.DS{} - - require.EqualValues(t, "Hello world 1", s.fetchBlob(ds, bn1)) - require.EqualValues(t, "Hello world 2", s.fetchBlob(ds, bn2)) -} - -func (s *MultiSourceDatastoreTestSuite) TestNotFoundRecheck() { - t := s.T() - - main := datastore.InMemory() - add := datastore.InMemory() - - bn := s.addStaticBlob(datastore.InMemory(), "Hello world") - - ds := New( - main, - WithDynamicDataRefreshTime(time.Hour), - WithNotFoundRecheckTime(time.Millisecond*10), - WithAdditionalDatastores(add), - ).(*multiSourceDatastore) - - // Try to fetch the blob while it is not found in the additional datastore, - s.ensureNotFound(ds, bn) - - // Add the blob to the additional datastore - s.addStaticBlob(add, "Hello world") - - // The not found state should be cached for a while - for i := 0; i < 10; i++ { - // Result still cached - s.ensureNotFound(ds, bn) - } - - time.Sleep(time.Millisecond * 20) - - // Should refresh by now - require.EqualValues(t, "Hello world", s.fetchBlob(ds, bn)) -} - -func (s *MultiSourceDatastoreTestSuite) TestDynamicLinkRefresh() { - t := s.T() - - main := datastore.InMemory() - add := datastore.InMemory() - - ds := New( - main, - WithDynamicDataRefreshTime(time.Millisecond*100), - WithNotFoundRecheckTime(time.Hour), - WithAdditionalDatastores(add), - ).(*multiSourceDatastore) - - // Create a new dynamic link - dlp, err := dynamiclink.Create(rand.Reader) - require.NoError(t, err) - - bn := dlp.BlobName() - - // Upload the data to the additional datastore - key := s.updateLink(t, dlp, 1, add, "Hello world") - - // The data should be available in the main datastore - require.EqualValues(t, "Hello world", s.fetchLink(ds, bn, key)) - - // Update the data in the additional datastore - s.updateLink(t, dlp, 2, add, "Hello world 2") - - // Old link data should be cached - for range 10 { - require.EqualValues(t, "Hello world", s.fetchLink(ds, bn, key)) - } - - time.Sleep(time.Millisecond * 200) - - // The updated data should be available in the main datastore by now - require.EqualValues(t, "Hello world 2", s.fetchLink(ds, bn, key)) -} - -func (s *MultiSourceDatastoreTestSuite) updateLink( - t *testing.T, - dlp *dynamiclink.Publisher, - version uint64, - ds datastore.DS, - content string, -) *common.BlobKey { - pr, key, err := dlp.UpdateLinkData(bytes.NewReader([]byte(content)), version) - require.NoError(t, err) - - err = ds.Update(t.Context(), dlp.BlobName(), pr.GetPublicDataReader()) - require.NoError(t, err) - - return key -} - -func (s *MultiSourceDatastoreTestSuite) fetchLink( - ds datastore.DS, - bn *common.BlobName, - key *common.BlobKey, -) string { - t := s.T() - - rdr, err := ds.Open(t.Context(), bn) - require.NoError(t, err) - - defer rdr.Close() - - pr, err := dynamiclink.FromPublicData(bn, rdr) - require.NoError(t, err) - - lrdr, err := pr.GetLinkDataReader(key) - require.NoError(t, err) - - data, err := io.ReadAll(lrdr) - require.NoError(t, err) - - return string(data) -} - -type testSyncReaderDS struct { - datastore.DS - waitChan chan struct{} - openCount atomic.Int32 -} - -func (s *testSyncReaderDS) Open(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) { - s.openCount.Add(1) - <-s.waitChan - return s.DS.Open(ctx, name) -} - -func (s *MultiSourceDatastoreTestSuite) TestParallelDownloads() { - t := s.T() - - main := datastore.InMemory() - add := datastore.InMemory() - addSync := &testSyncReaderDS{DS: add, waitChan: make(chan struct{})} - - ds := New( - main, - WithAdditionalDatastores(addSync), - WithDynamicDataRefreshTime(time.Hour), - ).(*multiSourceDatastore) - bn := s.addStaticBlob(add, "Hello world") - - startWg := sync.WaitGroup{} - finishWg := sync.WaitGroup{} - goroutinesDone := atomic.Int32{} - - for range 5 { - startWg.Add(1) - finishWg.Add(1) - - go func() { - defer finishWg.Done() - defer goroutinesDone.Add(1) - - startWg.Done() - rc, err := ds.Open(t.Context(), bn) - require.NoError(t, err) - err = rc.Close() - require.NoError(t, err) - }() - } - - startWg.Wait() - - require.Eventually(t, - func() bool { return addSync.openCount.Load() == 1 }, - time.Second, time.Millisecond, - "must start downloading the blob from additional datastore", - ) - - require.Zero(t, goroutinesDone.Load()) - - close(addSync.waitChan) - - finishWg.Wait() - - require.EqualValues(t, - 1, addSync.openCount.Load(), - "download should be started once for all goroutinesS", - ) -} diff --git a/pkg/datastore/storage.go b/pkg/datastore/storage.go index d9d3bfa..044bb7b 100644 --- a/pkg/datastore/storage.go +++ b/pkg/datastore/storage.go @@ -1,5 +1,5 @@ /* -Copyright © 2023 Bartłomiej Święcki (byo) +Copyright © 2025 Bartłomiej Święcki (byo) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ import ( "context" "io" - "github.com/cinode/go/pkg/common" + "github.com/cinode/go-datastore/pkg/common" ) type WriteCloseCanceller interface { diff --git a/pkg/datastore/storage_filesystem.go b/pkg/datastore/storage_filesystem.go deleted file mode 100644 index accf61f..0000000 --- a/pkg/datastore/storage_filesystem.go +++ /dev/null @@ -1,168 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package datastore - -import ( - "context" - "io" - "os" - "path/filepath" - - "github.com/cinode/go/pkg/common" -) - -const ( - fsSuffixCurrent = ".c" - fsSuffixUpload = ".u" -) - -type fileSystem struct { - path string -} - -var _ storage = (*fileSystem)(nil) - -func newStorageFilesystem(path string) (*fileSystem, error) { - err := os.MkdirAll(path, 0o755) - if err != nil { - return nil, err - } - return &fileSystem{path: path}, nil -} - -func (fs *fileSystem) kind() string { - return "FileSystem" -} - -func (fs *fileSystem) address() string { - return filePrefix + fs.path -} - -func (fs *fileSystem) openReadStream(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) { - rc, err := os.Open(fs.getFileName(name, fsSuffixCurrent)) - if os.IsNotExist(err) { - return nil, ErrNotFound - } - return rc, err -} - -func (fs *fileSystem) createTemporaryWriteStream(name *common.BlobName) (*os.File, error) { - tempName := fs.getFileName(name, fsSuffixUpload) - - // Ensure dir exists - err := os.MkdirAll(filepath.Dir(tempName), 0o755) - if err != nil { - return nil, err - } - - // Open file in exclusive mode, allow only a single upload at a time - fh, err := os.OpenFile( - tempName, - os.O_CREATE|os.O_EXCL|os.O_APPEND|os.O_WRONLY, - 0o644, - ) - if os.IsExist(err) { - return nil, ErrUploadInProgress - } - - if err != nil { - // Some OS error - return nil, err - } - - // Got temporary file handle - return fh, nil -} - -type fileSystemWriteCloser struct { - fs *os.File - destName string -} - -func (w *fileSystemWriteCloser) Write(b []byte) (int, error) { - return w.fs.Write(b) -} - -func (w *fileSystemWriteCloser) Cancel() { - if w.fs != nil { - w.fs.Close() - os.Remove(w.fs.Name()) - } -} - -func (w *fileSystemWriteCloser) Close() error { - err := w.fs.Close() - if err != nil { - // This is not covered by tests, I have no idea how to trigger that - os.Remove(w.fs.Name()) - return err - } - - err = os.Rename(w.fs.Name(), w.destName) - if err != nil { - return err - } - - w.fs = nil - return nil -} - -func (fs *fileSystem) openWriteStream(ctx context.Context, name *common.BlobName) (WriteCloseCanceller, error) { - fl, err := fs.createTemporaryWriteStream(name) - if err != nil { - return nil, err - } - - return &fileSystemWriteCloser{ - fs: fl, - destName: fs.getFileName(name, fsSuffixCurrent), - }, nil -} - -func (fs *fileSystem) exists(ctx context.Context, name *common.BlobName) (bool, error) { - _, err := os.Stat(fs.getFileName(name, fsSuffixCurrent)) - if os.IsNotExist(err) { - return false, nil - } - if err != nil { - return false, err - } - return true, nil -} - -func (fs *fileSystem) delete(ctx context.Context, name *common.BlobName) error { - err := os.Remove(fs.getFileName(name, fsSuffixCurrent)) - if os.IsNotExist(err) { - return ErrNotFound - } - return err -} - -func (fs *fileSystem) getFileName(name *common.BlobName, suffix string) string { - fNameParts := []string{fs.path} - - nameStr := name.String() - for i := 0; i < 3; i++ { - if len(nameStr) > 3 { - fNameParts = append(fNameParts, nameStr[:3]) - nameStr = nameStr[3:] - } - } - fNameParts = append(fNameParts, nameStr+suffix) - - return filepath.Join(fNameParts...) -} diff --git a/pkg/datastore/storage_filesystem_test.go b/pkg/datastore/storage_filesystem_test.go deleted file mode 100644 index 888392d..0000000 --- a/pkg/datastore/storage_filesystem_test.go +++ /dev/null @@ -1,129 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package datastore - -import ( - "os" - "path/filepath" - "testing" - - "github.com/cinode/go/pkg/datastore/testutils" - "github.com/stretchr/testify/require" -) - -func temporaryFS(t *testing.T) *fileSystem { - ds, err := newStorageFilesystem(t.TempDir()) - require.NoError(t, err) - return ds -} - -func touchFile(t *testing.T, fName string) { - err := os.MkdirAll(filepath.Dir(fName), 0o777) - require.NoError(t, err) - fl, err := os.Create(fName) - require.NoError(t, err) - fl.Close() -} - -func protect(t *testing.T, fName string) func() { - fi, err := os.Stat(fName) - require.NoError(t, err) - os.Chmod(fName, 0) - mode := fi.Mode() - return func() { os.Chmod(fName, mode) } -} - -func TestFilesystemKind(t *testing.T) { - fs := temporaryFS(t) - require.Equal(t, "FileSystem", fs.kind()) -} - -func TestFilesystemSaveFailureDir(t *testing.T) { - fs := temporaryFS(t) - - // Create file at directory location preventing - // creation of a directory - fName := fs.getFileName(testutils.EmptyBlobNameStatic, fsSuffixCurrent) - fName = filepath.Dir(fName) - touchFile(t, fName) - - w, err := fs.openWriteStream(t.Context(), testutils.EmptyBlobNameStatic) - require.IsType(t, &os.PathError{}, err) - require.Nil(t, w) -} - -func TestFilesystemSaveFailureTempFile(t *testing.T) { - fs := temporaryFS(t) - - // Create blob's directory as unmodifiable - fName := fs.getFileName(testutils.EmptyBlobNameStatic, fsSuffixCurrent) - dirPath := filepath.Dir(fName) - err := os.MkdirAll(dirPath, 0o777) - require.NoError(t, err) - defer protect(t, dirPath)() - - w, err := fs.openWriteStream(t.Context(), testutils.EmptyBlobNameStatic) - require.IsType(t, &os.PathError{}, err) - require.Nil(t, w) -} - -func TestFilesystemRenameFailure(t *testing.T) { - fs := temporaryFS(t) - - // Create directory where blob should be - fName := fs.getFileName(testutils.EmptyBlobNameStatic, fsSuffixCurrent) - os.MkdirAll(fName, 0o777) - - w, err := fs.openWriteStream(t.Context(), testutils.EmptyBlobNameStatic) - require.NoError(t, err) - - err = w.Close() - require.IsType(t, &os.LinkError{}, err) -} - -func TestFilesystemDeleteFailure(t *testing.T) { - fs := temporaryFS(t) - - // Create directory where blob should be with some file inside - fName := fs.getFileName(testutils.EmptyBlobNameStatic, fsSuffixCurrent) - os.MkdirAll(fName, 0o777) - touchFile(t, fName+"/keep.me") - - err := fs.delete(t.Context(), testutils.EmptyBlobNameStatic) - require.IsType(t, &os.PathError{}, err) -} - -func TestFilesystemDeleteNotFound(t *testing.T) { - fs := temporaryFS(t) - - err := fs.delete(t.Context(), testutils.EmptyBlobNameStatic) - require.ErrorIs(t, err, ErrNotFound) -} - -func TestFilesystemExistsFailure(t *testing.T) { - fs := temporaryFS(t) - - // Create blob's directory as unmodifiable - fName := fs.getFileName(testutils.EmptyBlobNameStatic, fsSuffixCurrent) - dirPath := filepath.Dir(fName) - err := os.MkdirAll(dirPath, 0o777) - require.NoError(t, err) - defer protect(t, dirPath)() - - _, err = fs.exists(t.Context(), testutils.EmptyBlobNameStatic) - require.IsType(t, &os.PathError{}, err) -} diff --git a/pkg/datastore/storage_memory.go b/pkg/datastore/storage_memory.go index 8d0e8a1..bbf2af7 100644 --- a/pkg/datastore/storage_memory.go +++ b/pkg/datastore/storage_memory.go @@ -22,7 +22,11 @@ import ( "io" "sync" - "github.com/cinode/go/pkg/common" + "github.com/cinode/go-datastore/pkg/common" +) + +const ( + memoryPrefix = "memory://" ) type memory struct { diff --git a/pkg/datastore/storage_memory_test.go b/pkg/datastore/storage_memory_test.go index 7830a1d..fce93ca 100644 --- a/pkg/datastore/storage_memory_test.go +++ b/pkg/datastore/storage_memory_test.go @@ -20,6 +20,7 @@ import ( "testing" "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" ) func temporaryMemory(_ *testing.T) *memory { @@ -30,3 +31,9 @@ func TestMemoryStorageKind(t *testing.T) { m := temporaryMemory(t) require.Equal(t, "Memory", m.kind()) } + +func TestInMemoryDatastoreTestSuite(t *testing.T) { + suite.Run(t, &TestSuite{ + CreateDS: func() (DS, error) { return InMemory(), nil }, + }) +} diff --git a/pkg/datastore/storage_raw_filesystem.go b/pkg/datastore/storage_raw_filesystem.go deleted file mode 100644 index ab2755c..0000000 --- a/pkg/datastore/storage_raw_filesystem.go +++ /dev/null @@ -1,117 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package datastore - -import ( - "context" - "fmt" - "io" - "os" - "path/filepath" - "sync/atomic" - - "github.com/cinode/go/pkg/common" -) - -type rawFileSystem struct { - path string - tempFileNum uint64 -} - -var _ storage = (*rawFileSystem)(nil) - -func newStorageRawFilesystem(path string) (*rawFileSystem, error) { - err := os.MkdirAll(path, 0o755) - if err != nil { - return nil, err - } - return &rawFileSystem{path: path}, nil -} - -func (fs *rawFileSystem) kind() string { - return "RawFileSystem" -} - -func (fs *rawFileSystem) address() string { - return rawFilePrefix + fs.path -} - -func (fs *rawFileSystem) openReadStream(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) { - rc, err := os.Open(filepath.Join(fs.path, name.String())) - if os.IsNotExist(err) { - return nil, ErrNotFound - } - return rc, err -} - -type rawFilesystemWriter struct { - file *os.File - destFileName string -} - -func (w *rawFilesystemWriter) Write(b []byte) (int, error) { - return w.file.Write(b) -} - -func (w *rawFilesystemWriter) Close() error { - err := w.file.Close() - if err != nil { - return err - } - - return os.Rename(w.file.Name(), w.destFileName) -} - -func (w *rawFilesystemWriter) Cancel() { - w.file.Close() - os.Remove(w.file.Name()) -} - -func (fs *rawFileSystem) openWriteStream(ctx context.Context, name *common.BlobName) (WriteCloseCanceller, error) { - tempNum := atomic.AddUint64(&fs.tempFileNum, 1) - - tempFileName := filepath.Join(fs.path, fmt.Sprintf("tempfile_%d", tempNum)) - - fl, err := os.Create(tempFileName) - if err != nil { - return nil, err - } - - return &rawFilesystemWriter{ - file: fl, - destFileName: filepath.Join(fs.path, name.String()), - }, nil -} - -func (fs *rawFileSystem) exists(ctx context.Context, name *common.BlobName) (bool, error) { - _, err := os.Stat(filepath.Join(fs.path, name.String())) - if os.IsNotExist(err) { - return false, nil - } - if err != nil { - return false, err - } - return true, nil -} - -func (fs *rawFileSystem) delete(ctx context.Context, name *common.BlobName) error { - err := os.Remove(filepath.Join(fs.path, name.String())) - if os.IsNotExist(err) { - return ErrNotFound - } - return err -} diff --git a/pkg/datastore/storage_test.go b/pkg/datastore/storage_test.go index 3ed399e..0b4cb0d 100644 --- a/pkg/datastore/storage_test.go +++ b/pkg/datastore/storage_test.go @@ -22,15 +22,15 @@ import ( "io" "testing" - "github.com/cinode/go/pkg/blobtypes" - "github.com/cinode/go/pkg/common" - "github.com/cinode/go/pkg/datastore/testutils" + "github.com/cinode/go-datastore/pkg/blobtypes" + "github.com/cinode/go-datastore/pkg/common" + "github.com/cinode/go-datastore/pkg/datastore/testutils" "github.com/stretchr/testify/require" ) func allTestStorages(t *testing.T) []storage { return []storage{ - temporaryFS(t), + // temporaryFS(t), // TODO: Extract to generic test suite? temporaryMemory(t), } } diff --git a/pkg/datastore/testutils/generate/main.go b/pkg/datastore/testutils/generate/main.go index 9d80a46..2c46e90 100644 --- a/pkg/datastore/testutils/generate/main.go +++ b/pkg/datastore/testutils/generate/main.go @@ -26,10 +26,10 @@ import ( "io" "os" - "github.com/cinode/go/pkg/blobtypes" - "github.com/cinode/go/pkg/common" - "github.com/cinode/go/pkg/internal/blobtypes/dynamiclink" - "github.com/cinode/go/pkg/utilities/golang" + "github.com/cinode/go-datastore/pkg/blobtypes" + "github.com/cinode/go-datastore/pkg/common" + "github.com/cinode/go-datastore/pkg/internal/blobtypes/dynamiclink" + "github.com/cinode/go-datastore/pkg/utilities/golang" "github.com/jbenet/go-base58" ) diff --git a/pkg/datastore/testutils/generate/tesblobs.go.tpl b/pkg/datastore/testutils/generate/tesblobs.go.tpl index 3beb888..e33f5fc 100644 --- a/pkg/datastore/testutils/generate/tesblobs.go.tpl +++ b/pkg/datastore/testutils/generate/tesblobs.go.tpl @@ -17,8 +17,8 @@ limitations under the License. package testutils import ( - "github.com/cinode/go/pkg/common" - "github.com/cinode/go/pkg/utilities/golang" + "github.com/cinode/go-datastore/pkg/common" + "github.com/cinode/go-datastore/pkg/utilities/golang" "github.com/jbenet/go-base58" ) diff --git a/pkg/datastore/testutils/testblobs.go b/pkg/datastore/testutils/testblobs.go index 230e8f4..b18751c 100644 --- a/pkg/datastore/testutils/testblobs.go +++ b/pkg/datastore/testutils/testblobs.go @@ -17,8 +17,8 @@ limitations under the License. package testutils import ( - "github.com/cinode/go/pkg/common" - "github.com/cinode/go/pkg/utilities/golang" + "github.com/cinode/go-datastore/pkg/common" + "github.com/cinode/go-datastore/pkg/utilities/golang" "github.com/jbenet/go-base58" ) diff --git a/pkg/datastore/testutils/testutils.go b/pkg/datastore/testutils/testutils.go index 2145a3d..4556c00 100644 --- a/pkg/datastore/testutils/testutils.go +++ b/pkg/datastore/testutils/testutils.go @@ -21,9 +21,9 @@ import ( "crypto/sha256" "io" - "github.com/cinode/go/pkg/blobtypes" - "github.com/cinode/go/pkg/common" - "github.com/cinode/go/pkg/utilities/golang" + "github.com/cinode/go-datastore/pkg/blobtypes" + "github.com/cinode/go-datastore/pkg/common" + "github.com/cinode/go-datastore/pkg/utilities/golang" ) var ( diff --git a/pkg/datastore/webcommon.go b/pkg/datastore/webcommon.go deleted file mode 100644 index 750533f..0000000 --- a/pkg/datastore/webcommon.go +++ /dev/null @@ -1,59 +0,0 @@ -/* -Copyright © 2023 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package datastore - -import ( - "errors" - - "github.com/cinode/go/pkg/blobtypes" - "github.com/cinode/go/pkg/common" -) - -type webErrResponse struct { - Code string `json:"code"` - Message string `json:"message"` -} - -var ( - webErrMap = map[string]error{ - "UNKNOWN_BLOB_TYPE": blobtypes.ErrUnknownBlobType, - "VALIDATION_FAILED": blobtypes.ErrValidationFailed, - "INVALID_BLOB_NAME": common.ErrInvalidBlobName, - "UPLOAD_IN_PROGRESS": ErrUploadInProgress, - "NO_FORM_FIELD": errNoData, - } -) - -type webNameResponse struct { - Name string `json:"name"` -} - -func webErrToCode(err error) string { - for code, errMatch := range webErrMap { - if errors.Is(err, errMatch) { - return code - } - } - return "" -} - -func webErrFromCode(code string) error { - if err, ok := webErrMap[code]; ok { - return err - } - return nil -} diff --git a/pkg/datastore/webconnector.go b/pkg/datastore/webconnector.go deleted file mode 100644 index b2b069e..0000000 --- a/pkg/datastore/webconnector.go +++ /dev/null @@ -1,249 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package datastore - -import ( - "bytes" - "context" - "crypto/sha256" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "net/url" - - "github.com/cinode/go/pkg/blobtypes" - "github.com/cinode/go/pkg/common" - "github.com/cinode/go/pkg/internal/blobtypes/dynamiclink" - "github.com/cinode/go/pkg/internal/utilities/validatingreader" -) - -var ( - ErrWebConnectionError = errors.New("connection error") -) - -type webConnector struct { - client *http.Client - customizeRequest func(*http.Request) error - baseURL string -} - -var _ DS = (*webConnector)(nil) - -type WebConnectorOption func(*webConnector) - -func WebOptionHTTPClient(client *http.Client) WebConnectorOption { - return func(wc *webConnector) { wc.client = client } -} - -func WebOptionCustomizeRequest(f func(*http.Request) error) WebConnectorOption { - return func(wc *webConnector) { wc.customizeRequest = f } -} - -// FromWeb returns Datastore implementation that connects to external url -func FromWeb(baseURL string, options ...WebConnectorOption) (DS, error) { - _, err := url.Parse(baseURL) - if err != nil { - return nil, err - } - - ret := &webConnector{ - baseURL: baseURL, - client: http.DefaultClient, - customizeRequest: func(r *http.Request) error { return nil }, - } - - for _, o := range options { - o(ret) - } - - return ret, nil -} - -func (w *webConnector) Kind() string { - return "Web" -} - -func (w *webConnector) Address() string { - return w.baseURL -} - -func (w *webConnector) Open(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) { - switch name.Type() { - case blobtypes.Static: - return w.openStatic(ctx, name) - case blobtypes.DynamicLink: - return w.openDynamicLink(ctx, name) - default: - return nil, blobtypes.ErrUnknownBlobType - } -} - -func (w *webConnector) openStatic(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, w.baseURL+name.String(), http.NoBody) - if err != nil { - return nil, err - } - - res, err := w.do(req) - if err != nil { - return nil, err - } - - err = w.errCheck(res) - if err != nil { - res.Body.Close() - return nil, err - } - - return struct { - io.Reader - io.Closer - }{ - Reader: validatingreader.NewHashValidation(res.Body, sha256.New(), name.Hash(), blobtypes.ErrValidationFailed), - Closer: res.Body, - }, nil -} - -func (w *webConnector) openDynamicLink(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, w.baseURL+name.String(), http.NoBody) - if err != nil { - return nil, err - } - - res, err := w.do(req) - if err != nil { - return nil, err - } - defer res.Body.Close() - - err = w.errCheck(res) - if err != nil { - return nil, err - } - - buff := bytes.NewBuffer(nil) - _, err = dynamiclink.FromPublicData(name, io.TeeReader(res.Body, buff)) - if err != nil { - return nil, err - } - - return io.NopCloser(bytes.NewReader(buff.Bytes())), nil -} - -func (w *webConnector) Update(ctx context.Context, name *common.BlobName, r io.Reader) error { - req, err := http.NewRequestWithContext( - ctx, - http.MethodPut, - w.baseURL+name.String(), - r, - ) - if err != nil { - return err - } - - req.Header.Set("Content-Type", "application/octet-stream") - req.Header.Set("Accept", "application/json") - res, err := w.do(req) - if err != nil { - return err - } - defer res.Body.Close() - - return w.errCheck(res) -} - -func (w *webConnector) Exists(ctx context.Context, name *common.BlobName) (bool, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodHead, w.baseURL+name.String(), http.NoBody) - if err != nil { - return false, err - } - res, err := w.do(req) - if err != nil { - return false, err - } - defer res.Body.Close() - - err = w.errCheck(res) - if errors.Is(err, ErrNotFound) { - return false, nil - } - - if err == nil { - return true, nil - } - return false, err -} - -func (w *webConnector) Delete(ctx context.Context, name *common.BlobName) error { - req, err := http.NewRequestWithContext(ctx, http.MethodDelete, w.baseURL+name.String(), http.NoBody) - if err != nil { - return err - } - - res, err := w.do(req) - if err != nil { - return err - } - defer res.Body.Close() - - return w.errCheck(res) -} - -func (w *webConnector) do(req *http.Request) (*http.Response, error) { - err := w.customizeRequest(req) - if err != nil { - return nil, err - } - - return w.client.Do(req) -} - -func (w *webConnector) errCheck(res *http.Response) error { - if res.StatusCode == http.StatusNotFound { - return ErrNotFound - } - if res.StatusCode == http.StatusBadRequest { - msg := webErrResponse{} - err := json.NewDecoder(res.Body).Decode(&msg) - if err == nil { - err := webErrFromCode(msg.Code) - if err != nil { - return err - } - return fmt.Errorf( - "%w: response status code: %v (%v), error code: %v, error message: %v", - ErrWebConnectionError, - res.StatusCode, - res.Status, - msg.Code, - msg.Message, - ) - } - // Fallthrough to code below if can't decode json error - } - if res.StatusCode >= 400 { - return fmt.Errorf( - "%w: response status code: %v (%v)", - ErrWebConnectionError, - res.StatusCode, - res.Status, - ) - } - return nil -} diff --git a/pkg/datastore/webconnector_test.go b/pkg/datastore/webconnector_test.go deleted file mode 100644 index 22b744a..0000000 --- a/pkg/datastore/webconnector_test.go +++ /dev/null @@ -1,170 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package datastore - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "net/http/httptest" - "net/url" - "testing" - - "github.com/cinode/go/pkg/blobtypes" - "github.com/cinode/go/pkg/datastore/testutils" - "github.com/stretchr/testify/require" -) - -func TestWebConnectorInvalidUrl(t *testing.T) { - _, err := FromWeb("://bad.url") - require.IsType(t, &url.Error{}, err) - - c, err := FromWeb("httpz://bad.url") - require.NoError(t, err) - - _, err = c.Open(t.Context(), testutils.EmptyBlobNameStatic) - require.IsType(t, &url.Error{}, err) - - _, err = c.Exists(t.Context(), testutils.EmptyBlobNameStatic) - require.IsType(t, &url.Error{}, err) - - err = c.Delete(t.Context(), testutils.EmptyBlobNameStatic) - require.IsType(t, &url.Error{}, err) - - err = c.Update(t.Context(), testutils.EmptyBlobNameStatic, bytes.NewBuffer(nil)) - require.IsType(t, &url.Error{}, err) -} - -func TestWebConnectorInvalidContext(t *testing.T) { - var nilCtx context.Context - - c, err := FromWeb("http://datastore.local") - require.NoError(t, err) - - for _, name := range testutils.EmptyBlobNamesOfAllTypes { - t.Run(fmt.Sprint(name.Type()), func(t *testing.T) { - _, err = c.Open(nilCtx, name) - require.ErrorContains(t, err, "nil Context") - - err = c.Update(nilCtx, name, bytes.NewReader(nil)) - require.ErrorContains(t, err, "nil Context") - - _, err = c.Exists(nilCtx, name) - require.ErrorContains(t, err, "nil Context") - - err = c.Delete(nilCtx, name) - require.ErrorContains(t, err, "nil Context") - }) - } -} - -func TestWebConnectorServerSideError(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.Error(w, "Error", http.StatusInternalServerError) - })) - defer server.Close() - - c, err := FromWeb(server.URL + "/") - require.NoError(t, err) - - for _, name := range testutils.EmptyBlobNamesOfAllTypes { - t.Run(fmt.Sprint(name.Type()), func(t *testing.T) { - _, err = c.Open(t.Context(), name) - require.ErrorIs(t, err, ErrWebConnectionError) - - _, err = c.Exists(t.Context(), name) - require.ErrorIs(t, err, ErrWebConnectionError) - - err = c.Delete(t.Context(), name) - require.ErrorIs(t, err, ErrWebConnectionError) - - err = c.Update(t.Context(), name, bytes.NewBuffer(nil)) - require.ErrorIs(t, err, ErrWebConnectionError) - }) - } -} - -func TestWebConnectorDetectInvalidBlobRead(t *testing.T) { - // Test web interface and web connector - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("Hello, I should not be here!")) - })) - defer server.Close() - - ds2, err := FromWeb(server.URL + "/") - require.NoError(t, err) - - for _, name := range testutils.EmptyBlobNamesOfAllTypes { - t.Run(fmt.Sprint(name.Type()), func(t *testing.T) { - rc, err := ds2.Open(t.Context(), name) - if err != nil { - // Either Open or Read could return an error - require.ErrorIs(t, err, blobtypes.ErrValidationFailed) - return - } - - _, err = io.ReadAll(rc) - require.ErrorIs(t, err, blobtypes.ErrValidationFailed) - - err = rc.Close() - require.NoError(t, err) - }) - } -} - -func TestWebConnectorInvalidErrorCode(t *testing.T) { - // Test web interface and web connector - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusBadRequest) - json.NewEncoder(w).Encode(&webErrResponse{ - Code: "SOME_UNKNOWN_CODE", - }) - })) - defer server.Close() - - ds2, err := FromWeb(server.URL + "/") - require.NoError(t, err) - - for _, name := range testutils.EmptyBlobNamesOfAllTypes { - t.Run(fmt.Sprint(name.Type()), func(t *testing.T) { - _, err = ds2.Open(t.Context(), testutils.EmptyBlobNameStatic) - require.ErrorIs(t, err, ErrWebConnectionError) - }) - } -} - -func TestWebConnectorOptions(t *testing.T) { - t.Run("http client", func(t *testing.T) { - cl := &http.Client{} - ds, err := FromWeb("http://test.local/", WebOptionHTTPClient(cl)) - require.NoError(t, err) - require.Equal(t, cl, ds.(*webConnector).client) - }) - - t.Run("customize request", func(t *testing.T) { - testErr := errors.New("test error") - f := func(r *http.Request) error { return testErr } - ds, err := FromWeb("http://test.local/", WebOptionCustomizeRequest(f)) - require.NoError(t, err) - require.Equal(t, testErr, ds.(*webConnector).customizeRequest(nil)) - }) -} diff --git a/pkg/datastore/webinterface.go b/pkg/datastore/webinterface.go deleted file mode 100644 index 124a8cd..0000000 --- a/pkg/datastore/webinterface.go +++ /dev/null @@ -1,261 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package datastore - -import ( - "encoding/json" - "errors" - "io" - "log/slog" - "mime/multipart" - "net/http" - - "github.com/cinode/go/pkg/common" -) - -var ( - errNoData = errors.New("no upload data") -) - -// WebInterface provides simple web interface for given Datastore -type webInterface struct { - ds DS - log *slog.Logger -} - -type WebInterfaceOption func(i *webInterface) - -func WebInterfaceOptionLogger(log *slog.Logger) WebInterfaceOption { - return func(i *webInterface) { i.log = log } -} - -// WebInterface returns http handler representing web interface to given -// Datastore instance -func WebInterface(ds DS, opts ...WebInterfaceOption) http.Handler { - ret := &webInterface{ - ds: ds, - } - - for _, o := range opts { - o(ret) - } - - if ret.log == nil { - ret.log = slog.Default() - } - - return ret -} - -func (i *webInterface) ServeHTTP(w http.ResponseWriter, r *http.Request) { - switch r.Method { - case http.MethodGet: - i.serveGet(w, r) - case http.MethodPut: - i.servePut(w, r) - case http.MethodDelete: - i.serveDelete(w, r) - case http.MethodHead: - i.serveHead(w, r) - default: - http.Error(w, "Unsupported method", http.StatusMethodNotAllowed) - } -} - -func (i *webInterface) getName(_ http.ResponseWriter, r *http.Request) (*common.BlobName, error) { - // Don't allow url queries and require path to start with '/' - if r.URL.Path[0] != '/' || r.URL.RawQuery != "" { - return nil, common.ErrInvalidBlobName - } - - bn, err := common.BlobNameFromString(r.URL.Path[1:]) - if err != nil { - return nil, err - } - - return bn, nil -} - -func (i *webInterface) sendName(name *common.BlobName, w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Content-type", "application/json") - _ = json.NewEncoder(w).Encode(&webNameResponse{ - Name: name.String(), - }) -} - -func (i *webInterface) sendError( - w http.ResponseWriter, - httpCode int, - code string, - message string, -) { - w.Header().Set("Content-type", "application/json") - w.WriteHeader(httpCode) - _ = json.NewEncoder(w).Encode(&webErrResponse{ - Code: code, - Message: message, - }) -} -func (i *webInterface) checkErr(err error, w http.ResponseWriter, r *http.Request) bool { - if err == nil { - return true - } - - if errors.Is(err, ErrNotFound) { - http.NotFound(w, r) - return false - } - - code := webErrToCode(err) - if code != "" { - i.sendError(w, http.StatusBadRequest, code, err.Error()) - return false - } - - i.log.Error( - "Internal error happened while processing the request", - slog.Any("err", err), - slog.Group("req", - slog.String("remoteAddr", r.RemoteAddr), - slog.String("method", r.Method), - slog.String("url", r.URL.String()), - ), - ) - http.Error(w, "Internal server error", http.StatusInternalServerError) - return false -} - -func (i *webInterface) serveGet(w http.ResponseWriter, r *http.Request) { - name, err := i.getName(w, r) - if !i.checkErr(err, w, r) { - return - } - - rc, err := i.ds.Open(r.Context(), name) - if !i.checkErr(err, w, r) { - return - } - - defer rc.Close() - - _, err = io.Copy(w, rc) - if err != nil { - i.log.Error("Error while copying blob data to response", "err", err) - // TODO: Drop the connection or other error passing mechanism? - // It may be too late to send the error to the user thus we have to assume - // that the blob will be validated on the other side - } -} - -type partReader struct { - p *multipart.Part - b io.Closer -} - -func (r *partReader) Read(b []byte) (int, error) { - return r.p.Read(b) -} - -func (r *partReader) Close() error { - err1 := r.p.Close() - err2 := r.b.Close() - if err1 != nil { - return err1 - } - return err2 -} - -func (i *webInterface) getUploadReader(r *http.Request) (io.ReadCloser, error) { - mpr, err := r.MultipartReader() - if err == http.ErrNotMultipart { - // Not multipart, read raw body data - return r.Body, nil - } - if err != nil { - return nil, err - } - - for { - // Get next part of the upload - part, err := mpr.NextPart() - if err == io.EOF { - return nil, errNoData - } - if err != nil { - return nil, err - } - - // Search for first file input - fn := part.FileName() - if fn != "" { - return &partReader{ - p: part, - b: r.Body, - }, nil - } - } -} - -func (i *webInterface) servePut(w http.ResponseWriter, r *http.Request) { - name, err := i.getName(w, r) - if !i.checkErr(err, w, r) { - return - } - - reader, err := i.getUploadReader(r) - if !i.checkErr(err, w, r) { - return - } - defer reader.Close() - - err = i.ds.Update(r.Context(), name, reader) - if !i.checkErr(err, w, r) { - return - } - - i.sendName(name, w, r) -} - -func (i *webInterface) serveDelete(w http.ResponseWriter, r *http.Request) { - name, err := i.getName(w, r) - if !i.checkErr(err, w, r) { - return - } - - err = i.ds.Delete(r.Context(), name) - if !i.checkErr(err, w, r) { - return - } - - i.sendName(name, w, r) -} - -func (i *webInterface) serveHead(w http.ResponseWriter, r *http.Request) { - name, err := i.getName(w, r) - if !i.checkErr(err, w, r) { - return - } - - exists, err := i.ds.Exists(r.Context(), name) - if !i.checkErr(err, w, r) { - return - } - - if !exists { - http.NotFound(w, r) - } -} diff --git a/pkg/datastore/webinterface_test.go b/pkg/datastore/webinterface_test.go deleted file mode 100644 index f1105d0..0000000 --- a/pkg/datastore/webinterface_test.go +++ /dev/null @@ -1,247 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package datastore - -import ( - "bytes" - "context" - "errors" - "io" - "log/slog" - "mime/multipart" - "net/http" - "net/http/httptest" - "testing" - - "github.com/cinode/go/pkg/common" - "github.com/cinode/go/pkg/datastore/testutils" - "github.com/stretchr/testify/require" -) - -func testServer(t *testing.T) string { - log := slog.New(slog.NewTextHandler(io.Discard, nil)) - - // Test web interface and web connector - server := httptest.NewServer(WebInterface( - InMemory(), - WebInterfaceOptionLogger(log), - )) - t.Cleanup(func() { server.Close() }) - return server.URL + "/" -} - -func testHTTPResponse( - t *testing.T, - method string, - path string, - data io.Reader, - code int, -) { - url := testServer(t) - testHTTPResponseOwnServer(t, method, url+path, data, code) -} - -func testHTTPResponseOwnServerContentType( - t *testing.T, - method string, - url string, - data io.Reader, - contentType string, - code int, -) { - req, err := http.NewRequest(method, url, data) - require.NoError(t, err) - - req.Header.Set("Content-Type", contentType) - resp, err := http.DefaultClient.Do(req) - require.NoError(t, err) - - defer resp.Body.Close() - - require.Equal(t, code, resp.StatusCode) -} - -func testHTTPResponseOwnServer( - t *testing.T, - method string, - url string, - data io.Reader, - code int, -) { - testHTTPResponseOwnServerContentType( - t, - method, - url, - data, - "application/octet-stream", - code, - ) -} - -func TestWebInterfaceInvalidMethod(t *testing.T) { - testHTTPResponse( - t, - http.MethodOptions, - "", - nil, - http.StatusMethodNotAllowed, - ) -} - -func TestWebInterfaceGetQueryString(t *testing.T) { - url := testServer(t) - testHTTPResponseOwnServer( - t, - http.MethodPut, - url+testutils.EmptyBlobNameStatic.String(), - bytes.NewBuffer(nil), - http.StatusOK, - ) - testHTTPResponseOwnServer( - t, - http.MethodGet, - url+testutils.EmptyBlobNameStatic.String()+"?param=value", - nil, - http.StatusBadRequest, - ) -} - -func TestWebInterfacePutQueryString(t *testing.T) { - testHTTPResponse( - t, - http.MethodPut, - testutils.EmptyBlobNameStatic.String()+"?param=value", - bytes.NewBuffer(nil), - http.StatusBadRequest, - ) - testHTTPResponse( - t, - http.MethodPut, - testutils.EmptyBlobNameStatic.String(), - bytes.NewBuffer(nil), - http.StatusOK, - ) -} - -func TestWebInterfaceHeadQueryString(t *testing.T) { - url := testServer(t) - testHTTPResponseOwnServer( - t, - http.MethodPut, - url+testutils.EmptyBlobNameStatic.String(), - bytes.NewBuffer(nil), - http.StatusOK, - ) - testHTTPResponseOwnServer( - t, - http.MethodHead, - url+testutils.EmptyBlobNameStatic.String()+"?param=value", - nil, - http.StatusBadRequest, - ) - testHTTPResponseOwnServer( - t, - http.MethodHead, - url+testutils.EmptyBlobNameStatic.String(), - nil, - http.StatusOK, - ) -} - -func TestWebInterfaceDeleteQueryString(t *testing.T) { - url := testServer(t) - testHTTPResponseOwnServer( - t, - http.MethodPut, - url+testutils.EmptyBlobNameStatic.String(), - bytes.NewBuffer(nil), - http.StatusOK, - ) - testHTTPResponseOwnServer( - t, - http.MethodDelete, - url+testutils.EmptyBlobNameStatic.String()+"?param=value", - nil, - http.StatusBadRequest, - ) - testHTTPResponseOwnServer( - t, - http.MethodDelete, - url+testutils.EmptyBlobNameStatic.String(), - nil, - http.StatusOK, - ) -} - -func TestWebIntefaceExistsFailure(t *testing.T) { - server := httptest.NewServer(WebInterface(&datastore{ - s: &mockStore{ - fExists: func(ctx context.Context, name *common.BlobName) (bool, error) { return false, errors.New("fail") }, - }, - })) - defer server.Close() - - testHTTPResponseOwnServer( - t, - http.MethodHead, - server.URL+"/"+testutils.EmptyBlobNameStatic.String(), - nil, - http.StatusInternalServerError, - ) -} - -func TestWebInterfaceMultipartSave(t *testing.T) { - url := testServer(t) - - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - _, err := writer.CreateFormFile("file", "file") - require.NoError(t, err) - err = writer.Close() - require.NoError(t, err) - - testHTTPResponseOwnServerContentType( - t, - http.MethodPut, - url+testutils.EmptyBlobNameStatic.String(), - body, - writer.FormDataContentType(), - http.StatusOK, - ) -} - -func TestWebInterfaceMultipartNoDataSave(t *testing.T) { - url := testServer(t) - - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - field, err := writer.CreateFormField("test") - require.NoError(t, err) - _, err = field.Write([]byte("test")) - require.NoError(t, err) - err = writer.Close() - require.NoError(t, err) - - testHTTPResponseOwnServerContentType( - t, - http.MethodPut, - url+testutils.EmptyBlobNameStatic.String(), - body, - writer.FormDataContentType(), - http.StatusBadRequest, - ) -} diff --git a/pkg/internal/blobtypes/dynamiclink/public.go b/pkg/internal/blobtypes/dynamiclink/public.go index 69f6108..a92b850 100644 --- a/pkg/internal/blobtypes/dynamiclink/public.go +++ b/pkg/internal/blobtypes/dynamiclink/public.go @@ -25,10 +25,10 @@ import ( "io" "testing/iotest" - "github.com/cinode/go/pkg/blobtypes" - "github.com/cinode/go/pkg/common" - "github.com/cinode/go/pkg/internal/utilities/cipherfactory" - "github.com/cinode/go/pkg/internal/utilities/validatingreader" + "github.com/cinode/go-datastore/pkg/blobtypes" + "github.com/cinode/go-datastore/pkg/common" + "github.com/cinode/go-datastore/pkg/internal/utilities/cipherfactory" + "github.com/cinode/go-datastore/pkg/internal/utilities/validatingreader" ) var ( diff --git a/pkg/internal/blobtypes/dynamiclink/public_test.go b/pkg/internal/blobtypes/dynamiclink/public_test.go index 79d568a..b6a269b 100644 --- a/pkg/internal/blobtypes/dynamiclink/public_test.go +++ b/pkg/internal/blobtypes/dynamiclink/public_test.go @@ -27,9 +27,9 @@ import ( "testing" "testing/iotest" - "github.com/cinode/go/pkg/blobtypes" - "github.com/cinode/go/pkg/common" - "github.com/cinode/go/pkg/internal/utilities/cipherfactory" + "github.com/cinode/go-datastore/pkg/blobtypes" + "github.com/cinode/go-datastore/pkg/common" + "github.com/cinode/go-datastore/pkg/internal/utilities/cipherfactory" "github.com/stretchr/testify/require" ) diff --git a/pkg/internal/blobtypes/dynamiclink/publisher.go b/pkg/internal/blobtypes/dynamiclink/publisher.go index ee0e44c..80a139b 100644 --- a/pkg/internal/blobtypes/dynamiclink/publisher.go +++ b/pkg/internal/blobtypes/dynamiclink/publisher.go @@ -23,9 +23,9 @@ import ( "errors" "io" - "github.com/cinode/go/pkg/blobtypes" - "github.com/cinode/go/pkg/common" - "github.com/cinode/go/pkg/internal/utilities/cipherfactory" + "github.com/cinode/go-datastore/pkg/blobtypes" + "github.com/cinode/go-datastore/pkg/common" + "github.com/cinode/go-datastore/pkg/internal/utilities/cipherfactory" ) var ( diff --git a/pkg/internal/blobtypes/dynamiclink/publisher_test.go b/pkg/internal/blobtypes/dynamiclink/publisher_test.go index 4d8842e..af32348 100644 --- a/pkg/internal/blobtypes/dynamiclink/publisher_test.go +++ b/pkg/internal/blobtypes/dynamiclink/publisher_test.go @@ -24,7 +24,7 @@ import ( "testing" "testing/iotest" - "github.com/cinode/go/pkg/common" + "github.com/cinode/go-datastore/pkg/common" "github.com/stretchr/testify/require" ) diff --git a/pkg/internal/blobtypes/dynamiclink/vectors_test.go b/pkg/internal/blobtypes/dynamiclink/vectors_test.go index e63e987..31d6f87 100644 --- a/pkg/internal/blobtypes/dynamiclink/vectors_test.go +++ b/pkg/internal/blobtypes/dynamiclink/vectors_test.go @@ -26,7 +26,7 @@ import ( "strings" "testing" - "github.com/cinode/go/pkg/common" + "github.com/cinode/go-datastore/pkg/common" "github.com/stretchr/testify/require" ) diff --git a/pkg/internal/utilities/cipherfactory/cipher_factory.go b/pkg/internal/utilities/cipherfactory/cipher_factory.go index 189d83c..364c2d6 100644 --- a/pkg/internal/utilities/cipherfactory/cipher_factory.go +++ b/pkg/internal/utilities/cipherfactory/cipher_factory.go @@ -22,7 +22,7 @@ import ( "fmt" "io" - "github.com/cinode/go/pkg/common" + "github.com/cinode/go-datastore/pkg/common" "golang.org/x/crypto/chacha20" ) diff --git a/pkg/internal/utilities/cipherfactory/cipher_factory_test.go b/pkg/internal/utilities/cipherfactory/cipher_factory_test.go index f3d2ce0..1b21fe6 100644 --- a/pkg/internal/utilities/cipherfactory/cipher_factory_test.go +++ b/pkg/internal/utilities/cipherfactory/cipher_factory_test.go @@ -21,7 +21,7 @@ import ( "io" "testing" - "github.com/cinode/go/pkg/common" + "github.com/cinode/go-datastore/pkg/common" "github.com/stretchr/testify/require" "golang.org/x/crypto/chacha20" ) diff --git a/pkg/internal/utilities/cipherfactory/generator.go b/pkg/internal/utilities/cipherfactory/generator.go index 06388b8..eb8acc8 100644 --- a/pkg/internal/utilities/cipherfactory/generator.go +++ b/pkg/internal/utilities/cipherfactory/generator.go @@ -1,5 +1,5 @@ /* -Copyright © 2023 Bartłomiej Święcki (byo) +Copyright © 2025 Bartłomiej Święcki (byo) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import ( "hash" "io" - "github.com/cinode/go/pkg/common" + "github.com/cinode/go-datastore/pkg/common" "golang.org/x/crypto/chacha20" ) diff --git a/pkg/internal/utilities/cipherfactory/generator_test.go b/pkg/internal/utilities/cipherfactory/generator_test.go index 3cb5710..66c8a6b 100644 --- a/pkg/internal/utilities/cipherfactory/generator_test.go +++ b/pkg/internal/utilities/cipherfactory/generator_test.go @@ -1,5 +1,5 @@ /* -Copyright © 2023 Bartłomiej Święcki (byo) +Copyright © 2025 Bartłomiej Święcki (byo) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ package cipherfactory import ( "testing" - "github.com/cinode/go/pkg/blobtypes" + "github.com/cinode/go-datastore/pkg/blobtypes" "github.com/stretchr/testify/require" ) diff --git a/pkg/internal/utilities/securefifo/secure_fifo.go b/pkg/internal/utilities/securefifo/secure_fifo.go deleted file mode 100644 index bcca5e8..0000000 --- a/pkg/internal/utilities/securefifo/secure_fifo.go +++ /dev/null @@ -1,165 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package securefifo - -import ( - "crypto/cipher" - "crypto/rand" - "io" - "os" - - "golang.org/x/crypto/chacha20" -) - -type Reader interface { - io.ReadCloser - - // Reset closes current reader and opens a new one that starts at the beginning of the data - Reset() (Reader, error) -} - -type Writer interface { - io.WriteCloser - - // Done closes current writer and opens SecureFifoReader stream for reading - Done() (Reader, error) -} - -type secureFifo struct { - fl *os.File - - key []byte - nonce []byte -} - -func (f *secureFifo) Close() error { - return f.fl.Close() -} - -func (f *secureFifo) getStream() cipher.Stream { - stream, _ := chacha20.NewUnauthenticatedCipher(f.key, f.nonce) - return stream -} - -func (f *secureFifo) openReader() (*reader, error) { - _, err := f.fl.Seek(0, io.SeekStart) - if err != nil { - return nil, err - } - - return &reader{ - sf: f, - r: cipher.StreamReader{S: f.getStream(), R: f.fl}, - }, nil -} - -type writer struct { - sf *secureFifo - w io.Writer -} - -func (w *writer) Write(b []byte) (int, error) { - return w.w.Write(b) -} - -func (w *writer) Close() error { - if w.sf == nil { - return nil - } - defer func() { w.sf = nil; w.w = nil }() - return w.sf.Close() -} - -func (w *writer) Done() (Reader, error) { - ret, err := w.sf.openReader() - if err != nil { - return nil, err - } - - w.sf = nil - w.w = nil - - return ret, nil -} - -type reader struct { - sf *secureFifo - r io.Reader -} - -func (r *reader) Read(b []byte) (int, error) { - return r.r.Read(b) -} - -func (r *reader) Close() error { - if r.sf == nil { - return nil - } - defer func() { r.sf = nil; r.r = nil }() - return r.sf.Close() -} - -func (r *reader) Reset() (Reader, error) { - ret, err := r.sf.openReader() - if err != nil { - return nil, err - } - - r.sf = nil - r.r = nil - - return ret, nil -} - -// New creates new secure fifo pipe. That pipe may handle large amounts of data by using a temporary storage -// but ensures that even if the data can be accessed from disk, it can not be decrypted. -func New() (wr Writer, err error) { - var randData [chacha20.KeySize + chacha20.NonceSize]byte - _, err = rand.Read(randData[:]) - if err != nil { - return nil, err - } - - tempFile, err := os.CreateTemp("", "secure-fifo") - if err != nil { - return nil, err - } - defer func() { - if err != nil { - tempFile.Close() - os.Remove(tempFile.Name()) - } - }() - - // Supported on Linux (to check on Mac OSX) - unlinking already opened file - // will still allow reading / writing that file by using already opened handles - err = os.Remove(tempFile.Name()) - if err != nil { - return nil, err - } - - sf := &secureFifo{ - key: randData[:chacha20.KeySize], - nonce: randData[chacha20.KeySize:], - fl: tempFile, - } - - return &writer{ - sf: sf, - w: cipher.StreamWriter{S: sf.getStream(), W: tempFile}, - }, nil -} diff --git a/pkg/internal/utilities/securefifo/secure_fifo_test.go b/pkg/internal/utilities/securefifo/secure_fifo_test.go deleted file mode 100644 index 4e4ae50..0000000 --- a/pkg/internal/utilities/securefifo/secure_fifo_test.go +++ /dev/null @@ -1,113 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package securefifo - -import ( - "fmt" - "io" - "os" - "strings" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestSecureFifoReadBack(t *testing.T) { - for i, d := range []struct { - data []byte - }{ - {data: []byte{}}, - {data: []byte("a")}, - {data: []byte(strings.Repeat("a", 15))}, - {data: []byte(strings.Repeat("a", 16))}, - {data: []byte(strings.Repeat("a", 17))}, - {data: []byte(strings.Repeat("a", 16*1024))}, - } { - t.Run(fmt.Sprint(i), func(t *testing.T) { - w, err := New() - require.NoError(t, err) - defer w.Close() - - n, err := w.Write(d.data) - require.NoError(t, err) - require.EqualValues(t, len(d.data), n) - - r, err := w.Done() - require.NoError(t, err) - defer r.Close() - - err = w.Close() - require.NoError(t, err) - - // Close must be idempotent - err = w.Close() - require.NoError(t, err) - - readBack, err := io.ReadAll(r) - require.NoError(t, err) - require.Equal(t, readBack, d.data) - - r2, err := r.Reset() - require.NoError(t, err) - - err = r.Close() - require.NoError(t, err) - - // Close must be idempotent - err = r.Close() - require.NoError(t, err) - - readBack, err = io.ReadAll(r2) - require.NoError(t, err) - require.Equal(t, readBack, d.data) - - err = r2.Close() - require.NoError(t, err) - }) - } -} - -func TestSecureFifoFileAccess(t *testing.T) { - w, err := New() - require.NoError(t, err) - defer w.Close() - - data := []byte("secret data") - - n, err := w.Write(data) - require.NoError(t, err) - require.EqualValues(t, len(data), n) - - // The file must not physically exist - it is unlinked so only the open handles - // keep it on disk - fl := w.(*writer).sf.fl - require.NoFileExists(t, fl.Name()) - - _, err = fl.Seek(0, io.SeekStart) - require.NoError(t, err) - - dataRead, err := io.ReadAll(fl) - require.NoError(t, err) - require.NotContains(t, dataRead, []byte("secret")) - - err = w.Close() - require.NoError(t, err) - - // File must be closed - err = fl.Close() - require.ErrorIs(t, err, os.ErrClosed) -} diff --git a/pkg/internal/utilities/validatingreader/hashvalidatingreader_test.go b/pkg/internal/utilities/validatingreader/hashvalidatingreader_test.go index 4f65f5d..c1204f7 100644 --- a/pkg/internal/utilities/validatingreader/hashvalidatingreader_test.go +++ b/pkg/internal/utilities/validatingreader/hashvalidatingreader_test.go @@ -23,7 +23,7 @@ import ( "io" "testing" - "github.com/cinode/go/pkg/internal/utilities/validatingreader" + "github.com/cinode/go-datastore/pkg/internal/utilities/validatingreader" "github.com/stretchr/testify/require" ) diff --git a/pkg/internal/utilities/validatingreader/oneofcheckreader_test.go b/pkg/internal/utilities/validatingreader/oneofcheckreader_test.go index 917aeef..03ddd11 100644 --- a/pkg/internal/utilities/validatingreader/oneofcheckreader_test.go +++ b/pkg/internal/utilities/validatingreader/oneofcheckreader_test.go @@ -22,7 +22,7 @@ import ( "io" "testing" - "github.com/cinode/go/pkg/internal/utilities/validatingreader" + "github.com/cinode/go-datastore/pkg/internal/utilities/validatingreader" "github.com/stretchr/testify/require" ) diff --git a/pkg/utilities/golang/test_utils.go b/pkg/utilities/golang/test_utils.go deleted file mode 100644 index b9f4b71..0000000 --- a/pkg/utilities/golang/test_utils.go +++ /dev/null @@ -1,28 +0,0 @@ -/* -Copyright © 2023 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package golang - -import ( - "os" - "testing" -) - -func SetTestOsArgs(t *testing.T, args ...string) { - currentArgs := os.Args - os.Args = args - t.Cleanup(func() { os.Args = currentArgs }) -} diff --git a/pkg/utilities/golang/test_utils_test.go b/pkg/utilities/golang/test_utils_test.go deleted file mode 100644 index 0d8759e..0000000 --- a/pkg/utilities/golang/test_utils_test.go +++ /dev/null @@ -1,33 +0,0 @@ -/* -Copyright © 2023 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package golang - -import ( - "os" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestSetTestOsArgs(t *testing.T) { - SetTestOsArgs(t, "initial", "args") - t.Run("sub-test", func(t *testing.T) { - SetTestOsArgs(t, "other", "set", "of", "args") - require.Equal(t, []string{"other", "set", "of", "args"}, os.Args) - }) - require.Equal(t, []string{"initial", "args"}, os.Args) -} diff --git a/pkg/utilities/httpserver/httpserver.go b/pkg/utilities/httpserver/httpserver.go deleted file mode 100644 index b9081a7..0000000 --- a/pkg/utilities/httpserver/httpserver.go +++ /dev/null @@ -1,119 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package httpserver - -import ( - "context" - "errors" - "log/slog" - "net" - "net/http" - "os" - "os/signal" - "strconv" - "sync" - "syscall" - "time" -) - -type cfg struct { - log *slog.Logger - handler http.Handler - listenAddr string - gracefulShutdownTimeout time.Duration -} - -type Option func(c *cfg) - -func ListenPort(port int) Option { return func(c *cfg) { c.listenAddr = ":" + strconv.Itoa(port) } } -func ListenAddr(listenAddr string) Option { return func(c *cfg) { c.listenAddr = listenAddr } } -func Logger(log *slog.Logger) Option { return func(c *cfg) { c.log = log } } - -func RunGracefully(ctx context.Context, handler http.Handler, opt ...Option) error { - c := cfg{ - handler: handler, - listenAddr: ":http", - log: slog.Default(), - gracefulShutdownTimeout: 5 * time.Second, - } - - for _, o := range opt { - o(&c) - } - - c.log.Info("Starting http server", "listenAddr", c.listenAddr) - - listener, err := net.Listen("tcp", c.listenAddr) - if err != nil { - return err - } - - ctx, signalCtxCancel := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM) - defer signalCtxCancel() - - return runUntilContextNotDone(ctx, c, listener) -} - -func runUntilContextNotDone(ctx context.Context, cfg cfg, listener net.Listener) error { - server := &http.Server{ - Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - cfg.log.Info( - "http request", - slog.Group("req", - slog.String("remoteAddr", r.RemoteAddr), - slog.String("method", r.Method), - slog.String("url", r.URL.String()), - ), - ) - cfg.handler.ServeHTTP(w, r) - }), - ReadHeaderTimeout: 5 * time.Second, // Prevent Slowloris attacks - } - - wg := sync.WaitGroup{} - wg.Go(func() { - <-ctx.Done() - - cfg.log.Info("Shutting down") - - shutdownCtx, cancel := context.WithTimeout(context.Background(), cfg.gracefulShutdownTimeout) - defer cancel() - - // TODO: More graceful way? - if err := server.Shutdown(shutdownCtx); err != nil { - cfg.log.Error("Failed to shutdown gracefully") - server.Close() - } else { - cfg.log.Info("Shutdown complete") - } - }) - defer wg.Wait() - - err := server.Serve(listener) - if errors.Is(err, http.ErrServerClosed) { - err = nil - } - - return err -} - -func FailResponseOnError(w http.ResponseWriter, err error) { - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } -} diff --git a/pkg/utilities/httpserver/httpserver_nosignal_test.go b/pkg/utilities/httpserver/httpserver_nosignal_test.go deleted file mode 100644 index 3242655..0000000 --- a/pkg/utilities/httpserver/httpserver_nosignal_test.go +++ /dev/null @@ -1,26 +0,0 @@ -//go:build windows - -/* -Copyright © 2023 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package httpserver - -import "testing" - -func getSignalFunc(t *testing.T) func() { - t.Skip("Sending signals is not supported on Windows") - return nil -} diff --git a/pkg/utilities/httpserver/httpserver_signal_test.go b/pkg/utilities/httpserver/httpserver_signal_test.go deleted file mode 100644 index 7d95401..0000000 --- a/pkg/utilities/httpserver/httpserver_signal_test.go +++ /dev/null @@ -1,30 +0,0 @@ -//go:build !windows - -/* -Copyright © 2023 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package httpserver - -import ( - "syscall" - "testing" -) - -func getSignalFunc(t *testing.T) func() { - return func() { - syscall.Kill(syscall.Getpid(), syscall.SIGINT) - } -} diff --git a/pkg/utilities/httpserver/httpserver_test.go b/pkg/utilities/httpserver/httpserver_test.go deleted file mode 100644 index 39a4489..0000000 --- a/pkg/utilities/httpserver/httpserver_test.go +++ /dev/null @@ -1,209 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package httpserver - -import ( - "bytes" - "context" - "errors" - "fmt" - "io" - "log/slog" - "net" - "net/http" - "net/http/httptest" - "sync" - "testing" - "time" - - "github.com/stretchr/testify/require" -) - -func TestCancelWithContext(t *testing.T) { - ctx, cancel := context.WithCancel(t.Context()) - go func() { - time.Sleep(10 * time.Millisecond) - cancel() - }() - start := time.Now() - err := RunGracefully( - ctx, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}), - ListenAddr(":0"), - ) - require.NoError(t, err) - require.Less(t, time.Since(start), time.Second) -} - -func TestCancelWithSignal(t *testing.T) { - signalFunc := getSignalFunc(t) // Some quirks needed since signals are not possible on Windows :facepalm: - go func() { - time.Sleep(10 * time.Millisecond) - signalFunc() - }() - start := time.Now() - err := RunGracefully( - t.Context(), - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}), - ListenAddr(":0"), - ) - require.NoError(t, err) - require.Less(t, time.Since(start), time.Second) -} - -func TestEnsureHandlerIsCalled(t *testing.T) { - handlerCalled := false - - ctx, shutdown := context.WithCancel(t.Context()) - defer shutdown() - - listener, err := net.Listen("tcp", ":0") - require.NoError(t, err) - - wg := sync.WaitGroup{} - defer wg.Wait() - - wg.Go(func() { - err := runUntilContextNotDone(ctx, - cfg{ - log: slog.Default(), - handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - handlerCalled = true - }), - gracefulShutdownTimeout: 5 * time.Second, - }, - listener, - ) - require.NoError(t, err) - }) - - resp, err := http.Get( - fmt.Sprintf("http://localhost:%d/", listener.Addr().(*net.TCPAddr).Port), - ) - require.NoError(t, err) - defer resp.Body.Close() - - _, err = io.ReadAll(resp.Body) - require.NoError(t, err) - - require.True(t, handlerCalled) - - shutdown() -} - -func TestCloseOnShutdownTimeout(t *testing.T) { - ctx, shutdown := context.WithCancel(t.Context()) - defer shutdown() - - listener, err := net.Listen("tcp", ":0") - require.NoError(t, err) - - wg := sync.WaitGroup{} - defer wg.Wait() - - handlerHang := make(chan struct{}) - requestStarted := make(chan struct{}) - serverClosed := make(chan struct{}) - - wg.Go(func() { - defer close(serverClosed) - err := runUntilContextNotDone(ctx, - cfg{ - handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Send some larger buffer to initiate connection, send all headers etc. - w.Write(bytes.Repeat([]byte("A"), 1024*1024)) - <-handlerHang - }), - log: slog.Default(), - // Very short graceful timeout to quickly switch to forcible shutdown - gracefulShutdownTimeout: time.Millisecond, - }, - listener, - ) - require.NoError(t, err) - }) - - wg.Go(func() { - resp, err := http.Get( - fmt.Sprintf("http://localhost:%d/", listener.Addr().(*net.TCPAddr).Port), - ) - require.NoError(t, err) - defer resp.Body.Close() - - // Synchronization point to ensure the request is in progress - close(requestStarted) - - io.ReadAll(resp.Body) - }) - - // Wait for the request to be in progress - <-requestStarted - - // Initiate shutdown - shutdown() - <-serverClosed - - // Let the handler finish - close(handlerHang) -} - -func TestFailOnInvalidListenAddr(t *testing.T) { - err := RunGracefully( - t.Context(), - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}), - ListenAddr("not-a-listen-address"), - ) - require.IsType(t, &net.OpError{}, err) -} - -func TestOptions(t *testing.T) { - t.Run("ListenPort", func(t *testing.T) { - config := cfg{} - ListenPort(54321)(&config) - require.Equal(t, ":54321", config.listenAddr) - }) - t.Run("ListenAddr", func(t *testing.T) { - config := cfg{} - ListenAddr(":12345")(&config) - require.Equal(t, ":12345", config.listenAddr) - }) - t.Run("Logger", func(t *testing.T) { - log := slog.New(slog.NewJSONHandler(bytes.NewBuffer(nil), nil)) - config := cfg{} - Logger(log)(&config) - require.Equal(t, log, config.log) - }) -} - -func TestFailResponseOnError(t *testing.T) { - var triggeredError error - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - FailResponseOnError(w, triggeredError) - })) - - resp, err := http.Get(server.URL) - resp.Body.Close() - require.NoError(t, err) - require.Equal(t, http.StatusOK, resp.StatusCode) - - triggeredError = errors.New("error") - - resp, err = http.Get(server.URL) - resp.Body.Close() - require.NoError(t, err) - require.Equal(t, http.StatusInternalServerError, resp.StatusCode) -} diff --git a/testvectors/testblobs/base.go b/testvectors/testblobs/base.go deleted file mode 100644 index ee1c107..0000000 --- a/testvectors/testblobs/base.go +++ /dev/null @@ -1,106 +0,0 @@ -/* -Copyright © 2023 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package testblobs - -import ( - "bytes" - "fmt" - "io" - "net/http" - "net/url" - - "github.com/cinode/go/pkg/cinodefs" - "github.com/cinode/go/pkg/common" -) - -type TestBlob struct { - UpdateDataset []byte - BlobName *common.BlobName - EncryptionKey *common.BlobKey - DecryptedDataset []byte -} - -func (s *TestBlob) Put(baseUrl string) error { - return s.PutWithAuth(baseUrl, "", "") -} - -func (s *TestBlob) PutWithAuth(baseUrl, username, password string) error { - finalUrl, err := url.JoinPath(baseUrl, s.BlobName.String()) - if err != nil { - return err - } - - req, err := http.NewRequest( - http.MethodPut, - finalUrl, - bytes.NewReader(s.UpdateDataset)) - if err != nil { - return err - } - - if username != "" || password != "" { - req.SetBasicAuth(username, password) - } - - resp, err := http.DefaultClient.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - body, err := io.ReadAll(resp.Body) - if err != nil { - return err - } - - if resp.StatusCode >= 400 { - return fmt.Errorf("invalid http status code %s (%d), body: %s", resp.Status, resp.StatusCode, string(body)) - } - - return nil -} - -func (s *TestBlob) Get(baseUrl string) ([]byte, error) { - finalUrl, err := url.JoinPath(baseUrl, s.BlobName.String()) - if err != nil { - return nil, err - } - - resp, err := http.Get(finalUrl) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - if resp.StatusCode >= 400 { - return nil, fmt.Errorf("invalid http status code %s (%d), body: %s", resp.Status, resp.StatusCode, string(body)) - } - - return body, nil -} - -func (s *TestBlob) Entrypoint() *cinodefs.Entrypoint { - return cinodefs.EntrypointFromBlobNameAndKey( - s.BlobName, - s.EncryptionKey, - ) -} diff --git a/testvectors/testblobs/dynamiclink.go b/testvectors/testblobs/dynamiclink.go deleted file mode 100644 index 422d226..0000000 --- a/testvectors/testblobs/dynamiclink.go +++ /dev/null @@ -1,56 +0,0 @@ -package testblobs - -import ( - "github.com/cinode/go/pkg/common" - "github.com/cinode/go/pkg/utilities/golang" -) - -var DynamicLink = TestBlob{ - []byte{ - 0x00, 0x11, 0xDA, 0xDD, 0x0F, 0x94, 0xBE, 0xCA, - 0x12, 0xBE, 0xBB, 0x74, 0x25, 0x52, 0x34, 0x65, - 0x1B, 0xFA, 0x5B, 0x3F, 0x57, 0x04, 0xF1, 0x8D, - 0xB3, 0x08, 0xB9, 0x45, 0xD1, 0x90, 0x26, 0x8D, - 0x5F, 0x34, 0xB9, 0x35, 0x2E, 0x7F, 0x97, 0x6C, - 0xA5, 0x49, 0x8C, 0xAF, 0xBA, 0x1C, 0x5B, 0x18, - 0xD8, 0x37, 0x0E, 0x4A, 0x0E, 0x9D, 0x3B, 0x5E, - 0x3B, 0x99, 0x31, 0x0B, 0xC7, 0xAE, 0xC4, 0x07, - 0x6F, 0x3A, 0x2B, 0x29, 0xE1, 0x67, 0x01, 0x11, - 0xF6, 0x23, 0x96, 0x87, 0x96, 0x53, 0xF0, 0x5B, - 0x34, 0x76, 0xD6, 0xBD, 0xBC, 0x9F, 0x14, 0x2C, - 0x25, 0x30, 0xBA, 0x78, 0x74, 0x0C, 0xED, 0xB4, - 0x17, 0x14, 0x64, 0x09, 0x61, 0x76, 0x38, 0x3E, - 0x07, 0x3D, 0x7E, 0x04, 0x7C, 0x23, 0x47, 0xD8, - 0xED, 0x18, 0x4E, 0x4F, 0xDD, 0x3C, 0x26, 0xE4, - 0xEB, 0xB4, 0xCC, 0x3F, 0x06, 0xE2, 0x50, 0x6C, - 0x7F, 0xFE, 0x00, 0x20, 0x0A, 0x21, 0xBB, 0x1F, - 0x2C, 0x9B, 0x7D, 0x15, 0x86, 0x2C, 0x01, 0xF0, - 0xFC, 0xE5, 0x89, 0xB2, 0x9F, 0x60, 0x3A, 0x51, - 0x44, 0x19, 0x25, 0xD0, 0x34, 0x3B, 0x84, 0x5C, - 0x74, 0x19, 0xE2, 0xFF, 0xD3, 0x64, 0xDE, 0xF1, - 0xCD, 0x81, 0x94, 0x48, 0x04, 0xBD, 0x86, 0x29, - 0xA5, 0xD7, 0xAA, 0xC1, 0x62, 0x16, 0x05, 0x35, - 0x65, 0xB5, 0xC4, 0x42, 0xC6, 0x28, 0xA2, 0x69, - 0x16, 0x67, 0x74, 0x21, 0x86, 0xA2, 0x59, 0x11, - 0xD8, 0x3F, 0xDD, 0xB1, 0x1F, 0x22, 0x7C, 0xD9, - 0x73, 0xCA, 0x26, 0x11, 0x29, 0x79, 0x03, 0xF9, - }, - golang.Must(common.BlobNameFromBytes([]byte{ - 0x4F, 0xDA, 0x7E, 0xE6, 0xF2, 0x71, 0xB5, 0xEF, - 0xFF, 0xD2, 0x05, 0x27, 0x0B, 0xBA, 0x11, 0x13, - 0x13, 0xF5, 0xC9, 0x06, 0x9D, 0x6C, 0x36, 0x5F, - 0x80, 0xD3, 0x50, 0xE3, 0xC5, 0x9B, 0x0E, 0x8D, - 0xE6, - })), - common.BlobKeyFromBytes([]byte{ - 0x00, 0x79, 0xD2, 0x68, 0xC8, 0xEB, 0xD6, 0xA1, - 0xBD, 0x5D, 0xE8, 0x63, 0x1C, 0xF7, 0x73, 0x73, - 0x77, 0x26, 0x99, 0x4E, 0xC7, 0x35, 0xD9, 0x81, - 0xB5, 0x20, 0xA8, 0xD7, 0xD9, 0x0B, 0xD5, 0x05, - 0x49, - }), - []byte{ - 0x64, 0x79, 0x6E, 0x61, 0x6D, 0x69, 0x63, 0x20, - 0x6C, 0x69, 0x6E, 0x6B, - }, -} From 27c2aa0b18b1c3dd095b106bcb8a292ac58934d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20=C5=9Awi=C4=99cki?= Date: Fri, 31 Oct 2025 23:38:20 +0100 Subject: [PATCH 02/10] Group errors into errors.go --- pkg/datastore/errors.go | 6 +++++- pkg/datastore/interface.go | 6 ------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/pkg/datastore/errors.go b/pkg/datastore/errors.go index 94fbbf8..5195559 100644 --- a/pkg/datastore/errors.go +++ b/pkg/datastore/errors.go @@ -1,5 +1,5 @@ /* -Copyright © 2022 Bartłomiej Święcki (byo) +Copyright © 2025 Bartłomiej Święcki (byo) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,5 +19,9 @@ package datastore import "errors" var ( + // ErrUploadInProgress will be used when another upload is already in progress for the same blob ErrUploadInProgress = errors.New("another upload is already in progress") + + // ErrNotFound will be used when blob with given name was not found in datastore + ErrNotFound = errors.New("not found") ) diff --git a/pkg/datastore/interface.go b/pkg/datastore/interface.go index 9e52faa..5699ab5 100644 --- a/pkg/datastore/interface.go +++ b/pkg/datastore/interface.go @@ -18,17 +18,11 @@ package datastore import ( "context" - "errors" "io" "github.com/cinode/go-datastore/pkg/common" ) -var ( - // ErrNotFound will be used when blob with given name was not found in datastore - ErrNotFound = errors.New("not found") -) - // DS interface contains the public interface of any conformant datastore // // Stored data is split into small chunks called blobs. Each blob has its From 0ace762f68747111c53537a6474e68bf6e30504c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20=C5=9Awi=C4=99cki?= Date: Fri, 31 Oct 2025 23:38:21 +0100 Subject: [PATCH 03/10] Don't use iotest in dynamic link public reader --- pkg/internal/blobtypes/dynamiclink/public.go | 4 +-- .../blobtypes/dynamiclink/public_test.go | 4 +-- .../blobtypes/dynamiclink/publisher_test.go | 8 ++--- pkg/internal/utilities/errreader/errreader.go | 31 +++++++++++++++++ .../utilities/errreader/errreader_test.go | 33 +++++++++++++++++++ 5 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 pkg/internal/utilities/errreader/errreader.go create mode 100644 pkg/internal/utilities/errreader/errreader_test.go diff --git a/pkg/internal/blobtypes/dynamiclink/public.go b/pkg/internal/blobtypes/dynamiclink/public.go index a92b850..dd625a7 100644 --- a/pkg/internal/blobtypes/dynamiclink/public.go +++ b/pkg/internal/blobtypes/dynamiclink/public.go @@ -23,11 +23,11 @@ import ( "fmt" "hash" "io" - "testing/iotest" "github.com/cinode/go-datastore/pkg/blobtypes" "github.com/cinode/go-datastore/pkg/common" "github.com/cinode/go-datastore/pkg/internal/utilities/cipherfactory" + "github.com/cinode/go-datastore/pkg/internal/utilities/errreader" "github.com/cinode/go-datastore/pkg/internal/utilities/validatingreader" ) @@ -204,7 +204,7 @@ func FromPublicData(name *common.BlobName, r io.Reader) (*PublicReader, error) { }() if err != nil { - dl.r = iotest.ErrReader(err) + dl.r = errreader.New(err) } else { dl.r = bytes.NewReader(elink) } diff --git a/pkg/internal/blobtypes/dynamiclink/public_test.go b/pkg/internal/blobtypes/dynamiclink/public_test.go index b6a269b..fd04e7d 100644 --- a/pkg/internal/blobtypes/dynamiclink/public_test.go +++ b/pkg/internal/blobtypes/dynamiclink/public_test.go @@ -25,11 +25,11 @@ import ( math_rand "math/rand" "sort" "testing" - "testing/iotest" "github.com/cinode/go-datastore/pkg/blobtypes" "github.com/cinode/go-datastore/pkg/common" "github.com/cinode/go-datastore/pkg/internal/utilities/cipherfactory" + "github.com/cinode/go-datastore/pkg/internal/utilities/errreader" "github.com/stretchr/testify/require" ) @@ -71,7 +71,7 @@ func TestFromPublicData(t *testing.T) { t.Run(fmt.Sprint(validBytes), func(t *testing.T) { rdr := io.MultiReader( bytes.NewReader(data[:validBytes]), - iotest.ErrReader(injectedErr), + errreader.New(injectedErr), ) dl, err := FromPublicData(dl.BlobName(), rdr) diff --git a/pkg/internal/blobtypes/dynamiclink/publisher_test.go b/pkg/internal/blobtypes/dynamiclink/publisher_test.go index af32348..817565d 100644 --- a/pkg/internal/blobtypes/dynamiclink/publisher_test.go +++ b/pkg/internal/blobtypes/dynamiclink/publisher_test.go @@ -22,9 +22,9 @@ import ( "errors" "io" "testing" - "testing/iotest" "github.com/cinode/go-datastore/pkg/common" + "github.com/cinode/go-datastore/pkg/internal/utilities/errreader" "github.com/stretchr/testify/require" ) @@ -44,7 +44,7 @@ func TestCreate(t *testing.T) { injectedErr := errors.New("test") r := io.MultiReader( io.LimitReader(rand.Reader, int64(goodBytes)), - iotest.ErrReader(injectedErr), + errreader.New(injectedErr), ) dl, err := Create(r) @@ -98,7 +98,7 @@ func TestReNonce(t *testing.T) { injectedErr := errors.New("test") r := io.MultiReader( io.LimitReader(rand.Reader, int64(goodBytes)), - iotest.ErrReader(injectedErr), + errreader.New(injectedErr), ) dl2, err := ReNonce(dl1, r) @@ -129,7 +129,7 @@ func TestPublisherUpdateLinkData(t *testing.T) { t.Run("failed data reader", func(t *testing.T) { injectedErr := errors.New("test") - pr2, key2, err := dl.UpdateLinkData(iotest.ErrReader(injectedErr), 3) + pr2, key2, err := dl.UpdateLinkData(errreader.New(injectedErr), 3) require.ErrorIs(t, err, injectedErr) require.Nil(t, pr2) require.Nil(t, key2) diff --git a/pkg/internal/utilities/errreader/errreader.go b/pkg/internal/utilities/errreader/errreader.go new file mode 100644 index 0000000..7f14816 --- /dev/null +++ b/pkg/internal/utilities/errreader/errreader.go @@ -0,0 +1,31 @@ +/* +Copyright © 2025 Bartłomiej Święcki (byo) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package errreader + +import "io" + +type errReader struct { + err error +} + +func (e errReader) Read(p []byte) (int, error) { + return 0, e.err +} + +func New(err error) io.Reader { + return &errReader{err: err} +} diff --git a/pkg/internal/utilities/errreader/errreader_test.go b/pkg/internal/utilities/errreader/errreader_test.go new file mode 100644 index 0000000..57c141a --- /dev/null +++ b/pkg/internal/utilities/errreader/errreader_test.go @@ -0,0 +1,33 @@ +/* +Copyright © 2025 Bartłomiej Święcki (byo) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package errreader + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestErrReader(t *testing.T) { + expectedErr := errors.New("test error") + r := New(expectedErr) + + n, err := r.Read(make([]byte, 10)) + require.Zero(t, n) + require.ErrorIs(t, err, expectedErr) +} From 3420b8b8c12292f75f72c2365272850c183630c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20=C5=9Awi=C4=99cki?= Date: Fri, 31 Oct 2025 23:38:22 +0100 Subject: [PATCH 04/10] Rename storage to StorageBackend and make it a public interface --- pkg/datastore/datastore.go | 12 ++-- pkg/datastore/datastore_dynamic_link.go | 6 +- pkg/datastore/datastore_static.go | 4 +- pkg/datastore/datastore_test.go | 12 ++-- .../{storage.go => storage_backend.go} | 14 ++-- ...ge_memory.go => storage_backend_memory.go} | 16 ++--- pkg/datastore/storage_memory_test.go | 6 +- pkg/datastore/storage_test.go | 72 +++++++++---------- 8 files changed, 71 insertions(+), 71 deletions(-) rename pkg/datastore/{storage.go => storage_backend.go} (71%) rename pkg/datastore/{storage_memory.go => storage_backend_memory.go} (84%) diff --git a/pkg/datastore/datastore.go b/pkg/datastore/datastore.go index d172788..16bc7ae 100644 --- a/pkg/datastore/datastore.go +++ b/pkg/datastore/datastore.go @@ -25,17 +25,17 @@ import ( ) type datastore struct { - s storage + s StorageBackend } var _ DS = (*datastore)(nil) func (ds *datastore) Kind() string { - return ds.s.kind() + return ds.s.Kind() } func (ds *datastore) Address() string { - return ds.s.address() + return ds.s.Address() } func (ds *datastore) Open(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) { @@ -61,11 +61,11 @@ func (ds *datastore) Update(ctx context.Context, name *common.BlobName, updateSt } func (ds *datastore) Exists(ctx context.Context, name *common.BlobName) (bool, error) { - return ds.s.exists(ctx, name) + return ds.s.Exists(ctx, name) } func (ds *datastore) Delete(ctx context.Context, name *common.BlobName) error { - return ds.s.delete(ctx, name) + return ds.s.Delete(ctx, name) } // InMemory constructs an in-memory datastore @@ -73,5 +73,5 @@ func (ds *datastore) Delete(ctx context.Context, name *common.BlobName) error { // The content is lost if the datastore is destroyed (either by garbage collection // or by program termination) func InMemory() DS { - return &datastore{s: newStorageMemory()} + return &datastore{s: NewInMemoryStorageBackend()} } diff --git a/pkg/datastore/datastore_dynamic_link.go b/pkg/datastore/datastore_dynamic_link.go index 4cd6320..04c8317 100644 --- a/pkg/datastore/datastore_dynamic_link.go +++ b/pkg/datastore/datastore_dynamic_link.go @@ -26,7 +26,7 @@ import ( ) func (ds *datastore) openDynamicLink(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) { - rc, err := ds.s.openReadStream(ctx, name) + rc, err := ds.s.OpenReadStream(ctx, name) if err != nil { return nil, err } @@ -55,7 +55,7 @@ func (ds *datastore) newLinkGreaterThanCurrent( ) ( bool, error, ) { - rc, err := ds.s.openReadStream(ctx, name) + rc, err := ds.s.OpenReadStream(ctx, name) if errors.Is(err, ErrNotFound) { return true, nil } @@ -73,7 +73,7 @@ func (ds *datastore) newLinkGreaterThanCurrent( } func (ds *datastore) updateDynamicLink(ctx context.Context, name *common.BlobName, updateStream io.Reader) error { - ws, err := ds.s.openWriteStream(ctx, name) + ws, err := ds.s.OpenWriteStream(ctx, name) if err != nil { return err } diff --git a/pkg/datastore/datastore_static.go b/pkg/datastore/datastore_static.go index a7100d2..0587bf7 100644 --- a/pkg/datastore/datastore_static.go +++ b/pkg/datastore/datastore_static.go @@ -28,7 +28,7 @@ import ( ) func (ds *datastore) openStatic(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) { - rc, err := ds.s.openReadStream(ctx, name) + rc, err := ds.s.OpenReadStream(ctx, name) if err != nil { return nil, err } @@ -48,7 +48,7 @@ func (ds *datastore) openStatic(ctx context.Context, name *common.BlobName) (io. } func (ds *datastore) updateStatic(ctx context.Context, name *common.BlobName, updateStream io.Reader) error { - outputStream, err := ds.s.openWriteStream(ctx, name) + outputStream, err := ds.s.OpenWriteStream(ctx, name) if err != nil { return err } diff --git a/pkg/datastore/datastore_test.go b/pkg/datastore/datastore_test.go index 3f38e54..325e870 100644 --- a/pkg/datastore/datastore_test.go +++ b/pkg/datastore/datastore_test.go @@ -38,22 +38,22 @@ type mockStore struct { fDelete func(ctx context.Context, name *common.BlobName) error } -func (s *mockStore) kind() string { +func (s *mockStore) Kind() string { return s.fKind() } -func (s *mockStore) address() string { +func (s *mockStore) Address() string { return s.fAddress() } -func (s *mockStore) openReadStream(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) { +func (s *mockStore) OpenReadStream(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) { return s.fOpenReadStream(ctx, name) } -func (s *mockStore) openWriteStream(ctx context.Context, name *common.BlobName) (WriteCloseCanceller, error) { +func (s *mockStore) OpenWriteStream(ctx context.Context, name *common.BlobName) (WriteCloseCanceller, error) { return s.fOpenWriteStream(ctx, name) } -func (s *mockStore) exists(ctx context.Context, name *common.BlobName) (bool, error) { +func (s *mockStore) Exists(ctx context.Context, name *common.BlobName) (bool, error) { return s.fExists(ctx, name) } -func (s *mockStore) delete(ctx context.Context, name *common.BlobName) error { +func (s *mockStore) Delete(ctx context.Context, name *common.BlobName) error { return s.fDelete(ctx, name) } diff --git a/pkg/datastore/storage.go b/pkg/datastore/storage_backend.go similarity index 71% rename from pkg/datastore/storage.go rename to pkg/datastore/storage_backend.go index 044bb7b..a6e6dd4 100644 --- a/pkg/datastore/storage.go +++ b/pkg/datastore/storage_backend.go @@ -28,11 +28,11 @@ type WriteCloseCanceller interface { Cancel() } -type storage interface { - kind() string - address() string - openReadStream(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) - openWriteStream(ctx context.Context, name *common.BlobName) (WriteCloseCanceller, error) - exists(ctx context.Context, name *common.BlobName) (bool, error) - delete(ctx context.Context, name *common.BlobName) error +type StorageBackend interface { + Kind() string + Address() string + OpenReadStream(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) + OpenWriteStream(ctx context.Context, name *common.BlobName) (WriteCloseCanceller, error) + Exists(ctx context.Context, name *common.BlobName) (bool, error) + Delete(ctx context.Context, name *common.BlobName) error } diff --git a/pkg/datastore/storage_memory.go b/pkg/datastore/storage_backend_memory.go similarity index 84% rename from pkg/datastore/storage_memory.go rename to pkg/datastore/storage_backend_memory.go index bbf2af7..74cfb59 100644 --- a/pkg/datastore/storage_memory.go +++ b/pkg/datastore/storage_backend_memory.go @@ -41,24 +41,24 @@ type memory struct { rw sync.RWMutex } -var _ storage = (*memory)(nil) +var _ StorageBackend = (*memory)(nil) -func newStorageMemory() *memory { +func NewInMemoryStorageBackend() StorageBackend { return &memory{ bmap: make(map[string][]byte), block: make(map[string]struct{}), } } -func (m *memory) kind() string { +func (m *memory) Kind() string { return "Memory" } -func (m *memory) address() string { +func (m *memory) Address() string { return memoryPrefix } -func (m *memory) openReadStream(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) { +func (m *memory) OpenReadStream(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) { m.rw.RLock() defer m.rw.RUnlock() @@ -96,7 +96,7 @@ func (w *memoryWriteCloser) Close() error { return nil } -func (m *memory) openWriteStream(ctx context.Context, name *common.BlobName) (WriteCloseCanceller, error) { +func (m *memory) OpenWriteStream(ctx context.Context, name *common.BlobName) (WriteCloseCanceller, error) { m.rw.Lock() defer m.rw.Unlock() @@ -115,7 +115,7 @@ func (m *memory) openWriteStream(ctx context.Context, name *common.BlobName) (Wr }, nil } -func (m *memory) exists(ctx context.Context, n *common.BlobName) (bool, error) { +func (m *memory) Exists(ctx context.Context, n *common.BlobName) (bool, error) { m.rw.RLock() defer m.rw.RUnlock() @@ -126,7 +126,7 @@ func (m *memory) exists(ctx context.Context, n *common.BlobName) (bool, error) { return true, nil } -func (m *memory) delete(ctx context.Context, n *common.BlobName) error { +func (m *memory) Delete(ctx context.Context, n *common.BlobName) error { m.rw.Lock() defer m.rw.Unlock() diff --git a/pkg/datastore/storage_memory_test.go b/pkg/datastore/storage_memory_test.go index fce93ca..2c33fdc 100644 --- a/pkg/datastore/storage_memory_test.go +++ b/pkg/datastore/storage_memory_test.go @@ -23,13 +23,13 @@ import ( "github.com/stretchr/testify/suite" ) -func temporaryMemory(_ *testing.T) *memory { - return newStorageMemory() +func temporaryMemory(_ *testing.T) StorageBackend { + return NewInMemoryStorageBackend() } func TestMemoryStorageKind(t *testing.T) { m := temporaryMemory(t) - require.Equal(t, "Memory", m.kind()) + require.Equal(t, "Memory", m.Kind()) } func TestInMemoryDatastoreTestSuite(t *testing.T) { diff --git a/pkg/datastore/storage_test.go b/pkg/datastore/storage_test.go index 0b4cb0d..b90791a 100644 --- a/pkg/datastore/storage_test.go +++ b/pkg/datastore/storage_test.go @@ -28,8 +28,8 @@ import ( "github.com/stretchr/testify/require" ) -func allTestStorages(t *testing.T) []storage { - return []storage{ +func allTestStorages(t *testing.T) []StorageBackend { + return []StorageBackend{ // temporaryFS(t), // TODO: Extract to generic test suite? temporaryMemory(t), } @@ -37,8 +37,8 @@ func allTestStorages(t *testing.T) []storage { func TestStorageOpenFailureNotFound(t *testing.T) { for _, st := range allTestStorages(t) { - t.Run(st.kind(), func(t *testing.T) { - r, err := st.openReadStream(t.Context(), testutils.EmptyBlobNameStatic) + t.Run(st.Kind(), func(t *testing.T) { + r, err := st.OpenReadStream(t.Context(), testutils.EmptyBlobNameStatic) require.ErrorIs(t, err, ErrNotFound) require.Nil(t, r) }) @@ -47,15 +47,15 @@ func TestStorageOpenFailureNotFound(t *testing.T) { func TestStorageSaveOpenSuccess(t *testing.T) { for _, st := range allTestStorages(t) { - t.Run(st.kind(), func(t *testing.T) { - exists, err := st.exists(t.Context(), testutils.EmptyBlobNameStatic) + t.Run(st.Kind(), func(t *testing.T) { + exists, err := st.Exists(t.Context(), testutils.EmptyBlobNameStatic) require.NoError(t, err) require.False(t, exists) - w, err := st.openWriteStream(t.Context(), testutils.EmptyBlobNameStatic) + w, err := st.OpenWriteStream(t.Context(), testutils.EmptyBlobNameStatic) require.NoError(t, err) - exists, err = st.exists(t.Context(), testutils.EmptyBlobNameStatic) + exists, err = st.Exists(t.Context(), testutils.EmptyBlobNameStatic) require.NoError(t, err) require.False(t, exists) @@ -66,11 +66,11 @@ func TestStorageSaveOpenSuccess(t *testing.T) { err = w.Close() require.NoError(t, err) - exists, err = st.exists(t.Context(), testutils.EmptyBlobNameStatic) + exists, err = st.Exists(t.Context(), testutils.EmptyBlobNameStatic) require.NoError(t, err) require.True(t, exists) - r, err := st.openReadStream(t.Context(), testutils.EmptyBlobNameStatic) + r, err := st.OpenReadStream(t.Context(), testutils.EmptyBlobNameStatic) require.NoError(t, err) b, err := io.ReadAll(r) @@ -85,15 +85,15 @@ func TestStorageSaveOpenSuccess(t *testing.T) { func TestStorageSaveOpenCancelSuccess(t *testing.T) { for _, st := range allTestStorages(t) { - t.Run(st.kind(), func(t *testing.T) { - exists, err := st.exists(t.Context(), testutils.EmptyBlobNameStatic) + t.Run(st.Kind(), func(t *testing.T) { + exists, err := st.Exists(t.Context(), testutils.EmptyBlobNameStatic) require.NoError(t, err) require.False(t, exists) - w, err := st.openWriteStream(t.Context(), testutils.EmptyBlobNameStatic) + w, err := st.OpenWriteStream(t.Context(), testutils.EmptyBlobNameStatic) require.NoError(t, err) - exists, err = st.exists(t.Context(), testutils.EmptyBlobNameStatic) + exists, err = st.Exists(t.Context(), testutils.EmptyBlobNameStatic) require.NoError(t, err) require.False(t, exists) @@ -101,17 +101,17 @@ func TestStorageSaveOpenCancelSuccess(t *testing.T) { require.NoError(t, err) require.Equal(t, 12, n) - exists, err = st.exists(t.Context(), testutils.EmptyBlobNameStatic) + exists, err = st.Exists(t.Context(), testutils.EmptyBlobNameStatic) require.NoError(t, err) require.False(t, exists) w.Cancel() - exists, err = st.exists(t.Context(), testutils.EmptyBlobNameStatic) + exists, err = st.Exists(t.Context(), testutils.EmptyBlobNameStatic) require.NoError(t, err) require.False(t, exists) - r, err := st.openReadStream(t.Context(), testutils.EmptyBlobNameStatic) + r, err := st.OpenReadStream(t.Context(), testutils.EmptyBlobNameStatic) require.ErrorIs(t, err, ErrNotFound) require.Nil(t, r) }) @@ -120,7 +120,7 @@ func TestStorageSaveOpenCancelSuccess(t *testing.T) { func TestStorageDelete(t *testing.T) { for _, st := range allTestStorages(t) { - t.Run(st.kind(), func(t *testing.T) { + t.Run(st.Kind(), func(t *testing.T) { blobNames := []*common.BlobName{} blobDatas := [][]byte{} @@ -137,13 +137,13 @@ func TestStorageDelete(t *testing.T) { blobNames = append(blobNames, bn) blobDatas = append(blobDatas, []byte(d)) - err = st.delete(t.Context(), bn) + err = st.Delete(t.Context(), bn) require.ErrorIs(t, err, ErrNotFound) - w, err := st.openWriteStream(t.Context(), bn) + w, err := st.OpenWriteStream(t.Context(), bn) require.NoError(t, err) - exists, err := st.exists(t.Context(), bn) + exists, err := st.Exists(t.Context(), bn) require.NoError(t, err) require.False(t, exists) @@ -154,7 +154,7 @@ func TestStorageDelete(t *testing.T) { err = w.Close() require.NoError(t, err) - exists, err = st.exists(t.Context(), bn) + exists, err = st.Exists(t.Context(), bn) require.NoError(t, err) require.True(t, exists) } @@ -162,15 +162,15 @@ func TestStorageDelete(t *testing.T) { t.Run("delete blob", func(t *testing.T) { const toDelete = 1 - err := st.delete(t.Context(), blobNames[toDelete]) + err := st.Delete(t.Context(), blobNames[toDelete]) require.NoError(t, err) - err = st.delete(t.Context(), blobNames[toDelete]) + err = st.Delete(t.Context(), blobNames[toDelete]) require.ErrorIs(t, err, ErrNotFound) for i := range blobNames { t.Run(fmt.Sprintf("exists test %d", i), func(t *testing.T) { - exists, err := st.exists(t.Context(), blobNames[i]) + exists, err := st.Exists(t.Context(), blobNames[i]) require.NoError(t, err) require.Equal(t, i != toDelete, exists) }) @@ -182,13 +182,13 @@ func TestStorageDelete(t *testing.T) { func TestStorageTooManySimultaneousSaves(t *testing.T) { for _, st := range allTestStorages(t) { - t.Run(st.kind(), func(t *testing.T) { + t.Run(st.Kind(), func(t *testing.T) { // Start the first writer - w1, err := st.openWriteStream(t.Context(), testutils.EmptyBlobNameStatic) + w1, err := st.OpenWriteStream(t.Context(), testutils.EmptyBlobNameStatic) require.NoError(t, err) // Any attempt to update while the update is in progress should fail now - w2, err := st.openWriteStream(t.Context(), testutils.EmptyBlobNameStatic) + w2, err := st.OpenWriteStream(t.Context(), testutils.EmptyBlobNameStatic) require.ErrorIs(t, err, ErrUploadInProgress) require.Nil(t, w2) @@ -197,7 +197,7 @@ func TestStorageTooManySimultaneousSaves(t *testing.T) { require.NoError(t, err) // We should be able to successfully read the ingested data - r, err := st.openReadStream(t.Context(), testutils.EmptyBlobNameStatic) + r, err := st.OpenReadStream(t.Context(), testutils.EmptyBlobNameStatic) require.NoError(t, err) b, err := io.ReadAll(r) @@ -212,31 +212,31 @@ func TestStorageTooManySimultaneousSaves(t *testing.T) { func TestStorageSaveWhileDeleting(t *testing.T) { for _, st := range allTestStorages(t) { - t.Run(st.kind(), func(t *testing.T) { - w, err := st.openWriteStream(t.Context(), testutils.EmptyBlobNameStatic) + t.Run(st.Kind(), func(t *testing.T) { + w, err := st.OpenWriteStream(t.Context(), testutils.EmptyBlobNameStatic) require.NoError(t, err) err = w.Close() require.NoError(t, err) - exists, err := st.exists(t.Context(), testutils.EmptyBlobNameStatic) + exists, err := st.Exists(t.Context(), testutils.EmptyBlobNameStatic) require.NoError(t, err) require.True(t, exists) - w, err = st.openWriteStream(t.Context(), testutils.EmptyBlobNameStatic) + w, err = st.OpenWriteStream(t.Context(), testutils.EmptyBlobNameStatic) require.NoError(t, err) - err = st.delete(t.Context(), testutils.EmptyBlobNameStatic) + err = st.Delete(t.Context(), testutils.EmptyBlobNameStatic) require.NoError(t, err) - exists, err = st.exists(t.Context(), testutils.EmptyBlobNameStatic) + exists, err = st.Exists(t.Context(), testutils.EmptyBlobNameStatic) require.NoError(t, err) require.False(t, exists) err = w.Close() require.NoError(t, err) - exists, err = st.exists(t.Context(), testutils.EmptyBlobNameStatic) + exists, err = st.Exists(t.Context(), testutils.EmptyBlobNameStatic) require.NoError(t, err) require.True(t, exists) }) From 3b72ba55686e3a33d1057465b2433e9021317c57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20=C5=9Awi=C4=99cki?= Date: Fri, 31 Oct 2025 23:38:23 +0100 Subject: [PATCH 05/10] Update common datastore testing code * Move datastore testsuite to separate package * Extract storage backend test suite * Move test vectors generation to sub-folder Preparation for using test vectors through embed * Add embedable version of test vectors That way testsvectors can be used without os package. --- pkg/datastore/storage_test.go | 244 ---------------- .../inmemory_test.go} | 28 +- .../interface_testsuite.go | 147 +++++----- .../storage_backend_testsuite.go | 267 ++++++++++++++++++ .../blobtypes/dynamiclink/vectors_test.go | 53 +--- testvectors/embedded.go | 67 +++++ testvectors/{generate.go => generate/main.go} | 18 +- testvectors/internal/testcase.go | 32 +++ 8 files changed, 471 insertions(+), 385 deletions(-) delete mode 100644 pkg/datastore/storage_test.go rename pkg/{datastore/storage_memory_test.go => datastoreconformancetest/inmemory_test.go} (52%) rename pkg/{datastore => datastoreconformancetest}/interface_testsuite.go (58%) create mode 100644 pkg/datastoreconformancetest/storage_backend_testsuite.go create mode 100644 testvectors/embedded.go rename testvectors/{generate.go => generate/main.go} (98%) create mode 100644 testvectors/internal/testcase.go diff --git a/pkg/datastore/storage_test.go b/pkg/datastore/storage_test.go deleted file mode 100644 index b90791a..0000000 --- a/pkg/datastore/storage_test.go +++ /dev/null @@ -1,244 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package datastore - -import ( - "crypto/sha256" - "fmt" - "io" - "testing" - - "github.com/cinode/go-datastore/pkg/blobtypes" - "github.com/cinode/go-datastore/pkg/common" - "github.com/cinode/go-datastore/pkg/datastore/testutils" - "github.com/stretchr/testify/require" -) - -func allTestStorages(t *testing.T) []StorageBackend { - return []StorageBackend{ - // temporaryFS(t), // TODO: Extract to generic test suite? - temporaryMemory(t), - } -} - -func TestStorageOpenFailureNotFound(t *testing.T) { - for _, st := range allTestStorages(t) { - t.Run(st.Kind(), func(t *testing.T) { - r, err := st.OpenReadStream(t.Context(), testutils.EmptyBlobNameStatic) - require.ErrorIs(t, err, ErrNotFound) - require.Nil(t, r) - }) - } -} - -func TestStorageSaveOpenSuccess(t *testing.T) { - for _, st := range allTestStorages(t) { - t.Run(st.Kind(), func(t *testing.T) { - exists, err := st.Exists(t.Context(), testutils.EmptyBlobNameStatic) - require.NoError(t, err) - require.False(t, exists) - - w, err := st.OpenWriteStream(t.Context(), testutils.EmptyBlobNameStatic) - require.NoError(t, err) - - exists, err = st.Exists(t.Context(), testutils.EmptyBlobNameStatic) - require.NoError(t, err) - require.False(t, exists) - - n, err := w.Write([]byte("Hello world!")) - require.NoError(t, err) - require.Equal(t, 12, n) - - err = w.Close() - require.NoError(t, err) - - exists, err = st.Exists(t.Context(), testutils.EmptyBlobNameStatic) - require.NoError(t, err) - require.True(t, exists) - - r, err := st.OpenReadStream(t.Context(), testutils.EmptyBlobNameStatic) - require.NoError(t, err) - - b, err := io.ReadAll(r) - require.NoError(t, err) - require.Equal(t, []byte("Hello world!"), b) - - err = r.Close() - require.NoError(t, err) - }) - } -} - -func TestStorageSaveOpenCancelSuccess(t *testing.T) { - for _, st := range allTestStorages(t) { - t.Run(st.Kind(), func(t *testing.T) { - exists, err := st.Exists(t.Context(), testutils.EmptyBlobNameStatic) - require.NoError(t, err) - require.False(t, exists) - - w, err := st.OpenWriteStream(t.Context(), testutils.EmptyBlobNameStatic) - require.NoError(t, err) - - exists, err = st.Exists(t.Context(), testutils.EmptyBlobNameStatic) - require.NoError(t, err) - require.False(t, exists) - - n, err := w.Write([]byte("Hello world!")) - require.NoError(t, err) - require.Equal(t, 12, n) - - exists, err = st.Exists(t.Context(), testutils.EmptyBlobNameStatic) - require.NoError(t, err) - require.False(t, exists) - - w.Cancel() - - exists, err = st.Exists(t.Context(), testutils.EmptyBlobNameStatic) - require.NoError(t, err) - require.False(t, exists) - - r, err := st.OpenReadStream(t.Context(), testutils.EmptyBlobNameStatic) - require.ErrorIs(t, err, ErrNotFound) - require.Nil(t, r) - }) - } -} - -func TestStorageDelete(t *testing.T) { - for _, st := range allTestStorages(t) { - t.Run(st.Kind(), func(t *testing.T) { - blobNames := []*common.BlobName{} - blobDatas := [][]byte{} - - t.Run("generate test data", func(t *testing.T) { - for _, d := range []string{ - "first", - "second", - "third", - } { - h := sha256.Sum256([]byte(d)) - bn, err := common.BlobNameFromHashAndType(h[:], blobtypes.Static) - require.NoError(t, err) - - blobNames = append(blobNames, bn) - blobDatas = append(blobDatas, []byte(d)) - - err = st.Delete(t.Context(), bn) - require.ErrorIs(t, err, ErrNotFound) - - w, err := st.OpenWriteStream(t.Context(), bn) - require.NoError(t, err) - - exists, err := st.Exists(t.Context(), bn) - require.NoError(t, err) - require.False(t, exists) - - n, err := w.Write([]byte(d)) - require.NoError(t, err) - require.Equal(t, len(d), n) - - err = w.Close() - require.NoError(t, err) - - exists, err = st.Exists(t.Context(), bn) - require.NoError(t, err) - require.True(t, exists) - } - }) - - t.Run("delete blob", func(t *testing.T) { - const toDelete = 1 - err := st.Delete(t.Context(), blobNames[toDelete]) - require.NoError(t, err) - - err = st.Delete(t.Context(), blobNames[toDelete]) - require.ErrorIs(t, err, ErrNotFound) - - for i := range blobNames { - t.Run(fmt.Sprintf("exists test %d", i), func(t *testing.T) { - exists, err := st.Exists(t.Context(), blobNames[i]) - require.NoError(t, err) - require.Equal(t, i != toDelete, exists) - }) - } - }) - }) - } -} - -func TestStorageTooManySimultaneousSaves(t *testing.T) { - for _, st := range allTestStorages(t) { - t.Run(st.Kind(), func(t *testing.T) { - // Start the first writer - w1, err := st.OpenWriteStream(t.Context(), testutils.EmptyBlobNameStatic) - require.NoError(t, err) - - // Any attempt to update while the update is in progress should fail now - w2, err := st.OpenWriteStream(t.Context(), testutils.EmptyBlobNameStatic) - require.ErrorIs(t, err, ErrUploadInProgress) - require.Nil(t, w2) - - // Finish the original ingestion - err = w1.Close() - require.NoError(t, err) - - // We should be able to successfully read the ingested data - r, err := st.OpenReadStream(t.Context(), testutils.EmptyBlobNameStatic) - require.NoError(t, err) - - b, err := io.ReadAll(r) - require.NoError(t, err) - require.Equal(t, []byte{}, b) - - err = r.Close() - require.NoError(t, err) - }) - } -} - -func TestStorageSaveWhileDeleting(t *testing.T) { - for _, st := range allTestStorages(t) { - t.Run(st.Kind(), func(t *testing.T) { - w, err := st.OpenWriteStream(t.Context(), testutils.EmptyBlobNameStatic) - require.NoError(t, err) - - err = w.Close() - require.NoError(t, err) - - exists, err := st.Exists(t.Context(), testutils.EmptyBlobNameStatic) - require.NoError(t, err) - require.True(t, exists) - - w, err = st.OpenWriteStream(t.Context(), testutils.EmptyBlobNameStatic) - require.NoError(t, err) - - err = st.Delete(t.Context(), testutils.EmptyBlobNameStatic) - require.NoError(t, err) - - exists, err = st.Exists(t.Context(), testutils.EmptyBlobNameStatic) - require.NoError(t, err) - require.False(t, exists) - - err = w.Close() - require.NoError(t, err) - - exists, err = st.Exists(t.Context(), testutils.EmptyBlobNameStatic) - require.NoError(t, err) - require.True(t, exists) - }) - } -} diff --git a/pkg/datastore/storage_memory_test.go b/pkg/datastoreconformancetest/inmemory_test.go similarity index 52% rename from pkg/datastore/storage_memory_test.go rename to pkg/datastoreconformancetest/inmemory_test.go index 2c33fdc..178a3ab 100644 --- a/pkg/datastore/storage_memory_test.go +++ b/pkg/datastoreconformancetest/inmemory_test.go @@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -14,26 +14,26 @@ See the License for the specific language governing permissions and limitations under the License. */ -package datastore +package datastoreconformancetest_test import ( "testing" - "github.com/stretchr/testify/require" + "github.com/cinode/go-datastore/pkg/datastore" + "github.com/cinode/go-datastore/pkg/datastoreconformancetest" "github.com/stretchr/testify/suite" ) -func temporaryMemory(_ *testing.T) StorageBackend { - return NewInMemoryStorageBackend() -} - -func TestMemoryStorageKind(t *testing.T) { - m := temporaryMemory(t) - require.Equal(t, "Memory", m.Kind()) +func TestInMemoryDatastoreTestSuite(t *testing.T) { + suite.Run(t, datastoreconformancetest.NewDatastoreTestSuite( + func() (datastore.DS, error) { return datastore.InMemory(), nil }, + "Memory", + )) } -func TestInMemoryDatastoreTestSuite(t *testing.T) { - suite.Run(t, &TestSuite{ - CreateDS: func() (DS, error) { return InMemory(), nil }, - }) +func TestInMemoryStorageBackendTestsuite(t *testing.T) { + suite.Run(t, datastoreconformancetest.NewStorageBackendTestSuite( + func() (datastore.StorageBackend, error) { return datastore.NewInMemoryStorageBackend(), nil }, + "Memory", + )) } diff --git a/pkg/datastore/interface_testsuite.go b/pkg/datastoreconformancetest/interface_testsuite.go similarity index 58% rename from pkg/datastore/interface_testsuite.go rename to pkg/datastoreconformancetest/interface_testsuite.go index 7790a0f..4b8babb 100644 --- a/pkg/datastore/interface_testsuite.go +++ b/pkg/datastoreconformancetest/interface_testsuite.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package datastore +package datastoreconformancetest import ( "bytes" @@ -27,88 +27,103 @@ import ( "github.com/cinode/go-datastore/pkg/blobtypes" "github.com/cinode/go-datastore/pkg/common" + "github.com/cinode/go-datastore/pkg/datastore" "github.com/cinode/go-datastore/pkg/datastore/testutils" "github.com/cinode/go-datastore/pkg/internal/blobtypes/dynamiclink" "github.com/stretchr/testify/suite" ) -type TestSuite struct { +type DatastoreTestSuite struct { suite.Suite - // CreateDS is a function that creates a new datastore for each test. - CreateDS func() (DS, error) + // ds is the current datastore instance to test. + ds datastore.DS - // DS is the current datastore instance to test. - DS DS + // createDS is a function that creates a new datastore for each test. + createDS func() (datastore.DS, error) + + // expectedKind is the expected kind of the datastore + expectedKind string +} + +func NewDatastoreTestSuite(createDS func() (datastore.DS, error), expectedKind string) *DatastoreTestSuite { + return &DatastoreTestSuite{ + createDS: createDS, + expectedKind: expectedKind, + } } -func (s *TestSuite) SetupTest() { - ds, err := s.CreateDS() +func (s *DatastoreTestSuite) SetupTest() { + ds, err := s.createDS() s.Require().NoError(err) - s.DS = ds + s.ds = ds +} + +func (s *DatastoreTestSuite) TestDatastoreKind() { + s.Require().Equal(s.expectedKind, s.ds.Kind()) } -func (s *TestSuite) TestOpenNonExisting() { +func (s *DatastoreTestSuite) TestOpenNonExisting() { for _, name := range testutils.EmptyBlobNamesOfAllTypes { s.Run(fmt.Sprint(name.Type()), func() { - r, err := s.DS.Open(context.Background(), name) - s.Require().ErrorIs(err, ErrNotFound) + r, err := s.ds.Open(context.Background(), name) + s.Require().ErrorIs(err, datastore.ErrNotFound) s.Require().Nil(r) }) } } -func (s *TestSuite) TestOpenInvalidBlobType() { +func (s *DatastoreTestSuite) TestOpenInvalidBlobType() { bn, err := common.BlobNameFromHashAndType(sha256.New().Sum(nil), common.NewBlobType(0xFF)) s.Require().NoError(err) - r, err := s.DS.Open(context.Background(), bn) + r, err := s.ds.Open(context.Background(), bn) s.Require().ErrorIs(err, blobtypes.ErrUnknownBlobType) s.Require().Nil(r) - err = s.DS.Update(context.Background(), bn, bytes.NewBuffer(nil)) + err = s.ds.Update(context.Background(), bn, bytes.NewBuffer(nil)) s.Require().ErrorIs(err, blobtypes.ErrUnknownBlobType) } -func (s *TestSuite) TestBlobValidationFailed() { +func (s *DatastoreTestSuite) TestBlobValidationFailed() { for _, name := range testutils.EmptyBlobNamesOfAllTypes { s.Run(fmt.Sprint(name.Type()), func() { - err := s.DS.Update(context.Background(), name, bytes.NewReader([]byte("test"))) + err := s.ds.Update(context.Background(), name, bytes.NewReader([]byte("test"))) s.Require().ErrorIs(err, blobtypes.ErrValidationFailed) }) } } -func (s *TestSuite) TestSaveSuccessfulStatic() { +func (s *DatastoreTestSuite) TestSaveSuccessfulStatic() { for _, b := range testutils.TestBlobs { - exists, err := s.DS.Exists(context.Background(), b.Name) + exists, err := s.ds.Exists(context.Background(), b.Name) s.Require().NoError(err) s.Require().False(exists) - err = s.DS.Update(context.Background(), b.Name, bytes.NewReader(b.Data)) + err = s.ds.Update(context.Background(), b.Name, bytes.NewReader(b.Data)) s.Require().NoError(err) - exists, err = s.DS.Exists(context.Background(), b.Name) + exists, err = s.ds.Exists(context.Background(), b.Name) s.Require().NoError(err) s.Require().True(exists) // Overwrite with the same data must be fine - err = s.DS.Update(context.Background(), b.Name, bytes.NewReader(b.Data)) + err = s.ds.Update(context.Background(), b.Name, bytes.NewReader(b.Data)) s.Require().NoError(err) - exists, err = s.DS.Exists(context.Background(), b.Name) + exists, err = s.ds.Exists(context.Background(), b.Name) s.Require().NoError(err) s.Require().True(exists) // Overwrite with wrong data must fail - err = s.DS.Update(context.Background(), b.Name, bytes.NewReader(append([]byte{0x00}, b.Data...))) + err = s.ds.Update(context.Background(), b.Name, bytes.NewReader(append([]byte{0x00}, b.Data...))) s.Require().ErrorIs(err, blobtypes.ErrValidationFailed) - exists, err = s.DS.Exists(context.Background(), b.Name) + exists, err = s.ds.Exists(context.Background(), b.Name) s.Require().NoError(err) s.Require().True(exists) - r, err := s.DS.Open(context.Background(), b.Name) + r, err := s.ds.Open(context.Background(), b.Name) s.Require().NoError(err) data, err := io.ReadAll(r) @@ -120,32 +135,32 @@ func (s *TestSuite) TestSaveSuccessfulStatic() { } } -func (s *TestSuite) TestErrorWhileUpdating() { +func (s *DatastoreTestSuite) TestErrorWhileUpdating() { for i, b := range testutils.TestBlobs { s.Run(fmt.Sprint(i), func() { errRet := errors.New("test error") - err := s.DS.Update(context.Background(), b.Name, testutils.BReader(b.Data, func() error { + err := s.ds.Update(context.Background(), b.Name, testutils.BReader(b.Data, func() error { return errRet }, nil)) s.Require().ErrorIs(err, errRet) - exists, err := s.DS.Exists(context.Background(), b.Name) + exists, err := s.ds.Exists(context.Background(), b.Name) s.Require().NoError(err) s.Require().False(exists) }) } } -func (s *TestSuite) TestErrorWhileOverwriting() { +func (s *DatastoreTestSuite) TestErrorWhileOverwriting() { for i, b := range testutils.TestBlobs { s.Run(fmt.Sprint(i), func() { - err := s.DS.Update(context.Background(), b.Name, bytes.NewReader(b.Data)) + err := s.ds.Update(context.Background(), b.Name, bytes.NewReader(b.Data)) s.Require().NoError(err) errRet := errors.New("cancel") - err = s.DS.Update(context.Background(), b.Name, testutils.BReader(b.Data, func() error { - exists, err := s.DS.Exists(context.Background(), b.Name) + err = s.ds.Update(context.Background(), b.Name, testutils.BReader(b.Data, func() error { + exists, err := s.ds.Exists(context.Background(), b.Name) s.Require().NoError(err) s.Require().True(exists) @@ -154,11 +169,11 @@ func (s *TestSuite) TestErrorWhileOverwriting() { s.Require().ErrorIs(err, errRet) - exists, err := s.DS.Exists(context.Background(), b.Name) + exists, err := s.ds.Exists(context.Background(), b.Name) s.Require().NoError(err) s.Require().True(exists) - r, err := s.DS.Open(context.Background(), b.Name) + r, err := s.ds.Open(context.Background(), b.Name) s.Require().NoError(err) data, err := io.ReadAll(r) @@ -171,58 +186,58 @@ func (s *TestSuite) TestErrorWhileOverwriting() { } } -func (s *TestSuite) TestDeleteNonExisting() { +func (s *DatastoreTestSuite) TestDeleteNonExisting() { b := testutils.TestBlobs[0] - err := s.DS.Update(context.Background(), b.Name, bytes.NewReader(b.Data)) + err := s.ds.Update(context.Background(), b.Name, bytes.NewReader(b.Data)) s.Require().NoError(err) - err = s.DS.Delete(context.Background(), testutils.TestBlobs[1].Name) - s.Require().ErrorIs(err, ErrNotFound) + err = s.ds.Delete(context.Background(), testutils.TestBlobs[1].Name) + s.Require().ErrorIs(err, datastore.ErrNotFound) - exists, err := s.DS.Exists(context.Background(), b.Name) + exists, err := s.ds.Exists(context.Background(), b.Name) s.Require().NoError(err) s.Require().True(exists) } -func (s *TestSuite) TestDeleteExisting() { +func (s *DatastoreTestSuite) TestDeleteExisting() { b := testutils.TestBlobs[0] - err := s.DS.Update(context.Background(), b.Name, bytes.NewReader(b.Data)) + err := s.ds.Update(context.Background(), b.Name, bytes.NewReader(b.Data)) s.Require().NoError(err) - exists, err := s.DS.Exists(context.Background(), b.Name) + exists, err := s.ds.Exists(context.Background(), b.Name) s.Require().NoError(err) s.Require().True(exists) - err = s.DS.Delete(context.Background(), b.Name) + err = s.ds.Delete(context.Background(), b.Name) s.Require().NoError(err) - exists, err = s.DS.Exists(context.Background(), b.Name) + exists, err = s.ds.Exists(context.Background(), b.Name) s.Require().NoError(err) s.Require().False(exists) - r, err := s.DS.Open(context.Background(), b.Name) - s.Require().ErrorIs(err, ErrNotFound) + r, err := s.ds.Open(context.Background(), b.Name) + s.Require().ErrorIs(err, datastore.ErrNotFound) s.Require().Nil(r) } -func (s *TestSuite) TestGetKind() { - k := s.DS.Kind() +func (s *DatastoreTestSuite) TestGetKind() { + k := s.ds.Kind() s.Require().NotEmpty(k) } -func (s *TestSuite) TestAddress() { - address := s.DS.Address() +func (s *DatastoreTestSuite) TestAddress() { + address := s.ds.Address() s.Require().Regexp(`^[a-zA-Z0-9_-]+://`, address) } -func (s *TestSuite) TestSimultaneousReads() { +func (s *DatastoreTestSuite) TestSimultaneousReads() { const threadCnt = 10 const readCnt = 200 // Prepare data for _, b := range testutils.TestBlobs { - err := s.DS.Update(context.Background(), b.Name, bytes.NewReader(b.Data)) + err := s.ds.Update(context.Background(), b.Name, bytes.NewReader(b.Data)) s.Require().NoError(err) } @@ -235,7 +250,7 @@ func (s *TestSuite) TestSimultaneousReads() { for n := 0; n < readCnt; n++ { b := testutils.TestBlobs[(i+n)%len(testutils.TestBlobs)] - r, err := s.DS.Open(context.Background(), b.Name) + r, err := s.ds.Open(context.Background(), b.Name) s.Require().NoError(err) data, err := io.ReadAll(r) @@ -251,7 +266,7 @@ func (s *TestSuite) TestSimultaneousReads() { wg.Wait() } -func (s *TestSuite) TestSimultaneousUpdates() { +func (s *DatastoreTestSuite) TestSimultaneousUpdates() { const threadCnt = 3 b := testutils.TestBlobs[0] @@ -259,15 +274,15 @@ func (s *TestSuite) TestSimultaneousUpdates() { for range threadCnt { wg.Go(func() { - err := s.DS.Update(context.Background(), b.Name, bytes.NewReader(b.Data)) - if errors.Is(err, ErrUploadInProgress) { + err := s.ds.Update(context.Background(), b.Name, bytes.NewReader(b.Data)) + if errors.Is(err, datastore.ErrUploadInProgress) { // TODO: We should be able to handle this case return } s.Require().NoError(err) - exists, err := s.DS.Exists(context.Background(), b.Name) + exists, err := s.ds.Exists(context.Background(), b.Name) s.Require().NoError(err) s.Require().True(exists) }) @@ -275,11 +290,11 @@ func (s *TestSuite) TestSimultaneousUpdates() { wg.Wait() - exists, err := s.DS.Exists(context.Background(), b.Name) + exists, err := s.ds.Exists(context.Background(), b.Name) s.Require().NoError(err) s.Require().True(exists) - r, err := s.DS.Open(context.Background(), b.Name) + r, err := s.ds.Open(context.Background(), b.Name) s.Require().NoError(err) data, err := io.ReadAll(r) @@ -290,8 +305,8 @@ func (s *TestSuite) TestSimultaneousUpdates() { s.Require().NoError(err) } -func (s *TestSuite) updateDynamicLink(num int) { - err := s.DS.Update( +func (s *DatastoreTestSuite) updateDynamicLink(num int) { + err := s.ds.Update( context.Background(), testutils.DynamicLinkPropagationData[num].Name, bytes.NewReader(testutils.DynamicLinkPropagationData[num].Data), @@ -299,8 +314,8 @@ func (s *TestSuite) updateDynamicLink(num int) { s.Require().NoError(err) } -func (s *TestSuite) readDynamicLinkData() []byte { - r, err := s.DS.Open(context.Background(), testutils.DynamicLinkPropagationData[0].Name) +func (s *DatastoreTestSuite) readDynamicLinkData() []byte { + r, err := s.ds.Open(context.Background(), testutils.DynamicLinkPropagationData[0].Name) s.Require().NoError(err) dl, err := dynamiclink.FromPublicData(testutils.DynamicLinkPropagationData[0].Name, r) @@ -315,14 +330,14 @@ func (s *TestSuite) readDynamicLinkData() []byte { return elink } -func (s *TestSuite) expectDynamicLinkData(num int) { +func (s *DatastoreTestSuite) expectDynamicLinkData(num int) { s.Require().Equal( testutils.DynamicLinkPropagationData[num].Expected, s.readDynamicLinkData(), ) } -func (s *TestSuite) TestDynamicLinkPropagation() { +func (s *DatastoreTestSuite) TestDynamicLinkPropagation() { s.updateDynamicLink(0) s.expectDynamicLinkData(0) diff --git a/pkg/datastoreconformancetest/storage_backend_testsuite.go b/pkg/datastoreconformancetest/storage_backend_testsuite.go new file mode 100644 index 0000000..6b92c62 --- /dev/null +++ b/pkg/datastoreconformancetest/storage_backend_testsuite.go @@ -0,0 +1,267 @@ +/* +Copyright © 2025 Bartłomiej Święcki (byo) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package datastoreconformancetest + +import ( + "crypto/sha256" + "fmt" + "io" + "testing" + + "github.com/cinode/go-datastore/pkg/blobtypes" + "github.com/cinode/go-datastore/pkg/common" + "github.com/cinode/go-datastore/pkg/datastore" + "github.com/cinode/go-datastore/pkg/datastore/testutils" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type StorageBackendTestSuite struct { + suite.Suite + + // st is the current storage backend instance to test. + st datastore.StorageBackend + + // CreateStorage is a function that creates a new storage backend for each test. + CreateStorage func() (datastore.StorageBackend, error) + + // ExpectedKind is the expected kind of the storage backend + ExpectedKind string +} + +func NewStorageBackendTestSuite( + createStorage func() ( + datastore.StorageBackend, + error, + ), + expectedKind string, +) *StorageBackendTestSuite { + return &StorageBackendTestSuite{ + CreateStorage: createStorage, + ExpectedKind: expectedKind, + } +} + +func (s *StorageBackendTestSuite) SetupTest() { + st, err := s.CreateStorage() + s.Require().NoError(err) + s.st = st +} + +func (s *StorageBackendTestSuite) TestStorageKind() { + t := s.T() + + kind := s.st.Kind() + require.Equal(t, s.ExpectedKind, kind) +} + +func (s *StorageBackendTestSuite) TestStorageOpenFailureNotFound() { + t := s.T() + + r, err := s.st.OpenReadStream(t.Context(), testutils.EmptyBlobNameStatic) + require.ErrorIs(t, err, datastore.ErrNotFound) + require.Nil(t, r) +} + +func (s *StorageBackendTestSuite) TestStorageSaveOpenSuccess() { + t := s.T() + + exists, err := s.st.Exists(t.Context(), testutils.EmptyBlobNameStatic) + require.NoError(t, err) + require.False(t, exists) + + w, err := s.st.OpenWriteStream(t.Context(), testutils.EmptyBlobNameStatic) + require.NoError(t, err) + + exists, err = s.st.Exists(t.Context(), testutils.EmptyBlobNameStatic) + require.NoError(t, err) + require.False(t, exists) + + n, err := w.Write([]byte("Hello world!")) + require.NoError(t, err) + require.Equal(t, 12, n) + + err = w.Close() + require.NoError(t, err) + + exists, err = s.st.Exists(t.Context(), testutils.EmptyBlobNameStatic) + require.NoError(t, err) + require.True(t, exists) + + r, err := s.st.OpenReadStream(t.Context(), testutils.EmptyBlobNameStatic) + require.NoError(t, err) + + b, err := io.ReadAll(r) + require.NoError(t, err) + require.Equal(t, []byte("Hello world!"), b) + + err = r.Close() + require.NoError(t, err) +} + +func (s *StorageBackendTestSuite) TestStorageSaveOpenCancelSuccess() { + t := s.T() + + exists, err := s.st.Exists(t.Context(), testutils.EmptyBlobNameStatic) + require.NoError(t, err) + require.False(t, exists) + + w, err := s.st.OpenWriteStream(t.Context(), testutils.EmptyBlobNameStatic) + require.NoError(t, err) + + exists, err = s.st.Exists(t.Context(), testutils.EmptyBlobNameStatic) + require.NoError(t, err) + require.False(t, exists) + + n, err := w.Write([]byte("Hello world!")) + require.NoError(t, err) + require.Equal(t, 12, n) + + exists, err = s.st.Exists(t.Context(), testutils.EmptyBlobNameStatic) + require.NoError(t, err) + require.False(t, exists) + + w.Cancel() + + exists, err = s.st.Exists(t.Context(), testutils.EmptyBlobNameStatic) + require.NoError(t, err) + require.False(t, exists) + + r, err := s.st.OpenReadStream(t.Context(), testutils.EmptyBlobNameStatic) + require.ErrorIs(t, err, datastore.ErrNotFound) + require.Nil(t, r) +} + +func (s *StorageBackendTestSuite) TestStorageDelete() { + t := s.T() + + blobNames := []*common.BlobName{} + blobDatas := [][]byte{} + + t.Run("generate test data", func(t *testing.T) { + for _, d := range []string{ + "first", + "second", + "third", + } { + h := sha256.Sum256([]byte(d)) + bn, err := common.BlobNameFromHashAndType(h[:], blobtypes.Static) + require.NoError(t, err) + + blobNames = append(blobNames, bn) + blobDatas = append(blobDatas, []byte(d)) + + err = s.st.Delete(t.Context(), bn) + require.ErrorIs(t, err, datastore.ErrNotFound) + + w, err := s.st.OpenWriteStream(t.Context(), bn) + require.NoError(t, err) + + exists, err := s.st.Exists(t.Context(), bn) + require.NoError(t, err) + require.False(t, exists) + + n, err := w.Write([]byte(d)) + require.NoError(t, err) + require.Equal(t, len(d), n) + + err = w.Close() + require.NoError(t, err) + + exists, err = s.st.Exists(t.Context(), bn) + require.NoError(t, err) + require.True(t, exists) + } + }) + + t.Run("delete blob", func(t *testing.T) { + const toDelete = 1 + + err := s.st.Delete(t.Context(), blobNames[toDelete]) + require.NoError(t, err) + + err = s.st.Delete(t.Context(), blobNames[toDelete]) + require.ErrorIs(t, err, datastore.ErrNotFound) + + for i := range blobNames { + t.Run(fmt.Sprintf("exists test %d", i), func(t *testing.T) { + exists, err := s.st.Exists(t.Context(), blobNames[i]) + require.NoError(t, err) + require.Equal(t, i != toDelete, exists) + }) + } + }) +} + +func (s *StorageBackendTestSuite) TestStorageTooManySimultaneousSaves() { + t := s.T() + + // Start the first writer + w1, err := s.st.OpenWriteStream(t.Context(), testutils.EmptyBlobNameStatic) + require.NoError(t, err) + + // Any attempt to update while the update is in progress should fail now + w2, err := s.st.OpenWriteStream(t.Context(), testutils.EmptyBlobNameStatic) + require.ErrorIs(t, err, datastore.ErrUploadInProgress) + require.Nil(t, w2) + + // Finish the original ingestion + err = w1.Close() + require.NoError(t, err) + + // We should be able to successfully read the ingested data + r, err := s.st.OpenReadStream(t.Context(), testutils.EmptyBlobNameStatic) + require.NoError(t, err) + + b, err := io.ReadAll(r) + require.NoError(t, err) + require.Equal(t, []byte{}, b) + + err = r.Close() + require.NoError(t, err) +} + +func (s *StorageBackendTestSuite) TestStorageSaveWhileDeleting() { + t := s.T() + + w, err := s.st.OpenWriteStream(t.Context(), testutils.EmptyBlobNameStatic) + require.NoError(t, err) + + err = w.Close() + require.NoError(t, err) + + exists, err := s.st.Exists(t.Context(), testutils.EmptyBlobNameStatic) + require.NoError(t, err) + require.True(t, exists) + + w, err = s.st.OpenWriteStream(t.Context(), testutils.EmptyBlobNameStatic) + require.NoError(t, err) + + err = s.st.Delete(t.Context(), testutils.EmptyBlobNameStatic) + require.NoError(t, err) + + exists, err = s.st.Exists(t.Context(), testutils.EmptyBlobNameStatic) + require.NoError(t, err) + require.False(t, exists) + + err = w.Close() + require.NoError(t, err) + + exists, err = s.st.Exists(t.Context(), testutils.EmptyBlobNameStatic) + require.NoError(t, err) + require.True(t, exists) +} diff --git a/pkg/internal/blobtypes/dynamiclink/vectors_test.go b/pkg/internal/blobtypes/dynamiclink/vectors_test.go index 31d6f87..5f562cc 100644 --- a/pkg/internal/blobtypes/dynamiclink/vectors_test.go +++ b/pkg/internal/blobtypes/dynamiclink/vectors_test.go @@ -18,52 +18,16 @@ package dynamiclink import ( "bytes" - "encoding/json" "io" - "io/fs" - "os" - "path/filepath" - "strings" "testing" "github.com/cinode/go-datastore/pkg/common" + "github.com/cinode/go-datastore/testvectors" "github.com/stretchr/testify/require" ) func TestVectors(t *testing.T) { - err := filepath.WalkDir("../../../../testvectors/dynamic", func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if d.IsDir() || !strings.HasSuffix(path, ".json") { - return nil - } - - testCase := struct { - Name string `json:"name"` - Description string `json:"description"` - GoErrorContains string `json:"go_error_contains"` - Details []string `json:"details"` - BlobName []byte `json:"blob_name"` - EncryptionKey []byte `json:"encryption_key"` - UpdateDataset []byte `json:"update_dataset"` - DecryptedDataset []byte `json:"decrypted_dataset"` - ValidPublicly bool `json:"valid_publicly"` - ValidPrivately bool `json:"valid_privately"` - }{} - - data, err := os.ReadFile(path) - if err != nil { - return err - } - - err = json.Unmarshal(data, &testCase) - if err != nil { - return err - } - - det := strings.Join(testCase.Details, "\n") - + for testCase := range testvectors.AllTestCases { t.Run(testCase.Name, func(t *testing.T) { t.Run("validate public scope", func(t *testing.T) { err := func() error { @@ -90,9 +54,9 @@ func TestVectors(t *testing.T) { }() if testCase.ValidPublicly { - require.NoError(t, err, det) + require.NoError(t, err, testCase.Details) } else { - require.ErrorContains(t, err, testCase.GoErrorContains, det) + require.ErrorContains(t, err, testCase.GoErrorContains, testCase.Details) } }) @@ -130,14 +94,11 @@ func TestVectors(t *testing.T) { }() if testCase.ValidPrivately { - require.NoError(t, err, det) + require.NoError(t, err, testCase.Details) } else { - require.ErrorContains(t, err, testCase.GoErrorContains, det) + require.ErrorContains(t, err, testCase.GoErrorContains, testCase.Details) } }) }) - - return nil - }) - require.NoError(t, err) + } } diff --git a/testvectors/embedded.go b/testvectors/embedded.go new file mode 100644 index 0000000..7d7e248 --- /dev/null +++ b/testvectors/embedded.go @@ -0,0 +1,67 @@ +/* +Copyright © 2025 Bartłomiej Święcki (byo) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package testvectors + +import ( + "embed" + "encoding/json" + "io/fs" + "strings" + + "github.com/cinode/go-datastore/pkg/utilities/golang" + "github.com/cinode/go-datastore/testvectors/internal" +) + +//go:embed dynamic/* +var testVectorsData embed.FS + +type TestCase = internal.TestCase + +var parsedTestCases = func() []*TestCase { + var testCases []*TestCase + + fs.WalkDir(testVectorsData, "dynamic", func(path string, d fs.DirEntry, err error) error { + golang.Must(err, err) + + if d.IsDir() || !strings.HasSuffix(path, ".json") { + return nil + } + + data := golang.Must(fs.ReadFile(testVectorsData, path)) + + testCase := TestCase{} + + err = json.Unmarshal(data, &testCase) + golang.Must(err, err) + + testCase.Details = strings.Join(testCase.DetailsLines, "\n") + + testCases = append(testCases, &testCase) + + return nil + }) + + return testCases +}() + +func AllTestCases(yield func(tc *TestCase) bool) { + for _, tc := range parsedTestCases { + if !yield(tc) { + return + } + } +} diff --git a/testvectors/generate.go b/testvectors/generate/main.go similarity index 98% rename from testvectors/generate.go rename to testvectors/generate/main.go index 88bed68..7e3806e 100644 --- a/testvectors/generate.go +++ b/testvectors/generate/main.go @@ -39,6 +39,7 @@ import ( "path/filepath" "strings" + "github.com/cinode/go-datastore/testvectors/internal" "golang.org/x/crypto/chacha20" ) @@ -46,20 +47,7 @@ func main() { generateTestVectorsForDynamicLinks() } -type TestCase struct { - Name string `json:"name"` - Description string `json:"description"` - Details string `json:"-"` - DetailsLines []string `json:"details,omitempty"` - WhenAdded string `json:"added_at"` - BlobName []byte `json:"blob_name"` - EncryptionKey []byte `json:"encryption_key"` - UpdateDataset []byte `json:"update_dataset"` - DecryptedDataset []byte `json:"decrypted_dataset"` - ValidPublicly bool `json:"valid_publicly"` - ValidPrivately bool `json:"valid_privately"` - GoErrorContains string `json:"go_error_contains,omitempty"` -} +type TestCase = internal.TestCase type gp struct { reservedByte *byte @@ -312,7 +300,7 @@ var ( ) func writeLinkData(tc TestCase) error { - fName := tc.Name + ".json" + fName := "../" + tc.Name + ".json" err := os.MkdirAll(filepath.Dir(fName), 0o777) if err != nil { return err diff --git a/testvectors/internal/testcase.go b/testvectors/internal/testcase.go new file mode 100644 index 0000000..34062ba --- /dev/null +++ b/testvectors/internal/testcase.go @@ -0,0 +1,32 @@ +/* +Copyright © 2025 Bartłomiej Święcki (byo) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package internal + +type TestCase struct { + Name string `json:"name"` + Description string `json:"description"` + Details string `json:"-"` + DetailsLines []string `json:"details,omitempty"` + WhenAdded string `json:"added_at"` + BlobName []byte `json:"blob_name"` + EncryptionKey []byte `json:"encryption_key"` + UpdateDataset []byte `json:"update_dataset"` + DecryptedDataset []byte `json:"decrypted_dataset"` + ValidPublicly bool `json:"valid_publicly"` + ValidPrivately bool `json:"valid_privately"` + GoErrorContains string `json:"go_error_contains,omitempty"` +} From 016cb62d32ba7e2750ad63dc4051dcac2345f073 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20=C5=9Awi=C4=99cki?= Date: Fri, 31 Oct 2025 23:38:23 +0100 Subject: [PATCH 06/10] Add picotestify --- go.mod | 9 +- go.sum | 12 +- pkg/blobtypes/list_test.go | 2 +- pkg/common/auth_info_test.go | 4 +- pkg/common/blob_keys_test.go | 4 +- pkg/common/blob_name_test.go | 6 +- pkg/common/blob_type_test.go | 8 +- pkg/datastore/datastore_test.go | 2 +- pkg/datastore/testutils/testblobs_test.go | 2 +- pkg/datastore/testutils/testutils_test.go | 2 +- pkg/datastoreconformancetest/inmemory_test.go | 2 +- .../interface_testsuite.go | 283 +++++++++-------- .../storage_backend_testsuite.go | 8 +- .../blobtypes/dynamiclink/public_test.go | 2 +- .../blobtypes/dynamiclink/publisher_test.go | 6 +- .../blobtypes/dynamiclink/utils_test.go | 4 +- .../blobtypes/dynamiclink/vectors_test.go | 2 +- pkg/internal/picotestify/assert/assert.go | 299 ++++++++++++++++++ pkg/internal/picotestify/require/require.go | 153 +++++++++ pkg/internal/picotestify/suite/suite.go | 87 +++++ .../cipherfactory/cipher_factory_test.go | 2 +- .../utilities/cipherfactory/generator_test.go | 2 +- .../utilities/errreader/errreader_test.go | 2 +- .../utilities/headwriter/headwriter_test.go | 4 +- .../hashvalidatingreader_test.go | 6 +- .../validatingreader/oneofcheckreader_test.go | 2 +- pkg/utilities/golang/assert_test.go | 4 +- pkg/utilities/golang/must_test.go | 4 +- 28 files changed, 739 insertions(+), 184 deletions(-) create mode 100644 pkg/internal/picotestify/assert/assert.go create mode 100644 pkg/internal/picotestify/require/require.go create mode 100644 pkg/internal/picotestify/suite/suite.go diff --git a/go.mod b/go.mod index 337b9cb..981ecfb 100644 --- a/go.mod +++ b/go.mod @@ -4,13 +4,8 @@ go 1.25.3 require ( github.com/jbenet/go-base58 v0.0.0-20150317085156-6237cf65f3a6 - github.com/stretchr/testify v1.11.1 golang.org/x/crypto v0.43.0 + golang.org/x/exp v0.0.0-20251017212417-90e834f514db ) -require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/sys v0.37.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) +require golang.org/x/sys v0.37.0 // indirect diff --git a/go.sum b/go.sum index 5c44e89..a5acc94 100644 --- a/go.sum +++ b/go.sum @@ -1,16 +1,8 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/jbenet/go-base58 v0.0.0-20150317085156-6237cf65f3a6 h1:4zOlv2my+vf98jT1nQt4bT/yKWUImevYPJ2H344CloE= github.com/jbenet/go-base58 v0.0.0-20150317085156-6237cf65f3a6/go.mod h1:r/8JmuR0qjuCiEhAolkfvdZgmPiHTnJaG0UXCSeR1Zo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/exp v0.0.0-20251017212417-90e834f514db h1:by6IehL4BH5k3e3SJmcoNbOobMey2SLpAF79iPOEBvw= +golang.org/x/exp v0.0.0-20251017212417-90e834f514db/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/blobtypes/list_test.go b/pkg/blobtypes/list_test.go index 25c95e4..8f0e93a 100644 --- a/pkg/blobtypes/list_test.go +++ b/pkg/blobtypes/list_test.go @@ -20,7 +20,7 @@ import ( "testing" "github.com/cinode/go-datastore/pkg/common" - "github.com/stretchr/testify/require" + "github.com/cinode/go-datastore/pkg/internal/picotestify/require" ) func TestToName(t *testing.T) { diff --git a/pkg/common/auth_info_test.go b/pkg/common/auth_info_test.go index a875052..c2f4eb1 100644 --- a/pkg/common/auth_info_test.go +++ b/pkg/common/auth_info_test.go @@ -1,5 +1,5 @@ /* -Copyright © 2023 Bartłomiej Święcki (byo) +Copyright © 2025 Bartłomiej Święcki (byo) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ package common import ( "testing" - "github.com/stretchr/testify/require" + "github.com/cinode/go-datastore/pkg/internal/picotestify/require" ) func TestAuthInfo(t *testing.T) { diff --git a/pkg/common/blob_keys_test.go b/pkg/common/blob_keys_test.go index 28b32df..af2f5ac 100644 --- a/pkg/common/blob_keys_test.go +++ b/pkg/common/blob_keys_test.go @@ -1,5 +1,5 @@ /* -Copyright © 2023 Bartłomiej Święcki (byo) +Copyright © 2025 Bartłomiej Święcki (byo) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ package common import ( "testing" - "github.com/stretchr/testify/require" + "github.com/cinode/go-datastore/pkg/internal/picotestify/require" ) func TestBlobKey(t *testing.T) { diff --git a/pkg/common/blob_name_test.go b/pkg/common/blob_name_test.go index f60e8c2..5d0ec07 100644 --- a/pkg/common/blob_name_test.go +++ b/pkg/common/blob_name_test.go @@ -1,5 +1,5 @@ /* -Copyright © 2023 Bartłomiej Święcki (byo) +Copyright © 2025 Bartłomiej Święcki (byo) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -21,8 +21,8 @@ import ( "fmt" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "github.com/cinode/go-datastore/pkg/internal/picotestify/assert" + "github.com/cinode/go-datastore/pkg/internal/picotestify/require" ) func TestBlobName(t *testing.T) { diff --git a/pkg/common/blob_type_test.go b/pkg/common/blob_type_test.go index c05529c..98f7311 100644 --- a/pkg/common/blob_type_test.go +++ b/pkg/common/blob_type_test.go @@ -1,5 +1,5 @@ /* -Copyright © 2023 Bartłomiej Święcki (byo) +Copyright © 2025 Bartłomiej Święcki (byo) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,11 +19,11 @@ package common import ( "testing" - "github.com/stretchr/testify/require" + "github.com/cinode/go-datastore/pkg/internal/picotestify/require" ) func TestBlobType(t *testing.T) { tp := NewBlobType(0x77) - require.EqualValues(t, tp.t, 0x77) - require.EqualValues(t, tp.IDByte(), 0x77) + require.Equal(t, tp.t, byte(0x77)) + require.Equal(t, tp.IDByte(), byte(0x77)) } diff --git a/pkg/datastore/datastore_test.go b/pkg/datastore/datastore_test.go index 325e870..2c74e6f 100644 --- a/pkg/datastore/datastore_test.go +++ b/pkg/datastore/datastore_test.go @@ -26,7 +26,7 @@ import ( "github.com/cinode/go-datastore/pkg/blobtypes" "github.com/cinode/go-datastore/pkg/common" "github.com/cinode/go-datastore/pkg/datastore/testutils" - "github.com/stretchr/testify/require" + "github.com/cinode/go-datastore/pkg/internal/picotestify/require" ) type mockStore struct { diff --git a/pkg/datastore/testutils/testblobs_test.go b/pkg/datastore/testutils/testblobs_test.go index cae2013..396f901 100644 --- a/pkg/datastore/testutils/testblobs_test.go +++ b/pkg/datastore/testutils/testblobs_test.go @@ -19,7 +19,7 @@ package testutils import ( "testing" - "github.com/stretchr/testify/require" + "github.com/cinode/go-datastore/pkg/internal/picotestify/require" ) func TestTestBlobs(t *testing.T) { diff --git a/pkg/datastore/testutils/testutils_test.go b/pkg/datastore/testutils/testutils_test.go index c917c1a..85d7cf7 100644 --- a/pkg/datastore/testutils/testutils_test.go +++ b/pkg/datastore/testutils/testutils_test.go @@ -21,7 +21,7 @@ import ( "io" "testing" - "github.com/stretchr/testify/require" + "github.com/cinode/go-datastore/pkg/internal/picotestify/require" ) func TestBReaderOnRead(t *testing.T) { diff --git a/pkg/datastoreconformancetest/inmemory_test.go b/pkg/datastoreconformancetest/inmemory_test.go index 178a3ab..0c9d269 100644 --- a/pkg/datastoreconformancetest/inmemory_test.go +++ b/pkg/datastoreconformancetest/inmemory_test.go @@ -21,7 +21,7 @@ import ( "github.com/cinode/go-datastore/pkg/datastore" "github.com/cinode/go-datastore/pkg/datastoreconformancetest" - "github.com/stretchr/testify/suite" + "github.com/cinode/go-datastore/pkg/internal/picotestify/suite" ) func TestInMemoryDatastoreTestSuite(t *testing.T) { diff --git a/pkg/datastoreconformancetest/interface_testsuite.go b/pkg/datastoreconformancetest/interface_testsuite.go index 4b8babb..ae3b9de 100644 --- a/pkg/datastoreconformancetest/interface_testsuite.go +++ b/pkg/datastoreconformancetest/interface_testsuite.go @@ -18,19 +18,20 @@ package datastoreconformancetest import ( "bytes" - "context" "crypto/sha256" "errors" "fmt" "io" "sync" + "testing" "github.com/cinode/go-datastore/pkg/blobtypes" "github.com/cinode/go-datastore/pkg/common" "github.com/cinode/go-datastore/pkg/datastore" "github.com/cinode/go-datastore/pkg/datastore/testutils" "github.com/cinode/go-datastore/pkg/internal/blobtypes/dynamiclink" - "github.com/stretchr/testify/suite" + "github.com/cinode/go-datastore/pkg/internal/picotestify/require" + "github.com/cinode/go-datastore/pkg/internal/picotestify/suite" ) type DatastoreTestSuite struct { @@ -55,190 +56,212 @@ func NewDatastoreTestSuite(createDS func() (datastore.DS, error), expectedKind s func (s *DatastoreTestSuite) SetupTest() { ds, err := s.createDS() - s.Require().NoError(err) + require.NoError(s.T(), err) s.ds = ds } func (s *DatastoreTestSuite) TestDatastoreKind() { - s.Require().Equal(s.expectedKind, s.ds.Kind()) + require.Equal(s.T(), s.expectedKind, s.ds.Kind()) } func (s *DatastoreTestSuite) TestOpenNonExisting() { + t := s.T() + for _, name := range testutils.EmptyBlobNamesOfAllTypes { - s.Run(fmt.Sprint(name.Type()), func() { - r, err := s.ds.Open(context.Background(), name) - s.Require().ErrorIs(err, datastore.ErrNotFound) - s.Require().Nil(r) + t.Run(fmt.Sprint(name.Type()), func(t *testing.T) { + r, err := s.ds.Open(t.Context(), name) + require.ErrorIs(t, err, datastore.ErrNotFound) + require.Nil(t, r) }) } } func (s *DatastoreTestSuite) TestOpenInvalidBlobType() { + t := s.T() + bn, err := common.BlobNameFromHashAndType(sha256.New().Sum(nil), common.NewBlobType(0xFF)) - s.Require().NoError(err) + require.NoError(t, err) - r, err := s.ds.Open(context.Background(), bn) - s.Require().ErrorIs(err, blobtypes.ErrUnknownBlobType) - s.Require().Nil(r) + r, err := s.ds.Open(t.Context(), bn) + require.ErrorIs(t, err, blobtypes.ErrUnknownBlobType) + require.Nil(t, r) - err = s.ds.Update(context.Background(), bn, bytes.NewBuffer(nil)) - s.Require().ErrorIs(err, blobtypes.ErrUnknownBlobType) + err = s.ds.Update(t.Context(), bn, bytes.NewBuffer(nil)) + require.ErrorIs(t, err, blobtypes.ErrUnknownBlobType) } func (s *DatastoreTestSuite) TestBlobValidationFailed() { + t := s.T() + for _, name := range testutils.EmptyBlobNamesOfAllTypes { - s.Run(fmt.Sprint(name.Type()), func() { - err := s.ds.Update(context.Background(), name, bytes.NewReader([]byte("test"))) - s.Require().ErrorIs(err, blobtypes.ErrValidationFailed) + t.Run(fmt.Sprint(name.Type()), func(t *testing.T) { + err := s.ds.Update(t.Context(), name, bytes.NewReader([]byte("test"))) + require.ErrorIs(t, err, blobtypes.ErrValidationFailed) }) } } func (s *DatastoreTestSuite) TestSaveSuccessfulStatic() { + t := s.T() + for _, b := range testutils.TestBlobs { - exists, err := s.ds.Exists(context.Background(), b.Name) - s.Require().NoError(err) - s.Require().False(exists) + exists, err := s.ds.Exists(t.Context(), b.Name) + require.NoError(t, err) + require.False(t, exists) - err = s.ds.Update(context.Background(), b.Name, bytes.NewReader(b.Data)) - s.Require().NoError(err) + err = s.ds.Update(t.Context(), b.Name, bytes.NewReader(b.Data)) + require.NoError(t, err) - exists, err = s.ds.Exists(context.Background(), b.Name) - s.Require().NoError(err) - s.Require().True(exists) + exists, err = s.ds.Exists(t.Context(), b.Name) + require.NoError(t, err) + require.True(t, exists) // Overwrite with the same data must be fine - err = s.ds.Update(context.Background(), b.Name, bytes.NewReader(b.Data)) - s.Require().NoError(err) + err = s.ds.Update(t.Context(), b.Name, bytes.NewReader(b.Data)) + require.NoError(t, err) - exists, err = s.ds.Exists(context.Background(), b.Name) - s.Require().NoError(err) - s.Require().True(exists) + exists, err = s.ds.Exists(t.Context(), b.Name) + require.NoError(t, err) + require.True(t, exists) // Overwrite with wrong data must fail - err = s.ds.Update(context.Background(), b.Name, bytes.NewReader(append([]byte{0x00}, b.Data...))) - s.Require().ErrorIs(err, blobtypes.ErrValidationFailed) + err = s.ds.Update(t.Context(), b.Name, bytes.NewReader(append([]byte{0x00}, b.Data...))) + require.ErrorIs(t, err, blobtypes.ErrValidationFailed) - exists, err = s.ds.Exists(context.Background(), b.Name) - s.Require().NoError(err) - s.Require().True(exists) + exists, err = s.ds.Exists(t.Context(), b.Name) + require.NoError(t, err) + require.True(t, exists) - r, err := s.ds.Open(context.Background(), b.Name) - s.Require().NoError(err) + r, err := s.ds.Open(t.Context(), b.Name) + require.NoError(t, err) data, err := io.ReadAll(r) - s.Require().NoError(err) - s.Require().Equal(b.Data, data) + require.NoError(t, err) + require.Equal(t, b.Data, data) err = r.Close() - s.Require().NoError(err) + require.NoError(t, err) } } func (s *DatastoreTestSuite) TestErrorWhileUpdating() { + t := s.T() + for i, b := range testutils.TestBlobs { - s.Run(fmt.Sprint(i), func() { + t.Run(fmt.Sprint(i), func(t *testing.T) { errRet := errors.New("test error") - err := s.ds.Update(context.Background(), b.Name, testutils.BReader(b.Data, func() error { + err := s.ds.Update(t.Context(), b.Name, testutils.BReader(b.Data, func() error { return errRet }, nil)) - s.Require().ErrorIs(err, errRet) + require.ErrorIs(t, err, errRet) - exists, err := s.ds.Exists(context.Background(), b.Name) - s.Require().NoError(err) - s.Require().False(exists) + exists, err := s.ds.Exists(t.Context(), b.Name) + require.NoError(t, err) + require.False(t, exists) }) } } func (s *DatastoreTestSuite) TestErrorWhileOverwriting() { + t := s.T() + for i, b := range testutils.TestBlobs { - s.Run(fmt.Sprint(i), func() { - err := s.ds.Update(context.Background(), b.Name, bytes.NewReader(b.Data)) - s.Require().NoError(err) + t.Run(fmt.Sprint(i), func(t *testing.T) { + err := s.ds.Update(t.Context(), b.Name, bytes.NewReader(b.Data)) + require.NoError(t, err) errRet := errors.New("cancel") - err = s.ds.Update(context.Background(), b.Name, testutils.BReader(b.Data, func() error { - exists, err := s.ds.Exists(context.Background(), b.Name) - s.Require().NoError(err) - s.Require().True(exists) + err = s.ds.Update(t.Context(), b.Name, testutils.BReader(b.Data, func() error { + exists, err := s.ds.Exists(t.Context(), b.Name) + require.NoError(t, err) + require.True(t, exists) return errRet }, nil)) - s.Require().ErrorIs(err, errRet) + require.ErrorIs(t, err, errRet) - exists, err := s.ds.Exists(context.Background(), b.Name) - s.Require().NoError(err) - s.Require().True(exists) + exists, err := s.ds.Exists(t.Context(), b.Name) + require.NoError(t, err) + require.True(t, exists) - r, err := s.ds.Open(context.Background(), b.Name) - s.Require().NoError(err) + r, err := s.ds.Open(t.Context(), b.Name) + require.NoError(t, err) data, err := io.ReadAll(r) - s.Require().NoError(err) - s.Require().Equal(b.Data, data) + require.NoError(t, err) + require.Equal(t, b.Data, data) err = r.Close() - s.Require().NoError(err) + require.NoError(t, err) }) } } func (s *DatastoreTestSuite) TestDeleteNonExisting() { + t := s.T() + b := testutils.TestBlobs[0] - err := s.ds.Update(context.Background(), b.Name, bytes.NewReader(b.Data)) - s.Require().NoError(err) + err := s.ds.Update(t.Context(), b.Name, bytes.NewReader(b.Data)) + require.NoError(t, err) - err = s.ds.Delete(context.Background(), testutils.TestBlobs[1].Name) - s.Require().ErrorIs(err, datastore.ErrNotFound) + err = s.ds.Delete(t.Context(), testutils.TestBlobs[1].Name) + require.ErrorIs(t, err, datastore.ErrNotFound) - exists, err := s.ds.Exists(context.Background(), b.Name) - s.Require().NoError(err) - s.Require().True(exists) + exists, err := s.ds.Exists(t.Context(), b.Name) + require.NoError(t, err) + require.True(t, exists) } func (s *DatastoreTestSuite) TestDeleteExisting() { + t := s.T() + b := testutils.TestBlobs[0] - err := s.ds.Update(context.Background(), b.Name, bytes.NewReader(b.Data)) - s.Require().NoError(err) + err := s.ds.Update(t.Context(), b.Name, bytes.NewReader(b.Data)) + require.NoError(t, err) - exists, err := s.ds.Exists(context.Background(), b.Name) - s.Require().NoError(err) - s.Require().True(exists) + exists, err := s.ds.Exists(t.Context(), b.Name) + require.NoError(t, err) + require.True(t, exists) - err = s.ds.Delete(context.Background(), b.Name) - s.Require().NoError(err) + err = s.ds.Delete(t.Context(), b.Name) + require.NoError(t, err) - exists, err = s.ds.Exists(context.Background(), b.Name) - s.Require().NoError(err) - s.Require().False(exists) + exists, err = s.ds.Exists(t.Context(), b.Name) + require.NoError(t, err) + require.False(t, exists) - r, err := s.ds.Open(context.Background(), b.Name) - s.Require().ErrorIs(err, datastore.ErrNotFound) - s.Require().Nil(r) + r, err := s.ds.Open(t.Context(), b.Name) + require.ErrorIs(t, err, datastore.ErrNotFound) + require.Nil(t, r) } func (s *DatastoreTestSuite) TestGetKind() { + t := s.T() + k := s.ds.Kind() - s.Require().NotEmpty(k) + require.NotEmpty(t, k) } func (s *DatastoreTestSuite) TestAddress() { + t := s.T() + address := s.ds.Address() - s.Require().Regexp(`^[a-zA-Z0-9_-]+://`, address) + require.Regexp(t, `^[a-zA-Z0-9_-]+://`, address) } func (s *DatastoreTestSuite) TestSimultaneousReads() { + t := s.T() + const threadCnt = 10 const readCnt = 200 // Prepare data for _, b := range testutils.TestBlobs { - err := s.ds.Update(context.Background(), b.Name, bytes.NewReader(b.Data)) - s.Require().NoError(err) + err := s.ds.Update(t.Context(), b.Name, bytes.NewReader(b.Data)) + require.NoError(t, err) } wg := sync.WaitGroup{} @@ -250,15 +273,15 @@ func (s *DatastoreTestSuite) TestSimultaneousReads() { for n := 0; n < readCnt; n++ { b := testutils.TestBlobs[(i+n)%len(testutils.TestBlobs)] - r, err := s.ds.Open(context.Background(), b.Name) - s.Require().NoError(err) + r, err := s.ds.Open(t.Context(), b.Name) + require.NoError(t, err) data, err := io.ReadAll(r) - s.Require().NoError(err) - s.Require().Equal(b.Data, data) + require.NoError(t, err) + require.Equal(t, b.Data, data) err = r.Close() - s.Require().NoError(err) + require.NoError(t, err) } }(i) } @@ -267,6 +290,8 @@ func (s *DatastoreTestSuite) TestSimultaneousReads() { } func (s *DatastoreTestSuite) TestSimultaneousUpdates() { + t := s.T() + const threadCnt = 3 b := testutils.TestBlobs[0] @@ -274,85 +299,87 @@ func (s *DatastoreTestSuite) TestSimultaneousUpdates() { for range threadCnt { wg.Go(func() { - err := s.ds.Update(context.Background(), b.Name, bytes.NewReader(b.Data)) + err := s.ds.Update(t.Context(), b.Name, bytes.NewReader(b.Data)) if errors.Is(err, datastore.ErrUploadInProgress) { // TODO: We should be able to handle this case return } - s.Require().NoError(err) + require.NoError(t, err) - exists, err := s.ds.Exists(context.Background(), b.Name) - s.Require().NoError(err) - s.Require().True(exists) + exists, err := s.ds.Exists(t.Context(), b.Name) + require.NoError(t, err) + require.True(t, exists) }) } wg.Wait() - exists, err := s.ds.Exists(context.Background(), b.Name) - s.Require().NoError(err) - s.Require().True(exists) + exists, err := s.ds.Exists(t.Context(), b.Name) + require.NoError(t, err) + require.True(t, exists) - r, err := s.ds.Open(context.Background(), b.Name) - s.Require().NoError(err) + r, err := s.ds.Open(t.Context(), b.Name) + require.NoError(t, err) data, err := io.ReadAll(r) - s.Require().NoError(err) - s.Require().Equal(b.Data, data) + require.NoError(t, err) + require.Equal(t, b.Data, data) err = r.Close() - s.Require().NoError(err) + require.NoError(t, err) } -func (s *DatastoreTestSuite) updateDynamicLink(num int) { +func (s *DatastoreTestSuite) updateDynamicLink(t *testing.T, num int) { err := s.ds.Update( - context.Background(), + t.Context(), testutils.DynamicLinkPropagationData[num].Name, bytes.NewReader(testutils.DynamicLinkPropagationData[num].Data), ) - s.Require().NoError(err) + require.NoError(t, err) } -func (s *DatastoreTestSuite) readDynamicLinkData() []byte { - r, err := s.ds.Open(context.Background(), testutils.DynamicLinkPropagationData[0].Name) - s.Require().NoError(err) +func (s *DatastoreTestSuite) readDynamicLinkData(t *testing.T) []byte { + r, err := s.ds.Open(t.Context(), testutils.DynamicLinkPropagationData[0].Name) + require.NoError(t, err) dl, err := dynamiclink.FromPublicData(testutils.DynamicLinkPropagationData[0].Name, r) - s.Require().NoError(err) + require.NoError(t, err) elink, err := io.ReadAll(dl.GetEncryptedLinkReader()) - s.Require().NoError(err) + require.NoError(t, err) err = r.Close() - s.Require().NoError(err) + require.NoError(t, err) return elink } -func (s *DatastoreTestSuite) expectDynamicLinkData(num int) { - s.Require().Equal( +func (s *DatastoreTestSuite) expectDynamicLinkData(t *testing.T, num int) { + require.Equal(t, testutils.DynamicLinkPropagationData[num].Expected, - s.readDynamicLinkData(), + s.readDynamicLinkData(t), ) } func (s *DatastoreTestSuite) TestDynamicLinkPropagation() { - s.updateDynamicLink(0) - s.expectDynamicLinkData(0) + t := s.T() + + s.updateDynamicLink(t, 0) + s.expectDynamicLinkData(t, 0) - s.updateDynamicLink(1) - s.expectDynamicLinkData(1) + s.updateDynamicLink(t, 1) + s.expectDynamicLinkData(t, 1) - s.updateDynamicLink(0) - s.expectDynamicLinkData(1) + s.updateDynamicLink(t, 0) + s.expectDynamicLinkData(t, 1) - s.updateDynamicLink(2) - s.expectDynamicLinkData(2) + s.updateDynamicLink(t, 2) + s.expectDynamicLinkData(t, 2) - s.updateDynamicLink(1) - s.expectDynamicLinkData(2) + s.updateDynamicLink(t, 1) + s.expectDynamicLinkData(t, 2) - s.updateDynamicLink(0) - s.expectDynamicLinkData(2) + s.updateDynamicLink(t, 0) + s.expectDynamicLinkData(t, 2) } diff --git a/pkg/datastoreconformancetest/storage_backend_testsuite.go b/pkg/datastoreconformancetest/storage_backend_testsuite.go index 6b92c62..706c2c3 100644 --- a/pkg/datastoreconformancetest/storage_backend_testsuite.go +++ b/pkg/datastoreconformancetest/storage_backend_testsuite.go @@ -26,8 +26,8 @@ import ( "github.com/cinode/go-datastore/pkg/common" "github.com/cinode/go-datastore/pkg/datastore" "github.com/cinode/go-datastore/pkg/datastore/testutils" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" + "github.com/cinode/go-datastore/pkg/internal/picotestify/require" + "github.com/cinode/go-datastore/pkg/internal/picotestify/suite" ) type StorageBackendTestSuite struct { @@ -57,8 +57,10 @@ func NewStorageBackendTestSuite( } func (s *StorageBackendTestSuite) SetupTest() { + t := s.T() + st, err := s.CreateStorage() - s.Require().NoError(err) + require.NoError(t, err) s.st = st } diff --git a/pkg/internal/blobtypes/dynamiclink/public_test.go b/pkg/internal/blobtypes/dynamiclink/public_test.go index fd04e7d..115248c 100644 --- a/pkg/internal/blobtypes/dynamiclink/public_test.go +++ b/pkg/internal/blobtypes/dynamiclink/public_test.go @@ -28,9 +28,9 @@ import ( "github.com/cinode/go-datastore/pkg/blobtypes" "github.com/cinode/go-datastore/pkg/common" + "github.com/cinode/go-datastore/pkg/internal/picotestify/require" "github.com/cinode/go-datastore/pkg/internal/utilities/cipherfactory" "github.com/cinode/go-datastore/pkg/internal/utilities/errreader" - "github.com/stretchr/testify/require" ) func TestFromPublicData(t *testing.T) { diff --git a/pkg/internal/blobtypes/dynamiclink/publisher_test.go b/pkg/internal/blobtypes/dynamiclink/publisher_test.go index 817565d..3d25313 100644 --- a/pkg/internal/blobtypes/dynamiclink/publisher_test.go +++ b/pkg/internal/blobtypes/dynamiclink/publisher_test.go @@ -24,8 +24,8 @@ import ( "testing" "github.com/cinode/go-datastore/pkg/common" + "github.com/cinode/go-datastore/pkg/internal/picotestify/require" "github.com/cinode/go-datastore/pkg/internal/utilities/errreader" - "github.com/stretchr/testify/require" ) func TestCreate(t *testing.T) { @@ -118,13 +118,13 @@ func TestPublisherUpdateLinkData(t *testing.T) { require.NotNil(t, pr.r) require.NotNil(t, pr.iv) require.NotNil(t, pr.signature) - require.EqualValues(t, 0, pr.contentVersion) + require.Equal(t, uint64(0), pr.contentVersion) t.Run("successful update", func(t *testing.T) { pr2, key2, err := dl.UpdateLinkData(io.LimitReader(rand.Reader, 32), 1) require.NoError(t, err) require.Equal(t, key, key2) - require.EqualValues(t, 1, pr2.contentVersion) + require.Equal(t, uint64(1), pr2.contentVersion) }) t.Run("failed data reader", func(t *testing.T) { diff --git a/pkg/internal/blobtypes/dynamiclink/utils_test.go b/pkg/internal/blobtypes/dynamiclink/utils_test.go index 26c5291..f9c8941 100644 --- a/pkg/internal/blobtypes/dynamiclink/utils_test.go +++ b/pkg/internal/blobtypes/dynamiclink/utils_test.go @@ -1,5 +1,5 @@ /* -Copyright © 2023 Bartłomiej Święcki (byo) +Copyright © 2025 Bartłomiej Święcki (byo) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ package dynamiclink import ( "testing" - "github.com/stretchr/testify/require" + "github.com/cinode/go-datastore/pkg/internal/picotestify/require" ) func TestPanicIf(t *testing.T) { diff --git a/pkg/internal/blobtypes/dynamiclink/vectors_test.go b/pkg/internal/blobtypes/dynamiclink/vectors_test.go index 5f562cc..6b4e2ed 100644 --- a/pkg/internal/blobtypes/dynamiclink/vectors_test.go +++ b/pkg/internal/blobtypes/dynamiclink/vectors_test.go @@ -22,8 +22,8 @@ import ( "testing" "github.com/cinode/go-datastore/pkg/common" + "github.com/cinode/go-datastore/pkg/internal/picotestify/require" "github.com/cinode/go-datastore/testvectors" - "github.com/stretchr/testify/require" ) func TestVectors(t *testing.T) { diff --git a/pkg/internal/picotestify/assert/assert.go b/pkg/internal/picotestify/assert/assert.go new file mode 100644 index 0000000..3de708e --- /dev/null +++ b/pkg/internal/picotestify/assert/assert.go @@ -0,0 +1,299 @@ +/* +Copyright © 2025 Bartłomiej Święcki (byo) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package assert + +import ( + "errors" + "fmt" + "reflect" + "regexp" + "strings" + + "golang.org/x/exp/constraints" +) + +type TestingT interface { + Helper() + Error(msgAndArgs ...any) +} + +func fail(t TestingT, msgAndArgs []any, fmtString string, fmtArgs ...any) { + t.Error( + append( + []any{fmt.Sprintf(fmtString, fmtArgs...)}, + msgAndArgs..., + )..., + ) +} + +func Equal[T any](t TestingT, expected, actual T, msgAndArgs ...any) bool { + if reflect.DeepEqual(expected, actual) { + return true + } + + t.Helper() + fail(t, msgAndArgs, "Values not equal, expected: %+v, actual: %+v", expected, actual) + + return false +} + +func NotEqual[T any](t TestingT, expected, actual T, msgAndArgs ...any) bool { + if !reflect.DeepEqual(expected, actual) { + return true + } + + t.Helper() + fail(t, msgAndArgs, "Values are equal: %v", actual) + + return false +} + +func True(t TestingT, condition bool, msgAndArgs ...any) bool { + if condition { + return true + } + + t.Helper() + fail(t, msgAndArgs, "Condition is not true") + + return false +} + +func False(t TestingT, condition bool, msgAndArgs ...any) bool { + if !condition { + return true + } + + t.Helper() + fail(t, msgAndArgs, "Condition is not false") + + return false +} + +func Nil(t TestingT, object any, msgAndArgs ...any) bool { + if isNil(object) { + return true + } + + t.Helper() + fail(t, msgAndArgs, "Object is not nil") + + return false +} + +func NotNil(t TestingT, object any, msgAndArgs ...any) bool { + if !isNil(object) { + return true + } + + t.Helper() + fail(t, msgAndArgs, "Object is nil") + + return false +} + +func isNil(object any) bool { + if object == nil { + return true + } + + value := reflect.ValueOf(object) + switch value.Kind() { + case + reflect.Chan, + reflect.Func, + reflect.Interface, + reflect.Map, + reflect.Pointer, + reflect.Slice, + reflect.UnsafePointer: + if value.IsNil() { + return true + } + } + + return false +} + +func NoError(t TestingT, err error, msgAndArgs ...any) bool { + if err == nil { + return true + } + + t.Helper() + fail(t, msgAndArgs, "Received unexpected error: %v", err) + + return false +} + +func ErrorIs(t TestingT, err error, target error, msgAndArgs ...any) bool { + if errors.Is(err, target) { + return true + } + + t.Helper() + fail(t, msgAndArgs, "Error is not %T: %v", target, err) + + return false +} + +func ErrorContains(t TestingT, err error, contains string, msgAndArgs ...any) bool { + if err != nil && strings.Contains(err.Error(), contains) { + return true + } + + t.Helper() + fail(t, msgAndArgs, "Error %q does not contain %q", err.Error(), contains) + + return false +} + +func NotEmpty(t TestingT, object any, msgAndArgs ...any) bool { + if !isEmpty(object) { + return true + } + + t.Helper() + fail(t, msgAndArgs, "Object is empty") + + return false +} + +func isEmpty(object any) bool { + if object == nil { + return true + } + + value := reflect.ValueOf(object) + switch value.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return value.Len() == 0 + } + + return false +} + +func Greater[T constraints.Ordered](t TestingT, a, b T, msgAndArgs ...any) bool { + if a > b { + return true + } + + t.Helper() + fail(t, msgAndArgs, "Expected %v to be greater than %v", a, b) + + return false +} + +func GreaterOrEqual[T constraints.Ordered](t TestingT, a, b T, msgAndArgs ...any) bool { + if a >= b { + return true + } + + t.Helper() + fail(t, msgAndArgs, "Expected %v to be greater or equal than %v", a, b) + + return false +} + +func isZero(value any) bool { + return value == nil || reflect.DeepEqual(value, reflect.Zero(reflect.TypeOf(value)).Interface()) +} + +func Zero(t TestingT, value any, msgAndArgs ...any) bool { + if isZero(value) { + return true + } + + t.Helper() + fail(t, msgAndArgs, "Expected %v to be zero", value) + + return false +} + +func NotZero(t TestingT, value any, msgAndArgs ...any) bool { + if !isZero(value) { + return true + } + + t.Helper() + fail(t, msgAndArgs, "Expected %v to not be zero", value) + + return false +} + +func didPanic(f func()) (didPanic bool) { + didPanic = true + + defer func() { recover() }() + + f() + didPanic = false + + return +} + +func Panics(t TestingT, f func(), msgAndArgs ...any) bool { + if didPanic(f) { + return true + } + + t.Helper() + fail(t, msgAndArgs, "Expected function to panic") + + return false +} + +func NotPanics(t TestingT, f func(), msgAndArgs ...any) bool { + if !didPanic(f) { + return true + } + + t.Helper() + fail(t, msgAndArgs, "Expected function not to panic") + + return false +} + +func Regexp(t TestingT, pattern string, text string, msgAndArgs ...any) bool { + if matches, err := regexp.MatchString(pattern, text); err != nil { + t.Helper() + fail(t, msgAndArgs, "Invalid regexp pattern: %v", err) + t.Error(msgAndArgs...) + + return false + } else if matches { + return true + } + + t.Helper() + fail(t, msgAndArgs, "Expected %v to match %v", text, pattern) + + return false +} + +func Len(t TestingT, obj any, len int, msgAndArgs ...any) bool { + r := reflect.ValueOf(obj) + + if r.Len() == len { + return true + } + + t.Helper() + fail(t, msgAndArgs, "Expected length of %d, found: %d", len, r.Len()) + + return false +} diff --git a/pkg/internal/picotestify/require/require.go b/pkg/internal/picotestify/require/require.go new file mode 100644 index 0000000..a90f468 --- /dev/null +++ b/pkg/internal/picotestify/require/require.go @@ -0,0 +1,153 @@ +/* +Copyright © 2025 Bartłomiej Święcki (byo) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package require + +import ( + "github.com/cinode/go-datastore/pkg/internal/picotestify/assert" + "golang.org/x/exp/constraints" +) + +type TestingT interface { + assert.TestingT + FailNow() +} + +func Equal[T any](t TestingT, expected, actual T, msgAndArgs ...any) { + t.Helper() + if !assert.Equal(t, expected, actual, msgAndArgs...) { + t.FailNow() + } +} + +func NotEqual[T any](t TestingT, expected, actual T, msgAndArgs ...any) { + t.Helper() + if !assert.NotEqual(t, expected, actual, msgAndArgs...) { + t.FailNow() + } +} + +func Greater[T constraints.Ordered](t TestingT, a, b T, msgAndArgs ...any) { + t.Helper() + if !assert.Greater(t, a, b, msgAndArgs...) { + t.FailNow() + } +} + +func GreaterOrEqual[T constraints.Ordered](t TestingT, a, b T, msgAndArgs ...any) { + t.Helper() + if !assert.GreaterOrEqual(t, a, b, msgAndArgs...) { + t.FailNow() + } +} + +func True(t TestingT, condition bool, msgAndArgs ...any) { + t.Helper() + if !assert.True(t, condition, msgAndArgs...) { + t.FailNow() + } +} + +func False(t TestingT, condition bool, msgAndArgs ...any) { + t.Helper() + if !assert.False(t, condition, msgAndArgs...) { + t.FailNow() + } +} + +func Nil(t TestingT, object any, msgAndArgs ...any) { + t.Helper() + if !assert.Nil(t, object, msgAndArgs...) { + t.FailNow() + } +} + +func NotNil(t TestingT, object any, msgAndArgs ...any) { + t.Helper() + if !assert.NotNil(t, object, msgAndArgs...) { + t.FailNow() + } +} + +func NoError(t TestingT, err error, msgAndArgs ...any) { + t.Helper() + if !assert.NoError(t, err, msgAndArgs...) { + t.FailNow() + } +} + +func ErrorIs(t TestingT, err, target error, msgAndArgs ...any) { + t.Helper() + if !assert.ErrorIs(t, err, target, msgAndArgs...) { + t.FailNow() + } +} + +func ErrorContains(t TestingT, err error, contains string, msgAndArgs ...any) { + t.Helper() + if !assert.ErrorContains(t, err, contains, msgAndArgs...) { + t.FailNow() + } +} + +func NotEmpty(t TestingT, object any, msgAndArgs ...any) { + t.Helper() + if !assert.NotEmpty(t, object, msgAndArgs...) { + t.FailNow() + } +} + +func Zero(t TestingT, value any, msgAndArgs ...any) { + t.Helper() + if !assert.Zero(t, value, msgAndArgs...) { + t.FailNow() + } +} + +func NotZero(t TestingT, value any, msgAndArgs ...any) { + t.Helper() + if !assert.NotZero(t, value, msgAndArgs...) { + t.FailNow() + } +} + +func Panics(t TestingT, f func(), msgAndArgs ...any) { + t.Helper() + if !assert.Panics(t, f, msgAndArgs...) { + t.FailNow() + } +} + +func NotPanics(t TestingT, f func(), msgAndArgs ...any) { + t.Helper() + if !assert.NotPanics(t, f, msgAndArgs...) { + t.FailNow() + } +} + +func Regexp(t TestingT, pattern string, text string, msgAndArgs ...any) { + t.Helper() + if !assert.Regexp(t, pattern, text, msgAndArgs...) { + t.FailNow() + } +} + +func Len(t TestingT, obj any, len int) { + t.Helper() + if !assert.Len(t, obj, len) { + t.FailNow() + } +} diff --git a/pkg/internal/picotestify/suite/suite.go b/pkg/internal/picotestify/suite/suite.go new file mode 100644 index 0000000..6aaf427 --- /dev/null +++ b/pkg/internal/picotestify/suite/suite.go @@ -0,0 +1,87 @@ +/* +Copyright © 2025 Bartłomiej Święcki (byo) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package suite + +import ( + "reflect" + "regexp" + "testing" +) + +type Suite struct { + t *testing.T +} + +type SuiteInterface interface { + T() *testing.T + SetT(t *testing.T) +} + +func (s *Suite) T() *testing.T { return s.t } +func (s *Suite) SetT(t *testing.T) { s.t = t } + +var testMethodRe = regexp.MustCompile("^Test") + +func listTests(suite SuiteInterface) func(yield func(string, func()) bool) { + return func(yield func(string, func()) bool) { + st := reflect.TypeOf(suite) + + for i := 0; i < st.NumMethod(); i++ { + method := st.Method(i) + if !testMethodRe.MatchString(method.Name) { + continue + } + + if !yield( + method.Name, + func() { method.Func.Call([]reflect.Value{reflect.ValueOf(suite)}) }, + ) { + return + } + } + } +} + +func Run(parentT *testing.T, suite SuiteInterface) { + suite.SetT(parentT) + + if setupSuite, isSetupSuite := suite.(interface{ SetupTestSuite() }); isSetupSuite { + setupSuite.SetupTestSuite() + } + + if tearDownSuite, isTearDownSuite := suite.(interface{ TearDownTestSuite() }); isTearDownSuite { + parentT.Cleanup(tearDownSuite.TearDownTestSuite) + } + + for name, testFunc := range listTests(suite) { + parentT.Run(name, func(childT *testing.T) { + suite.SetT(childT) + + if setupTest, isSetupTest := suite.(interface{ SetupTest() }); isSetupTest { + setupTest.SetupTest() + } + + if tearDownTest, isTearDownTest := suite.(interface{ TearDownTest() }); isTearDownTest { + childT.Cleanup(tearDownTest.TearDownTest) + } + + testFunc() + + suite.SetT(parentT) + }) + } +} diff --git a/pkg/internal/utilities/cipherfactory/cipher_factory_test.go b/pkg/internal/utilities/cipherfactory/cipher_factory_test.go index 1b21fe6..bc5c47a 100644 --- a/pkg/internal/utilities/cipherfactory/cipher_factory_test.go +++ b/pkg/internal/utilities/cipherfactory/cipher_factory_test.go @@ -22,7 +22,7 @@ import ( "testing" "github.com/cinode/go-datastore/pkg/common" - "github.com/stretchr/testify/require" + "github.com/cinode/go-datastore/pkg/internal/picotestify/require" "golang.org/x/crypto/chacha20" ) diff --git a/pkg/internal/utilities/cipherfactory/generator_test.go b/pkg/internal/utilities/cipherfactory/generator_test.go index 66c8a6b..ddf36ce 100644 --- a/pkg/internal/utilities/cipherfactory/generator_test.go +++ b/pkg/internal/utilities/cipherfactory/generator_test.go @@ -20,7 +20,7 @@ import ( "testing" "github.com/cinode/go-datastore/pkg/blobtypes" - "github.com/stretchr/testify/require" + "github.com/cinode/go-datastore/pkg/internal/picotestify/require" ) func TestGenerator(t *testing.T) { diff --git a/pkg/internal/utilities/errreader/errreader_test.go b/pkg/internal/utilities/errreader/errreader_test.go index 57c141a..77a80ad 100644 --- a/pkg/internal/utilities/errreader/errreader_test.go +++ b/pkg/internal/utilities/errreader/errreader_test.go @@ -20,7 +20,7 @@ import ( "errors" "testing" - "github.com/stretchr/testify/require" + "github.com/cinode/go-datastore/pkg/internal/picotestify/require" ) func TestErrReader(t *testing.T) { diff --git a/pkg/internal/utilities/headwriter/headwriter_test.go b/pkg/internal/utilities/headwriter/headwriter_test.go index d524c68..5834368 100644 --- a/pkg/internal/utilities/headwriter/headwriter_test.go +++ b/pkg/internal/utilities/headwriter/headwriter_test.go @@ -1,5 +1,5 @@ /* -Copyright © 2023 Bartłomiej Święcki (byo) +Copyright © 2025 Bartłomiej Święcki (byo) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ package headwriter import ( "testing" - "github.com/stretchr/testify/require" + "github.com/cinode/go-datastore/pkg/internal/picotestify/require" ) func TestHeadWriter(t *testing.T) { diff --git a/pkg/internal/utilities/validatingreader/hashvalidatingreader_test.go b/pkg/internal/utilities/validatingreader/hashvalidatingreader_test.go index c1204f7..0b7b037 100644 --- a/pkg/internal/utilities/validatingreader/hashvalidatingreader_test.go +++ b/pkg/internal/utilities/validatingreader/hashvalidatingreader_test.go @@ -23,8 +23,8 @@ import ( "io" "testing" + "github.com/cinode/go-datastore/pkg/internal/picotestify/require" "github.com/cinode/go-datastore/pkg/internal/utilities/validatingreader" - "github.com/stretchr/testify/require" ) func TestHashValidatingReader(t *testing.T) { @@ -46,7 +46,7 @@ func TestHashValidatingReader(t *testing.T) { readBack, err := io.ReadAll(r) require.NoError(t, err) - require.EqualValues(t, data, readBack) + require.Equal(t, data, readBack) }) t.Run("invalid hash", func(t *testing.T) { @@ -64,7 +64,7 @@ func TestHashValidatingReader(t *testing.T) { readBack, err := io.ReadAll(r) require.ErrorIs(t, err, retErr) - require.EqualValues(t, data, readBack) + require.Equal(t, data, readBack) }) } } diff --git a/pkg/internal/utilities/validatingreader/oneofcheckreader_test.go b/pkg/internal/utilities/validatingreader/oneofcheckreader_test.go index 03ddd11..354d470 100644 --- a/pkg/internal/utilities/validatingreader/oneofcheckreader_test.go +++ b/pkg/internal/utilities/validatingreader/oneofcheckreader_test.go @@ -22,8 +22,8 @@ import ( "io" "testing" + "github.com/cinode/go-datastore/pkg/internal/picotestify/require" "github.com/cinode/go-datastore/pkg/internal/utilities/validatingreader" - "github.com/stretchr/testify/require" ) func TestOnCloseCheckReader(t *testing.T) { diff --git a/pkg/utilities/golang/assert_test.go b/pkg/utilities/golang/assert_test.go index 55b3879..f84722c 100644 --- a/pkg/utilities/golang/assert_test.go +++ b/pkg/utilities/golang/assert_test.go @@ -1,5 +1,5 @@ /* -Copyright © 2023 Bartłomiej Święcki (byo) +Copyright © 2025 Bartłomiej Święcki (byo) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ package golang import ( "testing" - "github.com/stretchr/testify/require" + "github.com/cinode/go-datastore/pkg/internal/picotestify/require" ) func TestAssert(t *testing.T) { diff --git a/pkg/utilities/golang/must_test.go b/pkg/utilities/golang/must_test.go index 8d25c23..073a04e 100644 --- a/pkg/utilities/golang/must_test.go +++ b/pkg/utilities/golang/must_test.go @@ -1,5 +1,5 @@ /* -Copyright © 2023 Bartłomiej Święcki (byo) +Copyright © 2025 Bartłomiej Święcki (byo) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ import ( "errors" "testing" - "github.com/stretchr/testify/require" + "github.com/cinode/go-datastore/pkg/internal/picotestify/require" ) func TestMust(t *testing.T) { From c506cd502629d144225e6d5bfbbb7f77e549f977 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20=C5=9Awi=C4=99cki?= Date: Fri, 31 Oct 2025 23:38:24 +0100 Subject: [PATCH 07/10] Custom base58 implementation This implementation bases on existing bit.Int implementation of conversion to/from text representation of a given base. Two additional changes needed are for handling leading zeroes case which is unique to base58 and switching the alphabet. --- .vscode/settings.json | 1 - go.mod | 1 - go.sum | 2 - pkg/common/blob_name.go | 8 +- pkg/datastore/testutils/generate/main.go | 2 +- .../testutils/generate/tesblobs.go.tpl | 6 +- pkg/datastore/testutils/testblobs.go | 38 +++--- pkg/internal/base58/base58.go | 103 ++++++++++++++ pkg/internal/base58/base58_encode_decode.json | 23 ++++ pkg/internal/base58/base58_test.go | 128 ++++++++++++++++++ 10 files changed, 283 insertions(+), 29 deletions(-) create mode 100644 pkg/internal/base58/base58.go create mode 100644 pkg/internal/base58/base58_encode_decode.json create mode 100644 pkg/internal/base58/base58_test.go diff --git a/.vscode/settings.json b/.vscode/settings.json index 343ffc3..06fabc7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -20,7 +20,6 @@ "goveralls", "Hasher", "homefile", - "jbenet", "protobuf", "securefifo", "shogo", diff --git a/go.mod b/go.mod index 981ecfb..1c1acd2 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/cinode/go-datastore go 1.25.3 require ( - github.com/jbenet/go-base58 v0.0.0-20150317085156-6237cf65f3a6 golang.org/x/crypto v0.43.0 golang.org/x/exp v0.0.0-20251017212417-90e834f514db ) diff --git a/go.sum b/go.sum index a5acc94..713f6c3 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -github.com/jbenet/go-base58 v0.0.0-20150317085156-6237cf65f3a6 h1:4zOlv2my+vf98jT1nQt4bT/yKWUImevYPJ2H344CloE= -github.com/jbenet/go-base58 v0.0.0-20150317085156-6237cf65f3a6/go.mod h1:r/8JmuR0qjuCiEhAolkfvdZgmPiHTnJaG0UXCSeR1Zo= golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= golang.org/x/exp v0.0.0-20251017212417-90e834f514db h1:by6IehL4BH5k3e3SJmcoNbOobMey2SLpAF79iPOEBvw= diff --git a/pkg/common/blob_name.go b/pkg/common/blob_name.go index 5aff9f5..23cc261 100644 --- a/pkg/common/blob_name.go +++ b/pkg/common/blob_name.go @@ -20,7 +20,7 @@ import ( "crypto/subtle" "errors" - base58 "github.com/jbenet/go-base58" + "github.com/cinode/go-datastore/pkg/internal/base58" ) var ( @@ -59,7 +59,11 @@ func BlobNameFromHashAndType(hash []byte, t BlobType) (*BlobName, error) { // BlobNameFromString decodes base58-encoded string into blob name func BlobNameFromString(s string) (*BlobName, error) { - return BlobNameFromBytes(base58.Decode(s)) + decoded, err := base58.Decode(s) + if err != nil { + return nil, ErrInvalidBlobName + } + return BlobNameFromBytes(decoded) } func BlobNameFromBytes(n []byte) (*BlobName, error) { diff --git a/pkg/datastore/testutils/generate/main.go b/pkg/datastore/testutils/generate/main.go index 2c46e90..163c352 100644 --- a/pkg/datastore/testutils/generate/main.go +++ b/pkg/datastore/testutils/generate/main.go @@ -28,9 +28,9 @@ import ( "github.com/cinode/go-datastore/pkg/blobtypes" "github.com/cinode/go-datastore/pkg/common" + "github.com/cinode/go-datastore/pkg/internal/base58" "github.com/cinode/go-datastore/pkg/internal/blobtypes/dynamiclink" "github.com/cinode/go-datastore/pkg/utilities/golang" - "github.com/jbenet/go-base58" ) func errPanic(err error) { diff --git a/pkg/datastore/testutils/generate/tesblobs.go.tpl b/pkg/datastore/testutils/generate/tesblobs.go.tpl index e33f5fc..cb7dc07 100644 --- a/pkg/datastore/testutils/generate/tesblobs.go.tpl +++ b/pkg/datastore/testutils/generate/tesblobs.go.tpl @@ -19,7 +19,7 @@ package testutils import ( "github.com/cinode/go-datastore/pkg/common" "github.com/cinode/go-datastore/pkg/utilities/golang" - "github.com/jbenet/go-base58" + "github.com/cinode/go-datastore/pkg/internal/base58" ) // nolint:lll // test data vectors @@ -31,8 +31,8 @@ var TestBlobs = []struct { {{- range .TestBlobs }} { golang.Must(common.BlobNameFromString("{{ .Name }}")), - base58.Decode("{{ .Data }}"), - base58.Decode("{{ .Expected }}"), + golang.Must(base58.Decode("{{ .Data }}")), + golang.Must(base58.Decode("{{ .Expected }}")), }, {{- end }} } diff --git a/pkg/datastore/testutils/testblobs.go b/pkg/datastore/testutils/testblobs.go index b18751c..c0a00c5 100644 --- a/pkg/datastore/testutils/testblobs.go +++ b/pkg/datastore/testutils/testblobs.go @@ -18,8 +18,8 @@ package testutils import ( "github.com/cinode/go-datastore/pkg/common" + "github.com/cinode/go-datastore/pkg/internal/base58" "github.com/cinode/go-datastore/pkg/utilities/golang" - "github.com/jbenet/go-base58" ) // nolint:lll // test data vectors @@ -30,33 +30,33 @@ var TestBlobs = []struct { }{ { golang.Must(common.BlobNameFromString("KDc2ijtWc9mGxb5hP29YSBgkMLH8wCWnVimpvP3M6jdAk")), - base58.Decode("3A836b"), - base58.Decode("3A836b"), + golang.Must(base58.Decode("3A836b")), + golang.Must(base58.Decode("3A836b")), }, { golang.Must(common.BlobNameFromString("BG8WaXMAckEfbCuoiHpx2oMAS4zAaPqAqrgf5Q3YNzmHx")), - base58.Decode("AXG4Ffv"), - base58.Decode("AXG4Ffv"), + golang.Must(base58.Decode("AXG4Ffv")), + golang.Must(base58.Decode("AXG4Ffv")), }, { golang.Must(common.BlobNameFromString("2GLoj4Bk7SvjQngCT85gxWRu2DXCCjs9XWKsSpM85Wq3Ve")), - base58.Decode(""), - base58.Decode(""), + golang.Must(base58.Decode("")), + golang.Must(base58.Decode("")), }, { golang.Must(common.BlobNameFromString("251SEdnHjwyvUqX1EZnuKruta4yHMkTDed7LGoi3nUJwhx")), - base58.Decode("1DhLfjA9ij9QFBh7J8ysnN3uvGcsNQa7vaxKEwbYEMSEXuZbgyCtUAn5FhadZHuh7wergdpyrfuDX2TpddoWtu14HkVkFQsuHzNuPg3LAuyhQwiuKDxLtmjWkDExx651o7Gun8VYkbDVPvabYSa2Kgbei59YyUKhRztrfySngpUr17HDn38e6RT9hmmkfL8jL8FiTsqrkFgxCYKQXaBkHQBswy7rWUgP8kT65wJdAgXykW2WwyyNMKtYUiX2iLGGNDfbt4EFiJAQbPZJBtEdwnhP66hM"), - base58.Decode("9VBV1V9DJ2uqDd99zZaCsuQp6v95vwsfuty2wGQKDPZTg4cmbRqZUgZzJkgEJWk6ps2z87M5zRQ4FisjcskpSZoSxZL4Zjpb"), + golang.Must(base58.Decode("1DhLfjA9ij9QFBh7J8ysnN3uvGcsNQa7vaxKEwbYEMSEXuZbgyCtUAn5FhadZHuh7wergdpyrfuDX2TpddoWtu14HkVkFQsuHzNuPg3LAuyhQwiuKDxLtmjWkDExx651o7Gun8VYkbDVPvabYSa2Kgbei59YyUKhRztrfySngpUr17HDn38e6RT9hmmkfL8jL8FiTsqrkFgxCYKQXaBkHQBswy7rWUgP8kT65wJdAgXykW2WwyyNMKtYUiX2iLGGNDfbt4EFiJAQbPZJBtEdwnhP66hM")), + golang.Must(base58.Decode("9VBV1V9DJ2uqDd99zZaCsuQp6v95vwsfuty2wGQKDPZTg4cmbRqZUgZzJkgEJWk6ps2z87M5zRQ4FisjcskpSZoSxZL4Zjpb")), }, { golang.Must(common.BlobNameFromString("27vP1JG4VJNZvQJ4Zfhy3H5xKugurbh89B7rKTcStM9guB")), - base58.Decode("1eE2wp1836WtQmEbjdavggJvFPU7dZbQQH5EBS2LwBL2rYjArM9mjvWFEGSfXrQCHscqdGy68exskkPXpGko2HezEAoz4UyQevHphVR5QP1JdLYYmAb4yA63bSznXz6osc8EyxvcKtLGoyfss7omAwrtGLeq1NNiYniXBiJJtuJxtKanw4GAPzn8mpoqhmZQFd36VV5MtLNFpTz5S8ke7MZSkCRKYLJutBxev9fZ5xvt2gqYWEQizWgV691juLC4FA5H82cBq2ZKwUwF4ad1JVcu822AA"), - base58.Decode("PKfeHiNXhYXvq4nu6QKyVTgAXwiLBBJWg6LgZvpgMY82TU5WBBFMdTZQs18kD4iVpkGzH4fjupcRFZJVwJ6rouakMJF6mtvk6"), + golang.Must(base58.Decode("1eE2wp1836WtQmEbjdavggJvFPU7dZbQQH5EBS2LwBL2rYjArM9mjvWFEGSfXrQCHscqdGy68exskkPXpGko2HezEAoz4UyQevHphVR5QP1JdLYYmAb4yA63bSznXz6osc8EyxvcKtLGoyfss7omAwrtGLeq1NNiYniXBiJJtuJxtKanw4GAPzn8mpoqhmZQFd36VV5MtLNFpTz5S8ke7MZSkCRKYLJutBxev9fZ5xvt2gqYWEQizWgV691juLC4FA5H82cBq2ZKwUwF4ad1JVcu822AA")), + golang.Must(base58.Decode("PKfeHiNXhYXvq4nu6QKyVTgAXwiLBBJWg6LgZvpgMY82TU5WBBFMdTZQs18kD4iVpkGzH4fjupcRFZJVwJ6rouakMJF6mtvk6")), }, { golang.Must(common.BlobNameFromString("e3T1HcdDLc73NHed2SFu5XHUQx5KDwgdAYTMmmEk2Ekqm")), - base58.Decode("1yULPpEx3gjpKNBLCEzb2oj2xRcdGfr88CztgfYfEipBGiJCijqWBEEmVAQJ4F33AoJyYkq9Rmj6n9ChngFR7TP8jHjddQM2sKqyDi1NUAmWi7TdGCh79FXTGR12r1RNoNPfqUVv1YZjyNsCgw5cN9WetWgoj5jbdxrqkyq3UjnqM1gEfazdKCyfvWurWr3aWRy4GxQuAQDxfccpSkBxVfzchb4CyRftPt28Lc85g4qGA3oHiLDrwh1qX29gFuZqse8Nq3rLsTUT5vNiLbd1Kr"), - base58.Decode("ULEdmCvFAc593MMZ1Yyd6etYP6ofZE8jE41hLWp7mUUs2DyfP3y9BguoyNLK5KumSLqy6vWDGG81CnMkqa8iaiL1jz"), + golang.Must(base58.Decode("1yULPpEx3gjpKNBLCEzb2oj2xRcdGfr88CztgfYfEipBGiJCijqWBEEmVAQJ4F33AoJyYkq9Rmj6n9ChngFR7TP8jHjddQM2sKqyDi1NUAmWi7TdGCh79FXTGR12r1RNoNPfqUVv1YZjyNsCgw5cN9WetWgoj5jbdxrqkyq3UjnqM1gEfazdKCyfvWurWr3aWRy4GxQuAQDxfccpSkBxVfzchb4CyRftPt28Lc85g4qGA3oHiLDrwh1qX29gFuZqse8Nq3rLsTUT5vNiLbd1Kr")), + golang.Must(base58.Decode("ULEdmCvFAc593MMZ1Yyd6etYP6ofZE8jE41hLWp7mUUs2DyfP3y9BguoyNLK5KumSLqy6vWDGG81CnMkqa8iaiL1jz")), }, } @@ -68,17 +68,17 @@ var DynamicLinkPropagationData = []struct { }{ { golang.Must(common.BlobNameFromString("GUnL66Lyv2Qs4baxPhy59kF4dsB9HWakvTjMBjNGFLT6g")), - base58.Decode("17Jk3QJMCABypJWuAivYwMi43gN1KxPVy3qg1e4HYQFe8BCQPVm6GX8cQX3eKBQ2zdmzs9wRqESbGzCELdM9Kn7RoNmJiA7LY7hg66iPrWGfikzhJtRfFqS7eXs4ohLqbcaNXQt3i36JSxkeriTJBCEuzi86uAUqL9oJm5uqQJ7QBehXPDX7pjFZGi3QKA1JfPwsJUsZEwwfJPX2jhsZHDCgnpdRJoVaGQ6zj3u9PVoTCNqiy5m534o6Dejer4yJQWxvxeNJcRgyoCGRek1ByQGyChziW"), - base58.Decode("PiS95EiCcNaz4dkpMGYd1hSjPzTR28Rx7nTWwb8yocFVLVJoVWoqHjE8u9FtrSfB9qUufCHHRaS95oKmFE9WTrdNRr5zkQ5xn"), + golang.Must(base58.Decode("17Jk3QJMCABypJWuAivYwMi43gN1KxPVy3qg1e4HYQFe8BCQPVm6GX8cQX3eKBQ2zdmzs9wRqESbGzCELdM9Kn7RoNmJiA7LY7hg66iPrWGfikzhJtRfFqS7eXs4ohLqbcaNXQt3i36JSxkeriTJBCEuzi86uAUqL9oJm5uqQJ7QBehXPDX7pjFZGi3QKA1JfPwsJUsZEwwfJPX2jhsZHDCgnpdRJoVaGQ6zj3u9PVoTCNqiy5m534o6Dejer4yJQWxvxeNJcRgyoCGRek1ByQGyChziW")), + golang.Must(base58.Decode("PiS95EiCcNaz4dkpMGYd1hSjPzTR28Rx7nTWwb8yocFVLVJoVWoqHjE8u9FtrSfB9qUufCHHRaS95oKmFE9WTrdNRr5zkQ5xn")), }, { golang.Must(common.BlobNameFromString("GUnL66Lyv2Qs4baxPhy59kF4dsB9HWakvTjMBjNGFLT6g")), - base58.Decode("17Jk3QJMCABypJWuAivYwMi43gN1KxPVy3qg1e4HYQFe8BCQPVm6GX8aKUfQY1JGDimnrVjYEythtb2CP3SrmYzHf3typ12JKUuCcrHThgZodib6AjLrhV4qZFpqX2DcRscG1oHGX13Tyny8FhGTPio6mgHYze27vPpcNFbp1jx5ETXCTHuur9UpAfag1FSakh8CnKayFegQKEav5rfbfb75Y7hnovYncSPTcerdTnFyVqjDSXNhYophu1o8Nupffv6xpeMJMmcUDWhmm5ofWDpew7x7y"), - base58.Decode("GVqyiBNhD53H64AjZHs2hR631JBR7PcDRLSsk6mTaofpFDfZnWWGt6hHsonfW1jUFV4h87cVrQKB6kyKVkPisED1PY22bjGwR"), + golang.Must(base58.Decode("17Jk3QJMCABypJWuAivYwMi43gN1KxPVy3qg1e4HYQFe8BCQPVm6GX8aKUfQY1JGDimnrVjYEythtb2CP3SrmYzHf3typ12JKUuCcrHThgZodib6AjLrhV4qZFpqX2DcRscG1oHGX13Tyny8FhGTPio6mgHYze27vPpcNFbp1jx5ETXCTHuur9UpAfag1FSakh8CnKayFegQKEav5rfbfb75Y7hnovYncSPTcerdTnFyVqjDSXNhYophu1o8Nupffv6xpeMJMmcUDWhmm5ofWDpew7x7y")), + golang.Must(base58.Decode("GVqyiBNhD53H64AjZHs2hR631JBR7PcDRLSsk6mTaofpFDfZnWWGt6hHsonfW1jUFV4h87cVrQKB6kyKVkPisED1PY22bjGwR")), }, { golang.Must(common.BlobNameFromString("GUnL66Lyv2Qs4baxPhy59kF4dsB9HWakvTjMBjNGFLT6g")), - base58.Decode("17Jk3QJMCABypJWuAivYwMi43gN1KxPVy3qg1e4HYQFe8BCQPVm6GX8aApx2qggEBaUKFT9T3MNwPAicht7Zjiw1Crm2ffEvxFWPupKCcGea11YG4x3NsF2u57V3Z82bhBMfXFHDPywnLnBBx3SQe688vNitGiLzyjBqH9oMCaD3oVKdyKNtE9DmNuTgCRSTnj31FiAaWWzHtqMbVqxNToVp78hhkWudpJDiqJM1Z7DNPK8RGjYDNBrtcbzxfBk4gbSL9usgAGgV7Ty3fiDAmUx8RG2vk"), - base58.Decode("RNffUTysjj6v8JgFbNqpUL9CvnLjiDzM9fehH89p7iTFNXQtDjng1woWPnvUDvuZXSTsxw2ndUo6rfPFpZVuip29ZakLbu49n"), + golang.Must(base58.Decode("17Jk3QJMCABypJWuAivYwMi43gN1KxPVy3qg1e4HYQFe8BCQPVm6GX8aApx2qggEBaUKFT9T3MNwPAicht7Zjiw1Crm2ffEvxFWPupKCcGea11YG4x3NsF2u57V3Z82bhBMfXFHDPywnLnBBx3SQe688vNitGiLzyjBqH9oMCaD3oVKdyKNtE9DmNuTgCRSTnj31FiAaWWzHtqMbVqxNToVp78hhkWudpJDiqJM1Z7DNPK8RGjYDNBrtcbzxfBk4gbSL9usgAGgV7Ty3fiDAmUx8RG2vk")), + golang.Must(base58.Decode("RNffUTysjj6v8JgFbNqpUL9CvnLjiDzM9fehH89p7iTFNXQtDjng1woWPnvUDvuZXSTsxw2ndUo6rfPFpZVuip29ZakLbu49n")), }, } diff --git a/pkg/internal/base58/base58.go b/pkg/internal/base58/base58.go new file mode 100644 index 0000000..39d37cd --- /dev/null +++ b/pkg/internal/base58/base58.go @@ -0,0 +1,103 @@ +/* +Copyright © 2025 Bartłomiej Święcki (byo) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package base58 + +import ( + "errors" + "fmt" + "math/big" +) + +var ErrInvalidBase58Character = errors.New("invalid base58 character") + +var bn2btc, btc2bn = func() (bn2btc, btc2bn [256]byte) { + const btcAlphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" + const bitNumDigits = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUV" + + for i := range btcAlphabet { + bn2btc[bitNumDigits[i]] = btcAlphabet[i] + btc2bn[btcAlphabet[i]] = bitNumDigits[i] + } + + return bn2btc, btc2bn +}() + +func Encode(data []byte) string { + // Count leading zero bytes, those are treated especially + leadingZeros := 0 + for _, b := range data { + if b == 0 { + leadingZeros++ + } else { + break + } + } + + var bi big.Int + bi.SetBytes(data[leadingZeros:]) + + txt := bi.Text(58) + + if txt == "0" { + txt = "" + } + + res := make([]byte, leadingZeros+len(txt)) + + // Add '1' characters for leading zeros + for i := 0; i < leadingZeros; i++ { + res[i] = '1' + } + + // Convert the rest + for i, b := range txt { + res[leadingZeros+i] = bn2btc[b] + } + + return string(res) +} + +func Decode(s string) ([]byte, error) { + // Split into leading bytes and bit.Int-compatible representation + leadingZeros := 0 + leadingZerosDone := false + bnText := make([]byte, 0, len(s)) + for _, b := range s { + if !leadingZerosDone && b == '1' { + leadingZeros++ + } else if btc2bn[b] != 0 { + leadingZerosDone = true + bnText = append(bnText, btc2bn[b]) + } else { + return nil, fmt.Errorf("%w: '%v'", ErrInvalidBase58Character, b) + } + } + + if len(bnText) == 0 { + return make([]byte, leadingZeros), nil + } + + var bn big.Int + bn.SetString(string(bnText), 58) + + bnBytes := bn.Bytes() + + result := make([]byte, leadingZeros+len(bnBytes)) + copy(result[leadingZeros:], bnBytes) + + return result, nil +} diff --git a/pkg/internal/base58/base58_encode_decode.json b/pkg/internal/base58/base58_encode_decode.json new file mode 100644 index 0000000..7a94c63 --- /dev/null +++ b/pkg/internal/base58/base58_encode_decode.json @@ -0,0 +1,23 @@ +[ + ["", ""], + ["61", "2g"], + ["626262", "a3gV"], + ["636363", "aPEr"], + ["73696d706c792061206c6f6e6720737472696e67", "2cFupjhnEsSn59qHXstmK2ffpLv2"], + ["00eb15231dfceb60925886b67d065299925915aeb172c06647", "1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L"], + ["516b6fcd0f", "ABnLTmg"], + ["bf4f89001e670274dd", "3SEo3LWLoPntC"], + ["572e4794", "3EFU7m"], + ["ecac89cad93923c02321", "EJDM8drfXA6uyA"], + ["10c8511e", "Rt5zm"], + ["00000000000000000000", "1111111111"], + ["00000000000000000000000000000000000000000000000000000000000000000000000000000000", "1111111111111111111111111111111111111111"], + ["00000000000000000000000000000000000000000000000000000000000000000000000000000001", "1111111111111111111111111111111111111112"], + ["0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ec39d04c37e71e5d591881f6", "111111111111111111111111111111111111111111111111111111111111111111111111111111111111115TYzLYH1udmLdzCLM"], + ["000111d38e5fc9071ffcd20b4a763cc9ae4f252bb4e48fd66a835e252ada93ff480d6dd43dc62a641155a5", "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"], + ["000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", "1cWB5HCBdLjAuqGGReWE3R3CguuwSjw6RHn39s2yuDRTS5NsBgNiFpWgAnEx6VQi8csexkgYw3mdYrMHr8x9i7aEwP8kZ7vccXWqKDvGv3u1GxFKPuAkn8JCPPGDMf3vMMnbzm6Nh9zh1gcNsMvH3ZNLmP5fSG6DGbbi2tuwMWPthr4boWwCxf7ewSgNQeacyozhKDDQQ1qL5fQFUW52QKUZDZ5fw3KXNQJMcNTcaB723LchjeKun7MuGW5qyCBZYzA1KjofN1gYBV3NqyhQJ3Ns746GNuf9N2pQPmHz4xpnSrrfCvy6TVVz5d4PdrjeshsWQwpZsZGzvbdAdN8MKV5QsBDY"], + ["271F359E", "zzzzy"], + ["271F359F", "zzzzz"], + ["271F35A0", "211111"], + ["271F35A1", "211112"] + ] \ No newline at end of file diff --git a/pkg/internal/base58/base58_test.go b/pkg/internal/base58/base58_test.go new file mode 100644 index 0000000..b9d06d9 --- /dev/null +++ b/pkg/internal/base58/base58_test.go @@ -0,0 +1,128 @@ +/* +Copyright © 2025 Bartłomiej Święcki (byo) + +Licensed under the Apache License, + Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, + software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package base58_test + +import ( + _ "embed" + "encoding/hex" + "encoding/json" + "fmt" + "testing" + + "github.com/cinode/go-datastore/pkg/internal/base58" + "github.com/cinode/go-datastore/pkg/internal/picotestify/require" +) + +// Test cases from (MIT License): +// +// https://github.com/bitcoin/bitcoin/blob/master/src/test/data/base58_encode_decode.json +// +//go:embed base58_encode_decode.json +var testJSONData string + +type testCase struct { + data []byte + expected string +} + +func validTestCases(t require.TestingT) []testCase { + testCases := []testCase{ + { + []byte{}, + "", + }, + { + []byte("Hello World!"), + "2NEpo7TZRRrLZSi2U", + }, + { + []byte("The quick brown fox jumps over the lazy dog."), + "USm3fpXnKG5EUBx2ndxBDMPVciP5hGey2Jh4NDv6gmeo1LkMeiKrLJUUBk6Z", + }, + { + []byte{0x00, 0x00, 0x28, 0x7f, 0xb4, 0xcd}, + "11233QC4", + }, + } + + jsonTests := [][]string{} + require.NoError(t, json.Unmarshal([]byte(testJSONData), &jsonTests)) + + for _, tc := range jsonTests { + require.Len(t, tc, 2) + + data, err := hex.DecodeString(tc[0]) + require.NoError(t, err) + expected := tc[1] + + testCases = append(testCases, testCase{ + data: data, + expected: expected, + }) + } + return testCases +} + +func TestEncodeDecode(t *testing.T) { + for _, test := range validTestCases(t) { + t.Run(fmt.Sprintf("data=%v", test.data), func(t *testing.T) { + encoded := base58.Encode(test.data) + require.Equal(t, test.expected, encoded) + + decodedBack, err := base58.Decode(encoded) + require.NoError(t, err) + + require.Equal(t, test.data, decodedBack) + }) + } +} + +func TestErrorOnInvalidDecode(t *testing.T) { + for _, test := range []string{ + "@", + "3SEo3LWLoPntC@", + "@3SEo3LWLoPntC", + "3SEo3@LWLoPntC", + } { + t.Run(test, func(t *testing.T) { + decoded, err := base58.Decode(test) + require.Nil(t, decoded) + require.ErrorIs(t, err, base58.ErrInvalidBase58Character) + }) + } +} + +func FuzzEncodeDecode(f *testing.F) { + for _, test := range validTestCases(f) { + f.Add(test.data) + } + + f.Fuzz(func(t *testing.T, a []byte) { + if len(a) > 256 { + // No point in testing those + t.SkipNow() + } + + str := base58.Encode(a) + back, err := base58.Decode(str) + require.NoError(t, err) + require.Equal(t, a, back) + }) +} From 8366f11ff2e01582fb1815d7d201c30f20f3e174 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20=C5=9Awi=C4=99cki?= Date: Fri, 31 Oct 2025 23:38:25 +0100 Subject: [PATCH 08/10] Extract test vectors and common to separate modules --- .vscode/settings.json | 6 +- go.mod | 8 +- go.sum | 4 + pkg/blobtypes/list.go | 12 +- pkg/blobtypes/list_test.go | 6 +- pkg/common/auth_info.go | 29 - pkg/common/auth_info_test.go | 31 - pkg/common/blob_keys.go | 42 - pkg/common/blob_keys_test.go | 39 - pkg/common/blob_name.go | 101 -- pkg/common/blob_name_test.go | 73 - pkg/common/blob_type.go | 29 - pkg/common/blob_type_test.go | 29 - pkg/datastore/datastore.go | 10 +- pkg/datastore/datastore_dynamic_link.go | 8 +- pkg/datastore/datastore_static.go | 6 +- pkg/datastore/datastore_test.go | 26 +- pkg/datastore/interface.go | 10 +- pkg/datastore/storage_backend.go | 10 +- pkg/datastore/storage_backend_memory.go | 10 +- pkg/datastore/testutils/generate/main.go | 41 +- .../testutils/generate/tesblobs.go.tpl | 23 +- pkg/datastore/testutils/testblobs.go | 65 +- pkg/datastore/testutils/testblobs_test.go | 2 +- pkg/datastore/testutils/testutils.go | 10 +- pkg/datastore/testutils/testutils_test.go | 2 +- pkg/datastoreconformancetest/inmemory_test.go | 2 +- .../interface_testsuite.go | 8 +- .../storage_backend_testsuite.go | 10 +- pkg/internal/base58/base58.go | 103 -- pkg/internal/base58/base58_encode_decode.json | 23 - pkg/internal/base58/base58_test.go | 128 -- pkg/internal/blobtypes/dynamiclink/public.go | 16 +- .../blobtypes/dynamiclink/public_test.go | 31 +- .../blobtypes/dynamiclink/publisher.go | 14 +- .../blobtypes/dynamiclink/publisher_test.go | 6 +- .../blobtypes/dynamiclink/utils_test.go | 2 +- .../blobtypes/dynamiclink/vectors_test.go | 14 +- pkg/internal/picotestify/assert/assert.go | 299 ---- pkg/internal/picotestify/require/require.go | 153 -- pkg/internal/picotestify/suite/suite.go | 87 -- .../utilities/cipherfactory/cipher_factory.go | 8 +- .../cipherfactory/cipher_factory_test.go | 16 +- .../utilities/cipherfactory/generator.go | 24 +- .../utilities/cipherfactory/generator_test.go | 2 +- .../utilities/errreader/errreader_test.go | 2 +- .../utilities/headwriter/headwriter_test.go | 2 +- .../hashvalidatingreader_test.go | 2 +- .../validatingreader/oneofcheckreader_test.go | 2 +- pkg/utilities/golang/assert.go | 23 - pkg/utilities/golang/assert_test.go | 32 - pkg/utilities/golang/must.go | 24 - pkg/utilities/golang/must_test.go | 34 - .../private/001_keygen_signature_prefix.json | 24 - .../002_keygen_signature_blob_name.json | 19 - .../003_keygen_signature_corruption.json | 19 - .../private/004_keygen_hash_prefix.json | 21 - .../005_keygen_hash_encryption_alg.json | 21 - .../private/006_keygen_hash_blob_type.json | 21 - .../dynamic/attacks/private/007_key_type.json | 16 - .../attacks/private/008_key_corruption.json | 16 - .../attacks/private/009_link_data_prefix.json | 19 - .../010_key_validation_block_signature.json | 20 - ...1_key_validation_block_length_smaller.json | 20 - ...12_key_validation_block_length_larger.json | 20 - .../private/013_iv_has_gen_h_prefix.json | 21 - .../014_keygen_hash_encryption_alg.json | 21 - .../private/015_iv_gen_hash_blob_type.json | 21 - .../016_iv_gen_hash_blob_name_length.json | 19 - .../private/017_iv_gen_hash_blob_name.json | 19 - .../018_iv_gen_hash_content_version.json | 19 - .../private/019_iv_gen_hash_link_data.json | 19 - .../attacks/private/020_corrupted_iv.json | 18 - ...021_truncated_link_data_reserved_byte.json | 21 - ...link_data_key_validation_block_length.json | 20 - ..._data_incomplete_key_validation_block.json | 20 - .../dynamic/attacks/public/001_empty.json | 15 - .../attacks/public/002_reserved_byte.json | 18 - .../attacks/public/003_pubkey_truncated.json | 12 - .../attacks/public/004_nonce_truncated.json | 12 - .../005_blob_mismatch_reserved_byte.json | 17 - .../public/006_blob_mismatch_priv_key.json | 17 - .../public/007_blob_mismatch_nonce.json | 17 - .../public/008_truncated_signature.json | 12 - .../public/009_truncated_content_version.json | 12 - .../attacks/public/010_truncated_iv.json | 12 - .../public/011_signature_mismatch_prefix.json | 21 - .../012_signature_mismatch_blob_name.json | 25 - ...13_signature_mismatch_content_version.json | 19 - ...nature_mismatch_initialization_vector.json | 25 - ...mismatch_initialization_vector_length.json | 24 - ...ignature_mismatch_encrypted_link_data.json | 19 - ...17_signature_mismatch_signature_bytes.json | 12 - .../dynamic/correct/000_correct_link.json | 11 - .../dynamic/correct/001_correct_link.json | 11 - .../dynamic/correct/002_correct_link.json | 11 - .../dynamic/correct/003_correct_link.json | 11 - .../dynamic/correct/004_correct_link.json | 11 - .../dynamic/correct/005_correct_link.json | 11 - .../dynamic/correct/006_correct_link.json | 11 - .../dynamic/correct/007_correct_link.json | 11 - .../dynamic/correct/008_correct_link.json | 11 - .../dynamic/correct/009_correct_link.json | 11 - testvectors/embedded.go | 67 - testvectors/generate/main.go | 1247 ----------------- testvectors/internal/testcase.go | 32 - 106 files changed, 224 insertions(+), 3674 deletions(-) delete mode 100644 pkg/common/auth_info.go delete mode 100644 pkg/common/auth_info_test.go delete mode 100644 pkg/common/blob_keys.go delete mode 100644 pkg/common/blob_keys_test.go delete mode 100644 pkg/common/blob_name.go delete mode 100644 pkg/common/blob_name_test.go delete mode 100644 pkg/common/blob_type.go delete mode 100644 pkg/common/blob_type_test.go delete mode 100644 pkg/internal/base58/base58.go delete mode 100644 pkg/internal/base58/base58_encode_decode.json delete mode 100644 pkg/internal/base58/base58_test.go delete mode 100644 pkg/internal/picotestify/assert/assert.go delete mode 100644 pkg/internal/picotestify/require/require.go delete mode 100644 pkg/internal/picotestify/suite/suite.go delete mode 100644 pkg/utilities/golang/assert.go delete mode 100644 pkg/utilities/golang/assert_test.go delete mode 100644 pkg/utilities/golang/must.go delete mode 100644 pkg/utilities/golang/must_test.go delete mode 100644 testvectors/dynamic/attacks/private/001_keygen_signature_prefix.json delete mode 100644 testvectors/dynamic/attacks/private/002_keygen_signature_blob_name.json delete mode 100644 testvectors/dynamic/attacks/private/003_keygen_signature_corruption.json delete mode 100644 testvectors/dynamic/attacks/private/004_keygen_hash_prefix.json delete mode 100644 testvectors/dynamic/attacks/private/005_keygen_hash_encryption_alg.json delete mode 100644 testvectors/dynamic/attacks/private/006_keygen_hash_blob_type.json delete mode 100644 testvectors/dynamic/attacks/private/007_key_type.json delete mode 100644 testvectors/dynamic/attacks/private/008_key_corruption.json delete mode 100644 testvectors/dynamic/attacks/private/009_link_data_prefix.json delete mode 100644 testvectors/dynamic/attacks/private/010_key_validation_block_signature.json delete mode 100644 testvectors/dynamic/attacks/private/011_key_validation_block_length_smaller.json delete mode 100644 testvectors/dynamic/attacks/private/012_key_validation_block_length_larger.json delete mode 100644 testvectors/dynamic/attacks/private/013_iv_has_gen_h_prefix.json delete mode 100644 testvectors/dynamic/attacks/private/014_keygen_hash_encryption_alg.json delete mode 100644 testvectors/dynamic/attacks/private/015_iv_gen_hash_blob_type.json delete mode 100644 testvectors/dynamic/attacks/private/016_iv_gen_hash_blob_name_length.json delete mode 100644 testvectors/dynamic/attacks/private/017_iv_gen_hash_blob_name.json delete mode 100644 testvectors/dynamic/attacks/private/018_iv_gen_hash_content_version.json delete mode 100644 testvectors/dynamic/attacks/private/019_iv_gen_hash_link_data.json delete mode 100644 testvectors/dynamic/attacks/private/020_corrupted_iv.json delete mode 100644 testvectors/dynamic/attacks/private/021_truncated_link_data_reserved_byte.json delete mode 100644 testvectors/dynamic/attacks/private/022_truncated_link_data_key_validation_block_length.json delete mode 100644 testvectors/dynamic/attacks/private/023_truncated_link_data_incomplete_key_validation_block.json delete mode 100644 testvectors/dynamic/attacks/public/001_empty.json delete mode 100644 testvectors/dynamic/attacks/public/002_reserved_byte.json delete mode 100644 testvectors/dynamic/attacks/public/003_pubkey_truncated.json delete mode 100644 testvectors/dynamic/attacks/public/004_nonce_truncated.json delete mode 100644 testvectors/dynamic/attacks/public/005_blob_mismatch_reserved_byte.json delete mode 100644 testvectors/dynamic/attacks/public/006_blob_mismatch_priv_key.json delete mode 100644 testvectors/dynamic/attacks/public/007_blob_mismatch_nonce.json delete mode 100644 testvectors/dynamic/attacks/public/008_truncated_signature.json delete mode 100644 testvectors/dynamic/attacks/public/009_truncated_content_version.json delete mode 100644 testvectors/dynamic/attacks/public/010_truncated_iv.json delete mode 100644 testvectors/dynamic/attacks/public/011_signature_mismatch_prefix.json delete mode 100644 testvectors/dynamic/attacks/public/012_signature_mismatch_blob_name.json delete mode 100644 testvectors/dynamic/attacks/public/013_signature_mismatch_content_version.json delete mode 100644 testvectors/dynamic/attacks/public/014_signature_mismatch_initialization_vector.json delete mode 100644 testvectors/dynamic/attacks/public/015_signature_mismatch_initialization_vector_length.json delete mode 100644 testvectors/dynamic/attacks/public/016_signature_mismatch_encrypted_link_data.json delete mode 100644 testvectors/dynamic/attacks/public/017_signature_mismatch_signature_bytes.json delete mode 100644 testvectors/dynamic/correct/000_correct_link.json delete mode 100644 testvectors/dynamic/correct/001_correct_link.json delete mode 100644 testvectors/dynamic/correct/002_correct_link.json delete mode 100644 testvectors/dynamic/correct/003_correct_link.json delete mode 100644 testvectors/dynamic/correct/004_correct_link.json delete mode 100644 testvectors/dynamic/correct/005_correct_link.json delete mode 100644 testvectors/dynamic/correct/006_correct_link.json delete mode 100644 testvectors/dynamic/correct/007_correct_link.json delete mode 100644 testvectors/dynamic/correct/008_correct_link.json delete mode 100644 testvectors/dynamic/correct/009_correct_link.json delete mode 100644 testvectors/embedded.go delete mode 100644 testvectors/generate/main.go delete mode 100644 testvectors/internal/testcase.go diff --git a/.vscode/settings.json b/.vscode/settings.json index 06fabc7..19c07ad 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,6 +13,7 @@ "ciphertext", "containerimage", "coverprofile", + "cutl", "dynamiclink", "elink", "fifos", @@ -20,13 +21,16 @@ "goveralls", "Hasher", "homefile", + "picotestify", "protobuf", "securefifo", "shogo", "stretchr", "subdir", + "tesblobs", "testblobs", "testvectors", "validatingreader" - ] + ], + "go.testExplorer.showDynamicSubtestsInEditor": true, } \ No newline at end of file diff --git a/go.mod b/go.mod index 1c1acd2..8f776bc 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,12 @@ module github.com/cinode/go-datastore go 1.25.3 require ( + github.com/cinode/go-common v0.0.0-20251031214056-c3a6d95d1810 + github.com/cinode/go-testvectors v0.0.0-20251031214716-7bd08cc61fd1 golang.org/x/crypto v0.43.0 - golang.org/x/exp v0.0.0-20251017212417-90e834f514db ) -require golang.org/x/sys v0.37.0 // indirect +require ( + golang.org/x/exp v0.0.0-20251017212417-90e834f514db // indirect + golang.org/x/sys v0.37.0 // indirect +) diff --git a/go.sum b/go.sum index 713f6c3..5f3cc23 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,7 @@ +github.com/cinode/go-common v0.0.0-20251031214056-c3a6d95d1810 h1:1IakJkbW/5jUU/CktDiRjPd1sTW0nxIexpgUWigpAXM= +github.com/cinode/go-common v0.0.0-20251031214056-c3a6d95d1810/go.mod h1:3OWIcbBwu/xmycwRy9LpAJrKFuTVbrkXk4epOHRivYw= +github.com/cinode/go-testvectors v0.0.0-20251031214716-7bd08cc61fd1 h1:xtmnidyMf4DdB2lgcuhuCQcyzpDD30pXFdE3meaTzbQ= +github.com/cinode/go-testvectors v0.0.0-20251031214716-7bd08cc61fd1/go.mod h1:3+KwGRMjSDOsm24w/MPEuKmLM8eo9wPW24gxb+bA7ng= golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= golang.org/x/exp v0.0.0-20251017212417-90e834f514db h1:by6IehL4BH5k3e3SJmcoNbOobMey2SLpAF79iPOEBvw= diff --git a/pkg/blobtypes/list.go b/pkg/blobtypes/list.go index 9d81c64..2cb03a8 100644 --- a/pkg/blobtypes/list.go +++ b/pkg/blobtypes/list.go @@ -19,21 +19,21 @@ package blobtypes import ( "fmt" - "github.com/cinode/go-datastore/pkg/common" + "github.com/cinode/go-common/blob" ) var ( - Invalid = common.NewBlobType(0x00) - Static = common.NewBlobType(0x01) - DynamicLink = common.NewBlobType(0x02) + Invalid = blob.NewType(0x00) + Static = blob.NewType(0x01) + DynamicLink = blob.NewType(0x02) ) -var All = map[string]common.BlobType{ +var All = map[string]blob.Type{ "Static": Static, "DynamicLink": DynamicLink, } -func ToName(t common.BlobType) string { +func ToName(t blob.Type) string { for name, tp := range All { if tp == t { return name diff --git a/pkg/blobtypes/list_test.go b/pkg/blobtypes/list_test.go index 8f0e93a..823afcd 100644 --- a/pkg/blobtypes/list_test.go +++ b/pkg/blobtypes/list_test.go @@ -19,8 +19,8 @@ package blobtypes import ( "testing" - "github.com/cinode/go-datastore/pkg/common" - "github.com/cinode/go-datastore/pkg/internal/picotestify/require" + "github.com/cinode/go-common/blob" + "github.com/cinode/go-common/picotestify/require" ) func TestToName(t *testing.T) { @@ -31,6 +31,6 @@ func TestToName(t *testing.T) { }) t.Run("invalid type", func(t *testing.T) { require.Equal(t, "Invalid(0)", ToName(Invalid)) - require.Equal(t, "Invalid(255)", ToName(common.NewBlobType(255))) + require.Equal(t, "Invalid(255)", ToName(blob.NewType(255))) }) } diff --git a/pkg/common/auth_info.go b/pkg/common/auth_info.go deleted file mode 100644 index 6891bc3..0000000 --- a/pkg/common/auth_info.go +++ /dev/null @@ -1,29 +0,0 @@ -/* -Copyright © 2023 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package common - -import "crypto/subtle" - -// AuthInfo is an opaque data that is necessary to perform update of an existing blob. -// -// Currently used only for dynamic links, auth info contains all the necessary information -// to update the content of the blob. The representation is specific to the blob type -type AuthInfo struct{ data []byte } - -func AuthInfoFromBytes(ai []byte) *AuthInfo { return &AuthInfo{data: copyBytes(ai)} } -func (a *AuthInfo) Bytes() []byte { return copyBytes(a.data) } -func (a *AuthInfo) Equal(a2 *AuthInfo) bool { return subtle.ConstantTimeCompare(a.data, a2.data) == 1 } diff --git a/pkg/common/auth_info_test.go b/pkg/common/auth_info_test.go deleted file mode 100644 index c2f4eb1..0000000 --- a/pkg/common/auth_info_test.go +++ /dev/null @@ -1,31 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package common - -import ( - "testing" - - "github.com/cinode/go-datastore/pkg/internal/picotestify/require" -) - -func TestAuthInfo(t *testing.T) { - authInfoBytes := []byte{1, 2, 3} - authInfo := AuthInfoFromBytes(authInfoBytes) - require.Equal(t, authInfoBytes, authInfo.Bytes()) - require.True(t, authInfo.Equal(AuthInfoFromBytes(authInfoBytes))) - require.Nil(t, new(BlobKey).Bytes()) -} diff --git a/pkg/common/blob_keys.go b/pkg/common/blob_keys.go deleted file mode 100644 index 60e1adb..0000000 --- a/pkg/common/blob_keys.go +++ /dev/null @@ -1,42 +0,0 @@ -/* -Copyright © 2023 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package common - -import "crypto/subtle" - -func copyBytes(b []byte) []byte { - if b == nil { - return nil - } - ret := make([]byte, len(b)) - copy(ret, b) - return ret -} - -// Key with cipher type -type BlobKey struct{ key []byte } - -func BlobKeyFromBytes(key []byte) *BlobKey { return &BlobKey{key: copyBytes(key)} } -func (k *BlobKey) Bytes() []byte { return copyBytes(k.key) } -func (k *BlobKey) Equal(k2 *BlobKey) bool { return subtle.ConstantTimeCompare(k.key, k2.key) == 1 } - -// IV -type BlobIV struct{ iv []byte } - -func BlobIVFromBytes(iv []byte) *BlobIV { return &BlobIV{iv: copyBytes(iv)} } -func (i *BlobIV) Bytes() []byte { return copyBytes(i.iv) } -func (i *BlobIV) Equal(i2 *BlobIV) bool { return subtle.ConstantTimeCompare(i.iv, i2.iv) == 1 } diff --git a/pkg/common/blob_keys_test.go b/pkg/common/blob_keys_test.go deleted file mode 100644 index af2f5ac..0000000 --- a/pkg/common/blob_keys_test.go +++ /dev/null @@ -1,39 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package common - -import ( - "testing" - - "github.com/cinode/go-datastore/pkg/internal/picotestify/require" -) - -func TestBlobKey(t *testing.T) { - keyBytes := []byte{1, 2, 3} - key := BlobKeyFromBytes(keyBytes) - require.Equal(t, keyBytes, key.Bytes()) - require.True(t, key.Equal(BlobKeyFromBytes(keyBytes))) - require.Nil(t, new(BlobKey).Bytes()) -} - -func TestBlobIV(t *testing.T) { - ivBytes := []byte{1, 2, 3} - iv := BlobIVFromBytes(ivBytes) - require.Equal(t, ivBytes, iv.Bytes()) - require.True(t, iv.Equal(BlobIVFromBytes(ivBytes))) - require.Nil(t, new(BlobKey).Bytes()) -} diff --git a/pkg/common/blob_name.go b/pkg/common/blob_name.go deleted file mode 100644 index 23cc261..0000000 --- a/pkg/common/blob_name.go +++ /dev/null @@ -1,101 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package common - -import ( - "crypto/subtle" - "errors" - - "github.com/cinode/go-datastore/pkg/internal/base58" -) - -var ( - ErrInvalidBlobName = errors.New("invalid blob name") -) - -// BlobName is used to identify blobs. -// Internally it is a single array of bytes that represents -// both the type of the blob and internal hash used to create that blob. -// The type of the blob is not stored directly. Instead it is mixed -// with the hash of the blob to make sure that all bytes in the blob name -// are randomly distributed. -type BlobName struct { - bn []byte -} - -// BlobNameFromHashAndType generates the name of a blob from some hash (e.g. sha256 of blob's content) -// and given blob type -func BlobNameFromHashAndType(hash []byte, t BlobType) (*BlobName, error) { - if len(hash) == 0 || len(hash) > 0x7E { - return nil, ErrInvalidBlobName - } - - bn := make([]byte, len(hash)+1) - - copy(bn[1:], hash) - - scrambledTypeByte := t.t - for _, b := range hash { - scrambledTypeByte ^= b - } - bn[0] = scrambledTypeByte - - return &BlobName{bn: bn}, nil -} - -// BlobNameFromString decodes base58-encoded string into blob name -func BlobNameFromString(s string) (*BlobName, error) { - decoded, err := base58.Decode(s) - if err != nil { - return nil, ErrInvalidBlobName - } - return BlobNameFromBytes(decoded) -} - -func BlobNameFromBytes(n []byte) (*BlobName, error) { - if len(n) == 0 || len(n) > 0x7F { - return nil, ErrInvalidBlobName - } - return &BlobName{bn: copyBytes(n)}, nil -} - -// Returns base58-encoded blob name -func (b *BlobName) String() string { - return base58.Encode(b.bn) -} - -// Extracts hash from blob name -func (b *BlobName) Hash() []byte { - return b.bn[1:] -} - -// Extracts blob type from the name -func (b *BlobName) Type() BlobType { - ret := byte(0) - for _, by := range b.bn { - ret ^= by - } - return BlobType{t: ret} -} - -func (b *BlobName) Bytes() []byte { - return copyBytes(b.bn) -} - -func (b *BlobName) Equal(b2 *BlobName) bool { - return subtle.ConstantTimeCompare(b.bn, b2.bn) == 1 -} diff --git a/pkg/common/blob_name_test.go b/pkg/common/blob_name_test.go deleted file mode 100644 index 5d0ec07..0000000 --- a/pkg/common/blob_name_test.go +++ /dev/null @@ -1,73 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package common - -import ( - "crypto/sha256" - "fmt" - "testing" - - "github.com/cinode/go-datastore/pkg/internal/picotestify/assert" - "github.com/cinode/go-datastore/pkg/internal/picotestify/require" -) - -func TestBlobName(t *testing.T) { - for _, h := range [][]byte{ - {0}, {1}, {2}, {0xFE}, {0xFF}, - {0, 0, 0, 0}, - {1, 2, 3, 4, 5, 6}, - sha256.New().Sum(nil), - } { - for _, bt := range []BlobType{ - {t: 0x00}, - {t: 0x01}, - {t: 0x02}, - {t: 0xFE}, - {t: 0xFF}, - } { - t.Run(fmt.Sprintf("%v:%v", bt, h), func(t *testing.T) { - bn, err := BlobNameFromHashAndType(h, bt) - assert.NoError(t, err) - assert.NotEmpty(t, bn) - assert.Greater(t, len(bn.bn), len(h)) - assert.Equal(t, h, bn.Hash()) - assert.Equal(t, bt, bn.Type()) - - s := bn.String() - bn2, err := BlobNameFromString(s) - require.NoError(t, err) - require.Equal(t, bn, bn2) - require.True(t, bn.Equal(bn2)) - - b := bn.Bytes() - bn3, err := BlobNameFromBytes(b) - require.NoError(t, err) - require.Equal(t, bn, bn3) - require.True(t, bn.Equal(bn3)) - }) - } - } - - _, err := BlobNameFromString("!@#") - require.ErrorIs(t, err, ErrInvalidBlobName) - - _, err = BlobNameFromString("") - require.ErrorIs(t, err, ErrInvalidBlobName) - - _, err = BlobNameFromHashAndType(nil, BlobType{t: 0x00}) - require.ErrorIs(t, err, ErrInvalidBlobName) -} diff --git a/pkg/common/blob_type.go b/pkg/common/blob_type.go deleted file mode 100644 index 534e5d1..0000000 --- a/pkg/common/blob_type.go +++ /dev/null @@ -1,29 +0,0 @@ -/* -Copyright © 2022 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package common - -type BlobType struct { - t byte -} - -func NewBlobType(t byte) BlobType { - return BlobType{t: t} -} - -func (b BlobType) IDByte() byte { - return b.t -} diff --git a/pkg/common/blob_type_test.go b/pkg/common/blob_type_test.go deleted file mode 100644 index 98f7311..0000000 --- a/pkg/common/blob_type_test.go +++ /dev/null @@ -1,29 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package common - -import ( - "testing" - - "github.com/cinode/go-datastore/pkg/internal/picotestify/require" -) - -func TestBlobType(t *testing.T) { - tp := NewBlobType(0x77) - require.Equal(t, tp.t, byte(0x77)) - require.Equal(t, tp.IDByte(), byte(0x77)) -} diff --git a/pkg/datastore/datastore.go b/pkg/datastore/datastore.go index 16bc7ae..b22aa64 100644 --- a/pkg/datastore/datastore.go +++ b/pkg/datastore/datastore.go @@ -20,8 +20,8 @@ import ( "context" "io" + "github.com/cinode/go-common/blob" "github.com/cinode/go-datastore/pkg/blobtypes" - "github.com/cinode/go-datastore/pkg/common" ) type datastore struct { @@ -38,7 +38,7 @@ func (ds *datastore) Address() string { return ds.s.Address() } -func (ds *datastore) Open(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) { +func (ds *datastore) Open(ctx context.Context, name *blob.Name) (io.ReadCloser, error) { switch name.Type() { case blobtypes.Static: return ds.openStatic(ctx, name) @@ -49,7 +49,7 @@ func (ds *datastore) Open(ctx context.Context, name *common.BlobName) (io.ReadCl } } -func (ds *datastore) Update(ctx context.Context, name *common.BlobName, updateStream io.Reader) error { +func (ds *datastore) Update(ctx context.Context, name *blob.Name, updateStream io.Reader) error { switch name.Type() { case blobtypes.Static: return ds.updateStatic(ctx, name, updateStream) @@ -60,11 +60,11 @@ func (ds *datastore) Update(ctx context.Context, name *common.BlobName, updateSt } } -func (ds *datastore) Exists(ctx context.Context, name *common.BlobName) (bool, error) { +func (ds *datastore) Exists(ctx context.Context, name *blob.Name) (bool, error) { return ds.s.Exists(ctx, name) } -func (ds *datastore) Delete(ctx context.Context, name *common.BlobName) error { +func (ds *datastore) Delete(ctx context.Context, name *blob.Name) error { return ds.s.Delete(ctx, name) } diff --git a/pkg/datastore/datastore_dynamic_link.go b/pkg/datastore/datastore_dynamic_link.go index 04c8317..c2072dd 100644 --- a/pkg/datastore/datastore_dynamic_link.go +++ b/pkg/datastore/datastore_dynamic_link.go @@ -21,11 +21,11 @@ import ( "errors" "io" - "github.com/cinode/go-datastore/pkg/common" + "github.com/cinode/go-common/blob" "github.com/cinode/go-datastore/pkg/internal/blobtypes/dynamiclink" ) -func (ds *datastore) openDynamicLink(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) { +func (ds *datastore) openDynamicLink(ctx context.Context, name *blob.Name) (io.ReadCloser, error) { rc, err := ds.s.OpenReadStream(ctx, name) if err != nil { return nil, err @@ -50,7 +50,7 @@ func (ds *datastore) openDynamicLink(ctx context.Context, name *common.BlobName) // read from - only for comparison func (ds *datastore) newLinkGreaterThanCurrent( ctx context.Context, - name *common.BlobName, + name *blob.Name, newLink *dynamiclink.PublicReader, ) ( bool, error, @@ -72,7 +72,7 @@ func (ds *datastore) newLinkGreaterThanCurrent( return newLink.GreaterThan(dl), nil } -func (ds *datastore) updateDynamicLink(ctx context.Context, name *common.BlobName, updateStream io.Reader) error { +func (ds *datastore) updateDynamicLink(ctx context.Context, name *blob.Name, updateStream io.Reader) error { ws, err := ds.s.OpenWriteStream(ctx, name) if err != nil { return err diff --git a/pkg/datastore/datastore_static.go b/pkg/datastore/datastore_static.go index 0587bf7..ba751ca 100644 --- a/pkg/datastore/datastore_static.go +++ b/pkg/datastore/datastore_static.go @@ -22,12 +22,12 @@ import ( "crypto/sha256" "io" + "github.com/cinode/go-common/blob" "github.com/cinode/go-datastore/pkg/blobtypes" - "github.com/cinode/go-datastore/pkg/common" "github.com/cinode/go-datastore/pkg/internal/utilities/validatingreader" ) -func (ds *datastore) openStatic(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) { +func (ds *datastore) openStatic(ctx context.Context, name *blob.Name) (io.ReadCloser, error) { rc, err := ds.s.OpenReadStream(ctx, name) if err != nil { return nil, err @@ -47,7 +47,7 @@ func (ds *datastore) openStatic(ctx context.Context, name *common.BlobName) (io. }, nil } -func (ds *datastore) updateStatic(ctx context.Context, name *common.BlobName, updateStream io.Reader) error { +func (ds *datastore) updateStatic(ctx context.Context, name *blob.Name, updateStream io.Reader) error { outputStream, err := ds.s.OpenWriteStream(ctx, name) if err != nil { return err diff --git a/pkg/datastore/datastore_test.go b/pkg/datastore/datastore_test.go index 2c74e6f..d729bd0 100644 --- a/pkg/datastore/datastore_test.go +++ b/pkg/datastore/datastore_test.go @@ -23,19 +23,19 @@ import ( "io" "testing" + "github.com/cinode/go-common/blob" + "github.com/cinode/go-common/picotestify/require" "github.com/cinode/go-datastore/pkg/blobtypes" - "github.com/cinode/go-datastore/pkg/common" "github.com/cinode/go-datastore/pkg/datastore/testutils" - "github.com/cinode/go-datastore/pkg/internal/picotestify/require" ) type mockStore struct { fKind func() string fAddress func() string - fOpenReadStream func(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) - fOpenWriteStream func(ctx context.Context, name *common.BlobName) (WriteCloseCanceller, error) - fExists func(ctx context.Context, name *common.BlobName) (bool, error) - fDelete func(ctx context.Context, name *common.BlobName) error + fOpenReadStream func(ctx context.Context, name *blob.Name) (io.ReadCloser, error) + fOpenWriteStream func(ctx context.Context, name *blob.Name) (WriteCloseCanceller, error) + fExists func(ctx context.Context, name *blob.Name) (bool, error) + fDelete func(ctx context.Context, name *blob.Name) error } func (s *mockStore) Kind() string { @@ -44,16 +44,16 @@ func (s *mockStore) Kind() string { func (s *mockStore) Address() string { return s.fAddress() } -func (s *mockStore) OpenReadStream(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) { +func (s *mockStore) OpenReadStream(ctx context.Context, name *blob.Name) (io.ReadCloser, error) { return s.fOpenReadStream(ctx, name) } -func (s *mockStore) OpenWriteStream(ctx context.Context, name *common.BlobName) (WriteCloseCanceller, error) { +func (s *mockStore) OpenWriteStream(ctx context.Context, name *blob.Name) (WriteCloseCanceller, error) { return s.fOpenWriteStream(ctx, name) } -func (s *mockStore) Exists(ctx context.Context, name *common.BlobName) (bool, error) { +func (s *mockStore) Exists(ctx context.Context, name *blob.Name) (bool, error) { return s.fExists(ctx, name) } -func (s *mockStore) Delete(ctx context.Context, name *common.BlobName) error { +func (s *mockStore) Delete(ctx context.Context, name *blob.Name) error { return s.fDelete(ctx, name) } @@ -77,7 +77,7 @@ func TestDatastoreWriteFailure(t *testing.T) { t.Run("error on opening write stream", func(t *testing.T) { errRet := errors.New("error") ds := &datastore{s: &mockStore{ - fOpenWriteStream: func(ctx context.Context, name *common.BlobName) (WriteCloseCanceller, error) { + fOpenWriteStream: func(ctx context.Context, name *blob.Name) (WriteCloseCanceller, error) { return nil, errRet }, }} @@ -92,7 +92,7 @@ func TestDatastoreWriteFailure(t *testing.T) { closeCalled := false cancelCalled := false ds := &datastore{s: &mockStore{ - fOpenWriteStream: func(ctx context.Context, name *common.BlobName) (WriteCloseCanceller, error) { + fOpenWriteStream: func(ctx context.Context, name *blob.Name) (WriteCloseCanceller, error) { return &mockWriteCloseCanceller{ fWrite: func(b []byte) (int, error) { require.False(t, closeCalled) @@ -111,7 +111,7 @@ func TestDatastoreWriteFailure(t *testing.T) { }, }, nil }, - fOpenReadStream: func(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) { + fOpenReadStream: func(ctx context.Context, name *blob.Name) (io.ReadCloser, error) { return nil, ErrNotFound }, }} diff --git a/pkg/datastore/interface.go b/pkg/datastore/interface.go index 5699ab5..dd9e227 100644 --- a/pkg/datastore/interface.go +++ b/pkg/datastore/interface.go @@ -20,7 +20,7 @@ import ( "context" "io" - "github.com/cinode/go-datastore/pkg/common" + "github.com/cinode/go-common/blob" ) // DS interface contains the public interface of any conformant datastore @@ -59,20 +59,20 @@ type DS interface { // If a non-nil error is returned, the writer will be nil. Otherwise it // is necessary to call the `Close` on the returned reader once done // with the reader. - Open(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) + Open(ctx context.Context, name *blob.Name) (io.ReadCloser, error) // Update retrieves an update for given blob. The data is read from given // reader until it returns either EOF, ending successful save, or any other // error which will cancel the save - in such case this error will be // returned from this function. If the data does not pass validation, // ErrInvalidData will be returned. - Update(ctx context.Context, name *common.BlobName, r io.Reader) error + Update(ctx context.Context, name *blob.Name, r io.Reader) error // Exists does check whether blob of given name exists in the datastore. // Partially written blobs are equal to non-existing ones. Boolean value // returned indicates whether the blob exists or not, non-nil error indicates // that there was an error while trying to check blob's existence. - Exists(ctx context.Context, name *common.BlobName) (bool, error) + Exists(ctx context.Context, name *blob.Name) (bool, error) // Delete tries to remove blob with given name from the datastore. // If blob does not exist (which includes partially written blobs) @@ -81,5 +81,5 @@ type DS interface { // read the blob data. After the `Delete` call succeeds, trying to read // the blob with the `Open` should end up with an ErrNotFound error // until the blob is updated again with a successful `Update` call. - Delete(ctx context.Context, name *common.BlobName) error + Delete(ctx context.Context, name *blob.Name) error } diff --git a/pkg/datastore/storage_backend.go b/pkg/datastore/storage_backend.go index a6e6dd4..561b24f 100644 --- a/pkg/datastore/storage_backend.go +++ b/pkg/datastore/storage_backend.go @@ -20,7 +20,7 @@ import ( "context" "io" - "github.com/cinode/go-datastore/pkg/common" + "github.com/cinode/go-common/blob" ) type WriteCloseCanceller interface { @@ -31,8 +31,8 @@ type WriteCloseCanceller interface { type StorageBackend interface { Kind() string Address() string - OpenReadStream(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) - OpenWriteStream(ctx context.Context, name *common.BlobName) (WriteCloseCanceller, error) - Exists(ctx context.Context, name *common.BlobName) (bool, error) - Delete(ctx context.Context, name *common.BlobName) error + OpenReadStream(ctx context.Context, name *blob.Name) (io.ReadCloser, error) + OpenWriteStream(ctx context.Context, name *blob.Name) (WriteCloseCanceller, error) + Exists(ctx context.Context, name *blob.Name) (bool, error) + Delete(ctx context.Context, name *blob.Name) error } diff --git a/pkg/datastore/storage_backend_memory.go b/pkg/datastore/storage_backend_memory.go index 74cfb59..4b9baab 100644 --- a/pkg/datastore/storage_backend_memory.go +++ b/pkg/datastore/storage_backend_memory.go @@ -22,7 +22,7 @@ import ( "io" "sync" - "github.com/cinode/go-datastore/pkg/common" + "github.com/cinode/go-common/blob" ) const ( @@ -58,7 +58,7 @@ func (m *memory) Address() string { return memoryPrefix } -func (m *memory) OpenReadStream(ctx context.Context, name *common.BlobName) (io.ReadCloser, error) { +func (m *memory) OpenReadStream(ctx context.Context, name *blob.Name) (io.ReadCloser, error) { m.rw.RLock() defer m.rw.RUnlock() @@ -96,7 +96,7 @@ func (w *memoryWriteCloser) Close() error { return nil } -func (m *memory) OpenWriteStream(ctx context.Context, name *common.BlobName) (WriteCloseCanceller, error) { +func (m *memory) OpenWriteStream(ctx context.Context, name *blob.Name) (WriteCloseCanceller, error) { m.rw.Lock() defer m.rw.Unlock() @@ -115,7 +115,7 @@ func (m *memory) OpenWriteStream(ctx context.Context, name *common.BlobName) (Wr }, nil } -func (m *memory) Exists(ctx context.Context, n *common.BlobName) (bool, error) { +func (m *memory) Exists(ctx context.Context, n *blob.Name) (bool, error) { m.rw.RLock() defer m.rw.RUnlock() @@ -126,7 +126,7 @@ func (m *memory) Exists(ctx context.Context, n *common.BlobName) (bool, error) { return true, nil } -func (m *memory) Delete(ctx context.Context, n *common.BlobName) error { +func (m *memory) Delete(ctx context.Context, n *blob.Name) error { m.rw.Lock() defer m.rw.Unlock() diff --git a/pkg/datastore/testutils/generate/main.go b/pkg/datastore/testutils/generate/main.go index 163c352..74caa46 100644 --- a/pkg/datastore/testutils/generate/main.go +++ b/pkg/datastore/testutils/generate/main.go @@ -21,24 +21,18 @@ import ( "bytes" "crypto/sha256" _ "embed" + "encoding/hex" "fmt" "html/template" "io" "os" + "github.com/cinode/go-common/blob" + "github.com/cinode/go-common/cutl" "github.com/cinode/go-datastore/pkg/blobtypes" - "github.com/cinode/go-datastore/pkg/common" - "github.com/cinode/go-datastore/pkg/internal/base58" "github.com/cinode/go-datastore/pkg/internal/blobtypes/dynamiclink" - "github.com/cinode/go-datastore/pkg/utilities/golang" ) -func errPanic(err error) { - if err != nil { - panic(err) - } -} - type blobData struct { Name string Data string @@ -48,13 +42,14 @@ type blobData struct { func static(data string) blobData { content := []byte(data) hash := sha256.Sum256(content) - n, err := common.BlobNameFromHashAndType(hash[:], blobtypes.Static) - errPanic(err) + + n, err := blob.NameFromHashAndType(hash[:], blobtypes.Static) + cutl.PanicIfError(err) return blobData{ Name: n.String(), - Data: base58.Encode(content), - Expected: base58.Encode(content), + Data: hex.EncodeToString(content), + Expected: hex.EncodeToString(content), } } @@ -72,33 +67,33 @@ func dynamicLink(data string, version uint64, seed int) blobData { } dl, err := dynamiclink.Create(bytes.NewReader(pseudoRandBuffer)) - errPanic(err) + cutl.PanicIfError(err) pr, _, err := dl.UpdateLinkData(bytes.NewBufferString(data), version) - errPanic(err) + cutl.PanicIfError(err) buf, err := io.ReadAll(pr.GetPublicDataReader()) - errPanic(err) + cutl.PanicIfError(err) pr, err = dynamiclink.FromPublicData(dl.BlobName(), bytes.NewReader(buf)) - errPanic(err) + cutl.PanicIfError(err) elink, err := io.ReadAll(pr.GetEncryptedLinkReader()) - errPanic(err) + cutl.PanicIfError(err) return blobData{ Name: dl.BlobName().String(), - Data: base58.Encode(buf), - Expected: base58.Encode(elink), + Data: hex.EncodeToString(buf), + Expected: hex.EncodeToString(elink), } } //go:embed tesblobs.go.tpl var templateString string -var tmpl = golang.Must(template.New("testblobs").Parse(templateString)) +var tmpl = cutl.Must(template.New("testblobs").Parse(templateString)) func main() { - fl := golang.Must(os.Create("../testblobs.go")) + fl := cutl.Must(os.Create("../testblobs.go")) defer fl.Close() err := tmpl.Execute(fl, map[string]any{ @@ -116,7 +111,7 @@ func main() { dynamicLink("Test3", 20000, 999), }, }) - errPanic(err) + cutl.PanicIfError(err) fmt.Println("Successfully generated testblobs.go") } diff --git a/pkg/datastore/testutils/generate/tesblobs.go.tpl b/pkg/datastore/testutils/generate/tesblobs.go.tpl index cb7dc07..dd896ec 100644 --- a/pkg/datastore/testutils/generate/tesblobs.go.tpl +++ b/pkg/datastore/testutils/generate/tesblobs.go.tpl @@ -17,37 +17,38 @@ limitations under the License. package testutils import ( - "github.com/cinode/go-datastore/pkg/common" - "github.com/cinode/go-datastore/pkg/utilities/golang" - "github.com/cinode/go-datastore/pkg/internal/base58" + "encoding/hex" + + "github.com/cinode/go-common/blob" + "github.com/cinode/go-common/cutl" ) // nolint:lll // test data vectors var TestBlobs = []struct { - Name *common.BlobName + Name *blob.Name Data []byte Expected []byte }{ {{- range .TestBlobs }} { - golang.Must(common.BlobNameFromString("{{ .Name }}")), - golang.Must(base58.Decode("{{ .Data }}")), - golang.Must(base58.Decode("{{ .Expected }}")), + cutl.Must(blob.NameFromString("{{ .Name }}")), + cutl.Must(hex.DecodeString("{{ .Data }}")), + cutl.Must(hex.DecodeString("{{ .Expected }}")), }, {{- end }} } // nolint:lll // test data vectors var DynamicLinkPropagationData = []struct { - Name *common.BlobName + Name *blob.Name Data []byte Expected []byte }{ {{- range .DynamicLinkPropagationData }} { - golang.Must(common.BlobNameFromString("{{ .Name }}")), - base58.Decode("{{ .Data }}"), - base58.Decode("{{ .Expected }}"), + cutl.Must(blob.NameFromString("{{ .Name }}")), + cutl.Must(hex.DecodeString("{{ .Data }}")), + cutl.Must(hex.DecodeString("{{ .Expected }}")), }, {{- end }} } diff --git a/pkg/datastore/testutils/testblobs.go b/pkg/datastore/testutils/testblobs.go index c0a00c5..b274fb0 100644 --- a/pkg/datastore/testutils/testblobs.go +++ b/pkg/datastore/testutils/testblobs.go @@ -17,68 +17,69 @@ limitations under the License. package testutils import ( - "github.com/cinode/go-datastore/pkg/common" - "github.com/cinode/go-datastore/pkg/internal/base58" - "github.com/cinode/go-datastore/pkg/utilities/golang" + "encoding/hex" + + "github.com/cinode/go-common/blob" + "github.com/cinode/go-common/cutl" ) // nolint:lll // test data vectors var TestBlobs = []struct { - Name *common.BlobName + Name *blob.Name Data []byte Expected []byte }{ { - golang.Must(common.BlobNameFromString("KDc2ijtWc9mGxb5hP29YSBgkMLH8wCWnVimpvP3M6jdAk")), - golang.Must(base58.Decode("3A836b")), - golang.Must(base58.Decode("3A836b")), + cutl.Must(blob.NameFromString("KDc2ijtWc9mGxb5hP29YSBgkMLH8wCWnVimpvP3M6jdAk")), + cutl.Must(hex.DecodeString("54657374")), + cutl.Must(hex.DecodeString("54657374")), }, { - golang.Must(common.BlobNameFromString("BG8WaXMAckEfbCuoiHpx2oMAS4zAaPqAqrgf5Q3YNzmHx")), - golang.Must(base58.Decode("AXG4Ffv")), - golang.Must(base58.Decode("AXG4Ffv")), + cutl.Must(blob.NameFromString("BG8WaXMAckEfbCuoiHpx2oMAS4zAaPqAqrgf5Q3YNzmHx")), + cutl.Must(hex.DecodeString("5465737431")), + cutl.Must(hex.DecodeString("5465737431")), }, { - golang.Must(common.BlobNameFromString("2GLoj4Bk7SvjQngCT85gxWRu2DXCCjs9XWKsSpM85Wq3Ve")), - golang.Must(base58.Decode("")), - golang.Must(base58.Decode("")), + cutl.Must(blob.NameFromString("2GLoj4Bk7SvjQngCT85gxWRu2DXCCjs9XWKsSpM85Wq3Ve")), + cutl.Must(hex.DecodeString("")), + cutl.Must(hex.DecodeString("")), }, { - golang.Must(common.BlobNameFromString("251SEdnHjwyvUqX1EZnuKruta4yHMkTDed7LGoi3nUJwhx")), - golang.Must(base58.Decode("1DhLfjA9ij9QFBh7J8ysnN3uvGcsNQa7vaxKEwbYEMSEXuZbgyCtUAn5FhadZHuh7wergdpyrfuDX2TpddoWtu14HkVkFQsuHzNuPg3LAuyhQwiuKDxLtmjWkDExx651o7Gun8VYkbDVPvabYSa2Kgbei59YyUKhRztrfySngpUr17HDn38e6RT9hmmkfL8jL8FiTsqrkFgxCYKQXaBkHQBswy7rWUgP8kT65wJdAgXykW2WwyyNMKtYUiX2iLGGNDfbt4EFiJAQbPZJBtEdwnhP66hM")), - golang.Must(base58.Decode("9VBV1V9DJ2uqDd99zZaCsuQp6v95vwsfuty2wGQKDPZTg4cmbRqZUgZzJkgEJWk6ps2z87M5zRQ4FisjcskpSZoSxZL4Zjpb")), + cutl.Must(blob.NameFromString("251SEdnHjwyvUqX1EZnuKruta4yHMkTDed7LGoi3nUJwhx")), + cutl.Must(hex.DecodeString("00c44bbf343e578f995dc0e8b4d1119c64973003a2ad68a3b6d1ce6ed9a0a79f7b7daace91b391cace11617f9af0bbb83c9d8a18d930d099444f0f78c1c706511339c272a05403e635ca1d237c936878cfcb3c2456498bf2ca4e12e4246f8403f6b8c462dabfeadc0e0000000000000000189afcc9e1dba6ee8e3926cf2b22da225a5a59806eee5cc737c11b6f9a4f19082414b54cfba2f3498045e1a0c232e532a20ba81f98122839d6867de9b929251cf43d5c7c7be78ac0cbebf507c2c601422a2a6c33b1a2ba1525130673d0c040")), + cutl.Must(hex.DecodeString("c11b6f9a4f19082414b54cfba2f3498045e1a0c232e532a20ba81f98122839d6867de9b929251cf43d5c7c7be78ac0cbebf507c2c601422a2a6c33b1a2ba1525130673d0c040")), }, { - golang.Must(common.BlobNameFromString("27vP1JG4VJNZvQJ4Zfhy3H5xKugurbh89B7rKTcStM9guB")), - golang.Must(base58.Decode("1eE2wp1836WtQmEbjdavggJvFPU7dZbQQH5EBS2LwBL2rYjArM9mjvWFEGSfXrQCHscqdGy68exskkPXpGko2HezEAoz4UyQevHphVR5QP1JdLYYmAb4yA63bSznXz6osc8EyxvcKtLGoyfss7omAwrtGLeq1NNiYniXBiJJtuJxtKanw4GAPzn8mpoqhmZQFd36VV5MtLNFpTz5S8ke7MZSkCRKYLJutBxev9fZ5xvt2gqYWEQizWgV691juLC4FA5H82cBq2ZKwUwF4ad1JVcu822AA")), - golang.Must(base58.Decode("PKfeHiNXhYXvq4nu6QKyVTgAXwiLBBJWg6LgZvpgMY82TU5WBBFMdTZQs18kD4iVpkGzH4fjupcRFZJVwJ6rouakMJF6mtvk6")), + cutl.Must(blob.NameFromString("27vP1JG4VJNZvQJ4Zfhy3H5xKugurbh89B7rKTcStM9guB")), + cutl.Must(hex.DecodeString("008266a49daf44cd25f425ad31ffe88a21d160e9da947b51e733271f8ac71652f876882dc5dbc0dd3eead04bd6d49e826a7ed04a039e825b5353d52054209c18d19803cb68c63acc89e11a284b82a7515ef3b6eb64035d1b9052ea198c1874860afa1d98052f4f1304000000000000000118c001789a1e57030e92a620b6f89abf005bb1f7e389ddaa4a7315d299c9af674bfbdf81ba59fb8d99ad5d5e07779153e90bafaf6b2b755c1de90c0f6118bbc49096e090b5b4a6d17cccabfd10ea957b81552a63da648c5cc27d9ac4fecf630f")), + cutl.Must(hex.DecodeString("7315d299c9af674bfbdf81ba59fb8d99ad5d5e07779153e90bafaf6b2b755c1de90c0f6118bbc49096e090b5b4a6d17cccabfd10ea957b81552a63da648c5cc27d9ac4fecf630f")), }, { - golang.Must(common.BlobNameFromString("e3T1HcdDLc73NHed2SFu5XHUQx5KDwgdAYTMmmEk2Ekqm")), - golang.Must(base58.Decode("1yULPpEx3gjpKNBLCEzb2oj2xRcdGfr88CztgfYfEipBGiJCijqWBEEmVAQJ4F33AoJyYkq9Rmj6n9ChngFR7TP8jHjddQM2sKqyDi1NUAmWi7TdGCh79FXTGR12r1RNoNPfqUVv1YZjyNsCgw5cN9WetWgoj5jbdxrqkyq3UjnqM1gEfazdKCyfvWurWr3aWRy4GxQuAQDxfccpSkBxVfzchb4CyRftPt28Lc85g4qGA3oHiLDrwh1qX29gFuZqse8Nq3rLsTUT5vNiLbd1Kr")), - golang.Must(base58.Decode("ULEdmCvFAc593MMZ1Yyd6etYP6ofZE8jE41hLWp7mUUs2DyfP3y9BguoyNLK5KumSLqy6vWDGG81CnMkqa8iaiL1jz")), + cutl.Must(blob.NameFromString("e3T1HcdDLc73NHed2SFu5XHUQx5KDwgdAYTMmmEk2Ekqm")), + cutl.Must(hex.DecodeString("00628297af66c4e51f1f8d7c491e240ced24cb9172e0056ec3f008d781ecefe177d7b068640f5feec1bafa2974c8b3a793bdcc25b6a7032199b6bd690cb47ff576ec37b36f3fdf9787afbc056fbd42e04e3bad2950aabf80d7f5f8e5438ed6b256c4c5be65f8ba5c0d0000000000000002188ccb210ee53218f74d24c14a9417394a767d74529d6a3618462bd895adfbd18dbc2b95074edd121e734edbde1c9c8e10aea5f7cbfedc792030c43539b6b96c7fee06f003db9e62fcb76ed659983ca73d3fca600c9dafc9e282a5")), + cutl.Must(hex.DecodeString("462bd895adfbd18dbc2b95074edd121e734edbde1c9c8e10aea5f7cbfedc792030c43539b6b96c7fee06f003db9e62fcb76ed659983ca73d3fca600c9dafc9e282a5")), }, } // nolint:lll // test data vectors var DynamicLinkPropagationData = []struct { - Name *common.BlobName + Name *blob.Name Data []byte Expected []byte }{ { - golang.Must(common.BlobNameFromString("GUnL66Lyv2Qs4baxPhy59kF4dsB9HWakvTjMBjNGFLT6g")), - golang.Must(base58.Decode("17Jk3QJMCABypJWuAivYwMi43gN1KxPVy3qg1e4HYQFe8BCQPVm6GX8cQX3eKBQ2zdmzs9wRqESbGzCELdM9Kn7RoNmJiA7LY7hg66iPrWGfikzhJtRfFqS7eXs4ohLqbcaNXQt3i36JSxkeriTJBCEuzi86uAUqL9oJm5uqQJ7QBehXPDX7pjFZGi3QKA1JfPwsJUsZEwwfJPX2jhsZHDCgnpdRJoVaGQ6zj3u9PVoTCNqiy5m534o6Dejer4yJQWxvxeNJcRgyoCGRek1ByQGyChziW")), - golang.Must(base58.Decode("PiS95EiCcNaz4dkpMGYd1hSjPzTR28Rx7nTWwb8yocFVLVJoVWoqHjE8u9FtrSfB9qUufCHHRaS95oKmFE9WTrdNRr5zkQ5xn")), + cutl.Must(blob.NameFromString("GUnL66Lyv2Qs4baxPhy59kF4dsB9HWakvTjMBjNGFLT6g")), + cutl.Must(hex.DecodeString("0016170c399aa4af538d1fa5c58eb87a48350796f86ab350451ad155dc74d6ca2aab63effca2df99dbd72b548cf522d00e1e53e8de6c3a46f69894e9e4b0c400054a03f8d5857cb1fb55447404b3ab47aea74a7811b98063210c0b2397928e98589e9273c9afaa1100000000000000271018146a18768320ddd9ebc54edc464ccff1fc48dd401dfa4cbc751beb66b186c68be88fe3599d1d36d6974d41221e96d4b1578cf27708f74f79a3514596edeb9f7d100ea923940a5a9566898a746e6833a7f148b8f2e0cb3c4ed9383a225c0a9b")), + cutl.Must(hex.DecodeString("751beb66b186c68be88fe3599d1d36d6974d41221e96d4b1578cf27708f74f79a3514596edeb9f7d100ea923940a5a9566898a746e6833a7f148b8f2e0cb3c4ed9383a225c0a9b")), }, { - golang.Must(common.BlobNameFromString("GUnL66Lyv2Qs4baxPhy59kF4dsB9HWakvTjMBjNGFLT6g")), - golang.Must(base58.Decode("17Jk3QJMCABypJWuAivYwMi43gN1KxPVy3qg1e4HYQFe8BCQPVm6GX8aKUfQY1JGDimnrVjYEythtb2CP3SrmYzHf3typ12JKUuCcrHThgZodib6AjLrhV4qZFpqX2DcRscG1oHGX13Tyny8FhGTPio6mgHYze27vPpcNFbp1jx5ETXCTHuur9UpAfag1FSakh8CnKayFegQKEav5rfbfb75Y7hnovYncSPTcerdTnFyVqjDSXNhYophu1o8Nupffv6xpeMJMmcUDWhmm5ofWDpew7x7y")), - golang.Must(base58.Decode("GVqyiBNhD53H64AjZHs2hR631JBR7PcDRLSsk6mTaofpFDfZnWWGt6hHsonfW1jUFV4h87cVrQKB6kyKVkPisED1PY22bjGwR")), + cutl.Must(blob.NameFromString("GUnL66Lyv2Qs4baxPhy59kF4dsB9HWakvTjMBjNGFLT6g")), + cutl.Must(hex.DecodeString("0016170c399aa4af538d1fa5c58eb87a48350796f86ab350451ad155dc74d6ca2aab63effca2df99db7a2d9ef0c9cfac858a60942e250c1266ee5ccafb96fe56e1187ed334ab25d398baff9a69e062237b746b931e3f75ff46196e20d99a1ba7ee6ad8649e8e7375090000000000004e2018e6bd8bf59d9de1ab35a134ed9d15eaaa70064964f648cbf14fe66148a3af71d0a4f4a1817dda8f188b13cc03a122b62529a0ac3894f2654ed362e8ff78d24a7d8c3ba5120bfab4e7d7a8c260f3961732d6efdf25fcde89a530ab8e548e2780")), + cutl.Must(hex.DecodeString("4fe66148a3af71d0a4f4a1817dda8f188b13cc03a122b62529a0ac3894f2654ed362e8ff78d24a7d8c3ba5120bfab4e7d7a8c260f3961732d6efdf25fcde89a530ab8e548e2780")), }, { - golang.Must(common.BlobNameFromString("GUnL66Lyv2Qs4baxPhy59kF4dsB9HWakvTjMBjNGFLT6g")), - golang.Must(base58.Decode("17Jk3QJMCABypJWuAivYwMi43gN1KxPVy3qg1e4HYQFe8BCQPVm6GX8aApx2qggEBaUKFT9T3MNwPAicht7Zjiw1Crm2ffEvxFWPupKCcGea11YG4x3NsF2u57V3Z82bhBMfXFHDPywnLnBBx3SQe688vNitGiLzyjBqH9oMCaD3oVKdyKNtE9DmNuTgCRSTnj31FiAaWWzHtqMbVqxNToVp78hhkWudpJDiqJM1Z7DNPK8RGjYDNBrtcbzxfBk4gbSL9usgAGgV7Ty3fiDAmUx8RG2vk")), - golang.Must(base58.Decode("RNffUTysjj6v8JgFbNqpUL9CvnLjiDzM9fehH89p7iTFNXQtDjng1woWPnvUDvuZXSTsxw2ndUo6rfPFpZVuip29ZakLbu49n")), + cutl.Must(blob.NameFromString("GUnL66Lyv2Qs4baxPhy59kF4dsB9HWakvTjMBjNGFLT6g")), + cutl.Must(hex.DecodeString("0016170c399aa4af538d1fa5c58eb87a48350796f86ab350451ad155dc74d6ca2aab63effca2df99db7388571b272b87ba326c5c1f68daac7ebac471986efc9da1bbeb931fc3092753d8ac969c64db5e8d9d8631d8234ac4f3f5174e5fa01dd1818108cadf89b380030000000000004e201872d3ee3f68ff92213de311a2c138c11edc142470b92dd8a07da9d4c95b2d2fcdd3949a68a2844ea6af28d8ac45507eda0a9956503b6fc808e0b4e693f73adb8f50913b33135b9f13a7bc9b9087d71e136d58b6b069d642a8e734cc6bb953c9")), + cutl.Must(hex.DecodeString("7da9d4c95b2d2fcdd3949a68a2844ea6af28d8ac45507eda0a9956503b6fc808e0b4e693f73adb8f50913b33135b9f13a7bc9b9087d71e136d58b6b069d642a8e734cc6bb953c9")), }, } diff --git a/pkg/datastore/testutils/testblobs_test.go b/pkg/datastore/testutils/testblobs_test.go index 396f901..0beec9e 100644 --- a/pkg/datastore/testutils/testblobs_test.go +++ b/pkg/datastore/testutils/testblobs_test.go @@ -19,7 +19,7 @@ package testutils import ( "testing" - "github.com/cinode/go-datastore/pkg/internal/picotestify/require" + "github.com/cinode/go-common/picotestify/require" ) func TestTestBlobs(t *testing.T) { diff --git a/pkg/datastore/testutils/testutils.go b/pkg/datastore/testutils/testutils.go index 4556c00..91b7999 100644 --- a/pkg/datastore/testutils/testutils.go +++ b/pkg/datastore/testutils/testutils.go @@ -21,16 +21,16 @@ import ( "crypto/sha256" "io" + "github.com/cinode/go-common/blob" + "github.com/cinode/go-common/cutl" "github.com/cinode/go-datastore/pkg/blobtypes" - "github.com/cinode/go-datastore/pkg/common" - "github.com/cinode/go-datastore/pkg/utilities/golang" ) var ( - EmptyBlobNameStatic = golang.Must(common.BlobNameFromHashAndType(sha256.New().Sum(nil), blobtypes.Static)) - EmptyBlobNameDynamicLink = golang.Must(common.BlobNameFromHashAndType(sha256.New().Sum(nil), blobtypes.DynamicLink)) + EmptyBlobNameStatic = cutl.Must(blob.NameFromHashAndType(sha256.New().Sum(nil), blobtypes.Static)) + EmptyBlobNameDynamicLink = cutl.Must(blob.NameFromHashAndType(sha256.New().Sum(nil), blobtypes.DynamicLink)) - EmptyBlobNamesOfAllTypes = []*common.BlobName{ + EmptyBlobNamesOfAllTypes = []*blob.Name{ EmptyBlobNameStatic, EmptyBlobNameDynamicLink, } diff --git a/pkg/datastore/testutils/testutils_test.go b/pkg/datastore/testutils/testutils_test.go index 85d7cf7..eea6ca9 100644 --- a/pkg/datastore/testutils/testutils_test.go +++ b/pkg/datastore/testutils/testutils_test.go @@ -21,7 +21,7 @@ import ( "io" "testing" - "github.com/cinode/go-datastore/pkg/internal/picotestify/require" + "github.com/cinode/go-common/picotestify/require" ) func TestBReaderOnRead(t *testing.T) { diff --git a/pkg/datastoreconformancetest/inmemory_test.go b/pkg/datastoreconformancetest/inmemory_test.go index 0c9d269..59a5ec8 100644 --- a/pkg/datastoreconformancetest/inmemory_test.go +++ b/pkg/datastoreconformancetest/inmemory_test.go @@ -19,9 +19,9 @@ package datastoreconformancetest_test import ( "testing" + "github.com/cinode/go-common/picotestify/suite" "github.com/cinode/go-datastore/pkg/datastore" "github.com/cinode/go-datastore/pkg/datastoreconformancetest" - "github.com/cinode/go-datastore/pkg/internal/picotestify/suite" ) func TestInMemoryDatastoreTestSuite(t *testing.T) { diff --git a/pkg/datastoreconformancetest/interface_testsuite.go b/pkg/datastoreconformancetest/interface_testsuite.go index ae3b9de..9a17ee0 100644 --- a/pkg/datastoreconformancetest/interface_testsuite.go +++ b/pkg/datastoreconformancetest/interface_testsuite.go @@ -25,13 +25,13 @@ import ( "sync" "testing" + "github.com/cinode/go-common/blob" + "github.com/cinode/go-common/picotestify/require" + "github.com/cinode/go-common/picotestify/suite" "github.com/cinode/go-datastore/pkg/blobtypes" - "github.com/cinode/go-datastore/pkg/common" "github.com/cinode/go-datastore/pkg/datastore" "github.com/cinode/go-datastore/pkg/datastore/testutils" "github.com/cinode/go-datastore/pkg/internal/blobtypes/dynamiclink" - "github.com/cinode/go-datastore/pkg/internal/picotestify/require" - "github.com/cinode/go-datastore/pkg/internal/picotestify/suite" ) type DatastoreTestSuite struct { @@ -79,7 +79,7 @@ func (s *DatastoreTestSuite) TestOpenNonExisting() { func (s *DatastoreTestSuite) TestOpenInvalidBlobType() { t := s.T() - bn, err := common.BlobNameFromHashAndType(sha256.New().Sum(nil), common.NewBlobType(0xFF)) + bn, err := blob.NameFromHashAndType(sha256.New().Sum(nil), blob.NewType(0xFF)) require.NoError(t, err) r, err := s.ds.Open(t.Context(), bn) diff --git a/pkg/datastoreconformancetest/storage_backend_testsuite.go b/pkg/datastoreconformancetest/storage_backend_testsuite.go index 706c2c3..d488c13 100644 --- a/pkg/datastoreconformancetest/storage_backend_testsuite.go +++ b/pkg/datastoreconformancetest/storage_backend_testsuite.go @@ -22,12 +22,12 @@ import ( "io" "testing" + "github.com/cinode/go-common/blob" + "github.com/cinode/go-common/picotestify/require" + "github.com/cinode/go-common/picotestify/suite" "github.com/cinode/go-datastore/pkg/blobtypes" - "github.com/cinode/go-datastore/pkg/common" "github.com/cinode/go-datastore/pkg/datastore" "github.com/cinode/go-datastore/pkg/datastore/testutils" - "github.com/cinode/go-datastore/pkg/internal/picotestify/require" - "github.com/cinode/go-datastore/pkg/internal/picotestify/suite" ) type StorageBackendTestSuite struct { @@ -151,7 +151,7 @@ func (s *StorageBackendTestSuite) TestStorageSaveOpenCancelSuccess() { func (s *StorageBackendTestSuite) TestStorageDelete() { t := s.T() - blobNames := []*common.BlobName{} + blobNames := []*blob.Name{} blobDatas := [][]byte{} t.Run("generate test data", func(t *testing.T) { @@ -161,7 +161,7 @@ func (s *StorageBackendTestSuite) TestStorageDelete() { "third", } { h := sha256.Sum256([]byte(d)) - bn, err := common.BlobNameFromHashAndType(h[:], blobtypes.Static) + bn, err := blob.NameFromHashAndType(h[:], blobtypes.Static) require.NoError(t, err) blobNames = append(blobNames, bn) diff --git a/pkg/internal/base58/base58.go b/pkg/internal/base58/base58.go deleted file mode 100644 index 39d37cd..0000000 --- a/pkg/internal/base58/base58.go +++ /dev/null @@ -1,103 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package base58 - -import ( - "errors" - "fmt" - "math/big" -) - -var ErrInvalidBase58Character = errors.New("invalid base58 character") - -var bn2btc, btc2bn = func() (bn2btc, btc2bn [256]byte) { - const btcAlphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" - const bitNumDigits = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUV" - - for i := range btcAlphabet { - bn2btc[bitNumDigits[i]] = btcAlphabet[i] - btc2bn[btcAlphabet[i]] = bitNumDigits[i] - } - - return bn2btc, btc2bn -}() - -func Encode(data []byte) string { - // Count leading zero bytes, those are treated especially - leadingZeros := 0 - for _, b := range data { - if b == 0 { - leadingZeros++ - } else { - break - } - } - - var bi big.Int - bi.SetBytes(data[leadingZeros:]) - - txt := bi.Text(58) - - if txt == "0" { - txt = "" - } - - res := make([]byte, leadingZeros+len(txt)) - - // Add '1' characters for leading zeros - for i := 0; i < leadingZeros; i++ { - res[i] = '1' - } - - // Convert the rest - for i, b := range txt { - res[leadingZeros+i] = bn2btc[b] - } - - return string(res) -} - -func Decode(s string) ([]byte, error) { - // Split into leading bytes and bit.Int-compatible representation - leadingZeros := 0 - leadingZerosDone := false - bnText := make([]byte, 0, len(s)) - for _, b := range s { - if !leadingZerosDone && b == '1' { - leadingZeros++ - } else if btc2bn[b] != 0 { - leadingZerosDone = true - bnText = append(bnText, btc2bn[b]) - } else { - return nil, fmt.Errorf("%w: '%v'", ErrInvalidBase58Character, b) - } - } - - if len(bnText) == 0 { - return make([]byte, leadingZeros), nil - } - - var bn big.Int - bn.SetString(string(bnText), 58) - - bnBytes := bn.Bytes() - - result := make([]byte, leadingZeros+len(bnBytes)) - copy(result[leadingZeros:], bnBytes) - - return result, nil -} diff --git a/pkg/internal/base58/base58_encode_decode.json b/pkg/internal/base58/base58_encode_decode.json deleted file mode 100644 index 7a94c63..0000000 --- a/pkg/internal/base58/base58_encode_decode.json +++ /dev/null @@ -1,23 +0,0 @@ -[ - ["", ""], - ["61", "2g"], - ["626262", "a3gV"], - ["636363", "aPEr"], - ["73696d706c792061206c6f6e6720737472696e67", "2cFupjhnEsSn59qHXstmK2ffpLv2"], - ["00eb15231dfceb60925886b67d065299925915aeb172c06647", "1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L"], - ["516b6fcd0f", "ABnLTmg"], - ["bf4f89001e670274dd", "3SEo3LWLoPntC"], - ["572e4794", "3EFU7m"], - ["ecac89cad93923c02321", "EJDM8drfXA6uyA"], - ["10c8511e", "Rt5zm"], - ["00000000000000000000", "1111111111"], - ["00000000000000000000000000000000000000000000000000000000000000000000000000000000", "1111111111111111111111111111111111111111"], - ["00000000000000000000000000000000000000000000000000000000000000000000000000000001", "1111111111111111111111111111111111111112"], - ["0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ec39d04c37e71e5d591881f6", "111111111111111111111111111111111111111111111111111111111111111111111111111111111111115TYzLYH1udmLdzCLM"], - ["000111d38e5fc9071ffcd20b4a763cc9ae4f252bb4e48fd66a835e252ada93ff480d6dd43dc62a641155a5", "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"], - ["000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", "1cWB5HCBdLjAuqGGReWE3R3CguuwSjw6RHn39s2yuDRTS5NsBgNiFpWgAnEx6VQi8csexkgYw3mdYrMHr8x9i7aEwP8kZ7vccXWqKDvGv3u1GxFKPuAkn8JCPPGDMf3vMMnbzm6Nh9zh1gcNsMvH3ZNLmP5fSG6DGbbi2tuwMWPthr4boWwCxf7ewSgNQeacyozhKDDQQ1qL5fQFUW52QKUZDZ5fw3KXNQJMcNTcaB723LchjeKun7MuGW5qyCBZYzA1KjofN1gYBV3NqyhQJ3Ns746GNuf9N2pQPmHz4xpnSrrfCvy6TVVz5d4PdrjeshsWQwpZsZGzvbdAdN8MKV5QsBDY"], - ["271F359E", "zzzzy"], - ["271F359F", "zzzzz"], - ["271F35A0", "211111"], - ["271F35A1", "211112"] - ] \ No newline at end of file diff --git a/pkg/internal/base58/base58_test.go b/pkg/internal/base58/base58_test.go deleted file mode 100644 index b9d06d9..0000000 --- a/pkg/internal/base58/base58_test.go +++ /dev/null @@ -1,128 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, - Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, - software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package base58_test - -import ( - _ "embed" - "encoding/hex" - "encoding/json" - "fmt" - "testing" - - "github.com/cinode/go-datastore/pkg/internal/base58" - "github.com/cinode/go-datastore/pkg/internal/picotestify/require" -) - -// Test cases from (MIT License): -// -// https://github.com/bitcoin/bitcoin/blob/master/src/test/data/base58_encode_decode.json -// -//go:embed base58_encode_decode.json -var testJSONData string - -type testCase struct { - data []byte - expected string -} - -func validTestCases(t require.TestingT) []testCase { - testCases := []testCase{ - { - []byte{}, - "", - }, - { - []byte("Hello World!"), - "2NEpo7TZRRrLZSi2U", - }, - { - []byte("The quick brown fox jumps over the lazy dog."), - "USm3fpXnKG5EUBx2ndxBDMPVciP5hGey2Jh4NDv6gmeo1LkMeiKrLJUUBk6Z", - }, - { - []byte{0x00, 0x00, 0x28, 0x7f, 0xb4, 0xcd}, - "11233QC4", - }, - } - - jsonTests := [][]string{} - require.NoError(t, json.Unmarshal([]byte(testJSONData), &jsonTests)) - - for _, tc := range jsonTests { - require.Len(t, tc, 2) - - data, err := hex.DecodeString(tc[0]) - require.NoError(t, err) - expected := tc[1] - - testCases = append(testCases, testCase{ - data: data, - expected: expected, - }) - } - return testCases -} - -func TestEncodeDecode(t *testing.T) { - for _, test := range validTestCases(t) { - t.Run(fmt.Sprintf("data=%v", test.data), func(t *testing.T) { - encoded := base58.Encode(test.data) - require.Equal(t, test.expected, encoded) - - decodedBack, err := base58.Decode(encoded) - require.NoError(t, err) - - require.Equal(t, test.data, decodedBack) - }) - } -} - -func TestErrorOnInvalidDecode(t *testing.T) { - for _, test := range []string{ - "@", - "3SEo3LWLoPntC@", - "@3SEo3LWLoPntC", - "3SEo3@LWLoPntC", - } { - t.Run(test, func(t *testing.T) { - decoded, err := base58.Decode(test) - require.Nil(t, decoded) - require.ErrorIs(t, err, base58.ErrInvalidBase58Character) - }) - } -} - -func FuzzEncodeDecode(f *testing.F) { - for _, test := range validTestCases(f) { - f.Add(test.data) - } - - f.Fuzz(func(t *testing.T, a []byte) { - if len(a) > 256 { - // No point in testing those - t.SkipNow() - } - - str := base58.Encode(a) - back, err := base58.Decode(str) - require.NoError(t, err) - require.Equal(t, a, back) - }) -} diff --git a/pkg/internal/blobtypes/dynamiclink/public.go b/pkg/internal/blobtypes/dynamiclink/public.go index dd625a7..a566c0c 100644 --- a/pkg/internal/blobtypes/dynamiclink/public.go +++ b/pkg/internal/blobtypes/dynamiclink/public.go @@ -24,8 +24,8 @@ import ( "hash" "io" + "github.com/cinode/go-common/blob" "github.com/cinode/go-datastore/pkg/blobtypes" - "github.com/cinode/go-datastore/pkg/common" "github.com/cinode/go-datastore/pkg/internal/utilities/cipherfactory" "github.com/cinode/go-datastore/pkg/internal/utilities/errreader" "github.com/cinode/go-datastore/pkg/internal/utilities/validatingreader" @@ -94,14 +94,14 @@ type Public struct { nonce uint64 } -func (d *Public) BlobName() *common.BlobName { +func (d *Public) BlobName() *blob.Name { hasher := sha256.New() storeByte(hasher, reservedByteValue) storeBuff(hasher, d.publicKey) storeUint64(hasher, d.nonce) - bn, _ := common.BlobNameFromHashAndType(hasher.Sum(nil), blobtypes.DynamicLink) + bn, _ := blob.NameFromHashAndType(hasher.Sum(nil), blobtypes.DynamicLink) return bn } @@ -111,7 +111,7 @@ func (d *Public) BlobName() *common.BlobName { // the data on-the-fly from another reader). type PublicReader struct { r io.Reader - iv *common.BlobIV + iv *blob.IV signature []byte Public contentVersion uint64 @@ -121,7 +121,7 @@ type PublicReader struct { // // Invalid links are rejected - i.e. if there's any error while reading the data // or when the validation of the link fails for whatever reason -func FromPublicData(name *common.BlobName, r io.Reader) (*PublicReader, error) { +func FromPublicData(name *blob.Name, r io.Reader) (*PublicReader, error) { dl := PublicReader{ Public: Public{ publicKey: make([]byte, ed25519.PublicKeySize), @@ -177,7 +177,7 @@ func FromPublicData(name *common.BlobName, r io.Reader) (*PublicReader, error) { if err != nil { return nil, err } - dl.iv = common.BlobIVFromBytes(iv) + dl.iv = blob.IVFromBytes(iv) // Starting from validations at this point, errors are returned while reading. // This is to prepare for future improvements when real streaming is @@ -275,7 +275,7 @@ func (d *PublicReader) ivGeneratorPrefilled() cipherfactory.IVGenerator { return ivGenerator } -func (d *PublicReader) validateKeyInLinkData(key *common.BlobKey, r io.Reader) error { +func (d *PublicReader) validateKeyInLinkData(key *blob.Key, r io.Reader) error { // At the beginning of the data there's the key validation block, // that block contains a proof that the encryption key was deterministically derived // from the blob name (thus preventing weak key attack) @@ -307,7 +307,7 @@ func (d *PublicReader) validateKeyInLinkData(key *common.BlobKey, r io.Reader) e return nil } -func (d *PublicReader) GetLinkDataReader(key *common.BlobKey) (io.Reader, error) { +func (d *PublicReader) GetLinkDataReader(key *blob.Key) (io.Reader, error) { r, err := cipherfactory.StreamCipherReader(key, d.iv, d.GetEncryptedLinkReader()) if err != nil { return nil, err diff --git a/pkg/internal/blobtypes/dynamiclink/public_test.go b/pkg/internal/blobtypes/dynamiclink/public_test.go index 115248c..86b2dc2 100644 --- a/pkg/internal/blobtypes/dynamiclink/public_test.go +++ b/pkg/internal/blobtypes/dynamiclink/public_test.go @@ -26,9 +26,9 @@ import ( "sort" "testing" + "github.com/cinode/go-common/blob" + "github.com/cinode/go-common/picotestify/require" "github.com/cinode/go-datastore/pkg/blobtypes" - "github.com/cinode/go-datastore/pkg/common" - "github.com/cinode/go-datastore/pkg/internal/picotestify/require" "github.com/cinode/go-datastore/pkg/internal/utilities/cipherfactory" "github.com/cinode/go-datastore/pkg/internal/utilities/errreader" ) @@ -37,7 +37,7 @@ func TestFromPublicData(t *testing.T) { t.Run("Ensure we don't crash on truncated data", func(t *testing.T) { for i := 0; i < 1000; i++ { data := make([]byte, i) - dl, err := FromPublicData(&common.BlobName{}, bytes.NewReader(data)) + dl, err := FromPublicData(&blob.Name{}, bytes.NewReader(data)) require.ErrorIs(t, err, ErrInvalidDynamicLinkData) require.Nil(t, dl) } @@ -45,7 +45,7 @@ func TestFromPublicData(t *testing.T) { t.Run("Do not accept the link if reserved byte is not zero", func(t *testing.T) { data := []byte{0xFF, 0, 0, 0} - dl, err := FromPublicData(&common.BlobName{}, bytes.NewReader(data)) + dl, err := FromPublicData(&blob.Name{}, bytes.NewReader(data)) require.ErrorIs(t, err, ErrInvalidDynamicLinkData) require.ErrorIs(t, err, ErrInvalidDynamicLinkDataReservedByte) require.Nil(t, dl) @@ -88,6 +88,25 @@ func TestFromPublicData(t *testing.T) { } }) + t.Run("can't read data from reader twice", func(t *testing.T) { + dl, err := Create(rand.Reader) + require.NoError(t, err) + + pr, key, err := dl.UpdateLinkData(bytes.NewReader(nil), 0) + require.NoError(t, err) + require.NotEmpty(t, key) + + r := pr.GetPublicDataReader() + require.NotNil(t, r) + + _, err = io.ReadAll(r) + require.NoError(t, err) + + require.Panics(t, func() { + _ = pr.GetPublicDataReader() + }) + }) + t.Run("valid link serialization and deserialization", func(t *testing.T) { dl, err := Create(rand.Reader) require.NoError(t, err) @@ -305,7 +324,7 @@ func TestPublicReaderGetLinkDataReader(t *testing.T) { // Flip a single bit in IV ivBytes := pr.iv.Bytes() ivBytes[len(ivBytes)/2] ^= 0x80 - pr.iv = common.BlobIVFromBytes(ivBytes) + pr.iv = blob.IVFromBytes(ivBytes) // Because the IV is incorrect, key validation block that is encrypted will be invalid // thus the method will complain about key, not the IV that will fail first @@ -320,7 +339,7 @@ func TestPublicReaderGetLinkDataReader(t *testing.T) { pr, _, err := link.UpdateLinkData(bytes.NewReader([]byte("Hello world")), 0) require.NoError(t, err) - _, err = pr.GetLinkDataReader(&common.BlobKey{}) + _, err = pr.GetLinkDataReader(&blob.Key{}) require.ErrorIs(t, err, cipherfactory.ErrInvalidEncryptionConfigKeyType) }) } diff --git a/pkg/internal/blobtypes/dynamiclink/publisher.go b/pkg/internal/blobtypes/dynamiclink/publisher.go index 80a139b..dc376ed 100644 --- a/pkg/internal/blobtypes/dynamiclink/publisher.go +++ b/pkg/internal/blobtypes/dynamiclink/publisher.go @@ -23,8 +23,8 @@ import ( "errors" "io" + "github.com/cinode/go-common/blob" "github.com/cinode/go-datastore/pkg/blobtypes" - "github.com/cinode/go-datastore/pkg/common" "github.com/cinode/go-datastore/pkg/internal/utilities/cipherfactory" ) @@ -66,7 +66,7 @@ func Create(randSource io.Reader) (*Publisher, error) { }, nil } -func FromAuthInfo(authInfo *common.AuthInfo) (*Publisher, error) { +func FromAuthInfo(authInfo *blob.AuthInfo) (*Publisher, error) { authInfoBytes := authInfo.Bytes() if len(authInfoBytes) != 1+ed25519.SeedSize+8 || authInfoBytes[0] != 0 { return nil, ErrInvalidDynamicLinkAuthInfo @@ -100,15 +100,15 @@ func ReNonce(p *Publisher, randSource io.Reader) (*Publisher, error) { }, nil } -func (dl *Publisher) AuthInfo() *common.AuthInfo { +func (dl *Publisher) AuthInfo() *blob.AuthInfo { var ret [1 + ed25519.SeedSize + 8]byte ret[0] = reservedByteValue copy(ret[1:], dl.privKey.Seed()) binary.BigEndian.PutUint64(ret[1+ed25519.SeedSize:], dl.nonce) - return common.AuthInfoFromBytes(ret[:]) + return blob.AuthInfoFromBytes(ret[:]) } -func (dl *Publisher) calculateEncryptionKey() (key *common.BlobKey, signature []byte) { +func (dl *Publisher) calculateEncryptionKey() (key *blob.Key, signature []byte) { dataSeed := append( []byte{signatureForEncryptionKeyGeneration}, dl.BlobName().Bytes()..., @@ -124,12 +124,12 @@ func (dl *Publisher) calculateEncryptionKey() (key *common.BlobKey, signature [] return key, signature } -func (dl *Publisher) EncryptionKey() *common.BlobKey { +func (dl *Publisher) EncryptionKey() *blob.Key { key, _ := dl.calculateEncryptionKey() return key } -func (dl *Publisher) UpdateLinkData(r io.Reader, version uint64) (*PublicReader, *common.BlobKey, error) { +func (dl *Publisher) UpdateLinkData(r io.Reader, version uint64) (*PublicReader, *blob.Key, error) { encryptionKey, kvb := dl.calculateEncryptionKey() // key validation block precedes the link data diff --git a/pkg/internal/blobtypes/dynamiclink/publisher_test.go b/pkg/internal/blobtypes/dynamiclink/publisher_test.go index 3d25313..e8918ad 100644 --- a/pkg/internal/blobtypes/dynamiclink/publisher_test.go +++ b/pkg/internal/blobtypes/dynamiclink/publisher_test.go @@ -23,8 +23,8 @@ import ( "io" "testing" - "github.com/cinode/go-datastore/pkg/common" - "github.com/cinode/go-datastore/pkg/internal/picotestify/require" + "github.com/cinode/go-common/blob" + "github.com/cinode/go-common/picotestify/require" "github.com/cinode/go-datastore/pkg/internal/utilities/errreader" ) @@ -72,7 +72,7 @@ func TestFromAuthInfo(t *testing.T) { t.Run("Invalid auth info", func(t *testing.T) { authInfoBytes := authInfo.Bytes() for i := 0; i < len(authInfoBytes)-1; i++ { - brokenAuthInfo := common.AuthInfoFromBytes(authInfoBytes[:i]) + brokenAuthInfo := blob.AuthInfoFromBytes(authInfoBytes[:i]) dl2, err := FromAuthInfo(brokenAuthInfo) require.ErrorIs(t, err, ErrInvalidDynamicLinkAuthInfo) require.Nil(t, dl2) diff --git a/pkg/internal/blobtypes/dynamiclink/utils_test.go b/pkg/internal/blobtypes/dynamiclink/utils_test.go index f9c8941..93f2ce8 100644 --- a/pkg/internal/blobtypes/dynamiclink/utils_test.go +++ b/pkg/internal/blobtypes/dynamiclink/utils_test.go @@ -19,7 +19,7 @@ package dynamiclink import ( "testing" - "github.com/cinode/go-datastore/pkg/internal/picotestify/require" + "github.com/cinode/go-common/picotestify/require" ) func TestPanicIf(t *testing.T) { diff --git a/pkg/internal/blobtypes/dynamiclink/vectors_test.go b/pkg/internal/blobtypes/dynamiclink/vectors_test.go index 6b4e2ed..3850fc8 100644 --- a/pkg/internal/blobtypes/dynamiclink/vectors_test.go +++ b/pkg/internal/blobtypes/dynamiclink/vectors_test.go @@ -21,17 +21,17 @@ import ( "io" "testing" - "github.com/cinode/go-datastore/pkg/common" - "github.com/cinode/go-datastore/pkg/internal/picotestify/require" - "github.com/cinode/go-datastore/testvectors" + "github.com/cinode/go-common/blob" + "github.com/cinode/go-common/picotestify/require" + "github.com/cinode/go-testvectors/testvectors" ) func TestVectors(t *testing.T) { - for testCase := range testvectors.AllTestCases { + for _, testCase := range testvectors.TestCases { t.Run(testCase.Name, func(t *testing.T) { t.Run("validate public scope", func(t *testing.T) { err := func() error { - bn, err := common.BlobNameFromBytes(testCase.BlobName) + bn, err := blob.NameFromBytes(testCase.BlobName) if err != nil { return err } @@ -62,7 +62,7 @@ func TestVectors(t *testing.T) { t.Run("validate private scope", func(t *testing.T) { err := func() error { - bn, err := common.BlobNameFromBytes(testCase.BlobName) + bn, err := blob.NameFromBytes(testCase.BlobName) if err != nil { return err } @@ -76,7 +76,7 @@ func TestVectors(t *testing.T) { } dr, err := pr.GetLinkDataReader( - common.BlobKeyFromBytes(testCase.EncryptionKey), + blob.KeyFromBytes(testCase.EncryptionKey), ) if err != nil { return err diff --git a/pkg/internal/picotestify/assert/assert.go b/pkg/internal/picotestify/assert/assert.go deleted file mode 100644 index 3de708e..0000000 --- a/pkg/internal/picotestify/assert/assert.go +++ /dev/null @@ -1,299 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package assert - -import ( - "errors" - "fmt" - "reflect" - "regexp" - "strings" - - "golang.org/x/exp/constraints" -) - -type TestingT interface { - Helper() - Error(msgAndArgs ...any) -} - -func fail(t TestingT, msgAndArgs []any, fmtString string, fmtArgs ...any) { - t.Error( - append( - []any{fmt.Sprintf(fmtString, fmtArgs...)}, - msgAndArgs..., - )..., - ) -} - -func Equal[T any](t TestingT, expected, actual T, msgAndArgs ...any) bool { - if reflect.DeepEqual(expected, actual) { - return true - } - - t.Helper() - fail(t, msgAndArgs, "Values not equal, expected: %+v, actual: %+v", expected, actual) - - return false -} - -func NotEqual[T any](t TestingT, expected, actual T, msgAndArgs ...any) bool { - if !reflect.DeepEqual(expected, actual) { - return true - } - - t.Helper() - fail(t, msgAndArgs, "Values are equal: %v", actual) - - return false -} - -func True(t TestingT, condition bool, msgAndArgs ...any) bool { - if condition { - return true - } - - t.Helper() - fail(t, msgAndArgs, "Condition is not true") - - return false -} - -func False(t TestingT, condition bool, msgAndArgs ...any) bool { - if !condition { - return true - } - - t.Helper() - fail(t, msgAndArgs, "Condition is not false") - - return false -} - -func Nil(t TestingT, object any, msgAndArgs ...any) bool { - if isNil(object) { - return true - } - - t.Helper() - fail(t, msgAndArgs, "Object is not nil") - - return false -} - -func NotNil(t TestingT, object any, msgAndArgs ...any) bool { - if !isNil(object) { - return true - } - - t.Helper() - fail(t, msgAndArgs, "Object is nil") - - return false -} - -func isNil(object any) bool { - if object == nil { - return true - } - - value := reflect.ValueOf(object) - switch value.Kind() { - case - reflect.Chan, - reflect.Func, - reflect.Interface, - reflect.Map, - reflect.Pointer, - reflect.Slice, - reflect.UnsafePointer: - if value.IsNil() { - return true - } - } - - return false -} - -func NoError(t TestingT, err error, msgAndArgs ...any) bool { - if err == nil { - return true - } - - t.Helper() - fail(t, msgAndArgs, "Received unexpected error: %v", err) - - return false -} - -func ErrorIs(t TestingT, err error, target error, msgAndArgs ...any) bool { - if errors.Is(err, target) { - return true - } - - t.Helper() - fail(t, msgAndArgs, "Error is not %T: %v", target, err) - - return false -} - -func ErrorContains(t TestingT, err error, contains string, msgAndArgs ...any) bool { - if err != nil && strings.Contains(err.Error(), contains) { - return true - } - - t.Helper() - fail(t, msgAndArgs, "Error %q does not contain %q", err.Error(), contains) - - return false -} - -func NotEmpty(t TestingT, object any, msgAndArgs ...any) bool { - if !isEmpty(object) { - return true - } - - t.Helper() - fail(t, msgAndArgs, "Object is empty") - - return false -} - -func isEmpty(object any) bool { - if object == nil { - return true - } - - value := reflect.ValueOf(object) - switch value.Kind() { - case reflect.Array, reflect.Map, reflect.Slice, reflect.String: - return value.Len() == 0 - } - - return false -} - -func Greater[T constraints.Ordered](t TestingT, a, b T, msgAndArgs ...any) bool { - if a > b { - return true - } - - t.Helper() - fail(t, msgAndArgs, "Expected %v to be greater than %v", a, b) - - return false -} - -func GreaterOrEqual[T constraints.Ordered](t TestingT, a, b T, msgAndArgs ...any) bool { - if a >= b { - return true - } - - t.Helper() - fail(t, msgAndArgs, "Expected %v to be greater or equal than %v", a, b) - - return false -} - -func isZero(value any) bool { - return value == nil || reflect.DeepEqual(value, reflect.Zero(reflect.TypeOf(value)).Interface()) -} - -func Zero(t TestingT, value any, msgAndArgs ...any) bool { - if isZero(value) { - return true - } - - t.Helper() - fail(t, msgAndArgs, "Expected %v to be zero", value) - - return false -} - -func NotZero(t TestingT, value any, msgAndArgs ...any) bool { - if !isZero(value) { - return true - } - - t.Helper() - fail(t, msgAndArgs, "Expected %v to not be zero", value) - - return false -} - -func didPanic(f func()) (didPanic bool) { - didPanic = true - - defer func() { recover() }() - - f() - didPanic = false - - return -} - -func Panics(t TestingT, f func(), msgAndArgs ...any) bool { - if didPanic(f) { - return true - } - - t.Helper() - fail(t, msgAndArgs, "Expected function to panic") - - return false -} - -func NotPanics(t TestingT, f func(), msgAndArgs ...any) bool { - if !didPanic(f) { - return true - } - - t.Helper() - fail(t, msgAndArgs, "Expected function not to panic") - - return false -} - -func Regexp(t TestingT, pattern string, text string, msgAndArgs ...any) bool { - if matches, err := regexp.MatchString(pattern, text); err != nil { - t.Helper() - fail(t, msgAndArgs, "Invalid regexp pattern: %v", err) - t.Error(msgAndArgs...) - - return false - } else if matches { - return true - } - - t.Helper() - fail(t, msgAndArgs, "Expected %v to match %v", text, pattern) - - return false -} - -func Len(t TestingT, obj any, len int, msgAndArgs ...any) bool { - r := reflect.ValueOf(obj) - - if r.Len() == len { - return true - } - - t.Helper() - fail(t, msgAndArgs, "Expected length of %d, found: %d", len, r.Len()) - - return false -} diff --git a/pkg/internal/picotestify/require/require.go b/pkg/internal/picotestify/require/require.go deleted file mode 100644 index a90f468..0000000 --- a/pkg/internal/picotestify/require/require.go +++ /dev/null @@ -1,153 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package require - -import ( - "github.com/cinode/go-datastore/pkg/internal/picotestify/assert" - "golang.org/x/exp/constraints" -) - -type TestingT interface { - assert.TestingT - FailNow() -} - -func Equal[T any](t TestingT, expected, actual T, msgAndArgs ...any) { - t.Helper() - if !assert.Equal(t, expected, actual, msgAndArgs...) { - t.FailNow() - } -} - -func NotEqual[T any](t TestingT, expected, actual T, msgAndArgs ...any) { - t.Helper() - if !assert.NotEqual(t, expected, actual, msgAndArgs...) { - t.FailNow() - } -} - -func Greater[T constraints.Ordered](t TestingT, a, b T, msgAndArgs ...any) { - t.Helper() - if !assert.Greater(t, a, b, msgAndArgs...) { - t.FailNow() - } -} - -func GreaterOrEqual[T constraints.Ordered](t TestingT, a, b T, msgAndArgs ...any) { - t.Helper() - if !assert.GreaterOrEqual(t, a, b, msgAndArgs...) { - t.FailNow() - } -} - -func True(t TestingT, condition bool, msgAndArgs ...any) { - t.Helper() - if !assert.True(t, condition, msgAndArgs...) { - t.FailNow() - } -} - -func False(t TestingT, condition bool, msgAndArgs ...any) { - t.Helper() - if !assert.False(t, condition, msgAndArgs...) { - t.FailNow() - } -} - -func Nil(t TestingT, object any, msgAndArgs ...any) { - t.Helper() - if !assert.Nil(t, object, msgAndArgs...) { - t.FailNow() - } -} - -func NotNil(t TestingT, object any, msgAndArgs ...any) { - t.Helper() - if !assert.NotNil(t, object, msgAndArgs...) { - t.FailNow() - } -} - -func NoError(t TestingT, err error, msgAndArgs ...any) { - t.Helper() - if !assert.NoError(t, err, msgAndArgs...) { - t.FailNow() - } -} - -func ErrorIs(t TestingT, err, target error, msgAndArgs ...any) { - t.Helper() - if !assert.ErrorIs(t, err, target, msgAndArgs...) { - t.FailNow() - } -} - -func ErrorContains(t TestingT, err error, contains string, msgAndArgs ...any) { - t.Helper() - if !assert.ErrorContains(t, err, contains, msgAndArgs...) { - t.FailNow() - } -} - -func NotEmpty(t TestingT, object any, msgAndArgs ...any) { - t.Helper() - if !assert.NotEmpty(t, object, msgAndArgs...) { - t.FailNow() - } -} - -func Zero(t TestingT, value any, msgAndArgs ...any) { - t.Helper() - if !assert.Zero(t, value, msgAndArgs...) { - t.FailNow() - } -} - -func NotZero(t TestingT, value any, msgAndArgs ...any) { - t.Helper() - if !assert.NotZero(t, value, msgAndArgs...) { - t.FailNow() - } -} - -func Panics(t TestingT, f func(), msgAndArgs ...any) { - t.Helper() - if !assert.Panics(t, f, msgAndArgs...) { - t.FailNow() - } -} - -func NotPanics(t TestingT, f func(), msgAndArgs ...any) { - t.Helper() - if !assert.NotPanics(t, f, msgAndArgs...) { - t.FailNow() - } -} - -func Regexp(t TestingT, pattern string, text string, msgAndArgs ...any) { - t.Helper() - if !assert.Regexp(t, pattern, text, msgAndArgs...) { - t.FailNow() - } -} - -func Len(t TestingT, obj any, len int) { - t.Helper() - if !assert.Len(t, obj, len) { - t.FailNow() - } -} diff --git a/pkg/internal/picotestify/suite/suite.go b/pkg/internal/picotestify/suite/suite.go deleted file mode 100644 index 6aaf427..0000000 --- a/pkg/internal/picotestify/suite/suite.go +++ /dev/null @@ -1,87 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package suite - -import ( - "reflect" - "regexp" - "testing" -) - -type Suite struct { - t *testing.T -} - -type SuiteInterface interface { - T() *testing.T - SetT(t *testing.T) -} - -func (s *Suite) T() *testing.T { return s.t } -func (s *Suite) SetT(t *testing.T) { s.t = t } - -var testMethodRe = regexp.MustCompile("^Test") - -func listTests(suite SuiteInterface) func(yield func(string, func()) bool) { - return func(yield func(string, func()) bool) { - st := reflect.TypeOf(suite) - - for i := 0; i < st.NumMethod(); i++ { - method := st.Method(i) - if !testMethodRe.MatchString(method.Name) { - continue - } - - if !yield( - method.Name, - func() { method.Func.Call([]reflect.Value{reflect.ValueOf(suite)}) }, - ) { - return - } - } - } -} - -func Run(parentT *testing.T, suite SuiteInterface) { - suite.SetT(parentT) - - if setupSuite, isSetupSuite := suite.(interface{ SetupTestSuite() }); isSetupSuite { - setupSuite.SetupTestSuite() - } - - if tearDownSuite, isTearDownSuite := suite.(interface{ TearDownTestSuite() }); isTearDownSuite { - parentT.Cleanup(tearDownSuite.TearDownTestSuite) - } - - for name, testFunc := range listTests(suite) { - parentT.Run(name, func(childT *testing.T) { - suite.SetT(childT) - - if setupTest, isSetupTest := suite.(interface{ SetupTest() }); isSetupTest { - setupTest.SetupTest() - } - - if tearDownTest, isTearDownTest := suite.(interface{ TearDownTest() }); isTearDownTest { - childT.Cleanup(tearDownTest.TearDownTest) - } - - testFunc() - - suite.SetT(parentT) - }) - } -} diff --git a/pkg/internal/utilities/cipherfactory/cipher_factory.go b/pkg/internal/utilities/cipherfactory/cipher_factory.go index 364c2d6..7548496 100644 --- a/pkg/internal/utilities/cipherfactory/cipher_factory.go +++ b/pkg/internal/utilities/cipherfactory/cipher_factory.go @@ -22,7 +22,7 @@ import ( "fmt" "io" - "github.com/cinode/go-datastore/pkg/common" + "github.com/cinode/go-common/blob" "golang.org/x/crypto/chacha20" ) @@ -51,7 +51,7 @@ const ( reservedByteForKeyType byte = 0 ) -func StreamCipherReader(key *common.BlobKey, iv *common.BlobIV, r io.Reader) (io.Reader, error) { +func StreamCipherReader(key *blob.Key, iv *blob.IV, r io.Reader) (io.Reader, error) { stream, err := _cipherForKeyIV(key, iv) if err != nil { return nil, err @@ -59,7 +59,7 @@ func StreamCipherReader(key *common.BlobKey, iv *common.BlobIV, r io.Reader) (io return &cipher.StreamReader{S: stream, R: r}, nil } -func StreamCipherWriter(key *common.BlobKey, iv *common.BlobIV, w io.Writer) (io.Writer, error) { +func StreamCipherWriter(key *blob.Key, iv *blob.IV, w io.Writer) (io.Writer, error) { stream, err := _cipherForKeyIV(key, iv) if err != nil { return nil, err @@ -67,7 +67,7 @@ func StreamCipherWriter(key *common.BlobKey, iv *common.BlobIV, w io.Writer) (io return cipher.StreamWriter{S: stream, W: w}, nil } -func _cipherForKeyIV(key *common.BlobKey, iv *common.BlobIV) (cipher.Stream, error) { +func _cipherForKeyIV(key *blob.Key, iv *blob.IV) (cipher.Stream, error) { keyBytes := key.Bytes() if len(keyBytes) == 0 || keyBytes[0] != reservedByteForKeyType { return nil, ErrInvalidEncryptionConfigKeyType diff --git a/pkg/internal/utilities/cipherfactory/cipher_factory_test.go b/pkg/internal/utilities/cipherfactory/cipher_factory_test.go index bc5c47a..fb8d57b 100644 --- a/pkg/internal/utilities/cipherfactory/cipher_factory_test.go +++ b/pkg/internal/utilities/cipherfactory/cipher_factory_test.go @@ -21,8 +21,8 @@ import ( "io" "testing" - "github.com/cinode/go-datastore/pkg/common" - "github.com/cinode/go-datastore/pkg/internal/picotestify/require" + "github.com/cinode/go-common/blob" + "github.com/cinode/go-common/picotestify/require" "golang.org/x/crypto/chacha20" ) @@ -60,8 +60,8 @@ func TestCipherForKeyIV(t *testing.T) { } { t.Run(d.desc, func(t *testing.T) { sr, err := StreamCipherReader( - common.BlobKeyFromBytes(d.key), - common.BlobIVFromBytes(d.iv), + blob.KeyFromBytes(d.key), + blob.IVFromBytes(d.iv), bytes.NewReader([]byte{}), ) require.ErrorIs(t, err, d.err) @@ -70,8 +70,8 @@ func TestCipherForKeyIV(t *testing.T) { } sw, err := StreamCipherWriter( - common.BlobKeyFromBytes(d.key), - common.BlobIVFromBytes(d.iv), + blob.KeyFromBytes(d.key), + blob.IVFromBytes(d.iv), bytes.NewBuffer(nil), ) require.ErrorIs(t, err, d.err) @@ -83,8 +83,8 @@ func TestCipherForKeyIV(t *testing.T) { } func TestStreamCipherRoundtrip(t *testing.T) { - key := common.BlobKeyFromBytes(make([]byte, chacha20.KeySize+1)) - iv := common.BlobIVFromBytes(make([]byte, chacha20.NonceSizeX)) + key := blob.KeyFromBytes(make([]byte, chacha20.KeySize+1)) + iv := blob.IVFromBytes(make([]byte, chacha20.NonceSizeX)) data := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06} buf := bytes.NewBuffer(nil) diff --git a/pkg/internal/utilities/cipherfactory/generator.go b/pkg/internal/utilities/cipherfactory/generator.go index eb8acc8..830d341 100644 --- a/pkg/internal/utilities/cipherfactory/generator.go +++ b/pkg/internal/utilities/cipherfactory/generator.go @@ -21,7 +21,7 @@ import ( "hash" "io" - "github.com/cinode/go-datastore/pkg/common" + "github.com/cinode/go-common/blob" "golang.org/x/crypto/chacha20" ) @@ -33,7 +33,7 @@ const ( type KeyGenerator interface { io.Writer - Generate() *common.BlobKey + Generate() *blob.Key } type keyGenerator struct { @@ -42,8 +42,8 @@ type keyGenerator struct { func (g keyGenerator) Write(b []byte) (int, error) { return g.h.Write(b) } -func (g keyGenerator) Generate() *common.BlobKey { - return common.BlobKeyFromBytes(append( +func (g keyGenerator) Generate() *blob.Key { + return blob.KeyFromBytes(append( []byte{reservedByteForKeyType}, g.h.Sum(nil)[:chacha20.KeySize]..., )) @@ -51,7 +51,7 @@ func (g keyGenerator) Generate() *common.BlobKey { type IVGenerator interface { io.Writer - Generate() *common.BlobIV + Generate() *blob.IV } type ivGenerator struct { @@ -60,28 +60,28 @@ type ivGenerator struct { func (g ivGenerator) Write(b []byte) (int, error) { return g.h.Write(b) } -func (g ivGenerator) Generate() *common.BlobIV { - return common.BlobIVFromBytes(g.h.Sum(nil)[:chacha20.NonceSizeX]) +func (g ivGenerator) Generate() *blob.IV { + return blob.IVFromBytes(g.h.Sum(nil)[:chacha20.NonceSizeX]) } -func NewKeyGenerator(t common.BlobType) KeyGenerator { +func NewKeyGenerator(t blob.Type) KeyGenerator { h := sha256.New() h.Write([]byte{preambleHashKey, reservedByteForKeyType, t.IDByte()}) return keyGenerator{h: h} } -func NewIVGenerator(t common.BlobType) IVGenerator { +func NewIVGenerator(t blob.Type) IVGenerator { h := sha256.New() h.Write([]byte{preambleHashIV, reservedByteForKeyType, t.IDByte()}) return ivGenerator{h: h} } -var defaultXChaCha20IV = func() *common.BlobIV { +var defaultXChaCha20IV = func() *blob.IV { h := sha256.New() h.Write([]byte{preambleHashDefaultIV, reservedByteForKeyType}) - return common.BlobIVFromBytes(h.Sum(nil)[:chacha20.NonceSizeX]) + return blob.IVFromBytes(h.Sum(nil)[:chacha20.NonceSizeX]) }() -func DefaultIV(k *common.BlobKey) *common.BlobIV { +func DefaultIV(k *blob.Key) *blob.IV { return defaultXChaCha20IV } diff --git a/pkg/internal/utilities/cipherfactory/generator_test.go b/pkg/internal/utilities/cipherfactory/generator_test.go index ddf36ce..85eabe7 100644 --- a/pkg/internal/utilities/cipherfactory/generator_test.go +++ b/pkg/internal/utilities/cipherfactory/generator_test.go @@ -19,8 +19,8 @@ package cipherfactory import ( "testing" + "github.com/cinode/go-common/picotestify/require" "github.com/cinode/go-datastore/pkg/blobtypes" - "github.com/cinode/go-datastore/pkg/internal/picotestify/require" ) func TestGenerator(t *testing.T) { diff --git a/pkg/internal/utilities/errreader/errreader_test.go b/pkg/internal/utilities/errreader/errreader_test.go index 77a80ad..8414733 100644 --- a/pkg/internal/utilities/errreader/errreader_test.go +++ b/pkg/internal/utilities/errreader/errreader_test.go @@ -20,7 +20,7 @@ import ( "errors" "testing" - "github.com/cinode/go-datastore/pkg/internal/picotestify/require" + "github.com/cinode/go-common/picotestify/require" ) func TestErrReader(t *testing.T) { diff --git a/pkg/internal/utilities/headwriter/headwriter_test.go b/pkg/internal/utilities/headwriter/headwriter_test.go index 5834368..4ac3016 100644 --- a/pkg/internal/utilities/headwriter/headwriter_test.go +++ b/pkg/internal/utilities/headwriter/headwriter_test.go @@ -19,7 +19,7 @@ package headwriter import ( "testing" - "github.com/cinode/go-datastore/pkg/internal/picotestify/require" + "github.com/cinode/go-common/picotestify/require" ) func TestHeadWriter(t *testing.T) { diff --git a/pkg/internal/utilities/validatingreader/hashvalidatingreader_test.go b/pkg/internal/utilities/validatingreader/hashvalidatingreader_test.go index 0b7b037..6dc4310 100644 --- a/pkg/internal/utilities/validatingreader/hashvalidatingreader_test.go +++ b/pkg/internal/utilities/validatingreader/hashvalidatingreader_test.go @@ -23,7 +23,7 @@ import ( "io" "testing" - "github.com/cinode/go-datastore/pkg/internal/picotestify/require" + "github.com/cinode/go-common/picotestify/require" "github.com/cinode/go-datastore/pkg/internal/utilities/validatingreader" ) diff --git a/pkg/internal/utilities/validatingreader/oneofcheckreader_test.go b/pkg/internal/utilities/validatingreader/oneofcheckreader_test.go index 354d470..5edf142 100644 --- a/pkg/internal/utilities/validatingreader/oneofcheckreader_test.go +++ b/pkg/internal/utilities/validatingreader/oneofcheckreader_test.go @@ -22,7 +22,7 @@ import ( "io" "testing" - "github.com/cinode/go-datastore/pkg/internal/picotestify/require" + "github.com/cinode/go-common/picotestify/require" "github.com/cinode/go-datastore/pkg/internal/utilities/validatingreader" ) diff --git a/pkg/utilities/golang/assert.go b/pkg/utilities/golang/assert.go deleted file mode 100644 index 220e3c0..0000000 --- a/pkg/utilities/golang/assert.go +++ /dev/null @@ -1,23 +0,0 @@ -/* -Copyright © 2023 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package golang - -func Assert(b bool, message string) { - if !b { - panic("Assertion failed: " + message) - } -} diff --git a/pkg/utilities/golang/assert_test.go b/pkg/utilities/golang/assert_test.go deleted file mode 100644 index f84722c..0000000 --- a/pkg/utilities/golang/assert_test.go +++ /dev/null @@ -1,32 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package golang - -import ( - "testing" - - "github.com/cinode/go-datastore/pkg/internal/picotestify/require" -) - -func TestAssert(t *testing.T) { - require.NotPanics(t, func() { - Assert(true, "must not happen") - }) - require.Panics(t, func() { - Assert(false, "must panic") - }) -} diff --git a/pkg/utilities/golang/must.go b/pkg/utilities/golang/must.go deleted file mode 100644 index 49aca0d..0000000 --- a/pkg/utilities/golang/must.go +++ /dev/null @@ -1,24 +0,0 @@ -/* -Copyright © 2023 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package golang - -func Must[T any](val T, err error) T { - if err != nil { - panic(err) - } - return val -} diff --git a/pkg/utilities/golang/must_test.go b/pkg/utilities/golang/must_test.go deleted file mode 100644 index 073a04e..0000000 --- a/pkg/utilities/golang/must_test.go +++ /dev/null @@ -1,34 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package golang - -import ( - "errors" - "testing" - - "github.com/cinode/go-datastore/pkg/internal/picotestify/require" -) - -func TestMust(t *testing.T) { - require.NotPanics(t, func() { - require.Equal(t, 1234, Must(1234, nil)) - }) - - require.Panics(t, func() { - require.Equal(t, 1234, Must(1234, errors.New("error"))) - }) -} diff --git a/testvectors/dynamic/attacks/private/001_keygen_signature_prefix.json b/testvectors/dynamic/attacks/private/001_keygen_signature_prefix.json deleted file mode 100644 index a8dbb0c..0000000 --- a/testvectors/dynamic/attacks/private/001_keygen_signature_prefix.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "dynamic/attacks/private/001_keygen_signature_prefix", - "description": "Invalid encryption key - keygen signature data prefix", - "details": [ - "Data link encryption key is deterministically calculated from link's configuration.", - "It must be calculate based on unchanging link dataset and private key.", - "", - "When generating the encryption key, private key is used to calculate a signature", - "of blob's unchanging data. That signature must be different to the signature", - "used to sign the encrypted data itself thus a different prefix is used for each", - "signing schemes.", - "", - "This test ensures that an invalid prefix determining the kind of data being signed", - "results in encryption key validation failure." - ], - "added_at": "2023-01-20", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKWeenwoCRo7EevCHoIOhEm+RxHgfLMBVlMwVyQSxuluK4FiYzC6i6xdWPVEw2cMlT7f5BRgpsuVnXSiLp22a04FPX4EfCNH2O0YqBk7CjwA3PJLCJvffM7tSaMycq6W7YFOSgEVoXOdgKva40TvQry+/VaZ2xTyTmX1DjqzX56uPKyYftmxTeOKXLpyinPtfBksJEerBNIM5lUcg6DWwbwlYGQwCJcilNNJhFSykv0t+NPhvqZoG7DrDs8/8NSZR9USuyo=", - "decrypted_dataset": null, - "valid_publicly": true, - "valid_privately": false, - "go_error_contains": "invalid key validation block signature" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/private/002_keygen_signature_blob_name.json b/testvectors/dynamic/attacks/private/002_keygen_signature_blob_name.json deleted file mode 100644 index b6eae30..0000000 --- a/testvectors/dynamic/attacks/private/002_keygen_signature_blob_name.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "dynamic/attacks/private/002_keygen_signature_blob_name", - "description": "Invalid encryption key - keygen signature blob name", - "details": [ - "Data link encryption key is deterministically calculated from link's configuration.", - "It must be calculate based on unchanging link dataset and private key.", - "", - "This test ensures that an invalid blob name used in key generation signature will", - "result in rejection of the encryption key." - ], - "added_at": "2023-01-21", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKU1fFpdqEifzlQTbSgTPFm4f8S4sEl6ABS19kX7jqmZOIUoCI4aUNFnbIc8QV9396SYR6t57ZwDvzYRNFF6BlkNPX4EfCNH2O0Y80MdaI7ZHJizPRP8USTt5+pvn+iu4Zivo2ojEcTka5uEJrwdSXqBfylPcvhkMlXZ8SYbnxQpvCv0c2a44uW3MGeXdWUt828KYsA+6YJfYMbYZnXAAinAXqX7evGRy6dogJn+DAfJU5oSEa0EfJtjjswnW3a2Z1SKOog=", - "decrypted_dataset": null, - "valid_publicly": true, - "valid_privately": false, - "go_error_contains": "invalid key validation block signature" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/private/003_keygen_signature_corruption.json b/testvectors/dynamic/attacks/private/003_keygen_signature_corruption.json deleted file mode 100644 index 73eace0..0000000 --- a/testvectors/dynamic/attacks/private/003_keygen_signature_corruption.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "dynamic/attacks/private/003_keygen_signature_corruption", - "description": "Invalid encryption key - keygen signature corruption", - "details": [ - "Data link encryption key is deterministically calculated from link's configuration.", - "It must be calculate based on unchanging link dataset and private key.", - "", - "This test ensures that any corruption in the base signature will result in rejection", - "of the encryption key." - ], - "added_at": "2023-01-21", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKU1fFpdqEifzlQTbSgTPFm4f8S4sEl6ABS19kX7jqmZOIUoCI4aUNFnbIc8QV9396SYR6t57ZwDvzYRNFF6BlkNPX4EfCNH2O0Y80MdaI7ZHJizPRP8USTt5+pvn+iu4Zivo2ojEcTka5uEJrwdSXqBfylPcvhkMlXZ8SYbnxQpvCv0c2a44uW3MGeXdWUt828KYsA+6YJfYMbYZnXAAinAXqX7evGRy6dogJn+DAfJU5oSEa0EfJtjjswnW3a2Z1SKOog=", - "decrypted_dataset": null, - "valid_publicly": true, - "valid_privately": false, - "go_error_contains": "invalid key validation block signature" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/private/004_keygen_hash_prefix.json b/testvectors/dynamic/attacks/private/004_keygen_hash_prefix.json deleted file mode 100644 index a71cf0d..0000000 --- a/testvectors/dynamic/attacks/private/004_keygen_hash_prefix.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "dynamic/attacks/private/004_keygen_hash_prefix", - "description": "Invalid encryption key - keygen hash prefix", - "details": [ - "Data link encryption key is calculated by using a hash function.", - "To avoid potential security issue where the same hash function with", - "the same input data is reused in different context revealing such hash publicly,", - "each kind of data uses different prefix for the data being hashed mitigating the risk.", - "", - "This test ensures that invalid hashed data prefix results with an invalid key that is", - "rejected while trying to read the plaintext link data." - ], - "added_at": "2023-01-21", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AJV/2rE1BmxHb6lBSiDoSWdOWgBSPcwH+B7xfaSajdMH", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKWkQwwcaYeIB77MUKMFvHUBmGWR20/X3yCCE62gcYRNIxY3eaGXCivs0sEZ1/fpyt2pp9kx1Nf2xvMDWPyrKkkOPX4EfCNH2O0Y6b69T2nhGzJfvAl0Es0uiM9HUbCInDJ8cGa/U1W01m+rNnYzTGgd+ldooSTbgzQoMhWH4XJsZqg0sRAtDoso42fWiLHn0yooyjrcL2VlqHpM+zSIeQwZCaH9Q9/RpYRszrgJdfvv575Xjd1O65H37hxNFYAvgaXM2Qk=", - "decrypted_dataset": null, - "valid_publicly": true, - "valid_privately": false, - "go_error_contains": "key mismatch" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/private/005_keygen_hash_encryption_alg.json b/testvectors/dynamic/attacks/private/005_keygen_hash_encryption_alg.json deleted file mode 100644 index a22d61f..0000000 --- a/testvectors/dynamic/attacks/private/005_keygen_hash_encryption_alg.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "dynamic/attacks/private/005_keygen_hash_encryption_alg", - "description": "Invalid encryption key - keygen hash encryption alg", - "details": [ - "Data link encryption key is calculated by using a hash function.", - "To avoid potential security issue where the same hash function with", - "the same input data is reused for different encryption algorithms,", - "there's an encoded encryption algorithm info before the final data to be hashed.", - "", - "This test ensures that invalid encryption algorithm information used in the hashed", - "data results with an invalid key that is rejected while trying to read the plaintext link data." - ], - "added_at": "2023-01-21", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AAUewgRMphOrK0UFnoJcSaTYf8NVAe/JjSo339Ycc99H", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKUE9FgfPBcn7a4jhGGzE1gQDNpG1XxaosHZdvjyD9GM1uDiepYnHqUf/DDuE+HUQNh+3ACyJ63uxjqBaO9//o0MPX4EfCNH2O0Y6b69T2nhGzJfvAl0Es0uiM9HUbCInDJ8Nr3lDLbjcoTgXNWUBEfwCp3iVFzdaS0le3ovY7ChfE+CEl5iJQAfV151ic0e1K8+hK9xDo025ItSX5wpJsWppMD1h441tEMJXBDWwvGClFgE3sAkmvyRsQI5iFWX357NVJc=", - "decrypted_dataset": null, - "valid_publicly": true, - "valid_privately": false, - "go_error_contains": "key mismatch" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/private/006_keygen_hash_blob_type.json b/testvectors/dynamic/attacks/private/006_keygen_hash_blob_type.json deleted file mode 100644 index 74f5d63..0000000 --- a/testvectors/dynamic/attacks/private/006_keygen_hash_blob_type.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "dynamic/attacks/private/006_keygen_hash_blob_type", - "description": "Invalid encryption key - keygen hash blob type", - "details": [ - "Data link encryption key is calculated by using a hash function.", - "To avoid potential security issue where the same hash function with", - "the same input data is reused for different blob type, there's an encoded", - "blob type information before the final data to be hashed.", - "", - "This test ensures that invalid blob type used in the hashed data results", - "with an invalid key that is rejected while trying to read the plaintext link data." - ], - "added_at": "2023-01-21", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AAunvOZc08dHN2g46qDBvo1nF1dS4VbfeM4wewyOV+sK", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKVuqsbTLTxf27T0ZawbtR2PRytklKdVk+Kt4BVrcKUFWDhEbzdzuOmErjC2AsoUNRWHwWualmAXQ2tWk5e/OzIHPX4EfCNH2O0Y6b69T2nhGzJfvAl0Es0uiM9HUbCInDJ8bY6XwJbXAEPtelaXvSytOVNR6cbBMv165L9kzb4/v1/lmY0xb3VkrkbMX5BWU5eFGjqR/CS68ehkmEd7PlINcU4IGM9pvMGFck8NJ1lcUlv01mItBSgM8WnHq0n/dYxMgwo=", - "decrypted_dataset": null, - "valid_publicly": true, - "valid_privately": false, - "go_error_contains": "key mismatch" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/private/007_key_type.json b/testvectors/dynamic/attacks/private/007_key_type.json deleted file mode 100644 index 36cde0e..0000000 --- a/testvectors/dynamic/attacks/private/007_key_type.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "dynamic/attacks/private/007_key_type", - "description": "Invalid encryption key - keygen type", - "details": [ - "Encryption key contains encryption algorithm type information encoded before", - "key bytes. This test ensures that invalid encryption algorithm is rejected." - ], - "added_at": "2023-01-21", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "+3nSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKVjxQzro6lsghw1DNC5LjCXncInsaCn9jVGrPXjT45FsIUNecdrks7I1J9A131k9rFoqdIWNA9vNT73PzchD+AJPX4EfCNH2O0Y6b69T2nhGzJfvAl0Es0uiM9HUbCInDJ8cy6CL9r8EGm93UuHXPx9d6EscqgdiDobSvX1uUdeiQAjuPrG/eNwLUolfTtXweIDHz1sHnn7FjaIZ3km9eTyEnWNvD4L/nbOSAKRzEc+Gu3YakKH8o/fEz3XkHmrQPI6zr4=", - "decrypted_dataset": null, - "valid_publicly": true, - "valid_privately": false, - "go_error_contains": "wrong key type" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/private/008_key_corruption.json b/testvectors/dynamic/attacks/private/008_key_corruption.json deleted file mode 100644 index 43352f1..0000000 --- a/testvectors/dynamic/attacks/private/008_key_corruption.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "dynamic/attacks/private/008_key_corruption", - "description": "Invalid encryption key - keygen corruption", - "details": [ - "Encryption key contains encryption algorithm type information encoded before", - "key bytes. This test ensures that invalid encryption algorithm is rejected." - ], - "added_at": "2023-01-21", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKXVC0Qg6r7ExVpKEPZnyaQvIGpyCFXeulBRpzAIdWFPiar+flyeScrQxpizDRLCUzDGQf8NdyuBugzQoVoUpLEOPX4EfCNH2O0Y6b69T2nhGzJfvAl0Es0uiM9HUbCInDJ8/6ymctYQmphKYZziCM0WEY7AeFX1cOxXVKAgIaX6bb5dh92GT5QA5yVg08FvvYC+isDKbzM1tkqWWoIUOeezdLvnzHHrqDVJQlOlC+HCWFd8w4t8xhEueGXjIwLv5GyMUfo=", - "decrypted_dataset": null, - "valid_publicly": true, - "valid_privately": false, - "go_error_contains": "invalid value of the internal reserved byte" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/private/009_link_data_prefix.json b/testvectors/dynamic/attacks/private/009_link_data_prefix.json deleted file mode 100644 index faf8a16..0000000 --- a/testvectors/dynamic/attacks/private/009_link_data_prefix.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "dynamic/attacks/private/009_link_data_prefix", - "description": "Invalid link data - prefix", - "details": [ - "For client validation purposes, unencrypted data contains key validation block", - "next to the link information itself. That data contains prefix so that it can be", - "modified in the future supporting different validation block formats.", - "", - "This test checks if invalid prefix value makes the blob invalid." - ], - "added_at": "2023-01-21", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKV5QfahnFZvrGyiym1MN41V/z35hHqNoRRf6+NZeAVNyHx7n+UEHN5199I9lWF19gb/uHfwn94NnQMTHTD07gMCPX4EfCNH2O0YsylB+erSnppPCvYPKE9Wgh1F3ItklGwFgDDDupjfJ9u1x/j9hoEPwdxJzL40S+XhhhV3r/iSXrwOb4cJVl4NjRCKovkK4cKT0l1MQUvoANm3aFgTq8shbVBxKgPipOz9b1KYpvJOxrBzPliX+jw2blZ/yDLMlYPGXc0=", - "decrypted_dataset": null, - "valid_publicly": true, - "valid_privately": false, - "go_error_contains": "invalid value of the internal reserved byte" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/private/010_key_validation_block_signature.json b/testvectors/dynamic/attacks/private/010_key_validation_block_signature.json deleted file mode 100644 index 4a074cf..0000000 --- a/testvectors/dynamic/attacks/private/010_key_validation_block_signature.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "dynamic/attacks/private/010_key_validation_block_signature", - "description": "Invalid key validation block - signature", - "details": [ - "For client validation purposes, unencrypted data contains key validation block", - "next to the link information itself. That validation block contains the signature", - "used converted later to an encryption key with a hash function.", - "", - "This test checks if an invalid signature stored in key validation block", - "will end up with the link being rejected." - ], - "added_at": "2023-01-21", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKUC+7swaY8mFz23nC6erSpD4ldZmMJImoftTAo5o3hMROO5bkBGMxM9JlWe3qg6fbcaMxLSpfnjCYXBp36EA+AOPX4EfCNH2O0Yj2dN+xd/pihdzCdk++YWIhZeJhWEcPaYWXciAYs1zal9vq0Wkk4hPFyPP9K5zm+LPETuSajaHrUoNi6bIyS4e26TeMFF2ryzNmdcAJGMSAT99Qs9vx+sCSKMXkuSeiPEhkWrO0LxUDQNrEhEDJBENnQblFyDkVwbciE=", - "decrypted_dataset": null, - "valid_publicly": true, - "valid_privately": false, - "go_error_contains": "invalid key validation block signature" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/private/011_key_validation_block_length_smaller.json b/testvectors/dynamic/attacks/private/011_key_validation_block_length_smaller.json deleted file mode 100644 index cfcf8bf..0000000 --- a/testvectors/dynamic/attacks/private/011_key_validation_block_length_smaller.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "dynamic/attacks/private/011_key_validation_block_length_smaller", - "description": "Invalid key validation block - length smaller", - "details": [ - "For client validation purposes, unencrypted data contains key validation block", - "next to the link information itself. The data is length prefixed and must be of", - "a specific length to be accepted.", - "", - "This test checks whether key validation block smaller than the desired value", - "will result in rejection of the encryption key." - ], - "added_at": "2023-01-21", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKWg9xr+qjOoGnigGnvDHLe2RSd0kzfuO3zT04071JToTv/wLQJ9p+zWxk970mEiTZfyr7ogbcxecJ9ryb3bNLgNPX4EfCNH2O0Y6kQ9nS07M+dZexF0t+K37k4as1oGoXVRr6lx8+d9RFukiSF2D8LaQ64fXUwYwSAfFkWBWTxTvgueYxky7S7CDA62TnEktaYB97FL+ePe4AMoZOTKeYm/0qXtg1iQSu/DVhjl/xTpfS95Pi5gH+eMEK+alBw03GR4LmI=", - "decrypted_dataset": null, - "valid_publicly": true, - "valid_privately": false, - "go_error_contains": "invalid key validation block signature" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/private/012_key_validation_block_length_larger.json b/testvectors/dynamic/attacks/private/012_key_validation_block_length_larger.json deleted file mode 100644 index 3add16f..0000000 --- a/testvectors/dynamic/attacks/private/012_key_validation_block_length_larger.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "dynamic/attacks/private/012_key_validation_block_length_larger", - "description": "Invalid key validation block - length larger", - "details": [ - "For client validation purposes, unencrypted data contains key validation block", - "next to the link information itself. The data is length prefixed and must be of", - "a specific length to be accepted.", - "", - "This test checks whether key validation block larger than the desired value", - "will result in rejection of the encryption key." - ], - "added_at": "2023-01-21", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKV8Md/0jvgG2UZh+fl8GEjQ3E9+FesrC/j7qajMO7KYPD/0Bu8lL0J70v2r51TEQuUmkZJvjSSeMdk7izyP8UQFPX4EfCNH2O0Ytco50xzbLi2GqxTv11KVfEDl/QVHeYfVtUNIgg4LNS2UJpq90j/wLuMR2BegdEtZBptwZjx09iSk++04osS6zfeMX+DlvWlMWaAGVhoHX8TpZURtfQglAnO6YET4tvvdZ1UerAxRpwVcf/0keFaH/UrwgOQn4ugG7So=", - "decrypted_dataset": null, - "valid_publicly": true, - "valid_privately": false, - "go_error_contains": "invalid key validation block signature" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/private/013_iv_has_gen_h_prefix.json b/testvectors/dynamic/attacks/private/013_iv_has_gen_h_prefix.json deleted file mode 100644 index 560dc1a..0000000 --- a/testvectors/dynamic/attacks/private/013_iv_has_gen_h_prefix.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "dynamic/attacks/private/013_iv_has_gen_h_prefix", - "description": "Invalid encryption iv - iv gen hash prefix", - "details": [ - "Data link encryption iv is calculated by using a hash function.", - "To avoid potential security issue where the same hash function with", - "the same input data is reused in different context revealing such hash publicly,", - "each kind of data uses different prefix for the data being hashed mitigating the risk.", - "", - "This test ensures that invalid hashed data prefix results with an invalid iv that is", - "rejected while trying to read the plaintext link data." - ], - "added_at": "2023-01-21", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKWob8B7UYfyYp8feqKylmGvEH9FKMUUGdTAfe0M2Ezr3nZV0eDIx0jE0vUDKlVRhOndxnweTJuq3kdHtvgPMJ0FPX4EfCNH2O0YgKzidKLRbuytz1sRNOOOdfN6kvlleWCniv4FBLLmoPd007xT2Bc1VlS9Nr9HaSvsgm031VGsp5j2JdviW7R4nxaEPjsZvcQx0KflXKTK5FIR3UjIScaAgE9DRQhSA5ymVenEQWZWCj2EHQqyD0Uq4XbdKkO5RttvcA8=", - "decrypted_dataset": null, - "valid_publicly": true, - "valid_privately": false, - "go_error_contains": "iv mismatch" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/private/014_keygen_hash_encryption_alg.json b/testvectors/dynamic/attacks/private/014_keygen_hash_encryption_alg.json deleted file mode 100644 index dcaee50..0000000 --- a/testvectors/dynamic/attacks/private/014_keygen_hash_encryption_alg.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "dynamic/attacks/private/014_keygen_hash_encryption_alg", - "description": "Invalid encryption iv - iv gen hash encryption alg", - "details": [ - "Data link encryption iv is calculated by using a hash function.", - "To avoid potential security issue where the same hash function with", - "the same input data is reused for different encryption algorithms,", - "there's an encoded encryption algorithm info before the final data to be hashed.", - "", - "This test ensures that invalid encryption algorithm information used in the hashed", - "data results with an invalid iv that is rejected while trying to read the plaintext link data." - ], - "added_at": "2023-01-21", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKW8ENf8cJPsZ6gncV4Qa6PsQRyZur4aXqCD6rS+gjSokw82+HKsLAYpe0ytnohJLAiyO08dJJI06jCDSO1Jj0kDPX4EfCNH2O0YVxEkKsPPS/jhn6zt/YEoQTOdp2Fu9s2W68pnj1wPFVb3LF+qIu1MI7CsSd/53OojQEWCE2zDlLzcEAnX9mPdBK4CuOfouXN7sHCwTJxTAO8dMOuLAQ0+XCyG+Tl2+dFFHMk2xxWrxuIDxTlTaO5DzTxJHoZGEkeo9tw=", - "decrypted_dataset": null, - "valid_publicly": true, - "valid_privately": false, - "go_error_contains": "iv mismatch" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/private/015_iv_gen_hash_blob_type.json b/testvectors/dynamic/attacks/private/015_iv_gen_hash_blob_type.json deleted file mode 100644 index 60b997c..0000000 --- a/testvectors/dynamic/attacks/private/015_iv_gen_hash_blob_type.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "dynamic/attacks/private/015_iv_gen_hash_blob_type", - "description": "Invalid encryption iv - iv gen hash blob type", - "details": [ - "Data link encryption iv is calculated by using a hash function.", - "To avoid potential security issue where the same hash function with", - "the same input data is reused for different blob type, there's an encoded", - "blob type information before the final data to be hashed.", - "", - "This test ensures that invalid blob type used in the hashed data results", - "with an invalid iv that is rejected while trying to read the plaintext link data." - ], - "added_at": "2023-01-21", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKVk2y9TAAyCb/Ebm2gHhx7GJ6oX06SHjCZ4/AJVbq1MQuYT38/fbAAUpctSD9JghNMB6m41v9L4Kgdfh+reCIsMPX4EfCNH2O0YTnNDjEr91dQiRYJgQgmLUNH37MQ1oZ6XdthPCK3BvMOsNHBdwO8eNd+ibqqITqBMS61VaiJKEAJoNLsMFJRgeP+H5Bft65bfF3ZHC6bRkx/Y1ymvcaP8h0zSO/CHYW6Gbzox18ed4ptNL5V1NQclX7qC1neHRDTl2rw=", - "decrypted_dataset": null, - "valid_publicly": true, - "valid_privately": false, - "go_error_contains": "iv mismatch" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/private/016_iv_gen_hash_blob_name_length.json b/testvectors/dynamic/attacks/private/016_iv_gen_hash_blob_name_length.json deleted file mode 100644 index 68b47ba..0000000 --- a/testvectors/dynamic/attacks/private/016_iv_gen_hash_blob_name_length.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "dynamic/attacks/private/016_iv_gen_hash_blob_name_length", - "description": "Invalid encryption iv - iv gen hash blob name length", - "details": [ - "Data link encryption iv is calculated by using a hash function.", - "The iv value is calculated from both changing and unchanging data.", - "", - "This test ensures that invalid blob name length used in the hashed data results", - "with an invalid iv that is rejected while trying to read the plaintext link data." - ], - "added_at": "2023-01-21", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKVjDuIDqJUw8QrItiND0LcL9/kloFuQMOhuiH8eGCTr5Epuo5zSxLG8+Q015ZYQZU5B6zXSPk9m+T23GmBxqAIJPX4EfCNH2O0YaatI4ulz4UCfoBTD+A4ml1Xn2u42Fh+nwYgJalO3/AYZpln1BC6zqNSVV43KbK/qnCTW4bE82jbiNsfhLijD/+NBiK/OUDPpzqHfzHTgvA08viVfE5bpAx7h83ctBYJttKvgpyzuAJzlicmJ8r/DLh4Z7biRqRl4mwI=", - "decrypted_dataset": null, - "valid_publicly": true, - "valid_privately": false, - "go_error_contains": "iv mismatch" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/private/017_iv_gen_hash_blob_name.json b/testvectors/dynamic/attacks/private/017_iv_gen_hash_blob_name.json deleted file mode 100644 index 343cc41..0000000 --- a/testvectors/dynamic/attacks/private/017_iv_gen_hash_blob_name.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "dynamic/attacks/private/017_iv_gen_hash_blob_name", - "description": "Invalid encryption iv - iv gen hash blob name", - "details": [ - "Data link encryption iv is calculated by using a hash function.", - "The iv value is calculated from both changing and unchanging data.", - "", - "This test ensures that invalid blob name used in the hashed data results", - "with an invalid iv that is rejected while trying to read the plaintext link data." - ], - "added_at": "2023-01-21", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKXo0HQQ9gsfOi3V+7PLcN+tGrvZ4cD+tfJXSLkckLTvag80kig0O/J+NNVuqYGOmogP72GRQ+G3zrqS0DEskJwAPX4EfCNH2O0YXrBKV5i+mGJ1f7vuKXQs75qYkef/RSFv5J9HX7lOw+SFnwQB9vOAXJHr1GN2wsC4Ogdu8i/ug5T2+Sx/YEKvpB7hjkcASW53/3iRrQb7e4xpbYBdD7kOUSUjWtn0DFR5deENC8yV2Xf1xkIPaR/vxEv282ts6kjNHdQ=", - "decrypted_dataset": null, - "valid_publicly": true, - "valid_privately": false, - "go_error_contains": "iv mismatch" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/private/018_iv_gen_hash_content_version.json b/testvectors/dynamic/attacks/private/018_iv_gen_hash_content_version.json deleted file mode 100644 index 9d505c7..0000000 --- a/testvectors/dynamic/attacks/private/018_iv_gen_hash_content_version.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "dynamic/attacks/private/018_iv_gen_hash_content_version", - "description": "Invalid encryption iv - iv gen hash content version", - "details": [ - "Data link encryption iv is calculated by using a hash function.", - "The iv value is calculated from both changing and unchanging data.", - "", - "This test ensures that invalid content version used in the hashed data results", - "with an invalid iv that is rejected while trying to read the plaintext link data." - ], - "added_at": "2023-01-21", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKXhZ8/96lVVpdaJf6xGUyuraeYA0iVTuZsYQjIi9NBiGfg/NKg9rfOOQ13kInMN3WIJKIIWkOvs7U0ahdxsTcAAPX4EfCNH2O0YgR3Upkic1PUMSTWgwrdsYmx3jSw7vlhJACpu4h5k8t+n1cJYQlAquXGs6e+syLN6gn5xF9XRiZBJ0DcVU7q2XKvx3C+rIRKYojrqJhESnvHoN1ghqimvtHmjcboTJuQoXDn+o0ZTdDQbTj29646cqBktZx1Xo1uyHIQ=", - "decrypted_dataset": null, - "valid_publicly": true, - "valid_privately": false, - "go_error_contains": "iv mismatch" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/private/019_iv_gen_hash_link_data.json b/testvectors/dynamic/attacks/private/019_iv_gen_hash_link_data.json deleted file mode 100644 index f63fd38..0000000 --- a/testvectors/dynamic/attacks/private/019_iv_gen_hash_link_data.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "dynamic/attacks/private/019_iv_gen_hash_link_data", - "description": "Invalid encryption iv - iv gen hash link data", - "details": [ - "Data link encryption iv is calculated by using a hash function.", - "The iv value is calculated from both changing and unchanging data.", - "", - "This test ensures that invalid link data used in the hashed data results", - "with an invalid iv that is rejected while trying to read the plaintext link data." - ], - "added_at": "2023-01-21", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKVcjwq7TglFEwOUcmg/Xn3t9pUmLrA9gdOT5SM6Xrgt8IOYNsgXrQVuczWWXjMXwh7KQyHZo396hEwGWuz0LsAIPX4EfCNH2O0Yjdrr9pjtHy//iG/cutGqOwn1XlPanc+e83C9a/8PRxI9cDBR0J02TNPAq9ymapCiyvkQgCIw9c/FrujEzKSDP9eVfPF9p8RyUMY6Tp2oQQ4RpvTCCMyZ8KvU/z69P8pAR0D70pZuYsohV3TCbGYqgJJhFDxViOIAwqk=", - "decrypted_dataset": null, - "valid_publicly": true, - "valid_privately": false, - "go_error_contains": "iv mismatch" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/private/020_corrupted_iv.json b/testvectors/dynamic/attacks/private/020_corrupted_iv.json deleted file mode 100644 index 436365b..0000000 --- a/testvectors/dynamic/attacks/private/020_corrupted_iv.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "dynamic/attacks/private/020_corrupted_iv", - "description": "Invalid encryption iv - corrupted iv", - "details": [ - "Data link encryption iv is calculated by using a hash function.", - "The iv value is calculated from both changing and unchanging data.", - "", - "This test ensures that corrupted iv is rejected while trying to read the plaintext link data." - ], - "added_at": "2023-01-21", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKUSuAcEDjG5hbvfdHYESNOpDZ9lo6DioaHSFjJyyXd8Q9qPBB576RXhnxY8LUwpMA/lRF4U4bWGi42LCQ+60fEHPX4EfCNH2O0Y6b69T2nhGzJfvAl0Zc0uiM9HUbCInDJ8PMXgDvt6neE1KdDtaBWeXcT05fX1zGJYPykpBXoDsWbaPpGmlViECIIZYWmGae2NPeOeuna4rbm7HV4QBZ4CkQFGYQRgz21b3E7zm0q5ZHpNKKLhFzKuNnno7ij+ePcTkxA=", - "decrypted_dataset": null, - "valid_publicly": true, - "valid_privately": false, - "go_error_contains": "iv mismatch" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/private/021_truncated_link_data_reserved_byte.json b/testvectors/dynamic/attacks/private/021_truncated_link_data_reserved_byte.json deleted file mode 100644 index e51c8ca..0000000 --- a/testvectors/dynamic/attacks/private/021_truncated_link_data_reserved_byte.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "dynamic/attacks/private/021_truncated_link_data_reserved_byte", - "description": "Truncated link data - missing reserved byte", - "details": [ - "The link data can be properly encrypted and signed passing", - "public verification. However the internal dataset may be", - "corrupted. A truncated dataset should also be detected.", - "", - "The dataset can not be empty since there would be no", - "reserved internal byte value in such case. This test", - "checks whether such truncated link data would be detected." - ], - "added_at": "2023-01-24", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKUHeMyp4G+pBxwaPOgem0NVaCjhVE0yDbXoYREyuJwuRUUo+T7FcT777UdowHb0jzdSqZDrVwDqaiWKjjwDfBQOPX4EfCNH2O0Y6b69T2nhGzJfvAl0Es0uiM9HUbCInDJ8", - "decrypted_dataset": null, - "valid_publicly": true, - "valid_privately": false, - "go_error_contains": "data truncated while reading internal reserved byte" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/private/022_truncated_link_data_key_validation_block_length.json b/testvectors/dynamic/attacks/private/022_truncated_link_data_key_validation_block_length.json deleted file mode 100644 index 9c16d05..0000000 --- a/testvectors/dynamic/attacks/private/022_truncated_link_data_key_validation_block_length.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "dynamic/attacks/private/022_truncated_link_data_key_validation_block_length", - "description": "Truncated link data - missing key validation block length", - "details": [ - "The link data can be properly encrypted and signed passing", - "public verification. However the internal dataset may be", - "corrupted. A truncated dataset should also be detected.", - "", - "This test checks if missing key validation length (and following data)", - "will be detected as incorrect link data." - ], - "added_at": "2023-01-24", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKWJEaDcZ/hwHO31c7IkQ86t4RcalNFlHB/H9Ogg5K+IC1/ETzMB1jbBayucHFVzwz6zbguHMMxYIxwyun9BXNgFPX4EfCNH2O0Y6b69T2nhGzJfvAl0Es0uiM9HUbCInDJ8cw==", - "decrypted_dataset": null, - "valid_publicly": true, - "valid_privately": false, - "go_error_contains": "data truncated while reading key validation block" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/private/023_truncated_link_data_incomplete_key_validation_block.json b/testvectors/dynamic/attacks/private/023_truncated_link_data_incomplete_key_validation_block.json deleted file mode 100644 index 92ed527..0000000 --- a/testvectors/dynamic/attacks/private/023_truncated_link_data_incomplete_key_validation_block.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "dynamic/attacks/private/023_truncated_link_data_incomplete_key_validation_block", - "description": "Truncated link data - incomplete key validation block", - "details": [ - "The link data can be properly encrypted and signed passing", - "public verification. However the internal dataset may be", - "corrupted. A truncated dataset should also be detected.", - "", - "This test checks if truncated key validation block (and following data)", - "will be detected as incorrect link data." - ], - "added_at": "2023-01-24", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKXgy+0jtAQT8DUe6hxgDX0RpRRagJ9fR+5mLqGnHkAcCf5L6QILVitgHWZte3KkYWWgVoTc7VCZwo4Qhyp3qaMOPX4EfCNH2O0Y6b69T2nhGzJfvAl0Es0uiM9HUbCInDJ8cy6CLw==", - "decrypted_dataset": null, - "valid_publicly": true, - "valid_privately": false, - "go_error_contains": "data truncated while reading key validation block" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/public/001_empty.json b/testvectors/dynamic/attacks/public/001_empty.json deleted file mode 100644 index 4d39d16..0000000 --- a/testvectors/dynamic/attacks/public/001_empty.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "dynamic/attacks/public/001_empty", - "description": "Empty dataset", - "details": [ - "Empty dataset is an invalid blob." - ], - "added_at": "2023-01-20", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "", - "decrypted_dataset": null, - "valid_publicly": false, - "valid_privately": false, - "go_error_contains": "data truncated while reading reserved byte" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/public/002_reserved_byte.json b/testvectors/dynamic/attacks/public/002_reserved_byte.json deleted file mode 100644 index e05245b..0000000 --- a/testvectors/dynamic/attacks/public/002_reserved_byte.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "dynamic/attacks/public/002_reserved_byte", - "description": "Invalid reserved byte", - "details": [ - "The first byte in the dataset is reserved for future protocol", - "modifications without breaking backwards compatibility.", - "Currently the reserved byte must be zero, any byte other than", - "that must be rejected." - ], - "added_at": "2023-01-20", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "/xHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKXKxH+xboBkXf4ELe2rV7ZjlzR+AjFf/l9bp8mbSX2oz+9vRzne5worTdtyJv+Y6oMnrPkq51rWQCw/GiqRE1MLPX4EfCNH2O0YAJvtsO4X+FnTqzNaB1HYgsceJmi4jsv+2+HpXXUnqNoYKB2/kLJVo5EdNy45FhRe+/kXsXgHAQeI66PRpoUNvsUM3AC9T57WKnMi+1FzkNaik2RuHNfZno54A1zG83XsVMn4LUJArc+1PfcJ90XQIT23tHlLyHI2ejY=", - "decrypted_dataset": null, - "valid_publicly": false, - "valid_privately": false, - "go_error_contains": "invalid value of the reserved byte" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/public/003_pubkey_truncated.json b/testvectors/dynamic/attacks/public/003_pubkey_truncated.json deleted file mode 100644 index 3ebe667..0000000 --- a/testvectors/dynamic/attacks/public/003_pubkey_truncated.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "dynamic/attacks/public/003_pubkey_truncated", - "description": "Truncated public key", - "added_at": "2023-01-20", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRs=", - "decrypted_dataset": null, - "valid_publicly": false, - "valid_privately": false, - "go_error_contains": "data truncated while reading public key" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/public/004_nonce_truncated.json b/testvectors/dynamic/attacks/public/004_nonce_truncated.json deleted file mode 100644 index 8a0f207..0000000 --- a/testvectors/dynamic/attacks/public/004_nonce_truncated.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "dynamic/attacks/public/004_nonce_truncated", - "description": "Truncated nonce", - "added_at": "2023-01-20", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Lg==", - "decrypted_dataset": null, - "valid_publicly": false, - "valid_privately": false, - "go_error_contains": "data truncated while reading nonce" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/public/005_blob_mismatch_reserved_byte.json b/testvectors/dynamic/attacks/public/005_blob_mismatch_reserved_byte.json deleted file mode 100644 index 103c6c8..0000000 --- a/testvectors/dynamic/attacks/public/005_blob_mismatch_reserved_byte.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "dynamic/attacks/public/005_blob_mismatch_reserved_byte", - "description": "Blob name mismatch for reserved byte", - "details": [ - "Blob name is built based on unchanging data in the dynamic link", - "this test blob ensures that the reserved byte value must remain the same", - "to keep the same blob name" - ], - "added_at": "2023-01-20", - "blob_name": "goCVqNZzL5WouXFTJ4/BZftlP/GMtwVuIC6KdENKok8J", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKVjxQzro6lsghw1DNC5LjCXncInsaCn9jVGrPXjT45FsIUNecdrks7I1J9A131k9rFoqdIWNA9vNT73PzchD+AJPX4EfCNH2O0Y6b69T2nhGzJfvAl0Es0uiM9HUbCInDJ8cy6CL9r8EGm93UuHXPx9d6EscqgdiDobSvX1uUdeiQAjuPrG/eNwLUolfTtXweIDHz1sHnn7FjaIZ3km9eTyEnWNvD4L/nbOSAKRzEc+Gu3YakKH8o/fEz3XkHmrQPI6zr4=", - "decrypted_dataset": null, - "valid_publicly": false, - "valid_privately": false, - "go_error_contains": "blob name mismatch" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/public/006_blob_mismatch_priv_key.json b/testvectors/dynamic/attacks/public/006_blob_mismatch_priv_key.json deleted file mode 100644 index 23f233e..0000000 --- a/testvectors/dynamic/attacks/public/006_blob_mismatch_priv_key.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "dynamic/attacks/public/006_blob_mismatch_priv_key", - "description": "Blob name mismatch for key", - "details": [ - "Blob name is built based on unchanging data in the dynamic link", - "this test blob ensures that the public key must remain the same", - "to keep the same blob name" - ], - "added_at": "2023-01-20", - "blob_name": "TMyaPVUeSqwNqcK8XFWwFZ0ojka9EPkiIKA2ASygDFCS", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKVjxQzro6lsghw1DNC5LjCXncInsaCn9jVGrPXjT45FsIUNecdrks7I1J9A131k9rFoqdIWNA9vNT73PzchD+AJPX4EfCNH2O0Y6b69T2nhGzJfvAl0Es0uiM9HUbCInDJ8cy6CL9r8EGm93UuHXPx9d6EscqgdiDobSvX1uUdeiQAjuPrG/eNwLUolfTtXweIDHz1sHnn7FjaIZ3km9eTyEnWNvD4L/nbOSAKRzEc+Gu3YakKH8o/fEz3XkHmrQPI6zr4=", - "decrypted_dataset": null, - "valid_publicly": false, - "valid_privately": false, - "go_error_contains": "blob name mismatch" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/public/007_blob_mismatch_nonce.json b/testvectors/dynamic/attacks/public/007_blob_mismatch_nonce.json deleted file mode 100644 index 27f938f..0000000 --- a/testvectors/dynamic/attacks/public/007_blob_mismatch_nonce.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "dynamic/attacks/public/007_blob_mismatch_nonce", - "description": "Blob name mismatch for nonce", - "details": [ - "Blob name is built based on unchanging data in the dynamic link", - "this test blob ensures that the link nonce must remain the same", - "to keep the same blob name" - ], - "added_at": "2023-01-20", - "blob_name": "gNjaNhbuUJ1JD07Ns9GUm67i1iOr1zW7PbqY6Z6Ghu3l", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKVjxQzro6lsghw1DNC5LjCXncInsaCn9jVGrPXjT45FsIUNecdrks7I1J9A131k9rFoqdIWNA9vNT73PzchD+AJPX4EfCNH2O0Y6b69T2nhGzJfvAl0Es0uiM9HUbCInDJ8cy6CL9r8EGm93UuHXPx9d6EscqgdiDobSvX1uUdeiQAjuPrG/eNwLUolfTtXweIDHz1sHnn7FjaIZ3km9eTyEnWNvD4L/nbOSAKRzEc+Gu3YakKH8o/fEz3XkHmrQPI6zr4=", - "decrypted_dataset": null, - "valid_publicly": false, - "valid_privately": false, - "go_error_contains": "blob name mismatch" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/public/008_truncated_signature.json b/testvectors/dynamic/attacks/public/008_truncated_signature.json deleted file mode 100644 index 46e7660..0000000 --- a/testvectors/dynamic/attacks/public/008_truncated_signature.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "dynamic/attacks/public/008_truncated_signature", - "description": "Truncated signature", - "added_at": "2023-01-20", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKVjxQzro6lsghw1DNC5LjCXncInsaCn9jVGrPXjT45FsA==", - "decrypted_dataset": null, - "valid_publicly": false, - "valid_privately": false, - "go_error_contains": "data truncated while reading signature" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/public/009_truncated_content_version.json b/testvectors/dynamic/attacks/public/009_truncated_content_version.json deleted file mode 100644 index 640a34f..0000000 --- a/testvectors/dynamic/attacks/public/009_truncated_content_version.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "dynamic/attacks/public/009_truncated_content_version", - "description": "Truncated content version", - "added_at": "2023-01-20", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKVjxQzro6lsghw1DNC5LjCXncInsaCn9jVGrPXjT45FsIUNecdrks7I1J9A131k9rFoqdIWNA9vNT73PzchD+AJPX4EfA==", - "decrypted_dataset": null, - "valid_publicly": false, - "valid_privately": false, - "go_error_contains": "data truncated while reading content version" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/public/010_truncated_iv.json b/testvectors/dynamic/attacks/public/010_truncated_iv.json deleted file mode 100644 index 9fdc03a..0000000 --- a/testvectors/dynamic/attacks/public/010_truncated_iv.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "dynamic/attacks/public/010_truncated_iv", - "description": "Truncated iv", - "added_at": "2023-01-20", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKVjxQzro6lsghw1DNC5LjCXncInsaCn9jVGrPXjT45FsIUNecdrks7I1J9A131k9rFoqdIWNA9vNT73PzchD+AJPX4EfCNH2O0Y6b69T2nhGzJfvAk=", - "decrypted_dataset": null, - "valid_publicly": false, - "valid_privately": false, - "go_error_contains": "data truncated while reading iv" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/public/011_signature_mismatch_prefix.json b/testvectors/dynamic/attacks/public/011_signature_mismatch_prefix.json deleted file mode 100644 index 3475503..0000000 --- a/testvectors/dynamic/attacks/public/011_signature_mismatch_prefix.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "dynamic/attacks/public/011_signature_mismatch_prefix", - "description": "Signature mismatch - prefix byte", - "details": [ - "Dynamic link signature is protecting both unchanging and changing blob", - "data. Also to enable signatures for different independent data sources,", - "those are using additional prefixes to avoid signature collisions that could", - "be exploited by an attacker (e.g. by causing reveal of signature for", - "data in one source that has the same byte sequence as in other source).", - "This test ensures that the signature won't match if the data source prefix", - "is different." - ], - "added_at": "2023-01-20", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKXDf2s0SEnug1MyKncMoDeaUgfypwBDL7W23MFvxaW/EmA8y8rN/cC/bLax/HvfbohXolY55UoUnLd6oew94E8OPX4EfCNH2O0Y6b69T2nhGzJfvAl0Es0uiM9HUbCInDJ8cy6CL9r8EGm93UuHXPx9d6EscqgdiDobSvX1uUdeiQAjuPrG/eNwLUolfTtXweIDHz1sHnn7FjaIZ3km9eTyEnWNvD4L/nbOSAKRzEc+Gu3YakKH8o/fEz3XkHmrQPI6zr4=", - "decrypted_dataset": null, - "valid_publicly": false, - "valid_privately": false, - "go_error_contains": "signature mismatch" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/public/012_signature_mismatch_blob_name.json b/testvectors/dynamic/attacks/public/012_signature_mismatch_blob_name.json deleted file mode 100644 index d89634d..0000000 --- a/testvectors/dynamic/attacks/public/012_signature_mismatch_blob_name.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "dynamic/attacks/public/012_signature_mismatch_blob_name", - "description": "Signature mismatch - blob name", - "details": [ - "Dynamic link signature is protecting both unchanging and changing blob", - "data. The same public/private key pair can be used for different blob names.", - "To ensure that the signature for different blob names is different,", - "the blob name itself is included in the signed dataset.", - "", - "This prevents an attack where targeted person uses the same private key", - "but different nonces to sign data of different security level. If the same", - "signature would be created for the same dataset in different blob names,", - "attacker could use this to trick the victim to sign some less important", - "information and reuse it to replace other blob name that is of a high", - "importance." - ], - "added_at": "2023-01-20", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKW0zsgYWFquuknfXupCjpp2gD+AaoQ0zfiTIl/4wUQLsHYzemPRHj9fuqmvENjL1uIjUHTuEpo8NgbzmIjavBYBPX4EfCNH2O0Y6b69T2nhGzJfvAl0Es0uiM9HUbCInDJ8cy6CL9r8EGm93UuHXPx9d6EscqgdiDobSvX1uUdeiQAjuPrG/eNwLUolfTtXweIDHz1sHnn7FjaIZ3km9eTyEnWNvD4L/nbOSAKRzEc+Gu3YakKH8o/fEz3XkHmrQPI6zr4=", - "decrypted_dataset": null, - "valid_publicly": false, - "valid_privately": false, - "go_error_contains": "signature mismatch" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/public/013_signature_mismatch_content_version.json b/testvectors/dynamic/attacks/public/013_signature_mismatch_content_version.json deleted file mode 100644 index b8a2942..0000000 --- a/testvectors/dynamic/attacks/public/013_signature_mismatch_content_version.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "dynamic/attacks/public/013_signature_mismatch_content_version", - "description": "Signature mismatch - content version", - "details": [ - "Dynamic link signature is protecting both unchanging and changing blob", - "data. To enable forward progress, the link with higher content version", - "does replace the link with the lower one. The signature thus has to", - "include the content version so that the attacker can not reuse an older", - "version of the link to replace newer one by bumping the content version." - ], - "added_at": "2023-01-20", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKVED73385mwLRH0agLXn4RwVRtI3qj07DWu54od8epr2SM04PVH4+HPkD4G50f5lSwScULjsFsOT9CmW4DHOt8IPX4EfCNH2O0Y6b69T2nhGzJfvAl0Es0uiM9HUbCInDJ8cy6CL9r8EGm93UuHXPx9d6EscqgdiDobSvX1uUdeiQAjuPrG/eNwLUolfTtXweIDHz1sHnn7FjaIZ3km9eTyEnWNvD4L/nbOSAKRzEc+Gu3YakKH8o/fEz3XkHmrQPI6zr4=", - "decrypted_dataset": null, - "valid_publicly": false, - "valid_privately": false, - "go_error_contains": "signature mismatch" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/public/014_signature_mismatch_initialization_vector.json b/testvectors/dynamic/attacks/public/014_signature_mismatch_initialization_vector.json deleted file mode 100644 index 1245715..0000000 --- a/testvectors/dynamic/attacks/public/014_signature_mismatch_initialization_vector.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "dynamic/attacks/public/014_signature_mismatch_initialization_vector", - "description": "Signature mismatch - iv", - "details": [ - "Dynamic link signature is protecting both unchanging and changing blob", - "data. Changing data contains the initialization vector (IV) for the", - "cipher used to encrypt the data. That IV is calculated deterministically", - "from unencrypted dataset but can not be calculated on the public network", - "layer. The signature thus has to include the IV so that the attacker can", - "not destroy the link by scrambling the IV. Doing so would make it impossible", - "to decrypt the data. The IV may be of any length thus the encoding of the IV", - "used for signature calculation contains the length of the IV.", - "", - "This test checks if invalid IV length used while calculating link's signature", - "will result in failed link verification." - ], - "added_at": "2023-01-23", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKXHuJhjnqQyJ0gZqXCDxeLrBlRjmNvu2kCo8GnuhPmQfGTg3zJjxVlcDbW3irAK/1l1krGC4/GD3rnN8X3sbs4BPX4EfCNH2O0Y6b69T2nhGzJfvAl0Es0uiM9HUbCInDJ8cy6CL9r8EGm93UuHXPx9d6EscqgdiDobSvX1uUdeiQAjuPrG/eNwLUolfTtXweIDHz1sHnn7FjaIZ3km9eTyEnWNvD4L/nbOSAKRzEc+Gu3YakKH8o/fEz3XkHmrQPI6zr4=", - "decrypted_dataset": null, - "valid_publicly": false, - "valid_privately": false, - "go_error_contains": "signature mismatch" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/public/015_signature_mismatch_initialization_vector_length.json b/testvectors/dynamic/attacks/public/015_signature_mismatch_initialization_vector_length.json deleted file mode 100644 index 49ae486..0000000 --- a/testvectors/dynamic/attacks/public/015_signature_mismatch_initialization_vector_length.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "dynamic/attacks/public/015_signature_mismatch_initialization_vector_length", - "description": "Signature mismatch - iv", - "details": [ - "Dynamic link signature is protecting both unchanging and changing blob", - "data. Changing data contains the initialization vector (IV) for the", - "cipher used to encrypt the data. That IV is calculated deterministically", - "from unencrypted dataset but can not be calculated on the public network", - "layer. The signature thus has to include the IV so that the attacker can", - "not destroy the link by scrambling the IV. Doing so would make it impossible", - "to decrypt the data.", - "", - "This test checks if invalid IV bytes used while calculating link's signature", - "will result in failed link verification." - ], - "added_at": "2023-01-23", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKWJBFsO6s6Q2LLJm5AXiRKCpfDRaW6T2kXgfV+q61YawXb4ZU5gNp/4UHHaYqKyV+zOKj84r3yZ/PgS+LRInIgFPX4EfCNH2O0Y6b69T2nhGzJfvAl0Es0uiM9HUbCInDJ8cy6CL9r8EGm93UuHXPx9d6EscqgdiDobSvX1uUdeiQAjuPrG/eNwLUolfTtXweIDHz1sHnn7FjaIZ3km9eTyEnWNvD4L/nbOSAKRzEc+Gu3YakKH8o/fEz3XkHmrQPI6zr4=", - "decrypted_dataset": null, - "valid_publicly": false, - "valid_privately": false, - "go_error_contains": "signature mismatch" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/public/016_signature_mismatch_encrypted_link_data.json b/testvectors/dynamic/attacks/public/016_signature_mismatch_encrypted_link_data.json deleted file mode 100644 index 59834ee..0000000 --- a/testvectors/dynamic/attacks/public/016_signature_mismatch_encrypted_link_data.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "dynamic/attacks/public/016_signature_mismatch_encrypted_link_data", - "description": "Signature mismatch - encrypted link data", - "details": [ - "Dynamic link signature is protecting both unchanging and changing blob", - "data. The signature must protect the main encrypted link data.", - "The signature has to be calculated over the encrypted data so that the", - "network can detect invalid signatures without revealing the unencrypted", - "information." - ], - "added_at": "2023-01-20", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKWYLWO7qjYvnhu2+rgAl2YUxNqD+XzSWAG+sa7h1C2TsFAJR4F/jb9Nvj2XSpm+7TC2CKQZZbQJdWw8Fug8NokMPX4EfCNH2O0Y6b69T2nhGzJfvAl0Es0uiM9HUbCInDJ8cy6CL9r8EGm93UuHXPx9d6EscqgdiDobSvX1uUdeiQAjuPrG/eNwLUolfTtXweIDHz1sHnn7FjaIZ3km9eTyEnWNvD4L/nbOSAKRzEc+Gu3YakKH8o/fEz3XkHmrQPI6zr4=", - "decrypted_dataset": null, - "valid_publicly": false, - "valid_privately": false, - "go_error_contains": "signature mismatch" -} \ No newline at end of file diff --git a/testvectors/dynamic/attacks/public/017_signature_mismatch_signature_bytes.json b/testvectors/dynamic/attacks/public/017_signature_mismatch_signature_bytes.json deleted file mode 100644 index 7b5c1b6..0000000 --- a/testvectors/dynamic/attacks/public/017_signature_mismatch_signature_bytes.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "dynamic/attacks/public/017_signature_mismatch_signature_bytes", - "description": "Signature mismatch - corrupted signature bytes", - "added_at": "2023-01-20", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKVjxQzro6lsghw1DNC5LjCXncInsaCn9jVGrPXjT45FsAUNecdrks7I1J9A131k9rFoqdIWNA9vNT73PzchD+AJPX4EfCNH2O0Y6b69T2nhGzJfvAl0Es0uiM9HUbCInDJ8cy6CL9r8EGm93UuHXPx9d6EscqgdiDobSvX1uUdeiQAjuPrG/eNwLUolfTtXweIDHz1sHnn7FjaIZ3km9eTyEnWNvD4L/nbOSAKRzEc+Gu3YakKH8o/fEz3XkHmrQPI6zr4=", - "decrypted_dataset": null, - "valid_publicly": false, - "valid_privately": false, - "go_error_contains": "signature mismatch" -} \ No newline at end of file diff --git a/testvectors/dynamic/correct/000_correct_link.json b/testvectors/dynamic/correct/000_correct_link.json deleted file mode 100644 index 4062b22..0000000 --- a/testvectors/dynamic/correct/000_correct_link.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "dynamic/correct/000_correct_link", - "description": "Correct link - 00", - "added_at": "2023-01-20", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKVjxQzro6lsghw1DNC5LjCXncInsaCn9jVGrPXjT45FsIUNecdrks7I1J9A131k9rFoqdIWNA9vNT73PzchD+AJPX4EfCNH2O0Y6b69T2nhGzJfvAl0Es0uiM9HUbCInDJ8cy6CL9r8EGm93UuHXPx9d6EscqgdiDobSvX1uUdeiQAjuPrG/eNwLUolfTtXweIDHz1sHnn7FjaIZ3km9eTyEnWNvD4L/nbOSAKRzEc+Gu3YakKH8o/fEz3XkHmrQPI6zr4=", - "decrypted_dataset": "Mn7SWsJ02Hv5Dhs5kJKJX8tZkBxgY84+geNJO9QriPA=", - "valid_publicly": true, - "valid_privately": true -} \ No newline at end of file diff --git a/testvectors/dynamic/correct/001_correct_link.json b/testvectors/dynamic/correct/001_correct_link.json deleted file mode 100644 index 42ac80d..0000000 --- a/testvectors/dynamic/correct/001_correct_link.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "dynamic/correct/001_correct_link", - "description": "Correct link - 01", - "added_at": "2023-01-20", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKXdthu8s3YwWUqTSmqelFt25Se1U6dyQVRidWARcy4+9AYTR1De9XIKa0dKwIyrUnFBN1cHNh0vwdLCTI+XaFsPPX4EfCNH2O0YQJiu5rPzDi4lnR5ROVUhQHSIDb7WXaZdaVfHt4wkaX6zMxBroh2Jh+vtsgYQcJXiLA/yjUZZNhloM2lxdyfJPt35nV1XpMQ3sgEK6DQeZyOEZCDExqAq8fQruHJ2lfzDkGw/iWNu", - "decrypted_dataset": "TGluayBkYXRhIDAx", - "valid_publicly": true, - "valid_privately": true -} \ No newline at end of file diff --git a/testvectors/dynamic/correct/002_correct_link.json b/testvectors/dynamic/correct/002_correct_link.json deleted file mode 100644 index fa0b9cb..0000000 --- a/testvectors/dynamic/correct/002_correct_link.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "dynamic/correct/002_correct_link", - "description": "Correct link - 02", - "added_at": "2023-01-20", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKWvLjrSofgQyDGxKOS6T4BugJ372LbuNKEXxPgdxQUxDSAFdmlZTmf2aR6QjEFMXiyPBZPegHQ9m8psUBP5DTYEPX4EfCNH2O0YlqHjsPHll6um4/kynadL93ncII1yu5uz5seeQpNUC9I6p0Yv87Fp5XlE57pj2eTw77pWbWMBMz28cboU+mZ6YkZk3CoUW95FbdVrLzknSSQWghWpke1Ud31SqS/pGGHRal9RLW76", - "decrypted_dataset": "TGluayBkYXRhIDAy", - "valid_publicly": true, - "valid_privately": true -} \ No newline at end of file diff --git a/testvectors/dynamic/correct/003_correct_link.json b/testvectors/dynamic/correct/003_correct_link.json deleted file mode 100644 index 76442df..0000000 --- a/testvectors/dynamic/correct/003_correct_link.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "dynamic/correct/003_correct_link", - "description": "Correct link - 03", - "added_at": "2023-01-20", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKV8diGh48DyMYxUAf/uSpRfGE/ojkj49H+oQVBXSMgb8NeFJ9MlW1x+LQ6HuxJw0Pn+hs9vvKLHoiwnUaS5Lz0KPX4EfCNH2O0YbyuBh784Y4Vvxv8gnQdsXE8S3DVuZQ8Ja06KrgwOxn4qKhnSmSVc0JVhreFvZg9/lYXUNxCJDUt8o/Y4umVweAPAeiGsEOQKFoGJN+xGKvToxnkfHxnuPJmFkj174FB3rtj5G6Yb", - "decrypted_dataset": "TGluayBkYXRhIDAz", - "valid_publicly": true, - "valid_privately": true -} \ No newline at end of file diff --git a/testvectors/dynamic/correct/004_correct_link.json b/testvectors/dynamic/correct/004_correct_link.json deleted file mode 100644 index a09d4d6..0000000 --- a/testvectors/dynamic/correct/004_correct_link.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "dynamic/correct/004_correct_link", - "description": "Correct link - 04", - "added_at": "2023-01-20", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKWmo+OH3EEi2kr2DX/+slMI92f8wUcb4gz+I6e4OkVZ/zOz3LBRfK+aoTIm09OX2zZdDJ7J1EUnmilbiZt3+eEIPX4EfCNH2O0YlGlMnFYy/JBmgAjQxj6PrbjsxpQ7KKvQohP4NfRKFSETABR7LGjm9QvIVf0JUkwKTCj+o9i0JOFcNLfxODEUIfQZmXFDpC6tF/+uW0kmS+cIzT+qC/E4exfibIOttE4FHW5NauOE", - "decrypted_dataset": "TGluayBkYXRhIDA0", - "valid_publicly": true, - "valid_privately": true -} \ No newline at end of file diff --git a/testvectors/dynamic/correct/005_correct_link.json b/testvectors/dynamic/correct/005_correct_link.json deleted file mode 100644 index f04593d..0000000 --- a/testvectors/dynamic/correct/005_correct_link.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "dynamic/correct/005_correct_link", - "description": "Correct link - 05", - "added_at": "2023-01-20", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKVxWBVgEyD8AIfsjRgcXv45IjNyB0+wezcOgKSuAknc7mVdfRqJZWaQM1kFb/YLjBf2Os7l6nblxfWch8d9l4QNPX4EfCNH2O0YPWo4wmm+iw5CZtHxg6ohSiuRK+MrhKCaT6rw/CCiM+n4vCZhQRPCOm6AGckWt073oMPEHzbiFDY6jX7oOljahuuOtjKK6bEsRIFEzf/UsXybkXMg8RzYzfmP2i6VjvwR8FLU8jFk", - "decrypted_dataset": "TGluayBkYXRhIDA1", - "valid_publicly": true, - "valid_privately": true -} \ No newline at end of file diff --git a/testvectors/dynamic/correct/006_correct_link.json b/testvectors/dynamic/correct/006_correct_link.json deleted file mode 100644 index 3de86ff..0000000 --- a/testvectors/dynamic/correct/006_correct_link.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "dynamic/correct/006_correct_link", - "description": "Correct link - 06", - "added_at": "2023-01-20", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKXPoKfK+FkzuhgqTflnVLITvhFqunmeLk1o4DynuU/FHrAH3lYEzv4PkwrxBnNQ3Dx5Cv+FVbDaq+//0VXnoVcGPX4EfCNH2O0YJ80pb+K4wGt7L71OLiZytDOK39A35Mf89Aaf9FcNZ2wRis0xYn+qLFsUaV1aNnZLOngpoNuxnI1pbHx+6xz+0Mp2po7yglY2pwOpBHnkY27Dx0hVZnwdF/EgO7rPd7KnIzyQE5WJ", - "decrypted_dataset": "TGluayBkYXRhIDA2", - "valid_publicly": true, - "valid_privately": true -} \ No newline at end of file diff --git a/testvectors/dynamic/correct/007_correct_link.json b/testvectors/dynamic/correct/007_correct_link.json deleted file mode 100644 index 6b4e7e2..0000000 --- a/testvectors/dynamic/correct/007_correct_link.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "dynamic/correct/007_correct_link", - "description": "Correct link - 07", - "added_at": "2023-01-20", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKW5m3FBLx9KZEmOcmhbM3dti/fxWEju4KTrG07gjCOvTdGqbkkwYMsRTRzLgCT4PdSjvJOcxU71tDOmX6tfUFIGPX4EfCNH2O0YB+C107yVoWAJU531DZjHCpollszlQU/Y782MugMPsSRmR30P1/rq8bcEa8nc75Feswc9fSmDANWilYDue7tABdh9JqHabtYLLlw15nsvx13QTyOxMY9cB+Dh2eqIWftBmSNMX2mF", - "decrypted_dataset": "TGluayBkYXRhIDA3", - "valid_publicly": true, - "valid_privately": true -} \ No newline at end of file diff --git a/testvectors/dynamic/correct/008_correct_link.json b/testvectors/dynamic/correct/008_correct_link.json deleted file mode 100644 index b9637eb..0000000 --- a/testvectors/dynamic/correct/008_correct_link.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "dynamic/correct/008_correct_link", - "description": "Correct link - 08", - "added_at": "2023-01-20", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKUTp9TUW8DNKmcp66mNHsALLxCF4Oo8tW4jcEhdE6JcOj8y/6UWmGrPrPAEKm0CbC95O9ZVt2tPOTHZaxT7aQoDPX4EfCNH2O0YCaXt1Nb2h5QaoN7hWVza51g8+Rz3MpTYuILzZYbRFIEqJ9lYI0ZYVfWINJnXSRCve1QZbtLwcWG/QGUAsS/zdfwXjbvOlx36nBuEYIAp1wrp+Ks1DzIQOAo9+eIFXvnCfbD5ulqo", - "decrypted_dataset": "TGluayBkYXRhIDA4", - "valid_publicly": true, - "valid_privately": true -} \ No newline at end of file diff --git a/testvectors/dynamic/correct/009_correct_link.json b/testvectors/dynamic/correct/009_correct_link.json deleted file mode 100644 index ac4d867..0000000 --- a/testvectors/dynamic/correct/009_correct_link.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "dynamic/correct/009_correct_link", - "description": "Correct link - 09", - "added_at": "2023-01-20", - "blob_name": "T9p+5vJxte//0gUnC7oRExP1yQadbDZfgNNQ48WbDo3m", - "encryption_key": "AHnSaMjr1qG9XehjHPdzc3cmmU7HNdmBtSCo19kL1QVJ", - "update_dataset": "ABHa3Q+UvsoSvrt0JVI0ZRv6Wz9XBPGNswi5RdGQJo1fNLk1Ln+XbKVrfSOSRfo6ekJZqldQasZVk3tcCo30JeqF6Y7adSaw3FvTLNFn/9mgwlOyDPQ8excDgyYKOvsIOqCQ0T2uuNwKPX4EfCNH2O0YvfmbqmCSI1gQYDv7q30h1hmbiaqFQLelri58c0CJ+8OXuSOqeXlIbQsbeXm7vUzL0Tr7OoCBmOCUnziWv+dDFkigPINOblaARZlsAwRSYealBG4j48UyGEY273YewPMMlfq/CvDe", - "decrypted_dataset": "TGluayBkYXRhIDA5", - "valid_publicly": true, - "valid_privately": true -} \ No newline at end of file diff --git a/testvectors/embedded.go b/testvectors/embedded.go deleted file mode 100644 index 7d7e248..0000000 --- a/testvectors/embedded.go +++ /dev/null @@ -1,67 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package testvectors - -import ( - "embed" - "encoding/json" - "io/fs" - "strings" - - "github.com/cinode/go-datastore/pkg/utilities/golang" - "github.com/cinode/go-datastore/testvectors/internal" -) - -//go:embed dynamic/* -var testVectorsData embed.FS - -type TestCase = internal.TestCase - -var parsedTestCases = func() []*TestCase { - var testCases []*TestCase - - fs.WalkDir(testVectorsData, "dynamic", func(path string, d fs.DirEntry, err error) error { - golang.Must(err, err) - - if d.IsDir() || !strings.HasSuffix(path, ".json") { - return nil - } - - data := golang.Must(fs.ReadFile(testVectorsData, path)) - - testCase := TestCase{} - - err = json.Unmarshal(data, &testCase) - golang.Must(err, err) - - testCase.Details = strings.Join(testCase.DetailsLines, "\n") - - testCases = append(testCases, &testCase) - - return nil - }) - - return testCases -}() - -func AllTestCases(yield func(tc *TestCase) bool) { - for _, tc := range parsedTestCases { - if !yield(tc) { - return - } - } -} diff --git a/testvectors/generate/main.go b/testvectors/generate/main.go deleted file mode 100644 index 7e3806e..0000000 --- a/testvectors/generate/main.go +++ /dev/null @@ -1,1247 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// The generator application creates test vectors for dynamic data -// -// Those vectors contain both valid and invalid datasets. -// -// Vectors are created independently from the main code to ensure -// a bug in the main implementation does not result in false positives -// or negatives caused by propagation of the bug into the test -// vectors generation. Similarly, whenever the generation method -// is incorrect, it is more probable to find it with an independent -// implementation. -package main - -import ( - "bytes" - "crypto/cipher" - "crypto/ed25519" - "crypto/sha256" - "encoding/binary" - "encoding/json" - "fmt" - "os" - "os/exec" - "path/filepath" - "strings" - - "github.com/cinode/go-datastore/testvectors/internal" - "golang.org/x/crypto/chacha20" -) - -func main() { - generateTestVectorsForDynamicLinks() -} - -type TestCase = internal.TestCase - -type gp struct { - reservedByte *byte - privKey *ed25519.PrivateKey - nonce *uint64 - contentVersion *uint64 - - signatureHashPrefix *byte - signatureBlobName func([]byte) []byte - signatureContentVersion func(uint64) uint64 - signatureIVLen func(byte) byte - signatureIV func([]byte) []byte - signatureEncryptedLinkData func([]byte) []byte - signaturePostProcess func([]byte) []byte - - keyGenSignatureDataPrefix *byte - keyGenSignatureBlobName func([]byte) []byte - keyGenSignature func([]byte) []byte - keyGenHashPrefix *byte - keyGenHashEncryptionAlg *byte - keyGenHashBlobType *byte - - keyType *byte - keyBytes func([]byte) []byte - - linkDataPrefix *byte - - keyValidationBlockSignatureDataPrefix *byte - keyValidationBlockSignatureBlobName func([]byte) []byte - keyValidationSignature func([]byte) []byte - keyValidationBlockLength func(byte) byte - - ivGenHashPrefix *byte - ivGenEncryptionAlg *byte - ivGenBlobType *byte - ivGenBlobNameLength func(byte) byte - ivGenBlobName func([]byte) []byte - ivGenContentVersion func(ver uint64) uint64 - ivGenLinkData func([]byte) []byte - ivCorrupt func([]byte) []byte - - unencryptedDataBuffCorruption func([]byte) []byte - - linkData *[]byte - - truncateAt int -} - -func def[T any](b **T, v T) { - if *b == nil { - *b = &v - } -} - -func uint64p(v uint64) *uint64 { return &v } -func bytep(b byte) *byte { return &b } - -func defF[T any](b *func(T) T) { - cp := func(val any) any { - if val, ok := val.([]byte); ok { - return append([]byte{}, val...) - } - return val - } - - if *b == nil { - *b = func(t T) T { return t } - } else { - orig := *b - *b = func(t T) T { - return orig(cp(t).(T)) - } - } -} - -func genAll(gp gp) ([]byte, []byte, []byte, []byte) { - - // Set default parameters - def(&gp.reservedByte, 0) - def(&gp.privKey, ed25519.NewKeyFromSeed(seed1[:ed25519.SeedSize])) - def(&gp.nonce, binary.BigEndian.Uint64(seed2[:])) - def(&gp.contentVersion, binary.BigEndian.Uint64(seed2[8:])) - def(&gp.signatureHashPrefix, 0) - defF(&gp.signatureBlobName) - defF(&gp.signatureContentVersion) - defF(&gp.signatureIVLen) - defF(&gp.signatureIV) - defF(&gp.signatureEncryptedLinkData) - defF(&gp.signaturePostProcess) - def(&gp.keyGenSignatureDataPrefix, 0x01) - defF(&gp.keyGenSignatureBlobName) - defF(&gp.keyGenSignature) - def(&gp.keyGenHashPrefix, 0x01) // Hasher for key - def(&gp.keyGenHashEncryptionAlg, 0x00) // Hasher for chacha20 - def(&gp.keyGenHashBlobType, 0x02) // Dynamic link blob type - def(&gp.keyType, 0x00) // chacha20 key type - defF(&gp.keyBytes) - def(&gp.linkDataPrefix, 0x00) - def(&gp.keyValidationBlockSignatureDataPrefix, 0x01) - defF(&gp.keyValidationBlockSignatureBlobName) - defF(&gp.keyValidationSignature) - defF(&gp.keyValidationBlockLength) - def(&gp.ivGenHashPrefix, 0x02) // Hasher for iv - def(&gp.ivGenEncryptionAlg, 0x00) // Hasher for chacha20 - def(&gp.ivGenBlobType, 0x02) // Dynamic link blob type - defF(&gp.ivGenBlobNameLength) - defF(&gp.ivGenBlobName) - defF(&gp.ivGenContentVersion) - defF(&gp.ivGenLinkData) - defF(&gp.ivCorrupt) - defF(&gp.unencryptedDataBuffCorruption) - def(&gp.linkData, seed4[:]) - - // Start link data creation - it begins with unchanging link data - buff := []byte{} - buff = append(buff, *gp.reservedByte) - buff = append(buff, gp.privKey.Public().(ed25519.PublicKey)...) - buff = append(buff, 0, 0, 0, 0, 0, 0, 0, 0) - binary.BigEndian.PutUint64(buff[len(buff)-8:], *gp.nonce) - - hash := sha256.Sum256(buff) - - blobName := [sha256.Size + 1]byte{0x02} - copy(blobName[1:], hash[:]) - - for i := 1; i <= sha256.Size; i++ { - blobName[0] ^= blobName[i] - } - - keygenToSignBytes := append( - []byte{*gp.keyGenSignatureDataPrefix}, - gp.keyGenSignatureBlobName(blobName[:])..., - ) - - keyGenHasher := sha256.New() - keyGenHasher.Write([]byte{ - *gp.keyGenHashPrefix, - *gp.keyGenHashEncryptionAlg, - *gp.keyGenHashBlobType, - }) - keyGenHasher.Write(gp.keyGenSignature(ed25519.Sign(*gp.privKey, keygenToSignBytes))) - - key := append( - []byte{*gp.keyType}, - gp.keyBytes(keyGenHasher.Sum(nil)[:chacha20.KeySize])..., - ) - - keyValidationBlockKeygenToSignBytes := append( - []byte{*gp.keyValidationBlockSignatureDataPrefix}, - gp.keyValidationBlockSignatureBlobName(blobName[:])..., - ) - - keyValidationBlock := gp.keyValidationSignature(ed25519.Sign(*gp.privKey, keyValidationBlockKeygenToSignBytes)) - - unencryptedDataBuff := append( - []byte{ - *gp.linkDataPrefix, - gp.keyValidationBlockLength( - byte(len(keyValidationBlock)), - ), - }, - keyValidationBlock..., - ) - unencryptedDataBuff = append(unencryptedDataBuff, *gp.linkData...) - - ivGenHasher := sha256.New() - ivGenHasher.Write([]byte{ - *gp.ivGenHashPrefix, - *gp.ivGenEncryptionAlg, - *gp.ivGenBlobType, - gp.ivGenBlobNameLength(byte(len(blobName))), - }) - ivGenHasher.Write(gp.ivGenBlobName(blobName[:])) - - var verBuff [8]byte - binary.BigEndian.PutUint64(verBuff[:], gp.ivGenContentVersion(*gp.contentVersion)) - ivGenHasher.Write(verBuff[:]) - ivGenHasher.Write(gp.ivGenLinkData(unencryptedDataBuff)) - - iv := gp.ivCorrupt(ivGenHasher.Sum(nil)[:chacha20.NonceSizeX]) - - ccc, err := chacha20.NewUnauthenticatedCipher(key[1:], iv) - if err != nil { - panic(err) - } - encryptedLinkDataBuff := bytes.NewBuffer(nil) - cipher.StreamWriter{ - S: ccc, - W: encryptedLinkDataBuff, - }.Write(gp.unencryptedDataBuffCorruption(unencryptedDataBuff)) - - encryptedLinkData := encryptedLinkDataBuff.Bytes() - - toSignHasher := sha256.New() - toSignHasher.Write([]byte{*gp.signatureHashPrefix}) - bn := gp.signatureBlobName(blobName[:]) - toSignHasher.Write([]byte{byte(len(bn))}) - toSignHasher.Write(bn) - binary.BigEndian.PutUint64(verBuff[:], gp.signatureContentVersion(*gp.contentVersion)) - toSignHasher.Write(verBuff[:]) - toSignHasher.Write([]byte{gp.signatureIVLen(byte(len(iv)))}) - toSignHasher.Write(gp.signatureIV(iv)) - toSignHasher.Write(gp.signatureEncryptedLinkData(encryptedLinkData)) - - signature := ed25519.Sign(*gp.privKey, toSignHasher.Sum(nil)) - signature = gp.signaturePostProcess(signature) - - // Add changing data to the link buffer - buff = append(buff, signature...) - buff = append(buff, 0, 0, 0, 0, 0, 0, 0, 0) - binary.BigEndian.PutUint64(buff[len(buff)-8:], *gp.contentVersion) - buff = append(buff, byte(len(iv))) - buff = append(buff, iv...) - buff = append(buff, encryptedLinkData...) - - if gp.truncateAt > 0 { - buff = buff[:gp.truncateAt] - } - - return buff, blobName[:], key, *gp.linkData -} - -func genLink(gp gp) []byte { - link, _, _, _ := genAll(gp) - return link -} - -func blobName(gp gp) []byte { - _, blobName, _, _ := genAll(gp) - return blobName -} - -func key(gp gp) []byte { - _, _, key, _ := genAll(gp) - return key -} - -func decrypted(gp gp) []byte { - _, _, _, decrypted := genAll(gp) - return decrypted -} - -var ( - seed1 = sha256.Sum256([]byte("cinode test vectors data seed - 1")) - seed2 = sha256.Sum256([]byte("cinode test vectors data seed - 2")) - seed3 = sha256.Sum256([]byte("cinode test vectors data seed - 3")) - seed4 = sha256.Sum256([]byte("cinode test vectors data seed - 4")) - - privKey2 = ed25519.NewKeyFromSeed(seed3[:ed25519.SeedSize]) -) - -func writeLinkData(tc TestCase) error { - fName := "../" + tc.Name + ".json" - err := os.MkdirAll(filepath.Dir(fName), 0o777) - if err != nil { - return err - } - - l := strings.Split(tc.Details, "\n") - for i := range l { - l[i] = strings.TrimSpace(l[i]) - } - for len(l) > 0 && l[0] == "" { - l = l[1:] - } - for len(l) > 0 && l[len(l)-1] == "" { - l = l[:len(l)-1] - } - tc.DetailsLines = l - - jsonData, err := json.MarshalIndent(&tc, "", " ") - if err != nil { - return err - } - - err = os.WriteFile(fName, jsonData, 0o666) - if err != nil { - return err - } - - return nil -} - -func writeTestBlobCode( - name string, - updateDataset []byte, - blobName []byte, - encryptionKey []byte, - decryptedDataset []byte, -) error { - toBytes := func(buf []byte) string { - ret := "[]byte{" - for i, b := range buf { - if i%8 == 0 { - ret += "\n" - } - ret += fmt.Sprintf("0x%02X", b) - ret += "," - } - ret += "\n}" - return ret - } - code := fmt.Sprintf(` - package testblobs - - var %s = TestBlob { - %s, - %s, - %s, - %s, - } - `, - name, - toBytes(updateDataset), - toBytes(blobName), - toBytes(encryptionKey), - toBytes(decryptedDataset), - ) - - fileName := filepath.Join("testblobs", strings.ToLower(name)+".go") - err := os.WriteFile(fileName, []byte(code), 0o666) - if err != nil { - return err - } - - err = exec.Command("gofmt", "-w", fileName).Run() - if err != nil { - return err - } - - return nil -} - -func generateTestVectorsForDynamicLinks() { - // A simple dynamic link in form of a go code - { - linkData := []byte("dynamic link") - gp := gp{ - linkData: &linkData, - } - writeTestBlobCode( - "DynamicLink", - genLink(gp), - blobName(gp), - key(gp), - linkData, - ) - } - - // Completely valid links - for i := 0; i < 10; i++ { - linkData := []byte(fmt.Sprintf("Link data %02d", i)) - gp := gp{ - linkData: &linkData, - } - if i == 0 { - // Have the first blob with default dataset - gp.linkData = nil - } - writeLinkData(TestCase{ - Description: fmt.Sprintf("Correct link - %02d", i), - Name: fmt.Sprintf("dynamic/correct/%03d_correct_link", i), - WhenAdded: "2023-01-20", - UpdateDataset: genLink(gp), - BlobName: blobName(gp), - EncryptionKey: key(gp), - DecryptedDataset: decrypted(gp), - ValidPublicly: true, - ValidPrivately: true, - }) - } - - // Incorrect blobs on the public layer, those should be rejected by the network - // automatically, any failure to reject those can be exploited - - writeLinkData(TestCase{ - Details: ` - Empty dataset is an invalid blob. - `, - Description: "Empty dataset", - Name: "dynamic/attacks/public/001_empty", - WhenAdded: "2023-01-20", - UpdateDataset: []byte{}, - BlobName: blobName(gp{}), - EncryptionKey: key(gp{}), - GoErrorContains: "data truncated while reading reserved byte", - }) - - writeLinkData(TestCase{ - Details: ` - The first byte in the dataset is reserved for future protocol - modifications without breaking backwards compatibility. - Currently the reserved byte must be zero, any byte other than - that must be rejected. - `, - Description: "Invalid reserved byte", - Name: "dynamic/attacks/public/002_reserved_byte", - WhenAdded: "2023-01-20", - UpdateDataset: genLink(gp{ - reservedByte: bytep(0xFF), - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{}), - GoErrorContains: "invalid value of the reserved byte", - }) - - writeLinkData(TestCase{ - Description: "Truncated public key", - Name: "dynamic/attacks/public/003_pubkey_truncated", - WhenAdded: "2023-01-20", - UpdateDataset: genLink(gp{ - truncateAt: 1 + ed25519.PublicKeySize/2, - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{}), - GoErrorContains: "data truncated while reading public key", - }) - - writeLinkData(TestCase{ - Description: "Truncated nonce", - Name: "dynamic/attacks/public/004_nonce_truncated", - WhenAdded: "2023-01-20", - UpdateDataset: genLink(gp{ - truncateAt: 1 + ed25519.PublicKeySize + 4, - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{}), - GoErrorContains: "data truncated while reading nonce", - }) - - writeLinkData(TestCase{ - Details: ` - Blob name is built based on unchanging data in the dynamic link - this test blob ensures that the reserved byte value must remain the same - to keep the same blob name - `, - Description: "Blob name mismatch for reserved byte", - Name: "dynamic/attacks/public/005_blob_mismatch_reserved_byte", - WhenAdded: "2023-01-20", - UpdateDataset: genLink(gp{}), - BlobName: blobName(gp{ - reservedByte: bytep(0xFF), - }), - EncryptionKey: key(gp{}), - GoErrorContains: "blob name mismatch", - }) - - writeLinkData(TestCase{ - Details: ` - Blob name is built based on unchanging data in the dynamic link - this test blob ensures that the public key must remain the same - to keep the same blob name - `, - Description: "Blob name mismatch for key", - Name: "dynamic/attacks/public/006_blob_mismatch_priv_key", - WhenAdded: "2023-01-20", - UpdateDataset: genLink(gp{}), - BlobName: blobName(gp{ - privKey: &privKey2, - }), - EncryptionKey: key(gp{}), - GoErrorContains: "blob name mismatch", - }) - - writeLinkData(TestCase{ - Details: ` - Blob name is built based on unchanging data in the dynamic link - this test blob ensures that the link nonce must remain the same - to keep the same blob name - `, - Description: "Blob name mismatch for nonce", - Name: "dynamic/attacks/public/007_blob_mismatch_nonce", - WhenAdded: "2023-01-20", - UpdateDataset: genLink(gp{}), - BlobName: blobName(gp{ - nonce: uint64p(12345), - }), - EncryptionKey: key(gp{}), - GoErrorContains: "blob name mismatch", - }) - - writeLinkData(TestCase{ - Description: "Truncated signature", - Name: "dynamic/attacks/public/008_truncated_signature", - WhenAdded: "2023-01-20", - UpdateDataset: genLink(gp{ - truncateAt: 1 + ed25519.PublicKeySize + 8 + ed25519.SignatureSize/2, - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{}), - GoErrorContains: "data truncated while reading signature", - }) - - writeLinkData(TestCase{ - Description: "Truncated content version", - Name: "dynamic/attacks/public/009_truncated_content_version", - WhenAdded: "2023-01-20", - UpdateDataset: genLink(gp{ - truncateAt: 1 + ed25519.PublicKeySize + 8 + ed25519.SignatureSize + 4, - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{}), - GoErrorContains: "data truncated while reading content version", - }) - - writeLinkData(TestCase{ - Description: "Truncated iv", - Name: "dynamic/attacks/public/010_truncated_iv", - WhenAdded: "2023-01-20", - UpdateDataset: genLink(gp{ - truncateAt: 1 + ed25519.PublicKeySize + 8 + 8 + ed25519.SignatureSize + chacha20.NonceSizeX/2, - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{}), - GoErrorContains: "data truncated while reading iv", - }) - - writeLinkData(TestCase{ - Details: ` - Dynamic link signature is protecting both unchanging and changing blob - data. Also to enable signatures for different independent data sources, - those are using additional prefixes to avoid signature collisions that could - be exploited by an attacker (e.g. by causing reveal of signature for - data in one source that has the same byte sequence as in other source). - This test ensures that the signature won't match if the data source prefix - is different. - `, - Description: "Signature mismatch - prefix byte", - Name: "dynamic/attacks/public/011_signature_mismatch_prefix", - WhenAdded: "2023-01-20", - UpdateDataset: genLink(gp{ - signatureHashPrefix: bytep(0xFF), - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{}), - GoErrorContains: "signature mismatch", - }) - - writeLinkData(TestCase{ - Details: ` - Dynamic link signature is protecting both unchanging and changing blob - data. The same public/private key pair can be used for different blob names. - To ensure that the signature for different blob names is different, - the blob name itself is included in the signed dataset. - - This prevents an attack where targeted person uses the same private key - but different nonces to sign data of different security level. If the same - signature would be created for the same dataset in different blob names, - attacker could use this to trick the victim to sign some less important - information and reuse it to replace other blob name that is of a high - importance. - `, - Description: "Signature mismatch - blob name", - Name: "dynamic/attacks/public/012_signature_mismatch_blob_name", - WhenAdded: "2023-01-20", - UpdateDataset: genLink(gp{ - signatureBlobName: func(b []byte) []byte { - return blobName(gp{privKey: &privKey2}) - }, - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{}), - GoErrorContains: "signature mismatch", - }) - - writeLinkData(TestCase{ - Details: ` - Dynamic link signature is protecting both unchanging and changing blob - data. To enable forward progress, the link with higher content version - does replace the link with the lower one. The signature thus has to - include the content version so that the attacker can not reuse an older - version of the link to replace newer one by bumping the content version. - `, - Description: "Signature mismatch - content version", - Name: "dynamic/attacks/public/013_signature_mismatch_content_version", - WhenAdded: "2023-01-20", - UpdateDataset: genLink(gp{ - signatureContentVersion: func(u uint64) uint64 { return u + 1 }, - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{}), - GoErrorContains: "signature mismatch", - }) - - writeLinkData(TestCase{ - Details: ` - Dynamic link signature is protecting both unchanging and changing blob - data. Changing data contains the initialization vector (IV) for the - cipher used to encrypt the data. That IV is calculated deterministically - from unencrypted dataset but can not be calculated on the public network - layer. The signature thus has to include the IV so that the attacker can - not destroy the link by scrambling the IV. Doing so would make it impossible - to decrypt the data. The IV may be of any length thus the encoding of the IV - used for signature calculation contains the length of the IV. - - This test checks if invalid IV length used while calculating link's signature - will result in failed link verification. - `, - Description: "Signature mismatch - iv", - Name: "dynamic/attacks/public/014_signature_mismatch_initialization_vector", - WhenAdded: "2023-01-23", - UpdateDataset: genLink(gp{ - signatureIV: func(b []byte) []byte { - b[len(b)/2] ^= 0xE0 - return b - }, - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{}), - GoErrorContains: "signature mismatch", - }) - - writeLinkData(TestCase{ - Details: ` - Dynamic link signature is protecting both unchanging and changing blob - data. Changing data contains the initialization vector (IV) for the - cipher used to encrypt the data. That IV is calculated deterministically - from unencrypted dataset but can not be calculated on the public network - layer. The signature thus has to include the IV so that the attacker can - not destroy the link by scrambling the IV. Doing so would make it impossible - to decrypt the data. - - This test checks if invalid IV bytes used while calculating link's signature - will result in failed link verification. - `, - Description: "Signature mismatch - iv", - Name: "dynamic/attacks/public/015_signature_mismatch_initialization_vector_length", - WhenAdded: "2023-01-23", - UpdateDataset: genLink(gp{ - signatureIVLen: func(b byte) byte { return b + 1 }, - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{}), - GoErrorContains: "signature mismatch", - }) - - writeLinkData(TestCase{ - Details: ` - Dynamic link signature is protecting both unchanging and changing blob - data. The signature must protect the main encrypted link data. - The signature has to be calculated over the encrypted data so that the - network can detect invalid signatures without revealing the unencrypted - information. - `, - Description: "Signature mismatch - encrypted link data", - Name: "dynamic/attacks/public/016_signature_mismatch_encrypted_link_data", - WhenAdded: "2023-01-20", - UpdateDataset: genLink(gp{ - signatureEncryptedLinkData: func(b []byte) []byte { - // Modify single bit in the encrypted link data - b[len(b)/2] ^= 0x40 - return b - }, - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{}), - GoErrorContains: "signature mismatch", - }) - - writeLinkData(TestCase{ - Description: "Signature mismatch - corrupted signature bytes", - Name: "dynamic/attacks/public/017_signature_mismatch_signature_bytes", - WhenAdded: "2023-01-20", - UpdateDataset: genLink(gp{ - signaturePostProcess: func(b []byte) []byte { - // Change single bit in the middle of the signature - b[len(b)/2] ^= 0x80 - return b - }, - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{}), - GoErrorContains: "signature mismatch", - }) - - // Public data is valid but private data is incorrect, - // those blobs will only fail when trying to read the data but will be successfully - // propagated through the network when validating public dataset only. - // - // If validation fails at the private level, this means that the creator of the data - // is compromised. This could happen for various reasons: - // * Creator uses incorrect software producing invalid blogs - // * Creator was hacked and someone is disrupting the link creation process - // * Creator is a bad actor and tries to disrupt the network in some way - - writeLinkData(TestCase{ - Details: ` - Data link encryption key is deterministically calculated from link's configuration. - It must be calculate based on unchanging link dataset and private key. - - When generating the encryption key, private key is used to calculate a signature - of blob's unchanging data. That signature must be different to the signature - used to sign the encrypted data itself thus a different prefix is used for each - signing schemes. - - This test ensures that an invalid prefix determining the kind of data being signed - results in encryption key validation failure. - `, - Description: "Invalid encryption key - keygen signature data prefix", - Name: "dynamic/attacks/private/001_keygen_signature_prefix", - WhenAdded: "2023-01-20", - UpdateDataset: genLink(gp{ - keyValidationBlockSignatureDataPrefix: bytep(0x77), - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{}), - ValidPublicly: true, - GoErrorContains: "invalid key validation block signature", - }) - - writeLinkData(TestCase{ - Details: ` - Data link encryption key is deterministically calculated from link's configuration. - It must be calculate based on unchanging link dataset and private key. - - This test ensures that an invalid blob name used in key generation signature will - result in rejection of the encryption key. - `, - Description: "Invalid encryption key - keygen signature blob name", - Name: "dynamic/attacks/private/002_keygen_signature_blob_name", - WhenAdded: "2023-01-21", - UpdateDataset: genLink(gp{ - keyValidationBlockSignatureBlobName: func(b []byte) []byte { - b[len(b)/2] ^= 0x20 - return b - }, - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{}), - ValidPublicly: true, - GoErrorContains: "invalid key validation block signature", - }) - - writeLinkData(TestCase{ - Details: ` - Data link encryption key is deterministically calculated from link's configuration. - It must be calculate based on unchanging link dataset and private key. - - This test ensures that any corruption in the base signature will result in rejection - of the encryption key. - `, - Description: "Invalid encryption key - keygen signature corruption", - Name: "dynamic/attacks/private/003_keygen_signature_corruption", - WhenAdded: "2023-01-21", - UpdateDataset: genLink(gp{ - keyValidationBlockSignatureBlobName: func(b []byte) []byte { - b[len(b)/2] ^= 0x20 - return b - }, - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{}), - ValidPublicly: true, - GoErrorContains: "invalid key validation block signature", - }) - - writeLinkData(TestCase{ - Details: ` - Data link encryption key is calculated by using a hash function. - To avoid potential security issue where the same hash function with - the same input data is reused in different context revealing such hash publicly, - each kind of data uses different prefix for the data being hashed mitigating the risk. - - This test ensures that invalid hashed data prefix results with an invalid key that is - rejected while trying to read the plaintext link data. - `, - Description: "Invalid encryption key - keygen hash prefix", - Name: "dynamic/attacks/private/004_keygen_hash_prefix", - WhenAdded: "2023-01-21", - UpdateDataset: genLink(gp{ - keyGenHashPrefix: bytep(0xFE), - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{ - keyGenHashPrefix: bytep(0xFE), - }), - ValidPublicly: true, - GoErrorContains: "key mismatch", - }) - - writeLinkData(TestCase{ - Details: ` - Data link encryption key is calculated by using a hash function. - To avoid potential security issue where the same hash function with - the same input data is reused for different encryption algorithms, - there's an encoded encryption algorithm info before the final data to be hashed. - - This test ensures that invalid encryption algorithm information used in the hashed - data results with an invalid key that is rejected while trying to read the plaintext link data. - `, - Description: "Invalid encryption key - keygen hash encryption alg", - Name: "dynamic/attacks/private/005_keygen_hash_encryption_alg", - WhenAdded: "2023-01-21", - UpdateDataset: genLink(gp{ - keyGenHashEncryptionAlg: bytep(0xFD), - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{ - keyGenHashEncryptionAlg: bytep(0xFD), - }), - ValidPublicly: true, - GoErrorContains: "key mismatch", - }) - - writeLinkData(TestCase{ - Details: ` - Data link encryption key is calculated by using a hash function. - To avoid potential security issue where the same hash function with - the same input data is reused for different blob type, there's an encoded - blob type information before the final data to be hashed. - - This test ensures that invalid blob type used in the hashed data results - with an invalid key that is rejected while trying to read the plaintext link data. - `, - Description: "Invalid encryption key - keygen hash blob type", - Name: "dynamic/attacks/private/006_keygen_hash_blob_type", - WhenAdded: "2023-01-21", - UpdateDataset: genLink(gp{ - keyGenHashBlobType: bytep(0xFC), - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{ - keyGenHashBlobType: bytep(0xFC), - }), - ValidPublicly: true, - GoErrorContains: "key mismatch", - }) - - writeLinkData(TestCase{ - Details: ` - Encryption key contains encryption algorithm type information encoded before - key bytes. This test ensures that invalid encryption algorithm is rejected. - `, - Description: "Invalid encryption key - keygen type", - Name: "dynamic/attacks/private/007_key_type", - WhenAdded: "2023-01-21", - UpdateDataset: genLink(gp{ - keyType: bytep(0xFB), - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{ - keyType: bytep(0xFB), - }), - ValidPublicly: true, - GoErrorContains: "wrong key type", - }) - - writeLinkData(TestCase{ - Details: ` - Encryption key contains encryption algorithm type information encoded before - key bytes. This test ensures that invalid encryption algorithm is rejected. - `, - Description: "Invalid encryption key - keygen corruption", - Name: "dynamic/attacks/private/008_key_corruption", - WhenAdded: "2023-01-21", - UpdateDataset: genLink(gp{ - keyBytes: func(b []byte) []byte { - b[len(b)/2] ^= 0x10 - return b - }, - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{}), - ValidPublicly: true, - // Encryption key is invalid, we most likely get garbage there, - // since the reserved byte is at the beginning, it will contain the wrong value - GoErrorContains: "invalid value of the internal reserved byte", - }) - - writeLinkData(TestCase{ - Details: ` - For client validation purposes, unencrypted data contains key validation block - next to the link information itself. That data contains prefix so that it can be - modified in the future supporting different validation block formats. - - This test checks if invalid prefix value makes the blob invalid. - `, - Description: "Invalid link data - prefix", - Name: "dynamic/attacks/private/009_link_data_prefix", - WhenAdded: "2023-01-21", - UpdateDataset: genLink(gp{ - linkDataPrefix: bytep(0xFA), - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{}), - ValidPublicly: true, - GoErrorContains: "invalid value of the internal reserved byte", - }) - - writeLinkData(TestCase{ - Details: ` - For client validation purposes, unencrypted data contains key validation block - next to the link information itself. That validation block contains the signature - used converted later to an encryption key with a hash function. - - This test checks if an invalid signature stored in key validation block - will end up with the link being rejected. - `, - Description: "Invalid key validation block - signature", - Name: "dynamic/attacks/private/010_key_validation_block_signature", - WhenAdded: "2023-01-21", - UpdateDataset: genLink(gp{ - keyValidationSignature: func(b []byte) []byte { - b[len(b)/2] ^= 0x08 - return b - }, - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{}), - ValidPublicly: true, - GoErrorContains: "invalid key validation block signature", - }) - - writeLinkData(TestCase{ - Details: ` - For client validation purposes, unencrypted data contains key validation block - next to the link information itself. The data is length prefixed and must be of - a specific length to be accepted. - - This test checks whether key validation block smaller than the desired value - will result in rejection of the encryption key. - `, - Description: "Invalid key validation block - length smaller", - Name: "dynamic/attacks/private/011_key_validation_block_length_smaller", - WhenAdded: "2023-01-21", - UpdateDataset: genLink(gp{ - keyValidationBlockLength: func(b byte) byte { - return b - 1 - }, - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{}), - ValidPublicly: true, - GoErrorContains: "invalid key validation block signature", - }) - - writeLinkData(TestCase{ - Details: ` - For client validation purposes, unencrypted data contains key validation block - next to the link information itself. The data is length prefixed and must be of - a specific length to be accepted. - - This test checks whether key validation block larger than the desired value - will result in rejection of the encryption key. - `, - Description: "Invalid key validation block - length larger", - Name: "dynamic/attacks/private/012_key_validation_block_length_larger", - WhenAdded: "2023-01-21", - UpdateDataset: genLink(gp{ - keyValidationBlockLength: func(b byte) byte { - return b + 1 - }, - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{}), - ValidPublicly: true, - GoErrorContains: "invalid key validation block signature", - }) - - writeLinkData(TestCase{ - Details: ` - Data link encryption iv is calculated by using a hash function. - To avoid potential security issue where the same hash function with - the same input data is reused in different context revealing such hash publicly, - each kind of data uses different prefix for the data being hashed mitigating the risk. - - This test ensures that invalid hashed data prefix results with an invalid iv that is - rejected while trying to read the plaintext link data. - `, - Description: "Invalid encryption iv - iv gen hash prefix", - Name: "dynamic/attacks/private/013_iv_has_gen_h_prefix", - WhenAdded: "2023-01-21", - UpdateDataset: genLink(gp{ - ivGenHashPrefix: bytep(0xFE), - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{}), - ValidPublicly: true, - GoErrorContains: "iv mismatch", - }) - - writeLinkData(TestCase{ - Details: ` - Data link encryption iv is calculated by using a hash function. - To avoid potential security issue where the same hash function with - the same input data is reused for different encryption algorithms, - there's an encoded encryption algorithm info before the final data to be hashed. - - This test ensures that invalid encryption algorithm information used in the hashed - data results with an invalid iv that is rejected while trying to read the plaintext link data. - `, - Description: "Invalid encryption iv - iv gen hash encryption alg", - Name: "dynamic/attacks/private/014_keygen_hash_encryption_alg", - WhenAdded: "2023-01-21", - UpdateDataset: genLink(gp{ - ivGenEncryptionAlg: bytep(0xFD), - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{}), - ValidPublicly: true, - GoErrorContains: "iv mismatch", - }) - - writeLinkData(TestCase{ - Details: ` - Data link encryption iv is calculated by using a hash function. - To avoid potential security issue where the same hash function with - the same input data is reused for different blob type, there's an encoded - blob type information before the final data to be hashed. - - This test ensures that invalid blob type used in the hashed data results - with an invalid iv that is rejected while trying to read the plaintext link data. - `, - Description: "Invalid encryption iv - iv gen hash blob type", - Name: "dynamic/attacks/private/015_iv_gen_hash_blob_type", - WhenAdded: "2023-01-21", - UpdateDataset: genLink(gp{ - ivGenBlobType: bytep(0xFC), - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{}), - ValidPublicly: true, - GoErrorContains: "iv mismatch", - }) - - writeLinkData(TestCase{ - Details: ` - Data link encryption iv is calculated by using a hash function. - The iv value is calculated from both changing and unchanging data. - - This test ensures that invalid blob name length used in the hashed data results - with an invalid iv that is rejected while trying to read the plaintext link data. - `, - Description: "Invalid encryption iv - iv gen hash blob name length", - Name: "dynamic/attacks/private/016_iv_gen_hash_blob_name_length", - WhenAdded: "2023-01-21", - UpdateDataset: genLink(gp{ - ivGenBlobNameLength: func(b byte) byte { return b + 1 }, - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{}), - ValidPublicly: true, - GoErrorContains: "iv mismatch", - }) - - writeLinkData(TestCase{ - Details: ` - Data link encryption iv is calculated by using a hash function. - The iv value is calculated from both changing and unchanging data. - - This test ensures that invalid blob name used in the hashed data results - with an invalid iv that is rejected while trying to read the plaintext link data. - `, - Description: "Invalid encryption iv - iv gen hash blob name", - Name: "dynamic/attacks/private/017_iv_gen_hash_blob_name", - WhenAdded: "2023-01-21", - UpdateDataset: genLink(gp{ - ivGenBlobName: func(b []byte) []byte { - b[len(b)/2] ^= 0x04 - return b - }, - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{}), - ValidPublicly: true, - GoErrorContains: "iv mismatch", - }) - - writeLinkData(TestCase{ - Details: ` - Data link encryption iv is calculated by using a hash function. - The iv value is calculated from both changing and unchanging data. - - This test ensures that invalid content version used in the hashed data results - with an invalid iv that is rejected while trying to read the plaintext link data. - `, - Description: "Invalid encryption iv - iv gen hash content version", - Name: "dynamic/attacks/private/018_iv_gen_hash_content_version", - WhenAdded: "2023-01-21", - UpdateDataset: genLink(gp{ - ivGenContentVersion: func(ver uint64) uint64 { return ver + 1 }, - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{}), - ValidPublicly: true, - GoErrorContains: "iv mismatch", - }) - - writeLinkData(TestCase{ - Details: ` - Data link encryption iv is calculated by using a hash function. - The iv value is calculated from both changing and unchanging data. - - This test ensures that invalid link data used in the hashed data results - with an invalid iv that is rejected while trying to read the plaintext link data. - `, - Description: "Invalid encryption iv - iv gen hash link data", - Name: "dynamic/attacks/private/019_iv_gen_hash_link_data", - WhenAdded: "2023-01-21", - UpdateDataset: genLink(gp{ - ivGenLinkData: func(b []byte) []byte { - b[len(b)/2] ^= 0x01 - return b - }, - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{}), - ValidPublicly: true, - GoErrorContains: "iv mismatch", - }) - - writeLinkData(TestCase{ - Details: ` - Data link encryption iv is calculated by using a hash function. - The iv value is calculated from both changing and unchanging data. - - This test ensures that corrupted iv is rejected while trying to read the plaintext link data. - `, - Description: "Invalid encryption iv - corrupted iv", - Name: "dynamic/attacks/private/020_corrupted_iv", - WhenAdded: "2023-01-21", - UpdateDataset: genLink(gp{ - ivCorrupt: func(b []byte) []byte { - b[len(b)/2] ^= 0x77 - return b - }, - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{}), - ValidPublicly: true, - GoErrorContains: "iv mismatch", - }) - - writeLinkData(TestCase{ - Details: ` - The link data can be properly encrypted and signed passing - public verification. However the internal dataset may be - corrupted. A truncated dataset should also be detected. - - The dataset can not be empty since there would be no - reserved internal byte value in such case. This test - checks whether such truncated link data would be detected. - `, - Description: "Truncated link data - missing reserved byte", - Name: "dynamic/attacks/private/021_truncated_link_data_reserved_byte", - WhenAdded: "2023-01-24", - UpdateDataset: genLink(gp{ - unencryptedDataBuffCorruption: func(b []byte) []byte { return []byte{} }, - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{}), - ValidPublicly: true, - GoErrorContains: "data truncated while reading internal reserved byte", - }) - - writeLinkData(TestCase{ - Details: ` - The link data can be properly encrypted and signed passing - public verification. However the internal dataset may be - corrupted. A truncated dataset should also be detected. - - This test checks if missing key validation length (and following data) - will be detected as incorrect link data. - `, - Description: "Truncated link data - missing key validation block length", - Name: "dynamic/attacks/private/022_truncated_link_data_key_validation_block_length", - WhenAdded: "2023-01-24", - UpdateDataset: genLink(gp{ - unencryptedDataBuffCorruption: func(b []byte) []byte { return b[:1] }, - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{}), - ValidPublicly: true, - GoErrorContains: "data truncated while reading key validation block", - }) - - writeLinkData(TestCase{ - Details: ` - The link data can be properly encrypted and signed passing - public verification. However the internal dataset may be - corrupted. A truncated dataset should also be detected. - - This test checks if truncated key validation block (and following data) - will be detected as incorrect link data. - `, - Description: "Truncated link data - incomplete key validation block", - Name: "dynamic/attacks/private/023_truncated_link_data_incomplete_key_validation_block", - WhenAdded: "2023-01-24", - UpdateDataset: genLink(gp{ - unencryptedDataBuffCorruption: func(b []byte) []byte { return b[:4] }, - }), - BlobName: blobName(gp{}), - EncryptionKey: key(gp{}), - ValidPublicly: true, - GoErrorContains: "data truncated while reading key validation block", - }) -} diff --git a/testvectors/internal/testcase.go b/testvectors/internal/testcase.go deleted file mode 100644 index 34062ba..0000000 --- a/testvectors/internal/testcase.go +++ /dev/null @@ -1,32 +0,0 @@ -/* -Copyright © 2025 Bartłomiej Święcki (byo) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package internal - -type TestCase struct { - Name string `json:"name"` - Description string `json:"description"` - Details string `json:"-"` - DetailsLines []string `json:"details,omitempty"` - WhenAdded string `json:"added_at"` - BlobName []byte `json:"blob_name"` - EncryptionKey []byte `json:"encryption_key"` - UpdateDataset []byte `json:"update_dataset"` - DecryptedDataset []byte `json:"decrypted_dataset"` - ValidPublicly bool `json:"valid_publicly"` - ValidPrivately bool `json:"valid_privately"` - GoErrorContains string `json:"go_error_contains,omitempty"` -} From 9a6811daccc61dc441f67cc2dac8f4b558cb07a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20=C5=9Awi=C4=99cki?= Date: Fri, 31 Oct 2025 23:38:25 +0100 Subject: [PATCH 09/10] Update github actions --- .github/workflows/golangci-lint.yml | 26 --------- .github/workflows/pr-checks.yml | 88 +++++++++++++++++++++++++++++ .github/workflows/tests.yml | 55 ------------------ .vscode/settings.json | 1 + 4 files changed, 89 insertions(+), 81 deletions(-) delete mode 100644 .github/workflows/golangci-lint.yml create mode 100644 .github/workflows/pr-checks.yml delete mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml deleted file mode 100644 index 90e81eb..0000000 --- a/.github/workflows/golangci-lint.yml +++ /dev/null @@ -1,26 +0,0 @@ -permissions: - contents: read -name: golangci-lint - -on: - push: - branches: - - master - pull_request: - branches: - - master - -jobs: - golangci: - name: lint - runs-on: ubuntu-latest - steps: - - uses: actions/setup-go@v5 - with: - go-version: '1.25' - cache: false - - name: Checkout code - uses: actions/checkout@v4 - - name: golangci-lint - run: | - go tool golangci-lint run --timeout 5m diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml new file mode 100644 index 0000000..cc30bc5 --- /dev/null +++ b/.github/workflows/pr-checks.yml @@ -0,0 +1,88 @@ +name: Pull Request checks +on: + push: + branches: + - main + - master + pull_request: + +permissions: + contents: read + +jobs: + lint: + name: Verify linting + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-go@v6 + with: + go-version: stable + - name: Run go vet + run: go vet ./... + - name: golangci-lint + uses: golangci/golangci-lint-action@v8 + with: + version: v2.5 + + mod-tidy: + name: Check if go mod tidy is needed + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-go@v6 + with: + go-version: stable + - run: | + go mod tidy + if [ -n "$(git status --porcelain)" ]; then + echo "go mod tidy failed" + git diff + exit 1 + fi + + go-generate: + name: Check if generated files are up to date + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-go@v6 + with: + go-version: stable + - run: | + go generate ./... + if [ -n "$(git status --porcelain)" ]; then + echo "Generated files are out of date. Please run 'go generate ./...' and commit the changes." + git diff + exit 1 + fi + + test: + name: Run Go Tests + strategy: + matrix: + env: + - os: ubuntu-latest + coverage: true + - os: macos-latest + - os: windows-latest + continue-on-error: true + runs-on: ${{ matrix.env.os }} + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-go@v6 + with: + go-version: stable + - run: | + go test -v \ + ${{ matrix.env.coverage && '-coverprofile=profile.cov -coverpkg=./...' }} \ + ./... + continue-on-error: ${{ matrix.env['continue-on-error'] }} + - uses: shogo82148/actions-goveralls@v1 + if: ${{ matrix.env.coverage }} + with: + path-to-profile: profile.cov + - uses: codecov/codecov-action@v5 + if: ${{ matrix.env.coverage }} + with: + files: profile.cov diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index 8dfb0bb..0000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: Tests -on: [push] -jobs: - tests: - permissions: - contents: read - strategy: - matrix: - env: - - os: ubuntu-latest - coverage: true - generate-check: true - - os: macos-latest - - os: windows-latest - continue-on-error: true - runs-on: ${{ matrix.env.os }} - steps: - - uses: actions/setup-go@v5 - with: - go-version: "1.25" - - uses: actions/checkout@v4 - - - name: Check if generated files are up to date - if: matrix.env.generate-check - run: | - # Run go generate - go generate ./... - - # Check if any files were modified - if [ -n "$(git status --porcelain)" ]; then - echo "Generated files are out of date. Please run 'go generate ./...' and commit the changes." - git diff - exit 1 - fi - - - run: go vet ./... - - run: | - PREFIX="github.com/${GITHUB_REPOSITORY}" - - go test -v \ - ${{ matrix.env.coverage && '-coverprofile=profile.cov' }} \ - $( - go list ./... \ - | grep -v "${PREFIX}/testvectors" \ - | grep -v "${PREFIX}/pkg/datastore/testutils/generate" \ - ) - continue-on-error: ${{ matrix.env['continue-on-error'] }} - - uses: shogo82148/actions-goveralls@v1 - if: ${{ matrix.env.coverage }} - with: - path-to-profile: profile.cov - - uses: codecov/codecov-action@v5 - if: ${{ matrix.env.coverage }} - with: - files: profile.cov diff --git a/.vscode/settings.json b/.vscode/settings.json index 19c07ad..a86ff38 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -18,6 +18,7 @@ "elink", "fifos", "fsys", + "golangci", "goveralls", "Hasher", "homefile", From dccb050bb5ec4486c6fbb44652352cb297ac8fb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20=C5=9Awi=C4=99cki?= Date: Fri, 31 Oct 2025 23:38:26 +0100 Subject: [PATCH 10/10] Enable depguard linter --- .golangci.yml | 228 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 152 insertions(+), 76 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index fa23c88..a0303ae 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,88 +1,17 @@ -run: - timeout: 5m - -issues: - exclude-dirs: - - testvectors - - scripts - exclude-rules: - - path: _test\.go - linters: - - gocyclo - - errcheck - - dupl - - gosec - - path: _test\.go - text: "unused-parameter" - linters: - - revive - - text: 'shadow: declaration of "(err|ctx)" shadows declaration at' - linters: [ govet ] - -linters-settings: - govet: - enable-all: true - revive: - rules: - - name: blank-imports - - name: context-as-argument - - name: context-keys-type - - name: dot-imports - - name: error-return - - name: error-strings - - name: error-naming - - name: exported - - name: if-return - - name: increment-decrement - - name: var-naming - - name: var-declaration - - name: package-comments - - name: range - - name: receiver-naming - - name: time-naming - - name: unexported-return - - name: indent-error-flow - - name: errorf - staticcheck: - checks: ["all"] - stylecheck: - checks: ["all"] - misspell: - locale: US - gocyclo: - min-complexity: 15 - dupl: - threshold: 100 - goconst: - min-len: 3 - min-occurrences: 3 - gocritic: - enabled-tags: - - diagnostic - - experimental - - opinionated - - performance - - style - disabled-checks: - - hugeParam - - rangeValCopy - +version: "2" linters: - disable-all: true + default: none enable: - bodyclose - # - depguard + - depguard - dogsled - dupl - errcheck - goconst - gocritic - gocyclo - - gofmt - - goimports - goprintffuncname - gosec - - gosimple - govet - ineffassign - lll @@ -91,9 +20,156 @@ linters: - prealloc - revive - staticcheck - - stylecheck - - typecheck - unconvert - unparam - unused - whitespace + settings: + depguard: + rules: + conformance-test: + files: + - '**/pkg/datastoreconformancetest/**' + allow: + - github.com/cinode/go-datastore/pkg/ + - github.com/cinode/go-common/blob$ + - github.com/cinode/go-common/picotestify/ + - github.com/cinode/go-testvectors/testvectors$ + - bytes$ + - context$ + - crypto/ed25519$ + - crypto/sha256$ + - errors$ + - fmt$ + - io$ + - sync$ + - testing$ + main: + files: + - $all + - '!$test' + - '!**/pkg/datastore/testutils/generate/main.go' + - '!**/pkg/datastoreconformancetest/**' + allow: + - github.com/cinode/go-datastore/pkg + - github.com/cinode/go-common/blob$ + - github.com/cinode/go-common/cutl$ + - golang.org/x/crypto/chacha20$ + - bytes$ + - context$ + - crypto/cipher$ + - crypto/ed25519$ + - crypto/sha256$ + - crypto/subtle$ + - encoding/binary$ + - encoding/hex$ + - errors$ + - fmt$ + - hash$ + - io$ + - string$ + - sync$ + tests: + files: + - $test + allow: + - github.com/cinode/go-datastore/pkg + - github.com/cinode/go-common/blob$ + - github.com/cinode/go-common/picotestify/ + - github.com/cinode/go-testvectors/testvectors$ + - golang.org/x/crypto/chacha20$ + - bytes$ + - context$ + - crypto/ed25519$ + - crypto/rand$ + - crypto/sha256$ + - errors$ + - fmt$ + - io$ + - math/rand$ + - sort$ + - testing$ + dupl: + threshold: 100 + goconst: + min-len: 3 + min-occurrences: 3 + gocritic: + disabled-checks: + - hugeParam + - rangeValCopy + enabled-tags: + - diagnostic + - experimental + - opinionated + - performance + - style + gocyclo: + min-complexity: 15 + govet: + enable-all: true + misspell: + locale: US + revive: + rules: + - name: blank-imports + - name: context-as-argument + - name: context-keys-type + - name: dot-imports + - name: error-return + - name: error-strings + - name: error-naming + - name: exported + - name: if-return + - name: increment-decrement + - name: var-naming + - name: var-declaration + - name: package-comments + - name: range + - name: receiver-naming + - name: time-naming + - name: unexported-return + - name: indent-error-flow + - name: errorf + staticcheck: + checks: + - all + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + rules: + - linters: + - dupl + - errcheck + - gocyclo + - gosec + path: _test\.go + - linters: + - revive + path: _test\.go + text: unused-parameter + - linters: + - govet + text: 'shadow: declaration of "(err|ctx)" shadows declaration at' + paths: + - testvectors + - scripts + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gofmt + - goimports + exclusions: + generated: lax + paths: + - testvectors + - scripts + - third_party$ + - builtin$ + - examples$