summaryrefslogtreecommitdiff
path: root/vendor/github.com/cretz/bine/control
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/cretz/bine/control')
-rw-r--r--vendor/github.com/cretz/bine/control/cmd_authenticate.go108
-rw-r--r--vendor/github.com/cretz/bine/control/cmd_circuit.go38
-rw-r--r--vendor/github.com/cretz/bine/control/cmd_conf.go65
-rw-r--r--vendor/github.com/cretz/bine/control/cmd_event.go1218
-rw-r--r--vendor/github.com/cretz/bine/control/cmd_hiddenservice.go23
-rw-r--r--vendor/github.com/cretz/bine/control/cmd_misc.go92
-rw-r--r--vendor/github.com/cretz/bine/control/cmd_onion.go201
-rw-r--r--vendor/github.com/cretz/bine/control/cmd_protocolinfo.go76
-rw-r--r--vendor/github.com/cretz/bine/control/cmd_stream.go31
-rw-r--r--vendor/github.com/cretz/bine/control/conn.go102
-rw-r--r--vendor/github.com/cretz/bine/control/doc.go10
-rw-r--r--vendor/github.com/cretz/bine/control/keyval.go40
-rw-r--r--vendor/github.com/cretz/bine/control/response.go106
-rw-r--r--vendor/github.com/cretz/bine/control/status.go64
14 files changed, 2174 insertions, 0 deletions
diff --git a/vendor/github.com/cretz/bine/control/cmd_authenticate.go b/vendor/github.com/cretz/bine/control/cmd_authenticate.go
new file mode 100644
index 0000000..c43d864
--- /dev/null
+++ b/vendor/github.com/cretz/bine/control/cmd_authenticate.go
@@ -0,0 +1,108 @@
+package control
+
+import (
+ "crypto/hmac"
+ "crypto/rand"
+ "crypto/sha256"
+ "encoding/hex"
+ "io/ioutil"
+ "strings"
+)
+
+// Authenticate authenticates with the Tor instance using the "best" possible
+// authentication method if not already authenticated and sets the Authenticated
+// field. The password argument is optional, and will only be used if the
+// "SAFECOOKIE" and "NULL" authentication methods are not available and
+// "HASHEDPASSWORD" is.
+func (c *Conn) Authenticate(password string) error {
+ if c.Authenticated {
+ return nil
+ }
+ // Determine the supported authentication methods, and the cookie path.
+ pi, err := c.ProtocolInfo()
+ if err != nil {
+ return err
+ }
+ // Get the bytes to pass to with authenticate
+ var authBytes []byte
+ if pi.HasAuthMethod("NULL") {
+ // No auth bytes
+ } else if pi.HasAuthMethod("SAFECOOKIE") {
+ if pi.CookieFile == "" {
+ return c.protoErr("Invalid (empty) COOKIEFILE")
+ }
+ cookie, err := ioutil.ReadFile(pi.CookieFile)
+ if err != nil {
+ return c.protoErr("Failed to read COOKIEFILE: %v", err)
+ } else if len(cookie) != 32 {
+ return c.protoErr("Invalid cookie file length: %v", len(cookie))
+ }
+
+ // Send an AUTHCHALLENGE command, and parse the response.
+ var clientNonce [32]byte
+ if _, err := rand.Read(clientNonce[:]); err != nil {
+ return c.protoErr("Failed to generate clientNonce: %v", err)
+ }
+ resp, err := c.SendRequest("AUTHCHALLENGE %s %s", "SAFECOOKIE", hex.EncodeToString(clientNonce[:]))
+ if err != nil {
+ return err
+ }
+ splitResp := strings.Split(resp.Reply, " ")
+ if len(splitResp) != 3 || !strings.HasPrefix(splitResp[1], "SERVERHASH=") ||
+ !strings.HasPrefix(splitResp[2], "SERVERNONCE=") {
+ return c.protoErr("Invalid AUTHCHALLENGE response")
+ }
+ serverHash, err := hex.DecodeString(splitResp[1][11:])
+ if err != nil {
+ return c.protoErr("Failed to decode ServerHash: %v", err)
+ }
+ if len(serverHash) != 32 {
+ return c.protoErr("Invalid ServerHash length: %d", len(serverHash))
+ }
+ serverNonce, err := hex.DecodeString(splitResp[2][12:])
+ if err != nil {
+ return c.protoErr("Failed to decode ServerNonce: %v", err)
+ }
+ if len(serverNonce) != 32 {
+ return c.protoErr("Invalid ServerNonce length: %d", len(serverNonce))
+ }
+
+ // Validate the ServerHash.
+ m := hmac.New(sha256.New, []byte("Tor safe cookie authentication server-to-controller hash"))
+ m.Write(cookie)
+ m.Write(clientNonce[:])
+ m.Write(serverNonce)
+ dervServerHash := m.Sum(nil)
+ if !hmac.Equal(serverHash, dervServerHash) {
+ return c.protoErr("invalid ServerHash: mismatch")
+ }
+
+ // Calculate the ClientHash, and issue the AUTHENTICATE.
+ m = hmac.New(sha256.New, []byte("Tor safe cookie authentication controller-to-server hash"))
+ m.Write(cookie)
+ m.Write(clientNonce[:])
+ m.Write(serverNonce)
+ authBytes = m.Sum(nil)
+ } else if pi.HasAuthMethod("HASHEDPASSWORD") {
+ // Despite the name HASHEDPASSWORD, the raw password is actually sent. According to the code, this can either be
+ // a QuotedString, or base16 encoded, so go with the later since it's easier to handle.
+ if password == "" {
+ return c.protoErr("password auth needs a password")
+ }
+ authBytes = []byte(password)
+ } else {
+ return c.protoErr("No supported authentication methods")
+ }
+ // Send it
+ if err = c.sendAuthenticate(authBytes); err == nil {
+ c.Authenticated = true
+ }
+ return err
+}
+
+func (c *Conn) sendAuthenticate(byts []byte) error {
+ if len(byts) == 0 {
+ return c.sendRequestIgnoreResponse("AUTHENTICATE")
+ }
+ return c.sendRequestIgnoreResponse("AUTHENTICATE %v", hex.EncodeToString(byts))
+}
diff --git a/vendor/github.com/cretz/bine/control/cmd_circuit.go b/vendor/github.com/cretz/bine/control/cmd_circuit.go
new file mode 100644
index 0000000..f796145
--- /dev/null
+++ b/vendor/github.com/cretz/bine/control/cmd_circuit.go
@@ -0,0 +1,38 @@
+package control
+
+import (
+ "strings"
+)
+
+// ExtendCircuit invokes EXTENDCIRCUIT and returns the circuit ID on success.
+func (c *Conn) ExtendCircuit(circuitID string, path []string, purpose string) (string, error) {
+ if circuitID == "" {
+ circuitID = "0"
+ }
+ cmd := "EXTENDCIRCUIT " + circuitID
+ if len(path) > 0 {
+ cmd += " " + strings.Join(path, ",")
+ }
+ if purpose != "" {
+ cmd += " purpose=" + purpose
+ }
+ resp, err := c.SendRequest(cmd)
+ if err != nil {
+ return "", err
+ }
+ return resp.Reply[strings.LastIndexByte(resp.Reply, ' ')+1:], nil
+}
+
+// SetCircuitPurpose invokes SETCIRCUITPURPOSE.
+func (c *Conn) SetCircuitPurpose(circuitID string, purpose string) error {
+ return c.sendRequestIgnoreResponse("SETCIRCUITPURPOSE %v purpose=%v", circuitID, purpose)
+}
+
+// CloseCircuit invokes CLOSECIRCUIT.
+func (c *Conn) CloseCircuit(circuitID string, flags []string) error {
+ cmd := "CLOSECIRCUIT " + circuitID
+ for _, flag := range flags {
+ cmd += " " + flag
+ }
+ return c.sendRequestIgnoreResponse(cmd)
+}
diff --git a/vendor/github.com/cretz/bine/control/cmd_conf.go b/vendor/github.com/cretz/bine/control/cmd_conf.go
new file mode 100644
index 0000000..c0446e5
--- /dev/null
+++ b/vendor/github.com/cretz/bine/control/cmd_conf.go
@@ -0,0 +1,65 @@
+package control
+
+import (
+ "strings"
+
+ "github.com/cretz/bine/torutil"
+)
+
+// SetConf invokes SETCONF.
+func (c *Conn) SetConf(entries ...*KeyVal) error {
+ return c.sendSetConf("SETCONF", entries)
+}
+
+// ResetConf invokes RESETCONF.
+func (c *Conn) ResetConf(entries ...*KeyVal) error {
+ return c.sendSetConf("RESETCONF", entries)
+}
+
+func (c *Conn) sendSetConf(cmd string, entries []*KeyVal) error {
+ for _, entry := range entries {
+ cmd += " " + entry.Key
+ if entry.ValSet() {
+ cmd += "=" + torutil.EscapeSimpleQuotedStringIfNeeded(entry.Val)
+ }
+ }
+ return c.sendRequestIgnoreResponse(cmd)
+}
+
+// GetConf invokes GETCONF and returns the values for the requested keys.
+func (c *Conn) GetConf(keys ...string) ([]*KeyVal, error) {
+ resp, err := c.SendRequest("GETCONF %v", strings.Join(keys, " "))
+ if err != nil {
+ return nil, err
+ }
+ data := resp.DataWithReply()
+ ret := make([]*KeyVal, 0, len(data))
+ for _, data := range data {
+ key, val, ok := torutil.PartitionString(data, '=')
+ entry := &KeyVal{Key: key}
+ if ok {
+ if entry.Val, err = torutil.UnescapeSimpleQuotedStringIfNeeded(val); err != nil {
+ return nil, err
+ }
+ if len(entry.Val) == 0 {
+ entry.ValSetAndEmpty = true
+ }
+ }
+ ret = append(ret, entry)
+ }
+ return ret, nil
+}
+
+// SaveConf invokes SAVECONF.
+func (c *Conn) SaveConf(force bool) error {
+ cmd := "SAVECONF"
+ if force {
+ cmd += " FORCE"
+ }
+ return c.sendRequestIgnoreResponse(cmd)
+}
+
+// LoadConf invokes LOADCONF.
+func (c *Conn) LoadConf(conf string) error {
+ return c.sendRequestIgnoreResponse("+LOADCONF\r\n%v\r\n.", conf)
+}
diff --git a/vendor/github.com/cretz/bine/control/cmd_event.go b/vendor/github.com/cretz/bine/control/cmd_event.go
new file mode 100644
index 0000000..578a800
--- /dev/null
+++ b/vendor/github.com/cretz/bine/control/cmd_event.go
@@ -0,0 +1,1218 @@
+package control
+
+import (
+ "context"
+ "errors"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/cretz/bine/torutil"
+)
+
+// EventCode represents an asynchronous event code (ref control spec 4.1).
+type EventCode string
+
+// Event codes
+const (
+ EventCodeAddrMap EventCode = "ADDRMAP"
+ EventCodeBandwidth EventCode = "BW"
+ EventCodeBuildTimeoutSet EventCode = "BUILDTIMEOUT_SET"
+ EventCodeCellStats EventCode = "CELL_STATS"
+ EventCodeCircuit EventCode = "CIRC"
+ EventCodeCircuitBandwidth EventCode = "CIRC_BW"
+ EventCodeCircuitMinor EventCode = "CIRC_MINOR"
+ EventCodeClientsSeen EventCode = "CLIENTS_SEEN"
+ EventCodeConfChanged EventCode = "CONF_CHANGED"
+ EventCodeConnBandwidth EventCode = "CONN_BW"
+ EventCodeDescChanged EventCode = "DESCCHANGED"
+ EventCodeGuard EventCode = "GUARD"
+ EventCodeHSDesc EventCode = "HS_DESC"
+ EventCodeHSDescContent EventCode = "HS_DESC_CONTENT"
+ EventCodeLogDebug EventCode = "DEBUG"
+ EventCodeLogErr EventCode = "ERR"
+ EventCodeLogInfo EventCode = "INFO"
+ EventCodeLogNotice EventCode = "NOTICE"
+ EventCodeLogWarn EventCode = "WARN"
+ EventCodeNetworkLiveness EventCode = "NETWORK_LIVENESS"
+ EventCodeNetworkStatus EventCode = "NS"
+ EventCodeNewConsensus EventCode = "NEWCONSENSUS"
+ EventCodeNewDesc EventCode = "NEWDESC"
+ EventCodeORConn EventCode = "ORCONN"
+ EventCodeSignal EventCode = "SIGNAL"
+ EventCodeStatusClient EventCode = "STATUS_CLIENT"
+ EventCodeStatusGeneral EventCode = "STATUS_GENERAL"
+ EventCodeStatusServer EventCode = "STATUS_SERVER"
+ EventCodeStream EventCode = "STREAM"
+ EventCodeStreamBandwidth EventCode = "STREAM_BW"
+ EventCodeTokenBucketEmpty EventCode = "TB_EMPTY"
+ EventCodeTransportLaunched EventCode = "TRANSPORT_LAUNCHED"
+)
+
+// EventCodeUnrecognized is a special event code that is only used with
+// AddEventListener and RemoveEventListener to listen for events that are
+// unrecognized by this library (i.e. UnrecognizedEvent).
+var EventCodeUnrecognized EventCode = "<unrecognized>"
+
+var recognizedEventCodes = []EventCode{
+ EventCodeAddrMap,
+ EventCodeBandwidth,
+ EventCodeBuildTimeoutSet,
+ EventCodeCellStats,
+ EventCodeCircuit,
+ EventCodeCircuitBandwidth,
+ EventCodeCircuitMinor,
+ EventCodeClientsSeen,
+ EventCodeConfChanged,
+ EventCodeConnBandwidth,
+ EventCodeDescChanged,
+ EventCodeGuard,
+ EventCodeHSDesc,
+ EventCodeHSDescContent,
+ EventCodeLogDebug,
+ EventCodeLogErr,
+ EventCodeLogInfo,
+ EventCodeLogNotice,
+ EventCodeLogWarn,
+ EventCodeNetworkLiveness,
+ EventCodeNetworkStatus,
+ EventCodeNewConsensus,
+ EventCodeNewDesc,
+ EventCodeORConn,
+ EventCodeSignal,
+ EventCodeStatusClient,
+ EventCodeStatusGeneral,
+ EventCodeStatusServer,
+ EventCodeStream,
+ EventCodeStreamBandwidth,
+ EventCodeTokenBucketEmpty,
+ EventCodeTransportLaunched,
+}
+
+var recognizedEventCodesByCode = mapEventCodes()
+
+func mapEventCodes() map[EventCode]struct{} {
+ ret := make(map[EventCode]struct{}, len(recognizedEventCodes))
+ for _, eventCode := range recognizedEventCodes {
+ ret[eventCode] = struct{}{}
+ }
+ return ret
+}
+
+// EventCodes returns a new slice of all event codes that are recognized (i.e.
+// does not include EventCodeUnrecognized).
+func EventCodes() []EventCode {
+ ret := make([]EventCode, len(recognizedEventCodes))
+ copy(ret, recognizedEventCodes)
+ return ret
+}
+
+// ErrEventWaitSynchronousResponseOccurred is returned from EventWait (see docs)
+var ErrEventWaitSynchronousResponseOccurred = errors.New("Synchronous event occurred during EventWait")
+
+// EventWait waits for the predicate to be satisified or a non-event message to
+// come through. If a non-event comes through, the error
+// ErrEventWaitSynchronousResponseOccurred is returned. If there is an error in
+// the predicate or if the context completes or there is an error internally
+// handling the event, the error is returned. Otherwise, the event that true was
+// returned from the predicate for is returned.
+func (c *Conn) EventWait(
+ ctx context.Context, events []EventCode, predicate func(Event) (bool, error),
+) (Event, error) {
+ eventCh := make(chan Event, 10)
+ defer close(eventCh)
+ if err := c.AddEventListener(eventCh, events...); err != nil {
+ return nil, err
+ }
+ defer c.RemoveEventListener(eventCh, events...)
+ eventCtx, eventCancel := context.WithCancel(ctx)
+ defer eventCancel()
+ errCh := make(chan error, 1)
+ go func() { errCh <- c.HandleEvents(eventCtx) }()
+ for {
+ select {
+ case <-eventCtx.Done():
+ return nil, eventCtx.Err()
+ case err := <-errCh:
+ return nil, err
+ case event := <-eventCh:
+ if ok, err := predicate(event); err != nil {
+ return nil, err
+ } else if ok {
+ return event, nil
+ }
+ }
+ }
+}
+
+// HandleEvents loops until the context is closed dispatching async events. Can
+// dispatch events even after context is done and of course during synchronous
+// request. This will always end with an error, either from ctx.Done() or from
+// an error reading/handling the event.
+func (c *Conn) HandleEvents(ctx context.Context) error {
+ errCh := make(chan error, 1)
+ go func() {
+ for ctx.Err() == nil {
+ if err := c.HandleNextEvent(); err != nil {
+ errCh <- err
+ break
+ }
+ }
+ }()
+ select {
+ case err := <-errCh:
+ return err
+ case <-ctx.Done():
+ return ctx.Err()
+ }
+}
+
+// HandleNextEvent attempts to read and handle the next event. It will return on
+// first message seen, event or not. Otherwise it will wait until there is a
+// message read.
+func (c *Conn) HandleNextEvent() error {
+ c.readLock.Lock()
+ defer c.readLock.Unlock()
+ // We'll just peek for the next 3 bytes and see if they are async
+ byts, err := c.conn.R.Peek(3)
+ if err != nil {
+ return err
+ }
+ statusCode, err := strconv.Atoi(string(byts))
+ if err != nil || statusCode != StatusAsyncEvent {
+ return err
+ }
+ // Read the entire thing and handle it
+ resp, err := c.ReadResponse()
+ if err != nil {
+ return err
+ }
+ c.relayAsyncEvents(resp)
+ return nil
+}
+
+// AddEventListener adds the given channel as an event listener for the given
+// events. Then Tor is notified about which events should be listened to.
+// Callers are expected to call RemoveEventListener for the channel and all
+// event codes used here before closing the channel. If no events are provided,
+// this is essentially a no-op. The EventCodeUnrecognized event code can be used
+// to listen for unrecognized events.
+func (c *Conn) AddEventListener(ch chan<- Event, events ...EventCode) error {
+ c.addEventListenerToMap(ch, events...)
+ // If there is an error updating the events, remove what we just added
+ err := c.sendSetEvents()
+ if err != nil {
+ c.removeEventListenerFromMap(ch, events...)
+ }
+ return err
+}
+
+// RemoveEventListener removes the given channel from being sent to by the given
+// event codes. It is not an error to remove a channel from events
+// AddEventListener was not called for. Tor is notified about events which may
+// no longer be listened to. If no events are provided, this is essentially a
+// no-op.
+func (c *Conn) RemoveEventListener(ch chan<- Event, events ...EventCode) error {
+ c.removeEventListenerFromMap(ch, events...)
+ return c.sendSetEvents()
+}
+
+func (c *Conn) addEventListenerToMap(ch chan<- Event, events ...EventCode) {
+ c.eventListenersLock.Lock()
+ defer c.eventListenersLock.Unlock()
+ for _, event := range events {
+ // Must completely replace the array, never mutate it
+ prevArr := c.eventListeners[event]
+ newArr := make([]chan<- Event, len(prevArr)+1)
+ copy(newArr, prevArr)
+ newArr[len(newArr)-1] = ch
+ c.eventListeners[event] = newArr
+ }
+}
+
+func (c *Conn) removeEventListenerFromMap(ch chan<- Event, events ...EventCode) {
+ c.eventListenersLock.Lock()
+ defer c.eventListenersLock.Unlock()
+ for _, event := range events {
+ arr := c.eventListeners[event]
+ index := -1
+ for i, listener := range arr {
+ if listener == ch {
+ index = i
+ break
+ }
+ }
+ if index != -1 {
+ if len(arr) == 1 {
+ delete(c.eventListeners, event)
+ } else {
+ // Must completely replace the array, never mutate it
+ newArr := make([]chan<- Event, len(arr)-1)
+ copy(newArr, arr[:index])
+ copy(newArr[index:], arr[index+1:])
+ c.eventListeners[event] = newArr
+ }
+ }
+ }
+}
+
+func (c *Conn) sendSetEvents() error {
+ c.eventListenersLock.RLock()
+ cmd := "SETEVENTS"
+ for event := range c.eventListeners {
+ cmd += " " + string(event)
+ }
+ c.eventListenersLock.RUnlock()
+ return c.sendRequestIgnoreResponse(cmd)
+}
+
+func (c *Conn) relayAsyncEvents(resp *Response) {
+ var code, data string
+ var dataArray []string
+ if len(resp.Data) == 1 {
+ // On single line, part up to space, newline, or EOL is the code, rest is data
+ if index := strings.Index(resp.Data[0], " "); index != -1 {
+ code, data = resp.Data[0][:index], resp.Data[0][index+1:]
+ } else if index := strings.Index(resp.Data[0], "\r\n"); index != -1 {
+ code, data = resp.Data[0][:index], resp.Data[0][index+2:]
+ } else {
+ code, data = resp.Data[0], ""
+ }
+ } else if len(resp.Data) > 0 {
+ // If there are multiple lines, the entire first line is the code
+ code, dataArray = resp.Data[0], resp.Data[1:]
+ } else {
+ // Otherwise, the reply line has the data
+ code, data, _ = torutil.PartitionString(resp.Reply, ' ')
+ }
+ // Only relay if there are chans
+ eventCode := EventCode(code)
+ c.eventListenersLock.RLock()
+ chans := c.eventListeners[eventCode]
+ if _, ok := recognizedEventCodesByCode[eventCode]; !ok {
+ chans = append(chans, c.eventListeners[EventCodeUnrecognized]...)
+ }
+ c.eventListenersLock.RUnlock()
+ if len(chans) == 0 {
+ return
+ }
+ // Parse the event and only send if known event
+ if event := ParseEvent(eventCode, data, dataArray); event != nil {
+ for _, ch := range chans {
+ // Just send, if closed or blocking, that's not our problem
+ ch <- event
+ }
+ }
+}
+
+// Zero on fail
+func parseISOTime(str string) time.Time {
+ // Essentially time.RFC3339 but without 'T' or TZ info
+ const layout = "2006-01-02 15:04:05"
+ ret, err := time.Parse(layout, str)
+ if err != nil {
+ ret = time.Time{}
+ }
+ return ret
+}
+
+// Zero on fail
+func parseISOTime2Frac(str string) time.Time {
+ // Essentially time.RFC3339Nano but without TZ info
+ const layout = "2006-01-02T15:04:05.999999999"
+ ret, err := time.Parse(layout, str)
+ if err != nil {
+ ret = time.Time{}
+ }
+ return ret
+}
+
+// Event is the base interface for all known asynchronous events.
+type Event interface {
+ Code() EventCode
+}
+
+// ParseEvent returns an Event for the given code and data info. Raw is the raw
+// single line if it is a single-line event (even if it has newlines), dataArray
+// is the array of lines for multi-line events. Only one of the two needs to be
+// set. The response is never nil, but may be UnrecognizedEvent. Format errors
+// are ignored per the Tor spec.
+func ParseEvent(code EventCode, raw string, dataArray []string) Event {
+ switch code {
+ case EventCodeAddrMap:
+ return ParseAddrMapEvent(raw)
+ case EventCodeBandwidth:
+ return ParseBandwidthEvent(raw)
+ case EventCodeBuildTimeoutSet:
+ return ParseBuildTimeoutSetEvent(raw)
+ case EventCodeCellStats:
+ return ParseCellStatsEvent(raw)
+ case EventCodeCircuit:
+ return ParseCircuitEvent(raw)
+ case EventCodeCircuitBandwidth:
+ return ParseCircuitBandwidthEvent(raw)
+ case EventCodeCircuitMinor:
+ return ParseCircuitMinorEvent(raw)
+ case EventCodeClientsSeen:
+ return ParseClientsSeenEvent(raw)
+ case EventCodeConfChanged:
+ return ParseConfChangedEvent(dataArray)
+ case EventCodeConnBandwidth:
+ return ParseConnBandwidthEvent(raw)
+ case EventCodeDescChanged:
+ return ParseDescChangedEvent(raw)
+ case EventCodeGuard:
+ return ParseGuardEvent(raw)
+ case EventCodeHSDesc:
+ return ParseHSDescEvent(raw)
+ case EventCodeHSDescContent:
+ return ParseHSDescContentEvent(raw)
+ case EventCodeLogDebug, EventCodeLogErr, EventCodeLogInfo, EventCodeLogNotice, EventCodeLogWarn:
+ return ParseLogEvent(code, raw)
+ case EventCodeNetworkLiveness:
+ return ParseNetworkLivenessEvent(raw)
+ case EventCodeNetworkStatus:
+ return ParseNetworkStatusEvent(raw)
+ case EventCodeNewConsensus:
+ return ParseNewConsensusEvent(raw)
+ case EventCodeNewDesc:
+ return ParseNewDescEvent(raw)
+ case EventCodeORConn:
+ return ParseORConnEvent(raw)
+ case EventCodeSignal:
+ return ParseSignalEvent(raw)
+ case EventCodeStatusClient, EventCodeStatusGeneral, EventCodeStatusServer:
+ return ParseStatusEvent(code, raw)
+ case EventCodeStream:
+ return ParseStreamEvent(raw)
+ case EventCodeStreamBandwidth:
+ return ParseStreamBandwidthEvent(raw)
+ case EventCodeTokenBucketEmpty:
+ return ParseTokenBucketEmptyEvent(raw)
+ case EventCodeTransportLaunched:
+ return ParseTransportLaunchedEvent(raw)
+ default:
+ return ParseUnrecognizedEvent(code, raw, dataArray)
+ }
+}
+
+// CircuitEvent is CIRC in spec.
+type CircuitEvent struct {
+ Raw string
+ CircuitID string
+ Status string
+ Path []string
+ BuildFlags []string
+ Purpose string
+ HSState string
+ RendQuery string
+ TimeCreated time.Time
+ Reason string
+ RemoteReason string
+ SocksUsername string
+ SocksPassword string
+}
+
+// ParseCircuitEvent parses the event.
+func ParseCircuitEvent(raw string) *CircuitEvent {
+ event := &CircuitEvent{Raw: raw}
+ event.CircuitID, raw, _ = torutil.PartitionString(raw, ' ')
+ var ok bool
+ event.Status, raw, ok = torutil.PartitionString(raw, ' ')
+ var attr string
+ first := true
+ for ok {
+ attr, raw, ok = torutil.PartitionString(raw, ' ')
+ key, val, _ := torutil.PartitionString(attr, '=')
+ switch key {
+ case "BUILD_FLAGS":
+ event.BuildFlags = strings.Split(val, ",")
+ case "PURPOSE":
+ event.Purpose = val
+ case "HS_STATE":
+ event.HSState = val
+ case "REND_QUERY":
+ event.RendQuery = val
+ case "TIME_CREATED":
+ event.TimeCreated = parseISOTime2Frac(val)
+ case "REASON":
+ event.Reason = val
+ case "REMOTE_REASON":
+ event.RemoteReason = val
+ case "SOCKS_USERNAME":
+ event.SocksUsername = val
+ case "SOCKS_PASSWORD":
+ event.SocksPassword = val
+ default:
+ if first {
+ event.Path = strings.Split(val, ",")
+ }
+ }
+ first = false
+ }
+ return event
+}
+
+// Code implements Event.Code
+func (*CircuitEvent) Code() EventCode { return EventCodeCircuit }
+
+// StreamEvent is STREAM in spec.
+type StreamEvent struct {
+ Raw string
+ StreamID string
+ Status string
+ CircuitID string
+ TargetAddress string
+ TargetPort int
+ Reason string
+ RemoteReason string
+ Source string
+ SourceAddress string
+ SourcePort int
+ Purpose string
+}
+
+// ParseStreamEvent parses the event.
+func ParseStreamEvent(raw string) *StreamEvent {
+ event := &StreamEvent{Raw: raw}
+ event.StreamID, raw, _ = torutil.PartitionString(raw, ' ')
+ event.Status, raw, _ = torutil.PartitionString(raw, ' ')
+ event.CircuitID, raw, _ = torutil.PartitionString(raw, ' ')
+ var ok bool
+ event.TargetAddress, raw, ok = torutil.PartitionString(raw, ' ')
+ if target, port, hasPort := torutil.PartitionStringFromEnd(event.TargetAddress, ':'); hasPort {
+ event.TargetAddress = target
+ event.TargetPort, _ = strconv.Atoi(port)
+ }
+ var attr string
+ for ok {
+ attr, raw, ok = torutil.PartitionString(raw, ' ')
+ key, val, _ := torutil.PartitionString(attr, '=')
+ switch key {
+ case "REASON":
+ event.Reason = val
+ case "REMOTE_REASON":
+ event.RemoteReason = val
+ case "SOURCE":
+ event.Source = val
+ case "SOURCE_ADDR":
+ event.SourceAddress = val
+ if source, port, hasPort := torutil.PartitionStringFromEnd(event.SourceAddress, ':'); hasPort {
+ event.SourceAddress = source
+ event.SourcePort, _ = strconv.Atoi(port)
+ }
+ case "PURPOSE":
+ event.Purpose = val
+ }
+ }
+ return event
+}
+
+// Code implements Event.Code
+func (*StreamEvent) Code() EventCode { return EventCodeStream }
+
+// ORConnEvent is ORCONN in spec.
+type ORConnEvent struct {
+ Raw string
+ Target string
+ Status string
+ Reason string
+ NumCircuits int
+ ConnID string
+}
+
+// ParseORConnEvent parses the event.
+func ParseORConnEvent(raw string) *ORConnEvent {
+ event := &ORConnEvent{Raw: raw}
+ event.Target, raw, _ = torutil.PartitionString(raw, ' ')
+ var ok bool
+ event.Status, raw, ok = torutil.PartitionString(raw, ' ')
+ var attr string
+ for ok {
+ attr, raw, ok = torutil.PartitionString(raw, ' ')
+ key, val, _ := torutil.PartitionString(attr, '=')
+ switch key {
+ case "REASON":
+ event.Reason = val
+ case "NCIRCS":
+ event.NumCircuits, _ = strconv.Atoi(val)
+ case "ID":
+ event.ConnID = val
+ }
+ }
+ return event
+}
+
+// Code implements Event.Code
+func (*ORConnEvent) Code() EventCode { return EventCodeORConn }
+
+// BandwidthEvent is BW in spec.
+type BandwidthEvent struct {
+ Raw string
+ BytesRead int64
+ BytesWritten int64
+}
+
+// ParseBandwidthEvent parses the event.
+func ParseBandwidthEvent(raw string) *BandwidthEvent {
+ event := &BandwidthEvent{Raw: raw}
+ var temp string
+ temp, raw, _ = torutil.PartitionString(raw, ' ')
+ event.BytesRead, _ = strconv.ParseInt(temp, 10, 64)
+ temp, raw, _ = torutil.PartitionString(raw, ' ')
+ event.BytesWritten, _ = strconv.ParseInt(temp, 10, 64)
+ return event
+}
+
+// Code implements Event.Code
+func (*BandwidthEvent) Code() EventCode { return EventCodeBandwidth }
+
+// LogEvent is DEBUG, ERR, INFO, NOTICE, and WARN in spec.
+type LogEvent struct {
+ Severity EventCode
+ Raw string
+}
+
+// ParseLogEvent parses the event.
+func ParseLogEvent(severity EventCode, raw string) *LogEvent {
+ return &LogEvent{Severity: severity, Raw: raw}
+}
+
+// Code implements Event.Code
+func (l *LogEvent) Code() EventCode { return l.Severity }
+
+// NewDescEvent is NEWDESC in spec.
+type NewDescEvent struct {
+ Raw string
+ Descs []string
+}
+
+// ParseNewDescEvent parses the event.
+func ParseNewDescEvent(raw string) *NewDescEvent {
+ return &NewDescEvent{Raw: raw, Descs: strings.Split(raw, " ")}
+}
+
+// Code implements Event.Code
+func (*NewDescEvent) Code() EventCode { return EventCodeNewDesc }
+
+// AddrMapEvent is ADDRMAP in spec.
+type AddrMapEvent struct {
+ Raw string
+ Address string
+ NewAddress string
+ ErrorCode string
+ // Zero if no expire
+ Expires time.Time
+ // Sans double quotes
+ Cached string
+}
+
+// ParseAddrMapEvent parses the event.
+func ParseAddrMapEvent(raw string) *AddrMapEvent {
+ event := &AddrMapEvent{Raw: raw}
+ event.Address, raw, _ = torutil.PartitionString(raw, ' ')
+ event.NewAddress, raw, _ = torutil.PartitionString(raw, ' ')
+ var ok bool
+ // Skip local expiration, use UTC one later
+ _, raw, ok = torutil.PartitionString(raw, ' ')
+ var attr string
+ for ok {
+ attr, raw, ok = torutil.PartitionString(raw, ' ')
+ key, val, _ := torutil.PartitionString(attr, '=')
+ switch key {
+ case "error":
+ event.ErrorCode = val
+ case "EXPIRES":
+ val, _ = torutil.UnescapeSimpleQuotedString(val)
+ event.Expires = parseISOTime(val)
+ case "CACHED":
+ event.Cached, _ = torutil.UnescapeSimpleQuotedStringIfNeeded(val)
+ }
+ }
+ return event
+}
+
+// Code implements Event.Code
+func (*AddrMapEvent) Code() EventCode { return EventCodeAddrMap }
+
+// DescChangedEvent is DESCCHANGED in spec.
+type DescChangedEvent struct {
+ Raw string
+}
+
+// ParseDescChangedEvent parses the event.
+func ParseDescChangedEvent(raw string) *DescChangedEvent {
+ return &DescChangedEvent{Raw: raw}
+}
+
+// Code implements Event.Code
+func (*DescChangedEvent) Code() EventCode { return EventCodeDescChanged }
+
+// StatusEvent is STATUS_CLIENT, STATUS_GENERAL, and STATUS_SERVER in spec.
+type StatusEvent struct {
+ Raw string
+ Type EventCode
+ Severity string
+ Action string
+ Arguments map[string]string
+}
+
+// ParseStatusEvent parses the event.
+func ParseStatusEvent(typ EventCode, raw string) *StatusEvent {
+ event := &StatusEvent{Raw: raw, Type: typ, Arguments: map[string]string{}}
+ event.Severity, raw, _ = torutil.PartitionString(raw, ' ')
+ var ok bool
+ event.Action, raw, ok = torutil.PartitionString(raw, ' ')
+ var attr string
+ for ok {
+ attr, raw, ok = torutil.PartitionString(raw, ' ')
+ key, val, _ := torutil.PartitionString(attr, '=')
+ event.Arguments[key], _ = torutil.UnescapeSimpleQuotedStringIfNeeded(val)
+ }
+ return event
+}
+
+// Code implements Event.Code
+func (s *StatusEvent) Code() EventCode { return s.Type }
+
+// GuardEvent is GUARD in spec.
+type GuardEvent struct {
+ Raw string
+ Type string
+ Name string
+ Status string
+}
+
+// ParseGuardEvent parses the event.
+func ParseGuardEvent(raw string) *GuardEvent {
+ event := &GuardEvent{Raw: raw}
+ event.Type, raw, _ = torutil.PartitionString(raw, ' ')
+ event.Name, raw, _ = torutil.PartitionString(raw, ' ')
+ event.Status, raw, _ = torutil.PartitionString(raw, ' ')
+ return event
+}
+
+// Code implements Event.Code
+func (*GuardEvent) Code() EventCode { return EventCodeGuard }
+
+// NetworkStatusEvent is NS in spec.
+type NetworkStatusEvent struct {
+ Raw string
+}
+
+// ParseNetworkStatusEvent parses the event.
+func ParseNetworkStatusEvent(raw string) *NetworkStatusEvent {
+ return &NetworkStatusEvent{Raw: raw}
+}
+
+// Code implements Event.Code
+func (*NetworkStatusEvent) Code() EventCode { return EventCodeNetworkStatus }
+
+// StreamBandwidthEvent is STREAM_BW in spec.
+type StreamBandwidthEvent struct {
+ Raw string
+ BytesRead int64
+ BytesWritten int64
+ Time time.Time
+}
+
+// ParseStreamBandwidthEvent parses the event.
+func ParseStreamBandwidthEvent(raw string) *StreamBandwidthEvent {
+ event := &StreamBandwidthEvent{Raw: raw}
+ var temp string
+ temp, raw, _ = torutil.PartitionString(raw, ' ')
+ event.BytesRead, _ = strconv.ParseInt(temp, 10, 64)
+ temp, raw, _ = torutil.PartitionString(raw, ' ')
+ event.BytesWritten, _ = strconv.ParseInt(temp, 10, 64)
+ temp, raw, _ = torutil.PartitionString(raw, ' ')
+ temp, _ = torutil.UnescapeSimpleQuotedString(temp)
+ event.Time = parseISOTime2Frac(temp)
+ return event
+}
+
+// Code implements Event.Code
+func (*StreamBandwidthEvent) Code() EventCode { return EventCodeStreamBandwidth }
+
+// ClientsSeenEvent is CLIENTS_SEEN in spec.
+type ClientsSeenEvent struct {
+ Raw string
+ TimeStarted time.Time
+ CountrySummary map[string]int
+ IPVersions map[string]int
+}
+
+// ParseClientsSeenEvent parses the event.
+func ParseClientsSeenEvent(raw string) *ClientsSeenEvent {
+ event := &ClientsSeenEvent{Raw: raw}
+ var temp string
+ var ok bool
+ temp, raw, ok = torutil.PartitionString(raw, ' ')
+ temp, _ = torutil.UnescapeSimpleQuotedString(temp)
+ event.TimeStarted = parseISOTime(temp)
+ strToMap := func(str string) map[string]int {
+ ret := map[string]int{}
+ for _, keyVal := range strings.Split(str, ",") {
+ key, val, _ := torutil.PartitionString(keyVal, '=')
+ ret[key], _ = strconv.Atoi(val)
+ }
+ return ret
+ }
+ var attr string
+ for ok {
+ attr, raw, ok = torutil.PartitionString(raw, ' ')
+ key, val, _ := torutil.PartitionString(attr, '=')
+ switch key {
+ case "CountrySummary":
+ event.CountrySummary = strToMap(val)
+ case "IPVersions":
+ event.IPVersions = strToMap(val)
+ }
+ }
+ return event
+}
+
+// Code implements Event.Code
+func (*ClientsSeenEvent) Code() EventCode { return EventCodeClientsSeen }
+
+// NewConsensusEvent is NEWCONSENSUS in spec.
+type NewConsensusEvent struct {
+ Raw string
+}
+
+// ParseNewConsensusEvent parses the event.
+func ParseNewConsensusEvent(raw string) *NewConsensusEvent {
+ return &NewConsensusEvent{Raw: raw}
+}
+
+// Code implements Event.Code
+func (*NewConsensusEvent) Code() EventCode { return EventCodeNewConsensus }
+
+// BuildTimeoutSetEvent is BUILDTIMEOUT_SET in spec.
+type BuildTimeoutSetEvent struct {
+ Raw string
+ Type string
+ TotalTimes int
+ Timeout time.Duration
+ Xm int
+ Alpha float32
+ Quantile float32
+ TimeoutRate float32
+ CloseTimeout time.Duration
+ CloseRate float32
+}
+
+// ParseBuildTimeoutSetEvent parses the event.
+func ParseBuildTimeoutSetEvent(raw string) *BuildTimeoutSetEvent {
+ event := &BuildTimeoutSetEvent{Raw: raw}
+ var ok bool
+ event.Type, raw, ok = torutil.PartitionString(raw, ' ')
+ _, raw, ok = torutil.PartitionString(raw, ' ')
+ var attr string
+ parseFloat := func(val string) float32 {
+ f, _ := strconv.ParseFloat(val, 32)
+ return float32(f)
+ }
+ for ok {
+ attr, raw, ok = torutil.PartitionString(raw, ' ')
+ key, val, _ := torutil.PartitionString(attr, '=')
+ switch key {
+ case "TOTAL_TIMES":
+ event.TotalTimes, _ = strconv.Atoi(val)
+ case "TIMEOUT_MS":
+ if ms, err := strconv.ParseInt(val, 10, 64); err == nil {
+ event.Timeout = time.Duration(ms) * time.Millisecond
+ }
+ case "XM":
+ event.Xm, _ = strconv.Atoi(val)
+ case "ALPHA":
+ event.Alpha = parseFloat(val)
+ case "CUTOFF_QUANTILE":
+ event.Quantile = parseFloat(val)
+ case "TIMEOUT_RATE":
+ event.TimeoutRate = parseFloat(val)
+ case "CLOSE_MS":
+ if ms, err := strconv.ParseInt(val, 10, 64); err == nil {
+ event.CloseTimeout = time.Duration(ms) * time.Millisecond
+ }
+ case "CLOSE_RATE":
+ event.CloseRate = parseFloat(val)
+ }
+ }
+ return event
+}
+
+// Code implements Event.Code
+func (*BuildTimeoutSetEvent) Code() EventCode { return EventCodeBuildTimeoutSet }
+
+// SignalEvent is SIGNAL in spec.
+type SignalEvent struct {
+ Raw string
+}
+
+// ParseSignalEvent parses the event.
+func ParseSignalEvent(raw string) *SignalEvent {
+ return &SignalEvent{Raw: raw}
+}
+
+// Code implements Event.Code
+func (*SignalEvent) Code() EventCode { return EventCodeSignal }
+
+// ConfChangedEvent is CONF_CHANGED in spec.
+type ConfChangedEvent struct {
+ Raw []string
+}
+
+// ParseConfChangedEvent parses the event.
+func ParseConfChangedEvent(raw []string) *ConfChangedEvent {
+ // TODO: break into KeyVal and unescape strings
+ return &ConfChangedEvent{Raw: raw}
+}
+
+// Code implements Event.Code
+func (*ConfChangedEvent) Code() EventCode { return EventCodeConfChanged }
+
+// CircuitMinorEvent is CIRC_MINOR in spec.
+type CircuitMinorEvent struct {
+ Raw string
+ CircuitID string
+ Event string
+ Path []string
+ BuildFlags []string
+ Purpose string
+ HSState string
+ RendQuery string
+ TimeCreated time.Time
+ OldPurpose string
+ OldHSState string
+}
+
+// ParseCircuitMinorEvent parses the event.
+func ParseCircuitMinorEvent(raw string) *CircuitMinorEvent {
+ event := &CircuitMinorEvent{Raw: raw}
+ event.CircuitID, raw, _ = torutil.PartitionString(raw, ' ')
+ var ok bool
+ event.Event, raw, ok = torutil.PartitionString(raw, ' ')
+ var attr string
+ first := true
+ for ok {
+ attr, raw, ok = torutil.PartitionString(raw, ' ')
+ key, val, _ := torutil.PartitionString(attr, '=')
+ switch key {
+ case "BUILD_FLAGS":
+ event.BuildFlags = strings.Split(val, ",")
+ case "PURPOSE":
+ event.Purpose = val
+ case "HS_STATE":
+ event.HSState = val
+ case "REND_QUERY":
+ event.RendQuery = val
+ case "TIME_CREATED":
+ event.TimeCreated = parseISOTime2Frac(val)
+ case "OLD_PURPOSE":
+ event.OldPurpose = val
+ case "OLD_HS_STATE":
+ event.OldHSState = val
+ default:
+ if first {
+ event.Path = strings.Split(val, ",")
+ }
+ }
+ first = false
+ }
+ return event
+}
+
+// Code implements Event.Code
+func (*CircuitMinorEvent) Code() EventCode { return EventCodeCircuitMinor }
+
+// TransportLaunchedEvent is TRANSPORT_LAUNCHED in spec.
+type TransportLaunchedEvent struct {
+ Raw string
+ Type string
+ Name string
+ Address string
+ Port int
+}
+
+// ParseTransportLaunchedEvent parses the event.
+func ParseTransportLaunchedEvent(raw string) *TransportLaunchedEvent {
+ event := &TransportLaunchedEvent{Raw: raw}
+ event.Type, raw, _ = torutil.PartitionString(raw, ' ')
+ event.Name, raw, _ = torutil.PartitionString(raw, ' ')
+ event.Address, raw, _ = torutil.PartitionString(raw, ' ')
+ var temp string
+ temp, raw, _ = torutil.PartitionString(raw, ' ')
+ event.Port, _ = strconv.Atoi(temp)
+ return event
+}
+
+// Code implements Event.Code
+func (*TransportLaunchedEvent) Code() EventCode { return EventCodeTransportLaunched }
+
+// ConnBandwidthEvent is CONN_BW in spec.
+type ConnBandwidthEvent struct {
+ Raw string
+ ConnID string
+ ConnType string
+ BytesRead int64
+ BytesWritten int64
+}
+
+// ParseConnBandwidthEvent parses the event.
+func ParseConnBandwidthEvent(raw string) *ConnBandwidthEvent {
+ event := &ConnBandwidthEvent{Raw: raw}
+ ok := true
+ var attr string
+ for ok {
+ attr, raw, ok = torutil.PartitionString(raw, ' ')
+ key, val, _ := torutil.PartitionString(attr, '=')
+ switch key {
+ case "ID":
+ event.ConnID = val
+ case "TYPE":
+ event.ConnType = val
+ case "READ":
+ event.BytesRead, _ = strconv.ParseInt(val, 10, 64)
+ case "WRITTEN":
+ event.BytesWritten, _ = strconv.ParseInt(val, 10, 64)
+ }
+ }
+ return event
+}
+
+// Code implements Event.Code
+func (*ConnBandwidthEvent) Code() EventCode { return EventCodeConnBandwidth }
+
+// CircuitBandwidthEvent is CIRC_BW in spec.
+type CircuitBandwidthEvent struct {
+ Raw string
+ CircuitID string
+ BytesRead int64
+ BytesWritten int64
+ Time time.Time
+}
+
+// ParseCircuitBandwidthEvent parses the event.
+func ParseCircuitBandwidthEvent(raw string) *CircuitBandwidthEvent {
+ event := &CircuitBandwidthEvent{Raw: raw}
+ ok := true
+ var attr string
+ for ok {
+ attr, raw, ok = torutil.PartitionString(raw, ' ')
+ key, val, _ := torutil.PartitionString(attr, '=')
+ switch key {
+ case "ID":
+ event.CircuitID = val
+ case "READ":
+ event.BytesRead, _ = strconv.ParseInt(val, 10, 64)
+ case "WRITTEN":
+ event.BytesWritten, _ = strconv.ParseInt(val, 10, 64)
+ case "TIME":
+ event.Time = parseISOTime2Frac(val)
+ }
+ }
+ return event
+}
+
+// Code implements Event.Code
+func (*CircuitBandwidthEvent) Code() EventCode { return EventCodeCircuitBandwidth }
+
+// CellStatsEvent is CELL_STATS in spec.
+type CellStatsEvent struct {
+ Raw string
+ CircuitID string
+ InboundQueueID string
+ InboundConnID string
+ InboundAdded map[string]int
+ InboundRemoved map[string]int
+ InboundTime map[string]int
+ OutboundQueueID string
+ OutboundConnID string
+ OutboundAdded map[string]int
+ OutboundRemoved map[string]int
+ OutboundTime map[string]int
+}
+
+// ParseCellStatsEvent parses the event.
+func ParseCellStatsEvent(raw string) *CellStatsEvent {
+ event := &CellStatsEvent{Raw: raw}
+ ok := true
+ var attr string
+ toIntMap := func(val string) map[string]int {
+ ret := map[string]int{}
+ for _, v := range strings.Split(val, ",") {
+ key, val, _ := torutil.PartitionString(v, ':')
+ ret[key], _ = strconv.Atoi(val)
+ }
+ return ret
+ }
+ for ok {
+ attr, raw, ok = torutil.PartitionString(raw, ' ')
+ key, val, _ := torutil.PartitionString(attr, '=')
+ switch key {
+ case "ID":
+ event.CircuitID = val
+ case "InboundQueue":
+ event.InboundQueueID = val
+ case "InboundConn":
+ event.InboundConnID = val
+ case "InboundAdded":
+ event.InboundAdded = toIntMap(val)
+ case "InboundRemoved":
+ event.InboundRemoved = toIntMap(val)
+ case "InboundTime":
+ event.OutboundTime = toIntMap(val)
+ case "OutboundQueue":
+ event.OutboundQueueID = val
+ case "OutboundConn":
+ event.OutboundConnID = val
+ case "OutboundAdded":
+ event.OutboundAdded = toIntMap(val)
+ case "OutboundRemoved":
+ event.OutboundRemoved = toIntMap(val)
+ case "OutboundTime":
+ event.OutboundTime = toIntMap(val)
+ }
+ }
+ return event
+}
+
+// Code implements Event.Code
+func (*CellStatsEvent) Code() EventCode { return EventCodeCellStats }
+
+// TokenBucketEmptyEvent is TB_EMPTY in spec.
+type TokenBucketEmptyEvent struct {
+ Raw string
+ BucketName string
+ ConnID string
+ ReadBucketEmpty time.Duration
+ WriteBucketEmpty time.Duration
+ LastRefil time.Duration
+}
+
+// ParseTokenBucketEmptyEvent parses the event.
+func ParseTokenBucketEmptyEvent(raw string) *TokenBucketEmptyEvent {
+ event := &TokenBucketEmptyEvent{Raw: raw}
+ var ok bool
+ event.BucketName, raw, ok = torutil.PartitionString(raw, ' ')
+ var attr string
+ for ok {
+ attr, raw, ok = torutil.PartitionString(raw, ' ')
+ key, val, _ := torutil.PartitionString(attr, '=')
+ switch key {
+ case "ID":
+ event.ConnID = val
+ case "READ":
+ i, _ := strconv.ParseInt(val, 10, 64)
+ event.ReadBucketEmpty = time.Duration(i) * time.Millisecond
+ case "WRITTEN":
+ i, _ := strconv.ParseInt(val, 10, 64)
+ event.WriteBucketEmpty = time.Duration(i) * time.Millisecond
+ case "LAST":
+ i, _ := strconv.ParseInt(val, 10, 64)
+ event.LastRefil = time.Duration(i) * time.Millisecond
+ }
+ }
+ return event
+}
+
+// Code implements Event.Code
+func (*TokenBucketEmptyEvent) Code() EventCode { return EventCodeTokenBucketEmpty }
+
+// HSDescEvent is HS_DESC in spec.
+type HSDescEvent struct {
+ Raw string
+ Action string
+ Address string
+ AuthType string
+ HSDir string
+ DescID string
+ Reason string
+ Replica int
+ HSDirIndex string
+}
+
+// ParseHSDescEvent parses the event.
+func ParseHSDescEvent(raw string) *HSDescEvent {
+ event := &HSDescEvent{Raw: raw}
+ event.Action, raw, _ = torutil.PartitionString(raw, ' ')
+ event.Address, raw, _ = torutil.PartitionString(raw, ' ')
+ event.AuthType, raw, _ = torutil.PartitionString(raw, ' ')
+ var ok bool
+ event.HSDir, raw, ok = torutil.PartitionString(raw, ' ')
+ var attr string
+ first := true
+ for ok {
+ attr, raw, ok = torutil.PartitionString(raw, ' ')
+ key, val, valOk := torutil.PartitionString(attr, '=')
+ switch key {
+ case "REASON":
+ event.Reason = val
+ case "REPLICA":
+ event.Replica, _ = strconv.Atoi(val)
+ case "HSDIR_INDEX":
+ event.HSDirIndex = val
+ default:
+ if first && !valOk {
+ event.DescID = attr
+ }
+ }
+ first = false
+ }
+ return event
+}
+
+// Code implements Event.Code
+func (*HSDescEvent) Code() EventCode { return EventCodeHSDesc }
+
+// HSDescContentEvent is HS_DESC_CONTENT in spec.
+type HSDescContentEvent struct {
+ Raw string
+ Address string
+ DescID string
+ HSDir string
+ Descriptor string
+}
+
+// ParseHSDescContentEvent parses the event.
+func ParseHSDescContentEvent(raw string) *HSDescContentEvent {
+ event := &HSDescContentEvent{Raw: raw}
+ event.Address, raw, _ = torutil.PartitionString(raw, ' ')
+ event.DescID, raw, _ = torutil.PartitionString(raw, ' ')
+ newlineIndex := strings.Index(raw, "\r\n")
+ if newlineIndex != -1 {
+ event.HSDir, event.Descriptor = raw[:newlineIndex], raw[newlineIndex+2:]
+ }
+ return event
+}
+
+// Code implements Event.Code
+func (*HSDescContentEvent) Code() EventCode { return EventCodeHSDescContent }
+
+// NetworkLivenessEvent is NETWORK_LIVENESS in spec.
+type NetworkLivenessEvent struct {
+ Raw string
+}
+
+// ParseNetworkLivenessEvent parses the event.
+func ParseNetworkLivenessEvent(raw string) *NetworkLivenessEvent {
+ return &NetworkLivenessEvent{Raw: raw}
+}
+
+// Code implements Event.Code
+func (*NetworkLivenessEvent) Code() EventCode { return EventCodeNetworkLiveness }
+
+// UnrecognizedEvent is any unrecognized event code.
+type UnrecognizedEvent struct {
+ EventCode EventCode
+ RawSingleLine string
+ RawMultiLine []string
+}
+
+// ParseUnrecognizedEvent creates an unrecognized event with the given values.
+func ParseUnrecognizedEvent(eventCode EventCode, rawSingleLine string, rawMultiLine []string) *UnrecognizedEvent {
+ return &UnrecognizedEvent{EventCode: eventCode, RawSingleLine: rawSingleLine, RawMultiLine: rawMultiLine}
+}
+
+// Code implements Event.Code
+func (u *UnrecognizedEvent) Code() EventCode { return u.EventCode }
diff --git a/vendor/github.com/cretz/bine/control/cmd_hiddenservice.go b/vendor/github.com/cretz/bine/control/cmd_hiddenservice.go
new file mode 100644
index 0000000..ab52142
--- /dev/null
+++ b/vendor/github.com/cretz/bine/control/cmd_hiddenservice.go
@@ -0,0 +1,23 @@
+package control
+
+// GetHiddenServiceDescriptorAsync invokes HSFETCH.
+func (c *Conn) GetHiddenServiceDescriptorAsync(address string, server string) error {
+ cmd := "HSFETCH " + address
+ if server != "" {
+ cmd += " SERVER=" + server
+ }
+ return c.sendRequestIgnoreResponse(cmd)
+}
+
+// PostHiddenServiceDescriptorAsync invokes HSPOST.
+func (c *Conn) PostHiddenServiceDescriptorAsync(desc string, servers []string, address string) error {
+ cmd := "+HSPOST"
+ for _, server := range servers {
+ cmd += " SERVER=" + server
+ }
+ if address != "" {
+ cmd += "HSADDRESS=" + address
+ }
+ cmd += "\r\n" + desc + "\r\n."
+ return c.sendRequestIgnoreResponse(cmd)
+}
diff --git a/vendor/github.com/cretz/bine/control/cmd_misc.go b/vendor/github.com/cretz/bine/control/cmd_misc.go
new file mode 100644
index 0000000..6c3fa49
--- /dev/null
+++ b/vendor/github.com/cretz/bine/control/cmd_misc.go
@@ -0,0 +1,92 @@
+package control
+
+import (
+ "strings"
+
+ "github.com/cretz/bine/torutil"
+)
+
+// Signal invokes SIGNAL.
+func (c *Conn) Signal(signal string) error {
+ return c.sendRequestIgnoreResponse("SIGNAL %v", signal)
+}
+
+// Quit invokes QUIT.
+func (c *Conn) Quit() error {
+ return c.sendRequestIgnoreResponse("QUIT")
+}
+
+// MapAddresses invokes MAPADDRESS and returns mapped addresses.
+func (c *Conn) MapAddresses(addresses ...*KeyVal) ([]*KeyVal, error) {
+ cmd := "MAPADDRESS"
+ for _, address := range addresses {
+ cmd += " " + address.Key + "=" + address.Val
+ }
+ resp, err := c.SendRequest(cmd)
+ if err != nil {
+ return nil, err
+ }
+ data := resp.DataWithReply()
+ ret := make([]*KeyVal, 0, len(data))
+ for _, address := range data {
+ mappedAddress := &KeyVal{}
+ mappedAddress.Key, mappedAddress.Val, _ = torutil.PartitionString(address, '=')
+ ret = append(ret, mappedAddress)
+ }
+ return ret, nil
+}
+
+// GetInfo invokes GETINTO and returns values for requested keys.
+func (c *Conn) GetInfo(keys ...string) ([]*KeyVal, error) {
+ resp, err := c.SendRequest("GETINFO %v", strings.Join(keys, " "))
+ if err != nil {
+ return nil, err
+ }
+ ret := make([]*KeyVal, 0, len(resp.Data))
+ for _, val := range resp.Data {
+ infoVal := &KeyVal{}
+ infoVal.Key, infoVal.Val, _ = torutil.PartitionString(val, '=')
+ if infoVal.Val, err = torutil.UnescapeSimpleQuotedStringIfNeeded(infoVal.Val); err != nil {
+ return nil, err
+ }
+ ret = append(ret, infoVal)
+ }
+ return ret, nil
+}
+
+// PostDescriptor invokes POSTDESCRIPTOR.
+func (c *Conn) PostDescriptor(descriptor string, purpose string, cache string) error {
+ cmd := "+POSTDESCRIPTOR"
+ if purpose != "" {
+ cmd += " purpose=" + purpose
+ }
+ if cache != "" {
+ cmd += " cache=" + cache
+ }
+ cmd += "\r\n" + descriptor + "\r\n."
+ return c.sendRequestIgnoreResponse(cmd)
+}
+
+// UseFeatures invokes USEFEATURE.
+func (c *Conn) UseFeatures(features ...string) error {
+ return c.sendRequestIgnoreResponse("USEFEATURE " + strings.Join(features, " "))
+}
+
+// ResolveAsync invokes RESOLVE.
+func (c *Conn) ResolveAsync(address string, reverse bool) error {
+ cmd := "RESOLVE "
+ if reverse {
+ cmd += "mode=reverse "
+ }
+ return c.sendRequestIgnoreResponse(cmd + address)
+}
+
+// TakeOwnership invokes TAKEOWNERSHIP.
+func (c *Conn) TakeOwnership() error {
+ return c.sendRequestIgnoreResponse("TAKEOWNERSHIP")
+}
+
+// DropGuards invokes DROPGUARDS.
+func (c *Conn) DropGuards() error {
+ return c.sendRequestIgnoreResponse("DROPGUARDS")
+}
diff --git a/vendor/github.com/cretz/bine/control/cmd_onion.go b/vendor/github.com/cretz/bine/control/cmd_onion.go
new file mode 100644
index 0000000..b298d36
--- /dev/null
+++ b/vendor/github.com/cretz/bine/control/cmd_onion.go
@@ -0,0 +1,201 @@
+package control
+
+import (
+ "crypto/rsa"
+ "crypto/x509"
+ "encoding/base64"
+ "fmt"
+ "strconv"
+ "strings"
+
+ "github.com/cretz/bine/torutil"
+ "github.com/cretz/bine/torutil/ed25519"
+)
+
+// KeyType is a key type for Key in AddOnion.
+type KeyType string
+
+const (
+ // KeyTypeNew is NEW.
+ KeyTypeNew KeyType = "NEW"
+ // KeyTypeRSA1024 is RSA1024.
+ KeyTypeRSA1024 KeyType = "RSA1024"
+ // KeyTypeED25519V3 is ED25519-V3.
+ KeyTypeED25519V3 KeyType = "ED25519-V3"
+)
+
+// KeyAlgo is a key algorithm for GenKey on AddOnion.
+type KeyAlgo string
+
+const (
+ // KeyAlgoBest is BEST.
+ KeyAlgoBest KeyAlgo = "BEST"
+ // KeyAlgoRSA1024 is RSA1024.
+ KeyAlgoRSA1024 KeyAlgo = "RSA1024"
+ // KeyAlgoED25519V3 is ED25519-V3.
+ KeyAlgoED25519V3 KeyAlgo = "ED25519-V3"
+)
+
+// Key is a type of key to use for AddOnion. Implementations include GenKey,
+// RSAKey, and ED25519Key.
+type Key interface {
+ // Type is the KeyType for AddOnion.
+ Type() KeyType
+ // Blob is the serialized key for AddOnion.
+ Blob() string
+}
+
+// KeyFromString creates a Key for AddOnion based on a response string.
+func KeyFromString(str string) (Key, error) {
+ typ, blob, _ := torutil.PartitionString(str, ':')
+ switch KeyType(typ) {
+ case KeyTypeNew:
+ return GenKeyFromBlob(blob), nil
+ case KeyTypeRSA1024:
+ return RSA1024KeyFromBlob(blob)
+ case KeyTypeED25519V3:
+ return ED25519KeyFromBlob(blob)
+ default:
+ return nil, fmt.Errorf("Unrecognized key type: %v", typ)
+ }
+}
+
+// GenKey is a Key for AddOnion that asks Tor to generate a key for the given
+// algorithm.
+type GenKey KeyAlgo
+
+// GenKeyFromBlob creates a GenKey for the given response blob which is a
+// KeyAlgo.
+func GenKeyFromBlob(blob string) GenKey { return GenKey(KeyAlgo(blob)) }
+
+// Type implements Key.Type.
+func (GenKey) Type() KeyType { return KeyTypeNew }
+
+// Blob implements Key.Blob.
+func (g GenKey) Blob() string { return string(g) }
+
+// RSAKey is a Key for AddOnion that is a RSA-1024 key (i.e. v2).
+type RSAKey struct{ *rsa.PrivateKey }
+
+// RSA1024KeyFromBlob creates a RSAKey for the given response blob.
+func RSA1024KeyFromBlob(blob string) (*RSAKey, error) {
+ byts, err := base64.StdEncoding.DecodeString(blob)
+ if err != nil {
+ return nil, err
+ }
+ rsaKey, err := x509.ParsePKCS1PrivateKey(byts)
+ if err != nil {
+ return nil, err
+ }
+ return &RSAKey{rsaKey}, nil
+}
+
+// Type implements Key.Type.
+func (*RSAKey) Type() KeyType { return KeyTypeRSA1024 }
+
+// Blob implements Key.Blob.
+func (r *RSAKey) Blob() string {
+ return base64.StdEncoding.EncodeToString(x509.MarshalPKCS1PrivateKey(r.PrivateKey))
+}
+
+// ED25519Key is a Key for AddOnion that is a ed25519 key (i.e. v3).
+type ED25519Key struct{ ed25519.KeyPair }
+
+// ED25519KeyFromBlob creates a ED25519Key for the given response blob.
+func ED25519KeyFromBlob(blob string) (*ED25519Key, error) {
+ byts, err := base64.StdEncoding.DecodeString(blob)
+ if err != nil {
+ return nil, err
+ }
+ return &ED25519Key{ed25519.PrivateKey(byts).KeyPair()}, nil
+}
+
+// Type implements Key.Type.
+func (*ED25519Key) Type() KeyType { return KeyTypeED25519V3 }
+
+// Blob implements Key.Blob.
+func (e *ED25519Key) Blob() string { return base64.StdEncoding.EncodeToString(e.PrivateKey()) }
+
+// AddOnionRequest is a set of request params for AddOnion.
+type AddOnionRequest struct {
+ // Key is the key to use or GenKey if Tor should generate it.
+ Key Key
+ // Flags are ADD_ONION flags.
+ Flags []string
+ // MaxStreams is ADD_ONION MaxStreams.
+ MaxStreams int
+ // Ports are ADD_ONION Port values. Key is virtual port, Val is target
+ // port (or can be empty to use virtual port).
+ Ports []*KeyVal
+ // ClientAuths are ADD_ONION ClientAuth values. If value is empty string,
+ // Tor will generate the password.
+ ClientAuths map[string]string
+}
+
+// AddOnionResponse is the response for AddOnion.
+type AddOnionResponse struct {
+ // ServiceID is the ADD_ONION response ServiceID value.
+ ServiceID string
+ // Key is the ADD_ONION response PrivateKey value.
+ Key Key
+ // ClientAuths are the ADD_ONION response ClientAuth values.
+ ClientAuths map[string]string
+ // RawResponse is the raw ADD_ONION response.
+ RawResponse *Response
+}
+
+// AddOnion invokes ADD_ONION and returns its response.
+func (c *Conn) AddOnion(req *AddOnionRequest) (*AddOnionResponse, error) {
+ // Build command
+ if req.Key == nil {
+ return nil, c.protoErr("Key required")
+ }
+ cmd := "ADD_ONION " + string(req.Key.Type()) + ":" + req.Key.Blob()
+ if len(req.Flags) > 0 {
+ cmd += " Flags=" + strings.Join(req.Flags, ",")
+ }
+ if req.MaxStreams > 0 {
+ cmd += " MaxStreams=" + strconv.Itoa(req.MaxStreams)
+ }
+ for _, port := range req.Ports {
+ cmd += " Port=" + port.Key
+ if port.Val != "" {
+ cmd += "," + port.Val
+ }
+ }
+ for name, blob := range req.ClientAuths {
+ cmd += " ClientAuth=" + name
+ if blob != "" {
+ cmd += ":" + blob
+ }
+ }
+ // Invoke and read response
+ resp, err := c.SendRequest(cmd)
+ if err != nil {
+ return nil, err
+ }
+ ret := &AddOnionResponse{RawResponse: resp}
+ for _, data := range resp.Data {
+ key, val, _ := torutil.PartitionString(data, '=')
+ switch key {
+ case "ServiceID":
+ ret.ServiceID = val
+ case "PrivateKey":
+ if ret.Key, err = KeyFromString(val); err != nil {
+ return nil, err
+ }
+ case "ClientAuth":
+ name, pass, _ := torutil.PartitionString(val, ':')
+ if ret.ClientAuths == nil {
+ ret.ClientAuths = map[string]string{}
+ }
+ ret.ClientAuths[name] = pass
+ }
+ }
+ return ret, nil
+}
+
+// DelOnion invokes DELONION.
+func (c *Conn) DelOnion(serviceID string) error {
+ return c.sendRequestIgnoreResponse("DEL_ONION %v", serviceID)
+}
diff --git a/vendor/github.com/cretz/bine/control/cmd_protocolinfo.go b/vendor/github.com/cretz/bine/control/cmd_protocolinfo.go
new file mode 100644
index 0000000..346b36d
--- /dev/null
+++ b/vendor/github.com/cretz/bine/control/cmd_protocolinfo.go
@@ -0,0 +1,76 @@
+package control
+
+import (
+ "strings"
+
+ "github.com/cretz/bine/torutil"
+)
+
+// ProtocolInfo is the protocol info result of Conn.ProtocolInfo.
+type ProtocolInfo struct {
+ AuthMethods []string
+ CookieFile string
+ TorVersion string
+ RawResponse *Response
+}
+
+// HasAuthMethod checks if ProtocolInfo contains the requested auth method.
+func (p *ProtocolInfo) HasAuthMethod(authMethod string) bool {
+ for _, m := range p.AuthMethods {
+ if m == authMethod {
+ return true
+ }
+ }
+ return false
+}
+
+// ProtocolInfo invokes PROTOCOLINFO on first invocation and returns a cached
+// result on all others.
+func (c *Conn) ProtocolInfo() (*ProtocolInfo, error) {
+ var err error
+ if c.protocolInfo == nil {
+ c.protocolInfo, err = c.sendProtocolInfo()
+ }
+ return c.protocolInfo, err
+}
+
+func (c *Conn) sendProtocolInfo() (*ProtocolInfo, error) {
+ resp, err := c.SendRequest("PROTOCOLINFO")
+ if err != nil {
+ return nil, err
+ }
+ // Check data vals
+ ret := &ProtocolInfo{RawResponse: resp}
+ for _, piece := range resp.Data {
+ key, val, ok := torutil.PartitionString(piece, ' ')
+ if !ok {
+ continue
+ }
+ switch key {
+ case "PROTOCOLINFO":
+ if val != "1" {
+ return nil, c.protoErr("Invalid PIVERSION: %v", val)
+ }
+ case "AUTH":
+ methods, cookieFile, _ := torutil.PartitionString(val, ' ')
+ if !strings.HasPrefix(methods, "METHODS=") {
+ continue
+ }
+ if cookieFile != "" {
+ if !strings.HasPrefix(cookieFile, "COOKIEFILE=") {
+ continue
+ }
+ if ret.CookieFile, err = torutil.UnescapeSimpleQuotedString(cookieFile[11:]); err != nil {
+ continue
+ }
+ }
+ ret.AuthMethods = strings.Split(methods[8:], ",")
+ case "VERSION":
+ torVersion, _, _ := torutil.PartitionString(val, ' ')
+ if strings.HasPrefix(torVersion, "Tor=") {
+ ret.TorVersion, err = torutil.UnescapeSimpleQuotedString(torVersion[4:])
+ }
+ }
+ }
+ return ret, nil
+}
diff --git a/vendor/github.com/cretz/bine/control/cmd_stream.go b/vendor/github.com/cretz/bine/control/cmd_stream.go
new file mode 100644
index 0000000..6fde0a8
--- /dev/null
+++ b/vendor/github.com/cretz/bine/control/cmd_stream.go
@@ -0,0 +1,31 @@
+package control
+
+import (
+ "strconv"
+)
+
+// AttachStream invokes ATTACHSTREAM.
+func (c *Conn) AttachStream(streamID string, circuitID string, hopNum int) error {
+ if circuitID == "" {
+ circuitID = "0"
+ }
+ cmd := "ATTACHSTREAM " + streamID + " " + circuitID
+ if hopNum > 0 {
+ cmd += " HOP=" + strconv.Itoa(hopNum)
+ }
+ return c.sendRequestIgnoreResponse(cmd)
+}
+
+// RedirectStream invokes REDIRECTSTREAM.
+func (c *Conn) RedirectStream(streamID string, address string, port int) error {
+ cmd := "REDIRECTSTREAM " + streamID + " " + address
+ if port > 0 {
+ cmd += " " + strconv.Itoa(port)
+ }
+ return c.sendRequestIgnoreResponse(cmd)
+}
+
+// CloseStream invokes CLOSESTREAM.
+func (c *Conn) CloseStream(streamID string, reason string) error {
+ return c.sendRequestIgnoreResponse("CLOSESTREAM %v %v", streamID, reason)
+}
diff --git a/vendor/github.com/cretz/bine/control/conn.go b/vendor/github.com/cretz/bine/control/conn.go
new file mode 100644
index 0000000..02995e8
--- /dev/null
+++ b/vendor/github.com/cretz/bine/control/conn.go
@@ -0,0 +1,102 @@
+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...))
+}
diff --git a/vendor/github.com/cretz/bine/control/doc.go b/vendor/github.com/cretz/bine/control/doc.go
new file mode 100644
index 0000000..2be3dc1
--- /dev/null
+++ b/vendor/github.com/cretz/bine/control/doc.go
@@ -0,0 +1,10 @@
+// Package control implements a low-level client for the Tor control spec
+// version 1.
+//
+// The primary entrypoint is the Conn struct, instantiated with NewConn. This is
+// the low-level layer to the control port of an already-running Tor instance.
+// Most developers will prefer the tor package adjacent to this one for a higher
+// level abstraction over the process and this connection.
+//
+// Some of this code is lifted from https://github.com/yawning/bulb with thanks.
+package control
diff --git a/vendor/github.com/cretz/bine/control/keyval.go b/vendor/github.com/cretz/bine/control/keyval.go
new file mode 100644
index 0000000..00cefd8
--- /dev/null
+++ b/vendor/github.com/cretz/bine/control/keyval.go
@@ -0,0 +1,40 @@
+package control
+
+// KeyVal is a simple key-value struct. In cases where Val can be nil, an empty
+// string represents that unless ValSetAndEmpty is true.
+type KeyVal struct {
+ // Key is the always-present key
+ Key string
+
+ // Val is the value. If it's an empty string and nils are accepted/supported
+ // where this is used, it means nil unless ValSetAndEmpty is true.
+ Val string
+
+ // ValSetAndEmpty is true when Val is an empty string, the associated
+ // command supports nils, and Val should NOT be treated as nil. False
+ // otherwise.
+ ValSetAndEmpty bool
+}
+
+// NewKeyVal creates a new key-value pair.
+func NewKeyVal(key string, val string) *KeyVal {
+ return &KeyVal{Key: key, Val: val}
+}
+
+// KeyVals creates multiple new key-value pairs from the given strings. The
+// provided set of strings must have a length that is a multiple of 2.
+func KeyVals(keysAndVals ...string) []*KeyVal {
+ if len(keysAndVals)%2 != 0 {
+ panic("Expected multiple of 2")
+ }
+ ret := make([]*KeyVal, len(keysAndVals)/2)
+ for i := 0; i < len(ret); i++ {
+ ret[i] = NewKeyVal(keysAndVals[i*2], keysAndVals[i*2+1])
+ }
+ return ret
+}
+
+// ValSet returns true if Val is either non empty or ValSetAndEmpty is true.
+func (k *KeyVal) ValSet() bool {
+ return len(k.Val) > 0 || k.ValSetAndEmpty
+}
diff --git a/vendor/github.com/cretz/bine/control/response.go b/vendor/github.com/cretz/bine/control/response.go
new file mode 100644
index 0000000..705905e
--- /dev/null
+++ b/vendor/github.com/cretz/bine/control/response.go
@@ -0,0 +1,106 @@
+package control
+
+import (
+ "net/textproto"
+ "strconv"
+ "strings"
+)
+
+// Response is a response to a control port command or an asynchronous event.
+type Response struct {
+ // Err is the status code and string representation associated with a
+ // response. Responses that have completed successfully will also have Err
+ // set to indicate such.
+ Err *textproto.Error
+
+ // Reply is the text on the EndReplyLine of the response.
+ Reply string
+
+ // Data is the MidReplyLines/DataReplyLines of the response. Dot encoded
+ // data is "decoded" and presented as a single string (terminal ".CRLF"
+ // removed, all intervening CRs stripped).
+ Data []string
+
+ // RawLines is all of the lines of a response, without CRLFs.
+ RawLines []string
+}
+
+// IsOk returns true if the response status code indicates success or an
+// asynchronous event.
+func (r *Response) IsOk() bool {
+ switch r.Err.Code {
+ case StatusOk, StatusOkUnnecessary, StatusAsyncEvent:
+ return true
+ default:
+ return false
+ }
+}
+
+// DataWithReply returns a combination of Data and Reply to give a full set of
+// the lines of the response.
+func (r *Response) DataWithReply() []string {
+ ret := make([]string, len(r.Data)+1)
+ copy(ret, r.Data)
+ ret[len(ret)-1] = r.Reply
+ return ret
+}
+
+// IsAsync returns true if the response is an asynchronous event.
+func (r *Response) IsAsync() bool {
+ return r.Err.Code == StatusAsyncEvent
+}
+
+// ReadResponse returns the next response object.
+func (c *Conn) ReadResponse() (*Response, error) {
+ var resp *Response
+ var statusCode int
+ for {
+ line, err := c.conn.ReadLine()
+ if err != nil {
+ return nil, err
+ }
+ c.debugf("Read line: %v", line)
+
+ // Parse the line that was just read.
+ if len(line) < 4 {
+ return nil, c.protoErr("Truncated response: %v", line)
+ }
+ if code, err := strconv.Atoi(line[0:3]); err != nil || code < 100 {
+ return nil, c.protoErr("Invalid status code: %v", line[0:3])
+ } else if resp == nil {
+ resp = &Response{}
+ statusCode = code
+ } else if code != statusCode {
+ // The status code should stay fixed for all lines of the response, since events can't be interleaved with
+ // response lines.
+ return nil, c.protoErr("Status code changed: %v != %v", code, statusCode)
+ }
+ resp.RawLines = append(resp.RawLines, line)
+ switch line[3] {
+ case ' ':
+ // Final line in the response.
+ resp.Reply = line[4:]
+ resp.Err = statusCodeToError(statusCode, resp.Reply)
+ return resp, nil
+ case '-':
+ // Continuation, keep reading.
+ resp.Data = append(resp.Data, line[4:])
+ case '+':
+ // A "dot-encoded" payload follows.
+ dotBody, err := c.conn.ReadDotBytes()
+ if err != nil {
+ return nil, err
+ }
+ dotBodyStr := strings.TrimRight(string(dotBody), "\n\r")
+ // c.debugf("Read dot body:\n---\n%v\n---", dotBodyStr)
+ resp.Data = append(resp.Data, line[4:]+"\r\n"+dotBodyStr)
+ dotLines := strings.Split(dotBodyStr, "\n")
+ for _, dotLine := range dotLines[:len(dotLines)-1] {
+ resp.RawLines = append(resp.RawLines, dotLine)
+ }
+ resp.RawLines = append(resp.RawLines, ".")
+ default:
+ return nil, c.protoErr("Invalid separator: '%v'", line[3])
+ }
+ }
+}
diff --git a/vendor/github.com/cretz/bine/control/status.go b/vendor/github.com/cretz/bine/control/status.go
new file mode 100644
index 0000000..8be3464
--- /dev/null
+++ b/vendor/github.com/cretz/bine/control/status.go
@@ -0,0 +1,64 @@
+package control
+
+import (
+ "fmt"
+ "net/textproto"
+ "strings"
+)
+
+// The various control port StatusCode constants.
+const (
+ StatusOk = 250
+ StatusOkUnnecessary = 251
+
+ StatusErrResourceExhausted = 451
+ StatusErrSyntaxError = 500
+ StatusErrUnrecognizedCmd = 510
+ StatusErrUnimplementedCmd = 511
+ StatusErrSyntaxErrorArg = 512
+ StatusErrUnrecognizedCmdArg = 513
+ StatusErrAuthenticationRequired = 514
+ StatusErrBadAuthentication = 515
+ StatusErrUnspecifiedTorError = 550
+ StatusErrInternalError = 551
+ StatusErrUnrecognizedEntity = 552
+ StatusErrInvalidConfigValue = 553
+ StatusErrInvalidDescriptor = 554
+ StatusErrUnmanagedEntity = 555
+
+ StatusAsyncEvent = 650
+)
+
+var statusCodeStringMap = map[int]string{
+ StatusOk: "OK",
+ StatusOkUnnecessary: "Operation was unnecessary",
+
+ StatusErrResourceExhausted: "Resource exhausted",
+ StatusErrSyntaxError: "Syntax error: protocol",
+ StatusErrUnrecognizedCmd: "Unrecognized command",
+ StatusErrUnimplementedCmd: "Unimplemented command",
+ StatusErrSyntaxErrorArg: "Syntax error in command argument",
+ StatusErrUnrecognizedCmdArg: "Unrecognized command argument",
+ StatusErrAuthenticationRequired: "Authentication required",
+ StatusErrBadAuthentication: "Bad authentication",
+ StatusErrUnspecifiedTorError: "Unspecified Tor error",
+ StatusErrInternalError: "Internal error",
+ StatusErrUnrecognizedEntity: "Unrecognized entity",
+ StatusErrInvalidConfigValue: "Invalid configuration value",
+ StatusErrInvalidDescriptor: "Invalid descriptor",
+ StatusErrUnmanagedEntity: "Unmanaged entity",
+
+ StatusAsyncEvent: "Asynchronous event notification",
+}
+
+func statusCodeToError(code int, reply string) *textproto.Error {
+ err := new(textproto.Error)
+ err.Code = code
+ if msg, ok := statusCodeStringMap[code]; ok {
+ trimmedReply := strings.TrimSpace(strings.TrimPrefix(reply, msg))
+ err.Msg = fmt.Sprintf("%s: %s", msg, trimmedReply)
+ } else {
+ err.Msg = fmt.Sprintf("Unknown status code (%03d): %s", code, reply)
+ }
+ return err
+}