mirror of
https://github.com/ipfs/kubo.git
synced 2026-03-04 15:58:13 +08:00
commit
ae5e244354
14
Godeps/Godeps.json
generated
14
Godeps/Godeps.json
generated
@ -92,6 +92,10 @@
|
||||
"ImportPath": "github.com/facebookgo/stackerr",
|
||||
"Rev": "060fbf9364c89acd41bf710e9e92915a90e7a5b5"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/fd/go-nat",
|
||||
"Rev": "50e7633d5f27d81490026a13e5b92d2e42d8c6bb"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/gorilla/context",
|
||||
"Rev": "14f550f51af52180c2eefed15e5fd18d63c0a64a"
|
||||
@ -117,10 +121,18 @@
|
||||
"Comment": "v0.9.0-11-g6b1ef89",
|
||||
"Rev": "6b1ef893dc11e0447abda6da20a5203481878dda"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/huin/goupnp",
|
||||
"Rev": "223008361153d7d434c1f0ac990cd3fcae6931f5"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/inconshreveable/go-update",
|
||||
"Rev": "221d034a558b4c21b0624b2a450c076913854a57"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/jackpal/go-nat-pmp",
|
||||
"Rev": "a45aa3d54aef73b504e15eb71bea0e5565b5e6e1"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/jbenet/go-base58",
|
||||
"Rev": "568a28d73fd97651d3442392036a658b6976eed5"
|
||||
@ -185,7 +197,7 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/jbenet/goprocess",
|
||||
"Rev": "c5455a611a9e0cebac87e7ae421c3273c3224000"
|
||||
"Rev": "060ad098994e6adac22f0bd889eb756d4f5ad35b"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/kr/binarydist",
|
||||
|
||||
191
Godeps/_workspace/src/github.com/fd/go-nat/LICENSE
generated
vendored
Normal file
191
Godeps/_workspace/src/github.com/fd/go-nat/LICENSE
generated
vendored
Normal file
@ -0,0 +1,191 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and
|
||||
distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
||||
owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities
|
||||
that control, are controlled by, or are under common control with that entity.
|
||||
For the purposes of this definition, "control" means (i) the power, direct or
|
||||
indirect, to cause the direction or management of such entity, whether by
|
||||
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
||||
permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including
|
||||
but not limited to software source code, documentation source, and configuration
|
||||
files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or
|
||||
translation of a Source form, including but not limited to compiled object code,
|
||||
generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made
|
||||
available under the License, as indicated by a copyright notice that is included
|
||||
in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that
|
||||
is based on (or derived from) the Work and for which the editorial revisions,
|
||||
annotations, elaborations, or other modifications represent, as a whole, an
|
||||
original work of authorship. For the purposes of this License, Derivative Works
|
||||
shall not include works that remain separable from, or merely link (or bind by
|
||||
name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version
|
||||
of the Work and any modifications or additions to that Work or Derivative Works
|
||||
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
||||
by the copyright owner or by an individual or Legal Entity authorized to submit
|
||||
on behalf of the copyright owner. For the purposes of this definition,
|
||||
"submitted" means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems, and
|
||||
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
||||
the purpose of discussing and improving the Work, but excluding communication
|
||||
that is conspicuously marked or otherwise designated in writing by the copyright
|
||||
owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
|
||||
of whom a Contribution has been received by Licensor and subsequently
|
||||
incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the Work and such
|
||||
Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable (except as stated in this section) patent license to make, have
|
||||
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
|
||||
such license applies only to those patent claims licensable by such Contributor
|
||||
that are necessarily infringed by their Contribution(s) alone or by combination
|
||||
of their Contribution(s) with the Work to which such Contribution(s) was
|
||||
submitted. If You institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
||||
Contribution incorporated within the Work constitutes direct or contributory
|
||||
patent infringement, then any patent licenses granted to You under this License
|
||||
for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution.
|
||||
|
||||
You may reproduce and distribute copies of the Work or Derivative Works thereof
|
||||
in any medium, with or without modifications, and in Source or Object form,
|
||||
provided that You meet the following conditions:
|
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of
|
||||
this License; and
|
||||
You must cause any modified files to carry prominent notices stating that You
|
||||
changed the files; and
|
||||
You must retain, in the Source form of any Derivative Works that You distribute,
|
||||
all copyright, patent, trademark, and attribution notices from the Source form
|
||||
of the Work, excluding those notices that do not pertain to any part of the
|
||||
Derivative Works; and
|
||||
If the Work includes a "NOTICE" text file as part of its distribution, then any
|
||||
Derivative Works that You distribute must include a readable copy of the
|
||||
attribution notices contained within such NOTICE file, excluding those notices
|
||||
that do not pertain to any part of the Derivative Works, in at least one of the
|
||||
following places: within a NOTICE text file distributed as part of the
|
||||
Derivative Works; within the Source form or documentation, if provided along
|
||||
with the Derivative Works; or, within a display generated by the Derivative
|
||||
Works, if and wherever such third-party notices normally appear. The contents of
|
||||
the NOTICE file are for informational purposes only and do not modify the
|
||||
License. You may add Your own attribution notices within Derivative Works that
|
||||
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
||||
provided that such additional attribution notices cannot be construed as
|
||||
modifying the License.
|
||||
You may add Your own copyright statement to Your modifications and may provide
|
||||
additional or different license terms and conditions for use, reproduction, or
|
||||
distribution of Your modifications, or for any such Derivative Works as a whole,
|
||||
provided Your use, reproduction, and distribution of the Work otherwise complies
|
||||
with the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions.
|
||||
|
||||
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
||||
for inclusion in the Work by You to the Licensor shall be under the terms and
|
||||
conditions of this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
||||
any separate license agreement you may have executed with Licensor regarding
|
||||
such Contributions.
|
||||
|
||||
6. Trademarks.
|
||||
|
||||
This License does not grant permission to use the trade names, trademarks,
|
||||
service marks, or product names of the Licensor, except as required for
|
||||
reasonable and customary use in describing the origin of the Work and
|
||||
reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, Licensor provides the
|
||||
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
||||
including, without limitation, any warranties or conditions of TITLE,
|
||||
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
|
||||
solely responsible for determining the appropriateness of using or
|
||||
redistributing the Work and assume any risks associated with Your exercise of
|
||||
permissions under this License.
|
||||
|
||||
8. Limitation of Liability.
|
||||
|
||||
In no event and under no legal theory, whether in tort (including negligence),
|
||||
contract, or otherwise, unless required by applicable law (such as deliberate
|
||||
and grossly negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special, incidental,
|
||||
or consequential damages of any character arising as a result of this License or
|
||||
out of the use or inability to use the Work (including but not limited to
|
||||
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
||||
any and all other commercial damages or losses), even if such Contributor has
|
||||
been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability.
|
||||
|
||||
While redistributing the Work or Derivative Works thereof, You may choose to
|
||||
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
||||
other liability obligations and/or rights consistent with this License. However,
|
||||
in accepting such obligations, You may act only on Your own behalf and on Your
|
||||
sole responsibility, not on behalf of any other Contributor, and only if You
|
||||
agree to indemnify, defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason of your
|
||||
accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work
|
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate
|
||||
notice, with the fields enclosed by brackets "[]" replaced with your own
|
||||
identifying information. (Don't include the brackets!) The text should be
|
||||
enclosed in the appropriate comment syntax for the file format. We also
|
||||
recommend that a file or class name and description of purpose be included on
|
||||
the same "printed page" as the copyright notice for easier identification within
|
||||
third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
3
Godeps/_workspace/src/github.com/fd/go-nat/README.md
generated
vendored
Normal file
3
Godeps/_workspace/src/github.com/fd/go-nat/README.md
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# go-nat
|
||||
|
||||
[](https://godoc.org/github.com/fd/go-nat) [](https://sourcegraph.com/github.com/fd/go-nat)
|
||||
54
Godeps/_workspace/src/github.com/fd/go-nat/nat.go
generated
vendored
Normal file
54
Godeps/_workspace/src/github.com/fd/go-nat/nat.go
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
// Package nat implements NAT handling facilities
|
||||
package nat
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ErrNoExternalAddress = errors.New("no external address")
|
||||
var ErrNoInternalAddress = errors.New("no internal address")
|
||||
var ErrNoNATFound = errors.New("no NAT found")
|
||||
|
||||
// protocol is either "udp" or "tcp"
|
||||
type NAT interface {
|
||||
// Type returns the kind of NAT port mapping service that is used
|
||||
Type() string
|
||||
|
||||
// GetDeviceAddress returns the internal address of the gateway device.
|
||||
GetDeviceAddress() (addr net.IP, err error)
|
||||
|
||||
// GetExternalAddress returns the external address of the gateway device.
|
||||
GetExternalAddress() (addr net.IP, err error)
|
||||
|
||||
// GetInternalAddress returns the address of the local host.
|
||||
GetInternalAddress() (addr net.IP, err error)
|
||||
|
||||
// AddPortMapping maps a port on the local host to an external port.
|
||||
AddPortMapping(protocol string, internalPort int, description string, timeout time.Duration) (mappedExternalPort int, err error)
|
||||
|
||||
// DeletePortMapping removes a port mapping.
|
||||
DeletePortMapping(protocol string, internalPort int) (err error)
|
||||
}
|
||||
|
||||
// DiscoverGateway attempts to find a gateway device.
|
||||
func DiscoverGateway() (NAT, error) {
|
||||
select {
|
||||
case nat := <-discoverUPNP_IG1():
|
||||
return nat, nil
|
||||
case nat := <-discoverUPNP_IG2():
|
||||
return nat, nil
|
||||
case nat := <-discoverNATPMP():
|
||||
return nat, nil
|
||||
case <-time.After(10 * time.Second):
|
||||
return nil, ErrNoNATFound
|
||||
}
|
||||
}
|
||||
|
||||
func randomPort() int {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
return rand.Intn(math.MaxUint16-10000) + 10000
|
||||
}
|
||||
178
Godeps/_workspace/src/github.com/fd/go-nat/natpmp.go
generated
vendored
Normal file
178
Godeps/_workspace/src/github.com/fd/go-nat/natpmp.go
generated
vendored
Normal file
@ -0,0 +1,178 @@
|
||||
package nat
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jackpal/go-nat-pmp"
|
||||
)
|
||||
|
||||
var (
|
||||
_ NAT = (*natpmp_NAT)(nil)
|
||||
)
|
||||
|
||||
func natpmp_PotentialGateways() ([]net.IP, error) {
|
||||
_, ipNet_10, err := net.ParseCIDR("10.0.0.0/8")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, ipNet_172_16, err := net.ParseCIDR("172.16.0.0/12")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, ipNet_192_168, err := net.ParseCIDR("192.168.0.0/16")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ips []net.IP
|
||||
|
||||
for _, iface := range ifaces {
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
switch x := addr.(type) {
|
||||
case *net.IPNet:
|
||||
var ipNet *net.IPNet
|
||||
if ipNet_10.Contains(x.IP) {
|
||||
ipNet = ipNet_10
|
||||
} else if ipNet_172_16.Contains(x.IP) {
|
||||
ipNet = ipNet_172_16
|
||||
} else if ipNet_192_168.Contains(x.IP) {
|
||||
ipNet = ipNet_192_168
|
||||
}
|
||||
|
||||
if ipNet != nil {
|
||||
ip := x.IP.Mask(x.Mask)
|
||||
ip = ip.To4()
|
||||
if ip != nil {
|
||||
ip[3] = ip[3] | 0x01
|
||||
ips = append(ips, ip)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(ips) == 0 {
|
||||
return nil, ErrNoNATFound
|
||||
}
|
||||
|
||||
return ips, nil
|
||||
}
|
||||
|
||||
func discoverNATPMP() <-chan NAT {
|
||||
ips, err := natpmp_PotentialGateways()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
res := make(chan NAT, len(ips))
|
||||
|
||||
for _, ip := range ips {
|
||||
go discoverNATPMPWithAddr(res, ip)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func discoverNATPMPWithAddr(c chan NAT, ip net.IP) {
|
||||
client := natpmp.NewClient(ip)
|
||||
_, err := client.GetExternalAddress()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c <- &natpmp_NAT{client, ip, make(map[int]int)}
|
||||
}
|
||||
|
||||
type natpmp_NAT struct {
|
||||
c *natpmp.Client
|
||||
gateway net.IP
|
||||
ports map[int]int
|
||||
}
|
||||
|
||||
func (n *natpmp_NAT) GetDeviceAddress() (addr net.IP, err error) {
|
||||
return n.gateway, nil
|
||||
}
|
||||
|
||||
func (n *natpmp_NAT) GetInternalAddress() (addr net.IP, err error) {
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, iface := range ifaces {
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
switch x := addr.(type) {
|
||||
case *net.IPNet:
|
||||
if x.Contains(n.gateway) {
|
||||
return x.IP, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, ErrNoInternalAddress
|
||||
}
|
||||
|
||||
func (n *natpmp_NAT) GetExternalAddress() (addr net.IP, err error) {
|
||||
res, err := n.c.GetExternalAddress()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d := res.ExternalIPAddress
|
||||
return net.IPv4(d[0], d[1], d[2], d[3]), nil
|
||||
}
|
||||
|
||||
func (n *natpmp_NAT) AddPortMapping(protocol string, internalPort int, description string, timeout time.Duration) (int, error) {
|
||||
var (
|
||||
err error
|
||||
)
|
||||
|
||||
timeoutInSeconds := int(timeout / time.Second)
|
||||
|
||||
if externalPort := n.ports[internalPort]; externalPort > 0 {
|
||||
_, err = n.c.AddPortMapping(protocol, internalPort, externalPort, timeoutInSeconds)
|
||||
if err == nil {
|
||||
n.ports[internalPort] = externalPort
|
||||
return externalPort, nil
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
externalPort := randomPort()
|
||||
_, err = n.c.AddPortMapping(protocol, internalPort, externalPort, timeoutInSeconds)
|
||||
if err == nil {
|
||||
n.ports[internalPort] = externalPort
|
||||
return externalPort, nil
|
||||
}
|
||||
}
|
||||
|
||||
return 0, err
|
||||
}
|
||||
|
||||
func (n *natpmp_NAT) DeletePortMapping(protocol string, internalPort int) (err error) {
|
||||
delete(n.ports, internalPort)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *natpmp_NAT) Type() string {
|
||||
return "NAT-PMP"
|
||||
}
|
||||
239
Godeps/_workspace/src/github.com/fd/go-nat/upnp.go
generated
vendored
Normal file
239
Godeps/_workspace/src/github.com/fd/go-nat/upnp.go
generated
vendored
Normal file
@ -0,0 +1,239 @@
|
||||
package nat
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/huin/goupnp"
|
||||
"github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/huin/goupnp/dcps/internetgateway1"
|
||||
"github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/huin/goupnp/dcps/internetgateway2"
|
||||
)
|
||||
|
||||
var (
|
||||
_ NAT = (*upnp_NAT)(nil)
|
||||
)
|
||||
|
||||
func discoverUPNP_IG1() <-chan NAT {
|
||||
res := make(chan NAT, 1)
|
||||
go func() {
|
||||
|
||||
// find devices
|
||||
devs, err := goupnp.DiscoverDevices(internetgateway1.URN_WANConnectionDevice_1)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, dev := range devs {
|
||||
if dev.Root == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
dev.Root.Device.VisitServices(func(srv *goupnp.Service) {
|
||||
switch srv.ServiceType {
|
||||
case internetgateway1.URN_WANIPConnection_1:
|
||||
client := &internetgateway1.WANIPConnection1{ServiceClient: goupnp.ServiceClient{
|
||||
SOAPClient: srv.NewSOAPClient(),
|
||||
RootDevice: dev.Root,
|
||||
Service: srv,
|
||||
}}
|
||||
_, isNat, err := client.GetNATRSIPStatus()
|
||||
if err == nil && isNat {
|
||||
res <- &upnp_NAT{client, make(map[int]int), "UPNP (IG1-IP1)", dev.Root}
|
||||
return
|
||||
}
|
||||
|
||||
case internetgateway1.URN_WANPPPConnection_1:
|
||||
client := &internetgateway1.WANPPPConnection1{ServiceClient: goupnp.ServiceClient{
|
||||
SOAPClient: srv.NewSOAPClient(),
|
||||
RootDevice: dev.Root,
|
||||
Service: srv,
|
||||
}}
|
||||
_, isNat, err := client.GetNATRSIPStatus()
|
||||
if err == nil && isNat {
|
||||
res <- &upnp_NAT{client, make(map[int]int), "UPNP (IG1-PPP1)", dev.Root}
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}()
|
||||
return res
|
||||
}
|
||||
|
||||
func discoverUPNP_IG2() <-chan NAT {
|
||||
res := make(chan NAT, 1)
|
||||
go func() {
|
||||
|
||||
// find devices
|
||||
devs, err := goupnp.DiscoverDevices(internetgateway2.URN_WANConnectionDevice_2)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, dev := range devs {
|
||||
if dev.Root == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
dev.Root.Device.VisitServices(func(srv *goupnp.Service) {
|
||||
switch srv.ServiceType {
|
||||
case internetgateway2.URN_WANIPConnection_1:
|
||||
client := &internetgateway2.WANIPConnection1{ServiceClient: goupnp.ServiceClient{
|
||||
SOAPClient: srv.NewSOAPClient(),
|
||||
RootDevice: dev.Root,
|
||||
Service: srv,
|
||||
}}
|
||||
_, isNat, err := client.GetNATRSIPStatus()
|
||||
if err == nil && isNat {
|
||||
res <- &upnp_NAT{client, make(map[int]int), "UPNP (IG2-IP1)", dev.Root}
|
||||
return
|
||||
}
|
||||
|
||||
case internetgateway2.URN_WANIPConnection_2:
|
||||
client := &internetgateway2.WANIPConnection2{ServiceClient: goupnp.ServiceClient{
|
||||
SOAPClient: srv.NewSOAPClient(),
|
||||
RootDevice: dev.Root,
|
||||
Service: srv,
|
||||
}}
|
||||
_, isNat, err := client.GetNATRSIPStatus()
|
||||
if err == nil && isNat {
|
||||
res <- &upnp_NAT{client, make(map[int]int), "UPNP (IG2-IP2)", dev.Root}
|
||||
return
|
||||
}
|
||||
|
||||
case internetgateway2.URN_WANPPPConnection_1:
|
||||
client := &internetgateway2.WANPPPConnection1{ServiceClient: goupnp.ServiceClient{
|
||||
SOAPClient: srv.NewSOAPClient(),
|
||||
RootDevice: dev.Root,
|
||||
Service: srv,
|
||||
}}
|
||||
_, isNat, err := client.GetNATRSIPStatus()
|
||||
if err == nil && isNat {
|
||||
res <- &upnp_NAT{client, make(map[int]int), "UPNP (IG2-PPP1)", dev.Root}
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}()
|
||||
return res
|
||||
}
|
||||
|
||||
type upnp_NAT_Client interface {
|
||||
GetExternalIPAddress() (string, error)
|
||||
AddPortMapping(string, uint16, string, uint16, string, bool, string, uint32) error
|
||||
DeletePortMapping(string, uint16, string) error
|
||||
}
|
||||
|
||||
type upnp_NAT struct {
|
||||
c upnp_NAT_Client
|
||||
ports map[int]int
|
||||
typ string
|
||||
rootDevice *goupnp.RootDevice
|
||||
}
|
||||
|
||||
func (u *upnp_NAT) GetExternalAddress() (addr net.IP, err error) {
|
||||
ipString, err := u.c.GetExternalIPAddress()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ip := net.ParseIP(ipString)
|
||||
if ip == nil {
|
||||
return nil, ErrNoExternalAddress
|
||||
}
|
||||
|
||||
return ip, nil
|
||||
}
|
||||
|
||||
func mapProtocol(s string) string {
|
||||
switch s {
|
||||
case "udp":
|
||||
return "UDP"
|
||||
case "tcp":
|
||||
return "TCP"
|
||||
default:
|
||||
panic("invalid protocol: " + s)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *upnp_NAT) AddPortMapping(protocol string, internalPort int, description string, timeout time.Duration) (int, error) {
|
||||
ip, err := u.GetInternalAddress()
|
||||
if err != nil {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
timeoutInSeconds := uint32(timeout / time.Second)
|
||||
|
||||
if externalPort := u.ports[internalPort]; externalPort > 0 {
|
||||
err = u.c.AddPortMapping("", uint16(externalPort), mapProtocol(protocol), uint16(internalPort), ip.String(), true, description, timeoutInSeconds)
|
||||
if err == nil {
|
||||
return externalPort, nil
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
externalPort := randomPort()
|
||||
err = u.c.AddPortMapping("", uint16(externalPort), mapProtocol(protocol), uint16(internalPort), ip.String(), true, description, timeoutInSeconds)
|
||||
if err == nil {
|
||||
u.ports[internalPort] = externalPort
|
||||
return externalPort, nil
|
||||
}
|
||||
}
|
||||
|
||||
return 0, err
|
||||
}
|
||||
|
||||
func (u *upnp_NAT) DeletePortMapping(protocol string, internalPort int) error {
|
||||
if externalPort := u.ports[internalPort]; externalPort > 0 {
|
||||
delete(u.ports, internalPort)
|
||||
return u.c.DeletePortMapping("", uint16(externalPort), mapProtocol(protocol))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *upnp_NAT) GetDeviceAddress() (net.IP, error) {
|
||||
addr, err := net.ResolveUDPAddr("udp4", u.rootDevice.URLBase.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return addr.IP, nil
|
||||
}
|
||||
|
||||
func (u *upnp_NAT) GetInternalAddress() (net.IP, error) {
|
||||
devAddr, err := u.GetDeviceAddress()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, iface := range ifaces {
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
switch x := addr.(type) {
|
||||
case *net.IPNet:
|
||||
if x.Contains(devAddr) {
|
||||
return x.IP, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, ErrNoInternalAddress
|
||||
}
|
||||
|
||||
func (n *upnp_NAT) Type() string { return n.typ }
|
||||
23
Godeps/_workspace/src/github.com/huin/goupnp/LICENSE
generated
vendored
Normal file
23
Godeps/_workspace/src/github.com/huin/goupnp/LICENSE
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
Copyright (c) 2013, John Beisley <greatred@gmail.com>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
14
Godeps/_workspace/src/github.com/huin/goupnp/README.md
generated
vendored
Normal file
14
Godeps/_workspace/src/github.com/huin/goupnp/README.md
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
goupnp is a UPnP client library for Go
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Run `go get -u github.com/huin/goupnp`.
|
||||
|
||||
Regenerating dcps generated source code:
|
||||
----------------------------------------
|
||||
|
||||
1. Install gotasks: `go get -u github.com/jingweno/gotask`
|
||||
2. Change to the gotasks directory: `cd gotasks`
|
||||
3. Download UPnP specification data (if not done already): `wget http://upnp.org/resources/upnpresources.zip`
|
||||
4. Regenerate source code: `gotask specgen -s upnpresources.zip -o ../dcps`
|
||||
20
Godeps/_workspace/src/github.com/huin/goupnp/cmd/example_httpu_serving/example_httpu_serving.go
generated
vendored
Normal file
20
Godeps/_workspace/src/github.com/huin/goupnp/cmd/example_httpu_serving/example_httpu_serving.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/huin/goupnp/httpu"
|
||||
)
|
||||
|
||||
func main() {
|
||||
srv := httpu.Server{
|
||||
Addr: "239.255.255.250:1900",
|
||||
Multicast: true,
|
||||
Handler: httpu.HandlerFunc(func(r *http.Request) {
|
||||
log.Printf("Got %s %s message from %v: %v", r.Method, r.URL.Path, r.RemoteAddr, r.Header)
|
||||
}),
|
||||
}
|
||||
err := srv.ListenAndServe()
|
||||
log.Printf("Serving failed with error: %v", err)
|
||||
}
|
||||
67
Godeps/_workspace/src/github.com/huin/goupnp/cmd/example_internetgateway1/example_internetgateway1.go
generated
vendored
Normal file
67
Godeps/_workspace/src/github.com/huin/goupnp/cmd/example_internetgateway1/example_internetgateway1.go
generated
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/huin/goupnp/dcps/internetgateway1"
|
||||
)
|
||||
|
||||
func main() {
|
||||
clients, errors, err := internetgateway1.NewWANPPPConnection1Clients()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Got %d errors finding servers and %d successfully discovered.\n",
|
||||
len(errors), len(clients))
|
||||
for i, e := range errors {
|
||||
fmt.Printf("Error finding server #%d: %v\n", i+1, e)
|
||||
}
|
||||
|
||||
for _, c := range clients {
|
||||
dev := &c.ServiceClient.RootDevice.Device
|
||||
srv := c.ServiceClient.Service
|
||||
fmt.Println(dev.FriendlyName, " :: ", srv.String())
|
||||
scpd, err := srv.RequestSCDP()
|
||||
if err != nil {
|
||||
fmt.Printf(" Error requesting service SCPD: %v\n", err)
|
||||
} else {
|
||||
fmt.Println(" Available actions:")
|
||||
for _, action := range scpd.Actions {
|
||||
fmt.Printf(" * %s\n", action.Name)
|
||||
for _, arg := range action.Arguments {
|
||||
var varDesc string
|
||||
if stateVar := scpd.GetStateVariable(arg.RelatedStateVariable); stateVar != nil {
|
||||
varDesc = fmt.Sprintf(" (%s)", stateVar.DataType.Name)
|
||||
}
|
||||
fmt.Printf(" * [%s] %s%s\n", arg.Direction, arg.Name, varDesc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if scpd == nil || scpd.GetAction("GetExternalIPAddress") != nil {
|
||||
ip, err := c.GetExternalIPAddress()
|
||||
fmt.Println("GetExternalIPAddress: ", ip, err)
|
||||
}
|
||||
|
||||
if scpd == nil || scpd.GetAction("GetStatusInfo") != nil {
|
||||
status, lastErr, uptime, err := c.GetStatusInfo()
|
||||
fmt.Println("GetStatusInfo: ", status, lastErr, uptime, err)
|
||||
}
|
||||
|
||||
if scpd == nil || scpd.GetAction("GetIdleDisconnectTime") != nil {
|
||||
idleTime, err := c.GetIdleDisconnectTime()
|
||||
fmt.Println("GetIdleDisconnectTime: ", idleTime, err)
|
||||
}
|
||||
|
||||
if scpd == nil || scpd.GetAction("AddPortMapping") != nil {
|
||||
err := c.AddPortMapping("", 5000, "TCP", 5001, "192.168.1.2", true, "Test port mapping", 0)
|
||||
fmt.Println("AddPortMapping: ", err)
|
||||
}
|
||||
if scpd == nil || scpd.GetAction("DeletePortMapping") != nil {
|
||||
err := c.DeletePortMapping("", 5000, "TCP")
|
||||
fmt.Println("DeletePortMapping: ", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
3957
Godeps/_workspace/src/github.com/huin/goupnp/dcps/internetgateway1/internetgateway1.go
generated
vendored
Normal file
3957
Godeps/_workspace/src/github.com/huin/goupnp/dcps/internetgateway1/internetgateway1.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
5271
Godeps/_workspace/src/github.com/huin/goupnp/dcps/internetgateway2/internetgateway2.go
generated
vendored
Normal file
5271
Godeps/_workspace/src/github.com/huin/goupnp/dcps/internetgateway2/internetgateway2.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
184
Godeps/_workspace/src/github.com/huin/goupnp/device.go
generated
vendored
Normal file
184
Godeps/_workspace/src/github.com/huin/goupnp/device.go
generated
vendored
Normal file
@ -0,0 +1,184 @@
|
||||
// This file contains XML structures for communicating with UPnP devices.
|
||||
|
||||
package goupnp
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/huin/goupnp/scpd"
|
||||
"github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/huin/goupnp/soap"
|
||||
)
|
||||
|
||||
const (
|
||||
DeviceXMLNamespace = "urn:schemas-upnp-org:device-1-0"
|
||||
)
|
||||
|
||||
// RootDevice is the device description as described by section 2.3 "Device
|
||||
// description" in
|
||||
// http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf
|
||||
type RootDevice struct {
|
||||
XMLName xml.Name `xml:"root"`
|
||||
SpecVersion SpecVersion `xml:"specVersion"`
|
||||
URLBase url.URL `xml:"-"`
|
||||
URLBaseStr string `xml:"URLBase"`
|
||||
Device Device `xml:"device"`
|
||||
}
|
||||
|
||||
// SetURLBase sets the URLBase for the RootDevice and its underlying components.
|
||||
func (root *RootDevice) SetURLBase(urlBase *url.URL) {
|
||||
root.URLBase = *urlBase
|
||||
root.URLBaseStr = urlBase.String()
|
||||
root.Device.SetURLBase(urlBase)
|
||||
}
|
||||
|
||||
// SpecVersion is part of a RootDevice, describes the version of the
|
||||
// specification that the data adheres to.
|
||||
type SpecVersion struct {
|
||||
Major int32 `xml:"major"`
|
||||
Minor int32 `xml:"minor"`
|
||||
}
|
||||
|
||||
// Device is a UPnP device. It can have child devices.
|
||||
type Device struct {
|
||||
DeviceType string `xml:"deviceType"`
|
||||
FriendlyName string `xml:"friendlyName"`
|
||||
Manufacturer string `xml:"manufacturer"`
|
||||
ManufacturerURL URLField `xml:"manufacturerURL"`
|
||||
ModelDescription string `xml:"modelDescription"`
|
||||
ModelName string `xml:"modelName"`
|
||||
ModelNumber string `xml:"modelNumber"`
|
||||
ModelURL URLField `xml:"modelURL"`
|
||||
SerialNumber string `xml:"serialNumber"`
|
||||
UDN string `xml:"UDN"`
|
||||
UPC string `xml:"UPC,omitempty"`
|
||||
Icons []Icon `xml:"iconList>icon,omitempty"`
|
||||
Services []Service `xml:"serviceList>service,omitempty"`
|
||||
Devices []Device `xml:"deviceList>device,omitempty"`
|
||||
|
||||
// Extra observed elements:
|
||||
PresentationURL URLField `xml:"presentationURL"`
|
||||
}
|
||||
|
||||
// VisitDevices calls visitor for the device, and all its descendent devices.
|
||||
func (device *Device) VisitDevices(visitor func(*Device)) {
|
||||
visitor(device)
|
||||
for i := range device.Devices {
|
||||
device.Devices[i].VisitDevices(visitor)
|
||||
}
|
||||
}
|
||||
|
||||
// VisitServices calls visitor for all Services under the device and all its
|
||||
// descendent devices.
|
||||
func (device *Device) VisitServices(visitor func(*Service)) {
|
||||
device.VisitDevices(func(d *Device) {
|
||||
for i := range d.Services {
|
||||
visitor(&d.Services[i])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// FindService finds all (if any) Services under the device and its descendents
|
||||
// that have the given ServiceType.
|
||||
func (device *Device) FindService(serviceType string) []*Service {
|
||||
var services []*Service
|
||||
device.VisitServices(func(s *Service) {
|
||||
if s.ServiceType == serviceType {
|
||||
services = append(services, s)
|
||||
}
|
||||
})
|
||||
return services
|
||||
}
|
||||
|
||||
// SetURLBase sets the URLBase for the Device and its underlying components.
|
||||
func (device *Device) SetURLBase(urlBase *url.URL) {
|
||||
device.ManufacturerURL.SetURLBase(urlBase)
|
||||
device.ModelURL.SetURLBase(urlBase)
|
||||
device.PresentationURL.SetURLBase(urlBase)
|
||||
for i := range device.Icons {
|
||||
device.Icons[i].SetURLBase(urlBase)
|
||||
}
|
||||
for i := range device.Services {
|
||||
device.Services[i].SetURLBase(urlBase)
|
||||
}
|
||||
for i := range device.Devices {
|
||||
device.Devices[i].SetURLBase(urlBase)
|
||||
}
|
||||
}
|
||||
|
||||
func (device *Device) String() string {
|
||||
return fmt.Sprintf("Device ID %s : %s (%s)", device.UDN, device.DeviceType, device.FriendlyName)
|
||||
}
|
||||
|
||||
// Icon is a representative image that a device might include in its
|
||||
// description.
|
||||
type Icon struct {
|
||||
Mimetype string `xml:"mimetype"`
|
||||
Width int32 `xml:"width"`
|
||||
Height int32 `xml:"height"`
|
||||
Depth int32 `xml:"depth"`
|
||||
URL URLField `xml:"url"`
|
||||
}
|
||||
|
||||
// SetURLBase sets the URLBase for the Icon.
|
||||
func (icon *Icon) SetURLBase(url *url.URL) {
|
||||
icon.URL.SetURLBase(url)
|
||||
}
|
||||
|
||||
// Service is a service provided by a UPnP Device.
|
||||
type Service struct {
|
||||
ServiceType string `xml:"serviceType"`
|
||||
ServiceId string `xml:"serviceId"`
|
||||
SCPDURL URLField `xml:"SCPDURL"`
|
||||
ControlURL URLField `xml:"controlURL"`
|
||||
EventSubURL URLField `xml:"eventSubURL"`
|
||||
}
|
||||
|
||||
// SetURLBase sets the URLBase for the Service.
|
||||
func (srv *Service) SetURLBase(urlBase *url.URL) {
|
||||
srv.SCPDURL.SetURLBase(urlBase)
|
||||
srv.ControlURL.SetURLBase(urlBase)
|
||||
srv.EventSubURL.SetURLBase(urlBase)
|
||||
}
|
||||
|
||||
func (srv *Service) String() string {
|
||||
return fmt.Sprintf("Service ID %s : %s", srv.ServiceId, srv.ServiceType)
|
||||
}
|
||||
|
||||
// RequestSCDP requests the SCPD (soap actions and state variables description)
|
||||
// for the service.
|
||||
func (srv *Service) RequestSCDP() (*scpd.SCPD, error) {
|
||||
if !srv.SCPDURL.Ok {
|
||||
return nil, errors.New("bad/missing SCPD URL, or no URLBase has been set")
|
||||
}
|
||||
s := new(scpd.SCPD)
|
||||
if err := requestXml(srv.SCPDURL.URL.String(), scpd.SCPDXMLNamespace, s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (srv *Service) NewSOAPClient() *soap.SOAPClient {
|
||||
return soap.NewSOAPClient(srv.ControlURL.URL)
|
||||
}
|
||||
|
||||
// URLField is a URL that is part of a device description.
|
||||
type URLField struct {
|
||||
URL url.URL `xml:"-"`
|
||||
Ok bool `xml:"-"`
|
||||
Str string `xml:",chardata"`
|
||||
}
|
||||
|
||||
func (uf *URLField) SetURLBase(urlBase *url.URL) {
|
||||
refUrl, err := url.Parse(uf.Str)
|
||||
if err != nil {
|
||||
uf.URL = url.URL{}
|
||||
uf.Ok = false
|
||||
return
|
||||
}
|
||||
|
||||
uf.URL = *urlBase.ResolveReference(refUrl)
|
||||
uf.Ok = true
|
||||
}
|
||||
6
Godeps/_workspace/src/github.com/huin/goupnp/example/example.go
generated
vendored
Normal file
6
Godeps/_workspace/src/github.com/huin/goupnp/example/example.go
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
// Serves as examples of using the goupnp library.
|
||||
//
|
||||
// To run examples and see the output for your local network, run the following
|
||||
// command (specifically including the -v flag):
|
||||
// go test -v github.com/huin/goupnp/example
|
||||
package example
|
||||
62
Godeps/_workspace/src/github.com/huin/goupnp/example/example_test.go
generated
vendored
Normal file
62
Godeps/_workspace/src/github.com/huin/goupnp/example/example_test.go
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
package example_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/huin/goupnp"
|
||||
"github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/huin/goupnp/dcps/internetgateway1"
|
||||
)
|
||||
|
||||
// Use discovered WANPPPConnection1 services to find external IP addresses.
|
||||
func Example_WANPPPConnection1_GetExternalIPAddress() {
|
||||
clients, errors, err := internetgateway1.NewWANPPPConnection1Clients()
|
||||
extIPClients := make([]GetExternalIPAddresser, len(clients))
|
||||
for i, client := range clients {
|
||||
extIPClients[i] = client
|
||||
}
|
||||
DisplayExternalIPResults(extIPClients, errors, err)
|
||||
// Output:
|
||||
}
|
||||
|
||||
// Use discovered WANIPConnection services to find external IP addresses.
|
||||
func Example_WANIPConnection_GetExternalIPAddress() {
|
||||
clients, errors, err := internetgateway1.NewWANIPConnection1Clients()
|
||||
extIPClients := make([]GetExternalIPAddresser, len(clients))
|
||||
for i, client := range clients {
|
||||
extIPClients[i] = client
|
||||
}
|
||||
DisplayExternalIPResults(extIPClients, errors, err)
|
||||
// Output:
|
||||
}
|
||||
|
||||
type GetExternalIPAddresser interface {
|
||||
GetExternalIPAddress() (NewExternalIPAddress string, err error)
|
||||
GetServiceClient() *goupnp.ServiceClient
|
||||
}
|
||||
|
||||
func DisplayExternalIPResults(clients []GetExternalIPAddresser, errors []error, err error) {
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error discovering service with UPnP: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
fmt.Fprintf(os.Stderr, "Error discovering %d services:\n", len(errors))
|
||||
for _, err := range errors {
|
||||
fmt.Println(" ", err)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Successfully discovered %d services:\n", len(clients))
|
||||
for _, client := range clients {
|
||||
device := &client.GetServiceClient().RootDevice.Device
|
||||
|
||||
fmt.Fprintln(os.Stderr, " Device:", device.FriendlyName)
|
||||
if addr, err := client.GetExternalIPAddress(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, " Failed to get external IP address: %v\n", err)
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, " External IP address: %v\n", addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
539
Godeps/_workspace/src/github.com/huin/goupnp/gotasks/specgen_task.go
generated
vendored
Normal file
539
Godeps/_workspace/src/github.com/huin/goupnp/gotasks/specgen_task.go
generated
vendored
Normal file
@ -0,0 +1,539 @@
|
||||
// +build gotask
|
||||
|
||||
package gotasks
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/huin/goutil/codegen"
|
||||
"github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/huin/goupnp"
|
||||
"github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/huin/goupnp/scpd"
|
||||
"github.com/jingweno/gotask/tasking"
|
||||
)
|
||||
|
||||
var (
|
||||
deviceURNPrefix = "urn:schemas-upnp-org:device:"
|
||||
serviceURNPrefix = "urn:schemas-upnp-org:service:"
|
||||
)
|
||||
|
||||
// NAME
|
||||
// specgen - generates Go code from the UPnP specification files.
|
||||
//
|
||||
// DESCRIPTION
|
||||
// The specification is available for download from:
|
||||
//
|
||||
// OPTIONS
|
||||
// -s, --spec_filename=<upnpresources.zip>
|
||||
// Path to the specification file, available from http://upnp.org/resources/upnpresources.zip
|
||||
// -o, --out_dir=<output directory>
|
||||
// Path to the output directory. This is is where the DCP source files will be placed. Should normally correspond to the directory for github.com/huin/goupnp/dcps
|
||||
// --nogofmt
|
||||
// Disable passing the output through gofmt. Do this if debugging code output problems and needing to see the generated code prior to being passed through gofmt.
|
||||
func TaskSpecgen(t *tasking.T) {
|
||||
specFilename := t.Flags.String("spec-filename")
|
||||
if specFilename == "" {
|
||||
specFilename = t.Flags.String("s")
|
||||
}
|
||||
if specFilename == "" {
|
||||
t.Fatal("--spec_filename is required")
|
||||
}
|
||||
outDir := t.Flags.String("out-dir")
|
||||
if outDir == "" {
|
||||
outDir = t.Flags.String("o")
|
||||
}
|
||||
if outDir == "" {
|
||||
log.Fatal("--out_dir is required")
|
||||
}
|
||||
useGofmt := !t.Flags.Bool("nogofmt")
|
||||
|
||||
specArchive, err := openZipfile(specFilename)
|
||||
if err != nil {
|
||||
t.Fatalf("Error opening spec file: %v", err)
|
||||
}
|
||||
defer specArchive.Close()
|
||||
|
||||
dcpCol := newDcpsCollection()
|
||||
for _, f := range globFiles("standardizeddcps/*/*.zip", specArchive.Reader) {
|
||||
dirName := strings.TrimPrefix(f.Name, "standardizeddcps/")
|
||||
slashIndex := strings.Index(dirName, "/")
|
||||
if slashIndex == -1 {
|
||||
// Should not happen.
|
||||
t.Logf("Could not find / in %q", dirName)
|
||||
return
|
||||
}
|
||||
dirName = dirName[:slashIndex]
|
||||
|
||||
dcp := dcpCol.dcpForDir(dirName)
|
||||
if dcp == nil {
|
||||
t.Logf("No alias defined for directory %q: skipping %s\n", dirName, f.Name)
|
||||
continue
|
||||
} else {
|
||||
t.Logf("Alias found for directory %q: processing %s\n", dirName, f.Name)
|
||||
}
|
||||
|
||||
dcp.processZipFile(f)
|
||||
}
|
||||
|
||||
for _, dcp := range dcpCol.dcpByAlias {
|
||||
if err := dcp.writePackage(outDir, useGofmt); err != nil {
|
||||
log.Printf("Error writing package %q: %v", dcp.Metadata.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 futher documentation about the DCP.
|
||||
}
|
||||
|
||||
var dcpMetadataByDir = map[string]DCPMetadata{
|
||||
"Internet Gateway_1": {
|
||||
Name: "internetgateway1",
|
||||
OfficialName: "Internet Gateway Device v1",
|
||||
DocURL: "http://upnp.org/specs/gw/UPnP-gw-InternetGatewayDevice-v1-Device.pdf",
|
||||
},
|
||||
"Internet Gateway_2": {
|
||||
Name: "internetgateway2",
|
||||
OfficialName: "Internet Gateway Device v2",
|
||||
DocURL: "http://upnp.org/specs/gw/UPnP-gw-InternetGatewayDevice-v2-Device.pdf",
|
||||
},
|
||||
}
|
||||
|
||||
type dcpCollection struct {
|
||||
dcpByAlias map[string]*DCP
|
||||
}
|
||||
|
||||
func newDcpsCollection() *dcpCollection {
|
||||
c := &dcpCollection{
|
||||
dcpByAlias: make(map[string]*DCP),
|
||||
}
|
||||
for _, metadata := range dcpMetadataByDir {
|
||||
c.dcpByAlias[metadata.Name] = newDCP(metadata)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *dcpCollection) dcpForDir(dirName string) *DCP {
|
||||
metadata, ok := dcpMetadataByDir[dirName]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return c.dcpByAlias[metadata.Name]
|
||||
}
|
||||
|
||||
// DCP collects together information about a UPnP Device Control Protocol.
|
||||
type DCP struct {
|
||||
Metadata DCPMetadata
|
||||
DeviceTypes map[string]*URNParts
|
||||
ServiceTypes map[string]*URNParts
|
||||
Services []SCPDWithURN
|
||||
}
|
||||
|
||||
func newDCP(metadata DCPMetadata) *DCP {
|
||||
return &DCP{
|
||||
Metadata: metadata,
|
||||
DeviceTypes: make(map[string]*URNParts),
|
||||
ServiceTypes: make(map[string]*URNParts),
|
||||
}
|
||||
}
|
||||
|
||||
func (dcp *DCP) processZipFile(file *zip.File) {
|
||||
archive, err := openChildZip(file)
|
||||
if err != nil {
|
||||
log.Println("Error reading child zip file:", err)
|
||||
return
|
||||
}
|
||||
for _, deviceFile := range globFiles("*/device/*.xml", archive) {
|
||||
dcp.processDeviceFile(deviceFile)
|
||||
}
|
||||
for _, scpdFile := range globFiles("*/service/*.xml", archive) {
|
||||
dcp.processSCPDFile(scpdFile)
|
||||
}
|
||||
}
|
||||
|
||||
func (dcp *DCP) processDeviceFile(file *zip.File) {
|
||||
var device goupnp.Device
|
||||
if err := unmarshalXmlFile(file, &device); err != nil {
|
||||
log.Printf("Error decoding device XML from file %q: %v", file.Name, err)
|
||||
return
|
||||
}
|
||||
device.VisitDevices(func(d *goupnp.Device) {
|
||||
t := strings.TrimSpace(d.DeviceType)
|
||||
if t != "" {
|
||||
u, err := extractURNParts(t, deviceURNPrefix)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
dcp.DeviceTypes[t] = u
|
||||
}
|
||||
})
|
||||
device.VisitServices(func(s *goupnp.Service) {
|
||||
u, err := extractURNParts(s.ServiceType, serviceURNPrefix)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
dcp.ServiceTypes[s.ServiceType] = u
|
||||
})
|
||||
}
|
||||
|
||||
func (dcp *DCP) writePackage(outDir string, useGofmt bool) error {
|
||||
packageDirname := filepath.Join(outDir, dcp.Metadata.Name)
|
||||
err := os.MkdirAll(packageDirname, os.ModePerm)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
packageFilename := filepath.Join(packageDirname, dcp.Metadata.Name+".go")
|
||||
packageFile, err := os.Create(packageFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var output io.WriteCloser = packageFile
|
||||
if useGofmt {
|
||||
if output, err = codegen.NewGofmtWriteCloser(output); err != nil {
|
||||
packageFile.Close()
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err = packageTmpl.Execute(output, dcp); err != nil {
|
||||
output.Close()
|
||||
return err
|
||||
}
|
||||
return output.Close()
|
||||
}
|
||||
|
||||
func (dcp *DCP) processSCPDFile(file *zip.File) {
|
||||
scpd := new(scpd.SCPD)
|
||||
if err := unmarshalXmlFile(file, scpd); err != nil {
|
||||
log.Printf("Error decoding SCPD XML from file %q: %v", file.Name, err)
|
||||
return
|
||||
}
|
||||
scpd.Clean()
|
||||
urnParts, err := urnPartsFromSCPDFilename(file.Name)
|
||||
if err != nil {
|
||||
log.Printf("Could not recognize SCPD filename %q: %v", file.Name, err)
|
||||
return
|
||||
}
|
||||
dcp.Services = append(dcp.Services, SCPDWithURN{
|
||||
URNParts: urnParts,
|
||||
SCPD: scpd,
|
||||
})
|
||||
}
|
||||
|
||||
type SCPDWithURN struct {
|
||||
*URNParts
|
||||
SCPD *scpd.SCPD
|
||||
}
|
||||
|
||||
func (s *SCPDWithURN) WrapArgument(arg scpd.Argument) (*argumentWrapper, error) {
|
||||
relVar := s.SCPD.GetStateVariable(arg.RelatedStateVariable)
|
||||
if relVar == nil {
|
||||
return nil, fmt.Errorf("no such state variable: %q, for argument %q", arg.RelatedStateVariable, arg.Name)
|
||||
}
|
||||
cnv, ok := typeConvs[relVar.DataType.Name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown data type: %q, for state variable %q, for argument %q", relVar.DataType.Type, arg.RelatedStateVariable, arg.Name)
|
||||
}
|
||||
return &argumentWrapper{
|
||||
Argument: arg,
|
||||
relVar: relVar,
|
||||
conv: cnv,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type argumentWrapper struct {
|
||||
scpd.Argument
|
||||
relVar *scpd.StateVariable
|
||||
conv conv
|
||||
}
|
||||
|
||||
func (arg *argumentWrapper) AsParameter() string {
|
||||
return fmt.Sprintf("%s %s", arg.Name, arg.conv.ExtType)
|
||||
}
|
||||
|
||||
func (arg *argumentWrapper) Document() string {
|
||||
relVar := arg.relVar
|
||||
if rng := relVar.AllowedValueRange; rng != nil {
|
||||
var parts []string
|
||||
if rng.Minimum != "" {
|
||||
parts = append(parts, fmt.Sprintf("minimum=%s", rng.Minimum))
|
||||
}
|
||||
if rng.Maximum != "" {
|
||||
parts = append(parts, fmt.Sprintf("maximum=%s", rng.Maximum))
|
||||
}
|
||||
if rng.Step != "" {
|
||||
parts = append(parts, fmt.Sprintf("step=%s", rng.Step))
|
||||
}
|
||||
return "allowed value range: " + strings.Join(parts, ", ")
|
||||
}
|
||||
if len(relVar.AllowedValues) != 0 {
|
||||
return "allowed values: " + strings.Join(relVar.AllowedValues, ", ")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (arg *argumentWrapper) Marshal() string {
|
||||
return fmt.Sprintf("soap.Marshal%s(%s)", arg.conv.FuncSuffix, arg.Name)
|
||||
}
|
||||
|
||||
func (arg *argumentWrapper) Unmarshal(objVar string) string {
|
||||
return fmt.Sprintf("soap.Unmarshal%s(%s.%s)", arg.conv.FuncSuffix, objVar, arg.Name)
|
||||
}
|
||||
|
||||
type conv struct {
|
||||
FuncSuffix string
|
||||
ExtType string
|
||||
}
|
||||
|
||||
// typeConvs maps from a SOAP type (e.g "fixed.14.4") to the function name
|
||||
// suffix inside the soap module (e.g "Fixed14_4") and the Go type.
|
||||
var typeConvs = map[string]conv{
|
||||
"ui1": conv{"Ui1", "uint8"},
|
||||
"ui2": conv{"Ui2", "uint16"},
|
||||
"ui4": conv{"Ui4", "uint32"},
|
||||
"i1": conv{"I1", "int8"},
|
||||
"i2": conv{"I2", "int16"},
|
||||
"i4": conv{"I4", "int32"},
|
||||
"int": conv{"Int", "int64"},
|
||||
"r4": conv{"R4", "float32"},
|
||||
"r8": conv{"R8", "float64"},
|
||||
"number": conv{"R8", "float64"}, // Alias for r8.
|
||||
"fixed.14.4": conv{"Fixed14_4", "float64"},
|
||||
"float": conv{"R8", "float64"},
|
||||
"char": conv{"Char", "rune"},
|
||||
"string": conv{"String", "string"},
|
||||
"date": conv{"Date", "time.Time"},
|
||||
"dateTime": conv{"DateTime", "time.Time"},
|
||||
"dateTime.tz": conv{"DateTimeTz", "time.Time"},
|
||||
"time": conv{"TimeOfDay", "soap.TimeOfDay"},
|
||||
"time.tz": conv{"TimeOfDayTz", "soap.TimeOfDay"},
|
||||
"boolean": conv{"Boolean", "bool"},
|
||||
"bin.base64": conv{"BinBase64", "[]byte"},
|
||||
"bin.hex": conv{"BinHex", "[]byte"},
|
||||
}
|
||||
|
||||
type closeableZipReader struct {
|
||||
io.Closer
|
||||
*zip.Reader
|
||||
}
|
||||
|
||||
func openZipfile(filename string) (*closeableZipReader, error) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fi, err := file.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
archive, err := zip.NewReader(file, fi.Size())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &closeableZipReader{
|
||||
Closer: file,
|
||||
Reader: archive,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// openChildZip opens a zip file within another zip file.
|
||||
func openChildZip(file *zip.File) (*zip.Reader, error) {
|
||||
zipFile, err := file.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer zipFile.Close()
|
||||
|
||||
zipBytes, err := ioutil.ReadAll(zipFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return zip.NewReader(bytes.NewReader(zipBytes), int64(len(zipBytes)))
|
||||
}
|
||||
|
||||
func globFiles(pattern string, archive *zip.Reader) []*zip.File {
|
||||
var files []*zip.File
|
||||
for _, f := range archive.File {
|
||||
if matched, err := path.Match(pattern, f.Name); err != nil {
|
||||
// This shouldn't happen - all patterns are hard-coded, errors in them
|
||||
// are a programming error.
|
||||
panic(err)
|
||||
} else if matched {
|
||||
files = append(files, f)
|
||||
}
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
func unmarshalXmlFile(file *zip.File, data interface{}) error {
|
||||
r, err := file.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
decoder := xml.NewDecoder(r)
|
||||
r.Close()
|
||||
return decoder.Decode(data)
|
||||
}
|
||||
|
||||
type URNParts struct {
|
||||
URN string
|
||||
Name string
|
||||
Version string
|
||||
}
|
||||
|
||||
func (u *URNParts) Const() string {
|
||||
return fmt.Sprintf("URN_%s_%s", u.Name, u.Version)
|
||||
}
|
||||
|
||||
// extractURNParts extracts the name and version from a URN string.
|
||||
func extractURNParts(urn, expectedPrefix string) (*URNParts, error) {
|
||||
if !strings.HasPrefix(urn, expectedPrefix) {
|
||||
return nil, fmt.Errorf("%q does not have expected prefix %q", urn, expectedPrefix)
|
||||
}
|
||||
parts := strings.SplitN(strings.TrimPrefix(urn, expectedPrefix), ":", 2)
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("%q does not have a name and version", urn)
|
||||
}
|
||||
name, version := parts[0], parts[1]
|
||||
return &URNParts{urn, name, version}, nil
|
||||
}
|
||||
|
||||
var scpdFilenameRe = regexp.MustCompile(
|
||||
`.*/([a-zA-Z0-9]+)([0-9]+)\.xml`)
|
||||
|
||||
func urnPartsFromSCPDFilename(filename string) (*URNParts, error) {
|
||||
parts := scpdFilenameRe.FindStringSubmatch(filename)
|
||||
if len(parts) != 3 {
|
||||
return nil, fmt.Errorf("SCPD filename %q does not have expected number of parts", filename)
|
||||
}
|
||||
name, version := parts[1], parts[2]
|
||||
return &URNParts{
|
||||
URN: serviceURNPrefix + name + ":" + version,
|
||||
Name: name,
|
||||
Version: version,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var packageTmpl = template.Must(template.New("package").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}}
|
||||
//
|
||||
// Typically, use one of the New* functions to discover services on the local
|
||||
// network.
|
||||
package {{$name}}
|
||||
|
||||
// Generated file - do not edit by hand. See README.md
|
||||
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/huin/goupnp"
|
||||
"github.com/huin/goupnp/soap"
|
||||
)
|
||||
|
||||
// Hack to avoid Go complaining if time isn't used.
|
||||
var _ time.Time
|
||||
|
||||
// Device URNs:
|
||||
const ({{range .DeviceTypes}}
|
||||
{{.Const}} = "{{.URN}}"{{end}}
|
||||
)
|
||||
|
||||
// Service URNs:
|
||||
const ({{range .ServiceTypes}}
|
||||
{{.Const}} = "{{.URN}}"{{end}}
|
||||
)
|
||||
|
||||
{{range .Services}}
|
||||
{{$srv := .}}
|
||||
{{$srvIdent := printf "%s%s" .Name .Version}}
|
||||
|
||||
// {{$srvIdent}} is a client for UPnP SOAP service with URN "{{.URN}}". See
|
||||
// goupnp.ServiceClient, which contains RootDevice and Service attributes which
|
||||
// are provided for informational value.
|
||||
type {{$srvIdent}} struct {
|
||||
goupnp.ServiceClient
|
||||
}
|
||||
|
||||
// New{{$srvIdent}}Clients discovers instances of the service on the network,
|
||||
// and returns clients to any that are found. errors will contain an error for
|
||||
// any devices that replied but which could not be queried, and err will be set
|
||||
// if the discovery process failed outright.
|
||||
//
|
||||
// This is a typical entry calling point into this package.
|
||||
func New{{$srvIdent}}Clients() (clients []*{{$srvIdent}}, errors []error, err error) {
|
||||
var genericClients []goupnp.ServiceClient
|
||||
if genericClients, errors, err = goupnp.NewServiceClients({{$srv.Const}}); err != nil {
|
||||
return
|
||||
}
|
||||
clients = make([]*{{$srvIdent}}, len(genericClients))
|
||||
for i := range genericClients {
|
||||
clients[i] = &{{$srvIdent}}{genericClients[i]}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
{{range .SCPD.Actions}}{{/* loops over *SCPDWithURN values */}}
|
||||
|
||||
{{$inargs := .InputArguments}}{{$outargs := .OutputArguments}}
|
||||
// {{if $inargs}}Arguments:{{range $inargs}}{{$argWrap := $srv.WrapArgument .}}
|
||||
//
|
||||
// * {{.Name}}: {{$argWrap.Document}}{{end}}{{end}}
|
||||
//
|
||||
// {{if $outargs}}Return values:{{range $outargs}}{{$argWrap := $srv.WrapArgument .}}
|
||||
//
|
||||
// * {{.Name}}: {{$argWrap.Document}}{{end}}{{end}}
|
||||
func (client *{{$srvIdent}}) {{.Name}}({{range $inargs}}{{/*
|
||||
*/}}{{$argWrap := $srv.WrapArgument .}}{{$argWrap.AsParameter}}, {{end}}{{/*
|
||||
*/}}) ({{range $outargs}}{{/*
|
||||
*/}}{{$argWrap := $srv.WrapArgument .}}{{$argWrap.AsParameter}}, {{end}} err error) {
|
||||
// Request structure.
|
||||
request := {{if $inargs}}&{{template "argstruct" $inargs}}{{"{}"}}{{else}}{{"interface{}(nil)"}}{{end}}
|
||||
// BEGIN Marshal arguments into request.
|
||||
{{range $inargs}}{{$argWrap := $srv.WrapArgument .}}
|
||||
if request.{{.Name}}, err = {{$argWrap.Marshal}}; err != nil {
|
||||
return
|
||||
}{{end}}
|
||||
// END Marshal arguments into request.
|
||||
|
||||
// Response structure.
|
||||
response := {{if $outargs}}&{{template "argstruct" $outargs}}{{"{}"}}{{else}}{{"interface{}(nil)"}}{{end}}
|
||||
|
||||
// Perform the SOAP call.
|
||||
if err = client.SOAPClient.PerformAction({{$srv.URNParts.Const}}, "{{.Name}}", request, response); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// BEGIN Unmarshal arguments from response.
|
||||
{{range $outargs}}{{$argWrap := $srv.WrapArgument .}}
|
||||
if {{.Name}}, err = {{$argWrap.Unmarshal "response"}}; err != nil {
|
||||
return
|
||||
}{{end}}
|
||||
// END Unmarshal arguments from response.
|
||||
return
|
||||
}
|
||||
{{end}}{{/* range .SCPD.Actions */}}
|
||||
{{end}}{{/* range .Services */}}
|
||||
|
||||
{{define "argstruct"}}struct {{"{"}}{{range .}}
|
||||
{{.Name}} string
|
||||
{{end}}{{"}"}}{{end}}
|
||||
`))
|
||||
109
Godeps/_workspace/src/github.com/huin/goupnp/goupnp.go
generated
vendored
Normal file
109
Godeps/_workspace/src/github.com/huin/goupnp/goupnp.go
generated
vendored
Normal file
@ -0,0 +1,109 @@
|
||||
// goupnp is an implementation of a client for various UPnP services.
|
||||
//
|
||||
// For most uses, it is recommended to use the code-generated packages under
|
||||
// github.com/huin/goupnp/dcps. Example use is shown at
|
||||
// http://godoc.org/github.com/huin/goupnp/example
|
||||
//
|
||||
// A commonly used client is internetgateway1.WANPPPConnection1:
|
||||
// http://godoc.org/github.com/huin/goupnp/dcps/internetgateway1#WANPPPConnection1
|
||||
//
|
||||
// Currently only a couple of schemas have code generated for them from the
|
||||
// UPnP example XML specifications. Not all methods will work on these clients,
|
||||
// because the generated stubs contain the full set of specified methods from
|
||||
// the XML specifications, and the discovered services will likely support a
|
||||
// subset of those methods.
|
||||
package goupnp
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/huin/goupnp/httpu"
|
||||
"github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/huin/goupnp/ssdp"
|
||||
)
|
||||
|
||||
// ContextError is an error that wraps an error with some context information.
|
||||
type ContextError struct {
|
||||
Context string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (err ContextError) Error() string {
|
||||
return fmt.Sprintf("%s: %v", err.Context, err.Err)
|
||||
}
|
||||
|
||||
// MaybeRootDevice contains either a RootDevice or an error.
|
||||
type MaybeRootDevice struct {
|
||||
Root *RootDevice
|
||||
Err error
|
||||
}
|
||||
|
||||
// DiscoverDevices attempts to find targets of the given type. This is
|
||||
// typically the entry-point for this package. searchTarget is typically a URN
|
||||
// in the form "urn:schemas-upnp-org:device:..." or
|
||||
// "urn:schemas-upnp-org:service:...". A single error is returned for errors
|
||||
// while attempting to send the query. An error or RootDevice is returned for
|
||||
// each discovered RootDevice.
|
||||
func DiscoverDevices(searchTarget string) ([]MaybeRootDevice, error) {
|
||||
httpu, err := httpu.NewHTTPUClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer httpu.Close()
|
||||
responses, err := ssdp.SSDPRawSearch(httpu, string(searchTarget), 2, 3)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results := make([]MaybeRootDevice, len(responses))
|
||||
for i, response := range responses {
|
||||
maybe := &results[i]
|
||||
loc, err := response.Location()
|
||||
if err != nil {
|
||||
|
||||
maybe.Err = ContextError{"unexpected bad location from search", err}
|
||||
continue
|
||||
}
|
||||
locStr := loc.String()
|
||||
root := new(RootDevice)
|
||||
if err := requestXml(locStr, DeviceXMLNamespace, root); err != nil {
|
||||
maybe.Err = ContextError{fmt.Sprintf("error requesting root device details from %q", locStr), err}
|
||||
continue
|
||||
}
|
||||
var urlBaseStr string
|
||||
if root.URLBaseStr != "" {
|
||||
urlBaseStr = root.URLBaseStr
|
||||
} else {
|
||||
urlBaseStr = locStr
|
||||
}
|
||||
urlBase, err := url.Parse(urlBaseStr)
|
||||
if err != nil {
|
||||
maybe.Err = ContextError{fmt.Sprintf("error parsing location URL %q", locStr), err}
|
||||
continue
|
||||
}
|
||||
root.SetURLBase(urlBase)
|
||||
maybe.Root = root
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func requestXml(url string, defaultSpace string, doc interface{}) error {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("goupnp: got response status %s from %q",
|
||||
resp.Status, url)
|
||||
}
|
||||
|
||||
decoder := xml.NewDecoder(resp.Body)
|
||||
decoder.DefaultSpace = defaultSpace
|
||||
|
||||
return decoder.Decode(doc)
|
||||
}
|
||||
117
Godeps/_workspace/src/github.com/huin/goupnp/httpu/httpu.go
generated
vendored
Normal file
117
Godeps/_workspace/src/github.com/huin/goupnp/httpu/httpu.go
generated
vendored
Normal file
@ -0,0 +1,117 @@
|
||||
package httpu
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// HTTPUClient is a client for dealing with HTTPU (HTTP over UDP). Its typical
|
||||
// function is for HTTPMU, and particularly SSDP.
|
||||
type HTTPUClient struct {
|
||||
connLock sync.Mutex // Protects use of conn.
|
||||
conn net.PacketConn
|
||||
}
|
||||
|
||||
// NewHTTPUClient creates a new HTTPUClient, opening up a new UDP socket for the
|
||||
// purpose.
|
||||
func NewHTTPUClient() (*HTTPUClient, error) {
|
||||
conn, err := net.ListenPacket("udp", ":0")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &HTTPUClient{conn: conn}, nil
|
||||
}
|
||||
|
||||
// Close shuts down the client. The client will no longer be useful following
|
||||
// this.
|
||||
func (httpu *HTTPUClient) Close() error {
|
||||
httpu.connLock.Lock()
|
||||
defer httpu.connLock.Unlock()
|
||||
return httpu.conn.Close()
|
||||
}
|
||||
|
||||
// Do performs a request. The timeout is how long to wait for before returning
|
||||
// the responses that were received. An error is only returned for failing to
|
||||
// send the request. Failures in receipt simply do not add to the resulting
|
||||
// responses.
|
||||
//
|
||||
// Note that at present only one concurrent connection will happen per
|
||||
// HTTPUClient.
|
||||
func (httpu *HTTPUClient) Do(req *http.Request, timeout time.Duration, numSends int) ([]*http.Response, error) {
|
||||
httpu.connLock.Lock()
|
||||
defer httpu.connLock.Unlock()
|
||||
|
||||
// Create the request. This is a subset of what http.Request.Write does
|
||||
// deliberately to avoid creating extra fields which may confuse some
|
||||
// devices.
|
||||
var requestBuf bytes.Buffer
|
||||
method := req.Method
|
||||
if method == "" {
|
||||
method = "GET"
|
||||
}
|
||||
if _, err := fmt.Fprintf(&requestBuf, "%s %s HTTP/1.1\r\n", method, req.URL.RequestURI()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := req.Header.Write(&requestBuf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := requestBuf.Write([]byte{'\r', '\n'}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
destAddr, err := net.ResolveUDPAddr("udp", req.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = httpu.conn.SetDeadline(time.Now().Add(timeout)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Send request.
|
||||
for i := 0; i < numSends; i++ {
|
||||
if n, err := httpu.conn.WriteTo(requestBuf.Bytes(), destAddr); err != nil {
|
||||
return nil, err
|
||||
} else if n < len(requestBuf.Bytes()) {
|
||||
return nil, fmt.Errorf("httpu: wrote %d bytes rather than full %d in request",
|
||||
n, len(requestBuf.Bytes()))
|
||||
}
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
|
||||
// Await responses until timeout.
|
||||
var responses []*http.Response
|
||||
responseBytes := make([]byte, 2048)
|
||||
for {
|
||||
// 2048 bytes should be sufficient for most networks.
|
||||
n, _, err := httpu.conn.ReadFrom(responseBytes)
|
||||
if err != nil {
|
||||
if err, ok := err.(net.Error); ok {
|
||||
if err.Timeout() {
|
||||
break
|
||||
}
|
||||
if err.Temporary() {
|
||||
// Sleep in case this is a persistent error to avoid pegging CPU until deadline.
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Parse response.
|
||||
response, err := http.ReadResponse(bufio.NewReader(bytes.NewBuffer(responseBytes[:n])), req)
|
||||
if err != nil {
|
||||
log.Print("httpu: error while parsing response: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
responses = append(responses, response)
|
||||
}
|
||||
return responses, err
|
||||
}
|
||||
108
Godeps/_workspace/src/github.com/huin/goupnp/httpu/serve.go
generated
vendored
Normal file
108
Godeps/_workspace/src/github.com/huin/goupnp/httpu/serve.go
generated
vendored
Normal file
@ -0,0 +1,108 @@
|
||||
package httpu
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultMaxMessageBytes = 2048
|
||||
)
|
||||
|
||||
var (
|
||||
trailingWhitespaceRx = regexp.MustCompile(" +\r\n")
|
||||
crlf = []byte("\r\n")
|
||||
)
|
||||
|
||||
// Handler is the interface by which received HTTPU messages are passed to
|
||||
// handling code.
|
||||
type Handler interface {
|
||||
// ServeMessage is called for each HTTPU message received. peerAddr contains
|
||||
// the address that the message was received from.
|
||||
ServeMessage(r *http.Request)
|
||||
}
|
||||
|
||||
// HandlerFunc is a function-to-Handler adapter.
|
||||
type HandlerFunc func(r *http.Request)
|
||||
|
||||
func (f HandlerFunc) ServeMessage(r *http.Request) {
|
||||
f(r)
|
||||
}
|
||||
|
||||
// A Server defines parameters for running an HTTPU server.
|
||||
type Server struct {
|
||||
Addr string // UDP address to listen on
|
||||
Multicast bool // Should listen for multicast?
|
||||
Interface *net.Interface // Network interface to listen on for multicast, nil for default multicast interface
|
||||
Handler Handler // handler to invoke
|
||||
MaxMessageBytes int // maximum number of bytes to read from a packet, DefaultMaxMessageBytes if 0
|
||||
}
|
||||
|
||||
// ListenAndServe listens on the UDP network address srv.Addr. If srv.Multicast
|
||||
// is true, then a multicast UDP listener will be used on srv.Interface (or
|
||||
// default interface if nil).
|
||||
func (srv *Server) ListenAndServe() error {
|
||||
var err error
|
||||
|
||||
var addr *net.UDPAddr
|
||||
if addr, err = net.ResolveUDPAddr("udp", srv.Addr); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var conn net.PacketConn
|
||||
if srv.Multicast {
|
||||
if conn, err = net.ListenMulticastUDP("udp", srv.Interface, addr); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if conn, err = net.ListenUDP("udp", addr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return srv.Serve(conn)
|
||||
}
|
||||
|
||||
// Serve messages received on the given packet listener to the srv.Handler.
|
||||
func (srv *Server) Serve(l net.PacketConn) error {
|
||||
maxMessageBytes := DefaultMaxMessageBytes
|
||||
if srv.MaxMessageBytes != 0 {
|
||||
maxMessageBytes = srv.MaxMessageBytes
|
||||
}
|
||||
for {
|
||||
buf := make([]byte, maxMessageBytes)
|
||||
n, peerAddr, err := l.ReadFrom(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buf = buf[:n]
|
||||
|
||||
go func(buf []byte, peerAddr net.Addr) {
|
||||
// At least one router's UPnP implementation has added a trailing space
|
||||
// after "HTTP/1.1" - trim it.
|
||||
buf = trailingWhitespaceRx.ReplaceAllLiteral(buf, crlf)
|
||||
|
||||
req, err := http.ReadRequest(bufio.NewReader(bytes.NewBuffer(buf)))
|
||||
if err != nil {
|
||||
log.Printf("httpu: Failed to parse request: %v", err)
|
||||
return
|
||||
}
|
||||
req.RemoteAddr = peerAddr.String()
|
||||
srv.Handler.ServeMessage(req)
|
||||
// No need to call req.Body.Close - underlying reader is bytes.Buffer.
|
||||
}(buf, peerAddr)
|
||||
}
|
||||
}
|
||||
|
||||
// Serve messages received on the given packet listener to the given handler.
|
||||
func Serve(l net.PacketConn, handler Handler) error {
|
||||
srv := Server{
|
||||
Handler: handler,
|
||||
MaxMessageBytes: DefaultMaxMessageBytes,
|
||||
}
|
||||
return srv.Serve(l)
|
||||
}
|
||||
167
Godeps/_workspace/src/github.com/huin/goupnp/scpd/scpd.go
generated
vendored
Normal file
167
Godeps/_workspace/src/github.com/huin/goupnp/scpd/scpd.go
generated
vendored
Normal file
@ -0,0 +1,167 @@
|
||||
package scpd
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
SCPDXMLNamespace = "urn:schemas-upnp-org:service-1-0"
|
||||
)
|
||||
|
||||
func cleanWhitespace(s *string) {
|
||||
*s = strings.TrimSpace(*s)
|
||||
}
|
||||
|
||||
// SCPD is the service description as described by section 2.5 "Service
|
||||
// description" in
|
||||
// http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf
|
||||
type SCPD struct {
|
||||
XMLName xml.Name `xml:"scpd"`
|
||||
ConfigId string `xml:"configId,attr"`
|
||||
SpecVersion SpecVersion `xml:"specVersion"`
|
||||
Actions []Action `xml:"actionList>action"`
|
||||
StateVariables []StateVariable `xml:"serviceStateTable>stateVariable"`
|
||||
}
|
||||
|
||||
// Clean attempts to remove stray whitespace etc. in the structure. It seems
|
||||
// unfortunately common for stray whitespace to be present in SCPD documents,
|
||||
// this method attempts to make it easy to clean them out.
|
||||
func (scpd *SCPD) Clean() {
|
||||
cleanWhitespace(&scpd.ConfigId)
|
||||
for i := range scpd.Actions {
|
||||
scpd.Actions[i].clean()
|
||||
}
|
||||
for i := range scpd.StateVariables {
|
||||
scpd.StateVariables[i].clean()
|
||||
}
|
||||
}
|
||||
|
||||
func (scpd *SCPD) GetStateVariable(variable string) *StateVariable {
|
||||
for i := range scpd.StateVariables {
|
||||
v := &scpd.StateVariables[i]
|
||||
if v.Name == variable {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (scpd *SCPD) GetAction(action string) *Action {
|
||||
for i := range scpd.Actions {
|
||||
a := &scpd.Actions[i]
|
||||
if a.Name == action {
|
||||
return a
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SpecVersion is part of a SCPD document, describes the version of the
|
||||
// specification that the data adheres to.
|
||||
type SpecVersion struct {
|
||||
Major int32 `xml:"major"`
|
||||
Minor int32 `xml:"minor"`
|
||||
}
|
||||
|
||||
type Action struct {
|
||||
Name string `xml:"name"`
|
||||
Arguments []Argument `xml:"argumentList>argument"`
|
||||
}
|
||||
|
||||
func (action *Action) clean() {
|
||||
cleanWhitespace(&action.Name)
|
||||
for i := range action.Arguments {
|
||||
action.Arguments[i].clean()
|
||||
}
|
||||
}
|
||||
|
||||
func (action *Action) InputArguments() []*Argument {
|
||||
var result []*Argument
|
||||
for i := range action.Arguments {
|
||||
arg := &action.Arguments[i]
|
||||
if arg.IsInput() {
|
||||
result = append(result, arg)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (action *Action) OutputArguments() []*Argument {
|
||||
var result []*Argument
|
||||
for i := range action.Arguments {
|
||||
arg := &action.Arguments[i]
|
||||
if arg.IsOutput() {
|
||||
result = append(result, arg)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
type Argument struct {
|
||||
Name string `xml:"name"`
|
||||
Direction string `xml:"direction"` // in|out
|
||||
RelatedStateVariable string `xml:"relatedStateVariable"` // ?
|
||||
Retval string `xml:"retval"` // ?
|
||||
}
|
||||
|
||||
func (arg *Argument) clean() {
|
||||
cleanWhitespace(&arg.Name)
|
||||
cleanWhitespace(&arg.Direction)
|
||||
cleanWhitespace(&arg.RelatedStateVariable)
|
||||
cleanWhitespace(&arg.Retval)
|
||||
}
|
||||
|
||||
func (arg *Argument) IsInput() bool {
|
||||
return arg.Direction == "in"
|
||||
}
|
||||
|
||||
func (arg *Argument) IsOutput() bool {
|
||||
return arg.Direction == "out"
|
||||
}
|
||||
|
||||
type StateVariable struct {
|
||||
Name string `xml:"name"`
|
||||
SendEvents string `xml:"sendEvents,attr"` // yes|no
|
||||
Multicast string `xml:"multicast,attr"` // yes|no
|
||||
DataType DataType `xml:"dataType"`
|
||||
DefaultValue string `xml:"defaultValue"`
|
||||
AllowedValueRange *AllowedValueRange `xml:"allowedValueRange"`
|
||||
AllowedValues []string `xml:"allowedValueList>allowedValue"`
|
||||
}
|
||||
|
||||
func (v *StateVariable) clean() {
|
||||
cleanWhitespace(&v.Name)
|
||||
cleanWhitespace(&v.SendEvents)
|
||||
cleanWhitespace(&v.Multicast)
|
||||
v.DataType.clean()
|
||||
cleanWhitespace(&v.DefaultValue)
|
||||
if v.AllowedValueRange != nil {
|
||||
v.AllowedValueRange.clean()
|
||||
}
|
||||
for i := range v.AllowedValues {
|
||||
cleanWhitespace(&v.AllowedValues[i])
|
||||
}
|
||||
}
|
||||
|
||||
type AllowedValueRange struct {
|
||||
Minimum string `xml:"minimum"`
|
||||
Maximum string `xml:"maximum"`
|
||||
Step string `xml:"step"`
|
||||
}
|
||||
|
||||
func (r *AllowedValueRange) clean() {
|
||||
cleanWhitespace(&r.Minimum)
|
||||
cleanWhitespace(&r.Maximum)
|
||||
cleanWhitespace(&r.Step)
|
||||
}
|
||||
|
||||
type DataType struct {
|
||||
Name string `xml:",chardata"`
|
||||
Type string `xml:"type,attr"`
|
||||
}
|
||||
|
||||
func (dt *DataType) clean() {
|
||||
cleanWhitespace(&dt.Name)
|
||||
cleanWhitespace(&dt.Type)
|
||||
}
|
||||
56
Godeps/_workspace/src/github.com/huin/goupnp/service_client.go
generated
vendored
Normal file
56
Godeps/_workspace/src/github.com/huin/goupnp/service_client.go
generated
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
package goupnp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/huin/goupnp/soap"
|
||||
)
|
||||
|
||||
// ServiceClient is a SOAP client, root device and the service for the SOAP
|
||||
// client rolled into one value. The root device and service are intended to be
|
||||
// informational.
|
||||
type ServiceClient struct {
|
||||
SOAPClient *soap.SOAPClient
|
||||
RootDevice *RootDevice
|
||||
Service *Service
|
||||
}
|
||||
|
||||
func NewServiceClients(searchTarget string) (clients []ServiceClient, errors []error, err error) {
|
||||
var maybeRootDevices []MaybeRootDevice
|
||||
if maybeRootDevices, err = DiscoverDevices(searchTarget); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
clients = make([]ServiceClient, 0, len(maybeRootDevices))
|
||||
|
||||
for _, maybeRootDevice := range maybeRootDevices {
|
||||
if maybeRootDevice.Err != nil {
|
||||
errors = append(errors, maybeRootDevice.Err)
|
||||
continue
|
||||
}
|
||||
|
||||
device := &maybeRootDevice.Root.Device
|
||||
srvs := device.FindService(searchTarget)
|
||||
if len(srvs) == 0 {
|
||||
errors = append(errors, fmt.Errorf("goupnp: service %q not found within device %q (UDN=%q)",
|
||||
searchTarget, device.FriendlyName, device.UDN))
|
||||
continue
|
||||
}
|
||||
|
||||
for _, srv := range srvs {
|
||||
clients = append(clients, ServiceClient{
|
||||
SOAPClient: srv.NewSOAPClient(),
|
||||
RootDevice: maybeRootDevice.Root,
|
||||
Service: srv,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetServiceClient returns the ServiceClient itself. This is provided so that the
|
||||
// service client attributes can be accessed via an interface method on a
|
||||
// wrapping type.
|
||||
func (client *ServiceClient) GetServiceClient() *ServiceClient {
|
||||
return client
|
||||
}
|
||||
157
Godeps/_workspace/src/github.com/huin/goupnp/soap/soap.go
generated
vendored
Normal file
157
Godeps/_workspace/src/github.com/huin/goupnp/soap/soap.go
generated
vendored
Normal file
@ -0,0 +1,157 @@
|
||||
// Definition for the SOAP structure required for UPnP's SOAP usage.
|
||||
|
||||
package soap
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
const (
|
||||
soapEncodingStyle = "http://schemas.xmlsoap.org/soap/encoding/"
|
||||
soapPrefix = xml.Header + `<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body>`
|
||||
soapSuffix = `</s:Body></s:Envelope>`
|
||||
)
|
||||
|
||||
type SOAPClient struct {
|
||||
EndpointURL url.URL
|
||||
HTTPClient http.Client
|
||||
}
|
||||
|
||||
func NewSOAPClient(endpointURL url.URL) *SOAPClient {
|
||||
return &SOAPClient{
|
||||
EndpointURL: endpointURL,
|
||||
}
|
||||
}
|
||||
|
||||
// PerformSOAPAction makes a SOAP request, with the given action.
|
||||
// inAction and outAction must both be pointers to structs with string fields
|
||||
// only.
|
||||
func (client *SOAPClient) PerformAction(actionNamespace, actionName string, inAction interface{}, outAction interface{}) error {
|
||||
requestBytes, err := encodeRequestAction(actionNamespace, actionName, inAction)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
response, err := client.HTTPClient.Do(&http.Request{
|
||||
Method: "POST",
|
||||
URL: &client.EndpointURL,
|
||||
Header: http.Header{
|
||||
"SOAPACTION": []string{actionNamespace + "#" + actionName},
|
||||
"CONTENT-TYPE": []string{"text/xml; charset=\"utf-8\""},
|
||||
},
|
||||
Body: ioutil.NopCloser(bytes.NewBuffer(requestBytes)),
|
||||
// Set ContentLength to avoid chunked encoding - some servers might not support it.
|
||||
ContentLength: int64(len(requestBytes)),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("goupnp: error performing SOAP HTTP request: %v", err)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode != 200 {
|
||||
return fmt.Errorf("goupnp: SOAP request got HTTP %s", response.Status)
|
||||
}
|
||||
|
||||
responseEnv := newSOAPEnvelope()
|
||||
decoder := xml.NewDecoder(response.Body)
|
||||
if err := decoder.Decode(responseEnv); err != nil {
|
||||
return fmt.Errorf("goupnp: error decoding response body: %v", err)
|
||||
}
|
||||
|
||||
if responseEnv.Body.Fault != nil {
|
||||
return responseEnv.Body.Fault
|
||||
}
|
||||
|
||||
if outAction != nil {
|
||||
if err := xml.Unmarshal(responseEnv.Body.RawAction, outAction); err != nil {
|
||||
return fmt.Errorf("goupnp: error unmarshalling out action: %v, %v", err, responseEnv.Body.RawAction)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// newSOAPAction creates a soapEnvelope with the given action and arguments.
|
||||
func newSOAPEnvelope() *soapEnvelope {
|
||||
return &soapEnvelope{
|
||||
EncodingStyle: soapEncodingStyle,
|
||||
}
|
||||
}
|
||||
|
||||
// encodeRequestAction is a hacky way to create an encoded SOAP envelope
|
||||
// containing the given action. Experiments with one router have shown that it
|
||||
// 500s for requests where the outer default xmlns is set to the SOAP
|
||||
// namespace, and then reassigning the default namespace within that to the
|
||||
// service namespace. Hand-coding the outer XML to work-around this.
|
||||
func encodeRequestAction(actionNamespace, actionName string, inAction interface{}) ([]byte, error) {
|
||||
requestBuf := new(bytes.Buffer)
|
||||
requestBuf.WriteString(soapPrefix)
|
||||
requestBuf.WriteString(`<u:`)
|
||||
xml.EscapeText(requestBuf, []byte(actionName))
|
||||
requestBuf.WriteString(` xmlns:u="`)
|
||||
xml.EscapeText(requestBuf, []byte(actionNamespace))
|
||||
requestBuf.WriteString(`">`)
|
||||
if inAction != nil {
|
||||
if err := encodeRequestArgs(requestBuf, inAction); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
requestBuf.WriteString(`</u:`)
|
||||
xml.EscapeText(requestBuf, []byte(actionName))
|
||||
requestBuf.WriteString(`>`)
|
||||
requestBuf.WriteString(soapSuffix)
|
||||
return requestBuf.Bytes(), nil
|
||||
}
|
||||
|
||||
func encodeRequestArgs(w *bytes.Buffer, inAction interface{}) error {
|
||||
in := reflect.Indirect(reflect.ValueOf(inAction))
|
||||
if in.Kind() != reflect.Struct {
|
||||
return fmt.Errorf("goupnp: SOAP inAction is not a struct but of type %v", in.Type())
|
||||
}
|
||||
enc := xml.NewEncoder(w)
|
||||
nFields := in.NumField()
|
||||
inType := in.Type()
|
||||
for i := 0; i < nFields; i++ {
|
||||
field := inType.Field(i)
|
||||
argName := field.Name
|
||||
if nameOverride := field.Tag.Get("soap"); nameOverride != "" {
|
||||
argName = nameOverride
|
||||
}
|
||||
value := in.Field(i)
|
||||
if value.Kind() != reflect.String {
|
||||
return fmt.Errorf("goupnp: SOAP arg %q is not of type string, but of type %v", argName, value.Type())
|
||||
}
|
||||
if err := enc.EncodeElement(value.Interface(), xml.StartElement{xml.Name{"", argName}, nil}); err != nil {
|
||||
return fmt.Errorf("goupnp: error encoding SOAP arg %q: %v", argName, err)
|
||||
}
|
||||
}
|
||||
enc.Flush()
|
||||
return nil
|
||||
}
|
||||
|
||||
type soapEnvelope struct {
|
||||
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"`
|
||||
EncodingStyle string `xml:"http://schemas.xmlsoap.org/soap/envelope/ encodingStyle,attr"`
|
||||
Body soapBody `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"`
|
||||
}
|
||||
|
||||
type soapBody struct {
|
||||
Fault *SOAPFaultError `xml:"Fault"`
|
||||
RawAction []byte `xml:",innerxml"`
|
||||
}
|
||||
|
||||
// SOAPFaultError implements error, and contains SOAP fault information.
|
||||
type SOAPFaultError struct {
|
||||
FaultCode string `xml:"faultcode"`
|
||||
FaultString string `xml:"faultstring"`
|
||||
Detail string `xml:"detail"`
|
||||
}
|
||||
|
||||
func (err *SOAPFaultError) Error() string {
|
||||
return fmt.Sprintf("SOAP fault: %s", err.FaultString)
|
||||
}
|
||||
85
Godeps/_workspace/src/github.com/huin/goupnp/soap/soap_test.go
generated
vendored
Normal file
85
Godeps/_workspace/src/github.com/huin/goupnp/soap/soap_test.go
generated
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
package soap
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type capturingRoundTripper struct {
|
||||
err error
|
||||
resp *http.Response
|
||||
capturedReq *http.Request
|
||||
}
|
||||
|
||||
func (rt *capturingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
rt.capturedReq = req
|
||||
return rt.resp, rt.err
|
||||
}
|
||||
|
||||
func TestActionInputs(t *testing.T) {
|
||||
url, err := url.Parse("http://example.com/soap")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rt := &capturingRoundTripper{
|
||||
err: nil,
|
||||
resp: &http.Response{
|
||||
StatusCode: 200,
|
||||
Body: ioutil.NopCloser(bytes.NewBufferString(`
|
||||
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
|
||||
<s:Body>
|
||||
<u:myactionResponse xmlns:u="mynamespace">
|
||||
<A>valueA</A>
|
||||
<B>valueB</B>
|
||||
</u:myactionResponse>
|
||||
</s:Body>
|
||||
</s:Envelope>
|
||||
`)),
|
||||
},
|
||||
}
|
||||
client := SOAPClient{
|
||||
EndpointURL: *url,
|
||||
HTTPClient: http.Client{
|
||||
Transport: rt,
|
||||
},
|
||||
}
|
||||
|
||||
type In struct {
|
||||
Foo string
|
||||
Bar string `soap:"bar"`
|
||||
}
|
||||
type Out struct {
|
||||
A string
|
||||
B string
|
||||
}
|
||||
in := In{"foo", "bar"}
|
||||
gotOut := Out{}
|
||||
err = client.PerformAction("mynamespace", "myaction", &in, &gotOut)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
wantBody := (soapPrefix +
|
||||
`<u:myaction xmlns:u="mynamespace">` +
|
||||
`<Foo>foo</Foo>` +
|
||||
`<bar>bar</bar>` +
|
||||
`</u:myaction>` +
|
||||
soapSuffix)
|
||||
body, err := ioutil.ReadAll(rt.capturedReq.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
gotBody := string(body)
|
||||
if wantBody != gotBody {
|
||||
t.Errorf("Bad request body\nwant: %q\n got: %q", wantBody, gotBody)
|
||||
}
|
||||
|
||||
wantOut := Out{"valueA", "valueB"}
|
||||
if !reflect.DeepEqual(wantOut, gotOut) {
|
||||
t.Errorf("Bad output\nwant: %+v\n got: %+v", wantOut, gotOut)
|
||||
}
|
||||
}
|
||||
508
Godeps/_workspace/src/github.com/huin/goupnp/soap/types.go
generated
vendored
Normal file
508
Godeps/_workspace/src/github.com/huin/goupnp/soap/types.go
generated
vendored
Normal file
@ -0,0 +1,508 @@
|
||||
package soap
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
var (
|
||||
// localLoc acts like time.Local for this package, but is faked out by the
|
||||
// unit tests to ensure that things stay constant (especially when running
|
||||
// this test in a place where local time is UTC which might mask bugs).
|
||||
localLoc = time.Local
|
||||
)
|
||||
|
||||
func MarshalUi1(v uint8) (string, error) {
|
||||
return strconv.FormatUint(uint64(v), 10), nil
|
||||
}
|
||||
|
||||
func UnmarshalUi1(s string) (uint8, error) {
|
||||
v, err := strconv.ParseUint(s, 10, 8)
|
||||
return uint8(v), err
|
||||
}
|
||||
|
||||
func MarshalUi2(v uint16) (string, error) {
|
||||
return strconv.FormatUint(uint64(v), 10), nil
|
||||
}
|
||||
|
||||
func UnmarshalUi2(s string) (uint16, error) {
|
||||
v, err := strconv.ParseUint(s, 10, 16)
|
||||
return uint16(v), err
|
||||
}
|
||||
|
||||
func MarshalUi4(v uint32) (string, error) {
|
||||
return strconv.FormatUint(uint64(v), 10), nil
|
||||
}
|
||||
|
||||
func UnmarshalUi4(s string) (uint32, error) {
|
||||
v, err := strconv.ParseUint(s, 10, 32)
|
||||
return uint32(v), err
|
||||
}
|
||||
|
||||
func MarshalI1(v int8) (string, error) {
|
||||
return strconv.FormatInt(int64(v), 10), nil
|
||||
}
|
||||
|
||||
func UnmarshalI1(s string) (int8, error) {
|
||||
v, err := strconv.ParseInt(s, 10, 8)
|
||||
return int8(v), err
|
||||
}
|
||||
|
||||
func MarshalI2(v int16) (string, error) {
|
||||
return strconv.FormatInt(int64(v), 10), nil
|
||||
}
|
||||
|
||||
func UnmarshalI2(s string) (int16, error) {
|
||||
v, err := strconv.ParseInt(s, 10, 16)
|
||||
return int16(v), err
|
||||
}
|
||||
|
||||
func MarshalI4(v int32) (string, error) {
|
||||
return strconv.FormatInt(int64(v), 10), nil
|
||||
}
|
||||
|
||||
func UnmarshalI4(s string) (int32, error) {
|
||||
v, err := strconv.ParseInt(s, 10, 32)
|
||||
return int32(v), err
|
||||
}
|
||||
|
||||
func MarshalInt(v int64) (string, error) {
|
||||
return strconv.FormatInt(v, 10), nil
|
||||
}
|
||||
|
||||
func UnmarshalInt(s string) (int64, error) {
|
||||
return strconv.ParseInt(s, 10, 64)
|
||||
}
|
||||
|
||||
func MarshalR4(v float32) (string, error) {
|
||||
return strconv.FormatFloat(float64(v), 'G', -1, 32), nil
|
||||
}
|
||||
|
||||
func UnmarshalR4(s string) (float32, error) {
|
||||
v, err := strconv.ParseFloat(s, 32)
|
||||
return float32(v), err
|
||||
}
|
||||
|
||||
func MarshalR8(v float64) (string, error) {
|
||||
return strconv.FormatFloat(v, 'G', -1, 64), nil
|
||||
}
|
||||
|
||||
func UnmarshalR8(s string) (float64, error) {
|
||||
v, err := strconv.ParseFloat(s, 64)
|
||||
return float64(v), err
|
||||
}
|
||||
|
||||
// MarshalFixed14_4 marshals float64 to SOAP "fixed.14.4" type.
|
||||
func MarshalFixed14_4(v float64) (string, error) {
|
||||
if v >= 1e14 || v <= -1e14 {
|
||||
return "", fmt.Errorf("soap fixed14.4: value %v out of bounds", v)
|
||||
}
|
||||
return strconv.FormatFloat(v, 'f', 4, 64), nil
|
||||
}
|
||||
|
||||
// UnmarshalFixed14_4 unmarshals float64 from SOAP "fixed.14.4" type.
|
||||
func UnmarshalFixed14_4(s string) (float64, error) {
|
||||
v, err := strconv.ParseFloat(s, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if v >= 1e14 || v <= -1e14 {
|
||||
return 0, fmt.Errorf("soap fixed14.4: value %q out of bounds", s)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// MarshalChar marshals rune to SOAP "char" type.
|
||||
func MarshalChar(v rune) (string, error) {
|
||||
if v == 0 {
|
||||
return "", errors.New("soap char: rune 0 is not allowed")
|
||||
}
|
||||
return string(v), nil
|
||||
}
|
||||
|
||||
// UnmarshalChar unmarshals rune from SOAP "char" type.
|
||||
func UnmarshalChar(s string) (rune, error) {
|
||||
if len(s) == 0 {
|
||||
return 0, errors.New("soap char: got empty string")
|
||||
}
|
||||
r, n := utf8.DecodeRune([]byte(s))
|
||||
if n != len(s) {
|
||||
return 0, fmt.Errorf("soap char: value %q is not a single rune", s)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func MarshalString(v string) (string, error) {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func UnmarshalString(v string) (string, error) {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func parseInt(s string, err *error) int {
|
||||
v, parseErr := strconv.ParseInt(s, 10, 64)
|
||||
if parseErr != nil {
|
||||
*err = parseErr
|
||||
}
|
||||
return int(v)
|
||||
}
|
||||
|
||||
var dateRegexps = []*regexp.Regexp{
|
||||
// yyyy[-mm[-dd]]
|
||||
regexp.MustCompile(`^(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?$`),
|
||||
// yyyy[mm[dd]]
|
||||
regexp.MustCompile(`^(\d{4})(?:(\d{2})(?:(\d{2}))?)?$`),
|
||||
}
|
||||
|
||||
func parseDateParts(s string) (year, month, day int, err error) {
|
||||
var parts []string
|
||||
for _, re := range dateRegexps {
|
||||
parts = re.FindStringSubmatch(s)
|
||||
if parts != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if parts == nil {
|
||||
err = fmt.Errorf("soap date: value %q is not in a recognized ISO8601 date format", s)
|
||||
return
|
||||
}
|
||||
|
||||
year = parseInt(parts[1], &err)
|
||||
month = 1
|
||||
day = 1
|
||||
if len(parts[2]) != 0 {
|
||||
month = parseInt(parts[2], &err)
|
||||
if len(parts[3]) != 0 {
|
||||
day = parseInt(parts[3], &err)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err = fmt.Errorf("soap date: %q: %v", s, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var timeRegexps = []*regexp.Regexp{
|
||||
// hh[:mm[:ss]]
|
||||
regexp.MustCompile(`^(\d{2})(?::(\d{2})(?::(\d{2}))?)?$`),
|
||||
// hh[mm[ss]]
|
||||
regexp.MustCompile(`^(\d{2})(?:(\d{2})(?:(\d{2}))?)?$`),
|
||||
}
|
||||
|
||||
func parseTimeParts(s string) (hour, minute, second int, err error) {
|
||||
var parts []string
|
||||
for _, re := range timeRegexps {
|
||||
parts = re.FindStringSubmatch(s)
|
||||
if parts != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if parts == nil {
|
||||
err = fmt.Errorf("soap time: value %q is not in ISO8601 time format", s)
|
||||
return
|
||||
}
|
||||
|
||||
hour = parseInt(parts[1], &err)
|
||||
if len(parts[2]) != 0 {
|
||||
minute = parseInt(parts[2], &err)
|
||||
if len(parts[3]) != 0 {
|
||||
second = parseInt(parts[3], &err)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err = fmt.Errorf("soap time: %q: %v", s, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// (+|-)hh[[:]mm]
|
||||
var timezoneRegexp = regexp.MustCompile(`^([+-])(\d{2})(?::?(\d{2}))?$`)
|
||||
|
||||
func parseTimezone(s string) (offset int, err error) {
|
||||
if s == "Z" {
|
||||
return 0, nil
|
||||
}
|
||||
parts := timezoneRegexp.FindStringSubmatch(s)
|
||||
if parts == nil {
|
||||
err = fmt.Errorf("soap timezone: value %q is not in ISO8601 timezone format", s)
|
||||
return
|
||||
}
|
||||
|
||||
offset = parseInt(parts[2], &err) * 3600
|
||||
if len(parts[3]) != 0 {
|
||||
offset += parseInt(parts[3], &err) * 60
|
||||
}
|
||||
if parts[1] == "-" {
|
||||
offset = -offset
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err = fmt.Errorf("soap timezone: %q: %v", s, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var completeDateTimeZoneRegexp = regexp.MustCompile(`^([^T]+)(?:T([^-+Z]+)(.+)?)?$`)
|
||||
|
||||
// splitCompleteDateTimeZone splits date, time and timezone apart from an
|
||||
// ISO8601 string. It does not ensure that the contents of each part are
|
||||
// correct, it merely splits on certain delimiters.
|
||||
// e.g "2010-09-08T12:15:10+0700" => "2010-09-08", "12:15:10", "+0700".
|
||||
// Timezone can only be present if time is also present.
|
||||
func splitCompleteDateTimeZone(s string) (dateStr, timeStr, zoneStr string, err error) {
|
||||
parts := completeDateTimeZoneRegexp.FindStringSubmatch(s)
|
||||
if parts == nil {
|
||||
err = fmt.Errorf("soap date/time/zone: value %q is not in ISO8601 datetime format", s)
|
||||
return
|
||||
}
|
||||
dateStr = parts[1]
|
||||
timeStr = parts[2]
|
||||
zoneStr = parts[3]
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalDate marshals time.Time to SOAP "date" type. Note that this converts
|
||||
// to local time, and discards the time-of-day components.
|
||||
func MarshalDate(v time.Time) (string, error) {
|
||||
return v.In(localLoc).Format("2006-01-02"), nil
|
||||
}
|
||||
|
||||
var dateFmts = []string{"2006-01-02", "20060102"}
|
||||
|
||||
// UnmarshalDate unmarshals time.Time from SOAP "date" type. This outputs the
|
||||
// date as midnight in the local time zone.
|
||||
func UnmarshalDate(s string) (time.Time, error) {
|
||||
year, month, day, err := parseDateParts(s)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
return time.Date(year, time.Month(month), day, 0, 0, 0, 0, localLoc), nil
|
||||
}
|
||||
|
||||
// TimeOfDay is used in cases where SOAP "time" or "time.tz" is used.
|
||||
type TimeOfDay struct {
|
||||
// Duration of time since midnight.
|
||||
FromMidnight time.Duration
|
||||
|
||||
// Set to true if Offset is specified. If false, then the timezone is
|
||||
// unspecified (and by ISO8601 - implies some "local" time).
|
||||
HasOffset bool
|
||||
|
||||
// Offset is non-zero only if time.tz is used. It is otherwise ignored. If
|
||||
// non-zero, then it is regarded as a UTC offset in seconds. Note that the
|
||||
// sub-minutes is ignored by the marshal function.
|
||||
Offset int
|
||||
}
|
||||
|
||||
// MarshalTimeOfDay marshals TimeOfDay to the "time" type.
|
||||
func MarshalTimeOfDay(v TimeOfDay) (string, error) {
|
||||
d := int64(v.FromMidnight / time.Second)
|
||||
hour := d / 3600
|
||||
d = d % 3600
|
||||
minute := d / 60
|
||||
second := d % 60
|
||||
|
||||
return fmt.Sprintf("%02d:%02d:%02d", hour, minute, second), nil
|
||||
}
|
||||
|
||||
// UnmarshalTimeOfDay unmarshals TimeOfDay from the "time" type.
|
||||
func UnmarshalTimeOfDay(s string) (TimeOfDay, error) {
|
||||
t, err := UnmarshalTimeOfDayTz(s)
|
||||
if err != nil {
|
||||
return TimeOfDay{}, err
|
||||
} else if t.HasOffset {
|
||||
return TimeOfDay{}, fmt.Errorf("soap time: value %q contains unexpected timezone")
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// MarshalTimeOfDayTz marshals TimeOfDay to the "time.tz" type.
|
||||
func MarshalTimeOfDayTz(v TimeOfDay) (string, error) {
|
||||
d := int64(v.FromMidnight / time.Second)
|
||||
hour := d / 3600
|
||||
d = d % 3600
|
||||
minute := d / 60
|
||||
second := d % 60
|
||||
|
||||
tz := ""
|
||||
if v.HasOffset {
|
||||
if v.Offset == 0 {
|
||||
tz = "Z"
|
||||
} else {
|
||||
offsetMins := v.Offset / 60
|
||||
sign := '+'
|
||||
if offsetMins < 1 {
|
||||
offsetMins = -offsetMins
|
||||
sign = '-'
|
||||
}
|
||||
tz = fmt.Sprintf("%c%02d:%02d", sign, offsetMins/60, offsetMins%60)
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%02d:%02d:%02d%s", hour, minute, second, tz), nil
|
||||
}
|
||||
|
||||
// UnmarshalTimeOfDayTz unmarshals TimeOfDay from the "time.tz" type.
|
||||
func UnmarshalTimeOfDayTz(s string) (tod TimeOfDay, err error) {
|
||||
zoneIndex := strings.IndexAny(s, "Z+-")
|
||||
var timePart string
|
||||
var hasOffset bool
|
||||
var offset int
|
||||
if zoneIndex == -1 {
|
||||
hasOffset = false
|
||||
timePart = s
|
||||
} else {
|
||||
hasOffset = true
|
||||
timePart = s[:zoneIndex]
|
||||
if offset, err = parseTimezone(s[zoneIndex:]); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
hour, minute, second, err := parseTimeParts(timePart)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fromMidnight := time.Duration(hour*3600+minute*60+second) * time.Second
|
||||
|
||||
// ISO8601 special case - values up to 24:00:00 are allowed, so using
|
||||
// strictly greater-than for the maximum value.
|
||||
if fromMidnight > 24*time.Hour || minute >= 60 || second >= 60 {
|
||||
return TimeOfDay{}, fmt.Errorf("soap time.tz: value %q has value(s) out of range", s)
|
||||
}
|
||||
|
||||
return TimeOfDay{
|
||||
FromMidnight: time.Duration(hour*3600+minute*60+second) * time.Second,
|
||||
HasOffset: hasOffset,
|
||||
Offset: offset,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// MarshalDateTime marshals time.Time to SOAP "dateTime" type. Note that this
|
||||
// converts to local time.
|
||||
func MarshalDateTime(v time.Time) (string, error) {
|
||||
return v.In(localLoc).Format("2006-01-02T15:04:05"), nil
|
||||
}
|
||||
|
||||
// UnmarshalDateTime unmarshals time.Time from the SOAP "dateTime" type. This
|
||||
// returns a value in the local timezone.
|
||||
func UnmarshalDateTime(s string) (result time.Time, err error) {
|
||||
dateStr, timeStr, zoneStr, err := splitCompleteDateTimeZone(s)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(zoneStr) != 0 {
|
||||
err = fmt.Errorf("soap datetime: unexpected timezone in %q", s)
|
||||
return
|
||||
}
|
||||
|
||||
year, month, day, err := parseDateParts(dateStr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var hour, minute, second int
|
||||
if len(timeStr) != 0 {
|
||||
hour, minute, second, err = parseTimeParts(timeStr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
result = time.Date(year, time.Month(month), day, hour, minute, second, 0, localLoc)
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalDateTimeTz marshals time.Time to SOAP "dateTime.tz" type.
|
||||
func MarshalDateTimeTz(v time.Time) (string, error) {
|
||||
return v.Format("2006-01-02T15:04:05-07:00"), nil
|
||||
}
|
||||
|
||||
// UnmarshalDateTimeTz unmarshals time.Time from the SOAP "dateTime.tz" type.
|
||||
// This returns a value in the local timezone when the timezone is unspecified.
|
||||
func UnmarshalDateTimeTz(s string) (result time.Time, err error) {
|
||||
dateStr, timeStr, zoneStr, err := splitCompleteDateTimeZone(s)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
year, month, day, err := parseDateParts(dateStr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var hour, minute, second int
|
||||
var location *time.Location = localLoc
|
||||
if len(timeStr) != 0 {
|
||||
hour, minute, second, err = parseTimeParts(timeStr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(zoneStr) != 0 {
|
||||
var offset int
|
||||
offset, err = parseTimezone(zoneStr)
|
||||
if offset == 0 {
|
||||
location = time.UTC
|
||||
} else {
|
||||
location = time.FixedZone("", offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = time.Date(year, time.Month(month), day, hour, minute, second, 0, location)
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalBoolean marshals bool to SOAP "boolean" type.
|
||||
func MarshalBoolean(v bool) (string, error) {
|
||||
if v {
|
||||
return "1", nil
|
||||
}
|
||||
return "0", nil
|
||||
}
|
||||
|
||||
// UnmarshalBoolean unmarshals bool from the SOAP "boolean" type.
|
||||
func UnmarshalBoolean(s string) (bool, error) {
|
||||
switch s {
|
||||
case "0", "false", "no":
|
||||
return false, nil
|
||||
case "1", "true", "yes":
|
||||
return true, nil
|
||||
}
|
||||
return false, fmt.Errorf("soap boolean: %q is not a valid boolean value", s)
|
||||
}
|
||||
|
||||
// MarshalBinBase64 marshals []byte to SOAP "bin.base64" type.
|
||||
func MarshalBinBase64(v []byte) (string, error) {
|
||||
return base64.StdEncoding.EncodeToString(v), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinBase64 unmarshals []byte from the SOAP "bin.base64" type.
|
||||
func UnmarshalBinBase64(s string) ([]byte, error) {
|
||||
return base64.StdEncoding.DecodeString(s)
|
||||
}
|
||||
|
||||
// MarshalBinHex marshals []byte to SOAP "bin.hex" type.
|
||||
func MarshalBinHex(v []byte) (string, error) {
|
||||
return hex.EncodeToString(v), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinHex unmarshals []byte from the SOAP "bin.hex" type.
|
||||
func UnmarshalBinHex(s string) ([]byte, error) {
|
||||
return hex.DecodeString(s)
|
||||
}
|
||||
481
Godeps/_workspace/src/github.com/huin/goupnp/soap/types_test.go
generated
vendored
Normal file
481
Godeps/_workspace/src/github.com/huin/goupnp/soap/types_test.go
generated
vendored
Normal file
@ -0,0 +1,481 @@
|
||||
package soap
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type convTest interface {
|
||||
Marshal() (string, error)
|
||||
Unmarshal(string) (interface{}, error)
|
||||
Equal(result interface{}) bool
|
||||
}
|
||||
|
||||
// duper is an interface that convTest values may optionally also implement to
|
||||
// generate another convTest for a value in an otherwise identical testCase.
|
||||
type duper interface {
|
||||
Dupe(tag string) []convTest
|
||||
}
|
||||
|
||||
type testCase struct {
|
||||
value convTest
|
||||
str string
|
||||
wantMarshalErr bool
|
||||
wantUnmarshalErr bool
|
||||
noMarshal bool
|
||||
noUnMarshal bool
|
||||
tag string
|
||||
}
|
||||
|
||||
type Ui1Test uint8
|
||||
|
||||
func (v Ui1Test) Marshal() (string, error) {
|
||||
return MarshalUi1(uint8(v))
|
||||
}
|
||||
func (v Ui1Test) Unmarshal(s string) (interface{}, error) {
|
||||
return UnmarshalUi1(s)
|
||||
}
|
||||
func (v Ui1Test) Equal(result interface{}) bool {
|
||||
return uint8(v) == result.(uint8)
|
||||
}
|
||||
func (v Ui1Test) Dupe(tag string) []convTest {
|
||||
if tag == "dupe" {
|
||||
return []convTest{
|
||||
Ui2Test(v),
|
||||
Ui4Test(v),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Ui2Test uint16
|
||||
|
||||
func (v Ui2Test) Marshal() (string, error) {
|
||||
return MarshalUi2(uint16(v))
|
||||
}
|
||||
func (v Ui2Test) Unmarshal(s string) (interface{}, error) {
|
||||
return UnmarshalUi2(s)
|
||||
}
|
||||
func (v Ui2Test) Equal(result interface{}) bool {
|
||||
return uint16(v) == result.(uint16)
|
||||
}
|
||||
|
||||
type Ui4Test uint32
|
||||
|
||||
func (v Ui4Test) Marshal() (string, error) {
|
||||
return MarshalUi4(uint32(v))
|
||||
}
|
||||
func (v Ui4Test) Unmarshal(s string) (interface{}, error) {
|
||||
return UnmarshalUi4(s)
|
||||
}
|
||||
func (v Ui4Test) Equal(result interface{}) bool {
|
||||
return uint32(v) == result.(uint32)
|
||||
}
|
||||
|
||||
type I1Test int8
|
||||
|
||||
func (v I1Test) Marshal() (string, error) {
|
||||
return MarshalI1(int8(v))
|
||||
}
|
||||
func (v I1Test) Unmarshal(s string) (interface{}, error) {
|
||||
return UnmarshalI1(s)
|
||||
}
|
||||
func (v I1Test) Equal(result interface{}) bool {
|
||||
return int8(v) == result.(int8)
|
||||
}
|
||||
func (v I1Test) Dupe(tag string) []convTest {
|
||||
if tag == "dupe" {
|
||||
return []convTest{
|
||||
I2Test(v),
|
||||
I4Test(v),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type I2Test int16
|
||||
|
||||
func (v I2Test) Marshal() (string, error) {
|
||||
return MarshalI2(int16(v))
|
||||
}
|
||||
func (v I2Test) Unmarshal(s string) (interface{}, error) {
|
||||
return UnmarshalI2(s)
|
||||
}
|
||||
func (v I2Test) Equal(result interface{}) bool {
|
||||
return int16(v) == result.(int16)
|
||||
}
|
||||
|
||||
type I4Test int32
|
||||
|
||||
func (v I4Test) Marshal() (string, error) {
|
||||
return MarshalI4(int32(v))
|
||||
}
|
||||
func (v I4Test) Unmarshal(s string) (interface{}, error) {
|
||||
return UnmarshalI4(s)
|
||||
}
|
||||
func (v I4Test) Equal(result interface{}) bool {
|
||||
return int32(v) == result.(int32)
|
||||
}
|
||||
|
||||
type IntTest int64
|
||||
|
||||
func (v IntTest) Marshal() (string, error) {
|
||||
return MarshalInt(int64(v))
|
||||
}
|
||||
func (v IntTest) Unmarshal(s string) (interface{}, error) {
|
||||
return UnmarshalInt(s)
|
||||
}
|
||||
func (v IntTest) Equal(result interface{}) bool {
|
||||
return int64(v) == result.(int64)
|
||||
}
|
||||
|
||||
type Fixed14_4Test float64
|
||||
|
||||
func (v Fixed14_4Test) Marshal() (string, error) {
|
||||
return MarshalFixed14_4(float64(v))
|
||||
}
|
||||
func (v Fixed14_4Test) Unmarshal(s string) (interface{}, error) {
|
||||
return UnmarshalFixed14_4(s)
|
||||
}
|
||||
func (v Fixed14_4Test) Equal(result interface{}) bool {
|
||||
return math.Abs(float64(v)-result.(float64)) < 0.001
|
||||
}
|
||||
|
||||
type CharTest rune
|
||||
|
||||
func (v CharTest) Marshal() (string, error) {
|
||||
return MarshalChar(rune(v))
|
||||
}
|
||||
func (v CharTest) Unmarshal(s string) (interface{}, error) {
|
||||
return UnmarshalChar(s)
|
||||
}
|
||||
func (v CharTest) Equal(result interface{}) bool {
|
||||
return rune(v) == result.(rune)
|
||||
}
|
||||
|
||||
type DateTest struct{ time.Time }
|
||||
|
||||
func (v DateTest) Marshal() (string, error) {
|
||||
return MarshalDate(time.Time(v.Time))
|
||||
}
|
||||
func (v DateTest) Unmarshal(s string) (interface{}, error) {
|
||||
return UnmarshalDate(s)
|
||||
}
|
||||
func (v DateTest) Equal(result interface{}) bool {
|
||||
return v.Time.Equal(result.(time.Time))
|
||||
}
|
||||
func (v DateTest) Dupe(tag string) []convTest {
|
||||
if tag != "no:dateTime" {
|
||||
return []convTest{DateTimeTest{v.Time}}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type TimeOfDayTest struct {
|
||||
TimeOfDay
|
||||
}
|
||||
|
||||
func (v TimeOfDayTest) Marshal() (string, error) {
|
||||
return MarshalTimeOfDay(v.TimeOfDay)
|
||||
}
|
||||
func (v TimeOfDayTest) Unmarshal(s string) (interface{}, error) {
|
||||
return UnmarshalTimeOfDay(s)
|
||||
}
|
||||
func (v TimeOfDayTest) Equal(result interface{}) bool {
|
||||
return v.TimeOfDay == result.(TimeOfDay)
|
||||
}
|
||||
func (v TimeOfDayTest) Dupe(tag string) []convTest {
|
||||
if tag != "no:time.tz" {
|
||||
return []convTest{TimeOfDayTzTest{v.TimeOfDay}}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type TimeOfDayTzTest struct {
|
||||
TimeOfDay
|
||||
}
|
||||
|
||||
func (v TimeOfDayTzTest) Marshal() (string, error) {
|
||||
return MarshalTimeOfDayTz(v.TimeOfDay)
|
||||
}
|
||||
func (v TimeOfDayTzTest) Unmarshal(s string) (interface{}, error) {
|
||||
return UnmarshalTimeOfDayTz(s)
|
||||
}
|
||||
func (v TimeOfDayTzTest) Equal(result interface{}) bool {
|
||||
return v.TimeOfDay == result.(TimeOfDay)
|
||||
}
|
||||
|
||||
type DateTimeTest struct{ time.Time }
|
||||
|
||||
func (v DateTimeTest) Marshal() (string, error) {
|
||||
return MarshalDateTime(time.Time(v.Time))
|
||||
}
|
||||
func (v DateTimeTest) Unmarshal(s string) (interface{}, error) {
|
||||
return UnmarshalDateTime(s)
|
||||
}
|
||||
func (v DateTimeTest) Equal(result interface{}) bool {
|
||||
return v.Time.Equal(result.(time.Time))
|
||||
}
|
||||
func (v DateTimeTest) Dupe(tag string) []convTest {
|
||||
if tag != "no:dateTime.tz" {
|
||||
return []convTest{DateTimeTzTest{v.Time}}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type DateTimeTzTest struct{ time.Time }
|
||||
|
||||
func (v DateTimeTzTest) Marshal() (string, error) {
|
||||
return MarshalDateTimeTz(time.Time(v.Time))
|
||||
}
|
||||
func (v DateTimeTzTest) Unmarshal(s string) (interface{}, error) {
|
||||
return UnmarshalDateTimeTz(s)
|
||||
}
|
||||
func (v DateTimeTzTest) Equal(result interface{}) bool {
|
||||
return v.Time.Equal(result.(time.Time))
|
||||
}
|
||||
|
||||
type BooleanTest bool
|
||||
|
||||
func (v BooleanTest) Marshal() (string, error) {
|
||||
return MarshalBoolean(bool(v))
|
||||
}
|
||||
func (v BooleanTest) Unmarshal(s string) (interface{}, error) {
|
||||
return UnmarshalBoolean(s)
|
||||
}
|
||||
func (v BooleanTest) Equal(result interface{}) bool {
|
||||
return bool(v) == result.(bool)
|
||||
}
|
||||
|
||||
type BinBase64Test []byte
|
||||
|
||||
func (v BinBase64Test) Marshal() (string, error) {
|
||||
return MarshalBinBase64([]byte(v))
|
||||
}
|
||||
func (v BinBase64Test) Unmarshal(s string) (interface{}, error) {
|
||||
return UnmarshalBinBase64(s)
|
||||
}
|
||||
func (v BinBase64Test) Equal(result interface{}) bool {
|
||||
return bytes.Equal([]byte(v), result.([]byte))
|
||||
}
|
||||
|
||||
type BinHexTest []byte
|
||||
|
||||
func (v BinHexTest) Marshal() (string, error) {
|
||||
return MarshalBinHex([]byte(v))
|
||||
}
|
||||
func (v BinHexTest) Unmarshal(s string) (interface{}, error) {
|
||||
return UnmarshalBinHex(s)
|
||||
}
|
||||
func (v BinHexTest) Equal(result interface{}) bool {
|
||||
return bytes.Equal([]byte(v), result.([]byte))
|
||||
}
|
||||
|
||||
func Test(t *testing.T) {
|
||||
const time010203 time.Duration = (1*3600 + 2*60 + 3) * time.Second
|
||||
const time0102 time.Duration = (1*3600 + 2*60) * time.Second
|
||||
const time01 time.Duration = (1 * 3600) * time.Second
|
||||
const time235959 time.Duration = (23*3600 + 59*60 + 59) * time.Second
|
||||
|
||||
// Fake out the local time for the implementation.
|
||||
localLoc = time.FixedZone("Fake/Local", 6*3600)
|
||||
defer func() {
|
||||
localLoc = time.Local
|
||||
}()
|
||||
|
||||
tests := []testCase{
|
||||
// ui1
|
||||
{str: "", value: Ui1Test(0), wantUnmarshalErr: true, noMarshal: true, tag: "dupe"},
|
||||
{str: " ", value: Ui1Test(0), wantUnmarshalErr: true, noMarshal: true, tag: "dupe"},
|
||||
{str: "abc", value: Ui1Test(0), wantUnmarshalErr: true, noMarshal: true, tag: "dupe"},
|
||||
{str: "-1", value: Ui1Test(0), wantUnmarshalErr: true, noMarshal: true, tag: "dupe"},
|
||||
{str: "0", value: Ui1Test(0), tag: "dupe"},
|
||||
{str: "1", value: Ui1Test(1), tag: "dupe"},
|
||||
{str: "255", value: Ui1Test(255), tag: "dupe"},
|
||||
{str: "256", value: Ui1Test(0), wantUnmarshalErr: true, noMarshal: true},
|
||||
|
||||
// ui2
|
||||
{str: "65535", value: Ui2Test(65535)},
|
||||
{str: "65536", value: Ui2Test(0), wantUnmarshalErr: true, noMarshal: true},
|
||||
|
||||
// ui4
|
||||
{str: "4294967295", value: Ui4Test(4294967295)},
|
||||
{str: "4294967296", value: Ui4Test(0), wantUnmarshalErr: true, noMarshal: true},
|
||||
|
||||
// i1
|
||||
{str: "", value: I1Test(0), wantUnmarshalErr: true, noMarshal: true, tag: "dupe"},
|
||||
{str: " ", value: I1Test(0), wantUnmarshalErr: true, noMarshal: true, tag: "dupe"},
|
||||
{str: "abc", value: I1Test(0), wantUnmarshalErr: true, noMarshal: true, tag: "dupe"},
|
||||
{str: "0", value: I1Test(0), tag: "dupe"},
|
||||
{str: "-1", value: I1Test(-1), tag: "dupe"},
|
||||
{str: "127", value: I1Test(127), tag: "dupe"},
|
||||
{str: "-128", value: I1Test(-128), tag: "dupe"},
|
||||
{str: "128", value: I1Test(0), wantUnmarshalErr: true, noMarshal: true},
|
||||
{str: "-129", value: I1Test(0), wantUnmarshalErr: true, noMarshal: true},
|
||||
|
||||
// i2
|
||||
{str: "32767", value: I2Test(32767)},
|
||||
{str: "-32768", value: I2Test(-32768)},
|
||||
{str: "32768", value: I2Test(0), wantUnmarshalErr: true, noMarshal: true},
|
||||
{str: "-32769", value: I2Test(0), wantUnmarshalErr: true, noMarshal: true},
|
||||
|
||||
// i4
|
||||
{str: "2147483647", value: I4Test(2147483647)},
|
||||
{str: "-2147483648", value: I4Test(-2147483648)},
|
||||
{str: "2147483648", value: I4Test(0), wantUnmarshalErr: true, noMarshal: true},
|
||||
{str: "-2147483649", value: I4Test(0), wantUnmarshalErr: true, noMarshal: true},
|
||||
|
||||
// int
|
||||
{str: "9223372036854775807", value: IntTest(9223372036854775807)},
|
||||
{str: "-9223372036854775808", value: IntTest(-9223372036854775808)},
|
||||
{str: "9223372036854775808", value: IntTest(0), wantUnmarshalErr: true, noMarshal: true},
|
||||
{str: "-9223372036854775809", value: IntTest(0), wantUnmarshalErr: true, noMarshal: true},
|
||||
|
||||
// fixed.14.4
|
||||
{str: "0.0000", value: Fixed14_4Test(0)},
|
||||
{str: "1.0000", value: Fixed14_4Test(1)},
|
||||
{str: "1.2346", value: Fixed14_4Test(1.23456)},
|
||||
{str: "-1.0000", value: Fixed14_4Test(-1)},
|
||||
{str: "-1.2346", value: Fixed14_4Test(-1.23456)},
|
||||
{str: "10000000000000.0000", value: Fixed14_4Test(1e13)},
|
||||
{str: "100000000000000.0000", value: Fixed14_4Test(1e14), wantMarshalErr: true, wantUnmarshalErr: true},
|
||||
{str: "-10000000000000.0000", value: Fixed14_4Test(-1e13)},
|
||||
{str: "-100000000000000.0000", value: Fixed14_4Test(-1e14), wantMarshalErr: true, wantUnmarshalErr: true},
|
||||
|
||||
// char
|
||||
{str: "a", value: CharTest('a')},
|
||||
{str: "z", value: CharTest('z')},
|
||||
{str: "\u1234", value: CharTest(0x1234)},
|
||||
{str: "aa", value: CharTest(0), wantMarshalErr: true, wantUnmarshalErr: true},
|
||||
{str: "", value: CharTest(0), wantMarshalErr: true, wantUnmarshalErr: true},
|
||||
|
||||
// date
|
||||
{str: "2013-10-08", value: DateTest{time.Date(2013, 10, 8, 0, 0, 0, 0, localLoc)}, tag: "no:dateTime"},
|
||||
{str: "20131008", value: DateTest{time.Date(2013, 10, 8, 0, 0, 0, 0, localLoc)}, noMarshal: true, tag: "no:dateTime"},
|
||||
{str: "2013-10-08T10:30:50", value: DateTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:dateTime"},
|
||||
{str: "2013-10-08T10:30:50Z", value: DateTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:dateTime"},
|
||||
{str: "", value: DateTest{}, wantMarshalErr: true, wantUnmarshalErr: true, noMarshal: true},
|
||||
{str: "-1", value: DateTest{}, wantUnmarshalErr: true, noMarshal: true},
|
||||
|
||||
// time
|
||||
{str: "00:00:00", value: TimeOfDayTest{TimeOfDay{FromMidnight: 0}}},
|
||||
{str: "000000", value: TimeOfDayTest{TimeOfDay{FromMidnight: 0}}, noMarshal: true},
|
||||
{str: "24:00:00", value: TimeOfDayTest{TimeOfDay{FromMidnight: 24 * time.Hour}}, noMarshal: true}, // ISO8601 special case
|
||||
{str: "24:01:00", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true},
|
||||
{str: "24:00:01", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true},
|
||||
{str: "25:00:00", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true},
|
||||
{str: "00:60:00", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true},
|
||||
{str: "00:00:60", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true},
|
||||
{str: "01:02:03", value: TimeOfDayTest{TimeOfDay{FromMidnight: time010203}}},
|
||||
{str: "010203", value: TimeOfDayTest{TimeOfDay{FromMidnight: time010203}}, noMarshal: true},
|
||||
{str: "23:59:59", value: TimeOfDayTest{TimeOfDay{FromMidnight: time235959}}},
|
||||
{str: "235959", value: TimeOfDayTest{TimeOfDay{FromMidnight: time235959}}, noMarshal: true},
|
||||
{str: "01:02", value: TimeOfDayTest{TimeOfDay{FromMidnight: time0102}}, noMarshal: true},
|
||||
{str: "0102", value: TimeOfDayTest{TimeOfDay{FromMidnight: time0102}}, noMarshal: true},
|
||||
{str: "01", value: TimeOfDayTest{TimeOfDay{FromMidnight: time01}}, noMarshal: true},
|
||||
{str: "foo 01:02:03", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:time.tz"},
|
||||
{str: "foo\n01:02:03", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:time.tz"},
|
||||
{str: "01:02:03 foo", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:time.tz"},
|
||||
{str: "01:02:03\nfoo", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:time.tz"},
|
||||
{str: "01:02:03Z", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:time.tz"},
|
||||
{str: "01:02:03+01", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:time.tz"},
|
||||
{str: "01:02:03+01:23", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:time.tz"},
|
||||
{str: "01:02:03+0123", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:time.tz"},
|
||||
{str: "01:02:03-01", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:time.tz"},
|
||||
{str: "01:02:03-01:23", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:time.tz"},
|
||||
{str: "01:02:03-0123", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:time.tz"},
|
||||
|
||||
// time.tz
|
||||
{str: "24:00:01", value: TimeOfDayTzTest{}, wantUnmarshalErr: true, noMarshal: true},
|
||||
{str: "01Z", value: TimeOfDayTzTest{TimeOfDay{time01, true, 0}}, noMarshal: true},
|
||||
{str: "01:02:03Z", value: TimeOfDayTzTest{TimeOfDay{time010203, true, 0}}},
|
||||
{str: "01+01", value: TimeOfDayTzTest{TimeOfDay{time01, true, 3600}}, noMarshal: true},
|
||||
{str: "01:02:03+01", value: TimeOfDayTzTest{TimeOfDay{time010203, true, 3600}}, noMarshal: true},
|
||||
{str: "01:02:03+01:23", value: TimeOfDayTzTest{TimeOfDay{time010203, true, 3600 + 23*60}}},
|
||||
{str: "01:02:03+0123", value: TimeOfDayTzTest{TimeOfDay{time010203, true, 3600 + 23*60}}, noMarshal: true},
|
||||
{str: "01:02:03-01", value: TimeOfDayTzTest{TimeOfDay{time010203, true, -3600}}, noMarshal: true},
|
||||
{str: "01:02:03-01:23", value: TimeOfDayTzTest{TimeOfDay{time010203, true, -(3600 + 23*60)}}},
|
||||
{str: "01:02:03-0123", value: TimeOfDayTzTest{TimeOfDay{time010203, true, -(3600 + 23*60)}}, noMarshal: true},
|
||||
|
||||
// dateTime
|
||||
{str: "2013-10-08T00:00:00", value: DateTimeTest{time.Date(2013, 10, 8, 0, 0, 0, 0, localLoc)}, tag: "no:dateTime.tz"},
|
||||
{str: "20131008", value: DateTimeTest{time.Date(2013, 10, 8, 0, 0, 0, 0, localLoc)}, noMarshal: true},
|
||||
{str: "2013-10-08T10:30:50", value: DateTimeTest{time.Date(2013, 10, 8, 10, 30, 50, 0, localLoc)}, tag: "no:dateTime.tz"},
|
||||
{str: "2013-10-08T10:30:50T", value: DateTimeTest{}, wantUnmarshalErr: true, noMarshal: true},
|
||||
{str: "2013-10-08T10:30:50+01", value: DateTimeTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:dateTime.tz"},
|
||||
{str: "2013-10-08T10:30:50+01:23", value: DateTimeTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:dateTime.tz"},
|
||||
{str: "2013-10-08T10:30:50+0123", value: DateTimeTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:dateTime.tz"},
|
||||
{str: "2013-10-08T10:30:50-01", value: DateTimeTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:dateTime.tz"},
|
||||
{str: "2013-10-08T10:30:50-01:23", value: DateTimeTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:dateTime.tz"},
|
||||
{str: "2013-10-08T10:30:50-0123", value: DateTimeTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:dateTime.tz"},
|
||||
|
||||
// dateTime.tz
|
||||
{str: "2013-10-08T10:30:50", value: DateTimeTzTest{time.Date(2013, 10, 8, 10, 30, 50, 0, localLoc)}, noMarshal: true},
|
||||
{str: "2013-10-08T10:30:50+01", value: DateTimeTzTest{time.Date(2013, 10, 8, 10, 30, 50, 0, time.FixedZone("+01:00", 3600))}, noMarshal: true},
|
||||
{str: "2013-10-08T10:30:50+01:23", value: DateTimeTzTest{time.Date(2013, 10, 8, 10, 30, 50, 0, time.FixedZone("+01:23", 3600+23*60))}},
|
||||
{str: "2013-10-08T10:30:50+0123", value: DateTimeTzTest{time.Date(2013, 10, 8, 10, 30, 50, 0, time.FixedZone("+01:23", 3600+23*60))}, noMarshal: true},
|
||||
{str: "2013-10-08T10:30:50-01", value: DateTimeTzTest{time.Date(2013, 10, 8, 10, 30, 50, 0, time.FixedZone("-01:00", -3600))}, noMarshal: true},
|
||||
{str: "2013-10-08T10:30:50-01:23", value: DateTimeTzTest{time.Date(2013, 10, 8, 10, 30, 50, 0, time.FixedZone("-01:23", -(3600+23*60)))}},
|
||||
{str: "2013-10-08T10:30:50-0123", value: DateTimeTzTest{time.Date(2013, 10, 8, 10, 30, 50, 0, time.FixedZone("-01:23", -(3600+23*60)))}, noMarshal: true},
|
||||
|
||||
// boolean
|
||||
{str: "0", value: BooleanTest(false)},
|
||||
{str: "1", value: BooleanTest(true)},
|
||||
{str: "false", value: BooleanTest(false), noMarshal: true},
|
||||
{str: "true", value: BooleanTest(true), noMarshal: true},
|
||||
{str: "no", value: BooleanTest(false), noMarshal: true},
|
||||
{str: "yes", value: BooleanTest(true), noMarshal: true},
|
||||
{str: "", value: BooleanTest(false), noMarshal: true, wantUnmarshalErr: true},
|
||||
{str: "other", value: BooleanTest(false), noMarshal: true, wantUnmarshalErr: true},
|
||||
{str: "2", value: BooleanTest(false), noMarshal: true, wantUnmarshalErr: true},
|
||||
{str: "-1", value: BooleanTest(false), noMarshal: true, wantUnmarshalErr: true},
|
||||
|
||||
// bin.base64
|
||||
{str: "", value: BinBase64Test{}},
|
||||
{str: "YQ==", value: BinBase64Test("a")},
|
||||
{str: "TG9uZ2VyIFN0cmluZy4=", value: BinBase64Test("Longer String.")},
|
||||
{str: "TG9uZ2VyIEFsaWduZWQu", value: BinBase64Test("Longer Aligned.")},
|
||||
|
||||
// bin.hex
|
||||
{str: "", value: BinHexTest{}},
|
||||
{str: "61", value: BinHexTest("a")},
|
||||
{str: "4c6f6e67657220537472696e672e", value: BinHexTest("Longer String.")},
|
||||
{str: "4C6F6E67657220537472696E672E", value: BinHexTest("Longer String."), noMarshal: true},
|
||||
}
|
||||
|
||||
// Generate extra test cases from convTests that implement duper.
|
||||
var extras []testCase
|
||||
for i := range tests {
|
||||
if duper, ok := tests[i].value.(duper); ok {
|
||||
dupes := duper.Dupe(tests[i].tag)
|
||||
for _, duped := range dupes {
|
||||
dupedCase := testCase(tests[i])
|
||||
dupedCase.value = duped
|
||||
extras = append(extras, dupedCase)
|
||||
}
|
||||
}
|
||||
}
|
||||
tests = append(tests, extras...)
|
||||
|
||||
for _, test := range tests {
|
||||
if test.noMarshal {
|
||||
} else if resultStr, err := test.value.Marshal(); err != nil && !test.wantMarshalErr {
|
||||
t.Errorf("For %T marshal %v, want %q, got error: %v", test.value, test.value, test.str, err)
|
||||
} else if err == nil && test.wantMarshalErr {
|
||||
t.Errorf("For %T marshal %v, want error, got %q", test.value, test.value, resultStr)
|
||||
} else if err == nil && resultStr != test.str {
|
||||
t.Errorf("For %T marshal %v, want %q, got %q", test.value, test.value, test.str, resultStr)
|
||||
}
|
||||
|
||||
if test.noUnMarshal {
|
||||
} else if resultValue, err := test.value.Unmarshal(test.str); err != nil && !test.wantUnmarshalErr {
|
||||
t.Errorf("For %T unmarshal %q, want %v, got error: %v", test.value, test.str, test.value, err)
|
||||
} else if err == nil && test.wantUnmarshalErr {
|
||||
t.Errorf("For %T unmarshal %q, want error, got %v", test.value, test.str, resultValue)
|
||||
} else if err == nil && !test.value.Equal(resultValue) {
|
||||
t.Errorf("For %T unmarshal %q, want %v, got %v", test.value, test.str, test.value, resultValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
202
Godeps/_workspace/src/github.com/huin/goupnp/ssdp/registry.go
generated
vendored
Normal file
202
Godeps/_workspace/src/github.com/huin/goupnp/ssdp/registry.go
generated
vendored
Normal file
@ -0,0 +1,202 @@
|
||||
package ssdp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/huin/goupnp/httpu"
|
||||
)
|
||||
|
||||
const (
|
||||
maxExpiryTimeSeconds = 24 * 60 * 60
|
||||
)
|
||||
|
||||
var (
|
||||
maxAgeRx = regexp.MustCompile("max-age=([0-9]+)")
|
||||
)
|
||||
|
||||
type Entry struct {
|
||||
// The address that the entry data was actually received from.
|
||||
RemoteAddr string
|
||||
// Unique Service Name. Identifies a unique instance of a device or service.
|
||||
USN string
|
||||
// Notfication Type. The type of device or service being announced.
|
||||
NT string
|
||||
// Server's self-identifying string.
|
||||
Server string
|
||||
Host string
|
||||
// Location of the UPnP root device description.
|
||||
Location *url.URL
|
||||
|
||||
// Despite BOOTID,CONFIGID being required fields, apparently they are not
|
||||
// always set by devices. Set to -1 if not present.
|
||||
|
||||
BootID int32
|
||||
ConfigID int32
|
||||
|
||||
SearchPort uint16
|
||||
|
||||
// When the last update was received for this entry identified by this USN.
|
||||
LastUpdate time.Time
|
||||
// When the last update's cached values are advised to expire.
|
||||
CacheExpiry time.Time
|
||||
}
|
||||
|
||||
func newEntryFromRequest(r *http.Request) (*Entry, error) {
|
||||
now := time.Now()
|
||||
expiryDuration, err := parseCacheControlMaxAge(r.Header.Get("CACHE-CONTROL"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ssdp: error parsing CACHE-CONTROL max age: %v", err)
|
||||
}
|
||||
|
||||
loc, err := url.Parse(r.Header.Get("LOCATION"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ssdp: error parsing entry Location URL: %v", err)
|
||||
}
|
||||
|
||||
bootID, err := parseUpnpIntHeader(r.Header, "BOOTID.UPNP.ORG", -1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
configID, err := parseUpnpIntHeader(r.Header, "CONFIGID.UPNP.ORG", -1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
searchPort, err := parseUpnpIntHeader(r.Header, "SEARCHPORT.UPNP.ORG", ssdpSearchPort)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if searchPort < 1 || searchPort > 65535 {
|
||||
return nil, fmt.Errorf("ssdp: search port %d is out of range", searchPort)
|
||||
}
|
||||
|
||||
return &Entry{
|
||||
RemoteAddr: r.RemoteAddr,
|
||||
USN: r.Header.Get("USN"),
|
||||
NT: r.Header.Get("NT"),
|
||||
Server: r.Header.Get("SERVER"),
|
||||
Host: r.Header.Get("HOST"),
|
||||
Location: loc,
|
||||
BootID: bootID,
|
||||
ConfigID: configID,
|
||||
SearchPort: uint16(searchPort),
|
||||
LastUpdate: now,
|
||||
CacheExpiry: now.Add(expiryDuration),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseCacheControlMaxAge(cc string) (time.Duration, error) {
|
||||
matches := maxAgeRx.FindStringSubmatch(cc)
|
||||
if len(matches) != 2 {
|
||||
return 0, fmt.Errorf("did not find exactly one max-age in cache control header: %q", cc)
|
||||
}
|
||||
expirySeconds, err := strconv.ParseInt(matches[1], 10, 16)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if expirySeconds < 1 || expirySeconds > maxExpiryTimeSeconds {
|
||||
return 0, fmt.Errorf("rejecting bad expiry time of %d seconds", expirySeconds)
|
||||
}
|
||||
return time.Duration(expirySeconds) * time.Second, nil
|
||||
}
|
||||
|
||||
// parseUpnpIntHeader is intended to parse the
|
||||
// {BOOT,CONFIGID,SEARCHPORT}.UPNP.ORG header fields. It returns the def if
|
||||
// the head is empty or missing.
|
||||
func parseUpnpIntHeader(headers http.Header, headerName string, def int32) (int32, error) {
|
||||
s := headers.Get(headerName)
|
||||
if s == "" {
|
||||
return def, nil
|
||||
}
|
||||
v, err := strconv.ParseInt(s, 10, 32)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("ssdp: could not parse header %s: %v", headerName, err)
|
||||
}
|
||||
return int32(v), nil
|
||||
}
|
||||
|
||||
var _ httpu.Handler = new(Registry)
|
||||
|
||||
// Registry maintains knowledge of discovered devices and services.
|
||||
type Registry struct {
|
||||
lock sync.Mutex
|
||||
byUSN map[string]*Entry
|
||||
}
|
||||
|
||||
func NewRegistry() *Registry {
|
||||
return &Registry{
|
||||
byUSN: make(map[string]*Entry),
|
||||
}
|
||||
}
|
||||
|
||||
// ServeMessage implements httpu.Handler, and uses SSDP NOTIFY requests to
|
||||
// maintain the registry of devices and services.
|
||||
func (reg *Registry) ServeMessage(r *http.Request) {
|
||||
if r.Method != methodNotify {
|
||||
return
|
||||
}
|
||||
|
||||
nts := r.Header.Get("nts")
|
||||
|
||||
var err error
|
||||
switch nts {
|
||||
case ntsAlive:
|
||||
err = reg.handleNTSAlive(r)
|
||||
case ntsUpdate:
|
||||
err = reg.handleNTSUpdate(r)
|
||||
case ntsByebye:
|
||||
err = reg.handleNTSByebye(r)
|
||||
default:
|
||||
err = fmt.Errorf("unknown NTS value: %q", nts)
|
||||
}
|
||||
log.Printf("In %s request from %s: %v", nts, r.RemoteAddr, err)
|
||||
}
|
||||
|
||||
func (reg *Registry) handleNTSAlive(r *http.Request) error {
|
||||
entry, err := newEntryFromRequest(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reg.lock.Lock()
|
||||
defer reg.lock.Unlock()
|
||||
|
||||
reg.byUSN[entry.USN] = entry
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (reg *Registry) handleNTSUpdate(r *http.Request) error {
|
||||
entry, err := newEntryFromRequest(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nextBootID, err := parseUpnpIntHeader(r.Header, "NEXTBOOTID.UPNP.ORG", -1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
entry.BootID = nextBootID
|
||||
|
||||
reg.lock.Lock()
|
||||
defer reg.lock.Unlock()
|
||||
|
||||
reg.byUSN[entry.USN] = entry
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (reg *Registry) handleNTSByebye(r *http.Request) error {
|
||||
reg.lock.Lock()
|
||||
defer reg.lock.Unlock()
|
||||
|
||||
delete(reg.byUSN, r.Header.Get("USN"))
|
||||
|
||||
return nil
|
||||
}
|
||||
83
Godeps/_workspace/src/github.com/huin/goupnp/ssdp/ssdp.go
generated
vendored
Normal file
83
Godeps/_workspace/src/github.com/huin/goupnp/ssdp/ssdp.go
generated
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
package ssdp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/huin/goupnp/httpu"
|
||||
)
|
||||
|
||||
const (
|
||||
ssdpDiscover = `"ssdp:discover"`
|
||||
ntsAlive = `ssdp:alive`
|
||||
ntsByebye = `ssdp:byebye`
|
||||
ntsUpdate = `ssdp:update`
|
||||
ssdpUDP4Addr = "239.255.255.250:1900"
|
||||
ssdpSearchPort = 1900
|
||||
methodSearch = "M-SEARCH"
|
||||
methodNotify = "NOTIFY"
|
||||
)
|
||||
|
||||
// SSDPRawSearch performs a fairly raw SSDP search request, and returns the
|
||||
// unique response(s) that it receives. Each response has the requested
|
||||
// searchTarget, a USN, and a valid location. maxWaitSeconds states how long to
|
||||
// wait for responses in seconds, and must be a minimum of 1 (the
|
||||
// implementation waits an additional 100ms for responses to arrive), 2 is a
|
||||
// reasonable value for this. numSends is the number of requests to send - 3 is
|
||||
// a reasonable value for this.
|
||||
func SSDPRawSearch(httpu *httpu.HTTPUClient, searchTarget string, maxWaitSeconds int, numSends int) ([]*http.Response, error) {
|
||||
if maxWaitSeconds < 1 {
|
||||
return nil, errors.New("ssdp: maxWaitSeconds must be >= 1")
|
||||
}
|
||||
|
||||
seenUsns := make(map[string]bool)
|
||||
var responses []*http.Response
|
||||
req := http.Request{
|
||||
Method: methodSearch,
|
||||
// TODO: Support both IPv4 and IPv6.
|
||||
Host: ssdpUDP4Addr,
|
||||
URL: &url.URL{Opaque: "*"},
|
||||
Header: http.Header{
|
||||
// Putting headers in here avoids them being title-cased.
|
||||
// (The UPnP discovery protocol uses case-sensitive headers)
|
||||
"HOST": []string{ssdpUDP4Addr},
|
||||
"MX": []string{strconv.FormatInt(int64(maxWaitSeconds), 10)},
|
||||
"MAN": []string{ssdpDiscover},
|
||||
"ST": []string{searchTarget},
|
||||
},
|
||||
}
|
||||
allResponses, err := httpu.Do(&req, time.Duration(maxWaitSeconds)*time.Second+100*time.Millisecond, numSends)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, response := range allResponses {
|
||||
if response.StatusCode != 200 {
|
||||
log.Printf("ssdp: got response status code %q in search response", response.Status)
|
||||
continue
|
||||
}
|
||||
if st := response.Header.Get("ST"); st != searchTarget {
|
||||
log.Printf("ssdp: got unexpected search target result %q", st)
|
||||
continue
|
||||
}
|
||||
location, err := response.Location()
|
||||
if err != nil {
|
||||
log.Printf("ssdp: no usable location in search response (discarding): %v", err)
|
||||
continue
|
||||
}
|
||||
usn := response.Header.Get("USN")
|
||||
if usn == "" {
|
||||
log.Printf("ssdp: empty/missing USN in search response (using location instead): %v", err)
|
||||
usn = location.String()
|
||||
}
|
||||
if _, alreadySeen := seenUsns[usn]; !alreadySeen {
|
||||
seenUsns[usn] = true
|
||||
responses = append(responses, response)
|
||||
}
|
||||
}
|
||||
|
||||
return responses, nil
|
||||
}
|
||||
13
Godeps/_workspace/src/github.com/jackpal/go-nat-pmp/LICENSE
generated
vendored
Normal file
13
Godeps/_workspace/src/github.com/jackpal/go-nat-pmp/LICENSE
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
Copyright 2013 John Howard Palevich
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
48
Godeps/_workspace/src/github.com/jackpal/go-nat-pmp/README.md
generated
vendored
Normal file
48
Godeps/_workspace/src/github.com/jackpal/go-nat-pmp/README.md
generated
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
go-nat-pmp
|
||||
==========
|
||||
|
||||
A Go language client for the NAT-PMP internet protocol for port mapping and discovering the external
|
||||
IP address of a firewall.
|
||||
|
||||
NAT-PMP is supported by Apple brand routers and open source routers like Tomato and DD-WRT.
|
||||
|
||||
See http://tools.ietf.org/html/draft-cheshire-nat-pmp-03
|
||||
|
||||
Get the package
|
||||
---------------
|
||||
|
||||
go get -u github.com/jackpal/go-nat-pmp
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
import natpmp "github.com/jackpal/go-nat-pmp"
|
||||
|
||||
client := natpmp.NewClient(gatewayIP)
|
||||
response, err := client.GetExternalAddress()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
print("External IP address:", response.ExternalIPAddress)
|
||||
|
||||
Notes
|
||||
-----
|
||||
|
||||
There doesn't seem to be an easy way to programmatically determine the address of the default gateway.
|
||||
(Linux and OSX have a "routes" kernel API that can be examined to get this information, but there is
|
||||
no Go package for getting this information.)
|
||||
|
||||
Clients
|
||||
-------
|
||||
|
||||
This library is used in the Taipei Torrent BitTorrent client http://github.com/jackpal/Taipei-Torrent
|
||||
|
||||
Complete documentation
|
||||
----------------------
|
||||
|
||||
http://godoc.org/github.com/jackpal/go-nat-pmp
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
This project is licensed under the Apache License 2.0.
|
||||
184
Godeps/_workspace/src/github.com/jackpal/go-nat-pmp/natpmp.go
generated
vendored
Normal file
184
Godeps/_workspace/src/github.com/jackpal/go-nat-pmp/natpmp.go
generated
vendored
Normal file
@ -0,0 +1,184 @@
|
||||
package natpmp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Implement the NAT-PMP protocol, typically supported by Apple routers and open source
|
||||
// routers such as DD-WRT and Tomato.
|
||||
//
|
||||
// See http://tools.ietf.org/html/draft-cheshire-nat-pmp-03
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// client := natpmp.NewClient(gatewayIP)
|
||||
// response, err := client.GetExternalAddress()
|
||||
|
||||
const nAT_PMP_PORT = 5351
|
||||
|
||||
const nAT_TRIES = 9
|
||||
|
||||
const nAT_INITIAL_MS = 250
|
||||
|
||||
// The recommended mapping lifetime for AddPortMapping
|
||||
const RECOMMENDED_MAPPING_LIFETIME_SECONDS = 3600
|
||||
|
||||
// Client is a NAT-PMP protocol client.
|
||||
type Client struct {
|
||||
gateway net.IP
|
||||
}
|
||||
|
||||
// Create a NAT-PMP client for the NAT-PMP server at the gateway.
|
||||
func NewClient(gateway net.IP) (nat *Client) {
|
||||
return &Client{gateway}
|
||||
}
|
||||
|
||||
// Results of the NAT-PMP GetExternalAddress operation
|
||||
type GetExternalAddressResult struct {
|
||||
SecondsSinceStartOfEpoc uint32
|
||||
ExternalIPAddress [4]byte
|
||||
}
|
||||
|
||||
// Get the external address of the router.
|
||||
func (n *Client) GetExternalAddress() (result *GetExternalAddressResult, err error) {
|
||||
msg := make([]byte, 2)
|
||||
msg[0] = 0 // Version 0
|
||||
msg[1] = 0 // OP Code 0
|
||||
response, err := n.rpc(msg, 12)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
result = &GetExternalAddressResult{}
|
||||
result.SecondsSinceStartOfEpoc = readNetworkOrderUint32(response[4:8])
|
||||
copy(result.ExternalIPAddress[:], response[8:12])
|
||||
return
|
||||
}
|
||||
|
||||
// Results of the NAT-PMP AddPortMapping operation
|
||||
type AddPortMappingResult struct {
|
||||
SecondsSinceStartOfEpoc uint32
|
||||
InternalPort uint16
|
||||
MappedExternalPort uint16
|
||||
PortMappingLifetimeInSeconds uint32
|
||||
}
|
||||
|
||||
// Add (or delete) a port mapping. To delete a mapping, set the requestedExternalPort and lifetime to 0
|
||||
func (n *Client) AddPortMapping(protocol string, internalPort, requestedExternalPort int, lifetime int) (result *AddPortMappingResult, err error) {
|
||||
var opcode byte
|
||||
if protocol == "udp" {
|
||||
opcode = 1
|
||||
} else if protocol == "tcp" {
|
||||
opcode = 2
|
||||
} else {
|
||||
err = fmt.Errorf("unknown protocol %v", protocol)
|
||||
return
|
||||
}
|
||||
msg := make([]byte, 12)
|
||||
msg[0] = 0 // Version 0
|
||||
msg[1] = opcode
|
||||
writeNetworkOrderUint16(msg[4:6], uint16(internalPort))
|
||||
writeNetworkOrderUint16(msg[6:8], uint16(requestedExternalPort))
|
||||
writeNetworkOrderUint32(msg[8:12], uint32(lifetime))
|
||||
response, err := n.rpc(msg, 16)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
result = &AddPortMappingResult{}
|
||||
result.SecondsSinceStartOfEpoc = readNetworkOrderUint32(response[4:8])
|
||||
result.InternalPort = readNetworkOrderUint16(response[8:10])
|
||||
result.MappedExternalPort = readNetworkOrderUint16(response[10:12])
|
||||
result.PortMappingLifetimeInSeconds = readNetworkOrderUint32(response[12:16])
|
||||
return
|
||||
}
|
||||
|
||||
func (n *Client) rpc(msg []byte, resultSize int) (result []byte, err error) {
|
||||
var server net.UDPAddr
|
||||
server.IP = n.gateway
|
||||
server.Port = nAT_PMP_PORT
|
||||
conn, err := net.DialUDP("udp", nil, &server)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
result = make([]byte, resultSize)
|
||||
|
||||
needNewDeadline := true
|
||||
|
||||
var tries uint
|
||||
for tries = 0; tries < nAT_TRIES; {
|
||||
if needNewDeadline {
|
||||
err = conn.SetDeadline(time.Now().Add((nAT_INITIAL_MS << tries) * time.Millisecond))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
needNewDeadline = false
|
||||
}
|
||||
_, err = conn.Write(msg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var bytesRead int
|
||||
var remoteAddr *net.UDPAddr
|
||||
bytesRead, remoteAddr, err = conn.ReadFromUDP(result)
|
||||
if err != nil {
|
||||
if err.(net.Error).Timeout() {
|
||||
tries++
|
||||
needNewDeadline = true
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
if !remoteAddr.IP.Equal(n.gateway) {
|
||||
log.Printf("Ignoring packet because IPs differ:", remoteAddr, n.gateway)
|
||||
// Ignore this packet.
|
||||
// Continue without increasing retransmission timeout or deadline.
|
||||
continue
|
||||
}
|
||||
if bytesRead != resultSize {
|
||||
err = fmt.Errorf("unexpected result size %d, expected %d", bytesRead, resultSize)
|
||||
return
|
||||
}
|
||||
if result[0] != 0 {
|
||||
err = fmt.Errorf("unknown protocol version %d", result[0])
|
||||
return
|
||||
}
|
||||
expectedOp := msg[1] | 0x80
|
||||
if result[1] != expectedOp {
|
||||
err = fmt.Errorf("Unexpected opcode %d. Expected %d", result[1], expectedOp)
|
||||
return
|
||||
}
|
||||
resultCode := readNetworkOrderUint16(result[2:4])
|
||||
if resultCode != 0 {
|
||||
err = fmt.Errorf("Non-zero result code %d", resultCode)
|
||||
return
|
||||
}
|
||||
// If we got here the RPC is good.
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("Timed out trying to contact gateway")
|
||||
return
|
||||
}
|
||||
|
||||
func writeNetworkOrderUint16(buf []byte, d uint16) {
|
||||
buf[0] = byte(d >> 8)
|
||||
buf[1] = byte(d)
|
||||
}
|
||||
|
||||
func writeNetworkOrderUint32(buf []byte, d uint32) {
|
||||
buf[0] = byte(d >> 24)
|
||||
buf[1] = byte(d >> 16)
|
||||
buf[2] = byte(d >> 8)
|
||||
buf[3] = byte(d)
|
||||
}
|
||||
|
||||
func readNetworkOrderUint16(buf []byte) uint16 {
|
||||
return (uint16(buf[0]) << 8) | uint16(buf[1])
|
||||
}
|
||||
|
||||
func readNetworkOrderUint32(buf []byte) uint32 {
|
||||
return (uint32(buf[0]) << 24) | (uint32(buf[1]) << 16) | (uint32(buf[2]) << 8) | uint32(buf[3])
|
||||
}
|
||||
12
Godeps/_workspace/src/github.com/jbenet/goprocess/impl-mutex.go
generated
vendored
12
Godeps/_workspace/src/github.com/jbenet/goprocess/impl-mutex.go
generated
vendored
@ -35,6 +35,10 @@ func newProcess(tf TeardownFunc) *process {
|
||||
}
|
||||
|
||||
func (p *process) WaitFor(q Process) {
|
||||
if q == nil {
|
||||
panic("waiting for nil process")
|
||||
}
|
||||
|
||||
p.Lock()
|
||||
|
||||
select {
|
||||
@ -48,6 +52,10 @@ func (p *process) WaitFor(q Process) {
|
||||
}
|
||||
|
||||
func (p *process) AddChildNoWait(child Process) {
|
||||
if child == nil {
|
||||
panic("adding nil child process")
|
||||
}
|
||||
|
||||
p.Lock()
|
||||
|
||||
select {
|
||||
@ -61,6 +69,10 @@ func (p *process) AddChildNoWait(child Process) {
|
||||
}
|
||||
|
||||
func (p *process) AddChild(child Process) {
|
||||
if child == nil {
|
||||
panic("adding nil child process")
|
||||
}
|
||||
|
||||
p.Lock()
|
||||
|
||||
select {
|
||||
|
||||
19
core/core.go
19
core/core.go
@ -389,15 +389,13 @@ func loadPrivateKey(cfg *config.Identity, id peer.ID) (ic.PrivKey, error) {
|
||||
}
|
||||
|
||||
func listenAddresses(cfg *config.Config) ([]ma.Multiaddr, error) {
|
||||
|
||||
var err error
|
||||
listen := make([]ma.Multiaddr, len(cfg.Addresses.Swarm))
|
||||
for i, addr := range cfg.Addresses.Swarm {
|
||||
|
||||
listen[i], err = ma.NewMultiaddr(addr)
|
||||
var listen []ma.Multiaddr
|
||||
for _, addr := range cfg.Addresses.Swarm {
|
||||
maddr, err := ma.NewMultiaddr(addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failure to parse config.Addresses.Swarm[%d]: %s", i, cfg.Addresses.Swarm)
|
||||
return nil, fmt.Errorf("Failure to parse config.Addresses.Swarm: %s", cfg.Addresses.Swarm)
|
||||
}
|
||||
listen = append(listen, maddr)
|
||||
}
|
||||
|
||||
return listen, nil
|
||||
@ -413,7 +411,7 @@ func constructPeerHost(ctx context.Context, cfg *config.Config, id peer.ID, ps p
|
||||
// make sure we error out if our config does not have addresses we can use
|
||||
log.Debugf("Config.Addresses.Swarm:%s", listenAddrs)
|
||||
filteredAddrs := addrutil.FilterUsableAddrs(listenAddrs)
|
||||
log.Debugf("Config.Addresses.Swarm:%s (filtered)", listenAddrs)
|
||||
log.Debugf("Config.Addresses.Swarm:%s (filtered)", filteredAddrs)
|
||||
if len(filteredAddrs) < 1 {
|
||||
return nil, debugerror.Errorf("addresses in config not usable: %s", listenAddrs)
|
||||
}
|
||||
@ -423,7 +421,7 @@ func constructPeerHost(ctx context.Context, cfg *config.Config, id peer.ID, ps p
|
||||
return nil, debugerror.Wrap(err)
|
||||
}
|
||||
|
||||
peerhost := p2pbhost.New(network)
|
||||
peerhost := p2pbhost.New(network, p2pbhost.NATPortMap)
|
||||
// explicitly set these as our listen addrs.
|
||||
// (why not do it inside inet.NewNetwork? because this way we can
|
||||
// listen on addresses without necessarily advertising those publicly.)
|
||||
@ -431,7 +429,8 @@ func constructPeerHost(ctx context.Context, cfg *config.Config, id peer.ID, ps p
|
||||
if err != nil {
|
||||
return nil, debugerror.Wrap(err)
|
||||
}
|
||||
log.Info("Swarm listening at: %s", addrs)
|
||||
log.Infof("Swarm listening at: %s", addrs)
|
||||
|
||||
ps.AddAddresses(id, addrs)
|
||||
return peerhost, nil
|
||||
}
|
||||
|
||||
@ -1,10 +1,15 @@
|
||||
package basichost
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
|
||||
ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
|
||||
goprocess "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess"
|
||||
|
||||
eventlog "github.com/jbenet/go-ipfs/thirdparty/eventlog"
|
||||
|
||||
inat "github.com/jbenet/go-ipfs/p2p/nat"
|
||||
inet "github.com/jbenet/go-ipfs/p2p/net"
|
||||
peer "github.com/jbenet/go-ipfs/p2p/peer"
|
||||
protocol "github.com/jbenet/go-ipfs/p2p/protocol"
|
||||
@ -14,20 +19,47 @@ import (
|
||||
|
||||
var log = eventlog.Logger("p2p/host/basic")
|
||||
|
||||
// Option is a type used to pass in options to the host.
|
||||
type Option int
|
||||
|
||||
const (
|
||||
// NATPortMap makes the host attempt to open port-mapping in NAT devices
|
||||
// for all its listeners. Pass in this option in the constructor to
|
||||
// asynchronously a) find a gateway, b) open port mappings, c) republish
|
||||
// port mappings periodically. The NATed addresses are included in the
|
||||
// Host's Addrs() list.
|
||||
NATPortMap Option = iota
|
||||
)
|
||||
|
||||
// BasicHost is the basic implementation of the host.Host interface. This
|
||||
// particular host implementation:
|
||||
// * uses a protocol muxer to mux per-protocol streams
|
||||
// * uses an identity service to send + receive node information
|
||||
// * uses a relay service to allow hosts to relay conns for each other
|
||||
// * uses a nat service to establish NAT port mappings
|
||||
type BasicHost struct {
|
||||
network inet.Network
|
||||
mux *protocol.Mux
|
||||
ids *identify.IDService
|
||||
relay *relay.RelayService
|
||||
|
||||
natmu sync.Mutex
|
||||
nat *inat.NAT
|
||||
|
||||
proc goprocess.Process
|
||||
}
|
||||
|
||||
// New constructs and sets up a new *BasicHost with given Network
|
||||
func New(net inet.Network) *BasicHost {
|
||||
func New(net inet.Network, opts ...Option) *BasicHost {
|
||||
h := &BasicHost{
|
||||
network: net,
|
||||
mux: protocol.NewMux(),
|
||||
}
|
||||
|
||||
h.proc = goprocess.WithTeardown(func() error {
|
||||
return h.Network().Close()
|
||||
})
|
||||
|
||||
// setup host services
|
||||
h.ids = identify.NewIDService(h)
|
||||
h.relay = relay.NewRelayService(h, h.Mux().HandleSync)
|
||||
@ -35,9 +67,48 @@ func New(net inet.Network) *BasicHost {
|
||||
net.SetConnHandler(h.newConnHandler)
|
||||
net.SetStreamHandler(h.newStreamHandler)
|
||||
|
||||
for _, o := range opts {
|
||||
switch o {
|
||||
case NATPortMap:
|
||||
h.setupNATPortMap()
|
||||
}
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *BasicHost) setupNATPortMap() {
|
||||
// do this asynchronously to avoid blocking daemon startup
|
||||
|
||||
h.proc.Go(func(worker goprocess.Process) {
|
||||
nat := inat.DiscoverNAT()
|
||||
if nat == nil { // no nat, or failed to get it.
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case <-worker.Closing():
|
||||
nat.Close()
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
// wire up the nat to close when proc closes.
|
||||
h.proc.AddChild(nat.Process())
|
||||
|
||||
h.natmu.Lock()
|
||||
h.nat = nat
|
||||
h.natmu.Unlock()
|
||||
|
||||
addrs := h.Network().ListenAddresses()
|
||||
nat.PortMapAddrs(addrs)
|
||||
mapAddrs := nat.ExternalAddrs()
|
||||
if len(mapAddrs) > 0 {
|
||||
log.Infof("NAT mapping addrs: %s", mapAddrs)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// newConnHandler is the remote-opened conn handler for inet.Network
|
||||
func (h *BasicHost) newConnHandler(c inet.Conn) {
|
||||
h.ids.IdentifyConn(c)
|
||||
@ -143,7 +214,23 @@ func (h *BasicHost) dialPeer(ctx context.Context, p peer.ID) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *BasicHost) Addrs() []ma.Multiaddr {
|
||||
addrs, err := h.Network().InterfaceListenAddresses()
|
||||
if err != nil {
|
||||
log.Debug("error retrieving network interface addrs")
|
||||
}
|
||||
|
||||
h.natmu.Lock()
|
||||
nat := h.nat
|
||||
h.natmu.Unlock()
|
||||
if nat != nil {
|
||||
addrs = append(addrs, nat.ExternalAddrs()...)
|
||||
}
|
||||
|
||||
return addrs
|
||||
}
|
||||
|
||||
// Close shuts down the Host's services (network, etc).
|
||||
func (h *BasicHost) Close() error {
|
||||
return h.Network().Close()
|
||||
return h.proc.Close()
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ package host
|
||||
|
||||
import (
|
||||
context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
|
||||
ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
|
||||
|
||||
inet "github.com/jbenet/go-ipfs/p2p/net"
|
||||
peer "github.com/jbenet/go-ipfs/p2p/peer"
|
||||
@ -23,6 +24,9 @@ type Host interface {
|
||||
// Peerstore returns the Host's repository of Peer Addresses and Keys.
|
||||
Peerstore() peer.Peerstore
|
||||
|
||||
// Returns the listen addresses of the Host
|
||||
Addrs() []ma.Multiaddr
|
||||
|
||||
// Networks returns the Network interface of the Host
|
||||
Network() inet.Network
|
||||
|
||||
|
||||
410
p2p/nat/nat.go
Normal file
410
p2p/nat/nat.go
Normal file
@ -0,0 +1,410 @@
|
||||
package nat
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
|
||||
manet "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net"
|
||||
|
||||
nat "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/fd/go-nat"
|
||||
goprocess "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess"
|
||||
periodic "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess/periodic"
|
||||
eventlog "github.com/jbenet/go-ipfs/thirdparty/eventlog"
|
||||
notifier "github.com/jbenet/go-ipfs/thirdparty/notifier"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNoMapping signals no mapping exists for an address
|
||||
ErrNoMapping = errors.New("mapping not established")
|
||||
)
|
||||
|
||||
var log = eventlog.Logger("nat")
|
||||
|
||||
// MappingDuration is a default port mapping duration.
|
||||
// Port mappings are renewed every (MappingDuration / 3)
|
||||
const MappingDuration = time.Second * 60
|
||||
|
||||
// DiscoverNAT looks for a NAT device in the network and
|
||||
// returns an object that can manage port mappings.
|
||||
func DiscoverNAT() *NAT {
|
||||
nat, err := nat.DiscoverGateway()
|
||||
if err != nil {
|
||||
log.Debug("DiscoverGateway error:", err)
|
||||
return nil
|
||||
}
|
||||
addr, err := nat.GetDeviceAddress()
|
||||
if err != nil {
|
||||
log.Debug("DiscoverGateway address error:", err)
|
||||
} else {
|
||||
log.Debug("DiscoverGateway address:", addr)
|
||||
}
|
||||
return newNAT(nat)
|
||||
}
|
||||
|
||||
// NAT is an object that manages address port mappings in
|
||||
// NATs (Network Address Translators). It is a long-running
|
||||
// service that will periodically renew port mappings,
|
||||
// and keep an up-to-date list of all the external addresses.
|
||||
type NAT struct {
|
||||
nat nat.NAT
|
||||
proc goprocess.Process // manages nat mappings lifecycle
|
||||
|
||||
mappingmu sync.RWMutex // guards mappings
|
||||
mappings []*mapping
|
||||
|
||||
Notifier
|
||||
}
|
||||
|
||||
func newNAT(realNAT nat.NAT) *NAT {
|
||||
return &NAT{
|
||||
nat: realNAT,
|
||||
proc: goprocess.WithParent(goprocess.Background()),
|
||||
}
|
||||
}
|
||||
|
||||
// Close shuts down all port mappings. NAT can no longer be used.
|
||||
func (nat *NAT) Close() error {
|
||||
return nat.proc.Close()
|
||||
}
|
||||
|
||||
// Process returns the nat's life-cycle manager, for making it listen
|
||||
// to close signals.
|
||||
func (nat *NAT) Process() goprocess.Process {
|
||||
return nat.proc
|
||||
}
|
||||
|
||||
// Notifier is an object that assists NAT in notifying listeners.
|
||||
// It is implemented using github.com/jbenet/go-ipfs/thirdparty/notifier
|
||||
type Notifier struct {
|
||||
n notifier.Notifier
|
||||
}
|
||||
|
||||
func (n *Notifier) notifyAll(notify func(n Notifiee)) {
|
||||
n.n.NotifyAll(func(n notifier.Notifiee) {
|
||||
notify(n.(Notifiee))
|
||||
})
|
||||
}
|
||||
|
||||
// Notify signs up notifiee to listen to NAT events.
|
||||
func (n *Notifier) Notify(notifiee Notifiee) {
|
||||
n.n.Notify(n)
|
||||
}
|
||||
|
||||
// StopNotify stops signaling events to notifiee.
|
||||
func (n *Notifier) StopNotify(notifiee Notifiee) {
|
||||
n.n.StopNotify(notifiee)
|
||||
}
|
||||
|
||||
// Notifiee is an interface objects must implement to listen to NAT events.
|
||||
type Notifiee interface {
|
||||
|
||||
// Called every time a successful mapping happens
|
||||
// Warning: the port mapping may have changed. If that is the
|
||||
// case, both MappingSuccess and MappingChanged are called.
|
||||
MappingSuccess(nat *NAT, m Mapping)
|
||||
|
||||
// Called when mapping a port succeeds, but the mapping is
|
||||
// with a different port than an earlier success.
|
||||
MappingChanged(nat *NAT, m Mapping, oldport int, newport int)
|
||||
|
||||
// Called when a port mapping fails. NAT will continue attempting after
|
||||
// the next period. To stop trying, use: mapping.Close(). After this failure,
|
||||
// mapping.ExternalPort() will be zero, and nat.ExternalAddrs() will not
|
||||
// return the address for this mapping. With luck, the next attempt will
|
||||
// succeed, without the client needing to do anything.
|
||||
MappingFailed(nat *NAT, m Mapping, oldport int, err error)
|
||||
}
|
||||
|
||||
// Mapping represents a port mapping in a NAT.
|
||||
type Mapping interface {
|
||||
// NAT returns the NAT object this Mapping belongs to.
|
||||
NAT() *NAT
|
||||
|
||||
// Protocol returns the protocol of this port mapping. This is either
|
||||
// "tcp" or "udp" as no other protocols are likely to be NAT-supported.
|
||||
Protocol() string
|
||||
|
||||
// InternalPort returns the internal device port. Mapping will continue to
|
||||
// try to map InternalPort() to an external facing port.
|
||||
InternalPort() int
|
||||
|
||||
// ExternalPort returns the external facing port. If the mapping is not
|
||||
// established, port will be 0
|
||||
ExternalPort() int
|
||||
|
||||
// InternalAddr returns the internal address.
|
||||
InternalAddr() ma.Multiaddr
|
||||
|
||||
// ExternalAddr returns the external facing address. If the mapping is not
|
||||
// established, addr will be nil, and and ErrNoMapping will be returned.
|
||||
ExternalAddr() (addr ma.Multiaddr, err error)
|
||||
}
|
||||
|
||||
// keeps republishing
|
||||
type mapping struct {
|
||||
sync.Mutex // guards all fields
|
||||
|
||||
nat *NAT
|
||||
proto string
|
||||
intport int
|
||||
extport int
|
||||
intaddr ma.Multiaddr
|
||||
proc goprocess.Process
|
||||
}
|
||||
|
||||
func (m *mapping) NAT() *NAT {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
return m.nat
|
||||
}
|
||||
|
||||
func (m *mapping) Protocol() string {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
return m.proto
|
||||
}
|
||||
|
||||
func (m *mapping) InternalPort() int {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
return m.intport
|
||||
}
|
||||
|
||||
func (m *mapping) ExternalPort() int {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
return m.extport
|
||||
}
|
||||
|
||||
func (m *mapping) setExternalPort(p int) {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
m.extport = p
|
||||
}
|
||||
|
||||
func (m *mapping) InternalAddr() ma.Multiaddr {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
return m.intaddr
|
||||
}
|
||||
|
||||
func (m *mapping) ExternalAddr() (ma.Multiaddr, error) {
|
||||
if m.ExternalPort() == 0 { // dont even try right now.
|
||||
return nil, ErrNoMapping
|
||||
}
|
||||
|
||||
ip, err := m.nat.nat.GetExternalAddress()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ipmaddr, err := manet.FromIP(ip)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing ip")
|
||||
}
|
||||
|
||||
// call m.ExternalPort again, as mapping may have changed under our feet. (tocttou)
|
||||
extport := m.ExternalPort()
|
||||
if extport == 0 {
|
||||
return nil, ErrNoMapping
|
||||
}
|
||||
|
||||
tcp, err := ma.NewMultiaddr(fmt.Sprintf("/%s/%d", m.Protocol(), extport))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
maddr2 := ipmaddr.Encapsulate(tcp)
|
||||
return maddr2, nil
|
||||
}
|
||||
|
||||
func (m *mapping) Close() error {
|
||||
return m.proc.Close()
|
||||
}
|
||||
|
||||
// Mappings returns a slice of all NAT mappings
|
||||
func (nat *NAT) Mappings() []Mapping {
|
||||
nat.mappingmu.Lock()
|
||||
maps2 := make([]Mapping, len(nat.mappings))
|
||||
for i, m := range nat.mappings {
|
||||
maps2[i] = m
|
||||
}
|
||||
nat.mappingmu.Unlock()
|
||||
return maps2
|
||||
}
|
||||
|
||||
func (nat *NAT) addMapping(m *mapping) {
|
||||
// make mapping automatically close when nat is closed.
|
||||
nat.proc.AddChild(m.proc)
|
||||
|
||||
nat.mappingmu.Lock()
|
||||
nat.mappings = append(nat.mappings, m)
|
||||
nat.mappingmu.Unlock()
|
||||
}
|
||||
|
||||
// NewMapping attemps to construct a mapping on protocol and internal port
|
||||
// It will also periodically renew the mapping until the returned Mapping
|
||||
// -- or its parent NAT -- is Closed.
|
||||
//
|
||||
// May not succeed, and mappings may change over time;
|
||||
// NAT devices may not respect our port requests, and even lie.
|
||||
// Clients should not store the mapped results, but rather always
|
||||
// poll our object for the latest mappings.
|
||||
func (nat *NAT) NewMapping(maddr ma.Multiaddr) (Mapping, error) {
|
||||
if nat == nil {
|
||||
return nil, fmt.Errorf("no nat available")
|
||||
}
|
||||
|
||||
network, addr, err := manet.DialArgs(maddr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("DialArgs failed on addr:", maddr.String())
|
||||
}
|
||||
|
||||
switch network {
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
network = "tcp"
|
||||
case "udp", "udp4", "udp6":
|
||||
network = "udp"
|
||||
default:
|
||||
return nil, fmt.Errorf("transport not supported by NAT: %s", network)
|
||||
}
|
||||
|
||||
intports := strings.Split(addr, ":")[1]
|
||||
intport, err := strconv.Atoi(intports)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m := &mapping{
|
||||
nat: nat,
|
||||
proto: network,
|
||||
intport: intport,
|
||||
intaddr: maddr,
|
||||
}
|
||||
m.proc = periodic.Every(MappingDuration/3, func(worker goprocess.Process) {
|
||||
nat.establishMapping(m)
|
||||
})
|
||||
nat.addMapping(m)
|
||||
// do it once synchronously, so first mapping is done right away, and before exiting,
|
||||
// allowing users -- in the optimistic case -- to use results right after.
|
||||
nat.establishMapping(m)
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (nat *NAT) establishMapping(m *mapping) {
|
||||
oldport := m.ExternalPort()
|
||||
log.Debugf("Attempting port map: %s/%d", m.Protocol(), m.InternalPort())
|
||||
newport, err := nat.nat.AddPortMapping(m.Protocol(), m.InternalPort(), "http", MappingDuration)
|
||||
|
||||
failure := func() {
|
||||
m.setExternalPort(0) // clear mapping
|
||||
// TODO: log.Event
|
||||
log.Infof("failed to establish port mapping: %s", err)
|
||||
nat.Notifier.notifyAll(func(n Notifiee) {
|
||||
n.MappingFailed(nat, m, oldport, err)
|
||||
})
|
||||
|
||||
// we do not close if the mapping failed,
|
||||
// because it may work again next time.
|
||||
}
|
||||
|
||||
if err != nil || newport == 0 {
|
||||
failure()
|
||||
return
|
||||
}
|
||||
|
||||
m.setExternalPort(newport)
|
||||
ext, err := m.ExternalAddr()
|
||||
if err != nil {
|
||||
log.Debugf("NAT Mapping addr error: %s %s", m.InternalAddr(), err)
|
||||
failure()
|
||||
return
|
||||
}
|
||||
|
||||
log.Debugf("NAT Mapping: %s --> %s", m.InternalAddr(), ext)
|
||||
if oldport != 0 && newport != oldport {
|
||||
log.Infof("failed to renew same port mapping: ch %d -> %d", oldport, newport)
|
||||
nat.Notifier.notifyAll(func(n Notifiee) {
|
||||
n.MappingChanged(nat, m, oldport, newport)
|
||||
})
|
||||
}
|
||||
|
||||
nat.Notifier.notifyAll(func(n Notifiee) {
|
||||
n.MappingSuccess(nat, m)
|
||||
})
|
||||
}
|
||||
|
||||
// PortMapAddrs attempts to open (and continue to keep open)
|
||||
// port mappings for given addrs. This function blocks until
|
||||
// all addresses have been tried. This allows clients to
|
||||
// retrieve results immediately after:
|
||||
//
|
||||
// nat.PortMapAddrs(addrs)
|
||||
// mapped := nat.ExternalAddrs()
|
||||
//
|
||||
// Some may not succeed, and mappings may change over time;
|
||||
// NAT devices may not respect our port requests, and even lie.
|
||||
// Clients should not store the mapped results, but rather always
|
||||
// poll our object for the latest mappings.
|
||||
func (nat *NAT) PortMapAddrs(addrs []ma.Multiaddr) {
|
||||
// spin off addr mappings independently.
|
||||
var wg sync.WaitGroup
|
||||
for _, addr := range addrs {
|
||||
// do all of them concurrently
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
nat.NewMapping(addr)
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// MappedAddrs returns address mappings NAT believes have been
|
||||
// successfully established. Unsuccessful mappings are nil. This is:
|
||||
//
|
||||
// map[internalAddr]externalAddr
|
||||
//
|
||||
// This set of mappings _may not_ be correct, as NAT devices are finicky.
|
||||
// Consider this with _best effort_ semantics.
|
||||
func (nat *NAT) MappedAddrs() map[ma.Multiaddr]ma.Multiaddr {
|
||||
|
||||
mappings := nat.Mappings()
|
||||
addrmap := make(map[ma.Multiaddr]ma.Multiaddr, len(mappings))
|
||||
|
||||
for _, m := range mappings {
|
||||
i := m.InternalAddr()
|
||||
e, err := m.ExternalAddr()
|
||||
if err != nil {
|
||||
addrmap[i] = nil
|
||||
} else {
|
||||
addrmap[i] = e
|
||||
}
|
||||
}
|
||||
return addrmap
|
||||
}
|
||||
|
||||
// ExternalAddrs returns a list of addresses that NAT believes have
|
||||
// been successfully established. Unsuccessful mappings are omitted,
|
||||
// so nat.ExternalAddrs() may return less addresses than nat.InternalAddrs().
|
||||
// To see which addresses are mapped, use nat.MappedAddrs().
|
||||
//
|
||||
// This set of mappings _may not_ be correct, as NAT devices are finicky.
|
||||
// Consider this with _best effort_ semantics.
|
||||
func (nat *NAT) ExternalAddrs() []ma.Multiaddr {
|
||||
mappings := nat.Mappings()
|
||||
addrs := make([]ma.Multiaddr, 0, len(mappings))
|
||||
for _, m := range mappings {
|
||||
a, err := m.ExternalAddr()
|
||||
if err != nil {
|
||||
continue // this mapping not currently successful.
|
||||
}
|
||||
addrs = append(addrs, a)
|
||||
}
|
||||
return addrs
|
||||
}
|
||||
@ -152,7 +152,7 @@ func reuseErrShouldRetry(err error) bool {
|
||||
|
||||
// if it's a network timeout error, it's a legitimate failure.
|
||||
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
errno, ok := err.(syscall.Errno)
|
||||
|
||||
@ -67,11 +67,6 @@ func (mn *mocknet) AddPeer(k ic.PrivKey, a ma.Multiaddr) (host.Host, error) {
|
||||
}
|
||||
|
||||
h := bhost.New(n)
|
||||
|
||||
// make sure to add listening address!
|
||||
// this makes debugging things simpler as remembering to register
|
||||
// an address may cause unexpected failure.
|
||||
n.Peerstore().AddAddress(n.LocalPeer(), a)
|
||||
log.Debugf("mocknet added listen addr for peer: %s -- %s", n.LocalPeer(), a)
|
||||
|
||||
mn.cg.AddChildGroup(n.cg)
|
||||
|
||||
@ -141,17 +141,13 @@ func (ids *IDService) populateMessage(mes *pb.Identify, c inet.Conn) {
|
||||
// "public" address, at least in relation to us.
|
||||
mes.ObservedAddr = c.RemoteMultiaddr().Bytes()
|
||||
|
||||
// set listen addrs
|
||||
laddrs, err := ids.Host.Network().InterfaceListenAddresses()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
} else {
|
||||
mes.ListenAddrs = make([][]byte, len(laddrs))
|
||||
for i, addr := range laddrs {
|
||||
mes.ListenAddrs[i] = addr.Bytes()
|
||||
}
|
||||
log.Debugf("%s sent listen addrs to %s: %s", c.LocalPeer(), c.RemotePeer(), laddrs)
|
||||
// set listen addrs, get our latest addrs from Host.
|
||||
laddrs := ids.Host.Addrs()
|
||||
mes.ListenAddrs = make([][]byte, len(laddrs))
|
||||
for i, addr := range laddrs {
|
||||
mes.ListenAddrs[i] = addr.Bytes()
|
||||
}
|
||||
log.Debugf("%s sent listen addrs to %s: %s", c.LocalPeer(), c.RemotePeer(), laddrs)
|
||||
|
||||
// set protocol versions
|
||||
s := IpfsVersion.String()
|
||||
|
||||
115
thirdparty/notifier/notifier.go
vendored
Normal file
115
thirdparty/notifier/notifier.go
vendored
Normal file
@ -0,0 +1,115 @@
|
||||
// Package notifier provides a simple notification dispatcher
|
||||
// meant to be embedded in larger structres who wish to allow
|
||||
// clients to sign up for event notifications.
|
||||
package notifier
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Notifiee is a generic interface. Clients implement
|
||||
// their own Notifiee interfaces to ensure type-safety
|
||||
// of notifications:
|
||||
//
|
||||
// type RocketNotifiee interface{
|
||||
// Countdown(r Rocket, countdown time.Duration)
|
||||
// LiftedOff(Rocket)
|
||||
// ReachedOrbit(Rocket)
|
||||
// Detached(Rocket, Capsule)
|
||||
// Landed(Rocket)
|
||||
// }
|
||||
//
|
||||
type Notifiee interface{}
|
||||
|
||||
// Notifier is a notification dispatcher. It's meant
|
||||
// to be composed, and its zero-value is ready to be used.
|
||||
//
|
||||
// type Rocket struct {
|
||||
// notifier notifier.Notifier
|
||||
// }
|
||||
//
|
||||
type Notifier struct {
|
||||
mu sync.RWMutex // guards notifiees
|
||||
nots map[Notifiee]struct{}
|
||||
}
|
||||
|
||||
// Notify signs up Notifiee e for notifications. This function
|
||||
// is meant to be called behind your own type-safe function(s):
|
||||
//
|
||||
// // generic function for pattern-following
|
||||
// func (r *Rocket) Notify(n Notifiee) {
|
||||
// r.notifier.Notify(n)
|
||||
// }
|
||||
//
|
||||
// // or as part of other functions
|
||||
// func (r *Rocket) Onboard(a Astronaut) {
|
||||
// r.astronauts = append(r.austronauts, a)
|
||||
// r.notifier.Notify(a)
|
||||
// }
|
||||
//
|
||||
func (n *Notifier) Notify(e Notifiee) {
|
||||
n.mu.Lock()
|
||||
if n.nots == nil { // so that zero-value is ready to be used.
|
||||
n.nots = make(map[Notifiee]struct{})
|
||||
}
|
||||
n.nots[e] = struct{}{}
|
||||
n.mu.Unlock()
|
||||
}
|
||||
|
||||
// StopNotifying stops notifying Notifiee e. This function
|
||||
// is meant to be called behind your own type-safe function(s):
|
||||
//
|
||||
// // generic function for pattern-following
|
||||
// func (r *Rocket) StopNotify(n Notifiee) {
|
||||
// r.notifier.StopNotify(n)
|
||||
// }
|
||||
//
|
||||
// // or as part of other functions
|
||||
// func (r *Rocket) Detach(c Capsule) {
|
||||
// r.notifier.StopNotify(c)
|
||||
// r.capsule = nil
|
||||
// }
|
||||
//
|
||||
func (n *Notifier) StopNotify(e Notifiee) {
|
||||
n.mu.Lock()
|
||||
if n.nots != nil { // so that zero-value is ready to be used.
|
||||
delete(n.nots, e)
|
||||
}
|
||||
n.mu.Unlock()
|
||||
}
|
||||
|
||||
// NotifyAll messages the notifier's notifiees with a given notification.
|
||||
// This is done by calling the given function with each notifiee. It is
|
||||
// meant to be called with your own type-safe notification functions:
|
||||
//
|
||||
// func (r *Rocket) Launch() {
|
||||
// r.notifyAll(func(n Notifiee) {
|
||||
// n.Launched(r)
|
||||
// })
|
||||
// }
|
||||
//
|
||||
// // make it private so only you can use it. This function is necessary
|
||||
// // to make sure you only up-cast in one place. You control who you added
|
||||
// // to be a notifiee. If Go adds generics, maybe we can get rid of this
|
||||
// // method but for now it is like wrapping a type-less container with
|
||||
// // a type safe interface.
|
||||
// func (r *Rocket) notifyAll(notify func(Notifiee)) {
|
||||
// r.notifier.NotifyAll(func(n notifier.Notifiee) {
|
||||
// notify(n.(Notifiee))
|
||||
// })
|
||||
// }
|
||||
//
|
||||
// Note well: each notification is launched in its own goroutine, so they
|
||||
// can be processed concurrently, and so that whatever the notification does
|
||||
// it _never_ blocks out the client. This is so that consumers _cannot_ add
|
||||
// hooks into your object that block you accidentally.
|
||||
func (n *Notifier) NotifyAll(notify func(Notifiee)) {
|
||||
n.mu.Lock()
|
||||
if n.nots != nil { // so that zero-value is ready to be used.
|
||||
for notifiee := range n.nots {
|
||||
go notify(notifiee)
|
||||
// TODO find a good way to rate limit this without blocking notifier.
|
||||
}
|
||||
}
|
||||
n.mu.Unlock()
|
||||
}
|
||||
207
thirdparty/notifier/notifier_test.go
vendored
Normal file
207
thirdparty/notifier/notifier_test.go
vendored
Normal file
@ -0,0 +1,207 @@
|
||||
package notifier
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// test data structures
|
||||
type Router struct {
|
||||
queue chan Packet
|
||||
notifier Notifier
|
||||
}
|
||||
|
||||
type Packet struct{}
|
||||
|
||||
type RouterNotifiee interface {
|
||||
Enqueued(*Router, Packet)
|
||||
Forwarded(*Router, Packet)
|
||||
Dropped(*Router, Packet)
|
||||
}
|
||||
|
||||
func (r *Router) Notify(n RouterNotifiee) {
|
||||
r.notifier.Notify(n)
|
||||
}
|
||||
|
||||
func (r *Router) StopNotify(n RouterNotifiee) {
|
||||
r.notifier.StopNotify(n)
|
||||
}
|
||||
|
||||
func (r *Router) notifyAll(notify func(n RouterNotifiee)) {
|
||||
r.notifier.NotifyAll(func(n Notifiee) {
|
||||
notify(n.(RouterNotifiee))
|
||||
})
|
||||
}
|
||||
|
||||
func (r *Router) Receive(p Packet) {
|
||||
|
||||
select {
|
||||
case r.queue <- p: // enqueued
|
||||
r.notifyAll(func(n RouterNotifiee) {
|
||||
n.Enqueued(r, p)
|
||||
})
|
||||
|
||||
default: // drop
|
||||
r.notifyAll(func(n RouterNotifiee) {
|
||||
n.Dropped(r, p)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Router) Forward() {
|
||||
p := <-r.queue
|
||||
r.notifyAll(func(n RouterNotifiee) {
|
||||
n.Forwarded(r, p)
|
||||
})
|
||||
}
|
||||
|
||||
type Metrics struct {
|
||||
enqueued int
|
||||
forwarded int
|
||||
dropped int
|
||||
received chan struct{}
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func (m *Metrics) Enqueued(*Router, Packet) {
|
||||
m.Lock()
|
||||
m.enqueued++
|
||||
m.Unlock()
|
||||
if m.received != nil {
|
||||
m.received <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Metrics) Forwarded(*Router, Packet) {
|
||||
m.Lock()
|
||||
m.forwarded++
|
||||
m.Unlock()
|
||||
if m.received != nil {
|
||||
m.received <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Metrics) Dropped(*Router, Packet) {
|
||||
m.Lock()
|
||||
m.dropped++
|
||||
m.Unlock()
|
||||
if m.received != nil {
|
||||
m.received <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Metrics) String() string {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
return fmt.Sprintf("%d enqueued, %d forwarded, %d in queue, %d dropped",
|
||||
m.enqueued, m.forwarded, m.enqueued-m.forwarded, m.dropped)
|
||||
}
|
||||
|
||||
func TestNotifies(t *testing.T) {
|
||||
|
||||
m := Metrics{received: make(chan struct{})}
|
||||
r := Router{queue: make(chan Packet, 10)}
|
||||
r.Notify(&m)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
r.Receive(Packet{})
|
||||
<-m.received
|
||||
if m.enqueued != (1 + i) {
|
||||
t.Error("not notifying correctly", m.enqueued, 1+i)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
r.Receive(Packet{})
|
||||
<-m.received
|
||||
if m.enqueued != 10 {
|
||||
t.Error("not notifying correctly", m.enqueued, 10)
|
||||
}
|
||||
if m.dropped != (1 + i) {
|
||||
t.Error("not notifying correctly", m.dropped, 1+i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStopsNotifying(t *testing.T) {
|
||||
m := Metrics{received: make(chan struct{})}
|
||||
r := Router{queue: make(chan Packet, 10)}
|
||||
r.Notify(&m)
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
r.Receive(Packet{})
|
||||
<-m.received
|
||||
if m.enqueued != (1 + i) {
|
||||
t.Error("not notifying correctly")
|
||||
}
|
||||
}
|
||||
|
||||
r.StopNotify(&m)
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
r.Receive(Packet{})
|
||||
select {
|
||||
case <-m.received:
|
||||
t.Error("did not stop notifying")
|
||||
default:
|
||||
}
|
||||
if m.enqueued != 5 {
|
||||
t.Error("did not stop notifying")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestThreadsafe(t *testing.T) {
|
||||
N := 1000
|
||||
r := Router{queue: make(chan Packet, 10)}
|
||||
m1 := Metrics{received: make(chan struct{})}
|
||||
m2 := Metrics{received: make(chan struct{})}
|
||||
m3 := Metrics{received: make(chan struct{})}
|
||||
r.Notify(&m1)
|
||||
r.Notify(&m2)
|
||||
r.Notify(&m3)
|
||||
|
||||
var n int
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < N; i++ {
|
||||
n++
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
r.Receive(Packet{})
|
||||
}()
|
||||
|
||||
if i%3 == 0 {
|
||||
n++
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
r.Forward()
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// drain queues
|
||||
for i := 0; i < (n * 3); i++ {
|
||||
select {
|
||||
case <-m1.received:
|
||||
case <-m2.received:
|
||||
case <-m3.received:
|
||||
}
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// counts should be correct and all agree. and this should
|
||||
// run fine under `go test -race -cpu=5`
|
||||
|
||||
t.Log("m1", m1.String())
|
||||
t.Log("m2", m2.String())
|
||||
t.Log("m3", m3.String())
|
||||
|
||||
if m1.String() != m2.String() || m2.String() != m3.String() {
|
||||
t.Error("counts disagree")
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user