// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build darwin || dragonfly || freebsd || netbsd || openbsd

package routebsd

import (
	"net/netip"
	"runtime"
	"syscall"
)

// An Addr represents an address associated with packet routing.
type Addr interface {
	// Family returns an address family.
	Family() int
}

// A LinkAddr represents a link-layer address.
type LinkAddr struct {
	Index int    // interface index when attached
	Name  string // interface name when attached
	Addr  []byte // link-layer address when attached
}

// Family implements the Family method of Addr interface.
func (a *LinkAddr) Family() int { return syscall.AF_LINK }

func parseLinkAddr(b []byte) (Addr, error) {
	if len(b) < 8 {
		return nil, errInvalidAddr
	}
	_, a, err := parseKernelLinkAddr(syscall.AF_LINK, b[4:])
	if err != nil {
		return nil, err
	}
	a.(*LinkAddr).Index = int(nativeEndian.Uint16(b[2:4]))
	return a, nil
}

// parseKernelLinkAddr parses b as a link-layer address in
// conventional BSD kernel form.
func parseKernelLinkAddr(_ int, b []byte) (int, Addr, error) {
	// The encoding looks like the following:
	// +----------------------------+
	// | Type             (1 octet) |
	// +----------------------------+
	// | Name length      (1 octet) |
	// +----------------------------+
	// | Address length   (1 octet) |
	// +----------------------------+
	// | Selector length  (1 octet) |
	// +----------------------------+
	// | Data            (variable) |
	// +----------------------------+
	//
	// On some platforms, all-bit-one of length field means "don't
	// care".
	nlen, alen, slen := int(b[1]), int(b[2]), int(b[3])
	if nlen == 0xff {
		nlen = 0
	}
	if alen == 0xff {
		alen = 0
	}
	if slen == 0xff {
		slen = 0
	}
	l := 4 + nlen + alen + slen
	if len(b) < l {
		return 0, nil, errInvalidAddr
	}
	data := b[4:]
	var name string
	var addr []byte
	if nlen > 0 {
		name = string(data[:nlen])
		data = data[nlen:]
	}
	if alen > 0 {
		addr = data[:alen]
		data = data[alen:]
	}
	return l, &LinkAddr{Name: name, Addr: addr}, nil
}

// An InetAddr represent an internet address using IPv4 or IPv6.
type InetAddr struct {
	IP netip.Addr
}

func (a *InetAddr) Family() int {
	if a.IP.Is4() {
		return syscall.AF_INET
	} else {
		return syscall.AF_INET6
	}
}

// parseInetAddr parses b as an internet address for IPv4 or IPv6.
func parseInetAddr(af int, b []byte) (Addr, error) {
	const (
		off4    = 4  // offset of in_addr
		off6    = 8  // offset of in6_addr
		ipv4Len = 4  // length of IPv4 address in bytes
		ipv6Len = 16 // length of IPv6 address in bytes
	)
	switch af {
	case syscall.AF_INET:
		if len(b) < (off4+1) || len(b) < int(b[0]) {
			return nil, errInvalidAddr
		}
		sockAddrLen := int(b[0])
		var ip [ipv4Len]byte
		if sockAddrLen != 0 {
			// Calculate how many bytes of the address to copy:
			// either full IPv4 length or the available length.
			n := off4 + ipv4Len
			if sockAddrLen < n {
				n = sockAddrLen
			}
			copy(ip[:], b[off4:n])
		}
		a := &InetAddr{
			IP: netip.AddrFrom4(ip),
		}
		return a, nil
	case syscall.AF_INET6:
		if len(b) < (off6+1) || len(b) < int(b[0]) {
			return nil, errInvalidAddr
		}
		var ip [ipv6Len]byte
		sockAddrLen := int(b[0])
		if sockAddrLen != 0 {
			n := off6 + ipv6Len
			if sockAddrLen < n {
				n = sockAddrLen
			}
			copy(ip[:], b[off6:n])
			if ip[0] == 0xfe && ip[1]&0xc0 == 0x80 || ip[0] == 0xff && (ip[1]&0x0f == 0x01 || ip[1]&0x0f == 0x02) {
				// KAME based IPv6 protocol stack usually
				// embeds the interface index in the
				// interface-local or link-local address as
				// the kernel-internal form.
				id := int(bigEndian.Uint16(ip[2:4]))
				if id != 0 {
					ip[2], ip[3] = 0, 0
				}
			}
		}
		// The kernel can provide an integer zone ID.
		// We ignore it.
		a := &InetAddr{
			IP: netip.AddrFrom16(ip),
		}
		return a, nil
	default:
		return nil, errInvalidAddr
	}
}

