package main

import (
	"encoding/binary"
	"hash/crc32"
	"math/rand/v2"
)

const (
	etherBase = 0x88b5
	ethHdrLen = 14
	hdrLen    = 20
	hdrMagic  = 0x43424c54
	hdrVer    = 1
	minFrame  = ethHdrLen + hdrLen
	maxFrame  = 9216

	hdrCheckOff = 18
)

var crcTable = crc32.MakeTable(crc32.Castagnoli)

var patterns = []func([]byte){
	func(b []byte) { clear(b) },
	func(b []byte) {
		for i := range b {
			b[i] = 0xff
		}
	},
	func(b []byte) {
		for i := range b {
			if i%2 == 0 {
				b[i] = 0x55
			} else {
				b[i] = 0xaa
			}
		}
	},
	func(b []byte) {
		for i := range b {
			b[i] = byte(i)
		}
	},
	func(b []byte) {
		s := uint64(0x0123456789abcdef)
		for i := range b {
			s ^= s << 13
			s ^= s >> 7
			s ^= s << 17
			b[i] = byte(s)
		}
	},
	func(b []byte) {
		r := rand.New(rand.NewPCG(0xc0ffee, 0x5eed))
		for i := range b {
			b[i] = byte(r.Uint32())
		}
	},
}

// Every pattern is laid out at full length up front with its checksum at each
// size, so a sender picks a pattern by choosing a buffer, never by writing one.
type frameSpec struct {
	refs      [][]byte
	crcFor    []map[int]uint32
	dstMAC    [6]byte
	srcMAC    [6]byte
	etherType uint16
	sizes     []int
	maxSize   int
	maxPay    int
}

func newFrameSpec(dst, src [6]byte, etherType uint16, sizes []int) *frameSpec {
	maxSize := 0
	for _, s := range sizes {
		if s > maxSize {
			maxSize = s
		}
	}
	f := &frameSpec{
		dstMAC:    dst,
		srcMAC:    src,
		etherType: etherType,
		sizes:     sizes,
		maxSize:   maxSize,
		maxPay:    maxSize - minFrame,
	}
	for _, fill := range patterns {
		ref := make([]byte, f.maxPay)
		fill(ref)
		crcFor := make(map[int]uint32, len(sizes))
		for _, s := range sizes {
			crcFor[s-minFrame] = crc32.Checksum(ref[:s-minFrame], crcTable)
		}
		f.refs = append(f.refs, ref)
		f.crcFor = append(f.crcFor, crcFor)
	}
	return f
}

func (f *frameSpec) prefill(buf []byte, patIdx int) {
	copy(buf[0:6], f.dstMAC[:])
	copy(buf[6:12], f.srcMAC[:])
	binary.BigEndian.PutUint16(buf[12:14], f.etherType)
	copy(buf[minFrame:], f.refs[patIdx])
}

// Looked up rather than read from the frame: a checksum travelling beside the
// bytes it covers is only ever compared against itself.
func (f *frameSpec) expectedCRC(patIdx, payLen int) (uint32, bool) {
	if patIdx < 0 || patIdx >= len(f.crcFor) {
		return 0, false
	}
	crc, ok := f.crcFor[patIdx][payLen]
	return crc, ok
}

// A one's complement sum over the header, skipping the two bytes it is written
// into. The payload checksum covers the payload alone, so without this the
// sequence number is the one field on the wire that nothing checks, and a bit
// flipped in it arrives looking exactly like a legitimate frame from far in the
// future. Every single bit error changes the folded sum, which is the error a
// cable going marginal actually produces.
func headerCheck(h []byte) uint16 {
	var sum uint32
	for i := 0; i+1 < hdrLen; i += 2 {
		if i == hdrCheckOff {
			continue
		}
		sum += uint32(binary.BigEndian.Uint16(h[i:]))
	}
	for sum>>16 != 0 {
		sum = sum&0xffff + sum>>16
	}
	return ^uint16(sum)
}

func putHeader(buf []byte, patIdx int, stream uint16, seq uint64, payLen int) {
	h := buf[ethHdrLen:]
	binary.BigEndian.PutUint32(h[0:4], hdrMagic)
	h[4] = hdrVer
	h[5] = byte(patIdx)
	binary.BigEndian.PutUint16(h[6:8], stream)
	binary.BigEndian.PutUint64(h[8:16], seq)
	binary.BigEndian.PutUint16(h[16:18], uint16(payLen))
	binary.BigEndian.PutUint16(h[hdrCheckOff:], headerCheck(h))
}

type parsed struct {
	patIdx int
	stream uint16
	seq    uint64
	payLen int
}

// Why a frame was turned away. A header that is not ours and a header of ours
// that arrived damaged are different things to report: the first is somebody
// else on the wire, the second is the fault being tested for.
type hdrStatus int

const (
	hdrOK hdrStatus = iota
	hdrForeign
	hdrDamaged
)

func parseHeader(buf []byte) (parsed, hdrStatus) {
	var p parsed
	if len(buf) < minFrame {
		return p, hdrForeign
	}
	h := buf[ethHdrLen:]
	if binary.BigEndian.Uint32(h[0:4]) != hdrMagic {
		return p, hdrForeign
	}
	if binary.BigEndian.Uint16(h[hdrCheckOff:]) != headerCheck(h) {
		return p, hdrDamaged
	}
	// Both halves are the same build, so the only way this differs is a checksum
	// that happened to agree over damaged bytes.
	if h[4] != hdrVer {
		return p, hdrDamaged
	}
	p.patIdx = int(h[5])
	p.stream = binary.BigEndian.Uint16(h[6:8])
	p.seq = binary.BigEndian.Uint64(h[8:16])
	p.payLen = int(binary.BigEndian.Uint16(h[16:18]))
	if minFrame+p.payLen > len(buf) {
		return p, hdrDamaged
	}
	return p, hdrOK
}
