Compare commits

..

16 Commits

Author SHA1 Message Date
11e9df2080 chore: upgrade dependencies 2024-07-31 19:06:38 +02:00
7a4ff9bdbd merge upstream and upgrade go to 1.21 2023-12-03 12:19:53 +01:00
e42f04b51d Merge remote-tracking branch 'origin/main' into feat/openhome 2023-12-03 12:13:54 +01:00
Andrew Dunham
00783e79ec httpu: add context.Context and related interface
This adds a new interface for httpu that supports a Context, and uses
that context to set a deadline/timeout and also cancel the request if
the context is canceled. Additionally, add a new method to the SSDP
package that takes a ClientInterfaceCtx.

Updates #55

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
2023-08-29 19:12:39 +01:00
jybp
8ca2329ddb Use errors.As in test 2023-05-10 17:07:24 +01:00
jybp
c99b664f99 Fix test case 2023-05-10 17:07:24 +01:00
jybp
dc178c5d44 Fix faultcode+faultstring and get UPnPError details 2023-05-10 17:07:24 +01:00
John Beisley
15a204aa25 chore: gofmt. 2023-03-09 18:23:30 +00:00
John Beisley
e5bb4e5154 Include allowed string values in generated services. 2023-03-09 18:23:18 +00:00
John Beisley
1270e56d5f Fix error naming lint in srvdesc. 2023-03-09 17:08:45 +00:00
John Beisley
51ba21d432 Fix naming of soap/client.HTTPClient. 2023-03-09 16:35:15 +00:00
John Beisley
fe0b17f589 Introduce SOAPError type. 2023-03-09 16:34:46 +00:00
John Beisley
8e5cccc9ac Fix trivial lints in goupnp2srvgen. 2023-03-09 15:42:29 +00:00
John Beisley
d2cb593349 Include URL to upnpresources.zip in flag help. 2023-03-09 15:40:33 +00:00
Steve Hellwege
9278656124 Allow http.Client used in discovery to be modified (typically for security reasons)
[why]
The importing application may have some specific security requrirements that necessitate
a change to the http.Client or http.Transport used when fetching the xml from the UPnp server.
For example, the importing application may want to restrict localhost calls which could be
made by an attack server on the local network.

[how]
Create a global HTTPClient which defaults to http.DefaultClient.  This allows the importing
application to modify this global if it wishes to make changes to the http.Client/http.Transport
used when fetching the xml from the UPnP server.
2023-02-12 17:29:00 +00:00
Kz Ho
62bd5c75d8 Add sync.Pool to reuse packet conn buf object 2023-01-18 08:53:09 +00:00
25 changed files with 693 additions and 151 deletions

4
go.mod
View File

@ -1,5 +1,5 @@
module git.cyrilix.bzh/cyrilix/goupnp module git.cyrilix.bzh/cyrilix/goupnp
go 1.19 go 1.22
require golang.org/x/sync v0.1.0 require golang.org/x/sync v0.7.0

4
go.sum
View File

@ -1,2 +1,2 @@
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=

View File

