103 lines
2.8 KiB
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...))
|
|
}
|