Further reduce heap allocs (#56)

* work on tracking more heap allocations down

* write own heapless IP AppendFormatAddr functions

* remove potential Error method allocations

* more logging
This commit is contained in:
Pat Whittingslow
2026-03-15 00:11:59 +01:00
committed by GitHub
parent de2e756628
commit 376e1a0b4f
8 changed files with 245 additions and 23 deletions
+33 -16
View File
@@ -172,31 +172,31 @@ func (pc *PacketBreakdown) CaptureIPv6(dst []Frame, pkt []byte, bitOffset int) (
debuglog("pcap:ipv6:reclaimed") debuglog("pcap:ipv6:reclaimed")
proto := ifrm6.NextHeader() proto := ifrm6.NextHeader()
end := bitOffset + 40*octet end := bitOffset + 40*octet
var protoErrs []error var protoErr error
var crc lneto.CRC791 var crc lneto.CRC791
ifrm6.CRCWritePseudo(&crc) ifrm6.CRCWritePseudo(&crc)
switch proto { switch proto {
case lneto.IPProtoTCP: case lneto.IPProtoTCP:
if crc.PayloadSum16(ifrm6.Payload()) != 0 { if crc.PayloadSum16(ifrm6.Payload()) != 0 {
protoErrs = append(protoErrs, lneto.ErrBadCRC) protoErr = lneto.ErrBadCRC
} }
case lneto.IPProtoUDP, lneto.IPProtoUDPLite: case lneto.IPProtoUDP, lneto.IPProtoUDPLite:
ufrm, err := udp.NewFrame(ifrm6.Payload()) ufrm, err := udp.NewFrame(ifrm6.Payload())
if err != nil { if err != nil {
protoErrs = append(protoErrs, err) protoErr = err
break break
} }
ufrm.ValidateSize(pc.validator()) ufrm.ValidateSize(pc.validator())
if err = pc.validator().ErrPop(); err != nil { if err = pc.validator().ErrPop(); err != nil {
protoErrs = append(protoErrs, err) protoErr = err
break break
} }
frameLen := ufrm.Length() frameLen := ufrm.Length()
if crc.PayloadSum16(ufrm.RawData()[:frameLen]) != 0 { if crc.PayloadSum16(ufrm.RawData()[:frameLen]) != 0 {
protoErrs = append(protoErrs, lneto.ErrBadCRC) protoErr = lneto.ErrBadCRC
} }
} }
return pc.captureIPProto(proto, dst, pkt, end, protoErrs...) return pc.captureIPProto(proto, dst, pkt, end, protoErr)
} }
func (pc *PacketBreakdown) CaptureIPv4(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) { func (pc *PacketBreakdown) CaptureIPv4(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
@@ -231,8 +231,9 @@ func (pc *PacketBreakdown) CaptureIPv4(dst []Frame, pkt []byte, bitOffset int) (
debuglog("pcap:ipv4:crc-done") debuglog("pcap:ipv4:crc-done")
proto := ifrm4.Protocol() proto := ifrm4.Protocol()
end := bitOffset + octet*ifrm4.HeaderLength() end := bitOffset + octet*ifrm4.HeaderLength()
var protoErrs []error
var crc lneto.CRC791 var crc lneto.CRC791
var ipProtoErr error
payload := ifrm4.Payload() payload := ifrm4.Payload()
switch proto { switch proto {
case lneto.IPProtoTCP: case lneto.IPProtoTCP:
@@ -244,7 +245,7 @@ func (pc *PacketBreakdown) CaptureIPv4(dst []Frame, pkt []byte, bitOffset int) (
} }
ifrm4.CRCWriteTCPPseudo(&crc) ifrm4.CRCWriteTCPPseudo(&crc)
if crc.PayloadSum16(payload) != 0 { if crc.PayloadSum16(payload) != 0 {
protoErrs = append(protoErrs, lneto.ErrBadCRC) ipProtoErr = lneto.ErrBadCRC
} }
} }
case lneto.IPProtoUDP: case lneto.IPProtoUDP:
@@ -258,7 +259,7 @@ func (pc *PacketBreakdown) CaptureIPv4(dst []Frame, pkt []byte, bitOffset int) (
frameLen := ufrm.Length() frameLen := ufrm.Length()
ifrm4.CRCWriteUDPPseudo(&crc, frameLen) ifrm4.CRCWriteUDPPseudo(&crc, frameLen)
if crc.PayloadSum16(ufrm.RawData()[:frameLen]) != 0 { if crc.PayloadSum16(ufrm.RawData()[:frameLen]) != 0 {
protoErrs = append(protoErrs, lneto.ErrBadCRC) ipProtoErr = lneto.ErrBadCRC
} }
} }
} }
@@ -266,15 +267,15 @@ func (pc *PacketBreakdown) CaptureIPv4(dst []Frame, pkt []byte, bitOffset int) (
_, err := icmpv4.NewFrame(payload) _, err := icmpv4.NewFrame(payload)
if err == nil { if err == nil {
if crc.PayloadSum16(payload) != 0 { if crc.PayloadSum16(payload) != 0 {
protoErrs = append(protoErrs, lneto.ErrBadCRC) ipProtoErr = lneto.ErrBadCRC
} }
} }
} }
debuglog("pcap:ipv4:proto-crc-done") debuglog("pcap:ipv4:proto-crc-done")
return pc.captureIPProto(proto, dst, pkt, end, protoErrs...) return pc.captureIPProto(proto, dst, pkt, end, ipProtoErr)
} }
func (pc *PacketBreakdown) captureIPProto(proto lneto.IPProto, dst []Frame, pkt []byte, bitOffset int, ipProtoErrs ...error) (_ []Frame, err error) { func (pc *PacketBreakdown) captureIPProto(proto lneto.IPProto, dst []Frame, pkt []byte, bitOffset int, ipProtoErr error) (_ []Frame, err error) {
debuglog("pcap:ipproto:start") debuglog("pcap:ipproto:start")
nextFrame := len(dst) nextFrame := len(dst)
switch proto { switch proto {
@@ -292,8 +293,8 @@ func (pc *PacketBreakdown) captureIPProto(proto lneto.IPProto, dst []Frame, pkt
default: default:
reclaimRemainingFrame(&dst, "unknown proto", 0, bitOffset, octet*len(pkt)) reclaimRemainingFrame(&dst, "unknown proto", 0, bitOffset, octet*len(pkt))
} }
if len(ipProtoErrs) > 0 && len(dst) > nextFrame { if ipProtoErr != nil && len(dst) > nextFrame {
dst[nextFrame].Errors = append(dst[nextFrame].Errors, ipProtoErrs...) dst[nextFrame].Errors = append(dst[nextFrame].Errors, ipProtoErr)
} }
debuglog("pcap:ipproto:done") debuglog("pcap:ipproto:done")
return dst, err return dst, err
@@ -441,15 +442,19 @@ func (pc *PacketBreakdown) CaptureDNS(dst []Frame, pkt []byte, bitOffset int) ([
if err != nil && !incomplete { if err != nil && !incomplete {
return dst, err return dst, err
} }
debuglog("pcap:dns-decode")
finfo := reclaimFrame(&dst, "DNS", bitOffset, nil) finfo := reclaimFrame(&dst, "DNS", bitOffset, nil)
if incomplete { if incomplete {
finfo.Errors = append(finfo.Errors, ErrLimitExceeded) finfo.Errors = append(finfo.Errors, ErrLimitExceeded)
} }
finfo.Fields = append(finfo.Fields[:0], FrameField{ field := internal.SliceReclaim(&finfo.Fields)
*field = FrameField{
Name: "Data", Name: "Data",
FrameBitOffset: 0, FrameBitOffset: 0,
BitLength: int(off) * octet, BitLength: int(off) * octet,
}) SubFields: field.SubFields[:0], // Reuse subfields.
}
debuglog("pcap:dns-done")
return dst, nil return dst, nil
} }
@@ -463,6 +468,7 @@ func (pc *PacketBreakdown) CaptureNTP(dst []Frame, pkt []byte, bitOffset int) ([
return dst, err return dst, err
} }
reclaimFrame(&dst, "NTP", bitOffset, baseNTPFields[:]) reclaimFrame(&dst, "NTP", bitOffset, baseNTPFields[:])
debuglog("pcap:ntp")
return dst, nil return dst, nil
} }
@@ -490,6 +496,7 @@ func (pc *PacketBreakdown) CaptureDHCPv4(dst []Frame, pkt []byte, bitOffset int)
SubFields: optfield.SubFields[:0], SubFields: optfield.SubFields[:0],
Name: "options", Name: "options",
} }
debuglog("pcap:dhcp-opt0")
err = dfrm.ForEachOption(func(optoff int, opt dhcpv4.OptNum, data []byte) error { err = dfrm.ForEachOption(func(optoff int, opt dhcpv4.OptNum, data []byte) error {
if len(optfield.SubFields) >= pc.SubfieldLimit { if len(optfield.SubFields) >= pc.SubfieldLimit {
return ErrLimitExceeded return ErrLimitExceeded
@@ -541,11 +548,13 @@ func (pc *PacketBreakdown) CaptureDHCPv4(dst []Frame, pkt []byte, bitOffset int)
optfield.SubFields = append(optfield.SubFields, field) optfield.SubFields = append(optfield.SubFields, field)
return nil return nil
}) })
debuglog("pcap:dhcp-opt1")
// optfield already in finfo.Fields via SliceReclaim, no append needed. // optfield already in finfo.Fields via SliceReclaim, no append needed.
if err != nil { if err != nil {
finfo.Errors = append(finfo.Errors, err) finfo.Errors = append(finfo.Errors, err)
} }
} }
debuglog("pcap:dhcp-done")
return dst, nil return dst, nil
} }
@@ -739,6 +748,7 @@ func appendField(dst, pkt []byte, fieldBitStart, bitlen int, rightAligned bool)
} }
// Optimized path: field starts at byte boundary. // Optimized path: field starts at byte boundary.
dst = append(dst, pkt[octetsStart:octetsStart+octets]...) dst = append(dst, pkt[octetsStart:octetsStart+octets]...)
debuglog("pcap:appendField:optpath")
if lastOctetExcessBits != 0 { if lastOctetExcessBits != 0 {
dst[len(dst)-1] >>= lastOctetExcessBits dst[len(dst)-1] >>= lastOctetExcessBits
} }
@@ -750,7 +760,9 @@ func appendField(dst, pkt []byte, fieldBitStart, bitlen int, rightAligned bool)
if lastOctetExcessBits == 0 { if lastOctetExcessBits == 0 {
// Right aligned with no loose trailing bits. i.e: TCP flags. // Right aligned with no loose trailing bits. i.e: TCP flags.
dst = append(dst, pkt[octetsStart]&mask) dst = append(dst, pkt[octetsStart]&mask)
debuglog("pcap:appendField:rightalign1")
dst = append(dst, pkt[octetsStart+1:octetsStart+octets]...) dst = append(dst, pkt[octetsStart+1:octetsStart+octets]...)
debuglog("pcap:appendField:rightalign2")
return dst, nil return dst, nil
} }
// Right aligned with trailing bits. i.e: IPv6 Traffic Class. // Right aligned with trailing bits. i.e: IPv6 Traffic Class.
@@ -762,6 +774,7 @@ func appendField(dst, pkt []byte, fieldBitStart, bitlen int, rightAligned bool)
b := (pkt[octetsStart+i] & mask) << (8 - firstBitOffset) b := (pkt[octetsStart+i] & mask) << (8 - firstBitOffset)
b |= pkt[octetsStart+i+1] >> firstBitOffset b |= pkt[octetsStart+i+1] >> firstBitOffset
dst = append(dst, b) dst = append(dst, b)
debuglog("pcap:appendField:rightalign3")
} }
return dst, nil return dst, nil
} }
@@ -772,10 +785,12 @@ func appendField(dst, pkt []byte, fieldBitStart, bitlen int, rightAligned bool)
b := pkt[i+octetsStart] & mask b := pkt[i+octetsStart] & mask
b |= pkt[i+octetsStart+1] >> firstBitOffset b |= pkt[i+octetsStart+1] >> firstBitOffset
dst = append(dst, b) dst = append(dst, b)
debuglog("pcap:appendField:leftalign1")
} }
lastOctet := pkt[octetsStart+octets-1] & mask lastOctet := pkt[octetsStart+octets-1] & mask
lastOctet >>= lastOctetExcessBits lastOctet >>= lastOctetExcessBits
dst = append(dst, lastOctet) dst = append(dst, lastOctet)
debuglog("pcap:appendField:leftalign2")
return dst, nil return dst, nil
} }
@@ -1341,6 +1356,7 @@ func reclaimFrame(dst *[]Frame, proto string, bitOffset int, baseFields []FrameF
Fields: append(finfo.Fields[:0], baseFields...), Fields: append(finfo.Fields[:0], baseFields...),
Errors: finfo.Errors[:0], Errors: finfo.Errors[:0],
} }
debuglog("pcap:reclaim")
return finfo return finfo
} }
@@ -1352,6 +1368,7 @@ func reclaimRemainingFrame(dst *[]Frame, proto string, class FieldClass, pktBitO
Class: class, Class: class,
BitLength: pktBitLen - pktBitOffset, BitLength: pktBitLen - pktBitOffset,
}) })
debuglog("pcap:reclaim-rem")
} }
const enableDebug = internal.HeapAllocDebugging const enableDebug = internal.HeapAllocDebugging
+12 -3
View File
@@ -4,7 +4,6 @@ import (
"encoding/binary" "encoding/binary"
"encoding/hex" "encoding/hex"
"math" "math"
"net/netip"
"slices" "slices"
"strconv" "strconv"
"strings" "strings"
@@ -14,6 +13,8 @@ import (
"github.com/soypat/lneto" "github.com/soypat/lneto"
"github.com/soypat/lneto/ethernet" "github.com/soypat/lneto/ethernet"
"github.com/soypat/lneto/ipv4"
"github.com/soypat/lneto/ipv6"
"github.com/soypat/lneto/ntp" "github.com/soypat/lneto/ntp"
"github.com/soypat/lneto/tcp" "github.com/soypat/lneto/tcp"
) )
@@ -65,6 +66,7 @@ func (f *Formatter) FormatFrame(dst []byte, frm Frame, pkt []byte) (_ []byte, er
dst = append(dst, " bitlen="...) dst = append(dst, " bitlen="...)
dst = strconv.AppendInt(dst, int64(bitlen), 10) dst = strconv.AppendInt(dst, int64(bitlen), 10)
} }
debuglog("pcap:FormatFrame:int")
for ifield := range frm.Fields { for ifield := range frm.Fields {
field := frm.Fields[ifield] field := frm.Fields[ifield]
@@ -94,6 +96,7 @@ func (f *Formatter) FormatFrame(dst []byte, frm Frame, pkt []byte) (_ []byte, er
dst = append(dst, ';') dst = append(dst, ';')
} }
dst = append(dst, err.Error()...) dst = append(dst, err.Error()...)
debuglog("pcap:fmtframe:error")
} }
dst = append(dst, ')') dst = append(dst, ')')
} }
@@ -122,6 +125,7 @@ func (f *Formatter) FormatField(dst []byte, pktStartOff int, field FrameField, p
dst, err = f.formatField(dst, pktStartOff, field.SubFields[i], pkt) dst, err = f.formatField(dst, pktStartOff, field.SubFields[i], pkt)
} }
} }
debuglog("pcap:FormatField:end")
return dst, err return dst, err
} }
@@ -181,15 +185,20 @@ func (f *Formatter) formatField(dst []byte, pktStartOff int, field FrameField, p
return dst, err return dst, err
} }
dst = strconv.AppendUint(dst, v, 10) dst = strconv.AppendUint(dst, v, 10)
debuglog("pcap:fmtfield:uint")
} else if field.BitLength == 4*8 { } else if field.BitLength == 4*8 {
dst = netip.AddrFrom4([4]byte(f.buf)).AppendTo(dst) dst = ipv4.AppendFormatAddr(dst, [4]byte(f.buf))
debuglog("pcap:fmtfield:addr4")
} else if field.BitLength == 6*8 { } else if field.BitLength == 6*8 {
dst = ethernet.AppendAddr(dst, [6]byte(f.buf)) dst = ethernet.AppendAddr(dst, [6]byte(f.buf))
debuglog("pcap:fmtfield:addr6")
} else if field.BitLength == 16*8 { } else if field.BitLength == 16*8 {
dst = netip.AddrFrom16([16]byte(f.buf)).AppendTo(dst) dst = ipv6.AppendFormatAddr(dst, [16]byte(f.buf))
debuglog("pcap:fmtfield:addr16")
} else { } else {
dst = append(dst, "0x"...) dst = append(dst, "0x"...)
dst = hex.AppendEncode(dst, f.buf) dst = hex.AppendEncode(dst, f.buf)
debuglog("pcap:fmtfield:addrN")
} }
} }
return dst, err return dst, err
+14
View File
@@ -1,5 +1,7 @@
package ipv4 package ipv4
import "strconv"
const ( const (
sizeHeader = 20 sizeHeader = 20
) )
@@ -68,3 +70,15 @@ func b2u8(b bool) uint8 {
} }
return 0 return 0
} }
// AppendFormatAddr appends the dotted-decimal text representation of an IPv4 address
// to dst. Zero heap allocations.
func AppendFormatAddr(dst []byte, addr [4]byte) []byte {
for i, b := range addr {
if i != 0 {
dst = append(dst, '.')
}
dst = strconv.AppendUint(dst, uint64(b), 10)
}
return dst
}
+43
View File
@@ -0,0 +1,43 @@
package ipv4
import (
"net/netip"
"testing"
)
func TestAppendFormatAddr(t *testing.T) {
tests := []struct {
addr [4]byte
want string
}{
{addr: [4]byte{0, 0, 0, 0}, want: "0.0.0.0"},
{addr: [4]byte{127, 0, 0, 1}, want: "127.0.0.1"},
{addr: [4]byte{192, 168, 1, 1}, want: "192.168.1.1"},
{addr: [4]byte{255, 255, 255, 255}, want: "255.255.255.255"},
{addr: [4]byte{10, 0, 0, 1}, want: "10.0.0.1"},
{addr: [4]byte{1, 2, 3, 4}, want: "1.2.3.4"},
{addr: [4]byte{100, 99, 9, 0}, want: "100.99.9.0"},
}
for _, tc := range tests {
got := string(AppendFormatAddr(nil, tc.addr))
if got != tc.want {
t.Errorf("AppendFormatAddr(%v): got %q, want %q", tc.addr, got, tc.want)
}
// Cross-check with netip.
want := netip.AddrFrom4(tc.addr).String()
if got != want {
t.Errorf("AppendFormatAddr(%v) disagrees with netip: got %q, want %q", tc.addr, got, want)
}
}
}
func TestAppendFormatAddr_noAllocs(t *testing.T) {
var buf [24]byte
addr := [4]byte{192, 168, 1, 1}
allocs := testing.AllocsPerRun(100, func() {
_ = AppendFormatAddr(buf[:0], addr)
})
if allocs != 0 {
t.Errorf("expected 0 allocs, got %v", allocs)
}
}
+55
View File
@@ -7,3 +7,58 @@ const (
) )
type ToS = ipv4.ToS type ToS = ipv4.ToS
// AppendFormatAddr appends the canonical text representation of an IPv6 address
// to dst following RFC 5952 conventions (lowercase hex, :: compression for the
// longest run of consecutive zero groups of length ≥ 2). Zero heap allocations.
func AppendFormatAddr(dst []byte, addr [16]byte) []byte {
const hexDigits = "0123456789abcdef"
// Find the longest run of consecutive all-zero 16-bit groups for :: compression.
bestStart, bestLen := 0, 0
curStart := -1
for i := 0; i < 8; i++ {
if addr[i*2] == 0 && addr[i*2+1] == 0 {
if curStart < 0 {
curStart = i
}
if i-curStart+1 > bestLen {
bestStart = curStart
bestLen = i - curStart + 1
}
} else {
curStart = -1
}
}
if bestLen < 2 {
bestLen = 0 // RFC 5952 §4.2.2: do not compress a single 16-bit group.
}
needColon := false
for i := 0; i < 8; i++ {
if bestLen > 0 && i == bestStart {
dst = append(dst, ':', ':')
i += bestLen - 1 // skip compressed groups; loop increments i.
needColon = false
continue
}
if needColon {
dst = append(dst, ':')
}
needColon = true
hi := addr[i*2]
lo := addr[i*2+1]
v := uint16(hi)<<8 | uint16(lo)
if v >= 0x1000 {
dst = append(dst, hexDigits[hi>>4])
}
if v >= 0x100 {
dst = append(dst, hexDigits[hi&0xf])
}
if v >= 0x10 {
dst = append(dst, hexDigits[lo>>4])
}
dst = append(dst, hexDigits[lo&0xf])
}
return dst
}
+74
View File
@@ -0,0 +1,74 @@
package ipv6
import (
"net/netip"
"testing"
)
func TestAppendFormatAddr(t *testing.T) {
tests := []struct {
addr [16]byte
want string
}{
// All zeros → "::".
{addr: [16]byte{}, want: "::"},
// Loopback → "::1".
{addr: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, want: "::1"},
// Full address, no compression.
{addr: [16]byte{0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06}, want: "2001:db8:1:2:3:4:5:6"},
// Trailing zero run.
{addr: [16]byte{0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, want: "fe80::"},
// Leading non-zero + middle compression.
{addr: [16]byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, want: "2001:db8::1"},
// Link-local with interface ID.
{addr: [16]byte{0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, want: "fe80::1"},
// Two zero runs; compress the longer one (groups 3-6 len=4 vs group 1 len=1).
{addr: [16]byte{0x20, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, want: "2001:0:1::1"},
// Single zero group should NOT compress (RFC 5952 §4.2.2).
{addr: [16]byte{0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06}, want: "1:0:1:2:3:4:5:6"},
// All ff → no compression.
{addr: [16]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, want: "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"},
// IPv4-mapped ::ffff:192.168.1.1 — we format as pure hex groups.
{addr: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xc0, 0xa8, 0x01, 0x01}, want: "::ffff:c0a8:101"},
// Two equal-length zero runs; first one wins.
{addr: [16]byte{0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, want: "1::1:2:0:0:1"},
}
for _, tc := range tests {
got := string(AppendFormatAddr(nil, tc.addr))
if got != tc.want {
t.Errorf("AppendFormatAddr(%v):\n got %q\n want %q", tc.addr, got, tc.want)
}
}
}
func TestAppendFormatAddr_matchesNetip(t *testing.T) {
// Verify output matches netip.Addr.AppendTo for non-IPv4-mapped addresses.
addrs := [][16]byte{
{},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0x02, 0x00, 0x00, 0xff, 0xfe, 0x00, 0x00, 0x01},
{0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xfb},
{0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06},
{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
}
for _, addr := range addrs {
got := string(AppendFormatAddr(nil, addr))
want := netip.AddrFrom16(addr).String()
if got != want {
t.Errorf("mismatch for %v:\n got %q\n want %q", addr, got, want)
}
}
}
func TestAppendFormatAddr_noAllocs(t *testing.T) {
var buf [64]byte
addr := [16]byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
allocs := testing.AllocsPerRun(100, func() {
_ = AppendFormatAddr(buf[:0], addr)
})
if allocs != 0 {
t.Errorf("expected 0 allocs, got %v", allocs)
}
}
+4 -4
View File
@@ -187,19 +187,19 @@ func (tfrm Frame) String() string {
func (tfrm Frame) ValidateSize(v *lneto.Validator) { func (tfrm Frame) ValidateSize(v *lneto.Validator) {
off := tfrm.HeaderLength() off := tfrm.HeaderLength()
if off < sizeHeaderTCP { if off < sizeHeaderTCP {
v.AddBitPosErr(12*8, 4, lneto.ErrInvalidLengthField) v.AddError(lneto.ErrInvalidLengthField)
} }
if off > len(tfrm.RawData()) { if off > len(tfrm.RawData()) {
v.AddBitPosErr(12*8, 4, lneto.ErrInvalidLengthField) v.AddError(lneto.ErrShortBuffer)
} }
} }
func (tfrm Frame) ValidateExceptCRC(v *lneto.Validator) { func (tfrm Frame) ValidateExceptCRC(v *lneto.Validator) {
tfrm.ValidateSize(v) tfrm.ValidateSize(v)
if tfrm.DestinationPort() == 0 { if tfrm.DestinationPort() == 0 {
v.AddBitPosErr(2*8, 16, lneto.ErrZeroDestination) v.AddError(lneto.ErrZeroDestination)
} }
if tfrm.SourcePort() == 0 { if tfrm.SourcePort() == 0 {
v.AddBitPosErr(0, 16, lneto.ErrZeroSource) v.AddError(lneto.ErrZeroSource)
} }
} }
+10
View File
@@ -3,6 +3,7 @@ package lneto
import ( import (
"errors" "errors"
"fmt" "fmt"
"strconv"
) )
type ValidateFlags uint64 type ValidateFlags uint64
@@ -83,3 +84,12 @@ type BitPosErr struct {
func (bpe *BitPosErr) Error() string { func (bpe *BitPosErr) Error() string {
return fmt.Sprintf("%s at bits %d..%d", bpe.Err.Error(), bpe.BitStart, bpe.BitStart+bpe.BitLen) return fmt.Sprintf("%s at bits %d..%d", bpe.Err.Error(), bpe.BitStart, bpe.BitStart+bpe.BitLen)
} }
func (bpe *BitPosErr) AppendError(dst []byte) []byte {
dst = append(dst, bpe.Err.Error()...)
dst = append(dst, ": bits "...)
dst = strconv.AppendUint(dst, uint64(bpe.BitStart), 10)
dst = append(dst, '.', '.')
dst = strconv.AppendUint(dst, uint64(bpe.BitStart+bpe.BitLen), 10)
return dst
}