diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 43823ec..887ce0d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,38 +10,6 @@ env: CGO_ENABLED: 0 jobs: - watm_tinygo_v0_artifacts: - name: "watm: build ${{ matrix.watm.name }}.v0.tinygo.wasm with TinyGo" - runs-on: ubuntu-latest - strategy: - fail-fast: true - matrix: - watm: [ - { name: "plain", scheduler: "none", gc: "conservative", tags: "purego" }, - { name: "reverse", scheduler: "none", gc: "conservative", tags: "purego" }, - { name: "utls", scheduler: "asyncify", gc: "conservative", tags: "purego" } - ] - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 - with: - go-version: "1.22.x" - - uses: acifani/setup-tinygo@v2 - with: - tinygo-version: '0.32.0' - - name: Build WATM Artifacts - run: tinygo build -o ../../../${{ matrix.watm.name }}.v0.tinygo.wasm - -target=wasi -no-debug -scheduler=${{ matrix.watm.scheduler }} - -gc=${{ matrix.watm.gc }} -tags=${{ matrix.watm.tags }} - ./${{ matrix.watm.name }}/ - working-directory: ./tinygo/v0/examples/ - - name: Upload Artifacts - uses: actions/upload-artifact@v4 - with: - name: ${{ matrix.watm.name }}.v0.tinygo - path: ${{ matrix.watm.name }}.v0.tinygo.wasm - retention-days: 1 - watm_tinygo_v1_artifacts: name: "watm: build ${{ matrix.watm.name }}.v1.tinygo.wasm with TinyGo" runs-on: ubuntu-latest @@ -76,7 +44,6 @@ jobs: release: needs: - - watm_tinygo_v0_artifacts - watm_tinygo_v1_artifacts name: "Release WATM Examples for ${{ github.ref_name }}" runs-on: ubuntu-latest diff --git a/.github/workflows/watm.yml b/.github/workflows/watm.yml index ccca765..ffd56d6 100644 --- a/.github/workflows/watm.yml +++ b/.github/workflows/watm.yml @@ -37,29 +37,6 @@ jobs: go build -v ./... go test -v ./... - watm_build_tinygo_v0_examples: - name: "build ${{ matrix.examples }}.v0.tinygo.wasm w/ tinygo ${{ matrix.tinygo }} (go${{ matrix.go }})" - runs-on: ubuntu-latest - strategy: - fail-fast: true - matrix: - tinygo: [ "0.32.0" ] # latest tinygo version ONLY (1) - go: [ "1.21.x", "1.22.x" ] # latest 2 stable versions of Go. TODO: bump to 1.22.x once tinygo support added. - examples: [ "plain", "reverse", "utls" ] # Add examples here per ones under tinygo/v0/examples - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 - with: - go-version: ${{ matrix.go }} - - uses: acifani/setup-tinygo@v2 - with: - tinygo-version: ${{ matrix.tinygo }} - - name: Build - run: | - mkdir -p tmp - tinygo build -o tmp/${{ matrix.examples }}.wasm -target=wasi -tags=purego ./${{ matrix.examples }}/ - working-directory: ./tinygo/v0/examples/ - watm_build_tinygo_v1_examples: name: "build ${{ matrix.examples }}.v1.tinygo.wasm w/ tinygo ${{ matrix.tinygo }} (go${{ matrix.go }})" runs-on: ubuntu-latest diff --git a/tinygo/snippets/go.mod b/tinygo/snippets/go.mod index fe62ab7..3a26f76 100644 --- a/tinygo/snippets/go.mod +++ b/tinygo/snippets/go.mod @@ -2,6 +2,6 @@ module github.com/refraction-networking/watm/tinygo/snippets go 1.20 -replace github.com/tetratelabs/wazero v1.6.0 => github.com/refraction-networking/wazero v1.6.6-w +replace github.com/tetratelabs/wazero v1.7.3 => github.com/refraction-networking/wazero v1.7.3-w -require github.com/tetratelabs/wazero v1.6.0 +require github.com/tetratelabs/wazero v1.7.3 diff --git a/tinygo/snippets/go.sum b/tinygo/snippets/go.sum index 435e593..f5eee1e 100644 --- a/tinygo/snippets/go.sum +++ b/tinygo/snippets/go.sum @@ -1,2 +1,2 @@ -github.com/refraction-networking/wazero v1.6.6-w h1:GopGAQ5/Rah0vzGImewwOrOaXFR+FmgWlgGKs1JpVlw= -github.com/refraction-networking/wazero v1.6.6-w/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= +github.com/refraction-networking/wazero v1.7.3-w h1:Br3UuVPrKAD3pUSIlpT1+iBIYMbs8h2wS4d0ziU9Yoc= +github.com/refraction-networking/wazero v1.7.3-w/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= diff --git a/tinygo/snippets/poll_oneoff/main.go b/tinygo/snippets/poll_oneoff/main.go index 8660340..f7dfb82 100644 --- a/tinygo/snippets/poll_oneoff/main.go +++ b/tinygo/snippets/poll_oneoff/main.go @@ -18,8 +18,7 @@ var pollWasm []byte func main() { ctx := context.Background() - ctx = context.WithValue(ctx, experimental.FunctionListenerFactoryKey{}, - logging.NewHostLoggingListenerFactory(os.Stderr, logging.LogScopeFilesystem|logging.LogScopePoll|logging.LogScopeSock)) + ctx = experimental.WithFunctionListenerFactory(ctx, logging.NewHostLoggingListenerFactory(os.Stderr, logging.LogScopeFilesystem|logging.LogScopePoll|logging.LogScopeSock)) r := wazero.NewRuntime(ctx) defer r.Close(ctx) diff --git a/tinygo/v0/README.md b/tinygo/v0/README.md deleted file mode 100644 index afad012..0000000 --- a/tinygo/v0/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# `tinygo/v0` - -This directory contains package you might find useful when building a v0 WATM with TinyGo. \ No newline at end of file diff --git a/tinygo/v0/dialer.go b/tinygo/v0/dialer.go deleted file mode 100644 index 0a8d0ba..0000000 --- a/tinygo/v0/dialer.go +++ /dev/null @@ -1,52 +0,0 @@ -package v0 - -type dialer struct { - wt WrappingTransport - // dt DialingTransport -} - -func (d *dialer) ConfigurableTransport() ConfigurableTransport { - if d.wt != nil { - if wt, ok := d.wt.(ConfigurableTransport); ok { - return wt - } - } - - // if d.dt != nil { - // if dt, ok := d.dt.(ConfigurableTransport); ok { - // return dt - // } - // } - - return nil -} - -func (d *dialer) Initialize() { - // TODO: allow initialization on dialer -} - -var d dialer - -// BuildDialerWithWrappingTransport arms the dialer with a -// [WrappingTransport] that is used to wrap a [v0net.Conn] into -// another [net.Conn] by providing some high-level application -// layer protocol. -// -// Mutually exclusive with [BuildDialerWithDialingTransport]. -func BuildDialerWithWrappingTransport(wt WrappingTransport) { - d.wt = wt - // d.dt = nil -} - -// BuildDialerWithDialingTransport arms the dialer with a -// [DialingTransport] that is used to dial a remote address and -// provide high-level application layer protocol over the dialed -// connection. -// -// Mutually exclusive with [BuildDialerWithWrappingTransport]. -func BuildDialerWithDialingTransport(dt DialingTransport) { - // TODO: implement BuildDialerWithDialingTransport - // d.dt = dt - // d.wt = nil - panic("BuildDialerWithDialingTransport: not implemented") -} diff --git a/tinygo/v0/examples/go.mod b/tinygo/v0/examples/go.mod deleted file mode 100644 index 2213dcf..0000000 --- a/tinygo/v0/examples/go.mod +++ /dev/null @@ -1,22 +0,0 @@ -module github.com/refraction-networking/watm/tinygo/v0/examples - -go 1.21 - -replace golang.org/x/sys => ../../replaced/golang.org/x/sys@v0.19.0 - -replace github.com/refraction-networking/watm => ../../../ - -require ( - github.com/CosmWasm/tinyjson v0.9.0 - github.com/refraction-networking/utls v1.6.6-wasm - github.com/refraction-networking/watm v0.6.5 -) - -require ( - github.com/andybalholm/brotli v1.1.0 // indirect - github.com/cloudflare/circl v1.3.9 // indirect - github.com/josharian/intern v1.0.0 // indirect - github.com/klauspost/compress v1.17.9 // indirect - golang.org/x/crypto v0.24.0 // indirect - golang.org/x/sys v0.21.0 // indirect -) diff --git a/tinygo/v0/examples/go.sum b/tinygo/v0/examples/go.sum deleted file mode 100644 index 5149b10..0000000 --- a/tinygo/v0/examples/go.sum +++ /dev/null @@ -1,14 +0,0 @@ -github.com/CosmWasm/tinyjson v0.9.0 h1:sPjgikATp5W0vD/v/Qz99uQ6G/lh/SuK0Wfskqua4Co= -github.com/CosmWasm/tinyjson v0.9.0/go.mod h1:5+7QnSKrkIWnpIdhUT2t2EYzXnII3/3MlM0oDsBSbc8= -github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= -github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= -github.com/cloudflare/circl v1.3.9 h1:QFrlgFYf2Qpi8bSpVPK1HBvWpx16v/1TZivyo7pGuBE= -github.com/cloudflare/circl v1.3.9/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= -github.com/refraction-networking/utls v1.6.6-wasm h1:fayTy1wZzzNSZqJ9rzMgDOoGVLS9/u0YIRQ9fUurlc0= -github.com/refraction-networking/utls v1.6.6-wasm/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= diff --git a/tinygo/v0/examples/plain/README.md b/tinygo/v0/examples/plain/README.md deleted file mode 100644 index 3a8cd82..0000000 --- a/tinygo/v0/examples/plain/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Example: `plain.wasm` - -This example shows how to build a minimal WATM with TinyGo which passes through the received/sent data without any processing. - -## Build - -Go 1.20/1.21/1.22 is required to build this example. TinyGo started to support Go 1.22 in v0.31.0. - -### Debug - -```bash -tinygo build -o plain.wasm -target=wasi -scheduler=none -gc=conservative . -``` - -### Release - -```bash -tinygo build -o plain.wasm -target=wasi -no-debug -scheduler=none -gc=conservative . -``` \ No newline at end of file diff --git a/tinygo/v0/examples/plain/main.go b/tinygo/v0/examples/plain/main.go deleted file mode 100644 index 3544652..0000000 --- a/tinygo/v0/examples/plain/main.go +++ /dev/null @@ -1,12 +0,0 @@ -package main - -import v0 "github.com/refraction-networking/watm/tinygo/v0" - -func init() { - v0.WorkerFairness(false) // by default, use unfairWorker for better performance - v0.BuildDialerWithWrappingTransport(&PlainWrappingTransport{}) - v0.BuildListenerWithWrappingTransport(&PlainWrappingTransport{}) - v0.BuildRelayWithWrappingTransport(&PlainWrappingTransport{}, v0.RelayWrapRemote) -} - -func main() {} diff --git a/tinygo/v0/examples/plain/wrapper_transport.go b/tinygo/v0/examples/plain/wrapper_transport.go deleted file mode 100644 index 8c98754..0000000 --- a/tinygo/v0/examples/plain/wrapper_transport.go +++ /dev/null @@ -1,21 +0,0 @@ -package main - -import ( - v0 "github.com/refraction-networking/watm/tinygo/v0" - v0net "github.com/refraction-networking/watm/tinygo/v0/net" -) - -// type guard: PlainWrappingTransport must implement [v0.WrappingTransport]. -var _ v0.WrappingTransport = (*PlainWrappingTransport)(nil) - -type PlainWrappingTransport struct { -} - -func (rwt *PlainWrappingTransport) Wrap(conn v0net.Conn) (v0net.Conn, error) { - return &PlainConn{conn}, conn.SetNonBlock(true) // must set non-block, otherwise will block on read and lose fairness -} - -// PlainConn simply passes through the underlying Conn. -type PlainConn struct { - v0net.Conn // embedded Conn -} diff --git a/tinygo/v0/examples/reverse/README.md b/tinygo/v0/examples/reverse/README.md deleted file mode 100644 index d2e3dfb..0000000 --- a/tinygo/v0/examples/reverse/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Example: `reverse.wasm` - -This example shows how to build a minimal WATM with TinyGo which reverse the received string. - -## Build - -Go 1.20/1.21/1.22 is required to build this example. TinyGo started to support Go 1.22 in v0.31.0. - -### Debug - -```bash -tinygo build -o reverse.wasm -target=wasi -scheduler=none -gc=conservative . -``` - -### Release - -```bash -tinygo build -o reverse.wasm -target=wasi -no-debug -scheduler=none -gc=conservative . -``` \ No newline at end of file diff --git a/tinygo/v0/examples/reverse/main.go b/tinygo/v0/examples/reverse/main.go deleted file mode 100644 index dd0ec7a..0000000 --- a/tinygo/v0/examples/reverse/main.go +++ /dev/null @@ -1,11 +0,0 @@ -package main - -import v0 "github.com/refraction-networking/watm/tinygo/v0" - -func init() { - v0.BuildDialerWithWrappingTransport(&ReverseWrappingTransport{}) - v0.BuildListenerWithWrappingTransport(&ReverseWrappingTransport{}) - v0.BuildRelayWithWrappingTransport(&ReverseWrappingTransport{}, v0.RelayWrapRemote) -} - -func main() {} diff --git a/tinygo/v0/examples/utls/README.md b/tinygo/v0/examples/utls/README.md deleted file mode 100644 index 611fbcf..0000000 --- a/tinygo/v0/examples/utls/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Example: `uTLS.wasm` - -This example shows how to build a fully functional TLS client with TinyGo from [uTLS](https://github.com/refraction-networking/utls/tree/wasm). - -## Build - -Go 1.20/1.21/1.22 is required to build this example. TinyGo started to support Go 1.22 in v0.31.0. - -### Debug - -```bash -tinygo build -o utls.wasm -target=wasi -scheduler=asyncify -gc=conservative -tags=purego . -``` - -### Release - -```bash -tinygo build -o utls.wasm -target=wasi -no-debug -scheduler=asyncify -gc=conservative -tags=purego . -``` - -## Dependencies - -The `utls` imported must be from the `wasm` branch of [uTLS](https://github.com/refraction-networking/utls/tree/wasm). You may use a replace directive in `go.mod` to your local clone of uTLS, or use a tagged version with suffix `-wasm` (e.g., `v1.6.2-wasm`) to make sure it is tagged from the correct branch. \ No newline at end of file diff --git a/tinygo/v0/examples/utls/lib/lib.go b/tinygo/v0/examples/utls/lib/lib.go deleted file mode 100644 index b2d73c0..0000000 --- a/tinygo/v0/examples/utls/lib/lib.go +++ /dev/null @@ -1,63 +0,0 @@ -package lib - -import ( - tls "github.com/refraction-networking/utls" -) - -//tinyjson:json -type TLSConfig struct { - NextProtos []string `json:"next_protos"` - ApplicationSettings map[string][]byte `json:"application_settings"` - ServerName string `json:"server_name"` - InsecureSkipVerify bool `json:"insecure_skip_verify"` - InsecureSkipTimeVerify bool `json:"insecure_skip_time_verify"` - OmitEmptyPsk bool `json:"omit_empty_psk"` - InsecureServerNameToVerify string `json:"insecure_server_name_to_verify"` - SessionTicketsDisabled bool `json:"session_tickets_disabled"` - PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled"` - DynamicRecordSizingDisabled bool `json:"dynamic_record_sizing_disabled"` - ECHConfigs []byte `json:"ech_configs"` -} - -//tinyjson:json -type Configurables struct { - TLSConfig *TLSConfig `json:"tls_config"` // will be converted to tls.Config - ClientHelloID string `json:"client_hello_id"` // will be converted to tls.ClientHelloID -} - -func (c *Configurables) GetTLSConfig() *tls.Config { - config := &tls.Config{ - NextProtos: c.TLSConfig.NextProtos, - ApplicationSettings: c.TLSConfig.ApplicationSettings, - ServerName: c.TLSConfig.ServerName, - InsecureSkipVerify: c.TLSConfig.InsecureSkipVerify, - InsecureSkipTimeVerify: c.TLSConfig.InsecureSkipTimeVerify, - OmitEmptyPsk: c.TLSConfig.OmitEmptyPsk, - InsecureServerNameToVerify: c.TLSConfig.InsecureServerNameToVerify, - SessionTicketsDisabled: c.TLSConfig.SessionTicketsDisabled, - PQSignatureSchemesEnabled: c.TLSConfig.PQSignatureSchemesEnabled, - DynamicRecordSizingDisabled: c.TLSConfig.DynamicRecordSizingDisabled, - } - - echConfigs, err := tls.UnmarshalECHConfigs(c.TLSConfig.ECHConfigs) - if err == nil { // otherwise do we need to return an error or just ignore it? - config.ECHConfigs = echConfigs - } - - return config -} - -func (c *Configurables) GetClientHelloID() tls.ClientHelloID { - switch c.ClientHelloID { - case "HelloChrome_Auto", "HelloChrome", "Chrome", "chrome": - return tls.HelloChrome_Auto - case "HelloEdge_Auto", "HelloEdge", "Edge", "edge": - return tls.HelloEdge_Auto - case "HelloFirefox_Auto", "HelloFirefox", "Firefox", "firefox": - return tls.HelloFirefox_Auto - case "HelloSafari_Auto", "HelloSafari", "Safari", "safari": - return tls.HelloSafari_Auto - default: - panic("unknown client hello id") - } -} diff --git a/tinygo/v0/examples/utls/lib/lib_tinyjson.go b/tinygo/v0/examples/utls/lib/lib_tinyjson.go deleted file mode 100644 index f828880..0000000 --- a/tinygo/v0/examples/utls/lib/lib_tinyjson.go +++ /dev/null @@ -1,310 +0,0 @@ -// Code generated by tinyjson for marshaling/unmarshaling. DO NOT EDIT. - -package lib - -import ( - tinyjson "github.com/CosmWasm/tinyjson" - jlexer "github.com/CosmWasm/tinyjson/jlexer" - jwriter "github.com/CosmWasm/tinyjson/jwriter" -) - -// suppress unused package warning -var ( - _ *jlexer.Lexer - _ *jwriter.Writer - _ tinyjson.Marshaler -) - -func tinyjsonAded76e7DecodeGithubComRefractionNetworkingWatmTinygoV0ExamplesUtlsLib(in *jlexer.Lexer, out *TLSConfig) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } - switch key { - case "next_protos": - if in.IsNull() { - in.Skip() - out.NextProtos = nil - } else { - in.Delim('[') - if out.NextProtos == nil { - if !in.IsDelim(']') { - out.NextProtos = make([]string, 0, 4) - } else { - out.NextProtos = []string{} - } - } else { - out.NextProtos = (out.NextProtos)[:0] - } - for !in.IsDelim(']') { - var v1 string - v1 = string(in.String()) - out.NextProtos = append(out.NextProtos, v1) - in.WantComma() - } - in.Delim(']') - } - case "application_settings": - if in.IsNull() { - in.Skip() - } else { - in.Delim('{') - out.ApplicationSettings = make(map[string][]uint8) - for !in.IsDelim('}') { - key := string(in.String()) - in.WantColon() - var v2 []uint8 - if in.IsNull() { - in.Skip() - v2 = nil - } else { - v2 = in.Bytes() - } - (out.ApplicationSettings)[key] = v2 - in.WantComma() - } - in.Delim('}') - } - case "server_name": - out.ServerName = string(in.String()) - case "insecure_skip_verify": - out.InsecureSkipVerify = bool(in.Bool()) - case "insecure_skip_time_verify": - out.InsecureSkipTimeVerify = bool(in.Bool()) - case "omit_empty_psk": - out.OmitEmptyPsk = bool(in.Bool()) - case "insecure_server_name_to_verify": - out.InsecureServerNameToVerify = string(in.String()) - case "session_tickets_disabled": - out.SessionTicketsDisabled = bool(in.Bool()) - case "pq_signature_schemes_enabled": - out.PQSignatureSchemesEnabled = bool(in.Bool()) - case "dynamic_record_sizing_disabled": - out.DynamicRecordSizingDisabled = bool(in.Bool()) - case "ech_configs": - if in.IsNull() { - in.Skip() - out.ECHConfigs = nil - } else { - out.ECHConfigs = in.Bytes() - } - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func tinyjsonAded76e7EncodeGithubComRefractionNetworkingWatmTinygoV0ExamplesUtlsLib(out *jwriter.Writer, in TLSConfig) { - out.RawByte('{') - first := true - _ = first - { - const prefix string = ",\"next_protos\":" - out.RawString(prefix[1:]) - if in.NextProtos == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { - out.RawString("null") - } else { - out.RawByte('[') - for v5, v6 := range in.NextProtos { - if v5 > 0 { - out.RawByte(',') - } - out.String(string(v6)) - } - out.RawByte(']') - } - } - { - const prefix string = ",\"application_settings\":" - out.RawString(prefix) - if in.ApplicationSettings == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 { - out.RawString(`null`) - } else { - out.RawByte('{') - v7First := true - for v7Name, v7Value := range in.ApplicationSettings { - if v7First { - v7First = false - } else { - out.RawByte(',') - } - out.String(string(v7Name)) - out.RawByte(':') - out.Base64Bytes(v7Value) - } - out.RawByte('}') - } - } - { - const prefix string = ",\"server_name\":" - out.RawString(prefix) - out.String(string(in.ServerName)) - } - { - const prefix string = ",\"insecure_skip_verify\":" - out.RawString(prefix) - out.Bool(bool(in.InsecureSkipVerify)) - } - { - const prefix string = ",\"insecure_skip_time_verify\":" - out.RawString(prefix) - out.Bool(bool(in.InsecureSkipTimeVerify)) - } - { - const prefix string = ",\"omit_empty_psk\":" - out.RawString(prefix) - out.Bool(bool(in.OmitEmptyPsk)) - } - { - const prefix string = ",\"insecure_server_name_to_verify\":" - out.RawString(prefix) - out.String(string(in.InsecureServerNameToVerify)) - } - { - const prefix string = ",\"session_tickets_disabled\":" - out.RawString(prefix) - out.Bool(bool(in.SessionTicketsDisabled)) - } - { - const prefix string = ",\"pq_signature_schemes_enabled\":" - out.RawString(prefix) - out.Bool(bool(in.PQSignatureSchemesEnabled)) - } - { - const prefix string = ",\"dynamic_record_sizing_disabled\":" - out.RawString(prefix) - out.Bool(bool(in.DynamicRecordSizingDisabled)) - } - { - const prefix string = ",\"ech_configs\":" - out.RawString(prefix) - out.Base64Bytes(in.ECHConfigs) - } - out.RawByte('}') -} - -// MarshalJSON supports json.Marshaler interface -func (v TLSConfig) MarshalJSON() ([]byte, error) { - w := jwriter.Writer{} - tinyjsonAded76e7EncodeGithubComRefractionNetworkingWatmTinygoV0ExamplesUtlsLib(&w, v) - return w.Buffer.BuildBytes(), w.Error -} - -// MarshalTinyJSON supports tinyjson.Marshaler interface -func (v TLSConfig) MarshalTinyJSON(w *jwriter.Writer) { - tinyjsonAded76e7EncodeGithubComRefractionNetworkingWatmTinygoV0ExamplesUtlsLib(w, v) -} - -// UnmarshalJSON supports json.Unmarshaler interface -func (v *TLSConfig) UnmarshalJSON(data []byte) error { - r := jlexer.Lexer{Data: data} - tinyjsonAded76e7DecodeGithubComRefractionNetworkingWatmTinygoV0ExamplesUtlsLib(&r, v) - return r.Error() -} - -// UnmarshalTinyJSON supports tinyjson.Unmarshaler interface -func (v *TLSConfig) UnmarshalTinyJSON(l *jlexer.Lexer) { - tinyjsonAded76e7DecodeGithubComRefractionNetworkingWatmTinygoV0ExamplesUtlsLib(l, v) -} -func tinyjsonAded76e7DecodeGithubComRefractionNetworkingWatmTinygoV0ExamplesUtlsLib1(in *jlexer.Lexer, out *Configurables) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } - switch key { - case "tls_config": - if in.IsNull() { - in.Skip() - out.TLSConfig = nil - } else { - if out.TLSConfig == nil { - out.TLSConfig = new(TLSConfig) - } - (*out.TLSConfig).UnmarshalTinyJSON(in) - } - case "client_hello_id": - out.ClientHelloID = string(in.String()) - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func tinyjsonAded76e7EncodeGithubComRefractionNetworkingWatmTinygoV0ExamplesUtlsLib1(out *jwriter.Writer, in Configurables) { - out.RawByte('{') - first := true - _ = first - { - const prefix string = ",\"tls_config\":" - out.RawString(prefix[1:]) - if in.TLSConfig == nil { - out.RawString("null") - } else { - (*in.TLSConfig).MarshalTinyJSON(out) - } - } - { - const prefix string = ",\"client_hello_id\":" - out.RawString(prefix) - out.String(string(in.ClientHelloID)) - } - out.RawByte('}') -} - -// MarshalJSON supports json.Marshaler interface -func (v Configurables) MarshalJSON() ([]byte, error) { - w := jwriter.Writer{} - tinyjsonAded76e7EncodeGithubComRefractionNetworkingWatmTinygoV0ExamplesUtlsLib1(&w, v) - return w.Buffer.BuildBytes(), w.Error -} - -// MarshalTinyJSON supports tinyjson.Marshaler interface -func (v Configurables) MarshalTinyJSON(w *jwriter.Writer) { - tinyjsonAded76e7EncodeGithubComRefractionNetworkingWatmTinygoV0ExamplesUtlsLib1(w, v) -} - -// UnmarshalJSON supports json.Unmarshaler interface -func (v *Configurables) UnmarshalJSON(data []byte) error { - r := jlexer.Lexer{Data: data} - tinyjsonAded76e7DecodeGithubComRefractionNetworkingWatmTinygoV0ExamplesUtlsLib1(&r, v) - return r.Error() -} - -// UnmarshalTinyJSON supports tinyjson.Unmarshaler interface -func (v *Configurables) UnmarshalTinyJSON(l *jlexer.Lexer) { - tinyjsonAded76e7DecodeGithubComRefractionNetworkingWatmTinygoV0ExamplesUtlsLib1(l, v) -} diff --git a/tinygo/v0/examples/utls/main.go b/tinygo/v0/examples/utls/main.go deleted file mode 100644 index 2678c6a..0000000 --- a/tinygo/v0/examples/utls/main.go +++ /dev/null @@ -1,11 +0,0 @@ -package main - -import v0 "github.com/refraction-networking/watm/tinygo/v0" - -func init() { - v0.BuildDialerWithWrappingTransport(&UTLSClientWrappingTransport{}) - // v0.BuildListenerWithWrappingTransport(&UTLSClientWrappingTransport{}) - // v0.BuildRelayWithWrappingTransport(&UTLSClientWrappingTransport{}, v0.RelayWrapRemote) -} - -func main() {} diff --git a/tinygo/v0/examples/utls/wrapper_transport.go b/tinygo/v0/examples/utls/wrapper_transport.go deleted file mode 100644 index ea031b9..0000000 --- a/tinygo/v0/examples/utls/wrapper_transport.go +++ /dev/null @@ -1,69 +0,0 @@ -package main - -import ( - "github.com/CosmWasm/tinyjson" - tls "github.com/refraction-networking/utls" - v0 "github.com/refraction-networking/watm/tinygo/v0" - "github.com/refraction-networking/watm/tinygo/v0/examples/utls/lib" - v0net "github.com/refraction-networking/watm/tinygo/v0/net" -) - -// type guard: ReverseWrappingTransport must implement [v0.WrappingTransport]. -var _ v0.WrappingTransport = (*UTLSClientWrappingTransport)(nil) - -type UTLSClientWrappingTransport struct { - tlsConfig *tls.Config - clientHelloID tls.ClientHelloID -} - -func (uwt *UTLSClientWrappingTransport) Wrap(conn v0net.Conn) (v0net.Conn, error) { - if uwt.tlsConfig == nil { - uwt.tlsConfig = &tls.Config{InsecureSkipVerify: true} - } - - var emptyClientHelloID tls.ClientHelloID - if uwt.clientHelloID == emptyClientHelloID { - uwt.clientHelloID = tls.HelloChrome_Auto - } - - tlsConn := tls.UClient(conn, uwt.tlsConfig, uwt.clientHelloID) - if err := tlsConn.Handshake(); err != nil { - return nil, err - } - - if err := conn.SetNonBlock(true); err != nil { - return nil, err - } - - return &UTLSConn{ - Conn: conn, - tlsConn: tlsConn, - }, nil -} - -var _ v0.ConfigurableTransport = (*UTLSClientWrappingTransport)(nil) - -func (uwt *UTLSClientWrappingTransport) Configure(config []byte) error { - configurables := &lib.Configurables{} - if err := tinyjson.Unmarshal(config, configurables); err != nil { - return err - } - - uwt.tlsConfig = configurables.GetTLSConfig() - uwt.clientHelloID = configurables.GetClientHelloID() - - return nil -} - -type UTLSConn struct { - v0net.Conn // embedded Conn - tlsConn *tls.UConn -} - -func (uc *UTLSConn) Read(b []byte) (n int, err error) { - return uc.tlsConn.Read(b) -} - -func (uc *UTLSConn) Write(b []byte) (n int, err error) { - return uc.tlsConn.Write(b) -} diff --git a/tinygo/v0/exports.go b/tinygo/v0/exports.go deleted file mode 100644 index 23ca5b0..0000000 --- a/tinygo/v0/exports.go +++ /dev/null @@ -1,235 +0,0 @@ -package v0 - -import ( - "bytes" - "errors" - "log" - "os" - "syscall" - - v0net "github.com/refraction-networking/watm/tinygo/v0/net" - "github.com/refraction-networking/watm/wasip1" -) - -// Export the WATM version indicator. -// -// gaukas: I noticed that in Rust we can export a const variable -// but here in Go we have to export a function instead. Luckily -// in our standard we are not checking against its type but only -// the name. -// -//export _water_v0 -func _water_v0() {} - -//export _water_init -func _water_init() int32 { - // Check if dialer/listener/relay is configurable. If so, - // pull the config file from the host and configure them. - dct := d.ConfigurableTransport() - lct := l.ConfigurableTransport() - // rct := r.ConfigurableTransport() - if dct != nil || lct != nil /* || rct != nil */ { - config, err := readConfig() - if err == nil || config != nil { - if dct != nil { - dct.Configure(config) - } - - if lct != nil { - lct.Configure(config) - } - - // if rct != nil { - // rct.Configure(config) - // } - } else if !errors.Is(err, syscall.EACCES) { // EACCES means no config file provided by the host - return wasip1.EncodeWATERError(err.(syscall.Errno)) - } - } - - // TODO: initialize the dialer, listener, and relay - d.Initialize() - l.Initialize() - // r.Initialize() - - return 0 // ESUCCESS -} - -func readConfig() (config []byte, err error) { - fd, err := wasip1.DecodeWATERError(_import_pull_config()) - if err != nil { - return nil, err - } - - file := os.NewFile(uintptr(fd), "config") - if file == nil { - return nil, syscall.EBADF - } - - // read the config file - buf := new(bytes.Buffer) - _, err = buf.ReadFrom(file) - if err != nil { - log.Println("readConfig: (*bytes.Buffer).ReadFrom:", err) - return nil, syscall.EIO - } - - config = buf.Bytes() - - // close the file - if err := file.Close(); err != nil { - return config, syscall.EIO - } - - return config, nil -} - -//export _water_cancel_with -func _water_cancel_with(cancelFd int32) int32 { - cancelConn = v0net.RebuildTCPConn(cancelFd) - if err := cancelConn.(v0net.Conn).SetNonBlock(true); err != nil { - log.Printf("dial: cancelConn.SetNonblock: %v", err) - return wasip1.EncodeWATERError(err.(syscall.Errno)) - } - - return 0 // ESUCCESS -} - -//export _water_dial -func _water_dial(internalFd int32) (networkFd int32) { - if workerIdentity != identity_uninitialized { - return wasip1.EncodeWATERError(syscall.EBUSY) // device or resource busy (worker already initialized) - } - - // wrap the internalFd into a v0net.Conn - sourceConn = v0net.RebuildTCPConn(internalFd) - err := sourceConn.(*v0net.TCPConn).SetNonBlock(true) - if err != nil { - log.Printf("dial: sourceConn.SetNonblock: %v", err) - return wasip1.EncodeWATERError(err.(syscall.Errno)) - } - - if d.wt != nil { - // call v0net.Dial - rawNetworkConn, err := v0net.Dial("", "") - if err != nil { - log.Printf("dial: v0net.Dial: %v", err) - return wasip1.EncodeWATERError(err.(syscall.Errno)) - } - networkFd = rawNetworkConn.Fd() - - // Note: here we are not setting nonblocking mode on the - // networkConn -- it depends on the WrappingTransport to - // determine whether to set nonblocking mode or not. - - // wrap - remoteConn, err = d.wt.Wrap(rawNetworkConn) - if err != nil { - log.Printf("dial: d.wt.Wrap: %v", err) - return wasip1.EncodeWATERError(syscall.EPROTO) // protocol error - } - // TODO: implement _water_dial with DialingTransport - } else { - return wasip1.EncodeWATERError(syscall.EPERM) // operation not permitted - } - - workerIdentity = identity_dialer - return networkFd -} - -//export _water_accept -func _water_accept(internalFd int32) (networkFd int32) { - if workerIdentity != identity_uninitialized { - return wasip1.EncodeWATERError(syscall.EBUSY) // device or resource busy (worker already initialized) - } - - // wrap the internalFd into a v0net.Conn - sourceConn = v0net.RebuildTCPConn(internalFd) - err := sourceConn.(*v0net.TCPConn).SetNonBlock(true) - if err != nil { - log.Printf("dial: sourceConn.SetNonblock: %v", err) - return wasip1.EncodeWATERError(err.(syscall.Errno)) - } - - if d.wt != nil { - var lis v0net.Listener = &v0net.TCPListener{} - // call v0net.Listener.Accept - rawNetworkConn, err := lis.Accept() - if err != nil { - log.Printf("dial: v0net.Listener.Accept: %v", err) - return wasip1.EncodeWATERError(err.(syscall.Errno)) - } - networkFd = rawNetworkConn.Fd() - - // Note: here we are not setting nonblocking mode on the - // networkConn -- it depends on the WrappingTransport to - // determine whether to set nonblocking mode or not. - - // wrap - remoteConn, err = d.wt.Wrap(rawNetworkConn) - if err != nil { - log.Printf("dial: d.wt.Wrap: %v", err) - return wasip1.EncodeWATERError(syscall.EPROTO) // protocol error - } - // TODO: implement _water_accept with ListeningTransport - } else { - return wasip1.EncodeWATERError(syscall.EPERM) // operation not permitted - } - - workerIdentity = identity_listener - return networkFd -} - -//export _water_associate -func _water_associate() int32 { - if workerIdentity != identity_uninitialized { - return wasip1.EncodeWATERError(syscall.EBUSY) // device or resource busy (worker already initialized) - } - - if r.wt != nil { - var err error - var lis v0net.Listener = &v0net.TCPListener{} - sourceConn, err = lis.Accept() - if err != nil { - log.Printf("dial: v0net.Listener.Accept: %v", err) - return wasip1.EncodeWATERError(err.(syscall.Errno)) - } - - remoteConn, err = v0net.Dial("", "") - if err != nil { - log.Printf("dial: v0net.Dial: %v", err) - return wasip1.EncodeWATERError(err.(syscall.Errno)) - } - - if r.wrapSelection == RelayWrapRemote { - // wrap remoteConn - remoteConn, err = r.wt.Wrap(remoteConn.(*v0net.TCPConn)) - // set sourceConn, the not-wrapped one, to non-blocking mode - sourceConn.(*v0net.TCPConn).SetNonBlock(true) - } else { - // wrap sourceConn - sourceConn, err = r.wt.Wrap(sourceConn.(*v0net.TCPConn)) - // set remoteConn, the not-wrapped one, to non-blocking mode - remoteConn.(*v0net.TCPConn).SetNonBlock(true) - } - if err != nil { - log.Printf("dial: r.wt.Wrap: %v", err) - return wasip1.EncodeWATERError(syscall.EPROTO) // protocol error - } - } else { - return wasip1.EncodeWATERError(syscall.EPERM) // operation not permitted - } - - workerIdentity = identity_relay - return 0 -} - -//export _water_worker -func _water_worker() int32 { - if workerIdentity == identity_uninitialized { - log.Println("worker: uninitialized") - return wasip1.EncodeWATERError(syscall.ENOTCONN) // socket not connected - } - log.Printf("worker: working as %s", identityStrings[workerIdentity]) - return worker() -} diff --git a/tinygo/v0/imports_unsupported.go b/tinygo/v0/imports_unsupported.go deleted file mode 100644 index b064e9d..0000000 --- a/tinygo/v0/imports_unsupported.go +++ /dev/null @@ -1,31 +0,0 @@ -//go:build !wasip1 && !wasi - -package v0 - -import ( - "syscall" - "time" - "unsafe" - - "github.com/refraction-networking/watm/wasip1" -) - -func _import_host_defer() { - // just do nothing, since nothing really matters if not - // commanded by the host. -} - -// emulate the behavior when no config is provided on -// the host side. -func _import_pull_config() (fd int32) { - return wasip1.EncodeWATERError(syscall.ENOENT) -} - -// emulate the behavior when no file descriptors are -// ready and the timeout expires immediately. -func poll_oneoff(in, out unsafe.Pointer, nsubscriptions uint32, nevents unsafe.Pointer) uint32 { - // wait for a very short period to simulate the polling - time.Sleep(50 * time.Millisecond) - *(*uint32)(nevents) = nsubscriptions - return 0 -} diff --git a/tinygo/v0/imports_wasi.go b/tinygo/v0/imports_wasi.go deleted file mode 100644 index 9e23938..0000000 --- a/tinygo/v0/imports_wasi.go +++ /dev/null @@ -1,17 +0,0 @@ -//go:build wasip1 || wasi - -package v0 - -import "unsafe" - -//go:wasmimport env host_defer -//go:noescape -func _import_host_defer() - -//go:wasmimport env pull_config -//go:noescape -func _import_pull_config() (fd int32) - -//go:wasmimport wasi_snapshot_preview1 poll_oneoff -//go:noescape -func poll_oneoff(in, out unsafe.Pointer, nsubscriptions size, nevents unsafe.Pointer) errno diff --git a/tinygo/v0/listener.go b/tinygo/v0/listener.go deleted file mode 100644 index 2c333f2..0000000 --- a/tinygo/v0/listener.go +++ /dev/null @@ -1,52 +0,0 @@ -package v0 - -type listener struct { - wt WrappingTransport - // lt ListeningTransport -} - -func (l *listener) ConfigurableTransport() ConfigurableTransport { - if l.wt != nil { - if wt, ok := l.wt.(ConfigurableTransport); ok { - return wt - } - } - - // if l.lt != nil { - // if lt, ok := l.lt.(ConfigurableTransport); ok { - // return lt - // } - // } - - return nil -} - -func (l *listener) Initialize() { - // TODO: allow initialization on listener -} - -var l listener - -// BuildListenerWithWrappingTransport arms the listener with a -// [WrappingTransport] that is used to wrap a [v0net.Conn] into -// another [net.Conn] by providing some high-level application -// layer protocol. -// -// Mutually exclusive with [BuildListenerWithListeningTransport]. -func BuildListenerWithWrappingTransport(wt WrappingTransport) { - l.wt = wt - // l.lt = nil -} - -// BuildListenerWithListeningTransport arms the listener with a -// [ListeningTransport] that is used to accept incoming connections -// on a local address and provide high-level application layer -// protocol over the accepted connection. -// -// Mutually exclusive with [BuildListenerWithWrappingTransport]. -func BuildListenerWithListeningTransport(lt ListeningTransport) { - // TODO: implement BuildListenerWithListeningTransport - // l.lt = lt - // l.wt = nil - panic("BuildListenerWithListeningTransport: not implemented") -} diff --git a/tinygo/v0/net/conn.go b/tinygo/v0/net/conn.go deleted file mode 100644 index b1ef0d4..0000000 --- a/tinygo/v0/net/conn.go +++ /dev/null @@ -1,224 +0,0 @@ -package net - -import ( - "errors" - "io" - "net" - "os" - "syscall" - "time" -) - -// Conn is the interface for a generic stream-oriented network connection. -type Conn interface { - net.Conn - syscall.Conn - SetNonBlock(nonblocking bool) error - Fd() int32 -} - -// type guard: *TCPConn must implement Conn -var _ Conn = (*TCPConn)(nil) - -// TCPConn is a wrapper around a file descriptor that implements the [net.Conn]. -// -// Despite the name, this type is not specific to TCP connections. It can be used -// for any file descriptor that is connection-oriented. -type TCPConn struct { - rawConn *rawTCPConn - - readDeadline time.Time - writeDeadline time.Time -} - -// RebuildTCPConn recovers a [TCPConn] from a file descriptor. -func RebuildTCPConn(fd int32) *TCPConn { - return &TCPConn{ - rawConn: &rawTCPConn{ - fd: fd, - }, - } -} - -// Read implements [net.Conn.Read]. -func (c *TCPConn) Read(b []byte) (n int, err error) { - if rdl := c.readDeadline; rdl.IsZero() { - // if no deadline set, behavior depends on blocking mode of the - // underlying file descriptor. - return syscallFnFd(c.rawConn, func(fd uintptr) (int, error) { - n, err := syscall.Read(syscallFd(fd), b) - if n == 0 && err == nil { - err = io.EOF - } - if n < 0 && err != nil { - n = 0 - } - return n, err - }) - } else { - // readDeadline is set, if EAGAIN/EWOULDBLOCK is returned, - // we retry until the deadline is reached. - for { - if n, err = syscallFnFd(c.rawConn, func(fd uintptr) (int, error) { - n, err := syscall.Read(syscallFd(fd), b) - if n == 0 && err == nil { - err = io.EOF - } - if n < 0 && err != nil { - n = 0 - } - return n, err - }); errors.Is(err, syscall.EAGAIN) { - if time.Now().Before(rdl) { - continue - } - } - return n, err - } - } -} - -// Write implements [net.Conn.Write]. -func (c *TCPConn) Write(b []byte) (n int, writeErr error) { - if err := c.rawConn.Write(func(fd uintptr) (done bool) { - n, writeErr = writeFD(fd, b) - if errors.Is(writeErr, syscall.EAGAIN) { - if wdl := c.writeDeadline; wdl.IsZero() || time.Now().Before(wdl) { - return false - } - writeErr = os.ErrDeadlineExceeded - } - return true - }); err != nil { - return n, err - } - return -} - -// Close implements [net.Conn.Close]. -func (c *TCPConn) Close() error { - // if shutdownErr := syscallControlFd(c.rawConn, func(fd uintptr) error { - // return syscall.Shutdown(int(fd), syscall.SHUT_RDWR) - // }); shutdownErr != nil { - // return shutdownErr - // } else { - // return syscallControlFd(c.rawConn, func(fd uintptr) error { - // return syscall.Close(int(fd)) - // }) - // } - return syscallControlFd(c.rawConn, func(fd uintptr) error { - return syscall.Close(syscallFd(fd)) - }) -} - -// LocalAddr implements [net.Conn.LocalAddr]. -// -// This function is currently not implemented, but may be fleshed out in the -// future should there be better support for getting the local address of a -// socket managed by the Go runtime. -func (c *TCPConn) LocalAddr() net.Addr { - return nil -} - -// RemoteAddr implements [net.Conn.RemoteAddr]. -// -// This function is currently not implemented, but may be fleshed out in the -// future should there be better support for getting the remote address of a -// socket managed by the Go runtime. -func (c *TCPConn) RemoteAddr() net.Addr { - return nil -} - -// SetDeadline implements [net.Conn.SetDeadline]. -func (c *TCPConn) SetDeadline(t time.Time) error { - c.readDeadline = t - c.writeDeadline = t - - // set deadline will enable non-blocking mode - return c.SetNonBlock(true) -} - -// SetReadDeadline implements [net.Conn.SetReadDeadline]. -func (c *TCPConn) SetReadDeadline(t time.Time) error { - c.readDeadline = t - - // set deadline will enable non-blocking mode - return c.SetNonBlock(true) -} - -// SetWriteDeadline implements [net.Conn.SetWriteDeadline]. -func (c *TCPConn) SetWriteDeadline(t time.Time) error { - c.writeDeadline = t - - return nil -} - -// SyscallConn implements [syscall.Conn]. -func (c *TCPConn) SyscallConn() (syscall.RawConn, error) { - return c.rawConn, nil -} - -// SetNonBlock sets the socket to blocking or non-blocking mode. -func (c *TCPConn) SetNonBlock(nonblocking bool) error { - return syscallControlFd(c.rawConn, func(fd uintptr) error { - if errno := syscallSetNonblock(fd, nonblocking); errno != nil && !errors.Is(errno, syscall.Errno(0)) { - return errno - } else { - return nil - } - }) -} - -// Fd returns the file descriptor of the socket. -func (c *TCPConn) Fd() int32 { - return c.rawConn.fd -} - -// type guard: *rawTCPConn must implement [syscall.RawConn]. -var _ syscall.RawConn = (*rawTCPConn)(nil) - -// rawTCPConn is a wrapper around a file descriptor that implements the -// [syscall.RawConn] interface for some syscalls. -type rawTCPConn struct { - fd int32 -} - -// Control implements [syscall.RawConn.Control]. -func (rt *rawTCPConn) Control(f func(fd uintptr)) error { - if rt.fd == 0 { - return syscall.EBADF - } - - f(uintptr(rt.fd)) - return nil -} - -// Read implements [syscall.RawConn.Read]. -// -// Deprecated: Use [net.Conn.Read] instead. -func (rt *rawTCPConn) Read(f func(fd uintptr) (done bool)) error { - if rt.fd == 0 { - return syscall.EBADF - } - - for { - if f(uintptr(rt.fd)) { - return nil - } - } -} - -// Write implements [syscall.RawConn.Write]. -// -// Deprecated: Use [net.Conn.Write] instead. -func (rt *rawTCPConn) Write(f func(fd uintptr) (done bool)) error { - if rt.fd == 0 { - return syscall.EBADF - } - - for { - if f(uintptr(rt.fd)) { - return nil - } - } -} diff --git a/tinygo/v0/net/conn_test.go b/tinygo/v0/net/conn_test.go deleted file mode 100644 index 2600f73..0000000 --- a/tinygo/v0/net/conn_test.go +++ /dev/null @@ -1,267 +0,0 @@ -//go:build !wasip1 && !wasi - -package net_test - -import ( - "bytes" - "errors" - "io" - "net" - "runtime" - "syscall" - "testing" - "time" - - v0net "github.com/refraction-networking/watm/tinygo/v0/net" -) - -func tcpConnPair() (*net.TCPConn, *net.TCPConn, error) { - l, err := net.Listen("tcp", "localhost:0") - if err != nil { - return nil, nil, err - } - - dialConn, err := net.Dial("tcp", l.Addr().String()) - if err != nil { - return nil, nil, err - } - - acceptConn, err := l.Accept() - if err != nil { - return nil, nil, err - } - - return dialConn.(*net.TCPConn), acceptConn.(*net.TCPConn), nil -} - -func TestTCPConn_Read(t *testing.T) { - conn1, conn2, err := tcpConnPair() - if err != nil { - t.Fatal(err) - } - defer conn1.Close() - defer conn2.Close() - - var msgWr = []byte("hello") - var bufRd = make([]byte, 16) - - if _, err := conn1.Write(msgWr); err != nil { - t.Fatal(err) - } - - // rebuild conn2 as a *TCPConn - var tcpConn2 *v0net.TCPConn - - // expose conn2's fd - rawConn2, err := conn2.SyscallConn() - if err != nil { - t.Fatal(err) - } else if err := rawConn2.Control(func(fd uintptr) { - tcpConn2 = v0net.RebuildTCPConn(int32(fd)) - }); err != nil { - t.Fatal(err) - } - - n, err := tcpConn2.Read(bufRd) - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(bufRd[:n], msgWr) { - t.Fatalf("read: expected %s, got %s", msgWr, bufRd[:n]) - } - - // close the peer connection and read again - conn1.Close() - if _, err := tcpConn2.Read(bufRd); err == nil { - t.Fatal("read after peer-close: expected error, got nil") - } - - runtime.KeepAlive(conn1) - runtime.KeepAlive(conn2) -} - -func TestTCPConn_Write(t *testing.T) { - conn1, conn2, err := tcpConnPair() - if err != nil { - t.Fatal(err) - } - defer conn1.Close() - defer conn2.Close() - - var msgWr = []byte("hello") - var bufRd = make([]byte, 16) - - // rebuild conn1 as a *TCPConn - var tcpConn1 *v0net.TCPConn - - // expose conn1's fd - rawConn1, err := conn1.SyscallConn() - if err != nil { - t.Fatal(err) - } else if err := rawConn1.Control(func(fd uintptr) { - tcpConn1 = v0net.RebuildTCPConn(int32(fd)) - }); err != nil { - t.Fatal(err) - } - - if _, err := tcpConn1.Write(msgWr); err != nil { - t.Fatal(err) - } - - n, err := conn2.Read(bufRd) - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(bufRd[:n], msgWr) { - t.Fatalf("expected %s, got %s", msgWr, bufRd[:n]) - } - - runtime.KeepAlive(conn1) - runtime.KeepAlive(conn2) -} - -func TestTCPConn_Close(t *testing.T) { - conn1, conn2, err := tcpConnPair() - if err != nil { - t.Fatal(err) - } - defer conn1.Close() - defer conn2.Close() - - // rebuild conn1 as a *TCPConn - var tcpConn1 *v0net.TCPConn - - // expose conn1's fd - rawConn1, err := conn1.SyscallConn() - if err != nil { - t.Fatal(err) - } else if err := rawConn1.Control(func(fd uintptr) { - tcpConn1 = v0net.RebuildTCPConn(int32(fd)) - }); err != nil { - t.Fatal(err) - } - - if err := tcpConn1.Close(); err != nil { - t.Fatal(err) - } - - // At this point, conn1 (and the rebuilt tcpConn1) should not be writable - if _, err := conn1.Write([]byte("hello")); err == nil { - t.Fatal("expected error, got nil") - } else if _, err := tcpConn1.Write([]byte("hello")); err == nil { - t.Fatal("expected error, got nil") - } - - // Similarly, all tcpConn1, conn1 and conn2 should not be readable - var bufRd = make([]byte, 16) - if _, err := tcpConn1.Read(bufRd); err == nil { - t.Fatal("expected error, got nil") - } else if _, err := conn1.Read(bufRd); err == nil { - t.Fatal("expected error, got nil") - } else if _, err := conn2.Read(bufRd); !errors.Is(err, io.EOF) && !errors.Is(err, syscall.ECONNRESET) { - t.Fatalf("expected io.EOF or syscall.ECONNRESET, got %v", err) - } - - runtime.KeepAlive(conn1) - runtime.KeepAlive(conn2) -} - -func TestTCPConn_SetNonBlock(t *testing.T) { - conn1, conn2, err := tcpConnPair() - if err != nil { - t.Fatal(err) - } - defer conn1.Close() - defer conn2.Close() - - // rebuild conn1 as a *TCPConn - var tcpConn1 *v0net.TCPConn - - // expose conn1's fd - rawConn1, err := conn1.SyscallConn() - if err != nil { - t.Fatal(err) - } else if err := rawConn1.Control(func(fd uintptr) { - tcpConn1 = v0net.RebuildTCPConn(int32(fd)) - }); err != nil { - t.Fatal(err) - } - - if err := tcpConn1.SetNonBlock(true); err != nil { - t.Fatal(err) - } - - // since conn2 has not been written to, tcpConn1 should not be readable. - if _, err := tcpConn1.Read(make([]byte, 16)); !errors.Is(err, syscall.EAGAIN) { - t.Fatalf("expected %s, got %s", syscall.EAGAIN, err) - } - - // write to conn2 - if _, err := conn2.Write([]byte("hello")); err != nil { - t.Fatal(err) - } - time.Sleep(10 * time.Microsecond) // wait for the packet to get through - - // now tcpConn1 should be readable - if _, err := tcpConn1.Read(make([]byte, 16)); err != nil { - t.Fatal(err) - } - - runtime.KeepAlive(conn1) - runtime.KeepAlive(conn2) -} - -func TestTCPConn_SetReadDeadline(t *testing.T) { - conn1, conn2, err := tcpConnPair() - if err != nil { - t.Fatal(err) - } - defer conn1.Close() - defer conn2.Close() - - // rebuild conn1 as a *TCPConn - var tcpConn1 *v0net.TCPConn - - // expose conn1's fd - rawConn1, err := conn1.SyscallConn() - if err != nil { - t.Fatal(err) - } else if err := rawConn1.Control(func(fd uintptr) { - tcpConn1 = v0net.RebuildTCPConn(int32(fd)) - }); err != nil { - t.Fatal(err) - } - - // set read deadline to 10ms later - timeStart := time.Now() - if err := tcpConn1.SetReadDeadline(timeStart.Add(10 * time.Millisecond)); err != nil { - t.Fatal(err) - } - - // since conn2 has not been written to, tcpConn1 should not be readable. - if _, err := tcpConn1.Read(make([]byte, 16)); !errors.Is(err, syscall.EAGAIN) { - t.Fatalf("expected %s, got %s", syscall.EAGAIN, err) - } - if time.Since(timeStart) < 10*time.Millisecond { - t.Fatalf("expected read to block for at least 10ms, but it only blocked for %s", time.Since(timeStart)) - } - - // write to conn2 - if _, err := conn2.Write([]byte("hello")); err != nil { - t.Fatal(err) - } - - // now tcpConn1 should be readable - if err := tcpConn1.SetReadDeadline(time.Now().Add(10 * time.Millisecond)); err != nil { - t.Fatal(err) - } - - if _, err := tcpConn1.Read(make([]byte, 16)); err != nil { - t.Fatal(err) - } - - runtime.KeepAlive(conn1) - runtime.KeepAlive(conn2) -} diff --git a/tinygo/v0/net/dial.go b/tinygo/v0/net/dial.go deleted file mode 100644 index b90d753..0000000 --- a/tinygo/v0/net/dial.go +++ /dev/null @@ -1,24 +0,0 @@ -package net - -import ( - "github.com/refraction-networking/watm/wasip1" -) - -// Dial dials a remote host for a network connection. -// -// In v0, network and address parameters are ignored, and -// it is internally called as [HostManagedDial]. -func Dial(_, _ string) (Conn, error) { - return HostManagedDial() -} - -// HostManagedDial asks the host to dial a remote host predefined -// by the host. -func HostManagedDial() (Conn, error) { - fd, err := wasip1.DecodeWATERError(_import_host_dial()) - if err != nil { - return nil, err - } - - return RebuildTCPConn(fd), nil -} diff --git a/tinygo/v0/net/fd.go b/tinygo/v0/net/fd.go deleted file mode 100644 index 73da9a8..0000000 --- a/tinygo/v0/net/fd.go +++ /dev/null @@ -1,47 +0,0 @@ -package net - -import "syscall" - -// writeFD writes data to the file descriptor fd. When a partial write occurs, -// it will continue with the remaining data until all data is written or an -// error occurs. If no progress is made in a single write call, it will return -// syscall.EIO. -// -// It is ported from (*FD).Write in golang/go/src/internal/poll/fd_unix.go -func writeFD(fd uintptr, p []byte) (int, error) { - var nn int - for { - n, err := ignoringEINTRIO(syscall.Write, syscallFd(fd), p[nn:]) - if n > 0 { - nn += n - } - if nn == len(p) { - return nn, err - } - if err != nil { - return nn, err - } - if n == 0 { - return nn, syscall.EIO - } - - // // TODO: retry if EAGAIN or no progress? - // if n == 0 { - // noprogress++ - // } - // if noprogress == 10 { - // return nn, syscall.EIO - // } - // runtime.Gosched() - } -} - -// ignoringEINTRIO is like ignoringEINTR, but just for IO calls. -func ignoringEINTRIO(fn func(fd syscallFd, p []byte) (int, error), fd syscallFd, p []byte) (int, error) { - for { - n, err := fn(fd, p) - if err != syscall.EINTR { - return n, err - } - } -} diff --git a/tinygo/v0/net/import.go b/tinygo/v0/net/import.go deleted file mode 100644 index 253851e..0000000 --- a/tinygo/v0/net/import.go +++ /dev/null @@ -1,29 +0,0 @@ -//go:build !wasip1 && !wasi - -package net - -var hostDialedFD int32 = -1 - -func SetHostDialedFD(fd int32) { - hostDialedFD = fd -} - -// This function should be imported from the host in WASI. -// On non-WASI platforms, it mimicks the behavior of the host -// by returning a file descriptor of preset value. -func _import_host_dial() (fd int32) { - return hostDialedFD -} - -var hostAcceptedFD int32 = -1 - -func SetHostAcceptedFD(fd int32) { - hostAcceptedFD = fd -} - -// This function should be imported from the host in WASI. -// On non-WASI platforms, it mimicks the behavior of the host -// by returning a file descriptor of preset value. -func _import_host_accept() (fd int32) { - return hostAcceptedFD -} diff --git a/tinygo/v0/net/import_wasi.go b/tinygo/v0/net/import_wasi.go deleted file mode 100644 index 34ee0fd..0000000 --- a/tinygo/v0/net/import_wasi.go +++ /dev/null @@ -1,31 +0,0 @@ -//go:build wasi || wasip1 - -package net - -import "unsafe" - -// Import the host-imported dialer function. -// -//go:wasmimport env host_dial -//go:noescape -func _import_host_dial() (fd int32) - -// Import the host-imported acceptor function. -// -//go:wasmimport env host_accept -//go:noescape -func _import_host_accept() (fd int32) - -// Import wasi_snapshot_preview1's fd_fdstat_set_flags function -// until tinygo supports it. -// -//go:wasmimport wasi_snapshot_preview1 fd_fdstat_set_flags -//go:noescape -func fd_fdstat_set_flags(fd int32, flags uint32) uint32 - -// Import wasi_snapshot_preview1's fd_fdstat_set_flags function -// until tinygo supports it. -// -//go:wasmimport wasi_snapshot_preview1 fd_fdstat_get -//go:noescape -func fd_fdstat_get(fd int32, buf unsafe.Pointer) uint32 diff --git a/tinygo/v0/net/listener.go b/tinygo/v0/net/listener.go deleted file mode 100644 index 9f61f71..0000000 --- a/tinygo/v0/net/listener.go +++ /dev/null @@ -1,33 +0,0 @@ -package net - -import "github.com/refraction-networking/watm/wasip1" - -// Listener is the interface for a generic network listener. -type Listener interface { - Accept() (Conn, error) -} - -// type guard: *TCPListener must implement Listener -var _ Listener = (*TCPListener)(nil) - -// TCPListener is a fake TCP listener which calls to the host -// to accept a connection. -// -// By saying "fake", it means that the file descriptor is not -// managed inside the WATM, but by the host application. -type TCPListener struct { -} - -func (l *TCPListener) Accept() (Conn, error) { - return HostManagedAccept() -} - -// HostManagedAccept asks the host to accept a connection. -func HostManagedAccept() (Conn, error) { - fd, err := wasip1.DecodeWATERError(_import_host_accept()) - if err != nil { - return nil, err - } - - return RebuildTCPConn(fd), nil -} diff --git a/tinygo/v0/net/syscall.go b/tinygo/v0/net/syscall.go deleted file mode 100644 index d8b6b8e..0000000 --- a/tinygo/v0/net/syscall.go +++ /dev/null @@ -1,24 +0,0 @@ -package net - -import ( - "syscall" -) - -func syscallControlFd(rawConn syscall.RawConn, f func(fd uintptr) error) (err error) { - if controlErr := rawConn.Control(func(fd uintptr) { - err = f(fd) - }); controlErr != nil { - // panic(fmt.Sprintf("controlErr = %v", controlErr)) - return controlErr - } - return err -} - -func syscallFnFd(rawConn syscall.RawConn, f func(fd uintptr) (int, error)) (n int, err error) { - if controlErr := rawConn.Control(func(fd uintptr) { - n, err = f(fd) - }); controlErr != nil { - return 0, controlErr - } - return n, err -} diff --git a/tinygo/v0/net/syscall_unix.go b/tinygo/v0/net/syscall_unix.go deleted file mode 100644 index faa6406..0000000 --- a/tinygo/v0/net/syscall_unix.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build unix && !wasi && !wasip1 - -package net - -import "syscall" - -func syscallSetNonblock(fd uintptr, nonblocking bool) (err error) { - return syscall.SetNonblock(int(fd), nonblocking) -} - -type syscallFd = int diff --git a/tinygo/v0/net/syscall_wasi.go b/tinygo/v0/net/syscall_wasi.go deleted file mode 100644 index 245b48c..0000000 --- a/tinygo/v0/net/syscall_wasi.go +++ /dev/null @@ -1,48 +0,0 @@ -//go:build wasi || wasip1 - -package net - -import ( - "syscall" - "unsafe" -) - -func syscallSetNonblock(fd uintptr, nonblocking bool) error { - flags, err := fd_fdstat_get_flags(int32(fd)) - if err != nil { - return err - } - if nonblocking { - flags |= FDFLAG_NONBLOCK - } else { - flags &^= FDFLAG_NONBLOCK - } - errno := fd_fdstat_set_flags(int32(fd), flags) - return syscall.Errno(errno) -} - -func fd_fdstat_get_flags(fd int32) (uint32, error) { - var stat fdstat - errno := fd_fdstat_get(fd, unsafe.Pointer(&stat)) - if errno != 0 { - return 0, syscall.Errno(errno) - } - return uint32(stat.fdflags), nil -} - -type fdstat struct { - filetype uint8 - fdflags uint16 - rightsBase uint64 - rightsInheriting uint64 -} - -const ( - FDFLAG_APPEND = 0x0001 - FDFLAG_DSYNC = 0x0002 - FDFLAG_NONBLOCK = 0x0004 - FDFLAG_RSYNC = 0x0008 - FDFLAG_SYNC = 0x0010 -) - -type syscallFd = int diff --git a/tinygo/v0/net/syscall_windows.go b/tinygo/v0/net/syscall_windows.go deleted file mode 100644 index 1de3513..0000000 --- a/tinygo/v0/net/syscall_windows.go +++ /dev/null @@ -1,35 +0,0 @@ -//go:build windows - -package net - -import ( - "syscall" - "unsafe" -) - -const ( - _FIONBIO = 0x8004667e -) - -var ( - // modws2_32 is WinSock. - modws2_32 = syscall.NewLazyDLL("ws2_32.dll") - // procioctlsocket exposes ioctlsocket from WinSock. - procioctlsocket = modws2_32.NewProc("ioctlsocket") -) - -func syscallSetNonblock(fd uintptr, nonblocking bool) (err error) { - opt := uint64(0) - if nonblocking { - opt = 1 - } - // ioctlsocket(fd, FIONBIO, &opt) - _, _, errno := syscall.SyscallN( - procioctlsocket.Addr(), - uintptr(fd), - uintptr(_FIONBIO), - uintptr(unsafe.Pointer(&opt))) - return errno -} - -type syscallFd = syscall.Handle diff --git a/tinygo/v0/netpoll.go b/tinygo/v0/netpoll.go deleted file mode 100644 index 3379051..0000000 --- a/tinygo/v0/netpoll.go +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2023 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package v0 - -import ( - "fmt" - "syscall" - "unsafe" -) - -// WASI network poller. -// -// WASI preview 1 includes a poll_oneoff host function that behaves similarly -// to poll(2) on Linux. Like poll(2), poll_oneoff is level triggered. It -// accepts one or more subscriptions to FD read or write events. -// -// Major differences to poll(2): -// - the events are not written to the input entries (like pollfd.revents), and -// instead are appended to a separate events buffer. poll_oneoff writes zero -// or more events to the buffer (at most one per input subscription) and -// returns the number of events written. Although the index of the -// subscriptions might not match the index of the associated event in the -// events buffer, both the subscription and event structs contain a userdata -// field and when a subscription yields an event the userdata fields will -// match. -// - there's no explicit timeout parameter, although a time limit can be added -// by using "clock" subscriptions. -// - each FD subscription can either be for a read or a write, but not both. -// This is in contrast to poll(2) which accepts a mask with POLLIN and -// POLLOUT bits, allowing for a subscription to either, neither, or both -// reads and writes. -// -// Since poll_oneoff is similar to poll(2), the implementation here was derived -// from netpoll_aix.go. - -const ( - EventFdRead uint16 = iota + 1 // readable event - EventFdWrite // writeable event -) - -var ( - evts []event - subs []subscription -) - -type pollFd struct { - fd uintptr - events uint16 - revents uint16 // todo -} - -func _poll(fds []pollFd, maxTimeout int64) (nevents int32, err error) { - // Unlike poll(2), WASI's poll_oneoff doesn't accept a timeout directly. To - // prevent it from blocking indefinitely, a clock subscription with a - // timeout field needs to be submitted. Reserve a slot here for the clock - // subscription, and set fields that won't change between poll_oneoff calls. - - subs = make([]subscription, 1, 128) - evts = make([]event, 0, 128) - - timeout := &subs[0] - eventtype := timeout.u.eventtype() - *eventtype = eventtypeClock - clock := timeout.u.subscriptionClock() - clock.id = clockMonotonic - clock.precision = 1e3 - - // construct the subscriptions - for _, fd := range fds { - sub := subscription{} - sub.userdata = userdata(fd.fd) - subscription := sub.u.subscriptionFdReadwrite() - subscription.fd = int32(fd.fd) - if fd.events == EventFdRead { - eventtype := sub.u.eventtype() - *eventtype = eventtypeFdRead - } else if fd.events == EventFdWrite { - eventtype := sub.u.eventtype() - *eventtype = eventtypeFdWrite - } else { - panic(fmt.Sprintf("invalid event type: %d", fd.events)) - } - subs = append(subs, sub) - } - - // If maxTimeout >= 0, we include a subscription of type Clock that we use as - // a timeout. If maxTimeout < 0, we omit the subscription and allow poll_oneoff - // to block indefinitely. - pollsubs := subs - if maxTimeout >= 0 { - timeout := &subs[0] - clock := timeout.u.subscriptionClock() - clock.timeout = uint64(maxTimeout) - } else { - pollsubs = subs[1:] - } - - if len(pollsubs) == 0 { - return 0, nil - } - - evts = evts[:len(pollsubs)] - for i := range evts { - evts[i] = event{} - } - -retry: - errno := poll_oneoff(unsafe.Pointer(&pollsubs[0]), unsafe.Pointer(&evts[0]), uint32(len(pollsubs)), unsafe.Pointer(&nevents)) - if errno != 0 { - if errno != uint32(syscall.EINTR) { - return 0, syscall.Errno(errno) - } - - // If a timed sleep was interrupted, just return to - // let the caller retry. - if maxTimeout > 0 { - return 0, syscall.EAGAIN - } - goto retry - } - - // go through all events and see if any event.error is not ESUCCESS - lastEvtError := uint16(0) - for i, evt := range evts { - if evt.error != 0 { - fds[i].revents = evt.error - lastEvtError = evt.error - } - } - if lastEvtError != 0 { - return int32(nevents), syscall.Errno(lastEvtError) - } - - return int32(nevents), nil -} diff --git a/tinygo/v0/os.go b/tinygo/v0/os.go deleted file mode 100644 index c094cbb..0000000 --- a/tinygo/v0/os.go +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright 2023 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package v0 - -import "unsafe" - -// GOARCH=wasm currently has 64 bits pointers, but the WebAssembly host expects -// pointers to be 32 bits so we use this type alias to represent pointers in -// structs and arrays passed as arguments to WASI functions. -// -// Note that the use of an integer type prevents the compiler from tracking -// pointers passed to WASI functions, so we must use KeepAlive to explicitly -// retain the objects that could otherwise be reclaimed by the GC. -type uintptr32 = uint32 - -// https://github.com/WebAssembly/WASI/blob/a2b96e81c0586125cc4dc79a5be0b78d9a059925/legacy/preview1/docs.md#-size-u32 -type size = uint32 - -// https://github.com/WebAssembly/WASI/blob/a2b96e81c0586125cc4dc79a5be0b78d9a059925/legacy/preview1/docs.md#-errno-variant -type errno = uint32 - -// https://github.com/WebAssembly/WASI/blob/a2b96e81c0586125cc4dc79a5be0b78d9a059925/legacy/preview1/docs.md#-filesize-u64 -type filesize = uint64 - -// https://github.com/WebAssembly/WASI/blob/a2b96e81c0586125cc4dc79a5be0b78d9a059925/legacy/preview1/docs.md#-timestamp-u64 -type timestamp = uint64 - -// https://github.com/WebAssembly/WASI/blob/a2b96e81c0586125cc4dc79a5be0b78d9a059925/legacy/preview1/docs.md#-clockid-variant -type clockid = uint32 - -const ( - clockRealtime clockid = 0 - clockMonotonic clockid = 1 -) - -// https://github.com/WebAssembly/WASI/blob/a2b96e81c0586125cc4dc79a5be0b78d9a059925/legacy/preview1/docs.md#-iovec-record -type iovec struct { - buf uintptr32 - bufLen size -} - -type eventtype = uint8 - -const ( - eventtypeClock eventtype = iota - eventtypeFdRead - eventtypeFdWrite -) - -type eventrwflags = uint16 - -const ( - fdReadwriteHangup eventrwflags = 1 << iota -) - -type userdata = uint64 - -// The go:wasmimport directive currently does not accept values of type uint16 -// in arguments or returns of the function signature. Most WASI imports return -// an errno value, which we have to define as uint32 because of that limitation. -// However, the WASI errno type is intended to be a 16 bits integer, and in the -// event struct the error field should be of type errno. If we used the errno -// type for the error field it would result in a mismatching field alignment and -// struct size because errno is declared as a 32 bits type, so we declare the -// error field as a plain uint16. -type event struct { - userdata userdata - error uint16 - typ eventtype - p [5]uint8 // padding to 16 bytes - fdReadwrite eventFdReadwrite -} - -type eventFdReadwrite struct { - nbytes filesize - flags eventrwflags -} - -type subclockflags = uint16 - -const ( - subscriptionClockAbstime subclockflags = 1 << iota -) - -type subscriptionClock struct { - id clockid - timeout timestamp - precision timestamp - flags subclockflags -} - -type subscriptionFdReadwrite struct { - fd int32 -} - -type subscription struct { - userdata userdata - u subscriptionUnion -} - -type subscriptionUnion [5]uint64 - -func (u *subscriptionUnion) eventtype() *eventtype { - return (*eventtype)(unsafe.Pointer(&u[0])) -} - -func (u *subscriptionUnion) subscriptionClock() *subscriptionClock { - return (*subscriptionClock)(unsafe.Pointer(&u[1])) -} - -func (u *subscriptionUnion) subscriptionFdReadwrite() *subscriptionFdReadwrite { - return (*subscriptionFdReadwrite)(unsafe.Pointer(&u[1])) -} - -func usleep(usec uint32) { - var in subscription - var out event - var nevents size - - eventtype := in.u.eventtype() - *eventtype = eventtypeClock - - subscription := in.u.subscriptionClock() - subscription.id = clockMonotonic - subscription.timeout = timestamp(usec) * 1e3 - subscription.precision = 1e3 - - if poll_oneoff(unsafe.Pointer(&in), unsafe.Pointer(&out), 1, unsafe.Pointer(&nevents)) != 0 { - panic("wasi_snapshot_preview1.poll_oneoff") - } -} diff --git a/tinygo/v0/pluggable_transports.go b/tinygo/v0/pluggable_transports.go deleted file mode 100644 index 36da358..0000000 --- a/tinygo/v0/pluggable_transports.go +++ /dev/null @@ -1,90 +0,0 @@ -package v0 - -import ( - v0net "github.com/refraction-networking/watm/tinygo/v0/net" -) - -// WrappingTransport is the most basic transport type. It wraps -// a [v0net.Conn] into another [v0net.Conn] by providing some -// high-level application layer protocol. -type WrappingTransport interface { - // Wrap wraps a v0net.Conn into another v0net.Conn with a protocol - // wrapper layer. - // - // The returned v0net.Conn is NOT by default set to non-blocking. - // It is the responsibility of the transport to make it - // non-blocking by calling v0net.Conn.SetNonblock. This is to - // allow some transport to perform blocking operations such as - // TLS handshake. - // - // The transport SHOULD provide non-blocking v0net.Conn.Read - // operation on the returned v0net.Conn if possible, otherwise - // the worker may block on reading from a blocking connection. - // And it is highly recommended to pass all funtions other than - // Read and Write to the underlying v0net.Conn from the underlying - // dialer function. - Wrap(v0net.Conn) (v0net.Conn, error) -} - -// DialingTransport is a transport type that can be used to dial -// a remote address and provide high-level application layer -// protocol over the dialed connection. -type DialingTransport interface { - // SetDialer sets the dialer function that is used to dial - // a remote address. - // - // In v0, the input parameter of the dialer function is - // unused inside the WATM, given the connection is always - // managed by the host application. - // - // The returned v0net.Conn is NOT by default set to non-blocking. - // It is the responsibility of the transport to make it - // non-blocking by calling v0net.Conn.SetNonblock. This is to - // allow some transport to perform blocking operations such as - // TLS handshake. - SetDialer(dialer func(network, address string) (v0net.Conn, error)) - - // Dial dials a remote address and returns a v0net.Conn that - // provides high-level application layer protocol over the - // dialed connection. - // - // The transport SHOULD provide non-blocking v0net.Conn.Read - // operation on the returned v0net.Conn if possible, otherwise - // the worker may block on reading from a blocking connection. - // And it is highly recommended to pass all funtions other than - // Read and Write to the underlying v0net.Conn from the underlying - // dialer function. - Dial(network, address string) (v0net.Conn, error) -} - -// ListeningTransport is a transport type that can be used to -// accept incoming connections on a local address and provide -// high-level application layer protocol over the accepted -// connection. -type ListeningTransport interface { - // SetListener sets the listener that is used to accept - // incoming connections. - // - // The returned v0net.Conn is not by default non-blocking. - // It is the responsibility of the transport to make it - // non-blocking if required by calling v0net.Conn.SetNonblock. - SetListener(listener v0net.Listener) - - // Accept accepts an incoming connection and returns a - // net.Conn that provides high-level application layer - // protocol over the accepted connection. - // - // The transport SHOULD provide non-blocking v0net.Conn.Read - // operation on the returned v0net.Conn if possible, otherwise - // the worker may block on reading from a blocking connection. - // And it is highly recommended to pass all funtions other than - // Read and Write to the underlying v0net.Conn from the underlying - // dialer function. - Accept() (v0net.Conn, error) -} - -// ConfigurableTransport is a transport type that can be configured -// with a config file in the form of a byte slice. -type ConfigurableTransport interface { - Configure(config []byte) error -} diff --git a/tinygo/v0/relay.go b/tinygo/v0/relay.go deleted file mode 100644 index 835d416..0000000 --- a/tinygo/v0/relay.go +++ /dev/null @@ -1,78 +0,0 @@ -package v0 - -type RelayWrapSelection bool - -const ( - RelayWrapRemote RelayWrapSelection = false - RelayWrapSource RelayWrapSelection = true -) - -type relay struct { - wt WrappingTransport - wrapSelection RelayWrapSelection - // lt ListeningTransport - // dt DialingTransport -} - -func (r *relay) ConfigurableTransport() ConfigurableTransport { - if r.wt != nil { - if wt, ok := r.wt.(ConfigurableTransport); ok { - return wt - } - } - - // if r.lt != nil { - // if lt, ok := r.lt.(ConfigurableTransport); ok { - // return lt - // } - // } - - // if r.dt != nil { - // if dt, ok := r.dt.(ConfigurableTransport); ok { - // return dt - // } - // } - - return nil -} - -func (r *relay) Initialize() { - // TODO: allow initialization on relay -} - -var r relay - -// BuildRelayWithWrappingTransport arms the relay with a -// [WrappingTransport] that is used to wrap a [v0net.Conn] into -// another [net.Conn] by providing some high-level application -// layer protocol. -// -// The caller MUST keep in mind that the [WrappingTransport] is -// used to wrap the connection to the remote address, not the -// connection from the source address (the dialing peer). -// To reverse this behavior, i.e., wrap the inbounding connection, -// set wrapSelection to [RelayWrapSource]. -// -// Mutually exclusive with [BuildRelayWithListeningDialingTransport]. -func BuildRelayWithWrappingTransport(wt WrappingTransport, wrapSelection RelayWrapSelection) { - r.wt = wt - r.wrapSelection = wrapSelection - // r.lt = nil - // r.dt = nil -} - -// BuildRelayWithListeningDialingTransport arms the relay with a -// [ListeningTransport] that is used to accept incoming connections -// on a local address and provide high-level application layer -// protocol over the accepted connection, and a [DialingTransport] -// that is used to dial a remote address and provide high-level -// application layer protocol over the dialed connection. -// -// Mutually exclusive with [BuildRelayWithWrappingTransport]. -func BuildRelayWithListeningDialingTransport(lt ListeningTransport, dt DialingTransport) { - // TODO: implement BuildRelayWithListeningDialingTransport - // r.lt = lt - // r.dt = dt - // r.wt = nil - panic("BuildRelayWithListeningDialingTransport: not implemented") -} diff --git a/tinygo/v0/worker.go b/tinygo/v0/worker.go deleted file mode 100644 index 203277a..0000000 --- a/tinygo/v0/worker.go +++ /dev/null @@ -1,294 +0,0 @@ -package v0 - -import ( - "io" - "log" - "net" - "runtime" - "syscall" - - v0net "github.com/refraction-networking/watm/tinygo/v0/net" - "github.com/refraction-networking/watm/wasip1" -) - -type identity uint8 - -var workerIdentity identity = identity_uninitialized - -const ( - identity_uninitialized identity = iota - identity_dialer - identity_listener - identity_relay -) - -var identityStrings = map[identity]string{ - identity_dialer: "dialer", - identity_listener: "listener", - identity_relay: "relay", -} - -var sourceConn v0net.Conn // sourceConn is used to communicate between WASM and the host application or a dialing party (for relay only) -var remoteConn v0net.Conn // remoteConn is used to communicate between WASM and a dialed remote destination (for dialer/relay) or a dialing party (for listener only) -var cancelConn v0net.Conn // cancelConn is used to cancel the entire worker. - -var workerFn func() int32 = unfairWorker // by default, use unfairWorker for better performance under mostly unidirectional I/O - -var readBuf []byte = make([]byte, 16384) // 16k buffer for reading - -// WorkerFairness sets the fairness of a worker. -// -// If sourceConn or remoteConn will not work in non-blocking mode, -// it is highly recommended to set fair to true, otherwise it is most -// likely that the worker will block on reading from a blocking -// connection forever and therefore make no progress in the other -// direction. -func WorkerFairness(fair bool) { - if fair { - workerFn = fairWorker - } else { - workerFn = unfairWorker - } -} - -func worker() int32 { - defer _import_host_defer() - - if sourceConn == nil || remoteConn == nil || cancelConn == nil { - log.Println("worker: worker: sourceConn, remoteConn, or cancelConn is nil") - return wasip1.EncodeWATERError(syscall.EBADF) // bad file descriptor - } - - return workerFn() -} - -// untilError executes the given function until non-nil error is returned -func untilError(f func() error) error { - var err error - for err == nil { - err = f() - } - return err -} - -// unfairWorker works on all three connections with a priority order -// of cancelConn > sourceConn > remoteConn. -// -// It keeps working on the current connection until it returns an error, -// and if the error is EAGAIN, it switches to the next connection. If the -// connection is not properly set to non-blocking mode, i.e., never returns -// EAGAIN, this function will block forever and never work on a lower priority -// connection. Thus it is called unfairWorker. -func unfairWorker() int32 { - for { - pollFd := []pollFd{ - { - fd: uintptr(cancelConn.Fd()), - events: EventFdRead, - }, - { - fd: uintptr(sourceConn.Fd()), - events: EventFdRead, - }, - { - fd: uintptr(remoteConn.Fd()), - events: EventFdRead, - }, - } - - n, err := _poll(pollFd, -1) - if n == 0 { // TODO: re-evaluate the condition - if err == nil || err == syscall.EAGAIN { - runtime.Gosched() // yield the current goroutine - continue - } - log.Println("worker: unfairWorker: _poll:", err) - return int32(err.(syscall.Errno)) - } - - // 1st priority: cancelConn - _, err = cancelConn.Read(readBuf) - if !(err == syscall.EAGAIN) { - if err == io.EOF || err == nil { - log.Println("worker: unfairWorker: cancelConn is closed") - return wasip1.EncodeWATERError(syscall.ECANCELED) // operation canceled - } - log.Println("worker: unfairWorker: cancelConn.Read:", err) - return wasip1.EncodeWATERError(syscall.EIO) // input/output error - } - - // 2nd priority: sourceConn - if err := untilError(func() error { - nRead, readErr := sourceConn.Read(readBuf) - if readErr != nil { - if readErr != syscall.EAGAIN { - log.Println("worker: unfairWorker: sourceConn.Read:", readErr) - } - return readErr - } - - nWritten, writeErr := remoteConn.Write(readBuf[:nRead]) - if writeErr != nil { - log.Println("worker: unfairWorker: remoteConn.Write:", writeErr) - return writeErr - } - - if nRead != nWritten { - log.Printf("worker: unfairWorker: nRead != nWritten") - return syscall.EMSGSIZE // message too long to fit in send buffer even after auto partial write - } - - return nil - }); err != syscall.EAGAIN { // silently ignore EAGAIN - if err == io.EOF { - log.Println("worker: unfairWorker: sourceConn is closed") - return wasip1.EncodeWATERError(0) // success, no error - } - if errno, ok := err.(syscall.Errno); ok { - return wasip1.EncodeWATERError(errno) - } - return wasip1.EncodeWATERError(syscall.EIO) // input/output error - } - - // 3rd priority: remoteConn - if err := untilError(func() error { - nRead, readErr := remoteConn.Read(readBuf) - if readErr != nil { - if readErr != syscall.EAGAIN { - log.Println("worker: unfairWorker: remoteConn.Read:", readErr) - } - return readErr - } - - nWrite, writeErr := sourceConn.Write(readBuf[:nRead]) - if writeErr != nil { - log.Println("worker: unfairWorker: sourceConn.Write:", writeErr) - return writeErr - } - - if nRead != nWrite { - log.Printf("worker: unfairWorker: nRead != nWrite") - return syscall.EMSGSIZE // message too long to fit in send buffer even after auto partial write - } - - return nil - }); err != syscall.EAGAIN { // silently ignore EAGAIN - if err == io.EOF { - log.Println("worker: unfairWorker: remoteConn is closed") - return wasip1.EncodeWATERError(0) // success, no error - } - if errno, ok := err.(syscall.Errno); ok { - return wasip1.EncodeWATERError(errno) - } - return wasip1.EncodeWATERError(syscall.EIO) // input/output error - } - } -} - -// like unfairWorker, fairWorker also works on all three connections with a priority order -// of cancelConn > sourceConn > remoteConn. -// -// But different from unfairWorker, fairWorker spend equal amount of turns on each connection -// for calling Read. Therefore it has a better fairness than unfairWorker, which may still -// make progress if one of the connection is not properly set to non-blocking mode. -func fairWorker() int32 { - for { - pollFd := []pollFd{ - { - fd: uintptr(cancelConn.Fd()), - events: EventFdRead, - }, - { - fd: uintptr(sourceConn.Fd()), - events: EventFdRead, - }, - { - fd: uintptr(remoteConn.Fd()), - events: EventFdRead, - }, - } - - n, err := _poll(pollFd, -1) - if n == 0 { // TODO: re-evaluate the condition - if err == nil || err == syscall.EAGAIN { - runtime.Gosched() // yield the current goroutine - continue - } - log.Println("worker: fairWorker: _poll:", err) - return int32(err.(syscall.Errno)) - } - - // 1st priority: cancelConn - _, err = cancelConn.Read(readBuf) - if !(err == syscall.EAGAIN) { - if err == io.EOF || err == nil { - log.Println("worker: fairWorker: cancelConn is closed") - return wasip1.EncodeWATERError(syscall.ECANCELED) // operation canceled - } - log.Println("worker: fairWorker: cancelConn.Read:", err) - return wasip1.EncodeWATERError(syscall.EIO) // input/output error - } - - // 2nd priority: sourceConn -> remoteConn - if err := copyOnce( - "remoteConn", // dstName - "sourceConn", // srcName - remoteConn, // dst - sourceConn, // src - readBuf); err != nil { - if err == io.EOF { - return wasip1.EncodeWATERError(0) // success, no error - } - if errno, ok := err.(syscall.Errno); ok { - return wasip1.EncodeWATERError(errno) - } - return wasip1.EncodeWATERError(syscall.EIO) // other input/output error - } - - // 3rd priority: remoteConn -> sourceConn - if err := copyOnce( - "sourceConn", // dstName - "remoteConn", // srcName - sourceConn, // dst - remoteConn, // src - readBuf); err != nil { - if err == io.EOF { - return wasip1.EncodeWATERError(0) - } - if errno, ok := err.(syscall.Errno); ok { - return wasip1.EncodeWATERError(errno) - } - return wasip1.EncodeWATERError(syscall.EIO) // other input/output error - } - } -} - -func copyOnce(dstName, srcName string, dst, src net.Conn, buf []byte) error { - if len(buf) == 0 { - buf = make([]byte, 16384) // 16k buffer for reading - } - - nRead, readErr := src.Read(buf) - if !(readErr == syscall.EAGAIN) { // if EAGAIN, do nothing and return - if readErr == io.EOF { - log.Printf("worker: copyOnce: EOF on %s", srcName) - return io.EOF - } else if readErr != nil { - log.Printf("worker: copyOnce: %s.Read: %v", srcName, readErr) - return syscall.EIO // input/output error - } - - nWritten, writeErr := dst.Write(buf[:nRead]) - if writeErr != nil { - log.Printf("worker: copyOnce: %s.Write: %v", dstName, writeErr) - return syscall.EIO // no matter input/output error or EAGAIN we cannot retry async write yet - } - - if nRead != nWritten { - log.Printf("worker: copyOnce: %s.nRead != %s.nWritten", srcName, dstName) - return syscall.EIO // input/output error - } - } - - return nil -} diff --git a/tinygo/v1/config.go b/tinygo/v1/config.go new file mode 100644 index 0000000..a04fa82 --- /dev/null +++ b/tinygo/v1/config.go @@ -0,0 +1,44 @@ +package v1 + +import ( + "bytes" + "log" + "os" + "syscall" +) + +func readInboundConfig() (config []byte, err error) { + // check if /conf/inbound.cfg exists + file, err := os.Open("/conf/inbound.cfg") + if err != nil { + return nil, syscall.EACCES + } + return readConfig(file) +} + +func readOutboundConfig() (config []byte, err error) { + // check if /conf/outbound.cfg exists + file, err := os.Open("/conf/outbound.cfg") + if err != nil { + return nil, syscall.EACCES + } + return readConfig(file) +} + +func readConfig(file *os.File) (config []byte, err error) { + // read the config file + buf := new(bytes.Buffer) + _, err = buf.ReadFrom(file) + if err != nil { + log.Println("readConfig: (*bytes.Buffer).ReadFrom:", err) + return nil, syscall.EIO + } + + config = buf.Bytes() + + // close the file + if err = file.Close(); err != nil { + err = syscall.EIO + } + return +} diff --git a/tinygo/v1/dialer.go b/tinygo/v1/dialer.go index b8bc191..2a6ed13 100644 --- a/tinygo/v1/dialer.go +++ b/tinygo/v1/dialer.go @@ -1,19 +1,22 @@ package v1 +import v1net "github.com/refraction-networking/watm/tinygo/v1/net" + type dialer struct { + // outbound: pick one wt WrappingTransport dt DialingTransport + + locked bool } -func (d *dialer) ConfigurableTransport() ConfigurableTransport { +func (d *dialer) Configurable() Configurable { if d.wt != nil { - if wt, ok := d.wt.(ConfigurableTransport); ok { + if wt, ok := d.wt.(Configurable); ok { return wt } - } - - if d.dt != nil { - if dt, ok := d.dt.(ConfigurableTransport); ok { + } else if d.dt != nil { + if dt, ok := d.dt.(Configurable); ok { return dt } } @@ -21,21 +24,43 @@ func (d *dialer) ConfigurableTransport() ConfigurableTransport { return nil } -func (d *dialer) Initialize() { - // TODO: allow initialization on dialer -} - var globalDialer dialer +// BuildDialerWithOutboundTransport arms the dialer with a +// [WrappingTransport] or [DialingTransport] that is used to wrap +// a [v1net.Conn] into another [net.Conn] by providing some +// high-level application layer protocol or dial a remote address +// and provide high-level application layer protocol over the dialed +// connection. +func BuildDialerWithOutboundTransport(anyTransport any) { + switch t := anyTransport.(type) { + case WrappingTransport: + BuildDialerWithWrappingTransport(t) + case DialingTransport: + BuildDialerWithDialingTransport(t) + default: + panic("transport type not supported") + } +} + // BuildDialerWithWrappingTransport arms the dialer with a // [WrappingTransport] that is used to wrap a [v1net.Conn] into // another [net.Conn] by providing some high-level application // layer protocol. // +// The caller MUST keep in mind that the [WrappingTransport] is +// used to wrap the outbound connection (to the remote address), not the +// connection from the caller (the client). +// // Mutually exclusive with [BuildDialerWithDialingTransport]. func BuildDialerWithWrappingTransport(wt WrappingTransport) { + if globalDialer.locked { + panic("dialer is locked") + } + globalDialer.wt = wt globalDialer.dt = nil + globalDialer.locked = true } // BuildDialerWithDialingTransport arms the dialer with a @@ -45,36 +70,12 @@ func BuildDialerWithWrappingTransport(wt WrappingTransport) { // // Mutually exclusive with [BuildDialerWithWrappingTransport]. func BuildDialerWithDialingTransport(dt DialingTransport) { - panic("not implemented until Runtime start passing remote access into WATM") - // globalDialer.dt = dt - // globalDialer.wt = nil -} - -type fixedDialer struct { - fdt FixedDialingTransport -} - -// ConfigurableTransport returns the ConfigurableTransport of the -// underlying DialingTransport if it implements the interface. -func (f *fixedDialer) ConfigurableTransport() ConfigurableTransport { - if f.fdt != nil { - if dt, ok := f.fdt.(ConfigurableTransport); ok { - return dt - } + if globalDialer.locked { + panic("dialer is locked") } - return nil -} - -func (f *fixedDialer) Initialize() { - // TODO: allow initialization on dialer -} - -var globalFixedDialer fixedDialer - -// BuildFixedDialerWithFixedDialingTransport arms the fixedDialer with a -// [FixedDialingTransport] that provide high-level application layer protocol -// over the dialed connection. -func BuildFixedDialerWithFixedDialingTransport(fdt FixedDialingTransport) { - globalFixedDialer.fdt = fdt + globalDialer.dt = dt + dt.SetDialer(v1net.Dial) + globalDialer.wt = nil + globalDialer.locked = true } diff --git a/tinygo/v1/examples/go.mod b/tinygo/v1/examples/go.mod index eae6464..bd03404 100644 --- a/tinygo/v1/examples/go.mod +++ b/tinygo/v1/examples/go.mod @@ -8,7 +8,7 @@ replace github.com/refraction-networking/watm => ../../../ require ( github.com/CosmWasm/tinyjson v0.9.0 - github.com/refraction-networking/utls v1.6.6-wasm + github.com/refraction-networking/utls v1.6.7-wasm github.com/refraction-networking/watm v0.6.5 ) @@ -17,6 +17,6 @@ require ( github.com/cloudflare/circl v1.3.9 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/klauspost/compress v1.17.9 // indirect - golang.org/x/crypto v0.24.0 // indirect - golang.org/x/sys v0.21.0 // indirect + golang.org/x/crypto v0.25.0 // indirect + golang.org/x/sys v0.22.0 // indirect ) diff --git a/tinygo/v1/examples/go.sum b/tinygo/v1/examples/go.sum index 5149b10..c3f0bd2 100644 --- a/tinygo/v1/examples/go.sum +++ b/tinygo/v1/examples/go.sum @@ -8,7 +8,7 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= -github.com/refraction-networking/utls v1.6.6-wasm h1:fayTy1wZzzNSZqJ9rzMgDOoGVLS9/u0YIRQ9fUurlc0= -github.com/refraction-networking/utls v1.6.6-wasm/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +github.com/refraction-networking/utls v1.6.7-wasm h1:Ckc7ep+I9/9qeuXBrF3Lkx248sgUGt1JJBpya5hpth4= +github.com/refraction-networking/utls v1.6.7-wasm/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= diff --git a/tinygo/v1/examples/plain/dialing_transport.go b/tinygo/v1/examples/plain/dialing_transport.go index 410aeb6..9295a26 100644 --- a/tinygo/v1/examples/plain/dialing_transport.go +++ b/tinygo/v1/examples/plain/dialing_transport.go @@ -1,17 +1,23 @@ package main -import v1net "github.com/refraction-networking/watm/tinygo/v1/net" +import ( + v1 "github.com/refraction-networking/watm/tinygo/v1" + v1net "github.com/refraction-networking/watm/tinygo/v1/net" +) -type PlainFixedDialingTransport struct { +// type guard +var _ v1.DialingTransport = (*PlainDialingTransport)(nil) + +type PlainDialingTransport struct { dialer func(network, address string) (v1net.Conn, error) } -func (fdt *PlainFixedDialingTransport) SetDialer(dialer func(network, address string) (v1net.Conn, error)) { - fdt.dialer = dialer +func (dt *PlainDialingTransport) SetDialer(dialer func(network, address string) (v1net.Conn, error)) { + dt.dialer = dialer } -func (fdt *PlainFixedDialingTransport) DialFixed() (v1net.Conn, error) { - conn, err := fdt.dialer("tcp", "localhost:7700") // TODO: hardcoded address, any better idea? +func (dt *PlainDialingTransport) Dial(network, address string) (v1net.Conn, error) { + conn, err := dt.dialer(network, address) // dial the passed address if err != nil { return nil, err } diff --git a/tinygo/v1/examples/plain/listening_transport.go b/tinygo/v1/examples/plain/listening_transport.go new file mode 100644 index 0000000..f04a4ad --- /dev/null +++ b/tinygo/v1/examples/plain/listening_transport.go @@ -0,0 +1,26 @@ +package main + +import ( + v1 "github.com/refraction-networking/watm/tinygo/v1" + v1net "github.com/refraction-networking/watm/tinygo/v1/net" +) + +// type guard +var _ v1.ListeningTransport = (*PlainListeningTransport)(nil) + +type PlainListeningTransport struct { + listener v1net.Listener +} + +func (lt *PlainListeningTransport) SetListener(listener v1net.Listener) { + lt.listener = listener +} + +func (lt *PlainListeningTransport) Accept() (v1net.Conn, error) { + conn, err := lt.listener.Accept() // accept the connection + if err != nil { + return nil, err + } + + return &PlainConn{conn}, conn.SetNonBlock(true) // must set non-block, otherwise will block on read and lose fairness +} diff --git a/tinygo/v1/examples/plain/main.go b/tinygo/v1/examples/plain/main.go index 8cc3d7f..548e2a2 100644 --- a/tinygo/v1/examples/plain/main.go +++ b/tinygo/v1/examples/plain/main.go @@ -7,10 +7,9 @@ import ( func init() { v1.WorkerFairness(false) v1.SetReadBufferSize(1024) // 1024B buffer for copying data - v1.BuildDialerWithWrappingTransport(&PlainWrappingTransport{}) - v1.BuildListenerWithWrappingTransport(&PlainWrappingTransport{}) - v1.BuildRelayWithWrappingTransport(&PlainWrappingTransport{}, v1.RelayWrapRemote) - v1.BuildFixedDialerWithFixedDialingTransport(&PlainFixedDialingTransport{}) + v1.BuildDialerWithDialingTransport(&PlainDialingTransport{}) + v1.BuildListenerWithListeningTransport(&PlainListeningTransport{}) + v1.BuildRelayWithOutboundWrappingTransport(&PlainWrappingTransport{}) } func main() {} diff --git a/tinygo/v1/examples/plain/wrapper_transport.go b/tinygo/v1/examples/plain/wrapping_transport.go similarity index 87% rename from tinygo/v1/examples/plain/wrapper_transport.go rename to tinygo/v1/examples/plain/wrapping_transport.go index 1592e04..e761223 100644 --- a/tinygo/v1/examples/plain/wrapper_transport.go +++ b/tinygo/v1/examples/plain/wrapping_transport.go @@ -11,7 +11,7 @@ var _ v1.WrappingTransport = (*PlainWrappingTransport)(nil) type PlainWrappingTransport struct { } -func (rwt *PlainWrappingTransport) Wrap(conn v1net.Conn) (v1net.Conn, error) { +func (*PlainWrappingTransport) Wrap(conn v1net.Conn) (v1net.Conn, error) { return &PlainConn{conn}, conn.SetNonBlock(true) // must set non-block, otherwise will block on read and lose fairness } diff --git a/tinygo/v1/examples/reverse/dialing_transport.go b/tinygo/v1/examples/reverse/dialing_transport.go index f2a2fbf..47e30a8 100644 --- a/tinygo/v1/examples/reverse/dialing_transport.go +++ b/tinygo/v1/examples/reverse/dialing_transport.go @@ -2,16 +2,16 @@ package main import v1net "github.com/refraction-networking/watm/tinygo/v1/net" -type ReverseFixedDialingTransport struct { +type ReverseDialingTransport struct { dialer func(network, address string) (v1net.Conn, error) } -func (fdt *ReverseFixedDialingTransport) SetDialer(dialer func(network, address string) (v1net.Conn, error)) { - fdt.dialer = dialer +func (dt *ReverseDialingTransport) SetDialer(dialer func(network, address string) (v1net.Conn, error)) { + dt.dialer = dialer } -func (fdt *ReverseFixedDialingTransport) DialFixed() (v1net.Conn, error) { - conn, err := fdt.dialer("tcp", "localhost:7700") // TODO: hardcoded address, any better idea? +func (dt *ReverseDialingTransport) Dial(network, address string) (v1net.Conn, error) { + conn, err := dt.dialer(network, address) // dial the passed address if err != nil { return nil, err } diff --git a/tinygo/v1/examples/reverse/listening_transport.go b/tinygo/v1/examples/reverse/listening_transport.go new file mode 100644 index 0000000..6c39591 --- /dev/null +++ b/tinygo/v1/examples/reverse/listening_transport.go @@ -0,0 +1,26 @@ +package main + +import ( + v1 "github.com/refraction-networking/watm/tinygo/v1" + v1net "github.com/refraction-networking/watm/tinygo/v1/net" +) + +// type guard +var _ v1.ListeningTransport = (*ReverseListeningTransport)(nil) + +type ReverseListeningTransport struct { + listener v1net.Listener +} + +func (lt *ReverseListeningTransport) SetListener(listener v1net.Listener) { + lt.listener = listener +} + +func (lt *ReverseListeningTransport) Accept() (v1net.Conn, error) { + conn, err := lt.listener.Accept() // accept the connection + if err != nil { + return nil, err + } + + return &ReverseConn{conn}, conn.SetNonBlock(true) // must set non-block, otherwise will block on read and lose fairness +} diff --git a/tinygo/v1/examples/reverse/main.go b/tinygo/v1/examples/reverse/main.go index 487c9ba..9fe2f9c 100644 --- a/tinygo/v1/examples/reverse/main.go +++ b/tinygo/v1/examples/reverse/main.go @@ -3,10 +3,9 @@ package main import v1 "github.com/refraction-networking/watm/tinygo/v1" func init() { - v1.BuildDialerWithWrappingTransport(&ReverseWrappingTransport{}) - v1.BuildListenerWithWrappingTransport(&ReverseWrappingTransport{}) - v1.BuildRelayWithWrappingTransport(&ReverseWrappingTransport{}, v1.RelayWrapRemote) - v1.BuildFixedDialerWithFixedDialingTransport(&ReverseFixedDialingTransport{}) + v1.BuildDialerWithDialingTransport(&ReverseDialingTransport{}) + v1.BuildListenerWithListeningTransport(&ReverseListeningTransport{}) + v1.BuildRelayWithOutboundWrappingTransport(&ReverseWrappingTransport{}) } func main() {} diff --git a/tinygo/v0/examples/reverse/wrapper_transport.go b/tinygo/v1/examples/reverse/reverse_conn.go similarity index 51% rename from tinygo/v0/examples/reverse/wrapper_transport.go rename to tinygo/v1/examples/reverse/reverse_conn.go index a2f8927..35aedce 100644 --- a/tinygo/v0/examples/reverse/wrapper_transport.go +++ b/tinygo/v1/examples/reverse/reverse_conn.go @@ -1,22 +1,11 @@ package main import ( - v0 "github.com/refraction-networking/watm/tinygo/v0" - v0net "github.com/refraction-networking/watm/tinygo/v0/net" + v1net "github.com/refraction-networking/watm/tinygo/v1/net" ) -// type guard: ReverseWrappingTransport must implement [v0.WrappingTransport]. -var _ v0.WrappingTransport = (*ReverseWrappingTransport)(nil) - -type ReverseWrappingTransport struct { -} - -func (rwt *ReverseWrappingTransport) Wrap(conn v0net.Conn) (v0net.Conn, error) { - return &ReverseConn{conn}, conn.SetNonBlock(true) // must set non-block, otherwise will block on read and lose fairness -} - type ReverseConn struct { - v0net.Conn // embedded Conn + v1net.Conn // embedded Conn } func (rc *ReverseConn) Read(b []byte) (n int, err error) { diff --git a/tinygo/v1/examples/reverse/wrapper_transport.go b/tinygo/v1/examples/reverse/wrapper_transport.go index 859d797..cf282a5 100644 --- a/tinygo/v1/examples/reverse/wrapper_transport.go +++ b/tinygo/v1/examples/reverse/wrapper_transport.go @@ -14,33 +14,3 @@ type ReverseWrappingTransport struct { func (rwt *ReverseWrappingTransport) Wrap(conn v1net.Conn) (v1net.Conn, error) { return &ReverseConn{conn}, conn.SetNonBlock(true) // must set non-block, otherwise will block on read and lose fairness } - -type ReverseConn struct { - v1net.Conn // embedded Conn -} - -func (rc *ReverseConn) Read(b []byte) (n int, err error) { - tmpBuf := make([]byte, len(b)) - n, err = rc.Conn.Read(tmpBuf) - if err != nil { - return 0, err - } - - // reverse all bytes read successfully so far - for i := 0; i < n; i++ { - b[i] = tmpBuf[n-i-1] - } - - return n, err -} - -func (rc *ReverseConn) Write(b []byte) (n int, err error) { - tmpBuf := make([]byte, len(b)) - - // reverse the bytes to be written - for i := 0; i < len(b); i++ { - tmpBuf[i] = b[len(b)-i-1] - } - - return rc.Conn.Write(tmpBuf[:len(b)]) -} diff --git a/tinygo/v1/examples/utls/wrapper_transport.go b/tinygo/v1/examples/utls/wrapper_transport.go index 7252a43..d9598a9 100644 --- a/tinygo/v1/examples/utls/wrapper_transport.go +++ b/tinygo/v1/examples/utls/wrapper_transport.go @@ -44,7 +44,7 @@ func (uwt *UTLSClientWrappingTransport) Wrap(conn v1net.Conn) (v1net.Conn, error }, nil } -var _ v1.ConfigurableTransport = (*UTLSClientWrappingTransport)(nil) +var _ v1.Configurable = (*UTLSClientWrappingTransport)(nil) func (uwt *UTLSClientWrappingTransport) Configure(config []byte) error { configurables := &lib.Configurables{} diff --git a/tinygo/v1/exports.go b/tinygo/v1/exports.go index 8ad80c9..0912a78 100644 --- a/tinygo/v1/exports.go +++ b/tinygo/v1/exports.go @@ -1,118 +1,92 @@ package v1 import ( - "bytes" "errors" "log" - "os" "syscall" v1net "github.com/refraction-networking/watm/tinygo/v1/net" - "github.com/refraction-networking/watm/wasip1" ) -// TODO: gaukas: I feel this function can be hugely optimized. -// It is not necessary for us to check and configure -// all the transports, but maybe only the one to be used. -// Should we consider moving the configuration part to the -// role-specific functions? (e.g. _dial, _accept, _associate) -// -//export watm_init_v1 -func _init() int32 { - // Check if dialer/listener/relay is configurable. If so, - // pull the config file from the host and configure them. - dct := globalDialer.ConfigurableTransport() - fdct := globalFixedDialer.ConfigurableTransport() - lct := globalListener.ConfigurableTransport() - rct := globalRelay.ConfigurableTransport() - if dct != nil || lct != nil /* || rct != nil */ { - config, err := readConfig() - if err == nil || config != nil { - if dct != nil { - dct.Configure(config) - } +const ESUCCESS uint32 = 0 +const INVALID_FD int32 = -1 - if fdct != nil { - fdct.Configure(config) - } +var lastError syscall.Errno - if lct != nil { - lct.Configure(config) - } - - if rct != nil { - rct.Configure(config) - } - } else if !errors.Is(err, syscall.EACCES) { // EACCES means no config file provided by the host - return wasip1.EncodeWATERError(err.(syscall.Errno)) - } +//export watm_ctrlpipe_v1 +func _ctrlpipe(ctrlFd int32) uint32 { + ctrlConn = v1net.RebuildTCPConn(ctrlFd) + if err := ctrlConn.SetNonBlock(true); err != nil { + log.Printf("dial: ctrlConn.SetNonblock: %v", err) + return saveAndReturnError(err) } - - // TODO: initialize the dialer, listener, and relay - globalDialer.Initialize() - globalFixedDialer.Initialize() - globalListener.Initialize() - globalRelay.Initialize() - - return 0 // ESUCCESS + return ESUCCESS } -func readConfig() (config []byte, err error) { - // check if /conf/watm.cfg exists - file, err := os.Open("/conf/watm.cfg") - if err != nil { - return nil, syscall.EACCES +//export watm_userpipe_v1 +func _userpipe(userFd int32) uint32 { + if workerIdentity != identity_uninitialized { + return saveAndReturnError(syscall.EBUSY) } - // read the config file - buf := new(bytes.Buffer) - _, err = buf.ReadFrom(file) - if err != nil { - log.Println("readConfig: (*bytes.Buffer).ReadFrom:", err) - return nil, syscall.EIO + sourceConn = v1net.RebuildTCPConn(userFd) + if err := sourceConn.SetNonBlock(true); err != nil { + log.Printf("internal_pipe: sourceConn.SetNonblock: %v", err) + return saveAndReturnError(err) } + return ESUCCESS +} - config = buf.Bytes() - - // close the file - if err := file.Close(); err != nil { - return config, syscall.EIO +// _dial +// +// watm_dial_v1(networkType s32) -> s32 +// +//export watm_dial_v1 +func _dial(networkType uint32) (networkFd int32) { + if workerIdentity != identity_uninitialized { + lastError = syscall.EBUSY + return INVALID_FD } - return config, nil -} - -//export watm_ctrlpipe_v1 -func _ctrlpipe(ctrlFd int32) int32 { - ctrlConn = v1net.RebuildTCPConn(ctrlFd) - if err := ctrlConn.SetNonBlock(true); err != nil { - log.Printf("dial: ctrlConn.SetNonblock: %v", err) - return wasip1.EncodeWATERError(err.(syscall.Errno)) + if !globalDialer.locked { + panic("dialer is not built with any outbound transport") } - return 0 // ESUCCESS -} + // Check if dialer is configurable. If so, + // pull the config file from the host and configure it. + configurableDialer := globalDialer.Configurable() + if configurableDialer != nil { + config, err := readOutboundConfig() + if err == nil || config != nil { + configurableDialer.Configure(config) + } else if !errors.Is(err, syscall.EACCES) { // EACCES means no config file provided by the host + lastError = err.(syscall.Errno) + return INVALID_FD + } + } -//export watm_dial_v1 -func _dial(internalFd int32) (networkFd int32) { - if workerIdentity != identity_uninitialized { - return wasip1.EncodeWATERError(syscall.EBUSY) // device or resource busy (worker already initialized) + if sourceConn == nil { + log.Printf("internal_pipe: sourceConn is nil, _internal_pipe must be called first") + lastError = syscall.ENOTCONN + return INVALID_FD } - // wrap the internalFd into a v1net.Conn - sourceConn = v1net.RebuildTCPConn(internalFd) - err := sourceConn.(*v1net.TCPConn).SetNonBlock(true) - if err != nil { - log.Printf("dial: sourceConn.SetNonblock: %v", err) - return wasip1.EncodeWATERError(err.(syscall.Errno)) + var network string = v1net.ToNetworkTypeString(networkType) + var address string + var err error + if address, err = v1net.GetAddrSuggestion(); err != nil { + log.Printf("dial: v1net.GetAddrSuggestion: %v", err) + lastError = err.(syscall.Errno) + return INVALID_FD } if globalDialer.wt != nil { // call v1net.Dial - rawNetworkConn, err := v1net.DialFixed() + rawNetworkConn, err := v1net.Dial(network, address) if err != nil { - log.Printf("dial: v1net.Dial: %v", err) - return wasip1.EncodeWATERError(err.(syscall.Errno)) + log.Printf("dial: v1net.Dial(%s, %s): %v", network, address, err) + lastError = err.(syscall.Errno) + return INVALID_FD } networkFd = rawNetworkConn.Fd() @@ -124,59 +98,60 @@ func _dial(internalFd int32) (networkFd int32) { remoteConn, err = globalDialer.wt.Wrap(rawNetworkConn) if err != nil { log.Printf("dial: d.wt.Wrap: %v", err) - return wasip1.EncodeWATERError(syscall.EPROTO) // protocol error + lastError = syscall.EPROTO + return INVALID_FD } - // TODO: implementation using DialingTransport + } else if globalDialer.dt != nil { + // call dt.Dial + remoteConn, err = globalDialer.dt.Dial(network, address) + if err != nil { + log.Printf("dial: d.dt.Dial(%s, %s): %v", network, address, err) + lastError = err.(syscall.Errno) + return INVALID_FD + } + networkFd = remoteConn.Fd() } else { - return wasip1.EncodeWATERError(syscall.EPERM) // operation not permitted + lastError = syscall.EPERM + return INVALID_FD } + remoteConn.SetNonBlock(true) // at this point, it is safe to set non-blocking mode on remoteConn workerIdentity = identity_dialer return networkFd } -//export watm_dial_fixed_v1 -func _dial_fixed(internalFd int32) (networkFd int32) { +// _accept +// +// watm_accept_v1() -> s32 +// +//export watm_accept_v1 +func _accept() (networkFd int32) { if workerIdentity != identity_uninitialized { - return wasip1.EncodeWATERError(syscall.EBUSY) // device or resource busy (worker already initialized) + lastError = syscall.EBUSY + return INVALID_FD } - // wrap the internalFd into a v1net.Conn - sourceConn = v1net.RebuildTCPConn(internalFd) - err := sourceConn.(*v1net.TCPConn).SetNonBlock(true) - if err != nil { - log.Printf("dial: sourceConn.SetNonblock: %v", err) - return wasip1.EncodeWATERError(err.(syscall.Errno)) + if !globalListener.locked { + panic("listener is not built with any inbound transport") } - if globalFixedDialer.fdt != nil { - globalFixedDialer.fdt.SetDialer(v1net.Dial) - remoteConn, err = globalFixedDialer.fdt.DialFixed() - if err != nil { - log.Printf("dial: c.dt.Dial: %v", err) - return wasip1.EncodeWATERError(err.(syscall.Errno)) + // Check if listener is configurable. If so, + // pull the config file from the host and configure it. + configurableListener := globalListener.Configurable() + if configurableListener != nil { + config, err := readInboundConfig() + if err == nil || config != nil { + configurableListener.Configure(config) + } else if !errors.Is(err, syscall.EACCES) { // EACCES means no config file provided by the host + lastError = err.(syscall.Errno) + return INVALID_FD } - networkFd = remoteConn.Fd() - } else { - return wasip1.EncodeWATERError(syscall.EPERM) // operation not permitted } - workerIdentity = identity_connector - return networkFd -} - -//export watm_accept_v1 -func _accept(internalFd int32) (networkFd int32) { - if workerIdentity != identity_uninitialized { - return wasip1.EncodeWATERError(syscall.EBUSY) // device or resource busy (worker already initialized) - } - - // wrap the internalFd into a v1net.Conn - sourceConn = v1net.RebuildTCPConn(internalFd) - err := sourceConn.(*v1net.TCPConn).SetNonBlock(true) - if err != nil { - log.Printf("dial: sourceConn.SetNonblock: %v", err) - return wasip1.EncodeWATERError(err.(syscall.Errno)) + if sourceConn == nil { + log.Printf("internal_pipe: sourceConn is nil, _internal_pipe must be called first") + lastError = syscall.ENOTCONN + return INVALID_FD } if globalListener.wt != nil { @@ -185,7 +160,8 @@ func _accept(internalFd int32) (networkFd int32) { rawNetworkConn, err := lis.Accept() if err != nil { log.Printf("dial: v1net.Listener.Accept: %v", err) - return wasip1.EncodeWATERError(err.(syscall.Errno)) + lastError = err.(syscall.Errno) + return INVALID_FD } networkFd = rawNetworkConn.Fd() @@ -197,77 +173,158 @@ func _accept(internalFd int32) (networkFd int32) { remoteConn, err = globalListener.wt.Wrap(rawNetworkConn) if err != nil { log.Printf("dial: d.wt.Wrap: %v", err) - return wasip1.EncodeWATERError(syscall.EPROTO) // protocol error + lastError = syscall.EPROTO + return INVALID_FD } } else if globalListener.lt != nil { - globalListener.lt.SetListener(&v1net.TCPListener{}) // call v1net.ListeningTransport.Accept wrappedNetworkConn, err := globalListener.lt.Accept() if err != nil { log.Printf("dial: v1net.Listener.Accept: %v", err) - return wasip1.EncodeWATERError(err.(syscall.Errno)) + lastError = err.(syscall.Errno) + return INVALID_FD } networkFd = wrappedNetworkConn.Fd() remoteConn = wrappedNetworkConn } else { - return wasip1.EncodeWATERError(syscall.EPERM) // operation not permitted + lastError = syscall.EPERM + return INVALID_FD } + remoteConn.SetNonBlock(true) // at this point, it is safe to set non-blocking mode on remoteConn workerIdentity = identity_listener return networkFd } +// _associate +// +// watm_associate_v1(networkType s32) -> s32 +// //export watm_associate_v1 -func _associate() int32 { +func _associate(networkType uint32) uint32 { if workerIdentity != identity_uninitialized { - return wasip1.EncodeWATERError(syscall.EBUSY) // device or resource busy (worker already initialized) + return saveAndReturnError(syscall.EBUSY) // device or resource busy (worker already initialized) } - if globalRelay.wt != nil { - var err error - var lis v1net.Listener = &v1net.TCPListener{} - sourceConn, err = lis.Accept() - if err != nil { - log.Printf("dial: v1net.Listener.Accept: %v", err) - return wasip1.EncodeWATERError(err.(syscall.Errno)) + if !globalRelay.inboundLocked && !globalRelay.outboundLocked { + panic("relay is not built with either inbound or outbound transport") + } + + // Check if relay is configurable. If so, + // pull the config file from the host and configure it. + configurableInbRelay := globalRelay.InboundConfigurable() + if configurableInbRelay != nil { + config, err := readInboundConfig() + if err == nil || config != nil { + configurableInbRelay.Configure(config) + } else if !errors.Is(err, syscall.EACCES) { // EACCES means no config file provided by the host + return saveAndReturnError(err) + } + } + + configurableOutRelay := globalRelay.OutboundConfigurable() + if configurableOutRelay != nil { + config, err := readOutboundConfig() + if err == nil || config != nil { + configurableOutRelay.Configure(config) + } else if !errors.Is(err, syscall.EACCES) { // EACCES means no config file provided by the host + return saveAndReturnError(err) } + } - remoteConn, err = v1net.DialFixed() + // handle inbound connection + var err error + if globalRelay.inboundListeningTransport != nil { + sourceConn, err = globalRelay.inboundListeningTransport.Accept() if err != nil { - log.Printf("dial: v1net.Dial: %v", err) - return wasip1.EncodeWATERError(err.(syscall.Errno)) + log.Printf("dial: relay.ListeningTransport.Accept: %v", err) + return saveAndReturnError(err) } + } else { // we first accept the inbound connection, then wrap it if there's a wrapping transport set for it + sourceConn, err = (&v1net.TCPListener{}).Accept() + if err != nil { + log.Printf("dial: v1net.TCPListener.Accept: %v", err) + return saveAndReturnError(err) + } + + if globalRelay.inboundWrappingTransport != nil { + sourceConn, err = globalRelay.inboundWrappingTransport.Wrap(sourceConn.(*v1net.TCPConn)) + if err != nil { + log.Printf("dial: r.inboundWrappingTransport.Wrap: %v", err) + return saveAndReturnError(syscall.EPROTO) // protocol error + } + } + } - if globalRelay.wrapSelection == RelayWrapRemote { - // wrap remoteConn - remoteConn, err = globalRelay.wt.Wrap(remoteConn.(*v1net.TCPConn)) - // set sourceConn, the not-wrapped one, to non-blocking mode - sourceConn.(*v1net.TCPConn).SetNonBlock(true) - } else { - // wrap sourceConn - sourceConn, err = globalRelay.wt.Wrap(sourceConn.(*v1net.TCPConn)) - // set remoteConn, the not-wrapped one, to non-blocking mode - remoteConn.(*v1net.TCPConn).SetNonBlock(true) + // handle outbound connection + var network string = v1net.ToNetworkTypeString(networkType) + var address string + if address, err = v1net.GetAddrSuggestion(); err != nil { + log.Printf("dial: v1net.GetAddrSuggestion: %v", err) + return saveAndReturnError(err) + } + if globalRelay.outboundDialingTransport != nil { + remoteConn, err = globalRelay.outboundDialingTransport.Dial(network, address) + if err != nil { + log.Printf("dial: r.outboundDialingTransport.Dial: %v", err) + return saveAndReturnError(err) } + } else { // we first dial the outbound connection, then wrap it if there's a wrapping transport set for it + remoteConn, err = v1net.Dial(network, address) if err != nil { - log.Printf("dial: r.wt.Wrap: %v", err) - return wasip1.EncodeWATERError(syscall.EPROTO) // protocol error + log.Printf("dial: v1net.Dial: %v", err) + return saveAndReturnError(err) } - } else { - return wasip1.EncodeWATERError(syscall.EPERM) // operation not permitted + + if globalRelay.outboundWrappingTransport != nil { + remoteConn, err = globalRelay.outboundWrappingTransport.Wrap(remoteConn.(*v1net.TCPConn)) + if err != nil { + log.Printf("dial: r.outboundWrappingTransport.Wrap: %v", err) + return saveAndReturnError(syscall.EPROTO) // protocol error + } + } + } + + // set non-blocking mode on both connections + if err := sourceConn.SetNonBlock(true); err != nil { + log.Printf("dial: sourceConn.SetNonblock: %v", err) + return saveAndReturnError(err) + } + if err := remoteConn.SetNonBlock(true); err != nil { + log.Printf("dial: remoteConn.SetNonblock: %v", err) + return saveAndReturnError(err) } workerIdentity = identity_relay return 0 } +// _start +// +// watm_start_v1() -> s32 +// //export watm_start_v1 -func _start() int32 { +func _start() uint32 { if workerIdentity == identity_uninitialized { log.Println("worker: uninitialized") - return wasip1.EncodeWATERError(syscall.ENOTCONN) // socket not connected + return saveAndReturnError(syscall.ENOTCONN) // socket not connected } log.Printf("worker: working as %s", identityStrings[workerIdentity]) return worker() } + +//export watm_lasterror_v1 +func _lasterror() uint32 { + return uint32(lastError) +} + +func saveAndReturnError(err error) uint32 { + syscallErrno, ok := err.(syscall.Errno) + if !ok { + log.Printf("saveAndReturnError: %v is not a syscall.Errno", err) + lastError = syscall.ENOSYS + } + lastError = syscallErrno + return uint32(syscallErrno) +} diff --git a/tinygo/v1/fs.go b/tinygo/v1/fs.go deleted file mode 100644 index b7b1f99..0000000 --- a/tinygo/v1/fs.go +++ /dev/null @@ -1 +0,0 @@ -package v1 diff --git a/tinygo/v1/listener.go b/tinygo/v1/listener.go index 1f53726..f67629f 100644 --- a/tinygo/v1/listener.go +++ b/tinygo/v1/listener.go @@ -1,19 +1,22 @@ package v1 +import v1net "github.com/refraction-networking/watm/tinygo/v1/net" + type listener struct { + // incoming: pick one wt WrappingTransport lt ListeningTransport + + locked bool } -func (l *listener) ConfigurableTransport() ConfigurableTransport { +func (l *listener) Configurable() Configurable { if l.wt != nil { - if wt, ok := l.wt.(ConfigurableTransport); ok { + if wt, ok := l.wt.(Configurable); ok { return wt } - } - - if l.lt != nil { - if lt, ok := l.lt.(ConfigurableTransport); ok { + } else if l.lt != nil { + if lt, ok := l.lt.(Configurable); ok { return lt } } @@ -21,12 +24,23 @@ func (l *listener) ConfigurableTransport() ConfigurableTransport { return nil } -func (l *listener) Initialize() { - // TODO: allow initialization on listener -} - var globalListener listener +// BuildListenerWithInboundTransport arms the listener with an inbound +// transport that is used to accept inbound connections on a local +// address and provide high-level application layer protocol over the +// accepted connection. +func BuildListenerWithInboundTransport(anyTransport interface{}) { + switch t := anyTransport.(type) { + case WrappingTransport: + BuildListenerWithWrappingTransport(t) + case ListeningTransport: + BuildListenerWithListeningTransport(t) + default: + panic("transport type not supported") + } +} + // BuildListenerWithWrappingTransport arms the listener with a // [WrappingTransport] that is used to wrap a [v1net.Conn] into // another [net.Conn] by providing some high-level application @@ -34,8 +48,14 @@ var globalListener listener // // Mutually exclusive with [BuildListenerWithListeningTransport]. func BuildListenerWithWrappingTransport(wt WrappingTransport) { + if globalListener.locked { + panic("listener is locked") + } + globalListener.wt = wt globalListener.lt = nil + + globalListener.locked = true } // BuildListenerWithListeningTransport arms the listener with a @@ -45,6 +65,13 @@ func BuildListenerWithWrappingTransport(wt WrappingTransport) { // // Mutually exclusive with [BuildListenerWithWrappingTransport]. func BuildListenerWithListeningTransport(lt ListeningTransport) { + if globalListener.locked { + panic("listener is locked") + } + globalListener.lt = lt + lt.SetListener(&v1net.TCPListener{}) globalListener.wt = nil + + globalListener.locked = true } diff --git a/tinygo/v1/net/dial.go b/tinygo/v1/net/dial.go index ff94b7e..3ef1a56 100644 --- a/tinygo/v1/net/dial.go +++ b/tinygo/v1/net/dial.go @@ -1,13 +1,20 @@ package net import ( + "unsafe" + "github.com/refraction-networking/watm/tinygo/v1/wasiimport" "github.com/refraction-networking/watm/wasip1" ) -// DialFixed connects to a pre-determined-by-runtime address and returns a Conn. -func DialFixed() (Conn, error) { - fd, err := wasip1.DecodeWATERError(wasiimport.WaterDialFixed()) +// Dial dials a remote host for a network connection. +func Dial(network, address string) (Conn, error) { + var fd int32 + err := wasip1.ErrnoToError(wasiimport.WaterDial( + ToNetworkTypeInt(network), + makeIOVec([]byte(address)), 1, + unsafe.Pointer(&fd), + )) if err != nil { return nil, err } @@ -15,15 +22,19 @@ func DialFixed() (Conn, error) { return RebuildTCPConn(fd), nil } -// Dial dials a remote host for a network connection. -func Dial(network, address string) (Conn, error) { - fd, err := wasip1.DecodeWATERError(wasiimport.WaterDial( - makeIOVec([]byte(network)), 1, - makeIOVec([]byte(address)), 1, +// GetAddrSuggestion should be invoked by a dialer when it +// could not determine which address to dial. This function +// must be called before watm_dial_v1 returns. +func GetAddrSuggestion() (string, error) { + var addrBuf []byte = make([]byte, 256) + var nread size + err := wasip1.ErrnoToError(wasiimport.WaterGetAddrSuggestion( + makeIOVec(addrBuf), 1, + unsafe.Pointer(&nread), )) if err != nil { - return nil, err + return "", err } - return RebuildTCPConn(fd), nil + return string(addrBuf[:nread]), nil } diff --git a/tinygo/v1/net/listener.go b/tinygo/v1/net/listener.go index 15188aa..c733a13 100644 --- a/tinygo/v1/net/listener.go +++ b/tinygo/v1/net/listener.go @@ -1,6 +1,9 @@ package net import ( + "syscall" + "unsafe" + "github.com/refraction-networking/watm/tinygo/v1/wasiimport" "github.com/refraction-networking/watm/wasip1" ) @@ -20,10 +23,18 @@ var _ Listener = (*TCPListener)(nil) // By saying "fake", it means that the file descriptor is not // managed inside the WATM, but by the host application. type TCPListener struct { + closed bool } -func (*TCPListener) Accept() (Conn, error) { - fd, err := wasip1.DecodeWATERError(wasiimport.WaterAccept()) +func (tl *TCPListener) Accept() (Conn, error) { + if tl.closed { + return nil, syscall.EINVAL + } + + var fd int32 + err := wasip1.ErrnoToError(wasiimport.WaterAccept( + unsafe.Pointer(&fd), + )) if err != nil { return nil, err } @@ -33,6 +44,10 @@ func (*TCPListener) Accept() (Conn, error) { // TCPListener does not really keep a file descriptor for an // underlying TCP socket, so it does not need to close anything. -func (*TCPListener) Close() error { +func (tl *TCPListener) Close() error { + if tl.closed { + return syscall.EINVAL + } + tl.closed = true return nil } diff --git a/tinygo/v1/net/network_type.go b/tinygo/v1/net/network_type.go new file mode 100644 index 0000000..ce99ef7 --- /dev/null +++ b/tinygo/v1/net/network_type.go @@ -0,0 +1,34 @@ +package net + +const ( + NETWORK_UNKNOWN uint32 = iota + NETWORK_TCP + NETWORK_TCP4 + NETWORK_TCP6 +) + +func ToNetworkTypeString(networkType uint32) string { + switch networkType { + case NETWORK_TCP: + return "tcp" + case NETWORK_TCP4: + return "tcp4" + case NETWORK_TCP6: + return "tcp6" + default: + panic("unsupported network type") + } +} + +func ToNetworkTypeInt(networkType string) uint32 { + switch networkType { + case "tcp": + return NETWORK_TCP + case "tcp4": + return NETWORK_TCP4 + case "tcp6": + return NETWORK_TCP6 + default: + panic("unsupported network type") + } +} diff --git a/tinygo/v1/pluggable_transports.go b/tinygo/v1/pluggable_transports.go index f00ac3c..2ff6d88 100644 --- a/tinygo/v1/pluggable_transports.go +++ b/tinygo/v1/pluggable_transports.go @@ -26,30 +26,20 @@ type WrappingTransport interface { Wrap(v1net.Conn) (v1net.Conn, error) } -// Any transport requiring a dialer to be set before use should -// implement this interface. -type RequireDialer interface { +// DialingTransport is a transport type that can be used to dial +// a remote address and provide high-level application layer +// protocol over the dialed connection. +type DialingTransport interface { // SetDialer sets the dialer function that is used to dial // a remote address. // - // In v0, the input parameter of the dialer function is - // unused inside the WATM, given the connection is always - // managed by the host application. - // - // The returned v1net.Conn is NOT by default set to non-blocking. + // The returned v1net.Conn from this dialer function should NOT + // be assumed by the caller as a non-blocking one. // It is the responsibility of the transport to make it - // non-blocking by calling v1net.Conn.SetNonblock. This is to - // allow some transport to perform blocking operations such as - // TLS handshake. + // non-blocking by explicitly calling v1net.Conn.SetNonblock. + // This is to allow some transports to perform blocking operations, + // such as TLS handshake. SetDialer(dialer func(network, address string) (v1net.Conn, error)) -} - -// DialingTransport is a transport type that can be used to dial -// a remote address and provide high-level application layer -// protocol over the dialed connection. -type DialingTransport interface { - // A dialer must be set for a DialingTransport to be used. - RequireDialer // Dial connects to the given remote address and returns a // v1net.Conn that provides high-level application layer @@ -64,26 +54,6 @@ type DialingTransport interface { Dial(network, address string) (v1net.Conn, error) } -// FixedDialingTransport is a transport type that can be used to dial -// a otherwise-determined (unknown to its immediate caller) address and -// provide high-level application layer protocol over the dialed connection. -type FixedDialingTransport interface { - // A dialer must be set for a FixedDialingTransport to be used. - RequireDialer - - // DialFixed connects to a remote address determined by the - // FixedDialingTransport and returns a v1net.Conn that provides - // high-level application layer protocol over the dialed connection. - // - // The transport SHOULD provide non-blocking v1net.Conn.Read - // operation on the returned v1net.Conn if possible, otherwise - // the worker may block on reading from a blocking connection. - // And it is highly recommended to pass all funtions other than - // Read and Write to the underlying v1net.Conn from the underlying - // dialer function. - DialFixed() (v1net.Conn, error) -} - // ListeningTransport is a transport type that can be used to // accept incoming connections on a local address and provide // high-level application layer protocol over the accepted @@ -110,8 +80,8 @@ type ListeningTransport interface { Accept() (v1net.Conn, error) } -// ConfigurableTransport is a transport type that can be configured -// with a config file in the form of a byte slice. -type ConfigurableTransport interface { +// Configurable is an interface indicating the transport can be configured +// with a config file in the form of a byte array. +type Configurable interface { Configure(config []byte) error } diff --git a/tinygo/v1/relay.go b/tinygo/v1/relay.go index cfd7a6c..898c66c 100644 --- a/tinygo/v1/relay.go +++ b/tinygo/v1/relay.go @@ -1,78 +1,121 @@ package v1 -type RelayWrapSelection bool - -const ( - RelayWrapRemote RelayWrapSelection = false - RelayWrapSource RelayWrapSelection = true -) +import v1net "github.com/refraction-networking/watm/tinygo/v1/net" type relay struct { - wt WrappingTransport - wrapSelection RelayWrapSelection - // lt ListeningTransport - // dt DialingTransport + // inbound: pick one + inboundWrappingTransport WrappingTransport + inboundListeningTransport ListeningTransport + inboundLocked bool + + // outbound: pick one + outboundWrappingTransport WrappingTransport + outboundDialingTransport DialingTransport + outboundLocked bool } -func (r *relay) ConfigurableTransport() ConfigurableTransport { - if r.wt != nil { - if wt, ok := r.wt.(ConfigurableTransport); ok { +// InboundConfigurable returns non-nil if the relay is built with a +// configurable inbound transport. +func (r *relay) InboundConfigurable() Configurable { + if r.inboundWrappingTransport != nil { + if wt, ok := r.inboundWrappingTransport.(Configurable); ok { return wt } + } else if r.inboundListeningTransport != nil { + if lt, ok := r.inboundListeningTransport.(Configurable); ok { + return lt + } } - // if r.lt != nil { - // if lt, ok := r.lt.(ConfigurableTransport); ok { - // return lt - // } - // } - - // if r.dt != nil { - // if dt, ok := r.dt.(ConfigurableTransport); ok { - // return dt - // } - // } - return nil } -func (r *relay) Initialize() { - // TODO: allow initialization on relay +// OutboundConfigurable returns non-nil if the relay is built with a +// configurable outbound transport. +func (r *relay) OutboundConfigurable() Configurable { + if r.outboundWrappingTransport != nil { + if wt, ok := r.outboundWrappingTransport.(Configurable); ok { + return wt + } + } else if r.outboundDialingTransport != nil { + if dt, ok := r.outboundDialingTransport.(Configurable); ok { + return dt + } + } + + return nil } var globalRelay relay -// BuildRelayWithWrappingTransport arms the relay with a -// [WrappingTransport] that is used to wrap a [v1net.Conn] into -// another [net.Conn] by providing some high-level application -// layer protocol. -// -// The caller MUST keep in mind that the [WrappingTransport] is -// used to wrap the connection to the remote address, not the -// connection from the source address (the dialing peer). -// To reverse this behavior, i.e., wrap the inbounding connection, -// set wrapSelection to [RelayWrapSource]. +// BuildRelayWithInboundTransport arms the relay with an inbound +// transport that is used to accept inbound connections on a local +// address and provide high-level application layer protocol over the +// accepted connection. // -// Mutually exclusive with [BuildRelayWithListeningDialingTransport]. -func BuildRelayWithWrappingTransport(wt WrappingTransport, wrapSelection RelayWrapSelection) { - globalRelay.wt = wt - globalRelay.wrapSelection = wrapSelection - // r.lt = nil - // r.dt = nil +// Outbound transport must be set as well before or after calling +// this function to complete the relay configuration. Otherwise, +// the outbound transport will be plain. +func BuildRelayWithInboundTransport(anyTransport interface{}) { + switch t := anyTransport.(type) { + case WrappingTransport: + BuildRelayWithInboundWrappingTransport(t) + case ListeningTransport: + BuildRelayWithInboundListeningTransport(t) + default: + panic("transport type not supported") + } } -// BuildRelayWithListeningDialingTransport arms the relay with a -// [ListeningTransport] that is used to accept incoming connections -// on a local address and provide high-level application layer -// protocol over the accepted connection, and a [DialingTransport] -// that is used to dial a remote address and provide high-level -// application layer protocol over the dialed connection. -// -// Mutually exclusive with [BuildRelayWithWrappingTransport]. -func BuildRelayWithListeningDialingTransport(lt ListeningTransport, dt DialingTransport) { - // TODO: implement BuildRelayWithListeningDialingTransport - // r.lt = lt - // r.dt = dt - // r.wt = nil - panic("BuildRelayWithListeningDialingTransport: not implemented") +func BuildRelayWithOutboundTransport(anyTransport interface{}) { + switch t := anyTransport.(type) { + case WrappingTransport: + BuildRelayWithOutboundWrappingTransport(t) + case DialingTransport: + BuildRelayWithOutboundDialingTransport(t) + default: + panic("transport type not supported") + } +} + +func BuildRelayWithInboundWrappingTransport(wt WrappingTransport) { + if globalRelay.inboundLocked { + panic("relay is locked") + } + + globalRelay.inboundWrappingTransport = wt + globalRelay.inboundListeningTransport = nil + globalRelay.inboundLocked = true +} + +func BuildRelayWithInboundListeningTransport(lt ListeningTransport) { + if globalRelay.inboundLocked { + panic("relay is locked") + } + + globalRelay.inboundListeningTransport = lt + lt.SetListener(&v1net.TCPListener{}) + globalRelay.inboundWrappingTransport = nil + globalRelay.inboundLocked = true +} + +func BuildRelayWithOutboundWrappingTransport(wt WrappingTransport) { + if globalRelay.outboundLocked { + panic("relay is locked") + } + + globalRelay.outboundWrappingTransport = wt + globalRelay.outboundDialingTransport = nil + globalRelay.outboundLocked = true +} + +func BuildRelayWithOutboundDialingTransport(dt DialingTransport) { + if globalRelay.outboundLocked { + panic("relay is locked") + } + + globalRelay.outboundDialingTransport = dt + dt.SetDialer(v1net.Dial) + globalRelay.outboundWrappingTransport = nil + globalRelay.outboundLocked = true } diff --git a/tinygo/v1/usleep.go b/tinygo/v1/usleep.go deleted file mode 100644 index fa75d1e..0000000 --- a/tinygo/v1/usleep.go +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2023 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package v1 - -import ( - "unsafe" - - "github.com/refraction-networking/watm/tinygo/v1/wasiimport" -) - -// GOARCH=wasm currently has 64 bits pointers, but the WebAssembly host expects -// pointers to be 32 bits so we use this type alias to represent pointers in -// structs and arrays passed as arguments to WASI functions. -// -// Note that the use of an integer type prevents the compiler from tracking -// pointers passed to WASI functions, so we must use KeepAlive to explicitly -// retain the objects that could otherwise be reclaimed by the GC. -type uintptr32 = uint32 - -// https://github.com/WebAssembly/WASI/blob/a2b96e81c0586125cc4dc79a5be0b78d9a059925/legacy/preview1/docs.md#-size-u32 -type size = uint32 - -// https://github.com/WebAssembly/WASI/blob/a2b96e81c0586125cc4dc79a5be0b78d9a059925/legacy/preview1/docs.md#-errno-variant -type errno = uint32 - -// https://github.com/WebAssembly/WASI/blob/a2b96e81c0586125cc4dc79a5be0b78d9a059925/legacy/preview1/docs.md#-filesize-u64 -type filesize = uint64 - -// https://github.com/WebAssembly/WASI/blob/a2b96e81c0586125cc4dc79a5be0b78d9a059925/legacy/preview1/docs.md#-timestamp-u64 -type timestamp = uint64 - -// https://github.com/WebAssembly/WASI/blob/a2b96e81c0586125cc4dc79a5be0b78d9a059925/legacy/preview1/docs.md#-clockid-variant -type clockid = uint32 - -const ( - clockRealtime clockid = 0 - clockMonotonic clockid = 1 -) - -// https://github.com/WebAssembly/WASI/blob/a2b96e81c0586125cc4dc79a5be0b78d9a059925/legacy/preview1/docs.md#-iovec-record -type iovec struct { - buf uintptr32 - bufLen size -} - -type eventtype = uint8 - -const ( - eventtypeClock eventtype = iota - eventtypeFdRead - eventtypeFdWrite -) - -type eventrwflags = uint16 - -const ( - fdReadwriteHangup eventrwflags = 1 << iota -) - -type userdata = uint64 - -// The go:wasmimport directive currently does not accept values of type uint16 -// in arguments or returns of the function signature. Most WASI imports return -// an errno value, which we have to define as uint32 because of that limitation. -// However, the WASI errno type is intended to be a 16 bits integer, and in the -// event struct the error field should be of type errno. If we used the errno -// type for the error field it would result in a mismatching field alignment and -// struct size because errno is declared as a 32 bits type, so we declare the -// error field as a plain uint16. -type event struct { - userdata userdata - error uint16 - typ eventtype - p [5]uint8 // padding to 16 bytes - fdReadwrite eventFdReadwrite -} - -type eventFdReadwrite struct { - nbytes filesize - flags eventrwflags -} - -type subclockflags = uint16 - -const ( - subscriptionClockAbstime subclockflags = 1 << iota -) - -type subscriptionClock struct { - id clockid - timeout timestamp - precision timestamp - flags subclockflags -} - -type subscriptionFdReadwrite struct { - fd int32 -} - -type subscription struct { - userdata userdata - u subscriptionUnion -} - -type subscriptionUnion [5]uint64 - -func (u *subscriptionUnion) eventtype() *eventtype { - return (*eventtype)(unsafe.Pointer(&u[0])) -} - -func (u *subscriptionUnion) subscriptionClock() *subscriptionClock { - return (*subscriptionClock)(unsafe.Pointer(&u[1])) -} - -func (u *subscriptionUnion) subscriptionFdReadwrite() *subscriptionFdReadwrite { - return (*subscriptionFdReadwrite)(unsafe.Pointer(&u[1])) -} - -func usleep(usec uint32) { - var in subscription - var out event - var nevents size - - eventtype := in.u.eventtype() - *eventtype = eventtypeClock - - subscription := in.u.subscriptionClock() - subscription.id = clockMonotonic - subscription.timeout = timestamp(usec) * 1e3 - subscription.precision = 1e3 - - if wasiimport.PollOneoff(unsafe.Pointer(&in), unsafe.Pointer(&out), 1, unsafe.Pointer(&nevents)) != 0 { - panic("wasi_snapshot_preview1.poll_oneoff") - } -} diff --git a/tinygo/v1/wasiimport/import.go b/tinygo/v1/wasiimport/import.go index d2735f6..5b80d87 100644 --- a/tinygo/v1/wasiimport/import.go +++ b/tinygo/v1/wasiimport/import.go @@ -14,17 +14,12 @@ func SetWaterDialedFD(fd int32) { } func water_dial( + _ uint32, _ unsafe.Pointer, _ size, - _ unsafe.Pointer, _ size, -) (fd int32) { - return waterDialedFD -} - -// This function should be imported from the host in WASI. -// On non-WASI platforms, it mimicks the behavior of the host -// by returning a file descriptor of preset value. -func water_dial_fixed() (fd int32) { - return waterDialedFD + fd unsafe.Pointer, +) errno { + *(*int32)(fd) = waterDialedFD + return 0 } var waterAcceptedFD int32 = -1 @@ -36,13 +31,21 @@ func SetWaterAcceptedFD(fd int32) { // This function should be imported from the host in WASI. // On non-WASI platforms, it mimicks the behavior of the host // by returning a file descriptor of preset value. -func water_accept() (fd int32) { - return waterAcceptedFD +func water_accept(fd unsafe.Pointer) errno { + *(*int32)(fd) = waterAcceptedFD + return 0 +} + +func water_get_addr_suggestion( + unsafe.Pointer, size, + unsafe.Pointer, +) errno { + return 0 } // emulate the behavior when no file descriptors are // ready and the timeout expires immediately. -func poll_oneoff(_, _ unsafe.Pointer, nsubscriptions uint32, nevents unsafe.Pointer) uint32 { +func poll_oneoff(_, _ unsafe.Pointer, nsubscriptions uint32, nevents unsafe.Pointer) errno { // wait for a very short period to simulate the polling time.Sleep(50 * time.Millisecond) *(*uint32)(nevents) = nsubscriptions diff --git a/tinygo/v1/wasiimport/import_exported.go b/tinygo/v1/wasiimport/import_exported.go new file mode 100644 index 0000000..9e363e4 --- /dev/null +++ b/tinygo/v1/wasiimport/import_exported.go @@ -0,0 +1,37 @@ +package wasiimport + +import "unsafe" + +func WaterDial( + networkType uint32, + addressCiovs unsafe.Pointer, addressCiovsLen size, + fd unsafe.Pointer, +) errno { + return water_dial( + networkType, + addressCiovs, addressCiovsLen, + fd) +} + +func WaterAccept(fd unsafe.Pointer) errno { + return water_accept(fd) +} + +func WaterGetAddrSuggestion( + addressIovs unsafe.Pointer, addressIovsLen size, + nread unsafe.Pointer, +) errno { + return water_get_addr_suggestion(addressIovs, addressIovsLen, nread) +} + +func FdFdstatSetFlags(fd int32, flags uint32) uint32 { + return fd_fdstat_set_flags(fd, flags) +} + +func FdFdstatGet(fd int32, buf unsafe.Pointer) uint32 { + return fd_fdstat_get(fd, buf) +} + +func PollOneoff(in, out unsafe.Pointer, nsubscriptions size, nevents unsafe.Pointer) errno { + return poll_oneoff(in, out, nsubscriptions, nevents) +} diff --git a/tinygo/v1/wasiimport/import_wasi.go b/tinygo/v1/wasiimport/import_wasi.go index b290431..72d0241 100644 --- a/tinygo/v1/wasiimport/import_wasi.go +++ b/tinygo/v1/wasiimport/import_wasi.go @@ -4,44 +4,39 @@ package wasiimport import "unsafe" -// Import the Runtime-imported dial function, -// which takes iovs for network and address and -// returns a file descriptor for the dialed connection. -// //go:wasmimport env water_dial //go:noescape func water_dial( - networkIovs unsafe.Pointer, networkIovsLen size, - addressIovs unsafe.Pointer, addressIovsLen size, -) (fd int32) + networkType uint32, + addressCiovs unsafe.Pointer, addressCiovsLen size, + fd unsafe.Pointer, +) errno -// Import the Runtime-imported fixed dialer function, -// which returns a file descriptor for the dialled connection. -// -//go:wasmimport env water_dial_fixed +//go:wasmimport env water_accept //go:noescape -func water_dial_fixed() (fd int32) +func water_accept(fd unsafe.Pointer) errno -// Import the Runtime-imported acceptor function. -// -//go:wasmimport env water_accept +//go:wasmimport env water_get_addr_suggestion //go:noescape -func water_accept() (fd int32) +func water_get_addr_suggestion( + addressIovs unsafe.Pointer, addressIovsLen size, + nread unsafe.Pointer, +) errno -// Import wasi_snapshot_preview1's fd_fdstat_set_flags function -// until tinygo supports it. +// TODO: remove this once tinygo provides wrapper for fd_fdstat_set // //go:wasmimport wasi_snapshot_preview1 fd_fdstat_set_flags //go:noescape func fd_fdstat_set_flags(fd int32, flags uint32) uint32 -// Import wasi_snapshot_preview1's fd_fdstat_set_flags function -// until tinygo supports it. +// TODO: remove this once tinygo provides wrapper for fd_fdstat_get // //go:wasmimport wasi_snapshot_preview1 fd_fdstat_get //go:noescape func fd_fdstat_get(fd int32, buf unsafe.Pointer) uint32 +// TODO: remove this once tinygo provides wrapper for poll_oneoff +// //go:wasmimport wasi_snapshot_preview1 poll_oneoff //go:noescape func poll_oneoff(in, out unsafe.Pointer, nsubscriptions size, nevents unsafe.Pointer) errno diff --git a/tinygo/v1/wasiimport/symbols.go b/tinygo/v1/wasiimport/symbols.go deleted file mode 100644 index a63b9e7..0000000 --- a/tinygo/v1/wasiimport/symbols.go +++ /dev/null @@ -1,30 +0,0 @@ -package wasiimport - -import "unsafe" - -func WaterDial( - networkIOVs unsafe.Pointer, networkIOVsLen size, - addressIOVs unsafe.Pointer, addressIOVsLen size, -) (fd int32) { - return water_dial(networkIOVs, networkIOVsLen, addressIOVs, addressIOVsLen) -} - -func WaterDialFixed() (fd int32) { - return water_dial_fixed() -} - -func WaterAccept() (fd int32) { - return water_accept() -} - -func FdFdstatSetFlags(fd int32, flags uint32) uint32 { - return fd_fdstat_set_flags(fd, flags) -} - -func FdFdstatGet(fd int32, buf unsafe.Pointer) uint32 { - return fd_fdstat_get(fd, buf) -} - -func PollOneoff(in, out unsafe.Pointer, nsubscriptions size, nevents unsafe.Pointer) errno { - return poll_oneoff(in, out, nsubscriptions, nevents) -} diff --git a/tinygo/v1/worker.go b/tinygo/v1/worker.go index bf60c43..f4fbc30 100644 --- a/tinygo/v1/worker.go +++ b/tinygo/v1/worker.go @@ -8,7 +8,6 @@ import ( "syscall" v1net "github.com/refraction-networking/watm/tinygo/v1/net" - "github.com/refraction-networking/watm/wasip1" ) type identity uint8 @@ -34,7 +33,7 @@ var sourceConn v1net.Conn // sourceConn is used to communicate between WASM and var remoteConn v1net.Conn // remoteConn is used to communicate between WASM and a dialed remote destination (for dialer/relay) or a dialing party (for listener only) var ctrlConn v1net.Conn // ctrlConn is used to control the entire worker with control messages -var workerFn func() int32 = unfairWorker // by default, use unfairWorker for better performance under mostly unidirectional I/O +var workerFn func() uint32 = unfairWorker // by default, use unfairWorker for better performance under mostly unidirectional I/O var readBuf []byte = make([]byte, 1024) // 1024B buffer for reading, size can be updated with [SetReadBufferSize] @@ -57,10 +56,10 @@ func WorkerFairness(fair bool) { } } -func worker() int32 { +func worker() uint32 { if sourceConn == nil || remoteConn == nil || ctrlConn == nil { - log.Println("worker: worker: sourceConn, remoteConn, or ctrlConn is nil") - return wasip1.EncodeWATERError(syscall.EBADF) // bad file descriptor + log.Println("worker: at least one of sourceConn, remoteConn, and ctrlConn is nil") + return saveAndReturnError(syscall.EBADF) // bad file descriptor } return workerFn() @@ -83,7 +82,7 @@ func untilError(f func() error) error { // connection is not properly set to non-blocking mode, i.e., never returns // EAGAIN, this function will block forever and never work on a lower priority // connection. Thus it is called unfairWorker. -func unfairWorker() int32 { +func unfairWorker() uint32 { conns := []v1net.Conn{ctrlConn, sourceConn, remoteConn} evts := []uint16{v1net.EventFdRead, v1net.EventFdRead, v1net.EventFdRead} @@ -95,7 +94,7 @@ func unfairWorker() int32 { continue } log.Println("worker: unfairWorker: _poll:", err) - return int32(err.(syscall.Errno)) + return saveAndReturnError(err) } // 1st priority: ctrlConn @@ -103,10 +102,10 @@ func unfairWorker() int32 { if !(err == syscall.EAGAIN) { if err == io.EOF || err == nil { log.Println("worker: unfairWorker: ctrlConn is closed") - return wasip1.EncodeWATERError(syscall.ECANCELED) // operation canceled + return saveAndReturnError(syscall.ECANCELED) // operation canceled } log.Println("worker: unfairWorker: ctrlConn.Read:", err) - return wasip1.EncodeWATERError(syscall.EIO) // input/output error + return saveAndReturnError(syscall.EIO) // input/output error } // 2nd priority: sourceConn @@ -134,12 +133,12 @@ func unfairWorker() int32 { }); err != syscall.EAGAIN { // silently ignore EAGAIN if err == io.EOF { log.Println("worker: unfairWorker: sourceConn is closed") - return wasip1.EncodeWATERError(0) // success, no error + return saveAndReturnError(syscall.Errno(0)) // success, no error } if errno, ok := err.(syscall.Errno); ok { - return wasip1.EncodeWATERError(errno) + return saveAndReturnError(errno) } - return wasip1.EncodeWATERError(syscall.EIO) // input/output error + return saveAndReturnError(syscall.EIO) // input/output error } // 3rd priority: remoteConn @@ -167,12 +166,12 @@ func unfairWorker() int32 { }); err != syscall.EAGAIN { // silently ignore EAGAIN if err == io.EOF { log.Println("worker: unfairWorker: remoteConn is closed") - return wasip1.EncodeWATERError(0) // success, no error + return saveAndReturnError(syscall.Errno(0)) // success, no error } if errno, ok := err.(syscall.Errno); ok { - return wasip1.EncodeWATERError(errno) + return saveAndReturnError(errno) } - return wasip1.EncodeWATERError(syscall.EIO) // input/output error + return saveAndReturnError(syscall.EIO) // input/output error } } } @@ -185,7 +184,7 @@ func unfairWorker() int32 { // make progress if one of the connection is not properly set to non-blocking mode. // // TODO: use poll_oneoff instead of busy polling -func fairWorker() int32 { +func fairWorker() uint32 { conns := []v1net.Conn{ctrlConn, sourceConn, remoteConn} evts := []uint16{v1net.EventFdRead, v1net.EventFdRead, v1net.EventFdRead} @@ -197,7 +196,7 @@ func fairWorker() int32 { continue } log.Println("worker: unfairWorker: _poll:", err) - return int32(err.(syscall.Errno)) + return saveAndReturnError(err) } // 1st priority: ctrlConn @@ -205,10 +204,10 @@ func fairWorker() int32 { if !(err == syscall.EAGAIN) { if err == io.EOF || err == nil { log.Println("worker: fairWorker: ctrlConn is closed") - return wasip1.EncodeWATERError(syscall.ECANCELED) // operation canceled + return saveAndReturnError(syscall.ECANCELED) // operation canceled } log.Println("worker: fairWorker: ctrlConn.Read:", err) - return wasip1.EncodeWATERError(syscall.EIO) // input/output error + return saveAndReturnError(syscall.EIO) // input/output error } // 2nd priority: sourceConn -> remoteConn @@ -219,12 +218,12 @@ func fairWorker() int32 { sourceConn, // src readBuf); err != nil { if err == io.EOF { - return wasip1.EncodeWATERError(0) // success, no error + return saveAndReturnError(syscall.Errno(0)) // success, no error } if errno, ok := err.(syscall.Errno); ok { - return wasip1.EncodeWATERError(errno) + return saveAndReturnError(errno) } - return wasip1.EncodeWATERError(syscall.EIO) // other input/output error + return saveAndReturnError(syscall.EIO) // other input/output error } // 3rd priority: remoteConn -> sourceConn @@ -235,12 +234,12 @@ func fairWorker() int32 { remoteConn, // src readBuf); err != nil { if err == io.EOF { - return wasip1.EncodeWATERError(0) + return saveAndReturnError(syscall.Errno(0)) // success, no error } if errno, ok := err.(syscall.Errno); ok { - return wasip1.EncodeWATERError(errno) + return saveAndReturnError(errno) } - return wasip1.EncodeWATERError(syscall.EIO) // other input/output error + return saveAndReturnError(syscall.EIO) // other input/output error } } } diff --git a/wasip1/errno.go b/wasip1/errno.go index c36795f..2acb7d8 100644 --- a/wasip1/errno.go +++ b/wasip1/errno.go @@ -5,17 +5,17 @@ import ( "syscall" ) -// errno is just a copy of syscall.Errno from the Go standard library. -// -// The values are defined in syscall/tables_wasip1.go. -type errno syscall.Errno - // DecodeWATERError converts a error code returned by WATER API // into a syscall.Errno or a higher-level error in Go. // // It automatically detects whether the error code is a WATER error // or a success code (positive). In case of a success code, it // returns the code itself and a nil error. +// +// Deprecated: starting from WATM v1 API, returned errno is always +// a ground truth syscall.Errno and not multiplexed with other positive +// return values. Positive return values are always set via a pointer +// as a parameter to the function. func DecodeWATERError(errorCode int32) (n int32, err error) { if errorCode >= 0 { n = errorCode // such that when error code is 0, it will return 0, nil @@ -34,6 +34,11 @@ func DecodeWATERError(errorCode int32) (n int32, err error) { return } +// EncodeWATERError converts a syscall.Errno (positive) into a error code +// returned by WATER API (negative). +// +// Deprecated: starting from WATM v1 API, returned errno is always a ground +// truth syscall.Errno and not multiplexed with other positive return values. func EncodeWATERError(errno syscall.Errno) int32 { if errno == 0 { return 0 diff --git a/wasip1/error.go b/wasip1/error.go new file mode 100644 index 0000000..0dc2f8f --- /dev/null +++ b/wasip1/error.go @@ -0,0 +1,14 @@ +package wasip1 + +import "fmt" + +func ErrnoToError(errno uint32) error { + if errno == 0 { + return nil + } + + if syscallErrno, ok := mapErrno2Syscall[errno]; ok { + return syscallErrno + } + return fmt.Errorf("unknown errno %d", errno) +} diff --git a/wasip1/tables.go b/wasip1/tables.go index ac6d9bb..ddc2349 100644 --- a/wasip1/tables.go +++ b/wasip1/tables.go @@ -4,6 +4,8 @@ import ( "syscall" ) +type errno = uint32 + // errno is just a copy of syscall.Errno from the Go standard library. // // WATER error code is defined as the negative value of errno. @@ -88,85 +90,84 @@ const ( EOPNOTSUPP = ENOTSUP ) -// TODO: Auto-generate some day. (Hard-coded in binaries so not likely to change.) -var errorstr = [...]string{ - E2BIG: "Argument list too long", - EACCES: "Permission denied", - EADDRINUSE: "Address already in use", - EADDRNOTAVAIL: "Address not available", - EAFNOSUPPORT: "Address family not supported by protocol family", - EAGAIN: "Try again", - EALREADY: "Socket already connected", - EBADF: "Bad file number", - EBADMSG: "Trying to read unreadable message", - EBUSY: "Device or resource busy", - ECANCELED: "Operation canceled.", - ECHILD: "No child processes", - ECONNABORTED: "Connection aborted", - ECONNREFUSED: "Connection refused", - ECONNRESET: "Connection reset by peer", - EDEADLK: "Deadlock condition", - EDESTADDRREQ: "Destination address required", - EDOM: "Math arg out of domain of func", - EDQUOT: "Quota exceeded", - EEXIST: "File exists", - EFAULT: "Bad address", - EFBIG: "File too large", - EHOSTUNREACH: "Host is unreachable", - EIDRM: "Identifier removed", - EILSEQ: "EILSEQ", - EINPROGRESS: "Connection already in progress", - EINTR: "Interrupted system call", - EINVAL: "Invalid argument", - EIO: "I/O error", - EISCONN: "Socket is already connected", - EISDIR: "Is a directory", - ELOOP: "Too many symbolic links", - EMFILE: "Too many open files", - EMLINK: "Too many links", - EMSGSIZE: "Message too long", - EMULTIHOP: "Multihop attempted", - ENAMETOOLONG: "File name too long", - ENETDOWN: "Network interface is not configured", - ENETRESET: "Network dropped connection on reset", - ENETUNREACH: "Network is unreachable", - ENFILE: "File table overflow", - ENOBUFS: "No buffer space available", - ENODEV: "No such device", - ENOENT: "No such file or directory", - ENOEXEC: "Exec format error", - ENOLCK: "No record locks available", - ENOLINK: "The link has been severed", - ENOMEM: "Out of memory", - ENOMSG: "No message of desired type", - ENOPROTOOPT: "Protocol not available", - ENOSPC: "No space left on device", - ENOSYS: "Not implemented on wasip1", - ENOTCONN: "Socket is not connected", - ENOTDIR: "Not a directory", - ENOTEMPTY: "Directory not empty", - ENOTRECOVERABLE: "State not recoverable", - ENOTSOCK: "Socket operation on non-socket", - ENOTSUP: "Not supported", - ENOTTY: "Not a typewriter", - ENXIO: "No such device or address", - EOVERFLOW: "Value too large for defined data type", - EOWNERDEAD: "Owner died", - EPERM: "Operation not permitted", - EPIPE: "Broken pipe", - EPROTO: "Protocol error", - EPROTONOSUPPORT: "Unknown protocol", - EPROTOTYPE: "Protocol wrong type for socket", - ERANGE: "Math result not representable", - EROFS: "Read-only file system", - ESPIPE: "Illegal seek", - ESRCH: "No such process", - ESTALE: "Stale file handle", - ETIMEDOUT: "Connection timed out", - ETXTBSY: "Text file busy", - EXDEV: "Cross-device link", - ENOTCAPABLE: "Capabilities insufficient", -} +// var errorstr = [...]string{ +// E2BIG: "Argument list too long", +// EACCES: "Permission denied", +// EADDRINUSE: "Address already in use", +// EADDRNOTAVAIL: "Address not available", +// EAFNOSUPPORT: "Address family not supported by protocol family", +// EAGAIN: "Try again", +// EALREADY: "Socket already connected", +// EBADF: "Bad file number", +// EBADMSG: "Trying to read unreadable message", +// EBUSY: "Device or resource busy", +// ECANCELED: "Operation canceled.", +// ECHILD: "No child processes", +// ECONNABORTED: "Connection aborted", +// ECONNREFUSED: "Connection refused", +// ECONNRESET: "Connection reset by peer", +// EDEADLK: "Deadlock condition", +// EDESTADDRREQ: "Destination address required", +// EDOM: "Math arg out of domain of func", +// EDQUOT: "Quota exceeded", +// EEXIST: "File exists", +// EFAULT: "Bad address", +// EFBIG: "File too large", +// EHOSTUNREACH: "Host is unreachable", +// EIDRM: "Identifier removed", +// EILSEQ: "EILSEQ", +// EINPROGRESS: "Connection already in progress", +// EINTR: "Interrupted system call", +// EINVAL: "Invalid argument", +// EIO: "I/O error", +// EISCONN: "Socket is already connected", +// EISDIR: "Is a directory", +// ELOOP: "Too many symbolic links", +// EMFILE: "Too many open files", +// EMLINK: "Too many links", +// EMSGSIZE: "Message too long", +// EMULTIHOP: "Multihop attempted", +// ENAMETOOLONG: "File name too long", +// ENETDOWN: "Network interface is not configured", +// ENETRESET: "Network dropped connection on reset", +// ENETUNREACH: "Network is unreachable", +// ENFILE: "File table overflow", +// ENOBUFS: "No buffer space available", +// ENODEV: "No such device", +// ENOENT: "No such file or directory", +// ENOEXEC: "Exec format error", +// ENOLCK: "No record locks available", +// ENOLINK: "The link has been severed", +// ENOMEM: "Out of memory", +// ENOMSG: "No message of desired type", +// ENOPROTOOPT: "Protocol not available", +// ENOSPC: "No space left on device", +// ENOSYS: "Not implemented on wasip1", +// ENOTCONN: "Socket is not connected", +// ENOTDIR: "Not a directory", +// ENOTEMPTY: "Directory not empty", +// ENOTRECOVERABLE: "State not recoverable", +// ENOTSOCK: "Socket operation on non-socket", +// ENOTSUP: "Not supported", +// ENOTTY: "Not a typewriter", +// ENXIO: "No such device or address", +// EOVERFLOW: "Value too large for defined data type", +// EOWNERDEAD: "Owner died", +// EPERM: "Operation not permitted", +// EPIPE: "Broken pipe", +// EPROTO: "Protocol error", +// EPROTONOSUPPORT: "Unknown protocol", +// EPROTOTYPE: "Protocol wrong type for socket", +// ERANGE: "Math result not representable", +// EROFS: "Read-only file system", +// ESPIPE: "Illegal seek", +// ESRCH: "No such process", +// ESTALE: "Stale file handle", +// ETIMEDOUT: "Connection timed out", +// ETXTBSY: "Text file busy", +// EXDEV: "Cross-device link", +// ENOTCAPABLE: "Capabilities insufficient", +// } var mapSyscall2Errno = map[syscall.Errno]errno{ syscall.E2BIG: E2BIG,