diff --git a/dhcpv6/dhcpv6message.go b/dhcpv6/dhcpv6message.go index 8886c6c7..45fa5f1b 100644 --- a/dhcpv6/dhcpv6message.go +++ b/dhcpv6/dhcpv6message.go @@ -169,8 +169,8 @@ func (mo MessageOptions) BootFileURL() string { if opt == nil { return "" } - if u, ok := opt.(optBootFileURL); ok { - return string(u) + if u, ok := opt.(*optBootFileURL); ok { + return u.url } return "" } @@ -181,8 +181,8 @@ func (mo MessageOptions) BootFileParam() []string { if opt == nil { return nil } - if u, ok := opt.(optBootFileParam); ok { - return []string(u) + if u, ok := opt.(*optBootFileParam); ok { + return u.params } return nil } diff --git a/dhcpv6/option_4rd.go b/dhcpv6/option_4rd.go index 2faea257..34e1fb97 100644 --- a/dhcpv6/option_4rd.go +++ b/dhcpv6/option_4rd.go @@ -32,12 +32,10 @@ func (op *Opt4RD) LongString(indentSpace int) string { return fmt.Sprintf("%s: Options=%v", op.Code(), op.Options.LongString(indentSpace)) } -// ParseOpt4RD builds an Opt4RD structure from a sequence of bytes. +// FromBytes builds an Opt4RD structure from a sequence of bytes. // The input data does not include option code and length bytes -func ParseOpt4RD(data []byte) (*Opt4RD, error) { - var opt Opt4RD - err := opt.Options.FromBytes(data) - return &opt, err +func (op *Opt4RD) FromBytes(data []byte) error { + return op.Options.FromBytes(data) } // Opt4RDMapRule represents a 4RD Mapping Rule option @@ -105,18 +103,17 @@ func (op *Opt4RDMapRule) String() string { op.Code(), op.Prefix4.String(), op.Prefix6.String(), op.EABitsLength, op.WKPAuthorized) } -// ParseOpt4RDMapRule builds an Opt4RDMapRule structure from a sequence of bytes. +// FromBytes builds an Opt4RDMapRule structure from a sequence of bytes. // The input data does not include option code and length bytes. -func ParseOpt4RDMapRule(data []byte) (*Opt4RDMapRule, error) { - var opt Opt4RDMapRule +func (op *Opt4RDMapRule) FromBytes(data []byte) error { buf := uio.NewBigEndianBuffer(data) - opt.Prefix4.Mask = net.CIDRMask(int(buf.Read8()), 32) - opt.Prefix6.Mask = net.CIDRMask(int(buf.Read8()), 128) - opt.EABitsLength = buf.Read8() - opt.WKPAuthorized = (buf.Read8() & opt4RDWKPAuthorizedMask) != 0 - opt.Prefix4.IP = net.IP(buf.CopyN(net.IPv4len)) - opt.Prefix6.IP = net.IP(buf.CopyN(net.IPv6len)) - return &opt, buf.FinError() + op.Prefix4.Mask = net.CIDRMask(int(buf.Read8()), 32) + op.Prefix6.Mask = net.CIDRMask(int(buf.Read8()), 128) + op.EABitsLength = buf.Read8() + op.WKPAuthorized = (buf.Read8() & opt4RDWKPAuthorizedMask) != 0 + op.Prefix4.IP = net.IP(buf.CopyN(net.IPv4len)) + op.Prefix6.IP = net.IP(buf.CopyN(net.IPv6len)) + return buf.FinError() } // Opt4RDNonMapRule represents 4RD parameters other than mapping rules @@ -165,21 +162,20 @@ func (op *Opt4RDNonMapRule) String() string { op.Code(), op.HubAndSpoke, tClass, op.DomainPMTU) } -// ParseOpt4RDNonMapRule builds an Opt4RDNonMapRule structure from a sequence of bytes. +// FromBytes builds an Opt4RDNonMapRule structure from a sequence of bytes. // The input data does not include option code and length bytes -func ParseOpt4RDNonMapRule(data []byte) (*Opt4RDNonMapRule, error) { - var opt Opt4RDNonMapRule +func (op *Opt4RDNonMapRule) FromBytes(data []byte) error { buf := uio.NewBigEndianBuffer(data) flags := buf.Read8() - opt.HubAndSpoke = flags&opt4RDHubAndSpokeMask != 0 + op.HubAndSpoke = flags&opt4RDHubAndSpokeMask != 0 tClass := buf.Read8() if flags&opt4RDTrafficClassMask != 0 { - opt.TrafficClass = &tClass + op.TrafficClass = &tClass } - opt.DomainPMTU = buf.Read16() + op.DomainPMTU = buf.Read16() - return &opt, buf.FinError() + return buf.FinError() } diff --git a/dhcpv6/option_4rd_test.go b/dhcpv6/option_4rd_test.go index ad72445e..8cd5e015 100644 --- a/dhcpv6/option_4rd_test.go +++ b/dhcpv6/option_4rd_test.go @@ -2,6 +2,7 @@ package dhcpv6 import ( "net" + "reflect" "testing" "github.com/stretchr/testify/require" @@ -9,7 +10,8 @@ import ( func TestOpt4RDNonMapRuleParse(t *testing.T) { data := []byte{0x81, 0xaa, 0x05, 0xd4} - opt, err := ParseOpt4RDNonMapRule(data) + var opt Opt4RDNonMapRule + err := opt.FromBytes(data) require.NoError(t, err) require.True(t, opt.HubAndSpoke) require.NotNil(t, opt.TrafficClass) @@ -18,7 +20,8 @@ func TestOpt4RDNonMapRuleParse(t *testing.T) { // Remove the TrafficClass flag and check value is ignored data[0] = 0x80 - opt, err = ParseOpt4RDNonMapRule(data) + opt = Opt4RDNonMapRule{} + err = opt.FromBytes(data) require.NoError(t, err) require.True(t, opt.HubAndSpoke) require.Nil(t, opt.TrafficClass) @@ -77,7 +80,8 @@ func TestOpt4RDMapRuleParse(t *testing.T) { append(ip4addr.To4(), ip6addr...)..., ) - opt, err := ParseOpt4RDMapRule(data) + var opt Opt4RDMapRule + err = opt.FromBytes(data) require.NoError(t, err) require.EqualValues(t, *ip6net, opt.Prefix6) require.EqualValues(t, *ip4net, opt.Prefix4) @@ -162,9 +166,20 @@ func TestOpt4RDRoundTrip(t *testing.T) { }, } - rtOpt, err := ParseOpt4RD(opt.ToBytes()) + var rtOpt Opt4RD + err := rtOpt.FromBytes(opt.ToBytes()) require.NoError(t, err) require.NotNil(t, rtOpt) - require.Equal(t, opt, *rtOpt) + require.Equal(t, opt, rtOpt) + + var mo MessageOptions + mo.Options.Add(&opt) + + var got MessageOptions + if err := got.FromBytes(mo.ToBytes()); err != nil { + t.Errorf("FromBytes = %v", err) + } else if !reflect.DeepEqual(mo, got) { + t.Errorf("FromBytes = %v, want %v", got, mo) + } } diff --git a/dhcpv6/option_archtype.go b/dhcpv6/option_archtype.go index a5514cd8..2b778d84 100644 --- a/dhcpv6/option_archtype.go +++ b/dhcpv6/option_archtype.go @@ -26,10 +26,6 @@ func (op optClientArchType) String() string { return fmt.Sprintf("%s: %s", op.Code(), op.Archs) } -// parseOptClientArchType builds an OptClientArchType structure from -// a sequence of bytes The input data does not include option code and -// length bytes. -func parseOptClientArchType(data []byte) (*optClientArchType, error) { - var opt optClientArchType - return &opt, opt.FromBytes(data) +func (op *optClientArchType) FromBytes(p []byte) error { + return op.Archs.FromBytes(p) } diff --git a/dhcpv6/option_archtype_test.go b/dhcpv6/option_archtype_test.go index 0481c1a4..8ebd18f7 100644 --- a/dhcpv6/option_archtype_test.go +++ b/dhcpv6/option_archtype_test.go @@ -11,14 +11,16 @@ func TestParseOptClientArchType(t *testing.T) { data := []byte{ 0, 6, // EFI_IA32 } - opt, err := parseOptClientArchType(data) + var opt optClientArchType + err := opt.FromBytes(data) require.NoError(t, err) require.Equal(t, iana.EFI_IA32, opt.Archs[0]) } func TestParseOptClientArchTypeInvalid(t *testing.T) { data := []byte{42} - _, err := parseOptClientArchType(data) + var opt optClientArchType + err := opt.FromBytes(data) require.Error(t, err) } @@ -26,7 +28,8 @@ func TestOptClientArchTypeParseAndToBytes(t *testing.T) { data := []byte{ 0, 8, // EFI_XSCALE } - opt, err := parseOptClientArchType(data) + var opt optClientArchType + err := opt.FromBytes(data) require.NoError(t, err) require.Equal(t, data, opt.ToBytes()) } diff --git a/dhcpv6/option_bootfileparam.go b/dhcpv6/option_bootfileparam.go index 76180809..ba09ca04 100644 --- a/dhcpv6/option_bootfileparam.go +++ b/dhcpv6/option_bootfileparam.go @@ -9,10 +9,12 @@ import ( // OptBootFileParam returns a BootfileParam option as defined in RFC 5970 // Section 3.2. func OptBootFileParam(args ...string) Option { - return optBootFileParam(args) + return &optBootFileParam{args} } -type optBootFileParam []string +type optBootFileParam struct { + params []string +} // Code returns the option code func (optBootFileParam) Code() OptionCode { @@ -22,7 +24,7 @@ func (optBootFileParam) Code() OptionCode { // ToBytes serializes the option and returns it as a sequence of bytes func (op optBootFileParam) ToBytes() []byte { buf := uio.NewBigEndianBuffer(nil) - for _, param := range op { + for _, param := range op.params { if len(param) >= 1<<16 { // TODO: say something here instead of silently ignoring a parameter continue @@ -42,20 +44,16 @@ func (op optBootFileParam) ToBytes() []byte { } func (op optBootFileParam) String() string { - return fmt.Sprintf("%s: %v", op.Code(), ([]string)(op)) + return fmt.Sprintf("%s: %v", op.Code(), op.params) } -// parseOptBootFileParam builds an OptBootFileParam structure from a sequence +// FromBytes builds an OptBootFileParam structure from a sequence // of bytes. The input data does not include option code and length bytes. -func parseOptBootFileParam(data []byte) (optBootFileParam, error) { +func (op *optBootFileParam) FromBytes(data []byte) error { buf := uio.NewBigEndianBuffer(data) - var result optBootFileParam for buf.Has(2) { length := buf.Read16() - result = append(result, string(buf.CopyN(int(length)))) - } - if err := buf.FinError(); err != nil { - return nil, err + op.params = append(op.params, string(buf.CopyN(int(length)))) } - return result, nil + return buf.FinError() } diff --git a/dhcpv6/option_bootfileparam_test.go b/dhcpv6/option_bootfileparam_test.go index 467f2455..3bc266d6 100644 --- a/dhcpv6/option_bootfileparam_test.go +++ b/dhcpv6/option_bootfileparam_test.go @@ -41,8 +41,8 @@ func compileTestBootfileParams(t *testing.T, params []string) []byte { func TestOptBootFileParam(t *testing.T) { expected := string(compileTestBootfileParams(t, testBootfileParams1)) - opt, err := parseOptBootFileParam([]byte(expected)) - if err != nil { + var opt optBootFileParam + if err := opt.FromBytes([]byte(expected)); err != nil { t.Fatal(err) } if string(opt.ToBytes()) != expected { @@ -54,10 +54,10 @@ func TestParsedTypeOptBootFileParam(t *testing.T) { tryParse := func(compiled []byte, expected []string) { opt, err := ParseOption(OptionBootfileParam, compiled) require.NoError(t, err) - bootfileParamOpt, ok := opt.(optBootFileParam) + bootfileParamOpt, ok := opt.(*optBootFileParam) require.True(t, ok, fmt.Sprintf("invalid type: %T instead of %T", opt, bootfileParamOpt)) require.Equal(t, compiled, bootfileParamOpt.ToBytes()) - require.Equal(t, expected, ([]string)(bootfileParamOpt)) + require.Equal(t, expected, bootfileParamOpt.params) } tryParse( diff --git a/dhcpv6/option_bootfileurl.go b/dhcpv6/option_bootfileurl.go index 14611899..7a8e54a4 100644 --- a/dhcpv6/option_bootfileurl.go +++ b/dhcpv6/option_bootfileurl.go @@ -6,10 +6,12 @@ import ( // OptBootFileURL returns a OptionBootfileURL as defined by RFC 5970. func OptBootFileURL(url string) Option { - return optBootFileURL(url) + return &optBootFileURL{url} } -type optBootFileURL string +type optBootFileURL struct { + url string +} // Code returns the option code func (op optBootFileURL) Code() OptionCode { @@ -18,15 +20,16 @@ func (op optBootFileURL) Code() OptionCode { // ToBytes serializes the option and returns it as a sequence of bytes func (op optBootFileURL) ToBytes() []byte { - return []byte(op) + return []byte(op.url) } func (op optBootFileURL) String() string { - return fmt.Sprintf("%s: %s", op.Code(), string(op)) + return fmt.Sprintf("%s: %s", op.Code(), op.url) } -// parseOptBootFileURL builds an optBootFileURL structure from a sequence +// FromBytes builds an optBootFileURL structure from a sequence // of bytes. The input data does not include option code and length bytes. -func parseOptBootFileURL(data []byte) (optBootFileURL, error) { - return optBootFileURL(string(data)), nil +func (op *optBootFileURL) FromBytes(data []byte) error { + op.url = string(data) + return nil } diff --git a/dhcpv6/option_bootfileurl_test.go b/dhcpv6/option_bootfileurl_test.go index ac45ef51..b65d644d 100644 --- a/dhcpv6/option_bootfileurl_test.go +++ b/dhcpv6/option_bootfileurl_test.go @@ -9,11 +9,11 @@ import ( func TestOptBootFileURL(t *testing.T) { expected := "https://insomniac.slackware.it" - opt, err := parseOptBootFileURL([]byte(expected)) - if err != nil { + var opt optBootFileURL + if err := opt.FromBytes([]byte(expected)); err != nil { t.Fatal(err) } - if string(opt) != expected { + if opt.url != expected { t.Fatalf("Invalid boot file URL. Expected %v, got %v", expected, opt) } require.Contains(t, opt.String(), "https://insomniac.slackware.it", "String() should contain the correct BootFileUrl output") diff --git a/dhcpv6/option_clientid.go b/dhcpv6/option_clientid.go index d07c4d50..eea0d013 100644 --- a/dhcpv6/option_clientid.go +++ b/dhcpv6/option_clientid.go @@ -22,13 +22,11 @@ func (op *optClientID) String() string { return fmt.Sprintf("%s: %s", op.Code(), op.DUID) } -// parseOptClientID builds an OptClientId structure from a sequence +// FromBytes builds an optClientID structure from a sequence // of bytes. The input data does not include option code and length // bytes. -func parseOptClientID(data []byte) (*optClientID, error) { - cid, err := DUIDFromBytes(data) - if err != nil { - return nil, err - } - return &optClientID{cid}, nil +func (op *optClientID) FromBytes(data []byte) error { + var err error + op.DUID, err = DUIDFromBytes(data) + return err } diff --git a/dhcpv6/option_clientid_test.go b/dhcpv6/option_clientid_test.go index 0bac4108..dd637aef 100644 --- a/dhcpv6/option_clientid_test.go +++ b/dhcpv6/option_clientid_test.go @@ -2,19 +2,39 @@ package dhcpv6 import ( "net" + "reflect" "testing" "github.com/insomniacslk/dhcp/iana" "github.com/stretchr/testify/require" ) +func TestParseMessageOptionsWithClientID(t *testing.T) { + buf := []byte{ + 0, 1, // Client ID option + 0, 10, // length + 0, 3, // DUID_LL + 0, 1, // hwtype ethernet + 0, 1, 2, 3, 4, 5, // HW addr + } + + want := &DUIDLL{HWType: iana.HWTypeEthernet, LinkLayerAddr: net.HardwareAddr{0, 1, 2, 3, 4, 5}} + var mo MessageOptions + if err := mo.FromBytes(buf); err != nil { + t.Errorf("FromBytes = %v", err) + } else if got := mo.ClientID(); !reflect.DeepEqual(got, want) { + t.Errorf("ClientID = %v, want %v", got, want) + } +} + func TestParseOptClientID(t *testing.T) { data := []byte{ 0, 3, // DUID_LL 0, 1, // hwtype ethernet 0, 1, 2, 3, 4, 5, // hw addr } - opt, err := parseOptClientID(data) + var opt optClientID + err := opt.FromBytes(data) require.NoError(t, err) want := OptClientID( &DUIDLL{ @@ -22,7 +42,7 @@ func TestParseOptClientID(t *testing.T) { LinkLayerAddr: net.HardwareAddr([]byte{0, 1, 2, 3, 4, 5}), }, ) - require.Equal(t, want, opt) + require.Equal(t, want, &opt) } func TestOptClientIdToBytes(t *testing.T) { @@ -46,7 +66,8 @@ func TestOptClientIdDecodeEncode(t *testing.T) { 0, 1, // hwtype ethernet 5, 4, 3, 2, 1, 0, // hw addr } - opt, err := parseOptClientID(data) + var opt optClientID + err := opt.FromBytes(data) require.NoError(t, err) require.Equal(t, data, opt.ToBytes()) } @@ -73,7 +94,8 @@ func TestOptClientIdparseOptClientIDBogusDUID(t *testing.T) { 1, 2, 3, 4, 5, 6, 7, 8, 9, // a UUID should be 18 bytes not 17 10, 11, 12, 13, 14, 15, 16, 17, } - _, err := parseOptClientID(data) + var opt optClientID + err := opt.FromBytes(data) require.Error(t, err, "A truncated OptClientId DUID should return an error") } @@ -81,6 +103,7 @@ func TestOptClientIdparseOptClientIDInvalidTooShort(t *testing.T) { data := []byte{ 0, // truncated: DUIDs are at least 2 bytes } - _, err := parseOptClientID(data) + var opt optClientID + err := opt.FromBytes(data) require.Error(t, err, "A truncated OptClientId should return an error") } diff --git a/dhcpv6/option_clientlinklayeraddress.go b/dhcpv6/option_clientlinklayeraddress.go index d5ec0289..878a5763 100644 --- a/dhcpv6/option_clientlinklayeraddress.go +++ b/dhcpv6/option_clientlinklayeraddress.go @@ -36,12 +36,11 @@ func (op *optClientLinkLayerAddress) String() string { return fmt.Sprintf("%s: Type=%s LinkLayerAddress=%s", op.Code(), op.LinkLayerType, op.LinkLayerAddress) } -// parseOptClientLinkLayerAddress deserializes from bytes -// to build an optClientLinkLayerAddress structure. -func parseOptClientLinkLayerAddress(data []byte) (*optClientLinkLayerAddress, error) { - var opt optClientLinkLayerAddress +// FromBytes deserializes from bytes to build an optClientLinkLayerAddress +// structure. +func (op *optClientLinkLayerAddress) FromBytes(data []byte) error { buf := uio.NewBigEndianBuffer(data) - opt.LinkLayerType = iana.HWType(buf.Read16()) - opt.LinkLayerAddress = buf.ReadAll() - return &opt, buf.FinError() + op.LinkLayerType = iana.HWType(buf.Read16()) + op.LinkLayerAddress = buf.ReadAll() + return buf.FinError() } diff --git a/dhcpv6/option_clientlinklayeraddress_test.go b/dhcpv6/option_clientlinklayeraddress_test.go index 9e243a41..1ef40c52 100644 --- a/dhcpv6/option_clientlinklayeraddress_test.go +++ b/dhcpv6/option_clientlinklayeraddress_test.go @@ -14,7 +14,8 @@ func TestParseOptClientLinkLayerAddress(t *testing.T) { 0, 1, // LinkLayerType 164, 131, 231, 227, 223, 136, } - opt, err := parseOptClientLinkLayerAddress(data) + var opt optClientLinkLayerAddress + err := opt.FromBytes(data) require.NoError(t, err) require.Equal(t, OptionClientLinkLayerAddr, opt.Code()) diff --git a/dhcpv6/option_dhcpv4_msg.go b/dhcpv6/option_dhcpv4_msg.go index ad29353c..825ea8a2 100644 --- a/dhcpv6/option_dhcpv4_msg.go +++ b/dhcpv6/option_dhcpv4_msg.go @@ -40,12 +40,10 @@ func (op *OptDHCPv4Msg) LongString(indent int) string { return fmt.Sprintf("%s: {%v%s}", op.Code(), summary, ind) } -// ParseOptDHCPv4Msg builds an OptDHCPv4Msg structure -// from a sequence of bytes. The input data does not include option code and length -// bytes. -func ParseOptDHCPv4Msg(data []byte) (*OptDHCPv4Msg, error) { - var opt OptDHCPv4Msg +// FromBytes builds an OptDHCPv4Msg structure from a sequence of bytes. The +// input data does not include option code and length bytes. +func (op *OptDHCPv4Msg) FromBytes(data []byte) error { var err error - opt.Msg, err = dhcpv4.FromBytes(data) - return &opt, err + op.Msg, err = dhcpv4.FromBytes(data) + return err } diff --git a/dhcpv6/option_dhcpv4_msg_test.go b/dhcpv6/option_dhcpv4_msg_test.go index d8d3f6d7..e7bb1f6d 100644 --- a/dhcpv6/option_dhcpv4_msg_test.go +++ b/dhcpv6/option_dhcpv4_msg_test.go @@ -43,7 +43,8 @@ func TestParseOptDHCPv4Msg(t *testing.T) { // magic cookie, then no options data = append(data, magicCookie[:]...) - opt, err := ParseOptDHCPv4Msg(data) + var opt OptDHCPv4Msg + err := opt.FromBytes(data) d := opt.Msg require.NoError(t, err) require.Equal(t, d.OpCode, dhcpv4.OpcodeBootRequest) diff --git a/dhcpv6/option_dhcpv4_o_dhcpv6_server.go b/dhcpv6/option_dhcpv4_o_dhcpv6_server.go index f1f2d27f..1bd60af5 100644 --- a/dhcpv6/option_dhcpv4_o_dhcpv6_server.go +++ b/dhcpv6/option_dhcpv4_o_dhcpv6_server.go @@ -33,14 +33,12 @@ func (op *OptDHCP4oDHCP6Server) String() string { return fmt.Sprintf("%s: %v", op.Code(), op.DHCP4oDHCP6Servers) } -// ParseOptDHCP4oDHCP6Server builds an OptDHCP4oDHCP6Server structure -// from a sequence of bytes. The input data does not include option code and length -// bytes. -func ParseOptDHCP4oDHCP6Server(data []byte) (*OptDHCP4oDHCP6Server, error) { - var opt OptDHCP4oDHCP6Server +// FromBytes builds an OptDHCP4oDHCP6Server structure from a sequence of bytes. +// The input data does not include option code and length bytes. +func (op *OptDHCP4oDHCP6Server) FromBytes(data []byte) error { buf := uio.NewBigEndianBuffer(data) for buf.Has(net.IPv6len) { - opt.DHCP4oDHCP6Servers = append(opt.DHCP4oDHCP6Servers, buf.CopyN(net.IPv6len)) + op.DHCP4oDHCP6Servers = append(op.DHCP4oDHCP6Servers, buf.CopyN(net.IPv6len)) } - return &opt, buf.FinError() + return buf.FinError() } diff --git a/dhcpv6/option_dhcpv4_o_dhcpv6_server_test.go b/dhcpv6/option_dhcpv4_o_dhcpv6_server_test.go index ae5bcdc7..e9674c2a 100644 --- a/dhcpv6/option_dhcpv4_o_dhcpv6_server_test.go +++ b/dhcpv6/option_dhcpv4_o_dhcpv6_server_test.go @@ -2,11 +2,28 @@ package dhcpv6 import ( "net" + "reflect" "testing" "github.com/stretchr/testify/require" ) +func TestParseMessageOptionsWithDHCP4oDHCP6Server(t *testing.T) { + ip := net.IP{0x2a, 0x03, 0x28, 0x80, 0xff, 0xfe, 0x00, 0x0c, 0xfa, 0xce, 0xb0, 0x0c, 0x00, 0x00, 0x00, 0x35} + data := append([]byte{ + 0, 88, // DHCP4oDHCP6 option. + 0, 16, // length + }, ip...) + + want := []net.IP{ip} + var mo MessageOptions + if err := mo.FromBytes(data); err != nil { + t.Errorf("FromBytes = %v", err) + } else if got := mo.DHCP4oDHCP6Server(); !reflect.DeepEqual(got.DHCP4oDHCP6Servers, want) { + t.Errorf("FromBytes = %v, want %v", got.DHCP4oDHCP6Servers, want) + } +} + func TestParseOptDHCP4oDHCP6Server(t *testing.T) { data := []byte{ 0x2a, 0x03, 0x28, 0x80, 0xff, 0xfe, 0x00, 0x0c, 0xfa, 0xce, 0xb0, 0x0c, 0x00, 0x00, 0x00, 0x35, @@ -14,7 +31,8 @@ func TestParseOptDHCP4oDHCP6Server(t *testing.T) { expected := []net.IP{ net.IP(data), } - opt, err := ParseOptDHCP4oDHCP6Server(data) + var opt OptDHCP4oDHCP6Server + err := opt.FromBytes(data) require.NoError(t, err) require.Equal(t, expected, opt.DHCP4oDHCP6Servers) require.Equal(t, OptionDHCP4oDHCP6Server, opt.Code()) @@ -24,17 +42,17 @@ func TestParseOptDHCP4oDHCP6Server(t *testing.T) { func TestOptDHCP4oDHCP6ServerToBytes(t *testing.T) { ip1 := net.ParseIP("2a03:2880:fffe:c:face:b00c:0:35") ip2 := net.ParseIP("2001:4860:4860::8888") - servers := []net.IP{ip1, ip2} - expected := append([]byte{}, []byte(ip1)...) - expected = append(expected, []byte(ip2)...) - opt := OptDHCP4oDHCP6Server{DHCP4oDHCP6Servers: servers} - require.Equal(t, expected, opt.ToBytes()) + opt := OptDHCP4oDHCP6Server{DHCP4oDHCP6Servers: []net.IP{ip1, ip2}} + + want := []byte(append(ip1, ip2...)) + require.Equal(t, want, opt.ToBytes()) } func TestParseOptDHCP4oDHCP6ServerParseNoAddr(t *testing.T) { data := []byte{} var expected []net.IP - opt, err := ParseOptDHCP4oDHCP6Server(data) + var opt OptDHCP4oDHCP6Server + err := opt.FromBytes(data) require.NoError(t, err) require.Equal(t, expected, opt.DHCP4oDHCP6Servers) } @@ -49,6 +67,7 @@ func TestParseOptDHCP4oDHCP6ServerParseBogus(t *testing.T) { data := []byte{ 0x2a, 0x03, 0x28, 0x80, 0xff, 0xfe, 0x00, 0x0c, // invalid IPv6 address } - _, err := ParseOptDHCP4oDHCP6Server(data) + var opt OptDHCP4oDHCP6Server + err := opt.FromBytes(data) require.Error(t, err, "An invalid IPv6 address should return an error") } diff --git a/dhcpv6/option_dns.go b/dhcpv6/option_dns.go index f69973a1..af9bafea 100644 --- a/dhcpv6/option_dns.go +++ b/dhcpv6/option_dns.go @@ -34,14 +34,12 @@ func (op *optDNS) String() string { return fmt.Sprintf("%s: %v", op.Code(), op.NameServers) } -// parseOptDNS builds an optDNS structure -// from a sequence of bytes. The input data does not include option code and length -// bytes. -func parseOptDNS(data []byte) (*optDNS, error) { - var opt optDNS +// FromBytes builds an optDNS structure from a sequence of bytes. The input +// data does not include option code and length bytes. +func (op *optDNS) FromBytes(data []byte) error { buf := uio.NewBigEndianBuffer(data) for buf.Has(net.IPv6len) { - opt.NameServers = append(opt.NameServers, buf.CopyN(net.IPv6len)) + op.NameServers = append(op.NameServers, buf.CopyN(net.IPv6len)) } - return &opt, buf.FinError() + return buf.FinError() } diff --git a/dhcpv6/option_dns_test.go b/dhcpv6/option_dns_test.go index 34f22f7d..2c810764 100644 --- a/dhcpv6/option_dns_test.go +++ b/dhcpv6/option_dns_test.go @@ -14,7 +14,8 @@ func TestParseOptDNS(t *testing.T) { expected := []net.IP{ net.IP(data), } - opt, err := parseOptDNS(data) + var opt optDNS + err := opt.FromBytes(data) require.NoError(t, err) require.Equal(t, expected, opt.NameServers) require.Equal(t, OptionDNSRecursiveNameServer, opt.Code()) @@ -35,6 +36,7 @@ func TestParseOptDNSBogus(t *testing.T) { data := []byte{ 0x2a, 0x03, 0x28, 0x80, 0xff, 0xfe, 0x00, 0x0c, // invalid IPv6 address } - _, err := parseOptDNS(data) + var opt optDNS + err := opt.FromBytes(data) require.Error(t, err, "An invalid nameserver IPv6 address should return an error") } diff --git a/dhcpv6/option_domainsearchlist.go b/dhcpv6/option_domainsearchlist.go index e94799db..fd8aa01c 100644 --- a/dhcpv6/option_domainsearchlist.go +++ b/dhcpv6/option_domainsearchlist.go @@ -28,14 +28,10 @@ func (op *optDomainSearchList) String() string { return fmt.Sprintf("%s: %s", op.Code(), op.DomainSearchList) } -// ParseOptDomainSearchList builds an OptDomainSearchList structure from a sequence -// of bytes. The input data does not include option code and length bytes. -func parseOptDomainSearchList(data []byte) (*optDomainSearchList, error) { - var opt optDomainSearchList +// FromBytes builds an OptDomainSearchList structure from a sequence of bytes. +// The input data does not include option code and length bytes. +func (op *optDomainSearchList) FromBytes(data []byte) error { var err error - opt.DomainSearchList, err = rfc1035label.FromBytes(data) - if err != nil { - return nil, err - } - return &opt, nil + op.DomainSearchList, err = rfc1035label.FromBytes(data) + return err } diff --git a/dhcpv6/option_domainsearchlist_test.go b/dhcpv6/option_domainsearchlist_test.go index 433f710a..c03759c5 100644 --- a/dhcpv6/option_domainsearchlist_test.go +++ b/dhcpv6/option_domainsearchlist_test.go @@ -12,7 +12,8 @@ func TestParseOptDomainSearchList(t *testing.T) { 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0, 6, 's', 'u', 'b', 'n', 'e', 't', 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'o', 'r', 'g', 0, } - opt, err := parseOptDomainSearchList(data) + var opt optDomainSearchList + err := opt.FromBytes(data) require.NoError(t, err) require.Equal(t, OptionDomainSearchList, opt.Code()) require.Equal(t, 2, len(opt.DomainSearchList.Labels)) @@ -42,6 +43,7 @@ func TestParseOptDomainSearchListInvalidLength(t *testing.T) { 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0, 6, 's', 'u', 'b', 'n', 'e', 't', 7, 'e', // truncated } - _, err := parseOptDomainSearchList(data) + var opt optDomainSearchList + err := opt.FromBytes(data) require.Error(t, err, "A truncated OptDomainSearchList should return an error") } diff --git a/dhcpv6/option_elapsedtime.go b/dhcpv6/option_elapsedtime.go index 823c9425..14414285 100644 --- a/dhcpv6/option_elapsedtime.go +++ b/dhcpv6/option_elapsedtime.go @@ -32,11 +32,10 @@ func (op *optElapsedTime) String() string { return fmt.Sprintf("%s: %s", op.Code(), op.ElapsedTime) } -// build an optElapsedTime structure from a sequence of bytes. +// FromBytes builds an optElapsedTime structure from a sequence of bytes. // The input data does not include option code and length bytes. -func parseOptElapsedTime(data []byte) (*optElapsedTime, error) { - var opt optElapsedTime +func (op *optElapsedTime) FromBytes(data []byte) error { buf := uio.NewBigEndianBuffer(data) - opt.ElapsedTime = time.Duration(buf.Read16()) * 10 * time.Millisecond - return &opt, buf.FinError() + op.ElapsedTime = time.Duration(buf.Read16()) * 10 * time.Millisecond + return buf.FinError() } diff --git a/dhcpv6/option_elapsedtime_test.go b/dhcpv6/option_elapsedtime_test.go index 2155539f..b0d2dc1f 100644 --- a/dhcpv6/option_elapsedtime_test.go +++ b/dhcpv6/option_elapsedtime_test.go @@ -9,7 +9,8 @@ import ( ) func TestOptElapsedTime(t *testing.T) { - opt, err := parseOptElapsedTime([]byte{0xaa, 0xbb}) + var opt optElapsedTime + err := opt.FromBytes([]byte{0xaa, 0xbb}) if err != nil { t.Fatal(err) } @@ -35,9 +36,10 @@ func TestOptElapsedTimeString(t *testing.T) { } func TestOptElapsedTimeParseInvalidOption(t *testing.T) { - _, err := parseOptElapsedTime([]byte{0xaa}) + var opt optElapsedTime + err := opt.FromBytes([]byte{0xaa}) require.Error(t, err, "A short option should return an error") - _, err = parseOptElapsedTime([]byte{0xaa, 0xbb, 0xcc}) + err = opt.FromBytes([]byte{0xaa, 0xbb, 0xcc}) require.Error(t, err, "An option with too many bytes should return an error") } diff --git a/dhcpv6/option_fqdn.go b/dhcpv6/option_fqdn.go index 73a525a5..adb47fb3 100644 --- a/dhcpv6/option_fqdn.go +++ b/dhcpv6/option_fqdn.go @@ -32,15 +32,14 @@ func (op *OptFQDN) String() string { return fmt.Sprintf("%s: {Flags=%d DomainName=%s}", op.Code(), op.Flags, op.DomainName) } -// ParseOptFQDN deserializes from bytes to build a OptFQDN structure. -func ParseOptFQDN(data []byte) (*OptFQDN, error) { - var opt OptFQDN +// FromBytes deserializes from bytes to build a OptFQDN structure. +func (op *OptFQDN) FromBytes(data []byte) error { var err error buf := uio.NewBigEndianBuffer(data) - opt.Flags = buf.Read8() - opt.DomainName, err = rfc1035label.FromBytes(buf.ReadAll()) + op.Flags = buf.Read8() + op.DomainName, err = rfc1035label.FromBytes(buf.ReadAll()) if err != nil { - return nil, err + return err } - return &opt, buf.FinError() + return buf.FinError() } diff --git a/dhcpv6/option_fqdn_test.go b/dhcpv6/option_fqdn_test.go index 69720bad..0bb2ab46 100644 --- a/dhcpv6/option_fqdn_test.go +++ b/dhcpv6/option_fqdn_test.go @@ -14,7 +14,8 @@ func TestParseOptFQDN(t *testing.T) { 4, 'c', 'n', 'o', 's', 9, 'l', 'o', 'c', 'a', 'l', 'h', 'o', 's', 't', 0, } - opt, err := ParseOptFQDN(data) + var opt OptFQDN + err := opt.FromBytes(data) require.NoError(t, err) require.Equal(t, OptionFQDN, opt.Code()) diff --git a/dhcpv6/option_iaaddress.go b/dhcpv6/option_iaaddress.go index 072ba65f..bc562545 100644 --- a/dhcpv6/option_iaaddress.go +++ b/dhcpv6/option_iaaddress.go @@ -69,22 +69,20 @@ func (op *OptIAAddress) LongString(indent int) string { op.Code(), op.IPv6Addr, op.PreferredLifetime, op.ValidLifetime, op.Options.LongString(indent)) } -// ParseOptIAAddress builds an OptIAAddress structure from a sequence -// of bytes. The input data does not include option code and length -// bytes. -func ParseOptIAAddress(data []byte) (*OptIAAddress, error) { - var opt OptIAAddress +// FromBytes builds an OptIAAddress structure from a sequence of bytes. The +// input data does not include option code and length bytes. +func (op *OptIAAddress) FromBytes(data []byte) error { buf := uio.NewBigEndianBuffer(data) - opt.IPv6Addr = net.IP(buf.CopyN(net.IPv6len)) + op.IPv6Addr = net.IP(buf.CopyN(net.IPv6len)) var t1, t2 Duration t1.Unmarshal(buf) t2.Unmarshal(buf) - opt.PreferredLifetime = t1.Duration - opt.ValidLifetime = t2.Duration + op.PreferredLifetime = t1.Duration + op.ValidLifetime = t2.Duration - if err := opt.Options.FromBytes(buf.ReadAll()); err != nil { - return nil, err + if err := op.Options.FromBytes(buf.ReadAll()); err != nil { + return err } - return &opt, buf.FinError() + return buf.FinError() } diff --git a/dhcpv6/option_iaaddress_test.go b/dhcpv6/option_iaaddress_test.go index 26f1732f..2f3db503 100644 --- a/dhcpv6/option_iaaddress_test.go +++ b/dhcpv6/option_iaaddress_test.go @@ -15,7 +15,8 @@ func TestOptIAAddressParse(t *testing.T) { 0xe, 0xf, 0x1, 0x2, // valid lifetime 0, 8, 0, 2, 0xaa, 0xbb, // options }...) - opt, err := ParseOptIAAddress(data) + var opt OptIAAddress + err := opt.FromBytes(data) require.NoError(t, err) require.Equal(t, net.IP(ipaddr), opt.IPv6Addr) require.Equal(t, 0x0a0b0c0d*time.Second, opt.PreferredLifetime) @@ -28,7 +29,8 @@ func TestOptIAAddressParseInvalidTooShort(t *testing.T) { 0xa, 0xb, 0xc, 0xd, // preferred lifetime // truncated here } - _, err := ParseOptIAAddress(data) + var opt OptIAAddress + err := opt.FromBytes(data) require.Error(t, err) } @@ -39,7 +41,8 @@ func TestOptIAAddressParseInvalidBrokenOptions(t *testing.T) { 0xe, 0xf, 0x1, 0x2, // valid lifetime 0, 8, 0, 2, 0xaa, // broken options } - _, err := ParseOptIAAddress(data) + var opt OptIAAddress + err := opt.FromBytes(data) require.Error(t, err) } @@ -78,7 +81,8 @@ func TestOptIAAddressString(t *testing.T) { 0x00, 0x00, 0x00, 50, // valid lifetime 0, 8, 0, 2, 0xaa, 0xbb, // options }...) - opt, err := ParseOptIAAddress(data) + var opt OptIAAddress + err := opt.FromBytes(data) require.NoError(t, err) str := opt.String() diff --git a/dhcpv6/option_iapd.go b/dhcpv6/option_iapd.go index 62961031..d853cea4 100644 --- a/dhcpv6/option_iapd.go +++ b/dhcpv6/option_iapd.go @@ -79,21 +79,20 @@ func (op *OptIAPD) LongString(indentSpace int) string { return fmt.Sprintf("%s: IAID=%#x T1=%v T2=%v Options=%v", op.Code(), op.IaId, op.T1, op.T2, op.Options.LongString(indentSpace)) } -// ParseOptIAPD builds an OptIAPD structure from a sequence of bytes. -// The input data does not include option code and length bytes. -func ParseOptIAPD(data []byte) (*OptIAPD, error) { - var opt OptIAPD +// FromBytes builds an OptIAPD structure from a sequence of bytes. The input +// data does not include option code and length bytes. +func (op *OptIAPD) FromBytes(data []byte) error { buf := uio.NewBigEndianBuffer(data) - buf.ReadBytes(opt.IaId[:]) + buf.ReadBytes(op.IaId[:]) var t1, t2 Duration t1.Unmarshal(buf) t2.Unmarshal(buf) - opt.T1 = t1.Duration - opt.T2 = t2.Duration + op.T1 = t1.Duration + op.T2 = t2.Duration - if err := opt.Options.FromBytes(buf.ReadAll()); err != nil { - return nil, err + if err := op.Options.FromBytes(buf.ReadAll()); err != nil { + return err } - return &opt, buf.FinError() + return buf.FinError() } diff --git a/dhcpv6/option_iapd_test.go b/dhcpv6/option_iapd_test.go index 13175f46..398a23e3 100644 --- a/dhcpv6/option_iapd_test.go +++ b/dhcpv6/option_iapd_test.go @@ -2,12 +2,50 @@ package dhcpv6 import ( "net" + "reflect" "testing" "time" "github.com/stretchr/testify/require" ) +func TestParseMessageWithIAPD(t *testing.T) { + data := []byte{ + 0, 25, // IAPD option code + 0, 41, // length + 1, 0, 0, 0, // IAID + 0, 0, 0, 1, // T1 + 0, 0, 0, 2, // T2 + 0, 26, 0, 25, // 26 = IAPrefix Option, 25 = length + 0, 0, 0, 2, // IAPrefix preferredLifetime + 0, 0, 0, 4, // IAPrefix validLifetime + 36, // IAPrefix prefixLength + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, // IAPrefix ipv6Prefix + } + var got MessageOptions + if err := got.FromBytes(data); err != nil { + t.Errorf("FromBytes = %v", err) + } + + want := &OptIAPD{ + IaId: [4]byte{1, 0, 0, 0}, + T1: 1 * time.Second, + T2: 2 * time.Second, + Options: PDOptions{Options: Options{&OptIAPrefix{ + PreferredLifetime: 2 * time.Second, + ValidLifetime: 4 * time.Second, + Prefix: &net.IPNet{ + Mask: net.CIDRMask(36, 128), + IP: net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, + }, + Options: PrefixOptions{Options: Options{}}, + }}}, + } + if gotIAPD := got.OneIAPD(); !reflect.DeepEqual(gotIAPD, want) { + t.Errorf("OneIAPD = %v, want %v", gotIAPD, want) + } +} + func TestOptIAPDParseOptIAPD(t *testing.T) { data := []byte{ 1, 0, 0, 0, // IAID @@ -19,7 +57,8 @@ func TestOptIAPDParseOptIAPD(t *testing.T) { 36, // IAPrefix prefixLength 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, // IAPrefix ipv6Prefix } - opt, err := ParseOptIAPD(data) + var opt OptIAPD + err := opt.FromBytes(data) require.NoError(t, err) require.Equal(t, OptionIAPD, opt.Code()) require.Equal(t, [4]byte{1, 0, 0, 0}, opt.IaId) @@ -33,7 +72,8 @@ func TestOptIAPDParseOptIAPDInvalidLength(t *testing.T) { 0, 0, 0, 1, // T1 // truncated from here } - _, err := ParseOptIAPD(data) + var opt OptIAPD + err := opt.FromBytes(data) require.Error(t, err) } @@ -48,7 +88,8 @@ func TestOptIAPDParseOptIAPDInvalidOptions(t *testing.T) { 36, // IAPrefix prefixLength 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // IAPrefix ipv6Prefix missing last byte } - _, err := ParseOptIAPD(data) + var opt OptIAPD + err := opt.FromBytes(data) require.Error(t, err) } @@ -92,7 +133,8 @@ func TestOptIAPDString(t *testing.T) { 36, // IAPrefix prefixLength 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, // IAPrefix ipv6Prefix } - opt, err := ParseOptIAPD(data) + var opt OptIAPD + err := opt.FromBytes(data) require.NoError(t, err) str := opt.String() diff --git a/dhcpv6/option_iaprefix.go b/dhcpv6/option_iaprefix.go index fc41e0a7..f7d3e761 100644 --- a/dhcpv6/option_iaprefix.go +++ b/dhcpv6/option_iaprefix.go @@ -74,31 +74,30 @@ func (op *OptIAPrefix) String() string { op.Code(), op.PreferredLifetime, op.ValidLifetime, op.Prefix, op.Options) } -// ParseOptIAPrefix an OptIAPrefix structure from a sequence of bytes. The -// input data does not include option code and length bytes. -func ParseOptIAPrefix(data []byte) (*OptIAPrefix, error) { +// FromBytes an OptIAPrefix structure from a sequence of bytes. The input data +// does not include option code and length bytes. +func (op *OptIAPrefix) FromBytes(data []byte) error { buf := uio.NewBigEndianBuffer(data) - var opt OptIAPrefix var t1, t2 Duration t1.Unmarshal(buf) t2.Unmarshal(buf) - opt.PreferredLifetime = t1.Duration - opt.ValidLifetime = t2.Duration + op.PreferredLifetime = t1.Duration + op.ValidLifetime = t2.Duration length := buf.Read8() ip := net.IP(buf.CopyN(net.IPv6len)) if length == 0 { - opt.Prefix = nil + op.Prefix = nil } else { - opt.Prefix = &net.IPNet{ + op.Prefix = &net.IPNet{ Mask: net.CIDRMask(int(length), 128), IP: ip, } } - if err := opt.Options.FromBytes(buf.ReadAll()); err != nil { - return nil, err + if err := op.Options.FromBytes(buf.ReadAll()); err != nil { + return err } - return &opt, buf.FinError() + return buf.FinError() } diff --git a/dhcpv6/option_iaprefix_test.go b/dhcpv6/option_iaprefix_test.go index 27d0c951..be7e232d 100644 --- a/dhcpv6/option_iaprefix_test.go +++ b/dhcpv6/option_iaprefix_test.go @@ -17,8 +17,8 @@ func TestOptIAPrefix(t *testing.T) { 36, // prefixLength 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, // ipv6Prefix } - opt, err := ParseOptIAPrefix(buf) - if err != nil { + var opt OptIAPrefix + if err := opt.FromBytes(buf); err != nil { t.Fatal(err) } want := &OptIAPrefix{ @@ -30,7 +30,7 @@ func TestOptIAPrefix(t *testing.T) { }, Options: PrefixOptions{[]Option{}}, } - if !reflect.DeepEqual(want, opt) { + if !reflect.DeepEqual(want, &opt) { t.Errorf("parseIAPrefix = %v, want %v", opt, want) } } @@ -79,7 +79,8 @@ func TestOptIAPrefixParseInvalidTooShort(t *testing.T) { 36, // prefixLength 0, 0, 0, 0, 0, 0, 0, // truncated ipv6Prefix } - if opt, err := ParseOptIAPrefix(buf); err == nil { + var opt OptIAPrefix + if err := opt.FromBytes(buf); err == nil { t.Fatalf("ParseOptIAPrefix: Expected error on truncated option, got %v", opt) } } @@ -91,7 +92,8 @@ func TestOptIAPrefixString(t *testing.T) { 36, // prefixLength 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ipv6Prefix } - opt, err := ParseOptIAPrefix(buf) + var opt OptIAPrefix + err := opt.FromBytes(buf) require.NoError(t, err) str := opt.String() diff --git a/dhcpv6/option_informationrefreshtime.go b/dhcpv6/option_informationrefreshtime.go index 8b47b468..e0ba43c0 100644 --- a/dhcpv6/option_informationrefreshtime.go +++ b/dhcpv6/option_informationrefreshtime.go @@ -35,14 +35,13 @@ func (op *optInformationRefreshTime) String() string { return fmt.Sprintf("%s: %v", op.Code(), op.InformationRefreshtime) } -// parseOptInformationRefreshTime builds an optInformationRefreshTime structure from a sequence -// of bytes. The input data does not include option code and length bytes. -func parseOptInformationRefreshTime(data []byte) (*optInformationRefreshTime, error) { - var opt optInformationRefreshTime +// FromBytes builds an optInformationRefreshTime structure from a sequence of +// bytes. The input data does not include option code and length bytes. +func (op *optInformationRefreshTime) FromBytes(data []byte) error { buf := uio.NewBigEndianBuffer(data) var irt Duration irt.Unmarshal(buf) - opt.InformationRefreshtime = irt.Duration - return &opt, buf.FinError() + op.InformationRefreshtime = irt.Duration + return buf.FinError() } diff --git a/dhcpv6/option_informationrefreshtime_test.go b/dhcpv6/option_informationrefreshtime_test.go index 0428a1c6..452ac1d6 100644 --- a/dhcpv6/option_informationrefreshtime_test.go +++ b/dhcpv6/option_informationrefreshtime_test.go @@ -7,7 +7,8 @@ import ( ) func TestOptInformationRefreshTime(t *testing.T) { - opt, err := parseOptInformationRefreshTime([]byte{0xaa, 0xbb, 0xcc, 0xdd}) + var opt optInformationRefreshTime + err := opt.FromBytes([]byte{0xaa, 0xbb, 0xcc, 0xdd}) if err != nil { t.Fatal(err) } diff --git a/dhcpv6/option_interfaceid.go b/dhcpv6/option_interfaceid.go index 163af7bb..a6debba6 100644 --- a/dhcpv6/option_interfaceid.go +++ b/dhcpv6/option_interfaceid.go @@ -26,10 +26,9 @@ func (op *optInterfaceID) String() string { return fmt.Sprintf("%s: %v", op.Code(), op.ID) } -// build an optInterfaceID structure from a sequence of bytes. -// The input data does not include option code and length bytes. -func parseOptInterfaceID(data []byte) (*optInterfaceID, error) { - var opt optInterfaceID - opt.ID = append([]byte(nil), data...) - return &opt, nil +// FromBytes builds an optInterfaceID structure from a sequence of bytes. The +// input data does not include option code and length bytes. +func (op *optInterfaceID) FromBytes(data []byte) error { + op.ID = append([]byte(nil), data...) + return nil } diff --git a/dhcpv6/option_interfaceid_test.go b/dhcpv6/option_interfaceid_test.go index 45c17990..b38cadbf 100644 --- a/dhcpv6/option_interfaceid_test.go +++ b/dhcpv6/option_interfaceid_test.go @@ -9,8 +9,8 @@ import ( func TestParseOptInterfaceID(t *testing.T) { expected := []byte("DSLAM01 eth2/1/01/21") - opt, err := parseOptInterfaceID(expected) - if err != nil { + var opt optInterfaceID + if err := opt.FromBytes(expected); err != nil { t.Fatal(err) } if url := opt.ID; !bytes.Equal(url, expected) { diff --git a/dhcpv6/option_nontemporaryaddress.go b/dhcpv6/option_nontemporaryaddress.go index 76f6f4cd..27349319 100644 --- a/dhcpv6/option_nontemporaryaddress.go +++ b/dhcpv6/option_nontemporaryaddress.go @@ -102,21 +102,20 @@ func (op *OptIANA) LongString(indentSpace int) string { return fmt.Sprintf("%s: IAID=%#x T1=%s T2=%s Options=%s", op.Code(), op.IaId, op.T1, op.T2, op.Options.LongString(indentSpace)) } -// ParseOptIANA builds an OptIANA structure from a sequence of bytes. The +// FromBytes builds an OptIANA structure from a sequence of bytes. The // input data does not include option code and length bytes. -func ParseOptIANA(data []byte) (*OptIANA, error) { - var opt OptIANA +func (op *OptIANA) FromBytes(data []byte) error { buf := uio.NewBigEndianBuffer(data) - buf.ReadBytes(opt.IaId[:]) + buf.ReadBytes(op.IaId[:]) var t1, t2 Duration t1.Unmarshal(buf) t2.Unmarshal(buf) - opt.T1 = t1.Duration - opt.T2 = t2.Duration + op.T1 = t1.Duration + op.T2 = t2.Duration - if err := opt.Options.FromBytes(buf.ReadAll()); err != nil { - return nil, err + if err := op.Options.FromBytes(buf.ReadAll()); err != nil { + return err } - return &opt, buf.FinError() + return buf.FinError() } diff --git a/dhcpv6/option_nontemporaryaddress_test.go b/dhcpv6/option_nontemporaryaddress_test.go index 1ec58e81..3e5c55b1 100644 --- a/dhcpv6/option_nontemporaryaddress_test.go +++ b/dhcpv6/option_nontemporaryaddress_test.go @@ -2,12 +2,43 @@ package dhcpv6 import ( "net" + "reflect" "testing" "time" "github.com/stretchr/testify/require" ) +func TestParseMessageWithIANA(t *testing.T) { + data := []byte{ + 0, 3, // IANA option code + 0, 40, // length + 1, 0, 0, 0, // IAID + 0, 0, 0, 1, // T1 + 0, 0, 0, 2, // T2 + 0, 5, 0, 0x18, 0x24, 1, 0xdb, 0, 0x30, 0x10, 0xc0, 0x8f, 0xfa, 0xce, 0, 0, 0, 0x44, 0, 0, 0, 0, 0, 2, 0, 0, 0, 4, // options + } + var got MessageOptions + if err := got.FromBytes(data); err != nil { + t.Errorf("FromBytes = %v", err) + } + + want := &OptIANA{ + IaId: [4]byte{1, 0, 0, 0}, + T1: 1 * time.Second, + T2: 2 * time.Second, + Options: IdentityOptions{Options: Options{&OptIAAddress{ + IPv6Addr: net.IP{0x24, 1, 0xdb, 0, 0x30, 0x10, 0xc0, 0x8f, 0xfa, 0xce, 0, 0, 0, 0x44, 0, 0}, + PreferredLifetime: 2 * time.Second, + ValidLifetime: 4 * time.Second, + Options: AddressOptions{Options: Options{}}, + }}}, + } + if gotIANA := got.OneIANA(); !reflect.DeepEqual(gotIANA, want) { + t.Errorf("OneIANA = %v, want %v", gotIANA, want) + } +} + func TestOptIANAParseOptIANA(t *testing.T) { data := []byte{ 1, 0, 0, 0, // IAID @@ -15,7 +46,8 @@ func TestOptIANAParseOptIANA(t *testing.T) { 0, 0, 0, 2, // T2 0, 5, 0, 0x18, 0x24, 1, 0xdb, 0, 0x30, 0x10, 0xc0, 0x8f, 0xfa, 0xce, 0, 0, 0, 0x44, 0, 0, 0, 0, 0xb2, 0x7a, 0, 0, 0xc0, 0x8a, // options } - opt, err := ParseOptIANA(data) + var opt OptIANA + err := opt.FromBytes(data) require.NoError(t, err) require.Equal(t, OptionIANA, opt.Code()) } @@ -26,7 +58,8 @@ func TestOptIANAParseOptIANAInvalidLength(t *testing.T) { 0, 0, 0, 1, // T1 // truncated from here } - _, err := ParseOptIANA(data) + var opt OptIANA + err := opt.FromBytes(data) require.Error(t, err) } @@ -37,7 +70,8 @@ func TestOptIANAParseOptIANAInvalidOptions(t *testing.T) { 0, 0, 0, 2, // T2 0, 5, 0, 0x18, 0x24, 1, 0xdb, 0, 0x30, 0x10, 0xc0, 0x8f, 0xfa, 0xce, 0, 0, 0, 0x44, 0, 0, 0, 0, 0xb2, 0x7a, // truncated options } - _, err := ParseOptIANA(data) + var opt OptIANA + err := opt.FromBytes(data) require.Error(t, err) } @@ -118,7 +152,8 @@ func TestOptIANAString(t *testing.T) { 0, 0, 0, 2, // T2 0, 5, 0, 0x18, 0x24, 1, 0xdb, 0, 0x30, 0x10, 0xc0, 0x8f, 0xfa, 0xce, 0, 0, 0, 0x44, 0, 0, 0, 0, 0xb2, 0x7a, 0, 0, 0xc0, 0x8a, // options } - opt, err := ParseOptIANA(data) + var opt OptIANA + err := opt.FromBytes(data) require.NoError(t, err) str := opt.String() diff --git a/dhcpv6/option_ntp_server.go b/dhcpv6/option_ntp_server.go index a7aafb76..69a3e89b 100644 --- a/dhcpv6/option_ntp_server.go +++ b/dhcpv6/option_ntp_server.go @@ -18,17 +18,20 @@ func (n *NTPSuboptionSrvAddr) Code() OptionCode { // ToBytes returns the byte serialization of the suboption. func (n *NTPSuboptionSrvAddr) ToBytes() []byte { - buf := uio.NewBigEndianBuffer(nil) - buf.Write16(uint16(NTPSuboptionSrvAddrCode)) - buf.Write16(uint16(net.IPv6len)) - buf.WriteBytes(net.IP(*n).To16()) - return buf.Data() + return net.IP(*n).To16() } func (n *NTPSuboptionSrvAddr) String() string { return fmt.Sprintf("Server Address: %s", net.IP(*n).String()) } +// FromBytes parses NTP server address from a byte slice p. +func (n *NTPSuboptionSrvAddr) FromBytes(p []byte) error { + buf := uio.NewBigEndianBuffer(p) + *n = NTPSuboptionSrvAddr(buf.CopyN(net.IPv6len)) + return buf.FinError() +} + // NTPSuboptionMCAddr is NTP_SUBOPTION_MC_ADDR according to RFC 5908. type NTPSuboptionMCAddr net.IP @@ -39,19 +42,24 @@ func (n *NTPSuboptionMCAddr) Code() OptionCode { // ToBytes returns the byte serialization of the suboption. func (n *NTPSuboptionMCAddr) ToBytes() []byte { - buf := uio.NewBigEndianBuffer(nil) - buf.Write16(uint16(NTPSuboptionMCAddrCode)) - buf.Write16(uint16(net.IPv6len)) - buf.WriteBytes(net.IP(*n).To16()) - return buf.Data() + return net.IP(*n).To16() } func (n *NTPSuboptionMCAddr) String() string { return fmt.Sprintf("Multicast Address: %s", net.IP(*n).String()) } +// FromBytes parses NTP multicast address from a byte slice p. +func (n *NTPSuboptionMCAddr) FromBytes(p []byte) error { + buf := uio.NewBigEndianBuffer(p) + *n = NTPSuboptionMCAddr(buf.CopyN(net.IPv6len)) + return buf.FinError() +} + // NTPSuboptionSrvFQDN is NTP_SUBOPTION_SRV_FQDN according to RFC 5908. -type NTPSuboptionSrvFQDN rfc1035label.Labels +type NTPSuboptionSrvFQDN struct { + rfc1035label.Labels +} // Code returns the suboption code. func (n *NTPSuboptionSrvFQDN) Code() OptionCode { @@ -60,17 +68,16 @@ func (n *NTPSuboptionSrvFQDN) Code() OptionCode { // ToBytes returns the byte serialization of the suboption. func (n *NTPSuboptionSrvFQDN) ToBytes() []byte { - buf := uio.NewBigEndianBuffer(nil) - buf.Write16(uint16(NTPSuboptionSrvFQDNCode)) - l := rfc1035label.Labels(*n) - buf.Write16(uint16(l.Length())) - buf.WriteBytes(l.ToBytes()) - return buf.Data() + return n.Labels.ToBytes() } func (n *NTPSuboptionSrvFQDN) String() string { - l := rfc1035label.Labels(*n) - return fmt.Sprintf("Server FQDN: %s", l.String()) + return fmt.Sprintf("Server FQDN: %s", n.Labels.String()) +} + +// FromBytes parses an NTP server FQDN from a byte slice p. +func (n *NTPSuboptionSrvFQDN) FromBytes(p []byte) error { + return n.Labels.FromBytes(p) } // NTPSuboptionSrvAddr is the value of NTP_SUBOPTION_SRV_ADDR according to RFC 5908. @@ -82,53 +89,18 @@ const ( // parseNTPSuboption implements the OptionParser interface. func parseNTPSuboption(code OptionCode, data []byte) (Option, error) { - //var o Options - buf := uio.NewBigEndianBuffer(data) - length := len(data) - data, err := buf.ReadN(length) - if err != nil { - return nil, fmt.Errorf("failed to read %d bytes for suboption: %w", length, err) - } + var o Option switch code { - case NTPSuboptionSrvAddrCode, NTPSuboptionMCAddrCode: - if length != net.IPv6len { - return nil, fmt.Errorf("invalid suboption length, want %d, got %d", net.IPv6len, length) - } - var so Option - switch code { - case NTPSuboptionSrvAddrCode: - sos := NTPSuboptionSrvAddr(data) - so = &sos - case NTPSuboptionMCAddrCode: - som := NTPSuboptionMCAddr(data) - so = &som - } - return so, nil + case NTPSuboptionSrvAddrCode: + o = &NTPSuboptionSrvAddr{} + case NTPSuboptionMCAddrCode: + o = &NTPSuboptionMCAddr{} case NTPSuboptionSrvFQDNCode: - l, err := rfc1035label.FromBytes(data) - if err != nil { - return nil, fmt.Errorf("failed to parse rfc1035 labels: %w", err) - } - // TODO according to rfc3315, this label must not be compressed. - // Need to add support for compression detection to the - // `rfc1035label` package in order to do that. - so := NTPSuboptionSrvFQDN(*l) - return &so, nil + o = &NTPSuboptionSrvFQDN{} default: - gopt := OptionGeneric{OptionCode: code, OptionData: data} - return &gopt, nil - } -} - -// ParseOptNTPServer parses a sequence of bytes into an OptNTPServer object. -func ParseOptNTPServer(data []byte) (*OptNTPServer, error) { - var so Options - if err := so.FromBytesWithParser(data, parseNTPSuboption); err != nil { - return nil, err + o = &OptionGeneric{OptionCode: code} } - return &OptNTPServer{ - Suboptions: so, - }, nil + return o, o.FromBytes(data) } // OptNTPServer is an option NTP server as defined by RFC 5908. @@ -141,13 +113,14 @@ func (op *OptNTPServer) Code() OptionCode { return OptionNTPServer } +// FromBytes parses a sequence of bytes into an OptNTPServer object. +func (op *OptNTPServer) FromBytes(data []byte) error { + return op.Suboptions.FromBytesWithParser(data, parseNTPSuboption) +} + // ToBytes returns the option serialized to bytes. func (op *OptNTPServer) ToBytes() []byte { - buf := uio.NewBigEndianBuffer(nil) - for _, so := range op.Suboptions { - buf.WriteBytes(so.ToBytes()) - } - return buf.Data() + return op.Suboptions.ToBytes() } func (op *OptNTPServer) String() string { diff --git a/dhcpv6/option_ntp_server_test.go b/dhcpv6/option_ntp_server_test.go index 105a753e..59b3c698 100644 --- a/dhcpv6/option_ntp_server_test.go +++ b/dhcpv6/option_ntp_server_test.go @@ -13,25 +13,22 @@ func TestSuboptionSrvAddr(t *testing.T) { ip := net.ParseIP("2a03:2880:fffe:c:face:b00c:0:35") so := NTPSuboptionSrvAddr(ip) assert.Equal(t, NTPSuboptionSrvAddrCode, so.Code()) - expected := append([]byte{0x00, 0x01, 0x00, 0x10}, ip...) - assert.Equal(t, expected, so.ToBytes()) + assert.Equal(t, []byte(ip), so.ToBytes()) } func TestSuboptionMCAddr(t *testing.T) { ip := net.ParseIP("2a03:2880:fffe:c:face:b00c:0:35") so := NTPSuboptionMCAddr(ip) assert.Equal(t, NTPSuboptionMCAddrCode, so.Code()) - expected := append([]byte{0x00, 0x02, 0x00, 0x10}, ip...) - assert.Equal(t, expected, so.ToBytes()) + assert.Equal(t, []byte(ip), so.ToBytes()) } func TestSuboptionSrvFQDN(t *testing.T) { fqdn, err := rfc1035label.FromBytes([]byte("\x03ntp\x07example\x03com")) require.NoError(t, err) - so := NTPSuboptionSrvFQDN(*fqdn) + so := NTPSuboptionSrvFQDN{*fqdn} assert.Equal(t, NTPSuboptionSrvFQDNCode, so.Code()) - expected := append([]byte{0x00, 0x03, 0x00, 0x10}, fqdn.ToBytes()...) - assert.Equal(t, expected, so.ToBytes()) + assert.Equal(t, fqdn.ToBytes(), so.ToBytes()) } func TestSuboptionGeneric(t *testing.T) { @@ -40,7 +37,8 @@ func TestSuboptionGeneric(t *testing.T) { 0x00, 0x04, // length, 4 bytes 0x74, 0x65, 0x73, 0x74, // the ASCII bytes for the string "test" } - o, err := ParseOptNTPServer(data) + var o OptNTPServer + err := o.FromBytes(data) require.NoError(t, err) require.Equal(t, 1, len(o.Suboptions)) assert.IsType(t, &OptionGeneric{}, o.Suboptions[0]) @@ -67,7 +65,8 @@ func TestParseOptNTPServer(t *testing.T) { }...) data = append(data, fqdn.ToBytes()...) - o, err := ParseOptNTPServer(data) + var o OptNTPServer + err = o.FromBytes(data) require.NoError(t, err) require.NotNil(t, o) assert.Equal(t, 2, len(o.Suboptions)) @@ -78,11 +77,11 @@ func TestParseOptNTPServer(t *testing.T) { optFQDN, ok := o.Suboptions[1].(*NTPSuboptionSrvFQDN) require.True(t, ok) - assert.Equal(t, *fqdn, rfc1035label.Labels(*optFQDN)) + assert.Equal(t, *fqdn, optFQDN.Labels) var mo MessageOptions assert.Nil(t, mo.NTPServers()) - mo.Add(o) + mo.Add(&o) // MessageOptions.NTPServers only returns server address values. assert.Equal(t, []net.IP{ip}, mo.NTPServers()) } diff --git a/dhcpv6/option_relaymsg.go b/dhcpv6/option_relaymsg.go index 216f53ba..99627565 100644 --- a/dhcpv6/option_relaymsg.go +++ b/dhcpv6/option_relaymsg.go @@ -33,14 +33,10 @@ func (op *optRelayMsg) LongString(indent int) string { return fmt.Sprintf("%s: %v", op.Code(), op.Msg.LongString(indent)) } -// build an optRelayMsg structure from a sequence of bytes. -// The input data does not include option code and length bytes. -func parseOptRelayMsg(data []byte) (*optRelayMsg, error) { +// FromBytes build an optRelayMsg structure from a sequence of bytes. The input +// data does not include option code and length bytes. +func (op *optRelayMsg) FromBytes(data []byte) error { var err error - var opt optRelayMsg - opt.Msg, err = FromBytes(data) - if err != nil { - return nil, err - } - return &opt, nil + op.Msg, err = FromBytes(data) + return err } diff --git a/dhcpv6/option_relaymsg_test.go b/dhcpv6/option_relaymsg_test.go index e77a6c67..0b8e399e 100644 --- a/dhcpv6/option_relaymsg_test.go +++ b/dhcpv6/option_relaymsg_test.go @@ -9,7 +9,8 @@ import ( ) func TestRelayMsgParseOptRelayMsg(t *testing.T) { - opt, err := parseOptRelayMsg([]byte{ + var opt optRelayMsg + err := opt.FromBytes([]byte{ 1, // MessageTypeSolicit 0xaa, 0xbb, 0xcc, // transaction ID 0, 8, // option: elapsed time @@ -148,7 +149,8 @@ func TestSample(t *testing.T) { } func TestRelayMsgParseOptRelayMsgTooShort(t *testing.T) { - _, err := parseOptRelayMsg([]byte{ + var opt optRelayMsg + err := opt.FromBytes([]byte{ 1, // MessageTypeSolicit 0xaa, 0xbb, 0xcc, // transaction ID 0, 8, // option: elapsed time @@ -158,7 +160,8 @@ func TestRelayMsgParseOptRelayMsgTooShort(t *testing.T) { } func TestRelayMsgString(t *testing.T) { - opt, err := parseOptRelayMsg([]byte{ + var opt optRelayMsg + err := opt.FromBytes([]byte{ 1, // MessageTypeSolicit 0xaa, 0xbb, 0xcc, // transaction ID 0, 8, // option: elapsed time diff --git a/dhcpv6/option_relayport.go b/dhcpv6/option_relayport.go index 48736935..8bf9effc 100644 --- a/dhcpv6/option_relayport.go +++ b/dhcpv6/option_relayport.go @@ -32,11 +32,10 @@ func (op *optRelayPort) String() string { return fmt.Sprintf("%s: %d", op.Code(), op.DownstreamSourcePort) } -// build an optRelayPort structure from a sequence of bytes. -// The input data does not include option code and length bytes. -func parseOptRelayPort(data []byte) (*optRelayPort, error) { - var opt optRelayPort +// FromBytes build an optRelayPort structure from a sequence of bytes. The +// input data does not include option code and length bytes. +func (op *optRelayPort) FromBytes(data []byte) error { buf := uio.NewBigEndianBuffer(data) - opt.DownstreamSourcePort = buf.Read16() - return &opt, buf.FinError() + op.DownstreamSourcePort = buf.Read16() + return buf.FinError() } diff --git a/dhcpv6/option_relayport_test.go b/dhcpv6/option_relayport_test.go index 124af62a..d80e268b 100644 --- a/dhcpv6/option_relayport_test.go +++ b/dhcpv6/option_relayport_test.go @@ -7,9 +7,10 @@ import ( ) func TestParseRelayPort(t *testing.T) { - opt, err := parseOptRelayPort([]byte{0x12, 0x32}) + var opt optRelayPort + err := opt.FromBytes([]byte{0x12, 0x32}) require.NoError(t, err) - require.Equal(t, &optRelayPort{DownstreamSourcePort: 0x1232}, opt) + require.Equal(t, optRelayPort{DownstreamSourcePort: 0x1232}, opt) } func TestRelayPortToBytes(t *testing.T) { diff --git a/dhcpv6/option_remoteid.go b/dhcpv6/option_remoteid.go index 98331493..d07b0a2c 100644 --- a/dhcpv6/option_remoteid.go +++ b/dhcpv6/option_remoteid.go @@ -31,12 +31,11 @@ func (op *OptRemoteID) String() string { ) } -// ParseOptRemoteId builds an OptRemoteId structure from a sequence of bytes. -// The input data does not include option code and length bytes. -func ParseOptRemoteID(data []byte) (*OptRemoteID, error) { - var opt OptRemoteID +// FromBytes builds an OptRemoteId structure from a sequence of bytes. The +// input data does not include option code and length bytes. +func (op *OptRemoteID) FromBytes(data []byte) error { buf := uio.NewBigEndianBuffer(data) - opt.EnterpriseNumber = buf.Read32() - opt.RemoteID = buf.ReadAll() - return &opt, buf.FinError() + op.EnterpriseNumber = buf.Read32() + op.RemoteID = buf.ReadAll() + return buf.FinError() } diff --git a/dhcpv6/option_remoteid_test.go b/dhcpv6/option_remoteid_test.go index 77835de3..d8f848b2 100644 --- a/dhcpv6/option_remoteid_test.go +++ b/dhcpv6/option_remoteid_test.go @@ -11,8 +11,8 @@ func TestOptRemoteID(t *testing.T) { expected := []byte{0xaa, 0xbb, 0xcc, 0xdd} remoteId := []byte("DSLAM01 eth2/1/01/21") expected = append(expected, remoteId...) - opt, err := ParseOptRemoteID(expected) - if err != nil { + var opt OptRemoteID + if err := opt.FromBytes(expected); err != nil { t.Fatal(err) } if en := opt.EnterpriseNumber; en != 0xaabbccdd { @@ -37,7 +37,8 @@ func TestOptRemoteIDToBytes(t *testing.T) { func TestOptRemoteIDParseOptRemoteIDTooShort(t *testing.T) { buf := []byte{0xaa, 0xbb, 0xcc} - _, err := ParseOptRemoteID(buf) + var opt OptRemoteID + err := opt.FromBytes(buf) require.Error(t, err, "A short option should return an error") } @@ -46,7 +47,8 @@ func TestOptRemoteIDString(t *testing.T) { remoteId := []byte("Test1234") buf = append(buf, remoteId...) - opt, err := ParseOptRemoteID(buf) + var opt OptRemoteID + err := opt.FromBytes(buf) require.NoError(t, err) str := opt.String() require.Contains( diff --git a/dhcpv6/option_requestedoption_test.go b/dhcpv6/option_requestedoption_test.go index e5d7bfc8..bb837db3 100644 --- a/dhcpv6/option_requestedoption_test.go +++ b/dhcpv6/option_requestedoption_test.go @@ -1,11 +1,31 @@ package dhcpv6 import ( + "reflect" "testing" "github.com/stretchr/testify/require" ) +func TestParseMessageOptionsWithORO(t *testing.T) { + buf := []byte{ + 0, 6, // ORO option + 0, 2, // length + 0, 3, // IANA Option + 0, 6, // ORO + 0, 2, // length + 0, 4, // IATA + } + + want := OptionCodes{OptionIANA, OptionIATA} + var mo MessageOptions + if err := mo.FromBytes(buf); err != nil { + t.Errorf("FromBytes = %v", err) + } else if got := mo.RequestedOptions(); !reflect.DeepEqual(got, want) { + t.Errorf("ORO = %v, want %v", got, want) + } +} + func TestOptRequestedOption(t *testing.T) { expected := []byte{0, 1, 0, 2} var o optRequestedOption diff --git a/dhcpv6/option_serverid.go b/dhcpv6/option_serverid.go index bdfc2903..4c35cc1c 100644 --- a/dhcpv6/option_serverid.go +++ b/dhcpv6/option_serverid.go @@ -22,12 +22,10 @@ func (op *optServerID) String() string { return fmt.Sprintf("%s: %v", op.Code(), op.DUID) } -// parseOptServerID builds an optServerID structure from a sequence of bytes. -// The input data does not include option code and length bytes. -func parseOptServerID(data []byte) (*optServerID, error) { - sid, err := DUIDFromBytes(data) - if err != nil { - return nil, err - } - return &optServerID{sid}, nil +// FromBytes builds an optServerID structure from a sequence of bytes. The +// input data does not include option code and length bytes. +func (op *optServerID) FromBytes(data []byte) error { + var err error + op.DUID, err = DUIDFromBytes(data) + return err } diff --git a/dhcpv6/option_serverid_test.go b/dhcpv6/option_serverid_test.go index 556f5150..b0250c23 100644 --- a/dhcpv6/option_serverid_test.go +++ b/dhcpv6/option_serverid_test.go @@ -2,19 +2,39 @@ package dhcpv6 import ( "net" + "reflect" "testing" "github.com/insomniacslk/dhcp/iana" "github.com/stretchr/testify/require" ) +func TestParseMessageOptionsWithServerID(t *testing.T) { + buf := []byte{ + 0, 2, // Server ID option + 0, 10, // length + 0, 3, // DUID_LL + 0, 1, // hwtype ethernet + 0, 1, 2, 3, 4, 5, // HW addr + } + + want := &DUIDLL{HWType: iana.HWTypeEthernet, LinkLayerAddr: net.HardwareAddr{0, 1, 2, 3, 4, 5}} + var mo MessageOptions + if err := mo.FromBytes(buf); err != nil { + t.Errorf("FromBytes = %v", err) + } else if got := mo.ServerID(); !reflect.DeepEqual(got, want) { + t.Errorf("ServerID = %v, want %v", got, want) + } +} + func TestParseOptServerID(t *testing.T) { data := []byte{ 0, 3, // DUID_LL 0, 1, // hwtype ethernet 0, 1, 2, 3, 4, 5, // hw addr } - opt, err := parseOptServerID(data) + var opt optServerID + err := opt.FromBytes(data) require.NoError(t, err) want := OptServerID( &DUIDLL{ @@ -22,7 +42,7 @@ func TestParseOptServerID(t *testing.T) { LinkLayerAddr: net.HardwareAddr{0, 1, 2, 3, 4, 5}, }, ) - require.Equal(t, opt, want) + require.Equal(t, &opt, want) } func TestOptServerIdToBytes(t *testing.T) { @@ -46,7 +66,8 @@ func TestOptServerIdDecodeEncode(t *testing.T) { 0, 1, // hwtype ethernet 5, 4, 3, 2, 1, 0, // hw addr } - opt, err := parseOptServerID(data) + var opt optServerID + err := opt.FromBytes(data) require.NoError(t, err) require.Equal(t, data, opt.ToBytes()) } @@ -73,7 +94,8 @@ func TestOptServerIdparseOptServerIDBogusDUID(t *testing.T) { 1, 2, 3, 4, 5, 6, 7, 8, 9, // a UUID should be 18 bytes not 17 10, 11, 12, 13, 14, 15, 16, 17, } - _, err := parseOptServerID(data) + var opt optServerID + err := opt.FromBytes(data) require.Error(t, err, "A truncated OptServerId DUID should return an error") } @@ -81,6 +103,7 @@ func TestOptServerIdparseOptServerIDInvalidTooShort(t *testing.T) { data := []byte{ 0, // truncated: DUIDs are at least 2 bytes } - _, err := parseOptServerID(data) + var opt optServerID + err := opt.FromBytes(data) require.Error(t, err, "A truncated OptServerId should return an error") } diff --git a/dhcpv6/option_statuscode.go b/dhcpv6/option_statuscode.go index 067b9739..c745bc19 100644 --- a/dhcpv6/option_statuscode.go +++ b/dhcpv6/option_statuscode.go @@ -35,12 +35,11 @@ func (op *OptStatusCode) String() string { op.Code(), op.StatusCode, op.StatusCode, op.StatusMessage) } -// ParseOptStatusCode builds an OptStatusCode structure from a sequence of -// bytes. The input data does not include option code and length bytes. -func ParseOptStatusCode(data []byte) (*OptStatusCode, error) { - var opt OptStatusCode +// FromBytes builds an OptStatusCode structure from a sequence of bytes. The +// input data does not include option code and length bytes. +func (op *OptStatusCode) FromBytes(data []byte) error { buf := uio.NewBigEndianBuffer(data) - opt.StatusCode = iana.StatusCode(buf.Read16()) - opt.StatusMessage = string(buf.ReadAll()) - return &opt, buf.FinError() + op.StatusCode = iana.StatusCode(buf.Read16()) + op.StatusMessage = string(buf.ReadAll()) + return buf.FinError() } diff --git a/dhcpv6/option_statuscode_test.go b/dhcpv6/option_statuscode_test.go index 82225e0e..45829099 100644 --- a/dhcpv6/option_statuscode_test.go +++ b/dhcpv6/option_statuscode_test.go @@ -12,7 +12,8 @@ func TestParseOptStatusCode(t *testing.T) { 0, 5, // StatusUseMulticast 'u', 's', 'e', ' ', 'm', 'u', 'l', 't', 'i', 'c', 'a', 's', 't', } - opt, err := ParseOptStatusCode(data) + var opt OptStatusCode + err := opt.FromBytes(data) require.NoError(t, err) require.Equal(t, iana.StatusUseMulticast, opt.StatusCode) require.Equal(t, "use multicast", opt.StatusMessage) @@ -32,7 +33,8 @@ func TestOptStatusCodeToBytes(t *testing.T) { } func TestOptStatusCodeParseOptStatusCodeTooShort(t *testing.T) { - _, err := ParseOptStatusCode([]byte{0}) + var opt OptStatusCode + err := opt.FromBytes([]byte{0}) require.Error(t, err, "ParseOptStatusCode: Expected error on truncated option") } @@ -41,7 +43,8 @@ func TestOptStatusCodeString(t *testing.T) { 0, 5, // StatusUseMulticast 'u', 's', 'e', ' ', 'm', 'u', 'l', 't', 'i', 'c', 'a', 's', 't', } - opt, err := ParseOptStatusCode(data) + var opt OptStatusCode + err := opt.FromBytes(data) require.NoError(t, err) require.Contains( diff --git a/dhcpv6/option_temporaryaddress.go b/dhcpv6/option_temporaryaddress.go index 4a79bac8..8ccbe7b1 100644 --- a/dhcpv6/option_temporaryaddress.go +++ b/dhcpv6/option_temporaryaddress.go @@ -38,15 +38,14 @@ func (op *OptIATA) LongString(indentSpace int) string { return fmt.Sprintf("%s: IAID=%#x Options=%v", op.Code(), op.IaId, op.Options.LongString(indentSpace)) } -// ParseOptIATA builds an OptIATA structure from a sequence of bytes. The -// input data does not include option code and length bytes. -func ParseOptIATA(data []byte) (*OptIATA, error) { - var opt OptIATA +// FromBytes builds an OptIATA structure from a sequence of bytes. The input +// data does not include option code and length bytes. +func (op *OptIATA) FromBytes(data []byte) error { buf := uio.NewBigEndianBuffer(data) - buf.ReadBytes(opt.IaId[:]) + buf.ReadBytes(op.IaId[:]) - if err := opt.Options.FromBytes(buf.ReadAll()); err != nil { - return nil, err + if err := op.Options.FromBytes(buf.ReadAll()); err != nil { + return err } - return &opt, buf.FinError() + return buf.FinError() } diff --git a/dhcpv6/option_temporaryaddress_test.go b/dhcpv6/option_temporaryaddress_test.go index 9b48d5a5..6d94f8fa 100644 --- a/dhcpv6/option_temporaryaddress_test.go +++ b/dhcpv6/option_temporaryaddress_test.go @@ -2,18 +2,49 @@ package dhcpv6 import ( "net" + "reflect" "testing" "time" "github.com/stretchr/testify/require" ) +func TestParseMessageWithIATA(t *testing.T) { + data := []byte{ + 0, 4, // IATA option code + 0, 32, // length + 1, 0, 0, 0, // IAID + // IATA Options + 0, 5, 0, 0x18, 0x24, 1, 0xdb, 0, 0x30, 0x10, 0xc0, 0x8f, 0xfa, 0xce, 0, 0, 0, 0x44, 0, 0, // IP + 0, 0, 0, 2, // PreferredLifetime + 0, 0, 0, 4, // ValidLifetime + } + var got MessageOptions + if err := got.FromBytes(data); err != nil { + t.Errorf("FromBytes = %v", err) + } + + want := &OptIATA{ + IaId: [4]byte{1, 0, 0, 0}, + Options: IdentityOptions{Options: Options{&OptIAAddress{ + IPv6Addr: net.IP{0x24, 1, 0xdb, 0, 0x30, 0x10, 0xc0, 0x8f, 0xfa, 0xce, 0, 0, 0, 0x44, 0, 0}, + PreferredLifetime: 2 * time.Second, + ValidLifetime: 4 * time.Second, + Options: AddressOptions{Options: Options{}}, + }}}, + } + if gotIATA := got.OneIATA(); !reflect.DeepEqual(gotIATA, want) { + t.Errorf("OneIATA = %v, want %v", gotIATA, want) + } +} + func TestOptIATAParseOptIATA(t *testing.T) { data := []byte{ 1, 0, 0, 0, // IAID 0, 5, 0, 0x18, 0x24, 1, 0xdb, 0, 0x30, 0x10, 0xc0, 0x8f, 0xfa, 0xce, 0, 0, 0, 0x44, 0, 0, 0, 0, 0xb2, 0x7a, 0, 0, 0xc0, 0x8a, // options } - opt, err := ParseOptIATA(data) + var opt OptIATA + err := opt.FromBytes(data) require.NoError(t, err) require.Equal(t, OptionIATA, opt.Code()) } @@ -22,7 +53,8 @@ func TestOptIATAParseOptIATAInvalidLength(t *testing.T) { data := []byte{ 1, 0, 0, // truncated IAID } - _, err := ParseOptIATA(data) + var opt OptIATA + err := opt.FromBytes(data) require.Error(t, err) } @@ -31,7 +63,8 @@ func TestOptIATAParseOptIATAInvalidOptions(t *testing.T) { 1, 0, 0, 0, // IAID 0, 5, 0, 0x18, 0x24, 1, 0xdb, 0, 0x30, 0x10, 0xc0, 0x8f, 0xfa, 0xce, 0, 0, 0, 0x44, 0, 0, 0, 0, 0xb2, 0x7a, // truncated options } - _, err := ParseOptIATA(data) + var opt OptIATA + err := opt.FromBytes(data) require.Error(t, err) } @@ -106,7 +139,8 @@ func TestOptIATAString(t *testing.T) { 1, 0, 0, 0, // IAID 0, 5, 0, 0x18, 0x24, 1, 0xdb, 0, 0x30, 0x10, 0xc0, 0x8f, 0xfa, 0xce, 0, 0, 0, 0x44, 0, 0, 0, 0, 0xb2, 0x7a, 0, 0, 0xc0, 0x8a, // options } - opt, err := ParseOptIATA(data) + var opt OptIATA + err := opt.FromBytes(data) require.NoError(t, err) str := opt.String() diff --git a/dhcpv6/option_userclass.go b/dhcpv6/option_userclass.go index 00abcbd7..643fdd13 100644 --- a/dhcpv6/option_userclass.go +++ b/dhcpv6/option_userclass.go @@ -38,17 +38,16 @@ func (op *OptUserClass) String() string { return fmt.Sprintf("%s: [%s]", op.Code(), strings.Join(ucStrings, ", ")) } -// ParseOptUserClass builds an OptUserClass structure from a sequence of -// bytes. The input data does not include option code and length bytes. -func ParseOptUserClass(data []byte) (*OptUserClass, error) { - var opt OptUserClass +// FromBytes builds an OptUserClass structure from a sequence of bytes. The +// input data does not include option code and length bytes. +func (op *OptUserClass) FromBytes(data []byte) error { if len(data) == 0 { - return nil, fmt.Errorf("user class option must not be empty") + return fmt.Errorf("user class option must not be empty") } buf := uio.NewBigEndianBuffer(data) for buf.Has(2) { len := buf.Read16() - opt.UserClasses = append(opt.UserClasses, buf.CopyN(int(len))) + op.UserClasses = append(op.UserClasses, buf.CopyN(int(len))) } - return &opt, buf.FinError() + return buf.FinError() } diff --git a/dhcpv6/option_userclass_test.go b/dhcpv6/option_userclass_test.go index 50bc438b..2612da16 100644 --- a/dhcpv6/option_userclass_test.go +++ b/dhcpv6/option_userclass_test.go @@ -10,7 +10,8 @@ func TestParseOptUserClass(t *testing.T) { expected := []byte{ 0, 9, 'l', 'i', 'n', 'u', 'x', 'b', 'o', 'o', 't', } - opt, err := ParseOptUserClass(expected) + var opt OptUserClass + err := opt.FromBytes(expected) require.NoError(t, err) require.Equal(t, 1, len(opt.UserClasses)) require.Equal(t, []byte("linuxboot"), opt.UserClasses[0]) @@ -21,7 +22,8 @@ func TestParseOptUserClassMultiple(t *testing.T) { 0, 9, 'l', 'i', 'n', 'u', 'x', 'b', 'o', 'o', 't', 0, 4, 't', 'e', 's', 't', } - opt, err := ParseOptUserClass(expected) + var opt OptUserClass + err := opt.FromBytes(expected) require.NoError(t, err) require.Equal(t, len(opt.UserClasses), 2) require.Equal(t, []byte("linuxboot"), opt.UserClasses[0]) @@ -30,7 +32,8 @@ func TestParseOptUserClassMultiple(t *testing.T) { func TestParseOptUserClassNone(t *testing.T) { expected := []byte{} - _, err := ParseOptUserClass(expected) + var opt OptUserClass + err := opt.FromBytes(expected) require.Error(t, err) } @@ -65,14 +68,15 @@ func TestOptUserClassParseOptUserClassTooShort(t *testing.T) { 0, 9, 'l', 'i', 'n', 'u', 'x', 'b', 'o', 'o', 't', 0, 4, 't', 'e', } - _, err := ParseOptUserClass(buf) + var opt OptUserClass + err := opt.FromBytes(buf) require.Error(t, err, "ParseOptUserClass() should error if given truncated user classes") buf = []byte{ 0, 9, 'l', 'i', 'n', 'u', 'x', 'b', 'o', 'o', 't', 0, } - _, err = ParseOptUserClass(buf) + err = opt.FromBytes(buf) require.Error(t, err, "ParseOptUserClass() should error if given a truncated length") } @@ -81,7 +85,8 @@ func TestOptUserClassString(t *testing.T) { 0, 9, 'l', 'i', 'n', 'u', 'x', 'b', 'o', 'o', 't', 0, 4, 't', 'e', 's', 't', } - opt, err := ParseOptUserClass(data) + var opt OptUserClass + err := opt.FromBytes(data) require.NoError(t, err) require.Contains( diff --git a/dhcpv6/option_vendor_opts.go b/dhcpv6/option_vendor_opts.go index 11811c02..8412fd9c 100644 --- a/dhcpv6/option_vendor_opts.go +++ b/dhcpv6/option_vendor_opts.go @@ -38,16 +38,15 @@ func (op *OptVendorOpts) LongString(indent int) string { return fmt.Sprintf("%s: EnterpriseNumber=%v VendorOptions=%s", op.Code(), op.EnterpriseNumber, op.VendorOpts.LongString(indent)) } -// ParseOptVendorOpts builds an OptVendorOpts structure from a sequence of bytes. -// The input data does not include option code and length bytes. -func ParseOptVendorOpts(data []byte) (*OptVendorOpts, error) { - var opt OptVendorOpts +// FromBytes builds an OptVendorOpts structure from a sequence of bytes. The +// input data does not include option code and length bytes. +func (op *OptVendorOpts) FromBytes(data []byte) error { buf := uio.NewBigEndianBuffer(data) - opt.EnterpriseNumber = buf.Read32() - if err := opt.VendorOpts.FromBytesWithParser(buf.ReadAll(), vendParseOption); err != nil { - return nil, err + op.EnterpriseNumber = buf.Read32() + if err := op.VendorOpts.FromBytesWithParser(buf.ReadAll(), vendParseOption); err != nil { + return err } - return &opt, buf.FinError() + return buf.FinError() } // vendParseOption builds a GenericOption from a slice of bytes diff --git a/dhcpv6/option_vendor_opts_test.go b/dhcpv6/option_vendor_opts_test.go index f6c2b2a6..5caba9c7 100644 --- a/dhcpv6/option_vendor_opts_test.go +++ b/dhcpv6/option_vendor_opts_test.go @@ -17,13 +17,16 @@ func TestOptVendorOpts(t *testing.T) { expectedOpts := OptVendorOpts{} var vendorOpts []Option expectedOpts.VendorOpts = append(vendorOpts, &OptionGeneric{OptionCode: 1, OptionData: optData}) - opt, err := ParseOptVendorOpts(expected) + + var opt OptVendorOpts + err := opt.FromBytes(expected) require.NoError(t, err) require.Equal(t, uint32(0xaabbccdd), opt.EnterpriseNumber) require.Equal(t, expectedOpts.VendorOpts, opt.VendorOpts) shortData := make([]byte, 1) - _, err = ParseOptVendorOpts(shortData) + var opt2 OptVendorOpts + err = opt2.FromBytes(shortData) require.Error(t, err) } diff --git a/dhcpv6/option_vendorclass.go b/dhcpv6/option_vendorclass.go index 33297dc6..954dbd05 100644 --- a/dhcpv6/option_vendorclass.go +++ b/dhcpv6/option_vendorclass.go @@ -39,18 +39,18 @@ func (op *OptVendorClass) String() string { return fmt.Sprintf("%s: {EnterpriseNumber=%d Data=[%s]}", op.Code(), op.EnterpriseNumber, strings.Join(vcStrings, ", ")) } -// ParseOptVendorClass builds an OptVendorClass structure from a sequence of -// bytes. The input data does not include option code and length bytes. -func ParseOptVendorClass(data []byte) (*OptVendorClass, error) { - var opt OptVendorClass +// FromBytes builds an OptVendorClass structure from a sequence of bytes. The +// input data does not include option code and length bytes. +func (op *OptVendorClass) FromBytes(data []byte) error { buf := uio.NewBigEndianBuffer(data) - opt.EnterpriseNumber = buf.Read32() + *op = OptVendorClass{} + op.EnterpriseNumber = buf.Read32() for buf.Has(2) { len := buf.Read16() - opt.Data = append(opt.Data, buf.CopyN(int(len))) + op.Data = append(op.Data, buf.CopyN(int(len))) } - if len(opt.Data) < 1 { - return nil, errors.New("ParseOptVendorClass: at least one vendor class data is required") + if len(op.Data) < 1 { + return errors.New("ParseOptVendorClass: at least one vendor class data is required") } - return &opt, buf.FinError() + return buf.FinError() } diff --git a/dhcpv6/option_vendorclass_test.go b/dhcpv6/option_vendorclass_test.go index 819f5f6c..c691176d 100644 --- a/dhcpv6/option_vendorclass_test.go +++ b/dhcpv6/option_vendorclass_test.go @@ -12,7 +12,8 @@ func TestParseOptVendorClass(t *testing.T) { 0, 10, 'H', 'T', 'T', 'P', 'C', 'l', 'i', 'e', 'n', 't', 0, 4, 't', 'e', 's', 't', } - opt, err := ParseOptVendorClass(data) + var opt OptVendorClass + err := opt.FromBytes(data) require.NoError(t, err) require.Equal(t, OptionVendorClass, opt.Code()) require.Equal(t, 2, len(opt.Data)) @@ -42,13 +43,14 @@ func TestOptVendorClassParseOptVendorClassMalformed(t *testing.T) { buf := []byte{ 0xaa, 0xbb, // truncated EnterpriseNumber } - _, err := ParseOptVendorClass(buf) + var opt OptVendorClass + err := opt.FromBytes(buf) require.Error(t, err, "ParseOptVendorClass() should error if given truncated EnterpriseNumber") buf = []byte{ 0xaa, 0xbb, 0xcc, 0xdd, // EnterpriseNumber } - _, err = ParseOptVendorClass(buf) + err = opt.FromBytes(buf) require.Error(t, err, "ParseOptVendorClass() should error if given no vendor classes") buf = []byte{ @@ -56,7 +58,7 @@ func TestOptVendorClassParseOptVendorClassMalformed(t *testing.T) { 0, 9, 'l', 'i', 'n', 'u', 'x', 'b', 'o', 'o', 't', 0, 4, 't', 'e', } - _, err = ParseOptVendorClass(buf) + err = opt.FromBytes(buf) require.Error(t, err, "ParseOptVendorClass() should error if given truncated vendor classes") buf = []byte{ @@ -64,7 +66,7 @@ func TestOptVendorClassParseOptVendorClassMalformed(t *testing.T) { 0, 9, 'l', 'i', 'n', 'u', 'x', 'b', 'o', 'o', 't', 0, } - _, err = ParseOptVendorClass(buf) + err = opt.FromBytes(buf) require.Error(t, err, "ParseOptVendorClass() should error if given a truncated length") } @@ -74,7 +76,8 @@ func TestOptVendorClassString(t *testing.T) { 0, 9, 'l', 'i', 'n', 'u', 'x', 'b', 'o', 'o', 't', 0, 4, 't', 'e', 's', 't', } - opt, err := ParseOptVendorClass(data) + var opt OptVendorClass + err := opt.FromBytes(data) require.NoError(t, err) str := opt.String() diff --git a/dhcpv6/options.go b/dhcpv6/options.go index 308fc2c0..6a55082d 100644 --- a/dhcpv6/options.go +++ b/dhcpv6/options.go @@ -12,6 +12,7 @@ type Option interface { Code() OptionCode ToBytes() []byte String() string + FromBytes([]byte) error } type OptionGeneric struct { @@ -34,90 +35,87 @@ func (og *OptionGeneric) String() string { return fmt.Sprintf("%s: %v", og.OptionCode, og.OptionData) } +// FromBytes resets OptionData to p. +func (og *OptionGeneric) FromBytes(p []byte) error { + og.OptionData = append([]byte(nil), p...) + return nil +} + // ParseOption parses data according to the given code. +// +// Parse a sequence of bytes as a single DHCPv6 option. +// Returns the option structure, or an error if any. func ParseOption(code OptionCode, optData []byte) (Option, error) { - // Parse a sequence of bytes as a single DHCPv6 option. - // Returns the option structure, or an error if any. - var ( - err error - opt Option - ) + var opt Option switch code { case OptionClientID: - opt, err = parseOptClientID(optData) + opt = &optClientID{} case OptionServerID: - opt, err = parseOptServerID(optData) + opt = &optServerID{} case OptionIANA: - opt, err = ParseOptIANA(optData) + opt = &OptIANA{} case OptionIATA: - opt, err = ParseOptIATA(optData) + opt = &OptIATA{} case OptionIAAddr: - opt, err = ParseOptIAAddress(optData) + opt = &OptIAAddress{} case OptionORO: - var o optRequestedOption - err = o.FromBytes(optData) - opt = &o + opt = &optRequestedOption{} case OptionElapsedTime: - opt, err = parseOptElapsedTime(optData) + opt = &optElapsedTime{} case OptionRelayMsg: - opt, err = parseOptRelayMsg(optData) + opt = &optRelayMsg{} case OptionStatusCode: - opt, err = ParseOptStatusCode(optData) + opt = &OptStatusCode{} case OptionUserClass: - opt, err = ParseOptUserClass(optData) + opt = &OptUserClass{} case OptionVendorClass: - opt, err = ParseOptVendorClass(optData) + opt = &OptVendorClass{} case OptionVendorOpts: - opt, err = ParseOptVendorOpts(optData) + opt = &OptVendorOpts{} case OptionInterfaceID: - opt, err = parseOptInterfaceID(optData) + opt = &optInterfaceID{} case OptionDNSRecursiveNameServer: - opt, err = parseOptDNS(optData) + opt = &optDNS{} case OptionDomainSearchList: - opt, err = parseOptDomainSearchList(optData) + opt = &optDomainSearchList{} case OptionIAPD: - opt, err = ParseOptIAPD(optData) + opt = &OptIAPD{} case OptionIAPrefix: - opt, err = ParseOptIAPrefix(optData) + opt = &OptIAPrefix{} case OptionInformationRefreshTime: - opt, err = parseOptInformationRefreshTime(optData) + opt = &optInformationRefreshTime{} case OptionRemoteID: - opt, err = ParseOptRemoteID(optData) + opt = &OptRemoteID{} case OptionFQDN: - opt, err = ParseOptFQDN(optData) + opt = &OptFQDN{} case OptionNTPServer: - opt, err = ParseOptNTPServer(optData) + opt = &OptNTPServer{} case OptionBootfileURL: - opt, err = parseOptBootFileURL(optData) + opt = &optBootFileURL{} case OptionBootfileParam: - opt, err = parseOptBootFileParam(optData) + opt = &optBootFileParam{} case OptionClientArchType: - opt, err = parseOptClientArchType(optData) + opt = &optClientArchType{} case OptionNII: - var o OptNetworkInterfaceID - err = o.FromBytes(optData) - opt = &o + opt = &OptNetworkInterfaceID{} case OptionClientLinkLayerAddr: - opt, err = parseOptClientLinkLayerAddress(optData) + opt = &optClientLinkLayerAddress{} case OptionDHCPv4Msg: - opt, err = ParseOptDHCPv4Msg(optData) + opt = &OptDHCPv4Msg{} case OptionDHCP4oDHCP6Server: - opt, err = ParseOptDHCP4oDHCP6Server(optData) + opt = &OptDHCP4oDHCP6Server{} case Option4RD: - opt, err = ParseOpt4RD(optData) + opt = &Opt4RD{} case Option4RDMapRule: - opt, err = ParseOpt4RDMapRule(optData) + opt = &Opt4RDMapRule{} case Option4RDNonMapRule: - opt, err = ParseOpt4RDNonMapRule(optData) + opt = &Opt4RDNonMapRule{} case OptionRelayPort: - opt, err = parseOptRelayPort(optData) + opt = &optRelayPort{} default: - opt = &OptionGeneric{OptionCode: code, OptionData: optData} - } - if err != nil { - return nil, err + opt = &OptionGeneric{OptionCode: code} } - return opt, nil + return opt, opt.FromBytes(optData) } type longStringer interface {