Merge remote-tracking branch 'origin/main' into feat/openhome
This commit is contained in:
		
							
								
								
									
										11
									
								
								goupnp.go
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								goupnp.go
									
									
									
									
									
								
							@@ -85,7 +85,10 @@ func DiscoverDevicesCtx(ctx context.Context, searchTarget string) ([]MaybeRootDe
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	defer hcCleanup()
 | 
			
		||||
	responses, err := ssdp.SSDPRawSearchCtx(ctx, hc, string(searchTarget), 2, 3)
 | 
			
		||||
 | 
			
		||||
	searchCtx, cancel := context.WithTimeout(ctx, 2*time.Second)
 | 
			
		||||
	defer cancel()
 | 
			
		||||
	responses, err := ssdp.RawSearch(searchCtx, hc, string(searchTarget), 3)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
@@ -148,6 +151,10 @@ func DeviceByURL(loc *url.URL) (*RootDevice, error) {
 | 
			
		||||
// but should not be changed after requesting clients.
 | 
			
		||||
var CharsetReaderDefault func(charset string, input io.Reader) (io.Reader, error)
 | 
			
		||||
 | 
			
		||||
// HTTPClient specifies the http.Client object used when fetching the XML from the UPnP server.
 | 
			
		||||
// HTTPClient defaults the http.DefaultClient.  This may be overridden by the importing application.
 | 
			
		||||
var HTTPClientDefault = http.DefaultClient
 | 
			
		||||
 | 
			
		||||
func requestXml(ctx context.Context, url string, defaultSpace string, doc interface{}) error {
 | 
			
		||||
	ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
 | 
			
		||||
	defer cancel()
 | 
			
		||||
@@ -157,7 +164,7 @@ func requestXml(ctx context.Context, url string, defaultSpace string, doc interf
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resp, err := http.DefaultClient.Do(req)
 | 
			
		||||
	resp, err := HTTPClientDefault.Do(req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ package httpu
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"context"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
@@ -26,6 +27,27 @@ type ClientInterface interface {
 | 
			
		||||
	) ([]*http.Response, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ClientInterfaceCtx is the equivalent of ClientInterface, except with methods
 | 
			
		||||
// taking a context.Context parameter.
 | 
			
		||||
type ClientInterfaceCtx interface {
 | 
			
		||||
	// DoWithContext performs a request. If the input request has a
 | 
			
		||||
	// deadline, then that value will be used as the timeout for how long
 | 
			
		||||
	// to wait before returning the responses that were received. If the
 | 
			
		||||
	// request's context is canceled, this method will return immediately.
 | 
			
		||||
	//
 | 
			
		||||
	// If the request's context is never canceled, and does not have a
 | 
			
		||||
	// deadline, then this function WILL NEVER RETURN. You MUST set an
 | 
			
		||||
	// appropriate deadline on the context, or otherwise cancel it when you
 | 
			
		||||
	// want to finish an operation.
 | 
			
		||||
	//
 | 
			
		||||
	// An error is only returned for failing to send the request. Failures
 | 
			
		||||
	// in receipt simply do not add to the resulting responses.
 | 
			
		||||
	DoWithContext(
 | 
			
		||||
		req *http.Request,
 | 
			
		||||
		numSends int,
 | 
			
		||||
	) ([]*http.Response, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// HTTPUClient is a client for dealing with HTTPU (HTTP over UDP). Its typical
 | 
			
		||||
// function is for HTTPMU, and particularly SSDP.
 | 
			
		||||
type HTTPUClient struct {
 | 
			
		||||
@@ -34,6 +56,7 @@ type HTTPUClient struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var _ ClientInterface = &HTTPUClient{}
 | 
			
		||||
var _ ClientInterfaceCtx = &HTTPUClient{}
 | 
			
		||||
 | 
			
		||||
// NewHTTPUClient creates a new HTTPUClient, opening up a new UDP socket for the
 | 
			
		||||
// purpose.
 | 
			
		||||
@@ -75,6 +98,25 @@ func (httpu *HTTPUClient) Do(
 | 
			
		||||
	req *http.Request,
 | 
			
		||||
	timeout time.Duration,
 | 
			
		||||
	numSends int,
 | 
			
		||||
) ([]*http.Response, error) {
 | 
			
		||||
	ctx := req.Context()
 | 
			
		||||
	if timeout > 0 {
 | 
			
		||||
		var cancel func()
 | 
			
		||||
		ctx, cancel = context.WithTimeout(ctx, timeout)
 | 
			
		||||
		defer cancel()
 | 
			
		||||
		req = req.WithContext(ctx)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return httpu.DoWithContext(req, numSends)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DoWithContext implements ClientInterfaceCtx.DoWithContext.
 | 
			
		||||
//
 | 
			
		||||
// Make sure to read the documentation on the ClientInterfaceCtx interface
 | 
			
		||||
// regarding cancellation!
 | 
			
		||||
func (httpu *HTTPUClient) DoWithContext(
 | 
			
		||||
	req *http.Request,
 | 
			
		||||
	numSends int,
 | 
			
		||||
) ([]*http.Response, error) {
 | 
			
		||||
	httpu.connLock.Lock()
 | 
			
		||||
	defer httpu.connLock.Unlock()
 | 
			
		||||
@@ -101,10 +143,28 @@ func (httpu *HTTPUClient) Do(
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if err = httpu.conn.SetDeadline(time.Now().Add(timeout)); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
 | 
			
		||||
	// Handle context deadline/timeout
 | 
			
		||||
	ctx := req.Context()
 | 
			
		||||
	deadline, ok := ctx.Deadline()
 | 
			
		||||
	if ok {
 | 
			
		||||
		if err = httpu.conn.SetDeadline(deadline); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Handle context cancelation
 | 
			
		||||
	done := make(chan struct{})
 | 
			
		||||
	defer close(done)
 | 
			
		||||
	go func() {
 | 
			
		||||
		select {
 | 
			
		||||
		case <-ctx.Done():
 | 
			
		||||
			// if context is cancelled, stop any connections by setting time in the past.
 | 
			
		||||
			httpu.conn.SetDeadline(time.Now().Add(-time.Second))
 | 
			
		||||
		case <-done:
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	// Send request.
 | 
			
		||||
	for i := 0; i < numSends; i++ {
 | 
			
		||||
		if n, err := httpu.conn.WriteTo(requestBuf.Bytes(), destAddr); err != nil {
 | 
			
		||||
 
 | 
			
		||||
@@ -49,14 +49,14 @@ func (mc *MultiClient) Do(
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mc *MultiClient) sendRequests(
 | 
			
		||||
	results chan<-[]*http.Response,
 | 
			
		||||
	results chan<- []*http.Response,
 | 
			
		||||
	req *http.Request,
 | 
			
		||||
	timeout time.Duration,
 | 
			
		||||
	numSends int,
 | 
			
		||||
) error {
 | 
			
		||||
	tasks := &errgroup.Group{}
 | 
			
		||||
	for _, d := range mc.delegates {
 | 
			
		||||
		d := d  // copy for closure
 | 
			
		||||
		d := d // copy for closure
 | 
			
		||||
		tasks.Go(func() error {
 | 
			
		||||
			responses, err := d.Do(req, timeout, numSends)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
@@ -68,3 +68,65 @@ func (mc *MultiClient) sendRequests(
 | 
			
		||||
	}
 | 
			
		||||
	return tasks.Wait()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MultiClientCtx dispatches requests out to all the delegated clients.
 | 
			
		||||
type MultiClientCtx struct {
 | 
			
		||||
	// The HTTPU clients to delegate to.
 | 
			
		||||
	delegates []ClientInterfaceCtx
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var _ ClientInterfaceCtx = &MultiClientCtx{}
 | 
			
		||||
 | 
			
		||||
// NewMultiClient creates a new MultiClient that delegates to all the given
 | 
			
		||||
// clients.
 | 
			
		||||
func NewMultiClientCtx(delegates []ClientInterfaceCtx) *MultiClientCtx {
 | 
			
		||||
	return &MultiClientCtx{
 | 
			
		||||
		delegates: delegates,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DoWithContext implements ClientInterfaceCtx.DoWithContext.
 | 
			
		||||
func (mc *MultiClientCtx) DoWithContext(
 | 
			
		||||
	req *http.Request,
 | 
			
		||||
	numSends int,
 | 
			
		||||
) ([]*http.Response, error) {
 | 
			
		||||
	tasks, ctx := errgroup.WithContext(req.Context())
 | 
			
		||||
	req = req.WithContext(ctx) // so we cancel if the errgroup errors
 | 
			
		||||
	results := make(chan []*http.Response)
 | 
			
		||||
 | 
			
		||||
	// For each client, send the request to it and collect results.
 | 
			
		||||
	tasks.Go(func() error {
 | 
			
		||||
		defer close(results)
 | 
			
		||||
		return mc.sendRequestsCtx(results, req, numSends)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	var responses []*http.Response
 | 
			
		||||
	tasks.Go(func() error {
 | 
			
		||||
		for rs := range results {
 | 
			
		||||
			responses = append(responses, rs...)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	return responses, tasks.Wait()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mc *MultiClientCtx) sendRequestsCtx(
 | 
			
		||||
	results chan<- []*http.Response,
 | 
			
		||||
	req *http.Request,
 | 
			
		||||
	numSends int,
 | 
			
		||||
) error {
 | 
			
		||||
	tasks := &errgroup.Group{}
 | 
			
		||||
	for _, d := range mc.delegates {
 | 
			
		||||
		d := d // copy for closure
 | 
			
		||||
		tasks.Go(func() error {
 | 
			
		||||
			responses, err := d.DoWithContext(req, numSends)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			results <- responses
 | 
			
		||||
			return nil
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	return tasks.Wait()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ import (
 | 
			
		||||
	"net"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"sync"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
@@ -73,20 +74,25 @@ func (srv *Server) Serve(l net.PacketConn) error {
 | 
			
		||||
	if srv.MaxMessageBytes != 0 {
 | 
			
		||||
		maxMessageBytes = srv.MaxMessageBytes
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bufPool := &sync.Pool{
 | 
			
		||||
		New: func() interface{} {
 | 
			
		||||
			return make([]byte, maxMessageBytes)
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for {
 | 
			
		||||
		buf := make([]byte, maxMessageBytes)
 | 
			
		||||
		buf := bufPool.Get().([]byte)
 | 
			
		||||
		n, peerAddr, err := l.ReadFrom(buf)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		buf = buf[:n]
 | 
			
		||||
 | 
			
		||||
		go func(buf []byte, peerAddr net.Addr) {
 | 
			
		||||
		go func() {
 | 
			
		||||
			defer bufPool.Put(buf)
 | 
			
		||||
			// At least one router's UPnP implementation has added a trailing space
 | 
			
		||||
			// after "HTTP/1.1" - trim it.
 | 
			
		||||
			buf = trailingWhitespaceRx.ReplaceAllLiteral(buf, crlf)
 | 
			
		||||
			reqBuf := trailingWhitespaceRx.ReplaceAllLiteral(buf[:n], crlf)
 | 
			
		||||
 | 
			
		||||
			req, err := http.ReadRequest(bufio.NewReader(bytes.NewBuffer(buf)))
 | 
			
		||||
			req, err := http.ReadRequest(bufio.NewReader(bytes.NewBuffer(reqBuf)))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Printf("httpu: Failed to parse request: %v", err)
 | 
			
		||||
				return
 | 
			
		||||
@@ -94,7 +100,7 @@ func (srv *Server) Serve(l net.PacketConn) error {
 | 
			
		||||
			req.RemoteAddr = peerAddr.String()
 | 
			
		||||
			srv.Handler.ServeMessage(req)
 | 
			
		||||
			// No need to call req.Body.Close - underlying reader is bytes.Buffer.
 | 
			
		||||
		}(buf, peerAddr)
 | 
			
		||||
		}()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,14 +10,14 @@ import (
 | 
			
		||||
// httpuClient creates a HTTPU client that multiplexes to all multicast-capable
 | 
			
		||||
// IPv4 addresses on the host. Returns a function to clean up once the client is
 | 
			
		||||
// no longer required.
 | 
			
		||||
func httpuClient() (httpu.ClientInterface, func(), error) {
 | 
			
		||||
func httpuClient() (httpu.ClientInterfaceCtx, func(), error) {
 | 
			
		||||
	addrs, err := localIPv4MCastAddrs()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, ctxError(err, "requesting host IPv4 addresses")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	closers := make([]io.Closer, 0, len(addrs))
 | 
			
		||||
	delegates := make([]httpu.ClientInterface, 0, len(addrs))
 | 
			
		||||
	delegates := make([]httpu.ClientInterfaceCtx, 0, len(addrs))
 | 
			
		||||
	for _, addr := range addrs {
 | 
			
		||||
		c, err := httpu.NewHTTPUClientAddr(addr)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
@@ -34,7 +34,7 @@ func httpuClient() (httpu.ClientInterface, func(), error) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return httpu.NewMultiClient(delegates), closer, nil
 | 
			
		||||
	return httpu.NewMultiClientCtx(delegates), closer, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// localIPv2MCastAddrs returns the set of IPv4 addresses on multicast-able
 | 
			
		||||
 
 | 
			
		||||
@@ -194,9 +194,13 @@ type soapBody struct {
 | 
			
		||||
 | 
			
		||||
// SOAPFaultError implements error, and contains SOAP fault information.
 | 
			
		||||
type SOAPFaultError struct {
 | 
			
		||||
	FaultCode   string `xml:"faultCode"`
 | 
			
		||||
	FaultString string `xml:"faultString"`
 | 
			
		||||
	FaultCode   string `xml:"faultcode"`
 | 
			
		||||
	FaultString string `xml:"faultstring"`
 | 
			
		||||
	Detail      struct {
 | 
			
		||||
		UPnPError struct {
 | 
			
		||||
			Errorcode        int    `xml:"errorCode"`
 | 
			
		||||
			ErrorDescription string `xml:"errorDescription"`
 | 
			
		||||
		} `xml:"UPnPError"`
 | 
			
		||||
		Raw []byte `xml:",innerxml"`
 | 
			
		||||
	} `xml:"detail"`
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,10 +2,12 @@ package soap
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -87,6 +89,75 @@ func TestActionInputs(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUPnPError(t *testing.T) {
 | 
			
		||||
	t.Parallel()
 | 
			
		||||
	url, err := url.Parse("http://example.com/soap")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	body := `
 | 
			
		||||
	<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
 | 
			
		||||
		<s:Body>
 | 
			
		||||
			<s:Fault>
 | 
			
		||||
				<faultcode>s:Client</faultcode>
 | 
			
		||||
				<faultstring>UPnPError</faultstring>
 | 
			
		||||
				<detail>
 | 
			
		||||
					<UPnPError xmlns="urn:schemas-upnp-org:control-1-0">
 | 
			
		||||
						<errorCode>725</errorCode>
 | 
			
		||||
						<errorDescription>OnlyPermanentLeasesSupported</errorDescription>
 | 
			
		||||
					</UPnPError>
 | 
			
		||||
				</detail>
 | 
			
		||||
			</s:Fault>
 | 
			
		||||
		</s:Body>
 | 
			
		||||
	</s:Envelope>`
 | 
			
		||||
	rt := &capturingRoundTripper{
 | 
			
		||||
		resp: &http.Response{
 | 
			
		||||
			StatusCode:    500,
 | 
			
		||||
			ContentLength: int64(len(body)),
 | 
			
		||||
			Body:          ioutil.NopCloser(bytes.NewBufferString(body)),
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	client := SOAPClient{
 | 
			
		||||
		EndpointURL: *url,
 | 
			
		||||
		HTTPClient: http.Client{
 | 
			
		||||
			Transport: rt,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = client.PerformAction("mynamespace", "myaction", nil, nil)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatal("expected error, got nil")
 | 
			
		||||
	}
 | 
			
		||||
	if testing.Verbose() {
 | 
			
		||||
		t.Logf("%+v\n", err)
 | 
			
		||||
	}
 | 
			
		||||
	soapErr := &SOAPFaultError{}
 | 
			
		||||
	if ok := errors.As(err, &soapErr); !ok {
 | 
			
		||||
		t.Fatal("expected *SOAPFaultError")
 | 
			
		||||
	}
 | 
			
		||||
	if soapErr.FaultCode != "s:Client" {
 | 
			
		||||
		t.Fatalf("unexpected FaultCode: %s", soapErr.FaultCode)
 | 
			
		||||
	}
 | 
			
		||||
	if soapErr.FaultString != "UPnPError" {
 | 
			
		||||
		t.Fatalf("unexpected FaultString: %s", soapErr.FaultString)
 | 
			
		||||
	}
 | 
			
		||||
	if soapErr.Detail.UPnPError.Errorcode != 725 {
 | 
			
		||||
		t.Fatalf("unexpected UPnPError Errorcode: %d", soapErr.Detail.UPnPError.Errorcode)
 | 
			
		||||
	}
 | 
			
		||||
	if soapErr.Detail.UPnPError.ErrorDescription != "OnlyPermanentLeasesSupported" {
 | 
			
		||||
		t.Fatalf("unexpected UPnPError ErrorDescription: %s",
 | 
			
		||||
			soapErr.Detail.UPnPError.ErrorDescription)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !strings.EqualFold(string(soapErr.Detail.Raw), `
 | 
			
		||||
					<UPnPError xmlns="urn:schemas-upnp-org:control-1-0">
 | 
			
		||||
						<errorCode>725</errorCode>
 | 
			
		||||
						<errorDescription>OnlyPermanentLeasesSupported</errorDescription>
 | 
			
		||||
					</UPnPError>
 | 
			
		||||
				`) {
 | 
			
		||||
		t.Fatalf("unexpected Detail.Raw, got:\n%s", string(soapErr.Detail.Raw))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestEscapeXMLText(t *testing.T) {
 | 
			
		||||
	t.Parallel()
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										77
									
								
								ssdp/ssdp.go
									
									
									
									
									
								
							
							
						
						
									
										77
									
								
								ssdp/ssdp.go
									
									
									
									
									
								
							@@ -35,6 +35,15 @@ type HTTPUClient interface {
 | 
			
		||||
	) ([]*http.Response, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// HTTPUClientCtx is an optional interface that will be used to perform
 | 
			
		||||
// HTTP-over-UDP requests if the client implements it.
 | 
			
		||||
type HTTPUClientCtx interface {
 | 
			
		||||
	DoWithContext(
 | 
			
		||||
		req *http.Request,
 | 
			
		||||
		numSends int,
 | 
			
		||||
	) ([]*http.Response, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SSDPRawSearchCtx performs a fairly raw SSDP search request, and returns the
 | 
			
		||||
// unique response(s) that it receives. Each response has the requested
 | 
			
		||||
// searchTarget, a USN, and a valid location. maxWaitSeconds states how long to
 | 
			
		||||
@@ -49,8 +58,64 @@ func SSDPRawSearchCtx(
 | 
			
		||||
	maxWaitSeconds int,
 | 
			
		||||
	numSends int,
 | 
			
		||||
) ([]*http.Response, error) {
 | 
			
		||||
	req, err := prepareRequest(ctx, searchTarget, maxWaitSeconds)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	allResponses, err := httpu.Do(req, time.Duration(maxWaitSeconds)*time.Second+100*time.Millisecond, numSends)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return processSSDPResponses(searchTarget, allResponses)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RawSearch performs a fairly raw SSDP search request, and returns the
 | 
			
		||||
// unique response(s) that it receives. Each response has the requested
 | 
			
		||||
// searchTarget, a USN, and a valid location. If the provided context times out
 | 
			
		||||
// or is canceled, the search will be aborted. numSends is the number of
 | 
			
		||||
// requests to send - 3 is a reasonable value for this.
 | 
			
		||||
//
 | 
			
		||||
// The provided context should have a deadline, since the SSDP protocol
 | 
			
		||||
// requires the max wait time be included in search requests. If the context
 | 
			
		||||
// has no deadline, then a default deadline of 3 seconds will be applied.
 | 
			
		||||
func RawSearch(
 | 
			
		||||
	ctx context.Context,
 | 
			
		||||
	httpu HTTPUClientCtx,
 | 
			
		||||
	searchTarget string,
 | 
			
		||||
	numSends int,
 | 
			
		||||
) ([]*http.Response, error) {
 | 
			
		||||
	// We need a timeout value to include in the SSDP request; get it by
 | 
			
		||||
	// checking the deadline on the context.
 | 
			
		||||
	var maxWaitSeconds int
 | 
			
		||||
	if deadline, ok := ctx.Deadline(); ok {
 | 
			
		||||
		maxWaitSeconds = int(deadline.Sub(time.Now()) / time.Second)
 | 
			
		||||
	} else {
 | 
			
		||||
		// Pick a default timeout of 3 seconds if none was provided.
 | 
			
		||||
		maxWaitSeconds = 3
 | 
			
		||||
 | 
			
		||||
		var cancel func()
 | 
			
		||||
		ctx, cancel = context.WithTimeout(ctx, time.Duration(maxWaitSeconds)*time.Second)
 | 
			
		||||
		defer cancel()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	req, err := prepareRequest(ctx, searchTarget, maxWaitSeconds)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	allResponses, err := httpu.DoWithContext(req, numSends)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return processSSDPResponses(searchTarget, allResponses)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// prepareRequest checks the provided parameters and constructs a SSDP search
 | 
			
		||||
// request to be sent.
 | 
			
		||||
func prepareRequest(ctx context.Context, searchTarget string, maxWaitSeconds int) (*http.Request, error) {
 | 
			
		||||
	if maxWaitSeconds < 1 {
 | 
			
		||||
		return nil, errors.New("ssdp: maxWaitSeconds must be >= 1")
 | 
			
		||||
		return nil, errors.New("ssdp: request timeout must be at least 1s")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	req := (&http.Request{
 | 
			
		||||
@@ -67,11 +132,13 @@ func SSDPRawSearchCtx(
 | 
			
		||||
			"ST":   []string{searchTarget},
 | 
			
		||||
		},
 | 
			
		||||
	}).WithContext(ctx)
 | 
			
		||||
	allResponses, err := httpu.Do(req, time.Duration(maxWaitSeconds)*time.Second+100*time.Millisecond, numSends)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return req, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func processSSDPResponses(
 | 
			
		||||
	searchTarget string,
 | 
			
		||||
	allResponses []*http.Response,
 | 
			
		||||
) ([]*http.Response, error) {
 | 
			
		||||
	isExactSearch := searchTarget != SSDPAll && searchTarget != UPNPRootDevice
 | 
			
		||||
 | 
			
		||||
	seenIDs := make(map[string]bool)
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,9 @@ import (
 | 
			
		||||
	"github.com/huin/goupnp/v2alpha/description/typedesc"
 | 
			
		||||
	"github.com/huin/goupnp/v2alpha/description/xmlsrvdesc"
 | 
			
		||||
	"github.com/huin/goupnp/v2alpha/soap"
 | 
			
		||||
	"github.com/huin/goupnp/v2alpha/soap/types"
 | 
			
		||||
	"golang.org/x/exp/maps"
 | 
			
		||||
 | 
			
		||||
	soaptypes "github.com/huin/goupnp/v2alpha/soap/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
@@ -31,7 +33,9 @@ var (
 | 
			
		||||
	outputDir        = flag.String("output_dir", "", "Path to directory to write output in.")
 | 
			
		||||
	srvManifests     = flag.String("srv_manifests", "", "Path to srvmanifests.toml")
 | 
			
		||||
	srvTemplate      = flag.String("srv_template", "", "Path to srv.gotemplate.")
 | 
			
		||||
	upnpresourcesZip = flag.String("upnpresources_zip", "", "Path to upnpresources.zip.")
 | 
			
		||||
	upnpresourcesZip = flag.String("upnpresources_zip", "",
 | 
			
		||||
		"Path to upnpresources.zip, downloaded from "+
 | 
			
		||||
			"https://openconnectivity.org/upnp-specs/upnpresources.zip.")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const soapActionInterface = "SOAPActionInterface"
 | 
			
		||||
@@ -50,14 +54,14 @@ func run() error {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if *outputDir == "" {
 | 
			
		||||
		return errors.New("-output_dir is a required flag.")
 | 
			
		||||
		return errors.New("-output_dir is a required flag")
 | 
			
		||||
	}
 | 
			
		||||
	if err := os.MkdirAll(*outputDir, 0); err != nil {
 | 
			
		||||
		return fmt.Errorf("creating output_dir %q: %w", *outputDir, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if *srvManifests == "" {
 | 
			
		||||
		return errors.New("-srv_manifests is a required flag.")
 | 
			
		||||
		return errors.New("-srv_manifests is a required flag")
 | 
			
		||||
	}
 | 
			
		||||
	var manifests DCPSpecManifests
 | 
			
		||||
	_, err := toml.DecodeFile(*srvManifests, &manifests)
 | 
			
		||||
@@ -66,7 +70,7 @@ func run() error {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if *srvTemplate == "" {
 | 
			
		||||
		return errors.New("-srv_template is a required flag.")
 | 
			
		||||
		return errors.New("-srv_template is a required flag")
 | 
			
		||||
	}
 | 
			
		||||
	tmpl, err := template.New(filepath.Base(*srvTemplate)).Funcs(template.FuncMap{
 | 
			
		||||
		"args":  tmplfuncs.Args,
 | 
			
		||||
@@ -77,7 +81,7 @@ func run() error {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if *upnpresourcesZip == "" {
 | 
			
		||||
		return errors.New("-upnpresources_zip is a required flag.")
 | 
			
		||||
		return errors.New("-upnpresources_zip is a required flag")
 | 
			
		||||
	}
 | 
			
		||||
	f, err := os.Open(*upnpresourcesZip)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -91,7 +95,7 @@ func run() error {
 | 
			
		||||
 | 
			
		||||
	// Use default type map for now. Addtional types could be use instead or
 | 
			
		||||
	// as well as necessary for extended types.
 | 
			
		||||
	typeMap := types.TypeMap().Clone()
 | 
			
		||||
	typeMap := soaptypes.TypeMap().Clone()
 | 
			
		||||
	typeMap[soapActionInterface] = typedesc.TypeDesc{
 | 
			
		||||
		GoType: reflect.TypeOf((*soap.Action)(nil)).Elem(),
 | 
			
		||||
	}
 | 
			
		||||
@@ -158,7 +162,8 @@ func processService(
 | 
			
		||||
		return fmt.Errorf("transforming service description: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	imps, err := accumulateImports(sd, typeMap)
 | 
			
		||||
	imps := newImports()
 | 
			
		||||
	types, err := accumulateTypes(sd, typeMap, imps)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
@@ -167,6 +172,7 @@ func processService(
 | 
			
		||||
	err = tmpl.ExecuteTemplate(buf, "service", tmplArgs{
 | 
			
		||||
		Manifest: srvManifest,
 | 
			
		||||
		Imps:     imps,
 | 
			
		||||
		Types:    types,
 | 
			
		||||
		SCPD:     sd,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -219,14 +225,44 @@ type ServiceManifest struct {
 | 
			
		||||
type tmplArgs struct {
 | 
			
		||||
	Manifest *ServiceManifest
 | 
			
		||||
	Imps     *imports
 | 
			
		||||
	Types    *types
 | 
			
		||||
	SCPD     *srvdesc.SCPD
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type imports struct {
 | 
			
		||||
	// Maps from a type name like "ui4" to the `alias.name` for the import.
 | 
			
		||||
	TypeByName map[string]typeDesc
 | 
			
		||||
	// Each required import line, ordered by path.
 | 
			
		||||
	ImportLines []importItem
 | 
			
		||||
	// aliasByPath maps from import path to its imported alias.
 | 
			
		||||
	aliasByPath map[string]string
 | 
			
		||||
	// nextAlias is the number for the next import alias.
 | 
			
		||||
	nextAlias int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newImports() *imports {
 | 
			
		||||
	return &imports{
 | 
			
		||||
		aliasByPath: make(map[string]string),
 | 
			
		||||
		nextAlias:   1,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (imps *imports) getAliasForPath(path string) string {
 | 
			
		||||
	if alias, ok := imps.aliasByPath[path]; ok {
 | 
			
		||||
		return alias
 | 
			
		||||
	}
 | 
			
		||||
	alias := fmt.Sprintf("pkg%d", imps.nextAlias)
 | 
			
		||||
	imps.nextAlias++
 | 
			
		||||
	imps.ImportLines = append(imps.ImportLines, importItem{
 | 
			
		||||
		Alias: alias,
 | 
			
		||||
		Path:  path,
 | 
			
		||||
	})
 | 
			
		||||
	imps.aliasByPath[path] = alias
 | 
			
		||||
	return alias
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type types struct {
 | 
			
		||||
	// Maps from a type name like "ui4" to the `alias.name` for the import.
 | 
			
		||||
	TypeByName    map[string]typeDesc
 | 
			
		||||
	StringVarDefs []stringVarDef
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type typeDesc struct {
 | 
			
		||||
@@ -239,17 +275,41 @@ type typeDesc struct {
 | 
			
		||||
	Name string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type stringVarDef struct {
 | 
			
		||||
	Name          string
 | 
			
		||||
	AllowedValues []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type importItem struct {
 | 
			
		||||
	Alias string
 | 
			
		||||
	Path  string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func accumulateImports(srvDesc *srvdesc.SCPD, typeMap typedesc.TypeMap) (*imports, error) {
 | 
			
		||||
	typeNames := make(map[string]bool)
 | 
			
		||||
	typeNames[soapActionInterface] = true
 | 
			
		||||
// accumulateTypes creates type information, and adds any required imports for
 | 
			
		||||
// them.
 | 
			
		||||
func accumulateTypes(
 | 
			
		||||
	srvDesc *srvdesc.SCPD,
 | 
			
		||||
	typeMap typedesc.TypeMap,
 | 
			
		||||
	imps *imports,
 | 
			
		||||
) (*types, error) {
 | 
			
		||||
	typeNames := make(map[string]struct{})
 | 
			
		||||
	typeNames[soapActionInterface] = struct{}{}
 | 
			
		||||
 | 
			
		||||
	err := visitTypesSCPD(srvDesc, func(typeName string) {
 | 
			
		||||
		typeNames[typeName] = true
 | 
			
		||||
	var stringVarDefs []stringVarDef
 | 
			
		||||
	sortedVarNames := maps.Keys(srvDesc.VariableByName)
 | 
			
		||||
	sort.Strings(sortedVarNames)
 | 
			
		||||
	for _, svName := range sortedVarNames {
 | 
			
		||||
		sv := srvDesc.VariableByName[svName]
 | 
			
		||||
		if sv.DataType == "string" && len(sv.AllowedValues) > 0 {
 | 
			
		||||
			stringVarDefs = append(stringVarDefs, stringVarDef{
 | 
			
		||||
				Name:          svName,
 | 
			
		||||
				AllowedValues: srvDesc.VariableByName[svName].AllowedValues,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err := visitTypesSCPD(srvDesc, func(sv *srvdesc.StateVariable) {
 | 
			
		||||
		typeNames[sv.DataType] = struct{}{}
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
@@ -257,7 +317,7 @@ func accumulateImports(srvDesc *srvdesc.SCPD, typeMap typedesc.TypeMap) (*import
 | 
			
		||||
 | 
			
		||||
	// Have sorted list of import package paths. Partly for aesthetics of generated code, but also
 | 
			
		||||
	// to have stable-generated aliases.
 | 
			
		||||
	paths := make(map[string]bool)
 | 
			
		||||
	paths := make(map[string]struct{})
 | 
			
		||||
	for typeName := range typeNames {
 | 
			
		||||
		t, ok := typeMap[typeName]
 | 
			
		||||
		if !ok {
 | 
			
		||||
@@ -265,29 +325,17 @@ func accumulateImports(srvDesc *srvdesc.SCPD, typeMap typedesc.TypeMap) (*import
 | 
			
		||||
		}
 | 
			
		||||
		pkgPath := t.GoType.PkgPath()
 | 
			
		||||
		if pkgPath == "" {
 | 
			
		||||
			// Builtin type, ignore.
 | 
			
		||||
			// Builtin type, no import needed.
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		paths[pkgPath] = true
 | 
			
		||||
	}
 | 
			
		||||
	sortedPaths := make([]string, 0, len(paths))
 | 
			
		||||
	for path := range paths {
 | 
			
		||||
		sortedPaths = append(sortedPaths, path)
 | 
			
		||||
		paths[pkgPath] = struct{}{}
 | 
			
		||||
	}
 | 
			
		||||
	sortedPaths := maps.Keys(paths)
 | 
			
		||||
	sort.Strings(sortedPaths)
 | 
			
		||||
 | 
			
		||||
	// Generate import aliases.
 | 
			
		||||
	index := 1
 | 
			
		||||
	aliasByPath := make(map[string]string, len(paths))
 | 
			
		||||
	importLines := make([]importItem, 0, len(paths))
 | 
			
		||||
	// Generate import aliases in deterministic order.
 | 
			
		||||
	for _, path := range sortedPaths {
 | 
			
		||||
		alias := fmt.Sprintf("pkg%d", index)
 | 
			
		||||
		index++
 | 
			
		||||
		importLines = append(importLines, importItem{
 | 
			
		||||
			Alias: alias,
 | 
			
		||||
			Path:  path,
 | 
			
		||||
		})
 | 
			
		||||
		aliasByPath[path] = alias
 | 
			
		||||
		imps.getAliasForPath(path)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Populate typeByName.
 | 
			
		||||
@@ -295,28 +343,27 @@ func accumulateImports(srvDesc *srvdesc.SCPD, typeMap typedesc.TypeMap) (*import
 | 
			
		||||
	for typeName := range typeNames {
 | 
			
		||||
		goType := typeMap[typeName]
 | 
			
		||||
		pkgPath := goType.GoType.PkgPath()
 | 
			
		||||
		alias := aliasByPath[pkgPath]
 | 
			
		||||
		td := typeDesc{
 | 
			
		||||
			Name: goType.GoType.Name(),
 | 
			
		||||
		}
 | 
			
		||||
		if alias == "" {
 | 
			
		||||
		if pkgPath == "" {
 | 
			
		||||
			// Builtin type.
 | 
			
		||||
			td.AbsRef = td.Name
 | 
			
		||||
			td.Ref = td.Name
 | 
			
		||||
		} else {
 | 
			
		||||
			td.AbsRef = strconv.Quote(pkgPath) + "." + td.Name
 | 
			
		||||
			td.Ref = alias + "." + td.Name
 | 
			
		||||
			td.Ref = imps.getAliasForPath(pkgPath) + "." + td.Name
 | 
			
		||||
		}
 | 
			
		||||
		typeByName[typeName] = td
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &imports{
 | 
			
		||||
		TypeByName:  typeByName,
 | 
			
		||||
		ImportLines: importLines,
 | 
			
		||||
	return &types{
 | 
			
		||||
		TypeByName:    typeByName,
 | 
			
		||||
		StringVarDefs: stringVarDefs,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type typeVisitor func(typeName string)
 | 
			
		||||
type typeVisitor func(sv *srvdesc.StateVariable)
 | 
			
		||||
 | 
			
		||||
// visitTypesSCPD calls `visitor` with each data type name (e.g. "ui4") referenced
 | 
			
		||||
// by action arguments.`
 | 
			
		||||
@@ -335,14 +382,14 @@ func visitTypesAction(action *srvdesc.Action, visitor typeVisitor) error {
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		visitor(sv.DataType)
 | 
			
		||||
		visitor(sv)
 | 
			
		||||
	}
 | 
			
		||||
	for _, arg := range action.OutArgs {
 | 
			
		||||
		sv, err := arg.RelatedStateVariable()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		visitor(sv.DataType)
 | 
			
		||||
		visitor(sv)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,9 +10,9 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	BadDescriptionError         = errors.New("bad XML description")
 | 
			
		||||
	MissingDefinitionError      = errors.New("missing definition")
 | 
			
		||||
	UnsupportedDescriptionError = errors.New("unsupported XML description")
 | 
			
		||||
	ErrBadDescription         = errors.New("bad XML description")
 | 
			
		||||
	ErrMissingDefinition      = errors.New("missing definition")
 | 
			
		||||
	ErrUnsupportedDescription = errors.New("unsupported XML description")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// SCPD is the top level service description.
 | 
			
		||||
@@ -37,7 +37,7 @@ func FromXML(xmlDesc *xmlsrvdesc.SCPD) (*SCPD, error) {
 | 
			
		||||
		}
 | 
			
		||||
		if _, exists := stateVariables[sv.Name]; exists {
 | 
			
		||||
			return nil, fmt.Errorf("%w: multiple state variables with name %q",
 | 
			
		||||
				BadDescriptionError, sv.Name)
 | 
			
		||||
				ErrBadDescription, sv.Name)
 | 
			
		||||
		}
 | 
			
		||||
		stateVariables[sv.Name] = sv
 | 
			
		||||
	}
 | 
			
		||||
@@ -49,7 +49,7 @@ func FromXML(xmlDesc *xmlsrvdesc.SCPD) (*SCPD, error) {
 | 
			
		||||
		}
 | 
			
		||||
		if _, exists := actions[action.Name]; exists {
 | 
			
		||||
			return nil, fmt.Errorf("%w: multiple actions with name %q",
 | 
			
		||||
				BadDescriptionError, action.Name)
 | 
			
		||||
				ErrBadDescription, action.Name)
 | 
			
		||||
		}
 | 
			
		||||
		actions[action.Name] = action
 | 
			
		||||
	}
 | 
			
		||||
@@ -79,7 +79,7 @@ type Action struct {
 | 
			
		||||
// actionFromXML creates an Action from the given XML description.
 | 
			
		||||
func actionFromXML(xmlAction *xmlsrvdesc.Action, scpd *SCPD) (*Action, error) {
 | 
			
		||||
	if xmlAction.Name == "" {
 | 
			
		||||
		return nil, fmt.Errorf("%w: empty action name", BadDescriptionError)
 | 
			
		||||
		return nil, fmt.Errorf("%w: empty action name", ErrBadDescription)
 | 
			
		||||
	}
 | 
			
		||||
	action := &Action{
 | 
			
		||||
		SCPD: scpd,
 | 
			
		||||
@@ -99,7 +99,7 @@ func actionFromXML(xmlAction *xmlsrvdesc.Action, scpd *SCPD) (*Action, error) {
 | 
			
		||||
			outArgs = append(outArgs, arg)
 | 
			
		||||
		default:
 | 
			
		||||
			return nil, fmt.Errorf("%w: argument %q has invalid direction %q",
 | 
			
		||||
				BadDescriptionError, xmlArg.Name, xmlArg.Direction)
 | 
			
		||||
				ErrBadDescription, xmlArg.Name, xmlArg.Direction)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	action.InArgs = inArgs
 | 
			
		||||
@@ -117,10 +117,10 @@ type Argument struct {
 | 
			
		||||
// argumentFromXML creates an Argument from the XML description.
 | 
			
		||||
func argumentFromXML(xmlArg *xmlsrvdesc.Argument, action *Action) (*Argument, error) {
 | 
			
		||||
	if xmlArg.Name == "" {
 | 
			
		||||
		return nil, fmt.Errorf("%w: empty argument name", BadDescriptionError)
 | 
			
		||||
		return nil, fmt.Errorf("%w: empty argument name", ErrBadDescription)
 | 
			
		||||
	}
 | 
			
		||||
	if xmlArg.RelatedStateVariable == "" {
 | 
			
		||||
		return nil, fmt.Errorf("%w: empty related state variable", BadDescriptionError)
 | 
			
		||||
		return nil, fmt.Errorf("%w: empty related state variable", ErrBadDescription)
 | 
			
		||||
	}
 | 
			
		||||
	return &Argument{
 | 
			
		||||
		Action:                   action,
 | 
			
		||||
@@ -133,25 +133,32 @@ func (arg *Argument) RelatedStateVariable() (*StateVariable, error) {
 | 
			
		||||
	if v, ok := arg.Action.SCPD.VariableByName[arg.RelatedStateVariableName]; ok {
 | 
			
		||||
		return v, nil
 | 
			
		||||
	}
 | 
			
		||||
	return nil, fmt.Errorf("%w: state variable %q", MissingDefinitionError, arg.RelatedStateVariableName)
 | 
			
		||||
	return nil, fmt.Errorf("%w: state variable %q", ErrMissingDefinition, arg.RelatedStateVariableName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// StateVariable description data.
 | 
			
		||||
type StateVariable struct {
 | 
			
		||||
	Name     string
 | 
			
		||||
	DataType string
 | 
			
		||||
 | 
			
		||||
	AllowedValues []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func stateVariableFromXML(xmlSV *xmlsrvdesc.StateVariable) (*StateVariable, error) {
 | 
			
		||||
	if xmlSV.Name == "" {
 | 
			
		||||
		return nil, fmt.Errorf("%w: empty state variable name", BadDescriptionError)
 | 
			
		||||
		return nil, fmt.Errorf("%w: empty state variable name", ErrBadDescription)
 | 
			
		||||
	}
 | 
			
		||||
	if xmlSV.DataType.Type != "" {
 | 
			
		||||
		return nil, fmt.Errorf("%w: unsupported data type %q",
 | 
			
		||||
			UnsupportedDescriptionError, xmlSV.DataType.Type)
 | 
			
		||||
			ErrUnsupportedDescription, xmlSV.DataType.Type)
 | 
			
		||||
	}
 | 
			
		||||
	if xmlSV.DataType.Name != "string" && len(xmlSV.AllowedValues) > 0 {
 | 
			
		||||
		return nil, fmt.Errorf("%w: allowedValueList is currently unsupported for type %q",
 | 
			
		||||
			ErrUnsupportedDescription, xmlSV.DataType.Name)
 | 
			
		||||
	}
 | 
			
		||||
	return &StateVariable{
 | 
			
		||||
		Name:     xmlSV.Name,
 | 
			
		||||
		DataType: xmlSV.DataType.Name,
 | 
			
		||||
		Name:          xmlSV.Name,
 | 
			
		||||
		DataType:      xmlSV.DataType.Name,
 | 
			
		||||
		AllowedValues: xmlSV.AllowedValues,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,8 @@ module github.com/huin/goupnp/v2alpha
 | 
			
		||||
 | 
			
		||||
go 1.18
 | 
			
		||||
 | 
			
		||||
require github.com/google/go-cmp v0.5.7
 | 
			
		||||
require github.com/google/go-cmp v0.5.8
 | 
			
		||||
 | 
			
		||||
require github.com/BurntSushi/toml v1.1.0
 | 
			
		||||
 | 
			
		||||
require golang.org/x/exp v0.0.0-20230307190834-24139beb5833 // indirect
 | 
			
		||||
 
 | 
			
		||||
@@ -2,5 +2,9 @@ github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I
 | 
			
		||||
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 | 
			
		||||
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
 | 
			
		||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
 | 
			
		||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
 | 
			
		||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 | 
			
		||||
golang.org/x/exp v0.0.0-20230307190834-24139beb5833 h1:SChBja7BCQewoTAU7IgvucQKMIXrEpFxNMs0spT3/5s=
 | 
			
		||||
golang.org/x/exp v0.0.0-20230307190834-24139beb5833/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
 
 | 
			
		||||
@@ -8,15 +8,52 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/huin/goupnp/v2alpha/soap"
 | 
			
		||||
	"github.com/huin/goupnp/v2alpha/soap/envelope"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var _ HttpClient = &http.Client{}
 | 
			
		||||
var (
 | 
			
		||||
	// ErrSOAP can be used with errors.Is.
 | 
			
		||||
	ErrSOAP = errors.New("SOAP error")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// HttpClient defines the interface required of an HTTP client. It is a subset of *http.Client.
 | 
			
		||||
type HttpClient interface {
 | 
			
		||||
// SOAPError describes an error from this package, potentially including a
 | 
			
		||||
// lower-level cause.
 | 
			
		||||
type SOAPError struct {
 | 
			
		||||
	// description describes the error from the SOAP perspective.
 | 
			
		||||
	description string
 | 
			
		||||
	// cause may be nil.
 | 
			
		||||
	cause error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (se *SOAPError) Error() string {
 | 
			
		||||
	b := &strings.Builder{}
 | 
			
		||||
	b.WriteString("SOAP error")
 | 
			
		||||
	if se.description != "" {
 | 
			
		||||
		b.WriteString(": ")
 | 
			
		||||
		b.WriteString(se.description)
 | 
			
		||||
	}
 | 
			
		||||
	if se.cause != nil {
 | 
			
		||||
		b.WriteString(": ")
 | 
			
		||||
		b.WriteString(se.cause.Error())
 | 
			
		||||
	}
 | 
			
		||||
	return b.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (se *SOAPError) Is(target error) bool {
 | 
			
		||||
	return target == ErrSOAP
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (se *SOAPError) Unwrap() error {
 | 
			
		||||
	return se.cause
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var _ HTTPClient = &http.Client{}
 | 
			
		||||
 | 
			
		||||
// HTTPClient defines the interface required of an HTTP client. It is a subset of *http.Client.
 | 
			
		||||
type HTTPClient interface {
 | 
			
		||||
	Do(req *http.Request) (*http.Response, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -25,22 +62,21 @@ type Option func(*options)
 | 
			
		||||
 | 
			
		||||
// WithHTTPClient specifies an *http.Client to use instead of
 | 
			
		||||
// http.DefaultClient.
 | 
			
		||||
func WithHTTPClient(httpClient HttpClient) Option {
 | 
			
		||||
func WithHTTPClient(httpClient HTTPClient) Option {
 | 
			
		||||
	return func(o *options) {
 | 
			
		||||
		o.httpClient = httpClient
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type options struct {
 | 
			
		||||
	httpClient HttpClient
 | 
			
		||||
	httpClient HTTPClient
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Client is a SOAP client, attached to a specific SOAP endpoint.
 | 
			
		||||
// the zero value is not usable, use NewClient() to create an instance.
 | 
			
		||||
type Client struct {
 | 
			
		||||
	httpClient            HttpClient
 | 
			
		||||
	endpointURL           string
 | 
			
		||||
	maxErrorResponseBytes int
 | 
			
		||||
	httpClient  HTTPClient
 | 
			
		||||
	endpointURL string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New creates a new SOAP client, which will POST its requests to the
 | 
			
		||||
@@ -79,8 +115,10 @@ func (c *Client) Do(
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
 | 
			
		||||
	if resp.StatusCode != http.StatusOK {
 | 
			
		||||
		return fmt.Errorf("SOAP request got HTTP %s (%d)",
 | 
			
		||||
			resp.Status, resp.StatusCode)
 | 
			
		||||
		return &SOAPError{
 | 
			
		||||
			description: fmt.Sprintf("SOAP request got HTTP %s (%d)",
 | 
			
		||||
				resp.Status, resp.StatusCode),
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ParseResponseAction(resp, actionOut)
 | 
			
		||||
@@ -110,7 +148,10 @@ func SetRequestAction(
 | 
			
		||||
	buf := &bytes.Buffer{}
 | 
			
		||||
	err := envelope.Write(buf, actionIn)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("encoding envelope: %w", err)
 | 
			
		||||
		return &SOAPError{
 | 
			
		||||
			description: "encoding envelope",
 | 
			
		||||
			cause:       err,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	req.Body = io.NopCloser(buf)
 | 
			
		||||
@@ -131,31 +172,39 @@ func ParseResponseAction(
 | 
			
		||||
	actionOut *envelope.Action,
 | 
			
		||||
) error {
 | 
			
		||||
	if resp.Body == nil {
 | 
			
		||||
		return errors.New("missing response body")
 | 
			
		||||
		return &SOAPError{description: "missing HTTP response body"}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	buf := &bytes.Buffer{}
 | 
			
		||||
	if _, err := io.Copy(buf, resp.Body); err != nil {
 | 
			
		||||
		return fmt.Errorf("reading response body: %w", err)
 | 
			
		||||
		return &SOAPError{
 | 
			
		||||
			description: "reading HTTP response body",
 | 
			
		||||
			cause:       err,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := envelope.Read(buf, actionOut); err != nil {
 | 
			
		||||
		if _, ok := err.(*envelope.Fault); ok {
 | 
			
		||||
		if errors.Is(err, envelope.ErrFault) {
 | 
			
		||||
			// Parsed cleanly, got SOAP fault.
 | 
			
		||||
			return err
 | 
			
		||||
			return &SOAPError{
 | 
			
		||||
				description: "SOAP fault",
 | 
			
		||||
				cause:       err,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		// Parsing problem, provide some information for context.
 | 
			
		||||
		dispLen := buf.Len()
 | 
			
		||||
		truncMessage := ""
 | 
			
		||||
		if dispLen > 1024 {
 | 
			
		||||
			dispLen = 1024
 | 
			
		||||
			truncMessage = fmt.Sprintf("first %d bytes: ", dispLen)
 | 
			
		||||
			truncMessage = fmt.Sprintf("first %d bytes (total %d bytes): ", dispLen, buf.Len())
 | 
			
		||||
		}
 | 
			
		||||
		return &SOAPError{
 | 
			
		||||
			description: fmt.Sprintf(
 | 
			
		||||
				"parsing SOAP response from HTTP body (%s%q)",
 | 
			
		||||
				truncMessage, buf.Bytes()[:dispLen],
 | 
			
		||||
			),
 | 
			
		||||
			cause: err,
 | 
			
		||||
		}
 | 
			
		||||
		return fmt.Errorf(
 | 
			
		||||
			"parsing response body (%s%q): %w",
 | 
			
		||||
			truncMessage, buf.Bytes()[:dispLen],
 | 
			
		||||
			err,
 | 
			
		||||
		)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
 
 | 
			
		||||
@@ -321,9 +321,9 @@ var _ SOAPValue = &Fixed14_4{}
 | 
			
		||||
 | 
			
		||||
// Fixed14_4FromParts creates a Fixed14_4 from components.
 | 
			
		||||
// Bounds:
 | 
			
		||||
//   * Both intPart and fracPart must have the same sign.
 | 
			
		||||
//   * -1e14 < intPart < 1e14
 | 
			
		||||
//   * -1e4 < fracPart < 1e4
 | 
			
		||||
//   - Both intPart and fracPart must have the same sign.
 | 
			
		||||
//   - -1e14 < intPart < 1e14
 | 
			
		||||
//   - -1e4 < fracPart < 1e4
 | 
			
		||||
func Fixed14_4FromParts(intPart int64, fracPart int16) (Fixed14_4, error) {
 | 
			
		||||
	var v Fixed14_4
 | 
			
		||||
	err := v.SetParts(intPart, fracPart)
 | 
			
		||||
@@ -332,9 +332,9 @@ func Fixed14_4FromParts(intPart int64, fracPart int16) (Fixed14_4, error) {
 | 
			
		||||
 | 
			
		||||
// SetFromParts sets the value based on the integer component and the fractional component.
 | 
			
		||||
// Bounds:
 | 
			
		||||
//   * Both intPart and fracPart must have the same sign.
 | 
			
		||||
//   * -1e14 < intPart < 1e14
 | 
			
		||||
//   * -1e4 < fracPart < 1e4
 | 
			
		||||
//   - Both intPart and fracPart must have the same sign.
 | 
			
		||||
//   - -1e14 < intPart < 1e14
 | 
			
		||||
//   - -1e4 < fracPart < 1e4
 | 
			
		||||
func (v *Fixed14_4) SetParts(intPart int64, fracPart int16) error {
 | 
			
		||||
	if (intPart < 0) != (fracPart < 0) {
 | 
			
		||||
		return fmt.Errorf("want intPart and fracPart with same sign, got %d and %d",
 | 
			
		||||
 
 | 
			
		||||
@@ -34,6 +34,7 @@ func (a *DeleteDNSServer) RefResponse() any { return &a.Response }
 | 
			
		||||
 | 
			
		||||
// DeleteDNSServerRequest contains the "in" args for the "DeleteDNSServer" action.
 | 
			
		||||
type DeleteDNSServerRequest struct {
 | 
			
		||||
	// NewDNSServers relates to state variable DNSServers.
 | 
			
		||||
	NewDNSServers string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -64,6 +65,7 @@ func (a *DeleteIPRouter) RefResponse() any { return &a.Response }
 | 
			
		||||
 | 
			
		||||
// DeleteIPRouterRequest contains the "in" args for the "DeleteIPRouter" action.
 | 
			
		||||
type DeleteIPRouterRequest struct {
 | 
			
		||||
	// NewIPRouters relates to state variable IPRouters.
 | 
			
		||||
	NewIPRouters string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -94,6 +96,7 @@ func (a *DeleteReservedAddress) RefResponse() any { return &a.Response }
 | 
			
		||||
 | 
			
		||||
// DeleteReservedAddressRequest contains the "in" args for the "DeleteReservedAddress" action.
 | 
			
		||||
type DeleteReservedAddressRequest struct {
 | 
			
		||||
	// NewReservedAddresses relates to state variable ReservedAddresses.
 | 
			
		||||
	NewReservedAddresses string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -127,7 +130,9 @@ type GetAddressRangeRequest struct{}
 | 
			
		||||
 | 
			
		||||
// GetAddressRangeResponse contains the "out" args for the "GetAddressRange" action.
 | 
			
		||||
type GetAddressRangeResponse struct {
 | 
			
		||||
	// NewMinAddress relates to state variable MinAddress.
 | 
			
		||||
	NewMinAddress string
 | 
			
		||||
	// NewMaxAddress relates to state variable MaxAddress.
 | 
			
		||||
	NewMaxAddress string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -158,6 +163,7 @@ type GetDHCPRelayRequest struct{}
 | 
			
		||||
 | 
			
		||||
// GetDHCPRelayResponse contains the "out" args for the "GetDHCPRelay" action.
 | 
			
		||||
type GetDHCPRelayResponse struct {
 | 
			
		||||
	// NewDHCPRelay relates to state variable DHCPRelay.
 | 
			
		||||
	NewDHCPRelay pkg2.Boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -188,6 +194,7 @@ type GetDHCPServerConfigurableRequest struct{}
 | 
			
		||||
 | 
			
		||||
// GetDHCPServerConfigurableResponse contains the "out" args for the "GetDHCPServerConfigurable" action.
 | 
			
		||||
type GetDHCPServerConfigurableResponse struct {
 | 
			
		||||
	// NewDHCPServerConfigurable relates to state variable DHCPServerConfigurable.
 | 
			
		||||
	NewDHCPServerConfigurable pkg2.Boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -218,6 +225,7 @@ type GetDNSServersRequest struct{}
 | 
			
		||||
 | 
			
		||||
// GetDNSServersResponse contains the "out" args for the "GetDNSServers" action.
 | 
			
		||||
type GetDNSServersResponse struct {
 | 
			
		||||
	// NewDNSServers relates to state variable DNSServers.
 | 
			
		||||
	NewDNSServers string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -248,6 +256,7 @@ type GetDomainNameRequest struct{}
 | 
			
		||||
 | 
			
		||||
// GetDomainNameResponse contains the "out" args for the "GetDomainName" action.
 | 
			
		||||
type GetDomainNameResponse struct {
 | 
			
		||||
	// NewDomainName relates to state variable DomainName.
 | 
			
		||||
	NewDomainName string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -278,6 +287,7 @@ type GetIPRoutersListRequest struct{}
 | 
			
		||||
 | 
			
		||||
// GetIPRoutersListResponse contains the "out" args for the "GetIPRoutersList" action.
 | 
			
		||||
type GetIPRoutersListResponse struct {
 | 
			
		||||
	// NewIPRouters relates to state variable IPRouters.
 | 
			
		||||
	NewIPRouters string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -308,6 +318,7 @@ type GetReservedAddressesRequest struct{}
 | 
			
		||||
 | 
			
		||||
// GetReservedAddressesResponse contains the "out" args for the "GetReservedAddresses" action.
 | 
			
		||||
type GetReservedAddressesResponse struct {
 | 
			
		||||
	// NewReservedAddresses relates to state variable ReservedAddresses.
 | 
			
		||||
	NewReservedAddresses string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -338,6 +349,7 @@ type GetSubnetMaskRequest struct{}
 | 
			
		||||
 | 
			
		||||
// GetSubnetMaskResponse contains the "out" args for the "GetSubnetMask" action.
 | 
			
		||||
type GetSubnetMaskResponse struct {
 | 
			
		||||
	// NewSubnetMask relates to state variable SubnetMask.
 | 
			
		||||
	NewSubnetMask string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -365,7 +377,9 @@ func (a *SetAddressRange) RefResponse() any { return &a.Response }
 | 
			
		||||
 | 
			
		||||
// SetAddressRangeRequest contains the "in" args for the "SetAddressRange" action.
 | 
			
		||||
type SetAddressRangeRequest struct {
 | 
			
		||||
	// NewMinAddress relates to state variable MinAddress.
 | 
			
		||||
	NewMinAddress string
 | 
			
		||||
	// NewMaxAddress relates to state variable MaxAddress.
 | 
			
		||||
	NewMaxAddress string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -396,6 +410,7 @@ func (a *SetDHCPRelay) RefResponse() any { return &a.Response }
 | 
			
		||||
 | 
			
		||||
// SetDHCPRelayRequest contains the "in" args for the "SetDHCPRelay" action.
 | 
			
		||||
type SetDHCPRelayRequest struct {
 | 
			
		||||
	// NewDHCPRelay relates to state variable DHCPRelay.
 | 
			
		||||
	NewDHCPRelay pkg2.Boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -426,6 +441,7 @@ func (a *SetDHCPServerConfigurable) RefResponse() any { return &a.Response }
 | 
			
		||||
 | 
			
		||||
// SetDHCPServerConfigurableRequest contains the "in" args for the "SetDHCPServerConfigurable" action.
 | 
			
		||||
type SetDHCPServerConfigurableRequest struct {
 | 
			
		||||
	// NewDHCPServerConfigurable relates to state variable DHCPServerConfigurable.
 | 
			
		||||
	NewDHCPServerConfigurable pkg2.Boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -456,6 +472,7 @@ func (a *SetDNSServer) RefResponse() any { return &a.Response }
 | 
			
		||||
 | 
			
		||||
// SetDNSServerRequest contains the "in" args for the "SetDNSServer" action.
 | 
			
		||||
type SetDNSServerRequest struct {
 | 
			
		||||
	// NewDNSServers relates to state variable DNSServers.
 | 
			
		||||
	NewDNSServers string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -486,6 +503,7 @@ func (a *SetDomainName) RefResponse() any { return &a.Response }
 | 
			
		||||
 | 
			
		||||
// SetDomainNameRequest contains the "in" args for the "SetDomainName" action.
 | 
			
		||||
type SetDomainNameRequest struct {
 | 
			
		||||
	// NewDomainName relates to state variable DomainName.
 | 
			
		||||
	NewDomainName string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -516,6 +534,7 @@ func (a *SetIPRouter) RefResponse() any { return &a.Response }
 | 
			
		||||
 | 
			
		||||
// SetIPRouterRequest contains the "in" args for the "SetIPRouter" action.
 | 
			
		||||
type SetIPRouterRequest struct {
 | 
			
		||||
	// NewIPRouters relates to state variable IPRouters.
 | 
			
		||||
	NewIPRouters string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -546,6 +565,7 @@ func (a *SetReservedAddress) RefResponse() any { return &a.Response }
 | 
			
		||||
 | 
			
		||||
// SetReservedAddressRequest contains the "in" args for the "SetReservedAddress" action.
 | 
			
		||||
type SetReservedAddressRequest struct {
 | 
			
		||||
	// NewReservedAddresses relates to state variable ReservedAddresses.
 | 
			
		||||
	NewReservedAddresses string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -576,6 +596,7 @@ func (a *SetSubnetMask) RefResponse() any { return &a.Response }
 | 
			
		||||
 | 
			
		||||
// SetSubnetMaskRequest contains the "in" args for the "SetSubnetMask" action.
 | 
			
		||||
type SetSubnetMaskRequest struct {
 | 
			
		||||
	// NewSubnetMask relates to state variable SubnetMask.
 | 
			
		||||
	NewSubnetMask string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,35 @@ import (
 | 
			
		||||
	pkg2 "github.com/huin/goupnp/v2alpha/soap/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Allowed values for state variable ConnectionStatus.
 | 
			
		||||
const (
 | 
			
		||||
	ConnectionStatus_Unconfigured = "Unconfigured"
 | 
			
		||||
	ConnectionStatus_Connected    = "Connected"
 | 
			
		||||
	ConnectionStatus_Disconnected = "Disconnected"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Allowed values for state variable LastConnectionError.
 | 
			
		||||
const (
 | 
			
		||||
	LastConnectionError_ERROR_NONE = "ERROR_NONE"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Allowed values for state variable PortMappingProtocol.
 | 
			
		||||
const (
 | 
			
		||||
	PortMappingProtocol_TCP = "TCP"
 | 
			
		||||
	PortMappingProtocol_UDP = "UDP"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Allowed values for state variable PossibleConnectionTypes.
 | 
			
		||||
const (
 | 
			
		||||
	PossibleConnectionTypes_Unconfigured  = "Unconfigured"
 | 
			
		||||
	PossibleConnectionTypes_IP_Routed     = "IP_Routed"
 | 
			
		||||
	PossibleConnectionTypes_DHCP_Spoofed  = "DHCP_Spoofed"
 | 
			
		||||
	PossibleConnectionTypes_PPPoE_Bridged = "PPPoE_Bridged"
 | 
			
		||||
	PossibleConnectionTypes_PPTP_Relay    = "PPTP_Relay"
 | 
			
		||||
	PossibleConnectionTypes_L2TP_Relay    = "L2TP_Relay"
 | 
			
		||||
	PossibleConnectionTypes_PPPoE_Relay   = "PPPoE_Relay"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const ServiceType = "urn:schemas-upnp-org:service:WANPPPConnection:1"
 | 
			
		||||
 | 
			
		||||
// AddPortMapping provides request and response for the action.
 | 
			
		||||
@@ -34,14 +63,22 @@ func (a *AddPortMapping) RefResponse() any { return &a.Response }
 | 
			
		||||
 | 
			
		||||
// AddPortMappingRequest contains the "in" args for the "AddPortMapping" action.
 | 
			
		||||
type AddPortMappingRequest struct {
 | 
			
		||||
	NewRemoteHost             string
 | 
			
		||||
	NewExternalPort           pkg2.UI2
 | 
			
		||||
	NewProtocol               string
 | 
			
		||||
	NewInternalPort           pkg2.UI2
 | 
			
		||||
	NewInternalClient         string
 | 
			
		||||
	NewEnabled                pkg2.Boolean
 | 
			
		||||
	// NewRemoteHost relates to state variable RemoteHost.
 | 
			
		||||
	NewRemoteHost string
 | 
			
		||||
	// NewExternalPort relates to state variable ExternalPort.
 | 
			
		||||
	NewExternalPort pkg2.UI2
 | 
			
		||||
	// NewProtocol relates to state variable PortMappingProtocol (2 standard allowed values).
 | 
			
		||||
	NewProtocol string
 | 
			
		||||
	// NewInternalPort relates to state variable InternalPort.
 | 
			
		||||
	NewInternalPort pkg2.UI2
 | 
			
		||||
	// NewInternalClient relates to state variable InternalClient.
 | 
			
		||||
	NewInternalClient string
 | 
			
		||||
	// NewEnabled relates to state variable PortMappingEnabled.
 | 
			
		||||
	NewEnabled pkg2.Boolean
 | 
			
		||||
	// NewPortMappingDescription relates to state variable PortMappingDescription.
 | 
			
		||||
	NewPortMappingDescription string
 | 
			
		||||
	NewLeaseDuration          pkg2.UI4
 | 
			
		||||
	// NewLeaseDuration relates to state variable PortMappingLeaseDuration.
 | 
			
		||||
	NewLeaseDuration pkg2.UI4
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AddPortMappingResponse contains the "out" args for the "AddPortMapping" action.
 | 
			
		||||
@@ -71,7 +108,9 @@ func (a *ConfigureConnection) RefResponse() any { return &a.Response }
 | 
			
		||||
 | 
			
		||||
// ConfigureConnectionRequest contains the "in" args for the "ConfigureConnection" action.
 | 
			
		||||
type ConfigureConnectionRequest struct {
 | 
			
		||||
	// NewUserName relates to state variable UserName.
 | 
			
		||||
	NewUserName string
 | 
			
		||||
	// NewPassword relates to state variable Password.
 | 
			
		||||
	NewPassword string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -102,9 +141,12 @@ func (a *DeletePortMapping) RefResponse() any { return &a.Response }
 | 
			
		||||
 | 
			
		||||
// DeletePortMappingRequest contains the "in" args for the "DeletePortMapping" action.
 | 
			
		||||
type DeletePortMappingRequest struct {
 | 
			
		||||
	NewRemoteHost   string
 | 
			
		||||
	// NewRemoteHost relates to state variable RemoteHost.
 | 
			
		||||
	NewRemoteHost string
 | 
			
		||||
	// NewExternalPort relates to state variable ExternalPort.
 | 
			
		||||
	NewExternalPort pkg2.UI2
 | 
			
		||||
	NewProtocol     string
 | 
			
		||||
	// NewProtocol relates to state variable PortMappingProtocol (2 standard allowed values).
 | 
			
		||||
	NewProtocol string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeletePortMappingResponse contains the "out" args for the "DeletePortMapping" action.
 | 
			
		||||
@@ -165,6 +207,7 @@ type GetAutoDisconnectTimeRequest struct{}
 | 
			
		||||
 | 
			
		||||
// GetAutoDisconnectTimeResponse contains the "out" args for the "GetAutoDisconnectTime" action.
 | 
			
		||||
type GetAutoDisconnectTimeResponse struct {
 | 
			
		||||
	// NewAutoDisconnectTime relates to state variable AutoDisconnectTime.
 | 
			
		||||
	NewAutoDisconnectTime pkg2.UI4
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -195,7 +238,9 @@ type GetConnectionTypeInfoRequest struct{}
 | 
			
		||||
 | 
			
		||||
// GetConnectionTypeInfoResponse contains the "out" args for the "GetConnectionTypeInfo" action.
 | 
			
		||||
type GetConnectionTypeInfoResponse struct {
 | 
			
		||||
	NewConnectionType          string
 | 
			
		||||
	// NewConnectionType relates to state variable ConnectionType.
 | 
			
		||||
	NewConnectionType string
 | 
			
		||||
	// NewPossibleConnectionTypes relates to state variable PossibleConnectionTypes (7 standard allowed values).
 | 
			
		||||
	NewPossibleConnectionTypes string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -226,6 +271,7 @@ type GetExternalIPAddressRequest struct{}
 | 
			
		||||
 | 
			
		||||
// GetExternalIPAddressResponse contains the "out" args for the "GetExternalIPAddress" action.
 | 
			
		||||
type GetExternalIPAddressResponse struct {
 | 
			
		||||
	// NewExternalIPAddress relates to state variable ExternalIPAddress.
 | 
			
		||||
	NewExternalIPAddress string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -253,19 +299,28 @@ func (a *GetGenericPortMappingEntry) RefResponse() any { return &a.Response }
 | 
			
		||||
 | 
			
		||||
// GetGenericPortMappingEntryRequest contains the "in" args for the "GetGenericPortMappingEntry" action.
 | 
			
		||||
type GetGenericPortMappingEntryRequest struct {
 | 
			
		||||
	// NewPortMappingIndex relates to state variable PortMappingNumberOfEntries.
 | 
			
		||||
	NewPortMappingIndex pkg2.UI2
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetGenericPortMappingEntryResponse contains the "out" args for the "GetGenericPortMappingEntry" action.
 | 
			
		||||
type GetGenericPortMappingEntryResponse struct {
 | 
			
		||||
	NewRemoteHost             string
 | 
			
		||||
	NewExternalPort           pkg2.UI2
 | 
			
		||||
	NewProtocol               string
 | 
			
		||||
	NewInternalPort           pkg2.UI2
 | 
			
		||||
	NewInternalClient         string
 | 
			
		||||
	NewEnabled                pkg2.Boolean
 | 
			
		||||
	// NewRemoteHost relates to state variable RemoteHost.
 | 
			
		||||
	NewRemoteHost string
 | 
			
		||||
	// NewExternalPort relates to state variable ExternalPort.
 | 
			
		||||
	NewExternalPort pkg2.UI2
 | 
			
		||||
	// NewProtocol relates to state variable PortMappingProtocol (2 standard allowed values).
 | 
			
		||||
	NewProtocol string
 | 
			
		||||
	// NewInternalPort relates to state variable InternalPort.
 | 
			
		||||
	NewInternalPort pkg2.UI2
 | 
			
		||||
	// NewInternalClient relates to state variable InternalClient.
 | 
			
		||||
	NewInternalClient string
 | 
			
		||||
	// NewEnabled relates to state variable PortMappingEnabled.
 | 
			
		||||
	NewEnabled pkg2.Boolean
 | 
			
		||||
	// NewPortMappingDescription relates to state variable PortMappingDescription.
 | 
			
		||||
	NewPortMappingDescription string
 | 
			
		||||
	NewLeaseDuration          pkg2.UI4
 | 
			
		||||
	// NewLeaseDuration relates to state variable PortMappingLeaseDuration.
 | 
			
		||||
	NewLeaseDuration pkg2.UI4
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetIdleDisconnectTime provides request and response for the action.
 | 
			
		||||
@@ -295,6 +350,7 @@ type GetIdleDisconnectTimeRequest struct{}
 | 
			
		||||
 | 
			
		||||
// GetIdleDisconnectTimeResponse contains the "out" args for the "GetIdleDisconnectTime" action.
 | 
			
		||||
type GetIdleDisconnectTimeResponse struct {
 | 
			
		||||
	// NewIdleDisconnectTime relates to state variable IdleDisconnectTime.
 | 
			
		||||
	NewIdleDisconnectTime pkg2.UI4
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -325,7 +381,9 @@ type GetLinkLayerMaxBitRatesRequest struct{}
 | 
			
		||||
 | 
			
		||||
// GetLinkLayerMaxBitRatesResponse contains the "out" args for the "GetLinkLayerMaxBitRates" action.
 | 
			
		||||
type GetLinkLayerMaxBitRatesResponse struct {
 | 
			
		||||
	NewUpstreamMaxBitRate   pkg2.UI4
 | 
			
		||||
	// NewUpstreamMaxBitRate relates to state variable UpstreamMaxBitRate.
 | 
			
		||||
	NewUpstreamMaxBitRate pkg2.UI4
 | 
			
		||||
	// NewDownstreamMaxBitRate relates to state variable DownstreamMaxBitRate.
 | 
			
		||||
	NewDownstreamMaxBitRate pkg2.UI4
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -356,8 +414,10 @@ type GetNATRSIPStatusRequest struct{}
 | 
			
		||||
 | 
			
		||||
// GetNATRSIPStatusResponse contains the "out" args for the "GetNATRSIPStatus" action.
 | 
			
		||||
type GetNATRSIPStatusResponse struct {
 | 
			
		||||
	// NewRSIPAvailable relates to state variable RSIPAvailable.
 | 
			
		||||
	NewRSIPAvailable pkg2.Boolean
 | 
			
		||||
	NewNATEnabled    pkg2.Boolean
 | 
			
		||||
	// NewNATEnabled relates to state variable NATEnabled.
 | 
			
		||||
	NewNATEnabled pkg2.Boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetPPPAuthenticationProtocol provides request and response for the action.
 | 
			
		||||
@@ -387,6 +447,7 @@ type GetPPPAuthenticationProtocolRequest struct{}
 | 
			
		||||
 | 
			
		||||
// GetPPPAuthenticationProtocolResponse contains the "out" args for the "GetPPPAuthenticationProtocol" action.
 | 
			
		||||
type GetPPPAuthenticationProtocolResponse struct {
 | 
			
		||||
	// NewPPPAuthenticationProtocol relates to state variable PPPAuthenticationProtocol.
 | 
			
		||||
	NewPPPAuthenticationProtocol string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -417,6 +478,7 @@ type GetPPPCompressionProtocolRequest struct{}
 | 
			
		||||
 | 
			
		||||
// GetPPPCompressionProtocolResponse contains the "out" args for the "GetPPPCompressionProtocol" action.
 | 
			
		||||
type GetPPPCompressionProtocolResponse struct {
 | 
			
		||||
	// NewPPPCompressionProtocol relates to state variable PPPCompressionProtocol.
 | 
			
		||||
	NewPPPCompressionProtocol string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -447,6 +509,7 @@ type GetPPPEncryptionProtocolRequest struct{}
 | 
			
		||||
 | 
			
		||||
// GetPPPEncryptionProtocolResponse contains the "out" args for the "GetPPPEncryptionProtocol" action.
 | 
			
		||||
type GetPPPEncryptionProtocolResponse struct {
 | 
			
		||||
	// NewPPPEncryptionProtocol relates to state variable PPPEncryptionProtocol.
 | 
			
		||||
	NewPPPEncryptionProtocol string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -477,6 +540,7 @@ type GetPasswordRequest struct{}
 | 
			
		||||
 | 
			
		||||
// GetPasswordResponse contains the "out" args for the "GetPassword" action.
 | 
			
		||||
type GetPasswordResponse struct {
 | 
			
		||||
	// NewPassword relates to state variable Password.
 | 
			
		||||
	NewPassword string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -504,18 +568,26 @@ func (a *GetSpecificPortMappingEntry) RefResponse() any { return &a.Response }
 | 
			
		||||
 | 
			
		||||
// GetSpecificPortMappingEntryRequest contains the "in" args for the "GetSpecificPortMappingEntry" action.
 | 
			
		||||
type GetSpecificPortMappingEntryRequest struct {
 | 
			
		||||
	NewRemoteHost   string
 | 
			
		||||
	// NewRemoteHost relates to state variable RemoteHost.
 | 
			
		||||
	NewRemoteHost string
 | 
			
		||||
	// NewExternalPort relates to state variable ExternalPort.
 | 
			
		||||
	NewExternalPort pkg2.UI2
 | 
			
		||||
	NewProtocol     string
 | 
			
		||||
	// NewProtocol relates to state variable PortMappingProtocol (2 standard allowed values).
 | 
			
		||||
	NewProtocol string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetSpecificPortMappingEntryResponse contains the "out" args for the "GetSpecificPortMappingEntry" action.
 | 
			
		||||
type GetSpecificPortMappingEntryResponse struct {
 | 
			
		||||
	NewInternalPort           pkg2.UI2
 | 
			
		||||
	NewInternalClient         string
 | 
			
		||||
	NewEnabled                pkg2.Boolean
 | 
			
		||||
	// NewInternalPort relates to state variable InternalPort.
 | 
			
		||||
	NewInternalPort pkg2.UI2
 | 
			
		||||
	// NewInternalClient relates to state variable InternalClient.
 | 
			
		||||
	NewInternalClient string
 | 
			
		||||
	// NewEnabled relates to state variable PortMappingEnabled.
 | 
			
		||||
	NewEnabled pkg2.Boolean
 | 
			
		||||
	// NewPortMappingDescription relates to state variable PortMappingDescription.
 | 
			
		||||
	NewPortMappingDescription string
 | 
			
		||||
	NewLeaseDuration          pkg2.UI4
 | 
			
		||||
	// NewLeaseDuration relates to state variable PortMappingLeaseDuration.
 | 
			
		||||
	NewLeaseDuration pkg2.UI4
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetStatusInfo provides request and response for the action.
 | 
			
		||||
@@ -545,9 +617,12 @@ type GetStatusInfoRequest struct{}
 | 
			
		||||
 | 
			
		||||
// GetStatusInfoResponse contains the "out" args for the "GetStatusInfo" action.
 | 
			
		||||
type GetStatusInfoResponse struct {
 | 
			
		||||
	NewConnectionStatus    string
 | 
			
		||||
	// NewConnectionStatus relates to state variable ConnectionStatus (3 standard allowed values).
 | 
			
		||||
	NewConnectionStatus string
 | 
			
		||||
	// NewLastConnectionError relates to state variable LastConnectionError (1 standard allowed values).
 | 
			
		||||
	NewLastConnectionError string
 | 
			
		||||
	NewUptime              pkg2.UI4
 | 
			
		||||
	// NewUptime relates to state variable Uptime.
 | 
			
		||||
	NewUptime pkg2.UI4
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetUserName provides request and response for the action.
 | 
			
		||||
@@ -577,6 +652,7 @@ type GetUserNameRequest struct{}
 | 
			
		||||
 | 
			
		||||
// GetUserNameResponse contains the "out" args for the "GetUserName" action.
 | 
			
		||||
type GetUserNameResponse struct {
 | 
			
		||||
	// NewUserName relates to state variable UserName.
 | 
			
		||||
	NewUserName string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -607,6 +683,7 @@ type GetWarnDisconnectDelayRequest struct{}
 | 
			
		||||
 | 
			
		||||
// GetWarnDisconnectDelayResponse contains the "out" args for the "GetWarnDisconnectDelay" action.
 | 
			
		||||
type GetWarnDisconnectDelayResponse struct {
 | 
			
		||||
	// NewWarnDisconnectDelay relates to state variable WarnDisconnectDelay.
 | 
			
		||||
	NewWarnDisconnectDelay pkg2.UI4
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -690,6 +767,7 @@ func (a *SetAutoDisconnectTime) RefResponse() any { return &a.Response }
 | 
			
		||||
 | 
			
		||||
// SetAutoDisconnectTimeRequest contains the "in" args for the "SetAutoDisconnectTime" action.
 | 
			
		||||
type SetAutoDisconnectTimeRequest struct {
 | 
			
		||||
	// NewAutoDisconnectTime relates to state variable AutoDisconnectTime.
 | 
			
		||||
	NewAutoDisconnectTime pkg2.UI4
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -720,6 +798,7 @@ func (a *SetConnectionType) RefResponse() any { return &a.Response }
 | 
			
		||||
 | 
			
		||||
// SetConnectionTypeRequest contains the "in" args for the "SetConnectionType" action.
 | 
			
		||||
type SetConnectionTypeRequest struct {
 | 
			
		||||
	// NewConnectionType relates to state variable ConnectionType.
 | 
			
		||||
	NewConnectionType string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -750,6 +829,7 @@ func (a *SetIdleDisconnectTime) RefResponse() any { return &a.Response }
 | 
			
		||||
 | 
			
		||||
// SetIdleDisconnectTimeRequest contains the "in" args for the "SetIdleDisconnectTime" action.
 | 
			
		||||
type SetIdleDisconnectTimeRequest struct {
 | 
			
		||||
	// NewIdleDisconnectTime relates to state variable IdleDisconnectTime.
 | 
			
		||||
	NewIdleDisconnectTime pkg2.UI4
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -780,6 +860,7 @@ func (a *SetWarnDisconnectDelay) RefResponse() any { return &a.Response }
 | 
			
		||||
 | 
			
		||||
// SetWarnDisconnectDelayRequest contains the "in" args for the "SetWarnDisconnectDelay" action.
 | 
			
		||||
type SetWarnDisconnectDelayRequest struct {
 | 
			
		||||
	// NewWarnDisconnectDelay relates to state variable WarnDisconnectDelay.
 | 
			
		||||
	NewWarnDisconnectDelay pkg2.UI4
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
{{define "service"}}
 | 
			
		||||
{{- $Imps := .Imps -}}
 | 
			
		||||
{{- $Types := .Types -}}
 | 
			
		||||
// Package {{.Manifest.Package}} provides types for the {{quote .Manifest.ServiceType}} service.
 | 
			
		||||
{{- with .Manifest.DocumentURL}}
 | 
			
		||||
//
 | 
			
		||||
@@ -13,15 +14,28 @@ import (
 | 
			
		||||
{{- end}}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
{{range .Types.StringVarDefs}}
 | 
			
		||||
{{- $Name := .Name}}
 | 
			
		||||
{{- with .AllowedValues}}
 | 
			
		||||
// Allowed values for state variable {{$Name}}.
 | 
			
		||||
const (
 | 
			
		||||
{{- range .}}
 | 
			
		||||
  {{$Name}}_{{.}} = "{{.}}"
 | 
			
		||||
{{- end}}
 | 
			
		||||
)
 | 
			
		||||
{{- end}}
 | 
			
		||||
{{- end}}
 | 
			
		||||
 | 
			
		||||
const ServiceType = {{quote .Manifest.ServiceType}}
 | 
			
		||||
{{range .SCPD.SortedActions}}
 | 
			
		||||
{{- template "action" args "Action" . "Imps" $Imps}}
 | 
			
		||||
{{- template "action" args "Action" . "Imps" $Imps "Types" $Types}}
 | 
			
		||||
{{end}}
 | 
			
		||||
{{- end}}
 | 
			
		||||
 | 
			
		||||
{{define "action"}}
 | 
			
		||||
{{- $Imps := .Imps}}
 | 
			
		||||
{{- $soapActionType := index $Imps.TypeByName "SOAPActionInterface"}}
 | 
			
		||||
{{- $Types := .Types}}
 | 
			
		||||
{{- $soapActionType := index $Types.TypeByName "SOAPActionInterface"}}
 | 
			
		||||
// {{.Action.Name}} provides request and response for the action.
 | 
			
		||||
//
 | 
			
		||||
// ServiceType implements {{$soapActionType.AbsRef}}, self-describing the SOAP action.
 | 
			
		||||
@@ -43,18 +57,23 @@ func (a *{{.Action.Name}}) RefResponse() any { return &a.Response }
 | 
			
		||||
 | 
			
		||||
// {{.Action.Name}}Request contains the "in" args for the {{quote .Action.Name}} action.
 | 
			
		||||
type {{.Action.Name}}Request struct
 | 
			
		||||
{{- template "args" args "Args" .Action.InArgs "Imps" $Imps}}
 | 
			
		||||
{{- template "args" args "Args" .Action.InArgs "Imps" $Imps "Types" $Types}}
 | 
			
		||||
 | 
			
		||||
// {{.Action.Name}}Response contains the "out" args for the {{quote .Action.Name}} action.
 | 
			
		||||
type {{.Action.Name}}Response struct
 | 
			
		||||
{{- template "args" args "Args" .Action.OutArgs "Imps" $Imps}}
 | 
			
		||||
{{- template "args" args "Args" .Action.OutArgs "Imps" $Imps "Types" $Types}}
 | 
			
		||||
{{- end}}
 | 
			
		||||
 | 
			
		||||
{{define "args"}}
 | 
			
		||||
{{- $Imps := .Imps -}}
 | 
			
		||||
{{- $Types := .Types -}}
 | 
			
		||||
{ {{- with .Args}}
 | 
			
		||||
{{- range .}}
 | 
			
		||||
{{- $fieldType := index $Imps.TypeByName .RelatedStateVariable.DataType}}
 | 
			
		||||
{{- $fieldType := index $Types.TypeByName .RelatedStateVariable.DataType}}
 | 
			
		||||
  // {{.Name}} relates to state variable {{.RelatedStateVariable.Name}}
 | 
			
		||||
{{- with .RelatedStateVariable.AllowedValues}}
 | 
			
		||||
{{- ""}} ({{len .}} standard allowed values)
 | 
			
		||||
{{- end }}.
 | 
			
		||||
  {{.Name}} {{$fieldType.Ref}}
 | 
			
		||||
{{- end}}
 | 
			
		||||
{{end -}} }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user