// Copyright 2012 Google, Inc. All rights reserved.
// Copyright 2009-2011 Andreas Krennmair. All rights reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file in the root of the source
// tree.

package layers

import (
	"encoding/binary"
	"errors"
	"fmt"
	"net"
	"strings"

	"github.com/gopacket/gopacket"
)

type IPv4Flag uint8

const (
	IPv4EvilBit       IPv4Flag = 1 << 2 // http://tools.ietf.org/html/rfc3514 ;)
	IPv4DontFragment  IPv4Flag = 1 << 1
	IPv4MoreFragments IPv4Flag = 1 << 0
)

func (f IPv4Flag) String() string {
	var s []string
	if f&IPv4EvilBit != 0 {
		s = append(s, "Evil")
	}
	if f&IPv4DontFragment != 0 {
		s = append(s, "DF")
	}
	if f&IPv4MoreFragments != 0 {
		s = append(s, "MF")
	}
	return strings.Join(s, "|")
}

// IPv4 is the header of an IP packet.
type IPv4 struct {
	BaseLayer
	Version    uint8
	IHL        uint8
	TOS        uint8
	Length     uint16
	Id         uint16
	Flags      IPv4Flag
	FragOffset uint16
	TTL        uint8
	Protocol   IPProtocol
	Checksum   uint16
	SrcIP      net.IP
	DstIP      net.IP
	Options    []IPv4Option
	Padding    []byte
}

// LayerType returns LayerTypeIPv4
func (i *IPv4) LayerType() gopacket.LayerType { return LayerTypeIPv4 }
func (i *IPv4) NetworkFlow() gopacket.Flow {
	return gopacket.NewFlow(EndpointIPv4, i.SrcIP, i.DstIP)
}

type IPv4Option struct {
	OptionType   uint8
	OptionLength uint8
	OptionData   []byte
}

func (i IPv4Option) String() string {
	return fmt.Sprintf("IPv4Option(%v:%v)", i.OptionType, i.OptionData)
}

// for the current ipv4 options, return the number of bytes (including
// padding that the options used)
func (ip *IPv4) getIPv4OptionSize() uint8 {
	optionSize := uint8(0)
	for _, opt := range ip.Options {
		switch opt.OptionType {
		case 0:
			// this is the end of option lists
			optionSize++
		case 1:
			// this is the padding
			optionSize++
		default:
			optionSize += opt.OptionLength

		}
	}
	// make sure the options are aligned to 32 bit boundary
	if (optionSize % 4) != 0 {
		optionSize += 4 - (optionSize % 4)
	}
	return optionSize
}

// SerializeTo writes the serialized form of this layer into the
// SerializationBuffer, implementing gopacket.SerializableLayer.
func (ip *IPv4) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error {
	optionLength := ip.getIPv4OptionSize()
	bytes, err := b.PrependBytes(20 + int(optionLength))
	if err != nil {
		return err
	}
	if opts.FixLengths {
		ip.IHL = 5 + (optionLength / 4)
		ip.Length = uint16(len(b.Bytes()))
	}
	bytes[0] = (ip.Version << 4) | ip.IHL
	bytes[1] = ip.TOS
	binary.BigEndian.PutUint16(bytes[2:], ip.Length)
	binary.BigEndian.PutUint16(bytes[4:], ip.Id)
	binary.BigEndian.PutUint16(bytes[6:], ip.flagsfrags())
	bytes[8] = ip.TTL
	bytes[9] = byte(ip.Protocol)
	if err := ip.AddressTo4(); err != nil {
		return err
	}
	copy(bytes[12:16], ip.SrcIP)
	copy(bytes[16:20], ip.DstIP)

	curLocation := 20
	// Now, we will encode the options
	for _, opt := range ip.Options {
		switch opt.OptionType {
		case 0:
			// this is the end of option lists
			bytes[curLocation] = 0
			curLocation++
		case 1:
			// this is the padding
			bytes[curLocation] = 1
			curLocation++
		default:
			bytes[curLocation] = opt.OptionType
			bytes[curLocation+1] = opt.OptionLength

			// sanity checking to protect us from buffer overrun
			if len(opt.OptionData) > int(opt.OptionLength-2) {
				return errors.New("option length is smaller than length of option data")
			}
			copy(bytes[curLocation+2:curLocation+int(opt.OptionLength)], opt.OptionData)
			curLocation += int(opt.OptionLength)
		}
	}

	if opts.ComputeChecksums {
		// Clear checksum bytes
		bytes[10] = 0
		bytes[11] = 0

		csum := gopacket.ComputeChecksum(bytes, 0)
		ip.Checksum = gopacket.FoldChecksum(csum)
	}
	binary.BigEndian.PutUint16(bytes[10:], ip.Checksum)
	return nil
}

func (ip *IPv4) flagsfrags() (ff uint16) {
	ff |= uint16(ip.Flags) << 13
	ff |= ip.FragOffset
	return
}

