package ssdp

import (
	
	
	
	
	
	
	
	

	
)

const (
	maxExpiryTimeSeconds = 24 * 60 * 60
)

var (
	maxAgeRx = regexp.MustCompile("max-age= *([0-9]+)")
)

const (
	EventAlive = EventType(iota)
	EventUpdate
	EventByeBye
)

type EventType int8

func ( EventType) () string {
	switch  {
	case EventAlive:
		return "EventAlive"
	case EventUpdate:
		return "EventUpdate"
	case EventByeBye:
		return "EventByeBye"
	default:
		return fmt.Sprintf("EventUnknown(%d)", int8())
	}
}

type Update struct {
	// The USN of the service.
	USN string
	// What happened.
	EventType EventType
	// The entry, which is nil if the service was not known and
	// EventType==EventByeBye. The contents of this must not be modified as it is
	// shared with the registry and other listeners. Once created, the Registry
	// does not modify the Entry value - any updates are replaced with a new
	// Entry value.
	Entry *Entry
}

type Entry struct {
	// The address that the entry data was actually received from.
	RemoteAddr string
	// Unique Service Name. Identifies a unique instance of a device or service.
	USN string
	// Notfication Type. The type of device or service being announced.
	NT string
	// Server's self-identifying string.
	Server string
	Host   string
	// Location of the UPnP root device description.
	Location url.URL

	// Despite BOOTID,CONFIGID being required fields, apparently they are not
	// always set by devices. Set to -1 if not present.

	BootID   int32
	ConfigID int32

	SearchPort uint16

	// When the last update was received for this entry identified by this USN.
	LastUpdate time.Time
	// When the last update's cached values are advised to expire.
	CacheExpiry time.Time
}

func newEntryFromRequest( *http.Request) (*Entry, error) {
	 := time.Now()
	,  := parseCacheControlMaxAge(.Header.Get("CACHE-CONTROL"))
	if  != nil {
		return nil, fmt.Errorf("ssdp: error parsing CACHE-CONTROL max age: %v", )
	}

	,  := url.Parse(.Header.Get("LOCATION"))
	if  != nil {
		return nil, fmt.Errorf("ssdp: error parsing entry Location URL: %v", )
	}

	,  := parseUpnpIntHeader(.Header, "BOOTID.UPNP.ORG", -1)
	if  != nil {
		return nil, 
	}
	,  := parseUpnpIntHeader(.Header, "CONFIGID.UPNP.ORG", -1)
	if  != nil {
		return nil, 
	}
	,  := parseUpnpIntHeader(.Header, "SEARCHPORT.UPNP.ORG", ssdpSearchPort)
	if  != nil {
		return nil, 
	}

	if  < 1 ||  > 65535 {
		return nil, fmt.Errorf("ssdp: search port %d is out of range", )
	}

	return &Entry{
		RemoteAddr:  .RemoteAddr,
		USN:         .Header.Get("USN"),
		NT:          .Header.Get("NT"),
		Server:      .Header.Get("SERVER"),
		Host:        .Header.Get("HOST"),
		Location:    *,
		BootID:      ,
		ConfigID:    ,
		SearchPort:  uint16(),
		LastUpdate:  ,
		CacheExpiry: .Add(),
	}, nil
}

func parseCacheControlMaxAge( string) (time.Duration, error) {
	 := maxAgeRx.FindStringSubmatch()
	if len() != 2 {
		return 0, fmt.Errorf("did not find exactly one max-age in cache control header: %q", )
	}
	,  := strconv.ParseInt([1], 10, 16)
	if  != nil {
		return 0, 
	}
	if  < 1 ||  > maxExpiryTimeSeconds {
		return 0, fmt.Errorf("rejecting bad expiry time of %d seconds", )
	}
	return time.Duration() * time.Second, nil
}

