Status: Implemented (all development should follow this pattern) Last Updated: 2026-01-30
See also:
docs/architecture/core-design.mdfor the canonical architecture reference covering WireUpdate, RIB storage model, factory pattern, and type consolidation.
| Phase | Status | Description |
|---|---|---|
| Phase 1 | ✅ Done | Core iterator types (NLRIIterator, AttrIterator, ASPathIterator) |
| Phase 2 | ✅ Done | WireUpdate integration (iterator methods) |
| Phase 3 | ✅ Done | Direct formatting functions (FormatPrefixFromBytes, FormatASPathJSON, etc.) |
| Phase 4 | ✅ Done | RIB migration (Route.AttrIterator, Route.ASPathIterator) |
| Phase 5 | ✅ Done | Deprecate parsed types (PathAttributes, RouteUpdate, UpdateInfo) |
| Phase 6 | ✅ Done | RouteJSON, Builder done; PathAttributes removed (see plan/learned/105-pathattributes-removal.md) |
See plan/learned/102-buffer-first-migration.md for detailed implementation plan.
Ze uses a buffer-first architecture where BGP messages are represented as byte buffers with iterators and partial parsers. This eliminates duplication between wire format and parsed representations, enables zero-copy operations, and provides a single source of truth.
Core principle: One representation (bytes). Everything else is views/iterators.
┌─────────────────────────────────────────────────────────────┐
│ Message Buffer │
│ ┌──────────┬───────────┬──────────────┬─────────────────┐ │
│ │ Header │ Withdrawn │ Attributes │ NLRI │ │
│ │ 19 bytes │ (var) │ (var) │ (var) │ │
│ └──────────┴───────────┴──────────────┴─────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
┌─────────────────┼─────────────────┐
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ AttrIter │ │ NLRIIter │ │ASPathIter │
│ (offset) │ │ (offset) │ │ (offset) │
└───────────┘ └───────────┘ └───────────┘
│ │ │
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ Accessor │ │ Accessor │ │ Accessor │
│ (no alloc)│ │ (no alloc)│ │ (no alloc)│
└───────────┘ └───────────┘ └───────────┘
- Store wire bytes, not parsed structs
- Parse on demand via iterators
- Never duplicate data in different representations
// ❌ OLD: Allocates slice
func (u *Update) NLRIs() []nlri.NLRI
// ✅ NEW: Iterator, zero allocation
func (u *Update) NLRIIterator() *NLRIIterator
type NLRIIterator struct {
data []byte
offset int
family Family
addPath bool
}
func (it *NLRIIterator) Next() (prefix []byte, pathID uint32, ok bool)// Parse only what you need, where you need it
func ParseNLRI(buf []byte, off int, addPath bool) (prefix []byte, pathID uint32, nextOff int, err error)
func ParseASPathSegment(buf []byte, off int) (segType uint8, asns []byte, nextOff int, err error)
// Iterators return []byte views directly - no intermediate Span typeParsing depends on negotiated capabilities (ADD-PATH, ASN4). Context is passed as parameters, not as a struct:
// ADD-PATH: NLRI includes 4-byte path-id prefix
// ASN4: AS numbers are 4 bytes (not 2)
// Iterators accept context as constructor parameter
func NewNLRIIterator(data []byte, addPath bool) *NLRIIterator
func NewASPathIterator(data []byte, asn4 bool) *ASPathIterator// ❌ OLD: Parse to struct, then marshal
attrs := parseAttributes(buf)
json.Marshal(attrs)
// ✅ NEW: Format directly from buffer
func FormatAttributesJSON(buf []byte, asn4 bool, w io.Writer) error {
iter := NewAttrIterator(buf)
for typeCode, value, ok := iter.Next(); ok; typeCode, value, ok = iter.Next() {
switch typeCode {
case ORIGIN:
fmt.Fprintf(w, `"origin":%d`, value[0])
case AS_PATH:
formatASPathJSON(value, asn4, w)
// ...
}
}
}The core type wrapping raw BGP message bytes:
// WireUpdate wraps UPDATE message payload (after BGP header)
// Location: internal/component/bgp/wireu/wire_update.go
type WireUpdate struct {
payload []byte
sourceCtxID ContextID // For zero-copy decisions
messageID uint64 // Unique identifier
sourceID source.SourceID // Source that sent/created this message
}
// Existing section accessors (return raw bytes)
func (u *WireUpdate) Withdrawn() ([]byte, error)
func (u *WireUpdate) Attrs() (*AttributesWire, error)
func (u *WireUpdate) NLRI() ([]byte, error)
// Iterator accessors (Phase 2 - implemented)
func (u *WireUpdate) WithdrawnIterator(addPath bool) (*nlri.NLRIIterator, error)
func (u *WireUpdate) AttrIterator() (*attribute.AttrIterator, error)
func (u *WireUpdate) NLRIIterator(addPath bool) (*nlri.NLRIIterator, error)type AttrIterator struct {
data []byte
offset int
}
func NewAttrIterator(data []byte) AttrIterator // value return — stack-allocated
// Next returns the next attribute
// Returns (0, 0, nil, false) when exhausted
func (it *AttrIterator) Next() (typeCode uint8, flags uint8, value []byte, ok bool)
// Convenience: find specific attribute
func (it *AttrIterator) Find(typeCode uint8) ([]byte, bool)
// Zero-alloc standalone find — no pointer receiver, no heap escape
func AttrFind(data []byte, code AttributeCode) (hdrStart int, flags AttributeFlags, value []byte, found bool)type NLRIIterator struct {
data []byte
offset int
addPath bool
}
func NewNLRIIterator(data []byte, addPath bool) *NLRIIterator
// Next returns next NLRI
// prefix is a view into the buffer (not a copy)
// pathID is 0 if addPath is false
// Returns (nil, 0, false) when exhausted
func (it *NLRIIterator) Next() (prefix []byte, pathID uint32, ok bool)type ASPathIterator struct {
data []byte
offset int
asn4 bool
}
func NewASPathIterator(data []byte, asn4 bool) *ASPathIterator
// Next returns next segment
// asns is a view into buffer (raw bytes, 2 or 4 bytes per ASN)
// Returns (0, nil, false) when exhausted
func (it *ASPathIterator) Next() (segType uint8, asns []byte, ok bool)
// Convenience: iterate ASNs within current segment
func (it *ASPathIterator) ASNIterator(asns []byte) *ASNIteratortype UpdateBuilder struct {
buf []byte
attrsOff int
nlriOff int
ctx BuildContext
}
type BuildContext struct {
ASN4 bool
AddPath bool
MaxSize int // 4096 or 65535
}
func NewUpdateBuilder(ctx BuildContext) *UpdateBuilder
// Attribute writers
func (b *UpdateBuilder) WriteOrigin(origin uint8) error
func (b *UpdateBuilder) WriteASPath(segments []ASPathSegment) error
func (b *UpdateBuilder) WriteNextHop(addr netip.Addr) error
func (b *UpdateBuilder) WriteMED(med uint32) error
func (b *UpdateBuilder) WriteLocalPref(pref uint32) error
func (b *UpdateBuilder) WriteCommunities(comms []uint32) error
// NLRI writers
func (b *UpdateBuilder) WriteNLRI(prefix netip.Prefix, pathID uint32) error
func (b *UpdateBuilder) WriteWithdrawn(prefix netip.Prefix, pathID uint32) error
// Finalize
func (b *UpdateBuilder) Build() ([]byte, error)
func (b *UpdateBuilder) Reset()Routes store wire bytes as source of truth:
// internal/component/bgp/rib/route.go
type Route struct {
// Wire bytes (source of truth)
wireBytes []byte // packed path attributes
nlriWireBytes []byte // packed NLRI
sourceCtxID ContextID // for zero-copy compatibility check
// Parsed attributes (cached)
nlri nlri.NLRI
nextHop netip.Addr
attributes []attribute.Attribute
asPath *attribute.ASPath
// Reference counting
refCount atomic.Int32
}
// Access via iterators - parse on demand
func (r *Route) AttrIterator() AttrIterator {
return NewAttrIterator(r.wireBytes)
}
func (r *Route) ASPathIterator(asn4 bool) *ASPathIterator {
// Find AS_PATH attribute
iter := r.AttrIterator()
for typeCode, _, value, ok := iter.Next(); ok; typeCode, _, value, ok = iter.Next() {
if typeCode == AS_PATH_TYPE {
return NewASPathIterator(value, asn4)
}
}
return nil
}
// Zero-copy forwarding
func (r *Route) CanForwardDirect(destCtxID ContextID) bool {
return r.sourceCtxID == destCtxID
}
func (r *Route) WireBytes() []byte {
return r.wireBytes
}// Format UPDATE event directly to JSON writer
func FormatUpdateEventJSON(u *WireUpdate, addPath bool, w io.Writer) error {
w.Write([]byte(`{"type":"update"`))
// Announce section
w.Write([]byte(`,"announce":{`))
nlriIter, _ := u.NLRIIterator(addPath)
first := true
for prefix, pathID, ok := nlriIter.Next(); ok; prefix, pathID, ok = nlriIter.Next() {
if !first {
w.Write([]byte(`,`))
}
formatPrefixJSON(prefix, pathID, w)
first = false
}
w.Write([]byte(`}`))
// Attributes
w.Write([]byte(`,"attr":{`))
iter, _ := u.AttrIterator()
formatAttributesJSON(iter, w)
w.Write([]byte(`}}`))
return nil
}func FormatUpdateText(u *WireUpdate, addPath bool, w io.Writer) error {
// "update text as-path set [65001 65002] nhop set 192.168.1.1 nlri ipv4/unicast add 10.0.0.0/24"
// Format directly from buffer bytes
}Add iterator types alongside existing slice-returning methods:
// Keep existing (deprecated)
func (u *Update) NLRIs() []nlri.NLRI
// Add new
func (u *Update) NLRIIterator() *NLRIIteratorUpdate internal consumers to use iterators:
- RIB storage
- Route forwarding
- UPDATE building
Update API formatting to use direct buffer access:
- JSON encoder
- Text encoder
Once all consumers migrated:
- Remove
PathAttributesstruct - Remove
RouteUpdatestruct - Remove slice-returning methods
| Current Type | Replacement |
|---|---|
plugin.PathAttributes |
AttrIterator over buffer |
plugin.RouteUpdate |
Direct formatting from buffer |
[]attribute.Attribute |
AttrIterator |
[]nlri.NLRI |
NLRIIterator |
[]uint32 (AS-PATH) |
ASPathIterator |
rr.UpdateInfo |
WireUpdate + iterators |
plugin.RawMessage |
Simplified to buffer ref |
| Benefit | Description |
|---|---|
| Zero-copy passthrough | Route reflection = memcpy of buffer |
| Single source of truth | No sync between wire/parsed representations |
| Parse on demand | Only parse attributes API actually needs |
| Memory efficient | No slice allocations for AS-PATH, communities |
| Consistent | API and wire code use identical primitives |
| Simpler code | One way to do things, not three |
- Never store parsed slices - Store wire bytes, iterate on demand
- Never return slices from iterators - Return views (subslices) or format directly
- Use builders for construction -
UpdateBuilderfor new messages - Pass context as params - Context-dependent parsing (addPath bool, asn4 bool)
- Format directly to Writer - No intermediate JSON structs
docs/architecture/encoding-context.md- Context-dependent encodingdocs/architecture/update-building.md- Wire format constructiondocs/architecture/rib-transition.md- RIB ownership modelplan/spec-buffer-first-migration.md- Implementation spec
Last Updated: 2026-01-30