 9bdcbe0447
			
		
	
	9bdcbe0447
	
	
	
		
			
			Major integrations and fixes: - Added BACKBEAT SDK integration for P2P operation timing - Implemented beat-aware status tracking for distributed operations - Added Docker secrets support for secure license management - Resolved KACHING license validation via HTTPS/TLS - Updated docker-compose configuration for clean stack deployment - Disabled rollback policies to prevent deployment failures - Added license credential storage (CHORUS-DEV-MULTI-001) Technical improvements: - BACKBEAT P2P operation tracking with phase management - Enhanced configuration system with file-based secrets - Improved error handling for license validation - Clean separation of KACHING and CHORUS deployment stacks 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			205 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			205 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // This file contains XML structures for communicating with UPnP devices.
 | |
| 
 | |
| package goupnp
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"encoding/xml"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"net/url"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/huin/goupnp/scpd"
 | |
| 	"github.com/huin/goupnp/soap"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	DeviceXMLNamespace = "urn:schemas-upnp-org:device-1-0"
 | |
| )
 | |
| 
 | |
| // RootDevice is the device description as described by section 2.3 "Device
 | |
| // description" in
 | |
| // http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf
 | |
| type RootDevice struct {
 | |
| 	XMLName     xml.Name    `xml:"root"`
 | |
| 	SpecVersion SpecVersion `xml:"specVersion"`
 | |
| 	URLBase     url.URL     `xml:"-"`
 | |
| 	URLBaseStr  string      `xml:"URLBase"`
 | |
| 	Device      Device      `xml:"device"`
 | |
| }
 | |
| 
 | |
| // SetURLBase sets the URLBase for the RootDevice and its underlying components.
 | |
| func (root *RootDevice) SetURLBase(urlBase *url.URL) {
 | |
| 	root.URLBase = *urlBase
 | |
| 	root.URLBaseStr = urlBase.String()
 | |
| 	root.Device.SetURLBase(urlBase)
 | |
| }
 | |
| 
 | |
| // SpecVersion is part of a RootDevice, describes the version of the
 | |
| // specification that the data adheres to.
 | |
| type SpecVersion struct {
 | |
| 	Major int32 `xml:"major"`
 | |
| 	Minor int32 `xml:"minor"`
 | |
| }
 | |
| 
 | |
| // Device is a UPnP device. It can have child devices.
 | |
| type Device struct {
 | |
| 	DeviceType       string    `xml:"deviceType"`
 | |
| 	FriendlyName     string    `xml:"friendlyName"`
 | |
| 	Manufacturer     string    `xml:"manufacturer"`
 | |
| 	ManufacturerURL  URLField  `xml:"manufacturerURL"`
 | |
| 	ModelDescription string    `xml:"modelDescription"`
 | |
| 	ModelName        string    `xml:"modelName"`
 | |
| 	ModelNumber      string    `xml:"modelNumber"`
 | |
| 	ModelType        string    `xml:"modelType"`
 | |
| 	ModelURL         URLField  `xml:"modelURL"`
 | |
| 	SerialNumber     string    `xml:"serialNumber"`
 | |
| 	UDN              string    `xml:"UDN"`
 | |
| 	UPC              string    `xml:"UPC,omitempty"`
 | |
| 	Icons            []Icon    `xml:"iconList>icon,omitempty"`
 | |
| 	Services         []Service `xml:"serviceList>service,omitempty"`
 | |
| 	Devices          []Device  `xml:"deviceList>device,omitempty"`
 | |
| 
 | |
| 	// Extra observed elements:
 | |
| 	PresentationURL URLField `xml:"presentationURL"`
 | |
| }
 | |
| 
 | |
| // VisitDevices calls visitor for the device, and all its descendent devices.
 | |