@ -1,4 +1,6 @@
go 1.18 go 1.22
toolchain go1.22.5
use ( use (
. .

6
go.work.sum Normal file
View File

@ -0,0 +1,6 @@
golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=

View File

@ -85,7 +85,10 @@ func DiscoverDevicesCtx(ctx context.Context, searchTarget string) ([]MaybeRootDe
return nil, err return nil, err
} }
defer hcCleanup() 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 { if err != nil {
return nil, err return nil, err
} }
@ -148,6 +151,10 @@ func DeviceByURL(loc *url.URL) (*RootDevice, error) {
// but should not be changed after requesting clients. // but should not be changed after requesting clients.
var CharsetReaderDefault func(charset string, input io.Reader) (io.Reader, error) 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 { func requestXml(ctx context.Context, url string, defaultSpace string, doc interface{}) error {
ctx, cancel := context.WithTimeout(ctx, 3*time.Second) ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel() defer cancel()
@ -157,7 +164,7 @@ func requestXml(ctx context.Context, url string, defaultSpace string, doc interf
return err return err
} }
resp, err := http.DefaultClient.Do(req) resp, err := HTTPClientDefault.Do(req)
if err != nil { if err != nil {
return err return err
} }

View File

@ -3,6 +3,7 @@ package httpu
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"context"
"errors" "errors"
"fmt" "fmt"
"log" "log"
@ -26,6 +27,27 @@ type ClientInterface interface {
) ([]*http.Response, error) ) ([]*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 // HTTPUClient is a client for dealing with HTTPU (HTTP over UDP). Its typical
// function is for HTTPMU, and particularly SSDP. // function is for HTTPMU, and particularly SSDP.
type HTTPUClient struct { type HTTPUClient struct {
@ -34,6 +56,7 @@ type HTTPUClient struct {
} }
var _ ClientInterface = &HTTPUClient{} var _ ClientInterface = &HTTPUClient{}
var _ ClientInterfaceCtx = &HTTPUClient{}
// NewHTTPUClient creates a new HTTPUClient, opening up a new UDP socket for the // NewHTTPUClient creates a new HTTPUClient, opening up a new UDP socket for the
// purpose. // purpose.
@ -75,6 +98,25 @@ func (httpu *HTTPUClient) Do(
req *http.Request, req *http.Request,
timeout time.Duration, timeout time.Duration,
numSends int, 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) { ) ([]*http.Response, error) {
httpu.connLock.Lock() httpu.connLock.Lock()
defer httpu.connLock.Unlock() defer httpu.connLock.Unlock()
@ -101,9 +143,27 @@ func (httpu *HTTPUClient) Do(
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err = httpu.conn.SetDeadline(time.Now().Add(timeout)); err != nil {
// Handle context deadline/timeout
ctx := req.Context()
deadline, ok := ctx.Deadline()
if ok {
if err = httpu.conn.SetDeadline(deadline); err != nil {
return nil, err 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. // Send request.
for i := 0; i < numSends; i++ { for i := 0; i < numSends; i++ {

View File

@ -49,7 +49,7 @@ func (mc *MultiClient) Do(
} }
func (mc *MultiClient) sendRequests( func (mc *MultiClient) sendRequests(
results chan<-[]*http.Response, results chan<- []*http.Response,
req *http.Request, req *http.Request,
timeout time.Duration, timeout time.Duration,
numSends int, numSends int,
@ -68,3 +68,65 @@ func (mc *MultiClient) sendRequests(
} }
return tasks.Wait() 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()
}

View File

@ -7,6 +7,7 @@ import (
"net" "net"
"net/http" "net/http"
"regexp" "regexp"
"sync"
) )
const ( const (
@ -73,20 +74,25 @@ func (srv *Server) Serve(l net.PacketConn) error {
if srv.MaxMessageBytes != 0 { if srv.MaxMessageBytes != 0 {
maxMessageBytes = srv.MaxMessageBytes maxMessageBytes = srv.MaxMessageBytes
} }
bufPool := &sync.Pool{
New: func() interface{} {
return make([]byte, maxMessageBytes)
},
}
for { for {
buf := make([]byte, maxMessageBytes) buf := bufPool.Get().([]byte)
n, peerAddr, err := l.ReadFrom(buf) n, peerAddr, err := l.ReadFrom(buf)
if err != nil { if err != nil {
return err return err
} }
buf = buf[:n] go func() {
defer bufPool.Put(buf)
go func(buf []byte, peerAddr net.Addr) {
// At least one router's UPnP implementation has added a trailing space // At least one router's UPnP implementation has added a trailing space
// after "HTTP/1.1" - trim it. // 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 { if err != nil {
log.Printf("httpu: Failed to parse request: %v", err) log.Printf("httpu: Failed to parse request: %v", err)
return return
@ -94,7 +100,7 @@ func (srv *Server) Serve(l net.PacketConn) error {
req.RemoteAddr = peerAddr.String() req.RemoteAddr = peerAddr.String()
srv.Handler.ServeMessage(req) srv.Handler.ServeMessage(req)
// No need to call req.Body.Close - underlying reader is bytes.Buffer. // No need to call req.Body.Close - underlying reader is bytes.Buffer.
}(buf, peerAddr) }()
} }
} }

View File

@ -10,14 +10,14 @@ import (
// httpuClient creates a HTTPU client that multiplexes to all multicast-capable // 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 // IPv4 addresses on the host. Returns a function to clean up once the client is
// no longer required. // no longer required.
func httpuClient() (httpu.ClientInterface, func(), error) { func httpuClient() (httpu.ClientInterfaceCtx, func(), error) {
addrs, err := localIPv4MCastAddrs() addrs, err := localIPv4MCastAddrs()
if err != nil { if err != nil {
return nil, nil, ctxError(err, "requesting host IPv4 addresses") return nil, nil, ctxError(err, "requesting host IPv4 addresses")
} }
closers := make([]io.Closer, 0, len(addrs)) 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 { for _, addr := range addrs {
c, err := httpu.NewHTTPUClientAddr(addr) c, err := httpu.NewHTTPUClientAddr(addr)
if err != nil { 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 // localIPv2MCastAddrs returns the set of IPv4 addresses on multicast-able

View File

@ -194,9 +194,13 @@ type soapBody struct {
// SOAPFaultError implements error, and contains SOAP fault information. // SOAPFaultError implements error, and contains SOAP fault information.
type SOAPFaultError struct { type SOAPFaultError struct {
FaultCode string `xml:"faultCode"` FaultCode string `xml:"faultcode"`
FaultString string `xml:"faultString"` FaultString string `xml:"faultstring"`
Detail struct { Detail struct {
UPnPError struct {
Errorcode int `xml:"errorCode"`
ErrorDescription string `xml:"errorDescription"`
} `xml:"UPnPError"`
Raw []byte `xml:",innerxml"` Raw []byte `xml:",innerxml"`
} `xml:"detail"` } `xml:"detail"`
} }

View File

@ -2,10 +2,12 @@ package soap
import ( import (
"bytes" "bytes"
"errors"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"reflect" "reflect"
"strings"
"testing" "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) { func TestEscapeXMLText(t *testing.T) {
t.Parallel() t.Parallel()

View File

@ -35,6 +35,15 @@ type HTTPUClient interface {
) ([]*http.Response, error) ) ([]*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 // SSDPRawSearchCtx performs a fairly raw SSDP search request, and returns the
// unique response(s) that it receives. Each response has the requested // unique response(s) that it receives. Each response has the requested
// searchTarget, a USN, and a valid location. maxWaitSeconds states how long to // searchTarget, a USN, and a valid location. maxWaitSeconds states how long to
@ -49,8 +58,64 @@ func SSDPRawSearchCtx(
maxWaitSeconds int, maxWaitSeconds int,
numSends int, numSends int,
) ([]*http.Response, error) { ) ([]*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 { 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{ req := (&http.Request{
@ -67,11 +132,13 @@ func SSDPRawSearchCtx(
"ST": []string{searchTarget}, "ST": []string{searchTarget},
}, },
}).WithContext(ctx) }).WithContext(ctx)
allResponses, err := httpu.Do(req, time.Duration(maxWaitSeconds)*time.Second+100*time.Millisecond, numSends) return req, nil
if err != nil { }
return nil, err
}
func processSSDPResponses(
searchTarget string,
allResponses []*http.Response,
) ([]*http.Response, error) {
isExactSearch := searchTarget != SSDPAll && searchTarget != UPNPRootDevice isExactSearch := searchTarget != SSDPAll && searchTarget != UPNPRootDevice
seenIDs := make(map[string]bool) seenIDs := make(map[string]bool)

View File

@ -23,7 +23,9 @@ import (
"github.com/huin/goupnp/v2alpha/description/typedesc" "github.com/huin/goupnp/v2alpha/description/typedesc"
"github.com/huin/goupnp/v2alpha/description/xmlsrvdesc" "github.com/huin/goupnp/v2alpha/description/xmlsrvdesc"
"github.com/huin/goupnp/v2alpha/soap" "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 ( var (
@ -31,7 +33,9 @@ var (
outputDir = flag.String("output_dir", "", "Path to directory to write output in.") outputDir = flag.String("output_dir", "", "Path to directory to write output in.")
srvManifests = flag.String("srv_manifests", "", "Path to srvmanifests.toml") srvManifests = flag.String("srv_manifests", "", "Path to srvmanifests.toml")
srvTemplate = flag.String("srv_template", "", "Path to srv.gotemplate.") 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" const soapActionInterface = "SOAPActionInterface"
@ -50,14 +54,14 @@ func run() error {
} }
if *outputDir == "" { 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 { if err := os.MkdirAll(*outputDir, 0); err != nil {
return fmt.Errorf("creating output_dir %q: %w", *outputDir, err) return fmt.Errorf("creating output_dir %q: %w", *outputDir, err)
} }
if *srvManifests == "" { if *srvManifests == "" {
return errors.New("-srv_manifests is a required flag.") return errors.New("-srv_manifests is a required flag")
} }
var manifests DCPSpecManifests var manifests DCPSpecManifests
_, err := toml.DecodeFile(*srvManifests, &manifests) _, err := toml.DecodeFile(*srvManifests, &manifests)
@ -66,7 +70,7 @@ func run() error {
} }
if *srvTemplate == "" { 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{ tmpl, err := template.New(filepath.Base(*srvTemplate)).Funcs(template.FuncMap{
"args": tmplfuncs.Args, "args": tmplfuncs.Args,
@ -77,7 +81,7 @@ func run() error {
} }
if *upnpresourcesZip == "" { 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) f, err := os.Open(*upnpresourcesZip)
if err != nil { if err != nil {
@ -91,7 +95,7 @@ func run() error {
// Use default type map for now. Addtional types could be use instead or // Use default type map for now. Addtional types could be use instead or
// as well as necessary for extended types. // as well as necessary for extended types.
typeMap := types.TypeMap().Clone() typeMap := soaptypes.TypeMap().Clone()
typeMap[soapActionInterface] = typedesc.TypeDesc{ typeMap[soapActionInterface] = typedesc.TypeDesc{
GoType: reflect.TypeOf((*soap.Action)(nil)).Elem(), GoType: reflect.TypeOf((*soap.Action)(nil)).Elem(),
} }
@ -158,7 +162,8 @@ func processService(
return fmt.Errorf("transforming service description: %w", err) return fmt.Errorf("transforming service description: %w", err)
} }
imps, err := accumulateImports(sd, typeMap) imps := newImports()
types, err := accumulateTypes(sd, typeMap, imps)
if err != nil { if err != nil {
return err return err
} }
@ -167,6 +172,7 @@ func processService(
err = tmpl.ExecuteTemplate(buf, "service", tmplArgs{ err = tmpl.ExecuteTemplate(buf, "service", tmplArgs{
Manifest: srvManifest, Manifest: srvManifest,
Imps: imps, Imps: imps,
Types: types,
SCPD: sd, SCPD: sd,
}) })
if err != nil { if err != nil {
@ -219,14 +225,44 @@ type ServiceManifest struct {
type tmplArgs struct { type tmplArgs struct {
Manifest *ServiceManifest Manifest *ServiceManifest
Imps *imports Imps *imports
Types *types
SCPD *srvdesc.SCPD SCPD *srvdesc.SCPD
} }
type imports struct { 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. // Each required import line, ordered by path.
ImportLines []importItem 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 { type typeDesc struct {
@ -239,17 +275,41 @@ type typeDesc struct {
Name string Name string
} }
type stringVarDef struct {
Name string
AllowedValues []string
}
type importItem struct { type importItem struct {
Alias string Alias string
Path string Path string
} }
func accumulateImports(srvDesc *srvdesc.SCPD, typeMap typedesc.TypeMap) (*imports, error) { // accumulateTypes creates type information, and adds any required imports for
typeNames := make(map[string]bool) // them.
typeNames[soapActionInterface] = true 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) { var stringVarDefs []stringVarDef
typeNames[typeName] = true 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 { if err != nil {
return nil, err 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 // Have sorted list of import package paths. Partly for aesthetics of generated code, but also
// to have stable-generated aliases. // to have stable-generated aliases.
paths := make(map[string]bool) paths := make(map[string]struct{})
for typeName := range typeNames { for typeName := range typeNames {
t, ok := typeMap[typeName] t, ok := typeMap[typeName]
if !ok { if !ok {
@ -265,29 +325,17 @@ func accumulateImports(srvDesc *srvdesc.SCPD, typeMap typedesc.TypeMap) (*import
} }
pkgPath := t.GoType.PkgPath() pkgPath := t.GoType.PkgPath()
if pkgPath == "" { if pkgPath == "" {
// Builtin type, ignore. // Builtin type, no import needed.
continue continue
} }
paths[pkgPath] = true paths[pkgPath] = struct{}{}
}
sortedPaths := make([]string, 0, len(paths))
for path := range paths {
sortedPaths = append(sortedPaths, path)
} }
sortedPaths := maps.Keys(paths)
sort.Strings(sortedPaths) sort.Strings(sortedPaths)
// Generate import aliases. // Generate import aliases in deterministic order.
index := 1
aliasByPath := make(map[string]string, len(paths))
importLines := make([]importItem, 0, len(paths))
for _, path := range sortedPaths { for _, path := range sortedPaths {
alias := fmt.Sprintf("pkg%d", index) imps.getAliasForPath(path)
index++
importLines = append(importLines, importItem{
Alias: alias,
Path: path,
})
aliasByPath[path] = alias
} }
// Populate typeByName. // Populate typeByName.
@ -295,28 +343,27 @@ func accumulateImports(srvDesc *srvdesc.SCPD, typeMap typedesc.TypeMap) (*import
for typeName := range typeNames { for typeName := range typeNames {
goType := typeMap[typeName] goType := typeMap[typeName]
pkgPath := goType.GoType.PkgPath() pkgPath := goType.GoType.PkgPath()
alias := aliasByPath[pkgPath]
td := typeDesc{ td := typeDesc{
Name: goType.GoType.Name(), Name: goType.GoType.Name(),
} }
if alias == "" { if pkgPath == "" {
// Builtin type. // Builtin type.
td.AbsRef = td.Name td.AbsRef = td.Name
td.Ref = td.Name td.Ref = td.Name
} else { } else {
td.AbsRef = strconv.Quote(pkgPath) + "." + td.Name td.AbsRef = strconv.Quote(pkgPath) + "." + td.Name
td.Ref = alias + "." + td.Name td.Ref = imps.getAliasForPath(pkgPath) + "." + td.Name
} }
typeByName[typeName] = td typeByName[typeName] = td
} }
return &imports{ return &types{
TypeByName: typeByName, TypeByName: typeByName,
ImportLines: importLines, StringVarDefs: stringVarDefs,
}, nil }, nil
} }
type typeVisitor func(typeName string) type typeVisitor func(sv *srvdesc.StateVariable)
// visitTypesSCPD calls `visitor` with each data type name (e.g. "ui4") referenced // visitTypesSCPD calls `visitor` with each data type name (e.g. "ui4") referenced
// by action arguments.` // by action arguments.`
@ -335,14 +382,14 @@ func visitTypesAction(action *srvdesc.Action, visitor typeVisitor) error {
if err != nil { if err != nil {
return err return err
} }
visitor(sv.DataType) visitor(sv)
} }
for _, arg := range action.OutArgs { for _, arg := range action.OutArgs {
sv, err := arg.RelatedStateVariable() sv, err := arg.RelatedStateVariable()
if err != nil { if err != nil {
return err return err
} }
visitor(sv.DataType) visitor(sv)
} }
return nil return nil
} }

View File

@ -10,9 +10,9 @@ import (
) )
var ( var (
BadDescriptionError = errors.New("bad XML description") ErrBadDescription = errors.New("bad XML description")
MissingDefinitionError = errors.New("missing definition") ErrMissingDefinition = errors.New("missing definition")
UnsupportedDescriptionError = errors.New("unsupported XML description") ErrUnsupportedDescription = errors.New("unsupported XML description")
) )
// SCPD is the top level service 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 { if _, exists := stateVariables[sv.Name]; exists {
return nil, fmt.Errorf("%w: multiple state variables with name %q", return nil, fmt.Errorf("%w: multiple state variables with name %q",
BadDescriptionError, sv.Name) ErrBadDescription, sv.Name)
} }
stateVariables[sv.Name] = sv stateVariables[sv.Name] = sv
} }
@ -49,7 +49,7 @@ func FromXML(xmlDesc *xmlsrvdesc.SCPD) (*SCPD, error) {
} }
if _, exists := actions[action.Name]; exists { if _, exists := actions[action.Name]; exists {
return nil, fmt.Errorf("%w: multiple actions with name %q", return nil, fmt.Errorf("%w: multiple actions with name %q",
BadDescriptionError, action.Name) ErrBadDescription, action.Name)
} }
actions[action.Name] = action actions[action.Name] = action
} }
@ -79,7 +79,7 @@ type Action struct {
// actionFromXML creates an Action from the given XML description. // actionFromXML creates an Action from the given XML description.
func actionFromXML(xmlAction *xmlsrvdesc.Action, scpd *SCPD) (*Action, error) { func actionFromXML(xmlAction *xmlsrvdesc.Action, scpd *SCPD) (*Action, error) {
if xmlAction.Name == "" { if xmlAction.Name == "" {
return nil, fmt.Errorf("%w: empty action name", BadDescriptionError) return nil, fmt.Errorf("%w: empty action name", ErrBadDescription)
} }
action := &Action{ action := &Action{
SCPD: scpd, SCPD: scpd,
@ -99,7 +99,7 @@ func actionFromXML(xmlAction *xmlsrvdesc.Action, scpd *SCPD) (*Action, error) {
outArgs = append(outArgs, arg) outArgs = append(outArgs, arg)
default: default:
return nil, fmt.Errorf("%w: argument %q has invalid direction %q", return nil, fmt.Errorf("%w: argument %q has invalid direction %q",
BadDescriptionError, xmlArg.Name, xmlArg.Direction) ErrBadDescription, xmlArg.Name, xmlArg.Direction)
} }
} }
action.InArgs = inArgs action.InArgs = inArgs
@ -117,10 +117,10 @@ type Argument struct {
// argumentFromXML creates an Argument from the XML description. // argumentFromXML creates an Argument from the XML description.
func argumentFromXML(xmlArg *xmlsrvdesc.Argument, action *Action) (*Argument, error) { func argumentFromXML(xmlArg *xmlsrvdesc.Argument, action *Action) (*Argument, error) {
if xmlArg.Name == "" { if xmlArg.Name == "" {
return nil, fmt.Errorf("%w: empty argument name", BadDescriptionError) return nil, fmt.Errorf("%w: empty argument name", ErrBadDescription)
} }
if xmlArg.RelatedStateVariable == "" { 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{ return &Argument{
Action: action, Action: action,
@ -133,25 +133,32 @@ func (arg *Argument) RelatedStateVariable() (*StateVariable, error) {
if v, ok := arg.Action.SCPD.VariableByName[arg.RelatedStateVariableName]; ok { if v, ok := arg.Action.SCPD.VariableByName[arg.RelatedStateVariableName]; ok {
return v, nil 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. // StateVariable description data.
type StateVariable struct { type StateVariable struct {
Name string Name string
DataType string DataType string
AllowedValues []string
} }
func stateVariableFromXML(xmlSV *xmlsrvdesc.StateVariable) (*StateVariable, error) { func stateVariableFromXML(xmlSV *xmlsrvdesc.StateVariable) (*StateVariable, error) {
if xmlSV.Name == "" { 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 != "" { if xmlSV.DataType.Type != "" {
return nil, fmt.Errorf("%w: unsupported data type %q", 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{ return &StateVariable{
Name: xmlSV.Name, Name: xmlSV.Name,
DataType: xmlSV.DataType.Name, DataType: xmlSV.DataType.Name,
AllowedValues: xmlSV.AllowedValues,
}, nil }, nil
} }

View File

@ -2,6 +2,8 @@ module github.com/huin/goupnp/v2alpha
go 1.18 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 github.com/BurntSushi/toml v1.1.0
require golang.org/x/exp v0.0.0-20230307190834-24139beb5833 // indirect

View File

@ -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/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 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.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 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -8,15 +8,52 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"strings"
"github.com/huin/goupnp/v2alpha/soap" "github.com/huin/goupnp/v2alpha/soap"
"github.com/huin/goupnp/v2alpha/soap/envelope" "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. // SOAPError describes an error from this package, potentially including a
type HttpClient interface { // 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) Do(req *http.Request) (*http.Response, error)
} }
@ -25,22 +62,21 @@ type Option func(*options)
// WithHTTPClient specifies an *http.Client to use instead of // WithHTTPClient specifies an *http.Client to use instead of
// http.DefaultClient. // http.DefaultClient.
func WithHTTPClient(httpClient HttpClient) Option { func WithHTTPClient(httpClient HTTPClient) Option {
return func(o *options) { return func(o *options) {
o.httpClient = httpClient o.httpClient = httpClient
} }
} }
type options struct { type options struct {
httpClient HttpClient httpClient HTTPClient
} }
// Client is a SOAP client, attached to a specific SOAP endpoint. // Client is a SOAP client, attached to a specific SOAP endpoint.
// the zero value is not usable, use NewClient() to create an instance. // the zero value is not usable, use NewClient() to create an instance.
type Client struct { type Client struct {
httpClient HttpClient httpClient HTTPClient
endpointURL string endpointURL string
maxErrorResponseBytes int
} }
// New creates a new SOAP client, which will POST its requests to the // 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() defer resp.Body.Close()
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return fmt.Errorf("SOAP request got HTTP %s (%d)", return &SOAPError{
resp.Status, resp.StatusCode) description: fmt.Sprintf("SOAP request got HTTP %s (%d)",
resp.Status, resp.StatusCode),
}
} }
return ParseResponseAction(resp, actionOut) return ParseResponseAction(resp, actionOut)
@ -110,7 +148,10 @@ func SetRequestAction(
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
err := envelope.Write(buf, actionIn) err := envelope.Write(buf, actionIn)
if err != nil { if err != nil {
return fmt.Errorf("encoding envelope: %w", err) return &SOAPError{
description: "encoding envelope",
cause: err,
}
} }
req.Body = io.NopCloser(buf) req.Body = io.NopCloser(buf)
@ -131,31 +172,39 @@ func ParseResponseAction(
actionOut *envelope.Action, actionOut *envelope.Action,
) error { ) error {
if resp.Body == nil { if resp.Body == nil {
return errors.New("missing response body") return &SOAPError{description: "missing HTTP response body"}
} }
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
if _, err := io.Copy(buf, resp.Body); err != nil { 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 err := envelope.Read(buf, actionOut); err != nil {
if _, ok := err.(*envelope.Fault); ok { if errors.Is(err, envelope.ErrFault) {
// Parsed cleanly, got SOAP fault. // Parsed cleanly, got SOAP fault.
return err return &SOAPError{
description: "SOAP fault",
cause: err,
}
} }
// Parsing problem, provide some information for context. // Parsing problem, provide some information for context.
dispLen := buf.Len() dispLen := buf.Len()
truncMessage := "" truncMessage := ""
if dispLen > 1024 { if dispLen > 1024 {
dispLen = 1024 dispLen = 1024
truncMessage = fmt.Sprintf("first %d bytes: ", dispLen) truncMessage = fmt.Sprintf("first %d bytes (total %d bytes): ", dispLen, buf.Len())
} }
return fmt.Errorf( return &SOAPError{
"parsing response body (%s%q): %w", description: fmt.Sprintf(
"parsing SOAP response from HTTP body (%s%q)",
truncMessage, buf.Bytes()[:dispLen], truncMessage, buf.Bytes()[:dispLen],
err, ),
) cause: err,
}
} }
return nil return nil

View File

@ -321,9 +321,9 @@ var _ SOAPValue = &Fixed14_4{}
// Fixed14_4FromParts creates a Fixed14_4 from components. // Fixed14_4FromParts creates a Fixed14_4 from components.
// Bounds: // Bounds:
// * Both intPart and fracPart must have the same sign. // - Both intPart and fracPart must have the same sign.
// * -1e14 < intPart < 1e14 // - -1e14 < intPart < 1e14
// * -1e4 < fracPart < 1e4 // - -1e4 < fracPart < 1e4
func Fixed14_4FromParts(intPart int64, fracPart int16) (Fixed14_4, error) { func Fixed14_4FromParts(intPart int64, fracPart int16) (Fixed14_4, error) {
var v Fixed14_4 var v Fixed14_4
err := v.SetParts(intPart, fracPart) 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. // SetFromParts sets the value based on the integer component and the fractional component.
// Bounds: // Bounds:
// * Both intPart and fracPart must have the same sign. // - Both intPart and fracPart must have the same sign.
// * -1e14 < intPart < 1e14 // - -1e14 < intPart < 1e14
// * -1e4 < fracPart < 1e4 // - -1e4 < fracPart < 1e4
func (v *Fixed14_4) SetParts(intPart int64, fracPart int16) error { func (v *Fixed14_4) SetParts(intPart int64, fracPart int16) error {
if (intPart < 0) != (fracPart < 0) { if (intPart < 0) != (fracPart < 0) {
return fmt.Errorf("want intPart and fracPart with same sign, got %d and %d", return fmt.Errorf("want intPart and fracPart with same sign, got %d and %d",

View File

@ -34,6 +34,7 @@ func (a *DeleteDNSServer) RefResponse() any { return &a.Response }
// DeleteDNSServerRequest contains the "in" args for the "DeleteDNSServer" action. // DeleteDNSServerRequest contains the "in" args for the "DeleteDNSServer" action.
type DeleteDNSServerRequest struct { type DeleteDNSServerRequest struct {
// NewDNSServers relates to state variable DNSServers.
NewDNSServers string NewDNSServers string
} }
@ -64,6 +65,7 @@ func (a *DeleteIPRouter) RefResponse() any { return &a.Response }
// DeleteIPRouterRequest contains the "in" args for the "DeleteIPRouter" action. // DeleteIPRouterRequest contains the "in" args for the "DeleteIPRouter" action.
type DeleteIPRouterRequest struct { type DeleteIPRouterRequest struct {
// NewIPRouters relates to state variable IPRouters.
NewIPRouters string NewIPRouters string
} }
@ -94,6 +96,7 @@ func (a *DeleteReservedAddress) RefResponse() any { return &a.Response }
// DeleteReservedAddressRequest contains the "in" args for the "DeleteReservedAddress" action. // DeleteReservedAddressRequest contains the "in" args for the "DeleteReservedAddress" action.
type DeleteReservedAddressRequest struct { type DeleteReservedAddressRequest struct {
// NewReservedAddresses relates to state variable ReservedAddresses.
NewReservedAddresses string NewReservedAddresses string
} }
@ -127,7 +130,9 @@ type GetAddressRangeRequest struct{}
// GetAddressRangeResponse contains the "out" args for the "GetAddressRange" action. // GetAddressRangeResponse contains the "out" args for the "GetAddressRange" action.
type GetAddressRangeResponse struct { type GetAddressRangeResponse struct {
// NewMinAddress relates to state variable MinAddress.
NewMinAddress string NewMinAddress string
// NewMaxAddress relates to state variable MaxAddress.
NewMaxAddress string NewMaxAddress string
} }
@ -158,6 +163,7 @@ type GetDHCPRelayRequest struct{}
// GetDHCPRelayResponse contains the "out" args for the "GetDHCPRelay" action. // GetDHCPRelayResponse contains the "out" args for the "GetDHCPRelay" action.
type GetDHCPRelayResponse struct { type GetDHCPRelayResponse struct {
// NewDHCPRelay relates to state variable DHCPRelay.
NewDHCPRelay pkg2.Boolean NewDHCPRelay pkg2.Boolean
} }
@ -188,6 +194,7 @@ type GetDHCPServerConfigurableRequest struct{}
// GetDHCPServerConfigurableResponse contains the "out" args for the "GetDHCPServerConfigurable" action. // GetDHCPServerConfigurableResponse contains the "out" args for the "GetDHCPServerConfigurable" action.
type GetDHCPServerConfigurableResponse struct { type GetDHCPServerConfigurableResponse struct {
// NewDHCPServerConfigurable relates to state variable DHCPServerConfigurable.
NewDHCPServerConfigurable pkg2.Boolean NewDHCPServerConfigurable pkg2.Boolean
} }
@ -218,6 +225,7 @@ type GetDNSServersRequest struct{}
// GetDNSServersResponse contains the "out" args for the "GetDNSServers" action. // GetDNSServersResponse contains the "out" args for the "GetDNSServers" action.
type GetDNSServersResponse struct { type GetDNSServersResponse struct {
// NewDNSServers relates to state variable DNSServers.
NewDNSServers string NewDNSServers string
} }
@ -248,6 +256,7 @@ type GetDomainNameRequest struct{}
// GetDomainNameResponse contains the "out" args for the "GetDomainName" action. // GetDomainNameResponse contains the "out" args for the "GetDomainName" action.
type GetDomainNameResponse struct { type GetDomainNameResponse struct {
// NewDomainName relates to state variable DomainName.
NewDomainName string NewDomainName string
} }
@ -278,6 +287,7 @@ type GetIPRoutersListRequest struct{}
// GetIPRoutersListResponse contains the "out" args for the "GetIPRoutersList" action. // GetIPRoutersListResponse contains the "out" args for the "GetIPRoutersList" action.
type GetIPRoutersListResponse struct { type GetIPRoutersListResponse struct {
// NewIPRouters relates to state variable IPRouters.
NewIPRouters string NewIPRouters string
} }
@ -308,6 +318,7 @@ type GetReservedAddressesRequest struct{}
// GetReservedAddressesResponse contains the "out" args for the "GetReservedAddresses" action. // GetReservedAddressesResponse contains the "out" args for the "GetReservedAddresses" action.
type GetReservedAddressesResponse struct { type GetReservedAddressesResponse struct {
// NewReservedAddresses relates to state variable ReservedAddresses.
NewReservedAddresses string NewReservedAddresses string
} }
@ -338,6 +349,7 @@ type GetSubnetMaskRequest struct{}
// GetSubnetMaskResponse contains the "out" args for the "GetSubnetMask" action. // GetSubnetMaskResponse contains the "out" args for the "GetSubnetMask" action.
type GetSubnetMaskResponse struct { type GetSubnetMaskResponse struct {
// NewSubnetMask relates to state variable SubnetMask.
NewSubnetMask string NewSubnetMask string
} }
@ -365,7 +377,9 @@ func (a *SetAddressRange) RefResponse() any { return &a.Response }
// SetAddressRangeRequest contains the "in" args for the "SetAddressRange" action. // SetAddressRangeRequest contains the "in" args for the "SetAddressRange" action.
type SetAddressRangeRequest struct { type SetAddressRangeRequest struct {
// NewMinAddress relates to state variable MinAddress.
NewMinAddress string NewMinAddress string
// NewMaxAddress relates to state variable MaxAddress.
NewMaxAddress string NewMaxAddress string
} }
@ -396,6 +410,7 @@ func (a *SetDHCPRelay) RefResponse() any { return &a.Response }
// SetDHCPRelayRequest contains the "in" args for the "SetDHCPRelay" action. // SetDHCPRelayRequest contains the "in" args for the "SetDHCPRelay" action.
type SetDHCPRelayRequest struct { type SetDHCPRelayRequest struct {
// NewDHCPRelay relates to state variable DHCPRelay.
NewDHCPRelay pkg2.Boolean NewDHCPRelay pkg2.Boolean
} }
@ -426,6 +441,7 @@ func (a *SetDHCPServerConfigurable) RefResponse() any { return &a.Response }
// SetDHCPServerConfigurableRequest contains the "in" args for the "SetDHCPServerConfigurable" action. // SetDHCPServerConfigurableRequest contains the "in" args for the "SetDHCPServerConfigurable" action.
type SetDHCPServerConfigurableRequest struct { type SetDHCPServerConfigurableRequest struct {
// NewDHCPServerConfigurable relates to state variable DHCPServerConfigurable.
NewDHCPServerConfigurable pkg2.Boolean NewDHCPServerConfigurable pkg2.Boolean
} }
@ -456,6 +472,7 @@ func (a *SetDNSServer) RefResponse() any { return &a.Response }
// SetDNSServerRequest contains the "in" args for the "SetDNSServer" action. // SetDNSServerRequest contains the "in" args for the "SetDNSServer" action.
type SetDNSServerRequest struct { type SetDNSServerRequest struct {
// NewDNSServers relates to state variable DNSServers.
NewDNSServers string NewDNSServers string
} }
@ -486,6 +503,7 @@ func (a *SetDomainName) RefResponse() any { return &a.Response }
// SetDomainNameRequest contains the "in" args for the "SetDomainName" action. // SetDomainNameRequest contains the "in" args for the "SetDomainName" action.
type SetDomainNameRequest struct { type SetDomainNameRequest struct {
// NewDomainName relates to state variable DomainName.
NewDomainName string NewDomainName string
} }
@ -516,6 +534,7 @@ func (a *SetIPRouter) RefResponse() any { return &a.Response }
// SetIPRouterRequest contains the "in" args for the "SetIPRouter" action. // SetIPRouterRequest contains the "in" args for the "SetIPRouter" action.
type SetIPRouterRequest struct { type SetIPRouterRequest struct {
// NewIPRouters relates to state variable IPRouters.
NewIPRouters string NewIPRouters string
} }
@ -546,6 +565,7 @@ func (a *SetReservedAddress) RefResponse() any { return &a.Response }
// SetReservedAddressRequest contains the "in" args for the "SetReservedAddress" action. // SetReservedAddressRequest contains the "in" args for the "SetReservedAddress" action.
type SetReservedAddressRequest struct { type SetReservedAddressRequest struct {
// NewReservedAddresses relates to state variable ReservedAddresses.
NewReservedAddresses string NewReservedAddresses string
} }
@ -576,6 +596,7 @@ func (a *SetSubnetMask) RefResponse() any { return &a.Response }
// SetSubnetMaskRequest contains the "in" args for the "SetSubnetMask" action. // SetSubnetMaskRequest contains the "in" args for the "SetSubnetMask" action.
type SetSubnetMaskRequest struct { type SetSubnetMaskRequest struct {
// NewSubnetMask relates to state variable SubnetMask.
NewSubnetMask string NewSubnetMask string
} }

View File

@ -8,6 +8,35 @@ import (
pkg2 "github.com/huin/goupnp/v2alpha/soap/types" 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" const ServiceType = "urn:schemas-upnp-org:service:WANPPPConnection:1"
// AddPortMapping provides request and response for the action. // AddPortMapping provides request and response for the action.
@ -34,13 +63,21 @@ func (a *AddPortMapping) RefResponse() any { return &a.Response }
// AddPortMappingRequest contains the "in" args for the "AddPortMapping" action. // AddPortMappingRequest contains the "in" args for the "AddPortMapping" action.
type AddPortMappingRequest struct { type AddPortMappingRequest struct {
// NewRemoteHost relates to state variable RemoteHost.
NewRemoteHost string NewRemoteHost string
// NewExternalPort relates to state variable ExternalPort.
NewExternalPort pkg2.UI2 NewExternalPort pkg2.UI2
// NewProtocol relates to state variable PortMappingProtocol (2 standard allowed values).
NewProtocol string NewProtocol string
// NewInternalPort relates to state variable InternalPort.
NewInternalPort pkg2.UI2 NewInternalPort pkg2.UI2
// NewInternalClient relates to state variable InternalClient.
NewInternalClient string NewInternalClient string
// NewEnabled relates to state variable PortMappingEnabled.
NewEnabled pkg2.Boolean NewEnabled pkg2.Boolean
// NewPortMappingDescription relates to state variable PortMappingDescription.
NewPortMappingDescription string NewPortMappingDescription string
// NewLeaseDuration relates to state variable PortMappingLeaseDuration.
NewLeaseDuration pkg2.UI4 NewLeaseDuration pkg2.UI4
} }
@ -71,7 +108,9 @@ func (a *ConfigureConnection) RefResponse() any { return &a.Response }
// ConfigureConnectionRequest contains the "in" args for the "ConfigureConnection" action. // ConfigureConnectionRequest contains the "in" args for the "ConfigureConnection" action.
type ConfigureConnectionRequest struct { type ConfigureConnectionRequest struct {
// NewUserName relates to state variable UserName.
NewUserName string NewUserName string
// NewPassword relates to state variable Password.
NewPassword string NewPassword string
} }
@ -102,8 +141,11 @@ func (a *DeletePortMapping) RefResponse() any { return &a.Response }
// DeletePortMappingRequest contains the "in" args for the "DeletePortMapping" action. // DeletePortMappingRequest contains the "in" args for the "DeletePortMapping" action.
type DeletePortMappingRequest struct { type DeletePortMappingRequest struct {
// NewRemoteHost relates to state variable RemoteHost.
NewRemoteHost string NewRemoteHost string
// NewExternalPort relates to state variable ExternalPort.
NewExternalPort pkg2.UI2 NewExternalPort pkg2.UI2
// NewProtocol relates to state variable PortMappingProtocol (2 standard allowed values).
NewProtocol string NewProtocol string
} }
@ -165,6 +207,7 @@ type GetAutoDisconnectTimeRequest struct{}
// GetAutoDisconnectTimeResponse contains the "out" args for the "GetAutoDisconnectTime" action. // GetAutoDisconnectTimeResponse contains the "out" args for the "GetAutoDisconnectTime" action.
type GetAutoDisconnectTimeResponse struct { type GetAutoDisconnectTimeResponse struct {
// NewAutoDisconnectTime relates to state variable AutoDisconnectTime.
NewAutoDisconnectTime pkg2.UI4 NewAutoDisconnectTime pkg2.UI4
} }
@ -195,7 +238,9 @@ type GetConnectionTypeInfoRequest struct{}
// GetConnectionTypeInfoResponse contains the "out" args for the "GetConnectionTypeInfo" action. // GetConnectionTypeInfoResponse contains the "out" args for the "GetConnectionTypeInfo" action.
type GetConnectionTypeInfoResponse struct { type GetConnectionTypeInfoResponse struct {
// NewConnectionType relates to state variable ConnectionType.
NewConnectionType string NewConnectionType string
// NewPossibleConnectionTypes relates to state variable PossibleConnectionTypes (7 standard allowed values).
NewPossibleConnectionTypes string NewPossibleConnectionTypes string
} }
@ -226,6 +271,7 @@ type GetExternalIPAddressRequest struct{}
// GetExternalIPAddressResponse contains the "out" args for the "GetExternalIPAddress" action. // GetExternalIPAddressResponse contains the "out" args for the "GetExternalIPAddress" action.
type GetExternalIPAddressResponse struct { type GetExternalIPAddressResponse struct {
// NewExternalIPAddress relates to state variable ExternalIPAddress.
NewExternalIPAddress string NewExternalIPAddress string
} }
@ -253,18 +299,27 @@ func (a *GetGenericPortMappingEntry) RefResponse() any { return &a.Response }
// GetGenericPortMappingEntryRequest contains the "in" args for the "GetGenericPortMappingEntry" action. // GetGenericPortMappingEntryRequest contains the "in" args for the "GetGenericPortMappingEntry" action.
type GetGenericPortMappingEntryRequest struct { type GetGenericPortMappingEntryRequest struct {
// NewPortMappingIndex relates to state variable PortMappingNumberOfEntries.
NewPortMappingIndex pkg2.UI2 NewPortMappingIndex pkg2.UI2
} }
// GetGenericPortMappingEntryResponse contains the "out" args for the "GetGenericPortMappingEntry" action. // GetGenericPortMappingEntryResponse contains the "out" args for the "GetGenericPortMappingEntry" action.
type GetGenericPortMappingEntryResponse struct { type GetGenericPortMappingEntryResponse struct {
// NewRemoteHost relates to state variable RemoteHost.
NewRemoteHost string NewRemoteHost string
// NewExternalPort relates to state variable ExternalPort.
NewExternalPort pkg2.UI2 NewExternalPort pkg2.UI2
// NewProtocol relates to state variable PortMappingProtocol (2 standard allowed values).
NewProtocol string NewProtocol string
// NewInternalPort relates to state variable InternalPort.
NewInternalPort pkg2.UI2 NewInternalPort pkg2.UI2
// NewInternalClient relates to state variable InternalClient.
NewInternalClient string NewInternalClient string
// NewEnabled relates to state variable PortMappingEnabled.
NewEnabled pkg2.Boolean NewEnabled pkg2.Boolean
// NewPortMappingDescription relates to state variable PortMappingDescription.
NewPortMappingDescription string NewPortMappingDescription string
// NewLeaseDuration relates to state variable PortMappingLeaseDuration.
NewLeaseDuration pkg2.UI4 NewLeaseDuration pkg2.UI4
} }
@ -295,6 +350,7 @@ type GetIdleDisconnectTimeRequest struct{}
// GetIdleDisconnectTimeResponse contains the "out" args for the "GetIdleDisconnectTime" action. // GetIdleDisconnectTimeResponse contains the "out" args for the "GetIdleDisconnectTime" action.
type GetIdleDisconnectTimeResponse struct { type GetIdleDisconnectTimeResponse struct {
// NewIdleDisconnectTime relates to state variable IdleDisconnectTime.
NewIdleDisconnectTime pkg2.UI4 NewIdleDisconnectTime pkg2.UI4
} }
@ -325,7 +381,9 @@ type GetLinkLayerMaxBitRatesRequest struct{}
// GetLinkLayerMaxBitRatesResponse contains the "out" args for the "GetLinkLayerMaxBitRates" action. // GetLinkLayerMaxBitRatesResponse contains the "out" args for the "GetLinkLayerMaxBitRates" action.
type GetLinkLayerMaxBitRatesResponse struct { type GetLinkLayerMaxBitRatesResponse struct {
// NewUpstreamMaxBitRate relates to state variable UpstreamMaxBitRate.
NewUpstreamMaxBitRate pkg2.UI4 NewUpstreamMaxBitRate pkg2.UI4
// NewDownstreamMaxBitRate relates to state variable DownstreamMaxBitRate.
NewDownstreamMaxBitRate pkg2.UI4 NewDownstreamMaxBitRate pkg2.UI4
} }
@ -356,7 +414,9 @@ type GetNATRSIPStatusRequest struct{}
// GetNATRSIPStatusResponse contains the "out" args for the "GetNATRSIPStatus" action. // GetNATRSIPStatusResponse contains the "out" args for the "GetNATRSIPStatus" action.
type GetNATRSIPStatusResponse struct { type GetNATRSIPStatusResponse struct {
// NewRSIPAvailable relates to state variable RSIPAvailable.
NewRSIPAvailable pkg2.Boolean NewRSIPAvailable pkg2.Boolean
// NewNATEnabled relates to state variable NATEnabled.
NewNATEnabled pkg2.Boolean NewNATEnabled pkg2.Boolean
} }
@ -387,6 +447,7 @@ type GetPPPAuthenticationProtocolRequest struct{}
// GetPPPAuthenticationProtocolResponse contains the "out" args for the "GetPPPAuthenticationProtocol" action. // GetPPPAuthenticationProtocolResponse contains the "out" args for the "GetPPPAuthenticationProtocol" action.
type GetPPPAuthenticationProtocolResponse struct { type GetPPPAuthenticationProtocolResponse struct {
// NewPPPAuthenticationProtocol relates to state variable PPPAuthenticationProtocol.
NewPPPAuthenticationProtocol string NewPPPAuthenticationProtocol string
} }
@ -417,6 +478,7 @@ type GetPPPCompressionProtocolRequest struct{}
// GetPPPCompressionProtocolResponse contains the "out" args for the "GetPPPCompressionProtocol" action. // GetPPPCompressionProtocolResponse contains the "out" args for the "GetPPPCompressionProtocol" action.
type GetPPPCompressionProtocolResponse struct { type GetPPPCompressionProtocolResponse struct {
// NewPPPCompressionProtocol relates to state variable PPPCompressionProtocol.
NewPPPCompressionProtocol string NewPPPCompressionProtocol string
} }
@ -447,6 +509,7 @@ type GetPPPEncryptionProtocolRequest struct{}
// GetPPPEncryptionProtocolResponse contains the "out" args for the "GetPPPEncryptionProtocol" action. // GetPPPEncryptionProtocolResponse contains the "out" args for the "GetPPPEncryptionProtocol" action.
type GetPPPEncryptionProtocolResponse struct { type GetPPPEncryptionProtocolResponse struct {
// NewPPPEncryptionProtocol relates to state variable PPPEncryptionProtocol.
NewPPPEncryptionProtocol string NewPPPEncryptionProtocol string
} }
@ -477,6 +540,7 @@ type GetPasswordRequest struct{}
// GetPasswordResponse contains the "out" args for the "GetPassword" action. // GetPasswordResponse contains the "out" args for the "GetPassword" action.
type GetPasswordResponse struct { type GetPasswordResponse struct {
// NewPassword relates to state variable Password.
NewPassword string NewPassword string
} }
@ -504,17 +568,25 @@ func (a *GetSpecificPortMappingEntry) RefResponse() any { return &a.Response }
// GetSpecificPortMappingEntryRequest contains the "in" args for the "GetSpecificPortMappingEntry" action. // GetSpecificPortMappingEntryRequest contains the "in" args for the "GetSpecificPortMappingEntry" action.
type GetSpecificPortMappingEntryRequest struct { type GetSpecificPortMappingEntryRequest struct {
// NewRemoteHost relates to state variable RemoteHost.
NewRemoteHost string NewRemoteHost string
// NewExternalPort relates to state variable ExternalPort.
NewExternalPort pkg2.UI2 NewExternalPort pkg2.UI2
// NewProtocol relates to state variable PortMappingProtocol (2 standard allowed values).
NewProtocol string NewProtocol string
} }
// GetSpecificPortMappingEntryResponse contains the "out" args for the "GetSpecificPortMappingEntry" action. // GetSpecificPortMappingEntryResponse contains the "out" args for the "GetSpecificPortMappingEntry" action.
type GetSpecificPortMappingEntryResponse struct { type GetSpecificPortMappingEntryResponse struct {
// NewInternalPort relates to state variable InternalPort.
NewInternalPort pkg2.UI2 NewInternalPort pkg2.UI2
// NewInternalClient relates to state variable InternalClient.
NewInternalClient string NewInternalClient string
// NewEnabled relates to state variable PortMappingEnabled.
NewEnabled pkg2.Boolean NewEnabled pkg2.Boolean
// NewPortMappingDescription relates to state variable PortMappingDescription.
NewPortMappingDescription string NewPortMappingDescription string
// NewLeaseDuration relates to state variable PortMappingLeaseDuration.
NewLeaseDuration pkg2.UI4 NewLeaseDuration pkg2.UI4
} }
@ -545,8 +617,11 @@ type GetStatusInfoRequest struct{}
// GetStatusInfoResponse contains the "out" args for the "GetStatusInfo" action. // GetStatusInfoResponse contains the "out" args for the "GetStatusInfo" action.
type GetStatusInfoResponse struct { type GetStatusInfoResponse struct {
// NewConnectionStatus relates to state variable ConnectionStatus (3 standard allowed values).
NewConnectionStatus string NewConnectionStatus string
// NewLastConnectionError relates to state variable LastConnectionError (1 standard allowed values).
NewLastConnectionError string NewLastConnectionError string
// NewUptime relates to state variable Uptime.
NewUptime pkg2.UI4 NewUptime pkg2.UI4
} }
@ -577,6 +652,7 @@ type GetUserNameRequest struct{}
// GetUserNameResponse contains the "out" args for the "GetUserName" action. // GetUserNameResponse contains the "out" args for the "GetUserName" action.
type GetUserNameResponse struct { type GetUserNameResponse struct {
// NewUserName relates to state variable UserName.
NewUserName string NewUserName string
} }
@ -607,6 +683,7 @@ type GetWarnDisconnectDelayRequest struct{}
// GetWarnDisconnectDelayResponse contains the "out" args for the "GetWarnDisconnectDelay" action. // GetWarnDisconnectDelayResponse contains the "out" args for the "GetWarnDisconnectDelay" action.
type GetWarnDisconnectDelayResponse struct { type GetWarnDisconnectDelayResponse struct {
// NewWarnDisconnectDelay relates to state variable WarnDisconnectDelay.
NewWarnDisconnectDelay pkg2.UI4 NewWarnDisconnectDelay pkg2.UI4
} }
@ -690,6 +767,7 @@ func (a *SetAutoDisconnectTime) RefResponse() any { return &a.Response }
// SetAutoDisconnectTimeRequest contains the "in" args for the "SetAutoDisconnectTime" action. // SetAutoDisconnectTimeRequest contains the "in" args for the "SetAutoDisconnectTime" action.
type SetAutoDisconnectTimeRequest struct { type SetAutoDisconnectTimeRequest struct {
// NewAutoDisconnectTime relates to state variable AutoDisconnectTime.
NewAutoDisconnectTime pkg2.UI4 NewAutoDisconnectTime pkg2.UI4
} }
@ -720,6 +798,7 @@ func (a *SetConnectionType) RefResponse() any { return &a.Response }
// SetConnectionTypeRequest contains the "in" args for the "SetConnectionType" action. // SetConnectionTypeRequest contains the "in" args for the "SetConnectionType" action.
type SetConnectionTypeRequest struct { type SetConnectionTypeRequest struct {
// NewConnectionType relates to state variable ConnectionType.
NewConnectionType string NewConnectionType string
} }
@ -750,6 +829,7 @@ func (a *SetIdleDisconnectTime) RefResponse() any { return &a.Response }
// SetIdleDisconnectTimeRequest contains the "in" args for the "SetIdleDisconnectTime" action. // SetIdleDisconnectTimeRequest contains the "in" args for the "SetIdleDisconnectTime" action.
type SetIdleDisconnectTimeRequest struct { type SetIdleDisconnectTimeRequest struct {
// NewIdleDisconnectTime relates to state variable IdleDisconnectTime.
NewIdleDisconnectTime pkg2.UI4 NewIdleDisconnectTime pkg2.UI4
} }
@ -780,6 +860,7 @@ func (a *SetWarnDisconnectDelay) RefResponse() any { return &a.Response }
// SetWarnDisconnectDelayRequest contains the "in" args for the "SetWarnDisconnectDelay" action. // SetWarnDisconnectDelayRequest contains the "in" args for the "SetWarnDisconnectDelay" action.
type SetWarnDisconnectDelayRequest struct { type SetWarnDisconnectDelayRequest struct {
// NewWarnDisconnectDelay relates to state variable WarnDisconnectDelay.
NewWarnDisconnectDelay pkg2.UI4 NewWarnDisconnectDelay pkg2.UI4
} }

View File

@ -1,5 +1,6 @@
{{define "service"}} {{define "service"}}
{{- $Imps := .Imps -}} {{- $Imps := .Imps -}}
{{- $Types := .Types -}}
// Package {{.Manifest.Package}} provides types for the {{quote .Manifest.ServiceType}} service. // Package {{.Manifest.Package}} provides types for the {{quote .Manifest.ServiceType}} service.
{{- with .Manifest.DocumentURL}} {{- with .Manifest.DocumentURL}}
// //
@ -13,15 +14,28 @@ import (
{{- end}} {{- 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}} const ServiceType = {{quote .Manifest.ServiceType}}
{{range .SCPD.SortedActions}} {{range .SCPD.SortedActions}}
{{- template "action" args "Action" . "Imps" $Imps}} {{- template "action" args "Action" . "Imps" $Imps "Types" $Types}}
{{end}} {{end}}
{{- end}} {{- end}}
{{define "action"}} {{define "action"}}
{{- $Imps := .Imps}} {{- $Imps := .Imps}}
{{- $soapActionType := index $Imps.TypeByName "SOAPActionInterface"}} {{- $Types := .Types}}
{{- $soapActionType := index $Types.TypeByName "SOAPActionInterface"}}
// {{.Action.Name}} provides request and response for the action. // {{.Action.Name}} provides request and response for the action.
// //
// ServiceType implements {{$soapActionType.AbsRef}}, self-describing the SOAP 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. // {{.Action.Name}}Request contains the "in" args for the {{quote .Action.Name}} action.
type {{.Action.Name}}Request struct 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. // {{.Action.Name}}Response contains the "out" args for the {{quote .Action.Name}} action.
type {{.Action.Name}}Response struct type {{.Action.Name}}Response struct
{{- template "args" args "Args" .Action.OutArgs "Imps" $Imps}} {{- template "args" args "Args" .Action.OutArgs "Imps" $Imps "Types" $Types}}
{{- end}} {{- end}}
{{define "args"}} {{define "args"}}
{{- $Imps := .Imps -}} {{- $Imps := .Imps -}}
{{- $Types := .Types -}}
{ {{- with .Args}} { {{- with .Args}}
{{- range .}} {{- 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}} {{.Name}} {{$fieldType.Ref}}
{{- end}} {{- end}}
{{end -}} } {{end -}} }

View File

@ -20,7 +20,7 @@ type token struct{}
// A zero Group is valid, has no limit on the number of active goroutines, // A zero Group is valid, has no limit on the number of active goroutines,
// and does not cancel on error. // and does not cancel on error.
type Group struct { type Group struct {
cancel func() cancel func(error)
wg sync.WaitGroup wg sync.WaitGroup
@ -43,7 +43,7 @@ func (g *Group) done() {
// returns a non-nil error or the first time Wait returns, whichever occurs // returns a non-nil error or the first time Wait returns, whichever occurs
// first. // first.
func WithContext(ctx context.Context) (*Group, context.Context) { func WithContext(ctx context.Context) (*Group, context.Context) {
ctx, cancel := context.WithCancel(ctx) ctx, cancel := withCancelCause(ctx)
return &Group{cancel: cancel}, ctx return &Group{cancel: cancel}, ctx
} }
@ -52,7 +52,7 @@ func WithContext(ctx context.Context) (*Group, context.Context) {
func (g *Group) Wait() error { func (g *Group) Wait() error {
g.wg.Wait() g.wg.Wait()
if g.cancel != nil { if g.cancel != nil {
g.cancel() g.cancel(g.err)
} }
return g.err return g.err
} }
@ -76,7 +76,7 @@ func (g *Group) Go(f func() error) {
g.errOnce.Do(func() { g.errOnce.Do(func() {
g.err = err g.err = err
if g.cancel != nil { if g.cancel != nil {
g.cancel() g.cancel(g.err)
} }
}) })
} }
@ -105,7 +105,7 @@ func (g *Group) TryGo(f func() error) bool {
g.errOnce.Do(func() { g.errOnce.Do(func() {
g.err = err g.err = err
if g.cancel != nil { if g.cancel != nil {
g.cancel() g.cancel(g.err)
} }
}) })
} }

13
vendor/golang.org/x/sync/errgroup/go120.go generated vendored Normal file
View File

@ -0,0 +1,13 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.20
package errgroup
import "context"
func withCancelCause(parent context.Context) (context.Context, func(error)) {
return context.WithCancelCause(parent)
}

14
vendor/golang.org/x/sync/errgroup/pre_go120.go generated vendored Normal file
View File

@ -0,0 +1,14 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !go1.20
package errgroup
import "context"
func withCancelCause(parent context.Context) (context.Context, func(error)) {
ctx, cancel := context.WithCancel(parent)
return ctx, func(error) { cancel() }
}

4
vendor/modules.txt vendored
View File

@ -1,3 +1,3 @@
# golang.org/x/sync v0.1.0 # golang.org/x/sync v0.5.0
## explicit ## explicit; go 1.18
golang.org/x/sync/errgroup golang.org/x/sync/errgroup