// Copyright 2015 CoreOS, Inc.
//
// 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.

package network

import (
	"errors"
	"net"
	"reflect"
	"testing"
)

func mustParseMac(mac net.HardwareAddr, err error) net.HardwareAddr {
	if err != nil {
		panic(err)
	}
	return mac
}

func TestProcessVMwareNetconf(t *testing.T) {
	tests := []struct {
		config map[string]string

		interfaces []InterfaceGenerator
		err        error
	}{
		{},
		{
			config: map[string]string{
				"interface.0.dhcp": "yes",
			},
			interfaces: []InterfaceGenerator{
				&physicalInterface{logicalInterface{
					config: configMethodDHCP{},
				}},
			},
		},
		{
			config: map[string]string{
				"interface.0.mac":  "00:11:22:33:44:55",
				"interface.0.dhcp": "yes",
			},
			interfaces: []InterfaceGenerator{
				&physicalInterface{logicalInterface{
					hwaddr: mustParseMac(net.ParseMAC("00:11:22:33:44:55")),
					config: configMethodDHCP{hwaddress: mustParseMac(net.ParseMAC("00:11:22:33:44:55"))},
				}},
			},
		},
		{
			config: map[string]string{
				"interface.0.name": "eth0",
				"interface.0.dhcp": "yes",
			},
			interfaces: []InterfaceGenerator{
				&physicalInterface{logicalInterface{
					name:   "eth0",
					config: configMethodDHCP{},
				}},
			},
		},
		{
			config: map[string]string{
				"interface.0.mac":                 "00:11:22:33:44:55",
				"interface.0.ip.0.address":        "10.0.0.100/24",
				"interface.0.route.0.gateway":     "10.0.0.1",
				"interface.0.route.0.destination": "0.0.0.0/0",
			},
			interfaces: []InterfaceGenerator{
				&physicalInterface{logicalInterface{
					hwaddr: mustParseMac(net.ParseMAC("00:11:22:33:44:55")),
					config: configMethodStatic{
						hwaddress: mustParseMac(net.ParseMAC("00:11:22:33:44:55")),
						addresses: []net.IPNet{{IP: net.ParseIP("10.0.0.100"), Mask: net.CIDRMask(24, net.IPv4len*8)}},
						// I realize how upset you must be that I am shoving an IPMask into an IP. This is because net.IPv4zero is
						// actually a magic IPv6 address which ruins our equality check. What's that? Just use IP::Equal()? I'd rather
						// DeepEqual just handle that for me, but until Go gets operator overloading, we are stuck with this.
						routes: []route{{
							destination: net.IPNet{IP: net.IP(net.CIDRMask(0, net.IPv4len*8)), Mask: net.CIDRMask(0, net.IPv4len*8)},
							gateway:     net.ParseIP("10.0.0.1")},
						},
					},
				}},
			},
		},
		{
			config: map[string]string{
				"dns.server.0":                    "1.2.3.4",
				"dns.server.1":                    "5.6.7.8",
				"dns.domain.0":                    "coreos.com",
				"dns.domain.1":                    "example.com",
				"interface.0.mac":                 "00:11:22:33:44:55",
				"interface.0.ip.0.address":        "10.0.0.100/24",
				"interface.0.ip.1.address":        "10.0.0.101/24",
				"interface.0.route.0.gateway":     "10.0.0.1",
				"interface.0.route.0.destination": "0.0.0.0/0",
				"interface.1.name":                "eth0",
				"interface.1.ip.0.address":        "10.0.1.100/24",
				"interface.1.route.0.gateway":     "10.0.1.1",
				"interface.1.route.0.destination": "0.0.0.0/0",
				"interface.2.dhcp":                "yes",
				"interface.2.mac":                 "00:11:22:33:44:77",
			},
			interfaces: []InterfaceGenerator{
				&physicalInterface{logicalInterface{
					hwaddr: mustParseMac(net.ParseMAC("00:11:22:33:44:55")),
					config: configMethodStatic{
						hwaddress: mustParseMac(net.ParseMAC("00:11:22:33:44:55")),
						addresses: []net.IPNet{
							{IP: net.ParseIP("10.0.0.100"), Mask: net.CIDRMask(24, net.IPv4len*8)},
							{IP: net.ParseIP("10.0.0.101"), Mask: net.CIDRMask(24, net.IPv4len*8)},
						},
						routes: []route{{
							destination: net.IPNet{IP: net.IP(net.CIDRMask(0, net.IPv4len*8)), Mask: net.CIDRMask(0, net.IPv4len*8)},
							gateway:     net.ParseIP("10.0.0.1")},
						},
						nameservers: []net.IP{net.ParseIP("1.2.3.4"), net.ParseIP("5.6.7.8")},
						domains:     []string{"coreos.com", "example.com"},
					},
				}},
				&physicalInterface{logicalInterface{
					name: "eth0",
					config: configMethodStatic{
						addresses: []net.IPNet{{IP: net.ParseIP("10.0.1.100"), Mask: net.CIDRMask(24, net.IPv4len*8)}},
						routes: []route{{
							destination: net.IPNet{IP: net.IP(net.CIDRMask(0, net.IPv4len*8)), Mask: net.CIDRMask(0, net.IPv4len*8)},
							gateway:     net.ParseIP("10.0.1.1")},
						},
						nameservers: []net.IP{net.ParseIP("1.2.3.4"), net.ParseIP("5.6.7.8")},
						domains:     []string{"coreos.com", "example.com"},
					},
				}},
				&physicalInterface{logicalInterface{
					hwaddr: mustParseMac(net.ParseMAC("00:11:22:33:44:77")),
					config: configMethodDHCP{hwaddress: mustParseMac(net.ParseMAC("00:11:22:33:44:77"))},
				}},
			},
		},
		{
			config: map[string]string{"dns.server.0": "test dns"},
			err:    errors.New(`invalid nameserver: "test dns"`),
		},
	}

	for i, tt := range tests {
		interfaces, err := ProcessVMwareNetconf(tt.config)
		if !reflect.DeepEqual(tt.err, err) {
			t.Errorf("bad error (#%d): want %v, got %v", i, tt.err, err)
		}
		if !reflect.DeepEqual(tt.interfaces, interfaces) {
			t.Errorf("bad interfaces (#%d): want %#v, got %#v", i, tt.interfaces, interfaces)
			for _, iface := range tt.interfaces {
				t.Logf("  want: %#v", iface)
			}
			for _, iface := range interfaces {
				t.Logf("  got:  %#v", iface)
			}
		}
	}
}

