package natpmp

import (
	
	
	
)

// Implement the NAT-PMP protocol, typically supported by Apple routers and open source
// routers such as DD-WRT and Tomato.
//
// See https://tools.ietf.org/rfc/rfc6886.txt
//
// Usage:
//
//    client := natpmp.NewClient(gatewayIP)
//    response, err := client.GetExternalAddress()

// The recommended mapping lifetime for AddPortMapping.
const RECOMMENDED_MAPPING_LIFETIME_SECONDS = 3600

// Interface used to make remote procedure calls.
type caller interface {
	call(msg []byte, timeout time.Duration) (result []byte, err error)
}

// Client is a NAT-PMP protocol client.
type Client struct {
	caller  caller
	timeout time.Duration
}

// Create a NAT-PMP client for the NAT-PMP server at the gateway.
// Uses default timeout which is around 128 seconds.
func ( net.IP) ( *Client) {
	return &Client{&network{}, 0}
}

// Create a NAT-PMP client for the NAT-PMP server at the gateway, with a timeout.
// Timeout defines the total amount of time we will keep retrying before giving up.
func ( net.IP,  time.Duration) ( *Client) {
	return &Client{&network{}, }
}

// Results of the NAT-PMP GetExternalAddress operation.
type GetExternalAddressResult struct {
	SecondsSinceStartOfEpoc uint32
	ExternalIPAddress       [4]byte
}

// Get the external address of the router.
//
// Note that this call can take up to 128 seconds to return.
func ( *Client) () ( *GetExternalAddressResult,  error) {
	 := make([]byte, 2)
	[0] = 0 // Version 0
	[1] = 0 // OP Code 0
	,  := .rpc(, 12)
	if  != nil {
		return
	}
	 = &GetExternalAddressResult{}
	.SecondsSinceStartOfEpoc = readNetworkOrderUint32([4:8])
	copy(.ExternalIPAddress[:], [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.
// Note that this call can take up to 128 seconds to return.
func ( *Client) ( string, ,  int,  int) ( *AddPortMappingResult,  error) {
	var  byte
	if  == "udp" {
		 = 1
	} else if  == "tcp" {
		 = 2
	} else {
		 = fmt.Errorf("unknown protocol %v", )
		return
	}
	 := make([]byte, 12)
	[0] = 0 // Version 0
	[1] = 
	// [2:3] is reserved.
	writeNetworkOrderUint16([4:6], uint16())
	writeNetworkOrderUint16([6:8], uint16())
	writeNetworkOrderUint32([8:12], uint32())
	,  := .rpc(, 16)
	if  != nil {
		return
	}
	 = &AddPortMappingResult{}
	.SecondsSinceStartOfEpoc = readNetworkOrderUint32([4:8])
	.InternalPort = readNetworkOrderUint16([8:10])
	.MappedExternalPort = readNetworkOrderUint16([10:12])
	.PortMappingLifetimeInSeconds = readNetworkOrderUint32([12:16])
	return
}

func ( *Client) ( []byte,  int) ( []byte,  error) {
	,  = .caller.call(, .timeout)
	if  != nil {
		return
	}
	 = protocolChecks(, , )
	return
}

func protocolChecks( []byte,  int,  []byte) ( error) {
	if len() !=  {
		 = fmt.Errorf("unexpected result size %d, expected %d", len(), )
		return
	}
	if [0] != 0 {
		 = fmt.Errorf("unknown protocol version %d", [0])
		return
	}
	 := [1] | 0x80
	if [1] !=  {
		 = fmt.Errorf("Unexpected opcode %d. Expected %d", [1], )
		return
	}
	 := readNetworkOrderUint16([2:4])
	if  != 0 {
		 = fmt.Errorf("Non-zero result code %d", )
		return
	}
	// If we got here the RPC is good.
	return
}

func writeNetworkOrderUint16( []byte,  uint16) {
	[0] = byte( >> 8)
	[1] = byte()
}

func writeNetworkOrderUint32( []byte,  uint32) {
	[0] = byte( >> 24)
	[1] = byte( >> 16)
	[2] = byte( >> 8)
	[3] = byte()
}

func readNetworkOrderUint16( []byte) uint16 {
	return (uint16([0]) << 8) | uint16([1])
}

func readNetworkOrderUint32( []byte) uint32 {
	return (uint32([0]) << 24) | (uint32([1]) << 16) | (uint32([2]) << 8) | uint32([3])
}