Skip to content

Commit

Permalink
add support for Open Connectivity Foundation DCPs
Browse files Browse the repository at this point in the history
  • Loading branch information
mh-cbon committed Mar 22, 2022
1 parent ca81a64 commit 17711ba
Show file tree
Hide file tree
Showing 14 changed files with 6,425 additions and 84 deletions.
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
110 changes: 74 additions & 36 deletions cmd/goupnpdcpgen/metadata.go
Original file line number Diff line number Diff line change
@@ -1,69 +1,107 @@
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{totalBytesHack},
},
},
{
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"),
totalBytesHack,
},
},
},
{
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"),
totalBytesHack,
},
},
},
{
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"),
totalBytesHack,
},
},
},
}

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
}
variables := service.SCPD.StateVariables
for key, variable := range variables {
varName := variable.Name
if strings.HasSuffix(varName, "TotalBytesSent") ||
strings.HasSuffix(varName, "TotalBytesReceived") {
// Fix size of total bytes which is by default ui4 or maximum 4 GiB.
variable.DataType.Name = "ui8"
variables[key] = variable
}

break
}
}

return nil
}

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

type DCPHackFn func(*DCP) error
Loading

0 comments on commit 17711ba

Please sign in to comment.