func TestProcessAddressConfig(t *testing.T) {
	tests := []struct {
		config map[string]string
		prefix string

		addresses []net.IPNet
		err       error
	}{
		{},

		// static - ipv4
		{
			config: map[string]string{
				"ip.0.address": "10.0.0.100/24",
			},

			addresses: []net.IPNet{{IP: net.ParseIP("10.0.0.100"), Mask: net.CIDRMask(24, net.IPv4len*8)}},
		},
		{
			config: map[string]string{
				"this.is.a.prefix.ip.0.address": "10.0.0.100/24",
			},
			prefix: "this.is.a.prefix.",

			addresses: []net.IPNet{{IP: net.ParseIP("10.0.0.100"), Mask: net.CIDRMask(24, net.IPv4len*8)}},
		},
		{
			config: map[string]string{
				"ip.0.address": "10.0.0.100/24",
				"ip.1.address": "10.0.0.101/24",
				"ip.2.address": "10.0.0.102/24",
			},

			addresses: []net.IPNet{
				{IP: net.ParseIP("10.0.0.100"), Mask: net.CIDRMask(24, net.IPv4len*8)},
				{IP: net.ParseIP("10.0.0.101"), Mask: net.CIDRMask(24, net.IPv4len*8)},
				{IP: net.ParseIP("10.0.0.102"), Mask: net.CIDRMask(24, net.IPv4len*8)},
			},
		},

		// static - ipv6
		{
			config: map[string]string{
				"ip.0.address": "fe00::100/64",
			},

			addresses: []net.IPNet{{IP: net.ParseIP("fe00::100"), Mask: net.IPMask(net.CIDRMask(64, net.IPv6len*8))}},
		},
		{
			config: map[string]string{
				"ip.0.address": "fe00::100/64",
				"ip.1.address": "fe00::101/64",
				"ip.2.address": "fe00::102/64",
			},

			addresses: []net.IPNet{
				{IP: net.ParseIP("fe00::100"), Mask: net.CIDRMask(64, net.IPv6len*8)},
				{IP: net.ParseIP("fe00::101"), Mask: net.CIDRMask(64, net.IPv6len*8)},
				{IP: net.ParseIP("fe00::102"), Mask: net.CIDRMask(64, net.IPv6len*8)},
			},
		},

		// invalid
		{
			config: map[string]string{
				"ip.0.address": "test address",
			},

			err: errors.New(`invalid address: "test address"`),
		},
	}

	for i, tt := range tests {
		addresses, err := processAddressConfig(tt.config, tt.prefix)
		if !reflect.DeepEqual(tt.err, err) {
			t.Errorf("bad error (#%d): want %v, got %v", i, tt.err, err)
		}
		if err != nil {
			continue
		}

		if !reflect.DeepEqual(tt.addresses, addresses) {
			t.Errorf("bad addresses (#%d): want %#v, got %#v", i, tt.addresses, addresses)
		}
	}
}