// DecodeFromBytes decodes the given bytes into this layer.
func (ip *IPv4) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error {
	if len(data) < 20 {
		df.SetTruncated()
		return fmt.Errorf("Invalid ip4 header. Length %d less than 20", len(data))
	}

	ip.Length = binary.BigEndian.Uint16(data[2:4])
	ip.IHL = uint8(data[0]) & 0x0F

	// This code is added for the following enviroment:
	// * Windows 10 with TSO option activated. ( tested on Hyper-V, RealTek ethernet driver )
	if ip.Length == 0 {
		// If using TSO(TCP Segmentation Offload), length is zero.
		// The actual packet length is the length of data.
		ip.Length = uint16(len(data))
	}

	if ip.Length < 20 {
		return fmt.Errorf("Invalid (too small) IP length (%d < 20)", ip.Length)
	} else if ip.IHL < 5 {
		return fmt.Errorf("Invalid (too small) IP header length (%d < 5)", ip.IHL)
	} else if int(ip.IHL*4) > int(ip.Length) {
		return fmt.Errorf("Invalid IP header length > IP length (%d > %d)", ip.IHL, ip.Length)
	}

	if cmp := len(data) - int(ip.Length); cmp > 0 {
		data = data[:ip.Length]
	} else if cmp < 0 {
		df.SetTruncated()
		if int(ip.IHL)*4 > len(data) {
			return errors.New("Not all IP header bytes available")
		}
	}

	ip.Options = ip.Options[:0]
	ip.Contents = data[:ip.IHL*4]
	ip.Payload = data[ip.IHL*4:]

	// From here on, data contains the header options.
	headerOptionsData := data[20 : ip.IHL*4]
	// Pull out IP options
pullOutOptions:
	for len(headerOptionsData) > 0 {
		if ip.Options == nil {
			// Pre-allocate to avoid growing the slice too much.
			ip.Options = make([]IPv4Option, 0, 4)
		}

		opt := IPv4Option{OptionType: headerOptionsData[0]}
		switch opt.OptionType {
		case 0: // End of options
			opt.OptionLength = 1
			ip.Options = append(ip.Options, opt)
			ip.Padding = headerOptionsData[1:]
			headerOptionsData = headerOptionsData[1:]
			break pullOutOptions
		case 1: // 1 byte padding
			opt.OptionLength = 1
			headerOptionsData = headerOptionsData[1:]
			ip.Options = append(ip.Options, opt)
		default:
			if len(headerOptionsData) < 2 {
				df.SetTruncated()
				return fmt.Errorf("Invalid ip4 option length. Length %d less than 2", len(headerOptionsData))
			}

			opt.OptionLength = headerOptionsData[1]
			if len(headerOptionsData) < int(opt.OptionLength) {
				df.SetTruncated()
				return fmt.Errorf("IP option length exceeds remaining IP header size, option type %v length %v", opt.OptionType, opt.OptionLength)
			}
			if opt.OptionLength <= 2 {
				return fmt.Errorf("Invalid IP option type %v length %d. Must be greater than 2", opt.OptionType, opt.OptionLength)
			}
			opt.OptionData = headerOptionsData[2:opt.OptionLength]
			headerOptionsData = headerOptionsData[opt.OptionLength:]
			ip.Options = append(ip.Options, opt)
		}
	}

	flagsfrags := binary.BigEndian.Uint16(data[6:8])
	ip.Version = data[0] >> 4
	ip.TOS = data[1]
	ip.Id = binary.BigEndian.Uint16(data[4:6])
	ip.Flags = IPv4Flag(flagsfrags >> 13)
	ip.FragOffset = flagsfrags & 0x1FFF
	ip.TTL = data[8]
	ip.Protocol = IPProtocol(data[9])
	ip.Checksum = binary.BigEndian.Uint16(data[10:12])
	ip.SrcIP = data[12:16]
	ip.DstIP = data[16:20]

	return nil
}

func (i *IPv4) CanDecode() gopacket.LayerClass {
	return LayerTypeIPv4
}

func (i *IPv4) NextLayerType() gopacket.LayerType {
	if i.Flags&IPv4MoreFragments != 0 || i.FragOffset != 0 {
		return gopacket.LayerTypeFragment
	}
	return i.Protocol.LayerType()
}

func decodeIPv4(data []byte, p gopacket.PacketBuilder) error {
	ip := &IPv4{}
	err := ip.DecodeFromBytes(data, p)
	p.AddLayer(ip)
	p.SetNetworkLayer(ip)
	if err != nil {
		return err
	}
	return p.NextDecoder(ip.NextLayerType())
}

func checkIPv4Address(addr net.IP) (net.IP, error) {
	if c := addr.To4(); c != nil {
		return c, nil
	}
	if len(addr) == net.IPv6len {
		return nil, errors.New("address is IPv6")
	}
	return nil, fmt.Errorf("wrong length of %d bytes instead of %d", len(addr), net.IPv4len)
}

func (ip *IPv4) AddressTo4() error {
	var src, dst net.IP

	if addr, err := checkIPv4Address(ip.SrcIP); err != nil {
		return fmt.Errorf("Invalid source IPv4 address (%s)", err)
	} else {
		src = addr
	}
	if addr, err := checkIPv4Address(ip.DstIP); err != nil {
		return fmt.Errorf("Invalid destination IPv4 address (%s)", err)
	} else {
		dst = addr
	}
	ip.SrcIP = src
	ip.DstIP = dst
	return nil
}

func (ip *IPv4) VerifyChecksum() (error, gopacket.ChecksumVerificationResult) {
	existing := ip.Checksum
	verification := gopacket.ComputeChecksum(ip.Contents, 0)
	correct := gopacket.FoldChecksum(verification - uint32(existing))
	return nil, gopacket.ChecksumVerificationResult{
		Valid:   correct == existing,
		Correct: uint32(correct),
		Actual:  uint32(existing),
	}
}
