194 lines
4.4 KiB
Go
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
|
|
}
|