Skip to content

add support for Open Connectivity Foundation DCPs #49

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
*.zip
*.sublime-workspace
*.sublime-workspace
*.download
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
gen:
(cd cmd/goupnpdcpgen/; go install); go generate ./...
14 changes: 10 additions & 4 deletions cmd/goupnpdcpgen/codetemplate.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@ package main

import (
"html/template"
"path/filepath"
)

var packageTmpl = template.Must(template.New("package").Parse(`{{$name := .Metadata.Name}}
var templateFuncs = template.FuncMap{
"base": filepath.Base,
}

var packageTmpl = template.Must(template.New("package").Funcs(templateFuncs).Parse(`{{$name := .Metadata.Name}}
// Client for UPnP Device Control Protocol {{.Metadata.OfficialName}}.
// {{if .Metadata.DocURL}}
// This DCP is documented in detail at: {{.Metadata.DocURL}}{{end}}
// {{if .DocURLs}}
// This DCP is documented in detail at: {{range .DocURLs}}
// - {{.}}{{end}}{{end}}
//
// Typically, use one of the New* functions to create clients for services.
package {{$name}}
package {{$name | base}}

// ***********************************************************
// GENERATED FILE - DO NOT EDIT BY HAND. See README.md
Expand Down
36 changes: 24 additions & 12 deletions cmd/goupnpdcpgen/dcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
// DCP collects together information about a UPnP Device Control Protocol.
type DCP struct {
Metadata DCPMetadata
DocURLs []string
DeviceTypes map[string]*URNParts
ServiceTypes map[string]*URNParts
Services []SCPDWithURN
Expand All @@ -29,22 +30,33 @@ func newDCP(metadata DCPMetadata) *DCP {
}
}

func (dcp *DCP) processZipFile(filename string) error {
archive, err := zip.OpenReader(filename)
if err != nil {
return fmt.Errorf("error reading zip file %q: %v", filename, err)
}
defer archive.Close()
for _, deviceFile := range globFiles("*/device/*.xml", archive) {
if err := dcp.processDeviceFile(deviceFile); err != nil {
return err
func (dcp *DCP) Reset() {
dcp.DocURLs = nil
dcp.DeviceTypes = make(map[string]*URNParts)
dcp.ServiceTypes = make(map[string]*URNParts)
}

func (dcp *DCP) processZipFile(archive []*zip.File, devices, services []string) error {
var f int
for _, devicesGlob := range devices {
for _, deviceFile := range globFiles(devicesGlob, archive) {
if err := dcp.processDeviceFile(deviceFile); err != nil {
return err
}
f++
}
}
for _, scpdFile := range globFiles("*/service/*.xml", archive) {
if err := dcp.processSCPDFile(scpdFile); err != nil {
return err
for _, scpdsGlob := range services {
for _, scpdFile := range globFiles(scpdsGlob, archive) {
if err := dcp.processSCPDFile(scpdFile); err != nil {
return err
}
f++
}
}
if f < 1 {
return fmt.Errorf("no sdcp/device found in %q and %q", devices, services)
}
return nil
}

Expand Down
48 changes: 37 additions & 11 deletions cmd/goupnpdcpgen/fileutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,22 @@ import (
"os"
"path"
"regexp"
"strings"
)

func acquireFile(specFilename string, xmlSpecURL string) error {
if f, err := os.Open(specFilename); err != nil {
if !os.IsNotExist(err) {
return err
}
} else {
f.Close()
tmpFilename := specFilename + ".download"
defer os.Remove(tmpFilename)

if fileExists(specFilename) {
return nil
}

tmpFilename := specFilename + ".download"
if err := downloadFile(tmpFilename, xmlSpecURL); err != nil {
return err
}

return os.Rename(tmpFilename, specFilename)
return copyFile(specFilename, tmpFilename)
}

func downloadFile(filename, url string) error {
Expand Down Expand Up @@ -54,10 +52,11 @@ func downloadFile(filename, url string) error {
return w.Close()
}

func globFiles(pattern string, archive *zip.ReadCloser) []*zip.File {
func globFiles(pattern string, archive []*zip.File) []*zip.File {
var files []*zip.File
for _, f := range archive.File {
if matched, err := path.Match(pattern, f.Name); err != nil {
pattern = strings.ToLower(pattern)
for _, f := range archive {
if matched, err := path.Match(pattern, strings.ToLower(f.Name)); err != nil {
// This shouldn't happen - all patterns are hard-coded, errors in them
// are a programming error.
panic(err)
Expand Down Expand Up @@ -93,3 +92,30 @@ func urnPartsFromSCPDFilename(filename string) (*URNParts, error) {
Version: version,
}, nil
}

func copyFile(dst string, src string) error {
f, err := os.Open(src)
if err != nil {
return err
}
return writeFile(dst, f)
}

func writeFile(dst string, r io.ReadCloser) error {
defer r.Close()
f, err := os.Create(dst)
if err != nil {
return err
}
_, err = io.Copy(f, r)
return err
}

func fileExists(p string) bool {
f, err := os.Open(p)
if err != nil {
return !os.IsNotExist(err)
}
f.Close()
return true
}
24 changes: 9 additions & 15 deletions cmd/goupnpdcpgen/goupnpdcpgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,25 +35,19 @@ func run(dcpName, specsDir string, useGofmt bool) error {
return fmt.Errorf("could not create specs-dir %q: %v", specsDir, err)
}

for _, d := range dcpMetadata {
if d.Name != dcpName {
for _, metadata := range dcpMetadata {
if metadata.Name != dcpName {
continue
}
specFilename := filepath.Join(specsDir, d.Name+".zip")
err := acquireFile(specFilename, d.XMLSpecURL)

dcp := newDCP(metadata)

err := metadata.Src.process(".", metadata.Name, dcp)
if err != nil {
return fmt.Errorf("could not acquire spec for %s: %v", d.Name, err)
}
dcp := newDCP(d)
if err := dcp.processZipFile(specFilename); err != nil {
return fmt.Errorf("error processing spec for %s in file %q: %v", d.Name, specFilename, err)
return fmt.Errorf("error processing spec %s: %v", metadata.Name, err)
}
for i, hack := range d.Hacks {
if err := hack(dcp); err != nil {
return fmt.Errorf("error with Hack[%d] for %s: %v", i, d.Name, err)
}
}
if err := dcp.writeCode(d.Name+".go", useGofmt); err != nil {

if err := dcp.writeCode(filepath.Base(metadata.Name)+".go", useGofmt); err != nil {
return fmt.Errorf("error writing package %q: %v", dcp.Metadata.Name, err)
}

Expand Down
132 changes: 95 additions & 37 deletions cmd/goupnpdcpgen/metadata.go
Original file line number Diff line number Diff line change
@@ -1,69 +1,127 @@
package main

import (
"strings"
)

// DCP contains extra metadata to use when generating DCP source files.
type DCPMetadata struct {
Name string // What to name the Go DCP package.
OfficialName string // Official name for the DCP.
DocURL string // Optional - URL for further documentation about the DCP.
XMLSpecURL string // Where to download the XML spec from.
// Any special-case functions to run against the DCP before writing it out.
Hacks []DCPHackFn
Src dcpProvider
}

var dcpMetadata = []DCPMetadata{
{
Name: "internetgateway1",
OfficialName: "Internet Gateway Device v1",
DocURL: "http://upnp.org/specs/gw/UPnP-gw-InternetGatewayDevice-v1-Device.pdf",
XMLSpecURL: "http://upnp.org/specs/gw/UPnP-gw-IGD-TestFiles-20010921.zip",
Hacks: []DCPHackFn{totalBytesHack},
Src: upnpdotorg{
DocURL: "http://upnp.org/specs/gw/UPnP-gw-InternetGatewayDevice-v1-Device.pdf",
XMLSpecURL: "http://upnp.org/specs/gw/UPnP-gw-IGD-TestFiles-20010921.zip",
Hacks: []DCPHackFn{
fixTotalBytes("urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1"),
},
},
},
{
Name: "internetgateway2",
OfficialName: "Internet Gateway Device v2",
DocURL: "http://upnp.org/specs/gw/UPnP-gw-InternetGatewayDevice-v2-Device.pdf",
XMLSpecURL: "http://upnp.org/specs/gw/UPnP-gw-IGD-Testfiles-20110224.zip",
Hacks: []DCPHackFn{
func(dcp *DCP) error {
missingURN := "urn:schemas-upnp-org:service:WANIPv6FirewallControl:1"
if _, ok := dcp.ServiceTypes[missingURN]; ok {
return nil
}
urnParts, err := extractURNParts(missingURN, serviceURNPrefix)
if err != nil {
return err
}
dcp.ServiceTypes[missingURN] = urnParts
return nil
}, totalBytesHack,
Src: upnpdotorg{
DocURL: "http://upnp.org/specs/gw/UPnP-gw-InternetGatewayDevice-v2-Device.pdf",
XMLSpecURL: "http://upnp.org/specs/gw/UPnP-gw-IGD-Testfiles-20110224.zip",
Hacks: []DCPHackFn{
fixMissingURN("urn:schemas-upnp-org:service:WANIPv6FirewallControl:1"),
fixTotalBytes("urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1"),
},
},
},
{
Name: "av1",
OfficialName: "MediaServer v1 and MediaRenderer v1",
DocURL: "http://upnp.org/specs/av/av1/",
XMLSpecURL: "http://upnp.org/specs/av/UPnP-av-TestFiles-20070927.zip",
Src: upnpdotorg{
DocURL: "http://upnp.org/specs/av/av1/",
XMLSpecURL: "http://upnp.org/specs/av/UPnP-av-TestFiles-20070927.zip",
},
},
{
Name: "ocf/internetgateway1",
OfficialName: "Internet Gateway Device v1 - Open Connectivity Foundation",
Src: openconnectivitydotorg{
SpecsURL: ocfSpecsURL,
DocPath: "*/DeviceProtection_1/UPnP-gw-*v1*.pdf",
XMLSpecZipPath: "*/DeviceProtection_1/UPnP-gw-IGD-TestFiles-*.zip",
XMLServicePath: []string{"*/service/*1.xml"},
XMLDevicePath: []string{"*/device/*1.xml"},
Hacks: []DCPHackFn{
fixMissingURN("urn:schemas-upnp-org:service:DeviceProtection:1"),
fixMissingURN("urn:schemas-upnp-org:service:WANIPv6FirewallControl:1"),
fixTotalBytes(),
},
},
},
{
Name: "ocf/internetgateway2",
OfficialName: "Internet Gateway Device v2 - Open Connectivity Foundation",
Src: openconnectivitydotorg{
SpecsURL: ocfSpecsURL,
DocPath: "*/Internet Gateway_2/UPnP-gw-*.pdf",
XMLSpecZipPath: "*/Internet Gateway_2/UPnP-gw-IGD-TestFiles-*.zip",
XMLServicePath: []string{"*/service/*1.xml", "*/service/*2.xml"},
XMLDevicePath: []string{"*/device/*1.xml", "*/device/*2.xml"},
Hacks: []DCPHackFn{
fixMissingURN("urn:schemas-upnp-org:service:DeviceProtection:1"),
fixTotalBytes(),
},
},
},
}

func totalBytesHack(dcp *DCP) error {
for _, service := range dcp.Services {
if service.URN == "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1" {
variables := service.SCPD.StateVariables
for key, variable := range variables {
varName := variable.Name
if varName == "TotalBytesSent" || varName == "TotalBytesReceived" {
// Fix size of total bytes which is by default ui4 or maximum 4 GiB.
variable.DataType.Name = "ui8"
variables[key] = variable
func fixTotalBytes(malformedURNs ...string) func(dcp *DCP) error {
malformedVariables := []string{
"TotalBytesSent",
"TotalBytesReceived",
}
return func(dcp *DCP) error {
for _, service := range dcp.Services {
var process bool
for _, malformedURN := range malformedURNs {
if service.URN == malformedURN {
process = true
break
}
}
if process || len(malformedURNs) < 1 {
variables := service.SCPD.StateVariables
for key, variable := range variables {
varName := variable.Name
for _, malformedVariable := range malformedVariables {
if strings.HasSuffix(varName, malformedVariable) {
// Fix size of total bytes which is by default ui4 or maximum 4 GiB.
variable.DataType.Name = "ui8"
variables[key] = variable
}
}
}
}

break
}
return nil
}
}

return nil
func fixMissingURN(missingURNs ...string) func(dcp *DCP) error {
return func(dcp *DCP) error {
for _, missingURN := range missingURNs {
if _, ok := dcp.ServiceTypes[missingURN]; ok {
continue
}
urnParts, err := extractURNParts(missingURN, serviceURNPrefix)
if err != nil {
return err
}
dcp.ServiceTypes[missingURN] = urnParts
}
return nil
}
}

type DCPHackFn func(*DCP) error
Loading