package main

import (
	"encoding/binary"
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"sync/atomic"
	"time"
	"unsafe"

	"golang.org/x/sys/unix"
)

// The noise cable: a deliberately bad cable twisted around the one under test,
// there to radiate into it. Its ports are driven for the interference they
// produce, not measured: nothing is ever received from them, and nothing they
// count reaches the error columns.
//
// The wire cannot be quieted by going idle, because without EEE the PHYs
// signal at full power whether or not frames flow, and these PHYs live inside
// the SFP+ modules where no EEE control reaches them. Closing the port is the
// one switch the host actually has, so the cycle is built on it: links up and
// carrying frames for a spell, then administratively down for one. Every wake
// re-runs 10GBASE-T training, which is as loud as this wire ever gets.
const (
	noiseDriver   = "i40e"
	noiseFrameLen = 1514
	noiseUpSpan   = 5 * time.Second
	noiseDownSpan = 5 * time.Second
	noiseFrameGap = 10 * time.Millisecond

	noiseEther uint16 = probeEther + 1
)

// Kernel names shift with which drivers are built in, since ethN is handed out
// in link order rather than by slot. The driver name is the one label a port
// keeps across kernel configs, so pairs are found by it rather than named.
func driverPair(driver string) (string, string, error) {
	ents, err := os.ReadDir("/sys/class/net")
	if err != nil {
		return "", "", err
	}
	var names []string
	for _, e := range ents {
		link, err := os.Readlink("/sys/class/net/" + e.Name() + "/device/driver")
		if err != nil {
			continue
		}
		if filepath.Base(link) == driver {
			names = append(names, e.Name())
		}
	}
	if len(names) != 2 {
		return "", "", fmt.Errorf("want 2 %s interfaces, found %d [%s]",
			driver, len(names), strings.Join(names, " "))
	}
	sort.Strings(names)
	return names[0], names[1], nil
}

type noisePort struct {
	name  string
	fd    int
	frame []byte
}

type noiser struct {
	eps   [2]endpoint
	ports [2]noisePort

	// Whether the cable is judged present: both carriers seen during an up
	// phase. Latched across the down phase, where the missing carrier is our
	// own doing and says nothing about the cable.
	connected atomic.Bool
}

func newNoiser() (*noiser, error) {
	aName, bName, err := driverPair(noiseDriver)
	if err != nil {
		return nil, fmt.Errorf("noise: %w", err)
	}
	a, err := lookupEndpoint(aName)
	if err != nil {
		return nil, fmt.Errorf("noise: %w", err)
	}
	b, err := lookupEndpoint(bName)
	if err != nil {
		return nil, fmt.Errorf("noise: %w", err)
	}

	n := &noiser{eps: [2]endpoint{a, b}}
	for i, p := range [][2]endpoint{{a, b}, {b, a}} {
		fd, err := openTxSocket(p[0].idx)
		if err != nil {
			return nil, fmt.Errorf("noise tx socket %s: %w", p[0].name, err)
		}
		// The payload is left zero: the PCS scrambles everything on the wire,
		// so no pattern radiates differently from any other. The frame exists
		// to occupy the link, not to say anything.
		frame := make([]byte, noiseFrameLen)
		copy(frame[0:6], p[1].mac[:])
		copy(frame[6:12], p[0].mac[:])
		binary.BigEndian.PutUint16(frame[12:14], noiseEther)
		n.ports[i] = noisePort{name: p[0].name, fd: fd, frame: frame}
	}
	return n, nil
}

func (n *noiser) names() []string {
	return []string{n.eps[0].name, n.eps[1].name}
}

// Zero while the cable was there at the last verdict, one while it was not:
// the shape the error cells already colour by, so absence paints as the fault
// it is and presence as the usual green.
func (n *noiser) missing() uint64 {
	if n.connected.Load() {
		return 0
	}
	return 1
}

// The ports were reachable when the noiser was built, so one that stops taking
// the ioctl now is the interface going away underneath us, the same fault the
// counter reads stop for.
func (n *noiser) setLinks(fd int, up bool) {
	for i := range n.ports {
		var ifr flagsIfreq
		copy(ifr.name[:], n.ports[i].name)
		if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd),
			uintptr(unix.SIOCGIFFLAGS), uintptr(unsafe.Pointer(&ifr))); errno != 0 {
			panic(fmt.Sprintf("reading %s flags: %v", n.ports[i].name, errno))
		}
		if up {
			ifr.flags |= unix.IFF_UP
		} else {
			ifr.flags &^= unix.IFF_UP
		}
		if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd),
			uintptr(unix.SIOCSIFFLAGS), uintptr(unsafe.Pointer(&ifr))); errno != 0 {
			panic(fmt.Sprintf("setting %s flags: %v", n.ports[i].name, errno))
		}
	}
}

// Down reads as EINVAL rather than zero, and either way the answer is the
// same: no carrier here now.
func carrierUp(name string) bool {
	v, ok := readUint("/sys/class/net/" + name + "/carrier")
	return ok && v == 1
}

func (n *noiser) bothUp() bool {
	return carrierUp(n.ports[0].name) && carrierUp(n.ports[1].name)
}

// Send results are deliberately dropped: the cable is bad on purpose, the link
// comes and goes under the cycle, and a frame this side declined to send is as
// good as one the wire mangled. What matters is only ever what the test cable
// counted.
func (n *noiser) run(done *atomic.Bool) {
	fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
	if err != nil {
		panic(fmt.Sprintf("noise ioctl socket: %v", err))
	}
	defer unix.Close(fd)

	tick := time.NewTicker(noiseFrameGap)
	defer tick.Stop()

	for !done.Load() {
		n.setLinks(fd, true)
		linked := false
		for end := time.Now().Add(noiseUpSpan); time.Now().Before(end) && !done.Load(); {
			<-tick.C
			if !n.bothUp() {
				continue
			}
			linked = true
			n.connected.Store(true)
			for i := range n.ports {
				unix.Write(n.ports[i].fd, n.ports[i].frame)
			}
		}
		// A whole up phase with no link is many times the ~1s the wire needs
		// to train, so by now the silence is the cable's answer.
		n.connected.Store(linked)

		n.setLinks(fd, false)
		for end := time.Now().Add(noiseDownSpan); time.Now().Before(end) && !done.Load(); {
			<-tick.C
		}
	}
	// Left up rather than wherever the cycle stopped, so a run never strands
	// the ports down for whoever looks next.
	n.setLinks(fd, true)
}

func (n *noiser) close() {
	for i := range n.ports {
		unix.Close(n.ports[i].fd)
	}
}
