mirror of
https://github.com/soypat/lneto.git
synced 2026-08-14 11:53:43 +00:00
7d5830d7ab
* begin adding udp.MuxHandler * add udp MuxHandlerSIMO/MIMO * add tcp rx shutdown * icmpv6 client * icmpv6 Client shared NDP/Echo preparation * icmpv6 client ndp/echo split * icmpv6 client ndp/echo split done * icmpv6 adjustments * add dhcpv6 stubs * dhcpv4 preliminary revision * add dns.NextLabel * dns label name tweaks * dns begin work on TCP client * add dnstcp package * apply gofmt changes * add udp mux tests * clean up, remove StackBig for now * remove dnstcp so as to merged confident parts and we continue dnstcp work elsewhere
85 lines
2.5 KiB
Go
85 lines
2.5 KiB
Go
package dhcpv6
|
||
|
||
import (
|
||
"encoding/binary"
|
||
"io"
|
||
|
||
"github.com/soypat/lneto"
|
||
)
|
||
|
||
// NewFrame returns a Frame backed by buf.
|
||
// Returns an error if buf is shorter than [OptionsOffset] bytes.
|
||
func NewFrame(buf []byte) (Frame, error) {
|
||
if len(buf) < OptionsOffset {
|
||
return Frame{}, lneto.ErrTruncatedFrame
|
||
}
|
||
return Frame{buf: buf}, nil
|
||
}
|
||
|
||
// Frame encapsulates the raw bytes of a DHCPv6 client-server message (RFC 8415 §8)
|
||
// and provides methods for accessing and modifying its fields.
|
||
//
|
||
// Layout:
|
||
//
|
||
// Byte 0: msg-type
|
||
// Bytes 1-3: transaction-id (24-bit big-endian)
|
||
// Bytes 4+: options (code(2) + length(2) + data)
|
||
type Frame struct {
|
||
buf []byte
|
||
}
|
||
|
||
// MsgType returns the message type field.
|
||
func (frm Frame) MsgType() MsgType { return MsgType(frm.buf[0]) }
|
||
|
||
// SetMsgType sets the message type field.
|
||
func (frm Frame) SetMsgType(t MsgType) { frm.buf[0] = byte(t) }
|
||
|
||
// TransactionID returns the 24-bit transaction ID as a uint32 (upper byte is always zero).
|
||
func (frm Frame) TransactionID() uint32 {
|
||
return uint32(frm.buf[1])<<16 | uint32(frm.buf[2])<<8 | uint32(frm.buf[3])
|
||
}
|
||
|
||
// SetTransactionID writes the lower 24 bits of id into bytes 1–3.
|
||
func (frm Frame) SetTransactionID(id uint32) {
|
||
frm.buf[1] = byte(id >> 16)
|
||
frm.buf[2] = byte(id >> 8)
|
||
frm.buf[3] = byte(id)
|
||
}
|
||
|
||
// Options returns the options section of the frame (bytes from [OptionsOffset] onward).
|
||
func (frm Frame) Options() []byte { return frm.buf[OptionsOffset:] }
|
||
|
||
// ForEachOption iterates over all DHCPv6 options in the frame's options section.
|
||
// Each option is passed to fn as (byteOffset, optionCode, optionData).
|
||
// Iteration stops early if fn returns [io.EOF]; any other non-nil error is returned directly.
|
||
// If fn is nil, the function only validates the structure.
|
||
func (frm Frame) ForEachOption(fn func(off int, code OptCode, data []byte) error) error {
|
||
buf := frm.buf
|
||
ptr := OptionsOffset
|
||
for ptr+4 <= len(buf) {
|
||
code := OptCode(binary.BigEndian.Uint16(buf[ptr:]))
|
||
optlen := int(binary.BigEndian.Uint16(buf[ptr+2:]))
|
||
if ptr+4+optlen > len(buf) {
|
||
return lneto.ErrInvalidLengthField
|
||
}
|
||
if fn != nil {
|
||
err := fn(ptr, code, buf[ptr+4:ptr+4+optlen])
|
||
if err == io.EOF {
|
||
return nil
|
||
}
|
||
if err != nil {
|
||
return err
|
||
}
|
||
}
|
||
ptr += 4 + optlen
|
||
}
|
||
if ptr != len(buf) {
|
||
// 1–3 trailing bytes that cannot form a valid option header.
|
||
return lneto.ErrTruncatedFrame
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// ValidateSize validates the structure of the options section without invoking a callback.
|
||
func (frm Frame) ValidateSize() error { return frm.ForEachOption(nil) }
|