// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause

package packet

import (
	"encoding/binary"

	"github.com/metacubex/tailscale/types/ipproto"
)

// icmp4HeaderLength is the size of the ICMPv4 packet header, not
// including the outer IP layer or the variable "response data"
// trailer.
const icmp4HeaderLength = 4

// ICMP4Type is an ICMPv4 type, as specified in
// https://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml
type ICMP4Type uint8

const (
	ICMP4EchoReply    ICMP4Type = 0x00
	ICMP4EchoRequest  ICMP4Type = 0x08
	ICMP4Unreachable  ICMP4Type = 0x03
	ICMP4TimeExceeded ICMP4Type = 0x0b
	ICMP4ParamProblem ICMP4Type = 0x12
)

func (t ICMP4Type) String() string {
	switch t {
	case ICMP4EchoReply:
		return "EchoReply"
	case ICMP4EchoRequest:
		return "EchoRequest"
	case ICMP4Unreachable:
		return "Unreachable"
	case ICMP4TimeExceeded:
		return "TimeExceeded"
	case ICMP4ParamProblem:
		return "ParamProblem"
	default:
		return "Unknown"
	}
}

// ICMP4Code is an ICMPv4 code, as specified in
// https://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml
type ICMP4Code uint8

const (
	ICMP4NoCode ICMP4Code = 0
)

// ICMP4Header is an IPv4+ICMPv4 header.
type ICMP4Header struct {
	IP4Header
	Type ICMP4Type
	Code ICMP4Code
}

// Len implements Header.
func (h ICMP4Header) Len() int {
	return h.IP4Header.Len() + icmp4HeaderLength
}

// Marshal implements Header.
func (h ICMP4Header) Marshal(buf []byte) error {
	if len(buf) < h.Len() {
		return errSmallBuffer
	}
	if len(buf) > maxPacketLength {
		return errLargePacket
	}
	// The caller does not need to set this.
	h.IPProto = ipproto.ICMPv4

	buf[20] = uint8(h.Type)
	buf[21] = uint8(h.Code)

	h.IP4Header.Marshal(buf)

	binary.BigEndian.PutUint16(buf[22:24], ip4Checksum(buf))

	return nil
}

// ToResponse implements Header. TODO: it doesn't implement it
// correctly, instead it statically generates an ICMP Echo Reply
// packet.
func (h *ICMP4Header) ToResponse() {
	// TODO: this doesn't implement ToResponse correctly, as it
	// assumes the ICMP request type.
	h.Type = ICMP4EchoReply
	h.Code = ICMP4NoCode
	h.IP4Header.ToResponse()
}
