// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
package nat
import (
	"fmt"
	"io"
	"net"
	"net/http"
	"runtime"
	"strings"
	"testing"
	"github.com/huin/goupnp/httpu"
)
func TestUPNP_DDWRT(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skipf("disabled to avoid firewall prompt")
	}
	dev := &fakeIGD{
		t: t,
		ssdpResp: "HTTP/1.1 200 OK\r\n" +
			"Cache-Control: max-age=300\r\n" +
			"Date: Sun, 10 May 2015 10:05:33 GMT\r\n" +
			"Ext: \r\n" +
			"Location: http://{{listenAddr}}/InternetGatewayDevice.xml\r\n" +
			"Server: POSIX UPnP/1.0 DD-WRT Linux/V24\r\n" +
			"ST: urn:schemas-upnp-org:device:WANConnectionDevice:1\r\n" +
			"USN: uuid:CB2471CC-CF2E-9795-8D9C-E87B34C16800::urn:schemas-upnp-org:device:WANConnectionDevice:1\r\n" +
			"\r\n",
		httpResps: map[string]string{
			"GET /InternetGatewayDevice.xml": `
				 
				 
					 
						 1
						 0
					 
					 
						 urn:schemas-upnp-org:device:InternetGatewayDevice:1
						 DD-WRT
						 http://www.dd-wrt.com
						 Gateway
						 Asus RT-N16:DD-WRT
						 Asus RT-N16
						 V24
						 0000001
						 http://www.dd-wrt.com
						 uuid:A13AB4C3-3A14-E386-DE6A-EFEA923A06FE
						 
							 
								 urn:schemas-upnp-org:service:Layer3Forwarding:1
								 urn:upnp-org:serviceId:L3Forwarding1
								 /x_layer3forwarding.xml
								 /control?Layer3Forwarding
								 /event?Layer3Forwarding
							 
						 
						 
							 
								 urn:schemas-upnp-org:device:WANDevice:1
								 WANDevice
								 DD-WRT
								 http://www.dd-wrt.com
								 Gateway
								 router
								 http://www.dd-wrt.com
								 uuid:48FD569B-F9A9-96AE-4EE6-EB403D3DB91A
								 
									 
										 urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1
										 urn:upnp-org:serviceId:WANCommonIFC1
										 /x_wancommoninterfaceconfig.xml
										 /control?WANCommonInterfaceConfig
										 /event?WANCommonInterfaceConfig
									 
								 
								 
									 
										 urn:schemas-upnp-org:device:WANConnectionDevice:1
										 WAN Connection Device
										 DD-WRT
										 http://www.dd-wrt.com
										 Gateway
										 router
										 http://www.dd-wrt.com
										 uuid:CB2471CC-CF2E-9795-8D9C-E87B34C16800
										 
											 
												 urn:schemas-upnp-org:service:WANIPConnection:1
												 urn:upnp-org:serviceId:WANIPConn1
												 /x_wanipconnection.xml
												 /control?WANIPConnection
												 /event?WANIPConnection
											 
										 
									 
								 
							 
							 
								 urn:schemas-upnp-org:device:LANDevice:1
								 LANDevice
								 DD-WRT
								 http://www.dd-wrt.com
								 Gateway
								 router
								 http://www.dd-wrt.com
								 uuid:04021998-3B35-2BDB-7B3C-99DA4435DA09
								 
									 
										 urn:schemas-upnp-org:service:LANHostConfigManagement:1
										 urn:upnp-org:serviceId:LANHostCfg1
										 /x_lanhostconfigmanagement.xml
										 /control?LANHostConfigManagement
										 /event?LANHostConfigManagement
									 
								 
							 
						 
						 http://{{listenAddr}}
					 
				 
			`,
			// The response to our GetNATRSIPStatus call. This
			// particular implementation has a bug where the elements
			// inside u:GetNATRSIPStatusResponse are not properly
			// namespaced.
			"POST /control?WANIPConnection": `
				 
				 
				 
				 0
				 1
				 
				 
				 
			`,
		},
	}
	if err := dev.listen(); err != nil {
		t.Skipf("cannot listen: %v", err)
	}
	dev.serve()
	defer dev.close()
	// Attempt to discover the fake device.
	discovered := discoverUPnP()
	if discovered == nil {
		t.Fatalf("not discovered")
	}
	upnp, _ := discovered.(*upnp)
	if upnp.service != "IGDv1-IP1" {
		t.Errorf("upnp.service mismatch: got %q, want %q", upnp.service, "IGDv1-IP1")
	}
	wantURL := "http://" + dev.listener.Addr().String() + "/InternetGatewayDevice.xml"
	if upnp.dev.URLBaseStr != wantURL {
		t.Errorf("upnp.dev.URLBaseStr mismatch: got %q, want %q", upnp.dev.URLBaseStr, wantURL)
	}
}
// fakeIGD presents itself as a discoverable UPnP device which sends
// canned responses to HTTPU and HTTP requests.
type fakeIGD struct {
	t *testing.T // for logging
	listener      net.Listener
	mcastListener *net.UDPConn
	// This should be a complete HTTP response (including headers).
	// It is sent as the response to any sspd packet. Any occurrence
	// of "{{listenAddr}}" is replaced with the actual TCP listen
	// address of the HTTP server.
	ssdpResp string
	// This one should contain XML payloads for all requests
	// performed. The keys contain method and path, e.g. "GET /foo/bar".
	// As with ssdpResp, "{{listenAddr}}" is replaced with the TCP
	// listen address.
	httpResps map[string]string
}
// httpu.Handler
func (dev *fakeIGD) ServeMessage(r *http.Request) {
	dev.t.Logf(`HTTPU request %s %s`, r.Method, r.RequestURI)
	conn, err := net.Dial("udp4", r.RemoteAddr)
	if err != nil {
		fmt.Printf("reply Dial error: %v", err)
		return
	}
	defer conn.Close()
	io.WriteString(conn, dev.replaceListenAddr(dev.ssdpResp))
}
// http.Handler
func (dev *fakeIGD) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if resp, ok := dev.httpResps[r.Method+" "+r.RequestURI]; ok {
		dev.t.Logf(`HTTP request "%s %s" --> %d`, r.Method, r.RequestURI, 200)
		io.WriteString(w, dev.replaceListenAddr(resp))
	} else {
		dev.t.Logf(`HTTP request "%s %s" --> %d`, r.Method, r.RequestURI, 404)
		w.WriteHeader(http.StatusNotFound)
	}
}
func (dev *fakeIGD) replaceListenAddr(resp string) string {
	return strings.Replace(resp, "{{listenAddr}}", dev.listener.Addr().String(), -1)
}
func (dev *fakeIGD) listen() (err error) {
	if dev.listener, err = net.Listen("tcp", "127.0.0.1:0"); err != nil {
		return err
	}
	laddr := &net.UDPAddr{IP: net.ParseIP("239.255.255.250"), Port: 1900}
	if dev.mcastListener, err = net.ListenMulticastUDP("udp", nil, laddr); err != nil {
		dev.listener.Close()
		return err
	}
	return nil
}
func (dev *fakeIGD) serve() {
	go httpu.Serve(dev.mcastListener, dev)
	go http.Serve(dev.listener, dev)
}
func (dev *fakeIGD) close() {
	dev.mcastListener.Close()
	dev.listener.Close()
}