// parseKernelInetAddr parses b as an internet address in conventional
// BSD kernel form.
func parseKernelInetAddr(af int, b []byte) (int, Addr, error) {
	// The encoding looks similar to the NLRI encoding.
	// +----------------------------+
	// | Length           (1 octet) |
	// +----------------------------+
	// | Address prefix  (variable) |
	// +----------------------------+
	//
	// The differences between the kernel form and the NLRI
	// encoding are:
	//
	// - The length field of the kernel form indicates the prefix
	//   length in bytes, not in bits
	//
	// - In the kernel form, zero value of the length field
	//   doesn't mean 0.0.0.0/0 or ::/0
	//
	// - The kernel form appends leading bytes to the prefix field
	//   to make the <length, prefix> tuple to be conformed with
	//   the routing message boundary
	l := int(b[0])
	if runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
		// On Darwin, an address in the kernel form is also
		// used as a message filler.
		if l == 0 || len(b) > roundup(l) {
			l = roundup(l)
		}
	} else {
		l = roundup(l)
	}
	if len(b) < l {
		return 0, nil, errInvalidAddr
	}
	// Don't reorder case expressions.
	// The case expressions for IPv6 must come first.
	const (
		off4 = 4 // offset of in_addr
		off6 = 8 // offset of in6_addr
	)
	switch {
	case b[0] == syscall.SizeofSockaddrInet6:
		a := &InetAddr{
			IP: netip.AddrFrom16([16]byte(b[off6 : off6+16])),
		}
		return int(b[0]), a, nil
	case af == syscall.AF_INET6:
		var ab [16]byte
		if l-1 < off6 {
			copy(ab[:], b[1:l])
		} else {
			copy(ab[:], b[l-off6:l])
		}
		a := &InetAddr{
			IP: netip.AddrFrom16(ab),
		}
		return int(b[0]), a, nil
	case b[0] == syscall.SizeofSockaddrInet4:
		a := &InetAddr{
			IP: netip.AddrFrom4([4]byte(b[off4 : off4+4])),
		}
		return int(b[0]), a, nil
	default: // an old fashion, AF_UNSPEC or unknown means AF_INET
		var ab [4]byte
		if l-1 < off4 {
			copy(ab[:], b[1:l])
		} else {
			copy(ab[:], b[l-off4:l])
		}
		a := &InetAddr{
			IP: netip.AddrFrom4(ab),
		}
		return int(b[0]), a, nil
	}
}

func parseAddrs(attrs uint, b []byte) ([]Addr, error) {
	var as [syscall.RTAX_MAX]Addr
	af := int(syscall.AF_UNSPEC)
	for i := uint(0); i < syscall.RTAX_MAX && len(b) >= roundup(0); i++ {
		if attrs&(1<<i) == 0 {
			continue
		}
		if i <= syscall.RTAX_BRD {
			switch b[1] {
			case syscall.AF_LINK:
				a, err := parseLinkAddr(b)
				if err != nil {
					return nil, err
				}
				as[i] = a
				l := roundup(int(b[0]))
				if len(b) < l {
					return nil, errMessageTooShort
				}
				b = b[l:]
			case syscall.AF_INET, syscall.AF_INET6:
				af = int(b[1])
				a, err := parseInetAddr(af, b)
				if err != nil {
					return nil, err
				}
				as[i] = a
				l := roundup(int(b[0]))
				if len(b) < l {
					return nil, errMessageTooShort
				}
				b = b[l:]
			default:
				l, a, err := parseKernelInetAddr(af, b)
				if err != nil {
					return nil, err
				}
				as[i] = a
				ll := roundup(l)
				if len(b) < ll {
					b = b[l:]
				} else {
					b = b[ll:]
				}
			}
		} else {
			// Skip unknown addresses.
			l := roundup(int(b[0]))
			if len(b) < l {
				return nil, errMessageTooShort
			}
			b = b[l:]
		}
	}
	// The only remaining bytes in b should be alignment.
	// However, under some circumstances DragonFly BSD appears to put
	// more addresses in the message than are indicated in the address
	// bitmask, so don't check for this.
	return as[:], nil
}
