bine/control/conn.go

103 lines
2.8 KiB
Go

package control
import (
"fmt"
"io"
"net/textproto"
"sync"
)
// Conn is the connection to the Tor control port.
type Conn struct {
// DebugWriter is the writer that debug logs for this library (not Tor
// itself) will be written to. If nil, no debug logs are generated/written.
DebugWriter io.Writer
// This is the underlying connection.
conn *textproto.Conn
// This is set lazily by ProtocolInfo().
protocolInfo *ProtocolInfo
// True if Authenticate has been called successfully.
Authenticated bool
// The lock fot eventListeners
eventListenersLock sync.RWMutex
// The value slices can be traversed outside of lock, they are completely
// replaced on change, never mutated. But the map itself must be locked on
// when reading or writing.
eventListeners map[EventCode][]chan<- Event
// This mutex is locked on when an entire response needs to be read. It
// helps synchronize accesses to the response by the asynchronous response
// listeners and the synchronous responses.
readLock sync.Mutex
}
// NewConn creates a Conn from the given textproto connection.
func NewConn(conn *textproto.Conn) *Conn {
return &Conn{
conn: conn,
eventListeners: map[EventCode][]chan<- Event{},
}
}
func (c *Conn) sendRequestIgnoreResponse(format string, args ...interface{}) error {
_, err := c.SendRequest(format, args...)
return err
}
// SendRequest sends a synchronous request to Tor and awaits the response. If
// the response errors, the error result will be set, but the response will be
// set also. This is usually not directly used by callers, but instead called by
// higher-level methods.
func (c *Conn) SendRequest(format string, args ...interface{}) (*Response, error) {
if c.debugEnabled() {
c.debugf("Write line: %v", fmt.Sprintf(format, args...))
}
id, err := c.conn.Cmd(format, args...)
if err != nil {
return nil, err
}
c.readLock.Lock()
defer c.readLock.Unlock()
c.conn.StartResponse(id)
defer c.conn.EndResponse(id)
// Get the first non-async response
var resp *Response
for {
if resp, err = c.ReadResponse(); err != nil || !resp.IsAsync() {
break
}
c.relayAsyncEvents(resp)
}
if err == nil && !resp.IsOk() {
err = resp.Err
}
return resp, err
}
// Close sends a QUIT and closes the underlying Tor connection. This does not
// error if the QUIT is not accepted but does relay any error that occurs while
// closing the underlying connection.
func (c *Conn) Close() error {
// Ignore the response and ignore the error
c.Quit()
return c.conn.Close()
}
func (c *Conn) debugEnabled() bool {
return c.DebugWriter != nil
}
func (c *Conn) debugf(format string, args ...interface{}) {
if w := c.DebugWriter; w != nil {
fmt.Fprintf(w, format+"\n", args...)
}
}
func (*Conn) protoErr(format string, args ...interface{}) textproto.ProtocolError {
return textproto.ProtocolError(fmt.Sprintf(format, args...))
}