This repository has been archived on 2019-09-10. You can view files and clone it, but cannot push or open issues or pull requests.
asaur/conn.go

234 lines
5.8 KiB
Go

// conn.go - Controller connection instance.
//
// To the extent possible under law, Yawning Angel waived all copyright
// and related or neighboring rights to bulb, using the creative
// commons "cc0" public domain dedication. See LICENSE or
// <http://creativecommons.org/publicdomain/zero/1.0/> for full details.
// package asaur is a Go language interface to a Tor control port.
package asaur
import (
"errors"
gofmt "fmt"
"io"
"log"
"net"
"net/textproto"
"sync"
)
const (
maxEventBacklog = 16
maxResponseBacklog = 16
)
// ErrNoAsyncReader is the error returned when the asynchronous event handling
// is requested, but the helper go routine has not been started.
var ErrNoAsyncReader = errors.New("event requested without an async reader")
// Conn is a control port connection instance.
type Conn struct {
conn *textproto.Conn
isAuthenticated bool
debugLog bool
cachedPI *ProtocolInfo
asyncReaderLock sync.Mutex
asyncReaderRunning bool
eventChan chan *Response
respChan chan *Response
closeWg sync.WaitGroup
rdErrLock sync.Mutex
rdErr error
}
func (c *Conn) setRdErr(err error, force bool) {
c.rdErrLock.Lock()
defer c.rdErrLock.Unlock()
if c.rdErr == nil || force {
c.rdErr = err
}
}
func (c *Conn) getRdErr() error {
c.rdErrLock.Lock()
defer c.rdErrLock.Unlock()
return c.rdErr
}
func (c *Conn) isAsyncReaderRunning() bool {
c.asyncReaderLock.Lock()
defer c.asyncReaderLock.Unlock()
return c.asyncReaderRunning
}
func (c *Conn) asyncReader() {
for {
resp, err := c.ReadResponse()
if err != nil {
c.setRdErr(err, false)
break
}
if resp.IsAsync() {
c.eventChan <- resp
} else {
c.respChan <- resp
}
}
close(c.eventChan)
close(c.respChan)
c.closeWg.Done()
// In theory, we would lock and set asyncReaderRunning to false here, but
// once it's started, the only way it returns is if there is a catastrophic
// failure, or a graceful shutdown. Changing this will require redoing how
// Close() works.
}
// Debug enables/disables debug logging of control port chatter.
func (c *Conn) Debug(enable bool) {
c.debugLog = enable
}
// Close closes the connection.
func (c *Conn) Close() error {
c.asyncReaderLock.Lock()
defer c.asyncReaderLock.Unlock()
err := c.conn.Close()
if err != nil && c.asyncReaderRunning {
c.closeWg.Wait()
}
c.setRdErr(io.ErrClosedPipe, true)
return err
}
// StartAsyncReader starts the asynchronous reader go routine that allows
// asynchronous events to be handled. It must not be called simultaniously
// with Read, Request, or ReadResponse or undefined behavior will occur.
func (c *Conn) StartAsyncReader() {
c.asyncReaderLock.Lock()
defer c.asyncReaderLock.Unlock()
if c.asyncReaderRunning {
return
}
// Allocate the channels and kick off the read worker.
c.eventChan = make(chan *Response, maxEventBacklog)
c.respChan = make(chan *Response, maxResponseBacklog)
c.closeWg.Add(1)
go c.asyncReader()
c.asyncReaderRunning = true
}
// NextEvent returns the next asynchronous event received, blocking if
// neccecary. In order to enable asynchronous event handling, StartAsyncReader
// must be called first.
func (c *Conn) NextEvent() (*Response, error) {
if err := c.getRdErr(); err != nil {
return nil, err
}
if !c.isAsyncReaderRunning() {
return nil, ErrNoAsyncReader
}
resp, ok := <-c.eventChan
if resp != nil {
return resp, nil
} else if !ok {
return nil, io.ErrClosedPipe
}
panic("BUG: NextEvent() returned a nil response and error")
}
// Request sends a raw control port request and returns the response.
// If the async. reader is not currently running, events received while waiting
// for the response will be silently dropped. Calling Request simultaniously
// with StartAsyncReader, Read, Write, or ReadResponse will lead to undefined
// behavior.
func (c *Conn) Request(fmt string, args ...interface{}) (*Response, error) {
if err := c.getRdErr(); err != nil {
return nil, err
}
asyncResp := c.isAsyncReaderRunning()
if c.debugLog {
log.Printf("C: %s", gofmt.Sprintf(fmt, args...))
}
id, err := c.conn.Cmd(fmt, args...)
if err != nil {
return nil, err
}
c.conn.StartResponse(id)
defer c.conn.EndResponse(id)
var resp *Response
if asyncResp {
var ok bool
resp, ok = <-c.respChan
if resp == nil && !ok {
return nil, io.ErrClosedPipe
}
} else {
// Event handing requires the asyncReader() goroutine, try to get a
// response, while silently swallowing events.
for resp == nil || resp.IsAsync() {
resp, err = c.ReadResponse()
if err != nil {
return nil, err
}
}
}
if resp == nil {
panic("BUG: Request() returned a nil response and error")
}
if resp.IsOk() {
return resp, nil
}
return resp, resp.Err
}
// Read reads directly from the control port connection. Mixing this call
// with Request, ReadResponse, or asynchronous events will lead to undefined
// behavior.
func (c *Conn) Read(p []byte) (int, error) {
return c.conn.R.Read(p)
}
// Write writes directly from the control port connection. Mixing this call
// with Request will lead to undefined behavior.
func (c *Conn) Write(p []byte) (int, error) {
n, err := c.conn.W.Write(p)
if err == nil {
// If the write succeeds, but the flush fails, n will be incorrect...
return n, c.conn.W.Flush()
}
return n, err
}
// Dial connects to a given network/address and returns a new Conn for the
// connection.
func Dial(network, addr string) (*Conn, error) {
c, err := net.Dial(network, addr)
if err != nil {
return nil, err
}
return NewConn(c), nil
}
// NewConn returns a new Conn using c for I/O.
func NewConn(c io.ReadWriteCloser) *Conn {
conn := new(Conn)
conn.conn = textproto.NewConn(c)
return conn
}
func newProtocolError(fmt string, args ...interface{}) textproto.ProtocolError {
return textproto.ProtocolError(gofmt.Sprintf(fmt, args...))
}
var _ io.ReadWriteCloser = (*Conn)(nil)