func (filter *ObjectFilter) MatchesRouterDescriptor(desc *RouterDescriptor) bool {
	if filter.HasIPAddr(desc.Address) {
		return true
	}
	if filter.HasFingerprint(desc.Fingerprint) {
		return true
	}
	if filter.HasNickname(desc.Nickname) {
		return true
	}
	return false
}
MatchesRouterDescriptor returns true if fields of the given router
descriptor are present in the object filter, e.g., the descriptor's nickname
is part of the object filter.
package zoossh
import (
	"bytes"
	"fmt"
	"io"
	"net"
	"os"
	"strconv"
	"strings"
	"time"
)
const (
	publishedTimeLayout = "2006-01-02 15:04:05"
)
var descriptorAnnotations = map[Annotation]bool{
	Annotation{"server-descriptor", "1", "0"}:        true,
	Annotation{"bridge-server-descriptor", "1", "2"}: true,
}
type GetDescriptor func() *RouterDescriptor
type ExitPattern struct {
	AddressSpec string
	PortSpec    string
}
type RouterDescriptor struct {
	Nickname  string
	Address   net.IP
	ORPort    uint16
	SOCKSPort uint16
	DirPort   uint16
	BandwidthAvg   uint64
	BandwidthBurst uint64
	BandwidthObs   uint64
	OperatingSystem string
	TorVersion      string
	Published time.Time
	Uptime uint64
	Fingerprint Fingerprint
	Hibernating bool
	Family map[Fingerprint]bool
	Contact string
	HiddenServiceDir bool
	BridgeDistributionRequest string
	OnionKey     string
	NTorOnionKey string
	SigningKey   string
	RawAccept     string
	RawReject     string
	RawExitPolicy string
	Accept []*ExitPattern
	Reject []*ExitPattern
}
type RouterDescriptors struct {
	RouterDescriptors map[Fingerprint]GetDescriptor
}
func (rd *RouterDescriptor) String() string {
	return fmt.Sprintf("%s,%s,%s,%d,%d,%s,%d,%s,%s,%s",
		rd.Fingerprint,
		rd.Nickname,
		rd.Address,
		rd.ORPort,
		rd.DirPort,
		rd.Published.Format(time.RFC3339),
		rd.Uptime,
		strings.Replace(rd.OperatingSystem, ",", "", -1),
		strings.Replace(rd.TorVersion, ",", "", -1),
		strings.Replace(rd.Contact, ",", "", -1))
}
func (rd *RouterDescriptor) GetFingerprint() Fingerprint {
	return rd.Fingerprint
}
func (rd *RouterDescriptor) HasFamily(fingerprint Fingerprint) bool {
	_, ok := rd.Family[SanitiseFingerprint(fingerprint)]
	return ok
}
func (rds *RouterDescriptors) Length() int {
	return len(rds.RouterDescriptors)
}
func (rds *RouterDescriptors) Iterate(filter *ObjectFilter) <-chan Object {
	ch := make(chan Object)
	go func() {
		for _, getDesc := range rds.RouterDescriptors {
			desc := getDesc()
			if filter == nil || filter.IsEmpty() || filter.MatchesRouterDescriptor(desc) {
				ch <- desc
			}
		}
		close(ch)
	}()
	return ch
}
func (rds *RouterDescriptors) GetObject(fingerprint Fingerprint) (Object, bool) {
	return rds.Get(fingerprint)
}
func (rds *RouterDescriptors) Merge(objs ObjectSet) {
	for rdesc := range rds.Iterate(nil) {
		fpr := rdesc.GetFingerprint()
		_, exists := rds.Get(fpr)
		if !exists {
			rds.Set(fpr, rdesc.(*RouterDescriptor))
		}
	}
}
func NewRouterDescriptors() *RouterDescriptors {
	return &RouterDescriptors{RouterDescriptors: make(map[Fingerprint]GetDescriptor)}
}
func NewRouterDescriptor() *RouterDescriptor {
	return &RouterDescriptor{Family: make(map[Fingerprint]bool)}
}
func (rds *RouterDescriptors) ToSlice() []GetDescriptor {
	length := rds.Length()
	descs := make([]GetDescriptor, length)
	i := 0
	for _, getDesc := range rds.RouterDescriptors {
		descs[i] = getDesc
		i++
	}
	return descs
}
func (rds *RouterDescriptors) Get(fingerprint Fingerprint) (*RouterDescriptor, bool) {
	getDescriptor, exists := rds.RouterDescriptors[SanitiseFingerprint(fingerprint)]
	if !exists {
		return nil, exists
	}
	return getDescriptor(), exists
}
func (rds *RouterDescriptors) Set(fingerprint Fingerprint, descriptor *RouterDescriptor) {
	rds.RouterDescriptors[SanitiseFingerprint(fingerprint)] = func() *RouterDescriptor {
		return descriptor
	}
}
func LazyParseRawDescriptor(rawDescriptor string) (Fingerprint, GetDescriptor, error) {
	var fingerprint Fingerprint
	getDescriptor := func() *RouterDescriptor {
		_, f, _ := ParseRawDescriptor(rawDescriptor)
		return f()
	}
	lines := strings.Split(rawDescriptor, "\n")
	for _, line := range lines {
		words := strings.Split(line, " ")
		if words[0] == "opt" {
			words = words[1:]
		}
		if words[0] == "fingerprint" {
			fingerprint = Fingerprint(strings.Join(words[1:], ""))
			return SanitiseFingerprint(fingerprint), getDescriptor, nil
		}
	}
	return "", nil, fmt.Errorf("could not extract descriptor fingerprint")
}
func ParseRawDescriptor(rawDescriptor string) (Fingerprint, GetDescriptor, error) {
	var descriptor = NewRouterDescriptor()
	lines := strings.Split(rawDescriptor, "\n")
	for _, line := range lines {
		words := strings.Split(line, " ")
		if words[0] == "opt" {
			words = words[1:]
		}
		switch words[0] {
		case "router":
			descriptor.Nickname = words[1]
			descriptor.Address = net.ParseIP(words[2])
			descriptor.ORPort = StringToPort(words[3])
			descriptor.SOCKSPort = StringToPort(words[4])
			descriptor.DirPort = StringToPort(words[5])
		case "platform":
			for i := 0; i < len(words); i++ {
				if (strings.TrimSpace(words[i]) == "on") && (i < len(words)-1) {
					descriptor.OperatingSystem = strings.Join(words[i+1:], " ")
					descriptor.TorVersion = strings.Join(words[1:i-1], " ")
					break
				}
			}
		case "uptime":
			descriptor.Uptime, _ = strconv.ParseUint(words[1], 10, 64)
		case "published":
			time, _ := time.Parse(publishedTimeLayout, strings.Join(words[1:], " "))
			descriptor.Published = time
		case "fingerprint":
			descriptor.Fingerprint = SanitiseFingerprint(Fingerprint(strings.Join(words[1:], "")))
		case "hibernating":
			descriptor.Hibernating, _ = strconv.ParseBool(words[1])
		case "bandwidth":
			descriptor.BandwidthAvg, _ = strconv.ParseUint(words[1], 10, 64)
			descriptor.BandwidthBurst, _ = strconv.ParseUint(words[2], 10, 64)
			descriptor.BandwidthObs, _ = strconv.ParseUint(words[3], 10, 64)
		case "family":
			for _, word := range words[1:] {
				fpr := Fingerprint(strings.Trim(word, "$"))
				descriptor.Family[fpr] = true
			}
		case "contact":
			descriptor.Contact = strings.Join(words[1:], " ")
		case "hidden-service-dir":
			descriptor.HiddenServiceDir = true
		case "reject":
			descriptor.RawReject += words[1] + " "
			descriptor.RawExitPolicy += words[0] + " " + words[1] + "\n"
		case "accept":
			descriptor.RawAccept += words[1] + " "
			descriptor.RawExitPolicy += words[0] + " " + words[1] + "\n"
		case "bridge-distribution-request":
			descriptor.BridgeDistributionRequest = words[1]
		}
	}
	return descriptor.Fingerprint, func() *RouterDescriptor { return descriptor }, nil
}
func extractDescriptor(data []byte, atEOF bool) (advance int, token []byte, err error) {
	if atEOF && len(data) == 0 {
		return 0, nil, nil
	}
	start := 0
	if !bytes.HasPrefix(data, []byte("router ")) {
		start = bytes.Index(data, []byte("\nrouter "))
		if start < 0 {
			if atEOF {
				return 0, nil, fmt.Errorf("cannot find beginning of descriptor: \"\\nrouter \"")
			}
			return 0, nil, nil
		}
		start++
	}
	marker := []byte("\n-----END SIGNATURE-----\n")
	end := bytes.Index(data[start:], marker)
	if end >= 0 {
		return start + end + len(marker), data[start : start+end+len(marker)], nil
	}
	if atEOF {
		return start, nil, fmt.Errorf("cannot find end of descriptor: %q", marker)
	}
	return start, nil, nil
}