From 5cf9d306313c1ffbb8cce0a524987d5ebf61e353 Mon Sep 17 00:00:00 2001 From: iHsin Date: Tue, 9 Jun 2026 13:05:49 +0800 Subject: [PATCH 1/4] Strip Go bindings, keep lib-only cronet build pipeline Remove all Go binding code (root *.go, internal/cronet, test/) and the binding-related build subcommands (sync, extract-lib, publish, generate-net-errors). Keep only the C library build pipeline: - cmd/build-naive: build / package / download-toolchain / env - package now just copies .a/.so/.dll + headers and dumps link_flags.txt (no more cgo/purego/submodule glue generation) - go.mod trimmed to spf13/cobra only - Makefile/README updated for lib-only layout - CI: drop Go test/integration/publish steps; trigger on lib-only; release-libs publishes libs on lib-only push too Co-Authored-By: Claude Opus 4.8 --- .github/workflows/naive-build.yml | 315 +---- .golangci.yml | 48 - Makefile | 24 +- README.md | 149 +-- bidirectional_conn.go | 450 ------- bidirectional_stream_cgo.go | 157 --- bidirectional_stream_impl_cgo.go | 141 --- bidirectional_stream_impl_purego.go | 144 --- bidirectional_stream_map.go | 36 - bidirectional_stream_purego.go | 108 -- buffer_callback_cgo.go | 23 - buffer_callback_impl.go | 66 -- buffer_callback_impl_purego.go | 64 - buffer_callback_purego.go | 22 - buffer_cgo.go | 58 - buffer_impl_cgo.go | 89 -- buffer_purego.go | 59 - callback_types.go | 38 - cmd/build-naive/cmd_extract_lib.go | 120 -- cmd/build-naive/cmd_generate_net_errors.go | 214 ---- cmd/build-naive/cmd_package.go | 321 ++--- cmd/build-naive/cmd_publish.go | 292 ----- cmd/build-naive/cmd_sync.go | 116 -- date_time_cgo.go | 31 - date_time_purego.go | 26 - date_time_test.go | 18 - dialer_test.go | 228 ---- engine_cgo.go | 248 ---- engine_impl_cgo.go | 136 --- engine_params_cgo.go | 190 --- engine_params_experimental_options.go | 108 -- engine_params_purego.go | 143 --- engine_purego.go | 235 ---- engine_version_test.go | 24 - error_cgo.go | 77 -- error_codes.go | 46 - error_go.go | 31 - error_purego.go | 55 - executor_cgo.go | 22 - executor_impl_cgo.go | 66 -- executor_impl_purego.go | 74 -- executor_purego.go | 21 - go.mod | 12 +- http_header_cgo.go | 39 - http_header_purego.go | 31 - include_cgo.go | 6 - internal/cronet/api_purego.go | 1033 ----------------- internal/cronet/api_purego_float.go | 13 - internal/cronet/api_purego_float_stub.go | 15 - internal/cronet/api_purego_nonwindows.go | 8 - internal/cronet/api_purego_windows.go | 14 - internal/cronet/loader_unix.go | 933 --------------- internal/cronet/loader_windows.go | 911 --------------- internal/cronet/loader_windows_float.go | 15 - internal/cronet/symbols_purego.go | 334 ------ loader_purego.go | 9 - metrics_cgo.go | 210 ---- metrics_purego.go | 143 --- naive_client.go | 558 --------- naive_conn.go | 249 ---- naive_dns.go | 486 -------- naive_dns_unix.go | 122 -- naive_dns_unix_test.go | 204 ---- naive_dns_windows.go | 193 --- naive_dns_windows_go125.go | 26 - naive_dns_windows_legacy.go | 9 - naive_dns_windows_test.go | 173 --- naive_library_cgo.go | 7 - naive_library_purego.go | 9 - naive_socket_unix.go | 54 - naive_socket_windows.go | 89 -- naive_socket_windows_go125.go | 156 --- naive_socket_windows_legacy.go | 9 - net_error.go | 145 --- net_error_generated.go | 504 -------- public_key_pins_cgo.go | 74 -- public_key_pins_purego.go | 51 - quic_hint_cgo.go | 49 - quic_hint_purego.go | 39 - result.go | 102 -- runnable_cgo.go | 22 - runnable_impl_cgo.go | 68 -- runnable_impl_purego.go | 65 -- runnable_purego.go | 21 - socket_fd_unix_test.go | 136 --- socket_fd_windows_test.go | 166 --- test/.gitignore | 1 - test/artifact_test.go | 97 -- test/config/sing-box-quic.json | 21 - test/config/sing-box.json | 20 - test/ech_test.go | 250 ---- test/experimental_options_test.go | 363 ------ test/go.mod | 29 - test/go.sum | 45 - test/integration_test.go | 1028 ---------------- test/main_test.go | 649 ----------- test/quic_test.go | 516 -------- transport.go | 313 ----- types.go | 309 ----- upload_data_provider_cgo.go | 18 - upload_data_provider_handler.go | 46 - upload_data_provider_impl_cgo.go | 110 -- upload_data_provider_impl_purego.go | 117 -- upload_data_provider_purego.go | 17 - upload_data_sink_cgo.go | 54 - upload_data_sink_impl_cgo.go | 87 -- upload_data_sink_purego.go | 37 - url_request_callback_cgo.go | 18 - url_request_callback_impl_cgo.go | 139 --- url_request_callback_impl_purego.go | 172 --- url_request_callback_impl_purego_32bit.go | 174 --- url_request_callback_purego.go | 17 - url_request_cgo.go | 110 -- url_request_finished_info_cgo.go | 63 - url_request_finished_info_impl_cgo.go | 67 -- url_request_finished_info_listener_cgo.go | 18 - ...uest_finished_info_listener_impl_purego.go | 69 -- url_request_finished_info_listener_purego.go | 22 - url_request_finished_info_purego.go | 33 - url_request_impl_cgo.go | 125 -- url_request_params_cgo.go | 194 ---- url_request_params_purego.go | 121 -- url_request_purego.go | 53 - url_request_status_listener_cgo.go | 18 - url_request_status_listener_impl_cgo.go | 55 - url_request_status_listener_impl_purego.go | 51 - url_request_status_listener_purego.go | 22 - url_response_info_cgo.go | 138 --- url_response_info_purego.go | 59 - 129 files changed, 151 insertions(+), 18059 deletions(-) delete mode 100644 .golangci.yml delete mode 100644 bidirectional_conn.go delete mode 100644 bidirectional_stream_cgo.go delete mode 100644 bidirectional_stream_impl_cgo.go delete mode 100644 bidirectional_stream_impl_purego.go delete mode 100644 bidirectional_stream_map.go delete mode 100644 bidirectional_stream_purego.go delete mode 100644 buffer_callback_cgo.go delete mode 100644 buffer_callback_impl.go delete mode 100644 buffer_callback_impl_purego.go delete mode 100644 buffer_callback_purego.go delete mode 100644 buffer_cgo.go delete mode 100644 buffer_impl_cgo.go delete mode 100644 buffer_purego.go delete mode 100644 callback_types.go delete mode 100644 cmd/build-naive/cmd_extract_lib.go delete mode 100644 cmd/build-naive/cmd_generate_net_errors.go delete mode 100644 cmd/build-naive/cmd_publish.go delete mode 100644 cmd/build-naive/cmd_sync.go delete mode 100644 date_time_cgo.go delete mode 100644 date_time_purego.go delete mode 100644 date_time_test.go delete mode 100644 dialer_test.go delete mode 100644 engine_cgo.go delete mode 100644 engine_impl_cgo.go delete mode 100644 engine_params_cgo.go delete mode 100644 engine_params_experimental_options.go delete mode 100644 engine_params_purego.go delete mode 100644 engine_purego.go delete mode 100644 engine_version_test.go delete mode 100644 error_cgo.go delete mode 100644 error_codes.go delete mode 100644 error_go.go delete mode 100644 error_purego.go delete mode 100644 executor_cgo.go delete mode 100644 executor_impl_cgo.go delete mode 100644 executor_impl_purego.go delete mode 100644 executor_purego.go delete mode 100644 http_header_cgo.go delete mode 100644 http_header_purego.go delete mode 100644 include_cgo.go delete mode 100644 internal/cronet/api_purego.go delete mode 100644 internal/cronet/api_purego_float.go delete mode 100644 internal/cronet/api_purego_float_stub.go delete mode 100644 internal/cronet/api_purego_nonwindows.go delete mode 100644 internal/cronet/api_purego_windows.go delete mode 100644 internal/cronet/loader_unix.go delete mode 100644 internal/cronet/loader_windows.go delete mode 100644 internal/cronet/loader_windows_float.go delete mode 100644 internal/cronet/symbols_purego.go delete mode 100644 loader_purego.go delete mode 100644 metrics_cgo.go delete mode 100644 metrics_purego.go delete mode 100644 naive_client.go delete mode 100644 naive_conn.go delete mode 100644 naive_dns.go delete mode 100644 naive_dns_unix.go delete mode 100644 naive_dns_unix_test.go delete mode 100644 naive_dns_windows.go delete mode 100644 naive_dns_windows_go125.go delete mode 100644 naive_dns_windows_legacy.go delete mode 100644 naive_dns_windows_test.go delete mode 100644 naive_library_cgo.go delete mode 100644 naive_library_purego.go delete mode 100644 naive_socket_unix.go delete mode 100644 naive_socket_windows.go delete mode 100644 naive_socket_windows_go125.go delete mode 100644 naive_socket_windows_legacy.go delete mode 100644 net_error.go delete mode 100644 net_error_generated.go delete mode 100644 public_key_pins_cgo.go delete mode 100644 public_key_pins_purego.go delete mode 100644 quic_hint_cgo.go delete mode 100644 quic_hint_purego.go delete mode 100644 result.go delete mode 100644 runnable_cgo.go delete mode 100644 runnable_impl_cgo.go delete mode 100644 runnable_impl_purego.go delete mode 100644 runnable_purego.go delete mode 100644 socket_fd_unix_test.go delete mode 100644 socket_fd_windows_test.go delete mode 100644 test/.gitignore delete mode 100644 test/artifact_test.go delete mode 100644 test/config/sing-box-quic.json delete mode 100644 test/config/sing-box.json delete mode 100644 test/ech_test.go delete mode 100644 test/experimental_options_test.go delete mode 100644 test/go.mod delete mode 100644 test/go.sum delete mode 100644 test/integration_test.go delete mode 100644 test/main_test.go delete mode 100644 test/quic_test.go delete mode 100644 transport.go delete mode 100644 types.go delete mode 100644 upload_data_provider_cgo.go delete mode 100644 upload_data_provider_handler.go delete mode 100644 upload_data_provider_impl_cgo.go delete mode 100644 upload_data_provider_impl_purego.go delete mode 100644 upload_data_provider_purego.go delete mode 100644 upload_data_sink_cgo.go delete mode 100644 upload_data_sink_impl_cgo.go delete mode 100644 upload_data_sink_purego.go delete mode 100644 url_request_callback_cgo.go delete mode 100644 url_request_callback_impl_cgo.go delete mode 100644 url_request_callback_impl_purego.go delete mode 100644 url_request_callback_impl_purego_32bit.go delete mode 100644 url_request_callback_purego.go delete mode 100644 url_request_cgo.go delete mode 100644 url_request_finished_info_cgo.go delete mode 100644 url_request_finished_info_impl_cgo.go delete mode 100644 url_request_finished_info_listener_cgo.go delete mode 100644 url_request_finished_info_listener_impl_purego.go delete mode 100644 url_request_finished_info_listener_purego.go delete mode 100644 url_request_finished_info_purego.go delete mode 100644 url_request_impl_cgo.go delete mode 100644 url_request_params_cgo.go delete mode 100644 url_request_params_purego.go delete mode 100644 url_request_purego.go delete mode 100644 url_request_status_listener_cgo.go delete mode 100644 url_request_status_listener_impl_cgo.go delete mode 100644 url_request_status_listener_impl_purego.go delete mode 100644 url_request_status_listener_purego.go delete mode 100644 url_response_info_cgo.go delete mode 100644 url_response_info_purego.go diff --git a/.github/workflows/naive-build.yml b/.github/workflows/naive-build.yml index cfd1828b..19c94a67 100644 --- a/.github/workflows/naive-build.yml +++ b/.github/workflows/naive-build.yml @@ -8,9 +8,9 @@ on: type: string required: false push: - branches: [mirror] + branches: [mirror, lib-only] pull_request: - branches: [mirror] + branches: [mirror, lib-only] concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} @@ -68,62 +68,13 @@ jobs: if: steps.build-cache.outputs.cache-hit != 'true' run: ccache -s - name: Package - run: | - go run ./cmd/build-naive --target=linux/${{ matrix.arch }} package --local - go run ./cmd/build-naive --target=linux/${{ matrix.arch }} package - - name: Cache toolchain - uses: actions/cache@v4 - with: - path: | - naiveproxy/src/third_party/llvm-build - naiveproxy/src/gn/out - naiveproxy/src/chrome/build/pgo_profiles - naiveproxy/src/out/sysroot-build - key: toolchain-linux-${{ matrix.arch }}-${{ steps.chromium.outputs.version }} - restore-keys: toolchain-linux-${{ matrix.arch }}- - - name: Download clang and sysroot - run: go run ./cmd/build-naive --target=linux/${{ matrix.arch }} download-toolchain - - name: Install QEMU for cross-platform testing - run: | - sudo apt update - sudo apt install -y qemu-user-binfmt - - name: Build test binary - run: | - eval $(go run ./cmd/build-naive --target=linux/${{ matrix.arch }} env --export) - GO_TEST_LDFLAGS= - if [ "${{ matrix.arch }}" = "mipsle" ] || [ "${{ matrix.arch }}" = "mips64le" ]; then - GO_TEST_LDFLAGS=-ldflags=-w - fi - CGO_ENABLED=1 GOOS=linux GOARCH=${{ matrix.arch }} go test -c $GO_TEST_LDFLAGS -o cronet.test . - - name: Run test binary - run: | - eval $(go run ./cmd/build-naive --target=linux/${{ matrix.arch }} env --export) - if [ "${{ matrix.arch }}" != "amd64" ] && [ "${{ matrix.arch }}" != "386" ]; then - export QEMU_LD_PREFIX - fi - ./cronet.test -test.v -test.run=TestEngineVersion - - name: Build purego test binary - if: matrix.arch == 'amd64' || matrix.arch == 'arm64' || matrix.arch == 'loong64' - run: | - cp lib/linux_${{ matrix.arch }}/libcronet.so . - CGO_ENABLED=0 GOOS=linux GOARCH=${{ matrix.arch }} go test -tags with_purego -c -o cronet.purego.test . - - name: Run purego test binary - if: matrix.arch == 'amd64' || matrix.arch == 'arm64' || matrix.arch == 'loong64' - run: | - if [ "${{ matrix.arch }}" != "amd64" ]; then - eval $(go run ./cmd/build-naive --target=linux/${{ matrix.arch }} env --export) - export QEMU_LD_PREFIX - fi - export LD_LIBRARY_PATH=$PWD - ./cronet.purego.test -test.v -test.run=TestEngineVersion + run: go run ./cmd/build-naive --target=linux/${{ matrix.arch }} package - uses: actions/upload-artifact@v4 with: name: cronet-linux-${{ matrix.arch }} path: | lib/ include/ - include_cgo.go - lib_*_cgo.go darwin: runs-on: macos-15 @@ -188,22 +139,13 @@ jobs: if: steps.build-cache.outputs.cache-hit != 'true' run: ccache -s - name: Package - run: | - go run ./cmd/build-naive --target=${{ matrix.target }} package --local - go run ./cmd/build-naive --target=${{ matrix.target }} package - - name: Build and run test binary - if: matrix.target == 'darwin/arm64' - run: | - CGO_ENABLED=1 go test -c -o cronet.test . - ./cronet.test -test.v -test.run=TestEngineVersion + run: go run ./cmd/build-naive --target=${{ matrix.target }} package - uses: actions/upload-artifact@v4 with: name: cronet-${{ matrix.name }} path: | lib/ include/ - include_cgo.go - lib_*_cgo.go windows: runs-on: windows-2022 @@ -279,39 +221,19 @@ jobs: run: sccache -s - name: Package run: go run ./cmd/build-naive --target=windows/${{ matrix.arch }} package - - name: Build and run test binary - if: matrix.arch != 'arm64' - shell: bash - run: | - # Copy DLL to current directory for fallback loading - cp lib/windows_${{ matrix.arch }}/libcronet.dll . - CGO_ENABLED=0 \ - GOOS=windows \ - GOARCH=${{ matrix.arch }} \ - go test -tags with_purego -c -o cronet.test.exe . - ./cronet.test.exe -test.v -test.run=TestEngineVersion - uses: actions/upload-artifact@v4 with: name: cronet-windows-${{ matrix.arch }} path: | lib/ include/ - include_cgo.go android: runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: - include: - - arch: arm64 - cc: aarch64-linux-android23-clang - - arch: amd64 - cc: x86_64-linux-android23-clang - - arch: arm - cc: armv7a-linux-androideabi23-clang - - arch: "386" - cc: i686-linux-android23-clang + arch: [arm64, amd64, arm, "386"] steps: - uses: actions/checkout@v4 with: @@ -353,26 +275,13 @@ jobs: if: steps.build-cache.outputs.cache-hit != 'true' run: ccache -s - name: Package - run: | - go run ./cmd/build-naive --target=android/${{ matrix.arch }} package --local - go run ./cmd/build-naive --target=android/${{ matrix.arch }} package - - name: Setup Android NDK - uses: nttld/setup-ndk@v1 - id: setup-ndk - with: - ndk-version: r28 - - name: Build test binary - run: | - CC=${{ steps.setup-ndk.outputs.ndk-path }}/toolchains/llvm/prebuilt/linux-x86_64/bin/${{ matrix.cc }} \ - CGO_ENABLED=1 GOOS=android GOARCH=${{ matrix.arch }} go test -c -o cronet.test . + run: go run ./cmd/build-naive --target=android/${{ matrix.arch }} package - uses: actions/upload-artifact@v4 with: name: cronet-android-${{ matrix.arch }} path: | lib/ include/ - include_cgo.go - lib_*_cgo.go linux-musl: runs-on: ubuntu-24.04 @@ -421,224 +330,16 @@ jobs: if: steps.build-cache.outputs.cache-hit != 'true' run: ccache -s - name: Package - run: | - go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl package --local - go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl package - - name: Cache toolchain - uses: actions/cache@v4 - with: - path: | - naiveproxy/src/third_party/llvm-build - naiveproxy/src/gn/out - naiveproxy/src/chrome/build/pgo_profiles - naiveproxy/src/out/sysroot-build - key: toolchain-linux-musl-${{ matrix.arch }}-${{ steps.chromium.outputs.version }} - restore-keys: toolchain-linux-musl-${{ matrix.arch }}- - - name: Download toolchain - run: go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl download-toolchain - - name: Install QEMU for cross-platform testing - run: | - sudo apt update - sudo apt install -y qemu-user-binfmt - - name: Build test binary - run: | - eval $(go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl env --export) - [[ "${{ matrix.arch }}" == "mipsle" ]] && export GOMIPS=softfloat || true - GO_TEST_LDFLAGS= - [[ "${{ matrix.arch }}" == "mipsle" ]] && GO_TEST_LDFLAGS=-ldflags=-w || true - CGO_ENABLED=1 GOOS=linux GOARCH=${{ matrix.arch }} go test -c $GO_TEST_LDFLAGS -tags with_musl -o cronet.test . - - name: Run test binary - continue-on-error: true # musl/QEMU test is flaky, don't block CI - run: | - # Static binary - can run directly with QEMU binfmt or explicit QEMU for cross-arch - case "${{ matrix.arch }}" in - amd64) QEMU=qemu-x86_64 ;; - arm64) QEMU=qemu-aarch64 ;; - 386) QEMU=qemu-i386 ;; - arm) QEMU=qemu-arm ;; - loong64) QEMU=qemu-loongarch64 ;; - esac - timeout 60 $QEMU ./cronet.test -test.v -test.run=TestEngineVersion -test.timeout=30s + run: go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl package - uses: actions/upload-artifact@v4 with: name: cronet-linux-musl-${{ matrix.arch }} path: | lib/ include/ - include_cgo.go - lib_*_cgo.go - - integration-test: - if: github.event_name != 'workflow_dispatch' - needs: [linux, darwin, windows] - strategy: - fail-fast: false - matrix: - include: - # Linux - - name: linux-amd64-cgo - os: ubuntu-22.04 - artifact: cronet-linux-amd64 - target: linux/amd64 - mode: cgo - - name: linux-amd64-purego - os: ubuntu-22.04 - artifact: cronet-linux-amd64 - target: linux/amd64 - mode: purego - - name: linux-arm64-purego - os: ubuntu-22.04-arm - artifact: cronet-linux-arm64 - target: linux/arm64 - mode: purego - # macOS ARM64 - - name: darwin-arm64-macos15 - os: macos-15 - artifact: cronet-darwin-arm64 - target: darwin/arm64 - mode: cgo - - name: darwin-arm64-macos26 - os: macos-26 - artifact: cronet-darwin-arm64 - target: darwin/arm64 - mode: cgo - # macOS x64 (Intel) - - name: darwin-amd64-macos15 - os: macos-15-intel - artifact: cronet-darwin-amd64 - target: darwin/amd64 - mode: cgo - # Windows x64 - - name: windows-amd64-2022 - os: windows-2022 - artifact: cronet-windows-amd64 - target: windows/amd64 - mode: purego - - name: windows-amd64-2025 - os: windows-2025 - artifact: cronet-windows-amd64 - target: windows/amd64 - mode: purego - # Windows ARM64 - - name: windows-arm64 - os: windows-11-arm - artifact: cronet-windows-arm64 - target: windows/arm64 - mode: purego - runs-on: ${{ matrix.os }} - timeout-minutes: 40 - env: - CRONET_TEST_ARTIFACT_DIR: ${{ github.workspace }}/test-artifacts/${{ matrix.name }} - CRONET_TEST_CAPTURE_NETLOG: "1" - steps: - - uses: actions/checkout@v4 - with: - submodules: 'recursive' - - - uses: actions/setup-go@v5 - with: - go-version: ^1.24 - - - name: Download artifact - uses: actions/download-artifact@v4 - with: - name: ${{ matrix.artifact }} - path: . - - - name: Get Chromium version - id: chromium - shell: bash - run: echo "version=$(cat naiveproxy/CHROMIUM_VERSION)" >> $GITHUB_OUTPUT - - # Linux: restore toolchain cache (needed for env command) - - name: Restore toolchain cache (Linux) - if: runner.os == 'Linux' - uses: actions/cache/restore@v4 - with: - path: | - naiveproxy/src/third_party/llvm-build - naiveproxy/src/gn/out - naiveproxy/src/chrome/build/pgo_profiles - naiveproxy/src/out/sysroot-build - key: toolchain-linux-${{ contains(matrix.target, 'arm64') && 'arm64' || 'amd64' }}-${{ steps.chromium.outputs.version }} - restore-keys: toolchain-linux-${{ contains(matrix.target, 'arm64') && 'arm64' || 'amd64' }}- - - - name: Download toolchain (Linux) - if: runner.os == 'Linux' - run: go run ./cmd/build-naive --target=${{ matrix.target }} download-toolchain - - # Install iperf3 - - name: Install iperf3 (Linux) - if: runner.os == 'Linux' - run: | - sudo apt update - sudo apt install -y iperf3 - - - name: Install iperf3 (macOS) - if: runner.os == 'macOS' - run: brew install iperf3 - - - name: Install iperf3 (Windows) - if: runner.os == 'Windows' - run: choco install iperf3 - - # CGO mode tests - - name: Run integration tests (CGO) - if: matrix.mode == 'cgo' - timeout-minutes: 20 - run: | - mkdir -p "$CRONET_TEST_ARTIFACT_DIR" - eval $(go run ./cmd/build-naive --target=${{ matrix.target }} env --export) - cd test - LOG_PATH="$CRONET_TEST_ARTIFACT_DIR/go-test.log" - if ! CGO_ENABLED=1 go test -v -timeout=12m >"$LOG_PATH" 2>&1; then - tail -n 400 "$LOG_PATH" || true - exit 1 - fi - - # purego mode tests - - name: Run integration tests (purego - Linux) - if: matrix.mode == 'purego' && runner.os == 'Linux' - timeout-minutes: 20 - working-directory: test - run: | - mkdir -p "$CRONET_TEST_ARTIFACT_DIR" - export LD_LIBRARY_PATH=$PWD/../lib/linux_${{ contains(matrix.target, 'arm64') && 'arm64' || 'amd64' }} - LOG_PATH="$CRONET_TEST_ARTIFACT_DIR/go-test.log" - if ! CGO_ENABLED=0 go test -tags with_purego -v -timeout=12m >"$LOG_PATH" 2>&1; then - tail -n 400 "$LOG_PATH" || true - exit 1 - fi - - - name: Run integration tests (purego - Windows) - if: matrix.mode == 'purego' && runner.os == 'Windows' - timeout-minutes: 20 - working-directory: test - shell: bash - run: | - # Determine library directory based on target architecture - case "${{ matrix.target }}" in - */arm64) LIB_DIR="windows_arm64" ;; - */amd64) LIB_DIR="windows_amd64" ;; - esac - export PATH="$PWD/../lib/${LIB_DIR}:$PATH" - mkdir -p "$CRONET_TEST_ARTIFACT_DIR" - LOG_PATH="$CRONET_TEST_ARTIFACT_DIR/go-test.log" - if ! go test -tags with_purego -v -timeout=12m >"$LOG_PATH" 2>&1; then - tail -n 400 "$LOG_PATH" || true - exit 1 - fi - - - name: Upload integration logs (test failure) - if: always() && (failure() || cancelled()) - uses: actions/upload-artifact@v4 - with: - name: integration-logs-${{ matrix.name }} - path: test-artifacts/${{ matrix.name }}/ - if-no-files-found: warn release-libs: - if: github.event_name == 'push' && github.ref_name == 'mirror' + if: github.event_name == 'push' && (github.ref_name == 'mirror' || github.ref_name == 'lib-only') needs: [linux, linux-musl, darwin, windows, android] runs-on: ubuntu-22.04 steps: diff --git a/.golangci.yml b/.golangci.yml deleted file mode 100644 index 252e89a7..00000000 --- a/.golangci.yml +++ /dev/null @@ -1,48 +0,0 @@ -version: "2" -run: - go: "1.24" -linters: - default: none - enable: - - govet - - ineffassign - - staticcheck - settings: - staticcheck: - checks: - - all - - -S1000 - - -S1008 - - -S1017 - - -ST1001 - - -ST1003 - - -QF1001 - - -QF1003 - - -QF1008 - exclusions: - generated: lax - presets: - - comments - - common-false-positives - - legacy - - std-error-handling - paths: - - naiveproxy$ -formatters: - enable: - - gci - - gofumpt - settings: - gci: - sections: - - standard - - prefix(github.com/sagernet/) - - default - custom-order: true - exclusions: - generated: lax - paths: - - naiveproxy$ - - third_party$ - - builtin$ - - examples$ diff --git a/Makefile b/Makefile index 08966bc0..b418075a 100644 --- a/Makefile +++ b/Makefile @@ -1,31 +1,11 @@ TARGET ?= TARGET_FLAG = $(if $(TARGET),--target=$(TARGET),) +# Build the cronet libraries (.a/.so/.dll) and collect them into lib/ + include/. build: go run -v ./cmd/build-naive build $(TARGET_FLAG) - go run -v ./cmd/build-naive package --local $(TARGET_FLAG) go run -v ./cmd/build-naive package $(TARGET_FLAG) +# Convenience target for all Apple platforms. apple: TARGET="ios/arm64,ios/arm64/simulator,ios/amd64/simulator,tvos/arm64,tvos/arm64/simulator,tvos/amd64/simulator,darwin/arm64,darwin/amd64" make - -generate_net_errors: - go run ./cmd/build-naive generate-net-errors - -test: build - cd test && go test -a -count=1 -v . - -fmt: - @find . -name '*.go' -not -path './naiveproxy/*' -exec gofumpt -l -w {} + - @find . -name '*.go' -not -path './naiveproxy/*' -exec gofmt -s -w {} + - @gci write --custom-order -s standard -s "prefix(github.com/sagernet/)" -s "default" $$(find . -name '*.go' -not -path './naiveproxy/*') - -fmt_install: - go install -v mvdan.cc/gofumpt@v0.8.0 - go install -v github.com/daixiang0/gci@latest - -lint: - golangci-lint run - -lint_install: - go install -v github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.4.0 diff --git a/README.md b/README.md index 1a0e1c73..6d18b3ea 100644 --- a/README.md +++ b/README.md @@ -1,106 +1,107 @@ -# cronet-go +# cronet-libs -[![Reference](https://pkg.go.dev/badge/github.com/sagernet/cronet-go.svg)](https://pkg.go.dev/github.com/sagernet/cronet-go) +Prebuilt **cronet** static / shared libraries from [naiveproxy](https://github.com/klzgrad/naiveproxy) +(Chromium's network stack: HTTP/2, HTTP/3 QUIC, ECH, …). -Go bindings for [naiveproxy](https://github.com/klzgrad/naiveproxy). +This repository contains **only the C library build pipeline** — there are no Go +bindings here. It produces: + +- `libcronet.a` — static library (Linux glibc/musl, macOS, iOS, tvOS, Android) +- `libcronet.so` — shared library (Linux glibc) +- `libcronet.dll`— shared library (Windows) + +together with the C headers (`include/`) and the system link flags required to +link the static library (`lib//link_flags.txt`). ## Supported Platforms -| Target | OS | CPU | -|---------------|---------|-------| -| android/386 | android | x86 | -| android/amd64 | android | x64 | -| android/arm | android | arm | -| android/arm64 | android | arm64 | -| darwin/amd64 | mac | x64 | -| darwin/arm64 | mac | arm64 | -| ios/arm64 | ios | arm64 | -| ios/amd64 | ios | amd64 | -| linux/386 | linux | x86 | -| linux/amd64 | linux | x64 | -| linux/arm | linux | arm | +| Target | OS | CPU | +|---------------|---------|---------| +| android/386 | android | x86 | +| android/amd64 | android | x64 | +| android/arm | android | arm | +| android/arm64 | android | arm64 | +| darwin/amd64 | mac | x64 | +| darwin/arm64 | mac | arm64 | +| ios/arm64 | ios | arm64 | +| ios/amd64 | ios | amd64 | +| linux/386 | linux | x86 | +| linux/amd64 | linux | x64 | +| linux/arm | linux | arm | | linux/arm64 | linux | arm64 | | linux/loong64 | linux | loong64 | | windows/amd64 | win | x64 | -| windows/arm64 | win | arm64 | +| windows/arm64 | win | arm64 | -## System Requirements +## How it works -| Platform | Minimum Version | -|---------------|-----------------| -| macOS | 12.0 (Monterey) | -| iOS/tvOS | 15.0 | -| Windows | 10 | -| Android | 5.0 (API 21) | -| Linux (glibc) | glibc 2.31 (loong64: 2.36) | -| Linux (musl) | any (loong64: 1.2.5) | +The build is orchestrated by the small Go CLI in [`cmd/build-naive`](cmd/build-naive) +(the only Go code in the repo — it just drives `get-clang.sh` → `gn gen` → +`ninja`). It depends only on `github.com/spf13/cobra`. -## Downstream Build Requirements +Subcommands: -| Platform | Requirements | Go Build Flags | -|--------------------------------------|---------------------------------|-----------------------------------| -| Linux (glibc) | Chromium toolchain | - | -| Linux (musl) | Chromium toolchain | `-tags with_musl` | -| macOS / iOS | macOS Xcode | - | -| iOS simulator/ tvOS / tvOS simulator | macOS Xcode + SagerNet/gomobile | - | -| Windows | - | `CGO_ENABLED=0 -tags with_purego` | -| Android | Android NDK | - | +| Command | Purpose | +|---------------------|---------------------------------------------------------------| +| `build` | `gn gen` + `ninja` → produce `libcronet.{a,so,dll}` | +| `package` | Copy libs to `lib/` and headers to `include/`, dump link flags | +| `download-toolchain`| Download clang + sysroot without building | +| `env` | Print `CC`/`CXX`/`CGO_LDFLAGS` for cross-compiling consumers | -## Linux Build instructions +## Build instructions ```bash -git clone --recursive --depth=1 https://github.com/sagernet/cronet-go.git -cd cronet-go -go run ./cmd/build-naive --target=linux/amd64 download-toolchain -#go run ./cmd/build-naive --target=linux/amd64 --libc=musl download-toolchain - -# Outputs CC, CXX, and CGO_LDFLAGS=-fuse-ld=lld -export $(go run ./cmd/build-naive --target=linux/amd64 env) -#export $(go run ./cmd/build-naive --target=linux/amd64 --libc=musl env) - -cd /path/to/your/project -go build -# go build -tags with_musl +git clone --recursive --depth=1 +cd cronet-libs + +# Linux (host target). Add --target=os/arch to cross-compile, +# or --libc=musl for static musl builds. +go run ./cmd/build-naive build +go run ./cmd/build-naive package +# or simply: make ``` -### Directories to cache +Outputs land in: -```yaml -- cronet-go/naiveproxy/src/third_party/llvm-build/ -- cronet-go/naiveproxy/src/gn/out/ -- cronet-go/naiveproxy/src/chrome/build/pgo_profiles/ -- cronet-go/naiveproxy/src/out/sysroot-build/ +``` +lib/_/libcronet.a # static lib +lib/_/libcronet.so # Linux glibc shared lib +lib/_/libcronet.dll # Windows shared lib +lib/_/link_flags.txt # system libs/frameworks needed to link .a +include/*.h # C headers ``` -## Windows / purego Build Instructions - -For Windows or pure Go builds (no CGO), you need to distribute the dynamic library alongside your binary. - -### Download Library - -Download `libcronet.dll` (Windows) or `libcronet.so` (Linux) from [GitHub Releases](https://github.com/sagernet/cronet-go/releases). +The raw ninja output also remains under +`naiveproxy/src/out/cronet--/` if you prefer to grab it directly. -### Build with purego +### Cross-compiling ```bash -# Windows (purego is required) -CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -tags with_purego -o myapp.exe +go run ./cmd/build-naive --target=linux/arm64 download-toolchain +go run ./cmd/build-naive --target=linux/arm64 build +go run ./cmd/build-naive --target=linux/arm64 package -# Linux with purego (optional, for dynamic linking) -CGO_ENABLED=0 go build -tags with_purego -o myapp +# musl (static): +go run ./cmd/build-naive --target=linux/amd64 --libc=musl build ``` -### Distribution +### Directories worth caching (CI) -Place the library file in the same directory as your executable: -- Windows: `libcronet.dll` -- Linux: `libcronet.so` +```yaml +- naiveproxy/src/third_party/llvm-build/ +- naiveproxy/src/gn/out/ +- naiveproxy/src/chrome/build/pgo_profiles/ +- naiveproxy/src/out/sysroot-build/ +``` -### For Downstream Developers +## Linking against the static library -If you need to programmatically extract libraries from Go module dependencies (e.g., for CI/CD pipelines): +`libcronet.a` needs a number of system libraries; the exact set is written to +`lib//link_flags.txt` at package time. Example (Linux): ```bash -go run github.com/sagernet/cronet-go/cmd/build-naive@latest extract-lib --target windows/amd64 -n libcronet_amd64.dll -go run github.com/sagernet/cronet-go/cmd/build-naive@latest extract-lib --target linux/amd64 -n libcronet_amd64.so +cc myapp.c -Iinclude lib/linux_amd64/libcronet.a $(cat lib/linux_amd64/link_flags.txt | grep -v '^#') ``` + +On Windows / for dynamic linking, ship `libcronet.dll` / `libcronet.so` next to +your executable. diff --git a/bidirectional_conn.go b/bidirectional_conn.go deleted file mode 100644 index 81090f52..00000000 --- a/bidirectional_conn.go +++ /dev/null @@ -1,450 +0,0 @@ -package cronet - -import ( - "context" - "io" - "net" - "os" - "sync" - "time" - - "github.com/sagernet/sing/common/logger" -) - -type BidirectionalConn struct { - ctx context.Context - stream BidirectionalStream - logger logger.ContextLogger - cancelOnce sync.Once // Ensures Cancel is called at most once - destroyOnce sync.Once // Ensures Destroy is called at most once - readWaitHeaders bool - writeWaitHeaders bool - access sync.Mutex - close chan struct{} - done chan struct{} - err error - ready chan struct{} - handshake chan struct{} - read chan int - write chan struct{} - headers map[string]string - readSemaphore chan struct{} - writeSemaphore chan struct{} - readBuffer []byte - writeBuffer []byte - readDone chan struct{} - writeDone chan struct{} - readDoneOnce sync.Once - writeDoneOnce sync.Once - trackedConn *trackedNaiveConn -} - -func (e StreamEngine) CreateConn(ctx context.Context, l logger.ContextLogger, readWaitHeaders bool, writeWaitHeaders bool) *BidirectionalConn { - conn := &BidirectionalConn{ - ctx: ctx, - logger: l, - readWaitHeaders: readWaitHeaders, - writeWaitHeaders: writeWaitHeaders, - close: make(chan struct{}), - done: make(chan struct{}), - ready: make(chan struct{}), - handshake: make(chan struct{}), - read: make(chan int), - write: make(chan struct{}), - readSemaphore: make(chan struct{}, 1), - writeSemaphore: make(chan struct{}, 1), - readDone: make(chan struct{}, 1), - writeDone: make(chan struct{}, 1), - } - conn.readSemaphore <- struct{}{} - conn.writeSemaphore <- struct{}{} - conn.stream = e.CreateStream(&bidirectionalHandler{BidirectionalConn: conn}) - return conn -} - -func (c *BidirectionalConn) Start(method string, url string, headers map[string]string, priority int, endOfStream bool) error { - c.access.Lock() - defer c.access.Unlock() - select { - case <-c.close: - return net.ErrClosed - case <-c.done: - return net.ErrClosed - default: - } - if !c.stream.Start(method, url, headers, priority, endOfStream) { - return os.ErrInvalid - } - return nil -} - -func (c *BidirectionalConn) Read(p []byte) (n int, err error) { - select { - case <-c.close: - return 0, net.ErrClosed - case <-c.done: - return 0, net.ErrClosed - default: - } - if len(p) == 0 { - return 0, nil - } - - select { - case <-c.close: - return 0, net.ErrClosed - case <-c.done: - return 0, net.ErrClosed - case <-c.readSemaphore: - } - defer func() { c.readSemaphore <- struct{}{} }() - - if c.readWaitHeaders { - select { - case <-c.handshake: - break - case <-c.done: - return 0, c.err - } - } else { - select { - case <-c.ready: - break - case <-c.done: - return 0, c.err - } - } - - c.access.Lock() - - select { - case <-c.close: - c.access.Unlock() - return 0, net.ErrClosed - case <-c.done: - c.access.Unlock() - return 0, net.ErrClosed - default: - } - - if len(c.readBuffer) < len(p) { - c.readBuffer = make([]byte, len(p)) - } - readBuffer := c.readBuffer[:len(p)] - c.stream.Read(readBuffer) - c.access.Unlock() - - select { - case bytesRead := <-c.read: - if bytesRead > len(p) { - bytesRead = len(p) - } - if bytesRead > 0 { - copy(p, readBuffer[:bytesRead]) - } - return bytesRead, nil - case <-c.done: - <-c.readDone - return 0, c.err - case <-c.close: - <-c.readDone - return 0, net.ErrClosed - } -} - -func (c *BidirectionalConn) Write(p []byte) (n int, err error) { - select { - case <-c.close: - return 0, net.ErrClosed - case <-c.done: - return 0, net.ErrClosed - default: - } - if len(p) == 0 { - return 0, nil - } - - select { - case <-c.close: - return 0, net.ErrClosed - case <-c.done: - return 0, net.ErrClosed - case <-c.writeSemaphore: - } - defer func() { c.writeSemaphore <- struct{}{} }() - - if c.writeWaitHeaders { - select { - case <-c.handshake: - break - case <-c.done: - return 0, c.err - } - } else { - select { - case <-c.ready: - break - case <-c.done: - return 0, c.err - } - } - - c.access.Lock() - - select { - case <-c.close: - c.access.Unlock() - return 0, net.ErrClosed - case <-c.done: - c.access.Unlock() - return 0, net.ErrClosed - default: - } - - if len(c.writeBuffer) < len(p) { - c.writeBuffer = make([]byte, len(p)) - } - writeBuffer := c.writeBuffer[:len(p)] - copy(writeBuffer, p) - c.stream.Write(writeBuffer, false) - c.access.Unlock() - - select { - case <-c.write: - return len(p), nil - case <-c.done: - <-c.writeDone - return 0, c.err - case <-c.close: - <-c.writeDone - return 0, net.ErrClosed - } -} - -func (c *BidirectionalConn) Done() <-chan struct{} { - return c.done -} - -func (c *BidirectionalConn) bindTrackedNaiveConn(tracked *trackedNaiveConn) { - if tracked == nil { - return - } - - c.access.Lock() - select { - case <-c.done: - c.access.Unlock() - tracked.release() - return - default: - } - c.trackedConn = tracked - c.access.Unlock() -} - -func (c *BidirectionalConn) Err() error { - return c.err -} - -func (c *BidirectionalConn) Close() error { - c.access.Lock() - - select { - case <-c.close: - c.access.Unlock() - return net.ErrClosed - case <-c.done: - c.access.Unlock() - return nil // Stream already terminated normally - default: - } - - close(c.close) - c.access.Unlock() - - c.cancelOnce.Do(func() { - c.stream.Cancel() - }) - return nil -} - -func (c *BidirectionalConn) signalReadDone() { - c.readDoneOnce.Do(func() { close(c.readDone) }) -} - -func (c *BidirectionalConn) signalWriteDone() { - c.writeDoneOnce.Do(func() { close(c.writeDone) }) -} - -func (c *BidirectionalConn) LocalAddr() net.Addr { - return nil -} - -func (c *BidirectionalConn) RemoteAddr() net.Addr { - return nil -} - -func (c *BidirectionalConn) NeedAdditionalReadDeadline() bool { - return true -} - -func (c *BidirectionalConn) SetDeadline(t time.Time) error { - return os.ErrInvalid -} - -func (c *BidirectionalConn) SetReadDeadline(t time.Time) error { - return os.ErrInvalid -} - -func (c *BidirectionalConn) SetWriteDeadline(t time.Time) error { - return os.ErrInvalid -} - -func (c *BidirectionalConn) WaitForHeaders() (map[string]string, error) { - select { - case <-c.close: - return nil, net.ErrClosed - case <-c.done: - return nil, net.ErrClosed - default: - } - - select { - case <-c.handshake: - return c.headers, nil - case <-c.done: - return nil, c.err - } -} - -func (c *BidirectionalConn) WaitForHeadersContext(ctx context.Context) (map[string]string, error) { - select { - case <-c.close: - return nil, net.ErrClosed - case <-c.done: - return nil, net.ErrClosed - default: - } - - select { - case <-ctx.Done(): - return nil, ctx.Err() - case <-c.handshake: - return c.headers, nil - case <-c.done: - return nil, c.err - case <-c.close: - return nil, net.ErrClosed - } -} - -type bidirectionalHandler struct { - *BidirectionalConn - readyOnce sync.Once - handshakeOnce sync.Once - doneOnce sync.Once -} - -func (c *bidirectionalHandler) OnStreamReady(stream BidirectionalStream) { - c.readyOnce.Do(func() { close(c.ready) }) -} - -func (c *bidirectionalHandler) OnResponseHeadersReceived(stream BidirectionalStream, headers map[string]string, negotiatedProtocol string) { - c.headers = headers - c.logger.DebugContext(c.ctx, "response received, protocol: ", negotiatedProtocol, ", status: ", headers[":status"]) - c.handshakeOnce.Do(func() { close(c.handshake) }) -} - -func (c *bidirectionalHandler) OnReadCompleted(stream BidirectionalStream, bytesRead int) { - c.access.Lock() - - if c.err != nil { - c.access.Unlock() - c.signalReadDone() - return - } - - if bytesRead == 0 { - c.access.Unlock() - c.signalReadDone() - c.signalWriteDone() - c.cancelOnce.Do(func() {}) - c.Close(io.EOF) - return - } - - c.access.Unlock() - - select { - case <-c.close: - c.signalReadDone() - case <-c.done: - c.signalReadDone() - case c.read <- bytesRead: - } -} - -func (c *bidirectionalHandler) OnWriteCompleted(stream BidirectionalStream) { - c.access.Lock() - - if c.err != nil { - c.access.Unlock() - c.signalWriteDone() - return - } - - c.access.Unlock() - - select { - case <-c.close: - c.signalWriteDone() - case <-c.done: - c.signalWriteDone() - case c.write <- struct{}{}: - } -} - -func (c *bidirectionalHandler) OnResponseTrailersReceived(stream BidirectionalStream, trailers map[string]string) { -} - -func (c *bidirectionalHandler) OnSucceeded(stream BidirectionalStream) { - c.signalReadDone() - c.signalWriteDone() - c.cancelOnce.Do(func() {}) - c.Close(io.EOF) -} - -func (c *bidirectionalHandler) OnFailed(stream BidirectionalStream, netError int) { - c.logger.WarnContext(c.ctx, "stream failed: ", NetError(netError)) - c.signalReadDone() - c.signalWriteDone() - c.cancelOnce.Do(func() {}) - c.Close(NetError(netError)) -} - -func (c *bidirectionalHandler) OnCanceled(stream BidirectionalStream) { - c.logger.DebugContext(c.ctx, "stream canceled") - c.signalReadDone() - c.signalWriteDone() - c.cancelOnce.Do(func() {}) - c.Close(context.Canceled) -} - -func (c *bidirectionalHandler) Close(err error) { - c.doneOnce.Do(func() { - var tracked *trackedNaiveConn - c.access.Lock() - c.err = err - close(c.done) - tracked = c.trackedConn - c.access.Unlock() - - if tracked != nil { - tracked.release() - } - - c.destroyOnce.Do(func() { - c.stream.Destroy() - }) - }) -} diff --git a/bidirectional_stream_cgo.go b/bidirectional_stream_cgo.go deleted file mode 100644 index 82b1072d..00000000 --- a/bidirectional_stream_cgo.go +++ /dev/null @@ -1,157 +0,0 @@ -//go:build !with_purego - -package cronet - -// #include -// #include -// #include -// #include -import "C" - -import ( - "unsafe" -) - -func (e Engine) StreamEngine() StreamEngine { - return StreamEngine{uintptr(unsafe.Pointer(C.Cronet_Engine_GetStreamEngine(C.Cronet_EnginePtr(unsafe.Pointer(e.ptr)))))} -} - -// CreateStream -// Creates a new stream object that uses |engine| and |callback|. All stream -// tasks are performed asynchronously on the |engine| network thread. |callback| -// methods are invoked synchronously on the |engine| network thread, but must -// not run tasks on the current thread to prevent blocking networking operations -// and causing exceptions during shutdown. The |annotation| is stored in -// bidirectional stream for arbitrary use by application. -// -// Returned |bidirectional_stream*| is owned by the caller, and must be -// destroyed using |bidirectional_stream_destroy|. -// -// Both |calback| and |engine| must remain valid until stream is destroyed. -func (e StreamEngine) CreateStream(callback BidirectionalStreamCallback) BidirectionalStream { - if callback == nil { - panic("nil bidirectional stream callback") - } - ptr := C.bidirectional_stream_create((*C.stream_engine)(unsafe.Pointer(e.ptr)), nil, &bidirectionalStreamCallbackCGO) - ptrVal := uintptr(unsafe.Pointer(ptr)) - bidirectionalStreamAccess.Lock() - bidirectionalStreamMap[ptrVal] = &bidirectionalStreamEntry{callback: callback} - bidirectionalStreamAccess.Unlock() - return BidirectionalStream{ptrVal} -} - -// Destroy destroys stream object. Destroy could be called from any thread, including -// network thread, but is posted, so |stream| is valid until calling task is -// complete. The destroy operation is asynchronous - callbacks may still be -// invoked after this returns. The stream is marked as destroyed and callbacks -// will silently return. -func (c BidirectionalStream) Destroy() bool { - bidirectionalStreamAccess.RLock() - entry := bidirectionalStreamMap[c.ptr] - bidirectionalStreamAccess.RUnlock() - if entry != nil { - entry.destroyed.Store(true) - } - return C.bidirectional_stream_destroy((*C.bidirectional_stream)(unsafe.Pointer(c.ptr))) == 0 -} - -// DisableAutoFlush disables or enables auto flush. By default, data is flushed after -// every Write(). If the auto flush is disabled, -// the client should explicitly call Flush() to flush -// the data. -func (c BidirectionalStream) DisableAutoFlush(disable bool) { - C.bidirectional_stream_disable_auto_flush((*C.bidirectional_stream)(unsafe.Pointer(c.ptr)), C.bool(disable)) -} - -// DelayRequestHeadersUntilFlush delays sending request headers until Flush() -// is called. This flag is currently only respected when QUIC is negotiated. -// When true, QUIC will send request header frame along with data frame(s) -// as a single packet when possible. -func (c BidirectionalStream) DelayRequestHeadersUntilFlush(delay bool) { - C.bidirectional_stream_delay_request_headers_until_flush((*C.bidirectional_stream)(unsafe.Pointer(c.ptr)), C.bool(delay)) -} - -// Start starts the stream by sending request to |url| using |method| and |headers|. -// If |endOfStream| is true, then no data is expected to be written. The -// |method| is HTTP verb. -// noinspection GoDeferInLoop -func (c BidirectionalStream) Start(method string, url string, headers map[string]string, priority int, endOfStream bool) bool { - var headerArray C.bidirectional_stream_header_array - headerLen := len(headers) - if headerLen > 0 { - cHeadersPtr := C.malloc(C.size_t(int(C.sizeof_struct_bidirectional_stream_header) * headerLen)) - defer C.free(cHeadersPtr) - cType := (*C.bidirectional_stream_header)(cHeadersPtr) - cHeaders := unsafe.Slice(cType, headerLen) - var index int - for key, value := range headers { - cKey := C.CString(key) - defer C.free(unsafe.Pointer(cKey)) - cValue := C.CString(value) - defer C.free(unsafe.Pointer(cValue)) - cHeaders[index].key = cKey - cHeaders[index].value = cValue - index++ - } - headerArray = C.bidirectional_stream_header_array{ - C.size_t(headerLen), C.size_t(headerLen), &cHeaders[0], - } - } - - cMethod := C.CString(method) - defer C.free(unsafe.Pointer(cMethod)) - - cURL := C.CString(url) - defer C.free(unsafe.Pointer(cURL)) - - return C.bidirectional_stream_start((*C.bidirectional_stream)(unsafe.Pointer(c.ptr)), cURL, C.int(priority), cMethod, &headerArray, C.bool(endOfStream)) == 0 -} - -// Read reads response data into |buffer|. Must only be called -// at most once in response to each invocation of the -// OnStreamReady()/OnResponseHeaderReceived() and OnReadCompleted() -// methods of the BidirectionalStreamCallback. -// Each call will result in an invocation of the callback's -// OnReadCompleted() method if data is read, or its OnFailed() method if -// there's an error. The callback's OnSucceeded() method is also invoked if -// there is no more data to read and |end_of_stream| was previously sent. -func (c BidirectionalStream) Read(buffer []byte) int { - if len(buffer) == 0 { - return int(C.bidirectional_stream_read((*C.bidirectional_stream)(unsafe.Pointer(c.ptr)), nil, 0)) - } - return int(C.bidirectional_stream_read((*C.bidirectional_stream)(unsafe.Pointer(c.ptr)), (*C.char)((unsafe.Pointer)(&buffer[0])), C.int(len(buffer)))) -} - -// Write Writes request data from |buffer| If auto flush is -// disabled, data will be sent only after Flush() is -// called. -// Each call will result in an invocation the callback's BidirectionalStreamCallback.OnWriteCompleted() -// method if data is sent, or its BidirectionalStreamCallback.OnFailed() method if there's an error. -// The callback's BidirectionalStreamCallback.OnSucceeded() method is also invoked if |endOfStream| is -// set and all response data has been read. -func (c BidirectionalStream) Write(buffer []byte, endOfStream bool) int { - if len(buffer) == 0 { - return int(C.bidirectional_stream_write((*C.bidirectional_stream)(unsafe.Pointer(c.ptr)), nil, 0, C.bool(endOfStream))) - } - return int(C.bidirectional_stream_write((*C.bidirectional_stream)(unsafe.Pointer(c.ptr)), (*C.char)(unsafe.Pointer(&buffer[0])), C.int(len(buffer)), C.bool(endOfStream))) -} - -// Flush Flushes pending writes. This method should not be called before invocation of -// BidirectionalStreamCallback.OnStreamReady() method. -// For each previously called Write() -// a corresponding OnWriteCompleted() callback will be invoked when the buffer -// is sent.BidirectionalStream -func (c BidirectionalStream) Flush() { - C.bidirectional_stream_flush((*C.bidirectional_stream)(unsafe.Pointer(c.ptr))) -} - -// Cancel cancels the stream. Can be called at any time after -// Start(). The BidirectionalStreamCallback.OnCanceled() method will be invoked when cancellation -// is complete and no further callback methods will be invoked. If the -// stream has completed or has not started, calling -// Cancel() has no effect and BidirectionalStreamCallback.OnCanceled() will not -// be invoked. At most one callback method may be invoked after -// Cancel() has completed. -func (c BidirectionalStream) Cancel() { - C.bidirectional_stream_cancel((*C.bidirectional_stream)(unsafe.Pointer(c.ptr))) -} diff --git a/bidirectional_stream_impl_cgo.go b/bidirectional_stream_impl_cgo.go deleted file mode 100644 index 35b32032..00000000 --- a/bidirectional_stream_impl_cgo.go +++ /dev/null @@ -1,141 +0,0 @@ -//go:build !with_purego - -package cronet - -// #include -// #include -// #include -// #include -// extern CRONET_EXPORT void cronetBidirectionalStreamOnStreamReady(bidirectional_stream* stream); -// extern CRONET_EXPORT void cronetBidirectionalStreamOnResponseHeadersReceived(bidirectional_stream* stream, bidirectional_stream_header_array* headers, char* negotiated_protocol); -// extern CRONET_EXPORT void cronetBidirectionalStreamOnReadCompleted(bidirectional_stream* stream, char* data, int bytes_read); -// extern CRONET_EXPORT void cronetBidirectionalStreamOnWriteCompleted(bidirectional_stream* stream, char* data); -// extern CRONET_EXPORT void cronetBidirectionalStreamOnResponseTrailersReceived(bidirectional_stream* stream, bidirectional_stream_header_array* trailers); -// extern CRONET_EXPORT void cronetBidirectionalStreamOnSucceed(bidirectional_stream* stream); -// extern CRONET_EXPORT void cronetBidirectionalStreamOnFailed(bidirectional_stream* stream, int net_error); -// extern CRONET_EXPORT void cronetBidirectionalStreamOnCanceled(bidirectional_stream* stream); -import "C" - -import ( - "unsafe" -) - -var bidirectionalStreamCallbackCGO C.bidirectional_stream_callback - -func init() { - bidirectionalStreamCallbackCGO.on_stream_ready = (*[0]byte)(C.cronetBidirectionalStreamOnStreamReady) - bidirectionalStreamCallbackCGO.on_response_headers_received = (*[0]byte)(C.cronetBidirectionalStreamOnResponseHeadersReceived) - bidirectionalStreamCallbackCGO.on_read_completed = (*[0]byte)(C.cronetBidirectionalStreamOnReadCompleted) - bidirectionalStreamCallbackCGO.on_write_completed = (*[0]byte)(C.cronetBidirectionalStreamOnWriteCompleted) - bidirectionalStreamCallbackCGO.on_response_trailers_received = (*[0]byte)(C.cronetBidirectionalStreamOnResponseTrailersReceived) - bidirectionalStreamCallbackCGO.on_succeded = (*[0]byte)(C.cronetBidirectionalStreamOnSucceed) - bidirectionalStreamCallbackCGO.on_failed = (*[0]byte)(C.cronetBidirectionalStreamOnFailed) - bidirectionalStreamCallbackCGO.on_canceled = (*[0]byte)(C.cronetBidirectionalStreamOnCanceled) -} - -func instanceOfBidirectionalStream(stream *C.bidirectional_stream) BidirectionalStreamCallback { - return instanceOfBidirectionalStreamCallback(uintptr(unsafe.Pointer(stream))) -} - -func cBidirectionalStreamToGo(stream *C.bidirectional_stream) BidirectionalStream { - return BidirectionalStream{uintptr(unsafe.Pointer(stream))} -} - -//export cronetBidirectionalStreamOnStreamReady -func cronetBidirectionalStreamOnStreamReady(stream *C.bidirectional_stream) { - callback := instanceOfBidirectionalStream(stream) - if callback == nil { - return - } - callback.OnStreamReady(cBidirectionalStreamToGo(stream)) -} - -//export cronetBidirectionalStreamOnResponseHeadersReceived -func cronetBidirectionalStreamOnResponseHeadersReceived(stream *C.bidirectional_stream, headers *C.bidirectional_stream_header_array, negotiatedProtocol *C.char) { - callback := instanceOfBidirectionalStream(stream) - if callback == nil { - return - } - var headerMap map[string]string - if headers != nil && headers.count > 0 { - headerMap = make(map[string]string, int(headers.count)) - headersSlice := unsafe.Slice(headers.headers, int(headers.count)) - for _, header := range headersSlice { - key := C.GoString(header.key) - if len(key) == 0 { - continue - } - headerMap[key] = C.GoString(header.value) - } - } - callback.OnResponseHeadersReceived(cBidirectionalStreamToGo(stream), headerMap, C.GoString(negotiatedProtocol)) -} - -//export cronetBidirectionalStreamOnReadCompleted -func cronetBidirectionalStreamOnReadCompleted(stream *C.bidirectional_stream, data *C.char, bytesRead C.int) { - callback := instanceOfBidirectionalStream(stream) - if callback == nil { - return - } - callback.OnReadCompleted(cBidirectionalStreamToGo(stream), int(bytesRead)) -} - -//export cronetBidirectionalStreamOnWriteCompleted -func cronetBidirectionalStreamOnWriteCompleted(stream *C.bidirectional_stream, data *C.char) { - callback := instanceOfBidirectionalStream(stream) - if callback == nil { - return - } - callback.OnWriteCompleted(cBidirectionalStreamToGo(stream)) -} - -//export cronetBidirectionalStreamOnResponseTrailersReceived -func cronetBidirectionalStreamOnResponseTrailersReceived(stream *C.bidirectional_stream, trailers *C.bidirectional_stream_header_array) { - callback := instanceOfBidirectionalStream(stream) - if callback == nil { - return - } - var trailersMap map[string]string - if trailers != nil && trailers.count > 0 { - trailersMap = make(map[string]string, int(trailers.count)) - headersSlice := unsafe.Slice(trailers.headers, int(trailers.count)) - for _, header := range headersSlice { - key := C.GoString(header.key) - if len(key) == 0 { - continue - } - trailersMap[key] = C.GoString(header.value) - } - } - callback.OnResponseTrailersReceived(cBidirectionalStreamToGo(stream), trailersMap) -} - -//export cronetBidirectionalStreamOnSucceed -func cronetBidirectionalStreamOnSucceed(stream *C.bidirectional_stream) { - callback := instanceOfBidirectionalStream(stream) - if callback == nil { - return - } - callback.OnSucceeded(cBidirectionalStreamToGo(stream)) - cleanupBidirectionalStream(uintptr(unsafe.Pointer(stream))) -} - -//export cronetBidirectionalStreamOnFailed -func cronetBidirectionalStreamOnFailed(stream *C.bidirectional_stream, netError C.int) { - callback := instanceOfBidirectionalStream(stream) - if callback == nil { - return - } - callback.OnFailed(cBidirectionalStreamToGo(stream), int(netError)) - cleanupBidirectionalStream(uintptr(unsafe.Pointer(stream))) -} - -//export cronetBidirectionalStreamOnCanceled -func cronetBidirectionalStreamOnCanceled(stream *C.bidirectional_stream) { - callback := instanceOfBidirectionalStream(stream) - if callback == nil { - return - } - callback.OnCanceled(cBidirectionalStreamToGo(stream)) - cleanupBidirectionalStream(uintptr(unsafe.Pointer(stream))) -} diff --git a/bidirectional_stream_impl_purego.go b/bidirectional_stream_impl_purego.go deleted file mode 100644 index 83a18e61..00000000 --- a/bidirectional_stream_impl_purego.go +++ /dev/null @@ -1,144 +0,0 @@ -//go:build with_purego - -package cronet - -import ( - "unsafe" - - "github.com/sagernet/cronet-go/internal/cronet" - - "github.com/ebitengine/purego" -) - -type bidirectionalStreamCallbackStruct struct { - onStreamReady uintptr - onResponseHeadersReceived uintptr - onReadCompleted uintptr - onWriteCompleted uintptr - onResponseTrailersReceived uintptr - onSucceeded uintptr - onFailed uintptr - onCanceled uintptr -} - -type bidirectionalStreamHeaderArray struct { - count uintptr - capacity uintptr - headers uintptr -} - -type bidirectionalStreamHeader struct { - key uintptr // const char* - value uintptr // const char* -} - -var bsCallbackStructPurego bidirectionalStreamCallbackStruct - -func init() { - bsCallbackStructPurego.onStreamReady = purego.NewCallback(bsOnStreamReadyCallback) - bsCallbackStructPurego.onResponseHeadersReceived = purego.NewCallback(bsOnResponseHeadersReceivedCallback) - bsCallbackStructPurego.onReadCompleted = purego.NewCallback(bsOnReadCompletedCallback) - bsCallbackStructPurego.onWriteCompleted = purego.NewCallback(bsOnWriteCompletedCallback) - bsCallbackStructPurego.onResponseTrailersReceived = purego.NewCallback(bsOnResponseTrailersReceivedCallback) - bsCallbackStructPurego.onSucceeded = purego.NewCallback(bsOnSucceededCallback) - bsCallbackStructPurego.onFailed = purego.NewCallback(bsOnFailedCallback) - bsCallbackStructPurego.onCanceled = purego.NewCallback(bsOnCanceledCallback) -} - -func bsOnStreamReadyCallback(stream uintptr) uintptr { - cb := instanceOfBidirectionalStreamCallback(stream) - if cb == nil { - return 0 - } - cb.OnStreamReady(BidirectionalStream{stream}) - return 0 -} - -func bsOnResponseHeadersReceivedCallback(stream, headers, negotiatedProtocol uintptr) uintptr { - cb := instanceOfBidirectionalStreamCallback(stream) - if cb == nil { - return 0 - } - headerMap := parseHeaderArray(headers) - cb.OnResponseHeadersReceived(BidirectionalStream{stream}, headerMap, cronet.GoString(negotiatedProtocol)) - return 0 -} - -func bsOnReadCompletedCallback(stream, data uintptr, bytesRead int32) uintptr { - cb := instanceOfBidirectionalStreamCallback(stream) - if cb == nil { - return 0 - } - cb.OnReadCompleted(BidirectionalStream{stream}, int(bytesRead)) - return 0 -} - -func bsOnWriteCompletedCallback(stream, data uintptr) uintptr { - cb := instanceOfBidirectionalStreamCallback(stream) - if cb == nil { - return 0 - } - cb.OnWriteCompleted(BidirectionalStream{stream}) - return 0 -} - -func bsOnResponseTrailersReceivedCallback(stream, trailers uintptr) uintptr { - cb := instanceOfBidirectionalStreamCallback(stream) - if cb == nil { - return 0 - } - trailerMap := parseHeaderArray(trailers) - cb.OnResponseTrailersReceived(BidirectionalStream{stream}, trailerMap) - return 0 -} - -func bsOnSucceededCallback(stream uintptr) uintptr { - cb := instanceOfBidirectionalStreamCallback(stream) - if cb == nil { - return 0 - } - cb.OnSucceeded(BidirectionalStream{stream}) - cleanupBidirectionalStream(stream) - return 0 -} - -func bsOnFailedCallback(stream uintptr, netError int32) uintptr { - cb := instanceOfBidirectionalStreamCallback(stream) - if cb == nil { - return 0 - } - cb.OnFailed(BidirectionalStream{stream}, int(netError)) - cleanupBidirectionalStream(stream) - return 0 -} - -func bsOnCanceledCallback(stream uintptr) uintptr { - cb := instanceOfBidirectionalStreamCallback(stream) - if cb == nil { - return 0 - } - cb.OnCanceled(BidirectionalStream{stream}) - cleanupBidirectionalStream(stream) - return 0 -} - -func parseHeaderArray(ptr uintptr) map[string]string { - if ptr == 0 { - return nil - } - arr := (*bidirectionalStreamHeaderArray)(unsafe.Pointer(ptr)) - count := int(arr.count) - if count == 0 { - return nil - } - result := make(map[string]string, count) - headers := unsafe.Slice((*bidirectionalStreamHeader)(unsafe.Pointer(arr.headers)), count) - for _, h := range headers { - key := cronet.GoString(h.key) - if key == "" { - continue - } - result[key] = cronet.GoString(h.value) - } - return result -} diff --git a/bidirectional_stream_map.go b/bidirectional_stream_map.go deleted file mode 100644 index c0d4ff96..00000000 --- a/bidirectional_stream_map.go +++ /dev/null @@ -1,36 +0,0 @@ -package cronet - -import ( - "sync" - "sync/atomic" -) - -type bidirectionalStreamEntry struct { - callback BidirectionalStreamCallback - destroyed atomic.Bool -} - -var ( - bidirectionalStreamAccess sync.RWMutex - bidirectionalStreamMap map[uintptr]*bidirectionalStreamEntry -) - -func init() { - bidirectionalStreamMap = make(map[uintptr]*bidirectionalStreamEntry) -} - -func instanceOfBidirectionalStreamCallback(ptr uintptr) BidirectionalStreamCallback { - bidirectionalStreamAccess.RLock() - defer bidirectionalStreamAccess.RUnlock() - entry := bidirectionalStreamMap[ptr] - if entry == nil || entry.destroyed.Load() { - return nil - } - return entry.callback -} - -func cleanupBidirectionalStream(ptr uintptr) { - bidirectionalStreamAccess.Lock() - delete(bidirectionalStreamMap, ptr) - bidirectionalStreamAccess.Unlock() -} diff --git a/bidirectional_stream_purego.go b/bidirectional_stream_purego.go deleted file mode 100644 index 143b4648..00000000 --- a/bidirectional_stream_purego.go +++ /dev/null @@ -1,108 +0,0 @@ -//go:build with_purego - -package cronet - -import ( - "runtime" - "unsafe" - - "github.com/sagernet/cronet-go/internal/cronet" -) - -func (e Engine) StreamEngine() StreamEngine { - return StreamEngine{cronet.EngineGetStreamEngine(e.ptr)} -} - -// CreateStream creates a new stream object that uses |engine| and |callback|. -func (e StreamEngine) CreateStream(callback BidirectionalStreamCallback) BidirectionalStream { - if callback == nil { - panic("nil bidirectional stream callback") - } - ptr := cronet.BidirectionalStreamCreate(e.ptr, 0, uintptr(unsafe.Pointer(&bsCallbackStructPurego))) - bidirectionalStreamAccess.Lock() - bidirectionalStreamMap[ptr] = &bidirectionalStreamEntry{callback: callback} - bidirectionalStreamAccess.Unlock() - return BidirectionalStream{ptr} -} - -// Destroy destroys stream object. The destroy operation is asynchronous - -// callbacks may still be invoked after this returns. The stream is marked -// as destroyed and callbacks will silently return. -func (s BidirectionalStream) Destroy() bool { - bidirectionalStreamAccess.RLock() - entry := bidirectionalStreamMap[s.ptr] - bidirectionalStreamAccess.RUnlock() - if entry != nil { - entry.destroyed.Store(true) - } - return cronet.BidirectionalStreamDestroy(s.ptr) == 0 -} - -// Start starts the stream by sending request to |url| using |method| and |headers|. -func (c BidirectionalStream) Start(method string, url string, headers map[string]string, priority int, endOfStream bool) bool { - var headerArrayPtr uintptr - var cStringBacking [][]byte // Keep C string backing slices alive - - headerLen := len(headers) - if headerLen > 0 { - // Allocate header structs - headerStructs := make([]bidirectionalStreamHeader, headerLen) - var index int - for key, value := range headers { - keyPtr, keyBacking := cronet.CString(key) - valuePtr, valueBacking := cronet.CString(value) - cStringBacking = append(cStringBacking, keyBacking, valueBacking) - headerStructs[index].key = keyPtr - headerStructs[index].value = valuePtr - index++ - } - - headerArray := bidirectionalStreamHeaderArray{ - count: uintptr(headerLen), - capacity: uintptr(headerLen), - headers: uintptr(unsafe.Pointer(&headerStructs[0])), - } - headerArrayPtr = uintptr(unsafe.Pointer(&headerArray)) - - result := cronet.BidirectionalStreamStart(c.ptr, url, int32(priority), method, headerArrayPtr, endOfStream) == 0 - - // Keep all backing slices alive until after the call - runtime.KeepAlive(cStringBacking) - runtime.KeepAlive(headerStructs) - runtime.KeepAlive(headerArray) - - return result - } - - return cronet.BidirectionalStreamStart(c.ptr, url, int32(priority), method, 0, endOfStream) == 0 -} - -func (s BidirectionalStream) DisableAutoFlush(disable bool) { - cronet.BidirectionalStreamDisableAutoFlush(s.ptr, disable) -} - -func (s BidirectionalStream) DelayRequestHeadersUntilFlush(delay bool) { - cronet.BidirectionalStreamDelayRequestHeadersUntilFlush(s.ptr, delay) -} - -func (s BidirectionalStream) Read(buffer []byte) int { - if len(buffer) == 0 { - return int(cronet.BidirectionalStreamRead(s.ptr, 0, 0)) - } - return int(cronet.BidirectionalStreamRead(s.ptr, uintptr(unsafe.Pointer(&buffer[0])), int32(len(buffer)))) -} - -func (s BidirectionalStream) Write(buffer []byte, endOfStream bool) int { - if len(buffer) == 0 { - return int(cronet.BidirectionalStreamWrite(s.ptr, 0, 0, endOfStream)) - } - return int(cronet.BidirectionalStreamWrite(s.ptr, uintptr(unsafe.Pointer(&buffer[0])), int32(len(buffer)), endOfStream)) -} - -func (s BidirectionalStream) Flush() { - cronet.BidirectionalStreamFlush(s.ptr) -} - -func (s BidirectionalStream) Cancel() { - cronet.BidirectionalStreamCancel(s.ptr) -} diff --git a/buffer_callback_cgo.go b/buffer_callback_cgo.go deleted file mode 100644 index 17f8b8c7..00000000 --- a/buffer_callback_cgo.go +++ /dev/null @@ -1,23 +0,0 @@ -//go:build !with_purego - -package cronet - -// #include -// #include -// #include -import "C" - -import "unsafe" - -func (c BufferCallback) Destroy() { - c.destroy() - C.Cronet_BufferCallback_Destroy(C.Cronet_BufferCallbackPtr(unsafe.Pointer(c.ptr))) -} - -func (c BufferCallback) SetClientContext(context unsafe.Pointer) { - C.Cronet_BufferCallback_SetClientContext(C.Cronet_BufferCallbackPtr(unsafe.Pointer(c.ptr)), C.Cronet_ClientContext(context)) -} - -func (c BufferCallback) ClientContext() unsafe.Pointer { - return unsafe.Pointer(C.Cronet_BufferCallback_GetClientContext(C.Cronet_BufferCallbackPtr(unsafe.Pointer(c.ptr)))) -} diff --git a/buffer_callback_impl.go b/buffer_callback_impl.go deleted file mode 100644 index 931dc45f..00000000 --- a/buffer_callback_impl.go +++ /dev/null @@ -1,66 +0,0 @@ -//go:build !with_purego - -package cronet - -// #include -// #include -// #include -// extern CRONET_EXPORT void cronetBufferCallbackOnDestroy(Cronet_BufferCallbackPtr self,Cronet_BufferPtr buffer); -import "C" - -import ( - "sync" - "sync/atomic" - "unsafe" -) - -type bufferCallbackEntry struct { - callback BufferCallbackFunc - destroyed atomic.Bool -} - -var ( - bufferCallbackAccess sync.RWMutex - bufferCallbackMap map[uintptr]*bufferCallbackEntry -) - -func init() { - bufferCallbackMap = make(map[uintptr]*bufferCallbackEntry) -} - -func NewBufferCallback(callbackFunc BufferCallbackFunc) BufferCallback { - ptr := C.Cronet_BufferCallback_CreateWith((*[0]byte)(C.cronetBufferCallbackOnDestroy)) - ptrVal := uintptr(unsafe.Pointer(ptr)) - if callbackFunc != nil { - bufferCallbackAccess.Lock() - bufferCallbackMap[ptrVal] = &bufferCallbackEntry{callback: callbackFunc} - bufferCallbackAccess.Unlock() - } - return BufferCallback{ptrVal} -} - -func (c BufferCallback) destroy() { - bufferCallbackAccess.RLock() - entry := bufferCallbackMap[c.ptr] - bufferCallbackAccess.RUnlock() - if entry != nil { - entry.destroyed.Store(true) - } -} - -//export cronetBufferCallbackOnDestroy -func cronetBufferCallbackOnDestroy(self C.Cronet_BufferCallbackPtr, buffer C.Cronet_BufferPtr) { - ptrInt := uintptr(unsafe.Pointer(self)) - bufferCallbackAccess.RLock() - entry := bufferCallbackMap[ptrInt] - bufferCallbackAccess.RUnlock() - if entry == nil || entry.destroyed.Load() { - return - } - if entry.callback != nil { - entry.callback(BufferCallback{ptrInt}, Buffer{uintptr(unsafe.Pointer(buffer))}) - } - bufferCallbackAccess.Lock() - delete(bufferCallbackMap, ptrInt) - bufferCallbackAccess.Unlock() -} diff --git a/buffer_callback_impl_purego.go b/buffer_callback_impl_purego.go deleted file mode 100644 index 15d75700..00000000 --- a/buffer_callback_impl_purego.go +++ /dev/null @@ -1,64 +0,0 @@ -//go:build with_purego - -package cronet - -import ( - "sync" - "sync/atomic" - - "github.com/sagernet/cronet-go/internal/cronet" - - "github.com/ebitengine/purego" -) - -type bufferCallbackEntry struct { - callback BufferCallbackFunc - destroyed atomic.Bool -} - -var ( - bufferCallbackAccess sync.RWMutex - bufferCallbackMap map[uintptr]*bufferCallbackEntry - bufferCallbackOnDestroy uintptr -) - -func init() { - bufferCallbackMap = make(map[uintptr]*bufferCallbackEntry) - bufferCallbackOnDestroy = purego.NewCallback(onBufferDestroyCallback) -} - -func onBufferDestroyCallback(self, buffer uintptr) uintptr { - bufferCallbackAccess.RLock() - entry := bufferCallbackMap[self] - bufferCallbackAccess.RUnlock() - if entry == nil || entry.destroyed.Load() { - return 0 // Post-destroy callback, silently ignore - } - if entry.callback != nil { - entry.callback(BufferCallback{self}, Buffer{buffer}) - } - // OnDestroy is the cleanup signal - safe to delete - bufferCallbackAccess.Lock() - delete(bufferCallbackMap, self) - bufferCallbackAccess.Unlock() - return 0 -} - -func NewBufferCallback(callbackFunc BufferCallbackFunc) BufferCallback { - ptr := cronet.BufferCallbackCreateWith(bufferCallbackOnDestroy) - if callbackFunc != nil { - bufferCallbackAccess.Lock() - bufferCallbackMap[ptr] = &bufferCallbackEntry{callback: callbackFunc} - bufferCallbackAccess.Unlock() - } - return BufferCallback{ptr} -} - -func (c BufferCallback) destroy() { - bufferCallbackAccess.RLock() - entry := bufferCallbackMap[c.ptr] - bufferCallbackAccess.RUnlock() - if entry != nil { - entry.destroyed.Store(true) - } -} diff --git a/buffer_callback_purego.go b/buffer_callback_purego.go deleted file mode 100644 index 545cd3e4..00000000 --- a/buffer_callback_purego.go +++ /dev/null @@ -1,22 +0,0 @@ -//go:build with_purego - -package cronet - -import ( - "unsafe" - - "github.com/sagernet/cronet-go/internal/cronet" -) - -func (c BufferCallback) Destroy() { - c.destroy() - cronet.BufferCallbackDestroy(c.ptr) -} - -func (c BufferCallback) SetClientContext(context unsafe.Pointer) { - cronet.BufferCallbackSetClientContext(c.ptr, uintptr(context)) -} - -func (c BufferCallback) ClientContext() unsafe.Pointer { - return unsafe.Pointer(cronet.BufferCallbackGetClientContext(c.ptr)) -} diff --git a/buffer_cgo.go b/buffer_cgo.go deleted file mode 100644 index d1cc56bf..00000000 --- a/buffer_cgo.go +++ /dev/null @@ -1,58 +0,0 @@ -//go:build !with_purego - -package cronet - -// #include -// #include -// #include -import "C" - -import ( - "unsafe" -) - -func NewBuffer() Buffer { - return Buffer{uintptr(unsafe.Pointer(C.Cronet_Buffer_Create()))} -} - -func (b Buffer) Destroy() { - C.Cronet_Buffer_Destroy(C.Cronet_BufferPtr(unsafe.Pointer(b.ptr))) -} - -// InitWithDataAndCallback initialize Buffer with raw buffer |data| of |size| allocated by the app. -// The |callback| is invoked when buffer is destroyed. -func (b Buffer) InitWithDataAndCallback(data []byte, callback BufferCallback) { - C.Cronet_Buffer_InitWithDataAndCallback(C.Cronet_BufferPtr(unsafe.Pointer(b.ptr)), C.Cronet_RawDataPtr(unsafe.Pointer(&data[0])), C.uint64_t(len(data)), C.Cronet_BufferCallbackPtr(unsafe.Pointer(callback.ptr))) -} - -// InitWithAlloc initialize Buffer by allocating buffer of |size|. -// The content of allocated data is not initialized. -func (b Buffer) InitWithAlloc(size int64) { - C.Cronet_Buffer_InitWithAlloc(C.Cronet_BufferPtr(unsafe.Pointer(b.ptr)), C.uint64_t(size)) -} - -// Size return size of data owned by this buffer. -func (b Buffer) Size() int64 { - return int64(C.Cronet_Buffer_GetSize(C.Cronet_BufferPtr(unsafe.Pointer(b.ptr)))) -} - -// Data return raw pointer to |data| owned by this buffer. -func (b Buffer) Data() unsafe.Pointer { - return unsafe.Pointer(C.Cronet_Buffer_GetData(C.Cronet_BufferPtr(unsafe.Pointer(b.ptr)))) -} - -func (b Buffer) DataSlice() []byte { - size := b.Size() - if size == 0 { - return nil - } - return unsafe.Slice((*byte)(b.Data()), size) -} - -func (b Buffer) SetClientContext(context unsafe.Pointer) { - C.Cronet_Buffer_SetClientContext(C.Cronet_BufferPtr(unsafe.Pointer(b.ptr)), C.Cronet_ClientContext(context)) -} - -func (b Buffer) ClientContext() unsafe.Pointer { - return unsafe.Pointer(C.Cronet_Buffer_GetClientContext(C.Cronet_BufferPtr(unsafe.Pointer(b.ptr)))) -} diff --git a/buffer_impl_cgo.go b/buffer_impl_cgo.go deleted file mode 100644 index e827bf30..00000000 --- a/buffer_impl_cgo.go +++ /dev/null @@ -1,89 +0,0 @@ -//go:build !with_purego - -package cronet - -// #include -// #include -// #include -// extern CRONET_EXPORT void cronetBufferInitWithDataAndCallback(Cronet_BufferPtr self, Cronet_RawDataPtr data, uint64_t size, Cronet_BufferCallbackPtr callback); -// extern CRONET_EXPORT void cronetBufferInitWithAlloc(Cronet_BufferPtr self, uint64_t size); -// extern CRONET_EXPORT uint64_t cronetBufferGetSize(Cronet_BufferPtr self); -// extern CRONET_EXPORT Cronet_RawDataPtr cronetBufferGetData(Cronet_BufferPtr self); -import "C" - -import ( - "sync" - "unsafe" -) - -// BufferHandler is an interface for custom Buffer implementations (for testing/mocking). -type BufferHandler interface { - InitWithDataAndCallback(self Buffer, data unsafe.Pointer, size uint64, callback BufferCallback) - InitWithAlloc(self Buffer, size uint64) - GetSize(self Buffer) uint64 - GetData(self Buffer) unsafe.Pointer -} - -// NewBufferWith creates a new Buffer with custom handler (for testing/mocking). -func NewBufferWith(handler BufferHandler) Buffer { - ptr := C.Cronet_Buffer_CreateWith( - (*[0]byte)(C.cronetBufferInitWithDataAndCallback), - (*[0]byte)(C.cronetBufferInitWithAlloc), - (*[0]byte)(C.cronetBufferGetSize), - (*[0]byte)(C.cronetBufferGetData), - ) - ptrVal := uintptr(unsafe.Pointer(ptr)) - bufferHandlerAccess.Lock() - bufferHandlerMap[ptrVal] = handler - bufferHandlerAccess.Unlock() - return Buffer{ptrVal} -} - -var ( - bufferHandlerAccess sync.RWMutex - bufferHandlerMap map[uintptr]BufferHandler -) - -func init() { - bufferHandlerMap = make(map[uintptr]BufferHandler) -} - -func instanceOfBufferHandler(self C.Cronet_BufferPtr) BufferHandler { - bufferHandlerAccess.RLock() - defer bufferHandlerAccess.RUnlock() - return bufferHandlerMap[uintptr(unsafe.Pointer(self))] -} - -//export cronetBufferInitWithDataAndCallback -func cronetBufferInitWithDataAndCallback(self C.Cronet_BufferPtr, data C.Cronet_RawDataPtr, size C.uint64_t, callback C.Cronet_BufferCallbackPtr) { - handler := instanceOfBufferHandler(self) - if handler != nil { - handler.InitWithDataAndCallback(Buffer{uintptr(unsafe.Pointer(self))}, unsafe.Pointer(data), uint64(size), BufferCallback{uintptr(unsafe.Pointer(callback))}) - } -} - -//export cronetBufferInitWithAlloc -func cronetBufferInitWithAlloc(self C.Cronet_BufferPtr, size C.uint64_t) { - handler := instanceOfBufferHandler(self) - if handler != nil { - handler.InitWithAlloc(Buffer{uintptr(unsafe.Pointer(self))}, uint64(size)) - } -} - -//export cronetBufferGetSize -func cronetBufferGetSize(self C.Cronet_BufferPtr) C.uint64_t { - handler := instanceOfBufferHandler(self) - if handler != nil { - return C.uint64_t(handler.GetSize(Buffer{uintptr(unsafe.Pointer(self))})) - } - return 0 -} - -//export cronetBufferGetData -func cronetBufferGetData(self C.Cronet_BufferPtr) C.Cronet_RawDataPtr { - handler := instanceOfBufferHandler(self) - if handler != nil { - return C.Cronet_RawDataPtr(handler.GetData(Buffer{uintptr(unsafe.Pointer(self))})) - } - return nil -} diff --git a/buffer_purego.go b/buffer_purego.go deleted file mode 100644 index 12ff88c4..00000000 --- a/buffer_purego.go +++ /dev/null @@ -1,59 +0,0 @@ -//go:build with_purego - -package cronet - -import ( - "unsafe" - - "github.com/sagernet/cronet-go/internal/cronet" -) - -func NewBuffer() Buffer { - return Buffer{cronet.BufferCreate()} -} - -func (b Buffer) Destroy() { - cronet.BufferDestroy(b.ptr) -} - -func (b Buffer) SetClientContext(context unsafe.Pointer) { - cronet.BufferSetClientContext(b.ptr, uintptr(context)) -} - -func (b Buffer) ClientContext() unsafe.Pointer { - return unsafe.Pointer(cronet.BufferGetClientContext(b.ptr)) -} - -// InitWithDataAndCallback initializes Buffer with raw buffer |data| allocated by the app. -// The |callback| is invoked when buffer is destroyed. -func (b Buffer) InitWithDataAndCallback(data []byte, callback BufferCallback) { - if len(data) == 0 { - cronet.BufferInitWithDataAndCallback(b.ptr, 0, 0, callback.ptr) - return - } - cronet.BufferInitWithDataAndCallback(b.ptr, uintptr(unsafe.Pointer(&data[0])), uint64(len(data)), callback.ptr) -} - -// InitWithAlloc initializes Buffer by allocating buffer of |size|. -// The content of allocated data is not initialized. -func (b Buffer) InitWithAlloc(size int64) { - cronet.BufferInitWithAlloc(b.ptr, uint64(size)) -} - -// Size returns size of data owned by this buffer. -func (b Buffer) Size() int64 { - return int64(cronet.BufferGetSize(b.ptr)) -} - -// Data returns raw pointer to |data| owned by this buffer. -func (b Buffer) Data() unsafe.Pointer { - return unsafe.Pointer(cronet.BufferGetData(b.ptr)) -} - -func (b Buffer) DataSlice() []byte { - size := b.Size() - if size == 0 { - return nil - } - return unsafe.Slice((*byte)(b.Data()), size) -} diff --git a/callback_types.go b/callback_types.go deleted file mode 100644 index c9417db9..00000000 --- a/callback_types.go +++ /dev/null @@ -1,38 +0,0 @@ -package cronet - -// ExecutorExecuteFunc takes ownership of |command| and runs it synchronously or asynchronously. -// Destroys the |command| after execution, or if executor is shutting down. -type ExecutorExecuteFunc func(executor Executor, command Runnable) - -// RunnableRunFunc is the function type for Runnable.Run callback. -type RunnableRunFunc func(self Runnable) - -// BufferCallbackFunc is called when the Buffer is destroyed. -type BufferCallbackFunc func(callback BufferCallback, buffer Buffer) - -// URLRequestStatusListenerOnStatusFunc is called with the status of a URL request. -type URLRequestStatusListenerOnStatusFunc func(self URLRequestStatusListener, status URLRequestStatusListenerStatus) - -// URLRequestFinishedInfoListenerOnRequestFinishedFunc is called when a request finishes. -type URLRequestFinishedInfoListenerOnRequestFinishedFunc func(listener URLRequestFinishedInfoListener, requestInfo RequestFinishedInfo, responseInfo URLResponseInfo, error Error) - -// URLRequestCallbackHandler handles callbacks from URLRequest. -type URLRequestCallbackHandler interface { - // OnRedirectReceived is invoked whenever a redirect is encountered. - OnRedirectReceived(self URLRequestCallback, request URLRequest, info URLResponseInfo, newLocationUrl string) - - // OnResponseStarted is invoked when the final set of headers, after all redirects, is received. - OnResponseStarted(self URLRequestCallback, request URLRequest, info URLResponseInfo) - - // OnReadCompleted is invoked whenever part of the response body has been read. - OnReadCompleted(self URLRequestCallback, request URLRequest, info URLResponseInfo, buffer Buffer, bytesRead int64) - - // OnSucceeded is invoked when request is completed successfully. - OnSucceeded(self URLRequestCallback, request URLRequest, info URLResponseInfo) - - // OnFailed is invoked if request failed for any reason after URLRequest.Start(). - OnFailed(self URLRequestCallback, request URLRequest, info URLResponseInfo, error Error) - - // OnCanceled is invoked if request was canceled via URLRequest.Cancel(). - OnCanceled(self URLRequestCallback, request URLRequest, info URLResponseInfo) -} diff --git a/cmd/build-naive/cmd_extract_lib.go b/cmd/build-naive/cmd_extract_lib.go deleted file mode 100644 index bad59523..00000000 --- a/cmd/build-naive/cmd_extract_lib.go +++ /dev/null @@ -1,120 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "log" - "os" - "path/filepath" - "strings" - - "github.com/spf13/cobra" -) - -var ( - commandExtractLib = &cobra.Command{ - Use: "extract-lib", - Short: "Extract dynamic libraries from Go module dependencies", - Long: `Extract dynamic libraries (.so/.dll) from Go module dependencies. - -This command downloads the cronet-go lib submodule for the specified target -and extracts the dynamic library to the output directory. - -Supported targets: - - linux/amd64, linux/arm64, linux/386, linux/arm (glibc only) - - windows/amd64, windows/arm64 - -Not supported (use static linking via CGO instead): - - Linux musl (static only) - - macOS, iOS, tvOS, Android`, - Run: func(cmd *cobra.Command, args []string) { - targets := parseTargets() - extractLibraries(targets) - }, - } - outputDirectory string - outputName string -) - -func init() { - mainCommand.AddCommand(commandExtractLib) - commandExtractLib.Flags().StringVarP(&outputDirectory, "output", "o", ".", - "Output directory for extracted libraries") - commandExtractLib.Flags().StringVarP(&outputName, "name", "n", "", - "Output filename (default: libcronet.so or libcronet.dll)") -} - -func extractLibraries(targets []Target) { - if err := os.MkdirAll(outputDirectory, 0o755); err != nil { - log.Fatalf("failed to create output directory: %v", err) - } - - for _, target := range targets { - extractLibrary(target) - } - - log.Print("Extract complete!") -} - -func extractLibrary(target Target) { - libraryFilename := getDynamicLibraryFilename(target) - if libraryFilename == "" { - log.Fatalf("target %s/%s does not have a dynamic library available (use static linking via CGO instead)", - target.GOOS, target.ARCH) - } - - directoryName := getLibraryDirectoryName(target) - - // Get the latest commit from the go branch - goBranchCommit := runCommandOutput(".", "git", "ls-remote", "https://github.com/sagernet/cronet-go.git", "refs/heads/go") - if goBranchCommit == "" { - log.Fatal("failed to get go branch commit") - } - // Output format: "\trefs/heads/go\n" - commitHash := strings.Fields(goBranchCommit)[0] - - modulePath := fmt.Sprintf("github.com/sagernet/cronet-go/lib/%s@%s", directoryName, commitHash) - - log.Printf("Downloading module %s...", modulePath) - - output := runCommandOutput(".", "go", "mod", "download", "-json", modulePath) - - var moduleInfo struct { - Dir string `json:"Dir"` - } - if err := json.Unmarshal([]byte(output), &moduleInfo); err != nil { - log.Fatalf("failed to parse module info: %v", err) - } - - if moduleInfo.Dir == "" { - log.Fatalf("module directory not found in download output") - } - - sourcePath := filepath.Join(moduleInfo.Dir, libraryFilename) - if _, err := os.Stat(sourcePath); os.IsNotExist(err) { - log.Fatalf("library file not found: %s", sourcePath) - } - - destinationFilename := libraryFilename - if outputName != "" { - destinationFilename = outputName - } - destinationPath := filepath.Join(outputDirectory, destinationFilename) - copyFile(sourcePath, destinationPath) - - log.Printf("Extracted %s to %s", destinationFilename, outputDirectory) -} - -func getDynamicLibraryFilename(target Target) string { - switch target.GOOS { - case "windows": - return "libcronet.dll" - case "linux": - if target.Libc == "musl" { - return "" // musl builds are static only - } - return "libcronet.so" - default: - return "" // macOS, iOS, tvOS, Android use static libraries - } -} diff --git a/cmd/build-naive/cmd_generate_net_errors.go b/cmd/build-naive/cmd_generate_net_errors.go deleted file mode 100644 index d4a4c3fb..00000000 --- a/cmd/build-naive/cmd_generate_net_errors.go +++ /dev/null @@ -1,214 +0,0 @@ -package main - -import ( - "bufio" - "bytes" - "fmt" - "go/format" - "log" - "os" - "path/filepath" - "regexp" - "sort" - "strconv" - "strings" - "unicode" - - "github.com/spf13/cobra" -) - -var commandGenerateNetErrors = &cobra.Command{ - Use: "generate-net-errors", - Short: "Generate Go error constants from Chromium's net_error_list.h", - Run: runGenerateNetErrors, -} - -func init() { - mainCommand.AddCommand(commandGenerateNetErrors) -} - -type netErrorEntry struct { - name string - code int - description string - message string -} - -func runGenerateNetErrors(cmd *cobra.Command, args []string) { - sourceFile := filepath.Join(srcRoot, "net", "base", "net_error_list.h") - outputFile := filepath.Join(projectRoot, "net_error_generated.go") - - errors, err := parseNetErrorList(sourceFile) - if err != nil { - log.Fatalf("failed to parse %s: %v", sourceFile, err) - } - - err = generateNetErrorGoFile(errors, outputFile) - if err != nil { - log.Fatalf("failed to generate %s: %v", outputFile, err) - } - - log.Printf("generated %s with %d error codes", outputFile, len(errors)) -} - -var netErrorRegex = regexp.MustCompile(`NET_ERROR\(\s*(\w+)\s*,\s*(-?\d+)\s*\)`) - -func parseNetErrorList(filename string) ([]netErrorEntry, error) { - file, err := os.Open(filename) - if err != nil { - return nil, err - } - defer file.Close() - - var errors []netErrorEntry - var commentLines []string - scanner := bufio.NewScanner(file) - - for scanner.Scan() { - line := scanner.Text() - trimmed := strings.TrimSpace(line) - - if strings.HasPrefix(trimmed, "//") { - comment := strings.TrimPrefix(trimmed, "//") - comment = strings.TrimSpace(comment) - if !strings.Contains(comment, "no-include-guard") && - !strings.Contains(comment, "NOLINT") && - comment != "" { - commentLines = append(commentLines, comment) - } - continue - } - - if matches := netErrorRegex.FindStringSubmatch(line); matches != nil { - name := matches[1] - code, _ := strconv.Atoi(matches[2]) - - if strings.HasSuffix(name, "_END") { - commentLines = nil - continue - } - - description := buildNetErrorDescription(commentLines) - message := netErrorNameToMessage(name) - - errors = append(errors, netErrorEntry{ - name: name, - code: code, - description: description, - message: message, - }) - commentLines = nil - continue - } - - if trimmed == "" || (!strings.HasPrefix(trimmed, "//") && !strings.HasPrefix(trimmed, "NET_ERROR")) { - if trimmed != "" && !strings.HasPrefix(trimmed, "#") { - commentLines = nil - } - } - } - - return errors, scanner.Err() -} - -func buildNetErrorDescription(comments []string) string { - if len(comments) == 0 { - return "" - } - - var filtered []string - for _, c := range comments { - if strings.HasPrefix(c, "Ranges:") || - regexp.MustCompile(`^\d+-\d+\s+`).MatchString(c) || - strings.HasPrefix(c, "0-") { - continue - } - if strings.Contains(c, "was removed") { - continue - } - if strings.Contains(c, "is reserved") { - continue - } - filtered = append(filtered, c) - } - - return strings.Join(filtered, " ") -} - -func netErrorNameToMessage(name string) string { - words := strings.Split(strings.ToLower(name), "_") - if len(words) > 0 && words[0] == "err" { - words = words[1:] - } - return strings.Join(words, " ") -} - -func netErrorNameToGoName(name string) string { - parts := strings.Split(strings.ToLower(name), "_") - var result strings.Builder - for _, part := range parts { - if part == "" { - continue - } - switch strings.ToUpper(part) { - case "IO", "IP", "SSL", "TLS", "HTTP", "DNS", "URL", "TCP", "UDP", "QUIC", "CT", "PAC", "PKCS", "RST", "FIN", "ACK", "SOCKS", "MAC", "DH", "ECH", "CSP", "ORB": - result.WriteString(strings.ToUpper(part)) - case "HTTP2": - result.WriteString("HTTP2") - default: - runes := []rune(part) - runes[0] = unicode.ToUpper(runes[0]) - result.WriteString(string(runes)) - } - } - return result.String() -} - -func generateNetErrorGoFile(errors []netErrorEntry, filename string) error { - var buffer bytes.Buffer - - buffer.WriteString(`// Code generated by cmd/build-naive generate-net-errors. DO NOT EDIT. -// Source: naiveproxy/src/net/base/net_error_list.h - -package cronet - -// NetError constants from Chromium's net_error_list.h -const ( -`) - - for _, entry := range errors { - goName := "NetError" + netErrorNameToGoName(entry.name) - buffer.WriteString(fmt.Sprintf("\t%s NetError = %d\n", goName, entry.code)) - } - - buffer.WriteString(`) - -type netErrorEntry struct { - code NetError - name string - message string - description string -} - -`) - - sorted := make([]netErrorEntry, len(errors)) - copy(sorted, errors) - sort.Slice(sorted, func(i, j int) bool { return sorted[i].code < sorted[j].code }) - - buffer.WriteString(fmt.Sprintf("var netErrorInfo = [%d]netErrorEntry{\n", len(sorted))) - for _, entry := range sorted { - goName := "NetError" + netErrorNameToGoName(entry.name) - name := fmt.Sprintf("ERR_%s", entry.name) - buffer.WriteString(fmt.Sprintf("\t{%s, %q, %q, %q},\n", goName, name, entry.message, entry.description)) - } - buffer.WriteString("}\n") - - formatted, err := format.Source(buffer.Bytes()) - if err != nil { - os.WriteFile(filename+".unformatted", buffer.Bytes(), 0o644) - return fmt.Errorf("failed to format generated code: %w", err) - } - - return os.WriteFile(filename, formatted, 0o644) -} diff --git a/cmd/build-naive/cmd_package.go b/cmd/build-naive/cmd_package.go index 7576464f..88ca2ffa 100644 --- a/cmd/build-naive/cmd_package.go +++ b/cmd/build-naive/cmd_package.go @@ -12,21 +12,17 @@ import ( "github.com/spf13/cobra" ) -var ( - commandPackage = &cobra.Command{ - Use: "package", - Short: "Package libraries and generate CGO config files", - Run: func(cmd *cobra.Command, args []string) { - targets := parseTargets() - packageTargets(targets) - }, - } - localMode bool -) +var commandPackage = &cobra.Command{ + Use: "package", + Short: "Copy built libraries and C headers into lib/ and include/", + Run: func(cmd *cobra.Command, args []string) { + targets := parseTargets() + packageTargets(targets) + }, +} func init() { mainCommand.AddCommand(commandPackage) - commandPackage.Flags().BoolVar(&localMode, "local", false, "Generate CGO files in main module for local testing") } func packageTargets(targets []Target) { @@ -55,13 +51,15 @@ func packageTargets(targets []Target) { log.Print("Copied headers to include/") for _, t := range targets { - targetDirectory := filepath.Join(libraryDirectory, getLibraryDirectoryName(t)) + directoryName := getLibraryDirectoryName(t) + targetDirectory := filepath.Join(libraryDirectory, directoryName) os.MkdirAll(targetDirectory, 0o755) outputDirectory := getOutputDirectory(t) if t.GOOS == "windows" { - // Windows: only copy DLL (static linking not supported - Chromium uses MSVC, Go CGO only supports MinGW) + // Windows: only a DLL is produced (static linking not supported - + // Chromium uses MSVC, no MinGW static lib). sourceDLL := filepath.Join(srcRoot, outputDirectory, "cronet.dll") destinationDLL := filepath.Join(targetDirectory, "libcronet.dll") if _, err := os.Stat(sourceDLL); os.IsNotExist(err) { @@ -70,105 +68,72 @@ func packageTargets(targets []Target) { copyFile(sourceDLL, destinationDLL) log.Printf("Copied DLL for %s/%s", t.GOOS, t.ARCH) } + log.Printf("Packaged lib/%s", directoryName) + continue + } + + sourceStatic := filepath.Join(srcRoot, outputDirectory, "obj/components/cronet/libcronet_static.a") + destinationStatic := filepath.Join(targetDirectory, "libcronet.a") + if _, err := os.Stat(sourceStatic); os.IsNotExist(err) { + log.Printf("Warning: static library not found for %s, skipping", formatTargetLog(t)) } else { - sourceStatic := filepath.Join(srcRoot, outputDirectory, "obj/components/cronet/libcronet_static.a") - destinationStatic := filepath.Join(targetDirectory, "libcronet.a") - if _, err := os.Stat(sourceStatic); os.IsNotExist(err) { - log.Printf("Warning: static library not found for %s, skipping", formatTargetLog(t)) - } else { - copyFile(sourceStatic, destinationStatic) - log.Printf("Copied static library for %s", formatTargetLog(t)) - } + copyFile(sourceStatic, destinationStatic) + log.Printf("Copied static library for %s", formatTargetLog(t)) + } - // For Linux glibc, also copy shared library (for purego mode and release distribution) - if t.GOOS == "linux" && t.Libc != "musl" { - sourceShared := filepath.Join(srcRoot, outputDirectory, "libcronet.so") - destinationShared := filepath.Join(targetDirectory, "libcronet.so") - if _, err := os.Stat(sourceShared); err == nil { - copyFile(sourceShared, destinationShared) - log.Printf("Copied shared library for %s", formatTargetLog(t)) - } + // For Linux glibc, also copy the shared library. + if t.GOOS == "linux" && t.Libc != "musl" { + sourceShared := filepath.Join(srcRoot, outputDirectory, "libcronet.so") + destinationShared := filepath.Join(targetDirectory, "libcronet.so") + if _, err := os.Stat(sourceShared); err == nil { + copyFile(sourceShared, destinationShared) + log.Printf("Copied shared library for %s", formatTargetLog(t)) } } - } - generateCGOConfig() - if localMode { - generateLocalCGOFiles(targets) - } else { - generateSubmodules(targets) + // Dump the system link flags so the static library can actually be + // linked by a downstream C/C++ project. + writeLinkFlags(t, targetDirectory, outputDirectory) + + log.Printf("Packaged lib/%s", directoryName) } log.Print("Package complete!") } -func generateCGOConfig() { - content := `//go:build !with_purego - -package cronet - -// #cgo CFLAGS: -I${SRCDIR}/include -import "C" -` - path := filepath.Join(projectRoot, "include_cgo.go") - err := os.WriteFile(path, []byte(content), 0o644) +// writeLinkFlags extracts the libs/frameworks/ldflags that the static library +// depends on (parsed from the generated ninja file) and writes them to a plain +// text file next to the library. +func writeLinkFlags(t Target, targetDirectory, outputDirectory string) { + flags, err := extractLinkFlags(outputDirectory) if err != nil { - log.Fatalf("failed to write include_cgo.go: %v", err) + log.Printf("Warning: could not extract link flags for %s: %v", formatTargetLog(t), err) + return } - log.Print("Generated include_cgo.go") -} - -func generateLocalCGOFiles(targets []Target) { - for _, t := range targets { - if t.GOOS == "windows" { - log.Printf("Skipping local CGO file for %s (static linking not supported)", formatTargetLog(t)) - continue - } - - directoryName := getLibraryDirectoryName(t) - outputDirectory := getOutputDirectory(t) - - linkFlags, err := extractLinkFlags(outputDirectory) - if err != nil { - log.Fatalf("failed to extract link flags for %s: %v", formatTargetLog(t), err) - } - - buildTag := getBuildTag(t) - - var ldFlags []string - libraryPath := fmt.Sprintf("${SRCDIR}/lib/%s/libcronet.a", directoryName) - if t.GOOS == "darwin" || t.GOOS == "ios" { - ldFlags = append(ldFlags, libraryPath) - } else { - ldFlags = append(ldFlags, fmt.Sprintf("-L${SRCDIR}/lib/%s", directoryName), "-l:libcronet.a") - } - - ldFlags = append(ldFlags, linkFlags.LDFlags...) - ldFlags = append(ldFlags, linkFlags.Libs...) - ldFlags = append(ldFlags, linkFlags.Frameworks...) - - if t.GOOS == "linux" && t.Libc == "musl" { - ldFlags = append(ldFlags, "-static") - } - - cgoContent := fmt.Sprintf(`//go:build %s && !with_purego - -package cronet - -// #cgo LDFLAGS: %s -import "C" -`, buildTag, strings.Join(ldFlags, " ")) - - fileName := fmt.Sprintf("lib_%s_cgo.go", directoryName) - cgoPath := filepath.Join(projectRoot, fileName) - err = os.WriteFile(cgoPath, []byte(cgoContent), 0o644) - if err != nil { - log.Fatalf("failed to write %s: %v", fileName, err) - } + var b strings.Builder + if len(flags.LDFlags) > 0 { + fmt.Fprintf(&b, "# ldflags\n%s\n", strings.Join(flags.LDFlags, " ")) + } + if len(flags.Libs) > 0 { + fmt.Fprintf(&b, "# libs\n%s\n", strings.Join(flags.Libs, " ")) + } + if len(flags.Frameworks) > 0 { + fmt.Fprintf(&b, "# frameworks\n%s\n", strings.Join(flags.Frameworks, " ")) + } + if t.GOOS == "linux" && t.Libc == "musl" { + b.WriteString("# extra\n-static\n") + } + if b.Len() == 0 { + return + } - log.Printf("Generated %s", fileName) + path := filepath.Join(targetDirectory, "link_flags.txt") + if err := os.WriteFile(path, []byte(b.String()), 0o644); err != nil { + log.Printf("Warning: failed to write link_flags.txt for %s: %v", formatTargetLog(t), err) + return } + log.Printf("Wrote link flags for %s", formatTargetLog(t)) } func getLibraryDirectoryName(t Target) string { @@ -190,42 +155,6 @@ func getLibraryDirectoryName(t Target) string { return name } -func getBuildTag(t Target) string { - // iOS/tvOS use gomobile-compatible tags - if t.GOOS == "ios" { - parts := []string{"ios", t.ARCH} - - if t.Platform == "tvos" { - parts = append(parts, "tvos") - if t.Environment == "simulator" { - parts = append(parts, "tvossimulator") - } else { - parts = append(parts, "!tvossimulator") - } - } else { - parts = append(parts, "!tvos") - if t.Environment == "simulator" { - parts = append(parts, "iossimulator") - } else { - parts = append(parts, "!iossimulator") - } - } - - return strings.Join(parts, " && ") - } - - if t.Libc == "musl" { - return fmt.Sprintf("%s && !android && %s && with_musl", t.GOOS, t.ARCH) - } - if t.GOOS == "linux" { - return fmt.Sprintf("%s && !android && %s && !with_musl", t.GOOS, t.ARCH) - } - if t.GOOS == "darwin" { - return fmt.Sprintf("%s && !ios && %s", t.GOOS, t.ARCH) - } - return fmt.Sprintf("%s && %s", t.GOOS, t.ARCH) -} - type LinkFlags struct { Libs []string Frameworks []string @@ -253,8 +182,7 @@ func extractLinkFlags(outputDirectory string) (LinkFlags, error) { libsStr := strings.TrimSpace(matches[1]) if libsStr != "" { for _, lib := range strings.Fields(libsStr) { - // Filter out linker scripts (.lds) - they're not needed for - // static linking and Go's CGO rejects them as invalid flags + // Filter out linker scripts (.lds) - not needed for static linking. if !strings.HasSuffix(lib, ".lds") { flags.Libs = append(flags.Libs, lib) } @@ -299,129 +227,10 @@ func parseFrameworks(input string) []string { func parseLDFlags(input string) []string { var result []string for _, flag := range strings.Fields(input) { - // Extract -Wl,-wrap,* flags needed for Android allocator shim + // Extract -Wl,-wrap,* flags needed for Android allocator shim. if strings.HasPrefix(flag, "-Wl,-wrap,") { result = append(result, flag) } } return result } - -func generateSubmodules(targets []Target) { - versionFile := filepath.Join(naiveRoot, "CHROMIUM_VERSION") - versionData, err := os.ReadFile(versionFile) - if err != nil { - log.Fatalf("failed to read CHROMIUM_VERSION: %v", err) - } - chromiumVersion := strings.TrimSpace(string(versionData)) - - for _, t := range targets { - directoryName := getLibraryDirectoryName(t) - targetDirectory := filepath.Join(projectRoot, "lib", directoryName) - packageName := strings.ReplaceAll(directoryName, "-", "_") - - goModContent := fmt.Sprintf(`module github.com/sagernet/cronet-go/lib/%s - -go 1.20 -`, directoryName) - goModPath := filepath.Join(targetDirectory, "go.mod") - err := os.WriteFile(goModPath, []byte(goModContent), 0o644) - if err != nil { - log.Fatalf("failed to write go.mod: %v", err) - } - - if t.GOOS == "windows" { - // Windows: only generate purego mode files (version constant only) - // Static linking is not supported (Chromium uses MSVC, Go CGO only supports MinGW) - // DLL is copied to lib directory for downstream extraction, must be distributed alongside binary - generateWindowsPuregoFile(targetDirectory, packageName, chromiumVersion) - runCommand(targetDirectory, "go", "mod", "tidy") - log.Printf("Generated submodule lib/%s (purego only)", directoryName) - continue - } - - outputDirectory := getOutputDirectory(t) - linkFlags, err := extractLinkFlags(outputDirectory) - if err != nil { - log.Fatalf("failed to extract link flags for %s: %v", formatTargetLog(t), err) - } - - log.Printf("Extracted link flags for %s: libs=%v frameworks=%v ldflags=%v", formatTargetLog(t), linkFlags.Libs, linkFlags.Frameworks, linkFlags.LDFlags) - - buildTag := getBuildTag(t) - - var ldFlags []string - - if t.GOOS == "darwin" || t.GOOS == "ios" { - ldFlags = append(ldFlags, "${SRCDIR}/libcronet.a") - } else { - ldFlags = append(ldFlags, "-L${SRCDIR}", "-l:libcronet.a") - } - - ldFlags = append(ldFlags, linkFlags.LDFlags...) - ldFlags = append(ldFlags, linkFlags.Libs...) - ldFlags = append(ldFlags, linkFlags.Frameworks...) - - if t.GOOS == "linux" && t.Libc == "musl" { - ldFlags = append(ldFlags, "-static") - } - - cgoContent := fmt.Sprintf(`//go:build %s && !with_purego - -package %s - -// #cgo LDFLAGS: %s -import "C" - -const Version = "%s" -`, buildTag, packageName, strings.Join(ldFlags, " "), chromiumVersion) - - cgoPath := filepath.Join(targetDirectory, "libcronet_cgo.go") - err = os.WriteFile(cgoPath, []byte(cgoContent), 0o644) - if err != nil { - log.Fatalf("failed to write libcronet_cgo.go: %v", err) - } - - // Generate purego stub file for non-Windows platforms - // This allows the package to compile in purego mode (user must provide .so/.dylib) - generatePuregoStubFile(targetDirectory, packageName, chromiumVersion) - - runCommand(targetDirectory, "go", "mod", "tidy") - - log.Printf("Generated submodule lib/%s", directoryName) - } -} - -func generatePuregoStubFile(targetDirectory, packageName, chromiumVersion string) { - content := fmt.Sprintf(`//go:build with_purego - -package %s - -const Version = "%s" -`, packageName, chromiumVersion) - - stubPath := filepath.Join(targetDirectory, "libcronet_purego.go") - err := os.WriteFile(stubPath, []byte(content), 0o644) - if err != nil { - log.Fatalf("failed to write libcronet_purego.go: %v", err) - } - log.Printf("Generated libcronet_purego.go for %s", packageName) -} - -func generateWindowsPuregoFile(targetDirectory, packageName, chromiumVersion string) { - // Windows: generate a simple version file (DLL must be distributed alongside the binary) - // The DLL file is still copied to the lib directory for downstream extraction - content := fmt.Sprintf(`//go:build with_purego - -package %s - -const Version = "%s" -`, packageName, chromiumVersion) - - filePath := filepath.Join(targetDirectory, "libcronet.go") - err := os.WriteFile(filePath, []byte(content), 0o644) - if err != nil { - log.Fatalf("failed to write libcronet.go: %v", err) - } - log.Printf("Generated libcronet.go for %s (version only)", packageName) -} diff --git a/cmd/build-naive/cmd_publish.go b/cmd/build-naive/cmd_publish.go deleted file mode 100644 index 08e0c91a..00000000 --- a/cmd/build-naive/cmd_publish.go +++ /dev/null @@ -1,292 +0,0 @@ -package main - -import ( - "fmt" - "log" - "os" - "os/exec" - "path/filepath" - "strings" - "time" - - "github.com/spf13/cobra" -) - -var publishBranch string - -var commandPublish = &cobra.Command{ - Use: "publish", - Short: "Commit to go branch and push", - Run: func(cmd *cobra.Command, args []string) { - publish() - }, -} - -func init() { - commandPublish.Flags().StringVar(&publishBranch, "branch", "go", "Target branch to publish to") - mainCommand.AddCommand(commandPublish) -} - -func publish() { - log.Printf("Publishing to %s branch...", publishBranch) - - mainCommit := strings.TrimSpace(runCommandOutput(projectRoot, "git", "rev-parse", "HEAD")) - - temporaryDirectory, err := os.MkdirTemp("", "cronet-go-publish-") - if err != nil { - log.Fatalf("failed to create temp dir: %v", err) - } - defer func() { - runCommand(projectRoot, "git", "worktree", "remove", "--force", temporaryDirectory) - os.RemoveAll(temporaryDirectory) - }() - - runCommand(projectRoot, "git", "worktree", "add", temporaryDirectory, "HEAD") - - // === Step 1: Push main module + lib submodules === - log.Print("Step 1: Publishing main module and lib submodules...") - - copyDirectory(filepath.Join(projectRoot, "lib"), filepath.Join(temporaryDirectory, "lib")) - copyDirectory(filepath.Join(projectRoot, "include"), filepath.Join(temporaryDirectory, "include")) - copyFile(filepath.Join(projectRoot, "include_cgo.go"), filepath.Join(temporaryDirectory, "include_cgo.go")) - - // Use -f (force add) to include .gitignore'd files - runCommand(temporaryDirectory, "git", "add", "-f", "-A") - commitMessage := fmt.Sprintf("Build from %s", mainCommit[:8]) - runCommand(temporaryDirectory, "git", "commit", "-m", commitMessage) - - runCommand(temporaryDirectory, "git", "push", "-f", "origin", "HEAD:refs/heads/"+publishBranch) - - firstCommit := strings.TrimSpace(runCommandOutput(temporaryDirectory, "git", "rev-parse", "HEAD")) - commitTime := getCommitTime(temporaryDirectory, firstCommit) - pseudoVersion := formatPseudoVersion(commitTime, firstCommit) - - log.Printf("First commit: %s, pseudo-version: %s", firstCommit[:12], pseudoVersion) - - // === Step 2: Generate and push all package === - log.Print("Step 2: Generating all package...") - - libDirectory := filepath.Join(temporaryDirectory, "lib") - libEntries, err := os.ReadDir(libDirectory) - if err != nil { - log.Fatalf("failed to read lib directory: %v", err) - } - - var builtTargets []string - for _, entry := range libEntries { - if entry.IsDir() { - builtTargets = append(builtTargets, entry.Name()) - } - } - - if len(builtTargets) == 0 { - log.Fatal("no lib directories found") - } - - generateAllPackage(temporaryDirectory, pseudoVersion, builtTargets) - - // Fix lib submodules' go.mod to use correct pseudo-version - // (package stage's go mod tidy may have selected wrong version) - fixLibSubmoduleVersions(filepath.Join(temporaryDirectory, "lib"), builtTargets, pseudoVersion) - - // Use GOPROXY=direct to avoid proxy caching issues when using the new pseudo-version - runGoModTidy(filepath.Join(temporaryDirectory, "all")) - - // Force correct version after tidy (tidy may select a higher tagged version due to MVS) - forceMainModuleVersion(filepath.Join(temporaryDirectory, "all"), pseudoVersion) - - runCommand(temporaryDirectory, "git", "add", "-f", "-A") - runCommand(temporaryDirectory, "git", "commit", "-m", "Generate all package") - runCommand(temporaryDirectory, "git", "push", "origin", "HEAD:"+publishBranch) - - log.Printf("Published to %s branch!", publishBranch) -} - -func formatPseudoVersion(commitTime time.Time, commitHash string) string { - timestamp := commitTime.UTC().Format("20060102150405") - return fmt.Sprintf("v0.0.0-%s-%s", timestamp, commitHash[:12]) -} - -func getCommitTime(directory, commitHash string) time.Time { - output := runCommandOutput(directory, "git", "show", "-s", "--format=%cI", commitHash) - t, err := time.Parse(time.RFC3339, strings.TrimSpace(output)) - if err != nil { - log.Fatalf("failed to parse commit time: %v", err) - } - return t -} - -func generateAllPackage(directory, pseudoVersion string, builtTargets []string) { - allDirectory := filepath.Join(directory, "all") - err := os.MkdirAll(allDirectory, 0o755) - if err != nil { - log.Fatalf("failed to create all directory: %v", err) - } - - generateAllGoMod(allDirectory, pseudoVersion, builtTargets) - - for _, targetName := range builtTargets { - generatePlatformImportFile(allDirectory, targetName) - } - - log.Printf("Generated all package with %d platforms", len(builtTargets)) -} - -func generateAllGoMod(allDirectory, pseudoVersion string, builtTargets []string) { - var builder strings.Builder - builder.WriteString("module github.com/sagernet/cronet-go/all\n\n") - builder.WriteString("go 1.20\n\n") - builder.WriteString("require (\n") - builder.WriteString(fmt.Sprintf("\tgithub.com/sagernet/cronet-go %s\n", pseudoVersion)) - for _, targetName := range builtTargets { - builder.WriteString(fmt.Sprintf("\tgithub.com/sagernet/cronet-go/lib/%s %s\n", targetName, pseudoVersion)) - } - builder.WriteString(")\n") - - goModPath := filepath.Join(allDirectory, "go.mod") - err := os.WriteFile(goModPath, []byte(builder.String()), 0o644) - if err != nil { - log.Fatalf("failed to write go.mod: %v", err) - } -} - -func generatePlatformImportFile(allDirectory, targetName string) { - buildTag := getBuildTagForTarget(targetName) - packageName := strings.ReplaceAll(targetName, "-", "_") - - content := fmt.Sprintf(`//go:build %s - -package all - -import ( - _ "github.com/sagernet/cronet-go" - _ "github.com/sagernet/cronet-go/lib/%s" -) -`, buildTag, targetName) - - fileName := packageName + ".go" - filePath := filepath.Join(allDirectory, fileName) - err := os.WriteFile(filePath, []byte(content), 0o644) - if err != nil { - log.Fatalf("failed to write %s: %v", fileName, err) - } -} - -// getBuildTagForTarget returns the build tag for a given target directory name. -// Directory names follow the pattern: {platform}_{goarch}[_simulator][_musl] -// where platform is goos (linux, darwin, windows, android) or tvos/ios for Apple platforms. -func getBuildTagForTarget(targetName string) string { - parts := strings.Split(targetName, "_") - if len(parts) < 2 { - log.Fatalf("invalid target name: %s", targetName) - } - - goos := parts[0] - goarch := parts[1] - - isSimulator := false - isMusl := false - isTvOS := false - - for i := 2; i < len(parts); i++ { - switch parts[i] { - case "simulator": - isSimulator = true - case "musl": - isMusl = true - } - } - - // Handle tvOS: directory prefix is "tvos" but GOOS is "ios" - if goos == "tvos" { - isTvOS = true - goos = "ios" - } - - // Handle iOS/tvOS with gomobile-compatible tags - if goos == "ios" { - tagParts := []string{"ios", goarch} - - if isTvOS { - tagParts = append(tagParts, "tvos") - if isSimulator { - tagParts = append(tagParts, "tvossimulator") - } else { - tagParts = append(tagParts, "!tvossimulator") - } - } else { - tagParts = append(tagParts, "!tvos") - if isSimulator { - tagParts = append(tagParts, "iossimulator") - } else { - tagParts = append(tagParts, "!iossimulator") - } - } - - return strings.Join(tagParts, " && ") - } - - if isMusl { - return fmt.Sprintf("%s && !android && %s && with_musl", goos, goarch) - } - - if goos == "linux" { - return fmt.Sprintf("%s && !android && %s && !with_musl", goos, goarch) - } - - if goos == "darwin" { - return fmt.Sprintf("%s && !ios && %s", goos, goarch) - } - - // Windows: purego only - if goos == "windows" { - return fmt.Sprintf("%s && %s && with_purego", goos, goarch) - } - - return fmt.Sprintf("%s && %s", goos, goarch) -} - -func runGoModTidy(directory string) { - log.Printf("Running go mod tidy in %s with GOPROXY=direct...", directory) - command := exec.Command("go", "mod", "tidy") - command.Dir = directory - command.Env = append(os.Environ(), "GOPROXY=direct", "GOSUMDB=off") - command.Stdout = os.Stdout - command.Stderr = os.Stderr - err := command.Run() - if err != nil { - log.Fatalf("go mod tidy failed: %v", err) - } -} - -func forceMainModuleVersion(directory, version string) { - log.Printf("Forcing main module version to %s...", version) - runCommand(directory, "go", "mod", "edit", "-require=github.com/sagernet/cronet-go@"+version) -} - -func fixLibSubmoduleVersions(libDirectory string, targets []string, version string) { - log.Printf("Fixing lib submodule versions to %s...", version) - for _, targetName := range targets { - submoduleDirectory := filepath.Join(libDirectory, targetName) - goModPath := filepath.Join(submoduleDirectory, "go.mod") - - // Check if go.mod exists - if _, err := os.Stat(goModPath); os.IsNotExist(err) { - continue - } - - // Check if this submodule depends on main module - content, err := os.ReadFile(goModPath) - if err != nil { - log.Fatalf("failed to read %s: %v", goModPath, err) - } - - if !strings.Contains(string(content), "github.com/sagernet/cronet-go") { - continue - } - - // Force correct version - runCommand(submoduleDirectory, "go", "mod", "edit", "-require=github.com/sagernet/cronet-go@"+version) - log.Printf(" Fixed lib/%s", targetName) - } -} diff --git a/cmd/build-naive/cmd_sync.go b/cmd/build-naive/cmd_sync.go deleted file mode 100644 index 71d0cbbf..00000000 --- a/cmd/build-naive/cmd_sync.go +++ /dev/null @@ -1,116 +0,0 @@ -package main - -import ( - "fmt" - "log" - "net/http" - "os" - "os/exec" - "path/filepath" - "strings" - - "github.com/spf13/cobra" -) - -var commandSync = &cobra.Command{ - Use: "sync", - Short: "Download Chromium cronet components", - Run: func(cmd *cobra.Command, args []string) { - sync() - }, -} - -func init() { - mainCommand.AddCommand(commandSync) -} - -func sync() { - log.Print("Syncing Chromium cronet components...") - - versionFile := filepath.Join(naiveRoot, "CHROMIUM_VERSION") - versionData, err := os.ReadFile(versionFile) - if err != nil { - log.Fatalf("failed to read CHROMIUM_VERSION: %v", err) - } - version := strings.TrimSpace(string(versionData)) - log.Printf("Chromium version: %s", version) - - cronetDirectory := filepath.Join(srcRoot, "components", "cronet") - if _, err := os.Stat(cronetDirectory); err == nil { - status := runCommandOutput(naiveRoot, "git", "status", "--porcelain", "src/components/cronet") - if strings.TrimSpace(status) == "" { - log.Print("Components already up to date") - return - } - } - - components := []string{"cronet", "prefs"} - - for _, name := range components { - log.Printf("Downloading %s...", name) - - url := fmt.Sprintf( - "https://chromium.googlesource.com/chromium/src/+archive/refs/tags/%s/components/%s.tar.gz", - version, name) - - destinationDirectory := filepath.Join(srcRoot, "components", name) - - os.RemoveAll(destinationDirectory) - err := os.MkdirAll(destinationDirectory, 0o755) - if err != nil { - log.Fatalf("failed to create directory %s: %v", destinationDirectory, err) - } - - err = downloadAndExtract(url, destinationDirectory) - if err != nil { - log.Fatalf("failed to download %s: %v", name, err) - } - - log.Printf("Downloaded %s", name) - } - - log.Print("Creating git commit...") - runCommand(naiveRoot, "git", "add", - "src/components/cronet", - "src/components/grpc_support", - "src/components/prefs") - - commitMessage := fmt.Sprintf(`Add Chromium cronet components (v%s) - -Downloaded from Chromium source: -- components/cronet/ -- components/prefs/ - -The maintained grpc_support and native C API stay in-tree for cronet-go. - -Use 'go run ./cmd/build-naive sync' to re-download.`, version) - - runCommand(naiveRoot, "git", "commit", "-m", commitMessage) - - log.Print("Sync complete!") -} - -func downloadAndExtract(url, destinationDirectory string) error { - response, err := http.Get(url) - if err != nil { - return fmt.Errorf("HTTP request failed: %w", err) - } - defer response.Body.Close() - - if response.StatusCode != http.StatusOK { - return fmt.Errorf("HTTP %d: %s", response.StatusCode, response.Status) - } - - // Use tar command (simpler than using archive/tar with gzip) - command := exec.Command("tar", "-xzf", "-", "-C", destinationDirectory) - command.Stdin = response.Body - command.Stdout = os.Stdout - command.Stderr = os.Stderr - - err = command.Run() - if err != nil { - return fmt.Errorf("tar extraction failed: %w", err) - } - - return nil -} diff --git a/date_time_cgo.go b/date_time_cgo.go deleted file mode 100644 index 8571851c..00000000 --- a/date_time_cgo.go +++ /dev/null @@ -1,31 +0,0 @@ -//go:build !with_purego - -package cronet - -// #include -// #include -// #include -import "C" - -import ( - "time" - "unsafe" -) - -func NewDateTime() DateTime { - return DateTime{uintptr(unsafe.Pointer(C.Cronet_DateTime_Create()))} -} - -func (t DateTime) Destroy() { - C.Cronet_DateTime_Destroy(C.Cronet_DateTimePtr(unsafe.Pointer(t.ptr))) -} - -// SetValue -// Number of milliseconds since the UNIX epoch. -func (t DateTime) SetValue(value time.Time) { - C.Cronet_DateTime_value_set(C.Cronet_DateTimePtr(unsafe.Pointer(t.ptr)), C.int64_t(value.UnixMilli())) -} - -func (t DateTime) Value() time.Time { - return time.UnixMilli(int64(C.Cronet_DateTime_value_get(C.Cronet_DateTimePtr(unsafe.Pointer(t.ptr))))) -} diff --git a/date_time_purego.go b/date_time_purego.go deleted file mode 100644 index c471c692..00000000 --- a/date_time_purego.go +++ /dev/null @@ -1,26 +0,0 @@ -//go:build with_purego - -package cronet - -import ( - "time" - - "github.com/sagernet/cronet-go/internal/cronet" -) - -func NewDateTime() DateTime { - return DateTime{cronet.DateTimeCreate()} -} - -func (d DateTime) Destroy() { - cronet.DateTimeDestroy(d.ptr) -} - -// SetValue sets the number of milliseconds since the UNIX epoch. -func (d DateTime) SetValue(value time.Time) { - cronet.DateTimeValueSet(d.ptr, value.UnixMilli()) -} - -func (d DateTime) Value() time.Time { - return time.UnixMilli(cronet.DateTimeValueGet(d.ptr)) -} diff --git a/date_time_test.go b/date_time_test.go deleted file mode 100644 index 4db94567..00000000 --- a/date_time_test.go +++ /dev/null @@ -1,18 +0,0 @@ -package cronet_test - -import ( - "testing" - "time" - - "github.com/sagernet/cronet-go" -) - -func TestDateTime(t *testing.T) { - d := cronet.NewDateTime() - m := time.UnixMilli(time.Now().UnixMilli()) - d.SetValue(m) - if d.Value() != m { - t.Fatal("bad time") - } - d.Destroy() -} diff --git a/dialer_test.go b/dialer_test.go deleted file mode 100644 index 116c5ef2..00000000 --- a/dialer_test.go +++ /dev/null @@ -1,228 +0,0 @@ -package cronet - -import ( - "sync" - "testing" -) - -func TestDialerMapCleanup(t *testing.T) { - engine := NewEngine() - - engine.SetDialer(func(address string, port uint16) int { - return -104 // ERR_CONNECTION_FAILED - }) - - dialerAccess.RLock() - _, exists := dialerMap[engine.ptr] - dialerAccess.RUnlock() - - if !exists { - t.Error("dialer not registered in dialerMap") - } - - engine.Destroy() - - dialerAccess.RLock() - _, exists = dialerMap[engine.ptr] - dialerAccess.RUnlock() - - if exists { - t.Error("dialer not cleaned up after Engine.Destroy()") - } -} - -func TestUDPDialerMapCleanup(t *testing.T) { - engine := NewEngine() - - engine.SetUDPDialer(func(address string, port uint16) (int, string, uint16) { - return -104, "", 0 // ERR_CONNECTION_FAILED - }) - - udpDialerAccess.RLock() - _, exists := udpDialerMap[engine.ptr] - udpDialerAccess.RUnlock() - - if !exists { - t.Error("dialer not registered in udpDialerMap") - } - - engine.Destroy() - - udpDialerAccess.RLock() - _, exists = udpDialerMap[engine.ptr] - udpDialerAccess.RUnlock() - - if exists { - t.Error("dialer not cleaned up after Engine.Destroy()") - } -} - -func TestSetDialerNil(t *testing.T) { - engine := NewEngine() - defer engine.Destroy() - - engine.SetDialer(func(address string, port uint16) int { - return -104 - }) - - dialerAccess.RLock() - _, exists := dialerMap[engine.ptr] - dialerAccess.RUnlock() - - if !exists { - t.Error("dialer not registered") - } - - engine.SetDialer(nil) - - dialerAccess.RLock() - _, exists = dialerMap[engine.ptr] - dialerAccess.RUnlock() - - if exists { - t.Error("dialer not removed after SetDialer(nil)") - } -} - -func TestSetUDPDialerNil(t *testing.T) { - engine := NewEngine() - defer engine.Destroy() - - engine.SetUDPDialer(func(address string, port uint16) (int, string, uint16) { - return -104, "", 0 - }) - - udpDialerAccess.RLock() - _, exists := udpDialerMap[engine.ptr] - udpDialerAccess.RUnlock() - - if !exists { - t.Error("dialer not registered") - } - - engine.SetUDPDialer(nil) - - udpDialerAccess.RLock() - _, exists = udpDialerMap[engine.ptr] - udpDialerAccess.RUnlock() - - if exists { - t.Error("dialer not removed after SetUDPDialer(nil)") - } -} - -func TestSetDialerOverwrite(t *testing.T) { - engine := NewEngine() - defer engine.Destroy() - - callCount1 := 0 - callCount2 := 0 - - engine.SetDialer(func(address string, port uint16) int { - callCount1++ - return -104 - }) - - engine.SetDialer(func(address string, port uint16) int { - callCount2++ - return -102 - }) - - dialerAccess.RLock() - count := 0 - for k := range dialerMap { - if k == engine.ptr { - count++ - } - } - dialerAccess.RUnlock() - - if count != 1 { - t.Errorf("expected 1 entry in dialerMap, got %d", count) - } -} - -func TestDialerConcurrentAccess(t *testing.T) { - engine := NewEngine() - defer engine.Destroy() - - var wg sync.WaitGroup - iterations := 100 - - for i := 0; i < iterations; i++ { - wg.Add(1) - go func(n int) { - defer wg.Done() - if n%2 == 0 { - engine.SetDialer(func(address string, port uint16) int { - return -104 - }) - } else { - engine.SetDialer(nil) - } - }(i) - } - - for i := 0; i < iterations; i++ { - wg.Add(1) - go func() { - defer wg.Done() - dialerAccess.RLock() - _ = dialerMap[engine.ptr] - dialerAccess.RUnlock() - }() - } - - wg.Wait() - - dialerAccess.RLock() - count := 0 - for k := range dialerMap { - if k == engine.ptr { - count++ - } - } - dialerAccess.RUnlock() - - if count > 1 { - t.Errorf("dialerMap has duplicate entries for engine: %d", count) - } -} - -func TestMultipleEnginesDialers(t *testing.T) { - engine1 := NewEngine() - engine2 := NewEngine() - - engine1.SetDialer(func(address string, port uint16) int { - return -104 - }) - - engine2.SetDialer(func(address string, port uint16) int { - return -102 - }) - - dialerAccess.RLock() - _, exists1 := dialerMap[engine1.ptr] - _, exists2 := dialerMap[engine2.ptr] - dialerAccess.RUnlock() - - if !exists1 || !exists2 { - t.Error("both dialers should be registered") - } - - engine1.Destroy() - - dialerAccess.RLock() - _, exists1 = dialerMap[engine1.ptr] - _, exists2 = dialerMap[engine2.ptr] - dialerAccess.RUnlock() - - if exists1 { - t.Error("engine1's dialer should be removed") - } - if !exists2 { - t.Error("engine2's dialer should still exist") - } - - engine2.Destroy() -} diff --git a/engine_cgo.go b/engine_cgo.go deleted file mode 100644 index 0aed4bfe..00000000 --- a/engine_cgo.go +++ /dev/null @@ -1,248 +0,0 @@ -//go:build !with_purego - -package cronet - -// #include -// #include -// #include -// #include -// -// extern CRONET_EXPORT int cronetDialerCallback(void* context, char* address, uint16_t port); -// extern CRONET_EXPORT int cronetUdpDialerCallback(void* context, char* address, uint16_t port, char* out_local_address, uint16_t* out_local_port); -import "C" - -import ( - "sync" - "unsafe" -) - -var ( - dialerAccess sync.RWMutex - dialerMap = make(map[uintptr]Dialer) - udpDialerAccess sync.RWMutex - udpDialerMap = make(map[uintptr]UDPDialer) -) - -//export cronetDialerCallback -func cronetDialerCallback(context unsafe.Pointer, address *C.char, port C.uint16_t) C.int { - dialerAccess.RLock() - dialer, ok := dialerMap[uintptr(context)] - dialerAccess.RUnlock() - if !ok { - return -104 // ERR_CONNECTION_FAILED - } - return C.int(dialer(C.GoString(address), uint16(port))) -} - -//export cronetUdpDialerCallback -func cronetUdpDialerCallback(context unsafe.Pointer, address *C.char, port C.uint16_t, outLocalAddress *C.char, outLocalPort *C.uint16_t) C.int { - udpDialerAccess.RLock() - dialer, ok := udpDialerMap[uintptr(context)] - udpDialerAccess.RUnlock() - if !ok { - return -104 // ERR_CONNECTION_FAILED - } - fd, localAddress, localPort := dialer(C.GoString(address), uint16(port)) - - // Write output parameters - if outLocalAddress != nil && localAddress != "" { - localAddressC := C.CString(localAddress) - C.strcpy(outLocalAddress, localAddressC) - C.free(unsafe.Pointer(localAddressC)) - } - if outLocalPort != nil { - *outLocalPort = C.uint16_t(localPort) - } - - return C.int(fd) -} - -func NewEngine() Engine { - return Engine{uintptr(unsafe.Pointer(C.Cronet_Engine_Create()))} -} - -func (e Engine) Destroy() { - dialerAccess.Lock() - delete(dialerMap, e.ptr) - dialerAccess.Unlock() - udpDialerAccess.Lock() - delete(udpDialerMap, e.ptr) - udpDialerAccess.Unlock() - C.Cronet_Engine_Destroy(C.Cronet_EnginePtr(unsafe.Pointer(e.ptr))) -} - -// StartWithParams starts Engine using given |params|. The engine must be started once -// and only once before other methods can be used. -func (e Engine) StartWithParams(params EngineParams) Result { - return Result(C.Cronet_Engine_StartWithParams(C.Cronet_EnginePtr(unsafe.Pointer(e.ptr)), C.Cronet_EngineParamsPtr(unsafe.Pointer(params.ptr)))) -} - -// StartNetLogToFile starts NetLog logging to a file. The NetLog will contain events emitted -// by all live Engines. The NetLog is useful for debugging. -// The file can be viewed using a Chrome browser navigated to -// chrome://net-internals/#import -// Returns |true| if netlog has started successfully, |false| otherwise. -// @param fileName the complete file path. It must not be empty. If the file -// -// exists, it is truncated before starting. If actively logging, -// this method is ignored. -// -// @param logAll to include basic events, user cookies, -// -// credentials and all transferred bytes in the log. This option presents -// a privacy risk, since it exposes the user's credentials, and should -// only be used with the user's consent and in situations where the log -// won't be public. false to just include basic events. -func (e Engine) StartNetLogToFile(fileName string, logAll bool) bool { - cPath := C.CString(fileName) - result := C.Cronet_Engine_StartNetLogToFile(C.Cronet_EnginePtr(unsafe.Pointer(e.ptr)), cPath, C.bool(logAll)) - C.free(unsafe.Pointer(cPath)) - return bool(result) -} - -// StopNetLog Stops NetLog logging and flushes file to disk. If a logging session is -// not in progress, this call is ignored. This method blocks until the log is -// closed to ensure that log file is complete and available. -func (e Engine) StopNetLog() { - C.Cronet_Engine_StopNetLog(C.Cronet_EnginePtr(unsafe.Pointer(e.ptr))) -} - -// Shutdown shuts down the Engine if there are no active requests, -// otherwise returns a failure Result. -// -// Cannot be called on network thread - the thread Cronet calls into -// Executor on (which is different from the thread the Executor invokes -// callbacks on). This method blocks until all the Engine's resources have -// been cleaned up. -func (e Engine) Shutdown() Result { - return Result(C.Cronet_Engine_Shutdown(C.Cronet_EnginePtr(unsafe.Pointer(e.ptr)))) -} - -// Version returns a human-readable version string of the engine. -func (e Engine) Version() string { - return C.GoString(C.Cronet_Engine_GetVersionString(C.Cronet_EnginePtr(unsafe.Pointer(e.ptr)))) -} - -// DefaultUserAgent Returns default human-readable version string of the engine. Can be used -// before StartWithParams() is called. -func (e Engine) DefaultUserAgent() string { - return C.GoString(C.Cronet_Engine_GetDefaultUserAgent(C.Cronet_EnginePtr(unsafe.Pointer(e.ptr)))) -} - -// AddRequestFinishListener registers a listener that gets called at the end of each request. -// -// The listener is called on Executor. -// -// The listener is called before URLRequestCallbackHandler.OnCanceled(), -// URLRequestCallbackHandler.OnFailed() or -// URLRequestCallbackHandler.OnSucceeded() is called -- note that if Executor -// runs the listener asynchronously, the actual call to the listener -// may happen after a URLRequestCallbackHandler method is called. -// -// Listeners are only guaranteed to be called for requests that are started -// after the listener is added. -// -// Ownership is **not** taken for listener or Executor. -// -// Assuming the listener won't run again (there are no pending requests with -// the listener attached, either via Engine or UrlRequest), -// the app may destroy it once its OnRequestFinished() has started, -// even inside that method. -// -// Similarly, the app may destroy executor in or after OnRequestFinished()}. -// -// It's also OK to destroy executor in or after one of -// URLRequestCallbackHandler.OnCanceled(), URLRequestCallbackHandler.OnFailed() or -// URLRequestCallbackHandler.OnSucceeded(). -// -// Of course, both of these are only true if listener won't run again -// and executor isn't being used for anything else that might start -// running in the future. -// -// @param listener the listener for finished requests. -// @param executor the executor upon which to run listener. -func (e Engine) AddRequestFinishListener(listener URLRequestFinishedInfoListener, executor Executor) { - C.Cronet_Engine_AddRequestFinishedListener(C.Cronet_EnginePtr(unsafe.Pointer(e.ptr)), C.Cronet_RequestFinishedInfoListenerPtr(unsafe.Pointer(listener.ptr)), C.Cronet_ExecutorPtr(unsafe.Pointer(executor.ptr))) -} - -// RemoveRequestFinishListener unregisters a RequestFinishedInfoListener, -// including its association with its registered Executor. -func (e Engine) RemoveRequestFinishListener(listener URLRequestFinishedInfoListener) { - C.Cronet_Engine_RemoveRequestFinishedListener(C.Cronet_EnginePtr(unsafe.Pointer(e.ptr)), C.Cronet_RequestFinishedInfoListenerPtr(unsafe.Pointer(listener.ptr))) -} - -func (e Engine) SetClientContext(context unsafe.Pointer) { - C.Cronet_Engine_SetClientContext(C.Cronet_EnginePtr(unsafe.Pointer(e.ptr)), C.Cronet_ClientContext(context)) -} - -func (e Engine) ClientContext() unsafe.Pointer { - return unsafe.Pointer(C.Cronet_Engine_GetClientContext(C.Cronet_EnginePtr(unsafe.Pointer(e.ptr)))) -} - -// SetTrustedRootCertificates sets custom trusted root certificates for this engine. -// Must be called before StartWithParams(). -// pemRootCerts should be PEM-formatted certificates (can contain multiple certificates). -// Returns true if the certificates were successfully set, false if parsing failed. -func (e Engine) SetTrustedRootCertificates(pemRootCerts string) bool { - cPem := C.CString(pemRootCerts) - defer C.free(unsafe.Pointer(cPem)) - certVerifier := C.Cronet_CreateCertVerifierWithRootCerts(cPem) - if certVerifier == nil { - return false - } - C.Cronet_Engine_SetMockCertVerifierForTesting(C.Cronet_EnginePtr(unsafe.Pointer(e.ptr)), certVerifier) - return true -} - -// CloseAllConnections closes all connections managed by the engine's network session. -// This includes socket pools, HTTP stream pool, SPDY session pool, and QUIC session pool. -// Useful for releasing connection-related memory or speeding up engine shutdown. -func (e Engine) CloseAllConnections() { - C.Cronet_Engine_CloseAllConnections(C.Cronet_EnginePtr(unsafe.Pointer(e.ptr))) -} - -// SetDialer sets a custom dialer for TCP connections. -// When set, the engine will use this callback to establish TCP connections -// instead of the default system socket API. -// Must be called before StartWithParams(). -// Pass nil to disable custom dialing. -func (e Engine) SetDialer(dialer Dialer) { - if dialer == nil { - C.Cronet_Engine_SetDialer(C.Cronet_EnginePtr(unsafe.Pointer(e.ptr)), nil, nil) - dialerAccess.Lock() - delete(dialerMap, e.ptr) - dialerAccess.Unlock() - return - } - dialerAccess.Lock() - dialerMap[e.ptr] = dialer - dialerAccess.Unlock() - C.Cronet_Engine_SetDialer( - C.Cronet_EnginePtr(unsafe.Pointer(e.ptr)), - (*[0]byte)(C.cronetDialerCallback), - unsafe.Pointer(e.ptr), - ) -} - -// SetUDPDialer sets a custom dialer for UDP sockets. -// When set, the engine will use this callback to create UDP sockets instead of -// the default system socket API. -// Must be called before StartWithParams(). -// Pass nil to disable custom dialing. -func (e Engine) SetUDPDialer(dialer UDPDialer) { - if dialer == nil { - C.Cronet_Engine_SetUdpDialer(C.Cronet_EnginePtr(unsafe.Pointer(e.ptr)), nil, nil) - udpDialerAccess.Lock() - delete(udpDialerMap, e.ptr) - udpDialerAccess.Unlock() - return - } - udpDialerAccess.Lock() - udpDialerMap[e.ptr] = dialer - udpDialerAccess.Unlock() - C.Cronet_Engine_SetUdpDialer( - C.Cronet_EnginePtr(unsafe.Pointer(e.ptr)), - (*[0]byte)(C.cronetUdpDialerCallback), - unsafe.Pointer(e.ptr), - ) -} diff --git a/engine_impl_cgo.go b/engine_impl_cgo.go deleted file mode 100644 index b6ccd8fa..00000000 --- a/engine_impl_cgo.go +++ /dev/null @@ -1,136 +0,0 @@ -//go:build !with_purego - -package cronet - -// #include -// #include -// #include -// extern CRONET_EXPORT Cronet_RESULT cronetEngineStartWithParams(Cronet_EnginePtr self, Cronet_EngineParamsPtr params); -// extern CRONET_EXPORT bool cronetEngineStartNetLogToFile(Cronet_EnginePtr self, Cronet_String file_name, bool log_all); -// extern CRONET_EXPORT void cronetEngineStopNetLog(Cronet_EnginePtr self); -// extern CRONET_EXPORT Cronet_RESULT cronetEngineShutdown(Cronet_EnginePtr self); -// extern CRONET_EXPORT Cronet_String cronetEngineGetVersionString(Cronet_EnginePtr self); -// extern CRONET_EXPORT Cronet_String cronetEngineGetDefaultUserAgent(Cronet_EnginePtr self); -// extern CRONET_EXPORT void cronetEngineAddRequestFinishedListener(Cronet_EnginePtr self, Cronet_RequestFinishedInfoListenerPtr listener, Cronet_ExecutorPtr executor); -// extern CRONET_EXPORT void cronetEngineRemoveRequestFinishedListener(Cronet_EnginePtr self, Cronet_RequestFinishedInfoListenerPtr listener); -import "C" - -import ( - "sync" - "unsafe" -) - -// EngineHandler is an interface for custom Engine implementations (for testing/mocking). -type EngineHandler interface { - StartWithParams(self Engine, params EngineParams) Result - StartNetLogToFile(self Engine, fileName string, logAll bool) bool - StopNetLog(self Engine) - Shutdown(self Engine) Result - GetVersionString(self Engine) string - GetDefaultUserAgent(self Engine) string - AddRequestFinishedListener(self Engine, listener URLRequestFinishedInfoListener, executor Executor) - RemoveRequestFinishedListener(self Engine, listener URLRequestFinishedInfoListener) -} - -// NewEngineWith creates a new Engine with custom handler (for testing/mocking). -func NewEngineWith(handler EngineHandler) Engine { - ptr := C.Cronet_Engine_CreateWith( - (*[0]byte)(C.cronetEngineStartWithParams), - (*[0]byte)(C.cronetEngineStartNetLogToFile), - (*[0]byte)(C.cronetEngineStopNetLog), - (*[0]byte)(C.cronetEngineShutdown), - (*[0]byte)(C.cronetEngineGetVersionString), - (*[0]byte)(C.cronetEngineGetDefaultUserAgent), - (*[0]byte)(C.cronetEngineAddRequestFinishedListener), - (*[0]byte)(C.cronetEngineRemoveRequestFinishedListener), - ) - ptrVal := uintptr(unsafe.Pointer(ptr)) - engineHandlerAccess.Lock() - engineHandlerMap[ptrVal] = handler - engineHandlerAccess.Unlock() - return Engine{ptrVal} -} - -var ( - engineHandlerAccess sync.RWMutex - engineHandlerMap map[uintptr]EngineHandler -) - -func init() { - engineHandlerMap = make(map[uintptr]EngineHandler) -} - -func instanceOfEngineHandler(self C.Cronet_EnginePtr) EngineHandler { - engineHandlerAccess.RLock() - defer engineHandlerAccess.RUnlock() - return engineHandlerMap[uintptr(unsafe.Pointer(self))] -} - -//export cronetEngineStartWithParams -func cronetEngineStartWithParams(self C.Cronet_EnginePtr, params C.Cronet_EngineParamsPtr) C.Cronet_RESULT { - handler := instanceOfEngineHandler(self) - if handler != nil { - return C.Cronet_RESULT(handler.StartWithParams(Engine{uintptr(unsafe.Pointer(self))}, EngineParams{uintptr(unsafe.Pointer(params))})) - } - return C.Cronet_RESULT_SUCCESS -} - -//export cronetEngineStartNetLogToFile -func cronetEngineStartNetLogToFile(self C.Cronet_EnginePtr, fileName C.Cronet_String, logAll C.bool) C.bool { - handler := instanceOfEngineHandler(self) - if handler != nil { - return C.bool(handler.StartNetLogToFile(Engine{uintptr(unsafe.Pointer(self))}, C.GoString(fileName), bool(logAll))) - } - return C.bool(false) -} - -//export cronetEngineStopNetLog -func cronetEngineStopNetLog(self C.Cronet_EnginePtr) { - handler := instanceOfEngineHandler(self) - if handler != nil { - handler.StopNetLog(Engine{uintptr(unsafe.Pointer(self))}) - } -} - -//export cronetEngineShutdown -func cronetEngineShutdown(self C.Cronet_EnginePtr) C.Cronet_RESULT { - handler := instanceOfEngineHandler(self) - if handler != nil { - return C.Cronet_RESULT(handler.Shutdown(Engine{uintptr(unsafe.Pointer(self))})) - } - return C.Cronet_RESULT_SUCCESS -} - -//export cronetEngineGetVersionString -func cronetEngineGetVersionString(self C.Cronet_EnginePtr) C.Cronet_String { - handler := instanceOfEngineHandler(self) - if handler != nil { - return C.CString(handler.GetVersionString(Engine{uintptr(unsafe.Pointer(self))})) - } - return nil -} - -//export cronetEngineGetDefaultUserAgent -func cronetEngineGetDefaultUserAgent(self C.Cronet_EnginePtr) C.Cronet_String { - handler := instanceOfEngineHandler(self) - if handler != nil { - return C.CString(handler.GetDefaultUserAgent(Engine{uintptr(unsafe.Pointer(self))})) - } - return nil -} - -//export cronetEngineAddRequestFinishedListener -func cronetEngineAddRequestFinishedListener(self C.Cronet_EnginePtr, listener C.Cronet_RequestFinishedInfoListenerPtr, executor C.Cronet_ExecutorPtr) { - handler := instanceOfEngineHandler(self) - if handler != nil { - handler.AddRequestFinishedListener(Engine{uintptr(unsafe.Pointer(self))}, URLRequestFinishedInfoListener{uintptr(unsafe.Pointer(listener))}, Executor{uintptr(unsafe.Pointer(executor))}) - } -} - -//export cronetEngineRemoveRequestFinishedListener -func cronetEngineRemoveRequestFinishedListener(self C.Cronet_EnginePtr, listener C.Cronet_RequestFinishedInfoListenerPtr) { - handler := instanceOfEngineHandler(self) - if handler != nil { - handler.RemoveRequestFinishedListener(Engine{uintptr(unsafe.Pointer(self))}, URLRequestFinishedInfoListener{uintptr(unsafe.Pointer(listener))}) - } -} diff --git a/engine_params_cgo.go b/engine_params_cgo.go deleted file mode 100644 index 51aec7b5..00000000 --- a/engine_params_cgo.go +++ /dev/null @@ -1,190 +0,0 @@ -//go:build !with_purego - -package cronet - -// #include -// #include -// #include -import "C" - -import ( - "unsafe" -) - -func NewEngineParams() EngineParams { - return EngineParams{uintptr(unsafe.Pointer(C.Cronet_EngineParams_Create()))} -} - -func (p EngineParams) Destroy() { - C.Cronet_EngineParams_Destroy(C.Cronet_EngineParamsPtr(unsafe.Pointer(p.ptr))) -} - -// SetEnableCheckResult override strict result checking for all operations that return RESULT. -// If set to true, then failed result will cause native crash via SIGABRT. -func (p EngineParams) SetEnableCheckResult(enable bool) { - C.Cronet_EngineParams_enable_check_result_set(C.Cronet_EngineParamsPtr(unsafe.Pointer(p.ptr)), C.bool(enable)) -} - -func (p EngineParams) EnableCheckResult() bool { - return bool(C.Cronet_EngineParams_enable_check_result_get(C.Cronet_EngineParamsPtr(unsafe.Pointer(p.ptr)))) -} - -// SetUserAgent override of the User-Agent header for all requests. An explicitly -// set User-Agent header will override a value set using this param. -func (p EngineParams) SetUserAgent(userAgent string) { - cUserAgent := C.CString(userAgent) - C.Cronet_EngineParams_user_agent_set(C.Cronet_EngineParamsPtr(unsafe.Pointer(p.ptr)), cUserAgent) - C.free(unsafe.Pointer(cUserAgent)) -} - -func (p EngineParams) UserAgent() string { - return C.GoString(C.Cronet_EngineParams_user_agent_get(C.Cronet_EngineParamsPtr(unsafe.Pointer(p.ptr)))) -} - -// SetAcceptLanguage sets a default value for the Accept-Language header value for UrlRequests -// created by this engine. Explicitly setting the Accept-Language header -// value for individual UrlRequests will override this value. -func (p EngineParams) SetAcceptLanguage(acceptLanguage string) { - cAcceptLanguage := C.CString(acceptLanguage) - C.Cronet_EngineParams_accept_language_set(C.Cronet_EngineParamsPtr(unsafe.Pointer(p.ptr)), cAcceptLanguage) - C.free(unsafe.Pointer(cAcceptLanguage)) -} - -func (p EngineParams) AcceptLanguage() string { - return C.GoString(C.Cronet_EngineParams_accept_language_get(C.Cronet_EngineParamsPtr(unsafe.Pointer(p.ptr)))) -} - -// SetStoragePath sets directory for HTTP Cache and Prefs Storage. The directory must exist. -func (p EngineParams) SetStoragePath(storagePath string) { - cStoragePath := C.CString(storagePath) - C.Cronet_EngineParams_storage_path_set(C.Cronet_EngineParamsPtr(unsafe.Pointer(p.ptr)), cStoragePath) - C.free(unsafe.Pointer(cStoragePath)) -} - -func (p EngineParams) StoragePath() string { - return C.GoString(C.Cronet_EngineParams_storage_path_get(C.Cronet_EngineParamsPtr(unsafe.Pointer(p.ptr)))) -} - -// SetEnableQuic sets whether QUIC protocol -// is enabled. If QUIC is enabled, then QUIC User Agent Id -// containing application name and Cronet version is sent to the server. -func (p EngineParams) SetEnableQuic(enable bool) { - C.Cronet_EngineParams_enable_quic_set(C.Cronet_EngineParamsPtr(unsafe.Pointer(p.ptr)), C.bool(enable)) -} - -func (p EngineParams) EnableQuic() bool { - return bool(C.Cronet_EngineParams_enable_quic_get(C.Cronet_EngineParamsPtr(unsafe.Pointer(p.ptr)))) -} - -// SetEnableHTTP2 sets whether HTTP/2 -// protocol is enabled. -func (p EngineParams) SetEnableHTTP2(enable bool) { - C.Cronet_EngineParams_enable_http2_set(C.Cronet_EngineParamsPtr(unsafe.Pointer(p.ptr)), C.bool(enable)) -} - -func (p EngineParams) EnableHTTP2() bool { - return bool(C.Cronet_EngineParams_enable_http2_get(C.Cronet_EngineParamsPtr(unsafe.Pointer(p.ptr)))) -} - -// SetEnableBrotli sets whether Brotli compression is -// enabled. If enabled, Brotli will be advertised in Accept-Encoding request headers. -func (p EngineParams) SetEnableBrotli(enable bool) { - C.Cronet_EngineParams_enable_brotli_set(C.Cronet_EngineParamsPtr(unsafe.Pointer(p.ptr)), C.bool(enable)) -} - -func (p EngineParams) EnableBrotli() bool { - return bool(C.Cronet_EngineParams_enable_brotli_get(C.Cronet_EngineParamsPtr(unsafe.Pointer(p.ptr)))) -} - -// SetHTTPCacheMode enables or disables caching of HTTP data and other information like QUIC -// server information. -func (p EngineParams) SetHTTPCacheMode(mode EngineParamsHTTPCacheMode) { - C.Cronet_EngineParams_http_cache_mode_set(C.Cronet_EngineParamsPtr(unsafe.Pointer(p.ptr)), C.Cronet_EngineParams_HTTP_CACHE_MODE(mode)) -} - -func (p EngineParams) HTTPCacheMode() EngineParamsHTTPCacheMode { - return EngineParamsHTTPCacheMode(C.Cronet_EngineParams_http_cache_mode_get(C.Cronet_EngineParamsPtr(unsafe.Pointer(p.ptr)))) -} - -// SetHTTPCacheMaxSize sets Maximum size in bytes used to cache data (advisory and maybe exceeded at -// times) -func (p EngineParams) SetHTTPCacheMaxSize(maxSize int64) { - C.Cronet_EngineParams_http_cache_max_size_set(C.Cronet_EngineParamsPtr(unsafe.Pointer(p.ptr)), C.int64_t(maxSize)) -} - -func (p EngineParams) HTTPCacheMaxSize() int64 { - return int64(C.Cronet_EngineParams_http_cache_max_size_get(C.Cronet_EngineParamsPtr(unsafe.Pointer(p.ptr)))) -} - -// AddQuicHint add hints that hosts support QUIC. -func (p EngineParams) AddQuicHint(element QuicHint) { - C.Cronet_EngineParams_quic_hints_add(C.Cronet_EngineParamsPtr(unsafe.Pointer(p.ptr)), C.Cronet_QuicHintPtr(unsafe.Pointer(element.ptr))) -} - -func (p EngineParams) QuicHintSize() int { - return int(C.Cronet_EngineParams_quic_hints_size(C.Cronet_EngineParamsPtr(unsafe.Pointer(p.ptr)))) -} - -func (p EngineParams) QuicHintAt(index int) QuicHint { - return QuicHint{uintptr(unsafe.Pointer(C.Cronet_EngineParams_quic_hints_at(C.Cronet_EngineParamsPtr(unsafe.Pointer(p.ptr)), C.uint32_t(index))))} -} - -func (p EngineParams) ClearQuicHints() { - C.Cronet_EngineParams_quic_hints_clear(C.Cronet_EngineParamsPtr(unsafe.Pointer(p.ptr))) -} - -// AddPublicKeyPins pins a set of public keys for given hosts. See PublicKeyPins for explanation. -func (p EngineParams) AddPublicKeyPins(element PublicKeyPins) { - C.Cronet_EngineParams_public_key_pins_add(C.Cronet_EngineParamsPtr(unsafe.Pointer(p.ptr)), C.Cronet_PublicKeyPinsPtr(unsafe.Pointer(element.ptr))) -} - -func (p EngineParams) PublicKeyPinsSize() int { - return int(C.Cronet_EngineParams_public_key_pins_size(C.Cronet_EngineParamsPtr(unsafe.Pointer(p.ptr)))) -} - -func (p EngineParams) PublicKeyPinsAt(index int) PublicKeyPins { - return PublicKeyPins{uintptr(unsafe.Pointer(C.Cronet_EngineParams_public_key_pins_at(C.Cronet_EngineParamsPtr(unsafe.Pointer(p.ptr)), C.uint32_t(index))))} -} - -func (p EngineParams) ClearPublicKeyPins() { - C.Cronet_EngineParams_public_key_pins_clear(C.Cronet_EngineParamsPtr(unsafe.Pointer(p.ptr))) -} - -// SetEnablePublicKeyPinningBypassForLocalTrustAnchors enables or disables public key pinning bypass for local trust anchors. Disabling the -// bypass for local trust anchors is highly discouraged since it may prohibit the app -// from communicating with the pinned hosts. E.g., a user may want to send all traffic -// through an SSL enabled proxy by changing the device proxy settings and adding the -// proxy certificate to the list of local trust anchor. Disabling the bypass will most -// likely prevent the app from sending any traffic to the pinned hosts. For more -// information see 'How does key pinning interact with local proxies and filters?' at -// https://www.chromium.org/Home/chromium-security/security-faq -func (p EngineParams) SetEnablePublicKeyPinningBypassForLocalTrustAnchors(enable bool) { - C.Cronet_EngineParams_enable_public_key_pinning_bypass_for_local_trust_anchors_set(C.Cronet_EngineParamsPtr(unsafe.Pointer(p.ptr)), C.bool(enable)) -} - -func (p EngineParams) EnablePublicKeyPinningBypassForLocalTrustAnchors() bool { - return bool(C.Cronet_EngineParams_enable_public_key_pinning_bypass_for_local_trust_anchors_get(C.Cronet_EngineParamsPtr(unsafe.Pointer(p.ptr)))) -} - -// SetNetworkThreadPriority set optional network thread priority. NAN indicates unset, use default. -// On Android, corresponds to android.os.Process.setThreadPriority() values. -// On iOS, corresponds to NSThread::setThreadPriority values. -// Do not specify for other platforms. -func (p EngineParams) SetNetworkThreadPriority(priority float64) { - C.Cronet_EngineParams_network_thread_priority_set(C.Cronet_EngineParamsPtr(unsafe.Pointer(p.ptr)), C.double(priority)) -} - -func (p EngineParams) NetworkThreadPriority() float64 { - return float64(C.Cronet_EngineParams_network_thread_priority_get(C.Cronet_EngineParamsPtr(unsafe.Pointer(p.ptr)))) -} - -// SetExperimentalOptions set JSON formatted experimental options to be used in Cronet Engine. -func (p EngineParams) SetExperimentalOptions(options string) { - cOptions := C.CString(options) - C.Cronet_EngineParams_experimental_options_set(C.Cronet_EngineParamsPtr(unsafe.Pointer(p.ptr)), cOptions) - C.free(unsafe.Pointer(cOptions)) -} - -func (p EngineParams) ExperimentalOptions() string { - return C.GoString(C.Cronet_EngineParams_experimental_options_get(C.Cronet_EngineParamsPtr(unsafe.Pointer(p.ptr)))) -} diff --git a/engine_params_experimental_options.go b/engine_params_experimental_options.go deleted file mode 100644 index 24ee366d..00000000 --- a/engine_params_experimental_options.go +++ /dev/null @@ -1,108 +0,0 @@ -package cronet - -import ( - "encoding/json" - "strings" -) - -func (p EngineParams) SetExperimentalOption(key string, value any) error { - options := strings.TrimSpace(p.ExperimentalOptions()) - - experimentalOptions := make(map[string]any) - if options != "" { - if err := json.Unmarshal([]byte(options), &experimentalOptions); err != nil { - return err - } - } - - if value == nil { - delete(experimentalOptions, key) - } else { - experimentalOptions[key] = value - } - - encoded, err := json.Marshal(experimentalOptions) - if err != nil { - return err - } - p.SetExperimentalOptions(string(encoded)) - return nil -} - -func (p EngineParams) SetAsyncDNS(enable bool) error { - if !enable { - return p.SetExperimentalOption("AsyncDNS", nil) - } - return p.SetExperimentalOption("AsyncDNS", map[string]any{ - "enable": true, - }) -} - -// SetDNSServerOverride configures Cronet's built-in DNS client to exclusively use the -// provided nameserver addresses. -// -// The nameserver entries must be IP literals, in "ip:port" form (IPv6 in "[ip]:port" -// form). Passing an empty slice disables the override. -func (p EngineParams) SetDNSServerOverride(nameservers []string) error { - if len(nameservers) == 0 { - return p.SetExperimentalOption("DnsServerOverride", nil) - } - return p.SetExperimentalOption("DnsServerOverride", map[string]any{ - "nameservers": nameservers, - }) -} - -// SetHostResolverRules sets rules to override DNS resolution. -// Format: "MAP hostname ip" or "MAP *.example.com ip" or "EXCLUDE hostname". -// Multiple rules can be separated by commas: "MAP foo 1.2.3.4, MAP bar 5.6.7.8". -// See net/dns/mapped_host_resolver.h for full format. -func (p EngineParams) SetHostResolverRules(rules string) error { - if rules == "" { - return p.SetExperimentalOption("HostResolverRules", nil) - } - return p.SetExperimentalOption("HostResolverRules", map[string]any{ - "host_resolver_rules": rules, - }) -} - -// SetUseDnsHttpsSvcb enables or disables DNS HTTPS SVCB record lookups. -// When enabled, Chromium will query DNS for HTTPS records (type 65) which can -// contain ECH (Encrypted Client Hello) configurations and ALPN hints. -// This is required for ECH support. -func (p EngineParams) SetUseDnsHttpsSvcb(enable bool) error { - return p.SetExperimentalOption("UseDnsHttpsSvcb", map[string]any{ - "enable": enable, - }) -} - -func (p EngineParams) SetHTTP2Options(sessionMaxReceiveWindowSize, initialWindowSize uint64) error { - return p.SetExperimentalOption("HTTP2Options", map[string]any{ - "session_max_recv_window_size": sessionMaxReceiveWindowSize, - "initial_window_size": initialWindowSize, - }) -} - -func (p EngineParams) SetQUICOptions(connectionOptions string, initialStreamRecvWindowSize, initialSessionRecvWindowSize uint64) error { - options := map[string]any{} - if connectionOptions != "" { - options["connection_options"] = connectionOptions - } - if initialStreamRecvWindowSize > 0 { - options["initial_stream_recv_window_size"] = initialStreamRecvWindowSize - } - if initialSessionRecvWindowSize > 0 { - options["initial_session_recv_window_size"] = initialSessionRecvWindowSize - } - if len(options) == 0 { - return p.SetExperimentalOption("QUIC", nil) - } - return p.SetExperimentalOption("QUIC", options) -} - -func (p EngineParams) SetSocketPoolOptions(maxPerPool, maxPerProxyChain, maxPerGroup int) error { - return p.SetExperimentalOption("SocketPoolOptions", map[string]any{ - "max_sockets_per_pool": maxPerPool, - "max_sockets_per_proxy_chain": maxPerProxyChain, - "max_sockets_per_group": maxPerGroup, - }) -} diff --git a/engine_params_purego.go b/engine_params_purego.go deleted file mode 100644 index 6d8d9525..00000000 --- a/engine_params_purego.go +++ /dev/null @@ -1,143 +0,0 @@ -//go:build with_purego - -package cronet - -import ( - "github.com/sagernet/cronet-go/internal/cronet" -) - -func NewEngineParams() EngineParams { - return EngineParams{cronet.EngineParamsCreate()} -} - -func (p EngineParams) Destroy() { - cronet.EngineParamsDestroy(p.ptr) -} - -func (p EngineParams) SetEnableCheckResult(enable bool) { - cronet.EngineParamsEnableCheckResultSet(p.ptr, enable) -} - -func (p EngineParams) EnableCheckResult() bool { - return cronet.EngineParamsEnableCheckResultGet(p.ptr) -} - -func (p EngineParams) SetUserAgent(userAgent string) { - cronet.EngineParamsUserAgentSet(p.ptr, userAgent) -} - -func (p EngineParams) UserAgent() string { - return cronet.EngineParamsUserAgentGet(p.ptr) -} - -func (p EngineParams) SetAcceptLanguage(acceptLanguage string) { - cronet.EngineParamsAcceptLanguageSet(p.ptr, acceptLanguage) -} - -func (p EngineParams) AcceptLanguage() string { - return cronet.EngineParamsAcceptLanguageGet(p.ptr) -} - -func (p EngineParams) SetStoragePath(storagePath string) { - cronet.EngineParamsStoragePathSet(p.ptr, storagePath) -} - -func (p EngineParams) StoragePath() string { - return cronet.EngineParamsStoragePathGet(p.ptr) -} - -func (p EngineParams) SetEnableQuic(enable bool) { - cronet.EngineParamsEnableQuicSet(p.ptr, enable) -} - -func (p EngineParams) EnableQuic() bool { - return cronet.EngineParamsEnableQuicGet(p.ptr) -} - -func (p EngineParams) SetEnableHTTP2(enable bool) { - cronet.EngineParamsEnableHttp2Set(p.ptr, enable) -} - -func (p EngineParams) EnableHTTP2() bool { - return cronet.EngineParamsEnableHttp2Get(p.ptr) -} - -func (p EngineParams) SetEnableBrotli(enable bool) { - cronet.EngineParamsEnableBrotliSet(p.ptr, enable) -} - -func (p EngineParams) EnableBrotli() bool { - return cronet.EngineParamsEnableBrotliGet(p.ptr) -} - -func (p EngineParams) SetHTTPCacheMode(mode EngineParamsHTTPCacheMode) { - cronet.EngineParamsHttpCacheModeSet(p.ptr, int32(mode)) -} - -func (p EngineParams) HTTPCacheMode() EngineParamsHTTPCacheMode { - return EngineParamsHTTPCacheMode(cronet.EngineParamsHttpCacheModeGet(p.ptr)) -} - -func (p EngineParams) SetHTTPCacheMaxSize(size int64) { - cronet.EngineParamsHttpCacheMaxSizeSet(p.ptr, size) -} - -func (p EngineParams) HTTPCacheMaxSize() int64 { - return cronet.EngineParamsHttpCacheMaxSizeGet(p.ptr) -} - -func (p EngineParams) AddQuicHint(hint QuicHint) { - cronet.EngineParamsQuicHintsAdd(p.ptr, hint.ptr) -} - -func (p EngineParams) QuicHintSize() int { - return int(cronet.EngineParamsQuicHintsSize(p.ptr)) -} - -func (p EngineParams) QuicHintAt(index int) QuicHint { - return QuicHint{cronet.EngineParamsQuicHintsAt(p.ptr, uint32(index))} -} - -func (p EngineParams) ClearQuicHints() { - cronet.EngineParamsQuicHintsClear(p.ptr) -} - -func (p EngineParams) AddPublicKeyPins(pins PublicKeyPins) { - cronet.EngineParamsPublicKeyPinsAdd(p.ptr, pins.ptr) -} - -func (p EngineParams) PublicKeyPinsSize() int { - return int(cronet.EngineParamsPublicKeyPinsSize(p.ptr)) -} - -func (p EngineParams) PublicKeyPinsAt(index int) PublicKeyPins { - return PublicKeyPins{cronet.EngineParamsPublicKeyPinsAt(p.ptr, uint32(index))} -} - -func (p EngineParams) ClearPublicKeyPins() { - cronet.EngineParamsPublicKeyPinsClear(p.ptr) -} - -func (p EngineParams) SetEnablePublicKeyPinningBypassForLocalTrustAnchors(enable bool) { - cronet.EngineParamsEnablePublicKeyPinningBypassForLocalTrustAnchorsSet(p.ptr, enable) -} - -func (p EngineParams) EnablePublicKeyPinningBypassForLocalTrustAnchors() bool { - return cronet.EngineParamsEnablePublicKeyPinningBypassForLocalTrustAnchorsGet(p.ptr) -} - -func (p EngineParams) SetNetworkThreadPriority(priority float64) { - cronet.EngineParamsNetworkThreadPrioritySet(p.ptr, priority) -} - -func (p EngineParams) NetworkThreadPriority() float64 { - return cronet.EngineParamsNetworkThreadPriorityGet(p.ptr) -} - -func (p EngineParams) SetExperimentalOptions(options string) { - cronet.EngineParamsExperimentalOptionsSet(p.ptr, options) -} - -func (p EngineParams) ExperimentalOptions() string { - return cronet.EngineParamsExperimentalOptionsGet(p.ptr) -} diff --git a/engine_purego.go b/engine_purego.go deleted file mode 100644 index 164a5597..00000000 --- a/engine_purego.go +++ /dev/null @@ -1,235 +0,0 @@ -//go:build with_purego - -package cronet - -import ( - "sync" - "unsafe" - - "github.com/sagernet/cronet-go/internal/cronet" - - "github.com/ebitengine/purego" -) - -var ( - dialerAccess sync.RWMutex - dialerMap = make(map[uintptr]Dialer) - udpDialerAccess sync.RWMutex - udpDialerMap = make(map[uintptr]UDPDialer) - dialerCallback uintptr - udpDialerCallback uintptr -) - -func init() { - dialerCallback = purego.NewCallback(func(context uintptr, address uintptr, port uint16) int { - dialerAccess.RLock() - dialer, ok := dialerMap[context] - dialerAccess.RUnlock() - if !ok { - return -104 // ERR_CONNECTION_FAILED - } - return dialer(cronet.GoString(address), port) - }) - - udpDialerCallback = purego.NewCallback(func(context uintptr, address uintptr, port uint16, outLocalAddress uintptr, outLocalPort uintptr) int { - udpDialerAccess.RLock() - dialer, ok := udpDialerMap[context] - udpDialerAccess.RUnlock() - if !ok { - return -104 // ERR_CONNECTION_FAILED - } - fd, localAddress, localPort := dialer(cronet.GoString(address), port) - - // Write output parameters using unsafe - if outLocalAddress != 0 && localAddress != "" { - localAddressBytes := []byte(localAddress) - for i, b := range localAddressBytes { - *(*byte)(unsafe.Add(unsafe.Pointer(outLocalAddress), i)) = b - } - // Null terminator - *(*byte)(unsafe.Add(unsafe.Pointer(outLocalAddress), len(localAddressBytes))) = 0 - } - if outLocalPort != 0 { - *(*uint16)(unsafe.Pointer(outLocalPort)) = localPort - } - - return fd - }) -} - -func NewEngine() Engine { - return Engine{cronet.EngineCreate()} -} - -func (e Engine) Destroy() { - dialerAccess.Lock() - delete(dialerMap, e.ptr) - dialerAccess.Unlock() - udpDialerAccess.Lock() - delete(udpDialerMap, e.ptr) - udpDialerAccess.Unlock() - cronet.EngineDestroy(e.ptr) -} - -// StartWithParams starts Engine using given |params|. The engine must be started once -// and only once before other methods can be used. -func (e Engine) StartWithParams(params EngineParams) Result { - return Result(cronet.EngineStartWithParams(e.ptr, params.ptr)) -} - -// StartNetLogToFile starts NetLog logging to a file. The NetLog will contain events emitted -// by all live Engines. The NetLog is useful for debugging. -// The file can be viewed using a Chrome browser navigated to -// chrome://net-internals/#import -// Returns |true| if netlog has started successfully, |false| otherwise. -// @param fileName the complete file path. It must not be empty. If the file -// -// exists, it is truncated before starting. If actively logging, -// this method is ignored. -// -// @param logAll to include basic events, user cookies, -// -// credentials and all transferred bytes in the log. This option presents -// a privacy risk, since it exposes the user's credentials, and should -// only be used with the user's consent and in situations where the log -// won't be public. false to just include basic events. -func (e Engine) StartNetLogToFile(fileName string, logAll bool) bool { - return cronet.EngineStartNetLogToFile(e.ptr, fileName, logAll) -} - -// StopNetLog Stops NetLog logging and flushes file to disk. If a logging session is -// not in progress, this call is ignored. This method blocks until the log is -// closed to ensure that log file is complete and available. -func (e Engine) StopNetLog() { - cronet.EngineStopNetLog(e.ptr) -} - -// Shutdown shuts down the Engine if there are no active requests, -// otherwise returns a failure Result. -// -// Cannot be called on network thread - the thread Cronet calls into -// Executor on (which is different from the thread the Executor invokes -// callbacks on). This method blocks until all the Engine's resources have -// been cleaned up. -func (e Engine) Shutdown() Result { - return Result(cronet.EngineShutdown(e.ptr)) -} - -// Version returns a human-readable version string of the engine. -func (e Engine) Version() string { - return cronet.EngineGetVersionString(e.ptr) -} - -// DefaultUserAgent Returns default human-readable version string of the engine. Can be used -// before StartWithParams() is called. -func (e Engine) DefaultUserAgent() string { - return cronet.EngineGetDefaultUserAgent(e.ptr) -} - -// AddRequestFinishListener registers a listener that gets called at the end of each request. -// -// The listener is called on Executor. -// -// The listener is called before URLRequestCallbackHandler.OnCanceled(), -// URLRequestCallbackHandler.OnFailed() or -// URLRequestCallbackHandler.OnSucceeded() is called -- note that if Executor -// runs the listener asynchronously, the actual call to the listener -// may happen after a URLRequestCallbackHandler method is called. -// -// Listeners are only guaranteed to be called for requests that are started -// after the listener is added. -// -// Ownership is **not** taken for listener or Executor. -// -// Assuming the listener won't run again (there are no pending requests with -// the listener attached, either via Engine or UrlRequest), -// the app may destroy it once its OnRequestFinished() has started, -// even inside that method. -// -// Similarly, the app may destroy executor in or after OnRequestFinished()}. -// -// It's also OK to destroy executor in or after one of -// URLRequestCallbackHandler.OnCanceled(), URLRequestCallbackHandler.OnFailed() or -// URLRequestCallbackHandler.OnSucceeded(). -// -// Of course, both of these are only true if listener won't run again -// and executor isn't being used for anything else that might start -// running in the future. -// -// @param listener the listener for finished requests. -// @param executor the executor upon which to run listener. -func (e Engine) AddRequestFinishListener(listener URLRequestFinishedInfoListener, executor Executor) { - cronet.EngineAddRequestFinishedListener(e.ptr, listener.ptr, executor.ptr) -} - -// RemoveRequestFinishListener unregisters a RequestFinishedInfoListener, -// including its association with its registered Executor. -func (e Engine) RemoveRequestFinishListener(listener URLRequestFinishedInfoListener) { - cronet.EngineRemoveRequestFinishedListener(e.ptr, listener.ptr) -} - -func (e Engine) SetClientContext(context unsafe.Pointer) { - cronet.EngineSetClientContext(e.ptr, uintptr(context)) -} - -func (e Engine) ClientContext() unsafe.Pointer { - return unsafe.Pointer(cronet.EngineGetClientContext(e.ptr)) -} - -// SetTrustedRootCertificates sets custom trusted root certificates for this engine. -// Must be called before StartWithParams(). -// pemRootCerts should be PEM-formatted certificates (can contain multiple certificates). -// Returns true if the certificates were successfully set, false if parsing failed. -func (e Engine) SetTrustedRootCertificates(pemRootCerts string) bool { - certVerifier := cronet.CreateCertVerifierWithRootCerts(pemRootCerts) - if certVerifier == 0 { - return false - } - cronet.EngineSetMockCertVerifierForTesting(e.ptr, certVerifier) - return true -} - -// CloseAllConnections closes all connections managed by the engine's network session. -// This includes socket pools, HTTP stream pool, SPDY session pool, and QUIC session pool. -// Useful for releasing connection-related memory or speeding up engine shutdown. -func (e Engine) CloseAllConnections() { - cronet.EngineCloseAllConnections(e.ptr) -} - -// SetDialer sets a custom dialer for TCP connections. -// When set, the engine will use this callback to establish TCP connections -// instead of the default system socket API. -// Must be called before StartWithParams(). -// Pass nil to disable custom dialing. -func (e Engine) SetDialer(dialer Dialer) { - if dialer == nil { - cronet.EngineSetDialer(e.ptr, 0, 0) - dialerAccess.Lock() - delete(dialerMap, e.ptr) - dialerAccess.Unlock() - return - } - dialerAccess.Lock() - dialerMap[e.ptr] = dialer - dialerAccess.Unlock() - cronet.EngineSetDialer(e.ptr, dialerCallback, e.ptr) -} - -// SetUDPDialer sets a custom dialer for UDP sockets. -// When set, the engine will use this callback to create UDP sockets instead of -// the default system socket API. -// Must be called before StartWithParams(). -// Pass nil to disable custom dialing. -func (e Engine) SetUDPDialer(dialer UDPDialer) { - if dialer == nil { - cronet.EngineSetUdpDialer(e.ptr, 0, 0) - udpDialerAccess.Lock() - delete(udpDialerMap, e.ptr) - udpDialerAccess.Unlock() - return - } - udpDialerAccess.Lock() - udpDialerMap[e.ptr] = dialer - udpDialerAccess.Unlock() - cronet.EngineSetUdpDialer(e.ptr, udpDialerCallback, e.ptr) -} diff --git a/engine_version_test.go b/engine_version_test.go deleted file mode 100644 index 6cbb62ff..00000000 --- a/engine_version_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package cronet_test - -import ( - "fmt" - "testing" - - "github.com/sagernet/cronet-go" -) - -func TestEngineVersion(t *testing.T) { - params := cronet.NewEngineParams() - params.SetUserAgent("test") - engine := cronet.NewEngine() - engine.StartWithParams(params) - defer params.Destroy() - defer engine.Destroy() - defer engine.Shutdown() - - version := engine.Version() - fmt.Printf("Cronet Engine Version: %s\n", version) - if version == "" { - t.Fatal("engine version is empty") - } -} diff --git a/error_cgo.go b/error_cgo.go deleted file mode 100644 index 8eec2380..00000000 --- a/error_cgo.go +++ /dev/null @@ -1,77 +0,0 @@ -//go:build !with_purego - -package cronet - -// #include -// #include -// #include -import "C" - -import "unsafe" - -func NewError() Error { - return Error{uintptr(unsafe.Pointer(C.Cronet_Error_Create()))} -} - -func (e Error) Destroy() { - C.Cronet_Error_Destroy(C.Cronet_ErrorPtr(unsafe.Pointer(e.ptr))) -} - -// ErrorCode return the error code, one of ErrorCode values. -func (e Error) ErrorCode() ErrorCode { - return ErrorCode(C.Cronet_Error_error_code_get(C.Cronet_ErrorPtr(unsafe.Pointer(e.ptr)))) -} - -// Message explaining the error. -func (e Error) Message() string { - return C.GoString(C.Cronet_Error_message_get(C.Cronet_ErrorPtr(unsafe.Pointer(e.ptr)))) -} - -// InternalErrorCode is the cronet internal error code. This may provide more specific error -// diagnosis than ErrorCode(), but the constant values may change over time. -// See -// here -// for the latest list of values. -func (e Error) InternalErrorCode() int { - return int(C.Cronet_Error_internal_error_code_get(C.Cronet_ErrorPtr(unsafe.Pointer(e.ptr)))) -} - -// Retryable |true| if retrying this request right away might succeed, |false| -// otherwise. For example, is |true| when ErrorCode() is ErrorCodeErrorNetworkChanged -// because trying the request might succeed using the new -// network configuration, but |false| when ErrorCode() is -// ErrorCodeErrorInternetDisconnected because retrying the request right away will -// encounter the same failure (instead retrying should be delayed until device regains -// network connectivity). -func (e Error) Retryable() bool { - return bool(C.Cronet_Error_immediately_retryable_get(C.Cronet_ErrorPtr(unsafe.Pointer(e.ptr)))) -} - -// QuicDetailedErrorCode contains detailed QUIC error code from -// -// QuicErrorCode when the ErrorCode() code is ErrorCodeErrorQuicProtocolFailed. -func (e Error) QuicDetailedErrorCode() int { - return int(C.Cronet_Error_quic_detailed_error_code_get(C.Cronet_ErrorPtr(unsafe.Pointer(e.ptr)))) -} - -func (e Error) SetErrorCode(code ErrorCode) { - C.Cronet_Error_error_code_set(C.Cronet_ErrorPtr(unsafe.Pointer(e.ptr)), C.Cronet_Error_ERROR_CODE(code)) -} - -func (e Error) SetMessage(message string) { - cMessage := C.CString(message) - C.Cronet_Error_message_set(C.Cronet_ErrorPtr(unsafe.Pointer(e.ptr)), cMessage) - C.free(unsafe.Pointer(cMessage)) -} - -func (e Error) SetInternalErrorCode(code int32) { - C.Cronet_Error_internal_error_code_set(C.Cronet_ErrorPtr(unsafe.Pointer(e.ptr)), C.int32_t(code)) -} - -func (e Error) SetRetryable(retryable bool) { - C.Cronet_Error_immediately_retryable_set(C.Cronet_ErrorPtr(unsafe.Pointer(e.ptr)), C.bool(retryable)) -} - -func (e Error) SetQuicDetailedErrorCode(code int32) { - C.Cronet_Error_quic_detailed_error_code_set(C.Cronet_ErrorPtr(unsafe.Pointer(e.ptr)), C.int32_t(code)) -} diff --git a/error_codes.go b/error_codes.go deleted file mode 100644 index 53d12295..00000000 --- a/error_codes.go +++ /dev/null @@ -1,46 +0,0 @@ -package cronet - -// ErrorCode represents the error code returned by cronet. -type ErrorCode int - -const ( - // ErrorCodeErrorCallback indicating the error returned by app callback. - ErrorCodeErrorCallback ErrorCode = 0 - - // ErrorCodeErrorHostnameNotResolved indicating the host being sent the request could not be resolved to an IP address. - ErrorCodeErrorHostnameNotResolved ErrorCode = 1 - - // ErrorCodeErrorInternetDisconnected indicating the device was not connected to any network. - ErrorCodeErrorInternetDisconnected ErrorCode = 2 - - // ErrorCodeErrorNetworkChanged indicating that as the request was processed the network configuration changed. - ErrorCodeErrorNetworkChanged ErrorCode = 3 - - // ErrorCodeErrorTimedOut indicating a timeout expired. Timeouts expiring while attempting to connect will - // be reported as the more specific ErrorCodeErrorConnectionTimedOut. - ErrorCodeErrorTimedOut ErrorCode = 4 - - // ErrorCodeErrorConnectionClosed indicating the connection was closed unexpectedly. - ErrorCodeErrorConnectionClosed ErrorCode = 5 - - // ErrorCodeErrorConnectionTimedOut indicating the connection attempt timed out. - ErrorCodeErrorConnectionTimedOut ErrorCode = 6 - - // ErrorCodeErrorConnectionRefused indicating the connection attempt was refused. - ErrorCodeErrorConnectionRefused ErrorCode = 7 - - // ErrorCodeErrorConnectionReset indicating the connection was unexpectedly reset. - ErrorCodeErrorConnectionReset ErrorCode = 8 - - // ErrorCodeErrorAddressUnreachable indicating the IP address being contacted is unreachable, - // meaning there is no route to the specified host or network. - ErrorCodeErrorAddressUnreachable ErrorCode = 9 - - // ErrorCodeErrorQuicProtocolFailed indicating an error related to the QUIC protocol. - // When Error.ErrorCode() is this code, see Error.QuicDetailedErrorCode() for more information. - ErrorCodeErrorQuicProtocolFailed ErrorCode = 10 - - // ErrorCodeErrorOther indicating another type of error was encountered. - // Error.InternalErrorCode() can be consulted to get a more specific cause. - ErrorCodeErrorOther ErrorCode = 11 -) diff --git a/error_go.go b/error_go.go deleted file mode 100644 index 88afa307..00000000 --- a/error_go.go +++ /dev/null @@ -1,31 +0,0 @@ -package cronet - -type ErrorGo struct { - ErrorCode ErrorCode - Message string - InternalErrorCode int - Retryable bool - QuicDetailedErrorCode int -} - -func (e *ErrorGo) Error() string { - return e.Message -} - -func (e *ErrorGo) Timeout() bool { - return e.ErrorCode == ErrorCodeErrorConnectionTimedOut -} - -func (e *ErrorGo) Temporary() bool { - return e.Retryable -} - -func ErrorFromError(error Error) *ErrorGo { - return &ErrorGo{ - ErrorCode: error.ErrorCode(), - Message: error.Message(), - InternalErrorCode: error.InternalErrorCode(), - Retryable: error.Retryable(), - QuicDetailedErrorCode: error.QuicDetailedErrorCode(), - } -} diff --git a/error_purego.go b/error_purego.go deleted file mode 100644 index 351b6c6b..00000000 --- a/error_purego.go +++ /dev/null @@ -1,55 +0,0 @@ -//go:build with_purego - -package cronet - -import ( - "github.com/sagernet/cronet-go/internal/cronet" -) - -func NewError() Error { - return Error{cronet.ErrorCreate()} -} - -func (e Error) Destroy() { - cronet.ErrorDestroy(e.ptr) -} - -func (e Error) ErrorCode() ErrorCode { - return ErrorCode(cronet.ErrorErrorCodeGet(e.ptr)) -} - -func (e Error) Message() string { - return cronet.ErrorMessageGet(e.ptr) -} - -func (e Error) InternalErrorCode() int { - return int(cronet.ErrorInternalErrorCodeGet(e.ptr)) -} - -func (e Error) Retryable() bool { - return cronet.ErrorImmediatelyRetryableGet(e.ptr) -} - -func (e Error) QuicDetailedErrorCode() int { - return int(cronet.ErrorQuicDetailedErrorCodeGet(e.ptr)) -} - -func (e Error) SetErrorCode(code ErrorCode) { - cronet.ErrorErrorCodeSet(e.ptr, int32(code)) -} - -func (e Error) SetMessage(message string) { - cronet.ErrorMessageSet(e.ptr, message) -} - -func (e Error) SetInternalErrorCode(code int32) { - cronet.ErrorInternalErrorCodeSet(e.ptr, code) -} - -func (e Error) SetRetryable(retryable bool) { - cronet.ErrorImmediatelyRetryableSet(e.ptr, retryable) -} - -func (e Error) SetQuicDetailedErrorCode(code int32) { - cronet.ErrorQuicDetailedErrorCodeSet(e.ptr, code) -} diff --git a/executor_cgo.go b/executor_cgo.go deleted file mode 100644 index 14169a25..00000000 --- a/executor_cgo.go +++ /dev/null @@ -1,22 +0,0 @@ -//go:build !with_purego - -package cronet - -// #include -// #include -// #include -import "C" - -import "unsafe" - -func (e Executor) Execute(command Runnable) { - C.Cronet_Executor_Execute(C.Cronet_ExecutorPtr(unsafe.Pointer(e.ptr)), C.Cronet_RunnablePtr(unsafe.Pointer(command.ptr))) -} - -func (e Executor) SetClientContext(context unsafe.Pointer) { - C.Cronet_Executor_SetClientContext(C.Cronet_ExecutorPtr(unsafe.Pointer(e.ptr)), C.Cronet_ClientContext(context)) -} - -func (e Executor) ClientContext() unsafe.Pointer { - return unsafe.Pointer(C.Cronet_Executor_GetClientContext(C.Cronet_ExecutorPtr(unsafe.Pointer(e.ptr)))) -} diff --git a/executor_impl_cgo.go b/executor_impl_cgo.go deleted file mode 100644 index 0eb7dd0d..00000000 --- a/executor_impl_cgo.go +++ /dev/null @@ -1,66 +0,0 @@ -//go:build !with_purego - -package cronet - -// #include -// #include -// #include -// extern CRONET_EXPORT void cronetExecutorExecute(Cronet_ExecutorPtr self,Cronet_RunnablePtr command); -import "C" - -import ( - "sync" - "sync/atomic" - "unsafe" -) - -type executorEntry struct { - executeFunc ExecutorExecuteFunc - destroyed atomic.Bool -} - -var ( - executorAccess sync.RWMutex - executors map[uintptr]*executorEntry -) - -func init() { - executors = make(map[uintptr]*executorEntry) -} - -func NewExecutor(executeFunc ExecutorExecuteFunc) Executor { - if executeFunc == nil { - panic("nil executor execute function") - } - ptr := C.Cronet_Executor_CreateWith((*[0]byte)(C.cronetExecutorExecute)) - ptrVal := uintptr(unsafe.Pointer(ptr)) - executorAccess.Lock() - executors[ptrVal] = &executorEntry{executeFunc: executeFunc} - executorAccess.Unlock() - return Executor{ptrVal} -} - -func (e Executor) Destroy() { - executorAccess.Lock() - entry := executors[e.ptr] - if entry != nil { - entry.destroyed.Store(true) - } - executorAccess.Unlock() - C.Cronet_Executor_Destroy(C.Cronet_ExecutorPtr(unsafe.Pointer(e.ptr))) - // Cleanup after C destroy - executorAccess.Lock() - delete(executors, e.ptr) - executorAccess.Unlock() -} - -//export cronetExecutorExecute -func cronetExecutorExecute(self C.Cronet_ExecutorPtr, command C.Cronet_RunnablePtr) { - executorAccess.RLock() - entry := executors[uintptr(unsafe.Pointer(self))] - executorAccess.RUnlock() - if entry == nil || entry.destroyed.Load() { - return // Post-destroy callback, silently ignore - } - entry.executeFunc(Executor{uintptr(unsafe.Pointer(self))}, Runnable{uintptr(unsafe.Pointer(command))}) -} diff --git a/executor_impl_purego.go b/executor_impl_purego.go deleted file mode 100644 index f813648e..00000000 --- a/executor_impl_purego.go +++ /dev/null @@ -1,74 +0,0 @@ -//go:build with_purego - -package cronet - -// Design Philosophy: Fail-Fast -// -// This file intentionally does NOT use panic recovery in callbacks. -// If a callback handler panics, the process should crash immediately. -// Using recover() would mask programming errors and make debugging harder. -// Errors should be visible and cause immediate failure, not be silently swallowed. -// -// Note: Post-destroy callbacks silently return because they are expected -// async API behavior, not programming errors. - -import ( - "sync" - "sync/atomic" - - "github.com/sagernet/cronet-go/internal/cronet" - - "github.com/ebitengine/purego" -) - -type executorEntry struct { - executeFunc ExecutorExecuteFunc - destroyed atomic.Bool -} - -var ( - executorAccess sync.RWMutex - executors map[uintptr]*executorEntry - executorCallbackFn uintptr -) - -func init() { - executors = make(map[uintptr]*executorEntry) - executorCallbackFn = purego.NewCallback(executorExecuteCallback) -} - -func executorExecuteCallback(self, command uintptr) uintptr { - executorAccess.RLock() - entry := executors[self] - executorAccess.RUnlock() - if entry == nil || entry.destroyed.Load() { - return 0 // Post-destroy callback, silently ignore - } - entry.executeFunc(Executor{self}, Runnable{command}) - return 0 -} - -func NewExecutor(executeFunc ExecutorExecuteFunc) Executor { - if executeFunc == nil { - panic("nil executor execute function") - } - ptr := cronet.ExecutorCreateWith(executorCallbackFn) - executorAccess.Lock() - executors[ptr] = &executorEntry{executeFunc: executeFunc} - executorAccess.Unlock() - return Executor{ptr} -} - -func (e Executor) Destroy() { - executorAccess.Lock() - entry := executors[e.ptr] - if entry != nil { - entry.destroyed.Store(true) - } - executorAccess.Unlock() - cronet.ExecutorDestroy(e.ptr) - // Cleanup after C destroy - executorAccess.Lock() - delete(executors, e.ptr) - executorAccess.Unlock() -} diff --git a/executor_purego.go b/executor_purego.go deleted file mode 100644 index a0723d2d..00000000 --- a/executor_purego.go +++ /dev/null @@ -1,21 +0,0 @@ -//go:build with_purego - -package cronet - -import ( - "unsafe" - - "github.com/sagernet/cronet-go/internal/cronet" -) - -func (e Executor) SetClientContext(context unsafe.Pointer) { - cronet.ExecutorSetClientContext(e.ptr, uintptr(context)) -} - -func (e Executor) ClientContext() unsafe.Pointer { - return unsafe.Pointer(cronet.ExecutorGetClientContext(e.ptr)) -} - -func (e Executor) Execute(runnable Runnable) { - cronet.ExecutorExecute(e.ptr, runnable.ptr) -} diff --git a/go.mod b/go.mod index 00e78c96..821e1861 100644 --- a/go.mod +++ b/go.mod @@ -2,19 +2,9 @@ module github.com/sagernet/cronet-go go 1.20 -require ( - github.com/ebitengine/purego v0.9.1 - github.com/miekg/dns v1.1.50 - github.com/sagernet/sing v0.7.13 - github.com/spf13/cobra v1.4.0 - golang.org/x/sys v0.30.0 -) +require github.com/spf13/cobra v1.4.0 require ( github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/mod v0.4.2 // indirect - golang.org/x/net v0.35.0 // indirect - golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 // indirect - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect ) diff --git a/http_header_cgo.go b/http_header_cgo.go deleted file mode 100644 index c1f56876..00000000 --- a/http_header_cgo.go +++ /dev/null @@ -1,39 +0,0 @@ -//go:build !with_purego - -package cronet - -// #include -// #include -// #include -import "C" -import "unsafe" - -func NewHTTPHeader() HTTPHeader { - return HTTPHeader{uintptr(unsafe.Pointer(C.Cronet_HttpHeader_Create()))} -} - -func (h HTTPHeader) Destroy() { - C.Cronet_HttpHeader_Destroy(C.Cronet_HttpHeaderPtr(unsafe.Pointer(h.ptr))) -} - -// SetName sets header name -func (h HTTPHeader) SetName(name string) { - cName := C.CString(name) - C.Cronet_HttpHeader_name_set(C.Cronet_HttpHeaderPtr(unsafe.Pointer(h.ptr)), cName) - C.free(unsafe.Pointer(cName)) -} - -func (h HTTPHeader) Name() string { - return C.GoString(C.Cronet_HttpHeader_name_get(C.Cronet_HttpHeaderPtr(unsafe.Pointer(h.ptr)))) -} - -// SetValue sts header value -func (h HTTPHeader) SetValue(value string) { - cValue := C.CString(value) - C.Cronet_HttpHeader_value_set(C.Cronet_HttpHeaderPtr(unsafe.Pointer(h.ptr)), cValue) - C.free(unsafe.Pointer(cValue)) -} - -func (h HTTPHeader) Value() string { - return C.GoString(C.Cronet_HttpHeader_value_get(C.Cronet_HttpHeaderPtr(unsafe.Pointer(h.ptr)))) -} diff --git a/http_header_purego.go b/http_header_purego.go deleted file mode 100644 index 4da0f48b..00000000 --- a/http_header_purego.go +++ /dev/null @@ -1,31 +0,0 @@ -//go:build with_purego - -package cronet - -import ( - "github.com/sagernet/cronet-go/internal/cronet" -) - -func NewHTTPHeader() HTTPHeader { - return HTTPHeader{cronet.HttpHeaderCreate()} -} - -func (h HTTPHeader) Destroy() { - cronet.HttpHeaderDestroy(h.ptr) -} - -func (h HTTPHeader) SetName(name string) { - cronet.HttpHeaderNameSet(h.ptr, name) -} - -func (h HTTPHeader) Name() string { - return cronet.HttpHeaderNameGet(h.ptr) -} - -func (h HTTPHeader) SetValue(value string) { - cronet.HttpHeaderValueSet(h.ptr, value) -} - -func (h HTTPHeader) Value() string { - return cronet.HttpHeaderValueGet(h.ptr) -} diff --git a/include_cgo.go b/include_cgo.go deleted file mode 100644 index 4e859058..00000000 --- a/include_cgo.go +++ /dev/null @@ -1,6 +0,0 @@ -//go:build !with_purego - -package cronet - -// #cgo CFLAGS: -I${SRCDIR}/include -import "C" diff --git a/internal/cronet/api_purego.go b/internal/cronet/api_purego.go deleted file mode 100644 index c0578aa3..00000000 --- a/internal/cronet/api_purego.go +++ /dev/null @@ -1,1033 +0,0 @@ -//go:build with_purego - -package cronet - -import ( - "runtime" - "unsafe" -) - -// CString allocates a null-terminated C string from a Go string. -// It returns both the pointer and the backing byte slice. -// The caller must keep the byte slice alive (via runtime.KeepAlive) -// until the pointer is no longer needed by C code. -func CString(s string) (uintptr, []byte) { - b := make([]byte, len(s)+1) - copy(b, s) - b[len(s)] = 0 - return uintptr(unsafe.Pointer(&b[0])), b -} - -// Engine API - -func EngineCreate() uintptr { - ensureLoaded() - return cronetEngineCreate() -} - -func EngineDestroy(engine uintptr) { - cronetEngineDestroy(engine) -} - -func EngineStartNetLogToFile(engine uintptr, fileName string, logAll bool) bool { - return cronetEngineStartNetLogToFile(engine, fileName, logAll) -} - -func EngineStopNetLog(engine uintptr) { - cronetEngineStopNetLog(engine) -} - -func EngineShutdown(engine uintptr) int32 { - return cronetEngineShutdown(engine) -} - -func EngineGetVersionString(engine uintptr) string { - return GoString(cronetEngineGetVersionString(engine)) -} - -func EngineGetDefaultUserAgent(engine uintptr) string { - return GoString(cronetEngineGetDefaultUserAgent(engine)) -} - -func EngineAddRequestFinishedListener(engine, listener, executor uintptr) { - cronetEngineAddRequestFinishedListener(engine, listener, executor) -} - -func EngineRemoveRequestFinishedListener(engine, listener uintptr) { - cronetEngineRemoveRequestFinishedListener(engine, listener) -} - -func EngineSetClientContext(engine, context uintptr) { - cronetEngineSetClientContext(engine, context) -} - -func EngineGetClientContext(engine uintptr) uintptr { - return cronetEngineGetClientContext(engine) -} - -func EngineSetMockCertVerifierForTesting(engine, certVerifier uintptr) { - cronetEngineSetMockCertVerifierForTesting(engine, certVerifier) -} - -func EngineCloseAllConnections(engine uintptr) { - cronetEngineCloseAllConnections(engine) -} - -func EngineSetDialer(engine, dialer, context uintptr) { - cronetEngineSetDialer(engine, dialer, context) -} - -func EngineSetUdpDialer(engine, dialer, context uintptr) { - cronetEngineSetUdpDialer(engine, dialer, context) -} - -func EngineGetStreamEngine(engine uintptr) uintptr { - return cronetEngineGetStreamEngine(engine) -} - -// EngineParams API - -func EngineParamsCreate() uintptr { - ensureLoaded() - return cronetEngineParamsCreate() -} - -func EngineParamsDestroy(params uintptr) { - cronetEngineParamsDestroy(params) -} - -func EngineParamsEnableCheckResultSet(params uintptr, value bool) { - cronetEngineParamsEnableCheckResultSet(params, value) -} - -func EngineParamsUserAgentSet(params uintptr, userAgent string) { - cronetEngineParamsUserAgentSet(params, userAgent) -} - -func EngineParamsAcceptLanguageSet(params uintptr, acceptLanguage string) { - cronetEngineParamsAcceptLanguageSet(params, acceptLanguage) -} - -func EngineParamsStoragePathSet(params uintptr, storagePath string) { - cronetEngineParamsStoragePathSet(params, storagePath) -} - -func EngineParamsEnableQuicSet(params uintptr, value bool) { - cronetEngineParamsEnableQuicSet(params, value) -} - -func EngineParamsEnableHttp2Set(params uintptr, value bool) { - cronetEngineParamsEnableHttp2Set(params, value) -} - -func EngineParamsEnableBrotliSet(params uintptr, value bool) { - cronetEngineParamsEnableBrotliSet(params, value) -} - -func EngineParamsHttpCacheModeSet(params uintptr, mode int32) { - cronetEngineParamsHttpCacheModeSet(params, mode) -} - -func EngineParamsHttpCacheMaxSizeSet(params uintptr, size int64) { - cronetEngineParamsHttpCacheMaxSizeSet(params, size) -} - -func EngineParamsQuicHintsAdd(params, quicHint uintptr) { - cronetEngineParamsQuicHintsAdd(params, quicHint) -} - -func EngineParamsPublicKeyPinsAdd(params, publicKeyPins uintptr) { - cronetEngineParamsPublicKeyPinsAdd(params, publicKeyPins) -} - -func EngineParamsEnablePublicKeyPinningBypassForLocalTrustAnchorsSet(params uintptr, value bool) { - cronetEngineParamsEnablePublicKeyPinningBypassForLocalTrustAnchorsSet(params, value) -} - -func EngineParamsExperimentalOptionsSet(params uintptr, options string) { - cronetEngineParamsExperimentalOptionsSet(params, options) -} - -func EngineParamsEnableCheckResultGet(params uintptr) bool { - return cronetEngineParamsEnableCheckResultGet(params) -} - -func EngineParamsUserAgentGet(params uintptr) string { - return GoString(cronetEngineParamsUserAgentGet(params)) -} - -func EngineParamsAcceptLanguageGet(params uintptr) string { - return GoString(cronetEngineParamsAcceptLanguageGet(params)) -} - -func EngineParamsStoragePathGet(params uintptr) string { - return GoString(cronetEngineParamsStoragePathGet(params)) -} - -func EngineParamsEnableQuicGet(params uintptr) bool { - return cronetEngineParamsEnableQuicGet(params) -} - -func EngineParamsEnableHttp2Get(params uintptr) bool { - return cronetEngineParamsEnableHttp2Get(params) -} - -func EngineParamsEnableBrotliGet(params uintptr) bool { - return cronetEngineParamsEnableBrotliGet(params) -} - -func EngineParamsHttpCacheModeGet(params uintptr) int32 { - return cronetEngineParamsHttpCacheModeGet(params) -} - -func EngineParamsHttpCacheMaxSizeGet(params uintptr) int64 { - return cronetEngineParamsHttpCacheMaxSizeGet(params) -} - -func EngineParamsQuicHintsSize(params uintptr) uint32 { - return cronetEngineParamsQuicHintsSize(params) -} - -func EngineParamsQuicHintsAt(params uintptr, index uint32) uintptr { - return cronetEngineParamsQuicHintsAt(params, index) -} - -func EngineParamsQuicHintsClear(params uintptr) { - cronetEngineParamsQuicHintsClear(params) -} - -func EngineParamsPublicKeyPinsSize(params uintptr) uint32 { - return cronetEngineParamsPublicKeyPinsSize(params) -} - -func EngineParamsPublicKeyPinsAt(params uintptr, index uint32) uintptr { - return cronetEngineParamsPublicKeyPinsAt(params, index) -} - -func EngineParamsPublicKeyPinsClear(params uintptr) { - cronetEngineParamsPublicKeyPinsClear(params) -} - -func EngineParamsEnablePublicKeyPinningBypassForLocalTrustAnchorsGet(params uintptr) bool { - return cronetEngineParamsEnablePublicKeyPinningBypassForLocalTrustAnchorsGet(params) -} - -func EngineParamsExperimentalOptionsGet(params uintptr) string { - return GoString(cronetEngineParamsExperimentalOptionsGet(params)) -} - -// Certificate verifier functions - -func CreateCertVerifierWithRootCerts(pemRootCerts string) uintptr { - ensureLoaded() - return cronetCreateCertVerifierWithRootCerts(pemRootCerts) -} - -// Buffer API - -func BufferCreate() uintptr { - ensureLoaded() - return cronetBufferCreate() -} - -func BufferDestroy(buffer uintptr) { - cronetBufferDestroy(buffer) -} - -func BufferSetClientContext(buffer, context uintptr) { - cronetBufferSetClientContext(buffer, context) -} - -func BufferGetClientContext(buffer uintptr) uintptr { - return cronetBufferGetClientContext(buffer) -} - -func BufferInitWithDataAndCallback(buffer, data uintptr, size uint64, callback uintptr) { - cronetBufferInitWithDataAndCallback(buffer, data, size, callback) -} - -func BufferInitWithAlloc(buffer uintptr, size uint64) { - cronetBufferInitWithAlloc(buffer, size) -} - -func BufferGetSize(buffer uintptr) uint64 { - return cronetBufferGetSize(buffer) -} - -func BufferGetData(buffer uintptr) uintptr { - return cronetBufferGetData(buffer) -} - -// BufferCallback API - -func BufferCallbackDestroy(callback uintptr) { - cronetBufferCallbackDestroy(callback) -} - -func BufferCallbackSetClientContext(callback, context uintptr) { - cronetBufferCallbackSetClientContext(callback, context) -} - -func BufferCallbackGetClientContext(callback uintptr) uintptr { - return cronetBufferCallbackGetClientContext(callback) -} - -func BufferCallbackCreateWith(onDestroy uintptr) uintptr { - ensureLoaded() - return cronetBufferCallbackCreateWith(onDestroy) -} - -// Executor API - -func ExecutorDestroy(executor uintptr) { - cronetExecutorDestroy(executor) -} - -func ExecutorSetClientContext(executor, context uintptr) { - cronetExecutorSetClientContext(executor, context) -} - -func ExecutorGetClientContext(executor uintptr) uintptr { - return cronetExecutorGetClientContext(executor) -} - -func ExecutorExecute(executor, runnable uintptr) { - cronetExecutorExecute(executor, runnable) -} - -func ExecutorCreateWith(execute uintptr) uintptr { - ensureLoaded() - return cronetExecutorCreateWith(execute) -} - -// Runnable API - -func RunnableDestroy(runnable uintptr) { - cronetRunnableDestroy(runnable) -} - -func RunnableSetClientContext(runnable, context uintptr) { - cronetRunnableSetClientContext(runnable, context) -} - -func RunnableGetClientContext(runnable uintptr) uintptr { - return cronetRunnableGetClientContext(runnable) -} - -func RunnableRun(runnable uintptr) { - cronetRunnableRun(runnable) -} - -func RunnableCreateWith(run uintptr) uintptr { - ensureLoaded() - return cronetRunnableCreateWith(run) -} - -// URLRequest API - -func UrlRequestCreate() uintptr { - ensureLoaded() - return cronetUrlRequestCreate() -} - -func UrlRequestDestroy(request uintptr) { - cronetUrlRequestDestroy(request) -} - -func UrlRequestSetClientContext(request, context uintptr) { - cronetUrlRequestSetClientContext(request, context) -} - -func UrlRequestGetClientContext(request uintptr) uintptr { - return cronetUrlRequestGetClientContext(request) -} - -func UrlRequestInitWithParams(request, engine uintptr, url string, params, callback, executor uintptr) int32 { - return cronetUrlRequestInitWithParams(request, engine, url, params, callback, executor) -} - -func UrlRequestStart(request uintptr) int32 { - return cronetUrlRequestStart(request) -} - -func UrlRequestFollowRedirect(request uintptr) int32 { - return cronetUrlRequestFollowRedirect(request) -} - -func UrlRequestRead(request, buffer uintptr) int32 { - return cronetUrlRequestRead(request, buffer) -} - -func UrlRequestCancel(request uintptr) { - cronetUrlRequestCancel(request) -} - -func UrlRequestIsDone(request uintptr) bool { - return cronetUrlRequestIsDone(request) -} - -func UrlRequestGetStatus(request, listener uintptr) { - cronetUrlRequestGetStatus(request, listener) -} - -// URLRequestParams API - -func UrlRequestParamsCreate() uintptr { - ensureLoaded() - return cronetUrlRequestParamsCreate() -} - -func UrlRequestParamsDestroy(params uintptr) { - cronetUrlRequestParamsDestroy(params) -} - -func UrlRequestParamsHttpMethodSet(params uintptr, method string) { - cronetUrlRequestParamsHttpMethodSet(params, method) -} - -func UrlRequestParamsRequestHeadersAdd(params, header uintptr) { - cronetUrlRequestParamsRequestHeadersAdd(params, header) -} - -func UrlRequestParamsDisableCacheSet(params uintptr, value bool) { - cronetUrlRequestParamsDisableCacheSet(params, value) -} - -func UrlRequestParamsPrioritySet(params uintptr, priority int32) { - cronetUrlRequestParamsPrioritySet(params, priority) -} - -func UrlRequestParamsUploadDataProviderSet(params, provider uintptr) { - cronetUrlRequestParamsUploadDataProviderSet(params, provider) -} - -func UrlRequestParamsUploadDataProviderExecutorSet(params, executor uintptr) { - cronetUrlRequestParamsUploadDataProviderExecutorSet(params, executor) -} - -func UrlRequestParamsAllowDirectExecutorSet(params uintptr, value bool) { - cronetUrlRequestParamsAllowDirectExecutorSet(params, value) -} - -func UrlRequestParamsAnnotationsAdd(params, annotation uintptr) { - cronetUrlRequestParamsAnnotationsAdd(params, annotation) -} - -func UrlRequestParamsRequestFinishedListenerSet(params, listener uintptr) { - cronetUrlRequestParamsRequestFinishedListenerSet(params, listener) -} - -func UrlRequestParamsRequestFinishedExecutorSet(params, executor uintptr) { - cronetUrlRequestParamsRequestFinishedExecutorSet(params, executor) -} - -func UrlRequestParamsIdempotencySet(params uintptr, idempotency int32) { - cronetUrlRequestParamsIdempotencySet(params, idempotency) -} - -func UrlRequestParamsHttpMethodGet(params uintptr) string { - return GoString(cronetUrlRequestParamsHttpMethodGet(params)) -} - -func UrlRequestParamsRequestHeadersSize(params uintptr) uint32 { - return cronetUrlRequestParamsRequestHeadersSize(params) -} - -func UrlRequestParamsRequestHeadersAt(params uintptr, index uint32) uintptr { - return cronetUrlRequestParamsRequestHeadersAt(params, index) -} - -func UrlRequestParamsRequestHeadersClear(params uintptr) { - cronetUrlRequestParamsRequestHeadersClear(params) -} - -func UrlRequestParamsDisableCacheGet(params uintptr) bool { - return cronetUrlRequestParamsDisableCacheGet(params) -} - -func UrlRequestParamsPriorityGet(params uintptr) int32 { - return cronetUrlRequestParamsPriorityGet(params) -} - -func UrlRequestParamsUploadDataProviderGet(params uintptr) uintptr { - return cronetUrlRequestParamsUploadDataProviderGet(params) -} - -func UrlRequestParamsUploadDataProviderExecutorGet(params uintptr) uintptr { - return cronetUrlRequestParamsUploadDataProviderExecutorGet(params) -} - -func UrlRequestParamsAllowDirectExecutorGet(params uintptr) bool { - return cronetUrlRequestParamsAllowDirectExecutorGet(params) -} - -func UrlRequestParamsAnnotationsSize(params uintptr) uint32 { - return cronetUrlRequestParamsAnnotationsSize(params) -} - -func UrlRequestParamsAnnotationsAt(params uintptr, index uint32) uintptr { - return cronetUrlRequestParamsAnnotationsAt(params, index) -} - -func UrlRequestParamsAnnotationsClear(params uintptr) { - cronetUrlRequestParamsAnnotationsClear(params) -} - -func UrlRequestParamsRequestFinishedListenerGet(params uintptr) uintptr { - return cronetUrlRequestParamsRequestFinishedListenerGet(params) -} - -func UrlRequestParamsRequestFinishedExecutorGet(params uintptr) uintptr { - return cronetUrlRequestParamsRequestFinishedExecutorGet(params) -} - -func UrlRequestParamsIdempotencyGet(params uintptr) int32 { - return cronetUrlRequestParamsIdempotencyGet(params) -} - -// URLRequestCallback API - -func UrlRequestCallbackDestroy(callback uintptr) { - cronetUrlRequestCallbackDestroy(callback) -} - -func UrlRequestCallbackSetClientContext(callback, context uintptr) { - cronetUrlRequestCallbackSetClientContext(callback, context) -} - -func UrlRequestCallbackGetClientContext(callback uintptr) uintptr { - return cronetUrlRequestCallbackGetClientContext(callback) -} - -func UrlRequestCallbackCreateWith(onRedirectReceived, onResponseStarted, onReadCompleted, onSucceeded, onFailed, onCanceled uintptr) uintptr { - ensureLoaded() - return cronetUrlRequestCallbackCreateWith(onRedirectReceived, onResponseStarted, onReadCompleted, onSucceeded, onFailed, onCanceled) -} - -// URLRequestStatusListener API - -func UrlRequestStatusListenerDestroy(listener uintptr) { - cronetUrlRequestStatusListenerDestroy(listener) -} - -func UrlRequestStatusListenerSetClientContext(listener, context uintptr) { - cronetUrlRequestStatusListenerSetClientContext(listener, context) -} - -func UrlRequestStatusListenerGetClientContext(listener uintptr) uintptr { - return cronetUrlRequestStatusListenerGetClientContext(listener) -} - -func UrlRequestStatusListenerCreateWith(onStatus uintptr) uintptr { - ensureLoaded() - return cronetUrlRequestStatusListenerCreateWith(onStatus) -} - -// UploadDataProvider API - -func UploadDataProviderDestroy(provider uintptr) { - cronetUploadDataProviderDestroy(provider) -} - -func UploadDataProviderSetClientContext(provider, context uintptr) { - cronetUploadDataProviderSetClientContext(provider, context) -} - -func UploadDataProviderGetClientContext(provider uintptr) uintptr { - return cronetUploadDataProviderGetClientContext(provider) -} - -func UploadDataProviderCreateWith(getLength, read, rewind, close uintptr) uintptr { - ensureLoaded() - return cronetUploadDataProviderCreateWith(getLength, read, rewind, close) -} - -// UploadDataSink API - -func UploadDataSinkDestroy(sink uintptr) { - cronetUploadDataSinkDestroy(sink) -} - -func UploadDataSinkSetClientContext(sink, context uintptr) { - cronetUploadDataSinkSetClientContext(sink, context) -} - -func UploadDataSinkGetClientContext(sink uintptr) uintptr { - return cronetUploadDataSinkGetClientContext(sink) -} - -func UploadDataSinkOnReadSucceeded(sink uintptr, bytesRead uint64, finalChunk bool) { - cronetUploadDataSinkOnReadSucceeded(sink, bytesRead, finalChunk) -} - -func UploadDataSinkOnReadError(sink uintptr, message string) { - cronetUploadDataSinkOnReadError(sink, message) -} - -func UploadDataSinkOnRewindSucceeded(sink uintptr) { - cronetUploadDataSinkOnRewindSucceeded(sink) -} - -func UploadDataSinkOnRewindError(sink uintptr, message string) { - cronetUploadDataSinkOnRewindError(sink, message) -} - -// URLResponseInfo API - -func UrlResponseInfoCreate() uintptr { - ensureLoaded() - return cronetUrlResponseInfoCreate() -} - -func UrlResponseInfoDestroy(info uintptr) { - cronetUrlResponseInfoDestroy(info) -} - -func UrlResponseInfoUrlGet(info uintptr) string { - return GoString(cronetUrlResponseInfoUrlGet(info)) -} - -func UrlResponseInfoUrlChainSize(info uintptr) uint32 { - return cronetUrlResponseInfoUrlChainSize(info) -} - -func UrlResponseInfoUrlChainAt(info uintptr, index uint32) string { - return GoString(cronetUrlResponseInfoUrlChainAt(info, index)) -} - -func UrlResponseInfoHttpStatusCodeGet(info uintptr) int32 { - return cronetUrlResponseInfoHttpStatusCodeGet(info) -} - -func UrlResponseInfoHttpStatusTextGet(info uintptr) string { - return GoString(cronetUrlResponseInfoHttpStatusTextGet(info)) -} - -func UrlResponseInfoAllHeadersListSize(info uintptr) uint32 { - return cronetUrlResponseInfoAllHeadersListSize(info) -} - -func UrlResponseInfoAllHeadersListAt(info uintptr, index uint32) uintptr { - return cronetUrlResponseInfoAllHeadersListAt(info, index) -} - -func UrlResponseInfoWasCachedGet(info uintptr) bool { - return cronetUrlResponseInfoWasCachedGet(info) -} - -func UrlResponseInfoNegotiatedProtocolGet(info uintptr) string { - return GoString(cronetUrlResponseInfoNegotiatedProtocolGet(info)) -} - -func UrlResponseInfoProxyServerGet(info uintptr) string { - return GoString(cronetUrlResponseInfoProxyServerGet(info)) -} - -func UrlResponseInfoReceivedByteCountGet(info uintptr) int64 { - return cronetUrlResponseInfoReceivedByteCountGet(info) -} - -// Error API - -func ErrorCreate() uintptr { - ensureLoaded() - return cronetErrorCreate() -} - -func ErrorDestroy(err uintptr) { - cronetErrorDestroy(err) -} - -func ErrorErrorCodeGet(err uintptr) int32 { - return cronetErrorErrorCodeGet(err) -} - -func ErrorMessageGet(err uintptr) string { - return GoString(cronetErrorMessageGet(err)) -} - -func ErrorInternalErrorCodeGet(err uintptr) int32 { - return cronetErrorInternalErrorCodeGet(err) -} - -func ErrorImmediatelyRetryableGet(err uintptr) bool { - return cronetErrorImmediatelyRetryableGet(err) -} - -func ErrorQuicDetailedErrorCodeGet(err uintptr) int32 { - return cronetErrorQuicDetailedErrorCodeGet(err) -} - -func ErrorErrorCodeSet(err uintptr, code int32) { - cronetErrorErrorCodeSet(err, code) -} - -func ErrorMessageSet(err uintptr, message string) { - ptr, backing := CString(message) - cronetErrorMessageSet(err, ptr) - runtime.KeepAlive(backing) -} - -func ErrorInternalErrorCodeSet(err uintptr, code int32) { - cronetErrorInternalErrorCodeSet(err, code) -} - -func ErrorImmediatelyRetryableSet(err uintptr, retryable bool) { - cronetErrorImmediatelyRetryableSet(err, retryable) -} - -func ErrorQuicDetailedErrorCodeSet(err uintptr, code int32) { - cronetErrorQuicDetailedErrorCodeSet(err, code) -} - -// HttpHeader API - -func HttpHeaderCreate() uintptr { - ensureLoaded() - return cronetHttpHeaderCreate() -} - -func HttpHeaderDestroy(header uintptr) { - cronetHttpHeaderDestroy(header) -} - -func HttpHeaderNameSet(header uintptr, name string) { - cronetHttpHeaderNameSet(header, name) -} - -func HttpHeaderValueSet(header uintptr, value string) { - cronetHttpHeaderValueSet(header, value) -} - -func HttpHeaderNameGet(header uintptr) string { - return GoString(cronetHttpHeaderNameGet(header)) -} - -func HttpHeaderValueGet(header uintptr) string { - return GoString(cronetHttpHeaderValueGet(header)) -} - -// QuicHint API - -func QuicHintCreate() uintptr { - ensureLoaded() - return cronetQuicHintCreate() -} - -func QuicHintDestroy(hint uintptr) { - cronetQuicHintDestroy(hint) -} - -func QuicHintHostSet(hint uintptr, host string) { - cronetQuicHintHostSet(hint, host) -} - -func QuicHintPortSet(hint uintptr, port int32) { - cronetQuicHintPortSet(hint, port) -} - -func QuicHintAlternatePortSet(hint uintptr, port int32) { - cronetQuicHintAlternatePortSet(hint, port) -} - -func QuicHintHostGet(hint uintptr) string { - return GoString(cronetQuicHintHostGet(hint)) -} - -func QuicHintPortGet(hint uintptr) int32 { - return cronetQuicHintPortGet(hint) -} - -func QuicHintAlternatePortGet(hint uintptr) int32 { - return cronetQuicHintAlternatePortGet(hint) -} - -// PublicKeyPins API - -func PublicKeyPinsCreate() uintptr { - ensureLoaded() - return cronetPublicKeyPinsCreate() -} - -func PublicKeyPinsDestroy(pins uintptr) { - cronetPublicKeyPinsDestroy(pins) -} - -func PublicKeyPinsHostSet(pins uintptr, host string) { - cronetPublicKeyPinsHostSet(pins, host) -} - -func PublicKeyPinsPinsSha256Add(pins uintptr, pinSha256 string) { - cronetPublicKeyPinsPinsSha256Add(pins, pinSha256) -} - -func PublicKeyPinsIncludeSubdomainsSet(pins uintptr, value bool) { - cronetPublicKeyPinsIncludeSubdomainsSet(pins, value) -} - -func PublicKeyPinsExpirationDateSet(pins uintptr, date int64) { - cronetPublicKeyPinsExpirationDateSet(pins, date) -} - -func PublicKeyPinsHostGet(pins uintptr) string { - return GoString(cronetPublicKeyPinsHostGet(pins)) -} - -func PublicKeyPinsPinsSha256Size(pins uintptr) uint32 { - return cronetPublicKeyPinsPinsSha256Size(pins) -} - -func PublicKeyPinsPinsSha256At(pins uintptr, index uint32) string { - return GoString(cronetPublicKeyPinsPinsSha256At(pins, index)) -} - -func PublicKeyPinsIncludeSubdomainsGet(pins uintptr) bool { - return cronetPublicKeyPinsIncludeSubdomainsGet(pins) -} - -func PublicKeyPinsExpirationDateGet(pins uintptr) int64 { - return cronetPublicKeyPinsExpirationDateGet(pins) -} - -// DateTime API - -func DateTimeCreate() uintptr { - ensureLoaded() - return cronetDateTimeCreate() -} - -func DateTimeDestroy(dt uintptr) { - cronetDateTimeDestroy(dt) -} - -func DateTimeValueSet(dt uintptr, value int64) { - cronetDateTimeValueSet(dt, value) -} - -func DateTimeValueGet(dt uintptr) int64 { - return cronetDateTimeValueGet(dt) -} - -// Metrics API - -func MetricsCreate() uintptr { - ensureLoaded() - return cronetMetricsCreate() -} - -func MetricsDestroy(metrics uintptr) { - cronetMetricsDestroy(metrics) -} - -func MetricsRequestStartGet(metrics uintptr) uintptr { - return cronetMetricsRequestStartGet(metrics) -} - -func MetricsDnsStartGet(metrics uintptr) uintptr { - return cronetMetricsDnsStartGet(metrics) -} - -func MetricsDnsEndGet(metrics uintptr) uintptr { - return cronetMetricsDnsEndGet(metrics) -} - -func MetricsConnectStartGet(metrics uintptr) uintptr { - return cronetMetricsConnectStartGet(metrics) -} - -func MetricsConnectEndGet(metrics uintptr) uintptr { - return cronetMetricsConnectEndGet(metrics) -} - -func MetricsSslStartGet(metrics uintptr) uintptr { - return cronetMetricsSslStartGet(metrics) -} - -func MetricsSslEndGet(metrics uintptr) uintptr { - return cronetMetricsSslEndGet(metrics) -} - -func MetricsSendingStartGet(metrics uintptr) uintptr { - return cronetMetricsSendingStartGet(metrics) -} - -func MetricsSendingEndGet(metrics uintptr) uintptr { - return cronetMetricsSendingEndGet(metrics) -} - -func MetricsPushStartGet(metrics uintptr) uintptr { - return cronetMetricsPushStartGet(metrics) -} - -func MetricsPushEndGet(metrics uintptr) uintptr { - return cronetMetricsPushEndGet(metrics) -} - -func MetricsResponseStartGet(metrics uintptr) uintptr { - return cronetMetricsResponseStartGet(metrics) -} - -func MetricsRequestEndGet(metrics uintptr) uintptr { - return cronetMetricsRequestEndGet(metrics) -} - -func MetricsSocketReusedGet(metrics uintptr) bool { - return cronetMetricsSocketReusedGet(metrics) -} - -func MetricsSentByteCountGet(metrics uintptr) int64 { - return cronetMetricsSentByteCountGet(metrics) -} - -func MetricsReceivedByteCountGet(metrics uintptr) int64 { - return cronetMetricsReceivedByteCountGet(metrics) -} - -func MetricsRequestStartSet(metrics, dateTime uintptr) { - cronetMetricsRequestStartSet(metrics, dateTime) -} - -func MetricsDnsStartSet(metrics, dateTime uintptr) { - cronetMetricsDnsStartSet(metrics, dateTime) -} - -func MetricsDnsEndSet(metrics, dateTime uintptr) { - cronetMetricsDnsEndSet(metrics, dateTime) -} - -func MetricsConnectStartSet(metrics, dateTime uintptr) { - cronetMetricsConnectStartSet(metrics, dateTime) -} - -func MetricsConnectEndSet(metrics, dateTime uintptr) { - cronetMetricsConnectEndSet(metrics, dateTime) -} - -func MetricsSslStartSet(metrics, dateTime uintptr) { - cronetMetricsSslStartSet(metrics, dateTime) -} - -func MetricsSslEndSet(metrics, dateTime uintptr) { - cronetMetricsSslEndSet(metrics, dateTime) -} - -func MetricsSendingStartSet(metrics, dateTime uintptr) { - cronetMetricsSendingStartSet(metrics, dateTime) -} - -func MetricsSendingEndSet(metrics, dateTime uintptr) { - cronetMetricsSendingEndSet(metrics, dateTime) -} - -func MetricsPushStartSet(metrics, dateTime uintptr) { - cronetMetricsPushStartSet(metrics, dateTime) -} - -func MetricsPushEndSet(metrics, dateTime uintptr) { - cronetMetricsPushEndSet(metrics, dateTime) -} - -func MetricsResponseStartSet(metrics, dateTime uintptr) { - cronetMetricsResponseStartSet(metrics, dateTime) -} - -func MetricsRequestEndSet(metrics, dateTime uintptr) { - cronetMetricsRequestEndSet(metrics, dateTime) -} - -func MetricsSocketReusedSet(metrics uintptr, reused bool) { - cronetMetricsSocketReusedSet(metrics, reused) -} - -func MetricsSentByteCountSet(metrics uintptr, count int64) { - cronetMetricsSentByteCountSet(metrics, count) -} - -func MetricsReceivedByteCountSet(metrics uintptr, count int64) { - cronetMetricsReceivedByteCountSet(metrics, count) -} - -// RequestFinishedInfo API - -func RequestFinishedInfoCreate() uintptr { - ensureLoaded() - return cronetRequestFinishedInfoCreate() -} - -func RequestFinishedInfoDestroy(info uintptr) { - cronetRequestFinishedInfoDestroy(info) -} - -func RequestFinishedInfoMetricsGet(info uintptr) uintptr { - return cronetRequestFinishedInfoMetricsGet(info) -} - -func RequestFinishedInfoAnnotationsSize(info uintptr) uint32 { - return cronetRequestFinishedInfoAnnotationsSize(info) -} - -func RequestFinishedInfoAnnotationsAt(info uintptr, index uint32) uintptr { - return cronetRequestFinishedInfoAnnotationsAt(info, index) -} - -func RequestFinishedInfoFinishedReasonGet(info uintptr) int32 { - return cronetRequestFinishedInfoFinishedReasonGet(info) -} - -// RequestFinishedInfoListener API - -func RequestFinishedInfoListenerDestroy(listener uintptr) { - cronetRequestFinishedInfoListenerDestroy(listener) -} - -func RequestFinishedInfoListenerSetClientContext(listener, context uintptr) { - cronetRequestFinishedInfoListenerSetClientContext(listener, context) -} - -func RequestFinishedInfoListenerGetClientContext(listener uintptr) uintptr { - return cronetRequestFinishedInfoListenerGetClientContext(listener) -} - -func RequestFinishedInfoListenerCreateWith(onRequestFinished uintptr) uintptr { - ensureLoaded() - return cronetRequestFinishedInfoListenerCreateWith(onRequestFinished) -} - -// BidirectionalStream API - -func BidirectionalStreamCreate(engine, annotation, callback uintptr) uintptr { - ensureLoaded() - return bidirectionalStreamCreate(engine, annotation, callback) -} - -func BidirectionalStreamDestroy(stream uintptr) int32 { - return bidirectionalStreamDestroy(stream) -} - -func BidirectionalStreamDisableAutoFlush(stream uintptr, disable bool) { - bidirectionalStreamDisableAutoFlush(stream, disable) -} - -func BidirectionalStreamDelayRequestHeadersUntilFlush(stream uintptr, delay bool) { - bidirectionalStreamDelayRequestHeadersUntilFlush(stream, delay) -} - -func BidirectionalStreamStart(stream uintptr, url string, priority int32, method string, headers uintptr, endOfStream bool) int32 { - return bidirectionalStreamStart(stream, url, priority, method, headers, endOfStream) -} - -func BidirectionalStreamRead(stream, buffer uintptr, capacity int32) int32 { - return bidirectionalStreamRead(stream, buffer, capacity) -} - -func BidirectionalStreamWrite(stream, buffer uintptr, count int32, endOfStream bool) int32 { - return bidirectionalStreamWrite(stream, buffer, count, endOfStream) -} - -func BidirectionalStreamFlush(stream uintptr) { - bidirectionalStreamFlush(stream) -} - -func BidirectionalStreamCancel(stream uintptr) { - bidirectionalStreamCancel(stream) -} diff --git a/internal/cronet/api_purego_float.go b/internal/cronet/api_purego_float.go deleted file mode 100644 index bb48e43d..00000000 --- a/internal/cronet/api_purego_float.go +++ /dev/null @@ -1,13 +0,0 @@ -//go:build with_purego && (amd64 || arm64 || loong64 || mips64le || riscv64) - -package cronet - -func EngineParamsNetworkThreadPrioritySet(params uintptr, priority float64) { - ensureLoaded() - cronetEngineParamsNetworkThreadPrioritySet(params, priority) -} - -func EngineParamsNetworkThreadPriorityGet(params uintptr) float64 { - ensureLoaded() - return cronetEngineParamsNetworkThreadPriorityGet(params) -} diff --git a/internal/cronet/api_purego_float_stub.go b/internal/cronet/api_purego_float_stub.go deleted file mode 100644 index 6def46c3..00000000 --- a/internal/cronet/api_purego_float_stub.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build with_purego && (386 || arm || mipsle) - -package cronet - -// EngineParamsNetworkThreadPrioritySet is not supported on 32-bit platforms. -// purego does not support float parameters on 32-bit platforms. -func EngineParamsNetworkThreadPrioritySet(params uintptr, priority float64) { - panic("cronet: NetworkThreadPriority not supported on 32-bit platforms") -} - -// EngineParamsNetworkThreadPriorityGet is not supported on 32-bit platforms. -// purego does not support float parameters on 32-bit platforms. -func EngineParamsNetworkThreadPriorityGet(params uintptr) float64 { - panic("cronet: NetworkThreadPriority not supported on 32-bit platforms") -} diff --git a/internal/cronet/api_purego_nonwindows.go b/internal/cronet/api_purego_nonwindows.go deleted file mode 100644 index 6a4c08a6..00000000 --- a/internal/cronet/api_purego_nonwindows.go +++ /dev/null @@ -1,8 +0,0 @@ -//go:build with_purego && !windows - -package cronet - -// EngineStartWithParams starts the engine on non-Windows platforms. -func EngineStartWithParams(engine, params uintptr) int32 { - return cronetEngineStartWithParams(engine, params) -} diff --git a/internal/cronet/api_purego_windows.go b/internal/cronet/api_purego_windows.go deleted file mode 100644 index 78f510d7..00000000 --- a/internal/cronet/api_purego_windows.go +++ /dev/null @@ -1,14 +0,0 @@ -//go:build with_purego && windows - -package cronet - -import "runtime" - -// EngineStartWithParams starts the engine on Windows. -// On Windows, we lock the OS thread to ensure stable thread-local storage -// state during Chromium's initialization, which creates threads and message loops. -func EngineStartWithParams(engine, params uintptr) int32 { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - return cronetEngineStartWithParams(engine, params) -} diff --git a/internal/cronet/loader_unix.go b/internal/cronet/loader_unix.go deleted file mode 100644 index cd44aa8e..00000000 --- a/internal/cronet/loader_unix.go +++ /dev/null @@ -1,933 +0,0 @@ -//go:build with_purego && (linux || darwin || ios || android) - -package cronet - -import ( - "errors" - "fmt" - "os" - "path/filepath" - "runtime" - "sync" - - "github.com/ebitengine/purego" -) - -var ( - loadOnce sync.Once - loadError error - libHandle uintptr -) - -// LoadLibrary loads the cronet shared library from the given path. -// If path is empty, it searches in standard locations. -// This function is safe to call from multiple goroutines. -func LoadLibrary(path string) error { - loadOnce.Do(func() { - loadError = doLoadLibrary(path) - }) - return loadError -} - -// ensureLoaded attempts to load the library and panics if it fails. -// It's safe to call from multiple goroutines. -func ensureLoaded() { - err := LoadLibrary("") - if err != nil { - panic(err) - } -} - -func doLoadLibrary(path string) error { - if path == "" { - path = findLibrary() - } - - if path == "" { - return errors.New("cronet: library not found") - } - - handle, err := purego.Dlopen(path, purego.RTLD_NOW|purego.RTLD_GLOBAL) - if err != nil { - return fmt.Errorf("cronet: failed to load library %s: %w", path, err) - } - - libHandle = handle - return registerSymbols() -} - -func findLibrary() string { - var libName string - switch runtime.GOOS { - case "darwin", "ios": - libName = "libcronet.dylib" - case "linux", "android": - libName = "libcronet.so" - default: - return "" - } - - var searchPaths []string - executablePath, err := os.Executable() - if err == nil { - searchPaths = append(searchPaths, filepath.Dir(executablePath)) - } - - if ldPath := os.Getenv("LD_LIBRARY_PATH"); ldPath != "" { - paths := filepath.SplitList(ldPath) - searchPaths = append(searchPaths, paths...) - } - - if runtime.GOOS == "darwin" { - if dyldPath := os.Getenv("DYLD_LIBRARY_PATH"); dyldPath != "" { - paths := filepath.SplitList(dyldPath) - searchPaths = append(searchPaths, paths...) - } - } - - searchPaths = append(searchPaths, "/usr/local/lib", "/usr/lib") - - for _, searchPath := range searchPaths { - fullPath := filepath.Join(searchPath, libName) - if _, err := os.Stat(fullPath); err == nil { - return fullPath - } - } - - return "" -} - -func lookupSymbol(name string) (uintptr, error) { - if libHandle == 0 { - return 0, errors.New("cronet: library not loaded") - } - sym, err := purego.Dlsym(libHandle, name) - if err != nil { - return 0, fmt.Errorf("cronet: symbol %s not found: %w", name, err) - } - return sym, nil -} - -func registerFunc(fnPtr interface{}, name string) error { - sym, err := lookupSymbol(name) - if err != nil { - return err - } - purego.RegisterFunc(fnPtr, sym) - return nil -} - -func registerSymbols() error { - // Buffer - if err := registerFunc(&cronetBufferCreate, "Cronet_Buffer_Create"); err != nil { - return err - } - if err := registerFunc(&cronetBufferDestroy, "Cronet_Buffer_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetBufferSetClientContext, "Cronet_Buffer_SetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetBufferGetClientContext, "Cronet_Buffer_GetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetBufferInitWithDataAndCallback, "Cronet_Buffer_InitWithDataAndCallback"); err != nil { - return err - } - if err := registerFunc(&cronetBufferInitWithAlloc, "Cronet_Buffer_InitWithAlloc"); err != nil { - return err - } - if err := registerFunc(&cronetBufferGetSize, "Cronet_Buffer_GetSize"); err != nil { - return err - } - if err := registerFunc(&cronetBufferGetData, "Cronet_Buffer_GetData"); err != nil { - return err - } - - // BufferCallback - if err := registerFunc(&cronetBufferCallbackDestroy, "Cronet_BufferCallback_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetBufferCallbackSetClientContext, "Cronet_BufferCallback_SetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetBufferCallbackGetClientContext, "Cronet_BufferCallback_GetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetBufferCallbackCreateWith, "Cronet_BufferCallback_CreateWith"); err != nil { - return err - } - - // Runnable - if err := registerFunc(&cronetRunnableDestroy, "Cronet_Runnable_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetRunnableSetClientContext, "Cronet_Runnable_SetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetRunnableGetClientContext, "Cronet_Runnable_GetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetRunnableRun, "Cronet_Runnable_Run"); err != nil { - return err - } - if err := registerFunc(&cronetRunnableCreateWith, "Cronet_Runnable_CreateWith"); err != nil { - return err - } - - // Executor - if err := registerFunc(&cronetExecutorDestroy, "Cronet_Executor_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetExecutorSetClientContext, "Cronet_Executor_SetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetExecutorGetClientContext, "Cronet_Executor_GetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetExecutorExecute, "Cronet_Executor_Execute"); err != nil { - return err - } - if err := registerFunc(&cronetExecutorCreateWith, "Cronet_Executor_CreateWith"); err != nil { - return err - } - - // Engine - if err := registerFunc(&cronetEngineCreate, "Cronet_Engine_Create"); err != nil { - return err - } - if err := registerFunc(&cronetEngineDestroy, "Cronet_Engine_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetEngineSetClientContext, "Cronet_Engine_SetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetEngineGetClientContext, "Cronet_Engine_GetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetEngineStartWithParams, "Cronet_Engine_StartWithParams"); err != nil { - return err - } - if err := registerFunc(&cronetEngineStartNetLogToFile, "Cronet_Engine_StartNetLogToFile"); err != nil { - return err - } - if err := registerFunc(&cronetEngineStopNetLog, "Cronet_Engine_StopNetLog"); err != nil { - return err - } - if err := registerFunc(&cronetEngineShutdown, "Cronet_Engine_Shutdown"); err != nil { - return err - } - if err := registerFunc(&cronetEngineGetVersionString, "Cronet_Engine_GetVersionString"); err != nil { - return err - } - if err := registerFunc(&cronetEngineGetDefaultUserAgent, "Cronet_Engine_GetDefaultUserAgent"); err != nil { - return err - } - if err := registerFunc(&cronetEngineAddRequestFinishedListener, "Cronet_Engine_AddRequestFinishedListener"); err != nil { - return err - } - if err := registerFunc(&cronetEngineRemoveRequestFinishedListener, "Cronet_Engine_RemoveRequestFinishedListener"); err != nil { - return err - } - if err := registerFunc(&cronetEngineGetStreamEngine, "Cronet_Engine_GetStreamEngine"); err != nil { - return err - } - if err := registerFunc(&cronetEngineSetMockCertVerifierForTesting, "Cronet_Engine_SetMockCertVerifierForTesting"); err != nil { - return err - } - if err := registerFunc(&cronetEngineCloseAllConnections, "Cronet_Engine_CloseAllConnections"); err != nil { - return err - } - if err := registerFunc(&cronetEngineSetDialer, "Cronet_Engine_SetDialer"); err != nil { - return err - } - if err := registerFunc(&cronetEngineSetUdpDialer, "Cronet_Engine_SetUdpDialer"); err != nil { - return err - } - - // EngineParams - if err := registerFunc(&cronetEngineParamsCreate, "Cronet_EngineParams_Create"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsDestroy, "Cronet_EngineParams_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsEnableCheckResultSet, "Cronet_EngineParams_enable_check_result_set"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsUserAgentSet, "Cronet_EngineParams_user_agent_set"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsAcceptLanguageSet, "Cronet_EngineParams_accept_language_set"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsStoragePathSet, "Cronet_EngineParams_storage_path_set"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsEnableQuicSet, "Cronet_EngineParams_enable_quic_set"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsEnableHttp2Set, "Cronet_EngineParams_enable_http2_set"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsEnableBrotliSet, "Cronet_EngineParams_enable_brotli_set"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsHttpCacheModeSet, "Cronet_EngineParams_http_cache_mode_set"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsHttpCacheMaxSizeSet, "Cronet_EngineParams_http_cache_max_size_set"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsQuicHintsAdd, "Cronet_EngineParams_quic_hints_add"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsPublicKeyPinsAdd, "Cronet_EngineParams_public_key_pins_add"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsEnablePublicKeyPinningBypassForLocalTrustAnchorsSet, "Cronet_EngineParams_enable_public_key_pinning_bypass_for_local_trust_anchors_set"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsNetworkThreadPrioritySet, "Cronet_EngineParams_network_thread_priority_set"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsExperimentalOptionsSet, "Cronet_EngineParams_experimental_options_set"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsEnableCheckResultGet, "Cronet_EngineParams_enable_check_result_get"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsUserAgentGet, "Cronet_EngineParams_user_agent_get"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsAcceptLanguageGet, "Cronet_EngineParams_accept_language_get"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsStoragePathGet, "Cronet_EngineParams_storage_path_get"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsEnableQuicGet, "Cronet_EngineParams_enable_quic_get"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsEnableHttp2Get, "Cronet_EngineParams_enable_http2_get"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsEnableBrotliGet, "Cronet_EngineParams_enable_brotli_get"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsHttpCacheModeGet, "Cronet_EngineParams_http_cache_mode_get"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsHttpCacheMaxSizeGet, "Cronet_EngineParams_http_cache_max_size_get"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsQuicHintsSize, "Cronet_EngineParams_quic_hints_size"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsQuicHintsAt, "Cronet_EngineParams_quic_hints_at"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsQuicHintsClear, "Cronet_EngineParams_quic_hints_clear"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsPublicKeyPinsSize, "Cronet_EngineParams_public_key_pins_size"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsPublicKeyPinsAt, "Cronet_EngineParams_public_key_pins_at"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsPublicKeyPinsClear, "Cronet_EngineParams_public_key_pins_clear"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsEnablePublicKeyPinningBypassForLocalTrustAnchorsGet, "Cronet_EngineParams_enable_public_key_pinning_bypass_for_local_trust_anchors_get"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsNetworkThreadPriorityGet, "Cronet_EngineParams_network_thread_priority_get"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsExperimentalOptionsGet, "Cronet_EngineParams_experimental_options_get"); err != nil { - return err - } - - // UrlRequest - if err := registerFunc(&cronetUrlRequestCreate, "Cronet_UrlRequest_Create"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestDestroy, "Cronet_UrlRequest_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestSetClientContext, "Cronet_UrlRequest_SetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestGetClientContext, "Cronet_UrlRequest_GetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestInitWithParams, "Cronet_UrlRequest_InitWithParams"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestStart, "Cronet_UrlRequest_Start"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestFollowRedirect, "Cronet_UrlRequest_FollowRedirect"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestRead, "Cronet_UrlRequest_Read"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestCancel, "Cronet_UrlRequest_Cancel"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestIsDone, "Cronet_UrlRequest_IsDone"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestGetStatus, "Cronet_UrlRequest_GetStatus"); err != nil { - return err - } - - // UrlRequestParams - if err := registerFunc(&cronetUrlRequestParamsCreate, "Cronet_UrlRequestParams_Create"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsDestroy, "Cronet_UrlRequestParams_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsHttpMethodSet, "Cronet_UrlRequestParams_http_method_set"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsRequestHeadersAdd, "Cronet_UrlRequestParams_request_headers_add"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsDisableCacheSet, "Cronet_UrlRequestParams_disable_cache_set"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsPrioritySet, "Cronet_UrlRequestParams_priority_set"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsUploadDataProviderSet, "Cronet_UrlRequestParams_upload_data_provider_set"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsUploadDataProviderExecutorSet, "Cronet_UrlRequestParams_upload_data_provider_executor_set"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsAllowDirectExecutorSet, "Cronet_UrlRequestParams_allow_direct_executor_set"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsAnnotationsAdd, "Cronet_UrlRequestParams_annotations_add"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsRequestFinishedListenerSet, "Cronet_UrlRequestParams_request_finished_listener_set"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsRequestFinishedExecutorSet, "Cronet_UrlRequestParams_request_finished_executor_set"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsIdempotencySet, "Cronet_UrlRequestParams_idempotency_set"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsHttpMethodGet, "Cronet_UrlRequestParams_http_method_get"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsRequestHeadersSize, "Cronet_UrlRequestParams_request_headers_size"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsRequestHeadersAt, "Cronet_UrlRequestParams_request_headers_at"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsRequestHeadersClear, "Cronet_UrlRequestParams_request_headers_clear"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsDisableCacheGet, "Cronet_UrlRequestParams_disable_cache_get"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsPriorityGet, "Cronet_UrlRequestParams_priority_get"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsUploadDataProviderGet, "Cronet_UrlRequestParams_upload_data_provider_get"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsUploadDataProviderExecutorGet, "Cronet_UrlRequestParams_upload_data_provider_executor_get"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsAllowDirectExecutorGet, "Cronet_UrlRequestParams_allow_direct_executor_get"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsAnnotationsSize, "Cronet_UrlRequestParams_annotations_size"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsAnnotationsAt, "Cronet_UrlRequestParams_annotations_at"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsAnnotationsClear, "Cronet_UrlRequestParams_annotations_clear"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsRequestFinishedListenerGet, "Cronet_UrlRequestParams_request_finished_listener_get"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsRequestFinishedExecutorGet, "Cronet_UrlRequestParams_request_finished_executor_get"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsIdempotencyGet, "Cronet_UrlRequestParams_idempotency_get"); err != nil { - return err - } - - // UrlRequestCallback - if err := registerFunc(&cronetUrlRequestCallbackDestroy, "Cronet_UrlRequestCallback_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestCallbackSetClientContext, "Cronet_UrlRequestCallback_SetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestCallbackGetClientContext, "Cronet_UrlRequestCallback_GetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestCallbackCreateWith, "Cronet_UrlRequestCallback_CreateWith"); err != nil { - return err - } - - // UrlRequestStatusListener - if err := registerFunc(&cronetUrlRequestStatusListenerDestroy, "Cronet_UrlRequestStatusListener_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestStatusListenerSetClientContext, "Cronet_UrlRequestStatusListener_SetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestStatusListenerGetClientContext, "Cronet_UrlRequestStatusListener_GetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestStatusListenerCreateWith, "Cronet_UrlRequestStatusListener_CreateWith"); err != nil { - return err - } - - // UploadDataProvider - if err := registerFunc(&cronetUploadDataProviderDestroy, "Cronet_UploadDataProvider_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetUploadDataProviderSetClientContext, "Cronet_UploadDataProvider_SetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetUploadDataProviderGetClientContext, "Cronet_UploadDataProvider_GetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetUploadDataProviderCreateWith, "Cronet_UploadDataProvider_CreateWith"); err != nil { - return err - } - - // UploadDataSink - if err := registerFunc(&cronetUploadDataSinkDestroy, "Cronet_UploadDataSink_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetUploadDataSinkSetClientContext, "Cronet_UploadDataSink_SetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetUploadDataSinkGetClientContext, "Cronet_UploadDataSink_GetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetUploadDataSinkOnReadSucceeded, "Cronet_UploadDataSink_OnReadSucceeded"); err != nil { - return err - } - if err := registerFunc(&cronetUploadDataSinkOnReadError, "Cronet_UploadDataSink_OnReadError"); err != nil { - return err - } - if err := registerFunc(&cronetUploadDataSinkOnRewindSucceeded, "Cronet_UploadDataSink_OnRewindSucceeded"); err != nil { - return err - } - if err := registerFunc(&cronetUploadDataSinkOnRewindError, "Cronet_UploadDataSink_OnRewindError"); err != nil { - return err - } - - // UrlResponseInfo - if err := registerFunc(&cronetUrlResponseInfoCreate, "Cronet_UrlResponseInfo_Create"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoDestroy, "Cronet_UrlResponseInfo_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoUrlSet, "Cronet_UrlResponseInfo_url_set"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoUrlChainAdd, "Cronet_UrlResponseInfo_url_chain_add"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoHttpStatusCodeSet, "Cronet_UrlResponseInfo_http_status_code_set"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoHttpStatusTextSet, "Cronet_UrlResponseInfo_http_status_text_set"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoAllHeadersListAdd, "Cronet_UrlResponseInfo_all_headers_list_add"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoWasCachedSet, "Cronet_UrlResponseInfo_was_cached_set"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoNegotiatedProtocolSet, "Cronet_UrlResponseInfo_negotiated_protocol_set"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoProxyServerSet, "Cronet_UrlResponseInfo_proxy_server_set"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoReceivedByteCountSet, "Cronet_UrlResponseInfo_received_byte_count_set"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoUrlGet, "Cronet_UrlResponseInfo_url_get"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoUrlChainSize, "Cronet_UrlResponseInfo_url_chain_size"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoUrlChainAt, "Cronet_UrlResponseInfo_url_chain_at"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoUrlChainClear, "Cronet_UrlResponseInfo_url_chain_clear"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoHttpStatusCodeGet, "Cronet_UrlResponseInfo_http_status_code_get"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoHttpStatusTextGet, "Cronet_UrlResponseInfo_http_status_text_get"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoAllHeadersListSize, "Cronet_UrlResponseInfo_all_headers_list_size"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoAllHeadersListAt, "Cronet_UrlResponseInfo_all_headers_list_at"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoAllHeadersListClear, "Cronet_UrlResponseInfo_all_headers_list_clear"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoWasCachedGet, "Cronet_UrlResponseInfo_was_cached_get"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoNegotiatedProtocolGet, "Cronet_UrlResponseInfo_negotiated_protocol_get"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoProxyServerGet, "Cronet_UrlResponseInfo_proxy_server_get"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoReceivedByteCountGet, "Cronet_UrlResponseInfo_received_byte_count_get"); err != nil { - return err - } - - // Error - if err := registerFunc(&cronetErrorCreate, "Cronet_Error_Create"); err != nil { - return err - } - if err := registerFunc(&cronetErrorDestroy, "Cronet_Error_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetErrorErrorCodeSet, "Cronet_Error_error_code_set"); err != nil { - return err - } - if err := registerFunc(&cronetErrorMessageSet, "Cronet_Error_message_set"); err != nil { - return err - } - if err := registerFunc(&cronetErrorInternalErrorCodeSet, "Cronet_Error_internal_error_code_set"); err != nil { - return err - } - if err := registerFunc(&cronetErrorImmediatelyRetryableSet, "Cronet_Error_immediately_retryable_set"); err != nil { - return err - } - if err := registerFunc(&cronetErrorQuicDetailedErrorCodeSet, "Cronet_Error_quic_detailed_error_code_set"); err != nil { - return err - } - if err := registerFunc(&cronetErrorErrorCodeGet, "Cronet_Error_error_code_get"); err != nil { - return err - } - if err := registerFunc(&cronetErrorMessageGet, "Cronet_Error_message_get"); err != nil { - return err - } - if err := registerFunc(&cronetErrorInternalErrorCodeGet, "Cronet_Error_internal_error_code_get"); err != nil { - return err - } - if err := registerFunc(&cronetErrorImmediatelyRetryableGet, "Cronet_Error_immediately_retryable_get"); err != nil { - return err - } - if err := registerFunc(&cronetErrorQuicDetailedErrorCodeGet, "Cronet_Error_quic_detailed_error_code_get"); err != nil { - return err - } - - // HttpHeader - if err := registerFunc(&cronetHttpHeaderCreate, "Cronet_HttpHeader_Create"); err != nil { - return err - } - if err := registerFunc(&cronetHttpHeaderDestroy, "Cronet_HttpHeader_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetHttpHeaderNameSet, "Cronet_HttpHeader_name_set"); err != nil { - return err - } - if err := registerFunc(&cronetHttpHeaderValueSet, "Cronet_HttpHeader_value_set"); err != nil { - return err - } - if err := registerFunc(&cronetHttpHeaderNameGet, "Cronet_HttpHeader_name_get"); err != nil { - return err - } - if err := registerFunc(&cronetHttpHeaderValueGet, "Cronet_HttpHeader_value_get"); err != nil { - return err - } - - // QuicHint - if err := registerFunc(&cronetQuicHintCreate, "Cronet_QuicHint_Create"); err != nil { - return err - } - if err := registerFunc(&cronetQuicHintDestroy, "Cronet_QuicHint_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetQuicHintHostSet, "Cronet_QuicHint_host_set"); err != nil { - return err - } - if err := registerFunc(&cronetQuicHintPortSet, "Cronet_QuicHint_port_set"); err != nil { - return err - } - if err := registerFunc(&cronetQuicHintAlternatePortSet, "Cronet_QuicHint_alternate_port_set"); err != nil { - return err - } - if err := registerFunc(&cronetQuicHintHostGet, "Cronet_QuicHint_host_get"); err != nil { - return err - } - if err := registerFunc(&cronetQuicHintPortGet, "Cronet_QuicHint_port_get"); err != nil { - return err - } - if err := registerFunc(&cronetQuicHintAlternatePortGet, "Cronet_QuicHint_alternate_port_get"); err != nil { - return err - } - - // PublicKeyPins - if err := registerFunc(&cronetPublicKeyPinsCreate, "Cronet_PublicKeyPins_Create"); err != nil { - return err - } - if err := registerFunc(&cronetPublicKeyPinsDestroy, "Cronet_PublicKeyPins_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetPublicKeyPinsHostSet, "Cronet_PublicKeyPins_host_set"); err != nil { - return err - } - if err := registerFunc(&cronetPublicKeyPinsPinsSha256Add, "Cronet_PublicKeyPins_pins_sha256_add"); err != nil { - return err - } - if err := registerFunc(&cronetPublicKeyPinsIncludeSubdomainsSet, "Cronet_PublicKeyPins_include_subdomains_set"); err != nil { - return err - } - if err := registerFunc(&cronetPublicKeyPinsExpirationDateSet, "Cronet_PublicKeyPins_expiration_date_set"); err != nil { - return err - } - if err := registerFunc(&cronetPublicKeyPinsHostGet, "Cronet_PublicKeyPins_host_get"); err != nil { - return err - } - if err := registerFunc(&cronetPublicKeyPinsPinsSha256Size, "Cronet_PublicKeyPins_pins_sha256_size"); err != nil { - return err - } - if err := registerFunc(&cronetPublicKeyPinsPinsSha256At, "Cronet_PublicKeyPins_pins_sha256_at"); err != nil { - return err - } - if err := registerFunc(&cronetPublicKeyPinsPinsSha256Clear, "Cronet_PublicKeyPins_pins_sha256_clear"); err != nil { - return err - } - if err := registerFunc(&cronetPublicKeyPinsIncludeSubdomainsGet, "Cronet_PublicKeyPins_include_subdomains_get"); err != nil { - return err - } - if err := registerFunc(&cronetPublicKeyPinsExpirationDateGet, "Cronet_PublicKeyPins_expiration_date_get"); err != nil { - return err - } - - // DateTime - if err := registerFunc(&cronetDateTimeCreate, "Cronet_DateTime_Create"); err != nil { - return err - } - if err := registerFunc(&cronetDateTimeDestroy, "Cronet_DateTime_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetDateTimeValueSet, "Cronet_DateTime_value_set"); err != nil { - return err - } - if err := registerFunc(&cronetDateTimeValueGet, "Cronet_DateTime_value_get"); err != nil { - return err - } - - // Metrics - if err := registerFunc(&cronetMetricsCreate, "Cronet_Metrics_Create"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsDestroy, "Cronet_Metrics_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsRequestStartSet, "Cronet_Metrics_request_start_set"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsDnsStartSet, "Cronet_Metrics_dns_start_set"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsDnsEndSet, "Cronet_Metrics_dns_end_set"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsConnectStartSet, "Cronet_Metrics_connect_start_set"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsConnectEndSet, "Cronet_Metrics_connect_end_set"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsSslStartSet, "Cronet_Metrics_ssl_start_set"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsSslEndSet, "Cronet_Metrics_ssl_end_set"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsSendingStartSet, "Cronet_Metrics_sending_start_set"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsSendingEndSet, "Cronet_Metrics_sending_end_set"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsPushStartSet, "Cronet_Metrics_push_start_set"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsPushEndSet, "Cronet_Metrics_push_end_set"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsResponseStartSet, "Cronet_Metrics_response_start_set"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsRequestEndSet, "Cronet_Metrics_request_end_set"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsSocketReusedSet, "Cronet_Metrics_socket_reused_set"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsSentByteCountSet, "Cronet_Metrics_sent_byte_count_set"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsReceivedByteCountSet, "Cronet_Metrics_received_byte_count_set"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsRequestStartGet, "Cronet_Metrics_request_start_get"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsDnsStartGet, "Cronet_Metrics_dns_start_get"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsDnsEndGet, "Cronet_Metrics_dns_end_get"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsConnectStartGet, "Cronet_Metrics_connect_start_get"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsConnectEndGet, "Cronet_Metrics_connect_end_get"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsSslStartGet, "Cronet_Metrics_ssl_start_get"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsSslEndGet, "Cronet_Metrics_ssl_end_get"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsSendingStartGet, "Cronet_Metrics_sending_start_get"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsSendingEndGet, "Cronet_Metrics_sending_end_get"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsPushStartGet, "Cronet_Metrics_push_start_get"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsPushEndGet, "Cronet_Metrics_push_end_get"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsResponseStartGet, "Cronet_Metrics_response_start_get"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsRequestEndGet, "Cronet_Metrics_request_end_get"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsSocketReusedGet, "Cronet_Metrics_socket_reused_get"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsSentByteCountGet, "Cronet_Metrics_sent_byte_count_get"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsReceivedByteCountGet, "Cronet_Metrics_received_byte_count_get"); err != nil { - return err - } - - // RequestFinishedInfo - if err := registerFunc(&cronetRequestFinishedInfoCreate, "Cronet_RequestFinishedInfo_Create"); err != nil { - return err - } - if err := registerFunc(&cronetRequestFinishedInfoDestroy, "Cronet_RequestFinishedInfo_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetRequestFinishedInfoMetricsSet, "Cronet_RequestFinishedInfo_metrics_set"); err != nil { - return err - } - if err := registerFunc(&cronetRequestFinishedInfoAnnotationsAdd, "Cronet_RequestFinishedInfo_annotations_add"); err != nil { - return err - } - if err := registerFunc(&cronetRequestFinishedInfoFinishedReasonSet, "Cronet_RequestFinishedInfo_finished_reason_set"); err != nil { - return err - } - if err := registerFunc(&cronetRequestFinishedInfoMetricsGet, "Cronet_RequestFinishedInfo_metrics_get"); err != nil { - return err - } - if err := registerFunc(&cronetRequestFinishedInfoAnnotationsSize, "Cronet_RequestFinishedInfo_annotations_size"); err != nil { - return err - } - if err := registerFunc(&cronetRequestFinishedInfoAnnotationsAt, "Cronet_RequestFinishedInfo_annotations_at"); err != nil { - return err - } - if err := registerFunc(&cronetRequestFinishedInfoAnnotationsClear, "Cronet_RequestFinishedInfo_annotations_clear"); err != nil { - return err - } - if err := registerFunc(&cronetRequestFinishedInfoFinishedReasonGet, "Cronet_RequestFinishedInfo_finished_reason_get"); err != nil { - return err - } - - // RequestFinishedInfoListener - if err := registerFunc(&cronetRequestFinishedInfoListenerDestroy, "Cronet_RequestFinishedInfoListener_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetRequestFinishedInfoListenerSetClientContext, "Cronet_RequestFinishedInfoListener_SetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetRequestFinishedInfoListenerGetClientContext, "Cronet_RequestFinishedInfoListener_GetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetRequestFinishedInfoListenerCreateWith, "Cronet_RequestFinishedInfoListener_CreateWith"); err != nil { - return err - } - - // Custom cert verifier - if err := registerFunc(&cronetCreateCertVerifierWithRootCerts, "Cronet_CreateCertVerifierWithRootCerts"); err != nil { - return err - } - - // BidirectionalStream - if err := registerFunc(&bidirectionalStreamCreate, "bidirectional_stream_create"); err != nil { - return err - } - if err := registerFunc(&bidirectionalStreamDestroy, "bidirectional_stream_destroy"); err != nil { - return err - } - if err := registerFunc(&bidirectionalStreamDisableAutoFlush, "bidirectional_stream_disable_auto_flush"); err != nil { - return err - } - if err := registerFunc(&bidirectionalStreamDelayRequestHeadersUntilFlush, "bidirectional_stream_delay_request_headers_until_flush"); err != nil { - return err - } - if err := registerFunc(&bidirectionalStreamStart, "bidirectional_stream_start"); err != nil { - return err - } - if err := registerFunc(&bidirectionalStreamRead, "bidirectional_stream_read"); err != nil { - return err - } - if err := registerFunc(&bidirectionalStreamWrite, "bidirectional_stream_write"); err != nil { - return err - } - if err := registerFunc(&bidirectionalStreamFlush, "bidirectional_stream_flush"); err != nil { - return err - } - if err := registerFunc(&bidirectionalStreamCancel, "bidirectional_stream_cancel"); err != nil { - return err - } - - return nil -} diff --git a/internal/cronet/loader_windows.go b/internal/cronet/loader_windows.go deleted file mode 100644 index 59e8aa51..00000000 --- a/internal/cronet/loader_windows.go +++ /dev/null @@ -1,911 +0,0 @@ -//go:build with_purego && windows - -package cronet - -import ( - "errors" - "fmt" - "os" - "path/filepath" - "sync" - "syscall" - - "github.com/ebitengine/purego" -) - -var ( - loadOnce sync.Once - loadError error - libHandle uintptr -) - -func LoadLibrary(path string) error { - loadOnce.Do(func() { - loadError = doLoadLibrary(path) - }) - return loadError -} - -func ensureLoaded() { - err := LoadLibrary("") - if err != nil { - panic(err) - } -} - -func doLoadLibrary(path string) error { - if path == "" { - path = findLibrary() - } - - if path == "" { - return errors.New("cronet: library not found. Place libcronet.dll in the executable directory or PATH") - } - - handle, err := syscall.LoadLibrary(path) - if err != nil { - return fmt.Errorf("cronet: failed to load library %s: %w", path, err) - } - - libHandle = uintptr(handle) - return registerSymbols() -} - -func findLibrary() string { - libName := "libcronet.dll" - - var searchPaths []string - executablePath, err := os.Executable() - if err == nil { - searchPaths = append(searchPaths, filepath.Dir(executablePath)) - } - - if pathEnv := os.Getenv("PATH"); pathEnv != "" { - paths := filepath.SplitList(pathEnv) - searchPaths = append(searchPaths, paths...) - } - - for _, searchPath := range searchPaths { - fullPath := filepath.Join(searchPath, libName) - if _, err := os.Stat(fullPath); err == nil { - return fullPath - } - } - - return "" -} - -func lookupSymbol(name string) (uintptr, error) { - if libHandle == 0 { - return 0, errors.New("cronet: library not loaded") - } - - proc, err := syscall.GetProcAddress(syscall.Handle(libHandle), name) - if err != nil { - return 0, fmt.Errorf("cronet: symbol %s not found: %w", name, err) - } - return proc, nil -} - -func registerFunc(fnPtr interface{}, name string) error { - sym, err := lookupSymbol(name) - if err != nil { - return err - } - purego.RegisterFunc(fnPtr, sym) - return nil -} - -func registerSymbols() error { - // Buffer - if err := registerFunc(&cronetBufferCreate, "Cronet_Buffer_Create"); err != nil { - return err - } - if err := registerFunc(&cronetBufferDestroy, "Cronet_Buffer_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetBufferSetClientContext, "Cronet_Buffer_SetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetBufferGetClientContext, "Cronet_Buffer_GetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetBufferInitWithDataAndCallback, "Cronet_Buffer_InitWithDataAndCallback"); err != nil { - return err - } - if err := registerFunc(&cronetBufferInitWithAlloc, "Cronet_Buffer_InitWithAlloc"); err != nil { - return err - } - if err := registerFunc(&cronetBufferGetSize, "Cronet_Buffer_GetSize"); err != nil { - return err - } - if err := registerFunc(&cronetBufferGetData, "Cronet_Buffer_GetData"); err != nil { - return err - } - - // BufferCallback - if err := registerFunc(&cronetBufferCallbackDestroy, "Cronet_BufferCallback_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetBufferCallbackSetClientContext, "Cronet_BufferCallback_SetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetBufferCallbackGetClientContext, "Cronet_BufferCallback_GetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetBufferCallbackCreateWith, "Cronet_BufferCallback_CreateWith"); err != nil { - return err - } - - // Runnable - if err := registerFunc(&cronetRunnableDestroy, "Cronet_Runnable_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetRunnableSetClientContext, "Cronet_Runnable_SetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetRunnableGetClientContext, "Cronet_Runnable_GetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetRunnableRun, "Cronet_Runnable_Run"); err != nil { - return err - } - if err := registerFunc(&cronetRunnableCreateWith, "Cronet_Runnable_CreateWith"); err != nil { - return err - } - - // Executor - if err := registerFunc(&cronetExecutorDestroy, "Cronet_Executor_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetExecutorSetClientContext, "Cronet_Executor_SetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetExecutorGetClientContext, "Cronet_Executor_GetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetExecutorExecute, "Cronet_Executor_Execute"); err != nil { - return err - } - if err := registerFunc(&cronetExecutorCreateWith, "Cronet_Executor_CreateWith"); err != nil { - return err - } - - // Engine - if err := registerFunc(&cronetEngineCreate, "Cronet_Engine_Create"); err != nil { - return err - } - if err := registerFunc(&cronetEngineDestroy, "Cronet_Engine_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetEngineSetClientContext, "Cronet_Engine_SetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetEngineGetClientContext, "Cronet_Engine_GetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetEngineStartWithParams, "Cronet_Engine_StartWithParams"); err != nil { - return err - } - if err := registerFunc(&cronetEngineStartNetLogToFile, "Cronet_Engine_StartNetLogToFile"); err != nil { - return err - } - if err := registerFunc(&cronetEngineStopNetLog, "Cronet_Engine_StopNetLog"); err != nil { - return err - } - if err := registerFunc(&cronetEngineShutdown, "Cronet_Engine_Shutdown"); err != nil { - return err - } - if err := registerFunc(&cronetEngineGetVersionString, "Cronet_Engine_GetVersionString"); err != nil { - return err - } - if err := registerFunc(&cronetEngineGetDefaultUserAgent, "Cronet_Engine_GetDefaultUserAgent"); err != nil { - return err - } - if err := registerFunc(&cronetEngineAddRequestFinishedListener, "Cronet_Engine_AddRequestFinishedListener"); err != nil { - return err - } - if err := registerFunc(&cronetEngineRemoveRequestFinishedListener, "Cronet_Engine_RemoveRequestFinishedListener"); err != nil { - return err - } - if err := registerFunc(&cronetEngineGetStreamEngine, "Cronet_Engine_GetStreamEngine"); err != nil { - return err - } - if err := registerFunc(&cronetEngineSetMockCertVerifierForTesting, "Cronet_Engine_SetMockCertVerifierForTesting"); err != nil { - return err - } - if err := registerFunc(&cronetEngineCloseAllConnections, "Cronet_Engine_CloseAllConnections"); err != nil { - return err - } - if err := registerFunc(&cronetEngineSetDialer, "Cronet_Engine_SetDialer"); err != nil { - return err - } - if err := registerFunc(&cronetEngineSetUdpDialer, "Cronet_Engine_SetUdpDialer"); err != nil { - return err - } - - // EngineParams - if err := registerFunc(&cronetEngineParamsCreate, "Cronet_EngineParams_Create"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsDestroy, "Cronet_EngineParams_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsEnableCheckResultSet, "Cronet_EngineParams_enable_check_result_set"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsUserAgentSet, "Cronet_EngineParams_user_agent_set"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsAcceptLanguageSet, "Cronet_EngineParams_accept_language_set"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsStoragePathSet, "Cronet_EngineParams_storage_path_set"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsEnableQuicSet, "Cronet_EngineParams_enable_quic_set"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsEnableHttp2Set, "Cronet_EngineParams_enable_http2_set"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsEnableBrotliSet, "Cronet_EngineParams_enable_brotli_set"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsHttpCacheModeSet, "Cronet_EngineParams_http_cache_mode_set"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsHttpCacheMaxSizeSet, "Cronet_EngineParams_http_cache_max_size_set"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsQuicHintsAdd, "Cronet_EngineParams_quic_hints_add"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsPublicKeyPinsAdd, "Cronet_EngineParams_public_key_pins_add"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsEnablePublicKeyPinningBypassForLocalTrustAnchorsSet, "Cronet_EngineParams_enable_public_key_pinning_bypass_for_local_trust_anchors_set"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsExperimentalOptionsSet, "Cronet_EngineParams_experimental_options_set"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsEnableCheckResultGet, "Cronet_EngineParams_enable_check_result_get"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsUserAgentGet, "Cronet_EngineParams_user_agent_get"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsAcceptLanguageGet, "Cronet_EngineParams_accept_language_get"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsStoragePathGet, "Cronet_EngineParams_storage_path_get"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsEnableQuicGet, "Cronet_EngineParams_enable_quic_get"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsEnableHttp2Get, "Cronet_EngineParams_enable_http2_get"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsEnableBrotliGet, "Cronet_EngineParams_enable_brotli_get"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsHttpCacheModeGet, "Cronet_EngineParams_http_cache_mode_get"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsHttpCacheMaxSizeGet, "Cronet_EngineParams_http_cache_max_size_get"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsQuicHintsSize, "Cronet_EngineParams_quic_hints_size"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsQuicHintsAt, "Cronet_EngineParams_quic_hints_at"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsQuicHintsClear, "Cronet_EngineParams_quic_hints_clear"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsPublicKeyPinsSize, "Cronet_EngineParams_public_key_pins_size"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsPublicKeyPinsAt, "Cronet_EngineParams_public_key_pins_at"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsPublicKeyPinsClear, "Cronet_EngineParams_public_key_pins_clear"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsEnablePublicKeyPinningBypassForLocalTrustAnchorsGet, "Cronet_EngineParams_enable_public_key_pinning_bypass_for_local_trust_anchors_get"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsExperimentalOptionsGet, "Cronet_EngineParams_experimental_options_get"); err != nil { - return err - } - - // UrlRequest - if err := registerFunc(&cronetUrlRequestCreate, "Cronet_UrlRequest_Create"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestDestroy, "Cronet_UrlRequest_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestSetClientContext, "Cronet_UrlRequest_SetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestGetClientContext, "Cronet_UrlRequest_GetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestInitWithParams, "Cronet_UrlRequest_InitWithParams"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestStart, "Cronet_UrlRequest_Start"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestFollowRedirect, "Cronet_UrlRequest_FollowRedirect"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestRead, "Cronet_UrlRequest_Read"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestCancel, "Cronet_UrlRequest_Cancel"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestIsDone, "Cronet_UrlRequest_IsDone"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestGetStatus, "Cronet_UrlRequest_GetStatus"); err != nil { - return err - } - - // UrlRequestParams - if err := registerFunc(&cronetUrlRequestParamsCreate, "Cronet_UrlRequestParams_Create"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsDestroy, "Cronet_UrlRequestParams_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsHttpMethodSet, "Cronet_UrlRequestParams_http_method_set"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsRequestHeadersAdd, "Cronet_UrlRequestParams_request_headers_add"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsDisableCacheSet, "Cronet_UrlRequestParams_disable_cache_set"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsPrioritySet, "Cronet_UrlRequestParams_priority_set"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsUploadDataProviderSet, "Cronet_UrlRequestParams_upload_data_provider_set"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsUploadDataProviderExecutorSet, "Cronet_UrlRequestParams_upload_data_provider_executor_set"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsAllowDirectExecutorSet, "Cronet_UrlRequestParams_allow_direct_executor_set"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsAnnotationsAdd, "Cronet_UrlRequestParams_annotations_add"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsRequestFinishedListenerSet, "Cronet_UrlRequestParams_request_finished_listener_set"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsRequestFinishedExecutorSet, "Cronet_UrlRequestParams_request_finished_executor_set"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsIdempotencySet, "Cronet_UrlRequestParams_idempotency_set"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsHttpMethodGet, "Cronet_UrlRequestParams_http_method_get"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsRequestHeadersSize, "Cronet_UrlRequestParams_request_headers_size"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsRequestHeadersAt, "Cronet_UrlRequestParams_request_headers_at"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsRequestHeadersClear, "Cronet_UrlRequestParams_request_headers_clear"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsDisableCacheGet, "Cronet_UrlRequestParams_disable_cache_get"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsPriorityGet, "Cronet_UrlRequestParams_priority_get"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsUploadDataProviderGet, "Cronet_UrlRequestParams_upload_data_provider_get"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsUploadDataProviderExecutorGet, "Cronet_UrlRequestParams_upload_data_provider_executor_get"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsAllowDirectExecutorGet, "Cronet_UrlRequestParams_allow_direct_executor_get"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsAnnotationsSize, "Cronet_UrlRequestParams_annotations_size"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsAnnotationsAt, "Cronet_UrlRequestParams_annotations_at"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsAnnotationsClear, "Cronet_UrlRequestParams_annotations_clear"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsRequestFinishedListenerGet, "Cronet_UrlRequestParams_request_finished_listener_get"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsRequestFinishedExecutorGet, "Cronet_UrlRequestParams_request_finished_executor_get"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestParamsIdempotencyGet, "Cronet_UrlRequestParams_idempotency_get"); err != nil { - return err - } - - // UrlRequestCallback - if err := registerFunc(&cronetUrlRequestCallbackDestroy, "Cronet_UrlRequestCallback_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestCallbackSetClientContext, "Cronet_UrlRequestCallback_SetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestCallbackGetClientContext, "Cronet_UrlRequestCallback_GetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestCallbackCreateWith, "Cronet_UrlRequestCallback_CreateWith"); err != nil { - return err - } - - // UrlRequestStatusListener - if err := registerFunc(&cronetUrlRequestStatusListenerDestroy, "Cronet_UrlRequestStatusListener_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestStatusListenerSetClientContext, "Cronet_UrlRequestStatusListener_SetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestStatusListenerGetClientContext, "Cronet_UrlRequestStatusListener_GetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetUrlRequestStatusListenerCreateWith, "Cronet_UrlRequestStatusListener_CreateWith"); err != nil { - return err - } - - // UploadDataProvider - if err := registerFunc(&cronetUploadDataProviderDestroy, "Cronet_UploadDataProvider_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetUploadDataProviderSetClientContext, "Cronet_UploadDataProvider_SetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetUploadDataProviderGetClientContext, "Cronet_UploadDataProvider_GetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetUploadDataProviderCreateWith, "Cronet_UploadDataProvider_CreateWith"); err != nil { - return err - } - - // UploadDataSink - if err := registerFunc(&cronetUploadDataSinkDestroy, "Cronet_UploadDataSink_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetUploadDataSinkSetClientContext, "Cronet_UploadDataSink_SetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetUploadDataSinkGetClientContext, "Cronet_UploadDataSink_GetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetUploadDataSinkOnReadSucceeded, "Cronet_UploadDataSink_OnReadSucceeded"); err != nil { - return err - } - if err := registerFunc(&cronetUploadDataSinkOnReadError, "Cronet_UploadDataSink_OnReadError"); err != nil { - return err - } - if err := registerFunc(&cronetUploadDataSinkOnRewindSucceeded, "Cronet_UploadDataSink_OnRewindSucceeded"); err != nil { - return err - } - if err := registerFunc(&cronetUploadDataSinkOnRewindError, "Cronet_UploadDataSink_OnRewindError"); err != nil { - return err - } - - // UrlResponseInfo - if err := registerFunc(&cronetUrlResponseInfoCreate, "Cronet_UrlResponseInfo_Create"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoDestroy, "Cronet_UrlResponseInfo_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoUrlSet, "Cronet_UrlResponseInfo_url_set"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoUrlChainAdd, "Cronet_UrlResponseInfo_url_chain_add"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoHttpStatusCodeSet, "Cronet_UrlResponseInfo_http_status_code_set"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoHttpStatusTextSet, "Cronet_UrlResponseInfo_http_status_text_set"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoAllHeadersListAdd, "Cronet_UrlResponseInfo_all_headers_list_add"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoWasCachedSet, "Cronet_UrlResponseInfo_was_cached_set"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoNegotiatedProtocolSet, "Cronet_UrlResponseInfo_negotiated_protocol_set"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoProxyServerSet, "Cronet_UrlResponseInfo_proxy_server_set"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoReceivedByteCountSet, "Cronet_UrlResponseInfo_received_byte_count_set"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoUrlGet, "Cronet_UrlResponseInfo_url_get"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoUrlChainSize, "Cronet_UrlResponseInfo_url_chain_size"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoUrlChainAt, "Cronet_UrlResponseInfo_url_chain_at"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoUrlChainClear, "Cronet_UrlResponseInfo_url_chain_clear"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoHttpStatusCodeGet, "Cronet_UrlResponseInfo_http_status_code_get"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoHttpStatusTextGet, "Cronet_UrlResponseInfo_http_status_text_get"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoAllHeadersListSize, "Cronet_UrlResponseInfo_all_headers_list_size"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoAllHeadersListAt, "Cronet_UrlResponseInfo_all_headers_list_at"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoAllHeadersListClear, "Cronet_UrlResponseInfo_all_headers_list_clear"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoWasCachedGet, "Cronet_UrlResponseInfo_was_cached_get"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoNegotiatedProtocolGet, "Cronet_UrlResponseInfo_negotiated_protocol_get"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoProxyServerGet, "Cronet_UrlResponseInfo_proxy_server_get"); err != nil { - return err - } - if err := registerFunc(&cronetUrlResponseInfoReceivedByteCountGet, "Cronet_UrlResponseInfo_received_byte_count_get"); err != nil { - return err - } - - // Error - if err := registerFunc(&cronetErrorCreate, "Cronet_Error_Create"); err != nil { - return err - } - if err := registerFunc(&cronetErrorDestroy, "Cronet_Error_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetErrorErrorCodeSet, "Cronet_Error_error_code_set"); err != nil { - return err - } - if err := registerFunc(&cronetErrorMessageSet, "Cronet_Error_message_set"); err != nil { - return err - } - if err := registerFunc(&cronetErrorInternalErrorCodeSet, "Cronet_Error_internal_error_code_set"); err != nil { - return err - } - if err := registerFunc(&cronetErrorImmediatelyRetryableSet, "Cronet_Error_immediately_retryable_set"); err != nil { - return err - } - if err := registerFunc(&cronetErrorQuicDetailedErrorCodeSet, "Cronet_Error_quic_detailed_error_code_set"); err != nil { - return err - } - if err := registerFunc(&cronetErrorErrorCodeGet, "Cronet_Error_error_code_get"); err != nil { - return err - } - if err := registerFunc(&cronetErrorMessageGet, "Cronet_Error_message_get"); err != nil { - return err - } - if err := registerFunc(&cronetErrorInternalErrorCodeGet, "Cronet_Error_internal_error_code_get"); err != nil { - return err - } - if err := registerFunc(&cronetErrorImmediatelyRetryableGet, "Cronet_Error_immediately_retryable_get"); err != nil { - return err - } - if err := registerFunc(&cronetErrorQuicDetailedErrorCodeGet, "Cronet_Error_quic_detailed_error_code_get"); err != nil { - return err - } - - // HttpHeader - if err := registerFunc(&cronetHttpHeaderCreate, "Cronet_HttpHeader_Create"); err != nil { - return err - } - if err := registerFunc(&cronetHttpHeaderDestroy, "Cronet_HttpHeader_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetHttpHeaderNameSet, "Cronet_HttpHeader_name_set"); err != nil { - return err - } - if err := registerFunc(&cronetHttpHeaderValueSet, "Cronet_HttpHeader_value_set"); err != nil { - return err - } - if err := registerFunc(&cronetHttpHeaderNameGet, "Cronet_HttpHeader_name_get"); err != nil { - return err - } - if err := registerFunc(&cronetHttpHeaderValueGet, "Cronet_HttpHeader_value_get"); err != nil { - return err - } - - // QuicHint - if err := registerFunc(&cronetQuicHintCreate, "Cronet_QuicHint_Create"); err != nil { - return err - } - if err := registerFunc(&cronetQuicHintDestroy, "Cronet_QuicHint_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetQuicHintHostSet, "Cronet_QuicHint_host_set"); err != nil { - return err - } - if err := registerFunc(&cronetQuicHintPortSet, "Cronet_QuicHint_port_set"); err != nil { - return err - } - if err := registerFunc(&cronetQuicHintAlternatePortSet, "Cronet_QuicHint_alternate_port_set"); err != nil { - return err - } - if err := registerFunc(&cronetQuicHintHostGet, "Cronet_QuicHint_host_get"); err != nil { - return err - } - if err := registerFunc(&cronetQuicHintPortGet, "Cronet_QuicHint_port_get"); err != nil { - return err - } - if err := registerFunc(&cronetQuicHintAlternatePortGet, "Cronet_QuicHint_alternate_port_get"); err != nil { - return err - } - - // PublicKeyPins - if err := registerFunc(&cronetPublicKeyPinsCreate, "Cronet_PublicKeyPins_Create"); err != nil { - return err - } - if err := registerFunc(&cronetPublicKeyPinsDestroy, "Cronet_PublicKeyPins_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetPublicKeyPinsHostSet, "Cronet_PublicKeyPins_host_set"); err != nil { - return err - } - if err := registerFunc(&cronetPublicKeyPinsPinsSha256Add, "Cronet_PublicKeyPins_pins_sha256_add"); err != nil { - return err - } - if err := registerFunc(&cronetPublicKeyPinsIncludeSubdomainsSet, "Cronet_PublicKeyPins_include_subdomains_set"); err != nil { - return err - } - if err := registerFunc(&cronetPublicKeyPinsExpirationDateSet, "Cronet_PublicKeyPins_expiration_date_set"); err != nil { - return err - } - if err := registerFunc(&cronetPublicKeyPinsHostGet, "Cronet_PublicKeyPins_host_get"); err != nil { - return err - } - if err := registerFunc(&cronetPublicKeyPinsPinsSha256Size, "Cronet_PublicKeyPins_pins_sha256_size"); err != nil { - return err - } - if err := registerFunc(&cronetPublicKeyPinsPinsSha256At, "Cronet_PublicKeyPins_pins_sha256_at"); err != nil { - return err - } - if err := registerFunc(&cronetPublicKeyPinsPinsSha256Clear, "Cronet_PublicKeyPins_pins_sha256_clear"); err != nil { - return err - } - if err := registerFunc(&cronetPublicKeyPinsIncludeSubdomainsGet, "Cronet_PublicKeyPins_include_subdomains_get"); err != nil { - return err - } - if err := registerFunc(&cronetPublicKeyPinsExpirationDateGet, "Cronet_PublicKeyPins_expiration_date_get"); err != nil { - return err - } - - // DateTime - if err := registerFunc(&cronetDateTimeCreate, "Cronet_DateTime_Create"); err != nil { - return err - } - if err := registerFunc(&cronetDateTimeDestroy, "Cronet_DateTime_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetDateTimeValueSet, "Cronet_DateTime_value_set"); err != nil { - return err - } - if err := registerFunc(&cronetDateTimeValueGet, "Cronet_DateTime_value_get"); err != nil { - return err - } - - // Metrics - if err := registerFunc(&cronetMetricsCreate, "Cronet_Metrics_Create"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsDestroy, "Cronet_Metrics_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsRequestStartSet, "Cronet_Metrics_request_start_set"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsDnsStartSet, "Cronet_Metrics_dns_start_set"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsDnsEndSet, "Cronet_Metrics_dns_end_set"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsConnectStartSet, "Cronet_Metrics_connect_start_set"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsConnectEndSet, "Cronet_Metrics_connect_end_set"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsSslStartSet, "Cronet_Metrics_ssl_start_set"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsSslEndSet, "Cronet_Metrics_ssl_end_set"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsSendingStartSet, "Cronet_Metrics_sending_start_set"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsSendingEndSet, "Cronet_Metrics_sending_end_set"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsPushStartSet, "Cronet_Metrics_push_start_set"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsPushEndSet, "Cronet_Metrics_push_end_set"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsResponseStartSet, "Cronet_Metrics_response_start_set"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsRequestEndSet, "Cronet_Metrics_request_end_set"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsSocketReusedSet, "Cronet_Metrics_socket_reused_set"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsSentByteCountSet, "Cronet_Metrics_sent_byte_count_set"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsReceivedByteCountSet, "Cronet_Metrics_received_byte_count_set"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsRequestStartGet, "Cronet_Metrics_request_start_get"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsDnsStartGet, "Cronet_Metrics_dns_start_get"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsDnsEndGet, "Cronet_Metrics_dns_end_get"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsConnectStartGet, "Cronet_Metrics_connect_start_get"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsConnectEndGet, "Cronet_Metrics_connect_end_get"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsSslStartGet, "Cronet_Metrics_ssl_start_get"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsSslEndGet, "Cronet_Metrics_ssl_end_get"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsSendingStartGet, "Cronet_Metrics_sending_start_get"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsSendingEndGet, "Cronet_Metrics_sending_end_get"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsPushStartGet, "Cronet_Metrics_push_start_get"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsPushEndGet, "Cronet_Metrics_push_end_get"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsResponseStartGet, "Cronet_Metrics_response_start_get"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsRequestEndGet, "Cronet_Metrics_request_end_get"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsSocketReusedGet, "Cronet_Metrics_socket_reused_get"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsSentByteCountGet, "Cronet_Metrics_sent_byte_count_get"); err != nil { - return err - } - if err := registerFunc(&cronetMetricsReceivedByteCountGet, "Cronet_Metrics_received_byte_count_get"); err != nil { - return err - } - - // RequestFinishedInfo - if err := registerFunc(&cronetRequestFinishedInfoCreate, "Cronet_RequestFinishedInfo_Create"); err != nil { - return err - } - if err := registerFunc(&cronetRequestFinishedInfoDestroy, "Cronet_RequestFinishedInfo_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetRequestFinishedInfoMetricsSet, "Cronet_RequestFinishedInfo_metrics_set"); err != nil { - return err - } - if err := registerFunc(&cronetRequestFinishedInfoAnnotationsAdd, "Cronet_RequestFinishedInfo_annotations_add"); err != nil { - return err - } - if err := registerFunc(&cronetRequestFinishedInfoFinishedReasonSet, "Cronet_RequestFinishedInfo_finished_reason_set"); err != nil { - return err - } - if err := registerFunc(&cronetRequestFinishedInfoMetricsGet, "Cronet_RequestFinishedInfo_metrics_get"); err != nil { - return err - } - if err := registerFunc(&cronetRequestFinishedInfoAnnotationsSize, "Cronet_RequestFinishedInfo_annotations_size"); err != nil { - return err - } - if err := registerFunc(&cronetRequestFinishedInfoAnnotationsAt, "Cronet_RequestFinishedInfo_annotations_at"); err != nil { - return err - } - if err := registerFunc(&cronetRequestFinishedInfoAnnotationsClear, "Cronet_RequestFinishedInfo_annotations_clear"); err != nil { - return err - } - if err := registerFunc(&cronetRequestFinishedInfoFinishedReasonGet, "Cronet_RequestFinishedInfo_finished_reason_get"); err != nil { - return err - } - - // RequestFinishedInfoListener - if err := registerFunc(&cronetRequestFinishedInfoListenerDestroy, "Cronet_RequestFinishedInfoListener_Destroy"); err != nil { - return err - } - if err := registerFunc(&cronetRequestFinishedInfoListenerSetClientContext, "Cronet_RequestFinishedInfoListener_SetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetRequestFinishedInfoListenerGetClientContext, "Cronet_RequestFinishedInfoListener_GetClientContext"); err != nil { - return err - } - if err := registerFunc(&cronetRequestFinishedInfoListenerCreateWith, "Cronet_RequestFinishedInfoListener_CreateWith"); err != nil { - return err - } - - // Custom cert verifier - if err := registerFunc(&cronetCreateCertVerifierWithRootCerts, "Cronet_CreateCertVerifierWithRootCerts"); err != nil { - return err - } - - // BidirectionalStream - if err := registerFunc(&bidirectionalStreamCreate, "bidirectional_stream_create"); err != nil { - return err - } - if err := registerFunc(&bidirectionalStreamDestroy, "bidirectional_stream_destroy"); err != nil { - return err - } - if err := registerFunc(&bidirectionalStreamDisableAutoFlush, "bidirectional_stream_disable_auto_flush"); err != nil { - return err - } - if err := registerFunc(&bidirectionalStreamDelayRequestHeadersUntilFlush, "bidirectional_stream_delay_request_headers_until_flush"); err != nil { - return err - } - if err := registerFunc(&bidirectionalStreamStart, "bidirectional_stream_start"); err != nil { - return err - } - if err := registerFunc(&bidirectionalStreamRead, "bidirectional_stream_read"); err != nil { - return err - } - if err := registerFunc(&bidirectionalStreamWrite, "bidirectional_stream_write"); err != nil { - return err - } - if err := registerFunc(&bidirectionalStreamFlush, "bidirectional_stream_flush"); err != nil { - return err - } - if err := registerFunc(&bidirectionalStreamCancel, "bidirectional_stream_cancel"); err != nil { - return err - } - - // Register float functions (platform-specific, see loader_windows_float*.go) - if err := registerFloatFuncs(); err != nil { - return err - } - - return nil -} diff --git a/internal/cronet/loader_windows_float.go b/internal/cronet/loader_windows_float.go deleted file mode 100644 index 0a0be651..00000000 --- a/internal/cronet/loader_windows_float.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build with_purego && windows && (amd64 || arm64) - -package cronet - -// registerFloatFuncs registers functions that use float types. -// Only supported on 64-bit platforms due to purego limitations. -func registerFloatFuncs() error { - if err := registerFunc(&cronetEngineParamsNetworkThreadPrioritySet, "Cronet_EngineParams_network_thread_priority_set"); err != nil { - return err - } - if err := registerFunc(&cronetEngineParamsNetworkThreadPriorityGet, "Cronet_EngineParams_network_thread_priority_get"); err != nil { - return err - } - return nil -} diff --git a/internal/cronet/symbols_purego.go b/internal/cronet/symbols_purego.go deleted file mode 100644 index 509f7008..00000000 --- a/internal/cronet/symbols_purego.go +++ /dev/null @@ -1,334 +0,0 @@ -//go:build with_purego - -package cronet - -import "unsafe" - -// Cronet API function pointers loaded at runtime via purego. -// These are populated by the loader (loader_unix.go or loader_windows.go). - -var ( - // Buffer functions - cronetBufferCreate func() uintptr - cronetBufferDestroy func(uintptr) - cronetBufferSetClientContext func(uintptr, uintptr) - cronetBufferGetClientContext func(uintptr) uintptr - cronetBufferInitWithDataAndCallback func(uintptr, uintptr, uint64, uintptr) - cronetBufferInitWithAlloc func(uintptr, uint64) - cronetBufferGetSize func(uintptr) uint64 - cronetBufferGetData func(uintptr) uintptr - - // BufferCallback functions - cronetBufferCallbackDestroy func(uintptr) - cronetBufferCallbackSetClientContext func(uintptr, uintptr) - cronetBufferCallbackGetClientContext func(uintptr) uintptr - cronetBufferCallbackCreateWith func(uintptr) uintptr - - // Runnable functions - cronetRunnableDestroy func(uintptr) - cronetRunnableSetClientContext func(uintptr, uintptr) - cronetRunnableGetClientContext func(uintptr) uintptr - cronetRunnableRun func(uintptr) - cronetRunnableCreateWith func(uintptr) uintptr - - // Executor functions - cronetExecutorDestroy func(uintptr) - cronetExecutorSetClientContext func(uintptr, uintptr) - cronetExecutorGetClientContext func(uintptr) uintptr - cronetExecutorExecute func(uintptr, uintptr) - cronetExecutorCreateWith func(uintptr) uintptr - - // Engine functions - cronetEngineCreate func() uintptr - cronetEngineDestroy func(uintptr) - cronetEngineSetClientContext func(uintptr, uintptr) - cronetEngineGetClientContext func(uintptr) uintptr - cronetEngineStartWithParams func(uintptr, uintptr) int32 - cronetEngineStartNetLogToFile func(uintptr, string, bool) bool - cronetEngineStopNetLog func(uintptr) - cronetEngineShutdown func(uintptr) int32 - cronetEngineGetVersionString func(uintptr) uintptr - cronetEngineGetDefaultUserAgent func(uintptr) uintptr - cronetEngineAddRequestFinishedListener func(uintptr, uintptr, uintptr) - cronetEngineRemoveRequestFinishedListener func(uintptr, uintptr) - cronetEngineGetStreamEngine func(uintptr) uintptr - cronetEngineSetMockCertVerifierForTesting func(uintptr, uintptr) - cronetEngineCloseAllConnections func(uintptr) - cronetEngineSetDialer func(uintptr, uintptr, uintptr) - cronetEngineSetUdpDialer func(uintptr, uintptr, uintptr) - - // EngineParams functions - cronetEngineParamsCreate func() uintptr - cronetEngineParamsDestroy func(uintptr) - cronetEngineParamsEnableCheckResultSet func(uintptr, bool) - cronetEngineParamsUserAgentSet func(uintptr, string) - cronetEngineParamsAcceptLanguageSet func(uintptr, string) - cronetEngineParamsStoragePathSet func(uintptr, string) - cronetEngineParamsEnableQuicSet func(uintptr, bool) - cronetEngineParamsEnableHttp2Set func(uintptr, bool) - cronetEngineParamsEnableBrotliSet func(uintptr, bool) - cronetEngineParamsHttpCacheModeSet func(uintptr, int32) - cronetEngineParamsHttpCacheMaxSizeSet func(uintptr, int64) - cronetEngineParamsQuicHintsAdd func(uintptr, uintptr) - cronetEngineParamsPublicKeyPinsAdd func(uintptr, uintptr) - cronetEngineParamsEnablePublicKeyPinningBypassForLocalTrustAnchorsSet func(uintptr, bool) - cronetEngineParamsNetworkThreadPrioritySet func(uintptr, float64) - cronetEngineParamsExperimentalOptionsSet func(uintptr, string) - cronetEngineParamsEnableCheckResultGet func(uintptr) bool - cronetEngineParamsUserAgentGet func(uintptr) uintptr - cronetEngineParamsAcceptLanguageGet func(uintptr) uintptr - cronetEngineParamsStoragePathGet func(uintptr) uintptr - cronetEngineParamsEnableQuicGet func(uintptr) bool - cronetEngineParamsEnableHttp2Get func(uintptr) bool - cronetEngineParamsEnableBrotliGet func(uintptr) bool - cronetEngineParamsHttpCacheModeGet func(uintptr) int32 - cronetEngineParamsHttpCacheMaxSizeGet func(uintptr) int64 - cronetEngineParamsQuicHintsSize func(uintptr) uint32 - cronetEngineParamsQuicHintsAt func(uintptr, uint32) uintptr - cronetEngineParamsQuicHintsClear func(uintptr) - cronetEngineParamsPublicKeyPinsSize func(uintptr) uint32 - cronetEngineParamsPublicKeyPinsAt func(uintptr, uint32) uintptr - cronetEngineParamsPublicKeyPinsClear func(uintptr) - cronetEngineParamsEnablePublicKeyPinningBypassForLocalTrustAnchorsGet func(uintptr) bool - cronetEngineParamsNetworkThreadPriorityGet func(uintptr) float64 - cronetEngineParamsExperimentalOptionsGet func(uintptr) uintptr - - // UrlRequest functions - cronetUrlRequestCreate func() uintptr - cronetUrlRequestDestroy func(uintptr) - cronetUrlRequestSetClientContext func(uintptr, uintptr) - cronetUrlRequestGetClientContext func(uintptr) uintptr - cronetUrlRequestInitWithParams func(uintptr, uintptr, string, uintptr, uintptr, uintptr) int32 - cronetUrlRequestStart func(uintptr) int32 - cronetUrlRequestFollowRedirect func(uintptr) int32 - cronetUrlRequestRead func(uintptr, uintptr) int32 - cronetUrlRequestCancel func(uintptr) - cronetUrlRequestIsDone func(uintptr) bool - cronetUrlRequestGetStatus func(uintptr, uintptr) - - // UrlRequestParams functions - cronetUrlRequestParamsCreate func() uintptr - cronetUrlRequestParamsDestroy func(uintptr) - cronetUrlRequestParamsHttpMethodSet func(uintptr, string) - cronetUrlRequestParamsRequestHeadersAdd func(uintptr, uintptr) - cronetUrlRequestParamsDisableCacheSet func(uintptr, bool) - cronetUrlRequestParamsPrioritySet func(uintptr, int32) - cronetUrlRequestParamsUploadDataProviderSet func(uintptr, uintptr) - cronetUrlRequestParamsUploadDataProviderExecutorSet func(uintptr, uintptr) - cronetUrlRequestParamsAllowDirectExecutorSet func(uintptr, bool) - cronetUrlRequestParamsAnnotationsAdd func(uintptr, uintptr) - cronetUrlRequestParamsRequestFinishedListenerSet func(uintptr, uintptr) - cronetUrlRequestParamsRequestFinishedExecutorSet func(uintptr, uintptr) - cronetUrlRequestParamsIdempotencySet func(uintptr, int32) - cronetUrlRequestParamsHttpMethodGet func(uintptr) uintptr - cronetUrlRequestParamsRequestHeadersSize func(uintptr) uint32 - cronetUrlRequestParamsRequestHeadersAt func(uintptr, uint32) uintptr - cronetUrlRequestParamsRequestHeadersClear func(uintptr) - cronetUrlRequestParamsDisableCacheGet func(uintptr) bool - cronetUrlRequestParamsPriorityGet func(uintptr) int32 - cronetUrlRequestParamsUploadDataProviderGet func(uintptr) uintptr - cronetUrlRequestParamsUploadDataProviderExecutorGet func(uintptr) uintptr - cronetUrlRequestParamsAllowDirectExecutorGet func(uintptr) bool - cronetUrlRequestParamsAnnotationsSize func(uintptr) uint32 - cronetUrlRequestParamsAnnotationsAt func(uintptr, uint32) uintptr - cronetUrlRequestParamsAnnotationsClear func(uintptr) - cronetUrlRequestParamsRequestFinishedListenerGet func(uintptr) uintptr - cronetUrlRequestParamsRequestFinishedExecutorGet func(uintptr) uintptr - cronetUrlRequestParamsIdempotencyGet func(uintptr) int32 - - // UrlRequestCallback functions - cronetUrlRequestCallbackDestroy func(uintptr) - cronetUrlRequestCallbackSetClientContext func(uintptr, uintptr) - cronetUrlRequestCallbackGetClientContext func(uintptr) uintptr - cronetUrlRequestCallbackCreateWith func(uintptr, uintptr, uintptr, uintptr, uintptr, uintptr) uintptr - - // UrlRequestStatusListener functions - cronetUrlRequestStatusListenerDestroy func(uintptr) - cronetUrlRequestStatusListenerSetClientContext func(uintptr, uintptr) - cronetUrlRequestStatusListenerGetClientContext func(uintptr) uintptr - cronetUrlRequestStatusListenerCreateWith func(uintptr) uintptr - - // UploadDataProvider functions - cronetUploadDataProviderDestroy func(uintptr) - cronetUploadDataProviderSetClientContext func(uintptr, uintptr) - cronetUploadDataProviderGetClientContext func(uintptr) uintptr - cronetUploadDataProviderCreateWith func(uintptr, uintptr, uintptr, uintptr) uintptr - - // UploadDataSink functions - cronetUploadDataSinkDestroy func(uintptr) - cronetUploadDataSinkSetClientContext func(uintptr, uintptr) - cronetUploadDataSinkGetClientContext func(uintptr) uintptr - cronetUploadDataSinkOnReadSucceeded func(uintptr, uint64, bool) - cronetUploadDataSinkOnReadError func(uintptr, string) - cronetUploadDataSinkOnRewindSucceeded func(uintptr) - cronetUploadDataSinkOnRewindError func(uintptr, string) - - // UrlResponseInfo functions - cronetUrlResponseInfoCreate func() uintptr - cronetUrlResponseInfoDestroy func(uintptr) - cronetUrlResponseInfoUrlSet func(uintptr, uintptr) - cronetUrlResponseInfoUrlChainAdd func(uintptr, uintptr) - cronetUrlResponseInfoHttpStatusCodeSet func(uintptr, int32) - cronetUrlResponseInfoHttpStatusTextSet func(uintptr, uintptr) - cronetUrlResponseInfoAllHeadersListAdd func(uintptr, uintptr) - cronetUrlResponseInfoWasCachedSet func(uintptr, bool) - cronetUrlResponseInfoNegotiatedProtocolSet func(uintptr, uintptr) - cronetUrlResponseInfoProxyServerSet func(uintptr, uintptr) - cronetUrlResponseInfoReceivedByteCountSet func(uintptr, int64) - cronetUrlResponseInfoUrlGet func(uintptr) uintptr - cronetUrlResponseInfoUrlChainSize func(uintptr) uint32 - cronetUrlResponseInfoUrlChainAt func(uintptr, uint32) uintptr - cronetUrlResponseInfoUrlChainClear func(uintptr) - cronetUrlResponseInfoHttpStatusCodeGet func(uintptr) int32 - cronetUrlResponseInfoHttpStatusTextGet func(uintptr) uintptr - cronetUrlResponseInfoAllHeadersListSize func(uintptr) uint32 - cronetUrlResponseInfoAllHeadersListAt func(uintptr, uint32) uintptr - cronetUrlResponseInfoAllHeadersListClear func(uintptr) - cronetUrlResponseInfoWasCachedGet func(uintptr) bool - cronetUrlResponseInfoNegotiatedProtocolGet func(uintptr) uintptr - cronetUrlResponseInfoProxyServerGet func(uintptr) uintptr - cronetUrlResponseInfoReceivedByteCountGet func(uintptr) int64 - - // Error functions - cronetErrorCreate func() uintptr - cronetErrorDestroy func(uintptr) - cronetErrorErrorCodeSet func(uintptr, int32) - cronetErrorMessageSet func(uintptr, uintptr) - cronetErrorInternalErrorCodeSet func(uintptr, int32) - cronetErrorImmediatelyRetryableSet func(uintptr, bool) - cronetErrorQuicDetailedErrorCodeSet func(uintptr, int32) - cronetErrorErrorCodeGet func(uintptr) int32 - cronetErrorMessageGet func(uintptr) uintptr - cronetErrorInternalErrorCodeGet func(uintptr) int32 - cronetErrorImmediatelyRetryableGet func(uintptr) bool - cronetErrorQuicDetailedErrorCodeGet func(uintptr) int32 - - // HttpHeader functions - cronetHttpHeaderCreate func() uintptr - cronetHttpHeaderDestroy func(uintptr) - cronetHttpHeaderNameSet func(uintptr, string) - cronetHttpHeaderValueSet func(uintptr, string) - cronetHttpHeaderNameGet func(uintptr) uintptr - cronetHttpHeaderValueGet func(uintptr) uintptr - - // QuicHint functions - cronetQuicHintCreate func() uintptr - cronetQuicHintDestroy func(uintptr) - cronetQuicHintHostSet func(uintptr, string) - cronetQuicHintPortSet func(uintptr, int32) - cronetQuicHintAlternatePortSet func(uintptr, int32) - cronetQuicHintHostGet func(uintptr) uintptr - cronetQuicHintPortGet func(uintptr) int32 - cronetQuicHintAlternatePortGet func(uintptr) int32 - - // PublicKeyPins functions - cronetPublicKeyPinsCreate func() uintptr - cronetPublicKeyPinsDestroy func(uintptr) - cronetPublicKeyPinsHostSet func(uintptr, string) - cronetPublicKeyPinsPinsSha256Add func(uintptr, string) - cronetPublicKeyPinsIncludeSubdomainsSet func(uintptr, bool) - cronetPublicKeyPinsExpirationDateSet func(uintptr, int64) - cronetPublicKeyPinsHostGet func(uintptr) uintptr - cronetPublicKeyPinsPinsSha256Size func(uintptr) uint32 - cronetPublicKeyPinsPinsSha256At func(uintptr, uint32) uintptr - cronetPublicKeyPinsPinsSha256Clear func(uintptr) - cronetPublicKeyPinsIncludeSubdomainsGet func(uintptr) bool - cronetPublicKeyPinsExpirationDateGet func(uintptr) int64 - - // DateTime functions - cronetDateTimeCreate func() uintptr - cronetDateTimeDestroy func(uintptr) - cronetDateTimeValueSet func(uintptr, int64) - cronetDateTimeValueGet func(uintptr) int64 - - // Metrics functions - cronetMetricsCreate func() uintptr - cronetMetricsDestroy func(uintptr) - cronetMetricsRequestStartSet func(uintptr, uintptr) - cronetMetricsDnsStartSet func(uintptr, uintptr) - cronetMetricsDnsEndSet func(uintptr, uintptr) - cronetMetricsConnectStartSet func(uintptr, uintptr) - cronetMetricsConnectEndSet func(uintptr, uintptr) - cronetMetricsSslStartSet func(uintptr, uintptr) - cronetMetricsSslEndSet func(uintptr, uintptr) - cronetMetricsSendingStartSet func(uintptr, uintptr) - cronetMetricsSendingEndSet func(uintptr, uintptr) - cronetMetricsPushStartSet func(uintptr, uintptr) - cronetMetricsPushEndSet func(uintptr, uintptr) - cronetMetricsResponseStartSet func(uintptr, uintptr) - cronetMetricsRequestEndSet func(uintptr, uintptr) - cronetMetricsSocketReusedSet func(uintptr, bool) - cronetMetricsSentByteCountSet func(uintptr, int64) - cronetMetricsReceivedByteCountSet func(uintptr, int64) - cronetMetricsRequestStartGet func(uintptr) uintptr - cronetMetricsDnsStartGet func(uintptr) uintptr - cronetMetricsDnsEndGet func(uintptr) uintptr - cronetMetricsConnectStartGet func(uintptr) uintptr - cronetMetricsConnectEndGet func(uintptr) uintptr - cronetMetricsSslStartGet func(uintptr) uintptr - cronetMetricsSslEndGet func(uintptr) uintptr - cronetMetricsSendingStartGet func(uintptr) uintptr - cronetMetricsSendingEndGet func(uintptr) uintptr - cronetMetricsPushStartGet func(uintptr) uintptr - cronetMetricsPushEndGet func(uintptr) uintptr - cronetMetricsResponseStartGet func(uintptr) uintptr - cronetMetricsRequestEndGet func(uintptr) uintptr - cronetMetricsSocketReusedGet func(uintptr) bool - cronetMetricsSentByteCountGet func(uintptr) int64 - cronetMetricsReceivedByteCountGet func(uintptr) int64 - - // RequestFinishedInfo functions - cronetRequestFinishedInfoCreate func() uintptr - cronetRequestFinishedInfoDestroy func(uintptr) - cronetRequestFinishedInfoMetricsSet func(uintptr, uintptr) - cronetRequestFinishedInfoAnnotationsAdd func(uintptr, uintptr) - cronetRequestFinishedInfoFinishedReasonSet func(uintptr, int32) - cronetRequestFinishedInfoMetricsGet func(uintptr) uintptr - cronetRequestFinishedInfoAnnotationsSize func(uintptr) uint32 - cronetRequestFinishedInfoAnnotationsAt func(uintptr, uint32) uintptr - cronetRequestFinishedInfoAnnotationsClear func(uintptr) - cronetRequestFinishedInfoFinishedReasonGet func(uintptr) int32 - - // RequestFinishedInfoListener functions - cronetRequestFinishedInfoListenerDestroy func(uintptr) - cronetRequestFinishedInfoListenerSetClientContext func(uintptr, uintptr) - cronetRequestFinishedInfoListenerGetClientContext func(uintptr) uintptr - cronetRequestFinishedInfoListenerCreateWith func(uintptr) uintptr - - // Custom cert verifier functions - cronetCreateCertVerifierWithRootCerts func(string) uintptr -) - -// BidirectionalStream API function pointers -var ( - bidirectionalStreamCreate func(uintptr, uintptr, uintptr) uintptr - bidirectionalStreamDestroy func(uintptr) int32 - bidirectionalStreamDisableAutoFlush func(uintptr, bool) - bidirectionalStreamDelayRequestHeadersUntilFlush func(uintptr, bool) - bidirectionalStreamStart func(uintptr, string, int32, string, uintptr, bool) int32 - bidirectionalStreamRead func(uintptr, uintptr, int32) int32 - bidirectionalStreamWrite func(uintptr, uintptr, int32, bool) int32 - bidirectionalStreamFlush func(uintptr) - bidirectionalStreamCancel func(uintptr) -) - -// GoString converts a C string pointer to a Go string. -// Maximum string length is 64KB to prevent infinite loops on corrupted strings. -func GoString(ptr uintptr) string { - if ptr == 0 { - return "" - } - const maxLength = 65536 // 64KB max string length - var length int - for length < maxLength { - b := *(*byte)(unsafe.Pointer(ptr + uintptr(length))) - if b == 0 { - break - } - length++ - } - if length == 0 { - return "" - } - return string(unsafe.Slice((*byte)(unsafe.Pointer(ptr)), length)) -} diff --git a/loader_purego.go b/loader_purego.go deleted file mode 100644 index f8185e3d..00000000 --- a/loader_purego.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build with_purego - -package cronet - -import "github.com/sagernet/cronet-go/internal/cronet" - -func LoadLibrary(path string) error { - return cronet.LoadLibrary(path) -} diff --git a/metrics_cgo.go b/metrics_cgo.go deleted file mode 100644 index d5cb4745..00000000 --- a/metrics_cgo.go +++ /dev/null @@ -1,210 +0,0 @@ -//go:build !with_purego - -package cronet - -// #include -// #include -// #include -import "C" - -import "unsafe" - -func NewMetrics() Metrics { - return Metrics{uintptr(unsafe.Pointer(C.Cronet_Metrics_Create()))} -} - -func (m Metrics) Destroy() { - C.Cronet_Metrics_Destroy(C.Cronet_MetricsPtr(unsafe.Pointer(m.ptr))) -} - -// RequestStart -// Time when the request started, which corresponds to calling -// Cronet_UrlRequest_Start(). This timestamp will match the system clock at -// the time it represents. -func (m Metrics) RequestStart() DateTime { - return DateTime{uintptr(unsafe.Pointer(C.Cronet_Metrics_request_start_get(C.Cronet_MetricsPtr(unsafe.Pointer(m.ptr)))))} -} - -// DNSStart -// Time when DNS lookup started. This and DNSEnd will be set to -// non-null regardless of whether the result came from a DNS server or the -// local cache. Will equal null if the socket was reused (see SocketReused). -func (m Metrics) DNSStart() DateTime { - return DateTime{uintptr(unsafe.Pointer(C.Cronet_Metrics_dns_start_get(C.Cronet_MetricsPtr(unsafe.Pointer(m.ptr)))))} -} - -// DNSEnd -// Time when DNS lookup finished. This and DNSStart will return -// non-null regardless of whether the result came from a DNS server or the -// local cache. Will equal null if the socket was reused (see SocketReused). -func (m Metrics) DNSEnd() DateTime { - return DateTime{uintptr(unsafe.Pointer(C.Cronet_Metrics_dns_end_get(C.Cronet_MetricsPtr(unsafe.Pointer(m.ptr)))))} -} - -// ConnectStart -// Time when connection establishment started, typically when DNS resolution -// finishes. Will equal null if the socket was reused (see SocketReused). -func (m Metrics) ConnectStart() DateTime { - return DateTime{uintptr(unsafe.Pointer(C.Cronet_Metrics_connect_start_get(C.Cronet_MetricsPtr(unsafe.Pointer(m.ptr)))))} -} - -// ConnectEnd -// Time when connection establishment finished, after TCP connection is -// established and, if using HTTPS, SSL handshake is completed. For QUIC -// 0-RTT, this represents the time of handshake confirmation and might happen -// later than SendingStart. Will equal null if the socket was -// reused (see SocketReused). -func (m Metrics) ConnectEnd() DateTime { - return DateTime{uintptr(unsafe.Pointer(C.Cronet_Metrics_connect_end_get(C.Cronet_MetricsPtr(unsafe.Pointer(m.ptr)))))} -} - -// SSLStart -// Time when SSL handshake started. For QUIC, this will be the same time as -// ConnectStart. Will equal null if SSL is not used or if the -// socket was reused (see SocketReused). -func (m Metrics) SSLStart() DateTime { - return DateTime{uintptr(unsafe.Pointer(C.Cronet_Metrics_ssl_start_get(C.Cronet_MetricsPtr(unsafe.Pointer(m.ptr)))))} -} - -// SSLEnd -// Time when SSL handshake finished. For QUIC, this will be the same time as -// ConnectEnd. Will equal null if SSL is not used or if the socket -// was reused (see SocketReused). -func (m Metrics) SSLEnd() DateTime { - return DateTime{uintptr(unsafe.Pointer(C.Cronet_Metrics_ssl_end_get(C.Cronet_MetricsPtr(unsafe.Pointer(m.ptr)))))} -} - -// SendingStart -// Time when sending HTTP request headers started. -// -// Will equal null if the request failed or was canceled before sending -// started. -func (m Metrics) SendingStart() DateTime { - return DateTime{uintptr(unsafe.Pointer(C.Cronet_Metrics_sending_start_get(C.Cronet_MetricsPtr(unsafe.Pointer(m.ptr)))))} -} - -// SendingEnd -// Time when sending HTTP request body finished. (Sending request body -// happens after sending request headers.) -// -// Will equal null if the request failed or was canceled before sending -// ended. -func (m Metrics) SendingEnd() DateTime { - return DateTime{uintptr(unsafe.Pointer(C.Cronet_Metrics_sending_end_get(C.Cronet_MetricsPtr(unsafe.Pointer(m.ptr)))))} -} - -// PushStart -// Time when first byte of HTTP/2 server push was received. Will equal -// null if server push is not used. -func (m Metrics) PushStart() DateTime { - return DateTime{uintptr(unsafe.Pointer(C.Cronet_Metrics_push_start_get(C.Cronet_MetricsPtr(unsafe.Pointer(m.ptr)))))} -} - -// PushEnd -// Time when last byte of HTTP/2 server push was received. Will equal -// null if server push is not used. -func (m Metrics) PushEnd() DateTime { - return DateTime{uintptr(unsafe.Pointer(C.Cronet_Metrics_push_end_get(C.Cronet_MetricsPtr(unsafe.Pointer(m.ptr)))))} -} - -// ResponseStart -// Time when the end of the response headers was received. -// -// Will equal null if the request failed or was canceled before the response -// started. -func (m Metrics) ResponseStart() DateTime { - return DateTime{uintptr(unsafe.Pointer(C.Cronet_Metrics_response_start_get(C.Cronet_MetricsPtr(unsafe.Pointer(m.ptr)))))} -} - -// ResponseEnd -// Time when the request finished. -func (m Metrics) ResponseEnd() DateTime { - return DateTime{uintptr(unsafe.Pointer(C.Cronet_Metrics_request_end_get(C.Cronet_MetricsPtr(unsafe.Pointer(m.ptr)))))} -} - -// SocketReused -// True if the socket was reused from a previous request, false otherwise. -// In HTTP/2 or QUIC, if streams are multiplexed in a single connection, this -// will be {@code true} for all streams after the first. When {@code true}, -// DNS, connection, and SSL times will be null. -func (m Metrics) SocketReused() bool { - return bool(C.Cronet_Metrics_socket_reused_get(C.Cronet_MetricsPtr(unsafe.Pointer(m.ptr)))) -} - -// SentByteCount -// Returns total bytes sent over the network transport layer, or -1 if not -// collected. -func (m Metrics) SentByteCount() int64 { - return int64(C.Cronet_Metrics_sent_byte_count_get(C.Cronet_MetricsPtr(unsafe.Pointer(m.ptr)))) -} - -// ReceivedByteCount -// Total bytes received over the network transport layer, or -1 if not -// collected. Number of bytes does not include any previous redirects. -func (m Metrics) ReceivedByteCount() int64 { - return int64(C.Cronet_Metrics_received_byte_count_get(C.Cronet_MetricsPtr(unsafe.Pointer(m.ptr)))) -} - -func (m Metrics) SetRequestStart(t DateTime) { - C.Cronet_Metrics_request_start_set(C.Cronet_MetricsPtr(unsafe.Pointer(m.ptr)), C.Cronet_DateTimePtr(unsafe.Pointer(t.ptr))) -} - -func (m Metrics) SetDNSStart(t DateTime) { - C.Cronet_Metrics_dns_start_set(C.Cronet_MetricsPtr(unsafe.Pointer(m.ptr)), C.Cronet_DateTimePtr(unsafe.Pointer(t.ptr))) -} - -func (m Metrics) SetDNSEnd(t DateTime) { - C.Cronet_Metrics_dns_end_set(C.Cronet_MetricsPtr(unsafe.Pointer(m.ptr)), C.Cronet_DateTimePtr(unsafe.Pointer(t.ptr))) -} - -func (m Metrics) SetConnectStart(t DateTime) { - C.Cronet_Metrics_connect_start_set(C.Cronet_MetricsPtr(unsafe.Pointer(m.ptr)), C.Cronet_DateTimePtr(unsafe.Pointer(t.ptr))) -} - -func (m Metrics) SetConnectEnd(t DateTime) { - C.Cronet_Metrics_connect_end_set(C.Cronet_MetricsPtr(unsafe.Pointer(m.ptr)), C.Cronet_DateTimePtr(unsafe.Pointer(t.ptr))) -} - -func (m Metrics) SetSSLStart(t DateTime) { - C.Cronet_Metrics_ssl_start_set(C.Cronet_MetricsPtr(unsafe.Pointer(m.ptr)), C.Cronet_DateTimePtr(unsafe.Pointer(t.ptr))) -} - -func (m Metrics) SetSSLEnd(t DateTime) { - C.Cronet_Metrics_ssl_end_set(C.Cronet_MetricsPtr(unsafe.Pointer(m.ptr)), C.Cronet_DateTimePtr(unsafe.Pointer(t.ptr))) -} - -func (m Metrics) SetSendingStart(t DateTime) { - C.Cronet_Metrics_sending_start_set(C.Cronet_MetricsPtr(unsafe.Pointer(m.ptr)), C.Cronet_DateTimePtr(unsafe.Pointer(t.ptr))) -} - -func (m Metrics) SetSendingEnd(t DateTime) { - C.Cronet_Metrics_sending_end_set(C.Cronet_MetricsPtr(unsafe.Pointer(m.ptr)), C.Cronet_DateTimePtr(unsafe.Pointer(t.ptr))) -} - -func (m Metrics) SetPushStart(t DateTime) { - C.Cronet_Metrics_push_start_set(C.Cronet_MetricsPtr(unsafe.Pointer(m.ptr)), C.Cronet_DateTimePtr(unsafe.Pointer(t.ptr))) -} - -func (m Metrics) SetPushEnd(t DateTime) { - C.Cronet_Metrics_push_end_set(C.Cronet_MetricsPtr(unsafe.Pointer(m.ptr)), C.Cronet_DateTimePtr(unsafe.Pointer(t.ptr))) -} - -func (m Metrics) SetResponseStart(t DateTime) { - C.Cronet_Metrics_response_start_set(C.Cronet_MetricsPtr(unsafe.Pointer(m.ptr)), C.Cronet_DateTimePtr(unsafe.Pointer(t.ptr))) -} - -func (m Metrics) SetRequestEnd(t DateTime) { - C.Cronet_Metrics_request_end_set(C.Cronet_MetricsPtr(unsafe.Pointer(m.ptr)), C.Cronet_DateTimePtr(unsafe.Pointer(t.ptr))) -} - -func (m Metrics) SetSocketReused(reused bool) { - C.Cronet_Metrics_socket_reused_set(C.Cronet_MetricsPtr(unsafe.Pointer(m.ptr)), C.bool(reused)) -} - -func (m Metrics) SetSentByteCount(count int64) { - C.Cronet_Metrics_sent_byte_count_set(C.Cronet_MetricsPtr(unsafe.Pointer(m.ptr)), C.int64_t(count)) -} - -func (m Metrics) SetReceivedByteCount(count int64) { - C.Cronet_Metrics_received_byte_count_set(C.Cronet_MetricsPtr(unsafe.Pointer(m.ptr)), C.int64_t(count)) -} diff --git a/metrics_purego.go b/metrics_purego.go deleted file mode 100644 index fffd4fd9..00000000 --- a/metrics_purego.go +++ /dev/null @@ -1,143 +0,0 @@ -//go:build with_purego - -package cronet - -import ( - "github.com/sagernet/cronet-go/internal/cronet" -) - -func NewMetrics() Metrics { - return Metrics{cronet.MetricsCreate()} -} - -func (m Metrics) Destroy() { - cronet.MetricsDestroy(m.ptr) -} - -func (m Metrics) RequestStart() DateTime { - return DateTime{cronet.MetricsRequestStartGet(m.ptr)} -} - -func (m Metrics) DNSStart() DateTime { - return DateTime{cronet.MetricsDnsStartGet(m.ptr)} -} - -func (m Metrics) DNSEnd() DateTime { - return DateTime{cronet.MetricsDnsEndGet(m.ptr)} -} - -func (m Metrics) ConnectStart() DateTime { - return DateTime{cronet.MetricsConnectStartGet(m.ptr)} -} - -func (m Metrics) ConnectEnd() DateTime { - return DateTime{cronet.MetricsConnectEndGet(m.ptr)} -} - -func (m Metrics) SSLStart() DateTime { - return DateTime{cronet.MetricsSslStartGet(m.ptr)} -} - -func (m Metrics) SSLEnd() DateTime { - return DateTime{cronet.MetricsSslEndGet(m.ptr)} -} - -func (m Metrics) SendingStart() DateTime { - return DateTime{cronet.MetricsSendingStartGet(m.ptr)} -} - -func (m Metrics) SendingEnd() DateTime { - return DateTime{cronet.MetricsSendingEndGet(m.ptr)} -} - -func (m Metrics) PushStart() DateTime { - return DateTime{cronet.MetricsPushStartGet(m.ptr)} -} - -func (m Metrics) PushEnd() DateTime { - return DateTime{cronet.MetricsPushEndGet(m.ptr)} -} - -func (m Metrics) ResponseStart() DateTime { - return DateTime{cronet.MetricsResponseStartGet(m.ptr)} -} - -func (m Metrics) ResponseEnd() DateTime { - return DateTime{cronet.MetricsRequestEndGet(m.ptr)} -} - -func (m Metrics) SocketReused() bool { - return cronet.MetricsSocketReusedGet(m.ptr) -} - -func (m Metrics) SentByteCount() int64 { - return cronet.MetricsSentByteCountGet(m.ptr) -} - -func (m Metrics) ReceivedByteCount() int64 { - return cronet.MetricsReceivedByteCountGet(m.ptr) -} - -func (m Metrics) SetRequestStart(t DateTime) { - cronet.MetricsRequestStartSet(m.ptr, t.ptr) -} - -func (m Metrics) SetDNSStart(t DateTime) { - cronet.MetricsDnsStartSet(m.ptr, t.ptr) -} - -func (m Metrics) SetDNSEnd(t DateTime) { - cronet.MetricsDnsEndSet(m.ptr, t.ptr) -} - -func (m Metrics) SetConnectStart(t DateTime) { - cronet.MetricsConnectStartSet(m.ptr, t.ptr) -} - -func (m Metrics) SetConnectEnd(t DateTime) { - cronet.MetricsConnectEndSet(m.ptr, t.ptr) -} - -func (m Metrics) SetSSLStart(t DateTime) { - cronet.MetricsSslStartSet(m.ptr, t.ptr) -} - -func (m Metrics) SetSSLEnd(t DateTime) { - cronet.MetricsSslEndSet(m.ptr, t.ptr) -} - -func (m Metrics) SetSendingStart(t DateTime) { - cronet.MetricsSendingStartSet(m.ptr, t.ptr) -} - -func (m Metrics) SetSendingEnd(t DateTime) { - cronet.MetricsSendingEndSet(m.ptr, t.ptr) -} - -func (m Metrics) SetPushStart(t DateTime) { - cronet.MetricsPushStartSet(m.ptr, t.ptr) -} - -func (m Metrics) SetPushEnd(t DateTime) { - cronet.MetricsPushEndSet(m.ptr, t.ptr) -} - -func (m Metrics) SetResponseStart(t DateTime) { - cronet.MetricsResponseStartSet(m.ptr, t.ptr) -} - -func (m Metrics) SetRequestEnd(t DateTime) { - cronet.MetricsRequestEndSet(m.ptr, t.ptr) -} - -func (m Metrics) SetSocketReused(reused bool) { - cronet.MetricsSocketReusedSet(m.ptr, reused) -} - -func (m Metrics) SetSentByteCount(count int64) { - cronet.MetricsSentByteCountSet(m.ptr, count) -} - -func (m Metrics) SetReceivedByteCount(count int64) { - cronet.MetricsReceivedByteCountSet(m.ptr, count) -} diff --git a/naive_client.go b/naive_client.go deleted file mode 100644 index 32e3e125..00000000 --- a/naive_client.go +++ /dev/null @@ -1,558 +0,0 @@ -package cronet - -import ( - "context" - "encoding/base64" - "errors" - "net" - "net/url" - "os" - "runtime" - "sync" - "sync/atomic" - - - "github.com/sagernet/sing/common/bufio" - E "github.com/sagernet/sing/common/exceptions" - F "github.com/sagernet/sing/common/format" - "github.com/sagernet/sing/common/logger" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -var _ N.Dialer = (*NaiveClient)(nil) - -type QUICCongestionControl string - -const ( - QUICCongestionControlDefault QUICCongestionControl = "" - QUICCongestionControlBBR QUICCongestionControl = "TBBR" - QUICCongestionControlBBRv2 QUICCongestionControl = "B2ON" - QUICCongestionControlCubic QUICCongestionControl = "QBIC" - QUICCongestionControlReno QUICCongestionControl = "RENO" -) - -type clientState uint32 - -const ( - clientStateCreated clientState = iota - clientStateStarting - clientStateRunning - clientStateClosing - clientStateClosed -) - -type NaiveClient struct { - state atomic.Uint32 - ctx context.Context - dialer N.Dialer - logger logger.ContextLogger - serverAddress M.Socksaddr - serverName string - serverURL string - authorization string - concurrency int - extraHeaders map[string]string - receiveWindow uint64 - trustedRootCertificates string - dnsResolver DNSResolverFunc - echEnabled bool - echConfigList []byte - echQueryServerName string - echMutex sync.RWMutex - testForceUDPLoopback bool - quicEnabled bool - quicCongestionControl QUICCongestionControl - quicSessionReceiveWindow uint64 - counter atomic.Uint64 - started chan struct{} - engine Engine - streamEngine StreamEngine - activeConnections sync.WaitGroup - proxyWaitGroup sync.WaitGroup - proxyCancel context.CancelFunc -} - -type NaiveClientOptions struct { - Context context.Context - ServerAddress M.Socksaddr - ServerName string - Username string - Password string - InsecureConcurrency int - ReceiveWindow uint64 - ExtraHeaders map[string]string - TrustedRootCertificates string - DNSResolver DNSResolverFunc - Logger logger.ContextLogger - Dialer N.Dialer - ECHEnabled bool - ECHConfigList []byte - ECHQueryServerName string - TestForceUDPLoopback bool - QUIC bool - QUICCongestionControl QUICCongestionControl - QUICSessionReceiveWindow uint64 -} - -func NewNaiveClient(config NaiveClientOptions) (*NaiveClient, error) { - err := checkLibrary() - if err != nil { - return nil, err - } - if !config.ServerAddress.IsValid() { - return nil, E.New("invalid server address") - } - if config.DNSResolver == nil { - return nil, E.New("DNSResolver is required") - } - if config.QUIC && config.InsecureConcurrency > 1 { - return nil, E.New("insecure concurrency is not supported with QUIC") - } - - serverName := config.ServerName - if serverName == "" { - serverName = config.ServerAddress.AddrString() - } - - serverURL := &url.URL{ - Scheme: "https", - Host: net.JoinHostPort(serverName, F.ToString(config.ServerAddress.Port)), - } - - var authorization string - if config.Username != "" { - authorization = "Basic " + base64.StdEncoding.EncodeToString( - []byte(config.Username+":"+config.Password)) - } - - concurrency := config.InsecureConcurrency - if concurrency < 1 { - concurrency = 1 - } - - ctx := config.Context - if ctx == nil { - ctx = context.Background() - } - - dialer := config.Dialer - if dialer == nil { - dialer = N.SystemDialer - } - - l := config.Logger - if l == nil { - l = logger.NOP() - } - - return &NaiveClient{ - ctx: ctx, - dialer: dialer, - logger: l, - serverAddress: config.ServerAddress, - serverName: serverName, - serverURL: serverURL.String(), - authorization: authorization, - extraHeaders: config.ExtraHeaders, - concurrency: concurrency, - trustedRootCertificates: config.TrustedRootCertificates, - dnsResolver: config.DNSResolver, - echEnabled: config.ECHEnabled, - echConfigList: config.ECHConfigList, - echQueryServerName: config.ECHQueryServerName, - testForceUDPLoopback: config.TestForceUDPLoopback, - quicEnabled: config.QUIC, - quicCongestionControl: config.QUICCongestionControl, - receiveWindow: config.ReceiveWindow, - quicSessionReceiveWindow: config.QUICSessionReceiveWindow, - started: make(chan struct{}), - }, nil -} - -func (c *NaiveClient) Start() error { - if !c.state.CompareAndSwap(uint32(clientStateCreated), uint32(clientStateStarting)) { - state := clientState(c.state.Load()) - switch state { - case clientStateStarting: - return errors.New("start already in progress") - case clientStateRunning: - return errors.New("already started") - default: - return net.ErrClosed - } - } - - engine := NewEngine() - var startError error - - defer func() { - if startError != nil { - if c.proxyCancel != nil { - c.proxyCancel() - } - if engine.ptr != 0 { - engine.Shutdown() - engine.Destroy() - } - c.state.Store(uint32(clientStateClosed)) - close(c.started) - } - }() - - if c.trustedRootCertificates != "" { - if !engine.SetTrustedRootCertificates(c.trustedRootCertificates) { - startError = E.New("failed to set trusted CA certificates") - return startError - } - } - - proxyContext, proxyCancel := context.WithCancel(c.ctx) - c.proxyCancel = proxyCancel - - dnsServerAddress := M.ParseSocksaddrHostPort("127.0.0.1", 53) - dnsResolver := c.dnsResolver - - if c.serverName != c.serverAddress.AddrString() { - dnsResolver = wrapDNSResolverForServerRedirect(dnsResolver, c.serverName, c.serverAddress) - } - - if c.echEnabled { - echQueryServerName := c.echQueryServerName - if echQueryServerName == "" { - echQueryServerName = c.serverName - } - dnsResolver = wrapDNSResolverWithECH(dnsResolver, c.serverName, echQueryServerName, c.getECHConfigList, c.quicEnabled, c.logger) - } - - engine.SetDialer(func(address string, port uint16) int { - if address == dnsServerAddress.AddrString() && port == dnsServerAddress.Port { - fd, conn, err := createSocketPair() - if err != nil { - c.logger.ErrorContext(c.ctx, "socket pair failed: ", err) - return NetErrorConnectionFailed.Code() - } - - go func() { - _ = serveDNSStreamConn(proxyContext, conn, dnsResolver) - }() - - return fd - } - - destination := M.ParseSocksaddrHostPort(address, port) - c.logger.DebugContext(c.ctx, "open TCP connection to ", destination) - conn, err := c.dialer.DialContext(proxyContext, N.NetworkTCP, destination) - if err != nil { - c.logger.ErrorContext(c.ctx, "open TCP connection to ", destination, ": ", err) - return toNetError(err).Code() - } - - if tcpConn, ok := N.CastReader[*net.TCPConn](conn); ok { - fd, duplicateError := dupSocketFD(tcpConn) - if duplicateError == nil { - conn.Close() - return fd - } - } - - fd, pipeConn, err := createSocketPair() - if err != nil { - c.logger.ErrorContext(c.ctx, "socket pair failed: ", err) - conn.Close() - return NetErrorConnectionFailed.Code() - } - - c.proxyWaitGroup.Add(1) - go func() { - defer c.proxyWaitGroup.Done() - bufio.CopyConn(proxyContext, conn, pipeConn) - conn.Close() - pipeConn.Close() - }() - - return fd - }) - - engine.SetUDPDialer(func(address string, port uint16) (fd int, localAddress string, localPort uint16) { - if address == dnsServerAddress.AddrString() && port == dnsServerAddress.Port { - fd, conn, err := createPacketSocketPair(c.testForceUDPLoopback) - if err != nil { - c.logger.ErrorContext(c.ctx, "socket pair failed: ", err) - return NetErrorConnectionFailed.Code(), "", 0 - } - localAddr := M.SocksaddrFromNet(conn.LocalAddr()) - if localAddr.IsValid() { - localAddress = localAddr.AddrString() - localPort = localAddr.Port - } - - go func() { - _ = serveDNSPacketConn(proxyContext, conn, dnsResolver) - }() - - return fd, localAddress, localPort - } - - destination := M.ParseSocksaddrHostPort(address, port) - c.logger.DebugContext(c.ctx, "open UDP connection to ", destination) - conn, err := c.dialer.DialContext(proxyContext, N.NetworkUDP, destination) - if err != nil { - c.logger.ErrorContext(c.ctx, "open UDP connection to ", destination, ": ", err) - return toNetError(err).Code(), "", 0 - } - - localAddr := M.SocksaddrFromNet(conn.LocalAddr()) - if localAddr.IsValid() { - localAddress = localAddr.AddrString() - localPort = localAddr.Port - } - - if udpConn, ok := N.CastReader[*net.UDPConn](conn); ok { - fd, duplicateError := dupSocketFD(udpConn) - if duplicateError == nil { - conn.Close() - return fd, localAddress, localPort - } - } - - fd, pipeConn, err := createPacketSocketPair(c.testForceUDPLoopback) - if err != nil { - c.logger.ErrorContext(c.ctx, "socket pair failed: ", err) - conn.Close() - return NetErrorConnectionFailed.Code(), "", 0 - } - - remoteAddress := M.SocksaddrFromNet(conn.RemoteAddr()) - packetConn := bufio.NewUnbindPacketConn(conn) - pipePacketConn := bufio.NewUnbindPacketConnWithAddr(pipeConn.(net.Conn), remoteAddress) - - c.proxyWaitGroup.Add(1) - go func() { - defer c.proxyWaitGroup.Done() - _ = bufio.CopyPacketConn(proxyContext, packetConn, pipePacketConn) - }() - - return fd, localAddress, localPort - }) - - params := NewEngineParams() - if c.quicEnabled { - params.SetEnableQuic(true) - } else { - params.SetEnableHTTP2(true) - } - - startError = params.SetAsyncDNS(true) - if startError != nil { - return startError - } - startError = params.SetDNSServerOverride([]string{dnsServerAddress.String()}) - if startError != nil { - return startError - } - - startError = params.SetUseDnsHttpsSvcb(c.echEnabled) - if startError != nil { - return startError - } - - if c.quicEnabled { - streamReceiveWindow := c.receiveWindow - if streamReceiveWindow == 0 { - streamReceiveWindow = 8 * 1024 * 1024 - } - sessionReceiveWindow := c.quicSessionReceiveWindow - if sessionReceiveWindow == 0 { - sessionReceiveWindow = 20 * 1024 * 1024 - } - startError = params.SetQUICOptions(string(c.quicCongestionControl), streamReceiveWindow, sessionReceiveWindow) - if startError != nil { - return startError - } - } else { - if c.quicCongestionControl != "" { - startError = params.SetQUICOptions(string(c.quicCongestionControl), 0, 0) - if startError != nil { - return startError - } - } - receiveWindow := c.receiveWindow - if receiveWindow == 0 { - if runtime.GOOS == "ios" { - receiveWindow = 4 * 1024 * 1024 - } else { - receiveWindow = 128 * 1024 * 1024 - } - } - startError = params.SetHTTP2Options(receiveWindow, receiveWindow/2) - if startError != nil { - return startError - } - } - - startError = params.SetSocketPoolOptions(2048, 2048, 2040) - if startError != nil { - return startError - } - - result := engine.StartWithParams(params) - params.Destroy() - if result != ResultSuccess { - startError = E.New("failed to start engine: ", int(result)) - return startError - } - - c.engine = engine - c.streamEngine = engine.StreamEngine() - - c.state.Store(uint32(clientStateRunning)) - close(c.started) - return nil -} - -func (c *NaiveClient) Engine() Engine { - if clientState(c.state.Load()) != clientStateRunning { - return Engine{} - } - return c.engine -} - -func (c *NaiveClient) DialEarly(ctx context.Context, destination M.Socksaddr) (NaiveConn, error) { - state := clientState(c.state.Load()) - switch state { - case clientStateRunning: - case clientStateClosed, clientStateClosing: - return nil, net.ErrClosed - default: - select { - case <-c.started: - if clientState(c.state.Load()) != clientStateRunning { - return nil, net.ErrClosed - } - case <-c.ctx.Done(): - return nil, c.ctx.Err() - } - } - headers := map[string]string{ - "-connect-authority": destination.String(), - "Padding": generatePaddingHeader(), - } - if c.authorization != "" { - headers["proxy-authorization"] = c.authorization - } - if c.quicEnabled { - headers["-force-quic"] = "true" - } - for key, value := range c.extraHeaders { - headers[key] = value - } - - if c.concurrency > 1 { - concurrencyIndex := int(c.counter.Add(1) % uint64(c.concurrency)) - headers["-network-isolation-key"] = F.ToString("https://pool-", concurrencyIndex, ":443") - } - conn := c.streamEngine.CreateConn(ctx, c.logger, true, true) - err := conn.Start("CONNECT", c.serverURL, headers, 0, false) - if err != nil { - return nil, err - } - trackedConn := &trackedNaiveConn{ - NaiveConn: NewNaiveConn(ctx, conn, c.logger), - client: c, - } - c.activeConnections.Add(1) - conn.bindTrackedNaiveConn(trackedConn) - return trackedConn, nil -} - -func (c *NaiveClient) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - if N.NetworkName(network) != N.NetworkTCP { - return nil, os.ErrInvalid - } - conn, err := c.DialEarly(ctx, destination) - if err != nil { - return nil, err - } - err = conn.HandshakeContext(ctx) - if err != nil { - conn.Close() - return nil, err - } - return conn, nil -} - -func (c *NaiveClient) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - return nil, os.ErrInvalid -} - -func (c *NaiveClient) Close() error { - for { - state := clientState(c.state.Load()) - switch state { - case clientStateCreated: - if c.state.CompareAndSwap(uint32(clientStateCreated), uint32(clientStateClosed)) { - close(c.started) - return nil - } - - case clientStateStarting: - select { - case <-c.started: - continue - case <-c.ctx.Done(): - return c.ctx.Err() - } - - case clientStateRunning: - if !c.state.CompareAndSwap(uint32(clientStateRunning), uint32(clientStateClosing)) { - continue - } - return c.doClose() - - case clientStateClosing: - return nil - - case clientStateClosed: - return net.ErrClosed - } - } -} - -func (c *NaiveClient) doClose() error { - if c.proxyCancel != nil { - c.proxyCancel() - } - - c.engine.CloseAllConnections() - c.proxyWaitGroup.Wait() - c.activeConnections.Wait() - c.engine.Shutdown() - c.engine.Destroy() - - c.state.Store(uint32(clientStateClosed)) - return nil -} - -func (c *NaiveClient) getECHConfigList() []byte { - c.echMutex.RLock() - defer c.echMutex.RUnlock() - return c.echConfigList -} - -type trackedNaiveConn struct { - NaiveConn - client *NaiveClient - closeOnce sync.Once -} - -func (c *trackedNaiveConn) release() { - c.closeOnce.Do(func() { - c.client.activeConnections.Done() - }) -} - -func (c *trackedNaiveConn) Close() error { - c.release() - return c.NaiveConn.Close() -} diff --git a/naive_conn.go b/naive_conn.go deleted file mode 100644 index f1ab1b42..00000000 --- a/naive_conn.go +++ /dev/null @@ -1,249 +0,0 @@ -package cronet - -import ( - "context" - "encoding/binary" - "io" - "math/rand" - "net" - - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/baderror" - "github.com/sagernet/sing/common/buf" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/logger" - "github.com/sagernet/sing/common/rw" -) - -const ( - paddingCount = 8 - maxPaddingChunkSize = 65535 -) - -func generatePaddingHeader() string { - paddingLen := rand.Intn(32) + 30 - padding := make([]byte, paddingLen) - bits := rand.Uint64() - for i := 0; i < 16; i++ { - padding[i] = "!#$()+<>?@[]^`{}"[bits&15] - bits >>= 4 - } - for i := 16; i < paddingLen; i++ { - padding[i] = '~' - } - return string(padding) -} - -type paddingConn struct { - readPadding int - writePadding int - readRemaining int - paddingRemaining int -} - -func (p *paddingConn) readWithPadding(reader io.Reader, buffer []byte) (n int, err error) { - if p.readRemaining > 0 { - if len(buffer) > p.readRemaining { - buffer = buffer[:p.readRemaining] - } - n, err = reader.Read(buffer) - if err != nil { - return - } - p.readRemaining -= n - return - } - if p.paddingRemaining > 0 { - err = rw.SkipN(reader, p.paddingRemaining) - if err != nil { - return - } - p.paddingRemaining = 0 - } - if p.readPadding < paddingCount { - var paddingHeader []byte - if len(buffer) >= 3 { - paddingHeader = buffer[:3] - } else { - paddingHeader = make([]byte, 3) - } - _, err = io.ReadFull(reader, paddingHeader) - if err != nil { - return - } - originalDataSize := int(binary.BigEndian.Uint16(paddingHeader[:2])) - paddingSize := int(paddingHeader[2]) - if len(buffer) > originalDataSize { - buffer = buffer[:originalDataSize] - } - n, err = reader.Read(buffer) - if err != nil { - return - } - p.readPadding++ - p.readRemaining = originalDataSize - n - p.paddingRemaining = paddingSize - return - } - return reader.Read(buffer) -} - -func (p *paddingConn) writeWithPadding(writer io.Writer, data []byte) (n int, err error) { - if p.writePadding < paddingCount { - paddingSize := rand.Intn(256) - buffer := buf.NewSize(3 + len(data) + paddingSize) - defer buffer.Release() - header := buffer.Extend(3) - binary.BigEndian.PutUint16(header, uint16(len(data))) - header[2] = byte(paddingSize) - common.Must1(buffer.Write(data)) - if paddingSize > 0 { - common.Must(buffer.WriteZeroN(paddingSize)) - } - _, err = writer.Write(buffer.Bytes()) - if err == nil { - n = len(data) - } - p.writePadding++ - return - } - return writer.Write(data) -} - -func (p *paddingConn) writeBufferWithPadding(writer io.Writer, buffer *buf.Buffer) error { - if p.writePadding < paddingCount { - bufferLen := buffer.Len() - if bufferLen > maxPaddingChunkSize { - _, err := p.writeChunked(writer, buffer.Bytes()) - return err - } - paddingSize := rand.Intn(256) - header := buffer.ExtendHeader(3) - binary.BigEndian.PutUint16(header, uint16(bufferLen)) - header[2] = byte(paddingSize) - if paddingSize > 0 { - common.Must(buffer.WriteZeroN(paddingSize)) - } - p.writePadding++ - } - return common.Error(writer.Write(buffer.Bytes())) -} - -func (p *paddingConn) writeChunked(writer io.Writer, data []byte) (n int, err error) { - for len(data) > 0 { - var chunk []byte - if len(data) > maxPaddingChunkSize { - chunk = data[:maxPaddingChunkSize] - data = data[maxPaddingChunkSize:] - } else { - chunk = data - data = nil - } - var written int - written, err = p.writeWithPadding(writer, chunk) - n += written - if err != nil { - return - } - } - return -} - -func (p *paddingConn) frontHeadroom() int { - if p.writePadding < paddingCount { - return 3 - } - return 0 -} - -func (p *paddingConn) rearHeadroom() int { - if p.writePadding < paddingCount { - return 255 - } - return 0 -} - -func (p *paddingConn) writerMTU() int { - if p.writePadding < paddingCount { - return maxPaddingChunkSize - } - return 0 -} - -func (p *paddingConn) readerReplaceable() bool { - return p.readPadding == paddingCount -} - -func (p *paddingConn) writerReplaceable() bool { - return p.writePadding == paddingCount -} - -type NaiveConn interface { - net.Conn - Handshake() error - HandshakeContext(ctx context.Context) error -} -type naiveConn struct { - net.Conn - ctx context.Context - conn *BidirectionalConn - logger logger.ContextLogger - paddingConn -} - -func NewNaiveConn(ctx context.Context, conn *BidirectionalConn, l logger.ContextLogger) NaiveConn { - return &naiveConn{Conn: conn, ctx: ctx, conn: conn, logger: l} -} - -func (c *naiveConn) Handshake() error { - headers, err := c.conn.WaitForHeaders() - if err != nil { - c.logger.WarnContext(c.ctx, "handshake failed: ", err) - return err - } - if headers[":status"] != "200" { - err = E.New("unexpected response status: ", headers[":status"]) - c.logger.WarnContext(c.ctx, "handshake failed: ", err) - return err - } - c.logger.DebugContext(c.ctx, "handshake succeeded") - return nil -} - -func (c *naiveConn) HandshakeContext(ctx context.Context) error { - headers, err := c.conn.WaitForHeadersContext(ctx) - if err != nil { - c.logger.WarnContext(c.ctx, "handshake failed: ", err) - return err - } - if headers[":status"] != "200" { - err = E.New("unexpected response status: ", headers[":status"]) - c.logger.WarnContext(c.ctx, "handshake failed: ", err) - return err - } - c.logger.DebugContext(c.ctx, "handshake succeeded") - return nil -} - -func (c *naiveConn) Read(p []byte) (n int, err error) { - n, err = c.readWithPadding(c.Conn, p) - return n, baderror.WrapH2(err) -} - -func (c *naiveConn) Write(p []byte) (n int, err error) { - n, err = c.writeChunked(c.Conn, p) - return n, baderror.WrapH2(err) -} - -func (c *naiveConn) WriteBuffer(buffer *buf.Buffer) error { - defer buffer.Release() - err := c.writeBufferWithPadding(c.Conn, buffer) - return baderror.WrapH2(err) -} - -func (c *naiveConn) FrontHeadroom() int { return c.frontHeadroom() } -func (c *naiveConn) RearHeadroom() int { return c.rearHeadroom() } -func (c *naiveConn) WriterMTU() int { return c.writerMTU() } -func (c *naiveConn) Upstream() any { return c.Conn } -func (c *naiveConn) ReaderReplaceable() bool { return c.readerReplaceable() } -func (c *naiveConn) WriterReplaceable() bool { return c.writerReplaceable() } diff --git a/naive_dns.go b/naive_dns.go deleted file mode 100644 index 15dfc5ba..00000000 --- a/naive_dns.go +++ /dev/null @@ -1,486 +0,0 @@ -package cronet - -// DNS Hijacking Logic for ServerName Resolution -// -// Priority: serverName (SNI) = ServerName config > ServerAddress -// -// A/AAAA queries for serverName: -// - If ServerAddress is a domain (different from serverName): redirect query to ServerAddress -// - If ServerAddress is an IP: return synthetic response (mismatched type returns empty SUCCESS) -// -// HTTPS queries for serverName (ECH): -// - If fixed ECHConfigList exists: return synthetic response immediately -// - Otherwise: forward query (priority: ECHQueryServerName > serverName) -// - Always filter ipv4hint/ipv6hint to prevent incorrect IP usage - -import ( - "context" - "encoding/binary" - "io" - "net" - "net/netip" - "strconv" - "strings" - "time" - - "github.com/sagernet/sing/common/bufio" - "github.com/sagernet/sing/common/logger" - M "github.com/sagernet/sing/common/metadata" - - mDNS "github.com/miekg/dns" -) - -// DNSResolverFunc resolves a DNS request into a DNS response. -// -// The resolver is used by NaiveClient's optional in-process DNS server. The -// returned message should be a response to the request; the implementation -// will normalize the ID and question section as needed. -type DNSResolverFunc func(ctx context.Context, request *mDNS.Msg) (response *mDNS.Msg) - -const chromiumDNSUDPMaxSize = 512 - -func serveDNSPacketConn(ctx context.Context, conn net.PacketConn, resolver DNSResolverFunc) error { - defer conn.Close() - - if ctx.Done() != nil { - done := make(chan struct{}) - defer close(done) - go func() { - select { - case <-ctx.Done(): - conn.Close() - case <-done: - } - }() - } - - buffer := make([]byte, chromiumDNSUDPMaxSize) - for { - conn.SetReadDeadline(time.Now().Add(15 * time.Second)) - - n, remoteAddress, err := conn.ReadFrom(buffer) - if err != nil { - return err - } - - var request mDNS.Msg - err = request.Unpack(buffer[:n]) - if err != nil { - continue - } - - response := resolver(ctx, &request) - response = normalizeDNSResponse(&request, response) - - packed, err := response.Pack() - if err != nil { - continue - } - if len(packed) > chromiumDNSUDPMaxSize { - truncated := truncatedDNSResponse(&request, response.Rcode) - packed, err = truncated.Pack() - if err != nil { - continue - } - } - - var writeConn net.Conn - if c, ok := conn.(net.Conn); ok { - writeConn = c - } else { - writeConn = bufio.NewBindPacketConn(conn, remoteAddress) - } - _, _ = writeConn.Write(packed) - } -} - -func serveDNSStreamConn(ctx context.Context, conn net.Conn, resolver DNSResolverFunc) error { - defer conn.Close() - - if ctx.Done() != nil { - done := make(chan struct{}) - defer close(done) - go func() { - select { - case <-ctx.Done(): - conn.Close() - case <-done: - } - }() - } - - for { - conn.SetReadDeadline(time.Now().Add(15 * time.Second)) - - var queryLength uint16 - err := binary.Read(conn, binary.BigEndian, &queryLength) - if err != nil { - return err - } - if queryLength == 0 { - return nil - } - - query := make([]byte, int(queryLength)) - _, err = io.ReadFull(conn, query) - if err != nil { - return err - } - - var request mDNS.Msg - err = request.Unpack(query) - if err != nil { - continue - } - - response := resolver(ctx, &request) - response = normalizeDNSResponse(&request, response) - - packed, err := response.Pack() - if err != nil { - continue - } - - _ = conn.SetWriteDeadline(time.Now().Add(30 * time.Second)) - var lengthPrefix [2]byte - binary.BigEndian.PutUint16(lengthPrefix[:], uint16(len(packed))) - if _, err := conn.Write(lengthPrefix[:]); err != nil { - return err - } - if _, err := conn.Write(packed); err != nil { - return err - } - } -} - -func normalizeDNSResponse(request *mDNS.Msg, response *mDNS.Msg) *mDNS.Msg { - if response == nil { - fallback := new(mDNS.Msg) - fallback.SetReply(request) - fallback.Rcode = mDNS.RcodeServerFailure - return fallback - } - - response.Id = request.Id - response.Response = true - if len(response.Question) == 0 { - response.Question = request.Question - } - return response -} - -func truncatedDNSResponse(request *mDNS.Msg, rcode int) *mDNS.Msg { - response := new(mDNS.Msg) - response.SetReply(request) - response.Truncated = true - response.Rcode = rcode - return response -} - -func wrapDNSResolverWithECH( - resolver DNSResolverFunc, - serverName string, - echQueryServerName string, - echConfigGetter func() []byte, - quicEnabled bool, - l logger.ContextLogger, -) DNSResolverFunc { - return func(ctx context.Context, request *mDNS.Msg) *mDNS.Msg { - if len(request.Question) > 0 { - question := request.Question[0] - if question.Qtype == mDNS.TypeHTTPS && matchesServerName(question.Name, serverName) { - echConfig := echConfigGetter() - if len(echConfig) > 0 { - var alpn []string - if quicEnabled { - alpn = []string{"h3"} - } else { - alpn = []string{"h2"} - } - l.DebugContext(ctx, "ech config injected, length: ", len(echConfig)) - return injectECHConfig(request, nil, echConfig, alpn) - } - - var response *mDNS.Msg - if echQueryServerName != serverName { - redirectedRequest := request.Copy() - redirectedRequest.Question[0].Name = rewriteHTTPSQueryName(question.Name, serverName, echQueryServerName) - response = resolver(ctx, redirectedRequest) - if response != nil { - response = response.Copy() - response.Question = request.Question - rewriteHTTPSAnswerNames(response, echQueryServerName, serverName) - } - } else { - response = resolver(ctx, request) - if response != nil { - response = response.Copy() - } - } - - filterIPHintsFromHTTPS(response) - return response - } - } - return resolver(ctx, request) - } -} - -func rewriteHTTPSQueryName(queryName, fromServer, toServer string) string { - queryName = strings.TrimSuffix(queryName, ".") - fromServer = strings.TrimSuffix(fromServer, ".") - toServer = strings.TrimSuffix(toServer, ".") - - if strings.HasPrefix(queryName, "_") { - parts := strings.SplitN(queryName, "._https.", 2) - if len(parts) == 2 && strings.EqualFold(parts[1], fromServer) { - return parts[0] + "._https." + toServer + "." - } - } - - if strings.EqualFold(queryName, fromServer) { - return toServer + "." - } - - return queryName + "." -} - -func rewriteHTTPSAnswerNames(response *mDNS.Msg, fromServer, toServer string) { - fromServer = strings.TrimSuffix(fromServer, ".") - toServer = strings.TrimSuffix(toServer, ".") - - for _, rr := range response.Answer { - if https, ok := rr.(*mDNS.HTTPS); ok { - hdrName := strings.TrimSuffix(https.Hdr.Name, ".") - if strings.EqualFold(hdrName, fromServer) { - https.Hdr.Name = toServer + "." - } else if strings.HasPrefix(hdrName, "_") { - parts := strings.SplitN(hdrName, "._https.", 2) - if len(parts) == 2 && strings.EqualFold(parts[1], fromServer) { - https.Hdr.Name = parts[0] + "._https." + toServer + "." - } - } - targetName := strings.TrimSuffix(https.Target, ".") - if strings.EqualFold(targetName, fromServer) { - https.Target = toServer + "." - } - } - } -} - -func matchesServerName(queryName, serverName string) bool { - queryName = strings.TrimSuffix(queryName, ".") - serverName = strings.TrimSuffix(serverName, ".") - - if strings.EqualFold(queryName, serverName) { - return true - } - - if strings.HasPrefix(queryName, "_") { - parts := strings.SplitN(queryName, "._https.", 2) - if len(parts) == 2 { - return strings.EqualFold(parts[1], serverName) - } - } - - return false -} - -func injectECHConfig(request *mDNS.Msg, response *mDNS.Msg, echConfig []byte, alpn []string) *mDNS.Msg { - if response == nil { - response = new(mDNS.Msg) - response.SetReply(request) - } - - var servicePort uint16 - var hasServicePort bool - if len(request.Question) > 0 { - servicePort, hasServicePort = parseHTTPSServicePort(request.Question[0].Name) - } - - hasHTTPS := false - for _, rr := range response.Answer { - if https, ok := rr.(*mDNS.HTTPS); ok { - hasHTTPS = true - updateECHInSVCB(&https.SVCB, echConfig) - if hasServicePort { - updatePortInSVCB(&https.SVCB, servicePort) - } - } - } - - if !hasHTTPS && len(request.Question) > 0 { - queryName := request.Question[0].Name - targetName := queryName - if strings.HasPrefix(queryName, "_") { - parts := strings.SplitN(queryName, "._https.", 2) - if len(parts) == 2 { - targetName = parts[1] - } - } - - https := &mDNS.HTTPS{ - SVCB: mDNS.SVCB{ - Hdr: mDNS.RR_Header{ - Name: queryName, - Rrtype: mDNS.TypeHTTPS, - Class: mDNS.ClassINET, - Ttl: 300, - }, - Priority: 1, - Target: targetName, - }, - } - if hasServicePort { - https.Value = append(https.Value, &mDNS.SVCBPort{Port: servicePort}) - } - https.Value = append(https.Value, &mDNS.SVCBAlpn{Alpn: alpn}) - https.Value = append(https.Value, &mDNS.SVCBECHConfig{ECH: echConfig}) - response.Answer = append(response.Answer, https) - } - - return response -} - -func updateECHInSVCB(svcb *mDNS.SVCB, echConfig []byte) { - for i, kv := range svcb.Value { - if _, ok := kv.(*mDNS.SVCBECHConfig); ok { - svcb.Value[i] = &mDNS.SVCBECHConfig{ECH: echConfig} - return - } - } - svcb.Value = append(svcb.Value, &mDNS.SVCBECHConfig{ECH: echConfig}) -} - -func updatePortInSVCB(svcb *mDNS.SVCB, port uint16) { - for i, kv := range svcb.Value { - if _, ok := kv.(*mDNS.SVCBPort); ok { - svcb.Value[i] = &mDNS.SVCBPort{Port: port} - return - } - } - svcb.Value = append(svcb.Value, &mDNS.SVCBPort{Port: port}) -} - -func parseHTTPSServicePort(queryName string) (uint16, bool) { - trimmedName := strings.TrimSuffix(queryName, ".") - if !strings.HasPrefix(trimmedName, "_") { - return 0, false - } - parts := strings.SplitN(trimmedName, "._https.", 2) - if len(parts) != 2 { - return 0, false - } - portValue, err := strconv.Atoi(strings.TrimPrefix(parts[0], "_")) - if err != nil || portValue <= 0 || portValue > 65535 { - return 0, false - } - return uint16(portValue), true -} - -func filterIPHintsFromHTTPS(response *mDNS.Msg) { - if response == nil { - return - } - for _, rr := range response.Answer { - if https, ok := rr.(*mDNS.HTTPS); ok { - filterIPHintsFromSVCB(&https.SVCB) - } - } -} - -func filterIPHintsFromSVCB(svcb *mDNS.SVCB) { - filtered := svcb.Value[:0] - for _, kv := range svcb.Value { - switch kv.(type) { - case *mDNS.SVCBIPv4Hint, *mDNS.SVCBIPv6Hint: - default: - filtered = append(filtered, kv) - } - } - svcb.Value = filtered -} - -func wrapDNSResolverForServerRedirect( - resolver DNSResolverFunc, - serverName string, - serverAddress M.Socksaddr, -) DNSResolverFunc { - return func(ctx context.Context, request *mDNS.Msg) *mDNS.Msg { - if len(request.Question) == 0 { - return resolver(ctx, request) - } - - question := request.Question[0] - if question.Qtype != mDNS.TypeA && question.Qtype != mDNS.TypeAAAA { - return resolver(ctx, request) - } - - queryName := strings.TrimSuffix(question.Name, ".") - if !strings.EqualFold(queryName, serverName) { - return resolver(ctx, request) - } - - if serverAddress.IsIP() { - return synthesizeAddressResponse(request, serverAddress.Addr) - } - - redirectedRequest := request.Copy() - redirectedRequest.Question[0].Name = mDNS.Fqdn(serverAddress.AddrString()) - - response := resolver(ctx, redirectedRequest) - if response != nil { - response = response.Copy() - response.Question = request.Question - rewriteAddressAnswerNames(response, serverAddress.AddrString(), serverName) - } - return response - } -} - -func rewriteAddressAnswerNames(response *mDNS.Msg, fromDomain, toDomain string) { - fromDomain = strings.TrimSuffix(fromDomain, ".") - toDomain = strings.TrimSuffix(toDomain, ".") - toFQDN := toDomain + "." - - for _, rr := range response.Answer { - hdrName := strings.TrimSuffix(rr.Header().Name, ".") - if strings.EqualFold(hdrName, fromDomain) { - rr.Header().Name = toFQDN - } - } -} - -func synthesizeAddressResponse(request *mDNS.Msg, address netip.Addr) *mDNS.Msg { - response := new(mDNS.Msg) - response.SetReply(request) - - if len(request.Question) == 0 { - return response - } - - question := request.Question[0] - if question.Qtype == mDNS.TypeA && address.Is4() { - response.Answer = append(response.Answer, &mDNS.A{ - Hdr: mDNS.RR_Header{ - Name: question.Name, - Rrtype: mDNS.TypeA, - Class: mDNS.ClassINET, - Ttl: 300, - }, - A: address.AsSlice(), - }) - } else if question.Qtype == mDNS.TypeAAAA && address.Is6() { - response.Answer = append(response.Answer, &mDNS.AAAA{ - Hdr: mDNS.RR_Header{ - Name: question.Name, - Rrtype: mDNS.TypeAAAA, - Class: mDNS.ClassINET, - Ttl: 300, - }, - AAAA: address.AsSlice(), - }) - } - - return response -} diff --git a/naive_dns_unix.go b/naive_dns_unix.go deleted file mode 100644 index d5821087..00000000 --- a/naive_dns_unix.go +++ /dev/null @@ -1,122 +0,0 @@ -//go:build unix - -package cronet - -import ( - "net" - "os" - "syscall" - - E "github.com/sagernet/sing/common/exceptions" -) - -func createPacketSocketPair(forceUDPLoopback bool) (cronetFD int, proxyConn net.PacketConn, err error) { - if forceUDPLoopback { - return createUDPLoopbackPair() - } - - fds, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_DGRAM, 0) - if err != nil { - return -1, nil, E.Cause(err, "create dgram socketpair") - } - - syscall.CloseOnExec(fds[0]) - - file := os.NewFile(uintptr(fds[1]), "cronet-dgram-socketpair") - conn, err := net.FilePacketConn(file) - _ = file.Close() - if err != nil { - syscall.Close(fds[0]) - return -1, nil, E.Cause(err, "create packet conn from socketpair") - } - - return fds[0], conn, nil -} - -func createUDPLoopbackPair() (cronetFD int, proxyConn net.PacketConn, err error) { - // Create two UDP sockets and connect them to each other. - // Both sockets must be connected for bidirectional communication. - - // Step 1: Create proxyConn socket (unconnected initially to get a port) - proxyAddress, err := net.ResolveUDPAddr("udp", "127.0.0.1:0") - if err != nil { - return -1, nil, err - } - proxyUDPConn, err := net.ListenUDP("udp", proxyAddress) - if err != nil { - return -1, nil, err - } - proxyLocalAddress := proxyUDPConn.LocalAddr().(*net.UDPAddr) - - // Step 2: Create cronetConn socket connected to proxyConn - cronetAddress, err := net.ResolveUDPAddr("udp", "127.0.0.1:0") - if err != nil { - proxyUDPConn.Close() - return -1, nil, err - } - cronetConn, err := net.DialUDP("udp", cronetAddress, proxyLocalAddress) - if err != nil { - proxyUDPConn.Close() - return -1, nil, err - } - cronetLocalAddress := cronetConn.LocalAddr().(*net.UDPAddr) - - // Step 3: Connect proxyConn to cronetConn's address using syscall - proxyRawConn, err := proxyUDPConn.SyscallConn() - if err != nil { - cronetConn.Close() - proxyUDPConn.Close() - return -1, nil, err - } - - var connectError error - err = proxyRawConn.Control(func(fd uintptr) { - sockaddr := &syscall.SockaddrInet4{Port: cronetLocalAddress.Port} - copy(sockaddr.Addr[:], cronetLocalAddress.IP.To4()) - connectError = syscall.Connect(int(fd), sockaddr) - }) - if err != nil { - cronetConn.Close() - proxyUDPConn.Close() - return -1, nil, err - } - if connectError != nil { - cronetConn.Close() - proxyUDPConn.Close() - return -1, nil, connectError - } - - // Step 4: Duplicate cronetConn's fd for Chromium - cronetRawConn, err := cronetConn.SyscallConn() - if err != nil { - cronetConn.Close() - proxyUDPConn.Close() - return -1, nil, err - } - - var cronetFDValue int - var duplicateError error - err = cronetRawConn.Control(func(fd uintptr) { - dupFD, controlErr := syscall.Dup(int(fd)) - if controlErr != nil { - duplicateError = controlErr - return - } - syscall.CloseOnExec(dupFD) - cronetFDValue = dupFD - }) - if err != nil { - cronetConn.Close() - proxyUDPConn.Close() - return -1, nil, err - } - if duplicateError != nil { - cronetConn.Close() - proxyUDPConn.Close() - return -1, nil, duplicateError - } - - cronetConn.Close() - - return cronetFDValue, proxyUDPConn, nil -} diff --git a/naive_dns_unix_test.go b/naive_dns_unix_test.go deleted file mode 100644 index 2cc0f5d7..00000000 --- a/naive_dns_unix_test.go +++ /dev/null @@ -1,204 +0,0 @@ -//go:build unix - -package cronet - -import ( - "syscall" - "testing" -) - -func TestCreatePacketSocketPair(t *testing.T) { - fd, conn, err := createPacketSocketPair(false) - if err != nil { - t.Fatalf("createPacketSocketPair failed: %v", err) - } - defer syscall.Close(fd) - defer conn.Close() - - if fd <= 0 { - t.Errorf("expected valid fd, got %d", fd) - } -} - -func TestCreatePacketSocketPair_BidirectionalCommunication(t *testing.T) { - fd, conn, err := createPacketSocketPair(false) - if err != nil { - t.Fatalf("createPacketSocketPair failed: %v", err) - } - defer syscall.Close(fd) - defer conn.Close() - - // fd → conn (datagram boundary preserved) - testData := []byte("hello from fd") - _, err = syscall.Write(fd, testData) - if err != nil { - t.Fatalf("write to fd failed: %v", err) - } - - buf := make([]byte, 1024) - n, _, err := conn.ReadFrom(buf) - if err != nil { - t.Fatalf("read from conn failed: %v", err) - } - if string(buf[:n]) != string(testData) { - t.Errorf("expected %q, got %q", testData, buf[:n]) - } - - // conn → fd: For Unix socketpairs, use Write() via net.Conn interface - // (same as serveDNSPacketConn does when remoteAddress is nil) - streamConn, ok := conn.(interface{ Write([]byte) (int, error) }) - if !ok { - t.Fatal("PacketConn should implement Write for socketpair") - } - testData2 := []byte("hello from conn") - _, err = streamConn.Write(testData2) - if err != nil { - t.Fatalf("write to conn failed: %v", err) - } - - n, err = syscall.Read(fd, buf) - if err != nil { - t.Fatalf("read from fd failed: %v", err) - } - if string(buf[:n]) != string(testData2) { - t.Errorf("expected %q, got %q", testData2, buf[:n]) - } -} - -func TestCreatePacketSocketPair_MessageBoundary(t *testing.T) { - fd, conn, err := createPacketSocketPair(false) - if err != nil { - t.Fatalf("createPacketSocketPair failed: %v", err) - } - defer syscall.Close(fd) - defer conn.Close() - - // Send multiple different-sized messages - messages := [][]byte{ - []byte("short"), - make([]byte, 512), - make([]byte, 1400), - } - // Fill with recognizable patterns - for i := range messages[1] { - messages[1][i] = byte(i % 256) - } - for i := range messages[2] { - messages[2][i] = byte((i * 7) % 256) - } - - for _, msg := range messages { - _, err = syscall.Write(fd, msg) - if err != nil { - t.Fatalf("write failed: %v", err) - } - } - - // Verify each message boundary is preserved - for i, expected := range messages { - buf := make([]byte, 2048) - n, _, err := conn.ReadFrom(buf) - if err != nil { - t.Fatalf("read %d failed: %v", i, err) - } - if n != len(expected) { - t.Errorf("message %d: expected length %d, got %d", i, len(expected), n) - } - if string(buf[:n]) != string(expected) { - t.Errorf("message %d: content mismatch", i) - } - } -} - -func TestCreateUDPLoopbackPair(t *testing.T) { - fd, conn, err := createUDPLoopbackPair() - if err != nil { - t.Fatalf("createUDPLoopbackPair failed: %v", err) - } - defer syscall.Close(fd) - defer conn.Close() - - if fd <= 0 { - t.Errorf("expected valid fd, got %d", fd) - } -} - -func TestCreateUDPLoopbackPair_BidirectionalCommunication(t *testing.T) { - fd, conn, err := createUDPLoopbackPair() - if err != nil { - t.Fatalf("createUDPLoopbackPair failed: %v", err) - } - defer syscall.Close(fd) - defer conn.Close() - - err = syscall.SetNonblock(fd, false) - if err != nil { - t.Fatalf("failed to set blocking mode: %v", err) - } - testData := []byte("hello from fd via UDP loopback") - _, err = syscall.Write(fd, testData) - if err != nil { - t.Fatalf("write to fd failed: %v", err) - } - - buf := make([]byte, 1024) - n, remoteAddr, err := conn.ReadFrom(buf) - if err != nil { - t.Fatalf("read from conn failed: %v", err) - } - if string(buf[:n]) != string(testData) { - t.Errorf("expected %q, got %q", testData, buf[:n]) - } - if remoteAddr == nil { - t.Error("expected non-nil remote address for UDP loopback") - } - - // conn → fd: Use Write (both sockets are connected to each other) - streamConn, ok := conn.(interface{ Write([]byte) (int, error) }) - if !ok { - t.Fatal("connected UDP socket should implement Write") - } - testData2 := []byte("hello from conn via UDP loopback") - _, err = streamConn.Write(testData2) - if err != nil { - t.Fatalf("write to conn failed: %v", err) - } - - n, err = syscall.Read(fd, buf) - if err != nil { - t.Fatalf("read from fd failed: %v", err) - } - if string(buf[:n]) != string(testData2) { - t.Errorf("expected %q, got %q", testData2, buf[:n]) - } -} - -func TestCreatePacketSocketPair_ForceUDPLoopback(t *testing.T) { - // Test that forceUDPLoopback=true returns UDP loopback pair - fd, conn, err := createPacketSocketPair(true) - if err != nil { - t.Fatalf("createPacketSocketPair(true) failed: %v", err) - } - defer syscall.Close(fd) - defer conn.Close() - - // Verify it's a UDP connection by checking that ReadFrom returns a remote address - testData := []byte("test UDP loopback via forceUDPLoopback") - _, err = syscall.Write(fd, testData) - if err != nil { - t.Fatalf("write to fd failed: %v", err) - } - - buf := make([]byte, 1024) - n, remoteAddr, err := conn.ReadFrom(buf) - if err != nil { - t.Fatalf("read from conn failed: %v", err) - } - if string(buf[:n]) != string(testData) { - t.Errorf("expected %q, got %q", testData, buf[:n]) - } - // UDP loopback returns non-nil remote address, Unix socketpair returns nil - if remoteAddr == nil { - t.Error("forceUDPLoopback=true should return UDP loopback (non-nil remote address)") - } -} diff --git a/naive_dns_windows.go b/naive_dns_windows.go deleted file mode 100644 index 047ba6b1..00000000 --- a/naive_dns_windows.go +++ /dev/null @@ -1,193 +0,0 @@ -//go:build windows - -package cronet - -import ( - "encoding/binary" - "errors" - "io" - "net" - "sync" - "time" - - "golang.org/x/sys/windows" -) - -const maxFramedPacketSize = 65535 - -type framedPacketConn struct { - conn net.Conn - readMutex sync.Mutex - writeMutex sync.Mutex -} - -var _ net.PacketConn = (*framedPacketConn)(nil) - -func newFramedPacketConn(conn net.Conn) *framedPacketConn { - return &framedPacketConn{conn: conn} -} - -func (c *framedPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { - c.readMutex.Lock() - defer c.readMutex.Unlock() - - var lengthBuf [2]byte - _, err = io.ReadFull(c.conn, lengthBuf[:]) - if err != nil { - return 0, nil, err - } - length := binary.BigEndian.Uint16(lengthBuf[:]) - - if int(length) > len(p) { - return 0, nil, errors.New("buffer too small for packet") - } - - n, err = io.ReadFull(c.conn, p[:length]) - return n, nil, err -} - -func (c *framedPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { - if len(p) > maxFramedPacketSize { - return 0, errors.New("packet too large") - } - - // Write length prefix and payload as separate writes to avoid - // Windows AF_UNIX SOCK_STREAM message-mode behavior where a single - // large write becomes an indivisible "message" that cannot be read - // in smaller chunks (WSARecv returns WSAEMSGSIZE). - c.writeMutex.Lock() - defer c.writeMutex.Unlock() - - var lengthBuf [2]byte - binary.BigEndian.PutUint16(lengthBuf[:], uint16(len(p))) - _, err = c.conn.Write(lengthBuf[:]) - if err != nil { - return 0, err - } - _, err = c.conn.Write(p) - if err != nil { - return 0, err - } - return len(p), nil -} - -func (c *framedPacketConn) Close() error { - return c.conn.Close() -} - -func (c *framedPacketConn) LocalAddr() net.Addr { - return c.conn.LocalAddr() -} - -func (c *framedPacketConn) SetDeadline(t time.Time) error { - return c.conn.SetDeadline(t) -} - -func (c *framedPacketConn) SetReadDeadline(t time.Time) error { - return c.conn.SetReadDeadline(t) -} - -func (c *framedPacketConn) SetWriteDeadline(t time.Time) error { - return c.conn.SetWriteDeadline(t) -} - -func (c *framedPacketConn) Read(p []byte) (n int, err error) { - n, _, err = c.ReadFrom(p) - return -} - -func (c *framedPacketConn) Write(p []byte) (n int, err error) { - return c.WriteTo(p, nil) -} - -func (c *framedPacketConn) RemoteAddr() net.Addr { - return nil -} - -func createUDPLoopbackPair() (cronetFD int, proxyConn net.PacketConn, err error) { - proxyAddress, err := net.ResolveUDPAddr("udp", "127.0.0.1:0") - if err != nil { - return -1, nil, err - } - proxyUDPConn, err := net.ListenUDP("udp", proxyAddress) - if err != nil { - return -1, nil, err - } - proxyLocalAddress := proxyUDPConn.LocalAddr().(*net.UDPAddr) - - cronetAddress, err := net.ResolveUDPAddr("udp", "127.0.0.1:0") - if err != nil { - proxyUDPConn.Close() - return -1, nil, err - } - cronetConn, err := net.DialUDP("udp", cronetAddress, proxyLocalAddress) - if err != nil { - proxyUDPConn.Close() - return -1, nil, err - } - cronetLocalAddress := cronetConn.LocalAddr().(*net.UDPAddr) - - proxyRawConn, err := proxyUDPConn.SyscallConn() - if err != nil { - cronetConn.Close() - proxyUDPConn.Close() - return -1, nil, err - } - - var connectError error - err = proxyRawConn.Control(func(fd uintptr) { - sockaddr := &windows.SockaddrInet4{Port: cronetLocalAddress.Port} - copy(sockaddr.Addr[:], cronetLocalAddress.IP.To4()) - connectError = windows.Connect(windows.Handle(fd), sockaddr) - }) - if err != nil { - cronetConn.Close() - proxyUDPConn.Close() - return -1, nil, err - } - if connectError != nil { - cronetConn.Close() - proxyUDPConn.Close() - return -1, nil, connectError - } - - cronetRawConn, err := cronetConn.SyscallConn() - if err != nil { - cronetConn.Close() - proxyUDPConn.Close() - return -1, nil, err - } - - var cronetHandle windows.Handle - var duplicateError error - err = cronetRawConn.Control(func(fd uintptr) { - currentProcess, processError := windows.GetCurrentProcess() - if processError != nil { - duplicateError = processError - return - } - duplicateError = windows.DuplicateHandle( - currentProcess, - windows.Handle(fd), - currentProcess, - &cronetHandle, - 0, - false, - windows.DUPLICATE_SAME_ACCESS, - ) - }) - if err != nil { - cronetConn.Close() - proxyUDPConn.Close() - return -1, nil, err - } - if duplicateError != nil { - cronetConn.Close() - proxyUDPConn.Close() - return -1, nil, duplicateError - } - - cronetConn.Close() - - return int(cronetHandle), proxyUDPConn, nil -} diff --git a/naive_dns_windows_go125.go b/naive_dns_windows_go125.go deleted file mode 100644 index 3840dcdf..00000000 --- a/naive_dns_windows_go125.go +++ /dev/null @@ -1,26 +0,0 @@ -//go:build windows && go1.25 - -package cronet - -import "net" - -func createPacketSocketPair(forceUDPLoopback bool) (cronetFD int, proxyConn net.PacketConn, err error) { - // AF_UNIX SOCK_STREAM on Windows is message-mode: each send() is an - // atomic message and recv() with a buffer smaller than the message - // returns WSAEMSGSIZE. This breaks the framed read protocol where - // C++ reads a 2-byte length header first. Use UDP loopback instead. - // - // if forceUDPLoopback { - // return createUDPLoopbackPair() - // } - // - // // Try Unix socket pair first (Go 1.25+ has FileConn support) - // cronetFD, streamConn, err := createUnixSocketPair() - // if err != nil { - // return createUDPLoopbackPair() - // } - // - // return cronetFD, newFramedPacketConn(streamConn), nil - - return createUDPLoopbackPair() -} diff --git a/naive_dns_windows_legacy.go b/naive_dns_windows_legacy.go deleted file mode 100644 index f967c4aa..00000000 --- a/naive_dns_windows_legacy.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build windows && !go1.25 - -package cronet - -import "net" - -func createPacketSocketPair(forceUDPLoopback bool) (cronetFD int, proxyConn net.PacketConn, err error) { - return createUDPLoopbackPair() -} diff --git a/naive_dns_windows_test.go b/naive_dns_windows_test.go deleted file mode 100644 index 0d0b3723..00000000 --- a/naive_dns_windows_test.go +++ /dev/null @@ -1,173 +0,0 @@ -//go:build windows - -package cronet - -import ( - "bytes" - "encoding/binary" - "testing" - - "golang.org/x/sys/windows" -) - -func TestCreatePacketSocketPair(t *testing.T) { - fd, conn, err := createPacketSocketPair(false) - if err != nil { - t.Fatalf("createPacketSocketPair failed: %v", err) - } - defer windows.Closesocket(windows.Handle(fd)) - defer conn.Close() - - if fd <= 0 { - t.Errorf("expected valid fd, got %d", fd) - } -} - -func TestFramedPacketConn_ReadWrite(t *testing.T) { - fd, conn, err := createPacketSocketPair(false) - if err != nil { - t.Fatalf("createPacketSocketPair failed: %v", err) - } - defer windows.Closesocket(windows.Handle(fd)) - defer conn.Close() - - testData := []byte("framed message test") - _, err = conn.WriteTo(testData, nil) - if err != nil { - t.Fatalf("WriteTo failed: %v", err) - } - - buf := make([]byte, 1024) - var flags uint32 - var bytesReceived uint32 - wsaBuf := windows.WSABuf{Len: uint32(len(buf)), Buf: &buf[0]} - err = windows.WSARecv(windows.Handle(fd), &wsaBuf, 1, &bytesReceived, &flags, nil, nil) - if err != nil { - t.Fatalf("WSARecv failed: %v", err) - } - - if bytesReceived < 2+uint32(len(testData)) { - t.Fatalf("expected at least %d bytes, got %d", 2+len(testData), bytesReceived) - } - - length := binary.BigEndian.Uint16(buf[:2]) - if length != uint16(len(testData)) { - t.Errorf("expected length %d, got %d", len(testData), length) - } - if !bytes.Equal(buf[2:2+length], testData) { - t.Errorf("expected %q, got %q", testData, buf[2:2+length]) - } -} - -func TestFramedPacketConn_BidirectionalCommunication(t *testing.T) { - fd, conn, err := createPacketSocketPair(false) - if err != nil { - t.Fatalf("createPacketSocketPair failed: %v", err) - } - defer windows.Closesocket(windows.Handle(fd)) - defer conn.Close() - - testData := []byte("hello from fd") - frame := make([]byte, 2+len(testData)) - binary.BigEndian.PutUint16(frame[:2], uint16(len(testData))) - copy(frame[2:], testData) - - wsaBuf := windows.WSABuf{Len: uint32(len(frame)), Buf: &frame[0]} - var bytesSent uint32 - err = windows.WSASend(windows.Handle(fd), &wsaBuf, 1, &bytesSent, 0, nil, nil) - if err != nil { - t.Fatalf("WSASend failed: %v", err) - } - - buf := make([]byte, 1024) - n, _, err := conn.ReadFrom(buf) - if err != nil { - t.Fatalf("ReadFrom failed: %v", err) - } - if !bytes.Equal(buf[:n], testData) { - t.Errorf("expected %q, got %q", testData, buf[:n]) - } - - testData2 := []byte("hello from conn") - _, err = conn.WriteTo(testData2, nil) - if err != nil { - t.Fatalf("WriteTo failed: %v", err) - } - - var flags uint32 - var bytesReceived uint32 - buf2 := make([]byte, 1024) - wsaBuf2 := windows.WSABuf{Len: uint32(len(buf2)), Buf: &buf2[0]} - err = windows.WSARecv(windows.Handle(fd), &wsaBuf2, 1, &bytesReceived, &flags, nil, nil) - if err != nil { - t.Fatalf("WSARecv failed: %v", err) - } - - length := binary.BigEndian.Uint16(buf2[:2]) - if !bytes.Equal(buf2[2:2+length], testData2) { - t.Errorf("expected %q, got %q", testData2, buf2[2:2+length]) - } -} - -func TestFramedPacketConn_MessageBoundary(t *testing.T) { - fd, conn, err := createPacketSocketPair(false) - if err != nil { - t.Fatalf("createPacketSocketPair failed: %v", err) - } - defer windows.Closesocket(windows.Handle(fd)) - defer conn.Close() - - messages := [][]byte{ - []byte("msg1"), - []byte("longer message 2"), - []byte("m3"), - } - - for _, msg := range messages { - _, err = conn.WriteTo(msg, nil) - if err != nil { - t.Fatalf("WriteTo failed: %v", err) - } - } - - for i, expected := range messages { - lengthBuf := make([]byte, 2) - var flags uint32 - var bytesReceived uint32 - wsaBuf := windows.WSABuf{Len: 2, Buf: &lengthBuf[0]} - err = windows.WSARecv(windows.Handle(fd), &wsaBuf, 1, &bytesReceived, &flags, nil, nil) - if err != nil { - t.Fatalf("WSARecv length %d failed: %v", i, err) - } - - length := binary.BigEndian.Uint16(lengthBuf) - if length != uint16(len(expected)) { - t.Errorf("message %d: expected length %d, got %d", i, len(expected), length) - } - - payload := make([]byte, length) - wsaBuf2 := windows.WSABuf{Len: uint32(length), Buf: &payload[0]} - err = windows.WSARecv(windows.Handle(fd), &wsaBuf2, 1, &bytesReceived, &flags, nil, nil) - if err != nil { - t.Fatalf("WSARecv payload %d failed: %v", i, err) - } - if !bytes.Equal(payload, expected) { - t.Errorf("message %d: expected %q, got %q", i, expected, payload) - } - } -} - -func TestFramedPacketConn_MaxSize(t *testing.T) { - fd, conn, err := createPacketSocketPair(false) - if err != nil { - t.Fatalf("createPacketSocketPair failed: %v", err) - } - defer windows.Closesocket(windows.Handle(fd)) - defer conn.Close() - - hugePacket := make([]byte, 65536) - _, err = conn.WriteTo(hugePacket, nil) - if err == nil { - t.Error("expected error for packet larger than 65535, got nil") - } -} diff --git a/naive_library_cgo.go b/naive_library_cgo.go deleted file mode 100644 index e442935a..00000000 --- a/naive_library_cgo.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build !with_purego - -package cronet - -func checkLibrary() error { - return nil -} diff --git a/naive_library_purego.go b/naive_library_purego.go deleted file mode 100644 index 9a9fcfd1..00000000 --- a/naive_library_purego.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build with_purego - -package cronet - -import "github.com/sagernet/cronet-go/internal/cronet" - -func checkLibrary() error { - return cronet.LoadLibrary("") -} diff --git a/naive_socket_unix.go b/naive_socket_unix.go deleted file mode 100644 index 20006973..00000000 --- a/naive_socket_unix.go +++ /dev/null @@ -1,54 +0,0 @@ -//go:build unix - -package cronet - -import ( - "net" - "os" - "syscall" - - E "github.com/sagernet/sing/common/exceptions" -) - -func dupSocketFD(syscallConn syscall.Conn) (int, error) { - rawConn, err := syscallConn.SyscallConn() - if err != nil { - return -1, E.Cause(err, "get syscall conn") - } - var fd int - var controlError error - err = rawConn.Control(func(fdPtr uintptr) { - newFD, dupError := syscall.Dup(int(fdPtr)) - if dupError != nil { - controlError = E.Cause(dupError, "dup socket fd") - return - } - syscall.CloseOnExec(newFD) - fd = newFD - }) - if err != nil { - return -1, E.Cause(err, "control raw conn") - } - if controlError != nil { - return -1, controlError - } - return fd, nil -} - -func createSocketPair() (cronetFD int, proxyConn net.Conn, err error) { - fds, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0) - if err != nil { - return -1, nil, E.Cause(err, "create socketpair") - } - - syscall.CloseOnExec(fds[0]) - - file := os.NewFile(uintptr(fds[1]), "cronet-socketpair") - conn, err := net.FileConn(file) - _ = file.Close() - if err != nil { - syscall.Close(fds[0]) - return -1, nil, E.Cause(err, "create net conn from socketpair") - } - return fds[0], conn, nil -} diff --git a/naive_socket_windows.go b/naive_socket_windows.go deleted file mode 100644 index d583716e..00000000 --- a/naive_socket_windows.go +++ /dev/null @@ -1,89 +0,0 @@ -//go:build windows - -package cronet - -import ( - "net" - "syscall" - - E "github.com/sagernet/sing/common/exceptions" - - "golang.org/x/sys/windows" -) - -func dupSocketFD(syscallConn syscall.Conn) (int, error) { - rawConn, err := syscallConn.SyscallConn() - if err != nil { - return -1, E.Cause(err, "get syscall conn") - } - var socket windows.Handle - var controlError error - err = rawConn.Control(func(fdPtr uintptr) { - currentProcess := windows.CurrentProcess() - duplicateError := windows.DuplicateHandle( - currentProcess, - windows.Handle(fdPtr), - currentProcess, - &socket, - 0, - false, - windows.DUPLICATE_SAME_ACCESS, - ) - if duplicateError != nil { - controlError = E.Cause(duplicateError, "duplicate socket handle") - return - } - }) - if err != nil { - if socket != 0 { - _ = windows.Closesocket(socket) - } - return -1, E.Cause(err, "control raw conn") - } - if controlError != nil { - return -1, controlError - } - return int(socket), nil -} - -func createTCPLoopbackSocketPair() (cronetFD int, proxyConn net.Conn, err error) { - listener, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - return -1, nil, E.Cause(err, "create loopback listener") - } - defer listener.Close() - - var clientConn net.Conn - var clientError error - done := make(chan struct{}) - go func() { - clientConn, clientError = net.Dial("tcp", listener.Addr().String()) - close(done) - }() - - serverConn, err := listener.Accept() - if err != nil { - <-done - if clientConn != nil { - clientConn.Close() - } - return -1, nil, E.Cause(err, "accept loopback connection") - } - - <-done - if clientError != nil { - serverConn.Close() - return -1, nil, E.Cause(clientError, "dial loopback") - } - - tcpConn := serverConn.(*net.TCPConn) - fd, err := dupSocketFD(tcpConn) - if err != nil { - serverConn.Close() - clientConn.Close() - return -1, nil, E.Cause(err, "dup loopback socket") - } - - serverConn.Close() - return fd, clientConn, nil -} diff --git a/naive_socket_windows_go125.go b/naive_socket_windows_go125.go deleted file mode 100644 index ea7fee90..00000000 --- a/naive_socket_windows_go125.go +++ /dev/null @@ -1,156 +0,0 @@ -//go:build windows && go1.25 - -package cronet - -import ( - "crypto/rand" - "encoding/hex" - "net" - "os" - "path/filepath" - "strconv" - - "golang.org/x/sys/windows" -) - -var ( - winsockSystemLibrary = windows.NewLazySystemDLL("ws2_32.dll") - winsockProcAccept = winsockSystemLibrary.NewProc("accept") -) - -func createSocketPair() (cronetFD int, proxyConn net.Conn, err error) { - cronetFD, proxyConn, err = createUnixSocketPair() - if err == nil { - return - } - return createTCPLoopbackSocketPair() -} - -func createUnixSocketPair() (cronetFD int, proxyConn net.Conn, err error) { - socketSuffix, err := randomHexString(8) - if err != nil { - return -1, nil, err - } - socketBaseName := "cronet-go-" + strconv.Itoa(os.Getpid()) + "-" + socketSuffix + ".sock" - - candidates := []string{ - "@" + socketBaseName, - } - - temporaryPathCandidate := filepath.Join(os.TempDir(), socketBaseName) - if len(temporaryPathCandidate) < windows.UNIX_PATH_MAX { - candidates = append(candidates, temporaryPathCandidate) - } - - var lastError error - for _, name := range candidates { - cronetFD, proxyConn, lastError = createUnixSocketPairWithName(name) - if lastError == nil { - return cronetFD, proxyConn, nil - } - } - return -1, nil, lastError -} - -func createUnixSocketPairWithName(name string) (cronetFD int, proxyConn net.Conn, err error) { - if name != "" && name[0] != '@' { - _ = os.Remove(name) - } - - listenerSocket, err := windows.Socket(windows.AF_UNIX, windows.SOCK_STREAM, 0) - if err != nil { - return -1, nil, err - } - listenerClosed := false - closeListenerSocket := func() { - if listenerClosed { - return - } - listenerClosed = true - _ = windows.Closesocket(listenerSocket) - } - defer closeListenerSocket() - - listenerAddress := &windows.SockaddrUnix{Name: name} - err = windows.Bind(listenerSocket, listenerAddress) - if err != nil { - return -1, nil, err - } - err = windows.Listen(listenerSocket, 1) - if err != nil { - return -1, nil, err - } - - clientSocket, err := windows.Socket(windows.AF_UNIX, windows.SOCK_STREAM, 0) - if err != nil { - return -1, nil, err - } - // setUnixSocketBufferSize(clientSocket) // ineffective on Windows AF_UNIX - closeClientSocket := func() { - _ = windows.Closesocket(clientSocket) - } - - acceptedDone := make(chan struct{}) - var acceptedSocket windows.Handle - var acceptError error - go func() { - defer close(acceptedDone) - r1, _, callError := winsockProcAccept.Call(uintptr(listenerSocket), 0, 0) - if uintptr(r1) == uintptr(^uintptr(0)) { - acceptError = callError - return - } - acceptedSocket = windows.Handle(r1) - }() - - connectError := windows.Connect(clientSocket, listenerAddress) - if connectError != nil { - closeListenerSocket() - closeClientSocket() - <-acceptedDone - if acceptedSocket != 0 { - _ = windows.Closesocket(acceptedSocket) - } - return -1, nil, connectError - } - - <-acceptedDone - if acceptError != nil { - closeClientSocket() - return -1, nil, acceptError - } - - // setUnixSocketBufferSize(acceptedSocket) // ineffective on Windows AF_UNIX - closeListenerSocket() - if name != "" && name[0] != '@' { - _ = os.Remove(name) - } - - file := os.NewFile(uintptr(acceptedSocket), "unix") - proxyConn, err = net.FileConn(file) - file.Close() - if err != nil { - closeClientSocket() - return -1, nil, err - } - - return int(clientSocket), proxyConn, nil -} - -// setUnixSocketBufferSize is ineffective on Windows AF_UNIX sockets. -// SO_SNDBUF/SO_RCVBUF are silently ignored. -// -// func setUnixSocketBufferSize(socket windows.Handle) { -// const bufferSize = 2 * 1024 * 1024 -// _ = windows.SetsockoptInt(socket, windows.SOL_SOCKET, windows.SO_SNDBUF, bufferSize) -// _ = windows.SetsockoptInt(socket, windows.SOL_SOCKET, windows.SO_RCVBUF, bufferSize) -// } - -func randomHexString(byteCount int) (string, error) { - randomBytes := make([]byte, byteCount) - _, err := rand.Read(randomBytes) - if err != nil { - return "", err - } - return hex.EncodeToString(randomBytes), nil -} diff --git a/naive_socket_windows_legacy.go b/naive_socket_windows_legacy.go deleted file mode 100644 index 2aad315f..00000000 --- a/naive_socket_windows_legacy.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build windows && !go1.25 - -package cronet - -import "net" - -func createSocketPair() (cronetFD int, proxyConn net.Conn, err error) { - return createTCPLoopbackSocketPair() -} diff --git a/net_error.go b/net_error.go deleted file mode 100644 index 3d06fae2..00000000 --- a/net_error.go +++ /dev/null @@ -1,145 +0,0 @@ -package cronet - -import ( - "context" - "errors" - "net" - "os" - "sort" - "strconv" - "syscall" -) - -//go:generate go run ./cmd/build-naive generate-net-errors - -// NetError represents a Chromium network error code. -// Error codes are negative integers defined in Chromium's net/base/net_error_list.h. -type NetError int - -func lookupNetError(e NetError) (*netErrorEntry, bool) { - i := sort.Search(len(netErrorInfo), func(i int) bool { - return netErrorInfo[i].code >= e - }) - if i < len(netErrorInfo) && netErrorInfo[i].code == e { - return &netErrorInfo[i], true - } - return nil, false -} - -// Error implements the error interface with a Go-style lowercase message. -func (e NetError) Error() string { - if info, ok := lookupNetError(e); ok { - return info.message - } - return "network error " + strconv.Itoa(int(e)) -} - -// Name returns the Chromium error name (e.g., "ERR_CONNECTION_REFUSED"). -func (e NetError) Name() string { - if info, ok := lookupNetError(e); ok { - return info.name - } - return "ERR_UNKNOWN_" + strconv.Itoa(int(-e)) -} - -// Description returns the full description from Chromium source. -func (e NetError) Description() string { - if info, ok := lookupNetError(e); ok { - return info.description - } - return "" -} - -// Code returns the raw error code as an integer. -func (e NetError) Code() int { - return int(e) -} - -// Is implements errors.Is() support for comparing NetError with Go standard errors. -func (e NetError) Is(target error) bool { - if t, ok := target.(NetError); ok { - return e == t - } - - switch target { - case net.ErrClosed: - return e == NetErrorConnectionClosed || - e == NetErrorSocketNotConnected || - e == NetErrorConnectionReset || - e == NetErrorConnectionAborted - - case os.ErrDeadlineExceeded: - return e == NetErrorTimedOut || - e == NetErrorConnectionTimedOut - - case syscall.ECONNREFUSED: - return e == NetErrorConnectionRefused - - case syscall.ECONNRESET: - return e == NetErrorConnectionReset - - case syscall.ECONNABORTED: - return e == NetErrorConnectionAborted - - case syscall.ETIMEDOUT: - return e == NetErrorConnectionTimedOut - - case syscall.ENETUNREACH, syscall.EHOSTUNREACH: - return e == NetErrorAddressUnreachable - - case syscall.ENOTCONN: - return e == NetErrorSocketNotConnected - } - - return false -} - -// Timeout returns true if this error represents a timeout. -// This implements the net.Error interface. -func (e NetError) Timeout() bool { - return e == NetErrorTimedOut || e == NetErrorConnectionTimedOut -} - -// Temporary returns false. Chromium errors are not considered temporary. -// This implements the net.Error interface. -func (e NetError) Temporary() bool { - return false -} - -func toNetError(err error) NetError { - if err == nil { - return 0 - } - - if errors.Is(err, context.Canceled) { - return NetErrorAborted - } - if errors.Is(err, context.DeadlineExceeded) { - return NetErrorConnectionTimedOut - } - - var networkError net.Error - if errors.As(err, &networkError) && networkError.Timeout() { - return NetErrorConnectionTimedOut - } - - var syscallError *os.SyscallError - if errors.As(err, &syscallError) { - if errno, ok := syscallError.Err.(syscall.Errno); ok { - switch errno { - case syscall.ECONNREFUSED: - return NetErrorConnectionRefused - case syscall.ETIMEDOUT: - return NetErrorConnectionTimedOut - case syscall.ENETUNREACH, syscall.EHOSTUNREACH: - return NetErrorAddressUnreachable - case syscall.ECONNRESET: - return NetErrorConnectionReset - case syscall.ECONNABORTED: - return NetErrorConnectionAborted - } - } - } - - return NetErrorConnectionFailed -} diff --git a/net_error_generated.go b/net_error_generated.go deleted file mode 100644 index fc3b74df..00000000 --- a/net_error_generated.go +++ /dev/null @@ -1,504 +0,0 @@ -// Code generated by cmd/build-naive generate-net-errors. DO NOT EDIT. -// Source: naiveproxy/src/net/base/net_error_list.h - -package cronet - -// NetError constants from Chromium's net_error_list.h -const ( - NetErrorIOPending NetError = -1 - NetErrorFailed NetError = -2 - NetErrorAborted NetError = -3 - NetErrorInvalidArgument NetError = -4 - NetErrorInvalidHandle NetError = -5 - NetErrorFileNotFound NetError = -6 - NetErrorTimedOut NetError = -7 - NetErrorFileTooBig NetError = -8 - NetErrorUnexpected NetError = -9 - NetErrorAccessDenied NetError = -10 - NetErrorNotImplemented NetError = -11 - NetErrorInsufficientResources NetError = -12 - NetErrorOutOfMemory NetError = -13 - NetErrorUploadFileChanged NetError = -14 - NetErrorSocketNotConnected NetError = -15 - NetErrorFileExists NetError = -16 - NetErrorFilePathTooLong NetError = -17 - NetErrorFileNoSpace NetError = -18 - NetErrorFileVirusInfected NetError = -19 - NetErrorBlockedByClient NetError = -20 - NetErrorNetworkChanged NetError = -21 - NetErrorBlockedByAdministrator NetError = -22 - NetErrorSocketIsConnected NetError = -23 - NetErrorUploadStreamRewindNotSupported NetError = -25 - NetErrorContextShutDown NetError = -26 - NetErrorBlockedByResponse NetError = -27 - NetErrorCleartextNotPermitted NetError = -29 - NetErrorBlockedByCSP NetError = -30 - NetErrorBlockedByORB NetError = -32 - NetErrorNetworkAccessRevoked NetError = -33 - NetErrorBlockedByFingerprintingProtection NetError = -34 - NetErrorBlockedInIncognitoByAdministrator NetError = -35 - NetErrorLocalNetworkPermissionMissing NetError = -36 - NetErrorConnectionClosed NetError = -100 - NetErrorConnectionReset NetError = -101 - NetErrorConnectionRefused NetError = -102 - NetErrorConnectionAborted NetError = -103 - NetErrorConnectionFailed NetError = -104 - NetErrorNameNotResolved NetError = -105 - NetErrorInternetDisconnected NetError = -106 - NetErrorSSLProtocolError NetError = -107 - NetErrorAddressInvalid NetError = -108 - NetErrorAddressUnreachable NetError = -109 - NetErrorSSLClientAuthCertNeeded NetError = -110 - NetErrorTunnelConnectionFailed NetError = -111 - NetErrorSSLVersionOrCipherMismatch NetError = -113 - NetErrorSSLRenegotiationRequested NetError = -114 - NetErrorProxyAuthUnsupported NetError = -115 - NetErrorBadSSLClientAuthCert NetError = -117 - NetErrorConnectionTimedOut NetError = -118 - NetErrorHostResolverQueueTooLarge NetError = -119 - NetErrorSOCKSConnectionFailed NetError = -120 - NetErrorSOCKSConnectionHostUnreachable NetError = -121 - NetErrorAlpnNegotiationFailed NetError = -122 - NetErrorSSLNoRenegotiation NetError = -123 - NetErrorWinsockUnexpectedWrittenBytes NetError = -124 - NetErrorSSLDecompressionFailureAlert NetError = -125 - NetErrorSSLBadRecordMACAlert NetError = -126 - NetErrorProxyAuthRequested NetError = -127 - NetErrorProxyConnectionFailed NetError = -130 - NetErrorMandatoryProxyConfigurationFailed NetError = -131 - NetErrorPreconnectMaxSocketLimit NetError = -133 - NetErrorSSLClientAuthPrivateKeyAccessDenied NetError = -134 - NetErrorSSLClientAuthCertNoPrivateKey NetError = -135 - NetErrorProxyCertificateInvalid NetError = -136 - NetErrorNameResolutionFailed NetError = -137 - NetErrorNetworkAccessDenied NetError = -138 - NetErrorTemporarilyThrottled NetError = -139 - NetErrorSSLClientAuthSignatureFailed NetError = -141 - NetErrorMsgTooBig NetError = -142 - NetErrorWsProtocolError NetError = -145 - NetErrorAddressInUse NetError = -147 - NetErrorSSLPinnedKeyNotInCertChain NetError = -150 - NetErrorClientAuthCertTypeUnsupported NetError = -151 - NetErrorSSLDecryptErrorAlert NetError = -153 - NetErrorWsThrottleQueueTooLarge NetError = -154 - NetErrorSSLServerCertChanged NetError = -156 - NetErrorSSLUnrecognizedNameAlert NetError = -159 - NetErrorSocketSetReceiveBufferSizeError NetError = -160 - NetErrorSocketSetSendBufferSizeError NetError = -161 - NetErrorSocketReceiveBufferSizeUnchangeable NetError = -162 - NetErrorSocketSendBufferSizeUnchangeable NetError = -163 - NetErrorSSLClientAuthCertBadFormat NetError = -164 - NetErrorIcannNameCollision NetError = -166 - NetErrorSSLServerCertBadFormat NetError = -167 - NetErrorCTSthParsingFailed NetError = -168 - NetErrorCTSthIncomplete NetError = -169 - NetErrorUnableToReuseConnectionForProxyAuth NetError = -170 - NetErrorCTConsistencyProofParsingFailed NetError = -171 - NetErrorSSLObsoleteCipher NetError = -172 - NetErrorWsUpgrade NetError = -173 - NetErrorReadIfReadyNotImplemented NetError = -174 - NetErrorNoBufferSpace NetError = -176 - NetErrorSSLClientAuthNoCommonAlgorithms NetError = -177 - NetErrorEarlyDataRejected NetError = -178 - NetErrorWrongVersionOnEarlyData NetError = -179 - NetErrorTls13DowngradeDetected NetError = -180 - NetErrorSSLKeyUsageIncompatible NetError = -181 - NetErrorInvalidECHConfigList NetError = -182 - NetErrorECHNotNegotiated NetError = -183 - NetErrorECHFallbackCertificateInvalid NetError = -184 - NetErrorProxyUnableToConnectToDestination NetError = -186 - NetErrorProxyDelegateCanceledConnectRequest NetError = -187 - NetErrorProxyDelegateCanceledConnectResponse NetError = -188 - NetErrorCertCommonNameInvalid NetError = -200 - NetErrorCertDateInvalid NetError = -201 - NetErrorCertAuthorityInvalid NetError = -202 - NetErrorCertContainsErrors NetError = -203 - NetErrorCertNoRevocationMechanism NetError = -204 - NetErrorCertUnableToCheckRevocation NetError = -205 - NetErrorCertRevoked NetError = -206 - NetErrorCertInvalid NetError = -207 - NetErrorCertWeakSignatureAlgorithm NetError = -208 - NetErrorCertNonUniqueName NetError = -210 - NetErrorCertWeakKey NetError = -211 - NetErrorCertNameConstraintViolation NetError = -212 - NetErrorCertValidityTooLong NetError = -213 - NetErrorCertificateTransparencyRequired NetError = -214 - NetErrorCertKnownInterceptionBlocked NetError = -217 - NetErrorCertSelfSignedLocalNetwork NetError = -219 - NetErrorInvalidURL NetError = -300 - NetErrorDisallowedURLScheme NetError = -301 - NetErrorUnknownURLScheme NetError = -302 - NetErrorInvalidRedirect NetError = -303 - NetErrorTooManyRedirects NetError = -310 - NetErrorUnsafeRedirect NetError = -311 - NetErrorUnsafePort NetError = -312 - NetErrorInvalidResponse NetError = -320 - NetErrorInvalidChunkedEncoding NetError = -321 - NetErrorMethodNotSupported NetError = -322 - NetErrorUnexpectedProxyAuth NetError = -323 - NetErrorEmptyResponse NetError = -324 - NetErrorResponseHeadersTooBig NetError = -325 - NetErrorPACScriptFailed NetError = -327 - NetErrorRequestRangeNotSatisfiable NetError = -328 - NetErrorMalformedIdentity NetError = -329 - NetErrorContentDecodingFailed NetError = -330 - NetErrorNetworkIOSuspended NetError = -331 - NetErrorNoSupportedProxies NetError = -336 - NetErrorHTTP2ProtocolError NetError = -337 - NetErrorInvalidAuthCredentials NetError = -338 - NetErrorUnsupportedAuthScheme NetError = -339 - NetErrorEncodingDetectionFailed NetError = -340 - NetErrorMissingAuthCredentials NetError = -341 - NetErrorUnexpectedSecurityLibraryStatus NetError = -342 - NetErrorMisconfiguredAuthEnvironment NetError = -343 - NetErrorUndocumentedSecurityLibraryStatus NetError = -344 - NetErrorResponseBodyTooBigToDrain NetError = -345 - NetErrorResponseHeadersMultipleContentLength NetError = -346 - NetErrorIncompleteHTTP2Headers NetError = -347 - NetErrorPACNotInDhcp NetError = -348 - NetErrorResponseHeadersMultipleContentDisposition NetError = -349 - NetErrorResponseHeadersMultipleLocation NetError = -350 - NetErrorHTTP2ServerRefusedStream NetError = -351 - NetErrorHTTP2PingFailed NetError = -352 - NetErrorContentLengthMismatch NetError = -354 - NetErrorIncompleteChunkedEncoding NetError = -355 - NetErrorQUICProtocolError NetError = -356 - NetErrorResponseHeadersTruncated NetError = -357 - NetErrorQUICHandshakeFailed NetError = -358 - NetErrorHTTP2InadequateTransportSecurity NetError = -360 - NetErrorHTTP2FlowControlError NetError = -361 - NetErrorHTTP2FrameSizeError NetError = -362 - NetErrorHTTP2CompressionError NetError = -363 - NetErrorProxyAuthRequestedWithNoConnection NetError = -364 - NetErrorHTTP11Required NetError = -365 - NetErrorProxyHTTP11Required NetError = -366 - NetErrorPACScriptTerminated NetError = -367 - NetErrorInvalidHTTPResponse NetError = -370 - NetErrorContentDecodingInitFailed NetError = -371 - NetErrorHTTP2RSTStreamNoErrorReceived NetError = -372 - NetErrorTooManyRetries NetError = -375 - NetErrorHTTP2StreamClosed NetError = -376 - NetErrorHTTPResponseCodeFailure NetError = -379 - NetErrorQUICCertRootNotKnown NetError = -380 - NetErrorQUICGoawayRequestCanBeRetried NetError = -381 - NetErrorTooManyAcceptChRestarts NetError = -382 - NetErrorInconsistentIPAddressSpace NetError = -383 - NetErrorCachedIPAddressSpaceBlockedByLocalNetworkAccessPolicy NetError = -384 - NetErrorBlockedByLocalNetworkAccessChecks NetError = -385 - NetErrorZstdWindowSizeTooBig NetError = -386 - NetErrorDictionaryLoadFailed NetError = -387 - NetErrorUnexpectedContentDictionaryHeader NetError = -388 - NetErrorCacheMiss NetError = -400 - NetErrorCacheReadFailure NetError = -401 - NetErrorCacheWriteFailure NetError = -402 - NetErrorCacheOperationNotSupported NetError = -403 - NetErrorCacheOpenFailure NetError = -404 - NetErrorCacheCreateFailure NetError = -405 - NetErrorCacheRace NetError = -406 - NetErrorCacheChecksumReadFailure NetError = -407 - NetErrorCacheChecksumMismatch NetError = -408 - NetErrorCacheLockTimeout NetError = -409 - NetErrorCacheAuthFailureAfterRead NetError = -410 - NetErrorCacheEntryNotSuitable NetError = -411 - NetErrorCacheDoomFailure NetError = -412 - NetErrorCacheOpenOrCreateFailure NetError = -413 - NetErrorInsecureResponse NetError = -501 - NetErrorNoPrivateKeyForCert NetError = -502 - NetErrorAddUserCertFailed NetError = -503 - NetErrorInvalidSignedExchange NetError = -504 - NetErrorInvalidWebBundle NetError = -505 - NetErrorTrustTokenOperationFailed NetError = -506 - NetErrorTrustTokenOperationSuccessWithoutSendingRequest NetError = -507 - NetErrorHttpengineProviderInUse NetError = -508 - NetErrorPkcs12ImportBadPassword NetError = -701 - NetErrorPkcs12ImportFailed NetError = -702 - NetErrorImportCaCertNotCa NetError = -703 - NetErrorImportCertAlreadyExists NetError = -704 - NetErrorImportCaCertFailed NetError = -705 - NetErrorImportServerCertFailed NetError = -706 - NetErrorPkcs12ImportInvalidMAC NetError = -707 - NetErrorPkcs12ImportInvalidFile NetError = -708 - NetErrorPkcs12ImportUnsupported NetError = -709 - NetErrorKeyGenerationFailed NetError = -710 - NetErrorPrivateKeyExportFailed NetError = -712 - NetErrorSelfSignedCertGenerationFailed NetError = -713 - NetErrorCertDatabaseChanged NetError = -714 - NetErrorCertVerifierChanged NetError = -716 - NetErrorDNSMalformedResponse NetError = -800 - NetErrorDNSServerRequiresTCP NetError = -801 - NetErrorDNSTimedOut NetError = -803 - NetErrorDNSCacheMiss NetError = -804 - NetErrorDNSSearchEmpty NetError = -805 - NetErrorDNSSortError NetError = -806 - NetErrorDNSSecureResolverHostnameResolutionFailed NetError = -808 - NetErrorDNSNameHttpsOnly NetError = -809 - NetErrorDNSRequestCancelled NetError = -810 - NetErrorDNSNoMatchingSupportedAlpn NetError = -811 - NetErrorDNSSecureProbeRecordInvalid NetError = -814 - NetErrorDNSCacheInvalidationInProgress NetError = -815 - NetErrorDNSFormatError NetError = -816 - NetErrorDNSServerFailure NetError = -817 - NetErrorDNSNotImplemented NetError = -818 - NetErrorDNSRefused NetError = -819 - NetErrorDNSOtherFailure NetError = -820 - NetErrorBlobInvalidConstructionArguments NetError = -900 - NetErrorBlobOutOfMemory NetError = -901 - NetErrorBlobFileWriteFailed NetError = -902 - NetErrorBlobSourceDiedInTransit NetError = -903 - NetErrorBlobDereferencedWhileBuilding NetError = -904 - NetErrorBlobReferencedBlobBroken NetError = -905 - NetErrorBlobReferencedFileUnavailable NetError = -906 -) - -type netErrorEntry struct { - code NetError - name string - message string - description string -} - -var netErrorInfo = [243]netErrorEntry{ - {NetErrorBlobReferencedFileUnavailable, "ERR_BLOB_REFERENCED_FILE_UNAVAILABLE", "blob referenced file unavailable", "A file that we referenced during construction is not accessible to the renderer trying to create the blob."}, - {NetErrorBlobReferencedBlobBroken, "ERR_BLOB_REFERENCED_BLOB_BROKEN", "blob referenced blob broken", "A blob that we referenced during construction is broken, or a browser-side builder tries to build a blob with a blob reference that isn't finished constructing."}, - {NetErrorBlobDereferencedWhileBuilding, "ERR_BLOB_DEREFERENCED_WHILE_BUILDING", "blob dereferenced while building", "The renderer destructed the blob before it was done transferring, and there were no outstanding references (no one is waiting to read) to keep the blob alive."}, - {NetErrorBlobSourceDiedInTransit, "ERR_BLOB_SOURCE_DIED_IN_TRANSIT", "blob source died in transit", "The renderer was destroyed while data was in transit."}, - {NetErrorBlobFileWriteFailed, "ERR_BLOB_FILE_WRITE_FAILED", "blob file write failed", "We couldn't create or write to a file. File system error, like a full disk."}, - {NetErrorBlobOutOfMemory, "ERR_BLOB_OUT_OF_MEMORY", "blob out of memory", "We don't have enough memory for the blob."}, - {NetErrorBlobInvalidConstructionArguments, "ERR_BLOB_INVALID_CONSTRUCTION_ARGUMENTS", "blob invalid construction arguments", "The following errors are for mapped from a subset of invalid storage::BlobStatus. The construction arguments are invalid. This is considered a bad IPC."}, - {NetErrorDNSOtherFailure, "ERR_DNS_OTHER_FAILURE", "dns other failure", "The DNS server responded with an rcode indicating that the request failed, but the rcode is not one that we have a specific error code for. In other words, the rcode was not one of the following: - NOERR - FORMERR - SERVFAIL - NXDOMAIN - NOTIMP - REFUSED"}, - {NetErrorDNSRefused, "ERR_DNS_REFUSED", "dns refused", "The DNS server responded that the request was refused."}, - {NetErrorDNSNotImplemented, "ERR_DNS_NOT_IMPLEMENTED", "dns not implemented", "The DNS server responded that the query type is not implemented."}, - {NetErrorDNSServerFailure, "ERR_DNS_SERVER_FAILURE", "dns server failure", "The DNS server responded with a server failure response code."}, - {NetErrorDNSFormatError, "ERR_DNS_FORMAT_ERROR", "dns format error", "The DNS server responded with a format error response code."}, - {NetErrorDNSCacheInvalidationInProgress, "ERR_DNS_CACHE_INVALIDATION_IN_PROGRESS", "dns cache invalidation in progress", "Returned when DNS cache invalidation is in progress. This is a transient error. Callers may want to retry later."}, - {NetErrorDNSSecureProbeRecordInvalid, "ERR_DNS_SECURE_PROBE_RECORD_INVALID", "dns secure probe record invalid", "When checking whether secure DNS can be used, the response returned for the requested probe record either had no answer or was invalid."}, - {NetErrorDNSNoMatchingSupportedAlpn, "ERR_DNS_NO_MATCHING_SUPPORTED_ALPN", "dns no matching supported alpn", "The hostname resolution of HTTPS record was expected to be resolved with alpn values of supported protocols, but did not."}, - {NetErrorDNSRequestCancelled, "ERR_DNS_REQUEST_CANCELLED", "dns request cancelled", "All DNS requests associated with this job have been cancelled."}, - {NetErrorDNSNameHttpsOnly, "ERR_DNS_NAME_HTTPS_ONLY", "dns name https only", "DNS identified the request as disallowed for insecure connection (http/ws). Error should be handled as if an HTTP redirect was received to redirect to https or wss."}, - {NetErrorDNSSecureResolverHostnameResolutionFailed, "ERR_DNS_SECURE_RESOLVER_HOSTNAME_RESOLUTION_FAILED", "dns secure resolver hostname resolution failed", "Failed to resolve the hostname of a DNS-over-HTTPS server."}, - {NetErrorDNSSortError, "ERR_DNS_SORT_ERROR", "dns sort error", "Failed to sort addresses according to RFC3484."}, - {NetErrorDNSSearchEmpty, "ERR_DNS_SEARCH_EMPTY", "dns search empty", "Suffix search list rules prevent resolution of the given host name."}, - {NetErrorDNSCacheMiss, "ERR_DNS_CACHE_MISS", "dns cache miss", "The entry was not found in cache or other local sources, for lookups where only local sources were queried. TODO(ericorth): Consider renaming to DNS_LOCAL_MISS or something like that as the cache is not necessarily queried either."}, - {NetErrorDNSTimedOut, "ERR_DNS_TIMED_OUT", "dns timed out", "DNS transaction timed out."}, - {NetErrorDNSServerRequiresTCP, "ERR_DNS_SERVER_REQUIRES_TCP", "dns server requires tcp", "DNS server requires TCP"}, - {NetErrorDNSMalformedResponse, "ERR_DNS_MALFORMED_RESPONSE", "dns malformed response", "DNS error codes. DNS resolver received a malformed response."}, - {NetErrorCertVerifierChanged, "ERR_CERT_VERIFIER_CHANGED", "cert verifier changed", "The certificate verifier configuration changed in some way."}, - {NetErrorCertDatabaseChanged, "ERR_CERT_DATABASE_CHANGED", "cert database changed", "The certificate database changed in some way."}, - {NetErrorSelfSignedCertGenerationFailed, "ERR_SELF_SIGNED_CERT_GENERATION_FAILED", "self signed cert generation failed", "Self-signed certificate generation failed."}, - {NetErrorPrivateKeyExportFailed, "ERR_PRIVATE_KEY_EXPORT_FAILED", "private key export failed", "Failure to export private key."}, - {NetErrorKeyGenerationFailed, "ERR_KEY_GENERATION_FAILED", "key generation failed", "Key generation failed."}, - {NetErrorPkcs12ImportUnsupported, "ERR_PKCS12_IMPORT_UNSUPPORTED", "pkcs12 import unsupported", "PKCS #12 import failed due to unsupported features."}, - {NetErrorPkcs12ImportInvalidFile, "ERR_PKCS12_IMPORT_INVALID_FILE", "pkcs12 import invalid file", "PKCS #12 import failed due to invalid/corrupt file."}, - {NetErrorPkcs12ImportInvalidMAC, "ERR_PKCS12_IMPORT_INVALID_MAC", "pkcs12 import invalid mac", "PKCS #12 import failed due to invalid MAC."}, - {NetErrorImportServerCertFailed, "ERR_IMPORT_SERVER_CERT_FAILED", "import server cert failed", "Server certificate import failed due to some internal error."}, - {NetErrorImportCaCertFailed, "ERR_IMPORT_CA_CERT_FAILED", "import ca cert failed", "CA import failed due to some other error."}, - {NetErrorImportCertAlreadyExists, "ERR_IMPORT_CERT_ALREADY_EXISTS", "import cert already exists", "Import failed - certificate already exists in database. Note it's a little weird this is an error but reimporting a PKCS12 is ok (no-op). That's how Mozilla does it, though."}, - {NetErrorImportCaCertNotCa, "ERR_IMPORT_CA_CERT_NOT_CA", "import ca cert not ca", "CA import failed - not a CA cert."}, - {NetErrorPkcs12ImportFailed, "ERR_PKCS12_IMPORT_FAILED", "pkcs12 import failed", "PKCS #12 import failed due to other error."}, - {NetErrorPkcs12ImportBadPassword, "ERR_PKCS12_IMPORT_BAD_PASSWORD", "pkcs12 import bad password", "LINT.ThenChange( //components/cronet/android/java/src/org/chromium/net/impl/AndroidNetworkExceptionWrapper.java:HTTPENGINE_PROVIDER_IN_USE, //tools/metrics/histograms/enums.xml:HTTPResponseAndNetErrorCodes, //tools/metrics/histograms/enums.xml:NetErrorCodes, ) LINT.IfChange PKCS #12 import failed due to incorrect password."}, - {NetErrorHttpengineProviderInUse, "ERR_HTTPENGINE_PROVIDER_IN_USE", "httpengine provider in use", "This is a placeholder value that should never be used within //net. When Cronet APIs are being backed by HttpEngine (i.e., HttpEngineProvider is being used), org.chromium.net.NetworkException#getCronetInternalErrorCode is not supported (android.net.http.NetworkException#getCronetInternalErrorCode does not exist). In this scenario, getCronetInternalErrorCode will always return this error. This is a first step towards the deprecation of getCronetInternalErrorCode. Temporarily terminate, then restart, ITTT to avoid unsupported nesting. LINT.ThenChange( //tools/metrics/histograms/enums.xml:HTTPResponseAndNetErrorCodes, //tools/metrics/histograms/enums.xml:NetErrorCodes, ) LINT.IfChange(HTTPENGINE_PROVIDER_IN_USE)"}, - {NetErrorTrustTokenOperationSuccessWithoutSendingRequest, "ERR_TRUST_TOKEN_OPERATION_SUCCESS_WITHOUT_SENDING_REQUEST", "trust token operation success without sending request", "When handling a Trust Tokens protocol operation-executing request, the system was able to execute the request's Trust Tokens operation without sending the request to its destination: for instance, the results could have been present in a local cache (for redemption) or the operation could have been diverted to a local provider (for \"platform-provided\" issuance)."}, - {NetErrorTrustTokenOperationFailed, "ERR_TRUST_TOKEN_OPERATION_FAILED", "trust token operation failed", "A Trust Tokens protocol operation-executing request failed for one of a number of reasons (precondition failure, internal error, bad response)."}, - {NetErrorInvalidWebBundle, "ERR_INVALID_WEB_BUNDLE", "invalid web bundle", "An error occurred while handling a Web Bundle source."}, - {NetErrorInvalidSignedExchange, "ERR_INVALID_SIGNED_EXCHANGE", "invalid signed exchange", "An error occurred while handling a signed exchange."}, - {NetErrorAddUserCertFailed, "ERR_ADD_USER_CERT_FAILED", "add user cert failed", "An error adding a certificate to the OS certificate database."}, - {NetErrorNoPrivateKeyForCert, "ERR_NO_PRIVATE_KEY_FOR_CERT", "no private key for cert", "An attempt to import a client certificate failed, as the user's key database lacked a corresponding private key."}, - {NetErrorInsecureResponse, "ERR_INSECURE_RESPONSE", "insecure response", "The server's response was insecure (e.g. there was a cert error)."}, - {NetErrorCacheOpenOrCreateFailure, "ERR_CACHE_OPEN_OR_CREATE_FAILURE", "cache open or create failure", "The disk cache is unable to open or create this entry."}, - {NetErrorCacheDoomFailure, "ERR_CACHE_DOOM_FAILURE", "cache doom failure", "The disk cache is unable to doom this entry."}, - {NetErrorCacheEntryNotSuitable, "ERR_CACHE_ENTRY_NOT_SUITABLE", "cache entry not suitable", "Internal not-quite error code for the HTTP cache. In-memory hints suggest that the cache entry would not have been usable with the transaction's current configuration (e.g. load flags, mode, etc.)"}, - {NetErrorCacheAuthFailureAfterRead, "ERR_CACHE_AUTH_FAILURE_AFTER_READ", "cache auth failure after read", "Received a challenge after the transaction has read some data, and the credentials aren't available. There isn't a way to get them at that point."}, - {NetErrorCacheLockTimeout, "ERR_CACHE_LOCK_TIMEOUT", "cache lock timeout", "Internal error code for the HTTP cache. The cache lock timeout has fired."}, - {NetErrorCacheChecksumMismatch, "ERR_CACHE_CHECKSUM_MISMATCH", "cache checksum mismatch", "The cache found an entry with an invalid checksum. This can be returned from attempts to read from the cache. It is an internal error, returned by the SimpleCache backend, but not by any URLRequest methods or members."}, - {NetErrorCacheChecksumReadFailure, "ERR_CACHE_CHECKSUM_READ_FAILURE", "cache checksum read failure", "The cache was unable to read a checksum record on an entry. This can be returned from attempts to read from the cache. It is an internal error, returned by the SimpleCache backend, but not by any URLRequest methods or members."}, - {NetErrorCacheRace, "ERR_CACHE_RACE", "cache race", "Multiple transactions are racing to create disk cache entries. This is an internal error returned from the HttpCache to the HttpCacheTransaction that tells the transaction to restart the entry-creation logic because the state of the cache has changed."}, - {NetErrorCacheCreateFailure, "ERR_CACHE_CREATE_FAILURE", "cache create failure", "The disk cache is unable to create this entry."}, - {NetErrorCacheOpenFailure, "ERR_CACHE_OPEN_FAILURE", "cache open failure", "The disk cache is unable to open this entry."}, - {NetErrorCacheOperationNotSupported, "ERR_CACHE_OPERATION_NOT_SUPPORTED", "cache operation not supported", "The operation is not supported for this entry."}, - {NetErrorCacheWriteFailure, "ERR_CACHE_WRITE_FAILURE", "cache write failure", "Unable to write to the disk cache."}, - {NetErrorCacheReadFailure, "ERR_CACHE_READ_FAILURE", "cache read failure", "Unable to read from the disk cache."}, - {NetErrorCacheMiss, "ERR_CACHE_MISS", "cache miss", "The cache does not have the requested entry."}, - {NetErrorUnexpectedContentDictionaryHeader, "ERR_UNEXPECTED_CONTENT_DICTIONARY_HEADER", "unexpected content dictionary header", "The header of dictionary compressed stream does not match the expected value."}, - {NetErrorDictionaryLoadFailed, "ERR_DICTIONARY_LOAD_FAILED", "dictionary load failed", "The compression dictionary cannot be loaded."}, - {NetErrorZstdWindowSizeTooBig, "ERR_ZSTD_WINDOW_SIZE_TOO_BIG", "zstd window size too big", "Content decoding failed due to the zstd window size being too big (over 8MB)."}, - {NetErrorBlockedByLocalNetworkAccessChecks, "ERR_BLOCKED_BY_LOCAL_NETWORK_ACCESS_CHECKS", "blocked by local network access checks", "The connection is blocked by private network access checks."}, - {NetErrorCachedIPAddressSpaceBlockedByLocalNetworkAccessPolicy, "ERR_CACHED_IP_ADDRESS_SPACE_BLOCKED_BY_LOCAL_NETWORK_ACCESS_POLICY", "cached ip address space blocked by local network access policy", "The IP address space of the cached remote endpoint is blocked by private network access check."}, - {NetErrorInconsistentIPAddressSpace, "ERR_INCONSISTENT_IP_ADDRESS_SPACE", "inconsistent ip address space", "The IP address space of the remote endpoint differed from the previous observed value during the same request. Any cache entry for the affected request should be invalidated."}, - {NetErrorTooManyAcceptChRestarts, "ERR_TOO_MANY_ACCEPT_CH_RESTARTS", "too many accept ch restarts", "The ACCEPT_CH restart has been triggered too many times"}, - {NetErrorQUICGoawayRequestCanBeRetried, "ERR_QUIC_GOAWAY_REQUEST_CAN_BE_RETRIED", "quic goaway request can be retried", "A GOAWAY frame has been received indicating that the request has not been processed and is therefore safe to retry on a different connection."}, - {NetErrorQUICCertRootNotKnown, "ERR_QUIC_CERT_ROOT_NOT_KNOWN", "quic cert root not known", "The certificate presented on a QUIC connection does not chain to a known root and the origin connected to is not on a list of domains where unknown roots are allowed."}, - {NetErrorHTTPResponseCodeFailure, "ERR_HTTP_RESPONSE_CODE_FAILURE", "http response code failure", "Obsolete. HTTP/2 push is removed. NET_ERROR(HTTP2_CLIENT_REFUSED_STREAM, -377) Obsolete. HTTP/2 push is removed. NET_ERROR(HTTP2_PUSHED_RESPONSE_DOES_NOT_MATCH, -378) The server returned a non-2xx HTTP response code. Note that this error is only used by certain APIs that interpret the HTTP response itself. URLRequest for instance just passes most non-2xx response back as success."}, - {NetErrorHTTP2StreamClosed, "ERR_HTTP2_STREAM_CLOSED", "http2 stream closed", "Received an HTTP/2 frame on a closed stream."}, - {NetErrorTooManyRetries, "ERR_TOO_MANY_RETRIES", "too many retries", "Obsolete. HTTP/2 push is removed. NET_ERROR(HTTP2_PUSHED_STREAM_NOT_AVAILABLE, -373) Obsolete. HTTP/2 push is removed. NET_ERROR(HTTP2_CLAIMED_PUSHED_STREAM_RESET_BY_SERVER, -374) An HTTP transaction was retried too many times due for authentication or invalid certificates. This may be due to a bug in the net stack that would otherwise infinite loop, or if the server or proxy continually requests fresh credentials or presents a fresh invalid certificate."}, - {NetErrorHTTP2RSTStreamNoErrorReceived, "ERR_HTTP2_RST_STREAM_NO_ERROR_RECEIVED", "http2 rst stream no error received", "Received HTTP/2 RST_STREAM frame with NO_ERROR error code. This error should be handled internally by HTTP/2 code, and should not make it above the SpdyStream layer."}, - {NetErrorContentDecodingInitFailed, "ERR_CONTENT_DECODING_INIT_FAILED", "content decoding init failed", "Initializing content decoding failed."}, - {NetErrorInvalidHTTPResponse, "ERR_INVALID_HTTP_RESPONSE", "invalid http response", "Obsolete. Support for CNAME record detection was never fully implemented and is no longer needed since the IP Protection feature didn't launch. NET_ERROR(PROXY_REQUIRED, -368) Obsolete. Kept here to avoid reuse. Request is throttled because of a Backoff header. See: crbug.com/486891. NET_ERROR(TEMPORARY_BACKOFF, -369) The server was expected to return an HTTP/1.x response, but did not. Rather than treat it as HTTP/0.9, this error is returned."}, - {NetErrorPACScriptTerminated, "ERR_PAC_SCRIPT_TERMINATED", "pac script terminated", "The PAC script terminated fatally and must be reloaded."}, - {NetErrorProxyHTTP11Required, "ERR_PROXY_HTTP_1_1_REQUIRED", "proxy http 1 1 required", "HTTP_1_1_REQUIRED error code received on HTTP/2 session to proxy."}, - {NetErrorHTTP11Required, "ERR_HTTP_1_1_REQUIRED", "http 1 1 required", "HTTP_1_1_REQUIRED error code received on HTTP/2 session."}, - {NetErrorProxyAuthRequestedWithNoConnection, "ERR_PROXY_AUTH_REQUESTED_WITH_NO_CONNECTION", "proxy auth requested with no connection", "Proxy Auth Requested without a valid Client Socket Handle."}, - {NetErrorHTTP2CompressionError, "ERR_HTTP2_COMPRESSION_ERROR", "http2 compression error", "Decoding or encoding of compressed HTTP/2 headers failed."}, - {NetErrorHTTP2FrameSizeError, "ERR_HTTP2_FRAME_SIZE_ERROR", "http2 frame size error", "The peer sent an improperly sized HTTP/2 frame."}, - {NetErrorHTTP2FlowControlError, "ERR_HTTP2_FLOW_CONTROL_ERROR", "http2 flow control error", "The peer violated HTTP/2 flow control."}, - {NetErrorHTTP2InadequateTransportSecurity, "ERR_HTTP2_INADEQUATE_TRANSPORT_SECURITY", "http2 inadequate transport security", "Obsolete. Kept here to avoid reuse, as the old error can still appear on histograms. NET_ERROR(REQUEST_FOR_SECURE_RESOURCE_OVER_INSECURE_QUIC, -359) Transport security is inadequate for the HTTP/2 version."}, - {NetErrorQUICHandshakeFailed, "ERR_QUIC_HANDSHAKE_FAILED", "quic handshake failed", "The QUIC crypto handshake failed. This means that the server was unable to read any requests sent, so they may be resent."}, - {NetErrorResponseHeadersTruncated, "ERR_RESPONSE_HEADERS_TRUNCATED", "response headers truncated", "The HTTP headers were truncated by an EOF."}, - {NetErrorQUICProtocolError, "ERR_QUIC_PROTOCOL_ERROR", "quic protocol error", "There is a QUIC protocol error."}, - {NetErrorIncompleteChunkedEncoding, "ERR_INCOMPLETE_CHUNKED_ENCODING", "incomplete chunked encoding", "The HTTP response body is transferred with Chunked-Encoding, but the terminating zero-length chunk was never sent when the connection is closed."}, - {NetErrorContentLengthMismatch, "ERR_CONTENT_LENGTH_MISMATCH", "content length mismatch", "Obsolete. Kept here to avoid reuse, as the old error can still appear on histograms. NET_ERROR(PIPELINE_EVICTION, -353) The HTTP response body transferred fewer bytes than were advertised by the Content-Length header when the connection is closed."}, - {NetErrorHTTP2PingFailed, "ERR_HTTP2_PING_FAILED", "http2 ping failed", "HTTP/2 server didn't respond to the PING message."}, - {NetErrorHTTP2ServerRefusedStream, "ERR_HTTP2_SERVER_REFUSED_STREAM", "http2 server refused stream", "HTTP/2 server refused the request without processing, and sent either a GOAWAY frame with error code NO_ERROR and Last-Stream-ID lower than the stream id corresponding to the request indicating that this request has not been processed yet, or a RST_STREAM frame with error code REFUSED_STREAM. Client MAY retry (on a different connection). See RFC7540 Section 8.1.4."}, - {NetErrorResponseHeadersMultipleLocation, "ERR_RESPONSE_HEADERS_MULTIPLE_LOCATION", "response headers multiple location", "The HTTP response contained multiple Location headers."}, - {NetErrorResponseHeadersMultipleContentDisposition, "ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_DISPOSITION", "response headers multiple content disposition", "The HTTP response contained multiple Content-Disposition headers."}, - {NetErrorPACNotInDhcp, "ERR_PAC_NOT_IN_DHCP", "pac not in dhcp", "No PAC URL configuration could be retrieved from DHCP. This can indicate either a failure to retrieve the DHCP configuration, or that there was no PAC URL configured in DHCP."}, - {NetErrorIncompleteHTTP2Headers, "ERR_INCOMPLETE_HTTP2_HEADERS", "incomplete http2 headers", "HTTP/2 headers have been received, but not all of them - status or version headers are missing, so we're expecting additional frames to complete them."}, - {NetErrorResponseHeadersMultipleContentLength, "ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_LENGTH", "response headers multiple content length", "The HTTP response contained multiple distinct Content-Length headers."}, - {NetErrorResponseBodyTooBigToDrain, "ERR_RESPONSE_BODY_TOO_BIG_TO_DRAIN", "response body too big to drain", "The HTTP response was too big to drain."}, - {NetErrorUndocumentedSecurityLibraryStatus, "ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS", "undocumented security library status", "An undocumented SSPI or GSSAPI status code was returned."}, - {NetErrorMisconfiguredAuthEnvironment, "ERR_MISCONFIGURED_AUTH_ENVIRONMENT", "misconfigured auth environment", "The environment was not set up correctly for authentication (for example, no KDC could be found or the principal is unknown."}, - {NetErrorUnexpectedSecurityLibraryStatus, "ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS", "unexpected security library status", "An unexpected, but documented, SSPI or GSSAPI status code was returned."}, - {NetErrorMissingAuthCredentials, "ERR_MISSING_AUTH_CREDENTIALS", "missing auth credentials", "(GSSAPI) No Kerberos credentials were available during HTTP Authentication."}, - {NetErrorEncodingDetectionFailed, "ERR_ENCODING_DETECTION_FAILED", "encoding detection failed", "Detecting the encoding of the response failed."}, - {NetErrorUnsupportedAuthScheme, "ERR_UNSUPPORTED_AUTH_SCHEME", "unsupported auth scheme", "An HTTP Authentication scheme was tried which is not supported on this machine."}, - {NetErrorInvalidAuthCredentials, "ERR_INVALID_AUTH_CREDENTIALS", "invalid auth credentials", "Credentials could not be established during HTTP Authentication."}, - {NetErrorHTTP2ProtocolError, "ERR_HTTP2_PROTOCOL_ERROR", "http2 protocol error", "There is an HTTP/2 protocol error."}, - {NetErrorNoSupportedProxies, "ERR_NO_SUPPORTED_PROXIES", "no supported proxies", "Obsolete. This was in earlier SPDY implementations. NET_ERROR(SYN_REPLY_NOT_RECEIVED, -332) Obsolete. These were both used for FTP, which is no longer supported. NET_ERROR(ENCODING_CONVERSION_FAILED, -333) NET_ERROR(UNRECOGNIZED_FTP_DIRECTORY_LISTING_FORMAT, -334) Obsolete. Was only logged in NetLog when an HTTP/2 pushed stream expired. NET_ERROR(INVALID_SPDY_STREAM, -335) There are no supported proxies in the provided list."}, - {NetErrorNetworkIOSuspended, "ERR_NETWORK_IO_SUSPENDED", "network io suspended", "An operation could not be completed because all network IO is suspended."}, - {NetErrorContentDecodingFailed, "ERR_CONTENT_DECODING_FAILED", "content decoding failed", "Content decoding of the response body failed."}, - {NetErrorMalformedIdentity, "ERR_MALFORMED_IDENTITY", "malformed identity", "The identity used for authentication is invalid."}, - {NetErrorRequestRangeNotSatisfiable, "ERR_REQUEST_RANGE_NOT_SATISFIABLE", "request range not satisfiable", "The response was 416 (Requested range not satisfiable) and the server cannot satisfy the range requested."}, - {NetErrorPACScriptFailed, "ERR_PAC_SCRIPT_FAILED", "pac script failed", "The evaluation of the PAC script failed."}, - {NetErrorResponseHeadersTooBig, "ERR_RESPONSE_HEADERS_TOO_BIG", "response headers too big", "The headers section of the response is too large."}, - {NetErrorEmptyResponse, "ERR_EMPTY_RESPONSE", "empty response", "The server closed the connection without sending any data."}, - {NetErrorUnexpectedProxyAuth, "ERR_UNEXPECTED_PROXY_AUTH", "unexpected proxy auth", "The response was 407 (Proxy Authentication Required), yet we did not send the request to a proxy."}, - {NetErrorMethodNotSupported, "ERR_METHOD_NOT_SUPPORTED", "method not supported", "The server did not support the request method."}, - {NetErrorInvalidChunkedEncoding, "ERR_INVALID_CHUNKED_ENCODING", "invalid chunked encoding", "Error in chunked transfer encoding."}, - {NetErrorInvalidResponse, "ERR_INVALID_RESPONSE", "invalid response", "The server's response was invalid."}, - {NetErrorUnsafePort, "ERR_UNSAFE_PORT", "unsafe port", "Attempting to load an URL with an unsafe port number. These are port numbers that correspond to services, which are not robust to spurious input that may be constructed as a result of an allowed web construct (e.g., HTTP looks a lot like SMTP, so form submission to port 25 is denied)."}, - {NetErrorUnsafeRedirect, "ERR_UNSAFE_REDIRECT", "unsafe redirect", "Attempting to load an URL resulted in an unsafe redirect (e.g., a redirect to file:// is considered unsafe)."}, - {NetErrorTooManyRedirects, "ERR_TOO_MANY_REDIRECTS", "too many redirects", "Attempting to load an URL resulted in too many redirects."}, - {NetErrorInvalidRedirect, "ERR_INVALID_REDIRECT", "invalid redirect", "Attempting to load an URL resulted in a redirect to an invalid URL."}, - {NetErrorUnknownURLScheme, "ERR_UNKNOWN_URL_SCHEME", "unknown url scheme", "The scheme of the URL is unknown."}, - {NetErrorDisallowedURLScheme, "ERR_DISALLOWED_URL_SCHEME", "disallowed url scheme", "The scheme of the URL is disallowed."}, - {NetErrorInvalidURL, "ERR_INVALID_URL", "invalid url", "The URL is invalid."}, - {NetErrorCertSelfSignedLocalNetwork, "ERR_CERT_SELF_SIGNED_LOCAL_NETWORK", "cert self signed local network", "-218 was SSL_OBSOLETE_VERSION which is not longer used. TLS 1.0/1.1 instead cause SSL_VERSION_OR_CIPHER_MISMATCH now. The certificate is self signed and it's being used for either an RFC1918 IP literal URL, or a url ending in .local."}, - {NetErrorCertKnownInterceptionBlocked, "ERR_CERT_KNOWN_INTERCEPTION_BLOCKED", "cert known interception blocked", "-216 was QUIC_CERT_ROOT_NOT_KNOWN which has been renumbered to not be in the certificate error range. The certificate is known to be used for interception by an entity other the device owner."}, - {NetErrorCertificateTransparencyRequired, "ERR_CERTIFICATE_TRANSPARENCY_REQUIRED", "certificate transparency required", "Certificate Transparency was required for this connection, but the server did not provide CT information that complied with the policy."}, - {NetErrorCertValidityTooLong, "ERR_CERT_VALIDITY_TOO_LONG", "cert validity too long", "The certificate's validity period is too long."}, - {NetErrorCertNameConstraintViolation, "ERR_CERT_NAME_CONSTRAINT_VIOLATION", "cert name constraint violation", "The certificate claimed DNS names that are in violation of name constraints."}, - {NetErrorCertWeakKey, "ERR_CERT_WEAK_KEY", "cert weak key", "The server responded with a certificate that contains a weak key (e.g. a too-small RSA key)."}, - {NetErrorCertNonUniqueName, "ERR_CERT_NON_UNIQUE_NAME", "cert non unique name", "-209 is available: was CERT_NOT_IN_DNS. The host name specified in the certificate is not unique."}, - {NetErrorCertWeakSignatureAlgorithm, "ERR_CERT_WEAK_SIGNATURE_ALGORITHM", "cert weak signature algorithm", "The server responded with a certificate that is signed using a weak signature algorithm."}, - {NetErrorCertInvalid, "ERR_CERT_INVALID", "cert invalid", "The server responded with a certificate that is invalid. This error is not recoverable. MSDN describes this error as follows: \"The SSL certificate is invalid.\""}, - {NetErrorCertRevoked, "ERR_CERT_REVOKED", "cert revoked", "The server responded with a certificate has been revoked. We have the capability to ignore this error, but it is probably not the thing to do."}, - {NetErrorCertUnableToCheckRevocation, "ERR_CERT_UNABLE_TO_CHECK_REVOCATION", "cert unable to check revocation", "Revocation information for the security certificate for this site is not available. This could mean: 1. An attacker has compromised the private key in the certificate and is blocking our attempt to find out that the cert was revoked. 2. The certificate is unrevoked, but the revocation server is busy or unavailable."}, - {NetErrorCertNoRevocationMechanism, "ERR_CERT_NO_REVOCATION_MECHANISM", "cert no revocation mechanism", "The certificate has no mechanism for determining if it is revoked. In effect, this certificate cannot be revoked."}, - {NetErrorCertContainsErrors, "ERR_CERT_CONTAINS_ERRORS", "cert contains errors", "The server responded with a certificate that contains errors. This error is not recoverable. MSDN describes this error as follows: \"The SSL certificate contains errors.\" NOTE: It's unclear how this differs from ERR_CERT_INVALID. For consistency, use that code instead of this one from now on."}, - {NetErrorCertAuthorityInvalid, "ERR_CERT_AUTHORITY_INVALID", "cert authority invalid", "The server responded with a certificate that is signed by an authority we don't trust. The could mean: 1. An attacker has substituted the real certificate for a cert that contains their public key and is signed by their cousin. 2. The server operator has a legitimate certificate from a CA we don't know about, but should trust. 3. The server is presenting a self-signed certificate, providing no defense against active attackers (but foiling passive attackers)."}, - {NetErrorCertDateInvalid, "ERR_CERT_DATE_INVALID", "cert date invalid", "The server responded with a certificate that, by our clock, appears to either not yet be valid or to have expired. This could mean: 1. An attacker is presenting an old certificate for which they have managed to obtain the private key. 2. The server is misconfigured and is not presenting a valid cert. 3. Our clock is wrong."}, - {NetErrorCertCommonNameInvalid, "ERR_CERT_COMMON_NAME_INVALID", "cert common name invalid", "Certificate error codes The values of certificate error codes must be consecutive. The server responded with a certificate whose common name did not match the host name. This could mean: 1. An attacker has redirected our traffic to their server and is presenting a certificate for which they know the private key. 2. The server is misconfigured and responding with the wrong cert. 3. The user is on a wireless network and is being redirected to the network's login page. 4. The OS has used a DNS search suffix and the server doesn't have a certificate for the abbreviated name in the address bar."}, - {NetErrorProxyDelegateCanceledConnectResponse, "ERR_PROXY_DELEGATE_CANCELED_CONNECT_RESPONSE", "proxy delegate canceled connect response", ""}, - {NetErrorProxyDelegateCanceledConnectRequest, "ERR_PROXY_DELEGATE_CANCELED_CONNECT_REQUEST", "proxy delegate canceled connect request", "Some implementations of ProxyDelegate query a separate entity to know whether it should cancel tunnel prior to: - The HTTP CONNECT requests being sent out - The HTTP CONNECT response being parsed by //net An example is CronetProxyDelegate: Cronet allows developers to decide whether the tunnel being established should be canceled."}, - {NetErrorProxyUnableToConnectToDestination, "ERR_PROXY_UNABLE_TO_CONNECT_TO_DESTINATION", "proxy unable to connect to destination", "An attempt to proxy a request failed because the proxy wasn't able to successfully connect to the destination. This likely indicates an issue with the request itself (for instance, the hostname failed to resolve to an IP address or the destination server refused the connection). This error code is used to indicate that the error is outside the control of the proxy server and thus the proxy chain should not be marked as bad. This is in contrast to ERR_TUNNEL_CONNECTION_FAILED which is used for general purpose errors connecting to the proxy and by the proxy request response handling when a proxy delegate doesn't indicate via a different error code whether proxy fallback should occur. Note that for IP Protection proxies this error code causes the proxy to be marked as bad since the preference is to fail open for general purpose errors, but for other proxies this error does not cause the proxy to be marked as bad."}, - {NetErrorECHFallbackCertificateInvalid, "ERR_ECH_FALLBACK_CERTIFICATE_INVALID", "ech fallback certificate invalid", "ECH was enabled, the server was unable to decrypt the encrypted ClientHello, and additionally did not present a certificate valid for the public name."}, - {NetErrorECHNotNegotiated, "ERR_ECH_NOT_NEGOTIATED", "ech not negotiated", "ECH was enabled, but the server was unable to decrypt the encrypted ClientHello."}, - {NetErrorInvalidECHConfigList, "ERR_INVALID_ECH_CONFIG_LIST", "invalid ech config list", "The ECHConfigList fetched over DNS cannot be parsed."}, - {NetErrorSSLKeyUsageIncompatible, "ERR_SSL_KEY_USAGE_INCOMPATIBLE", "ssl key usage incompatible", "The server's certificate has a keyUsage extension incompatible with the negotiated TLS key exchange method."}, - {NetErrorTls13DowngradeDetected, "ERR_TLS13_DOWNGRADE_DETECTED", "tls13 downgrade detected", "TLS 1.3 was enabled, but a lower version was negotiated and the server returned a value indicating it supported TLS 1.3. This is part of a security check in TLS 1.3, but it may also indicate the user is behind a buggy TLS-terminating proxy which implemented TLS 1.2 incorrectly. (See https://crbug.com/boringssl/226.)"}, - {NetErrorWrongVersionOnEarlyData, "ERR_WRONG_VERSION_ON_EARLY_DATA", "wrong version on early data", "TLS 1.3 early data was offered, but the server responded with TLS 1.2 or earlier. This is an internal error code to account for a backwards-compatibility issue with early data and TLS 1.2. It will be received before any data is returned from the socket. The request should be retried with early data disabled. See https://tools.ietf.org/html/rfc8446#appendix-D.3 for details."}, - {NetErrorEarlyDataRejected, "ERR_EARLY_DATA_REJECTED", "early data rejected", "TLS 1.3 early data was rejected by the server. This will be received before any data is returned from the socket. The request should be retried with early data disabled."}, - {NetErrorSSLClientAuthNoCommonAlgorithms, "ERR_SSL_CLIENT_AUTH_NO_COMMON_ALGORITHMS", "ssl client auth no common algorithms", "There were no common signature algorithms between our client certificate private key and the server's preferences."}, - {NetErrorNoBufferSpace, "ERR_NO_BUFFER_SPACE", "no buffer space", "No socket buffer space is available."}, - {NetErrorReadIfReadyNotImplemented, "ERR_READ_IF_READY_NOT_IMPLEMENTED", "read if ready not implemented", "Socket ReadIfReady support is not implemented. This error should not be user visible, because the normal Read() method is used as a fallback."}, - {NetErrorWsUpgrade, "ERR_WS_UPGRADE", "ws upgrade", "When a WebSocket handshake is done successfully and the connection has been upgraded, the URLRequest is cancelled with this error code."}, - {NetErrorSSLObsoleteCipher, "ERR_SSL_OBSOLETE_CIPHER", "ssl obsolete cipher", "The SSL server required an unsupported cipher suite that has since been removed. This error will temporarily be signaled on a fallback for one or two releases immediately following a cipher suite's removal, after which the fallback will be removed."}, - {NetErrorCTConsistencyProofParsingFailed, "ERR_CT_CONSISTENCY_PROOF_PARSING_FAILED", "ct consistency proof parsing failed", "Certificate Transparency: Failed to parse the received consistency proof."}, - {NetErrorUnableToReuseConnectionForProxyAuth, "ERR_UNABLE_TO_REUSE_CONNECTION_FOR_PROXY_AUTH", "unable to reuse connection for proxy auth", "The attempt to reuse a connection to send proxy auth credentials failed before the AuthController was used to generate credentials. The caller should reuse the controller with a new connection. This error is only used internally by the network stack."}, - {NetErrorCTSthIncomplete, "ERR_CT_STH_INCOMPLETE", "ct sth incomplete", "Certificate Transparency: Received a signed tree head whose JSON parsing was OK but was missing some of the fields."}, - {NetErrorCTSthParsingFailed, "ERR_CT_STH_PARSING_FAILED", "ct sth parsing failed", "Certificate Transparency: Received a signed tree head that failed to parse."}, - {NetErrorSSLServerCertBadFormat, "ERR_SSL_SERVER_CERT_BAD_FORMAT", "ssl server cert bad format", "The SSL server presented a certificate which could not be decoded. This is not a certificate error code as no X509Certificate object is available. This error is fatal."}, - {NetErrorIcannNameCollision, "ERR_ICANN_NAME_COLLISION", "icann name collision", "Resolving a hostname to an IP address list included the IPv4 address \"127.0.53.53\". This is a special IP address which ICANN has recommended to indicate there was a name collision, and alert admins to a potential problem."}, - {NetErrorSSLClientAuthCertBadFormat, "ERR_SSL_CLIENT_AUTH_CERT_BAD_FORMAT", "ssl client auth cert bad format", "Failed to import a client certificate from the platform store into the SSL library."}, - {NetErrorSocketSendBufferSizeUnchangeable, "ERR_SOCKET_SEND_BUFFER_SIZE_UNCHANGEABLE", "socket send buffer size unchangeable", "Failed to set the socket's send buffer size as requested, despite success return code from setsockopt."}, - {NetErrorSocketReceiveBufferSizeUnchangeable, "ERR_SOCKET_RECEIVE_BUFFER_SIZE_UNCHANGEABLE", "socket receive buffer size unchangeable", "Failed to set the socket's receive buffer size as requested, despite success return code from setsockopt."}, - {NetErrorSocketSetSendBufferSizeError, "ERR_SOCKET_SET_SEND_BUFFER_SIZE_ERROR", "socket set send buffer size error", "Failed to set the socket's send buffer size as requested."}, - {NetErrorSocketSetReceiveBufferSizeError, "ERR_SOCKET_SET_RECEIVE_BUFFER_SIZE_ERROR", "socket set receive buffer size error", "Failed to set the socket's receive buffer size as requested."}, - {NetErrorSSLUnrecognizedNameAlert, "ERR_SSL_UNRECOGNIZED_NAME_ALERT", "ssl unrecognized name alert", "The SSL server sent us a fatal unrecognized_name alert."}, - {NetErrorSSLServerCertChanged, "ERR_SSL_SERVER_CERT_CHANGED", "ssl server cert changed", "The SSL server certificate changed in a renegotiation."}, - {NetErrorWsThrottleQueueTooLarge, "ERR_WS_THROTTLE_QUEUE_TOO_LARGE", "ws throttle queue too large", "There are too many pending WebSocketJob instances, so the new job was not pushed to the queue."}, - {NetErrorSSLDecryptErrorAlert, "ERR_SSL_DECRYPT_ERROR_ALERT", "ssl decrypt error alert", "An SSL peer sent us a fatal decrypt_error alert. This typically occurs when a peer could not correctly verify a signature (in CertificateVerify or ServerKeyExchange) or validate a Finished message."}, - {NetErrorClientAuthCertTypeUnsupported, "ERR_CLIENT_AUTH_CERT_TYPE_UNSUPPORTED", "client auth cert type unsupported", "Server request for client certificate did not contain any types we support."}, - {NetErrorSSLPinnedKeyNotInCertChain, "ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN", "ssl pinned key not in cert chain", "Obsolete: NET_ERROR(SSL_HANDSHAKE_NOT_COMPLETED, -148) NET_ERROR(SSL_BAD_PEER_PUBLIC_KEY, -149) The certificate didn't match the built-in public key pins for the host name. The pins are set in net/http/transport_security_state.cc and require that one of a set of public keys exist on the path from the leaf to the root."}, - {NetErrorAddressInUse, "ERR_ADDRESS_IN_USE", "address in use", "Returned when attempting to bind an address that is already in use."}, - {NetErrorWsProtocolError, "ERR_WS_PROTOCOL_ERROR", "ws protocol error", "Websocket protocol error. Indicates that we are terminating the connection due to a malformed frame or other protocol violation."}, - {NetErrorMsgTooBig, "ERR_MSG_TOO_BIG", "msg too big", "The message was too large for the transport. (for example a UDP message which exceeds size threshold)."}, - {NetErrorSSLClientAuthSignatureFailed, "ERR_SSL_CLIENT_AUTH_SIGNATURE_FAILED", "ssl client auth signature failed", "Obsolete, since we now use the catch-all ERR_TUNNEL_CONNECTION_FAILED when a proxy tried to redirect a request. NET_ERROR(HTTPS_PROXY_TUNNEL_RESPONSE_REDIRECT, -140) We were unable to sign the CertificateVerify data of an SSL client auth handshake with the client certificate's private key. Possible causes for this include the user implicitly or explicitly denying access to the private key, the private key may not be valid for signing, the key may be relying on a cached handle which is no longer valid, or the CSP won't allow arbitrary data to be signed."}, - {NetErrorTemporarilyThrottled, "ERR_TEMPORARILY_THROTTLED", "temporarily throttled", "The request throttler module cancelled this request to avoid DDOS."}, - {NetErrorNetworkAccessDenied, "ERR_NETWORK_ACCESS_DENIED", "network access denied", "Permission to access the network was denied. This is used to distinguish errors that were most likely caused by a firewall from other access denied errors. See also ERR_ACCESS_DENIED."}, - {NetErrorNameResolutionFailed, "ERR_NAME_RESOLUTION_FAILED", "name resolution failed", "An error occurred when trying to do a name resolution (DNS)."}, - {NetErrorProxyCertificateInvalid, "ERR_PROXY_CERTIFICATE_INVALID", "proxy certificate invalid", "The certificate presented by the HTTPS Proxy was invalid."}, - {NetErrorSSLClientAuthCertNoPrivateKey, "ERR_SSL_CLIENT_AUTH_CERT_NO_PRIVATE_KEY", "ssl client auth cert no private key", "The SSL client certificate has no private key."}, - {NetErrorSSLClientAuthPrivateKeyAccessDenied, "ERR_SSL_CLIENT_AUTH_PRIVATE_KEY_ACCESS_DENIED", "ssl client auth private key access denied", "The permission to use the SSL client certificate's private key was denied."}, - {NetErrorPreconnectMaxSocketLimit, "ERR_PRECONNECT_MAX_SOCKET_LIMIT", "preconnect max socket limit", "-132 was formerly ERR_ESET_ANTI_VIRUS_SSL_INTERCEPTION We've hit the max socket limit for the socket pool while preconnecting. We don't bother trying to preconnect more sockets."}, - {NetErrorMandatoryProxyConfigurationFailed, "ERR_MANDATORY_PROXY_CONFIGURATION_FAILED", "mandatory proxy configuration failed", "A mandatory proxy configuration could not be used. Currently this means that a mandatory PAC script could not be fetched, parsed or executed."}, - {NetErrorProxyConnectionFailed, "ERR_PROXY_CONNECTION_FAILED", "proxy connection failed", "Could not create a connection to the proxy server. An error occurred either in resolving its name, or in connecting a socket to it. Note that this does NOT include failures during the actual \"CONNECT\" method of an HTTP proxy."}, - {NetErrorProxyAuthRequested, "ERR_PROXY_AUTH_REQUESTED", "proxy auth requested", "The proxy requested authentication (for tunnel establishment)."}, - {NetErrorSSLBadRecordMACAlert, "ERR_SSL_BAD_RECORD_MAC_ALERT", "ssl bad record mac alert", "An SSL peer sent us a fatal bad_record_mac alert. This has been observed from servers with buggy DEFLATE support."}, - {NetErrorSSLDecompressionFailureAlert, "ERR_SSL_DECOMPRESSION_FAILURE_ALERT", "ssl decompression failure alert", "An SSL peer sent us a fatal decompression_failure alert. This typically occurs when a peer selects DEFLATE compression in the mistaken belief that it supports it."}, - {NetErrorWinsockUnexpectedWrittenBytes, "ERR_WINSOCK_UNEXPECTED_WRITTEN_BYTES", "winsock unexpected written bytes", "Winsock sometimes reports more data written than passed. This is probably due to a broken LSP."}, - {NetErrorSSLNoRenegotiation, "ERR_SSL_NO_RENEGOTIATION", "ssl no renegotiation", "The peer sent an SSL no_renegotiation alert message."}, - {NetErrorAlpnNegotiationFailed, "ERR_ALPN_NEGOTIATION_FAILED", "alpn negotiation failed", "The request to negotiate an alternate protocol failed."}, - {NetErrorSOCKSConnectionHostUnreachable, "ERR_SOCKS_CONNECTION_HOST_UNREACHABLE", "socks connection host unreachable", "The SOCKS proxy server failed establishing connection to the target host because that host is unreachable."}, - {NetErrorSOCKSConnectionFailed, "ERR_SOCKS_CONNECTION_FAILED", "socks connection failed", "Failed establishing a connection to the SOCKS proxy server for a target host."}, - {NetErrorHostResolverQueueTooLarge, "ERR_HOST_RESOLVER_QUEUE_TOO_LARGE", "host resolver queue too large", "There are too many pending DNS resolves, so a request in the queue was aborted."}, - {NetErrorConnectionTimedOut, "ERR_CONNECTION_TIMED_OUT", "connection timed out", "A connection attempt timed out."}, - {NetErrorBadSSLClientAuthCert, "ERR_BAD_SSL_CLIENT_AUTH_CERT", "bad ssl client auth cert", "The SSL handshake failed because of a bad or missing client certificate."}, - {NetErrorProxyAuthUnsupported, "ERR_PROXY_AUTH_UNSUPPORTED", "proxy auth unsupported", "The proxy requested authentication (for tunnel establishment) with an unsupported method."}, - {NetErrorSSLRenegotiationRequested, "ERR_SSL_RENEGOTIATION_REQUESTED", "ssl renegotiation requested", "The server requested a renegotiation (rehandshake)."}, - {NetErrorSSLVersionOrCipherMismatch, "ERR_SSL_VERSION_OR_CIPHER_MISMATCH", "ssl version or cipher mismatch", "Obsolete: NET_ERROR(NO_SSL_VERSIONS_ENABLED, -112) The client and server don't support a common SSL protocol version or cipher suite."}, - {NetErrorTunnelConnectionFailed, "ERR_TUNNEL_CONNECTION_FAILED", "tunnel connection failed", "A tunnel connection through the proxy could not be established. For more info see the comment on PROXY_UNABLE_TO_CONNECT_TO_DESTINATION."}, - {NetErrorSSLClientAuthCertNeeded, "ERR_SSL_CLIENT_AUTH_CERT_NEEDED", "ssl client auth cert needed", "The server requested a client certificate for SSL client authentication."}, - {NetErrorAddressUnreachable, "ERR_ADDRESS_UNREACHABLE", "address unreachable", "The IP address is unreachable. This usually means that there is no route to the specified host or network."}, - {NetErrorAddressInvalid, "ERR_ADDRESS_INVALID", "address invalid", "The IP address or port number is invalid (e.g., cannot connect to the IP address 0 or the port 0)."}, - {NetErrorSSLProtocolError, "ERR_SSL_PROTOCOL_ERROR", "ssl protocol error", "An SSL protocol error occurred."}, - {NetErrorInternetDisconnected, "ERR_INTERNET_DISCONNECTED", "internet disconnected", "The Internet connection has been lost."}, - {NetErrorNameNotResolved, "ERR_NAME_NOT_RESOLVED", "name not resolved", "The host name could not be resolved."}, - {NetErrorConnectionFailed, "ERR_CONNECTION_FAILED", "connection failed", "A connection attempt failed."}, - {NetErrorConnectionAborted, "ERR_CONNECTION_ABORTED", "connection aborted", "A connection timed out as a result of not receiving an ACK for data sent. This can include a FIN packet that did not get ACK'd."}, - {NetErrorConnectionRefused, "ERR_CONNECTION_REFUSED", "connection refused", "A connection attempt was refused."}, - {NetErrorConnectionReset, "ERR_CONNECTION_RESET", "connection reset", "A connection was reset (corresponding to a TCP RST)."}, - {NetErrorConnectionClosed, "ERR_CONNECTION_CLOSED", "connection closed", "A connection was closed (corresponding to a TCP FIN)."}, - {NetErrorLocalNetworkPermissionMissing, "ERR_LOCAL_NETWORK_PERMISSION_MISSING", "local network permission missing", "The request was blocked because the local network permission is missing. Note that this is different from BLOCKED_BY_LOCAL_NETWORK_ACCESS_CHECKS which is specifically for a CORS error code."}, - {NetErrorBlockedInIncognitoByAdministrator, "ERR_BLOCKED_IN_INCOGNITO_BY_ADMINISTRATOR", "blocked in incognito by administrator", "The request was blocked by the Incognito Mode URL block list configured by the domain administrator."}, - {NetErrorBlockedByFingerprintingProtection, "ERR_BLOCKED_BY_FINGERPRINTING_PROTECTION", "blocked by fingerprinting protection", "The request was blocked by fingerprinting protections."}, - {NetErrorNetworkAccessRevoked, "ERR_NETWORK_ACCESS_REVOKED", "network access revoked", "The request was blocked because it originated from a frame that has disabled network access."}, - {NetErrorBlockedByORB, "ERR_BLOCKED_BY_ORB", "blocked by orb", "The request was blocked because of no H/2 or QUIC session. The request was blocked by CORB or ORB."}, - {NetErrorBlockedByCSP, "ERR_BLOCKED_BY_CSP", "blocked by csp", "The request was blocked by a Content Security Policy"}, - {NetErrorCleartextNotPermitted, "ERR_CLEARTEXT_NOT_PERMITTED", "cleartext not permitted", "The request was blocked by system policy disallowing some or all cleartext requests. Used for NetworkSecurityPolicy on Android."}, - {NetErrorBlockedByResponse, "ERR_BLOCKED_BY_RESPONSE", "blocked by response", "The request failed because the response was delivered along with requirements which are not met ('X-Frame-Options' and 'Content-Security-Policy' ancestor checks and 'Cross-Origin-Resource-Policy' for instance)."}, - {NetErrorContextShutDown, "ERR_CONTEXT_SHUT_DOWN", "context shut down", "The request failed because the URLRequestContext is shutting down, or has been shut down."}, - {NetErrorUploadStreamRewindNotSupported, "ERR_UPLOAD_STREAM_REWIND_NOT_SUPPORTED", "upload stream rewind not supported", "The upload failed because the upload stream needed to be re-read, due to a retry or a redirect, but the upload stream doesn't support that operation."}, - {NetErrorSocketIsConnected, "ERR_SOCKET_IS_CONNECTED", "socket is connected", "The socket is already connected."}, - {NetErrorBlockedByAdministrator, "ERR_BLOCKED_BY_ADMINISTRATOR", "blocked by administrator", "The request was blocked by the URL block list configured by the domain administrator."}, - {NetErrorNetworkChanged, "ERR_NETWORK_CHANGED", "network changed", "The network changed."}, - {NetErrorBlockedByClient, "ERR_BLOCKED_BY_CLIENT", "blocked by client", "The client chose to block the request."}, - {NetErrorFileVirusInfected, "ERR_FILE_VIRUS_INFECTED", "file virus infected", "The file has a virus."}, - {NetErrorFileNoSpace, "ERR_FILE_NO_SPACE", "file no space", "Not enough room left on the disk."}, - {NetErrorFilePathTooLong, "ERR_FILE_PATH_TOO_LONG", "file path too long", "The path or file name is too long."}, - {NetErrorFileExists, "ERR_FILE_EXISTS", "file exists", "The file already exists."}, - {NetErrorSocketNotConnected, "ERR_SOCKET_NOT_CONNECTED", "socket not connected", "The socket is not connected."}, - {NetErrorUploadFileChanged, "ERR_UPLOAD_FILE_CHANGED", "upload file changed", "The file upload failed because the file's modification time was different from the expectation."}, - {NetErrorOutOfMemory, "ERR_OUT_OF_MEMORY", "out of memory", "Memory allocation failed."}, - {NetErrorInsufficientResources, "ERR_INSUFFICIENT_RESOURCES", "insufficient resources", "There were not enough resources to complete the operation."}, - {NetErrorNotImplemented, "ERR_NOT_IMPLEMENTED", "not implemented", "The operation failed because of unimplemented functionality."}, - {NetErrorAccessDenied, "ERR_ACCESS_DENIED", "access denied", "Permission to access a resource, other than the network, was denied."}, - {NetErrorUnexpected, "ERR_UNEXPECTED", "unexpected", "An unexpected error. This may be caused by a programming mistake or an invalid assumption."}, - {NetErrorFileTooBig, "ERR_FILE_TOO_BIG", "file too big", "The file is too large."}, - {NetErrorTimedOut, "ERR_TIMED_OUT", "timed out", "An operation timed out."}, - {NetErrorFileNotFound, "ERR_FILE_NOT_FOUND", "file not found", "The file or directory cannot be found."}, - {NetErrorInvalidHandle, "ERR_INVALID_HANDLE", "invalid handle", "The handle or file descriptor is invalid."}, - {NetErrorInvalidArgument, "ERR_INVALID_ARGUMENT", "invalid argument", "An argument to the function is incorrect."}, - {NetErrorAborted, "ERR_ABORTED", "aborted", "An operation was aborted (due to user action)."}, - {NetErrorFailed, "ERR_FAILED", "failed", "A generic failure occurred."}, - {NetErrorIOPending, "ERR_IO_PENDING", "io pending", "Copyright 2012 The Chromium Authors Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. This file intentionally does not have header guards, it's included inside a macro to generate enum values. The following line silences a presubmit and Tricium warning that would otherwise be triggered by this: This file contains the list of network errors. LINT.IfChange An asynchronous IO operation is not yet complete. This usually does not indicate a fatal error. Typically this error will be generated as a notification to wait for some external notification that the IO operation finally completed."}, -} diff --git a/public_key_pins_cgo.go b/public_key_pins_cgo.go deleted file mode 100644 index 5006efcb..00000000 --- a/public_key_pins_cgo.go +++ /dev/null @@ -1,74 +0,0 @@ -//go:build !with_purego - -package cronet - -// #include -// #include -// #include -import "C" - -import ( - "unsafe" -) - -func NewPublicKeyPins() PublicKeyPins { - return PublicKeyPins{uintptr(unsafe.Pointer(C.Cronet_PublicKeyPins_Create()))} -} - -func (p PublicKeyPins) Destroy() { - C.Cronet_PublicKeyPins_Destroy(C.Cronet_PublicKeyPinsPtr(unsafe.Pointer(p.ptr))) -} - -// SetHost set name of the host to which the public keys should be pinned. A host that -// consists only of digits and the dot character is treated as invalid. -func (p PublicKeyPins) SetHost(host string) { - cHost := C.CString(host) - C.Cronet_PublicKeyPins_host_set(C.Cronet_PublicKeyPinsPtr(unsafe.Pointer(p.ptr)), cHost) - C.free(unsafe.Pointer(cHost)) -} - -func (p PublicKeyPins) Host() string { - return C.GoString(C.Cronet_PublicKeyPins_host_get(C.Cronet_PublicKeyPinsPtr(unsafe.Pointer(p.ptr)))) -} - -// AddPinnedSHA256 add pins. each pin is the SHA-256 cryptographic -// hash (in the form of "sha256/") of the DER-encoded ASN.1 -// representation of the Subject Public Key Info (SPKI) of the host's X.509 certificate. -// Although, the method does not mandate the presence of the backup pin -// that can be used if the control of the primary private key has been -// lost, it is highly recommended to supply one. -func (p PublicKeyPins) AddPinnedSHA256(hash string) { - cHash := C.CString(hash) - C.Cronet_PublicKeyPins_pins_sha256_add(C.Cronet_PublicKeyPinsPtr(unsafe.Pointer(p.ptr)), cHash) - C.free(unsafe.Pointer(cHash)) -} - -func (p PublicKeyPins) PinnedSHA256Size() int { - return int(C.Cronet_PublicKeyPins_pins_sha256_size(C.Cronet_PublicKeyPinsPtr(unsafe.Pointer(p.ptr)))) -} - -func (p PublicKeyPins) PinnedSHA256At(index int) string { - return C.GoString(C.Cronet_PublicKeyPins_pins_sha256_at(C.Cronet_PublicKeyPinsPtr(unsafe.Pointer(p.ptr)), C.uint32_t(index))) -} - -func (p PublicKeyPins) ClearPinnedSHA256() { - C.Cronet_PublicKeyPins_pins_sha256_clear(C.Cronet_PublicKeyPinsPtr(unsafe.Pointer(p.ptr))) -} - -// SetIncludeSubdomains set whether the pinning policy should be applied to subdomains of |host|. -func (p PublicKeyPins) SetIncludeSubdomains(includeSubdomains bool) { - C.Cronet_PublicKeyPins_include_subdomains_set(C.Cronet_PublicKeyPinsPtr(unsafe.Pointer(p.ptr)), C.bool(includeSubdomains)) -} - -func (p PublicKeyPins) IncludeSubdomains() bool { - return bool(C.Cronet_PublicKeyPins_include_subdomains_get(C.Cronet_PublicKeyPinsPtr(unsafe.Pointer(p.ptr)))) -} - -// SetExpirationDate set the expiration date for the pins in milliseconds since epoch (as in java.util.Date). -func (p PublicKeyPins) SetExpirationDate(date int64) { - C.Cronet_PublicKeyPins_expiration_date_set(C.Cronet_PublicKeyPinsPtr(unsafe.Pointer(p.ptr)), C.int64_t(date)) -} - -func (p PublicKeyPins) ExpirationDate() int64 { - return int64(C.Cronet_PublicKeyPins_expiration_date_get(C.Cronet_PublicKeyPinsPtr(unsafe.Pointer(p.ptr)))) -} diff --git a/public_key_pins_purego.go b/public_key_pins_purego.go deleted file mode 100644 index d910f4b8..00000000 --- a/public_key_pins_purego.go +++ /dev/null @@ -1,51 +0,0 @@ -//go:build with_purego - -package cronet - -import ( - "github.com/sagernet/cronet-go/internal/cronet" -) - -func NewPublicKeyPins() PublicKeyPins { - return PublicKeyPins{cronet.PublicKeyPinsCreate()} -} - -func (p PublicKeyPins) Destroy() { - cronet.PublicKeyPinsDestroy(p.ptr) -} - -func (p PublicKeyPins) SetHost(host string) { - cronet.PublicKeyPinsHostSet(p.ptr, host) -} - -func (p PublicKeyPins) Host() string { - return cronet.PublicKeyPinsHostGet(p.ptr) -} - -func (p PublicKeyPins) AddPinSHA256(pin string) { - cronet.PublicKeyPinsPinsSha256Add(p.ptr, pin) -} - -func (p PublicKeyPins) PinSHA256Size() int { - return int(cronet.PublicKeyPinsPinsSha256Size(p.ptr)) -} - -func (p PublicKeyPins) PinSHA256At(index int) string { - return cronet.PublicKeyPinsPinsSha256At(p.ptr, uint32(index)) -} - -func (p PublicKeyPins) SetIncludeSubdomains(include bool) { - cronet.PublicKeyPinsIncludeSubdomainsSet(p.ptr, include) -} - -func (p PublicKeyPins) IncludeSubdomains() bool { - return cronet.PublicKeyPinsIncludeSubdomainsGet(p.ptr) -} - -func (p PublicKeyPins) SetExpirationDate(date int64) { - cronet.PublicKeyPinsExpirationDateSet(p.ptr, date) -} - -func (p PublicKeyPins) ExpirationDate() int64 { - return cronet.PublicKeyPinsExpirationDateGet(p.ptr) -} diff --git a/quic_hint_cgo.go b/quic_hint_cgo.go deleted file mode 100644 index 9dc3e982..00000000 --- a/quic_hint_cgo.go +++ /dev/null @@ -1,49 +0,0 @@ -//go:build !with_purego - -package cronet - -// #include -// #include -// #include -import "C" - -import ( - "unsafe" -) - -func NewQuicHint() QuicHint { - return QuicHint{uintptr(unsafe.Pointer(C.Cronet_QuicHint_Create()))} -} - -func (h QuicHint) Destroy() { - C.Cronet_QuicHint_Destroy(C.Cronet_QuicHintPtr(unsafe.Pointer(h.ptr))) -} - -// SetHost set name of the host that supports QUIC. -func (h QuicHint) SetHost(host string) { - cHost := C.CString(host) - C.Cronet_QuicHint_host_set(C.Cronet_QuicHintPtr(unsafe.Pointer(h.ptr)), cHost) - C.free(unsafe.Pointer(cHost)) -} - -func (h QuicHint) Host() string { - return C.GoString(C.Cronet_QuicHint_host_get(C.Cronet_QuicHintPtr(unsafe.Pointer(h.ptr)))) -} - -// SetPort set port of the server that supports QUIC. -func (h QuicHint) SetPort(port int32) { - C.Cronet_QuicHint_port_set(C.Cronet_QuicHintPtr(unsafe.Pointer(h.ptr)), C.int32_t(port)) -} - -func (h QuicHint) Port() int32 { - return int32(C.Cronet_QuicHint_port_get(C.Cronet_QuicHintPtr(unsafe.Pointer(h.ptr)))) -} - -// SetAlternatePort set alternate port to use for QUIC -func (h QuicHint) SetAlternatePort(port int32) { - C.Cronet_QuicHint_alternate_port_set(C.Cronet_QuicHintPtr(unsafe.Pointer(h.ptr)), C.int32_t(port)) -} - -func (h QuicHint) AlternatePort() int32 { - return int32(C.Cronet_QuicHint_alternate_port_get(C.Cronet_QuicHintPtr(unsafe.Pointer(h.ptr)))) -} diff --git a/quic_hint_purego.go b/quic_hint_purego.go deleted file mode 100644 index 497b375b..00000000 --- a/quic_hint_purego.go +++ /dev/null @@ -1,39 +0,0 @@ -//go:build with_purego - -package cronet - -import ( - "github.com/sagernet/cronet-go/internal/cronet" -) - -func NewQuicHint() QuicHint { - return QuicHint{cronet.QuicHintCreate()} -} - -func (q QuicHint) Destroy() { - cronet.QuicHintDestroy(q.ptr) -} - -func (q QuicHint) SetHost(host string) { - cronet.QuicHintHostSet(q.ptr, host) -} - -func (q QuicHint) Host() string { - return cronet.QuicHintHostGet(q.ptr) -} - -func (q QuicHint) SetPort(port int32) { - cronet.QuicHintPortSet(q.ptr, port) -} - -func (q QuicHint) Port() int32 { - return cronet.QuicHintPortGet(q.ptr) -} - -func (q QuicHint) SetAlternatePort(port int32) { - cronet.QuicHintAlternatePortSet(q.ptr, port) -} - -func (q QuicHint) AlternatePort() int32 { - return cronet.QuicHintAlternatePortGet(q.ptr) -} diff --git a/result.go b/result.go deleted file mode 100644 index 891baa1e..00000000 --- a/result.go +++ /dev/null @@ -1,102 +0,0 @@ -package cronet - -// Result is runtime result code returned by Engine and URLRequest. Equivalent to -// runtime exceptions in Android Java API. All results except SUCCESS trigger -// native crash (via SIGABRT triggered by CHECK failure) unless -// EngineParams.EnableCheckResult is set to false. -type Result int - -const ( - // ResultSuccess Operation completed successfully - ResultSuccess Result = 0 - - // ResultIllegalArgument Illegal argument - ResultIllegalArgument Result = -100 - - // ResultIllegalArgumentStoragePathMustExist Storage path must be set to existing directory - ResultIllegalArgumentStoragePathMustExist Result = -101 - - // ResultIllegalArgumentInvalidPin Public key pin is invalid - ResultIllegalArgumentInvalidPin Result = -102 - - // ResultIllegalArgumentInvalidHostname Host name is invalid - ResultIllegalArgumentInvalidHostname Result = -103 - - // ResultIllegalArgumentInvalidHttpMethod Invalid http method - ResultIllegalArgumentInvalidHttpMethod Result = -104 - - // ResultIllegalArgumentInvalidHttpHeader Invalid http header - ResultIllegalArgumentInvalidHttpHeader Result = -105 - - // ResultIllegalState Illegal state - ResultIllegalState Result = -200 - - // ResultIllegalStateStoragePathInUse Storage path is used by another engine - ResultIllegalStateStoragePathInUse Result = -201 - - // ResultIllegalStateCannotShutdownEngineFromNetworkThread Cannot shutdown engine from network thread - ResultIllegalStateCannotShutdownEngineFromNetworkThread Result = -202 - - // ResultIllegalStateEngineAlreadyStarted The engine has already started - ResultIllegalStateEngineAlreadyStarted Result = -203 - - // ResultIllegalStateRequestAlreadyStarted The request has already started - ResultIllegalStateRequestAlreadyStarted Result = -204 - - // ResultIllegalStateRequestNotInitialized The request is not initialized - ResultIllegalStateRequestNotInitialized Result = -205 - - // ResultIllegalStateRequestAlreadyInitialized The request is already initialized - ResultIllegalStateRequestAlreadyInitialized Result = -206 - - // ResultIllegalStateRequestNotStarted The request is not started - ResultIllegalStateRequestNotStarted Result = -207 - - // ResultIllegalStateUnexpectedRedirect No redirect to follow - ResultIllegalStateUnexpectedRedirect Result = -208 - - // ResultIllegalStateUnexpectedRead Unexpected read attempt - ResultIllegalStateUnexpectedRead Result = -209 - - // ResultIllegalStateReadFailed Unexpected read failure - ResultIllegalStateReadFailed Result = -210 - - // ResultNullPointer Null pointer or empty data - ResultNullPointer Result = -300 - - // ResultNullPointerHostname The hostname cannot be null - ResultNullPointerHostname Result = -301 - - // ResultNullPointerSha256Pins The set of SHA256 pins cannot be null - ResultNullPointerSha256Pins Result = -302 - - // ResultNullPointerExpirationDate The pin expiration date cannot be null - ResultNullPointerExpirationDate Result = -303 - - // ResultNullPointerEngine Engine is required - ResultNullPointerEngine Result = -304 - - // ResultNullPointerURL URL is required - ResultNullPointerURL Result = -305 - - // ResultNullPointerCallback Callback is required - ResultNullPointerCallback Result = -306 - - // ResultNullPointerExecutor Executor is required - ResultNullPointerExecutor Result = -307 - - // ResultNullPointerMethod Method is required - ResultNullPointerMethod Result = -308 - - // ResultNullPointerHeaderName Invalid header name - ResultNullPointerHeaderName Result = -309 - - // ResultNullPointerHeaderValue Invalid header value - ResultNullPointerHeaderValue Result = -310 - - // ResultNullPointerParams Params is required - ResultNullPointerParams Result = -311 - - // ResultNullPointerRequestFinishedInfoListenerExecutor Executor for RequestFinishedInfoListener is required - ResultNullPointerRequestFinishedInfoListenerExecutor Result = -312 -) diff --git a/runnable_cgo.go b/runnable_cgo.go deleted file mode 100644 index b4784e65..00000000 --- a/runnable_cgo.go +++ /dev/null @@ -1,22 +0,0 @@ -//go:build !with_purego - -package cronet - -// #include -// #include -// #include -import "C" - -import "unsafe" - -func (r Runnable) Run() { - C.Cronet_Runnable_Run(C.Cronet_RunnablePtr(unsafe.Pointer(r.ptr))) -} - -func (r Runnable) SetClientContext(context unsafe.Pointer) { - C.Cronet_Runnable_SetClientContext(C.Cronet_RunnablePtr(unsafe.Pointer(r.ptr)), C.Cronet_ClientContext(context)) -} - -func (r Runnable) ClientContext() unsafe.Pointer { - return unsafe.Pointer(C.Cronet_Runnable_GetClientContext(C.Cronet_RunnablePtr(unsafe.Pointer(r.ptr)))) -} diff --git a/runnable_impl_cgo.go b/runnable_impl_cgo.go deleted file mode 100644 index ae4c773a..00000000 --- a/runnable_impl_cgo.go +++ /dev/null @@ -1,68 +0,0 @@ -//go:build !with_purego - -package cronet - -// #include -// #include -// #include -// extern CRONET_EXPORT void cronetRunnableRun(Cronet_RunnablePtr self); -import "C" - -import ( - "sync" - "sync/atomic" - "unsafe" -) - -type runnableEntry struct { - runFunc RunnableRunFunc - destroyed atomic.Bool -} - -var ( - runnableAccess sync.RWMutex - runnableMap map[uintptr]*runnableEntry -) - -func init() { - runnableMap = make(map[uintptr]*runnableEntry) -} - -// NewRunnable creates a new Runnable with the given run function. -func NewRunnable(runFunc RunnableRunFunc) Runnable { - if runFunc == nil { - panic("nil runnable run function") - } - ptr := C.Cronet_Runnable_CreateWith((*[0]byte)(C.cronetRunnableRun)) - ptrVal := uintptr(unsafe.Pointer(ptr)) - runnableAccess.Lock() - runnableMap[ptrVal] = &runnableEntry{runFunc: runFunc} - runnableAccess.Unlock() - return Runnable{ptrVal} -} - -func (r Runnable) Destroy() { - runnableAccess.RLock() - entry := runnableMap[r.ptr] - runnableAccess.RUnlock() - if entry != nil { - entry.destroyed.Store(true) - } - C.Cronet_Runnable_Destroy(C.Cronet_RunnablePtr(unsafe.Pointer(r.ptr))) -} - -//export cronetRunnableRun -func cronetRunnableRun(self C.Cronet_RunnablePtr) { - ptr := uintptr(unsafe.Pointer(self)) - runnableAccess.RLock() - entry := runnableMap[ptr] - runnableAccess.RUnlock() - if entry == nil || entry.destroyed.Load() { - return // Post-destroy callback, silently ignore - } - entry.runFunc(Runnable{ptr}) - // Run is one-shot - safe to cleanup - runnableAccess.Lock() - delete(runnableMap, ptr) - runnableAccess.Unlock() -} diff --git a/runnable_impl_purego.go b/runnable_impl_purego.go deleted file mode 100644 index a1c4809a..00000000 --- a/runnable_impl_purego.go +++ /dev/null @@ -1,65 +0,0 @@ -//go:build with_purego - -package cronet - -import ( - "sync" - "sync/atomic" - - "github.com/sagernet/cronet-go/internal/cronet" - - "github.com/ebitengine/purego" -) - -type runnableEntry struct { - runFunc RunnableRunFunc - destroyed atomic.Bool -} - -var ( - runnableAccess sync.RWMutex - runnableMap map[uintptr]*runnableEntry - runnableCallbackFn uintptr -) - -func init() { - runnableMap = make(map[uintptr]*runnableEntry) - runnableCallbackFn = purego.NewCallback(runnableRunCallback) -} - -func runnableRunCallback(self uintptr) uintptr { - runnableAccess.RLock() - entry := runnableMap[self] - runnableAccess.RUnlock() - if entry == nil || entry.destroyed.Load() { - return 0 // Post-destroy callback, silently ignore - } - entry.runFunc(Runnable{self}) - // Run is one-shot - safe to cleanup - runnableAccess.Lock() - delete(runnableMap, self) - runnableAccess.Unlock() - return 0 -} - -// NewRunnable creates a new Runnable with the given run function. -func NewRunnable(runFunc RunnableRunFunc) Runnable { - if runFunc == nil { - panic("nil runnable run function") - } - ptr := cronet.RunnableCreateWith(runnableCallbackFn) - runnableAccess.Lock() - runnableMap[ptr] = &runnableEntry{runFunc: runFunc} - runnableAccess.Unlock() - return Runnable{ptr} -} - -func (r Runnable) Destroy() { - runnableAccess.RLock() - entry := runnableMap[r.ptr] - runnableAccess.RUnlock() - if entry != nil { - entry.destroyed.Store(true) - } - cronet.RunnableDestroy(r.ptr) -} diff --git a/runnable_purego.go b/runnable_purego.go deleted file mode 100644 index 67b0f68b..00000000 --- a/runnable_purego.go +++ /dev/null @@ -1,21 +0,0 @@ -//go:build with_purego - -package cronet - -import ( - "unsafe" - - "github.com/sagernet/cronet-go/internal/cronet" -) - -func (r Runnable) SetClientContext(context unsafe.Pointer) { - cronet.RunnableSetClientContext(r.ptr, uintptr(context)) -} - -func (r Runnable) ClientContext() unsafe.Pointer { - return unsafe.Pointer(cronet.RunnableGetClientContext(r.ptr)) -} - -func (r Runnable) Run() { - cronet.RunnableRun(r.ptr) -} diff --git a/socket_fd_unix_test.go b/socket_fd_unix_test.go deleted file mode 100644 index 9782b6f6..00000000 --- a/socket_fd_unix_test.go +++ /dev/null @@ -1,136 +0,0 @@ -//go:build unix - -package cronet - -import ( - "io" - "net" - "syscall" - "testing" - "time" -) - -func TestDupSocketFD(t *testing.T) { - // Create a local TCP server - listener, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatal(err) - } - defer listener.Close() - - serverDone := make(chan struct{}) - go func() { - defer close(serverDone) - conn, err := listener.Accept() - if err != nil { - return - } - defer conn.Close() - io.Copy(conn, conn) // echo server - }() - - // Dial the server - conn, err := net.Dial("tcp", listener.Addr().String()) - if err != nil { - t.Fatal(err) - } - - // Extract and duplicate the FD - tcpConn := conn.(*net.TCPConn) - fd, err := dupSocketFD(tcpConn) - if err != nil { - conn.Close() - t.Fatal(err) - } - - // Close the original connection - conn.Close() - - // Verify the duplicated FD is valid and usable - if fd < 0 { - t.Fatal("invalid fd returned") - } - - // Clean up - syscall.Close(fd) -} - -func TestDupSocketFD_InvalidConn(t *testing.T) { - // Test with a connection that doesn't support syscall.Conn - // This would require a mock - skip for now as it's hard to create - // a net.Conn that doesn't implement syscall.Conn - t.Skip("requires mock connection") -} - -func TestCreateSocketPair(t *testing.T) { - fd, conn, err := createSocketPair() - if err != nil { - t.Fatal(err) - } - defer syscall.Close(fd) - defer conn.Close() - - if fd < 0 { - t.Fatal("invalid fd returned") - } - - // Test bidirectional communication: fd -> conn - testData := []byte("hello from fd side") - done := make(chan error, 1) - go func() { - _, err := syscall.Write(fd, testData) - done <- err - }() - - buf := make([]byte, len(testData)) - conn.SetReadDeadline(time.Now().Add(5 * time.Second)) - _, err = io.ReadFull(conn, buf) - if err != nil { - t.Fatalf("failed to read from conn: %v", err) - } - if string(buf) != string(testData) { - t.Errorf("data mismatch: got %q, want %q", buf, testData) - } - - if err := <-done; err != nil { - t.Fatalf("failed to write to fd: %v", err) - } - - // Test bidirectional communication: conn -> fd - testData2 := []byte("hello from conn side") - go func() { - conn.Write(testData2) - }() - - buf2 := make([]byte, len(testData2)) - n, err := syscall.Read(fd, buf2) - if err != nil { - t.Fatalf("failed to read from fd: %v", err) - } - if n != len(testData2) || string(buf2[:n]) != string(testData2) { - t.Errorf("data mismatch: got %q, want %q", buf2[:n], testData2) - } -} - -func TestCreateSocketPair_MultipleCreation(t *testing.T) { - // Test that we can create multiple socket pairs - pairs := make([]struct { - fd int - conn net.Conn - }, 5) - - for i := range pairs { - fd, conn, err := createSocketPair() - if err != nil { - t.Fatalf("failed to create socket pair %d: %v", i, err) - } - pairs[i].fd = fd - pairs[i].conn = conn - } - - // Clean up - for _, pair := range pairs { - syscall.Close(pair.fd) - pair.conn.Close() - } -} diff --git a/socket_fd_windows_test.go b/socket_fd_windows_test.go deleted file mode 100644 index fb4d0d3d..00000000 --- a/socket_fd_windows_test.go +++ /dev/null @@ -1,166 +0,0 @@ -//go:build windows - -package cronet - -import ( - "io" - "net" - "testing" - "time" - "unsafe" - - "golang.org/x/sys/windows" -) - -var ( - modws2_32 = windows.NewLazySystemDLL("ws2_32.dll") - - procSend = modws2_32.NewProc("send") - procRecv = modws2_32.NewProc("recv") -) - -func winsockSend(socket windows.Handle, buf []byte) (int, error) { - r1, _, err := procSend.Call( - uintptr(socket), - uintptr(unsafe.Pointer(&buf[0])), - uintptr(len(buf)), - 0, - ) - n := int(r1) - if n == -1 { - return 0, err - } - return n, nil -} - -func winsockRecv(socket windows.Handle, buf []byte) (int, error) { - r1, _, err := procRecv.Call( - uintptr(socket), - uintptr(unsafe.Pointer(&buf[0])), - uintptr(len(buf)), - 0, - ) - n := int(r1) - if n == -1 { - return 0, err - } - return n, nil -} - -func TestDupSocketFD(t *testing.T) { - // Create a local TCP server - listener, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatal(err) - } - defer listener.Close() - - serverDone := make(chan struct{}) - go func() { - defer close(serverDone) - conn, err := listener.Accept() - if err != nil { - return - } - defer conn.Close() - io.Copy(conn, conn) // echo server - }() - - // Dial the server - conn, err := net.Dial("tcp", listener.Addr().String()) - if err != nil { - t.Fatal(err) - } - - // Extract and duplicate the handle - tcpConn := conn.(*net.TCPConn) - fd, err := dupSocketFD(tcpConn) - if err != nil { - conn.Close() - t.Fatal(err) - } - - // Close the original connection - conn.Close() - - // Verify the duplicated handle is valid - if fd < 0 { - t.Fatal("invalid handle returned") - } - - // Clean up - _ = windows.Closesocket(windows.Handle(fd)) -} - -func TestCreateSocketPair(t *testing.T) { - fd, conn, err := createSocketPair() - if err != nil { - t.Fatal(err) - } - defer windows.Closesocket(windows.Handle(fd)) - defer conn.Close() - - if fd < 0 { - t.Fatal("invalid handle returned") - } - - // Test bidirectional communication: fd (socket handle) -> conn - testData := []byte("hello from fd side") - done := make(chan error, 1) - go func() { - _, err := winsockSend(windows.Handle(fd), testData) - done <- err - }() - - buf := make([]byte, len(testData)) - conn.SetReadDeadline(time.Now().Add(5 * time.Second)) - _, err = io.ReadFull(conn, buf) - if err != nil { - t.Fatalf("failed to read from conn: %v", err) - } - if string(buf) != string(testData) { - t.Errorf("data mismatch: got %q, want %q", buf, testData) - } - - if err := <-done; err != nil { - t.Fatalf("failed to write to fd: %v", err) - } - - // Test bidirectional communication: conn -> fd - testData2 := []byte("hello from conn side") - go func() { - conn.Write(testData2) - }() - - buf2 := make([]byte, len(testData2)) - n, err := winsockRecv(windows.Handle(fd), buf2) - if err != nil { - t.Fatalf("failed to read from fd: %v", err) - } - if n != len(testData2) || string(buf2[:n]) != string(testData2) { - t.Errorf("data mismatch: got %q, want %q", buf2[:n], testData2) - } -} - -func TestCreateSocketPair_MultipleCreation(t *testing.T) { - // Test that we can create multiple socket pairs - pairs := make([]struct { - fd int - conn net.Conn - }, 5) - - for i := range pairs { - fd, conn, err := createSocketPair() - if err != nil { - t.Fatalf("failed to create socket pair %d: %v", i, err) - } - pairs[i].fd = fd - pairs[i].conn = conn - } - - // Clean up - for _, pair := range pairs { - _ = windows.Closesocket(windows.Handle(pair.fd)) - pair.conn.Close() - } -} diff --git a/test/.gitignore b/test/.gitignore deleted file mode 100644 index ae3c1726..00000000 --- a/test/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/bin/ diff --git a/test/artifact_test.go b/test/artifact_test.go deleted file mode 100644 index f88e7408..00000000 --- a/test/artifact_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package test - -import ( - "os" - "path/filepath" - "strings" - "testing" - "unicode" - - cronet "github.com/sagernet/cronet-go" - - "github.com/stretchr/testify/require" -) - -const ( - testArtifactDirEnv = "CRONET_TEST_ARTIFACT_DIR" - testCaptureNetLogEnv = "CRONET_TEST_CAPTURE_NETLOG" -) - -func artifactDir(t *testing.T, category string) string { - t.Helper() - - baseDir := t.TempDir() - if configuredDir := strings.TrimSpace(os.Getenv(testArtifactDirEnv)); configuredDir != "" { - baseDir = filepath.Join(configuredDir, sanitizeArtifactPathSegment(t.Name())) - } - if category != "" { - baseDir = filepath.Join(baseDir, category) - } - - err := os.MkdirAll(baseDir, 0o755) - require.NoError(t, err) - return baseDir -} - -func artifactPath(t *testing.T, category, fileName string) string { - t.Helper() - return filepath.Join(artifactDir(t, category), fileName) -} - -func createArtifactTempFile(t *testing.T, category, pattern string) (*os.File, string) { - t.Helper() - - file, err := os.CreateTemp(artifactDir(t, category), pattern) - require.NoError(t, err) - return file, file.Name() -} - -func startNetLogForTest(t *testing.T, client *cronet.NaiveClient, fileName string, required bool) string { - t.Helper() - - if !required && !shouldCaptureNetLog() { - return "" - } - - netLogPath := artifactPath(t, "netlog", fileName) - started := client.Engine().StartNetLogToFile(netLogPath, true) - if required { - require.True(t, started, "failed to start NetLog") - } else if !started { - t.Logf("failed to start optional NetLog at %s", netLogPath) - return "" - } - - t.Cleanup(func() { - client.Engine().StopNetLog() - }) - return netLogPath -} - -func shouldCaptureNetLog() bool { - value := strings.ToLower(strings.TrimSpace(os.Getenv(testCaptureNetLogEnv))) - switch value { - case "1", "true", "yes", "on": - return true - default: - return false - } -} - -func sanitizeArtifactPathSegment(value string) string { - sanitized := strings.Map(func(r rune) rune { - switch { - case unicode.IsLetter(r), unicode.IsDigit(r): - return r - case r == '-', r == '_', r == '.': - return r - default: - return '_' - } - }, value) - sanitized = strings.Trim(sanitized, "._") - if sanitized == "" { - return "test" - } - return sanitized -} diff --git a/test/config/sing-box-quic.json b/test/config/sing-box-quic.json deleted file mode 100644 index 8fb20c3b..00000000 --- a/test/config/sing-box-quic.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "inbounds": [ - { - "type": "naive", - "listen": "::", - "listen_port": 10002, - "network": "udp", - "users": [ - { - "username": "test", - "password": "test" - } - ], - "tls": { - "enabled": true, - "certificate_path": "/cert.pem", - "key_path": "/key.pem" - } - } - ] -} diff --git a/test/config/sing-box.json b/test/config/sing-box.json deleted file mode 100644 index 9d3580ca..00000000 --- a/test/config/sing-box.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "inbounds": [ - { - "type": "naive", - "listen": "::", - "listen_port": 10443, - "users": [ - { - "username": "test", - "password": "test" - } - ], - "tls": { - "enabled": true, - "certificate_path": "/cert.pem", - "key_path": "/key.pem" - } - } - ] -} diff --git a/test/ech_test.go b/test/ech_test.go deleted file mode 100644 index 0a7f8844..00000000 --- a/test/ech_test.go +++ /dev/null @@ -1,250 +0,0 @@ -package test - -import ( - "context" - "crypto/ecdh" - "crypto/rand" - "encoding/pem" - "fmt" - "io" - "net" - "os" - "path/filepath" - "strings" - "testing" - - cronet "github.com/sagernet/cronet-go" - M "github.com/sagernet/sing/common/metadata" - - mDNS "github.com/miekg/dns" - "github.com/stretchr/testify/require" - "golang.org/x/crypto/cryptobyte" -) - -// TestNaiveECH verifies that ECH (Encrypted Client Hello) works with NaiveClient. -// It starts a sing-box naive server with ECH enabled, then connects using -// NaiveClient with ECH config injected via DNS HTTPS records. -func TestNaiveECH(t *testing.T) { - echServerPort := reserveTCPPort(t) - echEchoPort := reserveTCPPort(t) - - // Generate ECH config and key - echConfigPEM, echKeyPEM, err := echKeygenDefault("not.example.org") - require.NoError(t, err) - - // Decode ECH config from PEM to wire format - echConfigBlock, _ := pem.Decode([]byte(echConfigPEM)) - require.NotNil(t, echConfigBlock, "failed to decode ECH config PEM") - echConfigWire := echConfigBlock.Bytes - - // Generate certificates - caPEM, certPath, keyPath := generateCertificate(t, "example.org") - caPEMContent, err := os.ReadFile(caPEM) - require.NoError(t, err) - - // Start naive server with ECH - startNaiveServerWithECH(t, certPath, keyPath, echKeyPEM, echServerPort) - - // Start echo server - startEchoServer(t, echEchoPort) - - // Create DNS resolver that returns localhost and handles HTTPS queries - dnsResolver := func(ctx context.Context, request *mDNS.Msg) *mDNS.Msg { - t.Logf("DNS resolver called: %v", request.Question) - response := new(mDNS.Msg) - response.SetReply(request) - for _, question := range request.Question { - switch question.Qtype { - case mDNS.TypeA: - t.Logf("Resolving %s to 127.0.0.1", question.Name) - response.Answer = append(response.Answer, &mDNS.A{ - Hdr: mDNS.RR_Header{ - Name: question.Name, - Rrtype: mDNS.TypeA, - Class: mDNS.ClassINET, - Ttl: 300, - }, - A: net.ParseIP("127.0.0.1"), - }) - case mDNS.TypeAAAA: - t.Logf("Resolving %s to ::1", question.Name) - response.Answer = append(response.Answer, &mDNS.AAAA{ - Hdr: mDNS.RR_Header{ - Name: question.Name, - Rrtype: mDNS.TypeAAAA, - Class: mDNS.ClassINET, - Ttl: 300, - }, - AAAA: net.ParseIP("::1"), - }) - case mDNS.TypeHTTPS: - t.Logf("HTTPS query for %s - will be handled by ECH wrapper", question.Name) - // The ECH config will be injected by wrapDNSResolverWithECH - } - } - return response - } - - // Create NaiveClient with ECH config - client, err := cronet.NewNaiveClient(cronet.NaiveClientOptions{ - ServerAddress: M.ParseSocksaddrHostPort("127.0.0.1", echServerPort), - ServerName: "example.org", - Username: "test", - Password: "test", - TrustedRootCertificates: string(caPEMContent), - DNSResolver: dnsResolver, - ECHEnabled: true, - ECHConfigList: echConfigWire, - }) - require.NoError(t, err) - require.NoError(t, client.Start()) - t.Cleanup(func() { client.Close() }) - - // Start NetLog to capture TLS handshake details - netLogPath := startNetLogForTest(t, client, "ech_netlog.json", true) - defer client.Engine().StopNetLog() - - // Make a connection - conn, err := client.DialEarly(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", echEchoPort)) - require.NoError(t, err) - defer conn.Close() - - // Test data transfer - testData := []byte("ECH test data!") - _, err = conn.Write(testData) - require.NoError(t, err) - - buf := make([]byte, len(testData)) - _, err = io.ReadFull(conn, buf) - require.NoError(t, err) - require.Equal(t, testData, buf) - - // Stop NetLog and verify ECH was used - client.Engine().StopNetLog() - - logContent, err := os.ReadFile(netLogPath) - require.NoError(t, err) - logStr := string(logContent) - - // Check that ECH was accepted in TLS handshake - // The NetLog should contain "encrypted_client_hello":true when ECH is used - require.True(t, strings.Contains(logStr, `"encrypted_client_hello":true`), - "ECH should be accepted in TLS handshake. NetLog saved to: %s", netLogPath) -} - -// startNaiveServerWithECH starts a sing-box naive server with ECH enabled. -func startNaiveServerWithECH(t *testing.T, certPath, keyPath, echKey string, port uint16) { - binary := ensureNaiveServer(t) - - // Create ECH config directory and write key file - echKeyPath := filepath.Join(t.TempDir(), "ech_key.pem") - err := os.WriteFile(echKeyPath, []byte(echKey), 0o644) - require.NoError(t, err) - - // Use forward slashes for JSON compatibility - certPath = filepath.ToSlash(certPath) - keyPath = filepath.ToSlash(keyPath) - echKeyPath = filepath.ToSlash(echKeyPath) - - // Create config with ECH enabled - config := fmt.Sprintf(`{ - "inbounds": [ - { - "type": "naive", - "listen": "::", - "listen_port": %d, - "users": [ - { - "username": "test", - "password": "test" - } - ], - "tls": { - "enabled": true, - "certificate_path": "%s", - "key_path": "%s", - "ech": { - "enabled": true, - "key_path": "%s" - } - } - } - ] -}`, port, certPath, keyPath, echKeyPath) - - configPath := filepath.Join(t.TempDir(), "sing-box-ech.json") - err = os.WriteFile(configPath, []byte(config), 0o644) - require.NoError(t, err) - - startNaiveServerWithConfig(t, binary, configPath, port, "tcp") -} - -// echKeygenDefault generates ECH config and key in PEM format. -// Ported from sing-box/common/tls/ech_shared.go. -func echKeygenDefault(publicName string) (configPEM string, keyPEM string, err error) { - echKey, err := ecdh.X25519().GenerateKey(rand.Reader) - if err != nil { - return - } - echConfig, err := marshalECHConfig(0, echKey.PublicKey().Bytes(), publicName, 0) - if err != nil { - return - } - configBuilder := cryptobyte.NewBuilder(nil) - configBuilder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) { - builder.AddBytes(echConfig) - }) - configBytes, err := configBuilder.Bytes() - if err != nil { - return - } - keyBuilder := cryptobyte.NewBuilder(nil) - keyBuilder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) { - builder.AddBytes(echKey.Bytes()) - }) - keyBuilder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) { - builder.AddBytes(echConfig) - }) - keyBytes, err := keyBuilder.Bytes() - if err != nil { - return - } - configPEM = string(pem.EncodeToMemory(&pem.Block{Type: "ECH CONFIGS", Bytes: configBytes})) - keyPEM = string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBytes})) - return -} - -// marshalECHConfig creates an ECH config in wire format. -// Ported from sing-box/common/tls/ech_shared.go. -func marshalECHConfig(id uint8, pubKey []byte, publicName string, maxNameLen uint8) ([]byte, error) { - const extensionEncryptedClientHello = 0xfe0d - const dhkemX25519HKDFSHA256 = 0x0020 - const kdfHKDFSHA256 = 0x0001 - builder := cryptobyte.NewBuilder(nil) - builder.AddUint16(extensionEncryptedClientHello) - builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) { - builder.AddUint8(id) - - builder.AddUint16(dhkemX25519HKDFSHA256) // The only DHKEM we support - builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) { - builder.AddBytes(pubKey) - }) - builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) { - const ( - aeadAES128GCM = 0x0001 - aeadAES256GCM = 0x0002 - aeadChaCha20Poly05 = 0x0003 - ) - for _, aeadID := range []uint16{aeadAES128GCM, aeadAES256GCM, aeadChaCha20Poly05} { - builder.AddUint16(kdfHKDFSHA256) // The only KDF we support - builder.AddUint16(aeadID) - } - }) - builder.AddUint8(maxNameLen) - builder.AddUint8LengthPrefixed(func(builder *cryptobyte.Builder) { - builder.AddBytes([]byte(publicName)) - }) - builder.AddUint16(0) // extensions - }) - return builder.Bytes() -} diff --git a/test/experimental_options_test.go b/test/experimental_options_test.go deleted file mode 100644 index 8834ed8a..00000000 --- a/test/experimental_options_test.go +++ /dev/null @@ -1,363 +0,0 @@ -package test - -import ( - "context" - "encoding/json" - "fmt" - "io" - "os" - "testing" - - cronet "github.com/sagernet/cronet-go" - M "github.com/sagernet/sing/common/metadata" - - "github.com/stretchr/testify/require" -) - -type netLogData struct { - Constants struct { - LogEventTypes map[string]int `json:"logEventTypes"` - } `json:"constants"` - Events []struct { - Type int `json:"type"` - Params json.RawMessage `json:"params"` - } `json:"events"` -} - -func parseQUICTransportParametersSent(t *testing.T, logContent []byte) string { - t.Helper() - var netLog netLogData - err := json.Unmarshal(logContent, &netLog) - require.NoError(t, err) - - eventType, ok := netLog.Constants.LogEventTypes["QUIC_SESSION_TRANSPORT_PARAMETERS_SENT"] - require.True(t, ok, "QUIC_SESSION_TRANSPORT_PARAMETERS_SENT not in netlog constants") - - for _, event := range netLog.Events { - if event.Type == eventType { - var params struct { - QuicTransportParameters string `json:"quic_transport_parameters"` - } - err := json.Unmarshal(event.Params, ¶ms) - require.NoError(t, err) - if params.QuicTransportParameters != "" { - return params.QuicTransportParameters - } - } - } - t.Fatal("QUIC_SESSION_TRANSPORT_PARAMETERS_SENT event not found in netlog") - return "" -} - -func TestHTTP2Options(t *testing.T) { - const ( - sessionMaxReceiveWindowSize = 134217728 - streamInitialWindowSize = 67108864 - defaultHTTP2InitialWindowSize = 65535 - ) - - env := setupTestEnv(t) - client := env.newNaiveClient(t, cronet.NaiveClientOptions{ - DNSResolver: localhostDNSResolver(t), - }) - - startEchoServer(t, 18100) - - netLogPath := startNetLogForTest(t, client, "http2_options_netlog.json", true) - - conn, err := client.DialEarly(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", 18100)) - require.NoError(t, err) - - testData := []byte("hello") - _, err = conn.Write(testData) - require.NoError(t, err) - - buffer := make([]byte, len(testData)) - _, err = io.ReadFull(conn, buffer) - require.NoError(t, err) - require.Equal(t, testData, buffer) - - conn.Close() - client.Engine().StopNetLog() - - logContent, err := os.ReadFile(netLogPath) - require.NoError(t, err) - logString := string(logContent) - - var netLog struct { - Constants struct { - LogEventTypes map[string]int `json:"logEventTypes"` - } `json:"constants"` - Events []struct { - Type int `json:"type"` - Params json.RawMessage `json:"params"` - } `json:"events"` - } - err = json.Unmarshal(logContent, &netLog) - require.NoError(t, err) - - // HTTP2_SESSION_SEND_SETTINGS should contain SETTINGS_INITIAL_WINDOW_SIZE = - // 67108864 (64 MB, naive default). - sendSettingsType, ok := netLog.Constants.LogEventTypes["HTTP2_SESSION_SEND_SETTINGS"] - require.True(t, ok, "expected HTTP2_SESSION_SEND_SETTINGS in NetLog constants") - sendSettingsFound := false - for _, event := range netLog.Events { - if event.Type == sendSettingsType { - sendSettingsFound = true - break - } - } - require.True(t, sendSettingsFound, "expected HTTP2_SESSION_SEND_SETTINGS event in netlog") - require.Contains(t, logString, - fmt.Sprintf("SETTINGS_INITIAL_WINDOW_SIZE) value:%d]", streamInitialWindowSize), - "expected SETTINGS_INITIAL_WINDOW_SIZE = 64 MB in HTTP/2 SETTINGS") - - // HTTP2_SESSION_SEND_WINDOW_UPDATE with stream_id 0 should carry - // delta = session_max_recv_window_size - default_initial_window_size. - sendWindowUpdateType, ok := netLog.Constants.LogEventTypes["HTTP2_SESSION_SEND_WINDOW_UPDATE"] - require.True(t, ok, "expected HTTP2_SESSION_SEND_WINDOW_UPDATE in NetLog constants") - expectedSessionWindowDelta := sessionMaxReceiveWindowSize - defaultHTTP2InitialWindowSize - sessionWindowUpdateFound := false - for _, event := range netLog.Events { - if event.Type != sendWindowUpdateType { - continue - } - - var params struct { - StreamID *int `json:"stream_id"` - Delta *int `json:"delta"` - } - err = json.Unmarshal(event.Params, ¶ms) - require.NoError(t, err) - - if params.StreamID != nil && - params.Delta != nil && - *params.StreamID == 0 && - *params.Delta == expectedSessionWindowDelta { - sessionWindowUpdateFound = true - break - } - } - require.True(t, sessionWindowUpdateFound, - "expected session WINDOW_UPDATE event with stream_id=0 and delta=%d", expectedSessionWindowDelta) -} - -func TestSocketPoolOptions(t *testing.T) { - params := cronet.NewEngineParams() - defer params.Destroy() - - err := params.SetSocketPoolOptions(2048, 2048, 2040) - require.NoError(t, err) - - options := params.ExperimentalOptions() - require.Contains(t, options, `"SocketPoolOptions"`, - "expected SocketPoolOptions key, got: %s", options) - require.Contains(t, options, `"max_sockets_per_pool":2048`, - "expected max_sockets_per_pool in options, got: %s", options) - require.Contains(t, options, `"max_sockets_per_proxy_chain":2048`, - "expected max_sockets_per_proxy_chain in options, got: %s", options) - require.Contains(t, options, `"max_sockets_per_group":2040`, - "expected max_sockets_per_group in options, got: %s", options) -} - -func TestQUICReceiveWindowCustomValues(t *testing.T) { - naiveQUICServerPort := reserveUDPPort(t) - caPem, certPem, keyPem := generateCertificate(t, "example.org") - caPemContent, err := os.ReadFile(caPem) - require.NoError(t, err) - - startNaiveQUICServer(t, certPem, keyPem, naiveQUICServerPort) - - const ( - customStreamWindow = 4 * 1024 * 1024 - customSessionWindow = 10 * 1024 * 1024 - ) - - client, err := cronet.NewNaiveClient(cronet.NaiveClientOptions{ - ServerAddress: M.ParseSocksaddrHostPort("127.0.0.1", naiveQUICServerPort), - ServerName: "example.org", - Username: "test", - Password: "test", - TrustedRootCertificates: string(caPemContent), - DNSResolver: localhostDNSResolverWithHTTPSResponse(t, naiveQUICServerPort, []string{"h3"}), - QUIC: true, - ReceiveWindow: customStreamWindow, - QUICSessionReceiveWindow: customSessionWindow, - }) - require.NoError(t, err) - require.NoError(t, client.Start()) - t.Cleanup(func() { client.Close() }) - - echoPort := reserveTCPPort(t) - startEchoServer(t, echoPort) - - netLogPath := startNetLogForTest(t, client, "quic_recv_window_custom.json", true) - - conn, err := client.DialEarly(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", echoPort)) - require.NoError(t, err) - - testData := []byte("quic custom window test") - _, err = conn.Write(testData) - require.NoError(t, err) - buf := make([]byte, len(testData)) - _, err = io.ReadFull(conn, buf) - require.NoError(t, err) - require.Equal(t, testData, buf) - - conn.Close() - client.Engine().StopNetLog() - - logContent, err := os.ReadFile(netLogPath) - require.NoError(t, err) - - tp := parseQUICTransportParametersSent(t, logContent) - require.Contains(t, tp, fmt.Sprintf("initial_max_data %d", customSessionWindow), - "expected session receive window in transport parameters") - require.Contains(t, tp, fmt.Sprintf("initial_max_stream_data_bidi_local %d", customStreamWindow), - "expected stream receive window in transport parameters") -} - -func TestQUICReceiveWindowDefaults(t *testing.T) { - naiveQUICServerPort := reserveUDPPort(t) - caPem, certPem, keyPem := generateCertificate(t, "example.org") - caPemContent, err := os.ReadFile(caPem) - require.NoError(t, err) - - startNaiveQUICServer(t, certPem, keyPem, naiveQUICServerPort) - - const ( - defaultStreamWindow = 8 * 1024 * 1024 - defaultSessionWindow = 20 * 1024 * 1024 - ) - - client, err := cronet.NewNaiveClient(cronet.NaiveClientOptions{ - ServerAddress: M.ParseSocksaddrHostPort("127.0.0.1", naiveQUICServerPort), - ServerName: "example.org", - Username: "test", - Password: "test", - TrustedRootCertificates: string(caPemContent), - DNSResolver: localhostDNSResolverWithHTTPSResponse(t, naiveQUICServerPort, []string{"h3"}), - QUIC: true, - }) - require.NoError(t, err) - require.NoError(t, client.Start()) - t.Cleanup(func() { client.Close() }) - - echoPort := reserveTCPPort(t) - startEchoServer(t, echoPort) - - netLogPath := startNetLogForTest(t, client, "quic_recv_window_defaults.json", true) - - conn, err := client.DialEarly(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", echoPort)) - require.NoError(t, err) - - testData := []byte("quic default window test") - _, err = conn.Write(testData) - require.NoError(t, err) - buf := make([]byte, len(testData)) - _, err = io.ReadFull(conn, buf) - require.NoError(t, err) - require.Equal(t, testData, buf) - - conn.Close() - client.Engine().StopNetLog() - - logContent, err := os.ReadFile(netLogPath) - require.NoError(t, err) - - tp := parseQUICTransportParametersSent(t, logContent) - require.Contains(t, tp, fmt.Sprintf("initial_max_data %d", defaultSessionWindow), - "expected default session receive window in transport parameters") - require.Contains(t, tp, fmt.Sprintf("initial_max_stream_data_bidi_local %d", defaultStreamWindow), - "expected default stream receive window in transport parameters") -} - -func TestQUICInsecureConcurrencyRejected(t *testing.T) { - naiveQUICServerPort := reserveUDPPort(t) - _, err := cronet.NewNaiveClient(cronet.NaiveClientOptions{ - ServerAddress: M.ParseSocksaddrHostPort("127.0.0.1", naiveQUICServerPort), - DNSResolver: localhostDNSResolver(t), - QUIC: true, - InsecureConcurrency: 2, - }) - require.Error(t, err) - require.Contains(t, err.Error(), "insecure concurrency is not supported with QUIC") - - client, err := cronet.NewNaiveClient(cronet.NaiveClientOptions{ - ServerAddress: M.ParseSocksaddrHostPort("127.0.0.1", naiveQUICServerPort), - DNSResolver: localhostDNSResolver(t), - QUIC: true, - InsecureConcurrency: 1, - }) - require.NoError(t, err) - _ = client -} - -func TestHTTP2StreamReceiveWindowCustom(t *testing.T) { - const ( - customStreamWindow = 32 * 1024 * 1024 - expectedInitialWindowSize = customStreamWindow / 2 - expectedSessionMaxRecvWindow = customStreamWindow - defaultHTTP2InitialWindowSize = 65535 - ) - - env := setupTestEnv(t) - client := env.newNaiveClient(t, cronet.NaiveClientOptions{ - DNSResolver: localhostDNSResolver(t), - ReceiveWindow: customStreamWindow, - }) - - startEchoServer(t, 18302) - - netLogPath := startNetLogForTest(t, client, "http2_custom_stream_window.json", true) - - conn, err := client.DialEarly(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", 18302)) - require.NoError(t, err) - - testData := []byte("http2 custom window") - _, err = conn.Write(testData) - require.NoError(t, err) - buf := make([]byte, len(testData)) - _, err = io.ReadFull(conn, buf) - require.NoError(t, err) - require.Equal(t, testData, buf) - - conn.Close() - client.Engine().StopNetLog() - - logContent, err := os.ReadFile(netLogPath) - require.NoError(t, err) - logString := string(logContent) - - require.Contains(t, logString, - fmt.Sprintf("SETTINGS_INITIAL_WINDOW_SIZE) value:%d]", expectedInitialWindowSize), - "expected SETTINGS_INITIAL_WINDOW_SIZE = 16 MB") - - var netLog netLogData - err = json.Unmarshal(logContent, &netLog) - require.NoError(t, err) - - sendWindowUpdateType, ok := netLog.Constants.LogEventTypes["HTTP2_SESSION_SEND_WINDOW_UPDATE"] - require.True(t, ok) - - expectedDelta := expectedSessionMaxRecvWindow - defaultHTTP2InitialWindowSize - found := false - for _, event := range netLog.Events { - if event.Type != sendWindowUpdateType { - continue - } - var params struct { - StreamID *int `json:"stream_id"` - Delta *int `json:"delta"` - } - err = json.Unmarshal(event.Params, ¶ms) - require.NoError(t, err) - if params.StreamID != nil && params.Delta != nil && - *params.StreamID == 0 && *params.Delta == expectedDelta { - found = true - break - } - } - require.True(t, found, - "expected session WINDOW_UPDATE with stream_id=0 and delta=%d", expectedDelta) -} diff --git a/test/go.mod b/test/go.mod deleted file mode 100644 index a476aa6e..00000000 --- a/test/go.mod +++ /dev/null @@ -1,29 +0,0 @@ -module github.com/sagernet/cronet-go/test - -go 1.24.0 - -require ( - github.com/miekg/dns v1.1.68 - github.com/sagernet/cronet-go v0.0.0 - github.com/sagernet/sing v0.7.13 - github.com/stretchr/testify v1.11.1 - go.uber.org/goleak v1.3.0 - golang.org/x/crypto v0.38.0 -) - -require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/ebitengine/purego v0.9.1 // indirect - github.com/kr/pretty v0.3.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rogpeppe/go-internal v1.14.1 // indirect - golang.org/x/mod v0.24.0 // indirect - golang.org/x/net v0.40.0 // indirect - golang.org/x/sync v0.14.0 // indirect - golang.org/x/sys v0.39.0 // indirect - golang.org/x/tools v0.33.0 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) - -replace github.com/sagernet/cronet-go => ../ diff --git a/test/go.sum b/test/go.sum deleted file mode 100644 index a614abb1..00000000 --- a/test/go.sum +++ /dev/null @@ -1,45 +0,0 @@ -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -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/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= -github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -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/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA= -github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -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/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -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/sagernet/sing v0.7.13 h1:XNYgd8e3cxMULs/LLJspdn/deHrnPWyrrglNHeCUAYM= -github.com/sagernet/sing v0.7.13/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= -golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= -golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= -golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= -golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= -golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= -golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= -golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= -golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/integration_test.go b/test/integration_test.go deleted file mode 100644 index c00846d0..00000000 --- a/test/integration_test.go +++ /dev/null @@ -1,1028 +0,0 @@ -package test - -import ( - "bytes" - "context" - "crypto/rand" - "encoding/json" - "errors" - "fmt" - "io" - "net" - "os" - "strings" - "sync" - "sync/atomic" - "syscall" - "testing" - "time" - - cronet "github.com/sagernet/cronet-go" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" - - mDNS "github.com/miekg/dns" - "github.com/stretchr/testify/require" -) - -// localhostDNSResolver returns a DNS resolver that resolves all A/AAAA queries to 127.0.0.1. -func localhostDNSResolver(t *testing.T) cronet.DNSResolverFunc { - return func(ctx context.Context, request *mDNS.Msg) *mDNS.Msg { - t.Logf("DNS resolver called for: %v", request.Question) - response := new(mDNS.Msg) - response.SetReply(request) - for _, question := range request.Question { - switch question.Qtype { - case mDNS.TypeA: - t.Logf("Resolving %s to 127.0.0.1", question.Name) - response.Answer = append(response.Answer, &mDNS.A{ - Hdr: mDNS.RR_Header{ - Name: question.Name, - Rrtype: mDNS.TypeA, - Class: mDNS.ClassINET, - Ttl: 300, - }, - A: net.ParseIP("127.0.0.1"), - }) - case mDNS.TypeAAAA: - t.Logf("Resolving %s to ::1", question.Name) - response.Answer = append(response.Answer, &mDNS.AAAA{ - Hdr: mDNS.RR_Header{ - Name: question.Name, - Rrtype: mDNS.TypeAAAA, - Class: mDNS.ClassINET, - Ttl: 300, - }, - AAAA: net.ParseIP("::1"), - }) - } - } - return response - } -} - -func localhostDNSResolverWithHTTPSResponse(t *testing.T, servicePort uint16, applicationProtocols []string) cronet.DNSResolverFunc { - baseResolver := localhostDNSResolver(t) - return func(ctx context.Context, request *mDNS.Msg) *mDNS.Msg { - response := baseResolver(ctx, request) - if len(request.Question) == 0 { - return response - } - question := request.Question[0] - if question.Qtype != mDNS.TypeHTTPS { - return response - } - if response == nil { - response = new(mDNS.Msg) - response.SetReply(request) - } - targetName := question.Name - if strings.HasPrefix(targetName, "_") { - parts := strings.SplitN(targetName, "._https.", 2) - if len(parts) == 2 { - targetName = parts[1] - } - } - httpsRecord := &mDNS.HTTPS{ - SVCB: mDNS.SVCB{ - Hdr: mDNS.RR_Header{ - Name: question.Name, - Rrtype: mDNS.TypeHTTPS, - Class: mDNS.ClassINET, - Ttl: 300, - }, - Priority: 1, - Target: targetName, - }, - } - if servicePort != 0 { - httpsRecord.Value = append(httpsRecord.Value, &mDNS.SVCBPort{Port: servicePort}) - } - if len(applicationProtocols) > 0 { - httpsRecord.Value = append(httpsRecord.Value, &mDNS.SVCBAlpn{Alpn: applicationProtocols}) - } - httpsRecord.Value = append(httpsRecord.Value, &mDNS.SVCBIPv4Hint{Hint: []net.IP{net.ParseIP("127.0.0.1")}}) - httpsRecord.Value = append(httpsRecord.Value, &mDNS.SVCBIPv6Hint{Hint: []net.IP{net.ParseIP("::1")}}) - response.Answer = append(response.Answer, httpsRecord) - return response - } -} - -// wrappedConn wraps a net.Conn but is NOT a *net.TCPConn, -// forcing the fallback path (socket pair proxy) in NaiveClient. -type wrappedConn struct { - net.Conn -} - -func (w *wrappedConn) SyscallConn() (syscall.RawConn, error) { - if conn, ok := w.Conn.(syscall.Conn); ok { - return conn.SyscallConn() - } - return nil, syscall.EINVAL -} - -// trackingDialer tracks dial calls and can wrap connections. -type trackingDialer struct { - underlying N.Dialer - dialCount atomic.Int64 - wrapConn bool // if true, wrap connections to force fallback path -} - -func (d *trackingDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - d.dialCount.Add(1) - conn, err := d.underlying.DialContext(ctx, network, destination) - if err != nil { - return nil, err - } - if d.wrapConn { - return &wrappedConn{Conn: conn}, nil - } - return conn, nil -} - -func (d *trackingDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - return d.underlying.ListenPacket(ctx, destination) -} - -// errorDialer always returns errors for testing error handling. -type errorDialer struct { - err error -} - -func (d *errorDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - return nil, d.err -} - -func (d *errorDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - return nil, d.err -} - -// TestNaiveCustomDialer verifies that a custom dialer is properly used. -func TestNaiveCustomDialer(t *testing.T) { - env := setupTestEnv(t) - startEchoServer(t, 17000) - - dialer := &trackingDialer{ - underlying: N.SystemDialer, - wrapConn: false, - } - - client := env.newNaiveClient(t, cronet.NaiveClientOptions{ - Dialer: dialer, - DNSResolver: localhostDNSResolver(t), - }) - - // Make a connection - conn, err := client.DialEarly(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", 17000)) - require.NoError(t, err) - defer conn.Close() - - // Verify connection works (this triggers the actual TCP connection) - testData := []byte("Custom dialer test!") - _, err = conn.Write(testData) - require.NoError(t, err) - - buf := make([]byte, len(testData)) - _, err = io.ReadFull(conn, buf) - require.NoError(t, err) - require.Equal(t, testData, buf) - - // Verify dialer was called (check after I/O since connection is established lazily) - require.Greater(t, dialer.dialCount.Load(), int64(0), "custom dialer should have been called") -} - -// TestNaivePipeProxy tests the socket pair proxy fallback path. -// This is triggered when the connection is not a *net.TCPConn. -func TestNaivePipeProxy(t *testing.T) { - env := setupTestEnv(t) - startEchoServer(t, 17001) - - dialer := &trackingDialer{ - underlying: N.SystemDialer, - wrapConn: true, // Force wrapped connections to trigger fallback path - } - - client := env.newNaiveClient(t, cronet.NaiveClientOptions{ - Dialer: dialer, - DNSResolver: localhostDNSResolver(t), - }) - - // Make a connection through the proxy path - conn, err := client.DialEarly(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", 17001)) - require.NoError(t, err) - defer conn.Close() - - // Verify data transfer works through the proxy path - testData := []byte("Pipe proxy test data!") - _, err = conn.Write(testData) - require.NoError(t, err) - - buf := make([]byte, len(testData)) - _, err = io.ReadFull(conn, buf) - require.NoError(t, err) - require.Equal(t, testData, buf) - - // Test multiple round trips - for i := 0; i < 10; i++ { - data := []byte("Round trip " + string(rune('0'+i))) - _, err = conn.Write(data) - require.NoError(t, err) - - readBuf := make([]byte, len(data)) - _, err = io.ReadFull(conn, readBuf) - require.NoError(t, err) - require.Equal(t, data, readBuf) - } -} - -// TestNaiveDialError tests error handling when dial fails. -func TestNaiveDialError(t *testing.T) { - env := setupTestEnv(t) - - testCases := []struct { - name string - err error - }{ - {"connection refused", errors.New("connection refused")}, - {"timeout", errors.New("i/o timeout")}, - {"network unreachable", errors.New("network is unreachable")}, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - client := env.newNaiveClient(t, cronet.NaiveClientOptions{ - Dialer: &errorDialer{err: tc.err}, - }) - - // Attempting to dial should fail - conn, err := client.DialEarly(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", 17002)) - require.NoError(t, err) - err = conn.Handshake() - require.Error(t, err, "expected dial to fail with %s", tc.name) - conn.Close() - }) - } -} - -// TestNaiveLargeTransfer tests data integrity with large transfers. -func TestNaiveLargeTransfer(t *testing.T) { - env := setupTestEnv(t) - startEchoServer(t, 17003) - - client := env.newNaiveClient(t, cronet.NaiveClientOptions{ - DNSResolver: localhostDNSResolver(t), - }) - - conn, err := client.DialEarly(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", 17003)) - require.NoError(t, err) - defer conn.Close() - - // Generate 1MB of random data - const dataSize = 1024 * 1024 - testData := make([]byte, dataSize) - _, err = rand.Read(testData) - require.NoError(t, err) - - // Write in background - writeDone := make(chan error, 1) - go func() { - _, err := conn.Write(testData) - writeDone <- err - }() - - // Read all data back - receivedData := make([]byte, dataSize) - _, err = io.ReadFull(conn, receivedData) - require.NoError(t, err) - - // Wait for write to complete - require.NoError(t, <-writeDone) - - // Verify data integrity - require.True(t, bytes.Equal(testData, receivedData), "data mismatch in large transfer") -} - -// TestNaiveRapidOpenClose tests stability with rapid connection open/close cycles. -func TestNaiveRapidOpenClose(t *testing.T) { - env := setupTestEnv(t) - startEchoServer(t, 17004) - - client := env.newNaiveClient(t, cronet.NaiveClientOptions{ - DNSResolver: localhostDNSResolver(t), - }) - - const iterations = 50 - - for i := 0; i < iterations; i++ { - func() { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - conn, err := client.DialContext(ctx, N.NetworkTCP, M.ParseSocksaddrHostPort("127.0.0.1", 17004)) - if err != nil { - t.Logf("iteration %d: dial failed (acceptable): %v", i, err) - return - } - - // Quick write/read cycle - testData := []byte("rapid test") - conn.Write(testData) - - buf := make([]byte, len(testData)) - io.ReadFull(conn, buf) - - conn.Close() - }() - } -} - -// TestNaiveGracefulShutdown tests that Close() properly waits for all connections. -func TestNaiveGracefulShutdown(t *testing.T) { - env := setupTestEnv(t) - startEchoServer(t, 17005) - - // Use wrapped dialer to force proxy path which has more cleanup work - dialer := &trackingDialer{ - underlying: N.SystemDialer, - wrapConn: true, - } - - config := cronet.NaiveClientOptions{ - ServerAddress: M.ParseSocksaddrHostPort("127.0.0.1", naiveServerPort), - ServerName: "example.org", - Username: "test", - Password: "test", - Dialer: dialer, - DNSResolver: localhostDNSResolver(t), - } - config.TrustedRootCertificates = string(env.caPEM) - - client, err := cronet.NewNaiveClient(config) - require.NoError(t, err) - require.NoError(t, client.Start()) - - const connectionCount = 5 - var wg sync.WaitGroup - conns := make([]net.Conn, connectionCount) - - // Open multiple connections - for i := 0; i < connectionCount; i++ { - conn, err := client.DialEarly(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", 17005)) - require.NoError(t, err) - conns[i] = conn - - // Start background activity on each connection - wg.Add(1) - go func(c net.Conn, idx int) { - defer wg.Done() - for j := 0; j < 5; j++ { - data := []byte("activity") - if _, err := c.Write(data); err != nil { - return - } - buf := make([]byte, len(data)) - if _, err := io.ReadFull(c, buf); err != nil { - return - } - time.Sleep(10 * time.Millisecond) - } - }(conn, i) - } - - // Wait a bit for activity to start - time.Sleep(50 * time.Millisecond) - - // Close all connections first - for _, conn := range conns { - conn.Close() - } - - // Wait for goroutines to finish - wg.Wait() - - // Now close the client - this should complete without hanging - closeDone := make(chan struct{}) - go func() { - client.Close() - close(closeDone) - }() - - select { - case <-closeDone: - // Success - case <-time.After(10 * time.Second): - t.Fatal("client.Close() timed out - possible resource leak") - } -} - -// TestNaivePipeProxyMultipleConnections tests multiple simultaneous connections -// through the proxy path. -func TestNaivePipeProxyMultipleConnections(t *testing.T) { - env := setupTestEnv(t) - startEchoServer(t, 17006) - - dialer := &trackingDialer{ - underlying: N.SystemDialer, - wrapConn: true, - } - - client := env.newNaiveClient(t, cronet.NaiveClientOptions{ - Dialer: dialer, - DNSResolver: localhostDNSResolver(t), - }) - - const connectionCount = 10 - var wg sync.WaitGroup - errChannel := make(chan error, connectionCount) - - for i := 0; i < connectionCount; i++ { - wg.Add(1) - go func(idx int) { - defer wg.Done() - - conn, err := client.DialEarly(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", 17006)) - if err != nil { - errChannel <- err - return - } - defer conn.Close() - - // Send unique data - testData := make([]byte, 100) - for j := range testData { - testData[j] = byte(idx) - } - - _, err = conn.Write(testData) - if err != nil { - errChannel <- err - return - } - - buf := make([]byte, len(testData)) - _, err = io.ReadFull(conn, buf) - if err != nil { - errChannel <- err - return - } - - if !bytes.Equal(testData, buf) { - errChannel <- errors.New("data mismatch") - return - } - }(i) - } - - wg.Wait() - close(errChannel) - - for err := range errChannel { - t.Errorf("connection error: %v", err) - } -} - -// TestDNSTCFallbackToTCP verifies that when UDP DNS returns TC (Truncated), -// Chromium falls back to TCP DNS and our TCP DNS interception works. -func TestDNSTCFallbackToTCP(t *testing.T) { - env := setupTestEnv(t) - startEchoServer(t, 17007) - - var dnsCallCount atomic.Int64 - - // DNS resolver that returns a large response (> 512 bytes) - // This will trigger TC on UDP, forcing fallback to TCP - largeDNSResolver := func(ctx context.Context, request *mDNS.Msg) *mDNS.Msg { - dnsCallCount.Add(1) - count := dnsCallCount.Load() - t.Logf("DNS resolver called (call #%d): %v", count, request.Question) - - response := new(mDNS.Msg) - response.SetReply(request) - - for _, question := range request.Question { - if question.Qtype == mDNS.TypeA { - response.Answer = append(response.Answer, &mDNS.A{ - Hdr: mDNS.RR_Header{ - Name: question.Name, - Rrtype: mDNS.TypeA, - Class: mDNS.ClassINET, - Ttl: 300, - }, - A: net.ParseIP("127.0.0.1"), - }) - } - } - - // Add many TXT records to exceed 512 bytes - for i := 0; i < 20; i++ { - response.Extra = append(response.Extra, &mDNS.TXT{ - Hdr: mDNS.RR_Header{ - Name: request.Question[0].Name, - Rrtype: mDNS.TypeTXT, - Class: mDNS.ClassINET, - Ttl: 300, - }, - Txt: []string{strings.Repeat("x", 50)}, - }) - } - return response - } - - serverAddress := M.ParseSocksaddrHostPort("proxy.invalid", naiveServerPort) - client := env.newNaiveClient(t, cronet.NaiveClientOptions{ - ServerAddress: serverAddress, - DNSResolver: largeDNSResolver, - }) - - // Make a connection - this will trigger DNS resolution - conn, err := client.DialEarly(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", 17007)) - require.NoError(t, err) - defer conn.Close() - - // Verify connection works - testData := []byte("TC fallback test!") - _, err = conn.Write(testData) - require.NoError(t, err) - - buf := make([]byte, len(testData)) - _, err = io.ReadFull(conn, buf) - require.NoError(t, err) - require.Equal(t, testData, buf) - - // DNS should be called at least twice (UDP with TC, then TCP) - require.GreaterOrEqual(t, dnsCallCount.Load(), int64(2), - "DNS resolver should be called at least twice (UDP TC + TCP fallback)") - t.Logf("DNS resolver was called %d times", dnsCallCount.Load()) -} - -// TestDNSInterceptionUDPLoopbackFallback verifies that DNS interception works -// when forcing the UDP loopback socket pair fallback path. -// This tests the path used on all platforms when Unix domain sockets are unavailable. -func TestDNSInterceptionUDPLoopbackFallback(t *testing.T) { - env := setupTestEnv(t) - startEchoServer(t, 17008) - - var dnsCallCount atomic.Int64 - - countingResolver := func(ctx context.Context, request *mDNS.Msg) *mDNS.Msg { - dnsCallCount.Add(1) - t.Logf("UDP loopback DNS resolver called: %v", request.Question) - response := new(mDNS.Msg) - response.SetReply(request) - for _, question := range request.Question { - if question.Qtype == mDNS.TypeA { - response.Answer = append(response.Answer, &mDNS.A{ - Hdr: mDNS.RR_Header{ - Name: question.Name, - Rrtype: mDNS.TypeA, - Class: mDNS.ClassINET, - Ttl: 300, - }, - A: net.ParseIP("127.0.0.1"), - }) - } - } - return response - } - - serverAddress := M.ParseSocksaddrHostPort("proxy.invalid", naiveServerPort) - client := env.newNaiveClient(t, cronet.NaiveClientOptions{ - ServerAddress: serverAddress, - DNSResolver: countingResolver, - TestForceUDPLoopback: true, // Force UDP loopback path - }) - - // Make a connection - this will trigger DNS resolution through UDP loopback - conn, err := client.DialEarly(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", 17008)) - require.NoError(t, err) - defer conn.Close() - - // Verify connection works - testData := []byte("UDP loopback fallback test!") - _, err = conn.Write(testData) - require.NoError(t, err) - - buf := make([]byte, len(testData)) - _, err = io.ReadFull(conn, buf) - require.NoError(t, err) - require.Equal(t, testData, buf) - - // Verify DNS resolver was called - require.Greater(t, dnsCallCount.Load(), int64(0), - "DNS resolver should have been called through UDP loopback path") - t.Logf("DNS resolver was called %d times via UDP loopback", dnsCallCount.Load()) -} - -// TestDNSInterceptionDefaultPath verifies that DNS interception works -// with the default platform-specific socketpair implementation. -// - Unix: AF_UNIX SOCK_DGRAM socketpair -// - Windows: AF_UNIX SOCK_STREAM + length-prefix framing -func TestDNSInterceptionDefaultPath(t *testing.T) { - env := setupTestEnv(t) - startEchoServer(t, 17009) - - var dnsCallCount atomic.Int64 - - countingResolver := func(ctx context.Context, request *mDNS.Msg) *mDNS.Msg { - dnsCallCount.Add(1) - t.Logf("Default path DNS resolver called: %v", request.Question) - response := new(mDNS.Msg) - response.SetReply(request) - for _, question := range request.Question { - if question.Qtype == mDNS.TypeA { - response.Answer = append(response.Answer, &mDNS.A{ - Hdr: mDNS.RR_Header{ - Name: question.Name, - Rrtype: mDNS.TypeA, - Class: mDNS.ClassINET, - Ttl: 300, - }, - A: net.ParseIP("127.0.0.1"), - }) - } - } - return response - } - - serverAddress := M.ParseSocksaddrHostPort("proxy.invalid", naiveServerPort) - client := env.newNaiveClient(t, cronet.NaiveClientOptions{ - ServerAddress: serverAddress, - DNSResolver: countingResolver, - TestForceUDPLoopback: false, // Use default path (Unix SOCK_DGRAM or Windows framed) - }) - - // Make a connection - this will trigger DNS resolution through default path - conn, err := client.DialEarly(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", 17009)) - require.NoError(t, err) - defer conn.Close() - - // Verify connection works - testData := []byte("Default path DNS interception test!") - _, err = conn.Write(testData) - require.NoError(t, err) - - buf := make([]byte, len(testData)) - _, err = io.ReadFull(conn, buf) - require.NoError(t, err) - require.Equal(t, testData, buf) - - // Verify DNS resolver was called - require.Greater(t, dnsCallCount.Load(), int64(0), - "DNS resolver should have been called through default path") - t.Logf("DNS resolver was called %d times via default path", dnsCallCount.Load()) -} - -// TestNaiveInsecureConcurrencySessionCount verifies that InsecureConcurrency -// actually creates multiple HTTP/2 sessions (regression test for -// PartitionConnectionsByNetworkIsolationKey feature) -func TestNaiveInsecureConcurrencySessionCount(t *testing.T) { - env := setupTestEnv(t) - client := env.newNaiveClient(t, cronet.NaiveClientOptions{ - InsecureConcurrency: 3, - DNSResolver: localhostDNSResolver(t), - }) - - // Start echo servers for each connection - const connectionCount = 6 - for i := 0; i < connectionCount; i++ { - startEchoServer(t, uint16(18000+i)) - } - - // Start NetLog - netLogPath := startNetLogForTest(t, client, "insecure_concurrency_netlog.json", true) - - // Send multiple sequential connections to trigger round-robin - for i := 0; i < connectionCount; i++ { - conn, err := client.DialEarly(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", uint16(18000+i))) - require.NoError(t, err) - - testData := []byte("test") - _, err = conn.Write(testData) - require.NoError(t, err) - - buf := make([]byte, len(testData)) - _, err = io.ReadFull(conn, buf) - require.NoError(t, err) - - conn.Close() - } - - // Stop NetLog and read results - client.Engine().StopNetLog() - - logContent, err := os.ReadFile(netLogPath) - require.NoError(t, err) - logStr := string(logContent) - - // Parse NetLog JSON to find the HTTP2_SESSION_INITIALIZED type ID - // (the numeric ID changes across Chromium versions). - var netLog struct { - Constants struct { - LogEventTypes map[string]int `json:"logEventTypes"` - } `json:"constants"` - } - require.NoError(t, json.Unmarshal(logContent, &netLog)) - typeID, ok := netLog.Constants.LogEventTypes["HTTP2_SESSION_INITIALIZED"] - require.True(t, ok, "HTTP2_SESSION_INITIALIZED not found in NetLog constants") - sessionCount := strings.Count(logStr, fmt.Sprintf(`"type":%d`, typeID)) - require.GreaterOrEqual(t, sessionCount, 3, - "Expected at least 3 HTTP/2 sessions with InsecureConcurrency=3, got %d. NetLog: %s", - sessionCount, netLogPath) -} - -// TestServerAddressDomainWithDifferentServerName verifies that when ServerAddress -// is a domain and ServerName is a different domain, the connection uses ServerAddress's -// resolved IP, not ServerName's IP. -// -// This tests the DNS redirect logic in wrapDNSResolverForServerRedirect. -func TestServerAddressDomainWithDifferentServerName(t *testing.T) { - env := setupTestEnv(t) - startEchoServer(t, 18100) - - // Track which domains were queried - var queriedDomains sync.Map - - // Create a DNS resolver that returns different IPs for different domains: - // - proxy.example.com -> 127.0.0.1 (correct server address) - // - example.org -> 10.255.255.1 (unreachable address, used as ServerName for TLS SNI) - dnsResolver := func(ctx context.Context, request *mDNS.Msg) *mDNS.Msg { - response := new(mDNS.Msg) - response.SetReply(request) - - for _, question := range request.Question { - name := strings.TrimSuffix(question.Name, ".") - t.Logf("DNS query for: %s (type %d)", name, question.Qtype) - queriedDomains.Store(name, true) - - var ip net.IP - switch { - case strings.EqualFold(name, "proxy.example.com"): - ip = net.ParseIP("127.0.0.1") - case strings.EqualFold(name, "example.org"): - // Use an unreachable IP to ensure we detect if wrong IP is used - ip = net.ParseIP("10.255.255.1") - default: - ip = net.ParseIP("127.0.0.1") - } - - switch question.Qtype { - case mDNS.TypeA: - if ip.To4() != nil { - t.Logf("Resolving %s to %s", name, ip) - response.Answer = append(response.Answer, &mDNS.A{ - Hdr: mDNS.RR_Header{ - Name: question.Name, - Rrtype: mDNS.TypeA, - Class: mDNS.ClassINET, - Ttl: 300, - }, - A: ip.To4(), - }) - } - case mDNS.TypeAAAA: - // Return empty for AAAA to force IPv4 - } - } - return response - } - - // Configure with ServerAddress as domain, ServerName as different domain - // ServerName = example.org (matches server certificate CN) - // ServerAddress = proxy.example.com (the actual server to connect to) - config := cronet.NaiveClientOptions{ - ServerAddress: M.ParseSocksaddrHostPort("proxy.example.com", naiveServerPort), - ServerName: "example.org", - Username: "test", - Password: "test", - TrustedRootCertificates: string(env.caPEM), - DNSResolver: dnsResolver, - } - - client, err := cronet.NewNaiveClient(config) - require.NoError(t, err) - require.NoError(t, client.Start()) - t.Cleanup(func() { client.Close() }) - - // If DNS redirect works correctly, this should connect to 127.0.0.1 (proxy.example.com) - // If DNS redirect fails, it would try to connect to 10.255.255.1 (example.org) and fail - conn, err := client.DialEarly(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", 18100)) - require.NoError(t, err, "Connection failed - DNS redirect may not be working correctly") - defer conn.Close() - - // Verify the connection works - testData := []byte("DNS redirect test!") - _, err = conn.Write(testData) - require.NoError(t, err) - - buf := make([]byte, len(testData)) - _, err = io.ReadFull(conn, buf) - require.NoError(t, err) - require.Equal(t, testData, buf) - - // Verify that proxy.example.com was queried (DNS redirect worked) - _, proxyQueried := queriedDomains.Load("proxy.example.com") - require.True(t, proxyQueried, "DNS redirect failed: proxy.example.com was not queried") - - t.Log("DNS redirect test passed: connection used ServerAddress IP, not ServerName IP") -} - -// TestServerAddressDomainWithNXDomainServerName verifies that when ServerAddress -// is a domain and ServerName returns NXDOMAIN, the connection still works by -// using ServerAddress's resolved IP. -// -// This tests user scenario 5: server=domain(resolvable), tls.server_name=domain(NXDOMAIN) -func TestServerAddressDomainWithNXDomainServerName(t *testing.T) { - env := setupTestEnv(t) - startEchoServer(t, 18101) - - // Track which domains were queried - var queriedDomains sync.Map - - // Create a DNS resolver: - // - proxy.example.com -> 127.0.0.1 (correct server address) - // - example.org -> NXDOMAIN (ServerName has no DNS records) - dnsResolver := func(ctx context.Context, request *mDNS.Msg) *mDNS.Msg { - response := new(mDNS.Msg) - response.SetReply(request) - - for _, question := range request.Question { - name := strings.TrimSuffix(question.Name, ".") - t.Logf("DNS query for: %s (type %d)", name, question.Qtype) - queriedDomains.Store(name, true) - - switch { - case strings.EqualFold(name, "proxy.example.com"): - switch question.Qtype { - case mDNS.TypeA: - t.Logf("Resolving %s to 127.0.0.1", name) - response.Answer = append(response.Answer, &mDNS.A{ - Hdr: mDNS.RR_Header{ - Name: question.Name, - Rrtype: mDNS.TypeA, - Class: mDNS.ClassINET, - Ttl: 300, - }, - A: net.ParseIP("127.0.0.1").To4(), - }) - } - case strings.EqualFold(name, "example.org") || - strings.HasSuffix(strings.ToLower(name), ".example.org"): - // Return NXDOMAIN for example.org (simulating empty DNS) - t.Logf("Returning NXDOMAIN for %s", name) - response.Rcode = mDNS.RcodeNameError - return response - } - } - return response - } - - // Configure with ServerAddress as domain, ServerName returns NXDOMAIN - config := cronet.NaiveClientOptions{ - ServerAddress: M.ParseSocksaddrHostPort("proxy.example.com", naiveServerPort), - ServerName: "example.org", - Username: "test", - Password: "test", - TrustedRootCertificates: string(env.caPEM), - DNSResolver: dnsResolver, - } - - client, err := cronet.NewNaiveClient(config) - require.NoError(t, err) - require.NoError(t, client.Start()) - t.Cleanup(func() { client.Close() }) - - // If DNS redirect works correctly, this should still connect to 127.0.0.1 (proxy.example.com) - // even though example.org returns NXDOMAIN - conn, err := client.DialEarly(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", 18101)) - require.NoError(t, err, "Connection failed - DNS redirect may not be working when ServerName returns NXDOMAIN") - defer conn.Close() - - // Verify the connection works - testData := []byte("NXDOMAIN redirect test!") - _, err = conn.Write(testData) - require.NoError(t, err) - - buf := make([]byte, len(testData)) - _, err = io.ReadFull(conn, buf) - require.NoError(t, err) - require.Equal(t, testData, buf) - - // Verify that proxy.example.com was queried (DNS redirect worked) - _, proxyQueried := queriedDomains.Load("proxy.example.com") - require.True(t, proxyQueried, "DNS redirect failed: proxy.example.com was not queried") - - t.Log("NXDOMAIN redirect test passed: connection used ServerAddress IP despite ServerName NXDOMAIN") -} - -// TestCloseAllConnections verifies that after calling CloseAllConnections(), -// new connections can still be established (connection pools are re-created on demand). -func TestCloseAllConnections(t *testing.T) { - env := setupTestEnv(t) - startEchoServer(t, 18200) - - client := env.newNaiveClient(t, cronet.NaiveClientOptions{ - DNSResolver: localhostDNSResolver(t), - }) - - startNetLogForTest(t, client, "close_all_connections_netlog.json", false) - - // Phase 1: establish a connection and verify it works - conn1, err := client.DialEarly(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", 18200)) - require.NoError(t, err) - - testData := []byte("before CloseAllConnections") - _, err = conn1.Write(testData) - require.NoError(t, err) - - buf := make([]byte, len(testData)) - _, err = io.ReadFull(conn1, buf) - require.NoError(t, err) - require.Equal(t, testData, buf) - - conn1.Close() - - // Phase 2: close all connection pools - client.Engine().CloseAllConnections() - - // Phase 3: verify new connections still work after pool reset - conn2, err := client.DialEarly(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", 18200)) - require.NoError(t, err) - defer conn2.Close() - - testData2 := []byte("after CloseAllConnections") - _, err = conn2.Write(testData2) - require.NoError(t, err) - - buf2 := make([]byte, len(testData2)) - _, err = io.ReadFull(conn2, buf2) - require.NoError(t, err) - require.Equal(t, testData2, buf2) -} - -// TestCloseAllConnectionsThenClientClose verifies that calling -// Engine().CloseAllConnections() on active connections does not leave -// NaiveClient.Close() blocked forever. -func TestCloseAllConnectionsThenClientClose(t *testing.T) { - env := setupTestEnv(t) - startEchoServer(t, 18201) - - config := cronet.NaiveClientOptions{ - ServerAddress: M.ParseSocksaddrHostPort("127.0.0.1", naiveServerPort), - ServerName: "example.org", - Username: "test", - Password: "test", - TrustedRootCertificates: string(env.caPEM), - DNSResolver: localhostDNSResolver(t), - } - - client, err := cronet.NewNaiveClient(config) - require.NoError(t, err) - require.NoError(t, client.Start()) - - // Start netlog manually (not via startNetLogForTest) because this test - // calls client.Close() in the test body, which destroys the engine. - // A t.Cleanup-based StopNetLog would crash on the already-destroyed engine. - if shouldCaptureNetLog() { - netLogPath := artifactPath(t, "netlog", "close_all_then_client_close_netlog.json") - client.Engine().StartNetLogToFile(netLogPath, true) - } - - conn, err := client.DialEarly(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", 18201)) - require.NoError(t, err) - - testData := []byte("before close-all") - _, err = conn.Write(testData) - require.NoError(t, err) - - buf := make([]byte, len(testData)) - _, err = io.ReadFull(conn, buf) - require.NoError(t, err) - require.Equal(t, testData, buf) - - client.Engine().CloseAllConnections() - - require.Eventually(t, func() bool { - _, writeErr := conn.Write([]byte("probe after close-all")) - return writeErr != nil - }, 5*time.Second, 20*time.Millisecond, "connection should auto-close after CloseAllConnections()") - - client.Engine().StopNetLog() - - closeDone := make(chan error, 1) - go func() { - closeDone <- client.Close() - }() - - select { - case closeErr := <-closeDone: - require.NoError(t, closeErr) - case <-time.After(5 * time.Second): - // Ensure test cleanup can progress even in the failure path. - _ = conn.Close() - select { - case <-closeDone: - case <-time.After(5 * time.Second): - } - t.Fatal("client.Close() timed out after Engine().CloseAllConnections()") - } -} diff --git a/test/main_test.go b/test/main_test.go deleted file mode 100644 index ce0be947..00000000 --- a/test/main_test.go +++ /dev/null @@ -1,649 +0,0 @@ -package test - -import ( - "archive/tar" - "archive/zip" - "compress/gzip" - "context" - "crypto/rand" - "crypto/rsa" - "crypto/sha1" - "crypto/x509" - "crypto/x509/pkix" - "encoding/asn1" - "encoding/pem" - "fmt" - "io" - "math/big" - "net" - "net/http" - "os" - "os/exec" - "path/filepath" - "runtime" - "strconv" - "strings" - "sync" - "testing" - "time" - - cronet "github.com/sagernet/cronet-go" - "github.com/sagernet/sing/common/bufio" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" - - "github.com/stretchr/testify/require" - "go.uber.org/goleak" -) - -const ( - naiveServerPort = 10443 - iperf3Port = 5201 - forwardPort = 15201 -) - -const naiveServerVersion = "1.12.24" - -func TestMain(m *testing.M) { - goleak.VerifyTestMain(m) -} - -// testEnv holds shared test environment resources -type testEnv struct { - caPEM []byte - certPath string - keyPath string -} - -func setupTestEnv(t *testing.T) *testEnv { - caPem, certPem, keyPem := generateCertificate(t, "example.org") - caPemContent, err := os.ReadFile(caPem) - require.NoError(t, err) - startNaiveServer(t, certPem, keyPem) - return &testEnv{ - caPEM: caPemContent, - certPath: certPem, - keyPath: keyPem, - } -} - -func (e *testEnv) newNaiveClient(t *testing.T, config cronet.NaiveClientOptions) *cronet.NaiveClient { - if !config.ServerAddress.IsValid() { - config.ServerAddress = M.ParseSocksaddrHostPort("127.0.0.1", naiveServerPort) - } - if config.ServerName == "" { - config.ServerName = "example.org" - } - if config.Username == "" { - config.Username = "test" - } - if config.Password == "" { - config.Password = "test" - } - if config.TrustedRootCertificates == "" { - config.TrustedRootCertificates = string(e.caPEM) - } - if config.DNSResolver == nil { - config.DNSResolver = localhostDNSResolver(t) - } - client, err := cronet.NewNaiveClient(config) - require.NoError(t, err) - require.NoError(t, client.Start()) - t.Cleanup(func() { client.Close() }) - return client -} - -func startEchoServer(t *testing.T, port uint16) { - listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) - require.NoError(t, err) - t.Cleanup(func() { listener.Close() }) - go func() { - for { - conn, err := listener.Accept() - if err != nil { - return - } - go func(c net.Conn) { - defer c.Close() - buffer := make([]byte, 32*1024) - for { - n, readErr := c.Read(buffer) - if n > 0 { - _, writeErr := c.Write(buffer[:n]) - if writeErr != nil { - return - } - } - if readErr != nil { - break - } - } - }(conn) - } - }() -} - -func reserveTCPPort(t *testing.T) uint16 { - t.Helper() - - listener, err := net.Listen("tcp", "127.0.0.1:0") - require.NoError(t, err) - defer listener.Close() - - return uint16(listener.Addr().(*net.TCPAddr).Port) -} - -func reserveUDPPort(t *testing.T) uint16 { - t.Helper() - - conn, err := net.ListenPacket("udp", "127.0.0.1:0") - require.NoError(t, err) - defer conn.Close() - - return uint16(conn.LocalAddr().(*net.UDPAddr).Port) -} - -// TestNaiveBasic verifies basic NaiveClient connectivity -func TestNaiveBasic(t *testing.T) { - env := setupTestEnv(t) - client := env.newNaiveClient(t, cronet.NaiveClientOptions{ - DNSResolver: localhostDNSResolver(t), - }) - startEchoServer(t, 15000) - - conn, err := client.DialEarly(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", 15000)) - require.NoError(t, err) - defer conn.Close() - - testData := []byte("Hello, NaiveProxy!") - _, err = conn.Write(testData) - require.NoError(t, err) - - buf := make([]byte, len(testData)) - _, err = io.ReadFull(conn, buf) - require.NoError(t, err) - require.Equal(t, testData, buf) -} - -// TestNaiveIperf3 measures throughput using iperf3 -func TestNaiveIperf3(t *testing.T) { - startIperf3Server(t) - env := setupTestEnv(t) - client := env.newNaiveClient(t, cronet.NaiveClientOptions{ - DNSResolver: localhostDNSResolver(t), - }) - - forwarder := startForwarder(t, forwardPort, client, iperf3Port) - defer forwarder.Close() - - runIperf3Client(t, forwardPort, 10) -} - -// TestNaiveConcurrency tests InsecureConcurrency feature -func TestNaiveConcurrency(t *testing.T) { - const concurrencyCount = 6 - - for i := 0; i < concurrencyCount; i++ { - startIperf3ServerOnPort(t, uint16(iperf3Port+i)) - } - env := setupTestEnv(t) - client := env.newNaiveClient(t, cronet.NaiveClientOptions{ - InsecureConcurrency: 3, - DNSResolver: localhostDNSResolver(t), - }) - - var waitGroup sync.WaitGroup - for i := 0; i < concurrencyCount; i++ { - waitGroup.Add(1) - go func(index int) { - defer waitGroup.Done() - serverPort := uint16(iperf3Port + index) - localPort := uint16(forwardPort + index) - forwarder := startForwarder(t, localPort, client, serverPort) - defer forwarder.Close() - runIperf3Client(t, localPort, 5) - }(i) - } - waitGroup.Wait() -} - -// TestNaiveParallel runs multiple iperf3 streams in parallel -func TestNaiveParallel(t *testing.T) { - startIperf3Server(t) - env := setupTestEnv(t) - client := env.newNaiveClient(t, cronet.NaiveClientOptions{ - DNSResolver: localhostDNSResolver(t), - }) - - forwarder := startForwarder(t, forwardPort, client, iperf3Port) - defer forwarder.Close() - - cmd := exec.Command("iperf3", "-c", "127.0.0.1", "-p", fmt.Sprint(forwardPort), "-t", "10", "-P", "4") - output, err := cmd.CombinedOutput() - t.Log(string(output)) - require.NoError(t, err) -} - -// TestNaiveCloseWhileReading tests that Close() works correctly when -// reads are in progress. This verifies the fix for buffer lifetime issues. -func TestNaiveCloseWhileReading(t *testing.T) { - env := setupTestEnv(t) - client := env.newNaiveClient(t, cronet.NaiveClientOptions{ - DNSResolver: localhostDNSResolver(t), - }) - startEchoServer(t, 16000) - - const iterations = 20 - - for i := 0; i < iterations; i++ { - func() { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - conn, err := client.DialContext(ctx, N.NetworkTCP, M.ParseSocksaddrHostPort("127.0.0.1", 16000)) - if err != nil { - t.Logf("iteration %d: dial failed: %v", i, err) - return - } - - // Write some data to trigger response - testData := make([]byte, 64) - rand.Read(testData) - conn.Write(testData) - - readDone := make(chan struct{}) - go func() { - defer close(readDone) - buf := make([]byte, 32) - conn.Read(buf) - }() - - // Close immediately while read might be in progress - time.Sleep(time.Millisecond) - conn.Close() - - select { - case <-readDone: - case <-time.After(3 * time.Second): - t.Fatalf("iteration %d: Read did not return after Close", i) - } - }() - } -} - -// Naive server (sing-box) utilities - -func ensureNaiveServer(t *testing.T) string { - binDirectory := "bin" - err := os.MkdirAll(binDirectory, 0o755) - require.NoError(t, err) - - binaryName := "sing-box" - if runtime.GOOS == "windows" { - binaryName = "sing-box.exe" - } - binaryPath := filepath.Join(binDirectory, binaryName) - - // Check if binary already exists - if _, err := os.Stat(binaryPath); err == nil { - return binaryPath - } - - // Download sing-box - extension := "tar.gz" - if runtime.GOOS == "windows" { - extension = "zip" - } - downloadURL := fmt.Sprintf( - "https://github.com/SagerNet/sing-box/releases/download/v%s/sing-box-%s-%s-%s.%s", - naiveServerVersion, naiveServerVersion, runtime.GOOS, runtime.GOARCH, extension, - ) - - t.Logf("Downloading sing-box from %s", downloadURL) - - // Use custom client and close idle connections after download to avoid goroutine leaks - httpClient := &http.Client{} - defer httpClient.CloseIdleConnections() - - response, err := httpClient.Get(downloadURL) - require.NoError(t, err) - defer response.Body.Close() - require.Equal(t, http.StatusOK, response.StatusCode, "failed to download sing-box: %s", response.Status) - - // Save to temp file - tempFile, err := os.CreateTemp("", "sing-box-*") - require.NoError(t, err) - defer os.Remove(tempFile.Name()) - - _, err = io.Copy(tempFile, response.Body) - require.NoError(t, err) - tempFile.Close() - - // Extract binary - if runtime.GOOS == "windows" { - extractZip(t, tempFile.Name(), binDirectory, binaryName) - } else { - extractTarGz(t, tempFile.Name(), binDirectory, binaryName) - } - - // Make executable on Unix - if runtime.GOOS != "windows" { - err = os.Chmod(binaryPath, 0o755) - require.NoError(t, err) - } - - return binaryPath -} - -func extractTarGz(t *testing.T, archivePath, destDirectory, binaryName string) { - file, err := os.Open(archivePath) - require.NoError(t, err) - defer file.Close() - - gzipReader, err := gzip.NewReader(file) - require.NoError(t, err) - defer gzipReader.Close() - - tarReader := tar.NewReader(gzipReader) - for { - header, err := tarReader.Next() - if err == io.EOF { - break - } - require.NoError(t, err) - - // Look for the sing-box binary (it's in a subdirectory) - if header.Typeflag == tar.TypeReg && strings.HasSuffix(header.Name, "/"+binaryName) { - destPath := filepath.Join(destDirectory, binaryName) - outFile, err := os.Create(destPath) - require.NoError(t, err) - _, err = io.Copy(outFile, tarReader) - outFile.Close() - require.NoError(t, err) - return - } - } - t.Fatalf("binary %s not found in archive", binaryName) -} - -func extractZip(t *testing.T, archivePath, destDirectory, binaryName string) { - zipReader, err := zip.OpenReader(archivePath) - require.NoError(t, err) - defer zipReader.Close() - - for _, file := range zipReader.File { - // Look for the sing-box binary (it's in a subdirectory) - if strings.HasSuffix(file.Name, "/"+binaryName) || strings.HasSuffix(file.Name, "\\"+binaryName) { - reader, err := file.Open() - require.NoError(t, err) - - destPath := filepath.Join(destDirectory, binaryName) - outFile, err := os.Create(destPath) - require.NoError(t, err) - - _, err = io.Copy(outFile, reader) - outFile.Close() - reader.Close() - require.NoError(t, err) - return - } - } - t.Fatalf("binary %s not found in archive", binaryName) -} - -func startNaiveServer(t *testing.T, certPem, keyPem string) { - binary := ensureNaiveServer(t) - - // Read config template and replace certificate paths - configTemplate, err := os.ReadFile("config/sing-box.json") - require.NoError(t, err) - - // Use forward slashes for JSON compatibility (works on all platforms) - certPem = filepath.ToSlash(certPem) - keyPem = filepath.ToSlash(keyPem) - - config := strings.ReplaceAll(string(configTemplate), "/cert.pem", certPem) - config = strings.ReplaceAll(config, "/key.pem", keyPem) - - // Write to temp config file - configPath := filepath.Join(t.TempDir(), "sing-box.json") - err = os.WriteFile(configPath, []byte(config), 0o644) - require.NoError(t, err) - - startNaiveServerWithConfig(t, binary, configPath, naiveServerPort, "tcp") -} - -func startNaiveServerWithConfig(t *testing.T, binary, configPath string, listenPort uint16, network string) { - cmd := exec.Command(binary, "run", "-c", configPath) - traceFile, tracePath := createArtifactTempFile(t, "trace", "sing-box-*.log") - if testing.Verbose() { - writer := io.MultiWriter(traceFile, os.Stderr) - cmd.Stdout = writer - cmd.Stderr = writer - } else { - cmd.Stdout = traceFile - cmd.Stderr = traceFile - } - if testing.Verbose() { - t.Logf("sing-box trace: %s", tracePath) - } - err := cmd.Start() - require.NoError(t, err, "failed to start sing-box; trace: %s", tracePath) - - waitForServerReady(t, network, listenPort, 10*time.Second, tracePath) - - t.Cleanup(func() { - _ = cmd.Process.Kill() - _ = cmd.Wait() - _ = traceFile.Close() - }) -} - -func waitForServerReady(t *testing.T, network string, port uint16, timeout time.Duration, tracePath string) { - t.Helper() - - deadline := time.Now().Add(timeout) - address := net.JoinHostPort("127.0.0.1", strconv.Itoa(int(port))) - var lastErr error - - for time.Now().Before(deadline) { - switch network { - case "udp": - ready, err := traceContainsServerReady(tracePath, port) - if err != nil { - lastErr = err - } else if ready { - return - } - default: - conn, err := net.DialTimeout("tcp", address, 200*time.Millisecond) - if err == nil { - _ = conn.Close() - return - } - lastErr = err - } - time.Sleep(50 * time.Millisecond) - } - - require.Failf( - t, - "server did not become ready", - "network=%s address=%s timeout=%s lastErr=%v trace=%s", - network, - address, - timeout, - lastErr, - tracePath, - ) -} - -func traceContainsServerReady(tracePath string, port uint16) (bool, error) { - traceContent, err := os.ReadFile(tracePath) - if err != nil { - return false, err - } - - content := strings.ToLower(string(traceContent)) - serverStarted := strings.Contains(content, "server started at") - portMarker := ":" + strconv.Itoa(int(port)) - return serverStarted && strings.Contains(content, portMarker), nil -} - -// iperf3 utilities - -func startIperf3Server(t *testing.T) { - startIperf3ServerOnPort(t, iperf3Port) -} - -func startIperf3ServerOnPort(t *testing.T, port uint16) { - cmd := exec.Command("iperf3", "-s", "-p", fmt.Sprint(port)) - if testing.Verbose() { - cmd.Stdout = os.Stderr - cmd.Stderr = os.Stderr - } - err := cmd.Start() - require.NoError(t, err) - - t.Cleanup(func() { - cmd.Process.Kill() - cmd.Wait() - }) -} - -func runIperf3Client(t *testing.T, port uint16, duration int) { - cmd := exec.Command("iperf3", "-c", "127.0.0.1", "-p", fmt.Sprint(port), "-t", fmt.Sprint(duration)) - output, err := cmd.CombinedOutput() - t.Log(string(output)) - require.NoError(t, err) -} - -// TCP Forwarder - -type Forwarder struct { - listener net.Listener - naiveClient *cronet.NaiveClient - targetPort uint16 - ctx context.Context - cancel context.CancelFunc -} - -func startForwarder(t *testing.T, listenPort uint16, naiveClient *cronet.NaiveClient, targetPort uint16) *Forwarder { - listener, err := net.Listen("tcp", M.ParseSocksaddrHostPort("127.0.0.1", listenPort).String()) - require.NoError(t, err) - - ctx, cancel := context.WithCancel(context.Background()) - forwarder := &Forwarder{ - listener: listener, - naiveClient: naiveClient, - targetPort: targetPort, - ctx: ctx, - cancel: cancel, - } - - go forwarder.acceptLoop() - return forwarder -} - -func (f *Forwarder) acceptLoop() { - for { - conn, err := f.listener.Accept() - if err != nil { - return - } - go f.handleConnection(conn) - } -} - -func (f *Forwarder) handleConnection(conn net.Conn) { - defer conn.Close() - - remoteConn, err := f.naiveClient.DialEarly(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", f.targetPort)) - if err != nil { - return - } - defer remoteConn.Close() - - bufio.CopyConn(f.ctx, conn, remoteConn) -} - -func (f *Forwarder) Close() error { - f.cancel() - return f.listener.Close() -} - -// Certificate utilities - -func generateCertificate(t *testing.T, domain string) (caPem, certPem, keyPem string) { - tempDir := t.TempDir() - - // Generate CA key - caKey, err := rsa.GenerateKey(rand.Reader, 2048) - require.NoError(t, err) - - // CA certificate - spkiASN1, err := x509.MarshalPKIXPublicKey(caKey.Public()) - require.NoError(t, err) - var spki struct { - Algorithm pkix.AlgorithmIdentifier - SubjectPublicKey asn1.BitString - } - _, err = asn1.Unmarshal(spkiASN1, &spki) - require.NoError(t, err) - skid := sha1.Sum(spki.SubjectPublicKey.Bytes) - - caTpl := &x509.Certificate{ - SerialNumber: randomSerialNumber(t), - Subject: pkix.Name{CommonName: "Test CA"}, - SubjectKeyId: skid[:], - NotBefore: time.Now(), - NotAfter: time.Now().AddDate(1, 0, 0), - KeyUsage: x509.KeyUsageCertSign, - BasicConstraintsValid: true, - IsCA: true, - } - - caCert, err := x509.CreateCertificate(rand.Reader, caTpl, caTpl, caKey.Public(), caKey) - require.NoError(t, err) - - caPem = filepath.Join(tempDir, "ca.pem") - err = os.WriteFile(caPem, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: caCert}), 0o644) - require.NoError(t, err) - - // Server key - serverKey, err := rsa.GenerateKey(rand.Reader, 2048) - require.NoError(t, err) - - // Server certificate - serverTpl := &x509.Certificate{ - SerialNumber: randomSerialNumber(t), - Subject: pkix.Name{CommonName: domain}, - DNSNames: []string{domain}, - NotBefore: time.Now(), - NotAfter: time.Now().AddDate(0, 1, 0), - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - } - - serverCert, err := x509.CreateCertificate(rand.Reader, serverTpl, caTpl, serverKey.Public(), caKey) - require.NoError(t, err) - - certPem = filepath.Join(tempDir, "cert.pem") - err = os.WriteFile(certPem, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: serverCert}), 0o644) - require.NoError(t, err) - - keyPem = filepath.Join(tempDir, "key.pem") - keyDER, err := x509.MarshalPKCS8PrivateKey(serverKey) - require.NoError(t, err) - err = os.WriteFile(keyPem, pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}), 0o644) - require.NoError(t, err) - - return -} - -func randomSerialNumber(t *testing.T) *big.Int { - serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) - serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) - require.NoError(t, err) - return serialNumber -} diff --git a/test/quic_test.go b/test/quic_test.go deleted file mode 100644 index 33ef2f7f..00000000 --- a/test/quic_test.go +++ /dev/null @@ -1,516 +0,0 @@ -package test - -import ( - "bytes" - "context" - "encoding/pem" - "fmt" - "io" - "net" - "os" - "path/filepath" - "strings" - "sync" - "sync/atomic" - "testing" - "time" - - cronet "github.com/sagernet/cronet-go" - M "github.com/sagernet/sing/common/metadata" - - mDNS "github.com/miekg/dns" - "github.com/stretchr/testify/require" -) - -func startNaiveQUICServer(t *testing.T, certPem, keyPem string, listenPort uint16) { - binary := ensureNaiveServer(t) - - configTemplate, err := os.ReadFile("config/sing-box-quic.json") - require.NoError(t, err) - - certPem = filepath.ToSlash(certPem) - keyPem = filepath.ToSlash(keyPem) - - config := strings.ReplaceAll(string(configTemplate), "/cert.pem", certPem) - config = strings.ReplaceAll(config, "/key.pem", keyPem) - config = strings.Replace(config, `"listen_port": 10002`, fmt.Sprintf(`"listen_port": %d`, listenPort), 1) - - configPath := filepath.Join(t.TempDir(), "sing-box-quic.json") - err = os.WriteFile(configPath, []byte(config), 0o644) - require.NoError(t, err) - - startNaiveServerWithConfig(t, binary, configPath, listenPort, "udp") -} - -func startNaiveQUICServerWithECH(t *testing.T, certPath, keyPath, echKey string, listenPort uint16) { - binary := ensureNaiveServer(t) - - echKeyPath := filepath.Join(t.TempDir(), "ech_key.pem") - err := os.WriteFile(echKeyPath, []byte(echKey), 0o644) - require.NoError(t, err) - - certPath = filepath.ToSlash(certPath) - keyPath = filepath.ToSlash(keyPath) - echKeyPath = filepath.ToSlash(echKeyPath) - - config := fmt.Sprintf(`{ - "inbounds": [ - { - "type": "naive", - "listen": "::", - "listen_port": %d, - "network": "udp", - "users": [ - { - "username": "test", - "password": "test" - } - ], - "tls": { - "enabled": true, - "certificate_path": "%s", - "key_path": "%s", - "ech": { - "enabled": true, - "key_path": "%s" - } - } - } - ] -}`, listenPort, certPath, keyPath, echKeyPath) - - configPath := filepath.Join(t.TempDir(), "sing-box-quic-ech.json") - err = os.WriteFile(configPath, []byte(config), 0o644) - require.NoError(t, err) - - startNaiveServerWithConfig(t, binary, configPath, listenPort, "udp") -} - -// TestNaiveQUIC verifies NaiveClient connectivity with QUIC protocol. -func TestNaiveQUIC(t *testing.T) { - naiveQUICServerPort := reserveUDPPort(t) - caPem, certPem, keyPem := generateCertificate(t, "example.org") - caPemContent, err := os.ReadFile(caPem) - require.NoError(t, err) - - startNaiveQUICServer(t, certPem, keyPem, naiveQUICServerPort) - - client, err := cronet.NewNaiveClient(cronet.NaiveClientOptions{ - ServerAddress: M.ParseSocksaddrHostPort("127.0.0.1", naiveQUICServerPort), - ServerName: "example.org", - Username: "test", - Password: "test", - TrustedRootCertificates: string(caPemContent), - DNSResolver: localhostDNSResolverWithHTTPSResponse(t, naiveQUICServerPort, []string{"h3"}), - - QUIC: true, - }) - require.NoError(t, err) - require.NoError(t, client.Start()) - t.Cleanup(func() { client.Close() }) - _ = startNetLogForTest(t, client, "quic_basic_netlog.json", false) - - echoPort := reserveTCPPort(t) - startEchoServer(t, echoPort) - - conn, err := client.DialEarly(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", echoPort)) - require.NoError(t, err) - defer conn.Close() - - testData := []byte("Hello, NaiveProxy QUIC!") - _, err = conn.Write(testData) - require.NoError(t, err) - - buf := make([]byte, len(testData)) - _, err = io.ReadFull(conn, buf) - require.NoError(t, err) - require.Equal(t, testData, buf) -} - -// TestNaiveQUICLargeTransfer tests data integrity with large transfers over QUIC. -func TestNaiveQUICLargeTransfer(t *testing.T) { - naiveQUICServerPort := reserveUDPPort(t) - caPem, certPem, keyPem := generateCertificate(t, "example.org") - caPemContent, err := os.ReadFile(caPem) - require.NoError(t, err) - - startNaiveQUICServer(t, certPem, keyPem, naiveQUICServerPort) - - client, err := cronet.NewNaiveClient(cronet.NaiveClientOptions{ - ServerAddress: M.ParseSocksaddrHostPort("127.0.0.1", naiveQUICServerPort), - ServerName: "example.org", - Username: "test", - Password: "test", - TrustedRootCertificates: string(caPemContent), - DNSResolver: localhostDNSResolverWithHTTPSResponse(t, naiveQUICServerPort, []string{"h3"}), - - QUIC: true, - }) - require.NoError(t, err) - require.NoError(t, client.Start()) - t.Cleanup(func() { client.Close() }) - _ = startNetLogForTest(t, client, "quic_large_transfer_netlog.json", false) - - echoPort := reserveTCPPort(t) - startEchoServer(t, echoPort) - - conn, err := client.DialEarly(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", echoPort)) - require.NoError(t, err) - defer conn.Close() - - // Generate 256KB of test data - const dataSize = 256 * 1024 - testData := make([]byte, dataSize) - for i := range testData { - testData[i] = byte(i % 256) - } - - // Write in background - writeDone := make(chan error, 1) - go func() { - _, err := conn.Write(testData) - writeDone <- err - }() - - // Read all data back - receivedData := make([]byte, dataSize) - _, err = io.ReadFull(conn, receivedData) - require.NoError(t, err) - - // Wait for write to complete - require.NoError(t, <-writeDone) - - // Verify data integrity - require.Equal(t, testData, receivedData, "data mismatch in large transfer over QUIC") -} - -func TestNaiveQUICDomainNon443DoesNotIssueHTTPSDNSQueryByDefault(t *testing.T) { - naiveQUICServerPort := reserveUDPPort(t) - caPem, certPem, keyPem := generateCertificate(t, "example.org") - caPemContent, err := os.ReadFile(caPem) - require.NoError(t, err) - - startNaiveQUICServer(t, certPem, keyPem, naiveQUICServerPort) - - queryObservation := &dnsQueryObservation{} - dnsResolver := makeQUICDomainResolver(queryObservation, 0, mDNS.RcodeNameError) - - client, err := cronet.NewNaiveClient(cronet.NaiveClientOptions{ - ServerAddress: M.ParseSocksaddrHostPort("example.org", naiveQUICServerPort), - ServerName: "example.org", - Username: "test", - Password: "test", - TrustedRootCertificates: string(caPemContent), - DNSResolver: dnsResolver, - - QUIC: true, - }) - require.NoError(t, err) - require.NoError(t, client.Start()) - t.Cleanup(func() { client.Close() }) - _ = startNetLogForTest(t, client, "quic_non443_default_netlog.json", false) - - echoPort := reserveTCPPort(t) - startEchoServer(t, echoPort) - - conn, err := client.DialEarly(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", echoPort)) - require.NoError(t, err) - defer conn.Close() - - testData := []byte("quic non-443 dns query test") - _, err = conn.Write(testData) - require.NoError(t, err) - - buffer := make([]byte, len(testData)) - _, err = io.ReadFull(conn, buffer) - require.NoError(t, err) - require.Equal(t, testData, buffer) - - require.Greater(t, queryObservation.aQueryCount.Load(), int64(0)) - require.Equal( - t, - int64(0), - queryObservation.httpsQueryCount.Load(), - "unexpected HTTPS DNS query in default QUIC mode: %v", - queryObservation.queryNames(), - ) -} - -func TestNaiveQUICDomainNon443ECHHTTPSDNSDelayAffectsHandshake(t *testing.T) { - naiveQUICServerPort := reserveUDPPort(t) - caPem, certPem, keyPem := generateCertificate(t, "example.org") - caPemContent, err := os.ReadFile(caPem) - require.NoError(t, err) - - startNaiveQUICServer(t, certPem, keyPem, naiveQUICServerPort) - - queryObservation := &dnsQueryObservation{} - dnsResolver := makeQUICDomainResolver(queryObservation, 2*time.Second, mDNS.RcodeNameError) - - client, err := cronet.NewNaiveClient(cronet.NaiveClientOptions{ - ServerAddress: M.ParseSocksaddrHostPort("example.org", naiveQUICServerPort), - ServerName: "example.org", - Username: "test", - Password: "test", - TrustedRootCertificates: string(caPemContent), - DNSResolver: dnsResolver, - ECHEnabled: true, - QUIC: true, - - }) - require.NoError(t, err) - require.NoError(t, client.Start()) - t.Cleanup(func() { client.Close() }) - _ = startNetLogForTest(t, client, "quic_non443_ech_dns_delay_netlog.json", false) - - echoPort := reserveTCPPort(t) - startEchoServer(t, echoPort) - - conn, err := client.DialEarly(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", echoPort)) - require.NoError(t, err) - defer conn.Close() - - testData := []byte("quic non-443 dns delay test") - startTime := time.Now() - _, err = conn.Write(testData) - require.NoError(t, err) - handshakeDuration := time.Since(startTime) - - buffer := make([]byte, len(testData)) - _, err = io.ReadFull(conn, buffer) - require.NoError(t, err) - require.Equal(t, testData, buffer) - - require.GreaterOrEqual(t, handshakeDuration, 1500*time.Millisecond) - require.Greater(t, queryObservation.httpsQueryCount.Load(), int64(0)) - require.True( - t, - queryObservation.hasHTTPSPortQuery(fmt.Sprintf("_%d._https.example.org", naiveQUICServerPort)), - "expected HTTPS query for _%d._https.example.org, got %v", - naiveQUICServerPort, - queryObservation.queryNames(), - ) -} - -func TestNaiveQUICDomainNon443ECHFixedConfigDisablesHTTPSLookup(t *testing.T) { - naiveQUICServerPort := reserveUDPPort(t) - echConfigPEM, echKeyPEM, err := echKeygenDefault("not.example.org") - require.NoError(t, err) - - echConfigBlock, _ := pem.Decode([]byte(echConfigPEM)) - require.NotNil(t, echConfigBlock) - - caPem, certPem, keyPem := generateCertificate(t, "example.org") - caPemContent, err := os.ReadFile(caPem) - require.NoError(t, err) - - startNaiveQUICServerWithECH(t, certPem, keyPem, echKeyPEM, naiveQUICServerPort) - - queryObservation := &dnsQueryObservation{} - dnsResolver := makeQUICDomainResolver(queryObservation, 2*time.Second, mDNS.RcodeNameError) - - client, err := cronet.NewNaiveClient(cronet.NaiveClientOptions{ - ServerAddress: M.ParseSocksaddrHostPort("example.org", naiveQUICServerPort), - ServerName: "example.org", - Username: "test", - Password: "test", - TrustedRootCertificates: string(caPemContent), - DNSResolver: dnsResolver, - ECHEnabled: true, - ECHConfigList: echConfigBlock.Bytes, - QUIC: true, - - }) - require.NoError(t, err) - require.NoError(t, client.Start()) - t.Cleanup(func() { client.Close() }) - - netLogPath := startNetLogForTest(t, client, "quic_ech_fixed_config_netlog.json", true) - - echoPort := reserveTCPPort(t) - startEchoServer(t, echoPort) - - conn, err := client.DialEarly(context.Background(), M.ParseSocksaddrHostPort("127.0.0.1", echoPort)) - require.NoError(t, err) - defer conn.Close() - - startTime := time.Now() - testData := []byte("quic non-443 fixed ech config") - _, err = conn.Write(testData) - require.NoError(t, err) - handshakeDuration := time.Since(startTime) - - buffer := make([]byte, len(testData)) - _, err = io.ReadFull(conn, buffer) - require.NoError(t, err) - require.Equal(t, testData, buffer) - - client.Engine().StopNetLog() - - logContent, err := os.ReadFile(netLogPath) - require.NoError(t, err) - logString := string(logContent) - - require.Less(t, handshakeDuration, 3500*time.Millisecond) - require.Equal(t, int64(0), queryObservation.httpsQueryCount.Load(), "unexpected resolver HTTPS query: %v", queryObservation.queryNames()) - require.Contains(t, logString, fmt.Sprintf("_%d._https.example.org", naiveQUICServerPort)) -} - -func TestNaiveQUICFixedIPSkipsServerDNSQueries(t *testing.T) { - naiveQUICServerPort := reserveUDPPort(t) - caPem, certPem, keyPem := generateCertificate(t, "example.org") - caPemContent, err := os.ReadFile(caPem) - require.NoError(t, err) - - startNaiveQUICServer(t, certPem, keyPem, naiveQUICServerPort) - - queryObservation := &dnsQueryObservation{} - dnsResolver := makeQUICDomainResolver(queryObservation, 0, mDNS.RcodeNameError) - - client, err := cronet.NewNaiveClient(cronet.NaiveClientOptions{ - ServerAddress: M.ParseSocksaddrHostPort("127.0.0.1", naiveQUICServerPort), - ServerName: "example.org", - Username: "test", - Password: "test", - TrustedRootCertificates: string(caPemContent), - DNSResolver: dnsResolver, - QUIC: true, - - }) - require.NoError(t, err) - require.NoError(t, client.Start()) - t.Cleanup(func() { client.Close() }) - _ = startNetLogForTest(t, client, "quic_fixed_ip_netlog.json", false) - - echoPort := reserveTCPPort(t) - startEchoServer(t, echoPort) - - testData := []byte("quic fixed ip dns test") - target := M.ParseSocksaddrHostPort("127.0.0.1", echoPort) - - const maxAttempts = 3 - var lastErr error - for attempt := 1; attempt <= maxAttempts; attempt++ { - conn, err := client.DialEarly(context.Background(), target) - if err != nil { - lastErr = err - } else { - _, err = conn.Write(testData) - if err == nil { - buffer := make([]byte, len(testData)) - _, err = io.ReadFull(conn, buffer) - if err == nil && !bytes.Equal(buffer, testData) { - err = fmt.Errorf("unexpected echo payload") - } - } - _ = conn.Close() - lastErr = err - } - - if lastErr == nil { - break - } - if attempt < maxAttempts && isRetryableQUICProtocolError(lastErr) { - t.Logf("retrying after transient QUIC error (%d/%d): %v", attempt, maxAttempts, lastErr) - time.Sleep(200 * time.Millisecond) - continue - } - require.NoError(t, lastErr) - } - require.NoError(t, lastErr) - - totalQueryCount := queryObservation.aQueryCount.Load() + - queryObservation.aaaaQueryCount.Load() + - queryObservation.httpsQueryCount.Load() - require.Equal(t, int64(0), totalQueryCount, "expected zero DNS queries, got %v", queryObservation.queryNames()) -} - -func isRetryableQUICProtocolError(err error) bool { - if err == nil { - return false - } - return strings.Contains(strings.ToLower(err.Error()), "quic protocol error") -} - -type dnsQueryObservation struct { - aQueryCount atomic.Int64 - aaaaQueryCount atomic.Int64 - httpsQueryCount atomic.Int64 - namesMutex sync.Mutex - names []string -} - -func (o *dnsQueryObservation) record(question mDNS.Question) { - name := strings.TrimSuffix(strings.ToLower(question.Name), ".") - - o.namesMutex.Lock() - o.names = append(o.names, name) - o.namesMutex.Unlock() - - switch question.Qtype { - case mDNS.TypeA: - o.aQueryCount.Add(1) - case mDNS.TypeAAAA: - o.aaaaQueryCount.Add(1) - case mDNS.TypeHTTPS: - o.httpsQueryCount.Add(1) - } -} - -func (o *dnsQueryObservation) queryNames() []string { - o.namesMutex.Lock() - defer o.namesMutex.Unlock() - - names := make([]string, len(o.names)) - copy(names, o.names) - return names -} - -func (o *dnsQueryObservation) hasHTTPSPortQuery(queryPrefix string) bool { - queryPrefix = strings.ToLower(queryPrefix) - for _, queryName := range o.queryNames() { - if strings.HasPrefix(queryName, queryPrefix) { - return true - } - } - return false -} - -func makeQUICDomainResolver( - observation *dnsQueryObservation, - httpsResponseDelay time.Duration, - httpsResponseCode int, -) cronet.DNSResolverFunc { - return func(ctx context.Context, request *mDNS.Msg) *mDNS.Msg { - response := new(mDNS.Msg) - response.SetReply(request) - - for _, question := range request.Question { - observation.record(question) - - switch question.Qtype { - case mDNS.TypeA: - response.Answer = append(response.Answer, &mDNS.A{ - Hdr: mDNS.RR_Header{ - Name: question.Name, - Rrtype: mDNS.TypeA, - Class: mDNS.ClassINET, - Ttl: 300, - }, - A: net.ParseIP("127.0.0.1").To4(), - }) - case mDNS.TypeAAAA: - case mDNS.TypeHTTPS: - if httpsResponseDelay > 0 { - select { - case <-time.After(httpsResponseDelay): - case <-ctx.Done(): - return response - } - } - response.Rcode = httpsResponseCode - } - } - return response - } -} diff --git a/transport.go b/transport.go deleted file mode 100644 index 3ddd234f..00000000 --- a/transport.go +++ /dev/null @@ -1,313 +0,0 @@ -package cronet - -import ( - "context" - "io" - "net" - "net/http" - "os" - "runtime" - "strconv" - "sync" -) - -// RoundTripper is a wrapper from URLRequest to http.RoundTripper -type RoundTripper struct { - CheckRedirect func(newLocationUrl string) bool - Engine Engine - Executor Executor - - closeEngine bool - closeExecutor bool -} - -func (t *RoundTripper) close() { - if t.closeEngine { - t.Engine.Shutdown() - t.Engine.Destroy() - } - if t.closeExecutor { - t.Executor.Destroy() - } -} - -func (t *RoundTripper) RoundTrip(request *http.Request) (*http.Response, error) { - var emptyEngine Engine - if t.Engine == emptyEngine { - engineParams := NewEngineParams() - engineParams.SetEnableHTTP2(true) - engineParams.SetEnableQuic(true) - engineParams.SetEnableBrotli(true) - engineParams.SetUserAgent("Go-http-client/1.1") - t.Engine = NewEngine() - t.Engine.StartWithParams(engineParams) - engineParams.Destroy() - t.closeEngine = true - runtime.SetFinalizer(t, (*RoundTripper).close) - } - var emptyExecutor Executor - if t.Executor == emptyExecutor { - t.Executor = NewExecutor(func(executor Executor, command Runnable) { - go func() { - command.Run() - command.Destroy() - }() - }) - t.closeExecutor = true - if !t.closeEngine { - runtime.SetFinalizer(t, (*RoundTripper).close) - } - } - - requestParams := NewURLRequestParams() - if request.Method == "" { - requestParams.SetMethod("GET") - } else { - requestParams.SetMethod(request.Method) - } - for key, values := range request.Header { - for _, value := range values { - header := NewHTTPHeader() - header.SetName(key) - header.SetValue(value) - requestParams.AddHeader(header) - header.Destroy() - } - } - if request.Body != nil { - uploadProvider := NewUploadDataProvider(&bodyUploadProvider{request.Body, request.GetBody, request.ContentLength}) - requestParams.SetUploadDataProvider(uploadProvider) - requestParams.SetUploadDataExecutor(t.Executor) - } - responseHandler := urlResponse{ - checkRedirect: t.CheckRedirect, - roundTripper: t, - response: http.Response{ - Request: request, - Proto: request.Proto, - ProtoMajor: request.ProtoMajor, - ProtoMinor: request.ProtoMinor, - Header: make(http.Header), - }, - read: make(chan int), - cancel: make(chan struct{}), - done: make(chan struct{}), - } - responseHandler.response.Body = &responseHandler - responseHandler.wg.Add(1) - go responseHandler.monitorContext(request.Context()) - - callback := NewURLRequestCallback(&responseHandler) - urlRequest := NewURLRequest() - responseHandler.request = urlRequest - urlRequest.InitWithParams(t.Engine, request.URL.String(), requestParams, callback, t.Executor) - requestParams.Destroy() - urlRequest.Start() - responseHandler.wg.Wait() - return &responseHandler.response, responseHandler.err -} - -type urlResponse struct { - checkRedirect func(newLocationUrl string) bool - - wg sync.WaitGroup - wgDone sync.Once - request URLRequest - response http.Response - err error - roundTripper *RoundTripper // prevent GC from finalizing RoundTripper while request is in progress - - access sync.Mutex - read chan int - readBuffer Buffer - cancel chan struct{} - done chan struct{} -} - -func (r *urlResponse) monitorContext(ctx context.Context) { - if ctx.Done() == nil { - return - } - select { - case <-r.cancel: - case <-r.done: - case <-ctx.Done(): - r.err = ctx.Err() - r.Close() - } -} - -func (r *urlResponse) OnRedirectReceived(self URLRequestCallback, request URLRequest, info URLResponseInfo, newLocationUrl string) { - if r.checkRedirect != nil && !r.checkRedirect(newLocationUrl) { - r.response.Status = info.StatusText() - r.response.StatusCode = info.StatusCode() - headerLen := info.HeaderSize() - for i := 0; i < headerLen; i++ { - header := info.HeaderAt(i) - r.response.Header.Set(header.Name(), header.Value()) - } - r.response.Body = io.NopCloser(io.MultiReader()) - r.wg.Done() - return - } - request.FollowRedirect() -} - -func (r *urlResponse) OnResponseStarted(self URLRequestCallback, request URLRequest, info URLResponseInfo) { - r.response.Status = info.StatusText() - r.response.StatusCode = info.StatusCode() - headerLen := info.HeaderSize() - - for i := 0; i < headerLen; i++ { - header := info.HeaderAt(i) - r.response.Header.Set(header.Name(), header.Value()) - } - contentLength, _ := strconv.Atoi(r.response.Header.Get("Content-Length")) - r.response.ContentLength = int64(contentLength) - r.response.TransferEncoding = r.response.Header.Values("Content-Transfer-Encoding") - r.wgDone.Do(r.wg.Done) -} - -func (r *urlResponse) Read(p []byte) (n int, err error) { - select { - case <-r.done: - return 0, r.err - default: - } - - r.access.Lock() - - select { - case <-r.done: - return 0, r.err - default: - } - - r.readBuffer = NewBuffer() - r.readBuffer.InitWithDataAndCallback(p, NewBufferCallback(nil)) - r.request.Read(r.readBuffer) - r.access.Unlock() - - select { - case bytesRead := <-r.read: - return bytesRead, nil - case <-r.cancel: - return 0, net.ErrClosed - case <-r.done: - return 0, r.err - } -} - -func (r *urlResponse) Close() error { - r.access.Lock() - select { - case <-r.cancel: - r.access.Unlock() - return os.ErrClosed - case <-r.done: - r.access.Unlock() - return os.ErrClosed - default: - close(r.cancel) - r.request.Cancel() - } - r.access.Unlock() - - // Wait for the cancel callback to complete before returning. - // This ensures that the request is fully destroyed before the caller - // can destroy the engine or executor. - <-r.done - return nil -} - -func (r *urlResponse) OnReadCompleted(self URLRequestCallback, request URLRequest, info URLResponseInfo, buffer Buffer, bytesRead int64) { - r.access.Lock() - defer r.access.Unlock() - - if bytesRead == 0 { - r.close(request, io.EOF) - return - } - - select { - case <-r.cancel: - case <-r.done: - case r.read <- int(bytesRead): - r.readBuffer.Destroy() - r.readBuffer = Buffer{} - } -} - -func (r *urlResponse) OnSucceeded(self URLRequestCallback, request URLRequest, info URLResponseInfo) { - r.close(request, io.EOF) -} - -func (r *urlResponse) OnFailed(self URLRequestCallback, request URLRequest, info URLResponseInfo, error Error) { - r.close(request, ErrorFromError(error)) -} - -func (r *urlResponse) OnCanceled(self URLRequestCallback, request URLRequest, info URLResponseInfo) { - r.close(request, context.Canceled) -} - -func (r *urlResponse) close(request URLRequest, err error) { - r.access.Lock() - defer r.access.Unlock() - - select { - case <-r.done: - return - default: - } - - if r.err == nil { - r.err = err - } - - r.wgDone.Do(r.wg.Done) - close(r.done) - request.Destroy() -} - -type bodyUploadProvider struct { - body io.ReadCloser - getBody func() (io.ReadCloser, error) - contentLength int64 -} - -func (p *bodyUploadProvider) Length(self UploadDataProvider) int64 { - return p.contentLength -} - -func (p *bodyUploadProvider) Read(self UploadDataProvider, sink UploadDataSink, buffer Buffer) { - n, err := p.body.Read(buffer.DataSlice()) - if err != nil { - if p.contentLength == -1 && err == io.EOF { - sink.OnReadSucceeded(0, true) - return - } - sink.OnReadError(err.Error()) - } else { - sink.OnReadSucceeded(int64(n), false) - } -} - -func (p *bodyUploadProvider) Rewind(self UploadDataProvider, sink UploadDataSink) { - if p.getBody == nil { - sink.OnRewindError("unsupported") - return - } - p.body.Close() - newBody, err := p.getBody() - if err != nil { - sink.OnRewindError(err.Error()) - return - } - p.body = newBody - sink.OnRewindSucceeded() -} - -func (p *bodyUploadProvider) Close(self UploadDataProvider) { - self.Destroy() - p.body.Close() -} diff --git a/types.go b/types.go deleted file mode 100644 index 6f54933d..00000000 --- a/types.go +++ /dev/null @@ -1,309 +0,0 @@ -package cronet - -// This file contains shared type definitions for both CGO and purego implementations. -// All pointer types are represented as uintptr for cross-implementation compatibility. - -// Engine is an engine to process URLRequest, which uses the best HTTP stack -// available on the current platform. An instance of this class can be started -// using StartWithParams. -type Engine struct { - ptr uintptr -} - -// EngineParams contains parameters for initializing a Cronet Engine. -type EngineParams struct { - ptr uintptr -} - -// Buffer provided by the application to read and write data. -type Buffer struct { - ptr uintptr -} - -// BufferCallback is called when the Buffer is destroyed. -type BufferCallback struct { - ptr uintptr -} - -// Executor is an interface provided by the app to run commands asynchronously. -type Executor struct { - ptr uintptr -} - -// Runnable is a command to be executed by an Executor. -type Runnable struct { - ptr uintptr -} - -// URLRequest controls an HTTP request (GET, PUT, POST etc). -// Initialized by InitWithParams(). -// Note: All methods must be called on the Executor passed to InitWithParams(). -type URLRequest struct { - ptr uintptr -} - -// URLRequestParams contains parameters for initializing a URLRequest. -type URLRequestParams struct { - ptr uintptr -} - -// URLRequestCallback is used to receive callbacks from URLRequest. -type URLRequestCallback struct { - ptr uintptr -} - -// URLResponseInfo contains response information for a URLRequest. -type URLResponseInfo struct { - ptr uintptr -} - -// Error is the base error passed to URLRequestCallbackHandler.OnFailed(). -type Error struct { - ptr uintptr -} - -// DateTime represents a date and time value from cronet. -type DateTime struct { - ptr uintptr -} - -// Metrics contains timing metrics for a URLRequest. -type Metrics struct { - ptr uintptr -} - -// RequestFinishedInfo contains information about a finished request. -type RequestFinishedInfo struct { - ptr uintptr -} - -// URLRequestFinishedInfoListener is called when a request finishes. -type URLRequestFinishedInfoListener struct { - ptr uintptr -} - -// URLRequestStatusListener receives status updates for a URLRequest. -type URLRequestStatusListener struct { - ptr uintptr -} - -// UploadDataProvider provides upload data to a URLRequest. -type UploadDataProvider struct { - ptr uintptr -} - -// UploadDataSink is used by UploadDataProvider to signal events. -type UploadDataSink struct { - ptr uintptr -} - -// StreamEngine is an opaque object representing a Bidirectional stream creating engine. -// Created and configured outside of this API to facilitate sharing with other components. -type StreamEngine struct { - ptr uintptr -} - -// BidirectionalStream is an opaque object representing a Bidirectional Stream. -type BidirectionalStream struct { - ptr uintptr -} - -// HTTPHeader represents an HTTP header key-value pair. -type HTTPHeader struct { - ptr uintptr -} - -// QuicHint contains hints for QUIC protocol usage. -type QuicHint struct { - ptr uintptr -} - -// PublicKeyPins contains public key pins for certificate validation. -type PublicKeyPins struct { - ptr uintptr -} - -// BidirectionalStreamCallback -// Set of callbacks used to receive callbacks from bidirectional stream. -type BidirectionalStreamCallback interface { - // OnStreamReady - // Invoked when the stream is ready for reading and writing. - // Consumer may call BidirectionalStream.Read() to start reading data. - // Consumer may call BidirectionalStream.Write() to start writing - // data. - OnStreamReady(stream BidirectionalStream) - - // OnResponseHeadersReceived - // Invoked when initial response headers are received. - // Consumer must call BidirectionalStream.Read() to start reading. - // Consumer may call BidirectionalStream.Write() to start writing or - // close the stream. Contents of |headers| is valid for duration of the call. - /// - OnResponseHeadersReceived(stream BidirectionalStream, headers map[string]string, negotiatedProtocol string) - - // OnReadCompleted - // Invoked when data is read into the buffer passed to - // BidirectionalStream.Read(). Only part of the buffer may be - // populated. To continue reading, call BidirectionalStream.Read(). - // It may be invoked after on_response_trailers_received()}, if there was - // pending read data before trailers were received. - // - // If |bytesRead| is 0, it means the remote side has signaled that it will - // send no more data; future calls to BidirectionalStream.Read() - // will result in the OnReadCompleted() callback or OnSucceeded() callback if - // BidirectionalStream.Write() was invoked with endOfStream set to - // true. - OnReadCompleted(stream BidirectionalStream, bytesRead int) - - // OnWriteCompleted - // Invoked when all data passed to BidirectionalStream.Write() is - // sent. To continue writing, call BidirectionalStream.Write(). - OnWriteCompleted(stream BidirectionalStream) - - // OnResponseTrailersReceived - // Invoked when trailers are received before closing the stream. Only invoked - // when server sends trailers, which it may not. May be invoked while there is - // read data remaining in local buffer. Contents of |trailers| is valid for - // duration of the call. - OnResponseTrailersReceived(stream BidirectionalStream, trailers map[string]string) - - // OnSucceeded - // Invoked when there is no data to be read or written and the stream is - // closed successfully remotely and locally. Once invoked, no further callback - // methods will be invoked. - OnSucceeded(stream BidirectionalStream) - - // OnFailed - // Invoked if the stream failed for any reason after - // BidirectionalStream.Start(). HTTP/2 error codes are - // mapped to chrome net error codes. Once invoked, no further callback methods - // will be invoked. - OnFailed(stream BidirectionalStream, netError int) - - // OnCanceled - // Invoked if the stream was canceled via - // BidirectionalStream.Cancel(). Once invoked, no further callback - // methods will be invoked. - OnCanceled(stream BidirectionalStream) -} - -// BidirectionalStreamHeaderArray is used to pass headers to bidirectional streams. -type BidirectionalStreamHeaderArray struct { - ptr uintptr -} - -// EngineParamsHTTPCacheMode specifies HTTP cache mode. -type EngineParamsHTTPCacheMode int32 - -const ( - // HTTPCacheModeDisabled disables caching for the engine. - HTTPCacheModeDisabled EngineParamsHTTPCacheMode = 0 - // HTTPCacheModeInMemory enables in-memory caching, including HTTP data. - HTTPCacheModeInMemory EngineParamsHTTPCacheMode = 1 - // HTTPCacheModeDiskNoHTTP enables on-disk caching, excluding HTTP data. - HTTPCacheModeDiskNoHTTP EngineParamsHTTPCacheMode = 2 - // HTTPCacheModeDisk enables on-disk caching, including HTTP data. - HTTPCacheModeDisk EngineParamsHTTPCacheMode = 3 -) - -// URLRequestParamsRequestPriority specifies request priority level. -type URLRequestParamsRequestPriority int - -const ( - // URLRequestParamsRequestPriorityIdle - // Lowest request priority. - URLRequestParamsRequestPriorityIdle URLRequestParamsRequestPriority = 0 - - // URLRequestParamsRequestPriorityLowest - // Very low request priority. - URLRequestParamsRequestPriorityLowest URLRequestParamsRequestPriority = 1 - - // URLRequestParamsRequestPriorityLow - // Low request priority. - URLRequestParamsRequestPriorityLow URLRequestParamsRequestPriority = 2 - - // URLRequestParamsRequestPriorityMedium - // Medium request priority. This is the default priority given to the request. - URLRequestParamsRequestPriorityMedium URLRequestParamsRequestPriority = 3 - - // URLRequestParamsRequestPriorityHighest - // Highest request priority. - URLRequestParamsRequestPriorityHighest URLRequestParamsRequestPriority = 4 -) - -// URLRequestParamsIdempotency specifies idempotency of a request. -type URLRequestParamsIdempotency int - -const ( - URLRequestParamsIdempotencyDefaultIdempotency URLRequestParamsIdempotency = 0 - URLRequestParamsIdempotencyIdempotent URLRequestParamsIdempotency = 1 - URLRequestParamsIdempotencyNotIdempotent URLRequestParamsIdempotency = 2 -) - -// URLRequestStatusListenerStatus specifies the status of a URL request. -type URLRequestStatusListenerStatus int - -const ( - URLRequestStatusListenerStatusInvalid URLRequestStatusListenerStatus = -1 - URLRequestStatusListenerStatusIdle URLRequestStatusListenerStatus = 0 - URLRequestStatusListenerStatusWaitingForStalledSocketPool URLRequestStatusListenerStatus = 1 - URLRequestStatusListenerStatusWaitingForAvailableSocket URLRequestStatusListenerStatus = 2 - URLRequestStatusListenerStatusWaitingForDelegate URLRequestStatusListenerStatus = 3 - URLRequestStatusListenerStatusWaitingForCache URLRequestStatusListenerStatus = 4 - URLRequestStatusListenerStatusDownloadingPacFile URLRequestStatusListenerStatus = 5 - URLRequestStatusListenerStatusResolvingProxyForURL URLRequestStatusListenerStatus = 6 - URLRequestStatusListenerStatusResolvingHostInPacFile URLRequestStatusListenerStatus = 7 - URLRequestStatusListenerStatusEstablishingProxyTunnel URLRequestStatusListenerStatus = 8 - URLRequestStatusListenerStatusResolvingHost URLRequestStatusListenerStatus = 9 - URLRequestStatusListenerStatusConnecting URLRequestStatusListenerStatus = 10 - URLRequestStatusListenerStatusSSLHandshake URLRequestStatusListenerStatus = 11 - URLRequestStatusListenerStatusSendingRequest URLRequestStatusListenerStatus = 12 - URLRequestStatusListenerStatusWaitingForResponse URLRequestStatusListenerStatus = 13 - URLRequestStatusListenerStatusReadingResponse URLRequestStatusListenerStatus = 14 -) - -// URLRequestFinishedInfoFinishedReason -// The reason why the request finished. -type URLRequestFinishedInfoFinishedReason int - -const ( - // URLRequestFinishedInfoFinishedReasonSucceeded - // The request succeeded. - URLRequestFinishedInfoFinishedReasonSucceeded URLRequestFinishedInfoFinishedReason = 0 - - // URLRequestFinishedInfoFinishedReasonFailed - // The request failed or returned an error. - URLRequestFinishedInfoFinishedReasonFailed URLRequestFinishedInfoFinishedReason = 1 - - // URLRequestFinishedInfoFinishedReasonCanceled - // The request was canceled. - URLRequestFinishedInfoFinishedReasonCanceled URLRequestFinishedInfoFinishedReason = 2 -) - -// Dialer is a callback function for custom TCP connection establishment. -// address: IP address string (e.g. "1.2.3.4" or "::1") -// port: Port number -// Returns: connected socket fd on success, negative net error code on failure. -// Common error codes: -// -// ERR_CONNECTION_REFUSED (-102) -// ERR_CONNECTION_FAILED (-104) -// ERR_ADDRESS_UNREACHABLE (-109) -// ERR_CONNECTION_TIMED_OUT (-118) -type Dialer func(address string, port uint16) int - -// UDPDialer is a callback function for custom UDP socket creation. -// address: IP address string (e.g. "1.2.3.4" or "::1") -// port: Port number -// Returns: -// - fd: socket fd on success, negative net error code on failure -// - localAddress: local IP address string (may be empty) -// - localPort: local port number -// -// The returned socket can be: -// - AF_INET/AF_INET6 SOCK_DGRAM: Standard UDP socket (may be connected) -// - AF_UNIX SOCK_DGRAM: Unix domain datagram socket (Unix/macOS/Linux) -// - AF_UNIX SOCK_STREAM: Unix domain stream socket (Windows, with framing) -// -// Cronet will NOT call connect() on the returned socket. -type UDPDialer func(address string, port uint16) (fd int, localAddress string, localPort uint16) diff --git a/upload_data_provider_cgo.go b/upload_data_provider_cgo.go deleted file mode 100644 index 136e30d0..00000000 --- a/upload_data_provider_cgo.go +++ /dev/null @@ -1,18 +0,0 @@ -//go:build !with_purego - -package cronet - -// #include -// #include -// #include -import "C" - -import "unsafe" - -func (p UploadDataProvider) SetClientContext(context unsafe.Pointer) { - C.Cronet_UploadDataProvider_SetClientContext(C.Cronet_UploadDataProviderPtr(unsafe.Pointer(p.ptr)), C.Cronet_ClientContext(context)) -} - -func (p UploadDataProvider) ClientContext() unsafe.Pointer { - return unsafe.Pointer(C.Cronet_UploadDataProvider_GetClientContext(C.Cronet_UploadDataProviderPtr(unsafe.Pointer(p.ptr)))) -} diff --git a/upload_data_provider_handler.go b/upload_data_provider_handler.go deleted file mode 100644 index a41c93e5..00000000 --- a/upload_data_provider_handler.go +++ /dev/null @@ -1,46 +0,0 @@ -package cronet - -// UploadDataProviderHandler is the interface that must be implemented to provide upload data. -type UploadDataProviderHandler interface { - // Length - // If this is a non-chunked upload, returns the length of the upload. Must - // always return -1 if this is a chunked upload. - Length(self UploadDataProvider) int64 - - // Read - // Reads upload data into |buffer|. Each call of this method must be followed be a - // single call, either synchronous or asynchronous, to - // UploadDataSink.OnReadSucceeded() on success - // or UploadDataSink.OnReadError() on failure. Neither read nor rewind - // will be called until one of those methods or the other is called. Even if - // the associated UrlRequest is canceled, one or the other must - // still be called before resources can be safely freed. - // - // @param sink The object to notify when the read has completed, - // successfully or otherwise. - // @param buffer The buffer to copy the read bytes into. - Read(self UploadDataProvider, sink UploadDataSink, buffer Buffer) - - // Rewind - // Rewinds upload data. Each call must be followed be a single - // call, either synchronous or asynchronous, to - // UploadDataSink.OnRewindSucceeded() on success or - // UploadDataSink.OnRewindError() on failure. Neither read nor rewind - // will be called until one of those methods or the other is called. - // Even if the associated UrlRequest is canceled, one or the other - // must still be called before resources can be safely freed. - // - // If rewinding is not supported, this should call - // UploadDataSink.OnRewindError(). Note that rewinding is required to - // follow redirects that preserve the upload body, and for retrying when the - // server times out stale sockets. - // - // @param sink The object to notify when the rewind operation has - // completed, successfully or otherwise. - Rewind(self UploadDataProvider, sink UploadDataSink) - - // Close - // Called when this UploadDataProvider is no longer needed by a request, so that resources - // (like a file) can be explicitly released. - Close(self UploadDataProvider) -} diff --git a/upload_data_provider_impl_cgo.go b/upload_data_provider_impl_cgo.go deleted file mode 100644 index fcec7048..00000000 --- a/upload_data_provider_impl_cgo.go +++ /dev/null @@ -1,110 +0,0 @@ -//go:build !with_purego - -package cronet - -// #include -// #include -// #include -// extern CRONET_EXPORT int64_t cronetUploadDataProviderGetLength(Cronet_UploadDataProviderPtr self); -// extern CRONET_EXPORT void cronetUploadDataProviderRead(Cronet_UploadDataProviderPtr self, Cronet_UploadDataSinkPtr upload_data_sink, Cronet_BufferPtr buffer); -// extern CRONET_EXPORT void cronetUploadDataProviderRewind(Cronet_UploadDataProviderPtr self, Cronet_UploadDataSinkPtr upload_data_sink); -// extern CRONET_EXPORT void cronetUploadDataProviderClose(Cronet_UploadDataProviderPtr self); -import "C" - -import ( - "sync" - "sync/atomic" - "unsafe" -) - -type uploadDataProviderEntry struct { - handler UploadDataProviderHandler - destroyed atomic.Bool -} - -var ( - uploadDataAccess sync.RWMutex - uploadDataProviderMap map[uintptr]*uploadDataProviderEntry -) - -func init() { - uploadDataProviderMap = make(map[uintptr]*uploadDataProviderEntry) -} - -func NewUploadDataProvider(handler UploadDataProviderHandler) UploadDataProvider { - if handler == nil { - panic("nil upload data provider handler") - } - ptr := C.Cronet_UploadDataProvider_CreateWith( - (*[0]byte)(C.cronetUploadDataProviderGetLength), - (*[0]byte)(C.cronetUploadDataProviderRead), - (*[0]byte)(C.cronetUploadDataProviderRewind), - (*[0]byte)(C.cronetUploadDataProviderClose), - ) - ptrVal := uintptr(unsafe.Pointer(ptr)) - uploadDataAccess.Lock() - uploadDataProviderMap[ptrVal] = &uploadDataProviderEntry{handler: handler} - uploadDataAccess.Unlock() - return UploadDataProvider{ptrVal} -} - -func (p UploadDataProvider) Destroy() { - uploadDataAccess.RLock() - entry := uploadDataProviderMap[p.ptr] - uploadDataAccess.RUnlock() - if entry != nil { - entry.destroyed.Store(true) - } - C.Cronet_UploadDataProvider_Destroy(C.Cronet_UploadDataProviderPtr(unsafe.Pointer(p.ptr))) -} - -func instanceOfUploadDataProvider(self C.Cronet_UploadDataProviderPtr) UploadDataProviderHandler { - uploadDataAccess.RLock() - defer uploadDataAccess.RUnlock() - entry := uploadDataProviderMap[uintptr(unsafe.Pointer(self))] - if entry == nil || entry.destroyed.Load() { - return nil - } - return entry.handler -} - -//export cronetUploadDataProviderGetLength -func cronetUploadDataProviderGetLength(self C.Cronet_UploadDataProviderPtr) C.int64_t { - handler := instanceOfUploadDataProvider(self) - if handler == nil { - return 0 // Post-destroy callback, return 0 - } - return C.int64_t(handler.Length(UploadDataProvider{uintptr(unsafe.Pointer(self))})) -} - -//export cronetUploadDataProviderRead -func cronetUploadDataProviderRead(self C.Cronet_UploadDataProviderPtr, sink C.Cronet_UploadDataSinkPtr, buffer C.Cronet_BufferPtr) { - handler := instanceOfUploadDataProvider(self) - if handler == nil { - return // Post-destroy callback, silently ignore - } - handler.Read(UploadDataProvider{uintptr(unsafe.Pointer(self))}, UploadDataSink{uintptr(unsafe.Pointer(sink))}, Buffer{uintptr(unsafe.Pointer(buffer))}) -} - -//export cronetUploadDataProviderRewind -func cronetUploadDataProviderRewind(self C.Cronet_UploadDataProviderPtr, sink C.Cronet_UploadDataSinkPtr) { - handler := instanceOfUploadDataProvider(self) - if handler == nil { - return // Post-destroy callback, silently ignore - } - handler.Rewind(UploadDataProvider{uintptr(unsafe.Pointer(self))}, UploadDataSink{uintptr(unsafe.Pointer(sink))}) -} - -//export cronetUploadDataProviderClose -func cronetUploadDataProviderClose(self C.Cronet_UploadDataProviderPtr) { - ptr := uintptr(unsafe.Pointer(self)) - handler := instanceOfUploadDataProvider(self) - if handler == nil { - return // Post-destroy callback, silently ignore - } - handler.Close(UploadDataProvider{ptr}) - // Close is terminal callback - safe to cleanup - uploadDataAccess.Lock() - delete(uploadDataProviderMap, ptr) - uploadDataAccess.Unlock() -} diff --git a/upload_data_provider_impl_purego.go b/upload_data_provider_impl_purego.go deleted file mode 100644 index 557139f8..00000000 --- a/upload_data_provider_impl_purego.go +++ /dev/null @@ -1,117 +0,0 @@ -//go:build with_purego - -package cronet - -import ( - "sync" - "sync/atomic" - - "github.com/sagernet/cronet-go/internal/cronet" - - "github.com/ebitengine/purego" -) - -type uploadDataProviderEntry struct { - handler UploadDataProviderHandler - destroyed atomic.Bool -} - -var ( - uploadDataAccess sync.RWMutex - uploadDataProviderMap map[uintptr]*uploadDataProviderEntry - - uploadDataProviderGetLength uintptr - uploadDataProviderRead uintptr - uploadDataProviderRewind uintptr - uploadDataProviderClose uintptr -) - -func init() { - uploadDataProviderMap = make(map[uintptr]*uploadDataProviderEntry) - - uploadDataProviderGetLength = purego.NewCallback(onGetLengthCallback) - uploadDataProviderRead = purego.NewCallback(onReadCallback) - uploadDataProviderRewind = purego.NewCallback(onRewindCallback) - uploadDataProviderClose = purego.NewCallback(onCloseCallback) -} - -func instanceOfUploadDataProvider(self uintptr) UploadDataProviderHandler { - uploadDataAccess.RLock() - defer uploadDataAccess.RUnlock() - entry := uploadDataProviderMap[self] - if entry == nil || entry.destroyed.Load() { - return nil - } - return entry.handler -} - -func onGetLengthCallback(self uintptr) uintptr { - handler := instanceOfUploadDataProvider(self) - if handler == nil { - return 0 - } - return uintptr(handler.Length(UploadDataProvider{self})) -} - -func onReadCallback(self, sink, buffer uintptr) uintptr { - handler := instanceOfUploadDataProvider(self) - if handler == nil { - return 0 - } - handler.Read( - UploadDataProvider{self}, - UploadDataSink{sink}, - Buffer{buffer}, - ) - return 0 -} - -func onRewindCallback(self, sink uintptr) uintptr { - handler := instanceOfUploadDataProvider(self) - if handler == nil { - return 0 - } - handler.Rewind( - UploadDataProvider{self}, - UploadDataSink{sink}, - ) - return 0 -} - -func onCloseCallback(self uintptr) uintptr { - handler := instanceOfUploadDataProvider(self) - if handler == nil { - return 0 - } - handler.Close(UploadDataProvider{self}) - uploadDataAccess.Lock() - delete(uploadDataProviderMap, self) - uploadDataAccess.Unlock() - return 0 -} - -func NewUploadDataProvider(handler UploadDataProviderHandler) UploadDataProvider { - if handler == nil { - panic("nil upload data provider handler") - } - ptr := cronet.UploadDataProviderCreateWith( - uploadDataProviderGetLength, - uploadDataProviderRead, - uploadDataProviderRewind, - uploadDataProviderClose, - ) - uploadDataAccess.Lock() - uploadDataProviderMap[ptr] = &uploadDataProviderEntry{handler: handler} - uploadDataAccess.Unlock() - return UploadDataProvider{ptr} -} - -func (p UploadDataProvider) Destroy() { - uploadDataAccess.RLock() - entry := uploadDataProviderMap[p.ptr] - uploadDataAccess.RUnlock() - if entry != nil { - entry.destroyed.Store(true) - } - cronet.UploadDataProviderDestroy(p.ptr) -} diff --git a/upload_data_provider_purego.go b/upload_data_provider_purego.go deleted file mode 100644 index 02d0b8be..00000000 --- a/upload_data_provider_purego.go +++ /dev/null @@ -1,17 +0,0 @@ -//go:build with_purego - -package cronet - -import ( - "unsafe" - - "github.com/sagernet/cronet-go/internal/cronet" -) - -func (p UploadDataProvider) SetClientContext(context unsafe.Pointer) { - cronet.UploadDataProviderSetClientContext(p.ptr, uintptr(context)) -} - -func (p UploadDataProvider) ClientContext() unsafe.Pointer { - return unsafe.Pointer(cronet.UploadDataProviderGetClientContext(p.ptr)) -} diff --git a/upload_data_sink_cgo.go b/upload_data_sink_cgo.go deleted file mode 100644 index a0d1f661..00000000 --- a/upload_data_sink_cgo.go +++ /dev/null @@ -1,54 +0,0 @@ -//go:build !with_purego - -package cronet - -// #include -// #include -// #include -import "C" -import "unsafe" - -// OnReadSucceeded -// -// Called by UploadDataProviderHandler when a read succeeds. -// -// @param bytesRead number of bytes read into buffer passed to UploadDataProviderHandler.Read(). -// @param finalChunk For chunked uploads, |true| if this is the final -// -// read. It must be |false| for non-chunked uploads. -func (s UploadDataSink) OnReadSucceeded(bytesRead int64, finalChunk bool) { - C.Cronet_UploadDataSink_OnReadSucceeded(C.Cronet_UploadDataSinkPtr(unsafe.Pointer(s.ptr)), C.uint64_t(bytesRead), C.bool(finalChunk)) -} - -// OnReadError -// Called by UploadDataProviderHandler when a read fails. -// @param message to pass on to URLRequestCallbackHandler.OnFailed(). -func (s UploadDataSink) OnReadError(message string) { - cMessage := C.CString(message) - C.Cronet_UploadDataSink_OnReadError(C.Cronet_UploadDataSinkPtr(unsafe.Pointer(s.ptr)), cMessage) - C.free(unsafe.Pointer(cMessage)) -} - -// OnRewindSucceeded -// Called by UploadDataProviderHandler when a rewind succeeds. -func (s UploadDataSink) OnRewindSucceeded() { - C.Cronet_UploadDataSink_OnRewindSucceeded(C.Cronet_UploadDataSinkPtr(unsafe.Pointer(s.ptr))) -} - -// OnRewindError -// Called by UploadDataProviderHandler when a rewind fails, or if rewinding -// uploads is not supported. -// * @param message to pass on to URLRequestCallbackHandler.OnFailed(). -func (s UploadDataSink) OnRewindError(message string) { - cMessage := C.CString(message) - C.Cronet_UploadDataSink_OnRewindError(C.Cronet_UploadDataSinkPtr(unsafe.Pointer(s.ptr)), cMessage) - C.free(unsafe.Pointer(cMessage)) -} - -func (s UploadDataSink) SetClientContext(context unsafe.Pointer) { - C.Cronet_UploadDataSink_SetClientContext(C.Cronet_UploadDataSinkPtr(unsafe.Pointer(s.ptr)), C.Cronet_ClientContext(context)) -} - -func (s UploadDataSink) ClientContext() unsafe.Pointer { - return unsafe.Pointer(C.Cronet_UploadDataSink_GetClientContext(C.Cronet_UploadDataSinkPtr(unsafe.Pointer(s.ptr)))) -} diff --git a/upload_data_sink_impl_cgo.go b/upload_data_sink_impl_cgo.go deleted file mode 100644 index 69d600b3..00000000 --- a/upload_data_sink_impl_cgo.go +++ /dev/null @@ -1,87 +0,0 @@ -//go:build !with_purego - -package cronet - -// #include -// #include -// #include -// extern CRONET_EXPORT void cronetUploadDataSinkOnReadSucceeded(Cronet_UploadDataSinkPtr self, uint64_t bytes_read, bool final_chunk); -// extern CRONET_EXPORT void cronetUploadDataSinkOnReadError(Cronet_UploadDataSinkPtr self, Cronet_String error_message); -// extern CRONET_EXPORT void cronetUploadDataSinkOnRewindSucceeded(Cronet_UploadDataSinkPtr self); -// extern CRONET_EXPORT void cronetUploadDataSinkOnRewindError(Cronet_UploadDataSinkPtr self, Cronet_String error_message); -import "C" - -import ( - "sync" - "unsafe" -) - -// UploadDataSinkHandler is an interface for custom UploadDataSink implementations (for testing/mocking). -type UploadDataSinkHandler interface { - OnReadSucceeded(self UploadDataSink, bytesRead uint64, finalChunk bool) - OnReadError(self UploadDataSink, errorMessage string) - OnRewindSucceeded(self UploadDataSink) - OnRewindError(self UploadDataSink, errorMessage string) -} - -// NewUploadDataSinkWith creates a new UploadDataSink with custom handler (for testing/mocking). -func NewUploadDataSinkWith(handler UploadDataSinkHandler) UploadDataSink { - ptr := C.Cronet_UploadDataSink_CreateWith( - (*[0]byte)(C.cronetUploadDataSinkOnReadSucceeded), - (*[0]byte)(C.cronetUploadDataSinkOnReadError), - (*[0]byte)(C.cronetUploadDataSinkOnRewindSucceeded), - (*[0]byte)(C.cronetUploadDataSinkOnRewindError), - ) - ptrVal := uintptr(unsafe.Pointer(ptr)) - uploadDataSinkHandlerAccess.Lock() - uploadDataSinkHandlerMap[ptrVal] = handler - uploadDataSinkHandlerAccess.Unlock() - return UploadDataSink{ptrVal} -} - -var ( - uploadDataSinkHandlerAccess sync.RWMutex - uploadDataSinkHandlerMap map[uintptr]UploadDataSinkHandler -) - -func init() { - uploadDataSinkHandlerMap = make(map[uintptr]UploadDataSinkHandler) -} - -func instanceOfUploadDataSinkHandler(self C.Cronet_UploadDataSinkPtr) UploadDataSinkHandler { - uploadDataSinkHandlerAccess.RLock() - defer uploadDataSinkHandlerAccess.RUnlock() - return uploadDataSinkHandlerMap[uintptr(unsafe.Pointer(self))] -} - -//export cronetUploadDataSinkOnReadSucceeded -func cronetUploadDataSinkOnReadSucceeded(self C.Cronet_UploadDataSinkPtr, bytesRead C.uint64_t, finalChunk C.bool) { - handler := instanceOfUploadDataSinkHandler(self) - if handler != nil { - handler.OnReadSucceeded(UploadDataSink{uintptr(unsafe.Pointer(self))}, uint64(bytesRead), bool(finalChunk)) - } -} - -//export cronetUploadDataSinkOnReadError -func cronetUploadDataSinkOnReadError(self C.Cronet_UploadDataSinkPtr, errorMessage C.Cronet_String) { - handler := instanceOfUploadDataSinkHandler(self) - if handler != nil { - handler.OnReadError(UploadDataSink{uintptr(unsafe.Pointer(self))}, C.GoString(errorMessage)) - } -} - -//export cronetUploadDataSinkOnRewindSucceeded -func cronetUploadDataSinkOnRewindSucceeded(self C.Cronet_UploadDataSinkPtr) { - handler := instanceOfUploadDataSinkHandler(self) - if handler != nil { - handler.OnRewindSucceeded(UploadDataSink{uintptr(unsafe.Pointer(self))}) - } -} - -//export cronetUploadDataSinkOnRewindError -func cronetUploadDataSinkOnRewindError(self C.Cronet_UploadDataSinkPtr, errorMessage C.Cronet_String) { - handler := instanceOfUploadDataSinkHandler(self) - if handler != nil { - handler.OnRewindError(UploadDataSink{uintptr(unsafe.Pointer(self))}, C.GoString(errorMessage)) - } -} diff --git a/upload_data_sink_purego.go b/upload_data_sink_purego.go deleted file mode 100644 index afa7d86e..00000000 --- a/upload_data_sink_purego.go +++ /dev/null @@ -1,37 +0,0 @@ -//go:build with_purego - -package cronet - -import ( - "unsafe" - - "github.com/sagernet/cronet-go/internal/cronet" -) - -func (s UploadDataSink) Destroy() { - cronet.UploadDataSinkDestroy(s.ptr) -} - -func (s UploadDataSink) SetClientContext(context unsafe.Pointer) { - cronet.UploadDataSinkSetClientContext(s.ptr, uintptr(context)) -} - -func (s UploadDataSink) ClientContext() unsafe.Pointer { - return unsafe.Pointer(cronet.UploadDataSinkGetClientContext(s.ptr)) -} - -func (s UploadDataSink) OnReadSucceeded(bytesRead int64, finalChunk bool) { - cronet.UploadDataSinkOnReadSucceeded(s.ptr, uint64(bytesRead), finalChunk) -} - -func (s UploadDataSink) OnReadError(message string) { - cronet.UploadDataSinkOnReadError(s.ptr, message) -} - -func (s UploadDataSink) OnRewindSucceeded() { - cronet.UploadDataSinkOnRewindSucceeded(s.ptr) -} - -func (s UploadDataSink) OnRewindError(message string) { - cronet.UploadDataSinkOnRewindError(s.ptr, message) -} diff --git a/url_request_callback_cgo.go b/url_request_callback_cgo.go deleted file mode 100644 index 0683210e..00000000 --- a/url_request_callback_cgo.go +++ /dev/null @@ -1,18 +0,0 @@ -//go:build !with_purego - -package cronet - -// #include -// #include -// #include -import "C" - -import "unsafe" - -func (c URLRequestCallback) SetClientContext(context unsafe.Pointer) { - C.Cronet_UrlRequestCallback_SetClientContext(C.Cronet_UrlRequestCallbackPtr(unsafe.Pointer(c.ptr)), C.Cronet_ClientContext(context)) -} - -func (c URLRequestCallback) ClientContext() unsafe.Pointer { - return unsafe.Pointer(C.Cronet_UrlRequestCallback_GetClientContext(C.Cronet_UrlRequestCallbackPtr(unsafe.Pointer(c.ptr)))) -} diff --git a/url_request_callback_impl_cgo.go b/url_request_callback_impl_cgo.go deleted file mode 100644 index 8264936c..00000000 --- a/url_request_callback_impl_cgo.go +++ /dev/null @@ -1,139 +0,0 @@ -//go:build !with_purego - -package cronet - -// #include -// #include -// #include -// extern CRONET_EXPORT void cronetURLRequestCallbackOnRedirectReceived(Cronet_UrlRequestCallbackPtr self, Cronet_UrlRequestPtr request, Cronet_UrlResponseInfoPtr info, Cronet_String new_location_url); -// extern CRONET_EXPORT void cronetURLRequestCallbackOnResponseStarted(Cronet_UrlRequestCallbackPtr self, Cronet_UrlRequestPtr request, Cronet_UrlResponseInfoPtr info); -// extern CRONET_EXPORT void cronetURLRequestCallbackOnReadCompleted(Cronet_UrlRequestCallbackPtr self, Cronet_UrlRequestPtr request, Cronet_UrlResponseInfoPtr info, Cronet_BufferPtr buffer, uint64_t bytes_read); -// extern CRONET_EXPORT void cronetURLRequestCallbackOnSucceeded(Cronet_UrlRequestCallbackPtr self, Cronet_UrlRequestPtr request, Cronet_UrlResponseInfoPtr info); -// extern CRONET_EXPORT void cronetURLRequestCallbackOnFailed(Cronet_UrlRequestCallbackPtr self, Cronet_UrlRequestPtr request, Cronet_UrlResponseInfoPtr info, Cronet_ErrorPtr error); -// extern CRONET_EXPORT void cronetURLRequestCallbackOnCanceled(Cronet_UrlRequestCallbackPtr self, Cronet_UrlRequestPtr request, Cronet_UrlResponseInfoPtr info); -import "C" - -import ( - "sync" - "sync/atomic" - "unsafe" -) - -type urlRequestCallbackEntry struct { - handler URLRequestCallbackHandler - destroyed atomic.Bool -} - -var ( - urlRequestCallbackAccess sync.RWMutex - urlRequestCallbackMap map[uintptr]*urlRequestCallbackEntry -) - -func init() { - urlRequestCallbackMap = make(map[uintptr]*urlRequestCallbackEntry) -} - -func NewURLRequestCallback(handler URLRequestCallbackHandler) URLRequestCallback { - if handler == nil { - panic("nil url request callback handler") - } - ptr := C.Cronet_UrlRequestCallback_CreateWith( - (*[0]byte)(C.cronetURLRequestCallbackOnRedirectReceived), - (*[0]byte)(C.cronetURLRequestCallbackOnResponseStarted), - (*[0]byte)(C.cronetURLRequestCallbackOnReadCompleted), - (*[0]byte)(C.cronetURLRequestCallbackOnSucceeded), - (*[0]byte)(C.cronetURLRequestCallbackOnFailed), - (*[0]byte)(C.cronetURLRequestCallbackOnCanceled), - ) - ptrVal := uintptr(unsafe.Pointer(ptr)) - urlRequestCallbackAccess.Lock() - urlRequestCallbackMap[ptrVal] = &urlRequestCallbackEntry{handler: handler} - urlRequestCallbackAccess.Unlock() - return URLRequestCallback{ptrVal} -} - -func (c URLRequestCallback) Destroy() { - urlRequestCallbackAccess.RLock() - entry := urlRequestCallbackMap[c.ptr] - urlRequestCallbackAccess.RUnlock() - if entry != nil { - entry.destroyed.Store(true) - } - C.Cronet_UrlRequestCallback_Destroy(C.Cronet_UrlRequestCallbackPtr(unsafe.Pointer(c.ptr))) -} - -func instanceOfURLRequestCallback(self C.Cronet_UrlRequestCallbackPtr) URLRequestCallbackHandler { - urlRequestCallbackAccess.RLock() - defer urlRequestCallbackAccess.RUnlock() - entry := urlRequestCallbackMap[uintptr(unsafe.Pointer(self))] - if entry == nil || entry.destroyed.Load() { - return nil - } - return entry.handler -} - -//export cronetURLRequestCallbackOnRedirectReceived -func cronetURLRequestCallbackOnRedirectReceived(self C.Cronet_UrlRequestCallbackPtr, request C.Cronet_UrlRequestPtr, info C.Cronet_UrlResponseInfoPtr, newLocationUrl C.Cronet_String) { - handler := instanceOfURLRequestCallback(self) - if handler == nil { - return // Post-destroy callback, silently ignore - } - handler.OnRedirectReceived(URLRequestCallback{uintptr(unsafe.Pointer(self))}, URLRequest{uintptr(unsafe.Pointer(request))}, URLResponseInfo{uintptr(unsafe.Pointer(info))}, C.GoString(newLocationUrl)) -} - -//export cronetURLRequestCallbackOnResponseStarted -func cronetURLRequestCallbackOnResponseStarted(self C.Cronet_UrlRequestCallbackPtr, request C.Cronet_UrlRequestPtr, info C.Cronet_UrlResponseInfoPtr) { - handler := instanceOfURLRequestCallback(self) - if handler == nil { - return // Post-destroy callback, silently ignore - } - handler.OnResponseStarted(URLRequestCallback{uintptr(unsafe.Pointer(self))}, URLRequest{uintptr(unsafe.Pointer(request))}, URLResponseInfo{uintptr(unsafe.Pointer(info))}) -} - -//export cronetURLRequestCallbackOnReadCompleted -func cronetURLRequestCallbackOnReadCompleted(self C.Cronet_UrlRequestCallbackPtr, request C.Cronet_UrlRequestPtr, info C.Cronet_UrlResponseInfoPtr, buffer C.Cronet_BufferPtr, bytesRead C.uint64_t) { - handler := instanceOfURLRequestCallback(self) - if handler == nil { - return // Post-destroy callback, silently ignore - } - handler.OnReadCompleted(URLRequestCallback{uintptr(unsafe.Pointer(self))}, URLRequest{uintptr(unsafe.Pointer(request))}, URLResponseInfo{uintptr(unsafe.Pointer(info))}, Buffer{uintptr(unsafe.Pointer(buffer))}, int64(bytesRead)) -} - -//export cronetURLRequestCallbackOnSucceeded -func cronetURLRequestCallbackOnSucceeded(self C.Cronet_UrlRequestCallbackPtr, request C.Cronet_UrlRequestPtr, info C.Cronet_UrlResponseInfoPtr) { - handler := instanceOfURLRequestCallback(self) - if handler == nil { - return // Post-destroy callback, silently ignore - } - handler.OnSucceeded(URLRequestCallback{uintptr(unsafe.Pointer(self))}, URLRequest{uintptr(unsafe.Pointer(request))}, URLResponseInfo{uintptr(unsafe.Pointer(info))}) - // Terminal callback - safe to cleanup - cleanupURLRequestCallback(uintptr(unsafe.Pointer(self))) -} - -//export cronetURLRequestCallbackOnFailed -func cronetURLRequestCallbackOnFailed(self C.Cronet_UrlRequestCallbackPtr, request C.Cronet_UrlRequestPtr, info C.Cronet_UrlResponseInfoPtr, error C.Cronet_ErrorPtr) { - handler := instanceOfURLRequestCallback(self) - if handler == nil { - return // Post-destroy callback, silently ignore - } - handler.OnFailed(URLRequestCallback{uintptr(unsafe.Pointer(self))}, URLRequest{uintptr(unsafe.Pointer(request))}, URLResponseInfo{uintptr(unsafe.Pointer(info))}, Error{uintptr(unsafe.Pointer(error))}) - // Terminal callback - safe to cleanup - cleanupURLRequestCallback(uintptr(unsafe.Pointer(self))) -} - -//export cronetURLRequestCallbackOnCanceled -func cronetURLRequestCallbackOnCanceled(self C.Cronet_UrlRequestCallbackPtr, request C.Cronet_UrlRequestPtr, info C.Cronet_UrlResponseInfoPtr) { - handler := instanceOfURLRequestCallback(self) - if handler == nil { - return // Post-destroy callback, silently ignore - } - handler.OnCanceled(URLRequestCallback{uintptr(unsafe.Pointer(self))}, URLRequest{uintptr(unsafe.Pointer(request))}, URLResponseInfo{uintptr(unsafe.Pointer(info))}) - // Terminal callback - safe to cleanup - cleanupURLRequestCallback(uintptr(unsafe.Pointer(self))) -} - -func cleanupURLRequestCallback(ptr uintptr) { - urlRequestCallbackAccess.Lock() - delete(urlRequestCallbackMap, ptr) - urlRequestCallbackAccess.Unlock() -} diff --git a/url_request_callback_impl_purego.go b/url_request_callback_impl_purego.go deleted file mode 100644 index 173e7708..00000000 --- a/url_request_callback_impl_purego.go +++ /dev/null @@ -1,172 +0,0 @@ -//go:build with_purego && !386 && !arm && !mipsle - -package cronet - -import ( - "sync" - "sync/atomic" - - "github.com/sagernet/cronet-go/internal/cronet" - - "github.com/ebitengine/purego" -) - -type urlRequestCallbackEntry struct { - handler URLRequestCallbackHandler - destroyed atomic.Bool -} - -var ( - urlRequestCallbackAccess sync.RWMutex - urlRequestCallbackMap map[uintptr]*urlRequestCallbackEntry - - urlRequestCallbackOnRedirectReceived uintptr - urlRequestCallbackOnResponseStarted uintptr - urlRequestCallbackOnReadCompleted uintptr - urlRequestCallbackOnSucceeded uintptr - urlRequestCallbackOnFailed uintptr - urlRequestCallbackOnCanceled uintptr -) - -func init() { - urlRequestCallbackMap = make(map[uintptr]*urlRequestCallbackEntry) - - urlRequestCallbackOnRedirectReceived = purego.NewCallback(onRedirectReceivedCallback) - urlRequestCallbackOnResponseStarted = purego.NewCallback(onResponseStartedCallback) - urlRequestCallbackOnReadCompleted = purego.NewCallback(onReadCompletedCallback) - urlRequestCallbackOnSucceeded = purego.NewCallback(onSucceededCallback) - urlRequestCallbackOnFailed = purego.NewCallback(onFailedCallback) - urlRequestCallbackOnCanceled = purego.NewCallback(onCanceledCallback) -} - -func instanceOfURLRequestCallback(self uintptr) URLRequestCallbackHandler { - urlRequestCallbackAccess.RLock() - defer urlRequestCallbackAccess.RUnlock() - entry := urlRequestCallbackMap[self] - if entry == nil || entry.destroyed.Load() { - return nil - } - return entry.handler -} - -func onRedirectReceivedCallback(self, request, info, newLocationUrl uintptr) uintptr { - handler := instanceOfURLRequestCallback(self) - if handler == nil { - return 0 // Post-destroy callback, silently ignore - } - handler.OnRedirectReceived( - URLRequestCallback{self}, - URLRequest{request}, - URLResponseInfo{info}, - cronet.GoString(newLocationUrl), - ) - return 0 -} - -func onResponseStartedCallback(self, request, info uintptr) uintptr { - handler := instanceOfURLRequestCallback(self) - if handler == nil { - return 0 // Post-destroy callback, silently ignore - } - handler.OnResponseStarted( - URLRequestCallback{self}, - URLRequest{request}, - URLResponseInfo{info}, - ) - return 0 -} - -func onReadCompletedCallback(self, request, info, buffer uintptr, bytesRead uint64) uintptr { - handler := instanceOfURLRequestCallback(self) - if handler == nil { - return 0 // Post-destroy callback, silently ignore - } - handler.OnReadCompleted( - URLRequestCallback{self}, - URLRequest{request}, - URLResponseInfo{info}, - Buffer{buffer}, - int64(bytesRead), - ) - return 0 -} - -func onSucceededCallback(self, request, info uintptr) uintptr { - handler := instanceOfURLRequestCallback(self) - if handler == nil { - return 0 // Post-destroy callback, silently ignore - } - handler.OnSucceeded( - URLRequestCallback{self}, - URLRequest{request}, - URLResponseInfo{info}, - ) - // Terminal callback - safe to cleanup - cleanupURLRequestCallback(self) - return 0 -} - -func onFailedCallback(self, request, info, err uintptr) uintptr { - handler := instanceOfURLRequestCallback(self) - if handler == nil { - return 0 // Post-destroy callback, silently ignore - } - handler.OnFailed( - URLRequestCallback{self}, - URLRequest{request}, - URLResponseInfo{info}, - Error{err}, - ) - // Terminal callback - safe to cleanup - cleanupURLRequestCallback(self) - return 0 -} - -func onCanceledCallback(self, request, info uintptr) uintptr { - handler := instanceOfURLRequestCallback(self) - if handler == nil { - return 0 // Post-destroy callback, silently ignore - } - handler.OnCanceled( - URLRequestCallback{self}, - URLRequest{request}, - URLResponseInfo{info}, - ) - // Terminal callback - safe to cleanup - cleanupURLRequestCallback(self) - return 0 -} - -func cleanupURLRequestCallback(ptr uintptr) { - urlRequestCallbackAccess.Lock() - delete(urlRequestCallbackMap, ptr) - urlRequestCallbackAccess.Unlock() -} - -func NewURLRequestCallback(handler URLRequestCallbackHandler) URLRequestCallback { - if handler == nil { - panic("nil url request callback handler") - } - ptr := cronet.UrlRequestCallbackCreateWith( - urlRequestCallbackOnRedirectReceived, - urlRequestCallbackOnResponseStarted, - urlRequestCallbackOnReadCompleted, - urlRequestCallbackOnSucceeded, - urlRequestCallbackOnFailed, - urlRequestCallbackOnCanceled, - ) - urlRequestCallbackAccess.Lock() - urlRequestCallbackMap[ptr] = &urlRequestCallbackEntry{handler: handler} - urlRequestCallbackAccess.Unlock() - return URLRequestCallback{ptr} -} - -func (c URLRequestCallback) Destroy() { - urlRequestCallbackAccess.RLock() - entry := urlRequestCallbackMap[c.ptr] - urlRequestCallbackAccess.RUnlock() - if entry != nil { - entry.destroyed.Store(true) - } - cronet.UrlRequestCallbackDestroy(c.ptr) -} diff --git a/url_request_callback_impl_purego_32bit.go b/url_request_callback_impl_purego_32bit.go deleted file mode 100644 index 4ef8b271..00000000 --- a/url_request_callback_impl_purego_32bit.go +++ /dev/null @@ -1,174 +0,0 @@ -//go:build with_purego && (386 || arm || mipsle) - -package cronet - -import ( - "sync" - "sync/atomic" - - "github.com/sagernet/cronet-go/internal/cronet" - - "github.com/ebitengine/purego" -) - -type urlRequestCallbackEntry struct { - handler URLRequestCallbackHandler - destroyed atomic.Bool -} - -var ( - urlRequestCallbackAccess sync.RWMutex - urlRequestCallbackMap map[uintptr]*urlRequestCallbackEntry - - urlRequestCallbackOnRedirectReceived uintptr - urlRequestCallbackOnResponseStarted uintptr - urlRequestCallbackOnReadCompleted uintptr - urlRequestCallbackOnSucceeded uintptr - urlRequestCallbackOnFailed uintptr - urlRequestCallbackOnCanceled uintptr -) - -func init() { - urlRequestCallbackMap = make(map[uintptr]*urlRequestCallbackEntry) - - urlRequestCallbackOnRedirectReceived = purego.NewCallback(onRedirectReceivedCallback) - urlRequestCallbackOnResponseStarted = purego.NewCallback(onResponseStartedCallback) - urlRequestCallbackOnReadCompleted = purego.NewCallback(onReadCompletedCallback) - urlRequestCallbackOnSucceeded = purego.NewCallback(onSucceededCallback) - urlRequestCallbackOnFailed = purego.NewCallback(onFailedCallback) - urlRequestCallbackOnCanceled = purego.NewCallback(onCanceledCallback) -} - -func instanceOfURLRequestCallback(self uintptr) URLRequestCallbackHandler { - urlRequestCallbackAccess.RLock() - defer urlRequestCallbackAccess.RUnlock() - entry := urlRequestCallbackMap[self] - if entry == nil || entry.destroyed.Load() { - return nil - } - return entry.handler -} - -func onRedirectReceivedCallback(self, request, info, newLocationUrl uintptr) uintptr { - handler := instanceOfURLRequestCallback(self) - if handler == nil { - return 0 // Post-destroy callback, silently ignore - } - handler.OnRedirectReceived( - URLRequestCallback{self}, - URLRequest{request}, - URLResponseInfo{info}, - cronet.GoString(newLocationUrl), - ) - return 0 -} - -func onResponseStartedCallback(self, request, info uintptr) uintptr { - handler := instanceOfURLRequestCallback(self) - if handler == nil { - return 0 // Post-destroy callback, silently ignore - } - handler.OnResponseStarted( - URLRequestCallback{self}, - URLRequest{request}, - URLResponseInfo{info}, - ) - return 0 -} - -// On 32-bit platforms, uint64 is passed as two 32-bit values (low, high) on the stack -func onReadCompletedCallback(self, request, info, buffer, bytesReadLow, bytesReadHigh uintptr) uintptr { - handler := instanceOfURLRequestCallback(self) - if handler == nil { - return 0 // Post-destroy callback, silently ignore - } - bytesRead := uint64(bytesReadLow) | (uint64(bytesReadHigh) << 32) - handler.OnReadCompleted( - URLRequestCallback{self}, - URLRequest{request}, - URLResponseInfo{info}, - Buffer{buffer}, - int64(bytesRead), - ) - return 0 -} - -func onSucceededCallback(self, request, info uintptr) uintptr { - handler := instanceOfURLRequestCallback(self) - if handler == nil { - return 0 // Post-destroy callback, silently ignore - } - handler.OnSucceeded( - URLRequestCallback{self}, - URLRequest{request}, - URLResponseInfo{info}, - ) - // Terminal callback - safe to cleanup - cleanupURLRequestCallback(self) - return 0 -} - -func onFailedCallback(self, request, info, err uintptr) uintptr { - handler := instanceOfURLRequestCallback(self) - if handler == nil { - return 0 // Post-destroy callback, silently ignore - } - handler.OnFailed( - URLRequestCallback{self}, - URLRequest{request}, - URLResponseInfo{info}, - Error{err}, - ) - // Terminal callback - safe to cleanup - cleanupURLRequestCallback(self) - return 0 -} - -func onCanceledCallback(self, request, info uintptr) uintptr { - handler := instanceOfURLRequestCallback(self) - if handler == nil { - return 0 // Post-destroy callback, silently ignore - } - handler.OnCanceled( - URLRequestCallback{self}, - URLRequest{request}, - URLResponseInfo{info}, - ) - // Terminal callback - safe to cleanup - cleanupURLRequestCallback(self) - return 0 -} - -func cleanupURLRequestCallback(ptr uintptr) { - urlRequestCallbackAccess.Lock() - delete(urlRequestCallbackMap, ptr) - urlRequestCallbackAccess.Unlock() -} - -func NewURLRequestCallback(handler URLRequestCallbackHandler) URLRequestCallback { - if handler == nil { - panic("nil url request callback handler") - } - ptr := cronet.UrlRequestCallbackCreateWith( - urlRequestCallbackOnRedirectReceived, - urlRequestCallbackOnResponseStarted, - urlRequestCallbackOnReadCompleted, - urlRequestCallbackOnSucceeded, - urlRequestCallbackOnFailed, - urlRequestCallbackOnCanceled, - ) - urlRequestCallbackAccess.Lock() - urlRequestCallbackMap[ptr] = &urlRequestCallbackEntry{handler: handler} - urlRequestCallbackAccess.Unlock() - return URLRequestCallback{ptr} -} - -func (c URLRequestCallback) Destroy() { - urlRequestCallbackAccess.RLock() - entry := urlRequestCallbackMap[c.ptr] - urlRequestCallbackAccess.RUnlock() - if entry != nil { - entry.destroyed.Store(true) - } - cronet.UrlRequestCallbackDestroy(c.ptr) -} diff --git a/url_request_callback_purego.go b/url_request_callback_purego.go deleted file mode 100644 index 928ae2f1..00000000 --- a/url_request_callback_purego.go +++ /dev/null @@ -1,17 +0,0 @@ -//go:build with_purego - -package cronet - -import ( - "unsafe" - - "github.com/sagernet/cronet-go/internal/cronet" -) - -func (c URLRequestCallback) SetClientContext(context unsafe.Pointer) { - cronet.UrlRequestCallbackSetClientContext(c.ptr, uintptr(context)) -} - -func (c URLRequestCallback) ClientContext() unsafe.Pointer { - return unsafe.Pointer(cronet.UrlRequestCallbackGetClientContext(c.ptr)) -} diff --git a/url_request_cgo.go b/url_request_cgo.go deleted file mode 100644 index 60556465..00000000 --- a/url_request_cgo.go +++ /dev/null @@ -1,110 +0,0 @@ -//go:build !with_purego - -package cronet - -// #include -// #include -// #include -import "C" -import "unsafe" - -func NewURLRequest() URLRequest { - return URLRequest{uintptr(unsafe.Pointer(C.Cronet_UrlRequest_Create()))} -} - -func (r URLRequest) Destroy() { - C.Cronet_UrlRequest_Destroy(C.Cronet_UrlRequestPtr(unsafe.Pointer(r.ptr))) -} - -// InitWithParams -// Initialized URLRequest to |url| with |params|. All methods of |callback| for -// request will be invoked on |executor|. The |executor| must not run tasks on -// the thread calling Executor.Execute() to prevent blocking networking -// operations and causing failure RESULTs during shutdown. -// -// @param engine Engine to process the request. -// @param url URL for the request. -// @param params additional parameters for the request, like headers and priority. -// @param callback Callback that gets invoked on different events. -// @param executor Executor on which all callbacks will be invoked. -func (r URLRequest) InitWithParams(engine Engine, url string, params URLRequestParams, callback URLRequestCallback, executor Executor) Result { - cURL := C.CString(url) - defer C.free(unsafe.Pointer(cURL)) - - return Result(C.Cronet_UrlRequest_InitWithParams(C.Cronet_UrlRequestPtr(unsafe.Pointer(r.ptr)), C.Cronet_EnginePtr(unsafe.Pointer(engine.ptr)), cURL, C.Cronet_UrlRequestParamsPtr(unsafe.Pointer(params.ptr)), C.Cronet_UrlRequestCallbackPtr(unsafe.Pointer(callback.ptr)), C.Cronet_ExecutorPtr(unsafe.Pointer(executor.ptr)))) -} - -// Start starts the request, all callbacks go to URLRequestCallbackHandler. May only be called -// once. May not be called if Cancel() has been called. -func (r URLRequest) Start() Result { - return Result(C.Cronet_UrlRequest_Start(C.Cronet_UrlRequestPtr(unsafe.Pointer(r.ptr)))) -} - -// FollowRedirect -// Follows a pending redirect. Must only be called at most once for each -// invocation of URLRequestCallbackHandler.OnRedirectReceived(). -func (r URLRequest) FollowRedirect() Result { - return Result(C.Cronet_UrlRequest_FollowRedirect(C.Cronet_UrlRequestPtr(unsafe.Pointer(r.ptr)))) -} - -// Read -// Attempts to read part of the response body into the provided buffer. -// Must only be called at most once in response to each invocation of the -// URLRequestCallbackHandler.OnResponseStarted() and -// URLRequestCallbackHandler.OnReadCompleted()} methods of the URLRequestCallbackHandler. -// Each call will result in an asynchronous call to -// either the URLRequestCallbackHandler.OnReadCompleted() method if data -// is read, its URLRequestCallbackHandler.OnSucceeded() method if -// there's no more data to read, or its URLRequestCallbackHandler.OnFailed() -// method if there's an error. -// This method transfers ownership of |buffer| to Cronet, and app should -// not access it until one of these callbacks is invoked. -// -// @param buffer to write response body to. The app must not read or -// -// modify buffer's position, limit, or data between its position and -// limit until the request calls back into the URLRequestCallbackHandler. -func (r URLRequest) Read(buffer Buffer) Result { - return Result(C.Cronet_UrlRequest_Read(C.Cronet_UrlRequestPtr(unsafe.Pointer(r.ptr)), C.Cronet_BufferPtr(unsafe.Pointer(buffer.ptr)))) -} - -// Cancel -// cancels the request. Can be called at any time. -// URLRequestCallbackHandler.OnCanceled() will be invoked when cancellation -// is complete and no further callback methods will be invoked. If the -// request has completed or has not started, calling Cancel() has no -// effect and URLRequestCallbackHandler.OnCanceled() will not be invoked. If the -// Executor passed in to UrlRequest.InitWithParams() runs -// tasks on a single thread, and Cancel() is called on that thread, -// no callback methods (besides URLRequestCallbackHandler.OnCanceled() will be invoked after -// Cancel() is called. Otherwise, at most one callback method may be -// invoked after Cancel() has completed. -func (r URLRequest) Cancel() { - C.Cronet_UrlRequest_Cancel(C.Cronet_UrlRequestPtr(unsafe.Pointer(r.ptr))) -} - -// IsDone -// Returns true if the request was successfully started and is now -// finished (completed, canceled, or failed). -func (r URLRequest) IsDone() bool { - return bool(C.Cronet_UrlRequest_IsDone(C.Cronet_UrlRequestPtr(unsafe.Pointer(r.ptr)))) -} - -// GetStatus -// Queries the status of the request. -// @param listener a URLRequestStatusListener that will be invoked with -// -// the request's current status. Listener will be invoked -// back on the Executor passed in when the request was -// created. -func (r URLRequest) GetStatus(listener URLRequestStatusListener) { - C.Cronet_UrlRequest_GetStatus(C.Cronet_UrlRequestPtr(unsafe.Pointer(r.ptr)), C.Cronet_UrlRequestStatusListenerPtr(unsafe.Pointer(listener.ptr))) -} - -func (r URLRequest) SetClientContext(context unsafe.Pointer) { - C.Cronet_UrlRequest_SetClientContext(C.Cronet_UrlRequestPtr(unsafe.Pointer(r.ptr)), C.Cronet_ClientContext(context)) -} - -func (r URLRequest) ClientContext() unsafe.Pointer { - return unsafe.Pointer(C.Cronet_UrlRequest_GetClientContext(C.Cronet_UrlRequestPtr(unsafe.Pointer(r.ptr)))) -} diff --git a/url_request_finished_info_cgo.go b/url_request_finished_info_cgo.go deleted file mode 100644 index 641bdc28..00000000 --- a/url_request_finished_info_cgo.go +++ /dev/null @@ -1,63 +0,0 @@ -//go:build !with_purego - -package cronet - -// #include -// #include -// #include -import "C" - -import "unsafe" - -// Note: RequestFinishedInfo is used in types.go, but the C type uses different naming. -// The Go type is defined in types.go as RequestFinishedInfo (without URL prefix for internal consistency). - -func NewURLRequestFinishedInfo() RequestFinishedInfo { - return RequestFinishedInfo{uintptr(unsafe.Pointer(C.Cronet_RequestFinishedInfo_Create()))} -} - -func (i RequestFinishedInfo) Destroy() { - C.Cronet_RequestFinishedInfo_Destroy(C.Cronet_RequestFinishedInfoPtr(unsafe.Pointer(i.ptr))) -} - -// Metrics -// Metrics collected for this request. -func (i RequestFinishedInfo) Metrics() Metrics { - return Metrics{uintptr(unsafe.Pointer(C.Cronet_RequestFinishedInfo_metrics_get(C.Cronet_RequestFinishedInfoPtr(unsafe.Pointer(i.ptr)))))} -} - -// AnnotationSize -// The objects that the caller has supplied when initiating the request, -// using URLRequestParams.AddAnnotation -// -// Annotations can be used to associate a RequestFinishedInfo with -// the original request or type of request. -func (i RequestFinishedInfo) AnnotationSize() int { - return int(C.Cronet_RequestFinishedInfo_annotations_size(C.Cronet_RequestFinishedInfoPtr(unsafe.Pointer(i.ptr)))) -} - -func (i RequestFinishedInfo) AnnotationAt(index int) unsafe.Pointer { - return unsafe.Pointer(C.Cronet_RequestFinishedInfo_annotations_at(C.Cronet_RequestFinishedInfoPtr(unsafe.Pointer(i.ptr)), C.uint32_t(index))) -} - -// FinishedReason -// Returns the reason why the request finished. -func (i RequestFinishedInfo) FinishedReason() URLRequestFinishedInfoFinishedReason { - return URLRequestFinishedInfoFinishedReason(C.Cronet_RequestFinishedInfo_finished_reason_get(C.Cronet_RequestFinishedInfoPtr(unsafe.Pointer(i.ptr)))) -} - -func (i RequestFinishedInfo) SetMetrics(metrics Metrics) { - C.Cronet_RequestFinishedInfo_metrics_set(C.Cronet_RequestFinishedInfoPtr(unsafe.Pointer(i.ptr)), C.Cronet_MetricsPtr(unsafe.Pointer(metrics.ptr))) -} - -func (i RequestFinishedInfo) AddAnnotation(annotation unsafe.Pointer) { - C.Cronet_RequestFinishedInfo_annotations_add(C.Cronet_RequestFinishedInfoPtr(unsafe.Pointer(i.ptr)), C.Cronet_RawDataPtr(annotation)) -} - -func (i RequestFinishedInfo) ClearAnnotations() { - C.Cronet_RequestFinishedInfo_annotations_clear(C.Cronet_RequestFinishedInfoPtr(unsafe.Pointer(i.ptr))) -} - -func (i RequestFinishedInfo) SetFinishedReason(reason URLRequestFinishedInfoFinishedReason) { - C.Cronet_RequestFinishedInfo_finished_reason_set(C.Cronet_RequestFinishedInfoPtr(unsafe.Pointer(i.ptr)), C.Cronet_RequestFinishedInfo_FINISHED_REASON(reason)) -} diff --git a/url_request_finished_info_impl_cgo.go b/url_request_finished_info_impl_cgo.go deleted file mode 100644 index c6c7400e..00000000 --- a/url_request_finished_info_impl_cgo.go +++ /dev/null @@ -1,67 +0,0 @@ -//go:build !with_purego - -package cronet - -// #include -// #include -// #include -// extern CRONET_EXPORT void cronetURLRequestFinishedInfoListenerOnRequestFinished(Cronet_RequestFinishedInfoListenerPtr self, Cronet_RequestFinishedInfoPtr request_info, Cronet_UrlResponseInfoPtr response_info, Cronet_ErrorPtr error); -import "C" - -import ( - "sync" - "sync/atomic" - "unsafe" -) - -type urlRequestFinishedInfoListenerEntry struct { - handler URLRequestFinishedInfoListenerOnRequestFinishedFunc - destroyed atomic.Bool -} - -var ( - urlRequestFinishedInfoListenerAccess sync.RWMutex - urlRequestFinishedInfoListenerMap map[uintptr]*urlRequestFinishedInfoListenerEntry -) - -func init() { - urlRequestFinishedInfoListenerMap = make(map[uintptr]*urlRequestFinishedInfoListenerEntry) -} - -func NewURLRequestFinishedInfoListener(finishedFunc URLRequestFinishedInfoListenerOnRequestFinishedFunc) URLRequestFinishedInfoListener { - if finishedFunc == nil { - panic("nil url request finished info listener function") - } - ptr := C.Cronet_RequestFinishedInfoListener_CreateWith((*[0]byte)(C.cronetURLRequestFinishedInfoListenerOnRequestFinished)) - ptrVal := uintptr(unsafe.Pointer(ptr)) - urlRequestFinishedInfoListenerAccess.Lock() - urlRequestFinishedInfoListenerMap[ptrVal] = &urlRequestFinishedInfoListenerEntry{handler: finishedFunc} - urlRequestFinishedInfoListenerAccess.Unlock() - return URLRequestFinishedInfoListener{ptrVal} -} - -func (l URLRequestFinishedInfoListener) Destroy() { - urlRequestFinishedInfoListenerAccess.Lock() - entry := urlRequestFinishedInfoListenerMap[l.ptr] - if entry != nil { - entry.destroyed.Store(true) - } - urlRequestFinishedInfoListenerAccess.Unlock() - C.Cronet_RequestFinishedInfoListener_Destroy(C.Cronet_RequestFinishedInfoListenerPtr(unsafe.Pointer(l.ptr))) - // Cleanup after C destroy - urlRequestFinishedInfoListenerAccess.Lock() - delete(urlRequestFinishedInfoListenerMap, l.ptr) - urlRequestFinishedInfoListenerAccess.Unlock() -} - -//export cronetURLRequestFinishedInfoListenerOnRequestFinished -func cronetURLRequestFinishedInfoListenerOnRequestFinished(self C.Cronet_RequestFinishedInfoListenerPtr, requestInfo C.Cronet_RequestFinishedInfoPtr, responseInfo C.Cronet_UrlResponseInfoPtr, error C.Cronet_ErrorPtr) { - ptr := uintptr(unsafe.Pointer(self)) - urlRequestFinishedInfoListenerAccess.RLock() - entry := urlRequestFinishedInfoListenerMap[ptr] - urlRequestFinishedInfoListenerAccess.RUnlock() - if entry == nil || entry.destroyed.Load() { - return // Post-destroy callback, silently ignore - } - entry.handler(URLRequestFinishedInfoListener{ptr}, RequestFinishedInfo{uintptr(unsafe.Pointer(requestInfo))}, URLResponseInfo{uintptr(unsafe.Pointer(responseInfo))}, Error{uintptr(unsafe.Pointer(error))}) -} diff --git a/url_request_finished_info_listener_cgo.go b/url_request_finished_info_listener_cgo.go deleted file mode 100644 index 2ff27d34..00000000 --- a/url_request_finished_info_listener_cgo.go +++ /dev/null @@ -1,18 +0,0 @@ -//go:build !with_purego - -package cronet - -// #include -// #include -// #include -import "C" - -import "unsafe" - -func (l URLRequestFinishedInfoListener) SetClientContext(context unsafe.Pointer) { - C.Cronet_RequestFinishedInfoListener_SetClientContext(C.Cronet_RequestFinishedInfoListenerPtr(unsafe.Pointer(l.ptr)), C.Cronet_ClientContext(context)) -} - -func (l URLRequestFinishedInfoListener) ClientContext() unsafe.Pointer { - return unsafe.Pointer(C.Cronet_RequestFinishedInfoListener_GetClientContext(C.Cronet_RequestFinishedInfoListenerPtr(unsafe.Pointer(l.ptr)))) -} diff --git a/url_request_finished_info_listener_impl_purego.go b/url_request_finished_info_listener_impl_purego.go deleted file mode 100644 index 11d5525a..00000000 --- a/url_request_finished_info_listener_impl_purego.go +++ /dev/null @@ -1,69 +0,0 @@ -//go:build with_purego - -package cronet - -import ( - "sync" - "sync/atomic" - - "github.com/sagernet/cronet-go/internal/cronet" - - "github.com/ebitengine/purego" -) - -type urlRequestFinishedInfoListenerEntry struct { - handler URLRequestFinishedInfoListenerOnRequestFinishedFunc - destroyed atomic.Bool -} - -var ( - urlRequestFinishedInfoListenerAccess sync.RWMutex - urlRequestFinishedInfoListenerMap map[uintptr]*urlRequestFinishedInfoListenerEntry - urlRequestFinishedInfoListenerOnRequestFinishedCallback uintptr -) - -func init() { - urlRequestFinishedInfoListenerMap = make(map[uintptr]*urlRequestFinishedInfoListenerEntry) - urlRequestFinishedInfoListenerOnRequestFinishedCallback = purego.NewCallback(cronetURLRequestFinishedInfoListenerOnRequestFinished) -} - -func cronetURLRequestFinishedInfoListenerOnRequestFinished(self, requestInfo, responseInfo, errorPtr uintptr) uintptr { - urlRequestFinishedInfoListenerAccess.RLock() - entry := urlRequestFinishedInfoListenerMap[self] - urlRequestFinishedInfoListenerAccess.RUnlock() - if entry == nil || entry.destroyed.Load() { - return 0 // Post-destroy callback, silently ignore - } - entry.handler( - URLRequestFinishedInfoListener{self}, - RequestFinishedInfo{requestInfo}, - URLResponseInfo{responseInfo}, - Error{errorPtr}, - ) - return 0 -} - -func NewURLRequestFinishedInfoListener(finishedFunc URLRequestFinishedInfoListenerOnRequestFinishedFunc) URLRequestFinishedInfoListener { - if finishedFunc == nil { - panic("nil url request finished info listener function") - } - ptr := cronet.RequestFinishedInfoListenerCreateWith(urlRequestFinishedInfoListenerOnRequestFinishedCallback) - urlRequestFinishedInfoListenerAccess.Lock() - urlRequestFinishedInfoListenerMap[ptr] = &urlRequestFinishedInfoListenerEntry{handler: finishedFunc} - urlRequestFinishedInfoListenerAccess.Unlock() - return URLRequestFinishedInfoListener{ptr} -} - -func (l URLRequestFinishedInfoListener) destroy() { - urlRequestFinishedInfoListenerAccess.Lock() - entry := urlRequestFinishedInfoListenerMap[l.ptr] - if entry != nil { - entry.destroyed.Store(true) - } - urlRequestFinishedInfoListenerAccess.Unlock() - cronet.RequestFinishedInfoListenerDestroy(l.ptr) - // Cleanup after C destroy - urlRequestFinishedInfoListenerAccess.Lock() - delete(urlRequestFinishedInfoListenerMap, l.ptr) - urlRequestFinishedInfoListenerAccess.Unlock() -} diff --git a/url_request_finished_info_listener_purego.go b/url_request_finished_info_listener_purego.go deleted file mode 100644 index 737e839b..00000000 --- a/url_request_finished_info_listener_purego.go +++ /dev/null @@ -1,22 +0,0 @@ -//go:build with_purego - -package cronet - -import ( - "unsafe" - - "github.com/sagernet/cronet-go/internal/cronet" -) - -func (l URLRequestFinishedInfoListener) Destroy() { - l.destroy() - cronet.RequestFinishedInfoListenerDestroy(l.ptr) -} - -func (l URLRequestFinishedInfoListener) SetClientContext(context unsafe.Pointer) { - cronet.RequestFinishedInfoListenerSetClientContext(l.ptr, uintptr(context)) -} - -func (l URLRequestFinishedInfoListener) ClientContext() unsafe.Pointer { - return unsafe.Pointer(cronet.RequestFinishedInfoListenerGetClientContext(l.ptr)) -} diff --git a/url_request_finished_info_purego.go b/url_request_finished_info_purego.go deleted file mode 100644 index 30fa1184..00000000 --- a/url_request_finished_info_purego.go +++ /dev/null @@ -1,33 +0,0 @@ -//go:build with_purego - -package cronet - -import ( - "unsafe" - - "github.com/sagernet/cronet-go/internal/cronet" -) - -func NewURLRequestFinishedInfo() RequestFinishedInfo { - return RequestFinishedInfo{cronet.RequestFinishedInfoCreate()} -} - -func (i RequestFinishedInfo) Destroy() { - cronet.RequestFinishedInfoDestroy(i.ptr) -} - -func (i RequestFinishedInfo) Metrics() Metrics { - return Metrics{cronet.RequestFinishedInfoMetricsGet(i.ptr)} -} - -func (i RequestFinishedInfo) AnnotationSize() int { - return int(cronet.RequestFinishedInfoAnnotationsSize(i.ptr)) -} - -func (i RequestFinishedInfo) AnnotationAt(index int) unsafe.Pointer { - return unsafe.Pointer(cronet.RequestFinishedInfoAnnotationsAt(i.ptr, uint32(index))) -} - -func (i RequestFinishedInfo) FinishedReason() URLRequestFinishedInfoFinishedReason { - return URLRequestFinishedInfoFinishedReason(cronet.RequestFinishedInfoFinishedReasonGet(i.ptr)) -} diff --git a/url_request_impl_cgo.go b/url_request_impl_cgo.go deleted file mode 100644 index 980a0e86..00000000 --- a/url_request_impl_cgo.go +++ /dev/null @@ -1,125 +0,0 @@ -//go:build !with_purego - -package cronet - -// #include -// #include -// #include -// extern CRONET_EXPORT Cronet_RESULT cronetUrlRequestInitWithParams(Cronet_UrlRequestPtr self, Cronet_EnginePtr engine, Cronet_String url, Cronet_UrlRequestParamsPtr params, Cronet_UrlRequestCallbackPtr callback, Cronet_ExecutorPtr executor); -// extern CRONET_EXPORT Cronet_RESULT cronetUrlRequestStart(Cronet_UrlRequestPtr self); -// extern CRONET_EXPORT Cronet_RESULT cronetUrlRequestFollowRedirect(Cronet_UrlRequestPtr self); -// extern CRONET_EXPORT Cronet_RESULT cronetUrlRequestRead(Cronet_UrlRequestPtr self, Cronet_BufferPtr buffer); -// extern CRONET_EXPORT void cronetUrlRequestCancel(Cronet_UrlRequestPtr self); -// extern CRONET_EXPORT bool cronetUrlRequestIsDone(Cronet_UrlRequestPtr self); -// extern CRONET_EXPORT void cronetUrlRequestGetStatus(Cronet_UrlRequestPtr self, Cronet_UrlRequestStatusListenerPtr listener); -import "C" - -import ( - "sync" - "unsafe" -) - -// URLRequestHandler is an interface for custom URLRequest implementations (for testing/mocking). -type URLRequestHandler interface { - InitWithParams(self URLRequest, engine Engine, url string, params URLRequestParams, callback URLRequestCallback, executor Executor) Result - Start(self URLRequest) Result - FollowRedirect(self URLRequest) Result - Read(self URLRequest, buffer Buffer) Result - Cancel(self URLRequest) - IsDone(self URLRequest) bool - GetStatus(self URLRequest, listener URLRequestStatusListener) -} - -// NewURLRequestWith creates a new URLRequest with custom handler (for testing/mocking). -func NewURLRequestWith(handler URLRequestHandler) URLRequest { - ptr := C.Cronet_UrlRequest_CreateWith( - (*[0]byte)(C.cronetUrlRequestInitWithParams), - (*[0]byte)(C.cronetUrlRequestStart), - (*[0]byte)(C.cronetUrlRequestFollowRedirect), - (*[0]byte)(C.cronetUrlRequestRead), - (*[0]byte)(C.cronetUrlRequestCancel), - (*[0]byte)(C.cronetUrlRequestIsDone), - (*[0]byte)(C.cronetUrlRequestGetStatus), - ) - ptrVal := uintptr(unsafe.Pointer(ptr)) - urlRequestHandlerAccess.Lock() - urlRequestHandlerMap[ptrVal] = handler - urlRequestHandlerAccess.Unlock() - return URLRequest{ptrVal} -} - -var ( - urlRequestHandlerAccess sync.RWMutex - urlRequestHandlerMap map[uintptr]URLRequestHandler -) - -func init() { - urlRequestHandlerMap = make(map[uintptr]URLRequestHandler) -} - -func instanceOfURLRequestHandler(self C.Cronet_UrlRequestPtr) URLRequestHandler { - urlRequestHandlerAccess.RLock() - defer urlRequestHandlerAccess.RUnlock() - return urlRequestHandlerMap[uintptr(unsafe.Pointer(self))] -} - -//export cronetUrlRequestInitWithParams -func cronetUrlRequestInitWithParams(self C.Cronet_UrlRequestPtr, engine C.Cronet_EnginePtr, url C.Cronet_String, params C.Cronet_UrlRequestParamsPtr, callback C.Cronet_UrlRequestCallbackPtr, executor C.Cronet_ExecutorPtr) C.Cronet_RESULT { - handler := instanceOfURLRequestHandler(self) - if handler != nil { - return C.Cronet_RESULT(handler.InitWithParams(URLRequest{uintptr(unsafe.Pointer(self))}, Engine{uintptr(unsafe.Pointer(engine))}, C.GoString(url), URLRequestParams{uintptr(unsafe.Pointer(params))}, URLRequestCallback{uintptr(unsafe.Pointer(callback))}, Executor{uintptr(unsafe.Pointer(executor))})) - } - return C.Cronet_RESULT_SUCCESS -} - -//export cronetUrlRequestStart -func cronetUrlRequestStart(self C.Cronet_UrlRequestPtr) C.Cronet_RESULT { - handler := instanceOfURLRequestHandler(self) - if handler != nil { - return C.Cronet_RESULT(handler.Start(URLRequest{uintptr(unsafe.Pointer(self))})) - } - return C.Cronet_RESULT_SUCCESS -} - -//export cronetUrlRequestFollowRedirect -func cronetUrlRequestFollowRedirect(self C.Cronet_UrlRequestPtr) C.Cronet_RESULT { - handler := instanceOfURLRequestHandler(self) - if handler != nil { - return C.Cronet_RESULT(handler.FollowRedirect(URLRequest{uintptr(unsafe.Pointer(self))})) - } - return C.Cronet_RESULT_SUCCESS -} - -//export cronetUrlRequestRead -func cronetUrlRequestRead(self C.Cronet_UrlRequestPtr, buffer C.Cronet_BufferPtr) C.Cronet_RESULT { - handler := instanceOfURLRequestHandler(self) - if handler != nil { - return C.Cronet_RESULT(handler.Read(URLRequest{uintptr(unsafe.Pointer(self))}, Buffer{uintptr(unsafe.Pointer(buffer))})) - } - return C.Cronet_RESULT_SUCCESS -} - -//export cronetUrlRequestCancel -func cronetUrlRequestCancel(self C.Cronet_UrlRequestPtr) { - handler := instanceOfURLRequestHandler(self) - if handler != nil { - handler.Cancel(URLRequest{uintptr(unsafe.Pointer(self))}) - } -} - -//export cronetUrlRequestIsDone -func cronetUrlRequestIsDone(self C.Cronet_UrlRequestPtr) C.bool { - handler := instanceOfURLRequestHandler(self) - if handler != nil { - return C.bool(handler.IsDone(URLRequest{uintptr(unsafe.Pointer(self))})) - } - return C.bool(false) -} - -//export cronetUrlRequestGetStatus -func cronetUrlRequestGetStatus(self C.Cronet_UrlRequestPtr, listener C.Cronet_UrlRequestStatusListenerPtr) { - handler := instanceOfURLRequestHandler(self) - if handler != nil { - handler.GetStatus(URLRequest{uintptr(unsafe.Pointer(self))}, URLRequestStatusListener{uintptr(unsafe.Pointer(listener))}) - } -} diff --git a/url_request_params_cgo.go b/url_request_params_cgo.go deleted file mode 100644 index 2093a4e3..00000000 --- a/url_request_params_cgo.go +++ /dev/null @@ -1,194 +0,0 @@ -//go:build !with_purego - -package cronet - -// #include -// #include -// #include -import "C" -import "unsafe" - -func NewURLRequestParams() URLRequestParams { - return URLRequestParams{uintptr(unsafe.Pointer(C.Cronet_UrlRequestParams_Create()))} -} - -func (p URLRequestParams) Destroy() { - C.Cronet_UrlRequestParams_Destroy(C.Cronet_UrlRequestParamsPtr(unsafe.Pointer(p.ptr))) -} - -// SetMethod -// The HTTP method verb to use for this request. -// -// The default when this value is not set is "GET" if the request has -// no body or "POST" if it does. -// -// Allowed methods are "GET", "HEAD", "DELETE", "POST", "PUT" and "CONNECT". -func (p URLRequestParams) SetMethod(method string) { - cMethod := C.CString(method) - C.Cronet_UrlRequestParams_http_method_set(C.Cronet_UrlRequestParamsPtr(unsafe.Pointer(p.ptr)), cMethod) - C.free(unsafe.Pointer(cMethod)) -} - -func (p URLRequestParams) Method() string { - return C.GoString(C.Cronet_UrlRequestParams_http_method_get(C.Cronet_UrlRequestParamsPtr(unsafe.Pointer(p.ptr)))) -} - -// AddHeader -// Add HTTP header for this request -func (p URLRequestParams) AddHeader(header HTTPHeader) { - C.Cronet_UrlRequestParams_request_headers_add(C.Cronet_UrlRequestParamsPtr(unsafe.Pointer(p.ptr)), C.Cronet_HttpHeaderPtr(unsafe.Pointer(header.ptr))) -} - -func (p URLRequestParams) HeaderSize() int { - return int(C.Cronet_UrlRequestParams_request_headers_size(C.Cronet_UrlRequestParamsPtr(unsafe.Pointer(p.ptr)))) -} - -func (p URLRequestParams) HeaderAt(index int) HTTPHeader { - return HTTPHeader{uintptr(unsafe.Pointer(C.Cronet_UrlRequestParams_request_headers_at(C.Cronet_UrlRequestParamsPtr(unsafe.Pointer(p.ptr)), C.uint32_t(index))))} -} - -func (p URLRequestParams) ClearHeaders() { - C.Cronet_UrlRequestParams_request_headers_clear(C.Cronet_UrlRequestParamsPtr(unsafe.Pointer(p.ptr))) -} - -// SetDisableCache -// Disables cache for the request. If context is not set up to use cache, -// this call has no effect. -func (p URLRequestParams) SetDisableCache(disable bool) { - C.Cronet_UrlRequestParams_disable_cache_set(C.Cronet_UrlRequestParamsPtr(unsafe.Pointer(p.ptr)), C.bool(disable)) -} - -func (p URLRequestParams) DisableCache() bool { - return bool(C.Cronet_UrlRequestParams_disable_cache_get(C.Cronet_UrlRequestParamsPtr(unsafe.Pointer(p.ptr)))) -} - -// SetPriority -// Priority of the request which should be one of the URLRequestParamsRequestPriority values. -func (p URLRequestParams) SetPriority(priority URLRequestParamsRequestPriority) { - C.Cronet_UrlRequestParams_priority_set(C.Cronet_UrlRequestParamsPtr(unsafe.Pointer(p.ptr)), C.Cronet_UrlRequestParams_REQUEST_PRIORITY(priority)) -} - -func (p URLRequestParams) Priority() URLRequestParamsRequestPriority { - return URLRequestParamsRequestPriority(C.Cronet_UrlRequestParams_priority_get(C.Cronet_UrlRequestParamsPtr(unsafe.Pointer(p.ptr)))) -} - -// SetUploadDataProvider -// Upload data provider. Setting this value switches method to "POST" if not -// explicitly set. Starting the request will fail if a Content-Type header is not set. -func (p URLRequestParams) SetUploadDataProvider(provider UploadDataProvider) { - C.Cronet_UrlRequestParams_upload_data_provider_set(C.Cronet_UrlRequestParamsPtr(unsafe.Pointer(p.ptr)), C.Cronet_UploadDataProviderPtr(unsafe.Pointer(provider.ptr))) -} - -func (p URLRequestParams) UploadDataProvider() UploadDataProvider { - return UploadDataProvider{uintptr(unsafe.Pointer(C.Cronet_UrlRequestParams_upload_data_provider_get(C.Cronet_UrlRequestParamsPtr(unsafe.Pointer(p.ptr)))))} -} - -// SetUploadDataExecutor -// Upload data provider executor that will be used to invoke uploadDataProvider. -func (p URLRequestParams) SetUploadDataExecutor(executor Executor) { - C.Cronet_UrlRequestParams_upload_data_provider_executor_set(C.Cronet_UrlRequestParamsPtr(unsafe.Pointer(p.ptr)), C.Cronet_ExecutorPtr(unsafe.Pointer(executor.ptr))) -} - -func (p URLRequestParams) UploadDataExecutor() Executor { - return Executor{uintptr(unsafe.Pointer(C.Cronet_UrlRequestParams_upload_data_provider_executor_get(C.Cronet_UrlRequestParamsPtr(unsafe.Pointer(p.ptr)))))} -} - -// SetAllowDirectExecutor -// Marks that the executors this request will use to notify callbacks (for -// UploadDataProvider and URLRequestCallback) is intentionally performing -// inline execution without switching to another thread. -// -//

Warning: This option makes it easy to accidentally block the network thread. -// It should not be used if your callbacks perform disk I/O, acquire locks, or call into -// other code you don't carefully control and audit. -func (p URLRequestParams) SetAllowDirectExecutor(allow bool) { - C.Cronet_UrlRequestParams_allow_direct_executor_set(C.Cronet_UrlRequestParamsPtr(unsafe.Pointer(p.ptr)), C.bool(allow)) -} - -func (p URLRequestParams) AllocDirectExecutor() bool { - return bool(C.Cronet_UrlRequestParams_allow_direct_executor_get(C.Cronet_UrlRequestParamsPtr(unsafe.Pointer(p.ptr)))) -} - -// AddAnnotation -// Associates the annotation object with this request. May add more than one. -// Passed through to a RequestFinishedInfoListener. -func (p URLRequestParams) AddAnnotation(annotation unsafe.Pointer) { - C.Cronet_UrlRequestParams_annotations_add(C.Cronet_UrlRequestParamsPtr(unsafe.Pointer(p.ptr)), C.Cronet_RawDataPtr(annotation)) -} - -func (p URLRequestParams) AnnotationSize() int { - return int(C.Cronet_UrlRequestParams_annotations_size(C.Cronet_UrlRequestParamsPtr(unsafe.Pointer(p.ptr)))) -} - -func (p URLRequestParams) AnnotationAt(index int) unsafe.Pointer { - return unsafe.Pointer(C.Cronet_UrlRequestParams_annotations_at(C.Cronet_UrlRequestParamsPtr(unsafe.Pointer(p.ptr)), C.uint32_t(index))) -} - -func (p URLRequestParams) ClearAnnotations() { - C.Cronet_UrlRequestParams_annotations_clear(C.Cronet_UrlRequestParamsPtr(unsafe.Pointer(p.ptr))) -} - -// SetRequestFinishedListener -// A listener that gets invoked at the end of each request. -// -// The listener is invoked with the request finished info on RequestFinishedExecutor, which must be set. -// -// The listener is called before URLRequestCallbackHandler.OnCanceled(), -// URLRequestCallbackHandler.OnFailed() or -// URLRequestCallbackHandler.OnSucceeded() is called -- note that if -// RequestFinishedListener runs the listener asynchronously, the actual -// call to the listener may happen after a {@code URLRequestCallbackHandler} method -// is called. -// -// Ownership is **not** taken. -// -// Assuming the listener won't run again (there are no pending requests with -// the listener attached, either via Engine or @code URLRequest), -// the app may destroy it once its URLRequestFinishedInfoListenerOnRequestFinishedFunc has started, -// even inside that method. -func (p URLRequestParams) SetRequestFinishedListener(listener URLRequestFinishedInfoListener) { - C.Cronet_UrlRequestParams_request_finished_listener_set(C.Cronet_UrlRequestParamsPtr(unsafe.Pointer(p.ptr)), C.Cronet_RequestFinishedInfoListenerPtr(unsafe.Pointer(listener.ptr))) -} - -func (p URLRequestParams) RequestFinishedListener() URLRequestFinishedInfoListener { - return URLRequestFinishedInfoListener{uintptr(unsafe.Pointer(C.Cronet_UrlRequestParams_request_finished_listener_get(C.Cronet_UrlRequestParamsPtr(unsafe.Pointer(p.ptr)))))} -} - -// SetRequestFinishedExecutor -// The Executor used to run the RequestFinishedListener. -// -// Ownership is **not** taken. -// -// # Similar to RequestFinishedListener, the app may destroy RequestFinishedExecutor in or after URLRequestFinishedInfoListenerOnRequestFinishedFunc -// -// It's also OK to destroy RequestFinishedExecutor in or after one -// of {@link URLRequestCallbackHandler.OnCanceled()}, {@link -// URLRequestCallbackHandler.OnFailed()} or {@link -// URLRequestCallbackHandler.OnSucceeded()}. -// -// Of course, both of these are only true if {@code -// request_finished_executor} isn't being used for anything else that might -// start running in the future. -func (p URLRequestParams) SetRequestFinishedExecutor(executor Executor) { - C.Cronet_UrlRequestParams_request_finished_executor_set(C.Cronet_UrlRequestParamsPtr(unsafe.Pointer(p.ptr)), C.Cronet_ExecutorPtr(unsafe.Pointer(executor.ptr))) -} - -func (p URLRequestParams) RequestFinishedExecutor() Executor { - return Executor{uintptr(unsafe.Pointer(C.Cronet_UrlRequestParams_request_finished_executor_get(C.Cronet_UrlRequestParamsPtr(unsafe.Pointer(p.ptr)))))} -} - -// SetIdempotency -// Idempotency of the request, which determines that if it is safe to enable -// 0-RTT for the Cronet request. By default, 0-RTT is only enabled for safe -// HTTP methods, i.e., GET, HEAD, OPTIONS, and TRACE. For other methods, -// enabling 0-RTT may cause security issues since a network observer can -// replay the request. If the request has any side effects, those effects can -// happen multiple times. It is only safe to enable the 0-RTT if it is known -// that the request is idempotent. -func (p URLRequestParams) SetIdempotency(idempotency URLRequestParamsIdempotency) { - C.Cronet_UrlRequestParams_idempotency_set(C.Cronet_UrlRequestParamsPtr(unsafe.Pointer(p.ptr)), C.Cronet_UrlRequestParams_IDEMPOTENCY(idempotency)) -} - -func (p URLRequestParams) Idempotency() URLRequestParamsIdempotency { - return URLRequestParamsIdempotency(C.Cronet_UrlRequestParams_idempotency_get(C.Cronet_UrlRequestParamsPtr(unsafe.Pointer(p.ptr)))) -} diff --git a/url_request_params_purego.go b/url_request_params_purego.go deleted file mode 100644 index fe87c089..00000000 --- a/url_request_params_purego.go +++ /dev/null @@ -1,121 +0,0 @@ -//go:build with_purego - -package cronet - -import ( - "unsafe" - - "github.com/sagernet/cronet-go/internal/cronet" -) - -func NewURLRequestParams() URLRequestParams { - return URLRequestParams{cronet.UrlRequestParamsCreate()} -} - -func (p URLRequestParams) Destroy() { - cronet.UrlRequestParamsDestroy(p.ptr) -} - -func (p URLRequestParams) SetMethod(method string) { - cronet.UrlRequestParamsHttpMethodSet(p.ptr, method) -} - -func (p URLRequestParams) Method() string { - return cronet.UrlRequestParamsHttpMethodGet(p.ptr) -} - -func (p URLRequestParams) AddHeader(header HTTPHeader) { - cronet.UrlRequestParamsRequestHeadersAdd(p.ptr, header.ptr) -} - -func (p URLRequestParams) HeaderSize() int { - return int(cronet.UrlRequestParamsRequestHeadersSize(p.ptr)) -} - -func (p URLRequestParams) HeaderAt(index int) HTTPHeader { - return HTTPHeader{cronet.UrlRequestParamsRequestHeadersAt(p.ptr, uint32(index))} -} - -func (p URLRequestParams) ClearHeaders() { - cronet.UrlRequestParamsRequestHeadersClear(p.ptr) -} - -func (p URLRequestParams) SetDisableCache(disable bool) { - cronet.UrlRequestParamsDisableCacheSet(p.ptr, disable) -} - -func (p URLRequestParams) DisableCache() bool { - return cronet.UrlRequestParamsDisableCacheGet(p.ptr) -} - -func (p URLRequestParams) SetPriority(priority URLRequestParamsRequestPriority) { - cronet.UrlRequestParamsPrioritySet(p.ptr, int32(priority)) -} - -func (p URLRequestParams) Priority() URLRequestParamsRequestPriority { - return URLRequestParamsRequestPriority(cronet.UrlRequestParamsPriorityGet(p.ptr)) -} - -func (p URLRequestParams) SetUploadDataProvider(provider UploadDataProvider) { - cronet.UrlRequestParamsUploadDataProviderSet(p.ptr, provider.ptr) -} - -func (p URLRequestParams) UploadDataProvider() UploadDataProvider { - return UploadDataProvider{cronet.UrlRequestParamsUploadDataProviderGet(p.ptr)} -} - -func (p URLRequestParams) SetUploadDataExecutor(executor Executor) { - cronet.UrlRequestParamsUploadDataProviderExecutorSet(p.ptr, executor.ptr) -} - -func (p URLRequestParams) UploadDataExecutor() Executor { - return Executor{cronet.UrlRequestParamsUploadDataProviderExecutorGet(p.ptr)} -} - -func (p URLRequestParams) SetAllowDirectExecutor(allow bool) { - cronet.UrlRequestParamsAllowDirectExecutorSet(p.ptr, allow) -} - -func (p URLRequestParams) AllocDirectExecutor() bool { - return cronet.UrlRequestParamsAllowDirectExecutorGet(p.ptr) -} - -func (p URLRequestParams) AddAnnotation(annotation unsafe.Pointer) { - cronet.UrlRequestParamsAnnotationsAdd(p.ptr, uintptr(annotation)) -} - -func (p URLRequestParams) AnnotationSize() int { - return int(cronet.UrlRequestParamsAnnotationsSize(p.ptr)) -} - -func (p URLRequestParams) AnnotationAt(index int) unsafe.Pointer { - return unsafe.Pointer(cronet.UrlRequestParamsAnnotationsAt(p.ptr, uint32(index))) -} - -func (p URLRequestParams) ClearAnnotations() { - cronet.UrlRequestParamsAnnotationsClear(p.ptr) -} - -func (p URLRequestParams) SetRequestFinishedListener(listener URLRequestFinishedInfoListener) { - cronet.UrlRequestParamsRequestFinishedListenerSet(p.ptr, listener.ptr) -} - -func (p URLRequestParams) RequestFinishedListener() URLRequestFinishedInfoListener { - return URLRequestFinishedInfoListener{cronet.UrlRequestParamsRequestFinishedListenerGet(p.ptr)} -} - -func (p URLRequestParams) SetRequestFinishedExecutor(executor Executor) { - cronet.UrlRequestParamsRequestFinishedExecutorSet(p.ptr, executor.ptr) -} - -func (p URLRequestParams) RequestFinishedExecutor() Executor { - return Executor{cronet.UrlRequestParamsRequestFinishedExecutorGet(p.ptr)} -} - -func (p URLRequestParams) SetIdempotency(idempotency URLRequestParamsIdempotency) { - cronet.UrlRequestParamsIdempotencySet(p.ptr, int32(idempotency)) -} - -func (p URLRequestParams) Idempotency() URLRequestParamsIdempotency { - return URLRequestParamsIdempotency(cronet.UrlRequestParamsIdempotencyGet(p.ptr)) -} diff --git a/url_request_purego.go b/url_request_purego.go deleted file mode 100644 index 48b36207..00000000 --- a/url_request_purego.go +++ /dev/null @@ -1,53 +0,0 @@ -//go:build with_purego - -package cronet - -import ( - "unsafe" - - "github.com/sagernet/cronet-go/internal/cronet" -) - -func NewURLRequest() URLRequest { - return URLRequest{cronet.UrlRequestCreate()} -} - -func (r URLRequest) Destroy() { - cronet.UrlRequestDestroy(r.ptr) -} - -func (r URLRequest) SetClientContext(context unsafe.Pointer) { - cronet.UrlRequestSetClientContext(r.ptr, uintptr(context)) -} - -func (r URLRequest) ClientContext() unsafe.Pointer { - return unsafe.Pointer(cronet.UrlRequestGetClientContext(r.ptr)) -} - -func (r URLRequest) InitWithParams(engine Engine, url string, params URLRequestParams, callback URLRequestCallback, executor Executor) Result { - return Result(cronet.UrlRequestInitWithParams(r.ptr, engine.ptr, url, params.ptr, callback.ptr, executor.ptr)) -} - -func (r URLRequest) Start() Result { - return Result(cronet.UrlRequestStart(r.ptr)) -} - -func (r URLRequest) FollowRedirect() Result { - return Result(cronet.UrlRequestFollowRedirect(r.ptr)) -} - -func (r URLRequest) Read(buffer Buffer) Result { - return Result(cronet.UrlRequestRead(r.ptr, buffer.ptr)) -} - -func (r URLRequest) Cancel() { - cronet.UrlRequestCancel(r.ptr) -} - -func (r URLRequest) IsDone() bool { - return cronet.UrlRequestIsDone(r.ptr) -} - -func (r URLRequest) GetStatus(listener URLRequestStatusListener) { - cronet.UrlRequestGetStatus(r.ptr, listener.ptr) -} diff --git a/url_request_status_listener_cgo.go b/url_request_status_listener_cgo.go deleted file mode 100644 index 76e2f1ab..00000000 --- a/url_request_status_listener_cgo.go +++ /dev/null @@ -1,18 +0,0 @@ -//go:build !with_purego - -package cronet - -// #include -// #include -// #include -import "C" - -import "unsafe" - -func (l URLRequestStatusListener) SetClientContext(context unsafe.Pointer) { - C.Cronet_UrlRequestStatusListener_SetClientContext(C.Cronet_UrlRequestStatusListenerPtr(unsafe.Pointer(l.ptr)), C.Cronet_ClientContext(context)) -} - -func (l URLRequestStatusListener) ClientContext() unsafe.Pointer { - return unsafe.Pointer(C.Cronet_UrlRequestStatusListener_GetClientContext(C.Cronet_UrlRequestStatusListenerPtr(unsafe.Pointer(l.ptr)))) -} diff --git a/url_request_status_listener_impl_cgo.go b/url_request_status_listener_impl_cgo.go deleted file mode 100644 index 4ed2cb02..00000000 --- a/url_request_status_listener_impl_cgo.go +++ /dev/null @@ -1,55 +0,0 @@ -//go:build !with_purego - -package cronet - -// #include -// #include -// #include -// extern CRONET_EXPORT void cronetURLRequestStatusListenerOnStatus(Cronet_UrlRequestStatusListenerPtr self, Cronet_UrlRequestStatusListener_Status status); -import "C" - -import ( - "sync" - "unsafe" -) - -func NewURLRequestStatusListener(onStatusFunc URLRequestStatusListenerOnStatusFunc) URLRequestStatusListener { - if onStatusFunc == nil { - panic("nil url request status listener function") - } - ptr := C.Cronet_UrlRequestStatusListener_CreateWith((*[0]byte)(C.cronetURLRequestStatusListenerOnStatus)) - ptrVal := uintptr(unsafe.Pointer(ptr)) - urlRequestStatusListenerAccess.Lock() - urlRequestStatusListenerMap[ptrVal] = onStatusFunc - urlRequestStatusListenerAccess.Unlock() - return URLRequestStatusListener{ptrVal} -} - -func (l URLRequestStatusListener) Destroy() { - C.Cronet_UrlRequestStatusListener_Destroy(C.Cronet_UrlRequestStatusListenerPtr(unsafe.Pointer(l.ptr))) - urlRequestStatusListenerAccess.Lock() - delete(urlRequestStatusListenerMap, l.ptr) - urlRequestStatusListenerAccess.Unlock() -} - -var ( - urlRequestStatusListenerAccess sync.RWMutex - urlRequestStatusListenerMap map[uintptr]URLRequestStatusListenerOnStatusFunc -) - -func init() { - urlRequestStatusListenerMap = make(map[uintptr]URLRequestStatusListenerOnStatusFunc) -} - -//export cronetURLRequestStatusListenerOnStatus -func cronetURLRequestStatusListenerOnStatus(self C.Cronet_UrlRequestStatusListenerPtr, status C.Cronet_UrlRequestStatusListener_Status) { - ptr := uintptr(unsafe.Pointer(self)) - urlRequestStatusListenerAccess.Lock() - listener := urlRequestStatusListenerMap[ptr] - delete(urlRequestStatusListenerMap, ptr) - urlRequestStatusListenerAccess.Unlock() - if listener == nil { - return // Race with Destroy() or already invoked - silently return - } - listener(URLRequestStatusListener{ptr}, URLRequestStatusListenerStatus(status)) -} diff --git a/url_request_status_listener_impl_purego.go b/url_request_status_listener_impl_purego.go deleted file mode 100644 index 32b67f7d..00000000 --- a/url_request_status_listener_impl_purego.go +++ /dev/null @@ -1,51 +0,0 @@ -//go:build with_purego - -package cronet - -import ( - "sync" - - "github.com/sagernet/cronet-go/internal/cronet" - - "github.com/ebitengine/purego" -) - -var ( - urlRequestStatusListenerAccess sync.RWMutex - urlRequestStatusListenerMap map[uintptr]URLRequestStatusListenerOnStatusFunc - urlRequestStatusListenerOnStatusCallback uintptr -) - -func init() { - urlRequestStatusListenerMap = make(map[uintptr]URLRequestStatusListenerOnStatusFunc) - urlRequestStatusListenerOnStatusCallback = purego.NewCallback(cronetURLRequestStatusListenerOnStatus) -} - -func cronetURLRequestStatusListenerOnStatus(self uintptr, status int32) uintptr { - urlRequestStatusListenerAccess.Lock() - listener := urlRequestStatusListenerMap[self] - delete(urlRequestStatusListenerMap, self) // One-shot callback - urlRequestStatusListenerAccess.Unlock() - if listener == nil { - return 0 // Race with Destroy() or already invoked - silently return - } - listener(URLRequestStatusListener{self}, URLRequestStatusListenerStatus(status)) - return 0 -} - -func NewURLRequestStatusListener(onStatusFunc URLRequestStatusListenerOnStatusFunc) URLRequestStatusListener { - if onStatusFunc == nil { - panic("nil url request status listener function") - } - ptr := cronet.UrlRequestStatusListenerCreateWith(urlRequestStatusListenerOnStatusCallback) - urlRequestStatusListenerAccess.Lock() - urlRequestStatusListenerMap[ptr] = onStatusFunc - urlRequestStatusListenerAccess.Unlock() - return URLRequestStatusListener{ptr} -} - -func (l URLRequestStatusListener) destroy() { - urlRequestStatusListenerAccess.Lock() - delete(urlRequestStatusListenerMap, l.ptr) - urlRequestStatusListenerAccess.Unlock() -} diff --git a/url_request_status_listener_purego.go b/url_request_status_listener_purego.go deleted file mode 100644 index b8e44630..00000000 --- a/url_request_status_listener_purego.go +++ /dev/null @@ -1,22 +0,0 @@ -//go:build with_purego - -package cronet - -import ( - "unsafe" - - "github.com/sagernet/cronet-go/internal/cronet" -) - -func (l URLRequestStatusListener) Destroy() { - l.destroy() - cronet.UrlRequestStatusListenerDestroy(l.ptr) -} - -func (l URLRequestStatusListener) SetClientContext(context unsafe.Pointer) { - cronet.UrlRequestStatusListenerSetClientContext(l.ptr, uintptr(context)) -} - -func (l URLRequestStatusListener) ClientContext() unsafe.Pointer { - return unsafe.Pointer(cronet.UrlRequestStatusListenerGetClientContext(l.ptr)) -} diff --git a/url_response_info_cgo.go b/url_response_info_cgo.go deleted file mode 100644 index f68fc423..00000000 --- a/url_response_info_cgo.go +++ /dev/null @@ -1,138 +0,0 @@ -//go:build !with_purego - -package cronet - -// #include -// #include -// #include -import "C" - -import "unsafe" - -func NewURLResponseInfo() URLResponseInfo { - return URLResponseInfo{uintptr(unsafe.Pointer(C.Cronet_UrlResponseInfo_Create()))} -} - -func (i URLResponseInfo) Destroy() { - C.Cronet_UrlResponseInfo_Destroy(C.Cronet_UrlResponseInfoPtr(unsafe.Pointer(i.ptr))) -} - -// The URL the response is for. This is the URL after following -// redirects, so it may not be the originally requested URL -func (i URLResponseInfo) URL() string { - return C.GoString(C.Cronet_UrlResponseInfo_url_get(C.Cronet_UrlResponseInfoPtr(unsafe.Pointer(i.ptr)))) -} - -// URLChainSize The URL chain. The first entry is the originally requested URL; -// the following entries are redirects followed. -func (i URLResponseInfo) URLChainSize() int { - return int(C.Cronet_UrlResponseInfo_url_chain_size(C.Cronet_UrlResponseInfoPtr(unsafe.Pointer(i.ptr)))) -} - -func (i URLResponseInfo) URLChainAt(index int) string { - return C.GoString(C.Cronet_UrlResponseInfo_url_chain_at(C.Cronet_UrlResponseInfoPtr(unsafe.Pointer(i.ptr)), C.uint32_t(index))) -} - -// StatusCode is the HTTP status code. When a resource is retrieved from the cache, -// whether it was revalidated or not, the original status code is returned. -func (i URLResponseInfo) StatusCode() int { - return int(C.Cronet_UrlResponseInfo_http_status_code_get(C.Cronet_UrlResponseInfoPtr(unsafe.Pointer(i.ptr)))) -} - -// StatusText is the HTTP status text of the status line. For example, if the -// request received a "HTTP/1.1 200 OK" response, this method returns "OK". -func (i URLResponseInfo) StatusText() string { - return C.GoString(C.Cronet_UrlResponseInfo_http_status_text_get(C.Cronet_UrlResponseInfoPtr(unsafe.Pointer(i.ptr)))) -} - -// HeaderSize list size of response header field and value pairs. -// The headers are in the same order they are received over the wire. -func (i URLResponseInfo) HeaderSize() int { - return int(C.Cronet_UrlResponseInfo_all_headers_list_size(C.Cronet_UrlResponseInfoPtr(unsafe.Pointer(i.ptr)))) -} - -func (i URLResponseInfo) HeaderAt(index int) HTTPHeader { - return HTTPHeader{uintptr(unsafe.Pointer(C.Cronet_UrlResponseInfo_all_headers_list_at(C.Cronet_UrlResponseInfoPtr(unsafe.Pointer(i.ptr)), C.uint32_t(index))))} -} - -// Cached true if the response came from the cache, including -// requests that were revalidated over the network before being retrieved -// from the cache, failed otherwise. -func (i URLResponseInfo) Cached() bool { - return bool(C.Cronet_UrlResponseInfo_was_cached_get(C.Cronet_UrlResponseInfoPtr(unsafe.Pointer(i.ptr)))) -} - -// NegotiatedProtocol is the protocol (for example 'quic/1+spdy/3') negotiated with the server. -// An empty string if no protocol was negotiated, the protocol is -// not known, or when using plain HTTP or HTTPS. -func (i URLResponseInfo) NegotiatedProtocol() string { - return C.GoString(C.Cronet_UrlResponseInfo_negotiated_protocol_get(C.Cronet_UrlResponseInfoPtr(unsafe.Pointer(i.ptr)))) -} - -// ProxyServer is the proxy server that was used for the request. -func (i URLResponseInfo) ProxyServer() string { - return C.GoString(C.Cronet_UrlResponseInfo_proxy_server_get(C.Cronet_UrlResponseInfoPtr(unsafe.Pointer(i.ptr)))) -} - -// ReceivedByteCount is a minimum count of bytes received from the network to process this -// request. This count may ignore certain overheads (for example IP and -// TCP/UDP framing, SSL handshake and framing, proxy handling). This count is -// taken prior to decompression (for example GZIP and Brotli) and includes -// headers and data from all redirects. -func (i URLResponseInfo) ReceivedByteCount() int64 { - return int64(C.Cronet_UrlResponseInfo_received_byte_count_get(C.Cronet_UrlResponseInfoPtr(unsafe.Pointer(i.ptr)))) -} - -func (i URLResponseInfo) SetURL(url string) { - cURL := C.CString(url) - C.Cronet_UrlResponseInfo_url_set(C.Cronet_UrlResponseInfoPtr(unsafe.Pointer(i.ptr)), cURL) - C.free(unsafe.Pointer(cURL)) -} - -func (i URLResponseInfo) AddURLChain(url string) { - cURL := C.CString(url) - C.Cronet_UrlResponseInfo_url_chain_add(C.Cronet_UrlResponseInfoPtr(unsafe.Pointer(i.ptr)), cURL) - C.free(unsafe.Pointer(cURL)) -} - -func (i URLResponseInfo) ClearURLChain() { - C.Cronet_UrlResponseInfo_url_chain_clear(C.Cronet_UrlResponseInfoPtr(unsafe.Pointer(i.ptr))) -} - -func (i URLResponseInfo) SetStatusCode(code int32) { - C.Cronet_UrlResponseInfo_http_status_code_set(C.Cronet_UrlResponseInfoPtr(unsafe.Pointer(i.ptr)), C.int32_t(code)) -} - -func (i URLResponseInfo) SetStatusText(text string) { - cText := C.CString(text) - C.Cronet_UrlResponseInfo_http_status_text_set(C.Cronet_UrlResponseInfoPtr(unsafe.Pointer(i.ptr)), cText) - C.free(unsafe.Pointer(cText)) -} - -func (i URLResponseInfo) AddHeader(header HTTPHeader) { - C.Cronet_UrlResponseInfo_all_headers_list_add(C.Cronet_UrlResponseInfoPtr(unsafe.Pointer(i.ptr)), C.Cronet_HttpHeaderPtr(unsafe.Pointer(header.ptr))) -} - -func (i URLResponseInfo) ClearHeaders() { - C.Cronet_UrlResponseInfo_all_headers_list_clear(C.Cronet_UrlResponseInfoPtr(unsafe.Pointer(i.ptr))) -} - -func (i URLResponseInfo) SetCached(cached bool) { - C.Cronet_UrlResponseInfo_was_cached_set(C.Cronet_UrlResponseInfoPtr(unsafe.Pointer(i.ptr)), C.bool(cached)) -} - -func (i URLResponseInfo) SetNegotiatedProtocol(protocol string) { - cProtocol := C.CString(protocol) - C.Cronet_UrlResponseInfo_negotiated_protocol_set(C.Cronet_UrlResponseInfoPtr(unsafe.Pointer(i.ptr)), cProtocol) - C.free(unsafe.Pointer(cProtocol)) -} - -func (i URLResponseInfo) SetProxyServer(proxy string) { - cProxy := C.CString(proxy) - C.Cronet_UrlResponseInfo_proxy_server_set(C.Cronet_UrlResponseInfoPtr(unsafe.Pointer(i.ptr)), cProxy) - C.free(unsafe.Pointer(cProxy)) -} - -func (i URLResponseInfo) SetReceivedByteCount(count int64) { - C.Cronet_UrlResponseInfo_received_byte_count_set(C.Cronet_UrlResponseInfoPtr(unsafe.Pointer(i.ptr)), C.int64_t(count)) -} diff --git a/url_response_info_purego.go b/url_response_info_purego.go deleted file mode 100644 index 65d766c4..00000000 --- a/url_response_info_purego.go +++ /dev/null @@ -1,59 +0,0 @@ -//go:build with_purego - -package cronet - -import ( - "github.com/sagernet/cronet-go/internal/cronet" -) - -func NewURLResponseInfo() URLResponseInfo { - return URLResponseInfo{cronet.UrlResponseInfoCreate()} -} - -func (i URLResponseInfo) Destroy() { - cronet.UrlResponseInfoDestroy(i.ptr) -} - -func (i URLResponseInfo) URL() string { - return cronet.UrlResponseInfoUrlGet(i.ptr) -} - -func (i URLResponseInfo) URLChainSize() int { - return int(cronet.UrlResponseInfoUrlChainSize(i.ptr)) -} - -func (i URLResponseInfo) URLChainAt(index int) string { - return cronet.UrlResponseInfoUrlChainAt(i.ptr, uint32(index)) -} - -func (i URLResponseInfo) StatusCode() int { - return int(cronet.UrlResponseInfoHttpStatusCodeGet(i.ptr)) -} - -func (i URLResponseInfo) StatusText() string { - return cronet.UrlResponseInfoHttpStatusTextGet(i.ptr) -} - -func (i URLResponseInfo) HeaderSize() int { - return int(cronet.UrlResponseInfoAllHeadersListSize(i.ptr)) -} - -func (i URLResponseInfo) HeaderAt(index int) HTTPHeader { - return HTTPHeader{cronet.UrlResponseInfoAllHeadersListAt(i.ptr, uint32(index))} -} - -func (i URLResponseInfo) Cached() bool { - return cronet.UrlResponseInfoWasCachedGet(i.ptr) -} - -func (i URLResponseInfo) NegotiatedProtocol() string { - return cronet.UrlResponseInfoNegotiatedProtocolGet(i.ptr) -} - -func (i URLResponseInfo) ProxyServer() string { - return cronet.UrlResponseInfoProxyServerGet(i.ptr) -} - -func (i URLResponseInfo) ReceivedByteCount() int64 { - return cronet.UrlResponseInfoReceivedByteCountGet(i.ptr) -} From ed8e8b302e1d2f4631958ae1941308ada5d67697 Mon Sep 17 00:00:00 2001 From: iHsin Date: Tue, 9 Jun 2026 14:36:52 +0800 Subject: [PATCH 2/4] Replace Go build tool with just + bash orchestration Drop the Go dependency entirely: building the cronet libraries no longer requires a Go toolchain. - scripts/build-naive.sh: faithful bash port of the former cmd/build-naive (target resolution, gn args, openwrt/musl + sysroot/clang mappings, get-clang driving, ninja targets, link-flag extraction, env). bash 3.2 compatible (macOS runners ship 3.2). - justfile: recipes (build/compile/package/apple/download-toolchain/env) driving the script; just is the orchestration entry point. - Remove cmd/, go.mod, go.sum. - CI: replace setup-go with taiki-e/install-action (just); call `just compile` / `just package`; cache key now hashes build-naive.sh. - README updated for the just/bash workflow. Pure logic (resolution, naming, sysroot/clang, env, gn args, link-flag parsing) verified locally against the previous Go behavior; gn/ninja execution and just-on-runners validated by CI. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/naive-build.yml | 52 +- Makefile | 11 - README.md | 48 +- cmd/build-naive/cmd.go | 305 ----------- cmd/build-naive/cmd_build.go | 350 ------------ cmd/build-naive/cmd_download_toolchain.go | 35 -- cmd/build-naive/cmd_env.go | 136 ----- cmd/build-naive/cmd_package.go | 236 --------- cmd/build-naive/main.go | 10 - go.mod | 10 - go.sum | 55 -- justfile | 51 ++ scripts/build-naive.sh | 613 ++++++++++++++++++++++ 13 files changed, 718 insertions(+), 1194 deletions(-) delete mode 100644 Makefile delete mode 100644 cmd/build-naive/cmd.go delete mode 100644 cmd/build-naive/cmd_build.go delete mode 100644 cmd/build-naive/cmd_download_toolchain.go delete mode 100644 cmd/build-naive/cmd_env.go delete mode 100644 cmd/build-naive/cmd_package.go delete mode 100644 cmd/build-naive/main.go delete mode 100644 go.mod delete mode 100644 go.sum create mode 100644 justfile create mode 100755 scripts/build-naive.sh diff --git a/.github/workflows/naive-build.yml b/.github/workflows/naive-build.yml index 19c94a67..5c8550fb 100644 --- a/.github/workflows/naive-build.yml +++ b/.github/workflows/naive-build.yml @@ -27,9 +27,9 @@ jobs: - uses: actions/checkout@v4 with: submodules: 'recursive' - - uses: actions/setup-go@v5 + - uses: taiki-e/install-action@v2 with: - go-version: ^1.25 + tool: just - name: Get naiveproxy commit id: naive run: echo "commit=$(git -C naiveproxy rev-parse HEAD)" >> $GITHUB_OUTPUT @@ -45,7 +45,7 @@ jobs: uses: actions/cache@v4 with: path: naiveproxy/src/out - key: naive-build-${{ steps.naive.outputs.commit }}-${{ hashFiles('cmd/build-naive/cmd_build.go', 'cmd/build-naive/cmd.go') }}-linux-${{ matrix.arch }} + key: naive-build-${{ steps.naive.outputs.commit }}-${{ hashFiles('scripts/build-naive.sh') }}-linux-${{ matrix.arch }} - name: Cache ccache files if: steps.build-cache.outputs.cache-hit != 'true' uses: actions/cache@v4 @@ -63,12 +63,12 @@ jobs: run: ccache -z - name: Build if: steps.build-cache.outputs.cache-hit != 'true' - run: go run ./cmd/build-naive --target=linux/${{ matrix.arch }} build + run: just compile linux/${{ matrix.arch }} - name: Show ccache stats if: steps.build-cache.outputs.cache-hit != 'true' run: ccache -s - name: Package - run: go run ./cmd/build-naive --target=linux/${{ matrix.arch }} package + run: just package linux/${{ matrix.arch }} - uses: actions/upload-artifact@v4 with: name: cronet-linux-${{ matrix.arch }} @@ -104,9 +104,9 @@ jobs: - uses: actions/checkout@v4 with: submodules: 'recursive' - - uses: actions/setup-go@v5 + - uses: taiki-e/install-action@v2 with: - go-version: ^1.22 + tool: just - name: Get naiveproxy commit id: naive run: echo "commit=$(git -C naiveproxy rev-parse HEAD)" >> $GITHUB_OUTPUT @@ -118,7 +118,7 @@ jobs: uses: actions/cache@v4 with: path: naiveproxy/src/out - key: naive-build-${{ steps.naive.outputs.commit }}-${{ hashFiles('cmd/build-naive/cmd_build.go', 'cmd/build-naive/cmd.go') }}-${{ matrix.name }} + key: naive-build-${{ steps.naive.outputs.commit }}-${{ hashFiles('scripts/build-naive.sh') }}-${{ matrix.name }} - name: Cache ccache files if: steps.build-cache.outputs.cache-hit != 'true' uses: actions/cache@v4 @@ -134,12 +134,12 @@ jobs: run: ccache -z - name: Build if: steps.build-cache.outputs.cache-hit != 'true' - run: go run ./cmd/build-naive --target=${{ matrix.target }} build + run: just compile ${{ matrix.target }} - name: Show ccache stats if: steps.build-cache.outputs.cache-hit != 'true' run: ccache -s - name: Package - run: go run ./cmd/build-naive --target=${{ matrix.target }} package + run: just package ${{ matrix.target }} - uses: actions/upload-artifact@v4 with: name: cronet-${{ matrix.name }} @@ -159,9 +159,9 @@ jobs: - uses: actions/checkout@v4 with: submodules: 'recursive' - - uses: actions/setup-go@v5 + - uses: taiki-e/install-action@v2 with: - go-version: ^1.22 + tool: just - name: Get naiveproxy commit id: naive shell: bash @@ -175,7 +175,7 @@ jobs: uses: actions/cache@v4 with: path: naiveproxy/src/out - key: naive-build-${{ steps.naive.outputs.commit }}-${{ hashFiles('cmd/build-naive/cmd_build.go', 'cmd/build-naive/cmd.go') }}-windows-${{ matrix.arch }} + key: naive-build-${{ steps.naive.outputs.commit }}-${{ hashFiles('scripts/build-naive.sh') }}-windows-${{ matrix.arch }} - name: Install tools if: steps.build-cache.outputs.cache-hit != 'true' run: | @@ -215,12 +215,14 @@ jobs: run: sccache -z - name: Build if: steps.build-cache.outputs.cache-hit != 'true' - run: go run ./cmd/build-naive --target=windows/${{ matrix.arch }} build + shell: bash + run: just compile windows/${{ matrix.arch }} - name: Show sccache stats if: steps.build-cache.outputs.cache-hit != 'true' run: sccache -s - name: Package - run: go run ./cmd/build-naive --target=windows/${{ matrix.arch }} package + shell: bash + run: just package windows/${{ matrix.arch }} - uses: actions/upload-artifact@v4 with: name: cronet-windows-${{ matrix.arch }} @@ -238,9 +240,9 @@ jobs: - uses: actions/checkout@v4 with: submodules: 'recursive' - - uses: actions/setup-go@v5 + - uses: taiki-e/install-action@v2 with: - go-version: ^1.22 + tool: just - name: Get naiveproxy commit id: naive run: echo "commit=$(git -C naiveproxy rev-parse HEAD)" >> $GITHUB_OUTPUT @@ -252,7 +254,7 @@ jobs: uses: actions/cache@v4 with: path: naiveproxy/src/out - key: naive-build-${{ steps.naive.outputs.commit }}-${{ hashFiles('cmd/build-naive/cmd_build.go', 'cmd/build-naive/cmd.go') }}-android-${{ matrix.arch }} + key: naive-build-${{ steps.naive.outputs.commit }}-${{ hashFiles('scripts/build-naive.sh') }}-android-${{ matrix.arch }} - name: Cache ccache files if: steps.build-cache.outputs.cache-hit != 'true' uses: actions/cache@v4 @@ -270,12 +272,12 @@ jobs: run: ccache -z - name: Build if: steps.build-cache.outputs.cache-hit != 'true' - run: go run ./cmd/build-naive --target=android/${{ matrix.arch }} build + run: just compile android/${{ matrix.arch }} - name: Show ccache stats if: steps.build-cache.outputs.cache-hit != 'true' run: ccache -s - name: Package - run: go run ./cmd/build-naive --target=android/${{ matrix.arch }} package + run: just package android/${{ matrix.arch }} - uses: actions/upload-artifact@v4 with: name: cronet-android-${{ matrix.arch }} @@ -293,9 +295,9 @@ jobs: - uses: actions/checkout@v4 with: submodules: 'recursive' - - uses: actions/setup-go@v5 + - uses: taiki-e/install-action@v2 with: - go-version: ^1.22 + tool: just - name: Get naiveproxy commit id: naive run: echo "commit=$(git -C naiveproxy rev-parse HEAD)" >> $GITHUB_OUTPUT @@ -307,7 +309,7 @@ jobs: uses: actions/cache@v4 with: path: naiveproxy/src/out - key: naive-build-${{ steps.naive.outputs.commit }}-${{ hashFiles('cmd/build-naive/cmd_build.go', 'cmd/build-naive/cmd.go') }}-linux-musl-${{ matrix.arch }} + key: naive-build-${{ steps.naive.outputs.commit }}-${{ hashFiles('scripts/build-naive.sh') }}-linux-musl-${{ matrix.arch }} - name: Cache ccache files if: steps.build-cache.outputs.cache-hit != 'true' uses: actions/cache@v4 @@ -325,12 +327,12 @@ jobs: run: ccache -z - name: Build if: steps.build-cache.outputs.cache-hit != 'true' - run: go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl build + run: just compile linux/${{ matrix.arch }} musl - name: Show ccache stats if: steps.build-cache.outputs.cache-hit != 'true' run: ccache -s - name: Package - run: go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl package + run: just package linux/${{ matrix.arch }} musl - uses: actions/upload-artifact@v4 with: name: cronet-linux-musl-${{ matrix.arch }} diff --git a/Makefile b/Makefile deleted file mode 100644 index b418075a..00000000 --- a/Makefile +++ /dev/null @@ -1,11 +0,0 @@ -TARGET ?= -TARGET_FLAG = $(if $(TARGET),--target=$(TARGET),) - -# Build the cronet libraries (.a/.so/.dll) and collect them into lib/ + include/. -build: - go run -v ./cmd/build-naive build $(TARGET_FLAG) - go run -v ./cmd/build-naive package $(TARGET_FLAG) - -# Convenience target for all Apple platforms. -apple: - TARGET="ios/arm64,ios/arm64/simulator,ios/amd64/simulator,tvos/arm64,tvos/arm64/simulator,tvos/amd64/simulator,darwin/arm64,darwin/amd64" make diff --git a/README.md b/README.md index 6d18b3ea..8dddde9b 100644 --- a/README.md +++ b/README.md @@ -35,18 +35,25 @@ link the static library (`lib//link_flags.txt`). ## How it works -The build is orchestrated by the small Go CLI in [`cmd/build-naive`](cmd/build-naive) -(the only Go code in the repo — it just drives `get-clang.sh` → `gn gen` → -`ninja`). It depends only on `github.com/spf13/cobra`. - -Subcommands: - -| Command | Purpose | -|---------------------|---------------------------------------------------------------| -| `build` | `gn gen` + `ninja` → produce `libcronet.{a,so,dll}` | -| `package` | Copy libs to `lib/` and headers to `include/`, dump link flags | -| `download-toolchain`| Download clang + sysroot without building | -| `env` | Print `CC`/`CXX`/`CGO_LDFLAGS` for cross-compiling consumers | +The build is orchestrated by [`just`](https://github.com/casey/just), whose +recipes drive the bash script [`scripts/build-naive.sh`](scripts/build-naive.sh) +(`get-clang.sh` → `gn gen` → `ninja`). **No Go (or any other) toolchain is +required** — only `bash`, `just`, and the usual Chromium build deps +(`gn`/`ninja`, a C/C++ toolchain). + +`just` recipes: + +| Recipe | Purpose | +|---------------------------------|----------------------------------------------------------------| +| `just build [target] [libc]` | `compile` + `package` in one go | +| `just compile [target] [libc]` | `gn gen` + `ninja` → produce `libcronet.{a,so,dll}` | +| `just package [target] [libc]` | Copy libs to `lib/` and headers to `include/`, dump link flags | +| `just apple` | Build every Apple platform | +| `just download-toolchain …` | Download clang + sysroot without building | +| `just env [target] [libc]` | Print `CC`/`CXX`/`CGO_LDFLAGS` for cross-compiling consumers | + +The script can also be invoked directly without `just`: +`bash scripts/build-naive.sh build --target linux/amd64`. ## Build instructions @@ -54,11 +61,11 @@ Subcommands: git clone --recursive --depth=1 cd cronet-libs -# Linux (host target). Add --target=os/arch to cross-compile, -# or --libc=musl for static musl builds. -go run ./cmd/build-naive build -go run ./cmd/build-naive package -# or simply: make +just # list recipes +just build # host target (compile + package) +just build linux/arm64 # cross-compile +just build linux/amd64 musl # static musl Linux build +just apple # all Apple platforms ``` Outputs land in: @@ -77,12 +84,11 @@ The raw ninja output also remains under ### Cross-compiling ```bash -go run ./cmd/build-naive --target=linux/arm64 download-toolchain -go run ./cmd/build-naive --target=linux/arm64 build -go run ./cmd/build-naive --target=linux/arm64 package +just download-toolchain linux/arm64 +just build linux/arm64 # musl (static): -go run ./cmd/build-naive --target=linux/amd64 --libc=musl build +just build linux/amd64 musl ``` ### Directories worth caching (CI) diff --git a/cmd/build-naive/cmd.go b/cmd/build-naive/cmd.go deleted file mode 100644 index 5f572fa5..00000000 --- a/cmd/build-naive/cmd.go +++ /dev/null @@ -1,305 +0,0 @@ -package main - -import ( - "bytes" - "io" - "log" - "os" - "os/exec" - "path/filepath" - "runtime" - "strings" - - "github.com/spf13/cobra" -) - -// Target represents a build target platform -type Target struct { - OS string // gn target_os: linux, mac, win, android, ios, openwrt - CPU string // gn target_cpu: x64, arm64, x86, arm - GOOS string // Go GOOS - ARCH string // Go GOARCH - Libc string // C library: "" (default), "glibc", or "musl" (Linux only) - Platform string // Apple: "iphoneos", "tvos" (GN target_platform) - Environment string // Apple: "device", "simulator" (GN target_environment) -} - -var allTargets = []Target{ - {OS: "linux", CPU: "x64", GOOS: "linux", ARCH: "amd64"}, - {OS: "linux", CPU: "arm64", GOOS: "linux", ARCH: "arm64"}, - {OS: "linux", CPU: "x86", GOOS: "linux", ARCH: "386"}, - {OS: "linux", CPU: "arm", GOOS: "linux", ARCH: "arm"}, - {OS: "linux", CPU: "loong64", GOOS: "linux", ARCH: "loong64"}, - {OS: "linux", CPU: "mipsel", GOOS: "linux", ARCH: "mipsle"}, - {OS: "linux", CPU: "mips64el", GOOS: "linux", ARCH: "mips64le"}, - {OS: "linux", CPU: "riscv64", GOOS: "linux", ARCH: "riscv64"}, - {OS: "mac", CPU: "x64", GOOS: "darwin", ARCH: "amd64"}, - {OS: "mac", CPU: "arm64", GOOS: "darwin", ARCH: "arm64"}, - {OS: "win", CPU: "x64", GOOS: "windows", ARCH: "amd64"}, - {OS: "win", CPU: "arm64", GOOS: "windows", ARCH: "arm64"}, - {OS: "ios", CPU: "arm64", GOOS: "ios", ARCH: "arm64", Platform: "iphoneos", Environment: "device"}, - {OS: "ios", CPU: "arm64", GOOS: "ios", ARCH: "arm64", Platform: "iphoneos", Environment: "simulator"}, - {OS: "ios", CPU: "x64", GOOS: "ios", ARCH: "amd64", Platform: "iphoneos", Environment: "simulator"}, - {OS: "ios", CPU: "arm64", GOOS: "ios", ARCH: "arm64", Platform: "tvos", Environment: "device"}, - {OS: "ios", CPU: "arm64", GOOS: "ios", ARCH: "arm64", Platform: "tvos", Environment: "simulator"}, - {OS: "ios", CPU: "x64", GOOS: "ios", ARCH: "amd64", Platform: "tvos", Environment: "simulator"}, - {OS: "android", CPU: "arm64", GOOS: "android", ARCH: "arm64"}, - {OS: "android", CPU: "x64", GOOS: "android", ARCH: "amd64"}, - {OS: "android", CPU: "arm", GOOS: "android", ARCH: "arm"}, - {OS: "android", CPU: "x86", GOOS: "android", ARCH: "386"}, -} - -var ( - projectRoot string - naiveRoot string - srcRoot string - targetStr string - libcStr string -) - -var mainCommand = &cobra.Command{ - Use: "build-naive", - Short: "Build tool for cronet-go naiveproxy integration", - PersistentPreRun: preRun, -} - -func init() { - log.SetFlags(0) - log.SetPrefix("[build] ") - mainCommand.PersistentFlags().StringVarP(&targetStr, "target", "t", "", "Comma-separated list of targets (e.g., linux/amd64,darwin/arm64). Empty means host only.") - mainCommand.PersistentFlags().StringVar(&libcStr, "libc", "", "C library for Linux: glibc (default) or musl") -} - -func preRun(cmd *cobra.Command, args []string) { - workingDirectory, err := os.Getwd() - if err != nil { - log.Fatalf("failed to get working directory: %v", err) - } - - for directory := workingDirectory; ; directory = filepath.Dir(directory) { - if _, err := os.Stat(filepath.Join(directory, "go.mod")); err == nil { - projectRoot = directory - break - } - if directory == filepath.Dir(directory) { - log.Fatal("could not find project root (go.mod)") - } - } - - naiveRoot = filepath.Join(projectRoot, "naiveproxy") - srcRoot = filepath.Join(naiveRoot, "src") -} - -func parseTargets() []Target { - if libcStr != "" && libcStr != "glibc" && libcStr != "musl" { - log.Fatalf("invalid libc: %s (expected glibc or musl)", libcStr) - } - - if targetStr == "" { - hostOS := runtime.GOOS - hostArch := runtime.GOARCH - for _, t := range allTargets { - if t.GOOS == hostOS && t.ARCH == hostArch { - target := t - target = applyLibc(target) - return []Target{target} - } - } - log.Fatalf("unsupported host platform: %s/%s", hostOS, hostArch) - } - - if targetStr == "all" { - targets := make([]Target, len(allTargets)) - for i, t := range allTargets { - targets[i] = applyLibc(t) - } - return targets - } - - var targets []Target - for _, part := range strings.Split(targetStr, ",") { - part = strings.TrimSpace(part) - parts := strings.Split(part, "/") - if len(parts) < 2 || len(parts) > 3 { - log.Fatalf("invalid target format: %s (expected os/arch or os/arch/variant)", part) - } - goos, goarch := parts[0], parts[1] - - // Handle iOS and tvOS targets with optional simulator suffix - if goos == "ios" || goos == "tvos" { - target := parseAppleTarget(goos, goarch, parts) - targets = append(targets, target) - continue - } - - if len(parts) != 2 { - log.Fatalf("invalid target format: %s (expected os/arch)", part) - } - - found := false - for _, t := range allTargets { - if t.GOOS == goos && t.ARCH == goarch && t.Platform == "" { - target := applyLibc(t) - targets = append(targets, target) - found = true - break - } - } - if !found { - log.Fatalf("unsupported target: %s/%s", goos, goarch) - } - } - return targets -} - -// parseAppleTarget parses iOS/tvOS target strings -// Formats: -// - ios/arm64 -> iOS device arm64 -// - ios/arm64/simulator -> iOS simulator arm64 -// - ios/amd64 -> iOS simulator amd64 (only simulator supports amd64) -// - tvos/arm64 -> tvOS device arm64 -// - tvos/arm64/simulator -> tvOS simulator arm64 -// - tvos/amd64 -> tvOS simulator amd64 -func parseAppleTarget(goos, goarch string, parts []string) Target { - isTvOS := goos == "tvos" - platform := "iphoneos" - if isTvOS { - platform = "tvos" - } - - environment := "device" - if len(parts) == 3 && parts[2] == "simulator" { - environment = "simulator" - } else if goarch == "amd64" { - // amd64 is only available for simulator - environment = "simulator" - } - - for _, t := range allTargets { - if t.GOOS == "ios" && t.ARCH == goarch && t.Platform == platform && t.Environment == environment { - return t - } - } - - log.Fatalf("unsupported target: %s/%s", goos, goarch) - return Target{} -} - -func applyLibc(target Target) Target { - if libcStr == "" || libcStr == "glibc" { - return target - } - - if target.GOOS != "linux" { - log.Fatalf("--libc=musl is only supported for Linux targets, not %s", target.GOOS) - } - - // For musl, we use openwrt as the target OS - target.OS = "openwrt" - target.Libc = "musl" - return target -} - -func hostToCPU(goarch string) string { - switch goarch { - case "amd64": - return "x64" - case "arm64": - return "arm64" - case "386": - return "x86" - case "arm": - return "arm" - case "loong64": - return "loong64" - case "mipsle": - return "mipsel" - case "mips64le": - return "mips64el" - default: - return goarch - } -} - -func runCommand(directory string, name string, args ...string) { - command := exec.Command(name, args...) - command.Dir = directory - command.Stdout = os.Stdout - command.Stderr = os.Stderr - err := command.Run() - if err != nil { - log.Fatalf("command failed: %s %s: %v", name, strings.Join(args, " "), err) - } -} - -func runCommandOutput(directory string, name string, args ...string) string { - command := exec.Command(name, args...) - command.Dir = directory - var stdout bytes.Buffer - command.Stdout = &stdout - command.Stderr = os.Stderr - err := command.Run() - if err != nil { - log.Fatalf("command failed: %s %s: %v", name, strings.Join(args, " "), err) - } - return stdout.String() -} - -func copyFile(source, destination string) { - sourceFile, err := os.Open(source) - if err != nil { - log.Fatalf("failed to open %s: %v", source, err) - } - defer sourceFile.Close() - - err = os.MkdirAll(filepath.Dir(destination), 0o755) - if err != nil { - log.Fatalf("failed to create directory for %s: %v", destination, err) - } - - destinationFile, err := os.Create(destination) - if err != nil { - log.Fatalf("failed to create %s: %v", destination, err) - } - defer destinationFile.Close() - - _, err = io.Copy(destinationFile, sourceFile) - if err != nil { - log.Fatalf("failed to copy %s to %s: %v", source, destination, err) - } -} - -func copyDirectory(source, destination string) { - os.MkdirAll(destination, 0o755) - entries, err := os.ReadDir(source) - if err != nil { - return - } - for _, entry := range entries { - sourcePath := filepath.Join(source, entry.Name()) - destinationPath := filepath.Join(destination, entry.Name()) - if entry.IsDir() { - copyDirectory(sourcePath, destinationPath) - } else { - copyFile(sourcePath, destinationPath) - } - } -} - -func copyGlob(sourceDirectory, destinationDirectory, pattern string) { - matches, _ := filepath.Glob(filepath.Join(sourceDirectory, pattern)) - for _, source := range matches { - relative, _ := filepath.Rel(sourceDirectory, source) - destination := filepath.Join(destinationDirectory, relative) - - info, err := os.Stat(source) - if err != nil { - continue - } - if info.IsDir() { - copyDirectory(source, destination) - } else { - copyFile(source, destination) - } - } -} diff --git a/cmd/build-naive/cmd_build.go b/cmd/build-naive/cmd_build.go deleted file mode 100644 index f42b1fe6..00000000 --- a/cmd/build-naive/cmd_build.go +++ /dev/null @@ -1,350 +0,0 @@ -package main - -import ( - "fmt" - "log" - "os" - "os/exec" - "path/filepath" - "runtime" - "strings" - - "github.com/spf13/cobra" -) - -var commandBuild = &cobra.Command{ - Use: "build", - Short: "Build cronet_static for specified targets", - Run: func(cmd *cobra.Command, args []string) { - targets := parseTargets() - build(targets) - }, -} - -func init() { - mainCommand.AddCommand(commandBuild) -} - -func formatTargetLog(t Target) string { - if t.Platform != "" { - platformName := "iOS" - if t.Platform == "tvos" { - platformName = "tvOS" - } - if t.Environment == "simulator" { - return fmt.Sprintf("%s Simulator %s", platformName, t.ARCH) - } - return fmt.Sprintf("%s %s", platformName, t.ARCH) - } - if t.Libc == "musl" { - return fmt.Sprintf("%s/%s (musl)", t.GOOS, t.ARCH) - } - return fmt.Sprintf("%s/%s", t.GOOS, t.ARCH) -} - -func getOutputDirectory(t Target) string { - if t.Platform != "" { - directory := fmt.Sprintf("out/cronet-%s-%s", t.Platform, t.CPU) - if t.Environment == "simulator" { - directory += "-simulator" - } - return directory - } - return fmt.Sprintf("out/cronet-%s-%s", t.OS, t.CPU) -} - -func build(targets []Target) { - log.Printf("Building cronet_static for %d target(s)", len(targets)) - - for _, t := range targets { - log.Printf("Building %s...", formatTargetLog(t)) - buildTarget(t) - } - - log.Print("Build complete!") -} - -func getExtraFlags(t Target) string { - flags := []string{ - fmt.Sprintf(`target_os="%s"`, t.OS), - fmt.Sprintf(`target_cpu="%s"`, t.CPU), - } - return strings.Join(flags, " ") -} - -type openwrtConfig struct { - target string // OpenWrt target (e.g., "x86", "armsr") - subtarget string // OpenWrt subtarget (e.g., "64", "generic") - arch string // OpenWrt arch (e.g., "x86_64", "aarch64") - release string // OpenWrt release version - gccVer string // GCC version in SDK - extraGNArgs []string // additional GN args passed to buildTarget -} - -func getOpenwrtConfig(t Target) openwrtConfig { - // Use OpenWrt 23.05.5 as it's stable and widely available - switch t.CPU { - case "x64": - return openwrtConfig{ - target: "x86", - subtarget: "64", - arch: "x86_64", - release: "23.05.5", - gccVer: "12.3.0", - } - case "arm64": - return openwrtConfig{ - target: "armsr", - subtarget: "armv8", - arch: "aarch64", - release: "23.05.5", - gccVer: "12.3.0", - } - case "x86": - return openwrtConfig{ - target: "x86", - subtarget: "generic", - arch: "i386_pentium4", - release: "23.05.5", - gccVer: "12.3.0", - } - case "arm": - return openwrtConfig{ - target: "armsr", - subtarget: "armv7", - arch: "arm_cortex-a15_neon-vfpv4", - release: "23.05.5", - gccVer: "12.3.0", - } - case "loong64": - // OpenWrt 24.10.0 is the first release with loongarch64 support - return openwrtConfig{ - target: "loongarch64", - subtarget: "generic", - arch: "loongarch64", - release: "24.10.5", - gccVer: "13.3.0", - } - case "mipsel": - // mipsel_24kc is MIPS32R2 with no hardware FPU (software float) - return openwrtConfig{ - target: "ramips", - subtarget: "rt305x", - arch: "mipsel_24kc", - release: "23.05.5", - gccVer: "12.3.0", - extraGNArgs: []string{`mips_float_abi="soft"`, `mips_arch_variant="r2"`}, - } - case "riscv64": - return openwrtConfig{ - target: "sifiveu", - subtarget: "generic", - arch: "riscv64", - release: "23.05.5", - gccVer: "12.3.0", - } - default: - log.Fatalf("unsupported CPU for musl: %s", t.CPU) - return openwrtConfig{} - } -} - -func getOpenwrtFlags(t Target) string { - config := getOpenwrtConfig(t) - return fmt.Sprintf( - `target="%s" subtarget="%s" arch="%s" release="%s" gcc_ver="%s"`, - config.target, config.subtarget, config.arch, config.release, config.gccVer, - ) -} - -func runGetClang(t Target) { - // For cross-compilation on Linux, we need to also build host sysroot first - // because GN needs host sysroot in addition to target sysroot. - // This applies to linux, android, and openwrt targets. - hostOS := runtime.GOOS - hostCPU := hostToCPU(runtime.GOARCH) - needsHostSysroot := hostOS == "linux" && (t.OS == "linux" || t.OS == "android" || t.OS == "openwrt") - if needsHostSysroot { - hostFlags := fmt.Sprintf(`target_os="linux" target_cpu="%s"`, hostCPU) - log.Printf("Running get-clang.sh for host sysroot with EXTRA_FLAGS=%s", hostFlags) - command := exec.Command("bash", "./get-clang.sh") - command.Dir = srcRoot - command.Env = append(os.Environ(), "EXTRA_FLAGS="+hostFlags) - command.Stdout = os.Stdout - command.Stderr = os.Stderr - err := command.Run() - if err != nil { - log.Fatalf("get-clang.sh (host) failed: %v", err) - } - - hostSysrootSource := getSysrootPath(Target{CPU: hostCPU}) - hostSysrootDestination := filepath.Join(srcRoot, "build/linux/debian_bullseye_amd64-sysroot") - if _, err := os.Stat(hostSysrootDestination); os.IsNotExist(err) { - log.Printf("Creating symlink for host sysroot: %s -> %s", hostSysrootDestination, hostSysrootSource) - err := os.Symlink(hostSysrootSource, hostSysrootDestination) - if err != nil { - log.Fatalf("failed to create host sysroot symlink: %v", err) - } - } - } - - extraFlags := getExtraFlags(t) - env := []string{"EXTRA_FLAGS=" + extraFlags} - - if t.OS == "openwrt" { - openwrtFlags := getOpenwrtFlags(t) - env = append(env, "OPENWRT_FLAGS="+openwrtFlags) - log.Printf("Running get-clang.sh with EXTRA_FLAGS=%s OPENWRT_FLAGS=%s", extraFlags, openwrtFlags) - } else { - log.Printf("Running get-clang.sh with EXTRA_FLAGS=%s", extraFlags) - } - - command := exec.Command("bash", "./get-clang.sh") - command.Dir = srcRoot - command.Env = append(os.Environ(), env...) - command.Stdout = os.Stdout - command.Stderr = os.Stderr - err := command.Run() - if err != nil { - log.Fatalf("get-clang.sh failed: %v", err) - } -} - -func buildTarget(t Target) { - runGetClang(t) - - outputDirectory := getOutputDirectory(t) - - args := []string{ - "is_official_build=true", - "is_debug=false", - "is_clang=true", - "use_clang_modules=false", - "use_thin_lto=false", // Disable ThinLTO so static lib can be linked with system clang - "fatal_linker_warnings=false", - "treat_warnings_as_errors=false", - "is_cronet_build=true", - "use_udev=false", - "use_aura=false", - "use_ozone=false", - "use_gio=false", - "use_glib=false", - "use_kerberos=false", - "disable_zstd_filter=false", - "enable_reporting=false", - "enable_bracketed_proxy_uris=true", - "enable_quic_proxy_support=true", - "use_nss_certs=false", - "enable_backup_ref_ptr_support=false", - "enable_dangling_raw_ptr_checks=false", - "exclude_unwind_tables=true", - "enable_resource_allowlist_generation=false", - "symbol_level=0", - "enable_dsyms=false", - "optimize_for_size=true", - fmt.Sprintf("target_os=\"%s\"", t.OS), - fmt.Sprintf("target_cpu=\"%s\"", t.CPU), - } - - switch t.OS { - case "mac": - args = append(args, "use_sysroot=false") - case "linux": - // Sysroot is handled by get-clang.sh, use the naiveproxy path - sysrootPath := getSysrootPath(t) - sysrootDirectory := strings.TrimPrefix(sysrootPath, srcRoot+string(filepath.Separator)) - args = append(args, "use_sysroot=true", fmt.Sprintf("target_sysroot=\"//%s\"", sysrootDirectory)) - if t.CPU == "x64" { - args = append(args, "use_cfi_icall=false", "is_cfi=false") - } - case "openwrt": - config := getOpenwrtConfig(t) - sysrootDirectory := fmt.Sprintf("out/sysroot-build/openwrt/%s/%s", config.release, config.arch) - args = append(args, - "use_sysroot=true", - fmt.Sprintf("target_sysroot=\"//%s\"", sysrootDirectory), - "build_static=true", // Static linking for musl - "use_allocator_shim=false", // Disable allocator shim for musl compatibility - "use_partition_alloc=false", // Disable PartitionAlloc to avoid GetStackTop() crash on musl - ) - args = append(args, config.extraGNArgs...) - if t.CPU == "x64" { - args = append(args, "use_cfi_icall=false", "is_cfi=false") - } - case "win": - args = append(args, "use_sysroot=false") - case "android": - args = append(args, - "use_sysroot=false", - "default_min_sdk_version=23", - ) - case "ios": - platform := t.Platform - if platform == "" { - platform = "iphoneos" - } - environment := t.Environment - if environment == "" { - environment = "device" - } - args = append(args, - "use_sysroot=false", - "ios_enable_code_signing=false", - fmt.Sprintf(`target_platform="%s"`, platform), - fmt.Sprintf(`target_environment="%s"`, environment), - `ios_deployment_target="15.0"`, - "enable_built_in_dns=true", - // Disable PartitionAlloc for iOS to avoid crashes when embedded in other apps - // PartitionAlloc initialization fails when the host app has its own allocator - "ios_partition_alloc_enabled=false", - ) - } - - if runtime.GOOS == "windows" { - sccachePath, _ := exec.LookPath("sccache") - if sccachePath != "" { - args = append(args, fmt.Sprintf(`cc_wrapper="%s"`, sccachePath)) - } - } else { - ccachePath, _ := exec.LookPath("ccache") - if ccachePath != "" { - args = append(args, fmt.Sprintf(`cc_wrapper="%s"`, ccachePath)) - } - } - - gnArgs := strings.Join(args, " ") - - gnPath := filepath.Join(srcRoot, "gn", "out", "gn") - if runtime.GOOS == "windows" { - gnPath += ".exe" - } - - log.Printf("Running: gn gen %s", outputDirectory) - gnCommand := exec.Command(gnPath, "gen", outputDirectory, "--args="+gnArgs) - gnCommand.Dir = srcRoot - gnCommand.Stdout = os.Stdout - gnCommand.Stderr = os.Stderr - // On Windows, use system Visual Studio instead of depot_tools - if runtime.GOOS == "windows" { - gnCommand.Env = append(os.Environ(), "DEPOT_TOOLS_WIN_TOOLCHAIN=0") - } - err := gnCommand.Run() - if err != nil { - log.Fatalf("gn gen failed: %v", err) - } - - if t.GOOS == "windows" { - // Windows: only build DLL (static linking not supported - Chromium uses MSVC, Go CGO only supports MinGW) - log.Printf("Running: ninja -C %s cronet", outputDirectory) - runCommand(srcRoot, "ninja", "-C", outputDirectory, "cronet") - } else { - log.Printf("Running: ninja -C %s cronet_static", outputDirectory) - runCommand(srcRoot, "ninja", "-C", outputDirectory, "cronet_static") - - // For Linux glibc, also build shared library for purego mode and release distribution - if t.GOOS == "linux" && t.Libc != "musl" { - log.Printf("Running: ninja -C %s cronet", outputDirectory) - runCommand(srcRoot, "ninja", "-C", outputDirectory, "cronet") - } - } -} diff --git a/cmd/build-naive/cmd_download_toolchain.go b/cmd/build-naive/cmd_download_toolchain.go deleted file mode 100644 index fb4a2e6f..00000000 --- a/cmd/build-naive/cmd_download_toolchain.go +++ /dev/null @@ -1,35 +0,0 @@ -package main - -import ( - "log" - - "github.com/spf13/cobra" -) - -var commandDownloadToolchain = &cobra.Command{ - Use: "download-toolchain", - Short: "Download clang and sysroot without building", - Run: func(cmd *cobra.Command, args []string) { - targets := parseTargets() - downloadToolchain(targets) - }, -} - -func init() { - mainCommand.AddCommand(commandDownloadToolchain) -} - -func downloadToolchain(targets []Target) { - log.Printf("Downloading toolchain for %d target(s)", len(targets)) - - for _, t := range targets { - if t.Libc == "musl" { - log.Printf("Downloading toolchain for %s/%s (musl)...", t.GOOS, t.ARCH) - } else { - log.Printf("Downloading toolchain for %s/%s...", t.GOOS, t.ARCH) - } - runGetClang(t) - } - - log.Print("Toolchain download complete!") -} diff --git a/cmd/build-naive/cmd_env.go b/cmd/build-naive/cmd_env.go deleted file mode 100644 index ccd85654..00000000 --- a/cmd/build-naive/cmd_env.go +++ /dev/null @@ -1,136 +0,0 @@ -package main - -import ( - "fmt" - "log" - "path/filepath" - "strings" - - "github.com/spf13/cobra" -) - -var ( - commandEnv = &cobra.Command{ - Use: "env", - Short: "Output environment variables for building downstream projects", - Run: func(cmd *cobra.Command, args []string) { - targets := parseTargets() - if len(targets) != 1 { - log.Fatal("env requires exactly one target") - } - printEnv(targets[0]) - }, - } - envExport bool -) - -func init() { - mainCommand.AddCommand(commandEnv) - commandEnv.Flags().BoolVar(&envExport, "export", false, "Prefix output with 'export' for use with eval") -} - -func getClangTarget(t Target) string { - if t.Libc == "musl" { - switch t.CPU { - case "x64": - return "x86_64-openwrt-linux-musl" - case "arm64": - return "aarch64-openwrt-linux-musl" - case "x86": - return "i486-openwrt-linux-musl" - case "arm": - return "arm-openwrt-linux-musleabi" - case "loong64": - return "loongarch64-openwrt-linux-musl" - case "mipsel": - return "mipsel-openwrt-linux-musl" - case "riscv64": - return "riscv64-openwrt-linux-musl" - } - } - switch t.CPU { - case "x64": - return "x86_64-linux-gnu" - case "arm64": - return "aarch64-linux-gnu" - case "x86": - return "i686-linux-gnu" - case "arm": - return "arm-linux-gnueabihf" - case "loong64": - return "loongarch64-linux-gnu" - case "mipsel": - return "mipsel-linux-gnu" - case "mips64el": - return "mips64el-linux-gnuabi64" - case "riscv64": - return "riscv64-linux-gnu" - } - return "" -} - -func getSysrootPath(t Target) string { - if t.Libc == "musl" { - config := getOpenwrtConfig(t) - return filepath.Join(srcRoot, "out/sysroot-build/openwrt", config.release, config.arch) - } - sysrootInfo := map[string]struct{ arch, release string }{ - "x64": {"amd64", "bullseye"}, - "arm64": {"arm64", "bullseye"}, - "x86": {"i386", "bullseye"}, - "arm": {"armhf", "bullseye"}, - "loong64": {"loong64", "sid"}, - "mipsel": {"mipsel", "bullseye"}, - "mips64el": {"mips64el", "bullseye"}, - "riscv64": {"riscv64", "trixie"}, - }[t.CPU] - return filepath.Join(srcRoot, "out/sysroot-build", sysrootInfo.release, sysrootInfo.release+"_"+sysrootInfo.arch+"_staging") -} - -func printEnv(t Target) { - if t.GOOS == "windows" { - log.Fatal("env command is not supported for Windows (use purego mode with embedded DLL)") - } - - prefix := "" - if envExport { - prefix = "export " - } - - // CGO_LDFLAGS: Only output toolchain flags that cannot be in #cgo LDFLAGS. - // Library paths and system libs are in the generated lib_*_cgo.go files. - if t.GOOS == "linux" { - var ldFlags []string - ldFlags = append(ldFlags, "-fuse-ld=lld") - if t.ARCH == "386" || t.ARCH == "arm" || t.ARCH == "loong64" || t.ARCH == "mipsle" || t.ARCH == "mips64le" { - ldFlags = append(ldFlags, "-Wl,-no-pie") - } - // Bullseye glibc CRT for MIPS marks GNU_STACK as executable; lld rejects by default - if (t.ARCH == "mipsle" || t.ARCH == "mips64le") && t.Libc != "musl" { - ldFlags = append(ldFlags, "-Wl,-z,execstack") - } - fmt.Printf("%sCGO_LDFLAGS=%s\n", prefix, shellQuote(strings.Join(ldFlags, " "), envExport)) - } - // Darwin/iOS: No CGO_LDFLAGS needed, all flags are in the generated cgo files - - // Linux-specific: CC, CXX for cross-compilation, QEMU_LD_PREFIX for running binaries - if t.GOOS == "linux" { - clangPath := filepath.Join(srcRoot, "third_party/llvm-build/Release+Asserts/bin/clang") - clangTarget := getClangTarget(t) - sysroot := getSysrootPath(t) - - cc := fmt.Sprintf("%s --target=%s --sysroot=%s", clangPath, clangTarget, sysroot) - cxx := fmt.Sprintf("%s++ --target=%s --sysroot=%s", clangPath, clangTarget, sysroot) - - fmt.Printf("%sCC=%s\n", prefix, shellQuote(cc, envExport)) - fmt.Printf("%sCXX=%s\n", prefix, shellQuote(cxx, envExport)) - fmt.Printf("%sQEMU_LD_PREFIX=%s\n", prefix, sysroot) - } -} - -func shellQuote(s string, quote bool) string { - if quote && strings.ContainsAny(s, " \t\n\"'\\$") { - return "\"" + strings.ReplaceAll(s, "\"", "\\\"") + "\"" - } - return s -} diff --git a/cmd/build-naive/cmd_package.go b/cmd/build-naive/cmd_package.go deleted file mode 100644 index 88ca2ffa..00000000 --- a/cmd/build-naive/cmd_package.go +++ /dev/null @@ -1,236 +0,0 @@ -package main - -import ( - "bufio" - "fmt" - "log" - "os" - "path/filepath" - "regexp" - "strings" - - "github.com/spf13/cobra" -) - -var commandPackage = &cobra.Command{ - Use: "package", - Short: "Copy built libraries and C headers into lib/ and include/", - Run: func(cmd *cobra.Command, args []string) { - targets := parseTargets() - packageTargets(targets) - }, -} - -func init() { - mainCommand.AddCommand(commandPackage) -} - -func packageTargets(targets []Target) { - log.Printf("Packaging libraries for %d target(s)", len(targets)) - - libraryDirectory := filepath.Join(projectRoot, "lib") - includeDirectory := filepath.Join(projectRoot, "include") - - os.RemoveAll(libraryDirectory) - os.RemoveAll(includeDirectory) - os.MkdirAll(includeDirectory, 0o755) - - headers := []struct { - source string - destination string - }{ - {filepath.Join(srcRoot, "components/cronet/native/include/cronet_c.h"), "cronet_c.h"}, - {filepath.Join(srcRoot, "components/cronet/native/include/cronet_export.h"), "cronet_export.h"}, - {filepath.Join(srcRoot, "components/cronet/native/generated/cronet.idl_c.h"), "cronet.idl_c.h"}, - {filepath.Join(srcRoot, "components/grpc_support/include/bidirectional_stream_c.h"), "bidirectional_stream_c.h"}, - } - - for _, header := range headers { - copyFile(header.source, filepath.Join(includeDirectory, header.destination)) - } - log.Print("Copied headers to include/") - - for _, t := range targets { - directoryName := getLibraryDirectoryName(t) - targetDirectory := filepath.Join(libraryDirectory, directoryName) - os.MkdirAll(targetDirectory, 0o755) - - outputDirectory := getOutputDirectory(t) - - if t.GOOS == "windows" { - // Windows: only a DLL is produced (static linking not supported - - // Chromium uses MSVC, no MinGW static lib). - sourceDLL := filepath.Join(srcRoot, outputDirectory, "cronet.dll") - destinationDLL := filepath.Join(targetDirectory, "libcronet.dll") - if _, err := os.Stat(sourceDLL); os.IsNotExist(err) { - log.Printf("Warning: DLL not found for %s/%s, skipping", t.GOOS, t.ARCH) - } else { - copyFile(sourceDLL, destinationDLL) - log.Printf("Copied DLL for %s/%s", t.GOOS, t.ARCH) - } - log.Printf("Packaged lib/%s", directoryName) - continue - } - - sourceStatic := filepath.Join(srcRoot, outputDirectory, "obj/components/cronet/libcronet_static.a") - destinationStatic := filepath.Join(targetDirectory, "libcronet.a") - if _, err := os.Stat(sourceStatic); os.IsNotExist(err) { - log.Printf("Warning: static library not found for %s, skipping", formatTargetLog(t)) - } else { - copyFile(sourceStatic, destinationStatic) - log.Printf("Copied static library for %s", formatTargetLog(t)) - } - - // For Linux glibc, also copy the shared library. - if t.GOOS == "linux" && t.Libc != "musl" { - sourceShared := filepath.Join(srcRoot, outputDirectory, "libcronet.so") - destinationShared := filepath.Join(targetDirectory, "libcronet.so") - if _, err := os.Stat(sourceShared); err == nil { - copyFile(sourceShared, destinationShared) - log.Printf("Copied shared library for %s", formatTargetLog(t)) - } - } - - // Dump the system link flags so the static library can actually be - // linked by a downstream C/C++ project. - writeLinkFlags(t, targetDirectory, outputDirectory) - - log.Printf("Packaged lib/%s", directoryName) - } - - log.Print("Package complete!") -} - -// writeLinkFlags extracts the libs/frameworks/ldflags that the static library -// depends on (parsed from the generated ninja file) and writes them to a plain -// text file next to the library. -func writeLinkFlags(t Target, targetDirectory, outputDirectory string) { - flags, err := extractLinkFlags(outputDirectory) - if err != nil { - log.Printf("Warning: could not extract link flags for %s: %v", formatTargetLog(t), err) - return - } - - var b strings.Builder - if len(flags.LDFlags) > 0 { - fmt.Fprintf(&b, "# ldflags\n%s\n", strings.Join(flags.LDFlags, " ")) - } - if len(flags.Libs) > 0 { - fmt.Fprintf(&b, "# libs\n%s\n", strings.Join(flags.Libs, " ")) - } - if len(flags.Frameworks) > 0 { - fmt.Fprintf(&b, "# frameworks\n%s\n", strings.Join(flags.Frameworks, " ")) - } - if t.GOOS == "linux" && t.Libc == "musl" { - b.WriteString("# extra\n-static\n") - } - if b.Len() == 0 { - return - } - - path := filepath.Join(targetDirectory, "link_flags.txt") - if err := os.WriteFile(path, []byte(b.String()), 0o644); err != nil { - log.Printf("Warning: failed to write link_flags.txt for %s: %v", formatTargetLog(t), err) - return - } - log.Printf("Wrote link flags for %s", formatTargetLog(t)) -} - -func getLibraryDirectoryName(t Target) string { - osName := t.GOOS - if t.Platform == "tvos" { - osName = "tvos" - } - - name := fmt.Sprintf("%s_%s", osName, t.ARCH) - - if t.Environment == "simulator" { - name += "_simulator" - } - - if t.Libc == "musl" { - name += "_musl" - } - - return name -} - -type LinkFlags struct { - Libs []string - Frameworks []string - LDFlags []string -} - -func extractLinkFlags(outputDirectory string) (LinkFlags, error) { - ninjaPath := filepath.Join(srcRoot, outputDirectory, "obj/components/cronet/cronet_sample.ninja") - file, err := os.Open(ninjaPath) - if err != nil { - return LinkFlags{}, fmt.Errorf("failed to open ninja file %s: %w", ninjaPath, err) - } - defer file.Close() - - var flags LinkFlags - libsRegex := regexp.MustCompile(`^\s*libs\s*=\s*(.*)$`) - frameworksRegex := regexp.MustCompile(`^\s*frameworks\s*=\s*(.*)$`) - ldflagsRegex := regexp.MustCompile(`^\s*ldflags\s*=\s*(.*)$`) - - scanner := bufio.NewScanner(file) - for scanner.Scan() { - line := scanner.Text() - - if matches := libsRegex.FindStringSubmatch(line); matches != nil { - libsStr := strings.TrimSpace(matches[1]) - if libsStr != "" { - for _, lib := range strings.Fields(libsStr) { - // Filter out linker scripts (.lds) - not needed for static linking. - if !strings.HasSuffix(lib, ".lds") { - flags.Libs = append(flags.Libs, lib) - } - } - } - } - - if matches := frameworksRegex.FindStringSubmatch(line); matches != nil { - frameworksStr := strings.TrimSpace(matches[1]) - if frameworksStr != "" { - flags.Frameworks = parseFrameworks(frameworksStr) - } - } - - if matches := ldflagsRegex.FindStringSubmatch(line); matches != nil { - ldflagsStr := strings.TrimSpace(matches[1]) - if ldflagsStr != "" { - flags.LDFlags = parseLDFlags(ldflagsStr) - } - } - } - - if err := scanner.Err(); err != nil { - return LinkFlags{}, fmt.Errorf("failed to read ninja file: %w", err) - } - - return flags, nil -} - -func parseFrameworks(input string) []string { - var result []string - parts := strings.Fields(input) - for i := 0; i < len(parts); i++ { - if parts[i] == "-framework" && i+1 < len(parts) { - result = append(result, "-framework "+parts[i+1]) - i++ - } - } - return result -} - -func parseLDFlags(input string) []string { - var result []string - for _, flag := range strings.Fields(input) { - // Extract -Wl,-wrap,* flags needed for Android allocator shim. - if strings.HasPrefix(flag, "-Wl,-wrap,") { - result = append(result, flag) - } - } - return result -} diff --git a/cmd/build-naive/main.go b/cmd/build-naive/main.go deleted file mode 100644 index 5124029f..00000000 --- a/cmd/build-naive/main.go +++ /dev/null @@ -1,10 +0,0 @@ -package main - -import "log" - -func main() { - err := mainCommand.Execute() - if err != nil { - log.Fatal(err) - } -} diff --git a/go.mod b/go.mod deleted file mode 100644 index 821e1861..00000000 --- a/go.mod +++ /dev/null @@ -1,10 +0,0 @@ -module github.com/sagernet/cronet-go - -go 1.20 - -require github.com/spf13/cobra v1.4.0 - -require ( - github.com/inconshreveable/mousetrap v1.0.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect -) diff --git a/go.sum b/go.sum deleted file mode 100644 index 1f05a8c6..00000000 --- a/go.sum +++ /dev/null @@ -1,55 +0,0 @@ -github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= -github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= -github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagernet/sing v0.7.13 h1:XNYgd8e3cxMULs/LLJspdn/deHrnPWyrrglNHeCUAYM= -github.com/sagernet/sing v0.7.13/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= -github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 h1:BonxutuHCTL0rBDnZlKjpGIQFTjyUVTexFOdWkB6Fg0= -golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -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-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/justfile b/justfile new file mode 100644 index 00000000..40d8c5d0 --- /dev/null +++ b/justfile @@ -0,0 +1,51 @@ +# Build orchestration for the cronet libraries. +# +# Recipes drive scripts/build-naive.sh (a bash port of the old Go tool). +# No Go toolchain is required — only bash, plus the usual Chromium build +# deps (gn/ninja, a C toolchain, etc.). +# +# Usage: +# just # list recipes +# just build # host target (compile + package) +# just build linux/arm64 # cross-compile +# just build linux/amd64 musl +# just apple # all Apple platforms + +set shell := ["bash", "-c"] + +script := "scripts/build-naive.sh" + +# List available recipes. +default: + @just --list + +# Compile + package cronet libs for TARGET (e.g. linux/amd64; empty = host). +# Optional LIBC=musl for static musl Linux builds. +build target="" libc="": + bash {{script}} build --target "{{target}}" --libc "{{libc}}" + bash {{script}} package --target "{{target}}" --libc "{{libc}}" + +# Only run gn gen + ninja (the expensive, cacheable step) for TARGET. +compile target="" libc="": + bash {{script}} build --target "{{target}}" --libc "{{libc}}" + +# Copy built libs/headers into lib/ + include/ and dump link flags for TARGET. +package target="" libc="": + bash {{script}} package --target "{{target}}" --libc "{{libc}}" + +# Build all Apple platforms (macOS, iOS, tvOS, and simulators). +apple: + bash {{script}} build --target "ios/arm64,ios/arm64/simulator,ios/amd64/simulator,tvos/arm64,tvos/arm64/simulator,tvos/amd64/simulator,darwin/arm64,darwin/amd64" + bash {{script}} package --target "ios/arm64,ios/arm64/simulator,ios/amd64/simulator,tvos/arm64,tvos/arm64/simulator,tvos/amd64/simulator,darwin/arm64,darwin/amd64" + +# Download clang + sysroot for TARGET without building. +download-toolchain target="" libc="": + bash {{script}} download-toolchain --target "{{target}}" --libc "{{libc}}" + +# Print CC/CXX/CGO_LDFLAGS env for cross-compiling consumers of TARGET. +env target="" libc="": + bash {{script}} env --target "{{target}}" --libc "{{libc}}" + +# Same as `env`, prefixed with `export ` for use with eval. +env-export target="" libc="": + bash {{script}} env --target "{{target}}" --libc "{{libc}}" --export diff --git a/scripts/build-naive.sh b/scripts/build-naive.sh new file mode 100755 index 00000000..53c0fa7f --- /dev/null +++ b/scripts/build-naive.sh @@ -0,0 +1,613 @@ +#!/usr/bin/env bash +# +# build-naive.sh — build orchestration for the cronet libraries. +# +# A faithful bash port of the former Go tool cmd/build-naive. It drives +# naiveproxy's get-clang.sh, then `gn gen` + `ninja`, and packages the +# resulting .a/.so/.dll into lib/ + include/. +# +# Subcommands: +# build gn gen + ninja -> libcronet.{a,so,dll} +# package copy libs/headers into lib/ + include/, dump link flags +# download-toolchain download clang + sysroot without building +# env print CC/CXX/CGO_LDFLAGS for cross-compiling consumers +# +# Flags: --target comma separated (e.g. linux/amd64,darwin/arm64); +# empty = host, "all" = every supported target +# --libc musl => static OpenWrt/musl Linux build +# --export (env only) prefix output with `export ` +# +# Written for bash 3.2+ (macOS ships 3.2); avoid set -u (empty-array hazard). + +set -eo pipefail + +LIB_SELF="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$LIB_SELF")" +NAIVE_ROOT="$PROJECT_ROOT/naiveproxy" +SRC_ROOT="$NAIVE_ROOT/src" + +log() { echo "[build] $*" >&2; } +die() { echo "[build] ERROR: $*" >&2; exit 1; } + +ALL_SPECS="linux/amd64 linux/arm64 linux/386 linux/arm linux/loong64 linux/mipsle linux/mips64le linux/riscv64 darwin/amd64 darwin/arm64 windows/amd64 windows/arm64 ios/arm64 ios/arm64/simulator ios/amd64 tvos/arm64 tvos/arm64/simulator tvos/amd64 android/arm64 android/amd64 android/arm android/386" + +# --------------------------------------------------------------------------- +# Host detection +# --------------------------------------------------------------------------- + +host_goos() { + case "$(uname -s)" in + Linux) echo linux ;; + Darwin) echo darwin ;; + MINGW*|MSYS*|CYGWIN*) echo windows ;; + *) echo unknown ;; + esac +} + +host_goarch() { + case "$(uname -m)" in + x86_64|amd64) echo amd64 ;; + aarch64|arm64) echo arm64 ;; + i686|i386) echo 386 ;; + armv7l|armv6l|arm) echo arm ;; + loongarch64) echo loong64 ;; + mips64el) echo mips64le ;; + mipsel) echo mipsle ;; + riscv64) echo riscv64 ;; + *) echo unknown ;; + esac +} + +arch_to_cpu() { + case "$1" in + amd64) echo x64 ;; + arm64) echo arm64 ;; + 386) echo x86 ;; + arm) echo arm ;; + loong64) echo loong64 ;; + mipsle) echo mipsel ;; + mips64le) echo mips64el ;; + *) echo "$1" ;; + esac +} + +# --------------------------------------------------------------------------- +# Target resolution -> sets T_OS T_CPU T_GOOS T_ARCH T_LIBC T_PLATFORM T_ENV +# --------------------------------------------------------------------------- + +resolve_target() { + local spec="$1" libc="$2" + T_OS=""; T_CPU=""; T_GOOS=""; T_ARCH=""; T_LIBC=""; T_PLATFORM=""; T_ENV="" + + if [ -z "$spec" ]; then + spec="$(host_goos)/$(host_goarch)" + fi + + local goos goarch variant + goos="${spec%%/*}" + local rest="${spec#*/}" + goarch="${rest%%/*}" + if [ "$rest" != "$goarch" ]; then + variant="${rest#*/}" + else + variant="" + fi + + case "$goos" in + ios|tvos) + resolve_apple "$goos" "$goarch" "$variant" + return + ;; + linux) T_OS=linux ;; + darwin) T_OS=mac ;; + windows) T_OS=win ;; + android) T_OS=android ;; + *) die "unsupported target: $spec" ;; + esac + + T_GOOS="$goos" + T_ARCH="$goarch" + T_CPU="$(arch_to_cpu "$goarch")" + + if [ -n "$libc" ] && [ "$libc" != glibc ]; then + [ "$libc" = musl ] || die "invalid libc: $libc (expected glibc or musl)" + [ "$goos" = linux ] || die "--libc=musl is only supported for Linux targets, not $goos" + T_OS=openwrt + T_LIBC=musl + fi +} + +resolve_apple() { + local goos="$1" goarch="$2" variant="$3" + T_GOOS=ios + T_OS=ios + T_ARCH="$goarch" + T_CPU="$(arch_to_cpu "$goarch")" + T_LIBC="" + if [ "$goos" = tvos ]; then T_PLATFORM=tvos; else T_PLATFORM=iphoneos; fi + if [ "$variant" = simulator ]; then + T_ENV=simulator + elif [ "$goarch" = amd64 ]; then + T_ENV=simulator + else + T_ENV=device + fi +} + +format_target() { + if [ -n "$T_PLATFORM" ]; then + local pn=iOS + [ "$T_PLATFORM" = tvos ] && pn=tvOS + if [ "$T_ENV" = simulator ]; then echo "$pn Simulator $T_ARCH"; else echo "$pn $T_ARCH"; fi + elif [ "$T_LIBC" = musl ]; then + echo "$T_GOOS/$T_ARCH (musl)" + else + echo "$T_GOOS/$T_ARCH" + fi +} + +output_dir() { + if [ -n "$T_PLATFORM" ]; then + local d="out/cronet-$T_PLATFORM-$T_CPU" + [ "$T_ENV" = simulator ] && d="$d-simulator" + echo "$d" + else + echo "out/cronet-$T_OS-$T_CPU" + fi +} + +lib_dir_name() { + local osn="$T_GOOS" + [ "$T_PLATFORM" = tvos ] && osn=tvos + local n="${osn}_${T_ARCH}" + [ "$T_ENV" = simulator ] && n="${n}_simulator" + [ "$T_LIBC" = musl ] && n="${n}_musl" + echo "$n" +} + +# --------------------------------------------------------------------------- +# OpenWrt / musl config -> sets OW_TARGET OW_SUBTARGET OW_ARCH OW_RELEASE +# OW_GCC OW_EXTRA[] +# --------------------------------------------------------------------------- + +openwrt_config() { + OW_EXTRA=() + case "$T_CPU" in + x64) OW_TARGET=x86; OW_SUBTARGET=64; OW_ARCH=x86_64; OW_RELEASE=23.05.5; OW_GCC=12.3.0 ;; + arm64) OW_TARGET=armsr; OW_SUBTARGET=armv8; OW_ARCH=aarch64; OW_RELEASE=23.05.5; OW_GCC=12.3.0 ;; + x86) OW_TARGET=x86; OW_SUBTARGET=generic; OW_ARCH=i386_pentium4; OW_RELEASE=23.05.5; OW_GCC=12.3.0 ;; + arm) OW_TARGET=armsr; OW_SUBTARGET=armv7; OW_ARCH="arm_cortex-a15_neon-vfpv4"; OW_RELEASE=23.05.5; OW_GCC=12.3.0 ;; + loong64) OW_TARGET=loongarch64; OW_SUBTARGET=generic; OW_ARCH=loongarch64; OW_RELEASE=24.10.5; OW_GCC=13.3.0 ;; + mipsel) OW_TARGET=ramips; OW_SUBTARGET=rt305x; OW_ARCH=mipsel_24kc; OW_RELEASE=23.05.5; OW_GCC=12.3.0; OW_EXTRA=('mips_float_abi="soft"' 'mips_arch_variant="r2"') ;; + riscv64) OW_TARGET=sifiveu; OW_SUBTARGET=generic; OW_ARCH=riscv64; OW_RELEASE=23.05.5; OW_GCC=12.3.0 ;; + *) die "unsupported CPU for musl: $T_CPU" ;; + esac +} + +# Debian sysroot path for a glibc CPU (ignores libc). +glibc_sysroot_path() { + local cpu="$1" arch rel + case "$cpu" in + x64) arch=amd64; rel=bullseye ;; + arm64) arch=arm64; rel=bullseye ;; + x86) arch=i386; rel=bullseye ;; + arm) arch=armhf; rel=bullseye ;; + loong64) arch=loong64; rel=sid ;; + mipsel) arch=mipsel; rel=bullseye ;; + mips64el) arch=mips64el; rel=bullseye ;; + riscv64) arch=riscv64; rel=trixie ;; + *) die "no sysroot for cpu: $cpu" ;; + esac + echo "$SRC_ROOT/out/sysroot-build/$rel/${rel}_${arch}_staging" +} + +sysroot_path() { + if [ "$T_LIBC" = musl ]; then + openwrt_config + echo "$SRC_ROOT/out/sysroot-build/openwrt/$OW_RELEASE/$OW_ARCH" + else + glibc_sysroot_path "$T_CPU" + fi +} + +clang_target() { + if [ "$T_LIBC" = musl ]; then + case "$T_CPU" in + x64) echo x86_64-openwrt-linux-musl ;; + arm64) echo aarch64-openwrt-linux-musl ;; + x86) echo i486-openwrt-linux-musl ;; + arm) echo arm-openwrt-linux-musleabi ;; + loong64) echo loongarch64-openwrt-linux-musl ;; + mipsel) echo mipsel-openwrt-linux-musl ;; + riscv64) echo riscv64-openwrt-linux-musl ;; + esac + return + fi + case "$T_CPU" in + x64) echo x86_64-linux-gnu ;; + arm64) echo aarch64-linux-gnu ;; + x86) echo i686-linux-gnu ;; + arm) echo arm-linux-gnueabihf ;; + loong64) echo loongarch64-linux-gnu ;; + mipsel) echo mipsel-linux-gnu ;; + mips64el) echo mips64el-linux-gnuabi64 ;; + riscv64) echo riscv64-linux-gnu ;; + esac +} + +# --------------------------------------------------------------------------- +# Toolchain +# --------------------------------------------------------------------------- + +run_get_clang() { + local hgoos; hgoos="$(host_goos)" + + if [ "$hgoos" = linux ] && { [ "$T_OS" = linux ] || [ "$T_OS" = android ] || [ "$T_OS" = openwrt ]; }; then + local hcpu; hcpu="$(arch_to_cpu "$(host_goarch)")" + local host_flags="target_os=\"linux\" target_cpu=\"$hcpu\"" + log "get-clang.sh (host sysroot) EXTRA_FLAGS=$host_flags" + ( cd "$SRC_ROOT" && EXTRA_FLAGS="$host_flags" bash ./get-clang.sh ) + + local host_src host_dst + host_src="$(glibc_sysroot_path "$hcpu")" + host_dst="$SRC_ROOT/build/linux/debian_bullseye_amd64-sysroot" + if [ ! -e "$host_dst" ]; then + log "symlink host sysroot $host_dst -> $host_src" + ln -s "$host_src" "$host_dst" + fi + fi + + local extra="target_os=\"$T_OS\" target_cpu=\"$T_CPU\"" + if [ "$T_OS" = openwrt ]; then + openwrt_config + local ow="target=\"$OW_TARGET\" subtarget=\"$OW_SUBTARGET\" arch=\"$OW_ARCH\" release=\"$OW_RELEASE\" gcc_ver=\"$OW_GCC\"" + log "get-clang.sh EXTRA_FLAGS=$extra OPENWRT_FLAGS=$ow" + ( cd "$SRC_ROOT" && EXTRA_FLAGS="$extra" OPENWRT_FLAGS="$ow" bash ./get-clang.sh ) + else + log "get-clang.sh EXTRA_FLAGS=$extra" + ( cd "$SRC_ROOT" && EXTRA_FLAGS="$extra" bash ./get-clang.sh ) + fi +} + +# --------------------------------------------------------------------------- +# Build (gn gen + ninja) +# --------------------------------------------------------------------------- + +compile_target() { + run_get_clang + + local outdir; outdir="$(output_dir)" + local hgoos; hgoos="$(host_goos)" + + local args + args=( + is_official_build=true + is_debug=false + is_clang=true + use_clang_modules=false + use_thin_lto=false + fatal_linker_warnings=false + treat_warnings_as_errors=false + is_cronet_build=true + use_udev=false + use_aura=false + use_ozone=false + use_gio=false + use_glib=false + use_kerberos=false + disable_zstd_filter=false + enable_reporting=false + enable_bracketed_proxy_uris=true + enable_quic_proxy_support=true + use_nss_certs=false + enable_backup_ref_ptr_support=false + enable_dangling_raw_ptr_checks=false + exclude_unwind_tables=true + enable_resource_allowlist_generation=false + symbol_level=0 + enable_dsyms=false + optimize_for_size=true + "target_os=\"$T_OS\"" + "target_cpu=\"$T_CPU\"" + ) + + case "$T_OS" in + mac) + args+=(use_sysroot=false) + ;; + linux) + local sp rel + sp="$(sysroot_path)" + rel="${sp#"$SRC_ROOT"/}" + args+=(use_sysroot=true "target_sysroot=\"//$rel\"") + [ "$T_CPU" = x64 ] && args+=(use_cfi_icall=false is_cfi=false) + ;; + openwrt) + openwrt_config + local sd="out/sysroot-build/openwrt/$OW_RELEASE/$OW_ARCH" + args+=(use_sysroot=true "target_sysroot=\"//$sd\"" build_static=true use_allocator_shim=false use_partition_alloc=false) + [ "${#OW_EXTRA[@]}" -gt 0 ] && args+=("${OW_EXTRA[@]}") + [ "$T_CPU" = x64 ] && args+=(use_cfi_icall=false is_cfi=false) + ;; + win) + args+=(use_sysroot=false) + ;; + android) + args+=(use_sysroot=false default_min_sdk_version=23) + ;; + ios) + local plat="$T_PLATFORM" envv="$T_ENV" + [ -z "$plat" ] && plat=iphoneos + [ -z "$envv" ] && envv=device + args+=( + use_sysroot=false + ios_enable_code_signing=false + "target_platform=\"$plat\"" + "target_environment=\"$envv\"" + 'ios_deployment_target="15.0"' + enable_built_in_dns=true + ios_partition_alloc_enabled=false + ) + ;; + esac + + if [ "$hgoos" = windows ]; then + if command -v sccache >/dev/null 2>&1; then + args+=("cc_wrapper=\"$(command -v sccache)\"") + fi + else + if command -v ccache >/dev/null 2>&1; then + args+=("cc_wrapper=\"$(command -v ccache)\"") + fi + fi + + local gn_args="${args[*]}" + local gn="$SRC_ROOT/gn/out/gn" + [ "$hgoos" = windows ] && gn="$gn.exe" + + log "gn gen $outdir" + if [ "$hgoos" = windows ]; then + ( cd "$SRC_ROOT" && DEPOT_TOOLS_WIN_TOOLCHAIN=0 "$gn" gen "$outdir" "--args=$gn_args" ) + else + ( cd "$SRC_ROOT" && "$gn" gen "$outdir" "--args=$gn_args" ) + fi + + if [ "$T_GOOS" = windows ]; then + log "ninja -C $outdir cronet" + ( cd "$SRC_ROOT" && ninja -C "$outdir" cronet ) + else + log "ninja -C $outdir cronet_static" + ( cd "$SRC_ROOT" && ninja -C "$outdir" cronet_static ) + if [ "$T_GOOS" = linux ] && [ "$T_LIBC" != musl ]; then + log "ninja -C $outdir cronet" + ( cd "$SRC_ROOT" && ninja -C "$outdir" cronet ) + fi + fi +} + +# --------------------------------------------------------------------------- +# Package (copy libs/headers + link flags) +# --------------------------------------------------------------------------- + +copy_headers() { + local inc="$PROJECT_ROOT/include" + mkdir -p "$inc" + cp "$SRC_ROOT/components/cronet/native/include/cronet_c.h" "$inc/cronet_c.h" + cp "$SRC_ROOT/components/cronet/native/include/cronet_export.h" "$inc/cronet_export.h" + cp "$SRC_ROOT/components/cronet/native/generated/cronet.idl_c.h" "$inc/cronet.idl_c.h" + cp "$SRC_ROOT/components/grpc_support/include/bidirectional_stream_c.h" "$inc/bidirectional_stream_c.h" + log "Copied headers to include/" +} + +# Parse the generated cronet_sample.ninja for the system link flags that the +# static library depends on, and write them to link_flags.txt. +write_link_flags() { + local td="$1" outdir="$2" + local ninja="$SRC_ROOT/$outdir/obj/components/cronet/cronet_sample.ninja" + if [ ! -f "$ninja" ]; then + log "Warning: ninja file missing for $(format_target): $ninja" + return + fi + + local libs frameworks ldflags + libs="$(grep -E '^[[:space:]]*libs[[:space:]]*=' "$ninja" | sed -E 's/^[[:space:]]*libs[[:space:]]*=[[:space:]]*//' | tr '\n' ' ')" + frameworks="$(grep -E '^[[:space:]]*frameworks[[:space:]]*=' "$ninja" | tail -1 | sed -E 's/^[[:space:]]*frameworks[[:space:]]*=[[:space:]]*//')" + ldflags="$(grep -E '^[[:space:]]*ldflags[[:space:]]*=' "$ninja" | tail -1 | sed -E 's/^[[:space:]]*ldflags[[:space:]]*=[[:space:]]*//')" + + local out="" tok + local keep="" + for tok in $ldflags; do + case "$tok" in -Wl,-wrap,*) keep="$keep $tok" ;; esac + done + keep="${keep# }" + [ -n "$keep" ] && out="${out}# ldflags\n${keep}\n" + + keep="" + for tok in $libs; do + case "$tok" in *.lds) : ;; *) keep="$keep $tok" ;; esac + done + keep="${keep# }" + [ -n "$keep" ] && out="${out}# libs\n${keep}\n" + + keep="" + set -- $frameworks + while [ $# -gt 0 ]; do + if [ "$1" = "-framework" ] && [ $# -ge 2 ]; then + keep="$keep -framework $2" + shift 2 + else + shift + fi + done + keep="${keep# }" + [ -n "$keep" ] && out="${out}# frameworks\n${keep}\n" + + if [ "$T_GOOS" = linux ] && [ "$T_LIBC" = musl ]; then + out="${out}# extra\n-static\n" + fi + + [ -n "$out" ] || return + printf '%b' "$out" > "$td/link_flags.txt" + log "Wrote link flags for $(format_target)" +} + +package_target() { + local name td outdir + name="$(lib_dir_name)" + td="$PROJECT_ROOT/lib/$name" + mkdir -p "$td" + outdir="$(output_dir)" + + if [ "$T_GOOS" = windows ]; then + local src="$SRC_ROOT/$outdir/cronet.dll" + if [ -f "$src" ]; then + cp "$src" "$td/libcronet.dll" + log "Copied DLL for $T_GOOS/$T_ARCH" + else + log "Warning: DLL not found for $T_GOOS/$T_ARCH, skipping" + fi + log "Packaged lib/$name" + return + fi + + local src_static="$SRC_ROOT/$outdir/obj/components/cronet/libcronet_static.a" + if [ -f "$src_static" ]; then + cp "$src_static" "$td/libcronet.a" + log "Copied static library for $(format_target)" + else + log "Warning: static library not found for $(format_target), skipping" + fi + + if [ "$T_GOOS" = linux ] && [ "$T_LIBC" != musl ]; then + local src_so="$SRC_ROOT/$outdir/libcronet.so" + if [ -f "$src_so" ]; then + cp "$src_so" "$td/libcronet.so" + log "Copied shared library for $(format_target)" + fi + fi + + write_link_flags "$td" "$outdir" + log "Packaged lib/$name" +} + +# --------------------------------------------------------------------------- +# env +# --------------------------------------------------------------------------- + +shell_quote() { + local s="$1" + if [ "$EXPORT" = 1 ] && printf '%s' "$s" | grep -qE '[[:space:]"'\''\\$]'; then + printf '"%s"' "$(printf '%s' "$s" | sed 's/"/\\"/g')" + else + printf '%s' "$s" + fi +} + +print_env() { + [ "$T_GOOS" = windows ] && die "env command is not supported for Windows (use the prebuilt DLL)" + + local prefix="" + [ "$EXPORT" = 1 ] && prefix="export " + + if [ "$T_GOOS" = linux ]; then + local ld="-fuse-ld=lld" + case "$T_ARCH" in + 386|arm|loong64|mipsle|mips64le) ld="$ld -Wl,-no-pie" ;; + esac + if { [ "$T_ARCH" = mipsle ] || [ "$T_ARCH" = mips64le ]; } && [ "$T_LIBC" != musl ]; then + ld="$ld -Wl,-z,execstack" + fi + printf '%sCGO_LDFLAGS=%s\n' "$prefix" "$(shell_quote "$ld")" + + local clang="$SRC_ROOT/third_party/llvm-build/Release+Asserts/bin/clang" + local ct sp + ct="$(clang_target)" + sp="$(sysroot_path)" + printf '%sCC=%s\n' "$prefix" "$(shell_quote "$clang --target=$ct --sysroot=$sp")" + printf '%sCXX=%s\n' "$prefix" "$(shell_quote "$clang++ --target=$ct --sysroot=$sp")" + printf '%sQEMU_LD_PREFIX=%s\n' "$prefix" "$sp" + fi +} + +# --------------------------------------------------------------------------- +# Target list expansion +# --------------------------------------------------------------------------- + +expand_targets() { + local spec="$1" + if [ "$spec" = all ]; then + echo "$ALL_SPECS" + elif [ -z "$spec" ]; then + echo "" # single host target (empty string) + else + echo "$spec" | tr ',' ' ' + fi +} + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- + +main() { + [ $# -ge 1 ] || die "usage: build-naive.sh [--target LIST] [--libc glibc|musl] [--export]" + local cmd="$1"; shift + + local target="" libc="" + EXPORT=0 + while [ $# -gt 0 ]; do + case "$1" in + --target) target="$2"; shift 2 ;; + --target=*) target="${1#--target=}"; shift ;; + -t) target="$2"; shift 2 ;; + --libc) libc="$2"; shift 2 ;; + --libc=*) libc="${1#--libc=}"; shift ;; + --export) EXPORT=1; shift ;; + "") shift ;; + *) die "unknown argument: $1" ;; + esac + done + + case "$cmd" in + build) + local specs; specs="$(expand_targets "$target")" + if [ -z "$target" ]; then + resolve_target "" "$libc"; log "Building $(format_target)..."; compile_target + else + for s in $specs; do resolve_target "$s" "$libc"; log "Building $(format_target)..."; compile_target; done + fi + log "Build complete!" + ;; + package) + copy_headers + local specs; specs="$(expand_targets "$target")" + if [ -z "$target" ]; then + resolve_target "" "$libc"; package_target + else + for s in $specs; do resolve_target "$s" "$libc"; package_target; done + fi + log "Package complete!" + ;; + download-toolchain) + local specs; specs="$(expand_targets "$target")" + if [ -z "$target" ]; then + resolve_target "" "$libc"; log "Downloading toolchain for $(format_target)..."; run_get_clang + else + for s in $specs; do resolve_target "$s" "$libc"; log "Downloading toolchain for $(format_target)..."; run_get_clang; done + fi + log "Toolchain download complete!" + ;; + env) + # env requires exactly one target + case "$target" in + *,*) die "env requires exactly one target" ;; + esac + resolve_target "$target" "$libc" + print_env + ;; + *) + die "unknown command: $cmd" + ;; + esac +} + +main "$@" From cbd86a0dc3c2979494a8029ba7811cbddb62cac2 Mon Sep 17 00:00:00 2001 From: iHsin Date: Tue, 9 Jun 2026 14:52:17 +0800 Subject: [PATCH 3/4] Replace build-naive.sh with a single-file cargo script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Port the bash orchestrator to scripts/build-naive.rs, a std-only cargo script run via `cargo -Zscript`. just remains the entry point. Why: the bash version failed on Windows because spawning gn.exe from MSYS bash path-mangled the gn arguments, and `command -v sccache` returned a POSIX path. The Rust binary is native (windows-msvc) and spawns gn.exe via std::process::Command, so — like the original Go tool — there is no MSYS mangling and cc_wrapper gets a real C:\ path. - scripts/build-naive.rs: faithful port (target resolution, gn args, openwrt/musl + sysroot/clang mappings, get-clang, ninja targets, link-flag extraction, env). Adds a `print-config` debug subcommand. - justfile: recipes call `cargo +nightly -Zscript`; bash shell on all platforms (the cmd.exe shell mangled quotes; bash does not, and never touches the gn args since the script spawns gn natively). - CI: add dtolnay/rust-toolchain@nightly; cache key hashes build-naive.rs; drop the now-unneeded `shell: bash` on the Windows build/package steps. - Remove scripts/build-naive.sh; README updated. Verified locally against the prior behavior: clean compile, and identical gn args / env output / config for linux (incl. mips no-pie+execstack), android, ios/tvos, darwin, windows, and musl targets. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/naive-build.yml | 17 +- README.md | 14 +- justfile | 30 +- scripts/build-naive.rs | 788 ++++++++++++++++++++++++++++++ scripts/build-naive.sh | 613 ----------------------- 5 files changed, 824 insertions(+), 638 deletions(-) create mode 100755 scripts/build-naive.rs delete mode 100755 scripts/build-naive.sh diff --git a/.github/workflows/naive-build.yml b/.github/workflows/naive-build.yml index 5c8550fb..ead92a1a 100644 --- a/.github/workflows/naive-build.yml +++ b/.github/workflows/naive-build.yml @@ -30,6 +30,7 @@ jobs: - uses: taiki-e/install-action@v2 with: tool: just + - uses: dtolnay/rust-toolchain@nightly - name: Get naiveproxy commit id: naive run: echo "commit=$(git -C naiveproxy rev-parse HEAD)" >> $GITHUB_OUTPUT @@ -45,7 +46,7 @@ jobs: uses: actions/cache@v4 with: path: naiveproxy/src/out - key: naive-build-${{ steps.naive.outputs.commit }}-${{ hashFiles('scripts/build-naive.sh') }}-linux-${{ matrix.arch }} + key: naive-build-${{ steps.naive.outputs.commit }}-${{ hashFiles('scripts/build-naive.rs') }}-linux-${{ matrix.arch }} - name: Cache ccache files if: steps.build-cache.outputs.cache-hit != 'true' uses: actions/cache@v4 @@ -107,6 +108,7 @@ jobs: - uses: taiki-e/install-action@v2 with: tool: just + - uses: dtolnay/rust-toolchain@nightly - name: Get naiveproxy commit id: naive run: echo "commit=$(git -C naiveproxy rev-parse HEAD)" >> $GITHUB_OUTPUT @@ -118,7 +120,7 @@ jobs: uses: actions/cache@v4 with: path: naiveproxy/src/out - key: naive-build-${{ steps.naive.outputs.commit }}-${{ hashFiles('scripts/build-naive.sh') }}-${{ matrix.name }} + key: naive-build-${{ steps.naive.outputs.commit }}-${{ hashFiles('scripts/build-naive.rs') }}-${{ matrix.name }} - name: Cache ccache files if: steps.build-cache.outputs.cache-hit != 'true' uses: actions/cache@v4 @@ -162,6 +164,7 @@ jobs: - uses: taiki-e/install-action@v2 with: tool: just + - uses: dtolnay/rust-toolchain@nightly - name: Get naiveproxy commit id: naive shell: bash @@ -175,7 +178,7 @@ jobs: uses: actions/cache@v4 with: path: naiveproxy/src/out - key: naive-build-${{ steps.naive.outputs.commit }}-${{ hashFiles('scripts/build-naive.sh') }}-windows-${{ matrix.arch }} + key: naive-build-${{ steps.naive.outputs.commit }}-${{ hashFiles('scripts/build-naive.rs') }}-windows-${{ matrix.arch }} - name: Install tools if: steps.build-cache.outputs.cache-hit != 'true' run: | @@ -215,13 +218,11 @@ jobs: run: sccache -z - name: Build if: steps.build-cache.outputs.cache-hit != 'true' - shell: bash run: just compile windows/${{ matrix.arch }} - name: Show sccache stats if: steps.build-cache.outputs.cache-hit != 'true' run: sccache -s - name: Package - shell: bash run: just package windows/${{ matrix.arch }} - uses: actions/upload-artifact@v4 with: @@ -243,6 +244,7 @@ jobs: - uses: taiki-e/install-action@v2 with: tool: just + - uses: dtolnay/rust-toolchain@nightly - name: Get naiveproxy commit id: naive run: echo "commit=$(git -C naiveproxy rev-parse HEAD)" >> $GITHUB_OUTPUT @@ -254,7 +256,7 @@ jobs: uses: actions/cache@v4 with: path: naiveproxy/src/out - key: naive-build-${{ steps.naive.outputs.commit }}-${{ hashFiles('scripts/build-naive.sh') }}-android-${{ matrix.arch }} + key: naive-build-${{ steps.naive.outputs.commit }}-${{ hashFiles('scripts/build-naive.rs') }}-android-${{ matrix.arch }} - name: Cache ccache files if: steps.build-cache.outputs.cache-hit != 'true' uses: actions/cache@v4 @@ -298,6 +300,7 @@ jobs: - uses: taiki-e/install-action@v2 with: tool: just + - uses: dtolnay/rust-toolchain@nightly - name: Get naiveproxy commit id: naive run: echo "commit=$(git -C naiveproxy rev-parse HEAD)" >> $GITHUB_OUTPUT @@ -309,7 +312,7 @@ jobs: uses: actions/cache@v4 with: path: naiveproxy/src/out - key: naive-build-${{ steps.naive.outputs.commit }}-${{ hashFiles('scripts/build-naive.sh') }}-linux-musl-${{ matrix.arch }} + key: naive-build-${{ steps.naive.outputs.commit }}-${{ hashFiles('scripts/build-naive.rs') }}-linux-musl-${{ matrix.arch }} - name: Cache ccache files if: steps.build-cache.outputs.cache-hit != 'true' uses: actions/cache@v4 diff --git a/README.md b/README.md index 8dddde9b..2e141ef2 100644 --- a/README.md +++ b/README.md @@ -36,10 +36,14 @@ link the static library (`lib//link_flags.txt`). ## How it works The build is orchestrated by [`just`](https://github.com/casey/just), whose -recipes drive the bash script [`scripts/build-naive.sh`](scripts/build-naive.sh) -(`get-clang.sh` → `gn gen` → `ninja`). **No Go (or any other) toolchain is -required** — only `bash`, `just`, and the usual Chromium build deps -(`gn`/`ninja`, a C/C++ toolchain). +recipes run the single-file cargo script +[`scripts/build-naive.rs`](scripts/build-naive.rs) (`get-clang.sh` → `gn gen` → +`ninja`). The script depends only on the Rust standard library, so it compiles +with no network access. + +**Requirements:** `just`, a Rust toolchain with `cargo -Zscript` support +(nightly), and the usual Chromium build deps (`gn`/`ninja`, a C/C++ toolchain). +No Go. `just` recipes: @@ -53,7 +57,7 @@ required** — only `bash`, `just`, and the usual Chromium build deps | `just env [target] [libc]` | Print `CC`/`CXX`/`CGO_LDFLAGS` for cross-compiling consumers | The script can also be invoked directly without `just`: -`bash scripts/build-naive.sh build --target linux/amd64`. +`cargo +nightly -Zscript scripts/build-naive.rs -- build --target linux/amd64`. ## Build instructions diff --git a/justfile b/justfile index 40d8c5d0..8b384bb1 100644 --- a/justfile +++ b/justfile @@ -1,8 +1,8 @@ # Build orchestration for the cronet libraries. # -# Recipes drive scripts/build-naive.sh (a bash port of the old Go tool). -# No Go toolchain is required — only bash, plus the usual Chromium build -# deps (gn/ninja, a C toolchain, etc.). +# Recipes drive scripts/build-naive.rs, a single-file cargo script (std only). +# Requires a Rust toolchain that supports `cargo -Zscript` (nightly), plus the +# usual Chromium build deps (gn/ninja, a C/C++ toolchain). No Go needed. # # Usage: # just # list recipes @@ -11,9 +11,13 @@ # just build linux/amd64 musl # just apple # all Apple platforms +# bash on every platform (Git bash on Windows). The cargo script spawns gn.exe +# natively via std::process::Command, so MSYS path-mangling never touches the +# gn arguments — only this thin `cargo` invocation runs under bash. set shell := ["bash", "-c"] -script := "scripts/build-naive.sh" +script := "scripts/build-naive.rs" +run := "cargo +nightly -Zscript " + script + " --" # List available recipes. default: @@ -22,30 +26,30 @@ default: # Compile + package cronet libs for TARGET (e.g. linux/amd64; empty = host). # Optional LIBC=musl for static musl Linux builds. build target="" libc="": - bash {{script}} build --target "{{target}}" --libc "{{libc}}" - bash {{script}} package --target "{{target}}" --libc "{{libc}}" + {{run}} build --target "{{target}}" --libc "{{libc}}" + {{run}} package --target "{{target}}" --libc "{{libc}}" # Only run gn gen + ninja (the expensive, cacheable step) for TARGET. compile target="" libc="": - bash {{script}} build --target "{{target}}" --libc "{{libc}}" + {{run}} build --target "{{target}}" --libc "{{libc}}" # Copy built libs/headers into lib/ + include/ and dump link flags for TARGET. package target="" libc="": - bash {{script}} package --target "{{target}}" --libc "{{libc}}" + {{run}} package --target "{{target}}" --libc "{{libc}}" # Build all Apple platforms (macOS, iOS, tvOS, and simulators). apple: - bash {{script}} build --target "ios/arm64,ios/arm64/simulator,ios/amd64/simulator,tvos/arm64,tvos/arm64/simulator,tvos/amd64/simulator,darwin/arm64,darwin/amd64" - bash {{script}} package --target "ios/arm64,ios/arm64/simulator,ios/amd64/simulator,tvos/arm64,tvos/arm64/simulator,tvos/amd64/simulator,darwin/arm64,darwin/amd64" + {{run}} build --target "ios/arm64,ios/arm64/simulator,ios/amd64/simulator,tvos/arm64,tvos/arm64/simulator,tvos/amd64/simulator,darwin/arm64,darwin/amd64" + {{run}} package --target "ios/arm64,ios/arm64/simulator,ios/amd64/simulator,tvos/arm64,tvos/arm64/simulator,tvos/amd64/simulator,darwin/arm64,darwin/amd64" # Download clang + sysroot for TARGET without building. download-toolchain target="" libc="": - bash {{script}} download-toolchain --target "{{target}}" --libc "{{libc}}" + {{run}} download-toolchain --target "{{target}}" --libc "{{libc}}" # Print CC/CXX/CGO_LDFLAGS env for cross-compiling consumers of TARGET. env target="" libc="": - bash {{script}} env --target "{{target}}" --libc "{{libc}}" + {{run}} env --target "{{target}}" --libc "{{libc}}" # Same as `env`, prefixed with `export ` for use with eval. env-export target="" libc="": - bash {{script}} env --target "{{target}}" --libc "{{libc}}" --export + {{run}} env --target "{{target}}" --libc "{{libc}}" --export diff --git a/scripts/build-naive.rs b/scripts/build-naive.rs new file mode 100755 index 00000000..1727597c --- /dev/null +++ b/scripts/build-naive.rs @@ -0,0 +1,788 @@ +#!/usr/bin/env -S cargo +nightly -Zscript +--- +[package] +edition = "2021" +--- +//! build-naive — cronet library build orchestration (single-file cargo script). +//! +//! A faithful port of the former Go/bash tool. Depends only on the Rust +//! standard library, so `cargo -Zscript` compiles it with no network access. +//! It drives naiveproxy's get-clang.sh, then `gn gen` + `ninja`, and packages +//! the resulting .a/.so/.dll into lib/ + include/. +//! +//! Subcommands: +//! build gn gen + ninja -> libcronet.{a,so,dll} +//! package copy libs/headers into lib/ + include/, dump link flags +//! download-toolchain download clang + sysroot without building +//! env print CC/CXX/CGO_LDFLAGS for cross-compiling consumers +//! print-config (debug) print the resolved config + gn args, run nothing +//! +//! Flags: --target (comma separated; empty = host, "all" = every target) +//! --libc musl => static OpenWrt/musl Linux build +//! --export (env only) prefix output with `export ` + +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::{exit, Command}; + +const ALL_SPECS: &[&str] = &[ + "linux/amd64", "linux/arm64", "linux/386", "linux/arm", "linux/loong64", + "linux/mipsle", "linux/mips64le", "linux/riscv64", + "darwin/amd64", "darwin/arm64", "windows/amd64", "windows/arm64", + "ios/arm64", "ios/arm64/simulator", "ios/amd64", + "tvos/arm64", "tvos/arm64/simulator", "tvos/amd64", + "android/arm64", "android/amd64", "android/arm", "android/386", +]; + +fn log(msg: &str) { + eprintln!("[build] {msg}"); +} + +fn die(msg: &str) -> ! { + eprintln!("[build] ERROR: {msg}"); + exit(1); +} + +// --------------------------------------------------------------------------- +// Host detection +// --------------------------------------------------------------------------- + +fn host_goos() -> &'static str { + match env::consts::OS { + "linux" => "linux", + "macos" => "darwin", + "windows" => "windows", + _ => "unknown", + } +} + +fn host_goarch() -> &'static str { + match env::consts::ARCH { + "x86_64" => "amd64", + "aarch64" => "arm64", + "x86" => "386", + "arm" => "arm", + "loongarch64" => "loong64", + "riscv64" => "riscv64", + "mips64" => "mips64le", + "mips" => "mipsle", + other => other, + } +} + +fn arch_to_cpu(arch: &str) -> String { + match arch { + "amd64" => "x64", + "arm64" => "arm64", + "386" => "x86", + "arm" => "arm", + "loong64" => "loong64", + "mipsle" => "mipsel", + "mips64le" => "mips64el", + other => other, + } + .to_string() +} + +// --------------------------------------------------------------------------- +// Target resolution +// --------------------------------------------------------------------------- + +#[derive(Clone, Default)] +struct Target { + os: String, // gn target_os: linux, mac, win, android, ios, openwrt + cpu: String, // gn target_cpu: x64, arm64, x86, arm, ... + goos: String, // Go GOOS + arch: String, // Go GOARCH + libc: String, // "" or "musl" + platform: String, // Apple: iphoneos | tvos + environment: String, // Apple: device | simulator +} + +fn resolve_target(spec: &str, libc: &str) -> Target { + let owned; + let spec = if spec.is_empty() { + owned = format!("{}/{}", host_goos(), host_goarch()); + owned.as_str() + } else { + spec + }; + + let parts: Vec<&str> = spec.split('/').collect(); + if parts.len() < 2 || parts.len() > 3 { + die(&format!("invalid target format: {spec} (expected os/arch or os/arch/variant)")); + } + let goos = parts[0]; + let goarch = parts[1]; + let variant = if parts.len() == 3 { parts[2] } else { "" }; + + if goos == "ios" || goos == "tvos" { + return resolve_apple(goos, goarch, variant); + } + + let mut t = Target::default(); + t.goos = goos.to_string(); + t.arch = goarch.to_string(); + t.cpu = arch_to_cpu(goarch); + t.os = match goos { + "linux" => "linux", + "darwin" => "mac", + "windows" => "win", + "android" => "android", + _ => die(&format!("unsupported target: {spec}")), + } + .to_string(); + + if !libc.is_empty() && libc != "glibc" { + if libc != "musl" { + die(&format!("invalid libc: {libc} (expected glibc or musl)")); + } + if goos != "linux" { + die(&format!("--libc=musl is only supported for Linux targets, not {goos}")); + } + t.os = "openwrt".to_string(); + t.libc = "musl".to_string(); + } + t +} + +fn resolve_apple(goos: &str, goarch: &str, variant: &str) -> Target { + let mut t = Target::default(); + t.goos = "ios".to_string(); + t.os = "ios".to_string(); + t.arch = goarch.to_string(); + t.cpu = arch_to_cpu(goarch); + t.platform = if goos == "tvos" { "tvos" } else { "iphoneos" }.to_string(); + t.environment = if variant == "simulator" { + "simulator" + } else if goarch == "amd64" { + "simulator" + } else { + "device" + } + .to_string(); + t +} + +fn format_target(t: &Target) -> String { + if !t.platform.is_empty() { + let pn = if t.platform == "tvos" { "tvOS" } else { "iOS" }; + if t.environment == "simulator" { + format!("{pn} Simulator {}", t.arch) + } else { + format!("{pn} {}", t.arch) + } + } else if t.libc == "musl" { + format!("{}/{} (musl)", t.goos, t.arch) + } else { + format!("{}/{}", t.goos, t.arch) + } +} + +fn output_dir(t: &Target) -> String { + if !t.platform.is_empty() { + let mut d = format!("out/cronet-{}-{}", t.platform, t.cpu); + if t.environment == "simulator" { + d.push_str("-simulator"); + } + d + } else { + format!("out/cronet-{}-{}", t.os, t.cpu) + } +} + +fn lib_dir_name(t: &Target) -> String { + let osn = if t.platform == "tvos" { "tvos".to_string() } else { t.goos.clone() }; + let mut n = format!("{osn}_{}", t.arch); + if t.environment == "simulator" { + n.push_str("_simulator"); + } + if t.libc == "musl" { + n.push_str("_musl"); + } + n +} + +// --------------------------------------------------------------------------- +// OpenWrt / musl + sysroot + clang config +// --------------------------------------------------------------------------- + +struct Openwrt { + target: &'static str, + subtarget: &'static str, + arch: &'static str, + release: &'static str, + gcc: &'static str, + extra: Vec<&'static str>, +} + +fn openwrt_config(t: &Target) -> Openwrt { + match t.cpu.as_str() { + "x64" => Openwrt { target: "x86", subtarget: "64", arch: "x86_64", release: "23.05.5", gcc: "12.3.0", extra: vec![] }, + "arm64" => Openwrt { target: "armsr", subtarget: "armv8", arch: "aarch64", release: "23.05.5", gcc: "12.3.0", extra: vec![] }, + "x86" => Openwrt { target: "x86", subtarget: "generic", arch: "i386_pentium4", release: "23.05.5", gcc: "12.3.0", extra: vec![] }, + "arm" => Openwrt { target: "armsr", subtarget: "armv7", arch: "arm_cortex-a15_neon-vfpv4", release: "23.05.5", gcc: "12.3.0", extra: vec![] }, + "loong64" => Openwrt { target: "loongarch64", subtarget: "generic", arch: "loongarch64", release: "24.10.5", gcc: "13.3.0", extra: vec![] }, + "mipsel" => Openwrt { target: "ramips", subtarget: "rt305x", arch: "mipsel_24kc", release: "23.05.5", gcc: "12.3.0", extra: vec![r#"mips_float_abi="soft""#, r#"mips_arch_variant="r2""#] }, + "riscv64" => Openwrt { target: "sifiveu", subtarget: "generic", arch: "riscv64", release: "23.05.5", gcc: "12.3.0", extra: vec![] }, + other => die(&format!("unsupported CPU for musl: {other}")), + } +} + +fn glibc_sysroot_path(src: &Path, cpu: &str) -> PathBuf { + let (arch, rel) = match cpu { + "x64" => ("amd64", "bullseye"), + "arm64" => ("arm64", "bullseye"), + "x86" => ("i386", "bullseye"), + "arm" => ("armhf", "bullseye"), + "loong64" => ("loong64", "sid"), + "mipsel" => ("mipsel", "bullseye"), + "mips64el" => ("mips64el", "bullseye"), + "riscv64" => ("riscv64", "trixie"), + other => die(&format!("no sysroot for cpu: {other}")), + }; + src.join(format!("out/sysroot-build/{rel}/{rel}_{arch}_staging")) +} + +fn sysroot_path(src: &Path, t: &Target) -> PathBuf { + if t.libc == "musl" { + let ow = openwrt_config(t); + src.join(format!("out/sysroot-build/openwrt/{}/{}", ow.release, ow.arch)) + } else { + glibc_sysroot_path(src, &t.cpu) + } +} + +fn clang_target(t: &Target) -> &'static str { + if t.libc == "musl" { + return match t.cpu.as_str() { + "x64" => "x86_64-openwrt-linux-musl", + "arm64" => "aarch64-openwrt-linux-musl", + "x86" => "i486-openwrt-linux-musl", + "arm" => "arm-openwrt-linux-musleabi", + "loong64" => "loongarch64-openwrt-linux-musl", + "mipsel" => "mipsel-openwrt-linux-musl", + "riscv64" => "riscv64-openwrt-linux-musl", + _ => "", + }; + } + match t.cpu.as_str() { + "x64" => "x86_64-linux-gnu", + "arm64" => "aarch64-linux-gnu", + "x86" => "i686-linux-gnu", + "arm" => "arm-linux-gnueabihf", + "loong64" => "loongarch64-linux-gnu", + "mipsel" => "mipsel-linux-gnu", + "mips64el" => "mips64el-linux-gnuabi64", + "riscv64" => "riscv64-linux-gnu", + _ => "", + } +} + +// --------------------------------------------------------------------------- +// Process helpers +// --------------------------------------------------------------------------- + +fn run(dir: &Path, program: &str, args: &[&str], envs: &[(&str, &str)]) { + let mut c = Command::new(program); + c.current_dir(dir).args(args); + for (k, v) in envs { + c.env(k, v); + } + let status = c + .status() + .unwrap_or_else(|e| die(&format!("failed to spawn {program}: {e}"))); + if !status.success() { + die(&format!("command failed: {program} {}", args.join(" "))); + } +} + +fn find_in_path(name: &str) -> Option { + let exts: &[&str] = if cfg!(windows) { &["", ".exe", ".bat", ".cmd"] } else { &[""] }; + let path = env::var_os("PATH")?; + for dir in env::split_paths(&path) { + for ext in exts { + let cand = dir.join(format!("{name}{ext}")); + if cand.is_file() { + return Some(cand); + } + } + } + None +} + +// --------------------------------------------------------------------------- +// Toolchain +// --------------------------------------------------------------------------- + +fn run_get_clang(src: &Path, t: &Target) { + if host_goos() == "linux" && (t.os == "linux" || t.os == "android" || t.os == "openwrt") { + let hcpu = arch_to_cpu(host_goarch()); + let host_flags = format!("target_os=\"linux\" target_cpu=\"{hcpu}\""); + log(&format!("get-clang.sh (host sysroot) EXTRA_FLAGS={host_flags}")); + run(src, "bash", &["./get-clang.sh"], &[("EXTRA_FLAGS", &host_flags)]); + + let host_src = glibc_sysroot_path(src, &hcpu); + let host_dst = src.join("build/linux/debian_bullseye_amd64-sysroot"); + if !host_dst.exists() { + log(&format!("symlink host sysroot {} -> {}", host_dst.display(), host_src.display())); + symlink_dir(&host_src, &host_dst); + } + } + + let extra = format!("target_os=\"{}\" target_cpu=\"{}\"", t.os, t.cpu); + if t.os == "openwrt" { + let ow = openwrt_config(t); + let owf = format!( + "target=\"{}\" subtarget=\"{}\" arch=\"{}\" release=\"{}\" gcc_ver=\"{}\"", + ow.target, ow.subtarget, ow.arch, ow.release, ow.gcc + ); + log(&format!("get-clang.sh EXTRA_FLAGS={extra} OPENWRT_FLAGS={owf}")); + run(src, "bash", &["./get-clang.sh"], &[("EXTRA_FLAGS", &extra), ("OPENWRT_FLAGS", &owf)]); + } else { + log(&format!("get-clang.sh EXTRA_FLAGS={extra}")); + run(src, "bash", &["./get-clang.sh"], &[("EXTRA_FLAGS", &extra)]); + } +} + +#[cfg(unix)] +fn symlink_dir(src: &Path, dst: &Path) { + std::os::unix::fs::symlink(src, dst).unwrap_or_else(|e| die(&format!("failed to create symlink: {e}"))); +} + +#[cfg(not(unix))] +fn symlink_dir(_src: &Path, _dst: &Path) { + // Host-sysroot symlinking only happens on a Linux host; unreachable elsewhere. +} + +// --------------------------------------------------------------------------- +// gn args + build +// --------------------------------------------------------------------------- + +fn build_gn_args(src: &Path, t: &Target) -> Vec { + let base = [ + "is_official_build=true", + "is_debug=false", + "is_clang=true", + "use_clang_modules=false", + "use_thin_lto=false", + "fatal_linker_warnings=false", + "treat_warnings_as_errors=false", + "is_cronet_build=true", + "use_udev=false", + "use_aura=false", + "use_ozone=false", + "use_gio=false", + "use_glib=false", + "use_kerberos=false", + "disable_zstd_filter=false", + "enable_reporting=false", + "enable_bracketed_proxy_uris=true", + "enable_quic_proxy_support=true", + "use_nss_certs=false", + "enable_backup_ref_ptr_support=false", + "enable_dangling_raw_ptr_checks=false", + "exclude_unwind_tables=true", + "enable_resource_allowlist_generation=false", + "symbol_level=0", + "enable_dsyms=false", + "optimize_for_size=true", + ]; + let mut args: Vec = base.iter().map(|s| s.to_string()).collect(); + args.push(format!("target_os=\"{}\"", t.os)); + args.push(format!("target_cpu=\"{}\"", t.cpu)); + + match t.os.as_str() { + "mac" => args.push("use_sysroot=false".to_string()), + "linux" => { + let sp = sysroot_path(src, t); + let rel = sp + .strip_prefix(src) + .map(|p| p.to_string_lossy().replace('\\', "/")) + .unwrap_or_else(|_| sp.to_string_lossy().to_string()); + args.push("use_sysroot=true".to_string()); + args.push(format!("target_sysroot=\"//{rel}\"")); + if t.cpu == "x64" { + args.push("use_cfi_icall=false".to_string()); + args.push("is_cfi=false".to_string()); + } + } + "openwrt" => { + let ow = openwrt_config(t); + let sd = format!("out/sysroot-build/openwrt/{}/{}", ow.release, ow.arch); + args.push("use_sysroot=true".to_string()); + args.push(format!("target_sysroot=\"//{sd}\"")); + args.push("build_static=true".to_string()); + args.push("use_allocator_shim=false".to_string()); + args.push("use_partition_alloc=false".to_string()); + for e in ow.extra { + args.push(e.to_string()); + } + if t.cpu == "x64" { + args.push("use_cfi_icall=false".to_string()); + args.push("is_cfi=false".to_string()); + } + } + "win" => args.push("use_sysroot=false".to_string()), + "android" => { + args.push("use_sysroot=false".to_string()); + args.push("default_min_sdk_version=23".to_string()); + } + "ios" => { + let plat = if t.platform.is_empty() { "iphoneos" } else { &t.platform }; + let envv = if t.environment.is_empty() { "device" } else { &t.environment }; + args.push("use_sysroot=false".to_string()); + args.push("ios_enable_code_signing=false".to_string()); + args.push(format!("target_platform=\"{plat}\"")); + args.push(format!("target_environment=\"{envv}\"")); + args.push("ios_deployment_target=\"15.0\"".to_string()); + args.push("enable_built_in_dns=true".to_string()); + args.push("ios_partition_alloc_enabled=false".to_string()); + } + _ => {} + } + + let wrapper = if host_goos() == "windows" { "sccache" } else { "ccache" }; + if let Some(p) = find_in_path(wrapper) { + args.push(format!("cc_wrapper=\"{}\"", p.to_string_lossy())); + } + + args +} + +fn compile_target(src: &Path, t: &Target) { + run_get_clang(src, t); + + let outdir = output_dir(t); + let gn_args = build_gn_args(src, t).join(" "); + + let gn = if host_goos() == "windows" { + src.join("gn/out/gn.exe") + } else { + src.join("gn/out/gn") + }; + let gn_str = gn.to_string_lossy().to_string(); + + log(&format!("gn gen {outdir}")); + let args_flag = format!("--args={gn_args}"); + let gen_args = ["gen", outdir.as_str(), args_flag.as_str()]; + if host_goos() == "windows" { + run(src, &gn_str, &gen_args, &[("DEPOT_TOOLS_WIN_TOOLCHAIN", "0")]); + } else { + run(src, &gn_str, &gen_args, &[]); + } + + if t.goos == "windows" { + log(&format!("ninja -C {outdir} cronet")); + run(src, "ninja", &["-C", &outdir, "cronet"], &[]); + } else { + log(&format!("ninja -C {outdir} cronet_static")); + run(src, "ninja", &["-C", &outdir, "cronet_static"], &[]); + if t.goos == "linux" && t.libc != "musl" { + log(&format!("ninja -C {outdir} cronet")); + run(src, "ninja", &["-C", &outdir, "cronet"], &[]); + } + } +} + +// --------------------------------------------------------------------------- +// Package +// --------------------------------------------------------------------------- + +fn copy_file(srcf: &Path, dst: &Path) { + if let Some(p) = dst.parent() { + let _ = fs::create_dir_all(p); + } + fs::copy(srcf, dst) + .unwrap_or_else(|e| die(&format!("failed to copy {} -> {}: {e}", srcf.display(), dst.display()))); +} + +fn copy_headers(root: &Path, src: &Path) { + let inc = root.join("include"); + let _ = fs::create_dir_all(&inc); + let headers = [ + ("components/cronet/native/include/cronet_c.h", "cronet_c.h"), + ("components/cronet/native/include/cronet_export.h", "cronet_export.h"), + ("components/cronet/native/generated/cronet.idl_c.h", "cronet.idl_c.h"), + ("components/grpc_support/include/bidirectional_stream_c.h", "bidirectional_stream_c.h"), + ]; + for (s, d) in headers { + copy_file(&src.join(s), &inc.join(d)); + } + log("Copied headers to include/"); +} + +struct LinkFlags { + libs: Vec, + frameworks: Vec, + ldflags: Vec, +} + +fn ninja_kv<'a>(line: &'a str, key: &str) -> Option<&'a str> { + let rest = line.trim_start().strip_prefix(key)?; + let rest = rest.trim_start().strip_prefix('=')?; + Some(rest.trim_start()) +} + +fn parse_frameworks(input: &str) -> Vec { + let toks: Vec<&str> = input.split_whitespace().collect(); + let mut out = Vec::new(); + let mut i = 0; + while i < toks.len() { + if toks[i] == "-framework" && i + 1 < toks.len() { + out.push(format!("-framework {}", toks[i + 1])); + i += 2; + } else { + i += 1; + } + } + out +} + +fn extract_link_flags(src: &Path, outdir: &str) -> Option { + let ninja = src.join(outdir).join("obj/components/cronet/cronet_sample.ninja"); + let content = fs::read_to_string(&ninja).ok()?; + let mut lf = LinkFlags { libs: vec![], frameworks: vec![], ldflags: vec![] }; + for line in content.lines() { + if let Some(rest) = ninja_kv(line, "libs") { + for tok in rest.split_whitespace() { + if !tok.ends_with(".lds") { + lf.libs.push(tok.to_string()); + } + } + } else if let Some(rest) = ninja_kv(line, "frameworks") { + lf.frameworks = parse_frameworks(rest); + } else if let Some(rest) = ninja_kv(line, "ldflags") { + lf.ldflags = rest + .split_whitespace() + .filter(|tok| tok.starts_with("-Wl,-wrap,")) + .map(String::from) + .collect(); + } + } + Some(lf) +} + +fn write_link_flags(src: &Path, t: &Target, td: &Path, outdir: &str) { + let lf = match extract_link_flags(src, outdir) { + Some(x) => x, + None => { + log(&format!("Warning: could not extract link flags for {}", format_target(t))); + return; + } + }; + let mut out = String::new(); + if !lf.ldflags.is_empty() { + out.push_str(&format!("# ldflags\n{}\n", lf.ldflags.join(" "))); + } + if !lf.libs.is_empty() { + out.push_str(&format!("# libs\n{}\n", lf.libs.join(" "))); + } + if !lf.frameworks.is_empty() { + out.push_str(&format!("# frameworks\n{}\n", lf.frameworks.join(" "))); + } + if t.goos == "linux" && t.libc == "musl" { + out.push_str("# extra\n-static\n"); + } + if out.is_empty() { + return; + } + fs::write(td.join("link_flags.txt"), out) + .unwrap_or_else(|e| die(&format!("failed to write link_flags.txt: {e}"))); + log(&format!("Wrote link flags for {}", format_target(t))); +} + +fn package_target(root: &Path, src: &Path, t: &Target) { + let name = lib_dir_name(t); + let td = root.join("lib").join(&name); + let _ = fs::create_dir_all(&td); + let outdir = output_dir(t); + + if t.goos == "windows" { + let s = src.join(&outdir).join("cronet.dll"); + if s.is_file() { + copy_file(&s, &td.join("libcronet.dll")); + log(&format!("Copied DLL for {}/{}", t.goos, t.arch)); + } else { + log(&format!("Warning: DLL not found for {}/{}, skipping", t.goos, t.arch)); + } + log(&format!("Packaged lib/{name}")); + return; + } + + let ss = src.join(&outdir).join("obj/components/cronet/libcronet_static.a"); + if ss.is_file() { + copy_file(&ss, &td.join("libcronet.a")); + log(&format!("Copied static library for {}", format_target(t))); + } else { + log(&format!("Warning: static library not found for {}, skipping", format_target(t))); + } + + if t.goos == "linux" && t.libc != "musl" { + let so = src.join(&outdir).join("libcronet.so"); + if so.is_file() { + copy_file(&so, &td.join("libcronet.so")); + log(&format!("Copied shared library for {}", format_target(t))); + } + } + + write_link_flags(src, t, &td, &outdir); + log(&format!("Packaged lib/{name}")); +} + +// --------------------------------------------------------------------------- +// env +// --------------------------------------------------------------------------- + +fn shell_quote(s: &str, export: bool) -> String { + if export && s.chars().any(|c| " \t\n\"'\\$".contains(c)) { + format!("\"{}\"", s.replace('"', "\\\"")) + } else { + s.to_string() + } +} + +fn print_env(src: &Path, t: &Target, export: bool) { + if t.goos == "windows" { + die("env command is not supported for Windows (use the prebuilt DLL)"); + } + let prefix = if export { "export " } else { "" }; + + if t.goos == "linux" { + let mut ld = vec!["-fuse-ld=lld".to_string()]; + if matches!(t.arch.as_str(), "386" | "arm" | "loong64" | "mipsle" | "mips64le") { + ld.push("-Wl,-no-pie".to_string()); + } + if (t.arch == "mipsle" || t.arch == "mips64le") && t.libc != "musl" { + ld.push("-Wl,-z,execstack".to_string()); + } + println!("{prefix}CGO_LDFLAGS={}", shell_quote(&ld.join(" "), export)); + + let clang = src.join("third_party/llvm-build/Release+Asserts/bin/clang"); + let ct = clang_target(t); + let sp = sysroot_path(src, t); + let cc = format!("{} --target={ct} --sysroot={}", clang.display(), sp.display()); + let cxx = format!("{}++ --target={ct} --sysroot={}", clang.display(), sp.display()); + println!("{prefix}CC={}", shell_quote(&cc, export)); + println!("{prefix}CXX={}", shell_quote(&cxx, export)); + println!("{prefix}QEMU_LD_PREFIX={}", sp.display()); + } +} + +fn print_config(src: &Path, t: &Target) { + let opt = |s: &str| if s.is_empty() { "-".to_string() } else { s.to_string() }; + println!( + "spec: os={} cpu={} goos={} arch={} libc={} platform={} env={}", + t.os, t.cpu, t.goos, t.arch, opt(&t.libc), opt(&t.platform), opt(&t.environment) + ); + println!("output_dir: {}", output_dir(t)); + println!("lib_dir_name: {}", lib_dir_name(t)); + println!("gn_args: {}", build_gn_args(src, t).join(" ")); +} + +// --------------------------------------------------------------------------- +// Project root + main +// --------------------------------------------------------------------------- + +fn project_root() -> PathBuf { + let cwd = env::current_dir().unwrap_or_else(|e| die(&format!("failed to get working directory: {e}"))); + let mut d = cwd.as_path(); + loop { + if d.join("naiveproxy").is_dir() { + return d.to_path_buf(); + } + match d.parent() { + Some(p) => d = p, + None => break, + } + } + cwd +} + +fn expand_targets(target: &str) -> Vec { + if target == "all" { + ALL_SPECS.iter().map(|s| s.to_string()).collect() + } else if target.is_empty() { + vec![String::new()] + } else { + target.split(',').map(|s| s.trim().to_string()).collect() + } +} + +fn main() { + let raw: Vec = env::args().skip(1).skip_while(|s| s == "--").collect(); + if raw.is_empty() { + die("usage: build-naive [--target LIST] [--libc glibc|musl] [--export]"); + } + let cmd = raw[0].clone(); + + let mut target = String::new(); + let mut libc = String::new(); + let mut export = false; + let mut i = 1; + while i < raw.len() { + let a = &raw[i]; + if a == "--target" || a == "-t" { + i += 1; + target = raw.get(i).cloned().unwrap_or_default(); + } else if let Some(v) = a.strip_prefix("--target=") { + target = v.to_string(); + } else if a == "--libc" { + i += 1; + libc = raw.get(i).cloned().unwrap_or_default(); + } else if let Some(v) = a.strip_prefix("--libc=") { + libc = v.to_string(); + } else if a == "--export" { + export = true; + } else if !a.is_empty() { + die(&format!("unknown argument: {a}")); + } + i += 1; + } + + let root = project_root(); + let src = root.join("naiveproxy").join("src"); + let specs = expand_targets(&target); + + match cmd.as_str() { + "build" => { + for s in &specs { + let t = resolve_target(s, &libc); + log(&format!("Building {}...", format_target(&t))); + compile_target(&src, &t); + } + log("Build complete!"); + } + "package" => { + copy_headers(&root, &src); + for s in &specs { + let t = resolve_target(s, &libc); + package_target(&root, &src, &t); + } + log("Package complete!"); + } + "download-toolchain" => { + for s in &specs { + let t = resolve_target(s, &libc); + log(&format!("Downloading toolchain for {}...", format_target(&t))); + run_get_clang(&src, &t); + } + log("Toolchain download complete!"); + } + "env" => { + if target.contains(',') { + die("env requires exactly one target"); + } + let t = resolve_target(&target, &libc); + print_env(&src, &t, export); + } + "print-config" => { + for s in &specs { + let t = resolve_target(s, &libc); + print_config(&src, &t); + } + } + other => die(&format!("unknown command: {other}")), + } +} diff --git a/scripts/build-naive.sh b/scripts/build-naive.sh deleted file mode 100755 index 53c0fa7f..00000000 --- a/scripts/build-naive.sh +++ /dev/null @@ -1,613 +0,0 @@ -#!/usr/bin/env bash -# -# build-naive.sh — build orchestration for the cronet libraries. -# -# A faithful bash port of the former Go tool cmd/build-naive. It drives -# naiveproxy's get-clang.sh, then `gn gen` + `ninja`, and packages the -# resulting .a/.so/.dll into lib/ + include/. -# -# Subcommands: -# build gn gen + ninja -> libcronet.{a,so,dll} -# package copy libs/headers into lib/ + include/, dump link flags -# download-toolchain download clang + sysroot without building -# env print CC/CXX/CGO_LDFLAGS for cross-compiling consumers -# -# Flags: --target comma separated (e.g. linux/amd64,darwin/arm64); -# empty = host, "all" = every supported target -# --libc musl => static OpenWrt/musl Linux build -# --export (env only) prefix output with `export ` -# -# Written for bash 3.2+ (macOS ships 3.2); avoid set -u (empty-array hazard). - -set -eo pipefail - -LIB_SELF="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(dirname "$LIB_SELF")" -NAIVE_ROOT="$PROJECT_ROOT/naiveproxy" -SRC_ROOT="$NAIVE_ROOT/src" - -log() { echo "[build] $*" >&2; } -die() { echo "[build] ERROR: $*" >&2; exit 1; } - -ALL_SPECS="linux/amd64 linux/arm64 linux/386 linux/arm linux/loong64 linux/mipsle linux/mips64le linux/riscv64 darwin/amd64 darwin/arm64 windows/amd64 windows/arm64 ios/arm64 ios/arm64/simulator ios/amd64 tvos/arm64 tvos/arm64/simulator tvos/amd64 android/arm64 android/amd64 android/arm android/386" - -# --------------------------------------------------------------------------- -# Host detection -# --------------------------------------------------------------------------- - -host_goos() { - case "$(uname -s)" in - Linux) echo linux ;; - Darwin) echo darwin ;; - MINGW*|MSYS*|CYGWIN*) echo windows ;; - *) echo unknown ;; - esac -} - -host_goarch() { - case "$(uname -m)" in - x86_64|amd64) echo amd64 ;; - aarch64|arm64) echo arm64 ;; - i686|i386) echo 386 ;; - armv7l|armv6l|arm) echo arm ;; - loongarch64) echo loong64 ;; - mips64el) echo mips64le ;; - mipsel) echo mipsle ;; - riscv64) echo riscv64 ;; - *) echo unknown ;; - esac -} - -arch_to_cpu() { - case "$1" in - amd64) echo x64 ;; - arm64) echo arm64 ;; - 386) echo x86 ;; - arm) echo arm ;; - loong64) echo loong64 ;; - mipsle) echo mipsel ;; - mips64le) echo mips64el ;; - *) echo "$1" ;; - esac -} - -# --------------------------------------------------------------------------- -# Target resolution -> sets T_OS T_CPU T_GOOS T_ARCH T_LIBC T_PLATFORM T_ENV -# --------------------------------------------------------------------------- - -resolve_target() { - local spec="$1" libc="$2" - T_OS=""; T_CPU=""; T_GOOS=""; T_ARCH=""; T_LIBC=""; T_PLATFORM=""; T_ENV="" - - if [ -z "$spec" ]; then - spec="$(host_goos)/$(host_goarch)" - fi - - local goos goarch variant - goos="${spec%%/*}" - local rest="${spec#*/}" - goarch="${rest%%/*}" - if [ "$rest" != "$goarch" ]; then - variant="${rest#*/}" - else - variant="" - fi - - case "$goos" in - ios|tvos) - resolve_apple "$goos" "$goarch" "$variant" - return - ;; - linux) T_OS=linux ;; - darwin) T_OS=mac ;; - windows) T_OS=win ;; - android) T_OS=android ;; - *) die "unsupported target: $spec" ;; - esac - - T_GOOS="$goos" - T_ARCH="$goarch" - T_CPU="$(arch_to_cpu "$goarch")" - - if [ -n "$libc" ] && [ "$libc" != glibc ]; then - [ "$libc" = musl ] || die "invalid libc: $libc (expected glibc or musl)" - [ "$goos" = linux ] || die "--libc=musl is only supported for Linux targets, not $goos" - T_OS=openwrt - T_LIBC=musl - fi -} - -resolve_apple() { - local goos="$1" goarch="$2" variant="$3" - T_GOOS=ios - T_OS=ios - T_ARCH="$goarch" - T_CPU="$(arch_to_cpu "$goarch")" - T_LIBC="" - if [ "$goos" = tvos ]; then T_PLATFORM=tvos; else T_PLATFORM=iphoneos; fi - if [ "$variant" = simulator ]; then - T_ENV=simulator - elif [ "$goarch" = amd64 ]; then - T_ENV=simulator - else - T_ENV=device - fi -} - -format_target() { - if [ -n "$T_PLATFORM" ]; then - local pn=iOS - [ "$T_PLATFORM" = tvos ] && pn=tvOS - if [ "$T_ENV" = simulator ]; then echo "$pn Simulator $T_ARCH"; else echo "$pn $T_ARCH"; fi - elif [ "$T_LIBC" = musl ]; then - echo "$T_GOOS/$T_ARCH (musl)" - else - echo "$T_GOOS/$T_ARCH" - fi -} - -output_dir() { - if [ -n "$T_PLATFORM" ]; then - local d="out/cronet-$T_PLATFORM-$T_CPU" - [ "$T_ENV" = simulator ] && d="$d-simulator" - echo "$d" - else - echo "out/cronet-$T_OS-$T_CPU" - fi -} - -lib_dir_name() { - local osn="$T_GOOS" - [ "$T_PLATFORM" = tvos ] && osn=tvos - local n="${osn}_${T_ARCH}" - [ "$T_ENV" = simulator ] && n="${n}_simulator" - [ "$T_LIBC" = musl ] && n="${n}_musl" - echo "$n" -} - -# --------------------------------------------------------------------------- -# OpenWrt / musl config -> sets OW_TARGET OW_SUBTARGET OW_ARCH OW_RELEASE -# OW_GCC OW_EXTRA[] -# --------------------------------------------------------------------------- - -openwrt_config() { - OW_EXTRA=() - case "$T_CPU" in - x64) OW_TARGET=x86; OW_SUBTARGET=64; OW_ARCH=x86_64; OW_RELEASE=23.05.5; OW_GCC=12.3.0 ;; - arm64) OW_TARGET=armsr; OW_SUBTARGET=armv8; OW_ARCH=aarch64; OW_RELEASE=23.05.5; OW_GCC=12.3.0 ;; - x86) OW_TARGET=x86; OW_SUBTARGET=generic; OW_ARCH=i386_pentium4; OW_RELEASE=23.05.5; OW_GCC=12.3.0 ;; - arm) OW_TARGET=armsr; OW_SUBTARGET=armv7; OW_ARCH="arm_cortex-a15_neon-vfpv4"; OW_RELEASE=23.05.5; OW_GCC=12.3.0 ;; - loong64) OW_TARGET=loongarch64; OW_SUBTARGET=generic; OW_ARCH=loongarch64; OW_RELEASE=24.10.5; OW_GCC=13.3.0 ;; - mipsel) OW_TARGET=ramips; OW_SUBTARGET=rt305x; OW_ARCH=mipsel_24kc; OW_RELEASE=23.05.5; OW_GCC=12.3.0; OW_EXTRA=('mips_float_abi="soft"' 'mips_arch_variant="r2"') ;; - riscv64) OW_TARGET=sifiveu; OW_SUBTARGET=generic; OW_ARCH=riscv64; OW_RELEASE=23.05.5; OW_GCC=12.3.0 ;; - *) die "unsupported CPU for musl: $T_CPU" ;; - esac -} - -# Debian sysroot path for a glibc CPU (ignores libc). -glibc_sysroot_path() { - local cpu="$1" arch rel - case "$cpu" in - x64) arch=amd64; rel=bullseye ;; - arm64) arch=arm64; rel=bullseye ;; - x86) arch=i386; rel=bullseye ;; - arm) arch=armhf; rel=bullseye ;; - loong64) arch=loong64; rel=sid ;; - mipsel) arch=mipsel; rel=bullseye ;; - mips64el) arch=mips64el; rel=bullseye ;; - riscv64) arch=riscv64; rel=trixie ;; - *) die "no sysroot for cpu: $cpu" ;; - esac - echo "$SRC_ROOT/out/sysroot-build/$rel/${rel}_${arch}_staging" -} - -sysroot_path() { - if [ "$T_LIBC" = musl ]; then - openwrt_config - echo "$SRC_ROOT/out/sysroot-build/openwrt/$OW_RELEASE/$OW_ARCH" - else - glibc_sysroot_path "$T_CPU" - fi -} - -clang_target() { - if [ "$T_LIBC" = musl ]; then - case "$T_CPU" in - x64) echo x86_64-openwrt-linux-musl ;; - arm64) echo aarch64-openwrt-linux-musl ;; - x86) echo i486-openwrt-linux-musl ;; - arm) echo arm-openwrt-linux-musleabi ;; - loong64) echo loongarch64-openwrt-linux-musl ;; - mipsel) echo mipsel-openwrt-linux-musl ;; - riscv64) echo riscv64-openwrt-linux-musl ;; - esac - return - fi - case "$T_CPU" in - x64) echo x86_64-linux-gnu ;; - arm64) echo aarch64-linux-gnu ;; - x86) echo i686-linux-gnu ;; - arm) echo arm-linux-gnueabihf ;; - loong64) echo loongarch64-linux-gnu ;; - mipsel) echo mipsel-linux-gnu ;; - mips64el) echo mips64el-linux-gnuabi64 ;; - riscv64) echo riscv64-linux-gnu ;; - esac -} - -# --------------------------------------------------------------------------- -# Toolchain -# --------------------------------------------------------------------------- - -run_get_clang() { - local hgoos; hgoos="$(host_goos)" - - if [ "$hgoos" = linux ] && { [ "$T_OS" = linux ] || [ "$T_OS" = android ] || [ "$T_OS" = openwrt ]; }; then - local hcpu; hcpu="$(arch_to_cpu "$(host_goarch)")" - local host_flags="target_os=\"linux\" target_cpu=\"$hcpu\"" - log "get-clang.sh (host sysroot) EXTRA_FLAGS=$host_flags" - ( cd "$SRC_ROOT" && EXTRA_FLAGS="$host_flags" bash ./get-clang.sh ) - - local host_src host_dst - host_src="$(glibc_sysroot_path "$hcpu")" - host_dst="$SRC_ROOT/build/linux/debian_bullseye_amd64-sysroot" - if [ ! -e "$host_dst" ]; then - log "symlink host sysroot $host_dst -> $host_src" - ln -s "$host_src" "$host_dst" - fi - fi - - local extra="target_os=\"$T_OS\" target_cpu=\"$T_CPU\"" - if [ "$T_OS" = openwrt ]; then - openwrt_config - local ow="target=\"$OW_TARGET\" subtarget=\"$OW_SUBTARGET\" arch=\"$OW_ARCH\" release=\"$OW_RELEASE\" gcc_ver=\"$OW_GCC\"" - log "get-clang.sh EXTRA_FLAGS=$extra OPENWRT_FLAGS=$ow" - ( cd "$SRC_ROOT" && EXTRA_FLAGS="$extra" OPENWRT_FLAGS="$ow" bash ./get-clang.sh ) - else - log "get-clang.sh EXTRA_FLAGS=$extra" - ( cd "$SRC_ROOT" && EXTRA_FLAGS="$extra" bash ./get-clang.sh ) - fi -} - -# --------------------------------------------------------------------------- -# Build (gn gen + ninja) -# --------------------------------------------------------------------------- - -compile_target() { - run_get_clang - - local outdir; outdir="$(output_dir)" - local hgoos; hgoos="$(host_goos)" - - local args - args=( - is_official_build=true - is_debug=false - is_clang=true - use_clang_modules=false - use_thin_lto=false - fatal_linker_warnings=false - treat_warnings_as_errors=false - is_cronet_build=true - use_udev=false - use_aura=false - use_ozone=false - use_gio=false - use_glib=false - use_kerberos=false - disable_zstd_filter=false - enable_reporting=false - enable_bracketed_proxy_uris=true - enable_quic_proxy_support=true - use_nss_certs=false - enable_backup_ref_ptr_support=false - enable_dangling_raw_ptr_checks=false - exclude_unwind_tables=true - enable_resource_allowlist_generation=false - symbol_level=0 - enable_dsyms=false - optimize_for_size=true - "target_os=\"$T_OS\"" - "target_cpu=\"$T_CPU\"" - ) - - case "$T_OS" in - mac) - args+=(use_sysroot=false) - ;; - linux) - local sp rel - sp="$(sysroot_path)" - rel="${sp#"$SRC_ROOT"/}" - args+=(use_sysroot=true "target_sysroot=\"//$rel\"") - [ "$T_CPU" = x64 ] && args+=(use_cfi_icall=false is_cfi=false) - ;; - openwrt) - openwrt_config - local sd="out/sysroot-build/openwrt/$OW_RELEASE/$OW_ARCH" - args+=(use_sysroot=true "target_sysroot=\"//$sd\"" build_static=true use_allocator_shim=false use_partition_alloc=false) - [ "${#OW_EXTRA[@]}" -gt 0 ] && args+=("${OW_EXTRA[@]}") - [ "$T_CPU" = x64 ] && args+=(use_cfi_icall=false is_cfi=false) - ;; - win) - args+=(use_sysroot=false) - ;; - android) - args+=(use_sysroot=false default_min_sdk_version=23) - ;; - ios) - local plat="$T_PLATFORM" envv="$T_ENV" - [ -z "$plat" ] && plat=iphoneos - [ -z "$envv" ] && envv=device - args+=( - use_sysroot=false - ios_enable_code_signing=false - "target_platform=\"$plat\"" - "target_environment=\"$envv\"" - 'ios_deployment_target="15.0"' - enable_built_in_dns=true - ios_partition_alloc_enabled=false - ) - ;; - esac - - if [ "$hgoos" = windows ]; then - if command -v sccache >/dev/null 2>&1; then - args+=("cc_wrapper=\"$(command -v sccache)\"") - fi - else - if command -v ccache >/dev/null 2>&1; then - args+=("cc_wrapper=\"$(command -v ccache)\"") - fi - fi - - local gn_args="${args[*]}" - local gn="$SRC_ROOT/gn/out/gn" - [ "$hgoos" = windows ] && gn="$gn.exe" - - log "gn gen $outdir" - if [ "$hgoos" = windows ]; then - ( cd "$SRC_ROOT" && DEPOT_TOOLS_WIN_TOOLCHAIN=0 "$gn" gen "$outdir" "--args=$gn_args" ) - else - ( cd "$SRC_ROOT" && "$gn" gen "$outdir" "--args=$gn_args" ) - fi - - if [ "$T_GOOS" = windows ]; then - log "ninja -C $outdir cronet" - ( cd "$SRC_ROOT" && ninja -C "$outdir" cronet ) - else - log "ninja -C $outdir cronet_static" - ( cd "$SRC_ROOT" && ninja -C "$outdir" cronet_static ) - if [ "$T_GOOS" = linux ] && [ "$T_LIBC" != musl ]; then - log "ninja -C $outdir cronet" - ( cd "$SRC_ROOT" && ninja -C "$outdir" cronet ) - fi - fi -} - -# --------------------------------------------------------------------------- -# Package (copy libs/headers + link flags) -# --------------------------------------------------------------------------- - -copy_headers() { - local inc="$PROJECT_ROOT/include" - mkdir -p "$inc" - cp "$SRC_ROOT/components/cronet/native/include/cronet_c.h" "$inc/cronet_c.h" - cp "$SRC_ROOT/components/cronet/native/include/cronet_export.h" "$inc/cronet_export.h" - cp "$SRC_ROOT/components/cronet/native/generated/cronet.idl_c.h" "$inc/cronet.idl_c.h" - cp "$SRC_ROOT/components/grpc_support/include/bidirectional_stream_c.h" "$inc/bidirectional_stream_c.h" - log "Copied headers to include/" -} - -# Parse the generated cronet_sample.ninja for the system link flags that the -# static library depends on, and write them to link_flags.txt. -write_link_flags() { - local td="$1" outdir="$2" - local ninja="$SRC_ROOT/$outdir/obj/components/cronet/cronet_sample.ninja" - if [ ! -f "$ninja" ]; then - log "Warning: ninja file missing for $(format_target): $ninja" - return - fi - - local libs frameworks ldflags - libs="$(grep -E '^[[:space:]]*libs[[:space:]]*=' "$ninja" | sed -E 's/^[[:space:]]*libs[[:space:]]*=[[:space:]]*//' | tr '\n' ' ')" - frameworks="$(grep -E '^[[:space:]]*frameworks[[:space:]]*=' "$ninja" | tail -1 | sed -E 's/^[[:space:]]*frameworks[[:space:]]*=[[:space:]]*//')" - ldflags="$(grep -E '^[[:space:]]*ldflags[[:space:]]*=' "$ninja" | tail -1 | sed -E 's/^[[:space:]]*ldflags[[:space:]]*=[[:space:]]*//')" - - local out="" tok - local keep="" - for tok in $ldflags; do - case "$tok" in -Wl,-wrap,*) keep="$keep $tok" ;; esac - done - keep="${keep# }" - [ -n "$keep" ] && out="${out}# ldflags\n${keep}\n" - - keep="" - for tok in $libs; do - case "$tok" in *.lds) : ;; *) keep="$keep $tok" ;; esac - done - keep="${keep# }" - [ -n "$keep" ] && out="${out}# libs\n${keep}\n" - - keep="" - set -- $frameworks - while [ $# -gt 0 ]; do - if [ "$1" = "-framework" ] && [ $# -ge 2 ]; then - keep="$keep -framework $2" - shift 2 - else - shift - fi - done - keep="${keep# }" - [ -n "$keep" ] && out="${out}# frameworks\n${keep}\n" - - if [ "$T_GOOS" = linux ] && [ "$T_LIBC" = musl ]; then - out="${out}# extra\n-static\n" - fi - - [ -n "$out" ] || return - printf '%b' "$out" > "$td/link_flags.txt" - log "Wrote link flags for $(format_target)" -} - -package_target() { - local name td outdir - name="$(lib_dir_name)" - td="$PROJECT_ROOT/lib/$name" - mkdir -p "$td" - outdir="$(output_dir)" - - if [ "$T_GOOS" = windows ]; then - local src="$SRC_ROOT/$outdir/cronet.dll" - if [ -f "$src" ]; then - cp "$src" "$td/libcronet.dll" - log "Copied DLL for $T_GOOS/$T_ARCH" - else - log "Warning: DLL not found for $T_GOOS/$T_ARCH, skipping" - fi - log "Packaged lib/$name" - return - fi - - local src_static="$SRC_ROOT/$outdir/obj/components/cronet/libcronet_static.a" - if [ -f "$src_static" ]; then - cp "$src_static" "$td/libcronet.a" - log "Copied static library for $(format_target)" - else - log "Warning: static library not found for $(format_target), skipping" - fi - - if [ "$T_GOOS" = linux ] && [ "$T_LIBC" != musl ]; then - local src_so="$SRC_ROOT/$outdir/libcronet.so" - if [ -f "$src_so" ]; then - cp "$src_so" "$td/libcronet.so" - log "Copied shared library for $(format_target)" - fi - fi - - write_link_flags "$td" "$outdir" - log "Packaged lib/$name" -} - -# --------------------------------------------------------------------------- -# env -# --------------------------------------------------------------------------- - -shell_quote() { - local s="$1" - if [ "$EXPORT" = 1 ] && printf '%s' "$s" | grep -qE '[[:space:]"'\''\\$]'; then - printf '"%s"' "$(printf '%s' "$s" | sed 's/"/\\"/g')" - else - printf '%s' "$s" - fi -} - -print_env() { - [ "$T_GOOS" = windows ] && die "env command is not supported for Windows (use the prebuilt DLL)" - - local prefix="" - [ "$EXPORT" = 1 ] && prefix="export " - - if [ "$T_GOOS" = linux ]; then - local ld="-fuse-ld=lld" - case "$T_ARCH" in - 386|arm|loong64|mipsle|mips64le) ld="$ld -Wl,-no-pie" ;; - esac - if { [ "$T_ARCH" = mipsle ] || [ "$T_ARCH" = mips64le ]; } && [ "$T_LIBC" != musl ]; then - ld="$ld -Wl,-z,execstack" - fi - printf '%sCGO_LDFLAGS=%s\n' "$prefix" "$(shell_quote "$ld")" - - local clang="$SRC_ROOT/third_party/llvm-build/Release+Asserts/bin/clang" - local ct sp - ct="$(clang_target)" - sp="$(sysroot_path)" - printf '%sCC=%s\n' "$prefix" "$(shell_quote "$clang --target=$ct --sysroot=$sp")" - printf '%sCXX=%s\n' "$prefix" "$(shell_quote "$clang++ --target=$ct --sysroot=$sp")" - printf '%sQEMU_LD_PREFIX=%s\n' "$prefix" "$sp" - fi -} - -# --------------------------------------------------------------------------- -# Target list expansion -# --------------------------------------------------------------------------- - -expand_targets() { - local spec="$1" - if [ "$spec" = all ]; then - echo "$ALL_SPECS" - elif [ -z "$spec" ]; then - echo "" # single host target (empty string) - else - echo "$spec" | tr ',' ' ' - fi -} - -# --------------------------------------------------------------------------- -# Main -# --------------------------------------------------------------------------- - -main() { - [ $# -ge 1 ] || die "usage: build-naive.sh [--target LIST] [--libc glibc|musl] [--export]" - local cmd="$1"; shift - - local target="" libc="" - EXPORT=0 - while [ $# -gt 0 ]; do - case "$1" in - --target) target="$2"; shift 2 ;; - --target=*) target="${1#--target=}"; shift ;; - -t) target="$2"; shift 2 ;; - --libc) libc="$2"; shift 2 ;; - --libc=*) libc="${1#--libc=}"; shift ;; - --export) EXPORT=1; shift ;; - "") shift ;; - *) die "unknown argument: $1" ;; - esac - done - - case "$cmd" in - build) - local specs; specs="$(expand_targets "$target")" - if [ -z "$target" ]; then - resolve_target "" "$libc"; log "Building $(format_target)..."; compile_target - else - for s in $specs; do resolve_target "$s" "$libc"; log "Building $(format_target)..."; compile_target; done - fi - log "Build complete!" - ;; - package) - copy_headers - local specs; specs="$(expand_targets "$target")" - if [ -z "$target" ]; then - resolve_target "" "$libc"; package_target - else - for s in $specs; do resolve_target "$s" "$libc"; package_target; done - fi - log "Package complete!" - ;; - download-toolchain) - local specs; specs="$(expand_targets "$target")" - if [ -z "$target" ]; then - resolve_target "" "$libc"; log "Downloading toolchain for $(format_target)..."; run_get_clang - else - for s in $specs; do resolve_target "$s" "$libc"; log "Downloading toolchain for $(format_target)..."; run_get_clang; done - fi - log "Toolchain download complete!" - ;; - env) - # env requires exactly one target - case "$target" in - *,*) die "env requires exactly one target" ;; - esac - resolve_target "$target" "$libc" - print_env - ;; - *) - die "unknown command: $cmd" - ;; - esac -} - -main "$@" From fb02231e9e2cd0e1e1ef1781ca29c25f64143352 Mon Sep 17 00:00:00 2001 From: iHsin Date: Tue, 9 Jun 2026 14:58:18 +0800 Subject: [PATCH 4/4] Use Git bash for get-clang.sh on Windows On GitHub's Windows runners a bare `bash` resolves to C:\Windows\System32\bash.exe (WSL), which has no distro installed, so get-clang.sh failed instantly with "Windows Subsystem for Linux has no installed distributions". Resolve Git's bash explicitly instead. All non-Windows jobs (linux, linux-musl, darwin/iOS/tvOS) already passed on the cargo-script port; this was the only Windows-specific failure. Co-Authored-By: Claude Opus 4.8 --- scripts/build-naive.rs | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/scripts/build-naive.rs b/scripts/build-naive.rs index 1727597c..fb009469 100755 --- a/scripts/build-naive.rs +++ b/scripts/build-naive.rs @@ -298,6 +298,37 @@ fn run(dir: &Path, program: &str, args: &[&str], envs: &[(&str, &str)]) { } } +/// Locate a real bash to run get-clang.sh. On Windows a bare `bash` resolves to +/// C:\Windows\System32\bash.exe (WSL), which has no distro on CI runners, so we +/// must use Git's bash explicitly. +fn bash_program() -> String { + if cfg!(windows) { + let candidates = [ + r"C:\Program Files\Git\bin\bash.exe", + r"C:\Program Files\Git\usr\bin\bash.exe", + r"C:\Program Files (x86)\Git\bin\bash.exe", + ]; + for c in candidates { + if Path::new(c).is_file() { + return c.to_string(); + } + } + if let Some(path) = env::var_os("PATH") { + for dir in env::split_paths(&path) { + let lower = dir.to_string_lossy().to_lowercase(); + if lower.contains(r"\windows\system32") || lower.contains(r"\windows\sysnative") { + continue; // skip WSL's bash + } + let cand = dir.join("bash.exe"); + if cand.is_file() { + return cand.to_string_lossy().to_string(); + } + } + } + } + "bash".to_string() +} + fn find_in_path(name: &str) -> Option { let exts: &[&str] = if cfg!(windows) { &["", ".exe", ".bat", ".cmd"] } else { &[""] }; let path = env::var_os("PATH")?; @@ -317,11 +348,13 @@ fn find_in_path(name: &str) -> Option { // --------------------------------------------------------------------------- fn run_get_clang(src: &Path, t: &Target) { + let bash = bash_program(); + if host_goos() == "linux" && (t.os == "linux" || t.os == "android" || t.os == "openwrt") { let hcpu = arch_to_cpu(host_goarch()); let host_flags = format!("target_os=\"linux\" target_cpu=\"{hcpu}\""); log(&format!("get-clang.sh (host sysroot) EXTRA_FLAGS={host_flags}")); - run(src, "bash", &["./get-clang.sh"], &[("EXTRA_FLAGS", &host_flags)]); + run(src, &bash, &["./get-clang.sh"], &[("EXTRA_FLAGS", &host_flags)]); let host_src = glibc_sysroot_path(src, &hcpu); let host_dst = src.join("build/linux/debian_bullseye_amd64-sysroot"); @@ -339,10 +372,10 @@ fn run_get_clang(src: &Path, t: &Target) { ow.target, ow.subtarget, ow.arch, ow.release, ow.gcc ); log(&format!("get-clang.sh EXTRA_FLAGS={extra} OPENWRT_FLAGS={owf}")); - run(src, "bash", &["./get-clang.sh"], &[("EXTRA_FLAGS", &extra), ("OPENWRT_FLAGS", &owf)]); + run(src, &bash, &["./get-clang.sh"], &[("EXTRA_FLAGS", &extra), ("OPENWRT_FLAGS", &owf)]); } else { log(&format!("get-clang.sh EXTRA_FLAGS={extra}")); - run(src, "bash", &["./get-clang.sh"], &[("EXTRA_FLAGS", &extra)]); + run(src, &bash, &["./get-clang.sh"], &[("EXTRA_FLAGS", &extra)]); } }