func TestProcessRouteConfig(t *testing.T) {
	tests := []struct {
		config map[string]string
		prefix string

		routes []route
		err    error
	}{
		{},

		{
			config: map[string]string{
				"route.0.gateway":     "10.0.0.1",
				"route.0.destination": "0.0.0.0/0",
			},

			routes: []route{{destination: net.IPNet{IP: net.IP(net.CIDRMask(0, net.IPv4len*8)), Mask: net.CIDRMask(0, net.IPv4len*8)}, gateway: net.ParseIP("10.0.0.1")}},
		},
		{
			config: map[string]string{
				"this.is.a.prefix.route.0.gateway":     "10.0.0.1",
				"this.is.a.prefix.route.0.destination": "0.0.0.0/0",
			},
			prefix: "this.is.a.prefix.",

			routes: []route{{destination: net.IPNet{IP: net.IP(net.CIDRMask(0, net.IPv4len*8)), Mask: net.CIDRMask(0, net.IPv4len*8)}, gateway: net.ParseIP("10.0.0.1")}},
		},
		{
			config: map[string]string{
				"route.0.gateway":     "fe00::1",
				"route.0.destination": "::/0",
			},

			routes: []route{{destination: net.IPNet{IP: net.IPv6zero, Mask: net.IPMask(net.IPv6zero)}, gateway: net.ParseIP("fe00::1")}},
		},

		// invalid
		{
			config: map[string]string{
				"route.0.gateway":     "test gateway",
				"route.0.destination": "0.0.0.0/0",
			},

			err: errors.New(`invalid gateway: "test gateway"`),
		},
		{
			config: map[string]string{
				"route.0.gateway":     "10.0.0.1",
				"route.0.destination": "test destination",
			},

			err: &net.ParseError{Type: "CIDR address", Text: "test destination"},
		},
	}

	for i, tt := range tests {
		routes, err := processRouteConfig(tt.config, tt.prefix)
		if !reflect.DeepEqual(tt.err, err) {
			t.Errorf("bad error (#%d): want %v, got %v", i, tt.err, err)
		}
		if err != nil {
			continue
		}

		if !reflect.DeepEqual(tt.routes, routes) {
			t.Errorf("bad routes (#%d): want %#v, got %#v", i, tt.routes, routes)
		}
	}
}

func TestProcessDHCPConfig(t *testing.T) {
	tests := []struct {
		config map[string]string
		prefix string

		dhcp bool
		err  error
	}{
		{},

		// prefix
		{config: map[string]string{"this.is.a.prefix.mac": ""}, prefix: "this.is.a.prefix.", dhcp: false},
		{config: map[string]string{"this.is.a.prefix.dhcp": "yes"}, prefix: "this.is.a.prefix.", dhcp: true},

		// dhcp
		{config: map[string]string{"dhcp": "yes"}, dhcp: true},
		{config: map[string]string{"dhcp": "no"}, dhcp: false},

		// invalid
		{config: map[string]string{"dhcp": "blah"}, err: errors.New(`invalid DHCP option: "blah"`)},
	}

	for i, tt := range tests {
		dhcp, err := processDHCPConfig(tt.config, tt.prefix)
		if !reflect.DeepEqual(tt.err, err) {
			t.Errorf("bad error (#%d): want %v, got %v", i, tt.err, err)
		}
		if err != nil {
			continue
		}

		if tt.dhcp != dhcp {
			t.Errorf("bad dhcp (#%d): want %v, got %v", i, tt.dhcp, dhcp)
		}
	}
}
