1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
|
package dhcp6
import (
"encoding"
"sort"
"github.com/mdlayher/dhcp6/internal/buffer"
)
// Options is a map of OptionCode keys with a slice of byte slice values.
//
// Its methods can be used to easily check for additional information from a
// packet. Get and GetOne should be used to access data from Options.
type Options map[OptionCode][][]byte
// Add adds a new OptionCode key and BinaryMarshaler struct's bytes to the
// Options map.
func (o Options) Add(key OptionCode, value encoding.BinaryMarshaler) error {
// Special case: since OptionRapidCommit actually has zero length, it is
// possible for an option key to appear with no value.
if value == nil {
o.AddRaw(key, nil)
return nil
}
b, err := value.MarshalBinary()
if err != nil {
return err
}
o.AddRaw(key, b)
return nil
}
// AddRaw adds a new OptionCode key and raw value byte slice to the
// Options map.
func (o Options) AddRaw(key OptionCode, value []byte) {
o[key] = append(o[key], value)
}
// Get attempts to retrieve all values specified by an OptionCode key.
//
// If a value is found, get returns a non-nil [][]byte and nil. If it is not
// found, Get returns nil and ErrOptionNotPresent.
func (o Options) Get(key OptionCode) ([][]byte, error) {
// Check for value by key.
v, ok := o[key]
if !ok {
return nil, ErrOptionNotPresent
}
// Some options can actually have zero length (OptionRapidCommit), so
// just return an empty byte slice if this is the case.
if len(v) == 0 {
return [][]byte{{}}, nil
}
return v, nil
}
// GetOne attempts to retrieve the first and only value specified by an
// OptionCode key. GetOne should only be used for OptionCode keys that must
// have at most one value.
//
// GetOne works just like Get, but if there is more than one value for the
// OptionCode key, ErrInvalidPacket will be returned.
func (o Options) GetOne(key OptionCode) ([]byte, error) {
vv, err := o.Get(key)
if err != nil {
return nil, err
}
if len(vv) != 1 {
return nil, ErrInvalidPacket
}
return vv[0], nil
}
// MarshalBinary allocates a buffer and writes options in their DHCPv6 binary
// format into the buffer.
func (o Options) MarshalBinary() ([]byte, error) {
b := buffer.New(nil)
for _, code := range o.sortedCodes() {
for _, data := range o[code] {
// 2 bytes: option code
b.Write16(uint16(code))
// 2 bytes: option length
b.Write16(uint16(len(data)))
// N bytes: option data
b.WriteBytes(data)
}
}
return b.Data(), nil
}
// UnmarshalBinary fills opts with option codes and corresponding values from
// an input byte slice.
//
// It is used with various different types to enable parsing of both top-level
// options, and options embedded within other options. If options data is
// malformed, it returns ErrInvalidOptions.
func (o *Options) UnmarshalBinary(p []byte) error {
buf := buffer.New(p)
*o = make(Options)
for buf.Len() >= 4 {
// 2 bytes: option code
// 2 bytes: option length n
// n bytes: data
code := OptionCode(buf.Read16())
length := buf.Read16()
// N bytes: option data
data := buf.Consume(int(length))
if data == nil {
return ErrInvalidOptions
}
data = data[:int(length):int(length)]
o.AddRaw(code, data)
}
// Report error for any trailing bytes
if buf.Len() != 0 {
return ErrInvalidOptions
}
return nil
}
// optionCodes implements sort.Interface.
type optionCodes []OptionCode
func (b optionCodes) Len() int { return len(b) }
func (b optionCodes) Less(i int, j int) bool { return b[i] < b[j] }
func (b optionCodes) Swap(i int, j int) { b[i], b[j] = b[j], b[i] }
func (o Options) sortedCodes() optionCodes {
var codes optionCodes
for code := range o {
codes = append(codes, code)
}
sort.Sort(codes)
return codes
}
|