// parseUpnpIntHeader is intended to parse the
// {BOOT,CONFIGID,SEARCHPORT}.UPNP.ORG header fields. It returns the def if
// the head is empty or missing.
func parseUpnpIntHeader( http.Header,  string,  int32) (int32, error) {
	 := .Get()
	if  == "" {
		return , nil
	}
	,  := strconv.ParseInt(, 10, 32)
	if  != nil {
		return 0, fmt.Errorf("ssdp: could not parse header %s: %v", , )
	}
	return int32(), nil
}

var _ httpu.Handler = new(Registry)

// Registry maintains knowledge of discovered devices and services.
//
// NOTE: the interface for this is experimental and may change, or go away
// entirely.
type Registry struct {
	lock  sync.Mutex
	byUSN map[string]*Entry

	listenersLock sync.RWMutex
	listeners     map[chan<- Update]struct{}
}

func () *Registry {
	return &Registry{
		byUSN:     make(map[string]*Entry),
		listeners: make(map[chan<- Update]struct{}),
	}
}

// NewServerAndRegistry is a convenience function to create a registry, and an
// httpu server to pass it messages. Call ListenAndServe on the server for
// messages to be processed.
func () (*httpu.Server, *Registry) {
	 := NewRegistry()
	 := &httpu.Server{
		Addr:      ssdpUDP4Addr,
		Multicast: true,
		Handler:   ,
	}
	return , 
}

func ( *Registry) ( chan<- Update) {
	.listenersLock.Lock()
	defer .listenersLock.Unlock()
	.listeners[] = struct{}{}
}

func ( *Registry) ( chan<- Update) {
	.listenersLock.Lock()
	defer .listenersLock.Unlock()
	delete(.listeners, )
}

func ( *Registry) ( Update) {
	.listenersLock.RLock()
	defer .listenersLock.RUnlock()
	for  := range .listeners {
		 <- 
	}
}

// GetService returns known service (or device) entries for the given service
// URN.
func ( *Registry) ( string) []*Entry {
	// Currently assumes that the map is small, so we do a linear search rather
	// than indexed to avoid maintaining two maps.
	var  []*Entry
	.lock.Lock()
	defer .lock.Unlock()
	for ,  := range .byUSN {
		if .NT ==  {
			 = append(, )
		}
	}
	return 
}

// ServeMessage implements httpu.Handler, and uses SSDP NOTIFY requests to
// maintain the registry of devices and services.
func ( *Registry) ( *http.Request) {
	if .Method != methodNotify {
		return
	}

	 := .Header.Get("nts")

	var  error
	switch  {
	case ntsAlive:
		 = .handleNTSAlive()
	case ntsUpdate:
		 = .handleNTSUpdate()
	case ntsByebye:
		 = .handleNTSByebye()
	default:
		 = fmt.Errorf("unknown NTS value: %q", )
	}
	if  != nil {
		log.Printf("goupnp/ssdp: failed to handle %s message from %s: %v", , .RemoteAddr, )
	}
}

func ( *Registry) ( *http.Request) error {
	,  := newEntryFromRequest()
	if  != nil {
		return 
	}

	.lock.Lock()
	.byUSN[.USN] = 
	.lock.Unlock()

	.sendUpdate(Update{
		USN:       .USN,
		EventType: EventAlive,
		Entry:     ,
	})

	return nil
}

func ( *Registry) ( *http.Request) error {
	,  := newEntryFromRequest()
	if  != nil {
		return 
	}
	,  := parseUpnpIntHeader(.Header, "NEXTBOOTID.UPNP.ORG", -1)
	if  != nil {
		return 
	}
	.BootID = 

	.lock.Lock()
	.byUSN[.USN] = 
	.lock.Unlock()

	.sendUpdate(Update{
		USN:       .USN,
		EventType: EventUpdate,
		Entry:     ,
	})

	return nil
}

func ( *Registry) ( *http.Request) error {
	 := .Header.Get("USN")

	.lock.Lock()
	 := .byUSN[]
	delete(.byUSN, )
	.lock.Unlock()

	.sendUpdate(Update{
		USN:       ,
		EventType: EventByeBye,
		Entry:     ,
	})

	return nil
}