feat: add zero-allocation D-Bus wire format, connection and tray example
- commit
- 731ad813f09f53aa5b854c41bf653ddec8076cc4
- author
- brice · · 2026-10-08 17:25 UTC
- parent
- 2b1569e
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
8 files · +2148 -7
A .github/workflows/ci.yml +29 -0
| @@ -0,0 +1,29 @@ | |||
| 1 | + | name: CI | |
| 2 | + | on: | |
| 3 | + | push: | |
| 4 | + | branches: [main] | |
| 5 | + | pull_request: | |
| 6 | + | permissions: | |
| 7 | + | contents: read | |
| 8 | + | concurrency: | |
| 9 | + | group: ci-${{ github.ref }} | |
| 10 | + | cancel-in-progress: true | |
| 11 | + | jobs: | |
| 12 | + | check: | |
| 13 | + | runs-on: ubuntu-latest | |
| 14 | + | steps: | |
| 15 | + | - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| 16 | + | with: | |
| 17 | + | persist-credentials: false | |
| 18 | + | - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7 | |
| 19 | + | with: | |
| 20 | + | go-version-file: go.mod | |
| 21 | + | # dbus-daemon runs the integration tests against a private bus. | |
| 22 | + | - run: sudo apt-get update && sudo apt-get install -y --no-install-recommends dbus | |
| 23 | + | - run: test -z "$(gofmt -l .)" | |
| 24 | + | - run: go vet ./... | |
| 25 | + | - run: go test -race -count=1 ./... | |
| 26 | + | - name: Fuzz smoke | |
| 27 | + | run: | | |
| 28 | + | go test -run '^$' -fuzz '^FuzzParse$' -fuzztime 20s . | |
| 29 | + | go test -run '^$' -fuzz '^FuzzSkip$' -fuzztime 20s . | |
| @@ -0,0 +1,29 @@ | |||||
| 1 | + | name: CI | |||
| 2 | + | on: | |||
| 3 | + | push: | |||
| 4 | + | branches: [main] | |||
| 5 | + | pull_request: | |||
| 6 | + | permissions: | |||
| 7 | + | contents: read | |||
| 8 | + | concurrency: | |||
| 9 | + | group: ci-${{ github.ref }} | |||
| 10 | + | cancel-in-progress: true | |||
| 11 | + | jobs: | |||
| 12 | + | check: | |||
| 13 | + | runs-on: ubuntu-latest | |||
| 14 | + | steps: | |||
| 15 | + | - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |||
| 16 | + | with: | |||
| 17 | + | persist-credentials: false | |||
| 18 | + | - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7 | |||
| 19 | + | with: | |||
| 20 | + | go-version-file: go.mod | |||
| 21 | + | # dbus-daemon runs the integration tests against a private bus. | |||
| 22 | + | - run: sudo apt-get update && sudo apt-get install -y --no-install-recommends dbus | |||
| 23 | + | - run: test -z "$(gofmt -l .)" | |||
| 24 | + | - run: go vet ./... | |||
| 25 | + | - run: go test -race -count=1 ./... | |||
| 26 | + | - name: Fuzz smoke | |||
| 27 | + | run: | | |||
| 28 | + | go test -run '^$' -fuzz '^FuzzParse$' -fuzztime 20s . | |||
| 29 | + | go test -run '^$' -fuzz '^FuzzSkip$' -fuzztime 20s . | |||
M README.md +105 -7
| @@ -2,17 +2,115 @@ | |||
| 2 | 2 | ||
| 3 | 3 | A small D-Bus client for Go that does not allocate once it is running. | |
| 4 | 4 | ||
| 5 | - | **Status: early development.** The API is not usable yet and will change. | |
| 5 | + | ```sh | |
| 6 | + | go get github.com/bnema/zerobus | |
| 7 | + | ``` | |
| 6 | 8 | ||
| 7 | - | ## Goals | |
| 9 | + | - **Zero allocations per message.** After the first few messages warm up its buffers, a call, a reply or a signal costs no heap allocation. | |
| 10 | + | - **Zero-copy reads.** Strings and byte arrays point straight into the receive buffer. | |
| 11 | + | - **Standard library only.** | |
| 12 | + | - **Small.** It connects over a Unix socket, authenticates with `EXTERNAL`, calls methods, emits signals, answers calls, and adds match rules. Nothing else. | |
| 8 | 13 | ||
| 9 | - | - **Zero allocations in steady state.** Reading signals and making calls reuses buffers; decoded strings and arrays point into the receive buffer. | |
| 10 | - | - **Only the standard library** (plus `golang.org/x/sys` if needed). | |
| 11 | - | - **A small client.** It connects to the session or system bus over a Unix socket, authenticates with `EXTERNAL`, calls methods, reads properties, matches signals and exports simple objects. | |
| 14 | + | **Status: v0.x.** The API may change between minor versions. | |
| 12 | 15 | ||
| 13 | - | ## Not a goal | |
| 16 | + | ## Call a method | |
| 14 | 17 | ||
| 15 | - | - A full D-Bus implementation: no TCP transports, no reflection-based marshalling, no code generation. | |
| 18 | + | ```go | |
| 19 | + | c, err := zerobus.SessionBus() | |
| 20 | + | if err != nil { | |
| 21 | + | log.Fatal(err) | |
| 22 | + | } | |
| 23 | + | defer c.Close() | |
| 24 | + | ||
| 25 | + | e := c.NewCall("org.freedesktop.DBus", "/org/freedesktop/DBus", | |
| 26 | + | "org.freedesktop.DBus", "GetNameOwner", "s") | |
| 27 | + | e.Str("org.freedesktop.Notifications") | |
| 28 | + | ||
| 29 | + | m, err := c.Call() | |
| 30 | + | if err != nil { | |
| 31 | + | log.Fatal(err) // a D-Bus error reply is a *zerobus.Error | |
| 32 | + | } | |
| 33 | + | owner := m.Body().Str() | |
| 34 | + | ``` | |
| 35 | + | ||
| 36 | + | Build a message with `NewCall`, `NewSignal`, `NewReply` or `NewError`, write its body in the order of its signature, then send it with `Call` (wait for the reply) or `Send` (do not wait). `Send` checks that the body matches the signature and refuses invalid names and strings before they reach the bus. | |
| 37 | + | ||
| 38 | + | ## Read values | |
| 39 | + | ||
| 40 | + | Read values in the order of the signature. Errors are sticky: check `Err()` once at the end. | |
| 41 | + | ||
| 42 | + | ```go | |
| 43 | + | r := m.Body() // signature "a{sv}" | |
| 44 | + | end := r.Array('{') | |
| 45 | + | for r.More(end) { | |
| 46 | + | r.Struct() | |
| 47 | + | key := r.Str() | |
| 48 | + | switch sig := r.Variant(); sig { | |
| 49 | + | case "s": | |
| 50 | + | fmt.Println(key, r.Str()) | |
| 51 | + | default: | |
| 52 | + | r.Skip(sig) // skip values you do not need | |
| 53 | + | } | |
| 54 | + | } | |
| 55 | + | if err := r.Err(); err != nil { | |
| 56 | + | return err | |
| 57 | + | } | |
| 58 | + | ``` | |
| 59 | + | ||
| 60 | + | **A received message is valid only until the next `ReadMessage` or `Call`.** The strings and byte slices it returns point into the receive buffer and are overwritten by the next message. Copy what you keep: | |
| 61 | + | ||
| 62 | + | ```go | |
| 63 | + | name := strings.Clone(r.Str()) | |
| 64 | + | icon := bytes.Clone(r.ByteArray()) | |
| 65 | + | ``` | |
| 66 | + | ||
| 67 | + | ## Receive signals | |
| 68 | + | ||
| 69 | + | ```go | |
| 70 | + | c.AddMatch("type='signal',interface='org.kde.StatusNotifierItem'") | |
| 71 | + | for { | |
| 72 | + | m, err := c.ReadMessage() | |
| 73 | + | if err != nil { | |
| 74 | + | return err | |
| 75 | + | } | |
| 76 | + | if m.Type == zerobus.TypeSignal { | |
| 77 | + | fmt.Println(m.Sender, m.Member) | |
| 78 | + | } | |
| 79 | + | } | |
| 80 | + | ``` | |
| 81 | + | ||
| 82 | + | `Call` reads messages until its reply arrives. Set `Conn.Handle` to receive the signals that arrive meanwhile; they are dropped otherwise. | |
| 83 | + | ||
| 84 | + | ## Answer calls | |
| 85 | + | ||
| 86 | + | ```go | |
| 87 | + | c.RequestName("org.example.Echo", zerobus.NameFlagDoNotQueue) | |
| 88 | + | for { | |
| 89 | + | m, err := c.ReadMessage() | |
| 90 | + | if err != nil { | |
| 91 | + | return err | |
| 92 | + | } | |
| 93 | + | if m.Type == zerobus.TypeMethodCall && m.Member == "Echo" { | |
| 94 | + | c.NewReply(m, "s").Str(m.Body().Str()) | |
| 95 | + | c.Send() | |
| 96 | + | } | |
| 97 | + | } | |
| 98 | + | ``` | |
| 99 | + | ||
| 100 | + | ## Concurrency | |
| 101 | + | ||
| 102 | + | A `Conn` is not safe for concurrent use. Use it from one goroutine, usually an event loop around `ReadMessage`. `Close` may be called from any goroutine: a blocked `ReadMessage` then returns `ErrClosed`. | |
| 103 | + | ||
| 104 | + | ## Not supported | |
| 105 | + | ||
| 106 | + | - Transports other than `unix:path` and `unix:abstract` (no TCP, no `launchd`, no `autolaunch`). | |
| 107 | + | - Passing file descriptors (`UNIX_FD` values can be read, but no descriptors are received). | |
| 108 | + | - Authentication other than `EXTERNAL`. | |
| 109 | + | - Reflection-based marshalling, introspection and code generation. | |
| 110 | + | ||
| 111 | + | ## Example | |
| 112 | + | ||
| 113 | + | [`examples/trayitems`](examples/trayitems) lists the system tray items on the session bus and prints their changes. | |
| 16 | 114 | ||
| 17 | 115 | ## License | |
| 18 | 116 | ||
| @@ -2,17 +2,115 @@ | |||||
| 2 | 2 | ||||
| 3 | A small D-Bus client for Go that does not allocate once it is running. | 3 | A small D-Bus client for Go that does not allocate once it is running. | ||
| 4 | 4 | ||||
| 5 | - | **Status: early development.** The API is not usable yet and will change. | 5 | + | ```sh |
| 6 | + | go get github.com/bnema/zerobus | |||
| 7 | + | ``` | |||
| 6 | 8 | ||||
| 7 | - | ## Goals | 9 | + | - **Zero allocations per message.** After the first few messages warm up its buffers, a call, a reply or a signal costs no heap allocation. |
| 10 | + | - **Zero-copy reads.** Strings and byte arrays point straight into the receive buffer. | |||
| 11 | + | - **Standard library only.** | |||
| 12 | + | - **Small.** It connects over a Unix socket, authenticates with `EXTERNAL`, calls methods, emits signals, answers calls, and adds match rules. Nothing else. | |||
| 8 | 13 | ||||
| 9 | - | - **Zero allocations in steady state.** Reading signals and making calls reuses buffers; decoded strings and arrays point into the receive buffer. | 14 | + | **Status: v0.x.** The API may change between minor versions. |
| 10 | - | - **Only the standard library** (plus `golang.org/x/sys` if needed). | |||
| 11 | - | - **A small client.** It connects to the session or system bus over a Unix socket, authenticates with `EXTERNAL`, calls methods, reads properties, matches signals and exports simple objects. | |||
| 12 | 15 | ||||
| 13 | - | ## Not a goal | 16 | + | ## Call a method |
| 14 | 17 | ||||
| 15 | - | - A full D-Bus implementation: no TCP transports, no reflection-based marshalling, no code generation. | 18 | + | ```go |
| 19 | + | c, err := zerobus.SessionBus() | |||
| 20 | + | if err != nil { | |||
| 21 | + | log.Fatal(err) | |||
| 22 | + | } | |||
| 23 | + | defer c.Close() | |||
| 24 | + | ||||
| 25 | + | e := c.NewCall("org.freedesktop.DBus", "/org/freedesktop/DBus", | |||
| 26 | + | "org.freedesktop.DBus", "GetNameOwner", "s") | |||
| 27 | + | e.Str("org.freedesktop.Notifications") | |||
| 28 | + | ||||
| 29 | + | m, err := c.Call() | |||
| 30 | + | if err != nil { | |||
| 31 | + | log.Fatal(err) // a D-Bus error reply is a *zerobus.Error | |||
| 32 | + | } | |||
| 33 | + | owner := m.Body().Str() | |||
| 34 | + | ``` | |||
| 35 | + | ||||
| 36 | + | Build a message with `NewCall`, `NewSignal`, `NewReply` or `NewError`, write its body in the order of its signature, then send it with `Call` (wait for the reply) or `Send` (do not wait). `Send` checks that the body matches the signature and refuses invalid names and strings before they reach the bus. | |||
| 37 | + | ||||
| 38 | + | ## Read values | |||
| 39 | + | ||||
| 40 | + | Read values in the order of the signature. Errors are sticky: check `Err()` once at the end. | |||
| 41 | + | ||||
| 42 | + | ```go | |||
| 43 | + | r := m.Body() // signature "a{sv}" | |||
| 44 | + | end := r.Array('{') | |||
| 45 | + | for r.More(end) { | |||
| 46 | + | r.Struct() | |||
| 47 | + | key := r.Str() | |||
| 48 | + | switch sig := r.Variant(); sig { | |||
| 49 | + | case "s": | |||
| 50 | + | fmt.Println(key, r.Str()) | |||
| 51 | + | default: | |||
| 52 | + | r.Skip(sig) // skip values you do not need | |||
| 53 | + | } | |||
| 54 | + | } | |||
| 55 | + | if err := r.Err(); err != nil { | |||
| 56 | + | return err | |||
| 57 | + | } | |||
| 58 | + | ``` | |||
| 59 | + | ||||
| 60 | + | **A received message is valid only until the next `ReadMessage` or `Call`.** The strings and byte slices it returns point into the receive buffer and are overwritten by the next message. Copy what you keep: | |||
| 61 | + | ||||
| 62 | + | ```go | |||
| 63 | + | name := strings.Clone(r.Str()) | |||
| 64 | + | icon := bytes.Clone(r.ByteArray()) | |||
| 65 | + | ``` | |||
| 66 | + | ||||
| 67 | + | ## Receive signals | |||
| 68 | + | ||||
| 69 | + | ```go | |||
| 70 | + | c.AddMatch("type='signal',interface='org.kde.StatusNotifierItem'") | |||
| 71 | + | for { | |||
| 72 | + | m, err := c.ReadMessage() | |||
| 73 | + | if err != nil { | |||
| 74 | + | return err | |||
| 75 | + | } | |||
| 76 | + | if m.Type == zerobus.TypeSignal { | |||
| 77 | + | fmt.Println(m.Sender, m.Member) | |||
| 78 | + | } | |||
| 79 | + | } | |||
| 80 | + | ``` | |||
| 81 | + | ||||
| 82 | + | `Call` reads messages until its reply arrives. Set `Conn.Handle` to receive the signals that arrive meanwhile; they are dropped otherwise. | |||
| 83 | + | ||||
| 84 | + | ## Answer calls | |||
| 85 | + | ||||
| 86 | + | ```go | |||
| 87 | + | c.RequestName("org.example.Echo", zerobus.NameFlagDoNotQueue) | |||
| 88 | + | for { | |||
| 89 | + | m, err := c.ReadMessage() | |||
| 90 | + | if err != nil { | |||
| 91 | + | return err | |||
| 92 | + | } | |||
| 93 | + | if m.Type == zerobus.TypeMethodCall && m.Member == "Echo" { | |||
| 94 | + | c.NewReply(m, "s").Str(m.Body().Str()) | |||
| 95 | + | c.Send() | |||
| 96 | + | } | |||
| 97 | + | } | |||
| 98 | + | ``` | |||
| 99 | + | ||||
| 100 | + | ## Concurrency | |||
| 101 | + | ||||
| 102 | + | A `Conn` is not safe for concurrent use. Use it from one goroutine, usually an event loop around `ReadMessage`. `Close` may be called from any goroutine: a blocked `ReadMessage` then returns `ErrClosed`. | |||
| 103 | + | ||||
| 104 | + | ## Not supported | |||
| 105 | + | ||||
| 106 | + | - Transports other than `unix:path` and `unix:abstract` (no TCP, no `launchd`, no `autolaunch`). | |||
| 107 | + | - Passing file descriptors (`UNIX_FD` values can be read, but no descriptors are received). | |||
| 108 | + | - Authentication other than `EXTERNAL`. | |||
| 109 | + | - Reflection-based marshalling, introspection and code generation. | |||
| 110 | + | ||||
| 111 | + | ## Example | |||
| 112 | + | ||||
| 113 | + | [`examples/trayitems`](examples/trayitems) lists the system tray items on the session bus and prints their changes. | |||
| 16 | 114 | ||||
| 17 | ## License | 115 | ## License | ||
| 18 | 116 | ||||
A conn.go +429 -0
| @@ -0,0 +1,429 @@ | |||
| 1 | + | package zerobus | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "bytes" | |
| 5 | + | "errors" | |
| 6 | + | "fmt" | |
| 7 | + | "io" | |
| 8 | + | "net" | |
| 9 | + | "os" | |
| 10 | + | "strconv" | |
| 11 | + | "strings" | |
| 12 | + | ) | |
| 13 | + | ||
| 14 | + | const ( | |
| 15 | + | busName = "org.freedesktop.DBus" | |
| 16 | + | busPath = "/org/freedesktop/DBus" | |
| 17 | + | maxAuthLine = 512 | |
| 18 | + | defaultBufSz = 16 << 10 | |
| 19 | + | ) | |
| 20 | + | ||
| 21 | + | // ErrClosed reports a read or send on a closed or broken connection. | |
| 22 | + | var ErrClosed = errors.New("zerobus: connection closed") | |
| 23 | + | ||
| 24 | + | // Error is a D-Bus error reply. | |
| 25 | + | type Error struct { | |
| 26 | + | Name string // such as org.freedesktop.DBus.Error.ServiceUnknown | |
| 27 | + | Message string // the first string of the body, if any | |
| 28 | + | } | |
| 29 | + | ||
| 30 | + | func (e *Error) Error() string { | |
| 31 | + | if e.Message == "" { | |
| 32 | + | return e.Name | |
| 33 | + | } | |
| 34 | + | return e.Name + ": " + e.Message | |
| 35 | + | } | |
| 36 | + | ||
| 37 | + | // Conn is a connection to a message bus. | |
| 38 | + | // | |
| 39 | + | // A Conn is not safe for concurrent use: read and send from one goroutine, | |
| 40 | + | // usually an event loop around ReadMessage. Close may be called from any | |
| 41 | + | // goroutine to stop a blocked read. | |
| 42 | + | type Conn struct { | |
| 43 | + | c *net.UnixConn | |
| 44 | + | serial uint32 | |
| 45 | + | name string | |
| 46 | + | ||
| 47 | + | rbuf []byte // received bytes; rbuf[start:end] are not read yet | |
| 48 | + | start, end int | |
| 49 | + | msg Message | |
| 50 | + | enc Encoder | |
| 51 | + | err error // sticky: the connection is unusable after it | |
| 52 | + | ||
| 53 | + | // Handle receives the messages that arrive while Call waits for its | |
| 54 | + | // reply. It may be nil; those messages are then dropped. It must not | |
| 55 | + | // call Call, Send or ReadMessage. | |
| 56 | + | Handle func(*Message) | |
| 57 | + | } | |
| 58 | + | ||
| 59 | + | // SessionBus connects to the session bus named by | |
| 60 | + | // DBUS_SESSION_BUS_ADDRESS, or $XDG_RUNTIME_DIR/bus when it is not set. | |
| 61 | + | func SessionBus() (*Conn, error) { | |
| 62 | + | addr := os.Getenv("DBUS_SESSION_BUS_ADDRESS") | |
| 63 | + | if addr == "" { | |
| 64 | + | dir := os.Getenv("XDG_RUNTIME_DIR") | |
| 65 | + | if dir == "" { | |
| 66 | + | return nil, errors.New("zerobus: no session bus: DBUS_SESSION_BUS_ADDRESS and XDG_RUNTIME_DIR are not set") | |
| 67 | + | } | |
| 68 | + | addr = "unix:path=" + dir + "/bus" | |
| 69 | + | } | |
| 70 | + | return Dial(addr) | |
| 71 | + | } | |
| 72 | + | ||
| 73 | + | // SystemBus connects to the system bus named by DBUS_SYSTEM_BUS_ADDRESS, or | |
| 74 | + | // the standard socket when it is not set. | |
| 75 | + | func SystemBus() (*Conn, error) { | |
| 76 | + | addr := os.Getenv("DBUS_SYSTEM_BUS_ADDRESS") | |
| 77 | + | if addr == "" { | |
| 78 | + | addr = "unix:path=/run/dbus/system_bus_socket" | |
| 79 | + | } | |
| 80 | + | return Dial(addr) | |
| 81 | + | } | |
| 82 | + | ||
| 83 | + | // Dial connects to a bus address such as "unix:path=/run/user/1000/bus", | |
| 84 | + | // authenticates, and registers with the bus. Only unix:path and | |
| 85 | + | // unix:abstract addresses are supported; with several addresses separated by | |
| 86 | + | // ';', the first that connects is used. | |
| 87 | + | func Dial(address string) (*Conn, error) { | |
| 88 | + | var errs []error | |
| 89 | + | for a := range strings.SplitSeq(address, ";") { | |
| 90 | + | if a == "" { | |
| 91 | + | continue | |
| 92 | + | } | |
| 93 | + | c, err := dialOne(a) | |
| 94 | + | if err == nil { | |
| 95 | + | return c, nil | |
| 96 | + | } | |
| 97 | + | errs = append(errs, err) | |
| 98 | + | } | |
| 99 | + | if len(errs) == 0 { | |
| 100 | + | return nil, fmt.Errorf("zerobus: empty bus address") | |
| 101 | + | } | |
| 102 | + | return nil, errors.Join(errs...) | |
| 103 | + | } | |
| 104 | + | ||
| 105 | + | func dialOne(addr string) (*Conn, error) { | |
| 106 | + | transport, params, ok := strings.Cut(addr, ":") | |
| 107 | + | if !ok || transport != "unix" { | |
| 108 | + | return nil, fmt.Errorf("zerobus: unsupported address %q", addr) | |
| 109 | + | } | |
| 110 | + | var sock string | |
| 111 | + | for kv := range strings.SplitSeq(params, ",") { | |
| 112 | + | k, v, _ := strings.Cut(kv, "=") | |
| 113 | + | v, err := unescape(v) | |
| 114 | + | if err != nil { | |
| 115 | + | return nil, fmt.Errorf("zerobus: address %q: %w", addr, err) | |
| 116 | + | } | |
| 117 | + | switch k { | |
| 118 | + | case "path": | |
| 119 | + | sock = v | |
| 120 | + | case "abstract": | |
| 121 | + | sock = "@" + v | |
| 122 | + | } | |
| 123 | + | } | |
| 124 | + | if sock == "" { | |
| 125 | + | return nil, fmt.Errorf("zerobus: address %q has no path or abstract socket", addr) | |
| 126 | + | } | |
| 127 | + | uc, err := net.DialUnix("unix", nil, &net.UnixAddr{Name: sock, Net: "unix"}) | |
| 128 | + | if err != nil { | |
| 129 | + | return nil, fmt.Errorf("zerobus: %w", err) | |
| 130 | + | } | |
| 131 | + | c := &Conn{c: uc, rbuf: make([]byte, defaultBufSz)} | |
| 132 | + | c.enc.buf = make([]byte, 0, defaultBufSz) | |
| 133 | + | if err := c.auth(); err != nil { | |
| 134 | + | return nil, errors.Join(err, uc.Close()) | |
| 135 | + | } | |
| 136 | + | if err := c.hello(); err != nil { | |
| 137 | + | return nil, errors.Join(err, uc.Close()) | |
| 138 | + | } | |
| 139 | + | return c, nil | |
| 140 | + | } | |
| 141 | + | ||
| 142 | + | // unescape decodes the %xx escapes of a D-Bus address value. | |
| 143 | + | func unescape(s string) (string, error) { | |
| 144 | + | if !strings.Contains(s, "%") { | |
| 145 | + | return s, nil | |
| 146 | + | } | |
| 147 | + | var b strings.Builder | |
| 148 | + | for i := 0; i < len(s); i++ { | |
| 149 | + | if s[i] != '%' { | |
| 150 | + | b.WriteByte(s[i]) | |
| 151 | + | continue | |
| 152 | + | } | |
| 153 | + | if i+2 >= len(s) { | |
| 154 | + | return "", errors.New("bad escape") | |
| 155 | + | } | |
| 156 | + | v, err := strconv.ParseUint(s[i+1:i+3], 16, 8) | |
| 157 | + | if err != nil { | |
| 158 | + | return "", errors.New("bad escape") | |
| 159 | + | } | |
| 160 | + | b.WriteByte(byte(v)) | |
| 161 | + | i += 2 | |
| 162 | + | } | |
| 163 | + | return b.String(), nil | |
| 164 | + | } | |
| 165 | + | ||
| 166 | + | // auth runs the SASL EXTERNAL handshake. The bus checks the peer | |
| 167 | + | // credentials of the socket against the user id sent here. | |
| 168 | + | func (c *Conn) auth() error { | |
| 169 | + | uid := strconv.Itoa(os.Getuid()) | |
| 170 | + | line := fmt.Appendf(nil, "\x00AUTH EXTERNAL %x\r\n", uid) | |
| 171 | + | if _, err := c.c.Write(line); err != nil { | |
| 172 | + | return fmt.Errorf("zerobus: auth: %w", err) | |
| 173 | + | } | |
| 174 | + | reply, err := c.authLine() | |
| 175 | + | if err != nil { | |
| 176 | + | return err | |
| 177 | + | } | |
| 178 | + | if !bytes.HasPrefix(reply, []byte("OK ")) { | |
| 179 | + | return fmt.Errorf("zerobus: auth rejected: %q", reply) | |
| 180 | + | } | |
| 181 | + | if _, err := c.c.Write([]byte("BEGIN\r\n")); err != nil { | |
| 182 | + | return fmt.Errorf("zerobus: auth: %w", err) | |
| 183 | + | } | |
| 184 | + | return nil | |
| 185 | + | } | |
| 186 | + | ||
| 187 | + | // authLine reads one line of the handshake. Bytes after it stay in rbuf for | |
| 188 | + | // the message reader. | |
| 189 | + | func (c *Conn) authLine() ([]byte, error) { | |
| 190 | + | for { | |
| 191 | + | if i := bytes.Index(c.rbuf[c.start:c.end], []byte("\r\n")); i >= 0 { | |
| 192 | + | line := c.rbuf[c.start : c.start+i] | |
| 193 | + | c.start += i + 2 | |
| 194 | + | return line, nil | |
| 195 | + | } | |
| 196 | + | if c.end-c.start >= maxAuthLine { | |
| 197 | + | return nil, errors.New("zerobus: auth: line too long") | |
| 198 | + | } | |
| 199 | + | n, err := c.c.Read(c.rbuf[c.end:]) | |
| 200 | + | if err != nil { | |
| 201 | + | return nil, fmt.Errorf("zerobus: auth: %w", err) | |
| 202 | + | } | |
| 203 | + | c.end += n | |
| 204 | + | } | |
| 205 | + | } | |
| 206 | + | ||
| 207 | + | func (c *Conn) hello() error { | |
| 208 | + | c.NewCall(busName, busPath, busName, "Hello", "") | |
| 209 | + | m, err := c.Call() | |
| 210 | + | if err != nil { | |
| 211 | + | return fmt.Errorf("zerobus: hello: %w", err) | |
| 212 | + | } | |
| 213 | + | body := m.Body() | |
| 214 | + | name := body.Str() | |
| 215 | + | if body.Err() != nil { | |
| 216 | + | return fmt.Errorf("zerobus: hello: %w", body.Err()) | |
| 217 | + | } | |
| 218 | + | c.name = strings.Clone(name) | |
| 219 | + | return nil | |
| 220 | + | } | |
| 221 | + | ||
| 222 | + | // UniqueName returns the name the bus gave this connection, such as ":1.42". | |
| 223 | + | func (c *Conn) UniqueName() string { return c.name } | |
| 224 | + | ||
| 225 | + | // Close closes the connection. A blocked ReadMessage returns ErrClosed. | |
| 226 | + | func (c *Conn) Close() error { return c.c.Close() } | |
| 227 | + | ||
| 228 | + | func (c *Conn) nextSerial() uint32 { | |
| 229 | + | c.serial++ | |
| 230 | + | if c.serial == 0 { | |
| 231 | + | c.serial = 1 | |
| 232 | + | } | |
| 233 | + | return c.serial | |
| 234 | + | } | |
| 235 | + | ||
| 236 | + | // NewCall starts a method call and returns the encoder for its body, which | |
| 237 | + | // must match sig. Send it with Send or Call. Empty dest and iface are left | |
| 238 | + | // out of the header. | |
| 239 | + | // | |
| 240 | + | // The connection has one encoder: send a message before starting another. | |
| 241 | + | func (c *Conn) NewCall(dest, path, iface, member, sig string) *Encoder { | |
| 242 | + | c.enc.begin(TypeMethodCall, c.nextSerial(), 0, dest, path, iface, member, "", sig) | |
| 243 | + | return &c.enc | |
| 244 | + | } | |
| 245 | + | ||
| 246 | + | // NewSignal starts a signal and returns the encoder for its body. | |
| 247 | + | func (c *Conn) NewSignal(path, iface, member, sig string) *Encoder { | |
| 248 | + | c.enc.begin(TypeSignal, c.nextSerial(), 0, "", path, iface, member, "", sig) | |
| 249 | + | return &c.enc | |
| 250 | + | } | |
| 251 | + | ||
| 252 | + | // NewReply starts the reply to the method call m and returns the encoder for | |
| 253 | + | // its body. | |
| 254 | + | func (c *Conn) NewReply(m *Message, sig string) *Encoder { | |
| 255 | + | c.enc.begin(TypeMethodReturn, c.nextSerial(), m.Serial, m.Sender, "", "", "", "", sig) | |
| 256 | + | return &c.enc | |
| 257 | + | } | |
| 258 | + | ||
| 259 | + | // NewError starts an error reply to the method call m. Write a message | |
| 260 | + | // string as the body when sig is "s". | |
| 261 | + | func (c *Conn) NewError(m *Message, name, sig string) *Encoder { | |
| 262 | + | c.enc.begin(TypeError, c.nextSerial(), m.Serial, m.Sender, "", "", "", name, sig) | |
| 263 | + | return &c.enc | |
| 264 | + | } | |
| 265 | + | ||
| 266 | + | // Send sends the message built since the last NewCall, NewSignal, NewReply | |
| 267 | + | // or NewError and returns its serial. | |
| 268 | + | func (c *Conn) Send() (uint32, error) { | |
| 269 | + | if c.err != nil { | |
| 270 | + | return 0, c.err | |
| 271 | + | } | |
| 272 | + | b, err := c.enc.finish() | |
| 273 | + | c.enc.body = 0 | |
| 274 | + | if err != nil { | |
| 275 | + | return 0, err | |
| 276 | + | } | |
| 277 | + | if _, err := c.c.Write(b); err != nil { | |
| 278 | + | c.err = closedErr(err) | |
| 279 | + | return 0, c.err | |
| 280 | + | } | |
| 281 | + | return readSerial(b), nil | |
| 282 | + | } | |
| 283 | + | ||
| 284 | + | func readSerial(b []byte) uint32 { | |
| 285 | + | return uint32(b[8]) | uint32(b[9])<<8 | uint32(b[10])<<16 | uint32(b[11])<<24 | |
| 286 | + | } | |
| 287 | + | ||
| 288 | + | // Call sends the method call being built and waits for its reply. Messages | |
| 289 | + | // that arrive meanwhile go to Handle. An error reply returns an *Error. | |
| 290 | + | // | |
| 291 | + | // The reply is valid until the next read, like ReadMessage's. | |
| 292 | + | func (c *Conn) Call() (*Message, error) { | |
| 293 | + | serial, err := c.Send() | |
| 294 | + | if err != nil { | |
| 295 | + | return nil, err | |
| 296 | + | } | |
| 297 | + | for { | |
| 298 | + | m, err := c.ReadMessage() | |
| 299 | + | if err != nil { | |
| 300 | + | return nil, err | |
| 301 | + | } | |
| 302 | + | if m.ReplySerial == serial && (m.Type == TypeMethodReturn || m.Type == TypeError) { | |
| 303 | + | if m.Type == TypeError { | |
| 304 | + | return m, replyError(m) | |
| 305 | + | } | |
| 306 | + | return m, nil | |
| 307 | + | } | |
| 308 | + | if c.Handle != nil { | |
| 309 | + | c.Handle(m) | |
| 310 | + | } | |
| 311 | + | } | |
| 312 | + | } | |
| 313 | + | ||
| 314 | + | // replyError builds an *Error from an error reply. It copies the strings so | |
| 315 | + | // the error stays valid; this is the only allocation of an error path. | |
| 316 | + | func replyError(m *Message) *Error { | |
| 317 | + | e := &Error{Name: strings.Clone(m.ErrorName)} | |
| 318 | + | if len(m.Signature) > 0 && m.Signature[0] == 's' { | |
| 319 | + | if s := m.Body().Str(); s != "" { | |
| 320 | + | e.Message = strings.Clone(s) | |
| 321 | + | } | |
| 322 | + | } | |
| 323 | + | return e | |
| 324 | + | } | |
| 325 | + | ||
| 326 | + | // ReadMessage blocks until the next message arrives. The message and its | |
| 327 | + | // strings are valid until the next call to ReadMessage or Call. | |
| 328 | + | func (c *Conn) ReadMessage() (*Message, error) { | |
| 329 | + | if c.err != nil { | |
| 330 | + | return nil, c.err | |
| 331 | + | } | |
| 332 | + | if err := c.fill(headerLen); err != nil { | |
| 333 | + | return nil, err | |
| 334 | + | } | |
| 335 | + | n, err := frameLen(c.rbuf[c.start:]) | |
| 336 | + | if err != nil { | |
| 337 | + | c.err = err | |
| 338 | + | return nil, err | |
| 339 | + | } | |
| 340 | + | if err := c.fill(n); err != nil { | |
| 341 | + | return nil, err | |
| 342 | + | } | |
| 343 | + | b := c.rbuf[c.start : c.start+n : c.start+n] | |
| 344 | + | c.start += n | |
| 345 | + | if err := parse(b, &c.msg); err != nil { | |
| 346 | + | c.err = err | |
| 347 | + | return nil, err | |
| 348 | + | } | |
| 349 | + | return &c.msg, nil | |
| 350 | + | } | |
| 351 | + | ||
| 352 | + | // fill reads until rbuf[start:] holds at least n bytes. It moves unread | |
| 353 | + | // bytes to the front, which is safe because the previous message is no | |
| 354 | + | // longer valid once a new read starts. | |
| 355 | + | func (c *Conn) fill(n int) error { | |
| 356 | + | if c.end-c.start >= n { | |
| 357 | + | return nil | |
| 358 | + | } | |
| 359 | + | if c.start > 0 { | |
| 360 | + | c.end = copy(c.rbuf, c.rbuf[c.start:c.end]) | |
| 361 | + | c.start = 0 | |
| 362 | + | } | |
| 363 | + | if n > len(c.rbuf) { | |
| 364 | + | grown := make([]byte, max(n, 2*len(c.rbuf))) | |
| 365 | + | copy(grown, c.rbuf[:c.end]) | |
| 366 | + | c.rbuf = grown | |
| 367 | + | } | |
| 368 | + | for c.end < n { | |
| 369 | + | k, err := c.c.Read(c.rbuf[c.end:]) | |
| 370 | + | c.end += k | |
| 371 | + | if err != nil && c.end < n { | |
| 372 | + | c.err = closedErr(err) | |
| 373 | + | return c.err | |
| 374 | + | } | |
| 375 | + | } | |
| 376 | + | return nil | |
| 377 | + | } | |
| 378 | + | ||
| 379 | + | func closedErr(err error) error { | |
| 380 | + | if errors.Is(err, io.EOF) || errors.Is(err, net.ErrClosed) { | |
| 381 | + | return ErrClosed | |
| 382 | + | } | |
| 383 | + | return fmt.Errorf("zerobus: %w", err) | |
| 384 | + | } | |
| 385 | + | ||
| 386 | + | // AddMatch asks the bus to send this connection the messages that match | |
| 387 | + | // rule, such as "type='signal',interface='org.kde.StatusNotifierItem'". | |
| 388 | + | func (c *Conn) AddMatch(rule string) error { | |
| 389 | + | c.NewCall(busName, busPath, busName, "AddMatch", "s").Str(rule) | |
| 390 | + | _, err := c.Call() | |
| 391 | + | return err | |
| 392 | + | } | |
| 393 | + | ||
| 394 | + | // RemoveMatch removes a rule added by AddMatch. | |
| 395 | + | func (c *Conn) RemoveMatch(rule string) error { | |
| 396 | + | c.NewCall(busName, busPath, busName, "RemoveMatch", "s").Str(rule) | |
| 397 | + | _, err := c.Call() | |
| 398 | + | return err | |
| 399 | + | } | |
| 400 | + | ||
| 401 | + | // Reply codes of RequestName. | |
| 402 | + | const ( | |
| 403 | + | NamePrimaryOwner = 1 | |
| 404 | + | NameInQueue = 2 | |
| 405 | + | NameExists = 3 | |
| 406 | + | NameAlreadyOwner = 4 | |
| 407 | + | ) | |
| 408 | + | ||
| 409 | + | // Flags of RequestName. | |
| 410 | + | const ( | |
| 411 | + | NameFlagAllowReplacement = 0x1 | |
| 412 | + | NameFlagReplaceExisting = 0x2 | |
| 413 | + | NameFlagDoNotQueue = 0x4 | |
| 414 | + | ) | |
| 415 | + | ||
| 416 | + | // RequestName asks the bus for a well-known name and returns its reply code, | |
| 417 | + | // such as NamePrimaryOwner. | |
| 418 | + | func (c *Conn) RequestName(name string, flags uint32) (uint32, error) { | |
| 419 | + | e := c.NewCall(busName, busPath, busName, "RequestName", "su") | |
| 420 | + | e.Str(name) | |
| 421 | + | e.Uint32(flags) | |
| 422 | + | m, err := c.Call() | |
| 423 | + | if err != nil { | |
| 424 | + | return 0, err | |
| 425 | + | } | |
| 426 | + | body := m.Body() | |
| 427 | + | code := body.Uint32() | |
| 428 | + | return code, body.Err() | |
| 429 | + | } | |
| @@ -0,0 +1,429 @@ | |||||
| 1 | + | package zerobus | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "bytes" | |||
| 5 | + | "errors" | |||
| 6 | + | "fmt" | |||
| 7 | + | "io" | |||
| 8 | + | "net" | |||
| 9 | + | "os" | |||
| 10 | + | "strconv" | |||
| 11 | + | "strings" | |||
| 12 | + | ) | |||
| 13 | + | ||||
| 14 | + | const ( | |||
| 15 | + | busName = "org.freedesktop.DBus" | |||
| 16 | + | busPath = "/org/freedesktop/DBus" | |||
| 17 | + | maxAuthLine = 512 | |||
| 18 | + | defaultBufSz = 16 << 10 | |||
| 19 | + | ) | |||
| 20 | + | ||||
| 21 | + | // ErrClosed reports a read or send on a closed or broken connection. | |||
| 22 | + | var ErrClosed = errors.New("zerobus: connection closed") | |||
| 23 | + | ||||
| 24 | + | // Error is a D-Bus error reply. | |||
| 25 | + | type Error struct { | |||
| 26 | + | Name string // such as org.freedesktop.DBus.Error.ServiceUnknown | |||
| 27 | + | Message string // the first string of the body, if any | |||
| 28 | + | } | |||
| 29 | + | ||||
| 30 | + | func (e *Error) Error() string { | |||
| 31 | + | if e.Message == "" { | |||
| 32 | + | return e.Name | |||
| 33 | + | } | |||
| 34 | + | return e.Name + ": " + e.Message | |||
| 35 | + | } | |||
| 36 | + | ||||
| 37 | + | // Conn is a connection to a message bus. | |||
| 38 | + | // | |||
| 39 | + | // A Conn is not safe for concurrent use: read and send from one goroutine, | |||
| 40 | + | // usually an event loop around ReadMessage. Close may be called from any | |||
| 41 | + | // goroutine to stop a blocked read. | |||
| 42 | + | type Conn struct { | |||
| 43 | + | c *net.UnixConn | |||
| 44 | + | serial uint32 | |||
| 45 | + | name string | |||
| 46 | + | ||||
| 47 | + | rbuf []byte // received bytes; rbuf[start:end] are not read yet | |||
| 48 | + | start, end int | |||
| 49 | + | msg Message | |||
| 50 | + | enc Encoder | |||
| 51 | + | err error // sticky: the connection is unusable after it | |||
| 52 | + | ||||
| 53 | + | // Handle receives the messages that arrive while Call waits for its | |||
| 54 | + | // reply. It may be nil; those messages are then dropped. It must not | |||
| 55 | + | // call Call, Send or ReadMessage. | |||
| 56 | + | Handle func(*Message) | |||
| 57 | + | } | |||
| 58 | + | ||||
| 59 | + | // SessionBus connects to the session bus named by | |||
| 60 | + | // DBUS_SESSION_BUS_ADDRESS, or $XDG_RUNTIME_DIR/bus when it is not set. | |||
| 61 | + | func SessionBus() (*Conn, error) { | |||
| 62 | + | addr := os.Getenv("DBUS_SESSION_BUS_ADDRESS") | |||
| 63 | + | if addr == "" { | |||
| 64 | + | dir := os.Getenv("XDG_RUNTIME_DIR") | |||
| 65 | + | if dir == "" { | |||
| 66 | + | return nil, errors.New("zerobus: no session bus: DBUS_SESSION_BUS_ADDRESS and XDG_RUNTIME_DIR are not set") | |||
| 67 | + | } | |||
| 68 | + | addr = "unix:path=" + dir + "/bus" | |||
| 69 | + | } | |||
| 70 | + | return Dial(addr) | |||
| 71 | + | } | |||
| 72 | + | ||||
| 73 | + | // SystemBus connects to the system bus named by DBUS_SYSTEM_BUS_ADDRESS, or | |||
| 74 | + | // the standard socket when it is not set. | |||
| 75 | + | func SystemBus() (*Conn, error) { | |||
| 76 | + | addr := os.Getenv("DBUS_SYSTEM_BUS_ADDRESS") | |||
| 77 | + | if addr == "" { | |||
| 78 | + | addr = "unix:path=/run/dbus/system_bus_socket" | |||
| 79 | + | } | |||
| 80 | + | return Dial(addr) | |||
| 81 | + | } | |||
| 82 | + | ||||
| 83 | + | // Dial connects to a bus address such as "unix:path=/run/user/1000/bus", | |||
| 84 | + | // authenticates, and registers with the bus. Only unix:path and | |||
| 85 | + | // unix:abstract addresses are supported; with several addresses separated by | |||
| 86 | + | // ';', the first that connects is used. | |||
| 87 | + | func Dial(address string) (*Conn, error) { | |||
| 88 | + | var errs []error | |||
| 89 | + | for a := range strings.SplitSeq(address, ";") { | |||
| 90 | + | if a == "" { | |||
| 91 | + | continue | |||
| 92 | + | } | |||
| 93 | + | c, err := dialOne(a) | |||
| 94 | + | if err == nil { | |||
| 95 | + | return c, nil | |||
| 96 | + | } | |||
| 97 | + | errs = append(errs, err) | |||
| 98 | + | } | |||
| 99 | + | if len(errs) == 0 { | |||
| 100 | + | return nil, fmt.Errorf("zerobus: empty bus address") | |||
| 101 | + | } | |||
| 102 | + | return nil, errors.Join(errs...) | |||
| 103 | + | } | |||
| 104 | + | ||||
| 105 | + | func dialOne(addr string) (*Conn, error) { | |||
| 106 | + | transport, params, ok := strings.Cut(addr, ":") | |||
| 107 | + | if !ok || transport != "unix" { | |||
| 108 | + | return nil, fmt.Errorf("zerobus: unsupported address %q", addr) | |||
| 109 | + | } | |||
| 110 | + | var sock string | |||
| 111 | + | for kv := range strings.SplitSeq(params, ",") { | |||
| 112 | + | k, v, _ := strings.Cut(kv, "=") | |||
| 113 | + | v, err := unescape(v) | |||
| 114 | + | if err != nil { | |||
| 115 | + | return nil, fmt.Errorf("zerobus: address %q: %w", addr, err) | |||
| 116 | + | } | |||
| 117 | + | switch k { | |||
| 118 | + | case "path": | |||
| 119 | + | sock = v | |||
| 120 | + | case "abstract": | |||
| 121 | + | sock = "@" + v | |||
| 122 | + | } | |||
| 123 | + | } | |||
| 124 | + | if sock == "" { | |||
| 125 | + | return nil, fmt.Errorf("zerobus: address %q has no path or abstract socket", addr) | |||
| 126 | + | } | |||
| 127 | + | uc, err := net.DialUnix("unix", nil, &net.UnixAddr{Name: sock, Net: "unix"}) | |||
| 128 | + | if err != nil { | |||
| 129 | + | return nil, fmt.Errorf("zerobus: %w", err) | |||
| 130 | + | } | |||
| 131 | + | c := &Conn{c: uc, rbuf: make([]byte, defaultBufSz)} | |||
| 132 | + | c.enc.buf = make([]byte, 0, defaultBufSz) | |||
| 133 | + | if err := c.auth(); err != nil { | |||
| 134 | + | return nil, errors.Join(err, uc.Close()) | |||
| 135 | + | } | |||
| 136 | + | if err := c.hello(); err != nil { | |||
| 137 | + | return nil, errors.Join(err, uc.Close()) | |||
| 138 | + | } | |||
| 139 | + | return c, nil | |||
| 140 | + | } | |||
| 141 | + | ||||
| 142 | + | // unescape decodes the %xx escapes of a D-Bus address value. | |||
| 143 | + | func unescape(s string) (string, error) { | |||
| 144 | + | if !strings.Contains(s, "%") { | |||
| 145 | + | return s, nil | |||
| 146 | + | } | |||
| 147 | + | var b strings.Builder | |||
| 148 | + | for i := 0; i < len(s); i++ { | |||
| 149 | + | if s[i] != '%' { | |||
| 150 | + | b.WriteByte(s[i]) | |||
| 151 | + | continue | |||
| 152 | + | } | |||
| 153 | + | if i+2 >= len(s) { | |||
| 154 | + | return "", errors.New("bad escape") | |||
| 155 | + | } | |||
| 156 | + | v, err := strconv.ParseUint(s[i+1:i+3], 16, 8) | |||
| 157 | + | if err != nil { | |||
| 158 | + | return "", errors.New("bad escape") | |||
| 159 | + | } | |||
| 160 | + | b.WriteByte(byte(v)) | |||
| 161 | + | i += 2 | |||
| 162 | + | } | |||
| 163 | + | return b.String(), nil | |||
| 164 | + | } | |||
| 165 | + | ||||
| 166 | + | // auth runs the SASL EXTERNAL handshake. The bus checks the peer | |||
| 167 | + | // credentials of the socket against the user id sent here. | |||
| 168 | + | func (c *Conn) auth() error { | |||
| 169 | + | uid := strconv.Itoa(os.Getuid()) | |||
| 170 | + | line := fmt.Appendf(nil, "\x00AUTH EXTERNAL %x\r\n", uid) | |||
| 171 | + | if _, err := c.c.Write(line); err != nil { | |||
| 172 | + | return fmt.Errorf("zerobus: auth: %w", err) | |||
| 173 | + | } | |||
| 174 | + | reply, err := c.authLine() | |||
| 175 | + | if err != nil { | |||
| 176 | + | return err | |||
| 177 | + | } | |||
| 178 | + | if !bytes.HasPrefix(reply, []byte("OK ")) { | |||
| 179 | + | return fmt.Errorf("zerobus: auth rejected: %q", reply) | |||
| 180 | + | } | |||
| 181 | + | if _, err := c.c.Write([]byte("BEGIN\r\n")); err != nil { | |||
| 182 | + | return fmt.Errorf("zerobus: auth: %w", err) | |||
| 183 | + | } | |||
| 184 | + | return nil | |||
| 185 | + | } | |||
| 186 | + | ||||
| 187 | + | // authLine reads one line of the handshake. Bytes after it stay in rbuf for | |||
| 188 | + | // the message reader. | |||
| 189 | + | func (c *Conn) authLine() ([]byte, error) { | |||
| 190 | + | for { | |||
| 191 | + | if i := bytes.Index(c.rbuf[c.start:c.end], []byte("\r\n")); i >= 0 { | |||
| 192 | + | line := c.rbuf[c.start : c.start+i] | |||
| 193 | + | c.start += i + 2 | |||
| 194 | + | return line, nil | |||
| 195 | + | } | |||
| 196 | + | if c.end-c.start >= maxAuthLine { | |||
| 197 | + | return nil, errors.New("zerobus: auth: line too long") | |||
| 198 | + | } | |||
| 199 | + | n, err := c.c.Read(c.rbuf[c.end:]) | |||
| 200 | + | if err != nil { | |||
| 201 | + | return nil, fmt.Errorf("zerobus: auth: %w", err) | |||
| 202 | + | } | |||
| 203 | + | c.end += n | |||
| 204 | + | } | |||
| 205 | + | } | |||
| 206 | + | ||||
| 207 | + | func (c *Conn) hello() error { | |||
| 208 | + | c.NewCall(busName, busPath, busName, "Hello", "") | |||
| 209 | + | m, err := c.Call() | |||
| 210 | + | if err != nil { | |||
| 211 | + | return fmt.Errorf("zerobus: hello: %w", err) | |||
| 212 | + | } | |||
| 213 | + | body := m.Body() | |||
| 214 | + | name := body.Str() | |||
| 215 | + | if body.Err() != nil { | |||
| 216 | + | return fmt.Errorf("zerobus: hello: %w", body.Err()) | |||
| 217 | + | } | |||
| 218 | + | c.name = strings.Clone(name) | |||
| 219 | + | return nil | |||
| 220 | + | } | |||
| 221 | + | ||||
| 222 | + | // UniqueName returns the name the bus gave this connection, such as ":1.42". | |||
| 223 | + | func (c *Conn) UniqueName() string { return c.name } | |||
| 224 | + | ||||
| 225 | + | // Close closes the connection. A blocked ReadMessage returns ErrClosed. | |||
| 226 | + | func (c *Conn) Close() error { return c.c.Close() } | |||
| 227 | + | ||||
| 228 | + | func (c *Conn) nextSerial() uint32 { | |||
| 229 | + | c.serial++ | |||
| 230 | + | if c.serial == 0 { | |||
| 231 | + | c.serial = 1 | |||
| 232 | + | } | |||
| 233 | + | return c.serial | |||
| 234 | + | } | |||
| 235 | + | ||||
| 236 | + | // NewCall starts a method call and returns the encoder for its body, which | |||
| 237 | + | // must match sig. Send it with Send or Call. Empty dest and iface are left | |||
| 238 | + | // out of the header. | |||
| 239 | + | // | |||
| 240 | + | // The connection has one encoder: send a message before starting another. | |||
| 241 | + | func (c *Conn) NewCall(dest, path, iface, member, sig string) *Encoder { | |||
| 242 | + | c.enc.begin(TypeMethodCall, c.nextSerial(), 0, dest, path, iface, member, "", sig) | |||
| 243 | + | return &c.enc | |||
| 244 | + | } | |||
| 245 | + | ||||
| 246 | + | // NewSignal starts a signal and returns the encoder for its body. | |||
| 247 | + | func (c *Conn) NewSignal(path, iface, member, sig string) *Encoder { | |||
| 248 | + | c.enc.begin(TypeSignal, c.nextSerial(), 0, "", path, iface, member, "", sig) | |||
| 249 | + | return &c.enc | |||
| 250 | + | } | |||
| 251 | + | ||||
| 252 | + | // NewReply starts the reply to the method call m and returns the encoder for | |||
| 253 | + | // its body. | |||
| 254 | + | func (c *Conn) NewReply(m *Message, sig string) *Encoder { | |||
| 255 | + | c.enc.begin(TypeMethodReturn, c.nextSerial(), m.Serial, m.Sender, "", "", "", "", sig) | |||
| 256 | + | return &c.enc | |||
| 257 | + | } | |||
| 258 | + | ||||
| 259 | + | // NewError starts an error reply to the method call m. Write a message | |||
| 260 | + | // string as the body when sig is "s". | |||
| 261 | + | func (c *Conn) NewError(m *Message, name, sig string) *Encoder { | |||
| 262 | + | c.enc.begin(TypeError, c.nextSerial(), m.Serial, m.Sender, "", "", "", name, sig) | |||
| 263 | + | return &c.enc | |||
| 264 | + | } | |||
| 265 | + | ||||
| 266 | + | // Send sends the message built since the last NewCall, NewSignal, NewReply | |||
| 267 | + | // or NewError and returns its serial. | |||
| 268 | + | func (c *Conn) Send() (uint32, error) { | |||
| 269 | + | if c.err != nil { | |||
| 270 | + | return 0, c.err | |||
| 271 | + | } | |||
| 272 | + | b, err := c.enc.finish() | |||
| 273 | + | c.enc.body = 0 | |||
| 274 | + | if err != nil { | |||
| 275 | + | return 0, err | |||
| 276 | + | } | |||
| 277 | + | if _, err := c.c.Write(b); err != nil { | |||
| 278 | + | c.err = closedErr(err) | |||
| 279 | + | return 0, c.err | |||
| 280 | + | } | |||
| 281 | + | return readSerial(b), nil | |||
| 282 | + | } | |||
| 283 | + | ||||
| 284 | + | func readSerial(b []byte) uint32 { | |||
| 285 | + | return uint32(b[8]) | uint32(b[9])<<8 | uint32(b[10])<<16 | uint32(b[11])<<24 | |||
| 286 | + | } | |||
| 287 | + | ||||
| 288 | + | // Call sends the method call being built and waits for its reply. Messages | |||
| 289 | + | // that arrive meanwhile go to Handle. An error reply returns an *Error. | |||
| 290 | + | // | |||
| 291 | + | // The reply is valid until the next read, like ReadMessage's. | |||
| 292 | + | func (c *Conn) Call() (*Message, error) { | |||
| 293 | + | serial, err := c.Send() | |||
| 294 | + | if err != nil { | |||
| 295 | + | return nil, err | |||
| 296 | + | } | |||
| 297 | + | for { | |||
| 298 | + | m, err := c.ReadMessage() | |||
| 299 | + | if err != nil { | |||
| 300 | + | return nil, err | |||
| 301 | + | } | |||
| 302 | + | if m.ReplySerial == serial && (m.Type == TypeMethodReturn || m.Type == TypeError) { | |||
| 303 | + | if m.Type == TypeError { | |||
| 304 | + | return m, replyError(m) | |||
| 305 | + | } | |||
| 306 | + | return m, nil | |||
| 307 | + | } | |||
| 308 | + | if c.Handle != nil { | |||
| 309 | + | c.Handle(m) | |||
| 310 | + | } | |||
| 311 | + | } | |||
| 312 | + | } | |||
| 313 | + | ||||
| 314 | + | // replyError builds an *Error from an error reply. It copies the strings so | |||
| 315 | + | // the error stays valid; this is the only allocation of an error path. | |||
| 316 | + | func replyError(m *Message) *Error { | |||
| 317 | + | e := &Error{Name: strings.Clone(m.ErrorName)} | |||
| 318 | + | if len(m.Signature) > 0 && m.Signature[0] == 's' { | |||
| 319 | + | if s := m.Body().Str(); s != "" { | |||
| 320 | + | e.Message = strings.Clone(s) | |||
| 321 | + | } | |||
| 322 | + | } | |||
| 323 | + | return e | |||
| 324 | + | } | |||
| 325 | + | ||||
| 326 | + | // ReadMessage blocks until the next message arrives. The message and its | |||
| 327 | + | // strings are valid until the next call to ReadMessage or Call. | |||
| 328 | + | func (c *Conn) ReadMessage() (*Message, error) { | |||
| 329 | + | if c.err != nil { | |||
| 330 | + | return nil, c.err | |||
| 331 | + | } | |||
| 332 | + | if err := c.fill(headerLen); err != nil { | |||
| 333 | + | return nil, err | |||
| 334 | + | } | |||
| 335 | + | n, err := frameLen(c.rbuf[c.start:]) | |||
| 336 | + | if err != nil { | |||
| 337 | + | c.err = err | |||
| 338 | + | return nil, err | |||
| 339 | + | } | |||
| 340 | + | if err := c.fill(n); err != nil { | |||
| 341 | + | return nil, err | |||
| 342 | + | } | |||
| 343 | + | b := c.rbuf[c.start : c.start+n : c.start+n] | |||
| 344 | + | c.start += n | |||
| 345 | + | if err := parse(b, &c.msg); err != nil { | |||
| 346 | + | c.err = err | |||
| 347 | + | return nil, err | |||
| 348 | + | } | |||
| 349 | + | return &c.msg, nil | |||
| 350 | + | } | |||
| 351 | + | ||||
| 352 | + | // fill reads until rbuf[start:] holds at least n bytes. It moves unread | |||
| 353 | + | // bytes to the front, which is safe because the previous message is no | |||
| 354 | + | // longer valid once a new read starts. | |||
| 355 | + | func (c *Conn) fill(n int) error { | |||
| 356 | + | if c.end-c.start >= n { | |||
| 357 | + | return nil | |||
| 358 | + | } | |||
| 359 | + | if c.start > 0 { | |||
| 360 | + | c.end = copy(c.rbuf, c.rbuf[c.start:c.end]) | |||
| 361 | + | c.start = 0 | |||
| 362 | + | } | |||
| 363 | + | if n > len(c.rbuf) { | |||
| 364 | + | grown := make([]byte, max(n, 2*len(c.rbuf))) | |||
| 365 | + | copy(grown, c.rbuf[:c.end]) | |||
| 366 | + | c.rbuf = grown | |||
| 367 | + | } | |||
| 368 | + | for c.end < n { | |||
| 369 | + | k, err := c.c.Read(c.rbuf[c.end:]) | |||
| 370 | + | c.end += k | |||
| 371 | + | if err != nil && c.end < n { | |||
| 372 | + | c.err = closedErr(err) | |||
| 373 | + | return c.err | |||
| 374 | + | } | |||
| 375 | + | } | |||
| 376 | + | return nil | |||
| 377 | + | } | |||
| 378 | + | ||||
| 379 | + | func closedErr(err error) error { | |||
| 380 | + | if errors.Is(err, io.EOF) || errors.Is(err, net.ErrClosed) { | |||
| 381 | + | return ErrClosed | |||
| 382 | + | } | |||
| 383 | + | return fmt.Errorf("zerobus: %w", err) | |||
| 384 | + | } | |||
| 385 | + | ||||
| 386 | + | // AddMatch asks the bus to send this connection the messages that match | |||
| 387 | + | // rule, such as "type='signal',interface='org.kde.StatusNotifierItem'". | |||
| 388 | + | func (c *Conn) AddMatch(rule string) error { | |||
| 389 | + | c.NewCall(busName, busPath, busName, "AddMatch", "s").Str(rule) | |||
| 390 | + | _, err := c.Call() | |||
| 391 | + | return err | |||
| 392 | + | } | |||
| 393 | + | ||||
| 394 | + | // RemoveMatch removes a rule added by AddMatch. | |||
| 395 | + | func (c *Conn) RemoveMatch(rule string) error { | |||
| 396 | + | c.NewCall(busName, busPath, busName, "RemoveMatch", "s").Str(rule) | |||
| 397 | + | _, err := c.Call() | |||
| 398 | + | return err | |||
| 399 | + | } | |||
| 400 | + | ||||
| 401 | + | // Reply codes of RequestName. | |||
| 402 | + | const ( | |||
| 403 | + | NamePrimaryOwner = 1 | |||
| 404 | + | NameInQueue = 2 | |||
| 405 | + | NameExists = 3 | |||
| 406 | + | NameAlreadyOwner = 4 | |||
| 407 | + | ) | |||
| 408 | + | ||||
| 409 | + | // Flags of RequestName. | |||
| 410 | + | const ( | |||
| 411 | + | NameFlagAllowReplacement = 0x1 | |||
| 412 | + | NameFlagReplaceExisting = 0x2 | |||
| 413 | + | NameFlagDoNotQueue = 0x4 | |||
| 414 | + | ) | |||
| 415 | + | ||||
| 416 | + | // RequestName asks the bus for a well-known name and returns its reply code, | |||
| 417 | + | // such as NamePrimaryOwner. | |||
| 418 | + | func (c *Conn) RequestName(name string, flags uint32) (uint32, error) { | |||
| 419 | + | e := c.NewCall(busName, busPath, busName, "RequestName", "su") | |||
| 420 | + | e.Str(name) | |||
| 421 | + | e.Uint32(flags) | |||
| 422 | + | m, err := c.Call() | |||
| 423 | + | if err != nil { | |||
| 424 | + | return 0, err | |||
| 425 | + | } | |||
| 426 | + | body := m.Body() | |||
| 427 | + | code := body.Uint32() | |||
| 428 | + | return code, body.Err() | |||
| 429 | + | } | |||
A conn_test.go +249 -0
| @@ -0,0 +1,249 @@ | |||
| 1 | + | package zerobus | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "bufio" | |
| 5 | + | "errors" | |
| 6 | + | "os/exec" | |
| 7 | + | "strings" | |
| 8 | + | "testing" | |
| 9 | + | "time" | |
| 10 | + | ) | |
| 11 | + | ||
| 12 | + | // privateBus starts a dbus-daemon for the test and returns its address. The | |
| 13 | + | // test is skipped when dbus-daemon is not installed. | |
| 14 | + | func privateBus(t *testing.T) string { | |
| 15 | + | t.Helper() | |
| 16 | + | path, err := exec.LookPath("dbus-daemon") | |
| 17 | + | if err != nil { | |
| 18 | + | t.Skip("dbus-daemon not installed") | |
| 19 | + | } | |
| 20 | + | cmd := exec.Command(path, "--session", "--nofork", "--print-address") | |
| 21 | + | out, err := cmd.StdoutPipe() | |
| 22 | + | if err != nil { | |
| 23 | + | t.Fatal(err) | |
| 24 | + | } | |
| 25 | + | if err := cmd.Start(); err != nil { | |
| 26 | + | t.Fatal(err) | |
| 27 | + | } | |
| 28 | + | t.Cleanup(func() { | |
| 29 | + | _ = cmd.Process.Kill() | |
| 30 | + | _ = cmd.Wait() | |
| 31 | + | }) | |
| 32 | + | line := make(chan string, 1) | |
| 33 | + | go func() { | |
| 34 | + | s, _ := bufio.NewReader(out).ReadString('\n') | |
| 35 | + | line <- strings.TrimSpace(s) | |
| 36 | + | }() | |
| 37 | + | select { | |
| 38 | + | case addr := <-line: | |
| 39 | + | if addr == "" { | |
| 40 | + | t.Fatal("dbus-daemon printed no address") | |
| 41 | + | } | |
| 42 | + | return addr | |
| 43 | + | case <-time.After(5 * time.Second): | |
| 44 | + | t.Fatal("dbus-daemon did not start") | |
| 45 | + | } | |
| 46 | + | return "" | |
| 47 | + | } | |
| 48 | + | ||
| 49 | + | func dial(t *testing.T, addr string) *Conn { | |
| 50 | + | t.Helper() | |
| 51 | + | c, err := Dial(addr) | |
| 52 | + | if err != nil { | |
| 53 | + | t.Fatal(err) | |
| 54 | + | } | |
| 55 | + | t.Cleanup(func() { _ = c.Close() }) | |
| 56 | + | return c | |
| 57 | + | } | |
| 58 | + | ||
| 59 | + | func TestHelloAndListNames(t *testing.T) { | |
| 60 | + | c := dial(t, privateBus(t)) | |
| 61 | + | if !strings.HasPrefix(c.UniqueName(), ":") { | |
| 62 | + | t.Fatalf("unique name %q", c.UniqueName()) | |
| 63 | + | } | |
| 64 | + | c.NewCall(busName, busPath, busName, "ListNames", "") | |
| 65 | + | m, err := c.Call() | |
| 66 | + | if err != nil { | |
| 67 | + | t.Fatal(err) | |
| 68 | + | } | |
| 69 | + | r := m.Body() | |
| 70 | + | found := false | |
| 71 | + | end := r.Array('s') | |
| 72 | + | for r.More(end) { | |
| 73 | + | if r.Str() == c.UniqueName() { | |
| 74 | + | found = true | |
| 75 | + | } | |
| 76 | + | } | |
| 77 | + | if !r.Done() || !found { | |
| 78 | + | t.Fatalf("own name not listed: %v", r.Err()) | |
| 79 | + | } | |
| 80 | + | } | |
| 81 | + | ||
| 82 | + | func TestErrorReply(t *testing.T) { | |
| 83 | + | c := dial(t, privateBus(t)) | |
| 84 | + | c.NewCall("org.example.Missing", "/", "org.example.I", "M", "").SetFlags(FlagNoAutoStart) | |
| 85 | + | _, err := c.Call() | |
| 86 | + | var de *Error | |
| 87 | + | // ServiceUnknown or NameHasNoOwner, depending on the bus. | |
| 88 | + | if !errors.As(err, &de) || !strings.HasPrefix(de.Name, "org.freedesktop.DBus.Error.") || de.Message == "" { | |
| 89 | + | t.Fatalf("err = %v", err) | |
| 90 | + | } | |
| 91 | + | // The connection stays usable after an error reply. | |
| 92 | + | if _, err := c.RequestName("org.example.Zerobus", NameFlagDoNotQueue); err != nil { | |
| 93 | + | t.Fatal(err) | |
| 94 | + | } | |
| 95 | + | } | |
| 96 | + | ||
| 97 | + | func TestSignalAndMethodBetweenPeers(t *testing.T) { | |
| 98 | + | addr := privateBus(t) | |
| 99 | + | server := dial(t, addr) | |
| 100 | + | client := dial(t, addr) | |
| 101 | + | ||
| 102 | + | if code, err := server.RequestName("org.example.Echo", NameFlagDoNotQueue); err != nil || code != NamePrimaryOwner { | |
| 103 | + | t.Fatalf("RequestName = %d, %v", code, err) | |
| 104 | + | } | |
| 105 | + | if err := client.AddMatch("type='signal',interface='org.example.Echo'"); err != nil { | |
| 106 | + | t.Fatal(err) | |
| 107 | + | } | |
| 108 | + | ||
| 109 | + | // The server answers one Echo call, then emits a signal. | |
| 110 | + | done := make(chan error, 1) | |
| 111 | + | go func() { | |
| 112 | + | for { | |
| 113 | + | m, err := server.ReadMessage() | |
| 114 | + | if err != nil { | |
| 115 | + | done <- err | |
| 116 | + | return | |
| 117 | + | } | |
| 118 | + | if m.Type != TypeMethodCall || m.Member != "Echo" { | |
| 119 | + | continue // NameAcquired and similar | |
| 120 | + | } | |
| 121 | + | s := m.Body().Str() | |
| 122 | + | server.NewReply(m, "s").Str(strings.ToUpper(s)) | |
| 123 | + | if _, err := server.Send(); err != nil { | |
| 124 | + | done <- err | |
| 125 | + | return | |
| 126 | + | } | |
| 127 | + | server.NewSignal("/org/example/Echo", "org.example.Echo", "Echoed", "s").Str(s) | |
| 128 | + | _, err = server.Send() | |
| 129 | + | done <- err | |
| 130 | + | return | |
| 131 | + | } | |
| 132 | + | }() | |
| 133 | + | ||
| 134 | + | client.NewCall("org.example.Echo", "/org/example/Echo", "org.example.Echo", "Echo", "s").Str("hi") | |
| 135 | + | m, err := client.Call() | |
| 136 | + | if err != nil { | |
| 137 | + | t.Fatal(err) | |
| 138 | + | } | |
| 139 | + | if got := m.Body().Str(); got != "HI" { | |
| 140 | + | t.Fatalf("reply %q", got) | |
| 141 | + | } | |
| 142 | + | if err := <-done; err != nil { | |
| 143 | + | t.Fatal(err) | |
| 144 | + | } | |
| 145 | + | for { | |
| 146 | + | m, err := client.ReadMessage() | |
| 147 | + | if err != nil { | |
| 148 | + | t.Fatal(err) | |
| 149 | + | } | |
| 150 | + | if m.Type == TypeSignal && m.Member == "Echoed" { | |
| 151 | + | if s := m.Body().Str(); s != "hi" || m.Sender != server.UniqueName() { | |
| 152 | + | t.Fatalf("signal %q from %q", s, m.Sender) | |
| 153 | + | } | |
| 154 | + | return | |
| 155 | + | } | |
| 156 | + | } | |
| 157 | + | } | |
| 158 | + | ||
| 159 | + | func TestCloseUnblocksRead(t *testing.T) { | |
| 160 | + | c := dial(t, privateBus(t)) | |
| 161 | + | errc := make(chan error, 1) | |
| 162 | + | go func() { | |
| 163 | + | for { | |
| 164 | + | if _, err := c.ReadMessage(); err != nil { | |
| 165 | + | errc <- err | |
| 166 | + | return | |
| 167 | + | } | |
| 168 | + | } | |
| 169 | + | }() | |
| 170 | + | time.Sleep(50 * time.Millisecond) | |
| 171 | + | if err := c.Close(); err != nil { | |
| 172 | + | t.Fatal(err) | |
| 173 | + | } | |
| 174 | + | select { | |
| 175 | + | case err := <-errc: | |
| 176 | + | if !errors.Is(err, ErrClosed) { | |
| 177 | + | t.Fatalf("err = %v, want ErrClosed", err) | |
| 178 | + | } | |
| 179 | + | case <-time.After(5 * time.Second): | |
| 180 | + | t.Fatal("ReadMessage did not return after Close") | |
| 181 | + | } | |
| 182 | + | } | |
| 183 | + | ||
| 184 | + | func TestCallNoAllocs(t *testing.T) { | |
| 185 | + | c := dial(t, privateBus(t)) | |
| 186 | + | call := func() { | |
| 187 | + | c.NewCall(busName, busPath, busName, "GetNameOwner", "s").Str(busName) | |
| 188 | + | m, err := c.Call() | |
| 189 | + | if err != nil { | |
| 190 | + | t.Fatal(err) | |
| 191 | + | } | |
| 192 | + | if m.Body().Str() == "" { | |
| 193 | + | t.Fatal("empty owner") | |
| 194 | + | } | |
| 195 | + | } | |
| 196 | + | call() // grow the buffers once | |
| 197 | + | if allocs := testing.AllocsPerRun(200, call); allocs != 0 { | |
| 198 | + | t.Fatalf("%v allocations per call, want 0", allocs) | |
| 199 | + | } | |
| 200 | + | } | |
| 201 | + | ||
| 202 | + | func TestLargeMessageGrowsBuffer(t *testing.T) { | |
| 203 | + | addr := privateBus(t) | |
| 204 | + | a := dial(t, addr) | |
| 205 | + | b := dial(t, addr) | |
| 206 | + | if err := b.AddMatch("type='signal',interface='org.example.Big'"); err != nil { | |
| 207 | + | t.Fatal(err) | |
| 208 | + | } | |
| 209 | + | payload := make([]byte, 3*defaultBufSz) | |
| 210 | + | for i := range payload { | |
| 211 | + | payload[i] = byte(i) | |
| 212 | + | } | |
| 213 | + | a.NewSignal("/a", "org.example.Big", "Blob", "ay").ByteArray(payload) | |
| 214 | + | if _, err := a.Send(); err != nil { | |
| 215 | + | t.Fatal(err) | |
| 216 | + | } | |
| 217 | + | for { | |
| 218 | + | m, err := b.ReadMessage() | |
| 219 | + | if err != nil { | |
| 220 | + | t.Fatal(err) | |
| 221 | + | } | |
| 222 | + | if m.Member == "Blob" { | |
| 223 | + | got := m.Body().ByteArray() | |
| 224 | + | if len(got) != len(payload) || got[len(got)-1] != payload[len(payload)-1] { | |
| 225 | + | t.Fatalf("payload of %d bytes", len(got)) | |
| 226 | + | } | |
| 227 | + | return | |
| 228 | + | } | |
| 229 | + | } | |
| 230 | + | } | |
| 231 | + | ||
| 232 | + | func TestDialErrors(t *testing.T) { | |
| 233 | + | for _, addr := range []string{"", "tcp:host=localhost,port=1", "unix:guid=abc", "unix:path=/nonexistent/zerobus"} { | |
| 234 | + | if c, err := Dial(addr); err == nil { | |
| 235 | + | _ = c.Close() | |
| 236 | + | t.Errorf("Dial(%q) succeeded", addr) | |
| 237 | + | } | |
| 238 | + | } | |
| 239 | + | } | |
| 240 | + | ||
| 241 | + | func TestUnescape(t *testing.T) { | |
| 242 | + | got, err := unescape("/tmp/a%20b%2c") | |
| 243 | + | if err != nil || got != "/tmp/a b," { | |
| 244 | + | t.Fatalf("got %q, %v", got, err) | |
| 245 | + | } | |
| 246 | + | if _, err := unescape("%zz"); err == nil { | |
| 247 | + | t.Fatal("bad escape accepted") | |
| 248 | + | } | |
| 249 | + | } | |
| @@ -0,0 +1,249 @@ | |||||
| 1 | + | package zerobus | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "bufio" | |||
| 5 | + | "errors" | |||
| 6 | + | "os/exec" | |||
| 7 | + | "strings" | |||
| 8 | + | "testing" | |||
| 9 | + | "time" | |||
| 10 | + | ) | |||
| 11 | + | ||||
| 12 | + | // privateBus starts a dbus-daemon for the test and returns its address. The | |||
| 13 | + | // test is skipped when dbus-daemon is not installed. | |||
| 14 | + | func privateBus(t *testing.T) string { | |||
| 15 | + | t.Helper() | |||
| 16 | + | path, err := exec.LookPath("dbus-daemon") | |||
| 17 | + | if err != nil { | |||
| 18 | + | t.Skip("dbus-daemon not installed") | |||
| 19 | + | } | |||
| 20 | + | cmd := exec.Command(path, "--session", "--nofork", "--print-address") | |||
| 21 | + | out, err := cmd.StdoutPipe() | |||
| 22 | + | if err != nil { | |||
| 23 | + | t.Fatal(err) | |||
| 24 | + | } | |||
| 25 | + | if err := cmd.Start(); err != nil { | |||
| 26 | + | t.Fatal(err) | |||
| 27 | + | } | |||
| 28 | + | t.Cleanup(func() { | |||
| 29 | + | _ = cmd.Process.Kill() | |||
| 30 | + | _ = cmd.Wait() | |||
| 31 | + | }) | |||
| 32 | + | line := make(chan string, 1) | |||
| 33 | + | go func() { | |||
| 34 | + | s, _ := bufio.NewReader(out).ReadString('\n') | |||
| 35 | + | line <- strings.TrimSpace(s) | |||
| 36 | + | }() | |||
| 37 | + | select { | |||
| 38 | + | case addr := <-line: | |||
| 39 | + | if addr == "" { | |||
| 40 | + | t.Fatal("dbus-daemon printed no address") | |||
| 41 | + | } | |||
| 42 | + | return addr | |||
| 43 | + | case <-time.After(5 * time.Second): | |||
| 44 | + | t.Fatal("dbus-daemon did not start") | |||
| 45 | + | } | |||
| 46 | + | return "" | |||
| 47 | + | } | |||
| 48 | + | ||||
| 49 | + | func dial(t *testing.T, addr string) *Conn { | |||
| 50 | + | t.Helper() | |||
| 51 | + | c, err := Dial(addr) | |||
| 52 | + | if err != nil { | |||
| 53 | + | t.Fatal(err) | |||
| 54 | + | } | |||
| 55 | + | t.Cleanup(func() { _ = c.Close() }) | |||
| 56 | + | return c | |||
| 57 | + | } | |||
| 58 | + | ||||
| 59 | + | func TestHelloAndListNames(t *testing.T) { | |||
| 60 | + | c := dial(t, privateBus(t)) | |||
| 61 | + | if !strings.HasPrefix(c.UniqueName(), ":") { | |||
| 62 | + | t.Fatalf("unique name %q", c.UniqueName()) | |||
| 63 | + | } | |||
| 64 | + | c.NewCall(busName, busPath, busName, "ListNames", "") | |||
| 65 | + | m, err := c.Call() | |||
| 66 | + | if err != nil { | |||
| 67 | + | t.Fatal(err) | |||
| 68 | + | } | |||
| 69 | + | r := m.Body() | |||
| 70 | + | found := false | |||
| 71 | + | end := r.Array('s') | |||
| 72 | + | for r.More(end) { | |||
| 73 | + | if r.Str() == c.UniqueName() { | |||
| 74 | + | found = true | |||
| 75 | + | } | |||
| 76 | + | } | |||
| 77 | + | if !r.Done() || !found { | |||
| 78 | + | t.Fatalf("own name not listed: %v", r.Err()) | |||
| 79 | + | } | |||
| 80 | + | } | |||
| 81 | + | ||||
| 82 | + | func TestErrorReply(t *testing.T) { | |||
| 83 | + | c := dial(t, privateBus(t)) | |||
| 84 | + | c.NewCall("org.example.Missing", "/", "org.example.I", "M", "").SetFlags(FlagNoAutoStart) | |||
| 85 | + | _, err := c.Call() | |||
| 86 | + | var de *Error | |||
| 87 | + | // ServiceUnknown or NameHasNoOwner, depending on the bus. | |||
| 88 | + | if !errors.As(err, &de) || !strings.HasPrefix(de.Name, "org.freedesktop.DBus.Error.") || de.Message == "" { | |||
| 89 | + | t.Fatalf("err = %v", err) | |||
| 90 | + | } | |||
| 91 | + | // The connection stays usable after an error reply. | |||
| 92 | + | if _, err := c.RequestName("org.example.Zerobus", NameFlagDoNotQueue); err != nil { | |||
| 93 | + | t.Fatal(err) | |||
| 94 | + | } | |||
| 95 | + | } | |||
| 96 | + | ||||
| 97 | + | func TestSignalAndMethodBetweenPeers(t *testing.T) { | |||
| 98 | + | addr := privateBus(t) | |||
| 99 | + | server := dial(t, addr) | |||
| 100 | + | client := dial(t, addr) | |||
| 101 | + | ||||
| 102 | + | if code, err := server.RequestName("org.example.Echo", NameFlagDoNotQueue); err != nil || code != NamePrimaryOwner { | |||
| 103 | + | t.Fatalf("RequestName = %d, %v", code, err) | |||
| 104 | + | } | |||
| 105 | + | if err := client.AddMatch("type='signal',interface='org.example.Echo'"); err != nil { | |||
| 106 | + | t.Fatal(err) | |||
| 107 | + | } | |||
| 108 | + | ||||
| 109 | + | // The server answers one Echo call, then emits a signal. | |||
| 110 | + | done := make(chan error, 1) | |||
| 111 | + | go func() { | |||
| 112 | + | for { | |||
| 113 | + | m, err := server.ReadMessage() | |||
| 114 | + | if err != nil { | |||
| 115 | + | done <- err | |||
| 116 | + | return | |||
| 117 | + | } | |||
| 118 | + | if m.Type != TypeMethodCall || m.Member != "Echo" { | |||
| 119 | + | continue // NameAcquired and similar | |||
| 120 | + | } | |||
| 121 | + | s := m.Body().Str() | |||
| 122 | + | server.NewReply(m, "s").Str(strings.ToUpper(s)) | |||
| 123 | + | if _, err := server.Send(); err != nil { | |||
| 124 | + | done <- err | |||
| 125 | + | return | |||
| 126 | + | } | |||
| 127 | + | server.NewSignal("/org/example/Echo", "org.example.Echo", "Echoed", "s").Str(s) | |||
| 128 | + | _, err = server.Send() | |||
| 129 | + | done <- err | |||
| 130 | + | return | |||
| 131 | + | } | |||
| 132 | + | }() | |||
| 133 | + | ||||
| 134 | + | client.NewCall("org.example.Echo", "/org/example/Echo", "org.example.Echo", "Echo", "s").Str("hi") | |||
| 135 | + | m, err := client.Call() | |||
| 136 | + | if err != nil { | |||
| 137 | + | t.Fatal(err) | |||
| 138 | + | } | |||
| 139 | + | if got := m.Body().Str(); got != "HI" { | |||
| 140 | + | t.Fatalf("reply %q", got) | |||
| 141 | + | } | |||
| 142 | + | if err := <-done; err != nil { | |||
| 143 | + | t.Fatal(err) | |||
| 144 | + | } | |||
| 145 | + | for { | |||
| 146 | + | m, err := client.ReadMessage() | |||
| 147 | + | if err != nil { | |||
| 148 | + | t.Fatal(err) | |||
| 149 | + | } | |||
| 150 | + | if m.Type == TypeSignal && m.Member == "Echoed" { | |||
| 151 | + | if s := m.Body().Str(); s != "hi" || m.Sender != server.UniqueName() { | |||
| 152 | + | t.Fatalf("signal %q from %q", s, m.Sender) | |||
| 153 | + | } | |||
| 154 | + | return | |||
| 155 | + | } | |||
| 156 | + | } | |||
| 157 | + | } | |||
| 158 | + | ||||
| 159 | + | func TestCloseUnblocksRead(t *testing.T) { | |||
| 160 | + | c := dial(t, privateBus(t)) | |||
| 161 | + | errc := make(chan error, 1) | |||
| 162 | + | go func() { | |||
| 163 | + | for { | |||
| 164 | + | if _, err := c.ReadMessage(); err != nil { | |||
| 165 | + | errc <- err | |||
| 166 | + | return | |||
| 167 | + | } | |||
| 168 | + | } | |||
| 169 | + | }() | |||
| 170 | + | time.Sleep(50 * time.Millisecond) | |||
| 171 | + | if err := c.Close(); err != nil { | |||
| 172 | + | t.Fatal(err) | |||
| 173 | + | } | |||
| 174 | + | select { | |||
| 175 | + | case err := <-errc: | |||
| 176 | + | if !errors.Is(err, ErrClosed) { | |||
| 177 | + | t.Fatalf("err = %v, want ErrClosed", err) | |||
| 178 | + | } | |||
| 179 | + | case <-time.After(5 * time.Second): | |||
| 180 | + | t.Fatal("ReadMessage did not return after Close") | |||
| 181 | + | } | |||
| 182 | + | } | |||
| 183 | + | ||||
| 184 | + | func TestCallNoAllocs(t *testing.T) { | |||
| 185 | + | c := dial(t, privateBus(t)) | |||
| 186 | + | call := func() { | |||
| 187 | + | c.NewCall(busName, busPath, busName, "GetNameOwner", "s").Str(busName) | |||
| 188 | + | m, err := c.Call() | |||
| 189 | + | if err != nil { | |||
| 190 | + | t.Fatal(err) | |||
| 191 | + | } | |||
| 192 | + | if m.Body().Str() == "" { | |||
| 193 | + | t.Fatal("empty owner") | |||
| 194 | + | } | |||
| 195 | + | } | |||
| 196 | + | call() // grow the buffers once | |||
| 197 | + | if allocs := testing.AllocsPerRun(200, call); allocs != 0 { | |||
| 198 | + | t.Fatalf("%v allocations per call, want 0", allocs) | |||
| 199 | + | } | |||
| 200 | + | } | |||
| 201 | + | ||||
| 202 | + | func TestLargeMessageGrowsBuffer(t *testing.T) { | |||
| 203 | + | addr := privateBus(t) | |||
| 204 | + | a := dial(t, addr) | |||
| 205 | + | b := dial(t, addr) | |||
| 206 | + | if err := b.AddMatch("type='signal',interface='org.example.Big'"); err != nil { | |||
| 207 | + | t.Fatal(err) | |||
| 208 | + | } | |||
| 209 | + | payload := make([]byte, 3*defaultBufSz) | |||
| 210 | + | for i := range payload { | |||
| 211 | + | payload[i] = byte(i) | |||
| 212 | + | } | |||
| 213 | + | a.NewSignal("/a", "org.example.Big", "Blob", "ay").ByteArray(payload) | |||
| 214 | + | if _, err := a.Send(); err != nil { | |||
| 215 | + | t.Fatal(err) | |||
| 216 | + | } | |||
| 217 | + | for { | |||
| 218 | + | m, err := b.ReadMessage() | |||
| 219 | + | if err != nil { | |||
| 220 | + | t.Fatal(err) | |||
| 221 | + | } | |||
| 222 | + | if m.Member == "Blob" { | |||
| 223 | + | got := m.Body().ByteArray() | |||
| 224 | + | if len(got) != len(payload) || got[len(got)-1] != payload[len(payload)-1] { | |||
| 225 | + | t.Fatalf("payload of %d bytes", len(got)) | |||
| 226 | + | } | |||
| 227 | + | return | |||
| 228 | + | } | |||
| 229 | + | } | |||
| 230 | + | } | |||
| 231 | + | ||||
| 232 | + | func TestDialErrors(t *testing.T) { | |||
| 233 | + | for _, addr := range []string{"", "tcp:host=localhost,port=1", "unix:guid=abc", "unix:path=/nonexistent/zerobus"} { | |||
| 234 | + | if c, err := Dial(addr); err == nil { | |||
| 235 | + | _ = c.Close() | |||
| 236 | + | t.Errorf("Dial(%q) succeeded", addr) | |||
| 237 | + | } | |||
| 238 | + | } | |||
| 239 | + | } | |||
| 240 | + | ||||
| 241 | + | func TestUnescape(t *testing.T) { | |||
| 242 | + | got, err := unescape("/tmp/a%20b%2c") | |||
| 243 | + | if err != nil || got != "/tmp/a b," { | |||
| 244 | + | t.Fatalf("got %q, %v", got, err) | |||
| 245 | + | } | |||
| 246 | + | if _, err := unescape("%zz"); err == nil { | |||
| 247 | + | t.Fatal("bad escape accepted") | |||
| 248 | + | } | |||
| 249 | + | } | |||
A examples/trayitems/main.go +88 -0
| @@ -0,0 +1,88 @@ | |||
| 1 | + | // Command trayitems lists the StatusNotifierItems registered on the session | |
| 2 | + | // bus, then prints their changes as they happen. | |
| 3 | + | package main | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "fmt" | |
| 7 | + | "log" | |
| 8 | + | "strings" | |
| 9 | + | ||
| 10 | + | "github.com/bnema/zerobus" | |
| 11 | + | ) | |
| 12 | + | ||
| 13 | + | const ( | |
| 14 | + | watcher = "org.kde.StatusNotifierWatcher" | |
| 15 | + | watcherPath = "/StatusNotifierWatcher" | |
| 16 | + | props = "org.freedesktop.DBus.Properties" | |
| 17 | + | item = "org.kde.StatusNotifierItem" | |
| 18 | + | ) | |
| 19 | + | ||
| 20 | + | func main() { | |
| 21 | + | c, err := zerobus.SessionBus() | |
| 22 | + | if err != nil { | |
| 23 | + | log.Fatal(err) | |
| 24 | + | } | |
| 25 | + | defer func() { _ = c.Close() }() | |
| 26 | + | ||
| 27 | + | e := c.NewCall(watcher, watcherPath, props, "Get", "ss") | |
| 28 | + | e.Str(watcher) | |
| 29 | + | e.Str("RegisteredStatusNotifierItems") | |
| 30 | + | m, err := c.Call() | |
| 31 | + | if err != nil { | |
| 32 | + | log.Fatal(err) | |
| 33 | + | } | |
| 34 | + | // The reply is a variant holding an array of strings. Copy the names: | |
| 35 | + | // the next call reuses the buffer they point into. | |
| 36 | + | r := m.Body() | |
| 37 | + | var items []string | |
| 38 | + | if r.Variant() == "as" { | |
| 39 | + | end := r.Array('s') | |
| 40 | + | for r.More(end) { | |
| 41 | + | items = append(items, strings.Clone(r.Str())) | |
| 42 | + | } | |
| 43 | + | } | |
| 44 | + | if r.Err() != nil { | |
| 45 | + | log.Fatal(r.Err()) | |
| 46 | + | } | |
| 47 | + | ||
| 48 | + | for _, it := range items { | |
| 49 | + | // An item is "bus-name/object/path", or only a bus name. | |
| 50 | + | dest, path, ok := strings.Cut(it, "/") | |
| 51 | + | if ok { | |
| 52 | + | path = "/" + path | |
| 53 | + | } else { | |
| 54 | + | path = "/StatusNotifierItem" | |
| 55 | + | } | |
| 56 | + | fmt.Printf("%s\n id=%q icon=%q status=%q\n", it, get(c, dest, path, "Id"), get(c, dest, path, "IconName"), get(c, dest, path, "Status")) | |
| 57 | + | } | |
| 58 | + | ||
| 59 | + | if err := c.AddMatch("type='signal',interface='" + item + "'"); err != nil { | |
| 60 | + | log.Fatal(err) | |
| 61 | + | } | |
| 62 | + | fmt.Println("waiting for changes (Ctrl+C to stop)") | |
| 63 | + | for { | |
| 64 | + | m, err := c.ReadMessage() | |
| 65 | + | if err != nil { | |
| 66 | + | log.Fatal(err) | |
| 67 | + | } | |
| 68 | + | if m.Type == zerobus.TypeSignal { | |
| 69 | + | fmt.Printf("%s %s %s\n", m.Sender, m.Path, m.Member) | |
| 70 | + | } | |
| 71 | + | } | |
| 72 | + | } | |
| 73 | + | ||
| 74 | + | // get reads a string property, or returns "" when it has none. | |
| 75 | + | func get(c *zerobus.Conn, dest, path, name string) string { | |
| 76 | + | e := c.NewCall(dest, path, props, "Get", "ss") | |
| 77 | + | e.Str(item) | |
| 78 | + | e.Str(name) | |
| 79 | + | m, err := c.Call() | |
| 80 | + | if err != nil { | |
| 81 | + | return "" | |
| 82 | + | } | |
| 83 | + | r := m.Body() | |
| 84 | + | if r.Variant() != "s" { | |
| 85 | + | return "" | |
| 86 | + | } | |
| 87 | + | return strings.Clone(r.Str()) | |
| 88 | + | } | |
| @@ -0,0 +1,88 @@ | |||||
| 1 | + | // Command trayitems lists the StatusNotifierItems registered on the session | |||
| 2 | + | // bus, then prints their changes as they happen. | |||
| 3 | + | package main | |||
| 4 | + | ||||
| 5 | + | import ( | |||
| 6 | + | "fmt" | |||
| 7 | + | "log" | |||
| 8 | + | "strings" | |||
| 9 | + | ||||
| 10 | + | "github.com/bnema/zerobus" | |||
| 11 | + | ) | |||
| 12 | + | ||||
| 13 | + | const ( | |||
| 14 | + | watcher = "org.kde.StatusNotifierWatcher" | |||
| 15 | + | watcherPath = "/StatusNotifierWatcher" | |||
| 16 | + | props = "org.freedesktop.DBus.Properties" | |||
| 17 | + | item = "org.kde.StatusNotifierItem" | |||
| 18 | + | ) | |||
| 19 | + | ||||
| 20 | + | func main() { | |||
| 21 | + | c, err := zerobus.SessionBus() | |||
| 22 | + | if err != nil { | |||
| 23 | + | log.Fatal(err) | |||
| 24 | + | } | |||
| 25 | + | defer func() { _ = c.Close() }() | |||
| 26 | + | ||||
| 27 | + | e := c.NewCall(watcher, watcherPath, props, "Get", "ss") | |||
| 28 | + | e.Str(watcher) | |||
| 29 | + | e.Str("RegisteredStatusNotifierItems") | |||
| 30 | + | m, err := c.Call() | |||
| 31 | + | if err != nil { | |||
| 32 | + | log.Fatal(err) | |||
| 33 | + | } | |||
| 34 | + | // The reply is a variant holding an array of strings. Copy the names: | |||
| 35 | + | // the next call reuses the buffer they point into. | |||
| 36 | + | r := m.Body() | |||
| 37 | + | var items []string | |||
| 38 | + | if r.Variant() == "as" { | |||
| 39 | + | end := r.Array('s') | |||
| 40 | + | for r.More(end) { | |||
| 41 | + | items = append(items, strings.Clone(r.Str())) | |||
| 42 | + | } | |||
| 43 | + | } | |||
| 44 | + | if r.Err() != nil { | |||
| 45 | + | log.Fatal(r.Err()) | |||
| 46 | + | } | |||
| 47 | + | ||||
| 48 | + | for _, it := range items { | |||
| 49 | + | // An item is "bus-name/object/path", or only a bus name. | |||
| 50 | + | dest, path, ok := strings.Cut(it, "/") | |||
| 51 | + | if ok { | |||
| 52 | + | path = "/" + path | |||
| 53 | + | } else { | |||
| 54 | + | path = "/StatusNotifierItem" | |||
| 55 | + | } | |||
| 56 | + | fmt.Printf("%s\n id=%q icon=%q status=%q\n", it, get(c, dest, path, "Id"), get(c, dest, path, "IconName"), get(c, dest, path, "Status")) | |||
| 57 | + | } | |||
| 58 | + | ||||
| 59 | + | if err := c.AddMatch("type='signal',interface='" + item + "'"); err != nil { | |||
| 60 | + | log.Fatal(err) | |||
| 61 | + | } | |||
| 62 | + | fmt.Println("waiting for changes (Ctrl+C to stop)") | |||
| 63 | + | for { | |||
| 64 | + | m, err := c.ReadMessage() | |||
| 65 | + | if err != nil { | |||
| 66 | + | log.Fatal(err) | |||
| 67 | + | } | |||
| 68 | + | if m.Type == zerobus.TypeSignal { | |||
| 69 | + | fmt.Printf("%s %s %s\n", m.Sender, m.Path, m.Member) | |||
| 70 | + | } | |||
| 71 | + | } | |||
| 72 | + | } | |||
| 73 | + | ||||
| 74 | + | // get reads a string property, or returns "" when it has none. | |||
| 75 | + | func get(c *zerobus.Conn, dest, path, name string) string { | |||
| 76 | + | e := c.NewCall(dest, path, props, "Get", "ss") | |||
| 77 | + | e.Str(item) | |||
| 78 | + | e.Str(name) | |||
| 79 | + | m, err := c.Call() | |||
| 80 | + | if err != nil { | |||
| 81 | + | return "" | |||
| 82 | + | } | |||
| 83 | + | r := m.Body() | |||
| 84 | + | if r.Variant() != "s" { | |||
| 85 | + | return "" | |||
| 86 | + | } | |||
| 87 | + | return strings.Clone(r.Str()) | |||
| 88 | + | } | |||
A message.go +269 -0
| @@ -0,0 +1,269 @@ | |||
| 1 | + | package zerobus | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "encoding/binary" | |
| 5 | + | "math" | |
| 6 | + | ) | |
| 7 | + | ||
| 8 | + | // Type is the kind of a message. | |
| 9 | + | type Type byte | |
| 10 | + | ||
| 11 | + | // Message types. | |
| 12 | + | const ( | |
| 13 | + | TypeMethodCall Type = 1 | |
| 14 | + | TypeMethodReturn Type = 2 | |
| 15 | + | TypeError Type = 3 | |
| 16 | + | TypeSignal Type = 4 | |
| 17 | + | ) | |
| 18 | + | ||
| 19 | + | // Flags are the header flags of a message. | |
| 20 | + | type Flags byte | |
| 21 | + | ||
| 22 | + | // Message flags. | |
| 23 | + | const ( | |
| 24 | + | // FlagNoReplyExpected tells the receiver not to answer a method call. | |
| 25 | + | FlagNoReplyExpected Flags = 0x1 | |
| 26 | + | // FlagNoAutoStart tells the bus not to start the destination service. | |
| 27 | + | FlagNoAutoStart Flags = 0x2 | |
| 28 | + | // FlagAllowInteractiveAuth allows the receiver to prompt the user. | |
| 29 | + | FlagAllowInteractiveAuth Flags = 0x4 | |
| 30 | + | ) | |
| 31 | + | ||
| 32 | + | // Header field codes. | |
| 33 | + | const ( | |
| 34 | + | fieldPath = 1 | |
| 35 | + | fieldInterface = 2 | |
| 36 | + | fieldMember = 3 | |
| 37 | + | fieldErrorName = 4 | |
| 38 | + | fieldReplySerial = 5 | |
| 39 | + | fieldDestination = 6 | |
| 40 | + | fieldSender = 7 | |
| 41 | + | fieldSignature = 8 | |
| 42 | + | fieldUnixFDs = 9 | |
| 43 | + | ) | |
| 44 | + | ||
| 45 | + | const headerLen = 16 // the fixed part, up to the length of the field array | |
| 46 | + | ||
| 47 | + | // Message is a received message. | |
| 48 | + | // | |
| 49 | + | // Its strings point into the connection's receive buffer: they and the | |
| 50 | + | // message itself are valid only until the next read on the connection. | |
| 51 | + | type Message struct { | |
| 52 | + | Type Type | |
| 53 | + | Flags Flags | |
| 54 | + | Serial uint32 | |
| 55 | + | ReplySerial uint32 | |
| 56 | + | Path string | |
| 57 | + | Interface string | |
| 58 | + | Member string | |
| 59 | + | ErrorName string | |
| 60 | + | Destination string | |
| 61 | + | Sender string | |
| 62 | + | Signature string // the signature of the body | |
| 63 | + | ||
| 64 | + | body []byte | |
| 65 | + | big bool | |
| 66 | + | r Reader | |
| 67 | + | } | |
| 68 | + | ||
| 69 | + | // Body returns a reader at the start of the message body. Each call starts | |
| 70 | + | // over from the beginning. | |
| 71 | + | func (m *Message) Body() *Reader { | |
| 72 | + | m.r = Reader{buf: m.body, big: m.big} | |
| 73 | + | return &m.r | |
| 74 | + | } | |
| 75 | + | ||
| 76 | + | // frameLen returns the full length of the message whose fixed header is b, | |
| 77 | + | // which must hold at least headerLen bytes. | |
| 78 | + | func frameLen(b []byte) (int, error) { | |
| 79 | + | var order binary.ByteOrder | |
| 80 | + | switch b[0] { | |
| 81 | + | case 'l': | |
| 82 | + | order = binary.LittleEndian | |
| 83 | + | case 'B': | |
| 84 | + | order = binary.BigEndian | |
| 85 | + | default: | |
| 86 | + | return 0, ErrMalformed | |
| 87 | + | } | |
| 88 | + | if b[3] != 1 { | |
| 89 | + | return 0, ErrMalformed // protocol version | |
| 90 | + | } | |
| 91 | + | body := uint64(order.Uint32(b[4:])) | |
| 92 | + | fields := uint64(order.Uint32(b[12:])) | |
| 93 | + | if body > maxMessage || fields > maxArray { | |
| 94 | + | return 0, ErrTooLarge | |
| 95 | + | } | |
| 96 | + | n := uint64(pad(headerLen+int(fields), 8)) + body | |
| 97 | + | if n > maxMessage { | |
| 98 | + | return 0, ErrTooLarge | |
| 99 | + | } | |
| 100 | + | return int(n), nil | |
| 101 | + | } | |
| 102 | + | ||
| 103 | + | // parse decodes the message b, whose length frameLen returned, into m. The | |
| 104 | + | // strings of m point into b. | |
| 105 | + | func parse(b []byte, m *Message) error { | |
| 106 | + | *m = Message{big: b[0] == 'B', Type: Type(b[1]), Flags: Flags(b[2])} | |
| 107 | + | r := Reader{buf: b, big: m.big, off: 4} | |
| 108 | + | bodyLen := int(r.Uint32()) | |
| 109 | + | m.Serial = r.Uint32() | |
| 110 | + | if m.Serial == 0 { | |
| 111 | + | return ErrMalformed | |
| 112 | + | } | |
| 113 | + | var fds uint32 | |
| 114 | + | end := r.Array('(') | |
| 115 | + | for r.More(end) { | |
| 116 | + | r.Struct() | |
| 117 | + | code := r.Byte() | |
| 118 | + | sig := r.Variant() | |
| 119 | + | if r.err != nil { | |
| 120 | + | break | |
| 121 | + | } | |
| 122 | + | want := byte('s') | |
| 123 | + | var dst *string | |
| 124 | + | switch code { | |
| 125 | + | case fieldPath: | |
| 126 | + | want, dst = 'o', &m.Path | |
| 127 | + | case fieldInterface: | |
| 128 | + | dst = &m.Interface | |
| 129 | + | case fieldMember: | |
| 130 | + | dst = &m.Member | |
| 131 | + | case fieldErrorName: | |
| 132 | + | dst = &m.ErrorName | |
| 133 | + | case fieldDestination: | |
| 134 | + | dst = &m.Destination | |
| 135 | + | case fieldSender: | |
| 136 | + | dst = &m.Sender | |
| 137 | + | case fieldSignature: | |
| 138 | + | want, dst = 'g', &m.Signature | |
| 139 | + | case fieldReplySerial, fieldUnixFDs: | |
| 140 | + | if sig != "u" { | |
| 141 | + | return ErrMalformed | |
| 142 | + | } | |
| 143 | + | if code == fieldReplySerial { | |
| 144 | + | m.ReplySerial = r.Uint32() | |
| 145 | + | } else { | |
| 146 | + | fds = r.Uint32() | |
| 147 | + | } | |
| 148 | + | continue | |
| 149 | + | default: | |
| 150 | + | r.Skip(sig) // unknown fields must be ignored | |
| 151 | + | continue | |
| 152 | + | } | |
| 153 | + | if len(sig) != 1 || sig[0] != want { | |
| 154 | + | return ErrMalformed | |
| 155 | + | } | |
| 156 | + | if want == 'g' { | |
| 157 | + | *dst = r.Signature() | |
| 158 | + | } else { | |
| 159 | + | *dst = r.Str() | |
| 160 | + | } | |
| 161 | + | } | |
| 162 | + | if r.err != nil { | |
| 163 | + | return r.err | |
| 164 | + | } | |
| 165 | + | if fds != 0 { | |
| 166 | + | return ErrMalformed // fd passing is not negotiated, so none may come | |
| 167 | + | } | |
| 168 | + | start := pad(r.off, 8) | |
| 169 | + | if start+bodyLen != len(b) || start > len(b) { | |
| 170 | + | return ErrMalformed | |
| 171 | + | } | |
| 172 | + | for _, c := range b[r.off:start] { | |
| 173 | + | if c != 0 { | |
| 174 | + | return ErrMalformed | |
| 175 | + | } | |
| 176 | + | } | |
| 177 | + | m.body = b[start:] | |
| 178 | + | if !validSignature(m.Signature) || m.Path != "" && !validPath(m.Path) { | |
| 179 | + | return ErrMalformed | |
| 180 | + | } | |
| 181 | + | switch m.Type { | |
| 182 | + | case TypeMethodCall: | |
| 183 | + | if m.Path == "" || m.Member == "" { | |
| 184 | + | return ErrMalformed | |
| 185 | + | } | |
| 186 | + | case TypeSignal: | |
| 187 | + | if m.Path == "" || m.Interface == "" || m.Member == "" { | |
| 188 | + | return ErrMalformed | |
| 189 | + | } | |
| 190 | + | case TypeError: | |
| 191 | + | if m.ErrorName == "" || m.ReplySerial == 0 { | |
| 192 | + | return ErrMalformed | |
| 193 | + | } | |
| 194 | + | case TypeMethodReturn: | |
| 195 | + | if m.ReplySerial == 0 { | |
| 196 | + | return ErrMalformed | |
| 197 | + | } | |
| 198 | + | } | |
| 199 | + | return nil | |
| 200 | + | } | |
| 201 | + | ||
| 202 | + | // begin starts a new message in e: the fixed header and the field array, up | |
| 203 | + | // to the body. Empty strings are left out of the header. | |
| 204 | + | func (e *Encoder) begin(t Type, serial, replySerial uint32, dest, path, iface, member, errName, sig string) { | |
| 205 | + | e.buf = append(e.buf[:0], 'l', byte(t), 0, 1, 0, 0, 0, 0) | |
| 206 | + | e.buf = binary.LittleEndian.AppendUint32(e.buf, serial) | |
| 207 | + | e.body, e.sig, e.err = 0, sig, nil | |
| 208 | + | // A bus disconnects a client that sends an invalid name. | |
| 209 | + | if dest != "" && !validBusName(dest) || iface != "" && !validInterface(iface) || | |
| 210 | + | member != "" && !validMember(member) || errName != "" && !validInterface(errName) { | |
| 211 | + | e.invalid() | |
| 212 | + | } | |
| 213 | + | fields := e.BeginArray('(') | |
| 214 | + | str := func(code byte, typ, v string) { | |
| 215 | + | if v == "" { | |
| 216 | + | return | |
| 217 | + | } | |
| 218 | + | e.Struct() | |
| 219 | + | e.Byte(code) | |
| 220 | + | e.Signature(typ) | |
| 221 | + | switch typ { | |
| 222 | + | case "o": | |
| 223 | + | e.ObjectPath(v) | |
| 224 | + | case "g": | |
| 225 | + | e.Signature(v) | |
| 226 | + | default: | |
| 227 | + | e.Str(v) | |
| 228 | + | } | |
| 229 | + | } | |
| 230 | + | str(fieldPath, "o", path) | |
| 231 | + | str(fieldInterface, "s", iface) | |
| 232 | + | str(fieldMember, "s", member) | |
| 233 | + | str(fieldErrorName, "s", errName) | |
| 234 | + | if replySerial != 0 { | |
| 235 | + | e.Struct() | |
| 236 | + | e.Byte(fieldReplySerial) | |
| 237 | + | e.Signature("u") | |
| 238 | + | e.Uint32(replySerial) | |
| 239 | + | } | |
| 240 | + | str(fieldDestination, "s", dest) | |
| 241 | + | str(fieldSignature, "g", sig) | |
| 242 | + | e.EndArray(fields) | |
| 243 | + | e.pad(8) | |
| 244 | + | e.body = len(e.buf) | |
| 245 | + | } | |
| 246 | + | ||
| 247 | + | // finish writes the body length and checks the body against the signature. | |
| 248 | + | // It returns the bytes to send. | |
| 249 | + | func (e *Encoder) finish() ([]byte, error) { | |
| 250 | + | if e.body == 0 { | |
| 251 | + | return nil, ErrInvalid | |
| 252 | + | } | |
| 253 | + | if e.err != nil { | |
| 254 | + | return nil, e.err | |
| 255 | + | } | |
| 256 | + | n := len(e.buf) - e.body | |
| 257 | + | if len(e.buf) > maxMessage || n > math.MaxUint32 { | |
| 258 | + | return nil, ErrTooLarge | |
| 259 | + | } | |
| 260 | + | binary.LittleEndian.PutUint32(e.buf[4:], uint32(n)) | |
| 261 | + | // Reading the body back with the signature proves it matches: a bus | |
| 262 | + | // disconnects a client that sends a body that does not. | |
| 263 | + | r := Reader{buf: e.buf[e.body:]} | |
| 264 | + | r.Skip(e.sig) | |
| 265 | + | if !r.Done() { | |
| 266 | + | return nil, ErrInvalid | |
| 267 | + | } | |
| 268 | + | return e.buf, nil | |
| 269 | + | } | |
| @@ -0,0 +1,269 @@ | |||||
| 1 | + | package zerobus | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "encoding/binary" | |||
| 5 | + | "math" | |||
| 6 | + | ) | |||
| 7 | + | ||||
| 8 | + | // Type is the kind of a message. | |||
| 9 | + | type Type byte | |||
| 10 | + | ||||
| 11 | + | // Message types. | |||
| 12 | + | const ( | |||
| 13 | + | TypeMethodCall Type = 1 | |||
| 14 | + | TypeMethodReturn Type = 2 | |||
| 15 | + | TypeError Type = 3 | |||
| 16 | + | TypeSignal Type = 4 | |||
| 17 | + | ) | |||
| 18 | + | ||||
| 19 | + | // Flags are the header flags of a message. | |||
| 20 | + | type Flags byte | |||
| 21 | + | ||||
| 22 | + | // Message flags. | |||
| 23 | + | const ( | |||
| 24 | + | // FlagNoReplyExpected tells the receiver not to answer a method call. | |||
| 25 | + | FlagNoReplyExpected Flags = 0x1 | |||
| 26 | + | // FlagNoAutoStart tells the bus not to start the destination service. | |||
| 27 | + | FlagNoAutoStart Flags = 0x2 | |||
| 28 | + | // FlagAllowInteractiveAuth allows the receiver to prompt the user. | |||
| 29 | + | FlagAllowInteractiveAuth Flags = 0x4 | |||
| 30 | + | ) | |||
| 31 | + | ||||
| 32 | + | // Header field codes. | |||
| 33 | + | const ( | |||
| 34 | + | fieldPath = 1 | |||
| 35 | + | fieldInterface = 2 | |||
| 36 | + | fieldMember = 3 | |||
| 37 | + | fieldErrorName = 4 | |||
| 38 | + | fieldReplySerial = 5 | |||
| 39 | + | fieldDestination = 6 | |||
| 40 | + | fieldSender = 7 | |||
| 41 | + | fieldSignature = 8 | |||
| 42 | + | fieldUnixFDs = 9 | |||
| 43 | + | ) | |||
| 44 | + | ||||
| 45 | + | const headerLen = 16 // the fixed part, up to the length of the field array | |||
| 46 | + | ||||
| 47 | + | // Message is a received message. | |||
| 48 | + | // | |||
| 49 | + | // Its strings point into the connection's receive buffer: they and the | |||
| 50 | + | // message itself are valid only until the next read on the connection. | |||
| 51 | + | type Message struct { | |||
| 52 | + | Type Type | |||
| 53 | + | Flags Flags | |||
| 54 | + | Serial uint32 | |||
| 55 | + | ReplySerial uint32 | |||
| 56 | + | Path string | |||
| 57 | + | Interface string | |||
| 58 | + | Member string | |||
| 59 | + | ErrorName string | |||
| 60 | + | Destination string | |||
| 61 | + | Sender string | |||
| 62 | + | Signature string // the signature of the body | |||
| 63 | + | ||||
| 64 | + | body []byte | |||
| 65 | + | big bool | |||
| 66 | + | r Reader | |||
| 67 | + | } | |||
| 68 | + | ||||
| 69 | + | // Body returns a reader at the start of the message body. Each call starts | |||
| 70 | + | // over from the beginning. | |||
| 71 | + | func (m *Message) Body() *Reader { | |||
| 72 | + | m.r = Reader{buf: m.body, big: m.big} | |||
| 73 | + | return &m.r | |||
| 74 | + | } | |||
| 75 | + | ||||
| 76 | + | // frameLen returns the full length of the message whose fixed header is b, | |||
| 77 | + | // which must hold at least headerLen bytes. | |||
| 78 | + | func frameLen(b []byte) (int, error) { | |||
| 79 | + | var order binary.ByteOrder | |||
| 80 | + | switch b[0] { | |||
| 81 | + | case 'l': | |||
| 82 | + | order = binary.LittleEndian | |||
| 83 | + | case 'B': | |||
| 84 | + | order = binary.BigEndian | |||
| 85 | + | default: | |||
| 86 | + | return 0, ErrMalformed | |||
| 87 | + | } | |||
| 88 | + | if b[3] != 1 { | |||
| 89 | + | return 0, ErrMalformed // protocol version | |||
| 90 | + | } | |||
| 91 | + | body := uint64(order.Uint32(b[4:])) | |||
| 92 | + | fields := uint64(order.Uint32(b[12:])) | |||
| 93 | + | if body > maxMessage || fields > maxArray { | |||
| 94 | + | return 0, ErrTooLarge | |||
| 95 | + | } | |||
| 96 | + | n := uint64(pad(headerLen+int(fields), 8)) + body | |||
| 97 | + | if n > maxMessage { | |||
| 98 | + | return 0, ErrTooLarge | |||
| 99 | + | } | |||
| 100 | + | return int(n), nil | |||
| 101 | + | } | |||
| 102 | + | ||||
| 103 | + | // parse decodes the message b, whose length frameLen returned, into m. The | |||
| 104 | + | // strings of m point into b. | |||
| 105 | + | func parse(b []byte, m *Message) error { | |||
| 106 | + | *m = Message{big: b[0] == 'B', Type: Type(b[1]), Flags: Flags(b[2])} | |||
| 107 | + | r := Reader{buf: b, big: m.big, off: 4} | |||
| 108 | + | bodyLen := int(r.Uint32()) | |||
| 109 | + | m.Serial = r.Uint32() | |||
| 110 | + | if m.Serial == 0 { | |||
| 111 | + | return ErrMalformed | |||
| 112 | + | } | |||
| 113 | + | var fds uint32 | |||
| 114 | + | end := r.Array('(') | |||
| 115 | + | for r.More(end) { | |||
| 116 | + | r.Struct() | |||
| 117 | + | code := r.Byte() | |||
| 118 | + | sig := r.Variant() | |||
| 119 | + | if r.err != nil { | |||
| 120 | + | break | |||
| 121 | + | } | |||
| 122 | + | want := byte('s') | |||
| 123 | + | var dst *string | |||
| 124 | + | switch code { | |||
| 125 | + | case fieldPath: | |||
| 126 | + | want, dst = 'o', &m.Path | |||
| 127 | + | case fieldInterface: | |||
| 128 | + | dst = &m.Interface | |||
| 129 | + | case fieldMember: | |||
| 130 | + | dst = &m.Member | |||
| 131 | + | case fieldErrorName: | |||
| 132 | + | dst = &m.ErrorName | |||
| 133 | + | case fieldDestination: | |||
| 134 | + | dst = &m.Destination | |||
| 135 | + | case fieldSender: | |||
| 136 | + | dst = &m.Sender | |||
| 137 | + | case fieldSignature: | |||
| 138 | + | want, dst = 'g', &m.Signature | |||
| 139 | + | case fieldReplySerial, fieldUnixFDs: | |||
| 140 | + | if sig != "u" { | |||
| 141 | + | return ErrMalformed | |||
| 142 | + | } | |||
| 143 | + | if code == fieldReplySerial { | |||
| 144 | + | m.ReplySerial = r.Uint32() | |||
| 145 | + | } else { | |||
| 146 | + | fds = r.Uint32() | |||
| 147 | + | } | |||
| 148 | + | continue | |||
| 149 | + | default: | |||
| 150 | + | r.Skip(sig) // unknown fields must be ignored | |||
| 151 | + | continue | |||
| 152 | + | } | |||
| 153 | + | if len(sig) != 1 || sig[0] != want { | |||
| 154 | + | return ErrMalformed | |||
| 155 | + | } | |||
| 156 | + | if want == 'g' { | |||
| 157 | + | *dst = r.Signature() | |||
| 158 | + | } else { | |||
| 159 | + | *dst = r.Str() | |||
| 160 | + | } | |||
| 161 | + | } | |||
| 162 | + | if r.err != nil { | |||
| 163 | + | return r.err | |||
| 164 | + | } | |||
| 165 | + | if fds != 0 { | |||
| 166 | + | return ErrMalformed // fd passing is not negotiated, so none may come | |||
| 167 | + | } | |||
| 168 | + | start := pad(r.off, 8) | |||
| 169 | + | if start+bodyLen != len(b) || start > len(b) { | |||
| 170 | + | return ErrMalformed | |||
| 171 | + | } | |||
| 172 | + | for _, c := range b[r.off:start] { | |||
| 173 | + | if c != 0 { | |||
| 174 | + | return ErrMalformed | |||
| 175 | + | } | |||
| 176 | + | } | |||
| 177 | + | m.body = b[start:] | |||
| 178 | + | if !validSignature(m.Signature) || m.Path != "" && !validPath(m.Path) { | |||
| 179 | + | return ErrMalformed | |||
| 180 | + | } | |||
| 181 | + | switch m.Type { | |||
| 182 | + | case TypeMethodCall: | |||
| 183 | + | if m.Path == "" || m.Member == "" { | |||
| 184 | + | return ErrMalformed | |||
| 185 | + | } | |||
| 186 | + | case TypeSignal: | |||
| 187 | + | if m.Path == "" || m.Interface == "" || m.Member == "" { | |||
| 188 | + | return ErrMalformed | |||
| 189 | + | } | |||
| 190 | + | case TypeError: | |||
| 191 | + | if m.ErrorName == "" || m.ReplySerial == 0 { | |||
| 192 | + | return ErrMalformed | |||
| 193 | + | } | |||
| 194 | + | case TypeMethodReturn: | |||
| 195 | + | if m.ReplySerial == 0 { | |||
| 196 | + | return ErrMalformed | |||
| 197 | + | } | |||
| 198 | + | } | |||
| 199 | + | return nil | |||
| 200 | + | } | |||
| 201 | + | ||||
| 202 | + | // begin starts a new message in e: the fixed header and the field array, up | |||
| 203 | + | // to the body. Empty strings are left out of the header. | |||
| 204 | + | func (e *Encoder) begin(t Type, serial, replySerial uint32, dest, path, iface, member, errName, sig string) { | |||
| 205 | + | e.buf = append(e.buf[:0], 'l', byte(t), 0, 1, 0, 0, 0, 0) | |||
| 206 | + | e.buf = binary.LittleEndian.AppendUint32(e.buf, serial) | |||
| 207 | + | e.body, e.sig, e.err = 0, sig, nil | |||
| 208 | + | // A bus disconnects a client that sends an invalid name. | |||
| 209 | + | if dest != "" && !validBusName(dest) || iface != "" && !validInterface(iface) || | |||
| 210 | + | member != "" && !validMember(member) || errName != "" && !validInterface(errName) { | |||
| 211 | + | e.invalid() | |||
| 212 | + | } | |||
| 213 | + | fields := e.BeginArray('(') | |||
| 214 | + | str := func(code byte, typ, v string) { | |||
| 215 | + | if v == "" { | |||
| 216 | + | return | |||
| 217 | + | } | |||
| 218 | + | e.Struct() | |||
| 219 | + | e.Byte(code) | |||
| 220 | + | e.Signature(typ) | |||
| 221 | + | switch typ { | |||
| 222 | + | case "o": | |||
| 223 | + | e.ObjectPath(v) | |||
| 224 | + | case "g": | |||
| 225 | + | e.Signature(v) | |||
| 226 | + | default: | |||
| 227 | + | e.Str(v) | |||
| 228 | + | } | |||
| 229 | + | } | |||
| 230 | + | str(fieldPath, "o", path) | |||
| 231 | + | str(fieldInterface, "s", iface) | |||
| 232 | + | str(fieldMember, "s", member) | |||
| 233 | + | str(fieldErrorName, "s", errName) | |||
| 234 | + | if replySerial != 0 { | |||
| 235 | + | e.Struct() | |||
| 236 | + | e.Byte(fieldReplySerial) | |||
| 237 | + | e.Signature("u") | |||
| 238 | + | e.Uint32(replySerial) | |||
| 239 | + | } | |||
| 240 | + | str(fieldDestination, "s", dest) | |||
| 241 | + | str(fieldSignature, "g", sig) | |||
| 242 | + | e.EndArray(fields) | |||
| 243 | + | e.pad(8) | |||
| 244 | + | e.body = len(e.buf) | |||
| 245 | + | } | |||
| 246 | + | ||||
| 247 | + | // finish writes the body length and checks the body against the signature. | |||
| 248 | + | // It returns the bytes to send. | |||
| 249 | + | func (e *Encoder) finish() ([]byte, error) { | |||
| 250 | + | if e.body == 0 { | |||
| 251 | + | return nil, ErrInvalid | |||
| 252 | + | } | |||
| 253 | + | if e.err != nil { | |||
| 254 | + | return nil, e.err | |||
| 255 | + | } | |||
| 256 | + | n := len(e.buf) - e.body | |||
| 257 | + | if len(e.buf) > maxMessage || n > math.MaxUint32 { | |||
| 258 | + | return nil, ErrTooLarge | |||
| 259 | + | } | |||
| 260 | + | binary.LittleEndian.PutUint32(e.buf[4:], uint32(n)) | |||
| 261 | + | // Reading the body back with the signature proves it matches: a bus | |||
| 262 | + | // disconnects a client that sends a body that does not. | |||
| 263 | + | r := Reader{buf: e.buf[e.body:]} | |||
| 264 | + | r.Skip(e.sig) | |||
| 265 | + | if !r.Done() { | |||
| 266 | + | return nil, ErrInvalid | |||
| 267 | + | } | |||
| 268 | + | return e.buf, nil | |||
| 269 | + | } | |||
A wire.go +622 -0
| @@ -0,0 +1,622 @@ | |||
| 1 | + | package zerobus | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "encoding/binary" | |
| 5 | + | "errors" | |
| 6 | + | "math" | |
| 7 | + | "strings" | |
| 8 | + | "unicode/utf8" | |
| 9 | + | "unsafe" | |
| 10 | + | ) | |
| 11 | + | ||
| 12 | + | const ( | |
| 13 | + | maxMessage = 128 << 20 // the D-Bus limit for a whole message | |
| 14 | + | maxArray = 64 << 20 // the D-Bus limit for one array | |
| 15 | + | maxDepth = 64 // 32 array + 32 struct levels | |
| 16 | + | ) | |
| 17 | + | ||
| 18 | + | var ( | |
| 19 | + | // ErrMalformed reports bytes that do not follow the D-Bus wire format. | |
| 20 | + | ErrMalformed = errors.New("zerobus: malformed message") | |
| 21 | + | // ErrTooLarge reports a message or array over the D-Bus size limits. | |
| 22 | + | ErrTooLarge = errors.New("zerobus: message too large") | |
| 23 | + | // ErrInvalid reports an outgoing value D-Bus does not allow, such as a | |
| 24 | + | // string with a NUL byte or a body that does not match its signature. | |
| 25 | + | ErrInvalid = errors.New("zerobus: invalid value") | |
| 26 | + | ) | |
| 27 | + | ||
| 28 | + | // alignOf returns the alignment of the type that starts with signature byte c. | |
| 29 | + | func alignOf(c byte) int { | |
| 30 | + | switch c { | |
| 31 | + | case 'n', 'q': | |
| 32 | + | return 2 | |
| 33 | + | case 'b', 'i', 'u', 'h', 's', 'o', 'a': | |
| 34 | + | return 4 | |
| 35 | + | case 'x', 't', 'd', '(', '{': | |
| 36 | + | return 8 | |
| 37 | + | } | |
| 38 | + | return 1 // y, g, v | |
| 39 | + | } | |
| 40 | + | ||
| 41 | + | // pad rounds off up to a multiple of n, a power of two. | |
| 42 | + | func pad(off, n int) int { return (off + n - 1) &^ (n - 1) } | |
| 43 | + | ||
| 44 | + | // sigEnd returns the index just after the single complete type that starts | |
| 45 | + | // at sig[i], or -1 if there is none. | |
| 46 | + | func sigEnd(sig string, i, depth int) int { | |
| 47 | + | if i >= len(sig) || depth > maxDepth { | |
| 48 | + | return -1 | |
| 49 | + | } | |
| 50 | + | switch sig[i] { | |
| 51 | + | case 'y', 'b', 'n', 'q', 'i', 'u', 'x', 't', 'd', 'h', 's', 'o', 'g', 'v': | |
| 52 | + | return i + 1 | |
| 53 | + | case 'a': | |
| 54 | + | if i+1 < len(sig) && sig[i+1] == '{' { | |
| 55 | + | return dictEnd(sig, i+1, depth+1) | |
| 56 | + | } | |
| 57 | + | return sigEnd(sig, i+1, depth+1) | |
| 58 | + | case '(': | |
| 59 | + | j := i + 1 | |
| 60 | + | if j < len(sig) && sig[j] == ')' { | |
| 61 | + | return -1 // empty structs are not allowed | |
| 62 | + | } | |
| 63 | + | for j < len(sig) && sig[j] != ')' { | |
| 64 | + | if j = sigEnd(sig, j, depth+1); j < 0 { | |
| 65 | + | return -1 | |
| 66 | + | } | |
| 67 | + | } | |
| 68 | + | if j >= len(sig) { | |
| 69 | + | return -1 | |
| 70 | + | } | |
| 71 | + | return j + 1 | |
| 72 | + | } | |
| 73 | + | return -1 // includes '{': a dict entry is only valid as an array element | |
| 74 | + | } | |
| 75 | + | ||
| 76 | + | // dictEnd returns the index just after the dict entry type at sig[i] ('{'). | |
| 77 | + | // Its key must be a basic type and it holds exactly two types. | |
| 78 | + | func dictEnd(sig string, i, depth int) int { | |
| 79 | + | j := i + 1 | |
| 80 | + | if j >= len(sig) || !basic(sig[j]) { | |
| 81 | + | return -1 | |
| 82 | + | } | |
| 83 | + | if j = sigEnd(sig, j+1, depth+1); j < 0 || j >= len(sig) || sig[j] != '}' { | |
| 84 | + | return -1 | |
| 85 | + | } | |
| 86 | + | return j + 1 | |
| 87 | + | } | |
| 88 | + | ||
| 89 | + | func basic(c byte) bool { | |
| 90 | + | switch c { | |
| 91 | + | case 'y', 'b', 'n', 'q', 'i', 'u', 'x', 't', 'd', 'h', 's', 'o', 'g': | |
| 92 | + | return true | |
| 93 | + | } | |
| 94 | + | return false | |
| 95 | + | } | |
| 96 | + | ||
| 97 | + | // validSignature reports whether sig is a list of complete types that fits | |
| 98 | + | // in a D-Bus signature. | |
| 99 | + | func validSignature(sig string) bool { | |
| 100 | + | if len(sig) > 255 { | |
| 101 | + | return false | |
| 102 | + | } | |
| 103 | + | for i := 0; i < len(sig); { | |
| 104 | + | if i = sigEnd(sig, i, 0); i < 0 { | |
| 105 | + | return false | |
| 106 | + | } | |
| 107 | + | } | |
| 108 | + | return true | |
| 109 | + | } | |
| 110 | + | ||
| 111 | + | // validPath reports whether p is a valid object path. | |
| 112 | + | func validPath(p string) bool { | |
| 113 | + | if p == "/" { | |
| 114 | + | return true | |
| 115 | + | } | |
| 116 | + | if len(p) < 2 || p[0] != '/' || p[len(p)-1] == '/' { | |
| 117 | + | return false | |
| 118 | + | } | |
| 119 | + | for i := 1; i < len(p); i++ { | |
| 120 | + | c := p[i] | |
| 121 | + | switch { | |
| 122 | + | case c == '/': | |
| 123 | + | if p[i-1] == '/' { | |
| 124 | + | return false | |
| 125 | + | } | |
| 126 | + | case c >= 'a' && c <= 'z', c >= 'A' && c <= 'Z', c >= '0' && c <= '9', c == '_': | |
| 127 | + | default: | |
| 128 | + | return false | |
| 129 | + | } | |
| 130 | + | } | |
| 131 | + | return true | |
| 132 | + | } | |
| 133 | + | ||
| 134 | + | // validMember reports whether s is a valid member name: letters, digits and | |
| 135 | + | // '_', not starting with a digit. | |
| 136 | + | func validMember(s string) bool { | |
| 137 | + | if s == "" || len(s) > 255 || s[0] >= '0' && s[0] <= '9' { | |
| 138 | + | return false | |
| 139 | + | } | |
| 140 | + | for i := 0; i < len(s); i++ { | |
| 141 | + | if !nameChar(s[i]) { | |
| 142 | + | return false | |
| 143 | + | } | |
| 144 | + | } | |
| 145 | + | return true | |
| 146 | + | } | |
| 147 | + | ||
| 148 | + | // nameChar reports whether c may appear in a member or interface name. | |
| 149 | + | func nameChar(c byte) bool { | |
| 150 | + | return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c == '_' | |
| 151 | + | } | |
| 152 | + | ||
| 153 | + | // validInterface reports whether s is a valid interface or error name: two | |
| 154 | + | // or more member-like elements separated by dots. | |
| 155 | + | func validInterface(s string) bool { | |
| 156 | + | if len(s) > 255 { | |
| 157 | + | return false | |
| 158 | + | } | |
| 159 | + | n := 0 | |
| 160 | + | for e := range strings.SplitSeq(s, ".") { | |
| 161 | + | if !validMember(e) { | |
| 162 | + | return false | |
| 163 | + | } | |
| 164 | + | n++ | |
| 165 | + | } | |
| 166 | + | return n >= 2 | |
| 167 | + | } | |
| 168 | + | ||
| 169 | + | // validBusName reports whether s is a valid unique (":1.42") or well-known | |
| 170 | + | // ("org.example.App") bus name. | |
| 171 | + | func validBusName(s string) bool { | |
| 172 | + | if s == "" || len(s) > 255 { | |
| 173 | + | return false | |
| 174 | + | } | |
| 175 | + | unique := s[0] == ':' | |
| 176 | + | if unique { | |
| 177 | + | s = s[1:] | |
| 178 | + | } | |
| 179 | + | n := 0 | |
| 180 | + | for e := range strings.SplitSeq(s, ".") { | |
| 181 | + | if e == "" || !unique && e[0] >= '0' && e[0] <= '9' { | |
| 182 | + | return false | |
| 183 | + | } | |
| 184 | + | for i := 0; i < len(e); i++ { | |
| 185 | + | if !nameChar(e[i]) && e[i] != '-' { | |
| 186 | + | return false | |
| 187 | + | } | |
| 188 | + | } | |
| 189 | + | n++ | |
| 190 | + | } | |
| 191 | + | return n >= 2 | |
| 192 | + | } | |
| 193 | + | ||
| 194 | + | // Reader decodes D-Bus values in place. | |
| 195 | + | // | |
| 196 | + | // Strings and byte slices it returns point into the message buffer: they are | |
| 197 | + | // valid only until the next Conn.ReadMessage. Copy them (strings.Clone, | |
| 198 | + | // bytes.Clone) to keep them longer. | |
| 199 | + | // | |
| 200 | + | // Read values in the order of the message signature. Errors are sticky: | |
| 201 | + | // after the first one, every read returns a zero value and Err reports it. | |
| 202 | + | type Reader struct { | |
| 203 | + | buf []byte | |
| 204 | + | off int | |
| 205 | + | big bool | |
| 206 | + | err error | |
| 207 | + | } | |
| 208 | + | ||
| 209 | + | // Err returns the first error the reader met, or nil. | |
| 210 | + | func (r *Reader) Err() error { return r.err } | |
| 211 | + | ||
| 212 | + | // Done reports whether every byte was read without error. | |
| 213 | + | func (r *Reader) Done() bool { return r.err == nil && r.off == len(r.buf) } | |
| 214 | + | ||
| 215 | + | func (r *Reader) fail() { | |
| 216 | + | if r.err == nil { | |
| 217 | + | r.err = ErrMalformed | |
| 218 | + | } | |
| 219 | + | } | |
| 220 | + | ||
| 221 | + | // take aligns to align and returns the next size bytes. | |
| 222 | + | func (r *Reader) take(align, size int) []byte { | |
| 223 | + | if r.err != nil { | |
| 224 | + | return nil | |
| 225 | + | } | |
| 226 | + | off := pad(r.off, align) | |
| 227 | + | if off > len(r.buf)-size { | |
| 228 | + | r.fail() | |
| 229 | + | return nil | |
| 230 | + | } | |
| 231 | + | r.off = off + size | |
| 232 | + | return r.buf[off:r.off] | |
| 233 | + | } | |
| 234 | + | ||
| 235 | + | // Byte reads a BYTE (y). | |
| 236 | + | func (r *Reader) Byte() byte { | |
| 237 | + | if b := r.take(1, 1); b != nil { | |
| 238 | + | return b[0] | |
| 239 | + | } | |
| 240 | + | return 0 | |
| 241 | + | } | |
| 242 | + | ||
| 243 | + | // Bool reads a BOOLEAN (b). | |
| 244 | + | func (r *Reader) Bool() bool { | |
| 245 | + | v := r.Uint32() | |
| 246 | + | if v > 1 { | |
| 247 | + | r.fail() | |
| 248 | + | return false | |
| 249 | + | } | |
| 250 | + | return v == 1 | |
| 251 | + | } | |
| 252 | + | ||
| 253 | + | // Int16 reads an INT16 (n). | |
| 254 | + | func (r *Reader) Int16() int16 { return int16(r.Uint16()) } | |
| 255 | + | ||
| 256 | + | // Uint16 reads a UINT16 (q). | |
| 257 | + | func (r *Reader) Uint16() uint16 { | |
| 258 | + | b := r.take(2, 2) | |
| 259 | + | if b == nil { | |
| 260 | + | return 0 | |
| 261 | + | } | |
| 262 | + | if r.big { | |
| 263 | + | return binary.BigEndian.Uint16(b) | |
| 264 | + | } | |
| 265 | + | return binary.LittleEndian.Uint16(b) | |
| 266 | + | } | |
| 267 | + | ||
| 268 | + | // Int32 reads an INT32 (i). | |
| 269 | + | func (r *Reader) Int32() int32 { return int32(r.Uint32()) } | |
| 270 | + | ||
| 271 | + | // Uint32 reads a UINT32 (u). | |
| 272 | + | func (r *Reader) Uint32() uint32 { | |
| 273 | + | b := r.take(4, 4) | |
| 274 | + | if b == nil { | |
| 275 | + | return 0 | |
| 276 | + | } | |
| 277 | + | if r.big { | |
| 278 | + | return binary.BigEndian.Uint32(b) | |
| 279 | + | } | |
| 280 | + | return binary.LittleEndian.Uint32(b) | |
| 281 | + | } | |
| 282 | + | ||
| 283 | + | // UnixFD reads a UNIX_FD (h): an index into the fds sent with the message. | |
| 284 | + | // zerobus does not receive file descriptors, so the index has nothing to | |
| 285 | + | // point to; it is only read so the following values can be. | |
| 286 | + | func (r *Reader) UnixFD() uint32 { return r.Uint32() } | |
| 287 | + | ||
| 288 | + | // Int64 reads an INT64 (x). | |
| 289 | + | func (r *Reader) Int64() int64 { return int64(r.Uint64()) } | |
| 290 | + | ||
| 291 | + | // Uint64 reads a UINT64 (t). | |
| 292 | + | func (r *Reader) Uint64() uint64 { | |
| 293 | + | b := r.take(8, 8) | |
| 294 | + | if b == nil { | |
| 295 | + | return 0 | |
| 296 | + | } | |
| 297 | + | if r.big { | |
| 298 | + | return binary.BigEndian.Uint64(b) | |
| 299 | + | } | |
| 300 | + | return binary.LittleEndian.Uint64(b) | |
| 301 | + | } | |
| 302 | + | ||
| 303 | + | // Float64 reads a DOUBLE (d). | |
| 304 | + | func (r *Reader) Float64() float64 { return math.Float64frombits(r.Uint64()) } | |
| 305 | + | ||
| 306 | + | // Str reads a STRING (s). The result points into the message buffer. | |
| 307 | + | func (r *Reader) Str() string { return r.str(int(r.Uint32())) } | |
| 308 | + | ||
| 309 | + | // ObjectPath reads an OBJECT_PATH (o). The result points into the message | |
| 310 | + | // buffer. | |
| 311 | + | func (r *Reader) ObjectPath() string { return r.Str() } | |
| 312 | + | ||
| 313 | + | // Signature reads a SIGNATURE (g). The result points into the message buffer. | |
| 314 | + | func (r *Reader) Signature() string { return r.str(int(r.Byte())) } | |
| 315 | + | ||
| 316 | + | // str returns the next n bytes as a string and skips the NUL after them. | |
| 317 | + | func (r *Reader) str(n int) string { | |
| 318 | + | if r.err != nil { | |
| 319 | + | return "" | |
| 320 | + | } | |
| 321 | + | if n < 0 || n > len(r.buf)-r.off-1 || r.buf[r.off+n] != 0 { | |
| 322 | + | r.fail() | |
| 323 | + | return "" | |
| 324 | + | } | |
| 325 | + | s := unsafe.String(unsafe.SliceData(r.buf[r.off:]), n) | |
| 326 | + | r.off += n + 1 | |
| 327 | + | return s | |
| 328 | + | } | |
| 329 | + | ||
| 330 | + | // Variant reads the signature of a VARIANT (v) and returns it. Read the | |
| 331 | + | // value that follows with the matching method, or skip it with Skip. | |
| 332 | + | func (r *Reader) Variant() string { | |
| 333 | + | sig := r.Signature() | |
| 334 | + | if r.err == nil && sigEnd(sig, 0, 0) != len(sig) { | |
| 335 | + | r.fail() | |
| 336 | + | return "" | |
| 337 | + | } | |
| 338 | + | return sig | |
| 339 | + | } | |
| 340 | + | ||
| 341 | + | // Struct aligns the reader to the start of a STRUCT or DICT_ENTRY. Call it | |
| 342 | + | // before reading the first field. | |
| 343 | + | func (r *Reader) Struct() { r.take(8, 0) } | |
| 344 | + | ||
| 345 | + | // Array reads the header of an ARRAY whose element type starts with the | |
| 346 | + | // signature byte elem, and returns the offset where the array ends. Read the | |
| 347 | + | // elements while More returns true: | |
| 348 | + | // | |
| 349 | + | // end := r.Array('s') | |
| 350 | + | // for r.More(end) { | |
| 351 | + | // name := r.Str() | |
| 352 | + | // } | |
| 353 | + | func (r *Reader) Array(elem byte) int { | |
| 354 | + | n := r.Uint32() | |
| 355 | + | if r.err != nil { | |
| 356 | + | return r.off | |
| 357 | + | } | |
| 358 | + | if n > maxArray { | |
| 359 | + | r.fail() | |
| 360 | + | return r.off | |
| 361 | + | } | |
| 362 | + | off := pad(r.off, alignOf(elem)) | |
| 363 | + | if off > len(r.buf)-int(n) { | |
| 364 | + | r.fail() | |
| 365 | + | return r.off | |
| 366 | + | } | |
| 367 | + | r.off = off | |
| 368 | + | return off + int(n) | |
| 369 | + | } | |
| 370 | + | ||
| 371 | + | // More reports whether the array that ends at end has another element. | |
| 372 | + | func (r *Reader) More(end int) bool { | |
| 373 | + | if r.err != nil { | |
| 374 | + | return false | |
| 375 | + | } | |
| 376 | + | if r.off < end { | |
| 377 | + | return true | |
| 378 | + | } | |
| 379 | + | if r.off > end { | |
| 380 | + | r.fail() | |
| 381 | + | } | |
| 382 | + | return false | |
| 383 | + | } | |
| 384 | + | ||
| 385 | + | // SkipTo moves to end, the value returned by Array. Use it to leave an array | |
| 386 | + | // before its last element. | |
| 387 | + | func (r *Reader) SkipTo(end int) { | |
| 388 | + | if r.err != nil { | |
| 389 | + | return | |
| 390 | + | } | |
| 391 | + | if end < r.off || end > len(r.buf) { | |
| 392 | + | r.fail() | |
| 393 | + | return | |
| 394 | + | } | |
| 395 | + | r.off = end | |
| 396 | + | } | |
| 397 | + | ||
| 398 | + | // ByteArray reads an ARRAY of BYTE (ay). The result points into the message | |
| 399 | + | // buffer. | |
| 400 | + | func (r *Reader) ByteArray() []byte { | |
| 401 | + | end := r.Array('y') | |
| 402 | + | if r.err != nil { | |
| 403 | + | return nil | |
| 404 | + | } | |
| 405 | + | b := r.buf[r.off:end:end] | |
| 406 | + | r.off = end | |
| 407 | + | return b | |
| 408 | + | } | |
| 409 | + | ||
| 410 | + | // Skip reads past values of the given signature. Arrays are skipped by their | |
| 411 | + | // length, without reading their elements. | |
| 412 | + | func (r *Reader) Skip(sig string) { r.skip(sig, 0) } | |
| 413 | + | ||
| 414 | + | func (r *Reader) skip(sig string, depth int) { | |
| 415 | + | for i := 0; i < len(sig) && r.err == nil; { | |
| 416 | + | i = r.skipOne(sig, i, depth) | |
| 417 | + | } | |
| 418 | + | } | |
| 419 | + | ||
| 420 | + | // skipOne skips the value of the type at sig[i] and returns the index of the | |
| 421 | + | // next type. | |
| 422 | + | func (r *Reader) skipOne(sig string, i, depth int) int { | |
| 423 | + | if depth > maxDepth { | |
| 424 | + | r.fail() | |
| 425 | + | return len(sig) | |
| 426 | + | } | |
| 427 | + | switch c := sig[i]; c { | |
| 428 | + | case 'y': | |
| 429 | + | r.take(1, 1) | |
| 430 | + | case 'n', 'q': | |
| 431 | + | r.take(2, 2) | |
| 432 | + | case 'b', 'i', 'u', 'h': | |
| 433 | + | r.take(4, 4) | |
| 434 | + | case 'x', 't', 'd': | |
| 435 | + | r.take(8, 8) | |
| 436 | + | case 's', 'o': | |
| 437 | + | r.Str() | |
| 438 | + | case 'g': | |
| 439 | + | r.Signature() | |
| 440 | + | case 'v': | |
| 441 | + | r.skip(r.Variant(), depth+1) | |
| 442 | + | case 'a': | |
| 443 | + | end := sigEnd(sig, i, depth) | |
| 444 | + | if end < 0 { | |
| 445 | + | r.fail() | |
| 446 | + | return len(sig) | |
| 447 | + | } | |
| 448 | + | r.SkipTo(r.Array(sig[i+1])) | |
| 449 | + | return end | |
| 450 | + | case '(', '{': | |
| 451 | + | closer := byte(')') | |
| 452 | + | if c == '{' { | |
| 453 | + | closer = '}' | |
| 454 | + | } | |
| 455 | + | r.Struct() | |
| 456 | + | j := i + 1 | |
| 457 | + | for j < len(sig) && sig[j] != closer && r.err == nil { | |
| 458 | + | j = r.skipOne(sig, j, depth+1) | |
| 459 | + | } | |
| 460 | + | if j >= len(sig) { | |
| 461 | + | r.fail() | |
| 462 | + | return len(sig) | |
| 463 | + | } | |
| 464 | + | return j + 1 | |
| 465 | + | default: | |
| 466 | + | r.fail() | |
| 467 | + | return len(sig) | |
| 468 | + | } | |
| 469 | + | return i + 1 | |
| 470 | + | } | |
| 471 | + | ||
| 472 | + | // Encoder appends D-Bus values to an outgoing message. Get one from | |
| 473 | + | // Conn.NewCall, Conn.Signal, Conn.Reply or Conn.Error, write the body values | |
| 474 | + | // in the order of the message signature, then call Conn.Send. | |
| 475 | + | // | |
| 476 | + | // Errors are sticky and reported by Conn.Send. | |
| 477 | + | type Encoder struct { | |
| 478 | + | buf []byte | |
| 479 | + | body int // offset of the body; 0 when no message is being built | |
| 480 | + | sig string // body signature, checked by finish | |
| 481 | + | err error | |
| 482 | + | } | |
| 483 | + | ||
| 484 | + | func (e *Encoder) invalid() { | |
| 485 | + | if e.err == nil { | |
| 486 | + | e.err = ErrInvalid | |
| 487 | + | } | |
| 488 | + | } | |
| 489 | + | ||
| 490 | + | func (e *Encoder) pad(n int) { | |
| 491 | + | for len(e.buf)&(n-1) != 0 { | |
| 492 | + | e.buf = append(e.buf, 0) | |
| 493 | + | } | |
| 494 | + | } | |
| 495 | + | ||
| 496 | + | // Byte writes a BYTE (y). | |
| 497 | + | func (e *Encoder) Byte(v byte) { e.buf = append(e.buf, v) } | |
| 498 | + | ||
| 499 | + | // Bool writes a BOOLEAN (b). | |
| 500 | + | func (e *Encoder) Bool(v bool) { | |
| 501 | + | var u uint32 | |
| 502 | + | if v { | |
| 503 | + | u = 1 | |
| 504 | + | } | |
| 505 | + | e.Uint32(u) | |
| 506 | + | } | |
| 507 | + | ||
| 508 | + | // Int16 writes an INT16 (n). | |
| 509 | + | func (e *Encoder) Int16(v int16) { e.Uint16(uint16(v)) } | |
| 510 | + | ||
| 511 | + | // Uint16 writes a UINT16 (q). | |
| 512 | + | func (e *Encoder) Uint16(v uint16) { | |
| 513 | + | e.pad(2) | |
| 514 | + | e.buf = binary.LittleEndian.AppendUint16(e.buf, v) | |
| 515 | + | } | |
| 516 | + | ||
| 517 | + | // Int32 writes an INT32 (i). | |
| 518 | + | func (e *Encoder) Int32(v int32) { e.Uint32(uint32(v)) } | |
| 519 | + | ||
| 520 | + | // Uint32 writes a UINT32 (u). | |
| 521 | + | func (e *Encoder) Uint32(v uint32) { | |
| 522 | + | e.pad(4) | |
| 523 | + | e.buf = binary.LittleEndian.AppendUint32(e.buf, v) | |
| 524 | + | } | |
| 525 | + | ||
| 526 | + | // Int64 writes an INT64 (x). | |
| 527 | + | func (e *Encoder) Int64(v int64) { e.Uint64(uint64(v)) } | |
| 528 | + | ||
| 529 | + | // Uint64 writes a UINT64 (t). | |
| 530 | + | func (e *Encoder) Uint64(v uint64) { | |
| 531 | + | e.pad(8) | |
| 532 | + | e.buf = binary.LittleEndian.AppendUint64(e.buf, v) | |
| 533 | + | } | |
| 534 | + | ||
| 535 | + | // Float64 writes a DOUBLE (d). | |
| 536 | + | func (e *Encoder) Float64(v float64) { e.Uint64(math.Float64bits(v)) } | |
| 537 | + | ||
| 538 | + | // Str writes a STRING (s). | |
| 539 | + | func (e *Encoder) Str(s string) { | |
| 540 | + | e.Uint32(uint32(len(s))) | |
| 541 | + | e.text(s) | |
| 542 | + | } | |
| 543 | + | ||
| 544 | + | // ObjectPath writes an OBJECT_PATH (o). | |
| 545 | + | func (e *Encoder) ObjectPath(p string) { | |
| 546 | + | if !validPath(p) { | |
| 547 | + | e.invalid() | |
| 548 | + | } | |
| 549 | + | e.Str(p) | |
| 550 | + | } | |
| 551 | + | ||
| 552 | + | // Signature writes a SIGNATURE (g). | |
| 553 | + | func (e *Encoder) Signature(sig string) { | |
| 554 | + | if !validSignature(sig) { | |
| 555 | + | e.invalid() | |
| 556 | + | } | |
| 557 | + | e.Byte(byte(len(sig))) | |
| 558 | + | e.text(sig) | |
| 559 | + | } | |
| 560 | + | ||
| 561 | + | // text appends s and its NUL terminator. | |
| 562 | + | func (e *Encoder) text(s string) { | |
| 563 | + | // The bus disconnects a client that sends a NUL or invalid UTF-8. | |
| 564 | + | if strings.IndexByte(s, 0) >= 0 || !utf8.ValidString(s) { | |
| 565 | + | e.invalid() | |
| 566 | + | } | |
| 567 | + | e.buf = append(e.buf, s...) | |
| 568 | + | e.buf = append(e.buf, 0) | |
| 569 | + | } | |
| 570 | + | ||
| 571 | + | // Variant writes the signature of a VARIANT (v). Write its value next. | |
| 572 | + | func (e *Encoder) Variant(sig string) { | |
| 573 | + | if sigEnd(sig, 0, 0) != len(sig) { | |
| 574 | + | e.invalid() | |
| 575 | + | } | |
| 576 | + | e.Signature(sig) | |
| 577 | + | } | |
| 578 | + | ||
| 579 | + | // Struct aligns the encoder to the start of a STRUCT or DICT_ENTRY. Call it | |
| 580 | + | // before writing the first field. | |
| 581 | + | func (e *Encoder) Struct() { e.pad(8) } | |
| 582 | + | ||
| 583 | + | // ArrayMark is an array being written; pass it to EndArray. | |
| 584 | + | type ArrayMark struct{ at, start int } | |
| 585 | + | ||
| 586 | + | // BeginArray starts an ARRAY whose element type starts with the signature | |
| 587 | + | // byte elem. Write the elements, then call EndArray: | |
| 588 | + | // | |
| 589 | + | // a := e.BeginArray('s') | |
| 590 | + | // e.Str("one") | |
| 591 | + | // e.Str("two") | |
| 592 | + | // e.EndArray(a) | |
| 593 | + | func (e *Encoder) BeginArray(elem byte) ArrayMark { | |
| 594 | + | e.pad(4) | |
| 595 | + | at := len(e.buf) | |
| 596 | + | e.buf = append(e.buf, 0, 0, 0, 0) | |
| 597 | + | e.pad(alignOf(elem)) | |
| 598 | + | return ArrayMark{at: at, start: len(e.buf)} | |
| 599 | + | } | |
| 600 | + | ||
| 601 | + | // EndArray finishes the array started by BeginArray. | |
| 602 | + | func (e *Encoder) EndArray(a ArrayMark) { | |
| 603 | + | n := len(e.buf) - a.start | |
| 604 | + | if n > maxArray { | |
| 605 | + | e.invalid() | |
| 606 | + | return | |
| 607 | + | } | |
| 608 | + | binary.LittleEndian.PutUint32(e.buf[a.at:], uint32(n)) | |
| 609 | + | } | |
| 610 | + | ||
| 611 | + | // ByteArray writes an ARRAY of BYTE (ay). | |
| 612 | + | func (e *Encoder) ByteArray(b []byte) { | |
| 613 | + | e.Uint32(uint32(len(b))) | |
| 614 | + | e.buf = append(e.buf, b...) | |
| 615 | + | } | |
| 616 | + | ||
| 617 | + | // SetFlags sets the flags of the message being built. | |
| 618 | + | func (e *Encoder) SetFlags(f Flags) { | |
| 619 | + | if e.body != 0 { | |
| 620 | + | e.buf[2] = byte(f) | |
| 621 | + | } | |
| 622 | + | } | |
| @@ -0,0 +1,622 @@ | |||||
| 1 | + | package zerobus | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "encoding/binary" | |||
| 5 | + | "errors" | |||
| 6 | + | "math" | |||
| 7 | + | "strings" | |||
| 8 | + | "unicode/utf8" | |||
| 9 | + | "unsafe" | |||
| 10 | + | ) | |||
| 11 | + | ||||
| 12 | + | const ( | |||
| 13 | + | maxMessage = 128 << 20 // the D-Bus limit for a whole message | |||
| 14 | + | maxArray = 64 << 20 // the D-Bus limit for one array | |||
| 15 | + | maxDepth = 64 // 32 array + 32 struct levels | |||
| 16 | + | ) | |||
| 17 | + | ||||
| 18 | + | var ( | |||
| 19 | + | // ErrMalformed reports bytes that do not follow the D-Bus wire format. | |||
| 20 | + | ErrMalformed = errors.New("zerobus: malformed message") | |||
| 21 | + | // ErrTooLarge reports a message or array over the D-Bus size limits. | |||
| 22 | + | ErrTooLarge = errors.New("zerobus: message too large") | |||
| 23 | + | // ErrInvalid reports an outgoing value D-Bus does not allow, such as a | |||
| 24 | + | // string with a NUL byte or a body that does not match its signature. | |||
| 25 | + | ErrInvalid = errors.New("zerobus: invalid value") | |||
| 26 | + | ) | |||
| 27 | + | ||||
| 28 | + | // alignOf returns the alignment of the type that starts with signature byte c. | |||
| 29 | + | func alignOf(c byte) int { | |||
| 30 | + | switch c { | |||
| 31 | + | case 'n', 'q': | |||
| 32 | + | return 2 | |||
| 33 | + | case 'b', 'i', 'u', 'h', 's', 'o', 'a': | |||
| 34 | + | return 4 | |||
| 35 | + | case 'x', 't', 'd', '(', '{': | |||
| 36 | + | return 8 | |||
| 37 | + | } | |||
| 38 | + | return 1 // y, g, v | |||
| 39 | + | } | |||
| 40 | + | ||||
| 41 | + | // pad rounds off up to a multiple of n, a power of two. | |||
| 42 | + | func pad(off, n int) int { return (off + n - 1) &^ (n - 1) } | |||
| 43 | + | ||||
| 44 | + | // sigEnd returns the index just after the single complete type that starts | |||
| 45 | + | // at sig[i], or -1 if there is none. | |||
| 46 | + | func sigEnd(sig string, i, depth int) int { | |||
| 47 | + | if i >= len(sig) || depth > maxDepth { | |||
| 48 | + | return -1 | |||
| 49 | + | } | |||
| 50 | + | switch sig[i] { | |||
| 51 | + | case 'y', 'b', 'n', 'q', 'i', 'u', 'x', 't', 'd', 'h', 's', 'o', 'g', 'v': | |||
| 52 | + | return i + 1 | |||
| 53 | + | case 'a': | |||
| 54 | + | if i+1 < len(sig) && sig[i+1] == '{' { | |||
| 55 | + | return dictEnd(sig, i+1, depth+1) | |||
| 56 | + | } | |||
| 57 | + | return sigEnd(sig, i+1, depth+1) | |||
| 58 | + | case '(': | |||
| 59 | + | j := i + 1 | |||
| 60 | + | if j < len(sig) && sig[j] == ')' { | |||
| 61 | + | return -1 // empty structs are not allowed | |||
| 62 | + | } | |||
| 63 | + | for j < len(sig) && sig[j] != ')' { | |||
| 64 | + | if j = sigEnd(sig, j, depth+1); j < 0 { | |||
| 65 | + | return -1 | |||
| 66 | + | } | |||
| 67 | + | } | |||
| 68 | + | if j >= len(sig) { | |||
| 69 | + | return -1 | |||
| 70 | + | } | |||
| 71 | + | return j + 1 | |||
| 72 | + | } | |||
| 73 | + | return -1 // includes '{': a dict entry is only valid as an array element | |||
| 74 | + | } | |||
| 75 | + | ||||
| 76 | + | // dictEnd returns the index just after the dict entry type at sig[i] ('{'). | |||
| 77 | + | // Its key must be a basic type and it holds exactly two types. | |||
| 78 | + | func dictEnd(sig string, i, depth int) int { | |||
| 79 | + | j := i + 1 | |||
| 80 | + | if j >= len(sig) || !basic(sig[j]) { | |||
| 81 | + | return -1 | |||
| 82 | + | } | |||
| 83 | + | if j = sigEnd(sig, j+1, depth+1); j < 0 || j >= len(sig) || sig[j] != '}' { | |||
| 84 | + | return -1 | |||
| 85 | + | } | |||
| 86 | + | return j + 1 | |||
| 87 | + | } | |||
| 88 | + | ||||
| 89 | + | func basic(c byte) bool { | |||
| 90 | + | switch c { | |||
| 91 | + | case 'y', 'b', 'n', 'q', 'i', 'u', 'x', 't', 'd', 'h', 's', 'o', 'g': | |||
| 92 | + | return true | |||
| 93 | + | } | |||
| 94 | + | return false | |||
| 95 | + | } | |||
| 96 | + | ||||
| 97 | + | // validSignature reports whether sig is a list of complete types that fits | |||
| 98 | + | // in a D-Bus signature. | |||
| 99 | + | func validSignature(sig string) bool { | |||
| 100 | + | if len(sig) > 255 { | |||
| 101 | + | return false | |||
| 102 | + | } | |||
| 103 | + | for i := 0; i < len(sig); { | |||
| 104 | + | if i = sigEnd(sig, i, 0); i < 0 { | |||
| 105 | + | return false | |||
| 106 | + | } | |||
| 107 | + | } | |||
| 108 | + | return true | |||
| 109 | + | } | |||
| 110 | + | ||||
| 111 | + | // validPath reports whether p is a valid object path. | |||
| 112 | + | func validPath(p string) bool { | |||
| 113 | + | if p == "/" { | |||
| 114 | + | return true | |||
| 115 | + | } | |||
| 116 | + | if len(p) < 2 || p[0] != '/' || p[len(p)-1] == '/' { | |||
| 117 | + | return false | |||
| 118 | + | } | |||
| 119 | + | for i := 1; i < len(p); i++ { | |||
| 120 | + | c := p[i] | |||
| 121 | + | switch { | |||
| 122 | + | case c == '/': | |||
| 123 | + | if p[i-1] == '/' { | |||
| 124 | + | return false | |||
| 125 | + | } | |||
| 126 | + | case c >= 'a' && c <= 'z', c >= 'A' && c <= 'Z', c >= '0' && c <= '9', c == '_': | |||
| 127 | + | default: | |||
| 128 | + | return false | |||
| 129 | + | } | |||
| 130 | + | } | |||
| 131 | + | return true | |||
| 132 | + | } | |||
| 133 | + | ||||
| 134 | + | // validMember reports whether s is a valid member name: letters, digits and | |||
| 135 | + | // '_', not starting with a digit. | |||
| 136 | + | func validMember(s string) bool { | |||
| 137 | + | if s == "" || len(s) > 255 || s[0] >= '0' && s[0] <= '9' { | |||
| 138 | + | return false | |||
| 139 | + | } | |||
| 140 | + | for i := 0; i < len(s); i++ { | |||
| 141 | + | if !nameChar(s[i]) { | |||
| 142 | + | return false | |||
| 143 | + | } | |||
| 144 | + | } | |||
| 145 | + | return true | |||
| 146 | + | } | |||
| 147 | + | ||||
| 148 | + | // nameChar reports whether c may appear in a member or interface name. | |||
| 149 | + | func nameChar(c byte) bool { | |||
| 150 | + | return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c == '_' | |||
| 151 | + | } | |||
| 152 | + | ||||
| 153 | + | // validInterface reports whether s is a valid interface or error name: two | |||
| 154 | + | // or more member-like elements separated by dots. | |||
| 155 | + | func validInterface(s string) bool { | |||
| 156 | + | if len(s) > 255 { | |||
| 157 | + | return false | |||
| 158 | + | } | |||
| 159 | + | n := 0 | |||
| 160 | + | for e := range strings.SplitSeq(s, ".") { | |||
| 161 | + | if !validMember(e) { | |||
| 162 | + | return false | |||
| 163 | + | } | |||
| 164 | + | n++ | |||
| 165 | + | } | |||
| 166 | + | return n >= 2 | |||
| 167 | + | } | |||
| 168 | + | ||||
| 169 | + | // validBusName reports whether s is a valid unique (":1.42") or well-known | |||
| 170 | + | // ("org.example.App") bus name. | |||
| 171 | + | func validBusName(s string) bool { | |||
| 172 | + | if s == "" || len(s) > 255 { | |||
| 173 | + | return false | |||
| 174 | + | } | |||
| 175 | + | unique := s[0] == ':' | |||
| 176 | + | if unique { | |||
| 177 | + | s = s[1:] | |||
| 178 | + | } | |||
| 179 | + | n := 0 | |||
| 180 | + | for e := range strings.SplitSeq(s, ".") { | |||
| 181 | + | if e == "" || !unique && e[0] >= '0' && e[0] <= '9' { | |||
| 182 | + | return false | |||
| 183 | + | } | |||
| 184 | + | for i := 0; i < len(e); i++ { | |||
| 185 | + | if !nameChar(e[i]) && e[i] != '-' { | |||
| 186 | + | return false | |||
| 187 | + | } | |||
| 188 | + | } | |||
| 189 | + | n++ | |||
| 190 | + | } | |||
| 191 | + | return n >= 2 | |||
| 192 | + | } | |||
| 193 | + | ||||
| 194 | + | // Reader decodes D-Bus values in place. | |||
| 195 | + | // | |||
| 196 | + | // Strings and byte slices it returns point into the message buffer: they are | |||
| 197 | + | // valid only until the next Conn.ReadMessage. Copy them (strings.Clone, | |||
| 198 | + | // bytes.Clone) to keep them longer. | |||
| 199 | + | // | |||
| 200 | + | // Read values in the order of the message signature. Errors are sticky: | |||
| 201 | + | // after the first one, every read returns a zero value and Err reports it. | |||
| 202 | + | type Reader struct { | |||
| 203 | + | buf []byte | |||
| 204 | + | off int | |||
| 205 | + | big bool | |||
| 206 | + | err error | |||
| 207 | + | } | |||
| 208 | + | ||||
| 209 | + | // Err returns the first error the reader met, or nil. | |||
| 210 | + | func (r *Reader) Err() error { return r.err } | |||
| 211 | + | ||||
| 212 | + | // Done reports whether every byte was read without error. | |||
| 213 | + | func (r *Reader) Done() bool { return r.err == nil && r.off == len(r.buf) } | |||
| 214 | + | ||||
| 215 | + | func (r *Reader) fail() { | |||
| 216 | + | if r.err == nil { | |||
| 217 | + | r.err = ErrMalformed | |||
| 218 | + | } | |||
| 219 | + | } | |||
| 220 | + | ||||
| 221 | + | // take aligns to align and returns the next size bytes. | |||
| 222 | + | func (r *Reader) take(align, size int) []byte { | |||
| 223 | + | if r.err != nil { | |||
| 224 | + | return nil | |||
| 225 | + | } | |||
| 226 | + | off := pad(r.off, align) | |||
| 227 | + | if off > len(r.buf)-size { | |||
| 228 | + | r.fail() | |||
| 229 | + | return nil | |||
| 230 | + | } | |||
| 231 | + | r.off = off + size | |||
| 232 | + | return r.buf[off:r.off] | |||
| 233 | + | } | |||
| 234 | + | ||||
| 235 | + | // Byte reads a BYTE (y). | |||
| 236 | + | func (r *Reader) Byte() byte { | |||
| 237 | + | if b := r.take(1, 1); b != nil { | |||
| 238 | + | return b[0] | |||
| 239 | + | } | |||
| 240 | + | return 0 | |||
| 241 | + | } | |||
| 242 | + | ||||
| 243 | + | // Bool reads a BOOLEAN (b). | |||
| 244 | + | func (r *Reader) Bool() bool { | |||
| 245 | + | v := r.Uint32() | |||
| 246 | + | if v > 1 { | |||
| 247 | + | r.fail() | |||
| 248 | + | return false | |||
| 249 | + | } | |||
| 250 | + | return v == 1 | |||
| 251 | + | } | |||
| 252 | + | ||||
| 253 | + | // Int16 reads an INT16 (n). | |||
| 254 | + | func (r *Reader) Int16() int16 { return int16(r.Uint16()) } | |||
| 255 | + | ||||
| 256 | + | // Uint16 reads a UINT16 (q). | |||
| 257 | + | func (r *Reader) Uint16() uint16 { | |||
| 258 | + | b := r.take(2, 2) | |||
| 259 | + | if b == nil { | |||
| 260 | + | return 0 | |||
| 261 | + | } | |||
| 262 | + | if r.big { | |||
| 263 | + | return binary.BigEndian.Uint16(b) | |||
| 264 | + | } | |||
| 265 | + | return binary.LittleEndian.Uint16(b) | |||
| 266 | + | } | |||
| 267 | + | ||||
| 268 | + | // Int32 reads an INT32 (i). | |||
| 269 | + | func (r *Reader) Int32() int32 { return int32(r.Uint32()) } | |||
| 270 | + | ||||
| 271 | + | // Uint32 reads a UINT32 (u). | |||
| 272 | + | func (r *Reader) Uint32() uint32 { | |||
| 273 | + | b := r.take(4, 4) | |||
| 274 | + | if b == nil { | |||
| 275 | + | return 0 | |||
| 276 | + | } | |||
| 277 | + | if r.big { | |||
| 278 | + | return binary.BigEndian.Uint32(b) | |||
| 279 | + | } | |||
| 280 | + | return binary.LittleEndian.Uint32(b) | |||
| 281 | + | } | |||
| 282 | + | ||||
| 283 | + | // UnixFD reads a UNIX_FD (h): an index into the fds sent with the message. | |||
| 284 | + | // zerobus does not receive file descriptors, so the index has nothing to | |||
| 285 | + | // point to; it is only read so the following values can be. | |||
| 286 | + | func (r *Reader) UnixFD() uint32 { return r.Uint32() } | |||
| 287 | + | ||||
| 288 | + | // Int64 reads an INT64 (x). | |||
| 289 | + | func (r *Reader) Int64() int64 { return int64(r.Uint64()) } | |||
| 290 | + | ||||
| 291 | + | // Uint64 reads a UINT64 (t). | |||
| 292 | + | func (r *Reader) Uint64() uint64 { | |||
| 293 | + | b := r.take(8, 8) | |||
| 294 | + | if b == nil { | |||
| 295 | + | return 0 | |||
| 296 | + | } | |||
| 297 | + | if r.big { | |||
| 298 | + | return binary.BigEndian.Uint64(b) | |||
| 299 | + | } | |||
| 300 | + | return binary.LittleEndian.Uint64(b) | |||
| 301 | + | } | |||
| 302 | + | ||||
| 303 | + | // Float64 reads a DOUBLE (d). | |||
| 304 | + | func (r *Reader) Float64() float64 { return math.Float64frombits(r.Uint64()) } | |||
| 305 | + | ||||
| 306 | + | // Str reads a STRING (s). The result points into the message buffer. | |||
| 307 | + | func (r *Reader) Str() string { return r.str(int(r.Uint32())) } | |||
| 308 | + | ||||
| 309 | + | // ObjectPath reads an OBJECT_PATH (o). The result points into the message | |||
| 310 | + | // buffer. | |||
| 311 | + | func (r *Reader) ObjectPath() string { return r.Str() } | |||
| 312 | + | ||||
| 313 | + | // Signature reads a SIGNATURE (g). The result points into the message buffer. | |||
| 314 | + | func (r *Reader) Signature() string { return r.str(int(r.Byte())) } | |||
| 315 | + | ||||
| 316 | + | // str returns the next n bytes as a string and skips the NUL after them. | |||
| 317 | + | func (r *Reader) str(n int) string { | |||
| 318 | + | if r.err != nil { | |||
| 319 | + | return "" | |||
| 320 | + | } | |||
| 321 | + | if n < 0 || n > len(r.buf)-r.off-1 || r.buf[r.off+n] != 0 { | |||
| 322 | + | r.fail() | |||
| 323 | + | return "" | |||
| 324 | + | } | |||
| 325 | + | s := unsafe.String(unsafe.SliceData(r.buf[r.off:]), n) | |||
| 326 | + | r.off += n + 1 | |||
| 327 | + | return s | |||
| 328 | + | } | |||
| 329 | + | ||||
| 330 | + | // Variant reads the signature of a VARIANT (v) and returns it. Read the | |||
| 331 | + | // value that follows with the matching method, or skip it with Skip. | |||
| 332 | + | func (r *Reader) Variant() string { | |||
| 333 | + | sig := r.Signature() | |||
| 334 | + | if r.err == nil && sigEnd(sig, 0, 0) != len(sig) { | |||
| 335 | + | r.fail() | |||
| 336 | + | return "" | |||
| 337 | + | } | |||
| 338 | + | return sig | |||
| 339 | + | } | |||
| 340 | + | ||||
| 341 | + | // Struct aligns the reader to the start of a STRUCT or DICT_ENTRY. Call it | |||
| 342 | + | // before reading the first field. | |||
| 343 | + | func (r *Reader) Struct() { r.take(8, 0) } | |||
| 344 | + | ||||
| 345 | + | // Array reads the header of an ARRAY whose element type starts with the | |||
| 346 | + | // signature byte elem, and returns the offset where the array ends. Read the | |||
| 347 | + | // elements while More returns true: | |||
| 348 | + | // | |||
| 349 | + | // end := r.Array('s') | |||
| 350 | + | // for r.More(end) { | |||
| 351 | + | // name := r.Str() | |||
| 352 | + | // } | |||
| 353 | + | func (r *Reader) Array(elem byte) int { | |||
| 354 | + | n := r.Uint32() | |||
| 355 | + | if r.err != nil { | |||
| 356 | + | return r.off | |||
| 357 | + | } | |||
| 358 | + | if n > maxArray { | |||
| 359 | + | r.fail() | |||
| 360 | + | return r.off | |||
| 361 | + | } | |||
| 362 | + | off := pad(r.off, alignOf(elem)) | |||
| 363 | + | if off > len(r.buf)-int(n) { | |||
| 364 | + | r.fail() | |||
| 365 | + | return r.off | |||
| 366 | + | } | |||
| 367 | + | r.off = off | |||
| 368 | + | return off + int(n) | |||
| 369 | + | } | |||
| 370 | + | ||||
| 371 | + | // More reports whether the array that ends at end has another element. | |||
| 372 | + | func (r *Reader) More(end int) bool { | |||
| 373 | + | if r.err != nil { | |||
| 374 | + | return false | |||
| 375 | + | } | |||
| 376 | + | if r.off < end { | |||
| 377 | + | return true | |||
| 378 | + | } | |||
| 379 | + | if r.off > end { | |||
| 380 | + | r.fail() | |||
| 381 | + | } | |||
| 382 | + | return false | |||
| 383 | + | } | |||
| 384 | + | ||||
| 385 | + | // SkipTo moves to end, the value returned by Array. Use it to leave an array | |||
| 386 | + | // before its last element. | |||
| 387 | + | func (r *Reader) SkipTo(end int) { | |||
| 388 | + | if r.err != nil { | |||
| 389 | + | return | |||
| 390 | + | } | |||
| 391 | + | if end < r.off || end > len(r.buf) { | |||
| 392 | + | r.fail() | |||
| 393 | + | return | |||
| 394 | + | } | |||
| 395 | + | r.off = end | |||
| 396 | + | } | |||
| 397 | + | ||||
| 398 | + | // ByteArray reads an ARRAY of BYTE (ay). The result points into the message | |||
| 399 | + | // buffer. | |||
| 400 | + | func (r *Reader) ByteArray() []byte { | |||
| 401 | + | end := r.Array('y') | |||
| 402 | + | if r.err != nil { | |||
| 403 | + | return nil | |||
| 404 | + | } | |||
| 405 | + | b := r.buf[r.off:end:end] | |||
| 406 | + | r.off = end | |||
| 407 | + | return b | |||
| 408 | + | } | |||
| 409 | + | ||||
| 410 | + | // Skip reads past values of the given signature. Arrays are skipped by their | |||
| 411 | + | // length, without reading their elements. | |||
| 412 | + | func (r *Reader) Skip(sig string) { r.skip(sig, 0) } | |||
| 413 | + | ||||
| 414 | + | func (r *Reader) skip(sig string, depth int) { | |||
| 415 | + | for i := 0; i < len(sig) && r.err == nil; { | |||
| 416 | + | i = r.skipOne(sig, i, depth) | |||
| 417 | + | } | |||
| 418 | + | } | |||
| 419 | + | ||||
| 420 | + | // skipOne skips the value of the type at sig[i] and returns the index of the | |||
| 421 | + | // next type. | |||
| 422 | + | func (r *Reader) skipOne(sig string, i, depth int) int { | |||
| 423 | + | if depth > maxDepth { | |||
| 424 | + | r.fail() | |||
| 425 | + | return len(sig) | |||
| 426 | + | } | |||
| 427 | + | switch c := sig[i]; c { | |||
| 428 | + | case 'y': | |||
| 429 | + | r.take(1, 1) | |||
| 430 | + | case 'n', 'q': | |||
| 431 | + | r.take(2, 2) | |||
| 432 | + | case 'b', 'i', 'u', 'h': | |||
| 433 | + | r.take(4, 4) | |||
| 434 | + | case 'x', 't', 'd': | |||
| 435 | + | r.take(8, 8) | |||
| 436 | + | case 's', 'o': | |||
| 437 | + | r.Str() | |||
| 438 | + | case 'g': | |||
| 439 | + | r.Signature() | |||
| 440 | + | case 'v': | |||
| 441 | + | r.skip(r.Variant(), depth+1) | |||
| 442 | + | case 'a': | |||
| 443 | + | end := sigEnd(sig, i, depth) | |||
| 444 | + | if end < 0 { | |||
| 445 | + | r.fail() | |||
| 446 | + | return len(sig) | |||
| 447 | + | } | |||
| 448 | + | r.SkipTo(r.Array(sig[i+1])) | |||
| 449 | + | return end | |||
| 450 | + | case '(', '{': | |||
| 451 | + | closer := byte(')') | |||
| 452 | + | if c == '{' { | |||
| 453 | + | closer = '}' | |||
| 454 | + | } | |||
| 455 | + | r.Struct() | |||
| 456 | + | j := i + 1 | |||
| 457 | + | for j < len(sig) && sig[j] != closer && r.err == nil { | |||
| 458 | + | j = r.skipOne(sig, j, depth+1) | |||
| 459 | + | } | |||
| 460 | + | if j >= len(sig) { | |||
| 461 | + | r.fail() | |||
| 462 | + | return len(sig) | |||
| 463 | + | } | |||
| 464 | + | return j + 1 | |||
| 465 | + | default: | |||
| 466 | + | r.fail() | |||
| 467 | + | return len(sig) | |||
| 468 | + | } | |||
| 469 | + | return i + 1 | |||
| 470 | + | } | |||
| 471 | + | ||||
| 472 | + | // Encoder appends D-Bus values to an outgoing message. Get one from | |||
| 473 | + | // Conn.NewCall, Conn.Signal, Conn.Reply or Conn.Error, write the body values | |||
| 474 | + | // in the order of the message signature, then call Conn.Send. | |||
| 475 | + | // | |||
| 476 | + | // Errors are sticky and reported by Conn.Send. | |||
| 477 | + | type Encoder struct { | |||
| 478 | + | buf []byte | |||
| 479 | + | body int // offset of the body; 0 when no message is being built | |||
| 480 | + | sig string // body signature, checked by finish | |||
| 481 | + | err error | |||
| 482 | + | } | |||
| 483 | + | ||||
| 484 | + | func (e *Encoder) invalid() { | |||
| 485 | + | if e.err == nil { | |||
| 486 | + | e.err = ErrInvalid | |||
| 487 | + | } | |||
| 488 | + | } | |||
| 489 | + | ||||
| 490 | + | func (e *Encoder) pad(n int) { | |||
| 491 | + | for len(e.buf)&(n-1) != 0 { | |||
| 492 | + | e.buf = append(e.buf, 0) | |||
| 493 | + | } | |||
| 494 | + | } | |||
| 495 | + | ||||
| 496 | + | // Byte writes a BYTE (y). | |||
| 497 | + | func (e *Encoder) Byte(v byte) { e.buf = append(e.buf, v) } | |||
| 498 | + | ||||
| 499 | + | // Bool writes a BOOLEAN (b). | |||
| 500 | + | func (e *Encoder) Bool(v bool) { | |||
| 501 | + | var u uint32 | |||
| 502 | + | if v { | |||
| 503 | + | u = 1 | |||
| 504 | + | } | |||
| 505 | + | e.Uint32(u) | |||
| 506 | + | } | |||
| 507 | + | ||||
| 508 | + | // Int16 writes an INT16 (n). | |||
| 509 | + | func (e *Encoder) Int16(v int16) { e.Uint16(uint16(v)) } | |||
| 510 | + | ||||
| 511 | + | // Uint16 writes a UINT16 (q). | |||
| 512 | + | func (e *Encoder) Uint16(v uint16) { | |||
| 513 | + | e.pad(2) | |||
| 514 | + | e.buf = binary.LittleEndian.AppendUint16(e.buf, v) | |||
| 515 | + | } | |||
| 516 | + | ||||
| 517 | + | // Int32 writes an INT32 (i). | |||
| 518 | + | func (e *Encoder) Int32(v int32) { e.Uint32(uint32(v)) } | |||
| 519 | + | ||||
| 520 | + | // Uint32 writes a UINT32 (u). | |||
| 521 | + | func (e *Encoder) Uint32(v uint32) { | |||
| 522 | + | e.pad(4) | |||
| 523 | + | e.buf = binary.LittleEndian.AppendUint32(e.buf, v) | |||
| 524 | + | } | |||
| 525 | + | ||||
| 526 | + | // Int64 writes an INT64 (x). | |||
| 527 | + | func (e *Encoder) Int64(v int64) { e.Uint64(uint64(v)) } | |||
| 528 | + | ||||
| 529 | + | // Uint64 writes a UINT64 (t). | |||
| 530 | + | func (e *Encoder) Uint64(v uint64) { | |||
| 531 | + | e.pad(8) | |||
| 532 | + | e.buf = binary.LittleEndian.AppendUint64(e.buf, v) | |||
| 533 | + | } | |||
| 534 | + | ||||
| 535 | + | // Float64 writes a DOUBLE (d). | |||
| 536 | + | func (e *Encoder) Float64(v float64) { e.Uint64(math.Float64bits(v)) } | |||
| 537 | + | ||||
| 538 | + | // Str writes a STRING (s). | |||
| 539 | + | func (e *Encoder) Str(s string) { | |||
| 540 | + | e.Uint32(uint32(len(s))) | |||
| 541 | + | e.text(s) | |||
| 542 | + | } | |||
| 543 | + | ||||
| 544 | + | // ObjectPath writes an OBJECT_PATH (o). | |||
| 545 | + | func (e *Encoder) ObjectPath(p string) { | |||
| 546 | + | if !validPath(p) { | |||
| 547 | + | e.invalid() | |||
| 548 | + | } | |||
| 549 | + | e.Str(p) | |||
| 550 | + | } | |||
| 551 | + | ||||
| 552 | + | // Signature writes a SIGNATURE (g). | |||
| 553 | + | func (e *Encoder) Signature(sig string) { | |||
| 554 | + | if !validSignature(sig) { | |||
| 555 | + | e.invalid() | |||
| 556 | + | } | |||
| 557 | + | e.Byte(byte(len(sig))) | |||
| 558 | + | e.text(sig) | |||
| 559 | + | } | |||
| 560 | + | ||||
| 561 | + | // text appends s and its NUL terminator. | |||
| 562 | + | func (e *Encoder) text(s string) { | |||
| 563 | + | // The bus disconnects a client that sends a NUL or invalid UTF-8. | |||
| 564 | + | if strings.IndexByte(s, 0) >= 0 || !utf8.ValidString(s) { | |||
| 565 | + | e.invalid() | |||
| 566 | + | } | |||
| 567 | + | e.buf = append(e.buf, s...) | |||
| 568 | + | e.buf = append(e.buf, 0) | |||
| 569 | + | } | |||
| 570 | + | ||||
| 571 | + | // Variant writes the signature of a VARIANT (v). Write its value next. | |||
| 572 | + | func (e *Encoder) Variant(sig string) { | |||
| 573 | + | if sigEnd(sig, 0, 0) != len(sig) { | |||
| 574 | + | e.invalid() | |||
| 575 | + | } | |||
| 576 | + | e.Signature(sig) | |||
| 577 | + | } | |||
| 578 | + | ||||
| 579 | + | // Struct aligns the encoder to the start of a STRUCT or DICT_ENTRY. Call it | |||
| 580 | + | // before writing the first field. | |||
| 581 | + | func (e *Encoder) Struct() { e.pad(8) } | |||
| 582 | + | ||||
| 583 | + | // ArrayMark is an array being written; pass it to EndArray. | |||
| 584 | + | type ArrayMark struct{ at, start int } | |||
| 585 | + | ||||
| 586 | + | // BeginArray starts an ARRAY whose element type starts with the signature | |||
| 587 | + | // byte elem. Write the elements, then call EndArray: | |||
| 588 | + | // | |||
| 589 | + | // a := e.BeginArray('s') | |||
| 590 | + | // e.Str("one") | |||
| 591 | + | // e.Str("two") | |||
| 592 | + | // e.EndArray(a) | |||
| 593 | + | func (e *Encoder) BeginArray(elem byte) ArrayMark { | |||
| 594 | + | e.pad(4) | |||
| 595 | + | at := len(e.buf) | |||
| 596 | + | e.buf = append(e.buf, 0, 0, 0, 0) | |||
| 597 | + | e.pad(alignOf(elem)) | |||
| 598 | + | return ArrayMark{at: at, start: len(e.buf)} | |||
| 599 | + | } | |||
| 600 | + | ||||
| 601 | + | // EndArray finishes the array started by BeginArray. | |||
| 602 | + | func (e *Encoder) EndArray(a ArrayMark) { | |||
| 603 | + | n := len(e.buf) - a.start | |||
| 604 | + | if n > maxArray { | |||
| 605 | + | e.invalid() | |||
| 606 | + | return | |||
| 607 | + | } | |||
| 608 | + | binary.LittleEndian.PutUint32(e.buf[a.at:], uint32(n)) | |||
| 609 | + | } | |||
| 610 | + | ||||
| 611 | + | // ByteArray writes an ARRAY of BYTE (ay). | |||
| 612 | + | func (e *Encoder) ByteArray(b []byte) { | |||
| 613 | + | e.Uint32(uint32(len(b))) | |||
| 614 | + | e.buf = append(e.buf, b...) | |||
| 615 | + | } | |||
| 616 | + | ||||
| 617 | + | // SetFlags sets the flags of the message being built. | |||
| 618 | + | func (e *Encoder) SetFlags(f Flags) { | |||
| 619 | + | if e.body != 0 { | |||
| 620 | + | e.buf[2] = byte(f) | |||
| 621 | + | } | |||
| 622 | + | } | |||
A wire_test.go +357 -0
| @@ -0,0 +1,357 @@ | |||
| 1 | + | package zerobus | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "errors" | |
| 5 | + | "testing" | |
| 6 | + | ) | |
| 7 | + | ||
| 8 | + | func TestSignatures(t *testing.T) { | |
| 9 | + | valid := []string{"", "s", "su", "a{sv}", "a(iiay)", "(s(ss))", "aa{s(ii)}", "a{oa{sa{sv}}}", "v", "g", "h"} | |
| 10 | + | invalid := []string{"a", "(", "()", "{sv}", "a{vs}", "a{s}", "a{sss}", "(s", "z", "a{(s)s}", "a{as}"} | |
| 11 | + | for _, s := range valid { | |
| 12 | + | if !validSignature(s) { | |
| 13 | + | t.Errorf("%q should be valid", s) | |
| 14 | + | } | |
| 15 | + | } | |
| 16 | + | for _, s := range invalid { | |
| 17 | + | if validSignature(s) { | |
| 18 | + | t.Errorf("%q should be invalid", s) | |
| 19 | + | } | |
| 20 | + | } | |
| 21 | + | } | |
| 22 | + | ||
| 23 | + | func TestNames(t *testing.T) { | |
| 24 | + | for _, s := range []string{"org.example", "org.kde.StatusNotifierWatcher", "a_b.C1"} { | |
| 25 | + | if !validInterface(s) { | |
| 26 | + | t.Errorf("interface %q should be valid", s) | |
| 27 | + | } | |
| 28 | + | } | |
| 29 | + | for _, s := range []string{"", "org", "org.", ".org.a", "org.1a", "org.a-b"} { | |
| 30 | + | if validInterface(s) { | |
| 31 | + | t.Errorf("interface %q should be invalid", s) | |
| 32 | + | } | |
| 33 | + | } | |
| 34 | + | for _, s := range []string{":1.42", "org.freedesktop.StatusNotifierItem-799476-1", "org.a"} { | |
| 35 | + | if !validBusName(s) { | |
| 36 | + | t.Errorf("bus name %q should be valid", s) | |
| 37 | + | } | |
| 38 | + | } | |
| 39 | + | for _, s := range []string{"", ":", "org", "org..a", "org.1a", "org.a/b"} { | |
| 40 | + | if validBusName(s) { | |
| 41 | + | t.Errorf("bus name %q should be invalid", s) | |
| 42 | + | } | |
| 43 | + | } | |
| 44 | + | for _, s := range []string{"", "1a", "a.b", "a-b"} { | |
| 45 | + | if validMember(s) { | |
| 46 | + | t.Errorf("member %q should be invalid", s) | |
| 47 | + | } | |
| 48 | + | } | |
| 49 | + | var e Encoder | |
| 50 | + | e.begin(TypeMethodCall, 1, 0, "bad name", "/a", "a.b", "M", "", "") | |
| 51 | + | if _, err := e.finish(); !errors.Is(err, ErrInvalid) { | |
| 52 | + | t.Fatalf("bad destination accepted: %v", err) | |
| 53 | + | } | |
| 54 | + | } | |
| 55 | + | ||
| 56 | + | func TestPaths(t *testing.T) { | |
| 57 | + | for _, p := range []string{"/", "/a", "/org/kde/StatusNotifierItem", "/a_1/B2"} { | |
| 58 | + | if !validPath(p) { | |
| 59 | + | t.Errorf("%q should be valid", p) | |
| 60 | + | } | |
| 61 | + | } | |
| 62 | + | for _, p := range []string{"", "a", "//", "/a/", "/a//b", "/a-b", "/a.b"} { | |
| 63 | + | if validPath(p) { | |
| 64 | + | t.Errorf("%q should be invalid", p) | |
| 65 | + | } | |
| 66 | + | } | |
| 67 | + | } | |
| 68 | + | ||
| 69 | + | // build encodes a method call with every type, the way Conn does. | |
| 70 | + | func build(e *Encoder) []byte { | |
| 71 | + | const sig = "ybnqiuxtdsogva{sv}ay(si)" | |
| 72 | + | e.begin(TypeMethodCall, 7, 0, "org.example", "/org/example", "org.example.I", "M", "", sig) | |
| 73 | + | e.Byte(0xAB) | |
| 74 | + | e.Bool(true) | |
| 75 | + | e.Int16(-2) | |
| 76 | + | e.Uint16(3) | |
| 77 | + | e.Int32(-4) | |
| 78 | + | e.Uint32(5) | |
| 79 | + | e.Int64(-6) | |
| 80 | + | e.Uint64(7) | |
| 81 | + | e.Float64(8.5) | |
| 82 | + | e.Str("héllo") | |
| 83 | + | e.ObjectPath("/a/b") | |
| 84 | + | e.Signature("a{sv}") | |
| 85 | + | e.Variant("u") | |
| 86 | + | e.Uint32(42) | |
| 87 | + | d := e.BeginArray('{') | |
| 88 | + | for _, k := range []string{"one", "two"} { | |
| 89 | + | e.Struct() | |
| 90 | + | e.Str(k) | |
| 91 | + | e.Variant("s") | |
| 92 | + | e.Str(k + "!") | |
| 93 | + | } | |
| 94 | + | e.EndArray(d) | |
| 95 | + | e.ByteArray([]byte{1, 2, 3}) | |
| 96 | + | e.Struct() | |
| 97 | + | e.Str("x") | |
| 98 | + | e.Int32(9) | |
| 99 | + | b, err := e.finish() | |
| 100 | + | if err != nil { | |
| 101 | + | panic(err) | |
| 102 | + | } | |
| 103 | + | return b | |
| 104 | + | } | |
| 105 | + | ||
| 106 | + | func TestRoundTrip(t *testing.T) { | |
| 107 | + | var e Encoder | |
| 108 | + | b := build(&e) | |
| 109 | + | n, err := frameLen(b) | |
| 110 | + | if err != nil || n != len(b) { | |
| 111 | + | t.Fatalf("frameLen = %d, %v; want %d", n, err, len(b)) | |
| 112 | + | } | |
| 113 | + | var m Message | |
| 114 | + | if err := parse(b, &m); err != nil { | |
| 115 | + | t.Fatal(err) | |
| 116 | + | } | |
| 117 | + | if m.Type != TypeMethodCall || m.Serial != 7 || m.Path != "/org/example" || m.Interface != "org.example.I" || | |
| 118 | + | m.Member != "M" || m.Destination != "org.example" || m.Signature != "ybnqiuxtdsogva{sv}ay(si)" { | |
| 119 | + | t.Fatalf("header: %+v", m) | |
| 120 | + | } | |
| 121 | + | r := m.Body() | |
| 122 | + | if r.Byte() != 0xAB || !r.Bool() || r.Int16() != -2 || r.Uint16() != 3 || r.Int32() != -4 || r.Uint32() != 5 || | |
| 123 | + | r.Int64() != -6 || r.Uint64() != 7 || r.Float64() != 8.5 || r.Str() != "héllo" || r.ObjectPath() != "/a/b" || | |
| 124 | + | r.Signature() != "a{sv}" { | |
| 125 | + | t.Fatalf("basic values: %v", r.Err()) | |
| 126 | + | } | |
| 127 | + | if sig := r.Variant(); sig != "u" || r.Uint32() != 42 { | |
| 128 | + | t.Fatal("variant") | |
| 129 | + | } | |
| 130 | + | var keys []string | |
| 131 | + | end := r.Array('{') | |
| 132 | + | for r.More(end) { | |
| 133 | + | r.Struct() | |
| 134 | + | k := r.Str() | |
| 135 | + | if r.Variant() != "s" || r.Str() != k+"!" { | |
| 136 | + | t.Fatal("dict value") | |
| 137 | + | } | |
| 138 | + | keys = append(keys, k) | |
| 139 | + | } | |
| 140 | + | if len(keys) != 2 || keys[0] != "one" || keys[1] != "two" { | |
| 141 | + | t.Fatalf("keys %v", keys) | |
| 142 | + | } | |
| 143 | + | if ay := r.ByteArray(); len(ay) != 3 || ay[2] != 3 { | |
| 144 | + | t.Fatalf("ay %v", ay) | |
| 145 | + | } | |
| 146 | + | r.Struct() | |
| 147 | + | if r.Str() != "x" || r.Int32() != 9 || !r.Done() { | |
| 148 | + | t.Fatalf("struct: %v", r.Err()) | |
| 149 | + | } | |
| 150 | + | ||
| 151 | + | // Skip the same body by its signature. | |
| 152 | + | r = m.Body() | |
| 153 | + | r.Skip(m.Signature) | |
| 154 | + | if !r.Done() { | |
| 155 | + | t.Fatalf("skip: %v off=%d len=%d", r.Err(), r.off, len(r.buf)) | |
| 156 | + | } | |
| 157 | + | } | |
| 158 | + | ||
| 159 | + | func TestEmptyArrayAlignment(t *testing.T) { | |
| 160 | + | // An empty array of 8-aligned elements still pads to the element | |
| 161 | + | // alignment, and its length excludes that padding. | |
| 162 | + | var e Encoder | |
| 163 | + | e.begin(TypeSignal, 1, 0, "", "/a", "a.b", "C", "", "a(ii)u") | |
| 164 | + | a := e.BeginArray('(') | |
| 165 | + | e.EndArray(a) | |
| 166 | + | e.Uint32(1) | |
| 167 | + | b, err := e.finish() | |
| 168 | + | if err != nil { | |
| 169 | + | t.Fatal(err) | |
| 170 | + | } | |
| 171 | + | var m Message | |
| 172 | + | if err := parse(b, &m); err != nil { | |
| 173 | + | t.Fatal(err) | |
| 174 | + | } | |
| 175 | + | r := m.Body() | |
| 176 | + | end := r.Array('(') | |
| 177 | + | if r.More(end) || r.Uint32() != 1 || !r.Done() { | |
| 178 | + | t.Fatalf("got %v", r.Err()) | |
| 179 | + | } | |
| 180 | + | } | |
| 181 | + | ||
| 182 | + | func TestBigEndian(t *testing.T) { | |
| 183 | + | // A signal from a big-endian peer: path /a, interface a.b, member C, | |
| 184 | + | // body "u" = 0x01020304. The field array is 55 bytes, then 1 byte of | |
| 185 | + | // padding before the body. | |
| 186 | + | be := []byte{'B', 4, 0, 1, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 55, | |
| 187 | + | 1, 1, 'o', 0, 0, 0, 0, 2, '/', 'a', 0, 0, 0, 0, 0, 0, | |
| 188 | + | 2, 1, 's', 0, 0, 0, 0, 3, 'a', '.', 'b', 0, 0, 0, 0, 0, | |
| 189 | + | 3, 1, 's', 0, 0, 0, 0, 1, 'C', 0, 0, 0, 0, 0, 0, 0, | |
| 190 | + | 8, 1, 'g', 0, 1, 'u', 0, 0, | |
| 191 | + | 1, 2, 3, 4} | |
| 192 | + | n, err := frameLen(be) | |
| 193 | + | if err != nil || n != len(be) { | |
| 194 | + | t.Fatalf("frameLen %d %v want %d", n, err, len(be)) | |
| 195 | + | } | |
| 196 | + | var m Message | |
| 197 | + | if err := parse(be, &m); err != nil { | |
| 198 | + | t.Fatal(err) | |
| 199 | + | } | |
| 200 | + | if m.Path != "/a" || m.Interface != "a.b" || m.Member != "C" || m.Body().Uint32() != 0x01020304 { | |
| 201 | + | t.Fatalf("%+v", m) | |
| 202 | + | } | |
| 203 | + | } | |
| 204 | + | ||
| 205 | + | func TestEncoderRejects(t *testing.T) { | |
| 206 | + | cases := map[string]func(e *Encoder){ | |
| 207 | + | "nul in string": func(e *Encoder) { e.Str("a\x00b") }, | |
| 208 | + | "bad utf-8": func(e *Encoder) { e.Str("\xff") }, | |
| 209 | + | "bad path": func(e *Encoder) { e.ObjectPath("a/b") }, | |
| 210 | + | "missing value": func(e *Encoder) {}, | |
| 211 | + | "wrong type": func(e *Encoder) { e.Byte(1) }, | |
| 212 | + | "extra value": func(e *Encoder) { e.Str("a"); e.Str("b") }, | |
| 213 | + | "bad variant sig": func(e *Encoder) { e.Variant("ss") }, | |
| 214 | + | } | |
| 215 | + | for name, f := range cases { | |
| 216 | + | var e Encoder | |
| 217 | + | e.begin(TypeSignal, 1, 0, "", "/a", "a.b", "C", "", "s") | |
| 218 | + | f(&e) | |
| 219 | + | if _, err := e.finish(); !errors.Is(err, ErrInvalid) { | |
| 220 | + | t.Errorf("%s: err = %v, want ErrInvalid", name, err) | |
| 221 | + | } | |
| 222 | + | } | |
| 223 | + | } | |
| 224 | + | ||
| 225 | + | func TestParseRejects(t *testing.T) { | |
| 226 | + | var e Encoder | |
| 227 | + | good := append([]byte(nil), build(&e)...) | |
| 228 | + | mutate := map[string]func(b []byte) []byte{ | |
| 229 | + | "bad endian": func(b []byte) []byte { b[0] = 'x'; return b }, | |
| 230 | + | "bad version": func(b []byte) []byte { b[3] = 2; return b }, | |
| 231 | + | "zero serial": func(b []byte) []byte { b[8], b[9], b[10], b[11] = 0, 0, 0, 0; return b }, | |
| 232 | + | "short body": func(b []byte) []byte { return b[:len(b)-1] }, | |
| 233 | + | "huge body": func(b []byte) []byte { b[7] = 0xff; return b }, | |
| 234 | + | "no member": func(b []byte) []byte { return dropField(b, fieldMember) }, | |
| 235 | + | "bad padding": func(b []byte) []byte { return dirtyPadding(b) }, | |
| 236 | + | } | |
| 237 | + | for name, f := range mutate { | |
| 238 | + | b := f(append([]byte(nil), good...)) | |
| 239 | + | var m Message | |
| 240 | + | n, err := frameLen(b) | |
| 241 | + | if err == nil && n == len(b) { | |
| 242 | + | err = parse(b, &m) | |
| 243 | + | } else if err == nil { | |
| 244 | + | err = ErrMalformed | |
| 245 | + | } | |
| 246 | + | if err == nil { | |
| 247 | + | t.Errorf("%s: accepted", name) | |
| 248 | + | } | |
| 249 | + | } | |
| 250 | + | } | |
| 251 | + | ||
| 252 | + | // dropField renames the header field code to an unknown one, so parse | |
| 253 | + | // ignores it. | |
| 254 | + | func dropField(b []byte, code byte) []byte { | |
| 255 | + | for i := 16; i < len(b)-1; i += 8 { | |
| 256 | + | if b[i] == code && b[i+1] == 1 { | |
| 257 | + | b[i] = 200 | |
| 258 | + | return b | |
| 259 | + | } | |
| 260 | + | } | |
| 261 | + | return b | |
| 262 | + | } | |
| 263 | + | ||
| 264 | + | // dirtyPadding sets the last padding byte before the body to a non-zero | |
| 265 | + | // value. | |
| 266 | + | func dirtyPadding(b []byte) []byte { | |
| 267 | + | fields := int(b[12]) | int(b[13])<<8 | |
| 268 | + | if end := headerLen + fields; end%8 != 0 { | |
| 269 | + | b[pad(end, 8)-1] = 1 | |
| 270 | + | } | |
| 271 | + | return b | |
| 272 | + | } | |
| 273 | + | ||
| 274 | + | func TestReaderStickyErrors(t *testing.T) { | |
| 275 | + | r := Reader{buf: []byte{1, 0, 0}} | |
| 276 | + | if r.Uint32() != 0 || r.Err() == nil { | |
| 277 | + | t.Fatal("short read should fail") | |
| 278 | + | } | |
| 279 | + | if r.Byte() != 0 || r.Str() != "" { | |
| 280 | + | t.Fatal("reads after an error should return zero values") | |
| 281 | + | } | |
| 282 | + | // A string without its NUL. | |
| 283 | + | r = Reader{buf: []byte{1, 0, 0, 0, 'a', 'b'}} | |
| 284 | + | if r.Str() != "" || r.Err() == nil { | |
| 285 | + | t.Fatal("string without NUL should fail") | |
| 286 | + | } | |
| 287 | + | // An array longer than the buffer. | |
| 288 | + | r = Reader{buf: []byte{0xff, 0, 0, 0}} | |
| 289 | + | end := r.Array('y') | |
| 290 | + | if r.More(end) || r.Err() == nil { | |
| 291 | + | t.Fatal("oversized array should fail") | |
| 292 | + | } | |
| 293 | + | } | |
| 294 | + | ||
| 295 | + | func TestEncodeParseNoAllocs(t *testing.T) { | |
| 296 | + | var e Encoder | |
| 297 | + | var m Message | |
| 298 | + | build(&e) // grow the buffer once | |
| 299 | + | allocs := testing.AllocsPerRun(100, func() { | |
| 300 | + | b := build(&e) | |
| 301 | + | if err := parse(b, &m); err != nil { | |
| 302 | + | t.Fatal(err) | |
| 303 | + | } | |
| 304 | + | r := m.Body() | |
| 305 | + | r.Skip(m.Signature) | |
| 306 | + | if !r.Done() { | |
| 307 | + | t.Fatal(r.Err()) | |
| 308 | + | } | |
| 309 | + | }) | |
| 310 | + | if allocs != 0 { | |
| 311 | + | t.Fatalf("%v allocations per message, want 0", allocs) | |
| 312 | + | } | |
| 313 | + | } | |
| 314 | + | ||
| 315 | + | func FuzzParse(f *testing.F) { | |
| 316 | + | var e Encoder | |
| 317 | + | f.Add(append([]byte(nil), build(&e)...)) | |
| 318 | + | f.Fuzz(func(t *testing.T, b []byte) { | |
| 319 | + | if len(b) < headerLen { | |
| 320 | + | return | |
| 321 | + | } | |
| 322 | + | n, err := frameLen(b) | |
| 323 | + | if err != nil || n > len(b) { | |
| 324 | + | return | |
| 325 | + | } | |
| 326 | + | var m Message | |
| 327 | + | if parse(b[:n], &m) != nil { | |
| 328 | + | return | |
| 329 | + | } | |
| 330 | + | r := m.Body() | |
| 331 | + | r.Skip(m.Signature) | |
| 332 | + | }) | |
| 333 | + | } | |
| 334 | + | ||
| 335 | + | func FuzzSkip(f *testing.F) { | |
| 336 | + | f.Add("a{sv}", []byte{8, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 'a', 0, 1, 'u', 0, 0, 0, 0, 7, 0, 0, 0}) | |
| 337 | + | f.Add("(v)", []byte{1, 'v', 0}) | |
| 338 | + | f.Fuzz(func(t *testing.T, sig string, b []byte) { | |
| 339 | + | if !validSignature(sig) { | |
| 340 | + | return | |
| 341 | + | } | |
| 342 | + | r := Reader{buf: b} | |
| 343 | + | r.Skip(sig) | |
| 344 | + | }) | |
| 345 | + | } | |
| 346 | + | ||
| 347 | + | func BenchmarkEncodeParse(b *testing.B) { | |
| 348 | + | var e Encoder | |
| 349 | + | var m Message | |
| 350 | + | b.ReportAllocs() | |
| 351 | + | for b.Loop() { | |
| 352 | + | buf := build(&e) | |
| 353 | + | if err := parse(buf, &m); err != nil { | |
| 354 | + | b.Fatal(err) | |
| 355 | + | } | |
| 356 | + | } | |
| 357 | + | } | |
| @@ -0,0 +1,357 @@ | |||||
| 1 | + | package zerobus | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "errors" | |||
| 5 | + | "testing" | |||
| 6 | + | ) | |||
| 7 | + | ||||
| 8 | + | func TestSignatures(t *testing.T) { | |||
| 9 | + | valid := []string{"", "s", "su", "a{sv}", "a(iiay)", "(s(ss))", "aa{s(ii)}", "a{oa{sa{sv}}}", "v", "g", "h"} | |||
| 10 | + | invalid := []string{"a", "(", "()", "{sv}", "a{vs}", "a{s}", "a{sss}", "(s", "z", "a{(s)s}", "a{as}"} | |||
| 11 | + | for _, s := range valid { | |||
| 12 | + | if !validSignature(s) { | |||
| 13 | + | t.Errorf("%q should be valid", s) | |||
| 14 | + | } | |||
| 15 | + | } | |||
| 16 | + | for _, s := range invalid { | |||
| 17 | + | if validSignature(s) { | |||
| 18 | + | t.Errorf("%q should be invalid", s) | |||
| 19 | + | } | |||
| 20 | + | } | |||
| 21 | + | } | |||
| 22 | + | ||||
| 23 | + | func TestNames(t *testing.T) { | |||
| 24 | + | for _, s := range []string{"org.example", "org.kde.StatusNotifierWatcher", "a_b.C1"} { | |||
| 25 | + | if !validInterface(s) { | |||
| 26 | + | t.Errorf("interface %q should be valid", s) | |||
| 27 | + | } | |||
| 28 | + | } | |||
| 29 | + | for _, s := range []string{"", "org", "org.", ".org.a", "org.1a", "org.a-b"} { | |||
| 30 | + | if validInterface(s) { | |||
| 31 | + | t.Errorf("interface %q should be invalid", s) | |||
| 32 | + | } | |||
| 33 | + | } | |||
| 34 | + | for _, s := range []string{":1.42", "org.freedesktop.StatusNotifierItem-799476-1", "org.a"} { | |||
| 35 | + | if !validBusName(s) { | |||
| 36 | + | t.Errorf("bus name %q should be valid", s) | |||
| 37 | + | } | |||
| 38 | + | } | |||
| 39 | + | for _, s := range []string{"", ":", "org", "org..a", "org.1a", "org.a/b"} { | |||
| 40 | + | if validBusName(s) { | |||
| 41 | + | t.Errorf("bus name %q should be invalid", s) | |||
| 42 | + | } | |||
| 43 | + | } | |||
| 44 | + | for _, s := range []string{"", "1a", "a.b", "a-b"} { | |||
| 45 | + | if validMember(s) { | |||
| 46 | + | t.Errorf("member %q should be invalid", s) | |||
| 47 | + | } | |||
| 48 | + | } | |||
| 49 | + | var e Encoder | |||
| 50 | + | e.begin(TypeMethodCall, 1, 0, "bad name", "/a", "a.b", "M", "", "") | |||
| 51 | + | if _, err := e.finish(); !errors.Is(err, ErrInvalid) { | |||
| 52 | + | t.Fatalf("bad destination accepted: %v", err) | |||
| 53 | + | } | |||
| 54 | + | } | |||
| 55 | + | ||||
| 56 | + | func TestPaths(t *testing.T) { | |||
| 57 | + | for _, p := range []string{"/", "/a", "/org/kde/StatusNotifierItem", "/a_1/B2"} { | |||
| 58 | + | if !validPath(p) { | |||
| 59 | + | t.Errorf("%q should be valid", p) | |||
| 60 | + | } | |||
| 61 | + | } | |||
| 62 | + | for _, p := range []string{"", "a", "//", "/a/", "/a//b", "/a-b", "/a.b"} { | |||
| 63 | + | if validPath(p) { | |||
| 64 | + | t.Errorf("%q should be invalid", p) | |||
| 65 | + | } | |||
| 66 | + | } | |||
| 67 | + | } | |||
| 68 | + | ||||
| 69 | + | // build encodes a method call with every type, the way Conn does. | |||
| 70 | + | func build(e *Encoder) []byte { | |||
| 71 | + | const sig = "ybnqiuxtdsogva{sv}ay(si)" | |||
| 72 | + | e.begin(TypeMethodCall, 7, 0, "org.example", "/org/example", "org.example.I", "M", "", sig) | |||
| 73 | + | e.Byte(0xAB) | |||
| 74 | + | e.Bool(true) | |||
| 75 | + | e.Int16(-2) | |||
| 76 | + | e.Uint16(3) | |||
| 77 | + | e.Int32(-4) | |||
| 78 | + | e.Uint32(5) | |||
| 79 | + | e.Int64(-6) | |||
| 80 | + | e.Uint64(7) | |||
| 81 | + | e.Float64(8.5) | |||
| 82 | + | e.Str("héllo") | |||
| 83 | + | e.ObjectPath("/a/b") | |||
| 84 | + | e.Signature("a{sv}") | |||
| 85 | + | e.Variant("u") | |||
| 86 | + | e.Uint32(42) | |||
| 87 | + | d := e.BeginArray('{') | |||
| 88 | + | for _, k := range []string{"one", "two"} { | |||
| 89 | + | e.Struct() | |||
| 90 | + | e.Str(k) | |||
| 91 | + | e.Variant("s") | |||
| 92 | + | e.Str(k + "!") | |||
| 93 | + | } | |||
| 94 | + | e.EndArray(d) | |||
| 95 | + | e.ByteArray([]byte{1, 2, 3}) | |||
| 96 | + | e.Struct() | |||
| 97 | + | e.Str("x") | |||
| 98 | + | e.Int32(9) | |||
| 99 | + | b, err := e.finish() | |||
| 100 | + | if err != nil { | |||
| 101 | + | panic(err) | |||
| 102 | + | } | |||
| 103 | + | return b | |||
| 104 | + | } | |||
| 105 | + | ||||
| 106 | + | func TestRoundTrip(t *testing.T) { | |||
| 107 | + | var e Encoder | |||
| 108 | + | b := build(&e) | |||
| 109 | + | n, err := frameLen(b) | |||
| 110 | + | if err != nil || n != len(b) { | |||
| 111 | + | t.Fatalf("frameLen = %d, %v; want %d", n, err, len(b)) | |||
| 112 | + | } | |||
| 113 | + | var m Message | |||
| 114 | + | if err := parse(b, &m); err != nil { | |||
| 115 | + | t.Fatal(err) | |||
| 116 | + | } | |||
| 117 | + | if m.Type != TypeMethodCall || m.Serial != 7 || m.Path != "/org/example" || m.Interface != "org.example.I" || | |||
| 118 | + | m.Member != "M" || m.Destination != "org.example" || m.Signature != "ybnqiuxtdsogva{sv}ay(si)" { | |||
| 119 | + | t.Fatalf("header: %+v", m) | |||
| 120 | + | } | |||
| 121 | + | r := m.Body() | |||
| 122 | + | if r.Byte() != 0xAB || !r.Bool() || r.Int16() != -2 || r.Uint16() != 3 || r.Int32() != -4 || r.Uint32() != 5 || | |||
| 123 | + | r.Int64() != -6 || r.Uint64() != 7 || r.Float64() != 8.5 || r.Str() != "héllo" || r.ObjectPath() != "/a/b" || | |||
| 124 | + | r.Signature() != "a{sv}" { | |||
| 125 | + | t.Fatalf("basic values: %v", r.Err()) | |||
| 126 | + | } | |||
| 127 | + | if sig := r.Variant(); sig != "u" || r.Uint32() != 42 { | |||
| 128 | + | t.Fatal("variant") | |||
| 129 | + | } | |||
| 130 | + | var keys []string | |||
| 131 | + | end := r.Array('{') | |||
| 132 | + | for r.More(end) { | |||
| 133 | + | r.Struct() | |||
| 134 | + | k := r.Str() | |||
| 135 | + | if r.Variant() != "s" || r.Str() != k+"!" { | |||
| 136 | + | t.Fatal("dict value") | |||
| 137 | + | } | |||
| 138 | + | keys = append(keys, k) | |||
| 139 | + | } | |||
| 140 | + | if len(keys) != 2 || keys[0] != "one" || keys[1] != "two" { | |||
| 141 | + | t.Fatalf("keys %v", keys) | |||
| 142 | + | } | |||
| 143 | + | if ay := r.ByteArray(); len(ay) != 3 || ay[2] != 3 { | |||
| 144 | + | t.Fatalf("ay %v", ay) | |||
| 145 | + | } | |||
| 146 | + | r.Struct() | |||
| 147 | + | if r.Str() != "x" || r.Int32() != 9 || !r.Done() { | |||
| 148 | + | t.Fatalf("struct: %v", r.Err()) | |||
| 149 | + | } | |||
| 150 | + | ||||
| 151 | + | // Skip the same body by its signature. | |||
| 152 | + | r = m.Body() | |||
| 153 | + | r.Skip(m.Signature) | |||
| 154 | + | if !r.Done() { | |||
| 155 | + | t.Fatalf("skip: %v off=%d len=%d", r.Err(), r.off, len(r.buf)) | |||
| 156 | + | } | |||
| 157 | + | } | |||
| 158 | + | ||||
| 159 | + | func TestEmptyArrayAlignment(t *testing.T) { | |||
| 160 | + | // An empty array of 8-aligned elements still pads to the element | |||
| 161 | + | // alignment, and its length excludes that padding. | |||
| 162 | + | var e Encoder | |||
| 163 | + | e.begin(TypeSignal, 1, 0, "", "/a", "a.b", "C", "", "a(ii)u") | |||
| 164 | + | a := e.BeginArray('(') | |||
| 165 | + | e.EndArray(a) | |||
| 166 | + | e.Uint32(1) | |||
| 167 | + | b, err := e.finish() | |||
| 168 | + | if err != nil { | |||
| 169 | + | t.Fatal(err) | |||
| 170 | + | } | |||
| 171 | + | var m Message | |||
| 172 | + | if err := parse(b, &m); err != nil { | |||
| 173 | + | t.Fatal(err) | |||
| 174 | + | } | |||
| 175 | + | r := m.Body() | |||
| 176 | + | end := r.Array('(') | |||
| 177 | + | if r.More(end) || r.Uint32() != 1 || !r.Done() { | |||
| 178 | + | t.Fatalf("got %v", r.Err()) | |||
| 179 | + | } | |||
| 180 | + | } | |||
| 181 | + | ||||
| 182 | + | func TestBigEndian(t *testing.T) { | |||
| 183 | + | // A signal from a big-endian peer: path /a, interface a.b, member C, | |||
| 184 | + | // body "u" = 0x01020304. The field array is 55 bytes, then 1 byte of | |||
| 185 | + | // padding before the body. | |||
| 186 | + | be := []byte{'B', 4, 0, 1, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 55, | |||
| 187 | + | 1, 1, 'o', 0, 0, 0, 0, 2, '/', 'a', 0, 0, 0, 0, 0, 0, | |||
| 188 | + | 2, 1, 's', 0, 0, 0, 0, 3, 'a', '.', 'b', 0, 0, 0, 0, 0, | |||
| 189 | + | 3, 1, 's', 0, 0, 0, 0, 1, 'C', 0, 0, 0, 0, 0, 0, 0, | |||
| 190 | + | 8, 1, 'g', 0, 1, 'u', 0, 0, | |||
| 191 | + | 1, 2, 3, 4} | |||
| 192 | + | n, err := frameLen(be) | |||
| 193 | + | if err != nil || n != len(be) { | |||
| 194 | + | t.Fatalf("frameLen %d %v want %d", n, err, len(be)) | |||
| 195 | + | } | |||
| 196 | + | var m Message | |||
| 197 | + | if err := parse(be, &m); err != nil { | |||
| 198 | + | t.Fatal(err) | |||
| 199 | + | } | |||
| 200 | + | if m.Path != "/a" || m.Interface != "a.b" || m.Member != "C" || m.Body().Uint32() != 0x01020304 { | |||
| 201 | + | t.Fatalf("%+v", m) | |||
| 202 | + | } | |||
| 203 | + | } | |||
| 204 | + | ||||
| 205 | + | func TestEncoderRejects(t *testing.T) { | |||
| 206 | + | cases := map[string]func(e *Encoder){ | |||
| 207 | + | "nul in string": func(e *Encoder) { e.Str("a\x00b") }, | |||
| 208 | + | "bad utf-8": func(e *Encoder) { e.Str("\xff") }, | |||
| 209 | + | "bad path": func(e *Encoder) { e.ObjectPath("a/b") }, | |||
| 210 | + | "missing value": func(e *Encoder) {}, | |||
| 211 | + | "wrong type": func(e *Encoder) { e.Byte(1) }, | |||
| 212 | + | "extra value": func(e *Encoder) { e.Str("a"); e.Str("b") }, | |||
| 213 | + | "bad variant sig": func(e *Encoder) { e.Variant("ss") }, | |||
| 214 | + | } | |||
| 215 | + | for name, f := range cases { | |||
| 216 | + | var e Encoder | |||
| 217 | + | e.begin(TypeSignal, 1, 0, "", "/a", "a.b", "C", "", "s") | |||
| 218 | + | f(&e) | |||
| 219 | + | if _, err := e.finish(); !errors.Is(err, ErrInvalid) { | |||
| 220 | + | t.Errorf("%s: err = %v, want ErrInvalid", name, err) | |||
| 221 | + | } | |||
| 222 | + | } | |||
| 223 | + | } | |||
| 224 | + | ||||
| 225 | + | func TestParseRejects(t *testing.T) { | |||
| 226 | + | var e Encoder | |||
| 227 | + | good := append([]byte(nil), build(&e)...) | |||
| 228 | + | mutate := map[string]func(b []byte) []byte{ | |||
| 229 | + | "bad endian": func(b []byte) []byte { b[0] = 'x'; return b }, | |||
| 230 | + | "bad version": func(b []byte) []byte { b[3] = 2; return b }, | |||
| 231 | + | "zero serial": func(b []byte) []byte { b[8], b[9], b[10], b[11] = 0, 0, 0, 0; return b }, | |||
| 232 | + | "short body": func(b []byte) []byte { return b[:len(b)-1] }, | |||
| 233 | + | "huge body": func(b []byte) []byte { b[7] = 0xff; return b }, | |||
| 234 | + | "no member": func(b []byte) []byte { return dropField(b, fieldMember) }, | |||
| 235 | + | "bad padding": func(b []byte) []byte { return dirtyPadding(b) }, | |||
| 236 | + | } | |||
| 237 | + | for name, f := range mutate { | |||
| 238 | + | b := f(append([]byte(nil), good...)) | |||
| 239 | + | var m Message | |||
| 240 | + | n, err := frameLen(b) | |||
| 241 | + | if err == nil && n == len(b) { | |||
| 242 | + | err = parse(b, &m) | |||
| 243 | + | } else if err == nil { | |||
| 244 | + | err = ErrMalformed | |||
| 245 | + | } | |||
| 246 | + | if err == nil { | |||
| 247 | + | t.Errorf("%s: accepted", name) | |||
| 248 | + | } | |||
| 249 | + | } | |||
| 250 | + | } | |||
| 251 | + | ||||
| 252 | + | // dropField renames the header field code to an unknown one, so parse | |||
| 253 | + | // ignores it. | |||
| 254 | + | func dropField(b []byte, code byte) []byte { | |||
| 255 | + | for i := 16; i < len(b)-1; i += 8 { | |||
| 256 | + | if b[i] == code && b[i+1] == 1 { | |||
| 257 | + | b[i] = 200 | |||
| 258 | + | return b | |||
| 259 | + | } | |||
| 260 | + | } | |||
| 261 | + | return b | |||
| 262 | + | } | |||
| 263 | + | ||||
| 264 | + | // dirtyPadding sets the last padding byte before the body to a non-zero | |||
| 265 | + | // value. | |||
| 266 | + | func dirtyPadding(b []byte) []byte { | |||
| 267 | + | fields := int(b[12]) | int(b[13])<<8 | |||
| 268 | + | if end := headerLen + fields; end%8 != 0 { | |||
| 269 | + | b[pad(end, 8)-1] = 1 | |||
| 270 | + | } | |||
| 271 | + | return b | |||
| 272 | + | } | |||
| 273 | + | ||||
| 274 | + | func TestReaderStickyErrors(t *testing.T) { | |||
| 275 | + | r := Reader{buf: []byte{1, 0, 0}} | |||
| 276 | + | if r.Uint32() != 0 || r.Err() == nil { | |||
| 277 | + | t.Fatal("short read should fail") | |||
| 278 | + | } | |||
| 279 | + | if r.Byte() != 0 || r.Str() != "" { | |||
| 280 | + | t.Fatal("reads after an error should return zero values") | |||
| 281 | + | } | |||
| 282 | + | // A string without its NUL. | |||
| 283 | + | r = Reader{buf: []byte{1, 0, 0, 0, 'a', 'b'}} | |||
| 284 | + | if r.Str() != "" || r.Err() == nil { | |||
| 285 | + | t.Fatal("string without NUL should fail") | |||
| 286 | + | } | |||
| 287 | + | // An array longer than the buffer. | |||
| 288 | + | r = Reader{buf: []byte{0xff, 0, 0, 0}} | |||
| 289 | + | end := r.Array('y') | |||
| 290 | + | if r.More(end) || r.Err() == nil { | |||
| 291 | + | t.Fatal("oversized array should fail") | |||
| 292 | + | } | |||
| 293 | + | } | |||
| 294 | + | ||||
| 295 | + | func TestEncodeParseNoAllocs(t *testing.T) { | |||
| 296 | + | var e Encoder | |||
| 297 | + | var m Message | |||
| 298 | + | build(&e) // grow the buffer once | |||
| 299 | + | allocs := testing.AllocsPerRun(100, func() { | |||
| 300 | + | b := build(&e) | |||
| 301 | + | if err := parse(b, &m); err != nil { | |||
| 302 | + | t.Fatal(err) | |||
| 303 | + | } | |||
| 304 | + | r := m.Body() | |||
| 305 | + | r.Skip(m.Signature) | |||
| 306 | + | if !r.Done() { | |||
| 307 | + | t.Fatal(r.Err()) | |||
| 308 | + | } | |||
| 309 | + | }) | |||
| 310 | + | if allocs != 0 { | |||
| 311 | + | t.Fatalf("%v allocations per message, want 0", allocs) | |||
| 312 | + | } | |||
| 313 | + | } | |||
| 314 | + | ||||
| 315 | + | func FuzzParse(f *testing.F) { | |||
| 316 | + | var e Encoder | |||
| 317 | + | f.Add(append([]byte(nil), build(&e)...)) | |||
| 318 | + | f.Fuzz(func(t *testing.T, b []byte) { | |||
| 319 | + | if len(b) < headerLen { | |||
| 320 | + | return | |||
| 321 | + | } | |||
| 322 | + | n, err := frameLen(b) | |||
| 323 | + | if err != nil || n > len(b) { | |||
| 324 | + | return | |||
| 325 | + | } | |||
| 326 | + | var m Message | |||
| 327 | + | if parse(b[:n], &m) != nil { | |||
| 328 | + | return | |||
| 329 | + | } | |||
| 330 | + | r := m.Body() | |||
| 331 | + | r.Skip(m.Signature) | |||
| 332 | + | }) | |||
| 333 | + | } | |||
| 334 | + | ||||
| 335 | + | func FuzzSkip(f *testing.F) { | |||
| 336 | + | f.Add("a{sv}", []byte{8, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 'a', 0, 1, 'u', 0, 0, 0, 0, 7, 0, 0, 0}) | |||
| 337 | + | f.Add("(v)", []byte{1, 'v', 0}) | |||
| 338 | + | f.Fuzz(func(t *testing.T, sig string, b []byte) { | |||
| 339 | + | if !validSignature(sig) { | |||
| 340 | + | return | |||
| 341 | + | } | |||
| 342 | + | r := Reader{buf: b} | |||
| 343 | + | r.Skip(sig) | |||
| 344 | + | }) | |||
| 345 | + | } | |||
| 346 | + | ||||
| 347 | + | func BenchmarkEncodeParse(b *testing.B) { | |||
| 348 | + | var e Encoder | |||
| 349 | + | var m Message | |||
| 350 | + | b.ReportAllocs() | |||
| 351 | + | for b.Loop() { | |||
| 352 | + | buf := build(&e) | |||
| 353 | + | if err := parse(buf, &m); err != nil { | |||
| 354 | + | b.Fatal(err) | |||
| 355 | + | } | |||
| 356 | + | } | |||
| 357 | + | } | |||