summaryrefslogtreecommitdiff
path: root/vendor/github.com/cretz/bine/control/cmd_event.go
diff options
context:
space:
mode:
authorkali kaneko (leap communications) <kali@leap.se>2021-11-29 01:46:27 +0100
committerkali kaneko (leap communications) <kali@leap.se>2021-11-29 18:14:16 +0100
commit18f52af5be3a9a0c73811706108f790d65ee9c67 (patch)
treee13cbacb47d56919caa9c44a2b45dec1497a7860 /vendor/github.com/cretz/bine/control/cmd_event.go
parentebcef0d57b6ecb5a40c6579f6be07182dd3033ba (diff)
[pkg] update vendor
Diffstat (limited to 'vendor/github.com/cretz/bine/control/cmd_event.go')
-rw-r--r--vendor/github.com/cretz/bine/control/cmd_event.go1218
1 files changed, 1218 insertions, 0 deletions
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 }