// Copyright 2018 Google LLC
//
// 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 header

import (
	"encoding/binary"

	tcpip "github.com/brewlin/net-protocol/protocol"
)

const (
	nextHdrFrag = 0
	fragOff     = 2
	more        = 3
	idV6        = 4
)

// IPv6FragmentFields contains the fields of an IPv6 fragment. It is used to describe the
// fields of a packet that needs to be encoded.
type IPv6FragmentFields struct {
	// NextHeader is the "next header" field of an IPv6 fragment.
	NextHeader uint8

	// FragmentOffset is the "fragment offset" field of an IPv6 fragment.
	FragmentOffset uint16

	// M is the "more" field of an IPv6 fragment.
	M bool

	// Identification is the "identification" field of an IPv6 fragment.
	Identification uint32
}

// IPv6Fragment represents an ipv6 fragment header stored in a byte array.
// Most of the methods of IPv6Fragment access to the underlying slice without
// checking the boundaries and could panic because of 'index out of range'.
// Always call IsValid() to validate an instance of IPv6Fragment before using other methods.
type IPv6Fragment []byte

const (
	// IPv6FragmentHeader header is the number used to specify that the next
	// header is a fragment header, per RFC 2460.
	IPv6FragmentHeader = 44

	// IPv6FragmentHeaderSize is the size of the fragment header.
	IPv6FragmentHeaderSize = 8
)

// Encode encodes all the fields of the ipv6 fragment.
func (b IPv6Fragment) Encode(i *IPv6FragmentFields) {
	b[nextHdrFrag] = i.NextHeader
	binary.BigEndian.PutUint16(b[fragOff:], i.FragmentOffset<<3)
	if i.M {
		b[more] |= 1
	}
	binary.BigEndian.PutUint32(b[idV6:], i.Identification)
}

// IsValid performs basic validation on the fragment header.
func (b IPv6Fragment) IsValid() bool {
	return len(b) >= IPv6FragmentHeaderSize
}

// NextHeader returns the value of the "next header" field of the ipv6 fragment.
func (b IPv6Fragment) NextHeader() uint8 {
	return b[nextHdrFrag]
}

// FragmentOffset returns the "fragment offset" field of the ipv6 fragment.
func (b IPv6Fragment) FragmentOffset() uint16 {
	return binary.BigEndian.Uint16(b[fragOff:]) >> 3
}

// More returns the "more" field of the ipv6 fragment.
func (b IPv6Fragment) More() bool {
	return b[more]&1 > 0
}

// Payload implements Network.Payload.
func (b IPv6Fragment) Payload() []byte {
	return b[IPv6FragmentHeaderSize:]
}

// ID returns the value of the identifier field of the ipv6 fragment.
func (b IPv6Fragment) ID() uint32 {
	return binary.BigEndian.Uint32(b[idV6:])
}

// TransportProtocol implements Network.TransportProtocol.
func (b IPv6Fragment) TransportProtocol() tcpip.TransportProtocolNumber {
	return tcpip.TransportProtocolNumber(b.NextHeader())
}

// The functions below have been added only to satisfy the Network interface.

// Checksum is not supported by IPv6Fragment.
func (b IPv6Fragment) Checksum() uint16 {
	panic("not supported")
}

// SourceAddress is not supported by IPv6Fragment.
func (b IPv6Fragment) SourceAddress() tcpip.Address {
	panic("not supported")
}

// DestinationAddress is not supported by IPv6Fragment.
func (b IPv6Fragment) DestinationAddress() tcpip.Address {
	panic("not supported")
}

// SetSourceAddress is not supported by IPv6Fragment.
func (b IPv6Fragment) SetSourceAddress(tcpip.Address) {
	panic("not supported")
}

// SetDestinationAddress is not supported by IPv6Fragment.
func (b IPv6Fragment) SetDestinationAddress(tcpip.Address) {
	panic("not supported")
}

// SetChecksum is not supported by IPv6Fragment.
func (b IPv6Fragment) SetChecksum(uint16) {
	panic("not supported")
}

// TOS is not supported by IPv6Fragment.
func (b IPv6Fragment) TOS() (uint8, uint32) {
	panic("not supported")
}

// SetTOS is not supported by IPv6Fragment.
func (b IPv6Fragment) SetTOS(t uint8, l uint32) {
	panic("not supported")
}
