mirror of
https://github.com/soypat/lneto.git
synced 2026-08-26 01:19:06 +00:00
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:
@@ -7,3 +7,58 @@ const (
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user