// Create a new Channel instance with a given service type
func NewChannel(service *Service, serviceName string) *Channel {
	serviceHash_BCrypt, _ := bcrypt.HashBytes([]byte(serviceName))
	serviceHash_Base64 := base64.StdEncoding.EncodeToString(serviceHash_BCrypt)

	channel := &Channel{
		serviceName: serviceName,
		serviceHash: serviceHash_Base64,

		servicePath: fmt.Sprintf("/%s", serviceName),

		peers:           make([]*Peer, 0),
		proxies:         make([]*Proxy, 0),
		broadcastBuffer: make(chan *WireMessage, 512),

		done: make(chan int, 1),
	}

	channel.proxyPath = fmt.Sprintf("/%s", GenerateId())

	go channel.messageDispatcher()

	log.Printf("New '%s' channel peer created.", channel.serviceName)

	service.Channels[channel.servicePath] = channel

	// Terminate channel when it is closed
	go func() {
		<-channel.stopNotify()
		delete(service.Channels, channel.servicePath)
	}()

	// Add TLS-SRP credentials for access to this service to credentials store
	// TODO isolate this per socket
	serviceTab[channel.serviceHash] = channel.serviceName

	go channel.advertise(service.ProxyPort)

	if service.discoveryBrowser != nil {

		// Attempt to resolve discovered unknown service hashes with this service name
		recordsCache := make(map[string]*DNSRecord)
		for _, cachedRecord := range service.discoveryBrowser.cachedDNSRecords {
			if bcrypt.Match(channel.serviceName, cachedRecord.Hash_BCrypt) {
				if dErr := dialProxyFromDNSRecord(cachedRecord, channel); dErr != nil {
					log.Printf("err: %v", dErr)
				}
			} else {
				// Maintain as an unresolved entry in cache
				recordsCache[cachedRecord.Hash_Base64] = cachedRecord
			}
		}

		// Replace unresolved DNS-SD service entries cache
		service.discoveryBrowser.cachedDNSRecords = recordsCache

	}

	return channel
}
func (ds *DiscoveryBrowser) Browse(service *NetworkWebSocket_Service, intervalSeconds, timeoutSeconds int) {

	// Don't run two browse processes at the same time
	if ds.inprogress {
		return
	}

	ds.inprogress = true

	entries := make(chan *mdns.ServiceEntry, 255)

	recordsCache := make(map[string]*NetworkWebSocket_DNSRecord)

	timeout := time.Duration(timeoutSeconds) * time.Second
	interval := time.Duration(intervalSeconds) * time.Second

	var targetIPv4 *net.UDPAddr
	var targetIPv6 *net.UDPAddr

	targetIPv4 = network_ipv4Addr
	targetIPv6 = network_ipv6Addr

	// Only look for Named Web Socket DNS-SD services
	params := &mdns.QueryParam{
		Service:  "_nws._tcp",
		Domain:   "local",
		Timeout:  timeout,
		Entries:  entries,
		IPv4mdns: targetIPv4,
		IPv6mdns: targetIPv6,
	}

	go func() {
		complete := false
		timeoutFinish := time.After(timeout)
		intervalFinish := time.After(interval)

		// Wait for responses until timeout
		for !complete {
			select {
			case discoveredService, ok := <-entries:

				if !ok {
					continue
				}

				serviceRecord, err := NewNetworkWebSocketRecordFromDNSRecord(discoveredService)
				if err != nil {
					log.Printf("err: %v", err)
					continue
				}

				// Ignore our own NetworkWebSocket services
				if service.isOwnProxyService(serviceRecord) {
					continue
				}

				// Ignore previously discovered NetworkWebSocket proxy services
				if service.isActiveProxyService(serviceRecord) {
					continue
				}

				// Resolve discovered service hash provided against available services
				var sock *NetworkWebSocket
				for _, knownService := range service.Channels {
					if bcrypt.Match(knownService.serviceName, serviceRecord.Hash_BCrypt) {
						sock = knownService
						break
					}
				}

				if sock != nil {
					// Create new web socket connection toward discovered proxy
					if _, dErr := sock.dialFromDNSRecord(serviceRecord); dErr != nil {
						log.Printf("err: %v", dErr)
						continue
					}
				} else {
					// Store as an unresolved DNS-SD record
					recordsCache[serviceRecord.Hash_Base64] = serviceRecord
					continue
				}

			case <-timeoutFinish:
				// Replace unresolved DNS records cache
				ds.cachedDNSRecords = recordsCache

				ds.inprogress = false
			case <-intervalFinish:
				complete = true
			}
		}
	}()

	// Run the mDNS/DNS-SD query
	err := mdns.Query(params)

	time.Sleep(interval) // sleep until next loop is scheduled

	if err != nil {
		log.Printf("Could not perform mDNS/DNS-SD query. %v", err)
		return
	}
}
func (ds *DiscoveryBrowser) Browse(service *Service, timeoutSeconds int) {

	entries := make(chan *mdns.ServiceEntry, 255)

	recordsCache := make(map[string]*DNSRecord, 255)

	timeout := time.Duration(timeoutSeconds) * time.Second

	var targetIPv4 *net.UDPAddr
	var targetIPv6 *net.UDPAddr

	targetIPv4 = network_ipv4Addr
	targetIPv6 = network_ipv6Addr

	// Only look for Network Web Socket DNS-SD services
	params := &mdns.QueryParam{
		Service:  "_nws._tcp",
		Domain:   "local",
		Timeout:  timeout,
		Entries:  entries,
		IPv4mdns: targetIPv4,
		IPv6mdns: targetIPv6,
	}

	go func() {
		complete := false
		timeoutFinish := time.After(timeout)

		// Wait for responses until timeout
		for !complete {
			select {
			case discoveredService, ok := <-entries:

				if !ok {
					continue
				}

				serviceRecord, err := NewServiceRecordFromDNSRecord(discoveredService)
				if err != nil {
					log.Printf("err: %v", err)
					continue
				}

				// Ignore our own Channel services
				if service.isOwnProxyService(serviceRecord) {
					continue
				}

				// Ignore previously discovered Channel proxy services
				if service.isActiveProxyService(serviceRecord) {
					continue
				}

				// Resolve discovered service hash provided against available services
				var channel *Channel
				for _, knownService := range service.Channels {
					if bcrypt.Match(knownService.serviceName, serviceRecord.Hash_BCrypt) {
						channel = knownService
						break
					}
				}

				if channel != nil {
					// Create new web socket connection toward discovered proxy
					if dErr := dialProxyFromDNSRecord(serviceRecord, channel); dErr != nil {
						log.Printf("err: %v", dErr)
						continue
					}
				} else {
					// Store as an unresolved DNS-SD record
					recordsCache[serviceRecord.Hash_Base64] = serviceRecord
					continue
				}

			case <-timeoutFinish:
				// Replace unresolved DNS records cache
				ds.cachedDNSRecords = recordsCache

				complete = true
			}
		}
	}()

	// Run the mDNS/DNS-SD query
	err := mdns.Query(params)

	if err != nil {
		log.Printf("Could not perform mDNS/DNS-SD query. %v", err)
		return
	}
}