diff --git a/.devcontainer/devcontainer-lock.json b/.devcontainer/devcontainer-lock.json new file mode 100644 index 0000000..de51286 --- /dev/null +++ b/.devcontainer/devcontainer-lock.json @@ -0,0 +1,9 @@ +{ + "features": { + "ghcr.io/devcontainers/features/github-cli:1": { + "version": "1.1.3", + "resolved": "ghcr.io/devcontainers/features/github-cli@sha256:bd7ab48a832228f633239277552c30b353867fef2e5b037e064b4e64f0b843f2", + "integrity": "sha256:bd7ab48a832228f633239277552c30b353867fef2e5b037e064b4e64f0b843f2" + } + } +} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..0fb3ff8 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,46 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/rust +{ + "name": "componentized-sockets", + "image": "mcr.microsoft.com/devcontainers/rust", + // Use 'mounts' to make the cargo cache persistent in a Docker Volume. + "mounts": [ + { + "source": "devcontainer-cargo-cache-${devcontainerId}", + "target": "/usr/local/cargo", + "type": "volume" + }, + { + "source": "devcontainer-rustup-cache-${devcontainerId}", + "target": "/usr/local/rustup", + "type": "volume" + } + ], + // Features to add to the dev container. More info: https://containers.dev/features. + "features": { + "ghcr.io/devcontainers/features/github-cli:1": {} + }, + // Use 'postCreateCommand' to run commands after the container is created. + "postCreateCommand": "echo \"export \\\"PATH=$(make -s tools-path):\\${PATH}\\\"\" >> ~/.bashrc && curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash && cargo check && make tools", + // Configure tool-specific properties. + "customizations": { + // Configure properties specific to VS Code. + "vscode": { + // Set *default* container specific settings.json values on container create. + "settings": { + "dev.containers.githubCLILoginWithToken": true + }, + "extensions": [ + "bytecodealliance.wit-idl", + "github.vscode-github-actions", + "ms-azuretools.vscode-containers", + "rust-lang.rust-analyzer", + "streetsidesoftware.code-spell-checker", + "tamasfe.even-better-toml", + "vadimcn.vscode-lldb" + ] + } + } + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b136473..8ec1844 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,18 +1,22 @@ version: 2 updates: - - package-ecosystem: github-actions - directory: "/" - schedule: - interval: daily - - package-ecosystem: cargo - directory: "/" - schedule: - interval: daily - - package-ecosystem: cargo - directory: "/tools" - schedule: - interval: daily - - package-ecosystem: rust-toolchain - directory: "/" - schedule: - interval: daily +- package-ecosystem: github-actions + directory: "/" + schedule: + interval: daily +- package-ecosystem: cargo + directory: "/" + schedule: + interval: daily +- package-ecosystem: cargo + directory: "/tools" + schedule: + interval: daily +- package-ecosystem: rust-toolchain + directory: "/" + schedule: + interval: daily +- package-ecosystem: devcontainers + directory: "/" + schedule: + interval: weekly diff --git a/.github/workflows/bump-version.yaml b/.github/workflows/bump-version.yaml new file mode 100644 index 0000000..7321cf4 --- /dev/null +++ b/.github/workflows/bump-version.yaml @@ -0,0 +1,159 @@ +name: Bump version + +on: + workflow_dispatch: + inputs: + version: + description: The new version of the interface package and crates, e.g. 0.1.0 or 0.2.0-dev + required: true + type: string + default: "0.1.0-dev" # the current version, kept current by scripts/bump-version.sh + +jobs: + # bumps the version with read only access, the changes are handed to the pull-request job as a + # patch so the third party actions used to build never run with write access + bump: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - uses: actions-rust-lang/setup-rust-toolchain@v2 + - name: Install cargo binstall + uses: cargo-bins/cargo-binstall@main + - name: Install tools + run: | + make tools + make -s tools-path >> "${GITHUB_PATH}" + - name: Bump version + # also fetches the wit dependencies for the new version, and builds and tests the components + run: scripts/bump-version.sh "${VERSION}" + env: + VERSION: ${{ inputs.version }} + - name: Collect changes + run: | + git add --all + git diff --cached --binary > bump-version.patch + - name: Upload changes + uses: actions/upload-artifact@v7 + with: + name: bump-version.patch + path: bump-version.patch + if-no-files-found: error + retention-days: 1 + + # opens the pull request using only first party actions and the gh cli + pull-request: + needs: + - bump + runs-on: ubuntu-latest + # the branch and pull request are created with a token for the custodian GitHub App rather than + # the GITHUB_TOKEN, which can't change workflow files and doesn't trigger the CI workflow + permissions: + contents: read + env: + VERSION: ${{ inputs.version }} + steps: + - name: Check custodian app credentials + run: | + if [ -z "${CLIENT_ID}" ] || [ -z "${PRIVATE_KEY}" ] ; then + echo "::error::the CUSTODIAN_CLIENT_ID and CUSTODIAN_PRIVATE_KEY secrets must be available to this repository, the private key of the custodian GitHub App is needed to create a token" + exit 1 + fi + env: + CLIENT_ID: ${{ secrets.CUSTODIAN_CLIENT_ID }} + PRIVATE_KEY: ${{ secrets.CUSTODIAN_PRIVATE_KEY }} + - name: Create custodian app token + id: app-token + uses: actions/create-github-app-token@v3 + with: + client-id: ${{ secrets.CUSTODIAN_CLIENT_ID }} + private-key: ${{ secrets.CUSTODIAN_PRIVATE_KEY }} + # only this repository, with only the permissions the bump needs + repositories: ${{ github.event.repository.name }} + permission-contents: write + permission-pull-requests: write + # the bump changes the default version in this workflow + permission-workflows: write + - uses: actions/checkout@v7 + with: + persist-credentials: false + - name: Download changes + uses: actions/download-artifact@v8 + with: + name: bump-version.patch + path: ${{ runner.temp }} + - name: Read current version + # the checkout is before the bump, the crates' workspace version is the current version + run: | + current=$( sed -n '/^\[workspace.package\]/,/^\[/s/^version = "\(.*\)"$/\1/p' Cargo.toml ) + echo "CURRENT_VERSION=${current}" >> "${GITHUB_ENV}" + - name: Commit changes + # the commit is created with the REST API, as the app's token can't push. The patch is applied + # locally only to find the changed files and their modes. + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + APP_SLUG: ${{ steps.app-token.outputs.app-slug }} + run: | + branch="bump-version/${VERSION}" + api="repos/${GITHUB_REPOSITORY}" + base=$( git rev-parse HEAD ) + git apply --index "${RUNNER_TEMP}/bump-version.patch" + + # a blob for each changed file, or a null sha for a deleted file + entries="${RUNNER_TEMP}/tree-entries.json" + echo '[]' > "${entries}" + git diff --cached --no-renames --name-status "${base}" | while IFS=$'\t' read -r status path ; do + if [ "${status}" = "D" ] ; then + entry=$( jq -n --arg path "${path}" '{path: $path, mode: "100644", type: "blob", sha: null}' ) + else + mode=$( git ls-files --stage -- "${path}" | cut -d' ' -f1 ) + sha=$( base64 < "${path}" | tr -d '\n' | jq -Rs '{encoding: "base64", content: .}' | gh api --method POST "${api}/git/blobs" --input - --jq .sha ) + entry=$( jq -n --arg path "${path}" --arg mode "${mode}" --arg sha "${sha}" '{path: $path, mode: $mode, type: "blob", sha: $sha}' ) + fi + jq --argjson entry "${entry}" '. + [$entry]' "${entries}" > "${entries}.tmp" && mv "${entries}.tmp" "${entries}" + echo "${status} ${path}" + done + tree=$( jq --arg base "$( git rev-parse "${base}^{tree}" )" '{base_tree: $base, tree: .}' "${entries}" | gh api --method POST "${api}/git/trees" --input - --jq .sha ) + + # authored and signed off (DCO) by the user who triggered the workflow, with their GitHub + # noreply email so the commit is attributed to them without exposing their email address. + # Committed by the custodian app's bot, which made the commit on their behalf. The commit is + # unsigned, GitHub only signs commits it attributes entirely to the app. + name=$( gh api "users/${GITHUB_ACTOR}" --jq '.name // .login' ) + name="${name:-${GITHUB_ACTOR}}" + email="${GITHUB_ACTOR_ID}+${GITHUB_ACTOR}@users.noreply.github.com" + bot="${APP_SLUG}[bot]" + bot_email="$( gh api "users/${bot}" --jq .id )+${bot}@users.noreply.github.com" + commit=$( jq -n \ + --arg message "$( printf 'Bump version from %s to %s\n\nSigned-off-by: %s <%s>' "${CURRENT_VERSION}" "${VERSION}" "${name}" "${email}" )" \ + --arg tree "${tree}" --arg parent "${base}" --arg name "${name}" --arg email "${email}" \ + --arg bot "${bot}" --arg bot_email "${bot_email}" \ + '{message: $message, tree: $tree, parents: [$parent], author: {name: $name, email: $email}, committer: {name: $bot, email: $bot_email}}' \ + | gh api --method POST "${api}/git/commits" --input - --jq .sha ) + echo "created commit ${commit}" + + # points the branch at the commit, replacing the branch left by an earlier run for the same version + if gh api "${api}/git/ref/heads/${branch}" --silent 2> /dev/null ; then + gh api --method PATCH "${api}/git/refs/heads/${branch}" -f sha="${commit}" -F force=true --silent + else + gh api --method POST "${api}/git/refs" -f ref="refs/heads/${branch}" -f sha="${commit}" --silent + fi + - name: Open pull request + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + run: | + branch="bump-version/${VERSION}" + if [ -n "$( gh pr list --head "${branch}" --state open --json number --jq '.[].number' )" ] ; then + echo "A pull request for ${branch} is already open, updated by the new commit" + exit 0 + fi + gh pr create \ + --base "${GITHUB_REF_NAME}" \ + --head "${branch}" \ + --title "Bump version from \`${CURRENT_VERSION}\` to \`${VERSION}\`" \ + --body "Bumps the wit package and crates from \`${CURRENT_VERSION}\` to \`${VERSION}\`. + + Triggered by @${GITHUB_ACTOR} from the [Bump version](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}) workflow." diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 160bb08..cd547f3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -21,7 +21,7 @@ jobs: # the versions pinned in tools/Cargo.toml, on the path for later steps run: | make tools - echo "${PWD}/target/tools/bin" >> "${GITHUB_PATH}" + make -s tools-path >> "${GITHUB_PATH}" - name: Sync wit run: make wit - name: Check for drift in generated wit @@ -73,7 +73,7 @@ jobs: - name: Install tools run: | make tools - echo "${PWD}/target/tools/bin" >> "${GITHUB_PATH}" + make -s tools-path >> "${GITHUB_PATH}" - name: Install cosign uses: sigstore/cosign-installer@v4.1.2 - name: Download components.tar diff --git a/.gitignore b/.gitignore index 43a6702..f3027d2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ /target .DS_Store /tools/Cargo.lock +# wit dependencies, fetched by `make wit` from the wkg.toml and wkg.lock files +**/wit/deps/ diff --git a/Makefile b/Makefile index 6bb7bea..8e35ec1 100644 --- a/Makefile +++ b/Makefile @@ -1,38 +1,47 @@ SHELL := /bin/bash -WKG_CONFIG := $(CURDIR)/.config/wasm-pkg/config.toml - export RUST_BACKTRACE ?= 1 export WASMTIME_BACKTRACE_DETAILS ?= 1 -COMPONENTS_DIR := $(abspath target/components) -TOOLS_DIR := $(abspath target/tools) -export PATH := $(TOOLS_DIR)/bin:$(PATH) +COMPONENTS_DIR := target/components +TOOLS_DIR := target/tools/$(shell rustc --print host-tuple) +# absolute, tools also run from other directories, e.g. `cd components && wkg fetch` +export PATH := $(abspath $(TOOLS_DIR))/bin:$(PATH) # cargo binstall downloads prebuilt binaries, without it the tools are built with cargo install CARGO_INSTALL := $(if $(shell command -v cargo-binstall 2> /dev/null),cargo binstall --no-confirm --disable-telemetry,cargo install) -COMPONENTS = $(sort $(notdir $(patsubst %/,%,$(dir $(wildcard $(addprefix components/*/,*.properties *.wac *.wkg Cargo.toml)))))) -TOOLS := static-config wac-cli wasm-tools wkg +COMPONENTS = $(sort $(foreach file,$(wildcard $(addprefix components/*/,wit/*.constants.wit *.properties *.wac *.wkg Cargo.toml)),$(word 2,$(subst /, ,$(file))))) +TOOLS := componentized-constants-cli static-config wac-cli wasm-tools wasmtime-cli wkg + +export WKG_CONFIG_FILE := $(abspath .config/wasm-pkg/config.toml) + +# a path relative to the root of the repository, e.g. `wit` for `components/../wit` +relpath = $(if $(filter $(CURDIR),$(abspath $(1))),.,$(patsubst $(CURDIR)/%,%,$(abspath $(1)))) + .PHONY: all all: components .PHONY: clean -clean: +clean: clean-wit cargo clean .PHONY: clean-components -clean-components: +clean-components: clean-wit rm -rf ${COMPONENTS_DIR} +.PHONY: clean-wit ## Remove the fetched wit dependencies, fetched again by `make wit` +clean-wit: + rm -rf $(WIT_DEPS) + .PHONY: test test: components cargo test --workspace tool_version = $(shell sed -n 's/^$(1) = "=\(.*\)"$$/\1/p' tools/Cargo.toml) -# a stamp naming the version of a tool installed in target/tools/bin, e.g. `wkg@0.16.1`, the binary +# a stamp naming the version of a tool installed in $(TOOLS_DIR)/bin, e.g. `wkg@0.16.1`, the binary # does not say which version it is. Bumping the pinned version names a stamp that does not exist yet, # so the tool is installed again. tool = $(TOOLS_DIR)/.installed/$(1)@$(call tool_version,$(1)) @@ -40,6 +49,10 @@ tool = $(TOOLS_DIR)/.installed/$(1)@$(call tool_version,$(1)) .PHONY: tools ## Install the cli tools pinned in tools/Cargo.toml tools: $(foreach name,$(TOOLS),$(call tool,$(name))) +.PHONY: tools-path ## Print the directory of the installed tools for this platform, to add to the PATH +tools-path: + @echo $(abspath $(TOOLS_DIR))/bin + define INSTALL_TOOL $(call tool,$1): @@ -61,7 +74,15 @@ define BUILD_COMPONENT .PHONY: components/$1 components/$1: ${COMPONENTS_DIR}/$1/$1.wasm ${COMPONENTS_DIR}/$1/$1.debug.wasm -ifneq ($(wildcard components/$1/$1.properties),) +ifneq ($(wildcard components/$1/wit/$1.constants.wit),) + +${COMPONENTS_DIR}/$1/$1.wasm: components/$1/wit/deps ${COMPONENTS_DIR}/$1/README.md | $(call tool,componentized-constants-cli) + constants --wit components/$1/wit -o ${COMPONENTS_DIR}/$1/$1.wasm + +${COMPONENTS_DIR}/$1/$1.debug.wasm: components/$1/wit/deps ${COMPONENTS_DIR}/$1/README.md | $(call tool,componentized-constants-cli) + constants --wit components/$1/wit -o ${COMPONENTS_DIR}/$1/$1.debug.wasm + +else ifneq ($(wildcard components/$1/$1.properties),) ${COMPONENTS_DIR}/$1/$1.wasm: components/$1/$1.properties ${COMPONENTS_DIR}/$1/README.md | $(call tool,static-config) static-config -f components/$1/$1.properties -o ${COMPONENTS_DIR}/$1/$1.wasm @@ -91,11 +112,11 @@ ${COMPONENTS_DIR}/$1/$1.debug.wasm: components/$1/$1.wkg ${COMPONENTS_DIR}/$1/RE # cargo is checked last, other strategies may have a Cargo.toml for tests of non-rust sources else ifneq ($(wildcard components/$1/Cargo.toml),) -${COMPONENTS_DIR}/$1/$1.wasm: Cargo.toml Cargo.lock components/wit/deps $(shell find components/$1 -type f) $(shell find crates -type f) ${COMPONENTS_DIR}/$1/README.md | $(call tool,wasm-tools) +${COMPONENTS_DIR}/$1/$1.wasm: Cargo.toml Cargo.lock components/wit/deps $(shell find components/$1 -type f) $(shell find crates -type f 2> /dev/null) ${COMPONENTS_DIR}/$1/README.md | $(call tool,wasm-tools) cargo build -p $1 --target wasm32-unknown-unknown --release wasm-tools component new target/wasm32-unknown-unknown/release/$(subst -,_,$1).wasm -o ${COMPONENTS_DIR}/$1/$1.wasm -${COMPONENTS_DIR}/$1/$1.debug.wasm: Cargo.toml Cargo.lock components/wit/deps $(shell find components/$1 -type f) $(shell find crates -type f) ${COMPONENTS_DIR}/$1/README.md | $(call tool,wasm-tools) +${COMPONENTS_DIR}/$1/$1.debug.wasm: Cargo.toml Cargo.lock components/wit/deps $(shell find components/$1 -type f) $(shell find crates -type f 2> /dev/null) ${COMPONENTS_DIR}/$1/README.md | $(call tool,wasm-tools) cargo build --target wasm32-unknown-unknown -p $1 wasm-tools component new target/wasm32-unknown-unknown/debug/$(subst -,_,$1).wasm -o ${COMPONENTS_DIR}/$1/$1.debug.wasm @@ -114,21 +135,27 @@ ${COMPONENTS_DIR}/interface.wasm: wit/deps README.md | $(call tool,wkg) wkg build -o ${COMPONENTS_DIR}/interface.wasm @cp README.md ${COMPONENTS_DIR}/README.md +# directories with a wkg.toml, each fetches the dependencies of its wit directory into wit/deps, +# e.g. `.` and `components` +WKG_DIRS := $(sort $(patsubst ./%,%,$(patsubst %/,%,$(dir $(shell find . -name wkg.toml -not -path './target/*' -not -path '*/deps/*'))))) + +# the wit/deps directory of a directory with a wkg.toml, e.g. `wit/deps` for `.` +wit_deps = $(patsubst ./%,%,$(1)/wit/deps) + +WIT_DEPS := $(foreach dir,$(WKG_DIRS),$(call wit_deps,$(dir))) + .PHONY: wit -wit: wit/deps components/wit/deps +wit: $(WIT_DEPS) -.PHONY: bump-interface-version ## Bump the interface package version, e.g. INTERFACE_VERSION=0.1.0 -bump-interface-version: -ifndef INTERFACE_VERSION - $(error INTERFACE_VERSION is undefined) -endif - scripts/bump-interface-version.sh $(INTERFACE_VERSION) +define FETCH_WIT + +# a package overridden with a local path, e.g. `{ path = "../wit" }`, has its dependencies fetched first +$(call wit_deps,$1): $1/wkg.toml $1/wkg.lock $(shell find $1/wit -type f -name "*.wit" -not -path "*/deps/*") $(foreach path,$(shell sed -n 's/.*path *= *"\(.*\)".*/\1/p' $1/wkg.toml),$(call relpath,$1/$(path))/deps) | $(call tool,wkg) + $(if $(filter .,$1),,cd $1 && )wkg fetch -wit/deps: wkg.toml $(shell find wit -type f -name "*.wit" -not -path "deps") | $(call tool,wkg) - wkg fetch --config $(WKG_CONFIG) +endef -components/wit/deps: wit/deps components/wkg.toml $(shell find components/wit -type f -name "*.wit" -not -path "deps") | $(call tool,wkg) - ( cd components && wkg fetch --config $(WKG_CONFIG) ) +$(foreach dir,$(WKG_DIRS),$(eval $(call FETCH_WIT,$(dir)))) # sign published components with cosign, `SIGN=false` to push without signing, e.g. to a local registry SIGN ?= true diff --git a/README.md b/README.md index a201887..c07459e 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,9 @@ Prereqs: make components ``` -The cli tools the build uses, [`static-config`](https://github.com/componentized/static-config), [`wasm-tools`](https://github.com/bytecodealliance/wasm-tools), [`wac`](https://github.com/bytecodealliance/wac) and [`wkg`](https://github.com/bytecodealliance/wasm-pkg-tools), are pinned in [`tools/Cargo.toml`](./tools/Cargo.toml) and installed into `target/tools` as needed, or ahead of time with `make tools`. Dependabot bumps the pinned versions. +The build creates each component in [`components`](./components) into `target/components`, e.g. the gate at `target/components/gate/gate.wasm`, along with `target/components/interface.wasm`, the `componentized:sockets` WIT package. Each component is also built with debug info, e.g. `target/components/gate/gate.debug.wasm`. + +The cli tools the build uses, [`static-config`](https://github.com/componentized/static-config), [`wasm-tools`](https://github.com/bytecodealliance/wasm-tools), [`wac`](https://github.com/bytecodealliance/wac), [`wasmtime`](https://github.com/bytecodealliance/wasmtime) and [`wkg`](https://github.com/bytecodealliance/wasm-pkg-tools), are pinned in [`tools/Cargo.toml`](./tools/Cargo.toml) and installed into `target/tools/`, e.g. `target/tools/aarch64-apple-darwin`, as needed, or ahead of time with `make tools`. Dependabot bumps the pinned versions. ## Community diff --git a/components/wit/deps/componentized-sockets-0.1.0-dev/package.wit b/components/wit/deps/componentized-sockets-0.1.0-dev/package.wit deleted file mode 100644 index a80a61d..0000000 --- a/components/wit/deps/componentized-sockets-0.1.0-dev/package.wit +++ /dev/null @@ -1,223 +0,0 @@ -package componentized:sockets@0.1.0-dev; - -/// Access control for wasi:sockets. -/// -/// A gate consults a latch before performing each socket operation. The latch either defers, -/// raising no objection, or denies the operation. A latch never grants an operation, an -/// operation proceeds when the latch defers. Several latches can be aggregated into one, the -/// operation is denied when any of them denies it. -/// -/// Deciding and acting on a decision are separate steps. `authorize` decides without side -/// effects, then the gate reports the final decision to `observe-decision`, where a latch updates -/// any state it keeps. A latch aggregated with others may not be asked to authorize an operation -/// another latch already denied, but it always observes the final decision. -@since(version = 0.1.0-dev) -interface latch { - @since(version = 0.1.0-dev) - use wasi:sockets/types@0.3.0.{ip-address, ip-address-family, ip-socket-address, tcp-socket, udp-socket}; - - /// Arguments to wasi:sockets/ip-name-lookup#resolve-addresses. - @since(version = 0.1.0-dev) - record resolve-addresses-args { - /// The name to look up, as passed by the caller. DNS search paths are not applied. - name: string, - } - - /// Items returned from calls to wasi:sockets/ip-name-lookup#resolve-addresses. - @since(version = 0.1.0-dev) - record resolve-addresses-returns-item { - /// An address the lookup returned. - ip-address: ip-address, - } - - /// Operations of the wasi:sockets/ip-name-lookup interface. - @since(version = 0.1.0-dev) - variant ip-name-lookup-operation { - /// Authorized before the name is looked up. A denial fails the lookup. - resolve-addresses(resolve-addresses-args), - /// Authorized for each address the lookup returned. A denied address is removed from the - /// result, the lookup succeeds with the remaining addresses. - resolve-addresses-return(resolve-addresses-returns-item), - } - - /// Arguments to wasi:sockets/types#tcp-socket.create. - @since(version = 0.1.0-dev) - record tcp-socket-create-args { - address-family: ip-address-family, - } - - /// Arguments to wasi:sockets/types#tcp-socket.bind. - @since(version = 0.1.0-dev) - record tcp-socket-bind-args { - local-address: ip-socket-address, - } - - /// Arguments to wasi:sockets/types#tcp-socket.connect. - @since(version = 0.1.0-dev) - record tcp-socket-connect-args { - remote-address: ip-socket-address, - } - - /// Operations of the wasi:sockets/types#tcp-socket interface. - /// - /// The borrowed socket is the socket the operation is performed on, a latch may query it, for - /// example for its local or remote address. - @since(version = 0.1.0-dev) - variant tcp-socket-operation { - /// Authorized before the socket is created. A denial fails the create. - create(tcp-socket-create-args), - /// Authorized before the socket is bound. A denial fails the bind. - bind(tuple, tcp-socket-bind-args>), - /// Authorized before the socket connects. A denial fails the connect, nothing is sent to - /// the remote address. - connect(tuple, tcp-socket-connect-args>), - /// Authorized before the socket starts listening. A denial fails the listen. - listen(tuple>), - /// Authorized for each connection a listening socket accepts, the borrowed socket is the - /// accepted connection. A denied connection is closed before it reaches the caller, the - /// listening socket keeps accepting other connections. - listen-connection(tuple>), - /// Authorized once per socket before data is sent, `send` can only be called once and - /// streams all of its data. A denial fails the send. - send(tuple>), - /// Authorized once per socket before data is received, `receive` can only be called once - /// and streams all of its data. A denial fails the receive. - receive(tuple>), - } - - /// Arguments to wasi:sockets/types#udp-socket.create. - @since(version = 0.1.0-dev) - record udp-socket-create-args { - address-family: ip-address-family, - } - - /// Arguments to wasi:sockets/types#udp-socket.bind. - @since(version = 0.1.0-dev) - record udp-socket-bind-args { - local-address: ip-socket-address, - } - - /// Arguments to wasi:sockets/types#udp-socket.connect. - @since(version = 0.1.0-dev) - record udp-socket-connect-args { - remote-address: ip-socket-address, - } - - /// Arguments to wasi:sockets/types#udp-socket.send. - @since(version = 0.1.0-dev) - record udp-socket-send-args { - /// The length of the datagram in bytes. - data-length: u64, - /// Where the datagram is sent. `none` for a connected socket sending to the remote - /// address it is connected to. - remote-address: option, - } - - /// Returned value from calls to wasi:sockets/types#udp-socket.receive. - @since(version = 0.1.0-dev) - record udp-socket-receive-returns { - /// The length of the datagram in bytes. - data-length: u64, - /// Where the datagram was sent from. UDP source addresses are not authenticated. - remote-address: ip-socket-address, - } - - /// Operations of wasi:sockets/types#udp-socket. - /// - /// The borrowed socket is the socket the operation is performed on, a latch may query it, for - /// example for its local or remote address. - @since(version = 0.1.0-dev) - variant udp-socket-operation { - /// Authorized before the socket is created. A denial fails the create. - create(udp-socket-create-args), - /// Authorized before the socket is bound. A denial fails the bind. - bind(tuple, udp-socket-bind-args>), - /// Authorized before the socket connects. A denial fails the connect. - connect(tuple, udp-socket-connect-args>), - /// Authorized for each datagram before it is sent. A denial fails the send, nothing is - /// sent. - send(tuple, udp-socket-send-args>), - /// Authorized for each datagram after it is received, before it reaches the caller. A - /// denied datagram is dropped and the receive continues with the next datagram, so a - /// sender cannot fail the caller's receive. - receive(tuple, udp-socket-receive-returns>), - } - - /// Operations for the wasi:sockets package. - @since(version = 0.1.0-dev) - variant operation { - ip-name-lookup(ip-name-lookup-operation), - tcp-socket(tcp-socket-operation), - udp-socket(udp-socket-operation), - } - - /// The name of a latch component, identifying which latch reported an error. - @since(version = 0.1.0-dev) - type latch-name = string; - - /// A latch was unable to decide or observe. The gate fails the operation, except for an - /// accepted connection, which is closed as if it were denied. - @since(version = 0.1.0-dev) - variant error-code { - /// The latch's configuration is invalid, the cause is logged by the latch. - invalid-config(latch-name), - /// The latch was unable to act on the final decision. - observation-failed(latch-name), - /// Any other error. - other(option), - } - - /// The reason an operation is denied, returned to the caller as the wasi:sockets error code. - @since(version = 0.1.0-dev) - variant sockets-error-code { - /// Access denied. - /// - /// POSIX equivalent: EACCES, EPERM - access-denied, - /// One of the arguments is invalid. - /// - /// POSIX equivalent: EINVAL, EDESTADDRREQ, EAFNOSUPPORT - invalid-argument, - /// A catch-all for errors not captured by the existing variants. - /// Implementations can use this to extend the error type without - /// breaking existing code. - other(option), - } - - /// Whether the operation is allowed to proceed. - @since(version = 0.1.0-dev) - variant decision { - /// No objection to the operation. - deferred, - /// The operation must not proceed. The error code is returned to the caller for - /// operations that fail. For traffic that is dropped instead, like an accepted connection - /// or a received datagram, it is only logged. - denied(sockets-error-code), - } - - /// Decide whether the operation may proceed. - /// - /// Must not have side effects. When latches are aggregated, a latch is not asked about an - /// operation another latch already denied, so state changed here could disagree with what - /// actually happened. Act on decisions in `observe-decision` instead. - @since(version = 0.1.0-dev) - authorize: func(operation: operation) -> result; - - /// Observe the final decision for an operation, after `authorize` returned a decision. - /// - /// Called for both deferred and denied operations, including operations this latch was not - /// asked to authorize because an aggregated latch denied them first. The final decision is - /// what the gate enforces, so this is where a latch updates any state it keeps, for example - /// remembering peers the caller is allowed to exchange traffic with. Not called when - /// `authorize` returned an error. - /// - /// An error fails the operation, since the latch could not act on the decision. - @since(version = 0.1.0-dev) - observe-decision: func(final-decision: decision, operation: operation) -> result<_, error-code>; -} - -world imports { - import wasi:clocks/types@0.3.0; - import wasi:sockets/types@0.3.0; - import latch; -} diff --git a/components/wit/deps/wasi-clocks-0.3.0/package.wit b/components/wit/deps/wasi-clocks-0.3.0/package.wit deleted file mode 100644 index dd7eafd..0000000 --- a/components/wit/deps/wasi-clocks-0.3.0/package.wit +++ /dev/null @@ -1,6 +0,0 @@ -package wasi:clocks@0.3.0; - -interface types { - type duration = u64; -} - diff --git a/components/wit/deps/wasi-config-0.2.0-rc.1/package.wit b/components/wit/deps/wasi-config-0.2.0-rc.1/package.wit deleted file mode 100644 index d8950ee..0000000 --- a/components/wit/deps/wasi-config-0.2.0-rc.1/package.wit +++ /dev/null @@ -1,33 +0,0 @@ -package wasi:config@0.2.0-rc.1; - -interface store { - /// An error type that encapsulates the different errors that can occur fetching configuration values. - variant error { - /// This indicates an error from an "upstream" config source. - /// As this could be almost _anything_ (such as Vault, Kubernetes ConfigMaps, KeyValue buckets, etc), - /// the error message is a string. - upstream(string), - /// This indicates an error from an I/O operation. - /// As this could be almost _anything_ (such as a file read, network connection, etc), - /// the error message is a string. - /// Depending on how this ends up being consumed, - /// we may consider moving this to use the `wasi:io/error` type instead. - /// For simplicity right now in supporting multiple implementations, it is being left as a string. - io(string), - } - - /// Gets a configuration value of type `string` associated with the `key`. - /// - /// The value is returned as an `option`. If the key is not found, - /// `Ok(none)` is returned. If an error occurs, an `Err(error)` is returned. - get: func(key: string) -> result, error>; - - /// Gets a list of configuration key-value pairs of type `string`. - /// - /// If an error occurs, an `Err(error)` is returned. - get-all: func() -> result>, error>; -} - -world imports { - import store; -} diff --git a/components/wit/deps/wasi-logging-0.1.0-draft/package.wit b/components/wit/deps/wasi-logging-0.1.0-draft/package.wit deleted file mode 100644 index 164cb5b..0000000 --- a/components/wit/deps/wasi-logging-0.1.0-draft/package.wit +++ /dev/null @@ -1,36 +0,0 @@ -package wasi:logging@0.1.0-draft; - -/// WASI Logging is a logging API intended to let users emit log messages with -/// simple priority levels and context values. -interface logging { - /// A log level, describing a kind of message. - enum level { - /// Describes messages about the values of variables and the flow of - /// control within a program. - trace, - /// Describes messages likely to be of interest to someone debugging a - /// program. - debug, - /// Describes messages likely to be of interest to someone monitoring a - /// program. - info, - /// Describes messages indicating hazardous situations. - warn, - /// Describes messages indicating serious errors. - error, - /// Describes messages indicating fatal errors. - critical, - } - - /// Emit a log message. - /// - /// A log message has a `level` describing what kind of message is being - /// sent, a context, which is an uninterpreted string meant to help - /// consumers group similar messages, and a string containing the message - /// text. - log: func(level: level, context: string, message: string); -} - -world imports { - import logging; -} diff --git a/components/wit/deps/wasi-random-0.3.0/package.wit b/components/wit/deps/wasi-random-0.3.0/package.wit deleted file mode 100644 index 0b9a55f..0000000 --- a/components/wit/deps/wasi-random-0.3.0/package.wit +++ /dev/null @@ -1,107 +0,0 @@ -package wasi:random@0.3.0; - -/// The insecure-seed interface for seeding hash-map DoS resistance. -/// -/// It is intended to be portable at least between Unix-family platforms and -/// Windows. -@since(version = 0.3.0) -interface insecure-seed { - /// Return a 128-bit value that may contain a pseudo-random value. - /// - /// The returned value is not required to be computed from a CSPRNG, and may - /// even be entirely deterministic. Host implementations are encouraged to - /// provide pseudo-random values to any program exposed to - /// attacker-controlled content, to enable DoS protection built into many - /// languages' hash-map implementations. - /// - /// This function is intended to only be called once, by a source language - /// to initialize Denial Of Service (DoS) protection in its hash-map - /// implementation. - /// - /// # Expected future evolution - /// - /// This will likely be changed to a value import, to prevent it from being - /// called multiple times and potentially used for purposes other than DoS - /// protection. - @since(version = 0.3.0) - get-insecure-seed: func() -> tuple; -} - -/// The insecure interface for insecure pseudo-random numbers. -/// -/// It is intended to be portable at least between Unix-family platforms and -/// Windows. -@since(version = 0.3.0) -interface insecure { - /// Return up to `max-len` insecure pseudo-random bytes. - /// - /// This function is not cryptographically secure. Do not use it for - /// anything related to security. - /// - /// There are no requirements on the values of the returned bytes, however - /// implementations are encouraged to return evenly distributed values with - /// a long period. - /// - /// Implementations MAY return fewer bytes than requested (a short read). - /// Callers that require exactly `max-len` bytes MUST call this function in - /// a loop until the desired number of bytes has been accumulated. - /// Implementations MUST return at least 1 byte when `max-len` is greater - /// than zero. When `max-len` is zero, implementations MUST return an empty - /// list without trapping. - @since(version = 0.3.0) - get-insecure-random-bytes: func(max-len: u64) -> list; - - /// Return an insecure pseudo-random `u64` value. - /// - /// This function returns the same type of pseudo-random data as - /// `get-insecure-random-bytes`, represented as a `u64`. - @since(version = 0.3.0) - get-insecure-random-u64: func() -> u64; -} - -/// WASI Random is a random data API. -/// -/// It is intended to be portable at least between Unix-family platforms and -/// Windows. -@since(version = 0.3.0) -interface random { - /// Return up to `max-len` cryptographically-secure random or pseudo-random - /// bytes. - /// - /// This function must produce data at least as cryptographically secure and - /// fast as an adequately seeded cryptographically-secure pseudo-random - /// number generator (CSPRNG). It must not block, from the perspective of - /// the calling program, under any circumstances, including on the first - /// request and on requests for numbers of bytes. The returned data must - /// always be unpredictable. - /// - /// Implementations MAY return fewer bytes than requested (a short read). - /// Callers that require exactly `max-len` bytes MUST call this function in - /// a loop until the desired number of bytes has been accumulated. - /// Implementations MUST return at least 1 byte when `max-len` is greater - /// than zero. When `max-len` is zero, implementations MUST return an empty - /// list without trapping. - /// - /// This function must always return fresh data. Deterministic environments - /// must omit this function, rather than implementing it with deterministic - /// data. - @since(version = 0.3.0) - get-random-bytes: func(max-len: u64) -> list; - - /// Return a cryptographically-secure random or pseudo-random `u64` value. - /// - /// This function returns the same type of data as `get-random-bytes`, - /// represented as a `u64`. - @since(version = 0.3.0) - get-random-u64: func() -> u64; -} - -@since(version = 0.3.0) -world imports { - @since(version = 0.3.0) - import random; - @since(version = 0.3.0) - import insecure; - @since(version = 0.3.0) - import insecure-seed; -} diff --git a/components/wit/deps/wasi-sockets-0.3.0/package.wit b/components/wit/deps/wasi-sockets-0.3.0/package.wit deleted file mode 100644 index 0dd27a4..0000000 --- a/components/wit/deps/wasi-sockets-0.3.0/package.wit +++ /dev/null @@ -1,839 +0,0 @@ -package wasi:sockets@0.3.0; - -@since(version = 0.3.0) -interface types { - @since(version = 0.3.0) - use wasi:clocks/types@0.3.0.{duration}; - - /// Error codes. - /// - /// In theory, every API can return any error code. - /// In practice, API's typically only return the errors documented per API - /// combined with a couple of errors that are always possible: - /// - `other` - /// - `access-denied` - /// - `not-supported` - /// - `out-of-memory` - /// - /// See each individual API for what the POSIX equivalents are. They sometimes differ per API. - @since(version = 0.3.0) - variant error-code { - /// Access denied. - /// - /// POSIX equivalent: EACCES, EPERM - access-denied, - /// The operation is not supported. - /// - /// POSIX equivalent: EOPNOTSUPP, ENOPROTOOPT, EPFNOSUPPORT, EPROTONOSUPPORT, ESOCKTNOSUPPORT - not-supported, - /// One of the arguments is invalid. - /// - /// POSIX equivalent: EINVAL, EDESTADDRREQ, EAFNOSUPPORT - invalid-argument, - /// Not enough memory to complete the operation. - /// - /// POSIX equivalent: ENOMEM, ENOBUFS - out-of-memory, - /// The operation timed out before it could finish completely. - /// - /// POSIX equivalent: ETIMEDOUT - timeout, - /// The operation is not valid in the socket's current state. - invalid-state, - /// The local address is not available. - /// - /// POSIX equivalent: EADDRNOTAVAIL - address-not-bindable, - /// A bind operation failed because the provided address is already in - /// use or because there are no ephemeral ports available. - /// - /// POSIX equivalent: EADDRINUSE - address-in-use, - /// The remote address is not reachable. - /// - /// POSIX equivalent: EHOSTUNREACH, EHOSTDOWN, ENETDOWN, ENETUNREACH, ENONET - remote-unreachable, - /// The connection was forcefully rejected. - /// - /// POSIX equivalent: ECONNREFUSED - connection-refused, - /// A write failed because the connection was broken. - /// - /// POSIX equivalent: EPIPE - connection-broken, - /// The connection was reset. - /// - /// POSIX equivalent: ECONNRESET - connection-reset, - /// The connection was aborted. - /// - /// POSIX equivalent: ECONNABORTED - connection-aborted, - /// The size of a datagram sent to a UDP socket exceeded the maximum - /// supported size. - /// - /// POSIX equivalent: EMSGSIZE - datagram-too-large, - /// A catch-all for errors not captured by the existing variants. - /// Implementations can use this to extend the error type without - /// breaking existing code. - other(option), - } - - @since(version = 0.3.0) - enum ip-address-family { - /// Similar to `AF_INET` in POSIX. - ipv4, - /// Similar to `AF_INET6` in POSIX. - ipv6, - } - - @since(version = 0.3.0) - type ipv4-address = tuple; - - @since(version = 0.3.0) - type ipv6-address = tuple; - - @since(version = 0.3.0) - variant ip-address { - ipv4(ipv4-address), - ipv6(ipv6-address), - } - - @since(version = 0.3.0) - record ipv4-socket-address { - /// sin_port - port: u16, - /// sin_addr - address: ipv4-address, - } - - @since(version = 0.3.0) - record ipv6-socket-address { - /// sin6_port - port: u16, - /// sin6_flowinfo - flow-info: u32, - /// sin6_addr - address: ipv6-address, - /// sin6_scope_id - scope-id: u32, - } - - @since(version = 0.3.0) - variant ip-socket-address { - ipv4(ipv4-socket-address), - ipv6(ipv6-socket-address), - } - - /// A TCP socket resource. - /// - /// The socket can be in one of the following states: - /// - `unbound` - /// - `bound` (See note below) - /// - `listening` - /// - `connecting` - /// - `connected` - /// - `closed` - /// See - /// for more information. - /// - /// Note: Except where explicitly mentioned, whenever this documentation uses - /// the term "bound" without backticks it actually means: in the `bound` state *or higher*. - /// (i.e. `bound`, `listening`, `connecting` or `connected`) - /// - /// WASI uses shared ownership semantics: the `tcp-socket` handle and all - /// derived `stream` and `future` values reference a single underlying OS - /// socket: - /// - Send/receive streams remain functional after the original `tcp-socket` - /// handle is dropped. - /// - The stream returned by `listen` behaves similarly. - /// - Client sockets returned by `tcp-socket::listen` are independent and do - /// not keep the listening socket alive. - /// - /// The OS socket is closed only after the last handle is dropped. This - /// model has observable effects; for example, it affects when the local - /// port binding is released. - /// - /// In addition to the general error codes documented on the - /// `types::error-code` type, TCP socket methods may always return - /// `error(invalid-state)` when in the `closed` state. - @since(version = 0.3.0) - resource tcp-socket { - /// Create a new TCP socket. - /// - /// Similar to `socket(AF_INET or AF_INET6, SOCK_STREAM, IPPROTO_TCP)` - /// in POSIX. On IPv6 sockets, IPV6_V6ONLY is enabled by default and - /// can't be configured otherwise. - /// - /// Unlike POSIX, WASI sockets have no notion of a socket-level - /// `O_NONBLOCK` flag. Instead they fully rely on the Component Model's - /// async support. - /// - /// # Typical errors - /// - `not-supported`: The `address-family` is not supported. (EAFNOSUPPORT) - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - create: static func(address-family: ip-address-family) -> result; - /// Bind the socket to the provided IP address and port. - /// - /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is - /// left to the implementation to decide which network interface(s) to - /// bind to. If the TCP/UDP port is zero, the socket will be bound to a - /// random free port. - /// - /// Bind can be attempted multiple times on the same socket, even with - /// different arguments on each iteration. But never concurrently and - /// only as long as the previous bind failed. Once a bind succeeds, the - /// binding can't be changed anymore. - /// - /// # Typical errors - /// - `invalid-argument`: The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows) - /// - `invalid-argument`: `local-address` is not a unicast address. (EINVAL) - /// - `invalid-argument`: `local-address` is an IPv4-mapped IPv6 address. (EINVAL) - /// - `invalid-state`: The socket is already bound. (EINVAL) - /// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) - /// - `address-in-use`: Address is already in use. (EADDRINUSE) - /// - `address-not-bindable`: `local-address` is not an address that can be bound to. (EADDRNOTAVAIL) - /// - /// # Implementors note - /// The bind operation shouldn't be affected by the TIME_WAIT state of a - /// recently closed socket on the same local address. In practice this - /// means that the SO_REUSEADDR socket option should be set implicitly - /// on all platforms, except on Windows where this is the default - /// behavior and SO_REUSEADDR performs something different. - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - bind: func(local-address: ip-socket-address) -> result<_, error-code>; - /// Connect to a remote endpoint. - /// - /// On success, the socket is transitioned into the `connected` state - /// and the `remote-address` of the socket is updated. - /// The `local-address` may be updated as well, based on the best network - /// path to `remote-address`. If the socket was not already explicitly - /// bound, this function will implicitly bind the socket to a random - /// free port. - /// - /// After a failed connection attempt, the socket will be in the `closed` - /// state and the only valid action left is to `drop` the socket. A single - /// socket can not be used to connect more than once. - /// - /// # Typical errors - /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) - /// - `invalid-argument`: `remote-address` is not a unicast address. (EINVAL, ENETUNREACH on Linux, EAFNOSUPPORT on MacOS) - /// - `invalid-argument`: `remote-address` is an IPv4-mapped IPv6 address. (EINVAL, EADDRNOTAVAIL on Illumos) - /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EADDRNOTAVAIL on Windows) - /// - `invalid-argument`: The port in `remote-address` is set to 0. (EADDRNOTAVAIL on Windows) - /// - `invalid-state`: The socket is already in the `connecting` state. (EALREADY) - /// - `invalid-state`: The socket is already in the `connected` state. (EISCONN) - /// - `invalid-state`: The socket is already in the `listening` state. (EOPNOTSUPP, EINVAL on Windows) - /// - `timeout`: Connection timed out. (ETIMEDOUT) - /// - `connection-refused`: The connection was forcefully rejected. (ECONNREFUSED) - /// - `connection-reset`: The connection was reset. (ECONNRESET) - /// - `connection-aborted`: The connection was aborted. (ECONNABORTED) - /// - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) - /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - connect: async func(remote-address: ip-socket-address) -> result<_, error-code>; - /// Start listening and return a stream of new inbound connections. - /// - /// Transitions the socket into the `listening` state. This can be called - /// at most once per socket. - /// - /// If the socket is not already explicitly bound, this function will - /// implicitly bind the socket to a random free port. - /// - /// Normally, the returned sockets are bound, in the `connected` state - /// and immediately ready for I/O. Though, depending on exact timing and - /// circumstances, a newly accepted connection may already be `closed` - /// by the time the server attempts to perform its first I/O on it. This - /// is true regardless of whether the WASI implementation uses - /// "synthesized" sockets or not (see Implementors Notes below). - /// - /// The following properties are inherited from the listener socket: - /// - `address-family` - /// - `keep-alive-enabled` - /// - `keep-alive-idle-time` - /// - `keep-alive-interval` - /// - `keep-alive-count` - /// - `hop-limit` - /// - `receive-buffer-size` - /// - `send-buffer-size` - /// - /// # Typical errors - /// - `invalid-state`: The socket is already in the `connected` state. (EISCONN, EINVAL on BSD) - /// - `invalid-state`: The socket is already in the `listening` state. - /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE) - /// - /// # Implementors note - /// This method returns a single perpetual stream that should only close - /// on fatal errors (if any). Yet, the POSIX' `accept` function may also - /// return transient errors (e.g. ECONNABORTED). The exact details differ - /// per operation system. For example, the Linux manual mentions: - /// - /// > Linux accept() passes already-pending network errors on the new - /// > socket as an error code from accept(). This behavior differs from - /// > other BSD socket implementations. For reliable operation the - /// > application should detect the network errors defined for the - /// > protocol after accept() and treat them like EAGAIN by retrying. - /// > In the case of TCP/IP, these are ENETDOWN, EPROTO, ENOPROTOOPT, - /// > EHOSTDOWN, ENONET, EHOSTUNREACH, EOPNOTSUPP, and ENETUNREACH. - /// Source: https://man7.org/linux/man-pages/man2/accept.2.html - /// - /// WASI implementations have two options to handle this: - /// - Optionally log it and then skip over non-fatal errors returned by - /// `accept`. Guest code never gets to see these failures. Or: - /// - Synthesize a `tcp-socket` resource that exposes the error when - /// attempting to send or receive on it. Guest code then sees these - /// failures as regular I/O errors. - /// - /// In either case, the stream returned by this `listen` method remains - /// operational. - /// - /// WASI requires `listen` to perform an implicit bind if the socket - /// has not already been bound. Not all platforms (notably Windows) - /// exhibit this behavior out of the box. On platforms that require it, - /// the WASI implementation can emulate this behavior by performing - /// the bind itself if the guest hasn't already done so. - /// - /// # References - /// - - /// - - /// - - /// - - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - listen: func() -> result, error-code>; - /// Transmit data to peer. - /// - /// The caller should close the stream when it has no more data to send - /// to the peer. Under normal circumstances this will cause a FIN packet - /// to be sent out. Closing the stream is equivalent to calling - /// `shutdown(SHUT_WR)` in POSIX. - /// - /// This function may be called at most once and returns once the full - /// contents of the stream are transmitted or an error is encountered. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not in the `connected` state. (ENOTCONN) - /// - `invalid-state`: `send` has already been called on this socket. - /// - `connection-broken`: The connection is not writable anymore. (EPIPE, ECONNABORTED on Windows) - /// - `connection-reset`: The connection was reset. (ECONNRESET) - /// - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - send: func(data: stream) -> future>; - /// Read data from peer. - /// - /// Returns a `stream` of data sent by the peer. The implementation - /// drops the stream once no more data is available. At that point, the - /// returned `future` resolves to: - /// - `ok` after a graceful shutdown from the peer (i.e. a FIN packet), or - /// - `err` if the socket was closed abnormally. - /// - /// `receive` may be called only once per socket. Subsequent calls return - /// a closed stream and a future resolved to `err(invalid-state)`. - /// - /// If the caller is not expecting to receive any more data from the peer, - /// they should drop the stream. Any data still in the receive queue - /// will be discarded. This is equivalent to calling `shutdown(SHUT_RD)` - /// in POSIX. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not in the `connected` state. (ENOTCONN) - /// - `invalid-state`: `receive` has already been called on this socket. - /// - `connection-reset`: The connection was reset. (ECONNRESET) - /// - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - receive: func() -> tuple, future>>; - /// Get the bound local address. - /// - /// POSIX mentions: - /// > If the socket has not been bound to a local name, the value - /// > stored in the object pointed to by `address` is unspecified. - /// - /// WASI is stricter and requires `get-local-address` to return - /// `invalid-state` when the socket hasn't been bound yet. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not bound to any local address. - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - get-local-address: func() -> result; - /// Get the remote address. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not connected to a remote address. (ENOTCONN) - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - get-remote-address: func() -> result; - /// Whether the socket is in the `listening` state. - /// - /// Equivalent to the SO_ACCEPTCONN socket option. - @since(version = 0.3.0) - get-is-listening: func() -> bool; - /// Whether this is a IPv4 or IPv6 socket. - /// - /// This is the value passed to the constructor. - /// - /// Equivalent to the SO_DOMAIN socket option. - @since(version = 0.3.0) - get-address-family: func() -> ip-address-family; - /// Hints the desired listen queue size. Implementations are free to - /// ignore this. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// Any other value will never cause an error, but it might be silently - /// clamped and/or rounded. - /// - /// # Typical errors - /// - `not-supported`: (set) The platform does not support changing the backlog size after the initial listen. - /// - `invalid-argument`: (set) The provided value was 0. - /// - `invalid-state`: (set) The socket is in the `connecting` or `connected` state. - @since(version = 0.3.0) - set-listen-backlog-size: func(value: u64) -> result<_, error-code>; - /// Enables or disables keepalive. - /// - /// The keepalive behavior can be adjusted using: - /// - `keep-alive-idle-time` - /// - `keep-alive-interval` - /// - `keep-alive-count` - /// These properties can be configured while `keep-alive-enabled` is - /// false, but only come into effect when `keep-alive-enabled` is true. - /// - /// Equivalent to the SO_KEEPALIVE socket option. - @since(version = 0.3.0) - get-keep-alive-enabled: func() -> result; - @since(version = 0.3.0) - set-keep-alive-enabled: func(value: bool) -> result<_, error-code>; - /// Amount of time the connection has to be idle before TCP starts - /// sending keepalive packets. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// All other values are accepted without error, but may be - /// clamped or rounded. As a result, the value read back from - /// this setting may differ from the value that was set. - /// - /// Equivalent to the TCP_KEEPIDLE socket option. (TCP_KEEPALIVE on MacOS) - /// - /// # Typical errors - /// - `invalid-argument`: (set) The provided value was 0. - @since(version = 0.3.0) - get-keep-alive-idle-time: func() -> result; - @since(version = 0.3.0) - set-keep-alive-idle-time: func(value: duration) -> result<_, error-code>; - /// The time between keepalive packets. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// All other values are accepted without error, but may be - /// clamped or rounded. As a result, the value read back from - /// this setting may differ from the value that was set. - /// - /// Equivalent to the TCP_KEEPINTVL socket option. - /// - /// # Typical errors - /// - `invalid-argument`: (set) The provided value was 0. - @since(version = 0.3.0) - get-keep-alive-interval: func() -> result; - @since(version = 0.3.0) - set-keep-alive-interval: func(value: duration) -> result<_, error-code>; - /// The maximum amount of keepalive packets TCP should send before - /// aborting the connection. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// All other values are accepted without error, but may be - /// clamped or rounded. As a result, the value read back from - /// this setting may differ from the value that was set. - /// - /// Equivalent to the TCP_KEEPCNT socket option. - /// - /// # Typical errors - /// - `invalid-argument`: (set) The provided value was 0. - @since(version = 0.3.0) - get-keep-alive-count: func() -> result; - @since(version = 0.3.0) - set-keep-alive-count: func(value: u32) -> result<_, error-code>; - /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// - /// # Typical errors - /// - `invalid-argument`: (set) The TTL value must be 1 or higher. - @since(version = 0.3.0) - get-hop-limit: func() -> result; - @since(version = 0.3.0) - set-hop-limit: func(value: u8) -> result<_, error-code>; - /// Kernel buffer space reserved for sending/receiving on this socket. - /// Implementations usually treat this as a cap the buffer can grow to, - /// rather than allocating the full amount immediately. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// All other values are accepted without error, but may be - /// clamped or rounded. As a result, the value read back from - /// this setting may differ from the value that was set. - /// - /// This is only a performance hint. The implementation may ignore it or - /// tweak it based on real traffic patterns. - /// Linux and macOS appear to behave differently depending on whether a - /// buffer size was explicitly set. When set, they tend to honor it; when - /// not set, they dynamically adjust the buffer size as the connection - /// progresses. This is especially noticeable when comparing the values - /// from before and after connection establishment. - /// - /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. - /// - /// # Typical errors - /// - `invalid-argument`: (set) The provided value was 0. - @since(version = 0.3.0) - get-receive-buffer-size: func() -> result; - @since(version = 0.3.0) - set-receive-buffer-size: func(value: u64) -> result<_, error-code>; - @since(version = 0.3.0) - get-send-buffer-size: func() -> result; - @since(version = 0.3.0) - set-send-buffer-size: func(value: u64) -> result<_, error-code>; - } - - /// A UDP socket handle. - @since(version = 0.3.0) - resource udp-socket { - /// Create a new UDP socket. - /// - /// Similar to `socket(AF_INET or AF_INET6, SOCK_DGRAM, IPPROTO_UDP)` - /// in POSIX. On IPv6 sockets, IPV6_V6ONLY is enabled by default and - /// can't be configured otherwise. - /// - /// Unlike POSIX, WASI sockets have no notion of a socket-level - /// `O_NONBLOCK` flag. Instead they fully rely on the Component Model's - /// async support. - /// - /// # References: - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - create: static func(address-family: ip-address-family) -> result; - /// Bind the socket to the provided IP address and port. - /// - /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is - /// left to the implementation to decide which network interface(s) to - /// bind to. If the port is zero, the socket will be bound to a random - /// free port. - /// - /// # Typical errors - /// - `invalid-argument`: The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows) - /// - `invalid-state`: The socket is already bound. (EINVAL) - /// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) - /// - `address-in-use`: Address is already in use. (EADDRINUSE) - /// - `address-not-bindable`: `local-address` is not an address that can be bound to. (EADDRNOTAVAIL) - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - bind: func(local-address: ip-socket-address) -> result<_, error-code>; - /// Associate this socket with a specific peer address. - /// - /// On success, the `remote-address` of the socket is updated. - /// The `local-address` may be updated as well, based on the best network - /// path to `remote-address`. If the socket was not already explicitly - /// bound, this function will implicitly bind the socket to a random - /// free port. - /// - /// When a UDP socket is "connected", the `send` and `receive` methods - /// are limited to communicating with that peer only: - /// - `send` can only be used to send to this destination. - /// - `receive` will only return datagrams sent from the provided `remote-address`. - /// - /// The name "connect" was kept to align with the existing POSIX - /// terminology. Other than that, this function only changes the local - /// socket configuration and does not generate any network traffic. - /// The peer is not aware of this "connection". - /// - /// This method may be called multiple times on the same socket to change - /// its association, but only the most recent one will be effective. - /// - /// # Typical errors - /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) - /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) - /// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) - /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) - /// - /// # Implementors note - /// If the socket is already connected, some platforms (e.g. Linux) - /// require a disconnect before connecting to a different peer address. - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - connect: func(remote-address: ip-socket-address) -> result<_, error-code>; - /// Dissociate this socket from its peer address. - /// - /// After calling this method, `send` & `receive` are free to communicate - /// with any remote address again. - /// - /// The POSIX equivalent of this is calling `connect` with an `AF_UNSPEC` address. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not connected. - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - disconnect: func() -> result<_, error-code>; - /// Send a message on the socket to a particular peer. - /// - /// If the socket is connected, the peer address may be left empty. In - /// that case this is equivalent to `send` in POSIX. Otherwise it is - /// equivalent to `sendto`. - /// - /// Additionally, if the socket is connected, a `remote-address` argument - /// _may_ be provided but then it must be identical to the address - /// passed to `connect`. - /// - /// If the socket has not been explicitly bound, it will be - /// implicitly bound to a random free port. - /// - /// Implementations may trap if the `data` length exceeds 64 KiB. - /// - /// # Typical errors - /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) - /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) - /// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) - /// - `invalid-argument`: The socket is in "connected" mode and `remote-address` is `some` value that does not match the address passed to `connect`. (EISCONN) - /// - `invalid-argument`: The socket is not "connected" and no value for `remote-address` was provided. (EDESTADDRREQ) - /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) - /// - `connection-refused`: The connection was refused. (ECONNREFUSED) - /// - `datagram-too-large`: The datagram is too large. (EMSGSIZE) - /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE) - /// - /// # Implementors note - /// WASI requires `send` to perform an implicit bind if the socket - /// has not been bound. Not all platforms (notably Windows) exhibit - /// this behavior natively. On such platforms, the WASI implementation - /// should emulate it by performing the bind if the guest has not - /// already done so. - /// - /// # References - /// - - /// - - /// - - /// - - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - send: async func(data: list, remote-address: option) -> result<_, error-code>; - /// Receive a message on the socket. - /// - /// On success, the return value contains a tuple of the received data - /// and the address of the sender. Theoretical maximum length of the - /// data is 64 KiB. Though in practice, it will typically be less than - /// 1500 bytes. - /// - /// If the socket is connected, the sender address is guaranteed to - /// match the remote address passed to `connect`. - /// - /// # Typical errors - /// - `invalid-state`: The socket has not been bound yet. - /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) - /// - `connection-refused`: The connection was refused. (ECONNREFUSED) - /// - /// # References - /// - - /// - - /// - - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - receive: async func() -> result, ip-socket-address>, error-code>; - /// Get the current bound address. - /// - /// POSIX mentions: - /// > If the socket has not been bound to a local name, the value - /// > stored in the object pointed to by `address` is unspecified. - /// - /// WASI is stricter and requires `get-local-address` to return - /// `invalid-state` when the socket hasn't been bound yet. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not bound to any local address. - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - get-local-address: func() -> result; - /// Get the address the socket is currently "connected" to. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not "connected" to a specific remote address. (ENOTCONN) - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - get-remote-address: func() -> result; - /// Whether this is a IPv4 or IPv6 socket. - /// - /// This is the value passed to the constructor. - /// - /// Equivalent to the SO_DOMAIN socket option. - @since(version = 0.3.0) - get-address-family: func() -> ip-address-family; - /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// - /// # Typical errors - /// - `invalid-argument`: (set) The TTL value must be 1 or higher. - @since(version = 0.3.0) - get-unicast-hop-limit: func() -> result; - @since(version = 0.3.0) - set-unicast-hop-limit: func(value: u8) -> result<_, error-code>; - /// Kernel buffer space reserved for sending/receiving on this socket. - /// Implementations usually treat this as a cap the buffer can grow to, - /// rather than allocating the full amount immediately. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// All other values are accepted without error, but may be - /// clamped or rounded. As a result, the value read back from - /// this setting may differ from the value that was set. - /// - /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. - /// - /// # Typical errors - /// - `invalid-argument`: (set) The provided value was 0. - @since(version = 0.3.0) - get-receive-buffer-size: func() -> result; - @since(version = 0.3.0) - set-receive-buffer-size: func(value: u64) -> result<_, error-code>; - @since(version = 0.3.0) - get-send-buffer-size: func() -> result; - @since(version = 0.3.0) - set-send-buffer-size: func(value: u64) -> result<_, error-code>; - } -} - -@since(version = 0.3.0) -interface ip-name-lookup { - @since(version = 0.3.0) - use types.{ip-address}; - - /// Lookup error codes. - @since(version = 0.3.0) - variant error-code { - /// Access denied. - /// - /// POSIX equivalent: EACCES, EPERM - access-denied, - /// `name` is a syntactically invalid domain name or IP address. - /// - /// POSIX equivalent: EINVAL - invalid-argument, - /// Name does not exist or has no suitable associated IP addresses. - /// - /// POSIX equivalent: EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY - name-unresolvable, - /// A temporary failure in name resolution occurred. - /// - /// POSIX equivalent: EAI_AGAIN - temporary-resolver-failure, - /// A permanent failure in name resolution occurred. - /// - /// POSIX equivalent: EAI_FAIL - permanent-resolver-failure, - /// A catch-all for errors not captured by the existing variants. - /// Implementations can use this to extend the error type without - /// breaking existing code. - other(option), - } - - /// Resolve an internet host name to a list of IP addresses. - /// - /// Unicode domain names are automatically converted to ASCII using IDNA - /// encoding. If the input is an IP address string, the address is parsed - /// and returned as-is without making any external requests. - /// - /// See the wasi-socket proposal README.md for a comparison with getaddrinfo. - /// - /// The results are returned in connection order preference. - /// - /// This function never succeeds with 0 results. It either fails or succeeds - /// with at least one address. Additionally, this function never returns - /// IPv4-mapped IPv6 addresses. - /// - /// # References: - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - resolve-addresses: async func(name: string) -> result, error-code>; -} - -@since(version = 0.3.0) -world imports { - @since(version = 0.3.0) - import wasi:clocks/types@0.3.0; - @since(version = 0.3.0) - import types; - @since(version = 0.3.0) - import ip-name-lookup; -} diff --git a/scripts/bump-interface-version.sh b/scripts/bump-interface-version.sh deleted file mode 100755 index 7df6739..0000000 --- a/scripts/bump-interface-version.sh +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env bash - -# Bump the version of the componentized:sockets interface package. -# -# scripts/bump-interface-version.sh -# -# Updates the package declaration and every reference to the package in tracked files, then -# refreshes the generated wit dependencies. Items whose `@since` names an unreleased (prerelease) -# version move to the new version, since they were never published under the old one. Items -# released under the old version keep their `@since`. -# -# 0.1.0-dev -> 0.1.0 releases 0.1.0, `@since(version = 0.1.0-dev)` becomes 0.1.0 -# 0.1.0 -> 0.2.0-dev starts 0.2.0, `@since(version = 0.1.0)` is unchanged - -set -euo pipefail - -cd "$(dirname "$0")/.." - -PACKAGE="${PACKAGE:-componentized:$(basename $(git rev-parse --show-toplevel))}" -SEMVER='^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$' - -new="${1:-}" -if [[ ! "$new" =~ $SEMVER ]]; then - echo "usage: $0 , e.g. 0.1.0 or 0.2.0-dev" >&2 - exit 1 -fi - -old=$(sed -n "s/^package ${PACKAGE}@\(.*\);$/\1/p" wit/worlds.wit) -if [[ -z "$old" ]]; then - echo "unable to find the ${PACKAGE} package declaration in wit/worlds.wit" >&2 - exit 1 -fi - -# succeeds when version $1 is lower than version $2, a prerelease is lower than its release -version_lt() { - local a_core="${1%%-*}" b_core="${2%%-*}" - local a_pre="" b_pre="" - [[ "$1" == *-* ]] && a_pre="${1#*-}" - [[ "$2" == *-* ]] && b_pre="${2#*-}" - if [[ "$a_core" != "$b_core" ]]; then - local IFS=. - local -a a=($a_core) b=($b_core) - for i in 0 1 2; do - (( a[i] < b[i] )) && return 0 - (( a[i] > b[i] )) && return 1 - done - fi - [[ -n "$a_pre" && -z "$b_pre" ]] && return 0 - [[ -z "$a_pre" && -n "$b_pre" ]] && return 1 - [[ -n "$a_pre" && "$a_pre" < "$b_pre" ]] -} - -if ! version_lt "$old" "$new"; then - echo "the new version ${new} must be greater than the current version ${old}" >&2 - exit 1 -fi - -old_re="${old//./\\.}" -files=$(git grep -l -E "${PACKAGE}(/[a-z0-9-]+)?@${old_re}" -- ':!components/wit/deps' || true) -for file in $files; do - sed -i.bak -E "s#(${PACKAGE}(/[a-z0-9-]+)?)@${old_re}#\1@${new}#g" "$file" - rm "$file.bak" - echo "updated ${file}" -done - -if [[ "$old" == *-* ]]; then - files=$(git grep -l -F "@since(version = ${old})" -- 'wit/*.wit' || true) - for file in $files; do - sed -i.bak "s/@since(version = ${old_re})/@since(version = ${new})/g" "$file" - rm "$file.bak" - echo "updated @since in ${file}" - done -fi - -# regenerate the wit dependencies for the new version -make wit components test - -echo "bumped ${PACKAGE} from ${old} to ${new}" diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh new file mode 100755 index 0000000..a3a9666 --- /dev/null +++ b/scripts/bump-version.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash + +# Bump the version of the wit interface package, and of the crates. +# +# scripts/bump-version.sh +# +# Updates the package declaration and every reference to the package in tracked files, then +# refreshes the generated wit dependencies. The crates share the interface's version: the +# workspace version the crates inherit, and the workspace's requirement on the library, move to the +# new version too. Items whose `@since` names an unreleased (prerelease) +# version move to the new version, since they were never published under the old one. Items +# released under the old version keep their `@since`. +# +# 0.1.0-dev -> 0.1.0 releases 0.1.0, `@since(version = 0.1.0-dev)` becomes 0.1.0 +# 0.1.0 -> 0.2.0-dev starts 0.2.0, `@since(version = 0.1.0)` is unchanged + +set -euo pipefail + +cd "$(dirname "$0")/.." + +PACKAGE="${PACKAGE:-componentized:$(basename $(git rev-parse --show-toplevel))}" +# the library crate, the workspace's requirement on it moves to the new version +LIBRARY="${LIBRARY:-componentized-constants}" +SEMVER='^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$' + +new="${1:-}" +if [[ ! "$new" =~ $SEMVER ]]; then + echo "usage: $0 , e.g. 0.1.0 or 0.2.0-dev" >&2 + exit 1 +fi + +old=$(sed -n "s/^package ${PACKAGE}@\(.*\);$/\1/p" wit/worlds.wit) +if [[ -z "$old" ]]; then + echo "unable to find the ${PACKAGE} package declaration in wit/worlds.wit" >&2 + exit 1 +fi + +# succeeds when version $1 is lower than version $2, a prerelease is lower than its release +version_lt() { + local a_core="${1%%-*}" b_core="${2%%-*}" + local a_pre="" b_pre="" + [[ "$1" == *-* ]] && a_pre="${1#*-}" + [[ "$2" == *-* ]] && b_pre="${2#*-}" + if [[ "$a_core" != "$b_core" ]]; then + local IFS=. + local -a a=($a_core) b=($b_core) + for i in 0 1 2; do + (( a[i] < b[i] )) && return 0 + (( a[i] > b[i] )) && return 1 + done + fi + [[ -n "$a_pre" && -z "$b_pre" ]] && return 0 + [[ -z "$a_pre" && -n "$b_pre" ]] && return 1 + [[ -n "$a_pre" && "$a_pre" < "$b_pre" ]] +} + +if ! version_lt "$old" "$new"; then + echo "the new version ${new} must be greater than the current version ${old}" >&2 + exit 1 +fi + +# the bump-version workflow offers the current version as the default for the next bump, checked +# before changing anything +workflow=.github/workflows/bump-version.yaml +workflow_default="default: \"${old}\" # the current version, kept current by scripts/bump-version.sh" +if ! grep -qF "$workflow_default" "$workflow"; then + echo "unable to find the current version as the default in ${workflow}, expected: ${workflow_default}" >&2 + exit 1 +fi + +old_re="${old//./\\.}" +# references to the package or one of its interfaces, an interface named for a keyword is escaped +# with `%`, e.g. `componentized:constants/%u8@0.1.0` +ref_re="${PACKAGE}(/%?[a-z0-9-]+)?" +# the fetched wit dependencies and the wkg.lock files are left to `make wit`, wkg replaces the +# dependencies and updates the locks for the new version +files=$(git grep --untracked -l -E "${ref_re}@${old_re}" -- ':(exclude,glob)**/wit/deps/**' ':(exclude,glob)**/wkg.lock' || true) +for file in $files; do + sed -i.bak -E "s#(${ref_re})@${old_re}#\1@${new}#g" "$file" + rm "$file.bak" + echo "updated ${file}" +done + +if [[ "$old" == *-* ]]; then + files=$(git grep --untracked -l -F "@since(version = ${old})" -- 'wit/*.wit' || true) + for file in $files; do + sed -i.bak "s/@since(version = ${old_re})/@since(version = ${new})/g" "$file" + rm "$file.bak" + echo "updated @since in ${file}" + done +fi + +# the version in the [workspace.package] section, inherited by the crates +perl -pi -e 'if (/^\[workspace\.package\]/ .. /^\[(?!workspace\.package\])/) { s/^version = "[^"]*"/version = "'"${new}"'"/ }' Cargo.toml +echo "updated the workspace version in Cargo.toml" +perl -pi -e 's/^(\Q'"${LIBRARY}"'\E = \{.*\bversion = ")[^"]*(")/${1}'"${new}"'${2}/' Cargo.toml +echo "updated the ${LIBRARY} requirement in Cargo.toml" + +perl -pi -e 's{^(\s+)\Q'"${workflow_default}"'\E$}{${1}'"${workflow_default/\"${old}\"/\"${new}\"}"'}' "$workflow" +echo "updated the default version in ${workflow}" + +# regenerate the wit dependencies for the new version +make wit components test + +echo "bumped ${PACKAGE} from ${old} to ${new}" \ No newline at end of file diff --git a/tools/Cargo.toml b/tools/Cargo.toml index 514cd37..5991273 100644 --- a/tools/Cargo.toml +++ b/tools/Cargo.toml @@ -13,9 +13,11 @@ publish = false path = "lib.rs" [dependencies] +componentized-constants-cli = "=0.1.0-dev" static-config = "=0.2.0" wac-cli = "=0.11.0" wasm-tools = "=1.259.0" +wasmtime-cli = "=49.0.2" wkg = "=0.16.1" # not a member of the repository's workspace diff --git a/wit/deps/wasi-clocks-0.3.0/package.wit b/wit/deps/wasi-clocks-0.3.0/package.wit deleted file mode 100644 index dd7eafd..0000000 --- a/wit/deps/wasi-clocks-0.3.0/package.wit +++ /dev/null @@ -1,6 +0,0 @@ -package wasi:clocks@0.3.0; - -interface types { - type duration = u64; -} - diff --git a/wit/deps/wasi-sockets-0.3.0/package.wit b/wit/deps/wasi-sockets-0.3.0/package.wit deleted file mode 100644 index 0dd27a4..0000000 --- a/wit/deps/wasi-sockets-0.3.0/package.wit +++ /dev/null @@ -1,839 +0,0 @@ -package wasi:sockets@0.3.0; - -@since(version = 0.3.0) -interface types { - @since(version = 0.3.0) - use wasi:clocks/types@0.3.0.{duration}; - - /// Error codes. - /// - /// In theory, every API can return any error code. - /// In practice, API's typically only return the errors documented per API - /// combined with a couple of errors that are always possible: - /// - `other` - /// - `access-denied` - /// - `not-supported` - /// - `out-of-memory` - /// - /// See each individual API for what the POSIX equivalents are. They sometimes differ per API. - @since(version = 0.3.0) - variant error-code { - /// Access denied. - /// - /// POSIX equivalent: EACCES, EPERM - access-denied, - /// The operation is not supported. - /// - /// POSIX equivalent: EOPNOTSUPP, ENOPROTOOPT, EPFNOSUPPORT, EPROTONOSUPPORT, ESOCKTNOSUPPORT - not-supported, - /// One of the arguments is invalid. - /// - /// POSIX equivalent: EINVAL, EDESTADDRREQ, EAFNOSUPPORT - invalid-argument, - /// Not enough memory to complete the operation. - /// - /// POSIX equivalent: ENOMEM, ENOBUFS - out-of-memory, - /// The operation timed out before it could finish completely. - /// - /// POSIX equivalent: ETIMEDOUT - timeout, - /// The operation is not valid in the socket's current state. - invalid-state, - /// The local address is not available. - /// - /// POSIX equivalent: EADDRNOTAVAIL - address-not-bindable, - /// A bind operation failed because the provided address is already in - /// use or because there are no ephemeral ports available. - /// - /// POSIX equivalent: EADDRINUSE - address-in-use, - /// The remote address is not reachable. - /// - /// POSIX equivalent: EHOSTUNREACH, EHOSTDOWN, ENETDOWN, ENETUNREACH, ENONET - remote-unreachable, - /// The connection was forcefully rejected. - /// - /// POSIX equivalent: ECONNREFUSED - connection-refused, - /// A write failed because the connection was broken. - /// - /// POSIX equivalent: EPIPE - connection-broken, - /// The connection was reset. - /// - /// POSIX equivalent: ECONNRESET - connection-reset, - /// The connection was aborted. - /// - /// POSIX equivalent: ECONNABORTED - connection-aborted, - /// The size of a datagram sent to a UDP socket exceeded the maximum - /// supported size. - /// - /// POSIX equivalent: EMSGSIZE - datagram-too-large, - /// A catch-all for errors not captured by the existing variants. - /// Implementations can use this to extend the error type without - /// breaking existing code. - other(option), - } - - @since(version = 0.3.0) - enum ip-address-family { - /// Similar to `AF_INET` in POSIX. - ipv4, - /// Similar to `AF_INET6` in POSIX. - ipv6, - } - - @since(version = 0.3.0) - type ipv4-address = tuple; - - @since(version = 0.3.0) - type ipv6-address = tuple; - - @since(version = 0.3.0) - variant ip-address { - ipv4(ipv4-address), - ipv6(ipv6-address), - } - - @since(version = 0.3.0) - record ipv4-socket-address { - /// sin_port - port: u16, - /// sin_addr - address: ipv4-address, - } - - @since(version = 0.3.0) - record ipv6-socket-address { - /// sin6_port - port: u16, - /// sin6_flowinfo - flow-info: u32, - /// sin6_addr - address: ipv6-address, - /// sin6_scope_id - scope-id: u32, - } - - @since(version = 0.3.0) - variant ip-socket-address { - ipv4(ipv4-socket-address), - ipv6(ipv6-socket-address), - } - - /// A TCP socket resource. - /// - /// The socket can be in one of the following states: - /// - `unbound` - /// - `bound` (See note below) - /// - `listening` - /// - `connecting` - /// - `connected` - /// - `closed` - /// See - /// for more information. - /// - /// Note: Except where explicitly mentioned, whenever this documentation uses - /// the term "bound" without backticks it actually means: in the `bound` state *or higher*. - /// (i.e. `bound`, `listening`, `connecting` or `connected`) - /// - /// WASI uses shared ownership semantics: the `tcp-socket` handle and all - /// derived `stream` and `future` values reference a single underlying OS - /// socket: - /// - Send/receive streams remain functional after the original `tcp-socket` - /// handle is dropped. - /// - The stream returned by `listen` behaves similarly. - /// - Client sockets returned by `tcp-socket::listen` are independent and do - /// not keep the listening socket alive. - /// - /// The OS socket is closed only after the last handle is dropped. This - /// model has observable effects; for example, it affects when the local - /// port binding is released. - /// - /// In addition to the general error codes documented on the - /// `types::error-code` type, TCP socket methods may always return - /// `error(invalid-state)` when in the `closed` state. - @since(version = 0.3.0) - resource tcp-socket { - /// Create a new TCP socket. - /// - /// Similar to `socket(AF_INET or AF_INET6, SOCK_STREAM, IPPROTO_TCP)` - /// in POSIX. On IPv6 sockets, IPV6_V6ONLY is enabled by default and - /// can't be configured otherwise. - /// - /// Unlike POSIX, WASI sockets have no notion of a socket-level - /// `O_NONBLOCK` flag. Instead they fully rely on the Component Model's - /// async support. - /// - /// # Typical errors - /// - `not-supported`: The `address-family` is not supported. (EAFNOSUPPORT) - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - create: static func(address-family: ip-address-family) -> result; - /// Bind the socket to the provided IP address and port. - /// - /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is - /// left to the implementation to decide which network interface(s) to - /// bind to. If the TCP/UDP port is zero, the socket will be bound to a - /// random free port. - /// - /// Bind can be attempted multiple times on the same socket, even with - /// different arguments on each iteration. But never concurrently and - /// only as long as the previous bind failed. Once a bind succeeds, the - /// binding can't be changed anymore. - /// - /// # Typical errors - /// - `invalid-argument`: The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows) - /// - `invalid-argument`: `local-address` is not a unicast address. (EINVAL) - /// - `invalid-argument`: `local-address` is an IPv4-mapped IPv6 address. (EINVAL) - /// - `invalid-state`: The socket is already bound. (EINVAL) - /// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) - /// - `address-in-use`: Address is already in use. (EADDRINUSE) - /// - `address-not-bindable`: `local-address` is not an address that can be bound to. (EADDRNOTAVAIL) - /// - /// # Implementors note - /// The bind operation shouldn't be affected by the TIME_WAIT state of a - /// recently closed socket on the same local address. In practice this - /// means that the SO_REUSEADDR socket option should be set implicitly - /// on all platforms, except on Windows where this is the default - /// behavior and SO_REUSEADDR performs something different. - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - bind: func(local-address: ip-socket-address) -> result<_, error-code>; - /// Connect to a remote endpoint. - /// - /// On success, the socket is transitioned into the `connected` state - /// and the `remote-address` of the socket is updated. - /// The `local-address` may be updated as well, based on the best network - /// path to `remote-address`. If the socket was not already explicitly - /// bound, this function will implicitly bind the socket to a random - /// free port. - /// - /// After a failed connection attempt, the socket will be in the `closed` - /// state and the only valid action left is to `drop` the socket. A single - /// socket can not be used to connect more than once. - /// - /// # Typical errors - /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) - /// - `invalid-argument`: `remote-address` is not a unicast address. (EINVAL, ENETUNREACH on Linux, EAFNOSUPPORT on MacOS) - /// - `invalid-argument`: `remote-address` is an IPv4-mapped IPv6 address. (EINVAL, EADDRNOTAVAIL on Illumos) - /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EADDRNOTAVAIL on Windows) - /// - `invalid-argument`: The port in `remote-address` is set to 0. (EADDRNOTAVAIL on Windows) - /// - `invalid-state`: The socket is already in the `connecting` state. (EALREADY) - /// - `invalid-state`: The socket is already in the `connected` state. (EISCONN) - /// - `invalid-state`: The socket is already in the `listening` state. (EOPNOTSUPP, EINVAL on Windows) - /// - `timeout`: Connection timed out. (ETIMEDOUT) - /// - `connection-refused`: The connection was forcefully rejected. (ECONNREFUSED) - /// - `connection-reset`: The connection was reset. (ECONNRESET) - /// - `connection-aborted`: The connection was aborted. (ECONNABORTED) - /// - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) - /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - connect: async func(remote-address: ip-socket-address) -> result<_, error-code>; - /// Start listening and return a stream of new inbound connections. - /// - /// Transitions the socket into the `listening` state. This can be called - /// at most once per socket. - /// - /// If the socket is not already explicitly bound, this function will - /// implicitly bind the socket to a random free port. - /// - /// Normally, the returned sockets are bound, in the `connected` state - /// and immediately ready for I/O. Though, depending on exact timing and - /// circumstances, a newly accepted connection may already be `closed` - /// by the time the server attempts to perform its first I/O on it. This - /// is true regardless of whether the WASI implementation uses - /// "synthesized" sockets or not (see Implementors Notes below). - /// - /// The following properties are inherited from the listener socket: - /// - `address-family` - /// - `keep-alive-enabled` - /// - `keep-alive-idle-time` - /// - `keep-alive-interval` - /// - `keep-alive-count` - /// - `hop-limit` - /// - `receive-buffer-size` - /// - `send-buffer-size` - /// - /// # Typical errors - /// - `invalid-state`: The socket is already in the `connected` state. (EISCONN, EINVAL on BSD) - /// - `invalid-state`: The socket is already in the `listening` state. - /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE) - /// - /// # Implementors note - /// This method returns a single perpetual stream that should only close - /// on fatal errors (if any). Yet, the POSIX' `accept` function may also - /// return transient errors (e.g. ECONNABORTED). The exact details differ - /// per operation system. For example, the Linux manual mentions: - /// - /// > Linux accept() passes already-pending network errors on the new - /// > socket as an error code from accept(). This behavior differs from - /// > other BSD socket implementations. For reliable operation the - /// > application should detect the network errors defined for the - /// > protocol after accept() and treat them like EAGAIN by retrying. - /// > In the case of TCP/IP, these are ENETDOWN, EPROTO, ENOPROTOOPT, - /// > EHOSTDOWN, ENONET, EHOSTUNREACH, EOPNOTSUPP, and ENETUNREACH. - /// Source: https://man7.org/linux/man-pages/man2/accept.2.html - /// - /// WASI implementations have two options to handle this: - /// - Optionally log it and then skip over non-fatal errors returned by - /// `accept`. Guest code never gets to see these failures. Or: - /// - Synthesize a `tcp-socket` resource that exposes the error when - /// attempting to send or receive on it. Guest code then sees these - /// failures as regular I/O errors. - /// - /// In either case, the stream returned by this `listen` method remains - /// operational. - /// - /// WASI requires `listen` to perform an implicit bind if the socket - /// has not already been bound. Not all platforms (notably Windows) - /// exhibit this behavior out of the box. On platforms that require it, - /// the WASI implementation can emulate this behavior by performing - /// the bind itself if the guest hasn't already done so. - /// - /// # References - /// - - /// - - /// - - /// - - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - listen: func() -> result, error-code>; - /// Transmit data to peer. - /// - /// The caller should close the stream when it has no more data to send - /// to the peer. Under normal circumstances this will cause a FIN packet - /// to be sent out. Closing the stream is equivalent to calling - /// `shutdown(SHUT_WR)` in POSIX. - /// - /// This function may be called at most once and returns once the full - /// contents of the stream are transmitted or an error is encountered. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not in the `connected` state. (ENOTCONN) - /// - `invalid-state`: `send` has already been called on this socket. - /// - `connection-broken`: The connection is not writable anymore. (EPIPE, ECONNABORTED on Windows) - /// - `connection-reset`: The connection was reset. (ECONNRESET) - /// - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - send: func(data: stream) -> future>; - /// Read data from peer. - /// - /// Returns a `stream` of data sent by the peer. The implementation - /// drops the stream once no more data is available. At that point, the - /// returned `future` resolves to: - /// - `ok` after a graceful shutdown from the peer (i.e. a FIN packet), or - /// - `err` if the socket was closed abnormally. - /// - /// `receive` may be called only once per socket. Subsequent calls return - /// a closed stream and a future resolved to `err(invalid-state)`. - /// - /// If the caller is not expecting to receive any more data from the peer, - /// they should drop the stream. Any data still in the receive queue - /// will be discarded. This is equivalent to calling `shutdown(SHUT_RD)` - /// in POSIX. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not in the `connected` state. (ENOTCONN) - /// - `invalid-state`: `receive` has already been called on this socket. - /// - `connection-reset`: The connection was reset. (ECONNRESET) - /// - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - receive: func() -> tuple, future>>; - /// Get the bound local address. - /// - /// POSIX mentions: - /// > If the socket has not been bound to a local name, the value - /// > stored in the object pointed to by `address` is unspecified. - /// - /// WASI is stricter and requires `get-local-address` to return - /// `invalid-state` when the socket hasn't been bound yet. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not bound to any local address. - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - get-local-address: func() -> result; - /// Get the remote address. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not connected to a remote address. (ENOTCONN) - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - get-remote-address: func() -> result; - /// Whether the socket is in the `listening` state. - /// - /// Equivalent to the SO_ACCEPTCONN socket option. - @since(version = 0.3.0) - get-is-listening: func() -> bool; - /// Whether this is a IPv4 or IPv6 socket. - /// - /// This is the value passed to the constructor. - /// - /// Equivalent to the SO_DOMAIN socket option. - @since(version = 0.3.0) - get-address-family: func() -> ip-address-family; - /// Hints the desired listen queue size. Implementations are free to - /// ignore this. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// Any other value will never cause an error, but it might be silently - /// clamped and/or rounded. - /// - /// # Typical errors - /// - `not-supported`: (set) The platform does not support changing the backlog size after the initial listen. - /// - `invalid-argument`: (set) The provided value was 0. - /// - `invalid-state`: (set) The socket is in the `connecting` or `connected` state. - @since(version = 0.3.0) - set-listen-backlog-size: func(value: u64) -> result<_, error-code>; - /// Enables or disables keepalive. - /// - /// The keepalive behavior can be adjusted using: - /// - `keep-alive-idle-time` - /// - `keep-alive-interval` - /// - `keep-alive-count` - /// These properties can be configured while `keep-alive-enabled` is - /// false, but only come into effect when `keep-alive-enabled` is true. - /// - /// Equivalent to the SO_KEEPALIVE socket option. - @since(version = 0.3.0) - get-keep-alive-enabled: func() -> result; - @since(version = 0.3.0) - set-keep-alive-enabled: func(value: bool) -> result<_, error-code>; - /// Amount of time the connection has to be idle before TCP starts - /// sending keepalive packets. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// All other values are accepted without error, but may be - /// clamped or rounded. As a result, the value read back from - /// this setting may differ from the value that was set. - /// - /// Equivalent to the TCP_KEEPIDLE socket option. (TCP_KEEPALIVE on MacOS) - /// - /// # Typical errors - /// - `invalid-argument`: (set) The provided value was 0. - @since(version = 0.3.0) - get-keep-alive-idle-time: func() -> result; - @since(version = 0.3.0) - set-keep-alive-idle-time: func(value: duration) -> result<_, error-code>; - /// The time between keepalive packets. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// All other values are accepted without error, but may be - /// clamped or rounded. As a result, the value read back from - /// this setting may differ from the value that was set. - /// - /// Equivalent to the TCP_KEEPINTVL socket option. - /// - /// # Typical errors - /// - `invalid-argument`: (set) The provided value was 0. - @since(version = 0.3.0) - get-keep-alive-interval: func() -> result; - @since(version = 0.3.0) - set-keep-alive-interval: func(value: duration) -> result<_, error-code>; - /// The maximum amount of keepalive packets TCP should send before - /// aborting the connection. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// All other values are accepted without error, but may be - /// clamped or rounded. As a result, the value read back from - /// this setting may differ from the value that was set. - /// - /// Equivalent to the TCP_KEEPCNT socket option. - /// - /// # Typical errors - /// - `invalid-argument`: (set) The provided value was 0. - @since(version = 0.3.0) - get-keep-alive-count: func() -> result; - @since(version = 0.3.0) - set-keep-alive-count: func(value: u32) -> result<_, error-code>; - /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// - /// # Typical errors - /// - `invalid-argument`: (set) The TTL value must be 1 or higher. - @since(version = 0.3.0) - get-hop-limit: func() -> result; - @since(version = 0.3.0) - set-hop-limit: func(value: u8) -> result<_, error-code>; - /// Kernel buffer space reserved for sending/receiving on this socket. - /// Implementations usually treat this as a cap the buffer can grow to, - /// rather than allocating the full amount immediately. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// All other values are accepted without error, but may be - /// clamped or rounded. As a result, the value read back from - /// this setting may differ from the value that was set. - /// - /// This is only a performance hint. The implementation may ignore it or - /// tweak it based on real traffic patterns. - /// Linux and macOS appear to behave differently depending on whether a - /// buffer size was explicitly set. When set, they tend to honor it; when - /// not set, they dynamically adjust the buffer size as the connection - /// progresses. This is especially noticeable when comparing the values - /// from before and after connection establishment. - /// - /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. - /// - /// # Typical errors - /// - `invalid-argument`: (set) The provided value was 0. - @since(version = 0.3.0) - get-receive-buffer-size: func() -> result; - @since(version = 0.3.0) - set-receive-buffer-size: func(value: u64) -> result<_, error-code>; - @since(version = 0.3.0) - get-send-buffer-size: func() -> result; - @since(version = 0.3.0) - set-send-buffer-size: func(value: u64) -> result<_, error-code>; - } - - /// A UDP socket handle. - @since(version = 0.3.0) - resource udp-socket { - /// Create a new UDP socket. - /// - /// Similar to `socket(AF_INET or AF_INET6, SOCK_DGRAM, IPPROTO_UDP)` - /// in POSIX. On IPv6 sockets, IPV6_V6ONLY is enabled by default and - /// can't be configured otherwise. - /// - /// Unlike POSIX, WASI sockets have no notion of a socket-level - /// `O_NONBLOCK` flag. Instead they fully rely on the Component Model's - /// async support. - /// - /// # References: - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - create: static func(address-family: ip-address-family) -> result; - /// Bind the socket to the provided IP address and port. - /// - /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is - /// left to the implementation to decide which network interface(s) to - /// bind to. If the port is zero, the socket will be bound to a random - /// free port. - /// - /// # Typical errors - /// - `invalid-argument`: The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows) - /// - `invalid-state`: The socket is already bound. (EINVAL) - /// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) - /// - `address-in-use`: Address is already in use. (EADDRINUSE) - /// - `address-not-bindable`: `local-address` is not an address that can be bound to. (EADDRNOTAVAIL) - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - bind: func(local-address: ip-socket-address) -> result<_, error-code>; - /// Associate this socket with a specific peer address. - /// - /// On success, the `remote-address` of the socket is updated. - /// The `local-address` may be updated as well, based on the best network - /// path to `remote-address`. If the socket was not already explicitly - /// bound, this function will implicitly bind the socket to a random - /// free port. - /// - /// When a UDP socket is "connected", the `send` and `receive` methods - /// are limited to communicating with that peer only: - /// - `send` can only be used to send to this destination. - /// - `receive` will only return datagrams sent from the provided `remote-address`. - /// - /// The name "connect" was kept to align with the existing POSIX - /// terminology. Other than that, this function only changes the local - /// socket configuration and does not generate any network traffic. - /// The peer is not aware of this "connection". - /// - /// This method may be called multiple times on the same socket to change - /// its association, but only the most recent one will be effective. - /// - /// # Typical errors - /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) - /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) - /// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) - /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) - /// - /// # Implementors note - /// If the socket is already connected, some platforms (e.g. Linux) - /// require a disconnect before connecting to a different peer address. - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - connect: func(remote-address: ip-socket-address) -> result<_, error-code>; - /// Dissociate this socket from its peer address. - /// - /// After calling this method, `send` & `receive` are free to communicate - /// with any remote address again. - /// - /// The POSIX equivalent of this is calling `connect` with an `AF_UNSPEC` address. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not connected. - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - disconnect: func() -> result<_, error-code>; - /// Send a message on the socket to a particular peer. - /// - /// If the socket is connected, the peer address may be left empty. In - /// that case this is equivalent to `send` in POSIX. Otherwise it is - /// equivalent to `sendto`. - /// - /// Additionally, if the socket is connected, a `remote-address` argument - /// _may_ be provided but then it must be identical to the address - /// passed to `connect`. - /// - /// If the socket has not been explicitly bound, it will be - /// implicitly bound to a random free port. - /// - /// Implementations may trap if the `data` length exceeds 64 KiB. - /// - /// # Typical errors - /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) - /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) - /// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) - /// - `invalid-argument`: The socket is in "connected" mode and `remote-address` is `some` value that does not match the address passed to `connect`. (EISCONN) - /// - `invalid-argument`: The socket is not "connected" and no value for `remote-address` was provided. (EDESTADDRREQ) - /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) - /// - `connection-refused`: The connection was refused. (ECONNREFUSED) - /// - `datagram-too-large`: The datagram is too large. (EMSGSIZE) - /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE) - /// - /// # Implementors note - /// WASI requires `send` to perform an implicit bind if the socket - /// has not been bound. Not all platforms (notably Windows) exhibit - /// this behavior natively. On such platforms, the WASI implementation - /// should emulate it by performing the bind if the guest has not - /// already done so. - /// - /// # References - /// - - /// - - /// - - /// - - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - send: async func(data: list, remote-address: option) -> result<_, error-code>; - /// Receive a message on the socket. - /// - /// On success, the return value contains a tuple of the received data - /// and the address of the sender. Theoretical maximum length of the - /// data is 64 KiB. Though in practice, it will typically be less than - /// 1500 bytes. - /// - /// If the socket is connected, the sender address is guaranteed to - /// match the remote address passed to `connect`. - /// - /// # Typical errors - /// - `invalid-state`: The socket has not been bound yet. - /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) - /// - `connection-refused`: The connection was refused. (ECONNREFUSED) - /// - /// # References - /// - - /// - - /// - - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - receive: async func() -> result, ip-socket-address>, error-code>; - /// Get the current bound address. - /// - /// POSIX mentions: - /// > If the socket has not been bound to a local name, the value - /// > stored in the object pointed to by `address` is unspecified. - /// - /// WASI is stricter and requires `get-local-address` to return - /// `invalid-state` when the socket hasn't been bound yet. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not bound to any local address. - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - get-local-address: func() -> result; - /// Get the address the socket is currently "connected" to. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not "connected" to a specific remote address. (ENOTCONN) - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - get-remote-address: func() -> result; - /// Whether this is a IPv4 or IPv6 socket. - /// - /// This is the value passed to the constructor. - /// - /// Equivalent to the SO_DOMAIN socket option. - @since(version = 0.3.0) - get-address-family: func() -> ip-address-family; - /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// - /// # Typical errors - /// - `invalid-argument`: (set) The TTL value must be 1 or higher. - @since(version = 0.3.0) - get-unicast-hop-limit: func() -> result; - @since(version = 0.3.0) - set-unicast-hop-limit: func(value: u8) -> result<_, error-code>; - /// Kernel buffer space reserved for sending/receiving on this socket. - /// Implementations usually treat this as a cap the buffer can grow to, - /// rather than allocating the full amount immediately. - /// - /// If the provided value is 0, an `invalid-argument` error is returned. - /// All other values are accepted without error, but may be - /// clamped or rounded. As a result, the value read back from - /// this setting may differ from the value that was set. - /// - /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. - /// - /// # Typical errors - /// - `invalid-argument`: (set) The provided value was 0. - @since(version = 0.3.0) - get-receive-buffer-size: func() -> result; - @since(version = 0.3.0) - set-receive-buffer-size: func(value: u64) -> result<_, error-code>; - @since(version = 0.3.0) - get-send-buffer-size: func() -> result; - @since(version = 0.3.0) - set-send-buffer-size: func(value: u64) -> result<_, error-code>; - } -} - -@since(version = 0.3.0) -interface ip-name-lookup { - @since(version = 0.3.0) - use types.{ip-address}; - - /// Lookup error codes. - @since(version = 0.3.0) - variant error-code { - /// Access denied. - /// - /// POSIX equivalent: EACCES, EPERM - access-denied, - /// `name` is a syntactically invalid domain name or IP address. - /// - /// POSIX equivalent: EINVAL - invalid-argument, - /// Name does not exist or has no suitable associated IP addresses. - /// - /// POSIX equivalent: EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY - name-unresolvable, - /// A temporary failure in name resolution occurred. - /// - /// POSIX equivalent: EAI_AGAIN - temporary-resolver-failure, - /// A permanent failure in name resolution occurred. - /// - /// POSIX equivalent: EAI_FAIL - permanent-resolver-failure, - /// A catch-all for errors not captured by the existing variants. - /// Implementations can use this to extend the error type without - /// breaking existing code. - other(option), - } - - /// Resolve an internet host name to a list of IP addresses. - /// - /// Unicode domain names are automatically converted to ASCII using IDNA - /// encoding. If the input is an IP address string, the address is parsed - /// and returned as-is without making any external requests. - /// - /// See the wasi-socket proposal README.md for a comparison with getaddrinfo. - /// - /// The results are returned in connection order preference. - /// - /// This function never succeeds with 0 results. It either fails or succeeds - /// with at least one address. Additionally, this function never returns - /// IPv4-mapped IPv6 addresses. - /// - /// # References: - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - resolve-addresses: async func(name: string) -> result, error-code>; -} - -@since(version = 0.3.0) -world imports { - @since(version = 0.3.0) - import wasi:clocks/types@0.3.0; - @since(version = 0.3.0) - import types; - @since(version = 0.3.0) - import ip-name-lookup; -}