Files
cabletest/frame.go
T

194 lines
4.4 KiB
Go

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
}