github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/dbusutil/dbustest/dbustest.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2020 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package dbustest 21 22 import ( 23 "bytes" 24 "encoding/binary" 25 "fmt" 26 "sync" 27 28 "github.com/godbus/dbus" 29 ) 30 31 // testDBusClientName is the unique name of the connected client. 32 const testDBusClientName = ":test" 33 34 // DBusHandlerFunc is the type of handler function for interacting with test DBus. 35 // 36 // The handler is called for each message that arrives to the bus from the test 37 // client. The handler can respond by returning zero or more messages. 38 // Typically one message is returned (method response or error). Additional 39 // messages can be returned to represent signals emitted during message 40 // handling. 41 // 42 // The handler is not called for internal messages related to DBus itself. 43 type DBusHandlerFunc func(msg *dbus.Message, n int) ([]*dbus.Message, error) 44 45 // testDBusStream provides io.ReadWriteCloser for dbus.NewConn. 46 // 47 // Internally the stream uses separate input and output buffers. Input buffer 48 // is written to by DBus. Output buffer is written to internally, in response 49 // to internal interactions with DBus or in response to message handler 50 // function, provided by test code, returning DBus messages to send. 51 // 52 // Before authentication is completed buffers are used to operate a 53 // line-oriented, text protocol. After authentication the buffers exchange DBus 54 // messages exclusively. 55 // 56 // The authentication phase is handled internally and is not exposed to tests. 57 // 58 // Anything written to the output buffer is passed to DBus for processing. 59 // Anything read from input buffer is decoded and either handled internally or 60 // delegated to the handler function. 61 // 62 // DBus implementation we use requires Read to block while data is unavailable 63 // so a condition variable is used to provide blocking behavior. 64 type testDBusStream struct { 65 handler DBusHandlerFunc 66 67 m sync.Mutex 68 readable sync.Cond 69 70 outputBuf, inputBuf bytes.Buffer 71 closed bool 72 authDone bool 73 n int 74 } 75 76 func (s *testDBusStream) decodeRequest() { 77 // s.m is locked 78 if !s.authDone { 79 // Before authentication is done process the text protocol anticipating 80 // the TEST authentication used by NewDBusTestConn call below. 81 msg := s.inputBuf.String() 82 s.inputBuf.Reset() 83 switch msg { 84 case "\x00": 85 // initial NUL byte, ignore 86 case "AUTH\r\n": 87 s.outputBuf.WriteString("REJECTED TEST\r\n") 88 case "AUTH TEST TEST\r\n": 89 s.outputBuf.WriteString("OK test://\r\n") 90 case "CANCEL\r\n": 91 s.outputBuf.WriteString("REJECTED\r\n") 92 case "BEGIN\r\n": 93 s.authDone = true 94 default: 95 panic(fmt.Errorf("unrecognized authentication message %q", msg)) 96 } 97 s.readable.Signal() 98 return 99 } 100 101 // After authentication the buffer must contain marshaled DBus messages. 102 msgIn, err := dbus.DecodeMessage(&s.inputBuf) 103 if err != nil { 104 panic(fmt.Errorf("cannot decode incoming message: %v", err)) 105 } 106 switch s.n { 107 case 0: 108 // The very first message we receive is a Hello message sent from 109 // NewDBusTestConn below. This message, along with the NameAcquired 110 // signal we send below, allow DBus to associate the client with the 111 // responses sent by test. 112 s.sendMsg(&dbus.Message{ 113 Type: dbus.TypeMethodReply, 114 Headers: map[dbus.HeaderField]dbus.Variant{ 115 dbus.FieldDestination: dbus.MakeVariant(testDBusClientName), 116 dbus.FieldSender: dbus.MakeVariant("org.freedesktop.DBus"), 117 dbus.FieldReplySerial: dbus.MakeVariant(msgIn.Serial()), 118 dbus.FieldSignature: dbus.MakeVariant(dbus.SignatureOf("")), 119 }, 120 Body: []interface{}{":test"}, 121 }) 122 s.sendMsg(&dbus.Message{ 123 Type: dbus.TypeSignal, 124 Headers: map[dbus.HeaderField]dbus.Variant{ 125 dbus.FieldPath: dbus.MakeVariant(dbus.ObjectPath("/org/freedesktop/DBus")), 126 dbus.FieldInterface: dbus.MakeVariant("org.freedesktop.DBus"), 127 dbus.FieldMember: dbus.MakeVariant("NameAcquired"), 128 dbus.FieldDestination: dbus.MakeVariant(testDBusClientName), 129 dbus.FieldSender: dbus.MakeVariant("org.freedesktop.DBus"), 130 dbus.FieldSignature: dbus.MakeVariant(dbus.SignatureOf("")), 131 }, 132 Body: []interface{}{testDBusClientName}, 133 }) 134 default: 135 msgOutList, err := s.handler(msgIn, s.n-1) 136 if err != nil { 137 panic("cannot handle message: " + err.Error()) 138 } 139 for _, msgOut := range msgOutList { 140 // Test code does not need to provide the address of the sender. 141 if _, ok := msgOut.Headers[dbus.FieldSender]; !ok { 142 msgOut.Headers[dbus.FieldSender] = dbus.MakeVariant(testDBusClientName) 143 } 144 s.sendMsg(msgOut) 145 } 146 } 147 s.n++ 148 } 149 150 func (s *testDBusStream) sendMsg(msg *dbus.Message) { 151 // TODO: handle big endian if we ever get big endian machines again. 152 if err := msg.EncodeTo(&s.outputBuf, binary.LittleEndian); err != nil { 153 panic(fmt.Errorf("cannot encode outgoing message: %v", err)) 154 } 155 // s.m is locked 156 s.readable.Signal() 157 } 158 159 func (s *testDBusStream) Read(p []byte) (n int, err error) { 160 s.m.Lock() 161 defer s.m.Unlock() 162 163 // When the buffer is empty block until more data arrives. DBus 164 // continuously blocks on reading and premature empty read is treated as an 165 // EOF, terminating the message flow. 166 if s.outputBuf.Len() == 0 { 167 s.readable.Wait() 168 } 169 170 if s.closed { 171 return 0, fmt.Errorf("stream is closed") 172 } 173 return s.outputBuf.Read(p) 174 } 175 176 func (s *testDBusStream) Write(p []byte) (n int, err error) { 177 s.m.Lock() 178 defer s.m.Unlock() 179 180 if s.closed { 181 return 0, fmt.Errorf("stream is closed") 182 } 183 184 n, err = s.inputBuf.Write(p) 185 s.decodeRequest() 186 return n, err 187 } 188 189 func (s *testDBusStream) Close() error { 190 s.m.Lock() 191 defer s.m.Unlock() 192 s.closed = true 193 s.readable.Signal() 194 return nil 195 } 196 197 func newTestDBusStream(handler DBusHandlerFunc) *testDBusStream { 198 s := &testDBusStream{handler: handler} 199 s.readable.L = &s.m 200 return s 201 } 202 203 // testAuth implements DBus authentication protocol used during testing. 204 type testAuth struct{} 205 206 func (a *testAuth) FirstData() (name, resp []byte, status dbus.AuthStatus) { 207 return []byte("TEST"), []byte("TEST"), dbus.AuthOk 208 } 209 210 func (a *testAuth) HandleData(data []byte) (resp []byte, status dbus.AuthStatus) { 211 return []byte(""), dbus.AuthOk 212 } 213 214 // Connection returns a DBus connection for writing unit tests. 215 // 216 // The handler function is called for each message sent to the bus. It can 217 // return any number of messages to send in response. The counter aids in 218 // testing a sequence of messages that is expected. 219 func Connection(handler DBusHandlerFunc) (*dbus.Conn, error) { 220 conn, err := dbus.NewConn(newTestDBusStream(handler)) 221 if err != nil { 222 return nil, err 223 } 224 if err = conn.Auth([]dbus.Auth{&testAuth{}}); err != nil { 225 _ = conn.Close() 226 return nil, err 227 } 228 if err = conn.Hello(); err != nil { 229 _ = conn.Close() 230 return nil, err 231 } 232 return conn, nil 233 }