| func (device *Device) VisitDevices(visitor func(*Device)) {
 | |
| 	visitor(device)
 | |
| 	for i := range device.Devices {
 | |
| 		device.Devices[i].VisitDevices(visitor)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // VisitServices calls visitor for all Services under the device and all its
 | |
| // descendent devices.
 | |
| func (device *Device) VisitServices(visitor func(*Service)) {
 | |
| 	device.VisitDevices(func(d *Device) {
 | |
| 		for i := range d.Services {
 | |
| 			visitor(&d.Services[i])
 | |
| 		}
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // FindService finds all (if any) Services under the device and its descendents
 | |
| // that have the given ServiceType.
 | |
| func (device *Device) FindService(serviceType string) []*Service {
 | |
| 	var services []*Service
 | |
| 	device.VisitServices(func(s *Service) {
 | |
| 		if s.ServiceType == serviceType {
 | |
| 			services = append(services, s)
 | |
| 		}
 | |
| 	})
 | |
| 	return services
 | |
| }
 | |
| 
 | |
| // SetURLBase sets the URLBase for the Device and its underlying components.
 | |
| func (device *Device) SetURLBase(urlBase *url.URL) {
 | |
| 	device.ManufacturerURL.SetURLBase(urlBase)
 | |
| 	device.ModelURL.SetURLBase(urlBase)
 | |
| 	device.PresentationURL.SetURLBase(urlBase)
 | |
| 	for i := range device.Icons {
 | |
| 		device.Icons[i].SetURLBase(urlBase)
 | |
| 	}
 | |
| 	for i := range device.Services {
 | |
| 		device.Services[i].SetURLBase(urlBase)
 | |
| 	}
 | |
| 	for i := range device.Devices {
 | |
| 		device.Devices[i].SetURLBase(urlBase)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (device *Device) String() string {
 | |
| 	return fmt.Sprintf("Device ID %s : %s (%s)", device.UDN, device.DeviceType, device.FriendlyName)
 | |
| }
 | |
| 
 | |
| // Icon is a representative image that a device might include in its
 | |
| // description.
 | |
| type Icon struct {
 | |
| 	Mimetype string   `xml:"mimetype"`
 | |
| 	Width    int32    `xml:"width"`
 | |
| 	Height   int32    `xml:"height"`
 | |
| 	Depth    int32    `xml:"depth"`
 | |
| 	URL      URLField `xml:"url"`
 | |
| }
 | |
| 
 | |
| // SetURLBase sets the URLBase for the Icon.
 | |
| func (icon *Icon) SetURLBase(url *url.URL) {
 | |
| 	icon.URL.SetURLBase(url)
 | |
| }
 | |
| 
 | |
| // Service is a service provided by a UPnP Device.
 | |
| type Service struct {
 | |
| 	ServiceType string   `xml:"serviceType"`
 | |
| 	ServiceId   string   `xml:"serviceId"`
 | |
| 	SCPDURL     URLField `xml:"SCPDURL"`
 | |
| 	ControlURL  URLField `xml:"controlURL"`
 | |
| 	EventSubURL URLField `xml:"eventSubURL"`
 | |
| }
 | |
| 
 | |
| // SetURLBase sets the URLBase for the Service.
 | |
| func (srv *Service) SetURLBase(urlBase *url.URL) {
 | |
| 	srv.SCPDURL.SetURLBase(urlBase)
 | |
| 	srv.ControlURL.SetURLBase(urlBase)
 | |
| 	srv.EventSubURL.SetURLBase(urlBase)
 | |
| }
 | |
| 
 | |
| func (srv *Service) String() string {
 | |
| 	return fmt.Sprintf("Service ID %s : %s", srv.ServiceId, srv.ServiceType)
 | |
| }
 | |
| 
 | |
| // RequestSCPDCtx requests the SCPD (soap actions and state variables description)
 | |
| // for the service.
 | |
| func (srv *Service) RequestSCPDCtx(ctx context.Context) (*scpd.SCPD, error) {
 | |
| 	if !srv.SCPDURL.Ok {
 | |
| 		return nil, errors.New("bad/missing SCPD URL, or no URLBase has been set")
 | |
| 	}
 | |
| 	s := new(scpd.SCPD)
 | |
| 	if err := requestXml(ctx, srv.SCPDURL.URL.String(), scpd.SCPDXMLNamespace, s); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return s, nil
 | |
| }
 | |
| 
 | |
| // RequestSCPD is the legacy version of RequestSCPDCtx, but uses
 | |
| // context.Background() as the context.
 | |
| func (srv *Service) RequestSCPD() (*scpd.SCPD, error) {
 | |
| 	return srv.RequestSCPDCtx(context.Background())
 | |
| }
 | |
| 
 | |
| // RequestSCDP is for compatibility only, prefer RequestSCPD. This was a
 | |
| // misspelling of RequestSCDP.
 | |
| func (srv *Service) RequestSCDP() (*scpd.SCPD, error) {
 | |
| 	return srv.RequestSCPD()
 | |
| }
 | |
| 
 | |
| func (srv *Service) NewSOAPClient() *soap.SOAPClient {
 | |
| 	return soap.NewSOAPClient(srv.ControlURL.URL)
 | |
| }
 | |
| 
 | |
| // URLField is a URL that is part of a device description.
 | |
| type URLField struct {
 | |
| 	URL url.URL `xml:"-"`
 | |
| 	Ok  bool    `xml:"-"`
 | |
| 	Str string  `xml:",chardata"`
 | |
| }
 | |
| 
 | |
| func (uf *URLField) SetURLBase(urlBase *url.URL) {
 | |
| 	str := uf.Str
 | |
| 	if !strings.Contains(str, "://") && !strings.HasPrefix(str, "/") {
 | |
| 		str = "/" + str
 | |
| 	}
 | |
| 
 | |
| 	refUrl, err := url.Parse(str)
 | |
| 	if err != nil {
 | |
| 		uf.URL = url.URL{}
 | |
| 		uf.Ok = false
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	uf.URL = *urlBase.ResolveReference(refUrl)
 | |
| 	uf.Ok = true
 | |
| }
 |