diff options
72 files changed, 295 insertions, 319 deletions
diff --git a/dhcpv4/bsdp/boot_image.go b/dhcpv4/bsdp/boot_image.go index fa9b1a6..954dcb6 100644 --- a/dhcpv4/bsdp/boot_image.go +++ b/dhcpv4/bsdp/boot_image.go @@ -1,7 +1,6 @@ package bsdp import ( - "encoding/binary" "fmt" "github.com/u-root/u-root/pkg/uio" @@ -36,15 +35,16 @@ type BootImageID struct { Index uint16 } -// ToBytes serializes a BootImageID to network-order bytes. -func (b BootImageID) ToBytes() []byte { - bytes := make([]byte, 4) +// Marshal writes the binary representation to buf. +func (b BootImageID) Marshal(buf *uio.Lexer) { + var byte0 byte if b.IsInstall { - bytes[0] |= 0x80 + byte0 |= 0x80 } - bytes[0] |= byte(b.ImageType) - binary.BigEndian.PutUint16(bytes[2:], b.Index) - return bytes + byte0 |= byte(b.ImageType) + buf.Write8(byte0) + buf.Write8(byte(0)) + buf.Write16(b.Index) } // String converts a BootImageID to a human-readable representation. @@ -78,12 +78,11 @@ type BootImage struct { Name string } -// ToBytes converts a BootImage to a slice of bytes. -func (b *BootImage) ToBytes() []byte { - bytes := b.ID.ToBytes() - bytes = append(bytes, byte(len(b.Name))) - bytes = append(bytes, []byte(b.Name)...) - return bytes +// Marshal write a BootImage to buf. +func (b BootImage) Marshal(buf *uio.Lexer) { + b.ID.Marshal(buf) + buf.Write8(uint8(len(b.Name))) + buf.WriteBytes([]byte(b.Name)) } // String converts a BootImage to a human-readable representation. diff --git a/dhcpv4/bsdp/boot_image_test.go b/dhcpv4/bsdp/boot_image_test.go index d8e3aeb..996b8a0 100644 --- a/dhcpv4/bsdp/boot_image_test.go +++ b/dhcpv4/bsdp/boot_image_test.go @@ -13,12 +13,12 @@ func TestBootImageIDToBytes(t *testing.T) { ImageType: BootImageTypeMacOSX, Index: 0x1000, } - actual := b.ToBytes() + actual := uio.ToBigEndian(b) expected := []byte{0x81, 0, 0x10, 0} require.Equal(t, expected, actual) b.IsInstall = false - actual = b.ToBytes() + actual = uio.ToBigEndian(b) expected = []byte{0x01, 0, 0x10, 0} require.Equal(t, expected, actual) } @@ -30,7 +30,7 @@ func TestBootImageIDFromBytes(t *testing.T) { Index: 0x1000, } var newBootImage BootImageID - require.NoError(t, uio.FromBigEndian(&newBootImage, b.ToBytes())) + require.NoError(t, uio.FromBigEndian(&newBootImage, uio.ToBigEndian(b))) require.Equal(t, b, newBootImage) b = BootImageID{ @@ -38,7 +38,7 @@ func TestBootImageIDFromBytes(t *testing.T) { ImageType: BootImageTypeMacOSX, Index: 0x1011, } - require.NoError(t, uio.FromBigEndian(&newBootImage, b.ToBytes())) + require.NoError(t, uio.FromBigEndian(&newBootImage, uio.ToBigEndian(b))) require.Equal(t, b, newBootImage) } @@ -70,7 +70,7 @@ func TestBootImageToBytes(t *testing.T) { 6, // len(Name) 98, 115, 100, 112, 45, 49, // byte-encoding of Name } - actual := b.ToBytes() + actual := uio.ToBigEndian(b) require.Equal(t, expected, actual) b = BootImage{ @@ -86,7 +86,7 @@ func TestBootImageToBytes(t *testing.T) { 7, // len(Name) 98, 115, 100, 112, 45, 50, 49, // byte-encoding of Name } - actual = b.ToBytes() + actual = uio.ToBigEndian(b) require.Equal(t, expected, actual) } diff --git a/dhcpv4/bsdp/bsdp.go b/dhcpv4/bsdp/bsdp.go index 6bc0cfd..12f3c06 100644 --- a/dhcpv4/bsdp/bsdp.go +++ b/dhcpv4/bsdp/bsdp.go @@ -36,7 +36,7 @@ func ParseBootImageListFromAck(ack dhcpv4.DHCPv4) ([]BootImage, error) { if opt == nil { return nil, errors.New("ParseBootImageListFromAck: could not find vendor-specific option") } - vendorOpt, err := ParseOptVendorSpecificInformation(opt.ToBytes()[2:]) + vendorOpt, err := ParseOptVendorSpecificInformation(opt.ToBytes()) if err != nil { return nil, err } @@ -60,7 +60,7 @@ func MessageTypeFromPacket(packet *dhcpv4.DHCPv4) *MessageType { err error ) for _, opt := range packet.GetOption(dhcpv4.OptionVendorSpecificInformation) { - if vendorOpts, err = ParseOptVendorSpecificInformation(opt.ToBytes()[2:]); err == nil { + if vendorOpts, err = ParseOptVendorSpecificInformation(opt.ToBytes()); err == nil { if o := vendorOpts.GetOneOption(OptionMessageType); o != nil { if optMessageType, ok := o.(*OptMessageType); ok { return &optMessageType.Type diff --git a/dhcpv4/bsdp/bsdp_option_boot_image_list.go b/dhcpv4/bsdp/bsdp_option_boot_image_list.go index d018655..ae2e59e 100644 --- a/dhcpv4/bsdp/bsdp_option_boot_image_list.go +++ b/dhcpv4/bsdp/bsdp_option_boot_image_list.go @@ -37,12 +37,11 @@ func (o *OptBootImageList) Code() dhcpv4.OptionCode { // ToBytes returns a serialized stream of bytes for this option. func (o *OptBootImageList) ToBytes() []byte { - bs := make([]byte, 0, 2+o.Length()) - bs = append(bs, []byte{byte(o.Code()), byte(o.Length())}...) + buf := uio.NewBigEndianBuffer(nil) for _, image := range o.Images { - bs = append(bs, image.ToBytes()...) + image.Marshal(buf) } - return bs + return buf.Data() } // String returns a human-readable string for this option. diff --git a/dhcpv4/bsdp/bsdp_option_boot_image_list_test.go b/dhcpv4/bsdp/bsdp_option_boot_image_list_test.go index 0819d64..8e9c27f 100644 --- a/dhcpv4/bsdp/bsdp_option_boot_image_list_test.go +++ b/dhcpv4/bsdp/bsdp_option_boot_image_list_test.go @@ -29,8 +29,6 @@ func TestOptBootImageListInterfaceMethods(t *testing.T) { require.Equal(t, OptionBootImageList, o.Code(), "Code") require.Equal(t, 22, o.Length(), "Length") expectedBytes := []byte{ - 9, // code - 22, // length // boot image 1 0x1, 0x0, 0x03, 0xe9, // ID 6, // name length diff --git a/dhcpv4/bsdp/bsdp_option_default_boot_image_id.go b/dhcpv4/bsdp/bsdp_option_default_boot_image_id.go index 52f7780..d75c883 100644 --- a/dhcpv4/bsdp/bsdp_option_default_boot_image_id.go +++ b/dhcpv4/bsdp/bsdp_option_default_boot_image_id.go @@ -33,8 +33,7 @@ func (o *OptDefaultBootImageID) Code() dhcpv4.OptionCode { // ToBytes returns a serialized stream of bytes for this option. func (o *OptDefaultBootImageID) ToBytes() []byte { - serializedID := o.ID.ToBytes() - return append([]byte{byte(o.Code()), byte(len(serializedID))}, serializedID...) + return uio.ToBigEndian(o.ID) } // String returns a human-readable string for this option. diff --git a/dhcpv4/bsdp/bsdp_option_default_boot_image_id_test.go b/dhcpv4/bsdp/bsdp_option_default_boot_image_id_test.go index ad29c30..eb63457 100644 --- a/dhcpv4/bsdp/bsdp_option_default_boot_image_id_test.go +++ b/dhcpv4/bsdp/bsdp_option_default_boot_image_id_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/stretchr/testify/require" + "github.com/u-root/u-root/pkg/uio" ) func TestOptDefaultBootImageIDInterfaceMethods(t *testing.T) { @@ -11,13 +12,12 @@ func TestOptDefaultBootImageIDInterfaceMethods(t *testing.T) { o := OptDefaultBootImageID{b} require.Equal(t, OptionDefaultBootImageID, o.Code(), "Code") require.Equal(t, 4, o.Length(), "Length") - expectedBytes := []byte{byte(OptionDefaultBootImageID), 4} - require.Equal(t, append(expectedBytes, b.ToBytes()...), o.ToBytes(), "ToBytes") + require.Equal(t, uio.ToBigEndian(b), o.ToBytes(), "ToBytes") } func TestParseOptDefaultBootImageID(t *testing.T) { b := BootImageID{IsInstall: true, ImageType: BootImageTypeMacOSX, Index: 1001} - o, err := ParseOptDefaultBootImageID(b.ToBytes()) + o, err := ParseOptDefaultBootImageID(uio.ToBigEndian(b)) require.NoError(t, err) require.Equal(t, &OptDefaultBootImageID{b}, o) diff --git a/dhcpv4/bsdp/bsdp_option_generic.go b/dhcpv4/bsdp/bsdp_option_generic.go index 6702e9c..cfcffb8 100644 --- a/dhcpv4/bsdp/bsdp_option_generic.go +++ b/dhcpv4/bsdp/bsdp_option_generic.go @@ -27,7 +27,7 @@ func (o OptGeneric) Code() dhcpv4.OptionCode { // ToBytes returns a serialized generic option as a slice of bytes. func (o OptGeneric) ToBytes() []byte { - return append([]byte{byte(o.Code()), byte(o.Length())}, o.Data...) + return o.Data } // String returns a human-readable representation of a generic option. diff --git a/dhcpv4/bsdp/bsdp_option_generic_test.go b/dhcpv4/bsdp/bsdp_option_generic_test.go index eae77e1..131af9f 100644 --- a/dhcpv4/bsdp/bsdp_option_generic_test.go +++ b/dhcpv4/bsdp/bsdp_option_generic_test.go @@ -36,7 +36,7 @@ func TestOptGenericToBytes(t *testing.T) { Data: []byte{192, 168, 0, 1}, } serialized := o.ToBytes() - expected := []byte{3, 4, 192, 168, 0, 1} + expected := []byte{192, 168, 0, 1} require.Equal(t, expected, serialized) } diff --git a/dhcpv4/bsdp/bsdp_option_machine_name.go b/dhcpv4/bsdp/bsdp_option_machine_name.go index cffba2e..ef38921 100644 --- a/dhcpv4/bsdp/bsdp_option_machine_name.go +++ b/dhcpv4/bsdp/bsdp_option_machine_name.go @@ -25,7 +25,7 @@ func (o *OptMachineName) Code() dhcpv4.OptionCode { // ToBytes returns a serialized stream of bytes for this option. func (o *OptMachineName) ToBytes() []byte { - return append([]byte{byte(o.Code()), byte(o.Length())}, []byte(o.Name)...) + return []byte(o.Name) } // String returns a human-readable string for this option. diff --git a/dhcpv4/bsdp/bsdp_option_machine_name_test.go b/dhcpv4/bsdp/bsdp_option_machine_name_test.go index 712bc49..c06ae7b 100644 --- a/dhcpv4/bsdp/bsdp_option_machine_name_test.go +++ b/dhcpv4/bsdp/bsdp_option_machine_name_test.go @@ -10,7 +10,7 @@ func TestOptMachineNameInterfaceMethods(t *testing.T) { o := OptMachineName{"somebox"} require.Equal(t, OptionMachineName, o.Code(), "Code") require.Equal(t, 7, o.Length(), "Length") - expectedBytes := []byte{130, 7, 's', 'o', 'm', 'e', 'b', 'o', 'x'} + expectedBytes := []byte{'s', 'o', 'm', 'e', 'b', 'o', 'x'} require.Equal(t, expectedBytes, o.ToBytes(), "ToBytes") } diff --git a/dhcpv4/bsdp/bsdp_option_message_type.go b/dhcpv4/bsdp/bsdp_option_message_type.go index 8c3c3d4..c427dd1 100644 --- a/dhcpv4/bsdp/bsdp_option_message_type.go +++ b/dhcpv4/bsdp/bsdp_option_message_type.go @@ -53,7 +53,7 @@ func (o *OptMessageType) Code() dhcpv4.OptionCode { // ToBytes returns a serialized stream of bytes for this option. func (o *OptMessageType) ToBytes() []byte { - return []byte{byte(o.Code()), 1, byte(o.Type)} + return []byte{byte(o.Type)} } // String returns a human-readable string for this option. diff --git a/dhcpv4/bsdp/bsdp_option_message_type_test.go b/dhcpv4/bsdp/bsdp_option_message_type_test.go index 41652be..5dcda6c 100644 --- a/dhcpv4/bsdp/bsdp_option_message_type_test.go +++ b/dhcpv4/bsdp/bsdp_option_message_type_test.go @@ -10,7 +10,7 @@ func TestOptMessageTypeInterfaceMethods(t *testing.T) { o := OptMessageType{MessageTypeList} require.Equal(t, OptionMessageType, o.Code(), "Code") require.Equal(t, 1, o.Length(), "Length") - require.Equal(t, []byte{1, 1, 1}, o.ToBytes(), "ToBytes") + require.Equal(t, []byte{1}, o.ToBytes(), "ToBytes") } func TestParseOptMessageType(t *testing.T) { diff --git a/dhcpv4/bsdp/bsdp_option_reply_port.go b/dhcpv4/bsdp/bsdp_option_reply_port.go index da5e9c4..39277c3 100644 --- a/dhcpv4/bsdp/bsdp_option_reply_port.go +++ b/dhcpv4/bsdp/bsdp_option_reply_port.go @@ -1,7 +1,6 @@ package bsdp import ( - "encoding/binary" "fmt" "github.com/insomniacslk/dhcp/dhcpv4" @@ -32,9 +31,9 @@ func (o *OptReplyPort) Code() dhcpv4.OptionCode { // ToBytes returns a serialized stream of bytes for this option. func (o *OptReplyPort) ToBytes() []byte { - serialized := make([]byte, 2) - binary.BigEndian.PutUint16(serialized, o.Port) - return append([]byte{byte(o.Code()), 2}, serialized...) + buf := uio.NewBigEndianBuffer(nil) + buf.Write16(o.Port) + return buf.Data() } // String returns a human-readable string for this option. diff --git a/dhcpv4/bsdp/bsdp_option_reply_port_test.go b/dhcpv4/bsdp/bsdp_option_reply_port_test.go index 719bbc8..413c977 100644 --- a/dhcpv4/bsdp/bsdp_option_reply_port_test.go +++ b/dhcpv4/bsdp/bsdp_option_reply_port_test.go @@ -10,7 +10,7 @@ func TestOptReplyPortInterfaceMethods(t *testing.T) { o := OptReplyPort{1234} require.Equal(t, OptionReplyPort, o.Code(), "Code") require.Equal(t, 2, o.Length(), "Length") - require.Equal(t, []byte{byte(OptionReplyPort), 2, 4, 210}, o.ToBytes(), "ToBytes") + require.Equal(t, []byte{4, 210}, o.ToBytes(), "ToBytes") } func TestParseOptReplyPort(t *testing.T) { diff --git a/dhcpv4/bsdp/bsdp_option_selected_boot_image_id.go b/dhcpv4/bsdp/bsdp_option_selected_boot_image_id.go index 52b6eab..9079346 100644 --- a/dhcpv4/bsdp/bsdp_option_selected_boot_image_id.go +++ b/dhcpv4/bsdp/bsdp_option_selected_boot_image_id.go @@ -33,8 +33,7 @@ func (o *OptSelectedBootImageID) Code() dhcpv4.OptionCode { // ToBytes returns a serialized stream of bytes for this option. func (o *OptSelectedBootImageID) ToBytes() []byte { - serializedID := o.ID.ToBytes() - return append([]byte{byte(o.Code()), byte(len(serializedID))}, serializedID...) + return uio.ToBigEndian(o.ID) } // String returns a human-readable string for this option. diff --git a/dhcpv4/bsdp/bsdp_option_selected_boot_image_id_test.go b/dhcpv4/bsdp/bsdp_option_selected_boot_image_id_test.go index a55fd9f..d9bf2df 100644 --- a/dhcpv4/bsdp/bsdp_option_selected_boot_image_id_test.go +++ b/dhcpv4/bsdp/bsdp_option_selected_boot_image_id_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/stretchr/testify/require" + "github.com/u-root/u-root/pkg/uio" ) func TestOptSelectedBootImageIDInterfaceMethods(t *testing.T) { @@ -11,19 +12,17 @@ func TestOptSelectedBootImageIDInterfaceMethods(t *testing.T) { o := OptSelectedBootImageID{b} require.Equal(t, OptionSelectedBootImageID, o.Code(), "Code") require.Equal(t, 4, o.Length(), "Length") - expectedBytes := []byte{byte(OptionSelectedBootImageID), 4} - require.Equal(t, append(expectedBytes, b.ToBytes()...), o.ToBytes(), "ToBytes") + require.Equal(t, uio.ToBigEndian(b), o.ToBytes(), "ToBytes") } func TestParseOptSelectedBootImageID(t *testing.T) { b := BootImageID{IsInstall: true, ImageType: BootImageTypeMacOSX, Index: 1001} - data := b.ToBytes() - o, err := ParseOptSelectedBootImageID(data) + o, err := ParseOptSelectedBootImageID(uio.ToBigEndian(b)) require.NoError(t, err) require.Equal(t, &OptSelectedBootImageID{b}, o) // Short byte stream - data = []byte{} + data := []byte{} _, err = ParseOptSelectedBootImageID(data) require.Error(t, err, "should get error from short byte stream") diff --git a/dhcpv4/bsdp/bsdp_option_server_identifier.go b/dhcpv4/bsdp/bsdp_option_server_identifier.go index 26ec37a..704d4f2 100644 --- a/dhcpv4/bsdp/bsdp_option_server_identifier.go +++ b/dhcpv4/bsdp/bsdp_option_server_identifier.go @@ -27,8 +27,7 @@ func (o *OptServerIdentifier) Code() dhcpv4.OptionCode { // ToBytes returns a serialized stream of bytes for this option. func (o *OptServerIdentifier) ToBytes() []byte { - ret := []byte{byte(o.Code()), byte(o.Length())} - return append(ret, o.ServerID.To4()...) + return o.ServerID.To4() } // String returns a human-readable string. diff --git a/dhcpv4/bsdp/bsdp_option_server_identifier_test.go b/dhcpv4/bsdp/bsdp_option_server_identifier_test.go index d832c40..8c4555e 100644 --- a/dhcpv4/bsdp/bsdp_option_server_identifier_test.go +++ b/dhcpv4/bsdp/bsdp_option_server_identifier_test.go @@ -11,7 +11,7 @@ func TestOptServerIdentifierInterfaceMethods(t *testing.T) { ip := net.IP{192, 168, 0, 1} o := OptServerIdentifier{ServerID: ip} require.Equal(t, OptionServerIdentifier, o.Code(), "Code") - expectedBytes := []byte{3, 4, 192, 168, 0, 1} + expectedBytes := []byte{192, 168, 0, 1} require.Equal(t, expectedBytes, o.ToBytes(), "ToBytes") require.Equal(t, 4, o.Length(), "Length") require.Equal(t, "BSDP Server Identifier -> 192.168.0.1", o.String(), "String") diff --git a/dhcpv4/bsdp/bsdp_option_server_priority.go b/dhcpv4/bsdp/bsdp_option_server_priority.go index 66bfa44..b362f01 100644 --- a/dhcpv4/bsdp/bsdp_option_server_priority.go +++ b/dhcpv4/bsdp/bsdp_option_server_priority.go @@ -1,7 +1,6 @@ package bsdp import ( - "encoding/binary" "fmt" "github.com/insomniacslk/dhcp/dhcpv4" @@ -27,9 +26,9 @@ func (o *OptServerPriority) Code() dhcpv4.OptionCode { // ToBytes returns a serialized stream of bytes for this option. func (o *OptServerPriority) ToBytes() []byte { - serialized := make([]byte, 2) - binary.BigEndian.PutUint16(serialized, uint16(o.Priority)) - return append([]byte{byte(o.Code()), byte(o.Length())}, serialized...) + buf := uio.NewBigEndianBuffer(nil) + buf.Write16(o.Priority) + return buf.Data() } // String returns a human-readable string. diff --git a/dhcpv4/bsdp/bsdp_option_server_priority_test.go b/dhcpv4/bsdp/bsdp_option_server_priority_test.go index cbcef1d..91a21b6 100644 --- a/dhcpv4/bsdp/bsdp_option_server_priority_test.go +++ b/dhcpv4/bsdp/bsdp_option_server_priority_test.go @@ -9,7 +9,7 @@ import ( func TestOptServerPriorityInterfaceMethods(t *testing.T) { o := OptServerPriority{Priority: 100} require.Equal(t, OptionServerPriority, o.Code(), "Code") - require.Equal(t, []byte{4, 2, 0, 100}, o.ToBytes(), "ToBytes") + require.Equal(t, []byte{0, 100}, o.ToBytes(), "ToBytes") require.Equal(t, 2, o.Length(), "Length") require.Equal(t, "BSDP Server Priority -> 100", o.String(), "String") } diff --git a/dhcpv4/bsdp/bsdp_option_version.go b/dhcpv4/bsdp/bsdp_option_version.go index 38158d7..9314cbe 100644 --- a/dhcpv4/bsdp/bsdp_option_version.go +++ b/dhcpv4/bsdp/bsdp_option_version.go @@ -34,7 +34,7 @@ func (o *OptVersion) Code() dhcpv4.OptionCode { // ToBytes returns a serialized stream of bytes for this option. func (o *OptVersion) ToBytes() []byte { - return append([]byte{byte(o.Code()), 2}, o.Version...) + return o.Version } // String returns a human-readable string for this option. diff --git a/dhcpv4/bsdp/bsdp_option_version_test.go b/dhcpv4/bsdp/bsdp_option_version_test.go index d28f243..a74ea0e 100644 --- a/dhcpv4/bsdp/bsdp_option_version_test.go +++ b/dhcpv4/bsdp/bsdp_option_version_test.go @@ -10,7 +10,7 @@ func TestOptVersionInterfaceMethods(t *testing.T) { o := OptVersion{Version1_1} require.Equal(t, OptionVersion, o.Code(), "Code") require.Equal(t, 2, o.Length(), "Length") - require.Equal(t, []byte{2, 2, 1, 1}, o.ToBytes(), "ToBytes") + require.Equal(t, []byte{1, 1}, o.ToBytes(), "ToBytes") } func TestParseOptVersion(t *testing.T) { diff --git a/dhcpv4/bsdp/option_vendor_specific_information.go b/dhcpv4/bsdp/option_vendor_specific_information.go index 4219e40..2207059 100644 --- a/dhcpv4/bsdp/option_vendor_specific_information.go +++ b/dhcpv4/bsdp/option_vendor_specific_information.go @@ -4,6 +4,7 @@ import ( "strings" "github.com/insomniacslk/dhcp/dhcpv4" + "github.com/u-root/u-root/pkg/uio" ) // OptVendorSpecificInformation encapsulates the BSDP-specific options used for @@ -64,13 +65,7 @@ func (o *OptVendorSpecificInformation) Code() dhcpv4.OptionCode { // ToBytes returns a serialized stream of bytes for this option. func (o *OptVendorSpecificInformation) ToBytes() []byte { - bs := []byte{byte(o.Code()), byte(o.Length())} - - // Append data section - for _, opt := range o.Options { - bs = append(bs, opt.ToBytes()...) - } - return bs + return uio.ToBigEndian(o.Options) } // String returns a human-readable string for this option. diff --git a/dhcpv4/bsdp/option_vendor_specific_information_test.go b/dhcpv4/bsdp/option_vendor_specific_information_test.go index 689797b..e62589e 100644 --- a/dhcpv4/bsdp/option_vendor_specific_information_test.go +++ b/dhcpv4/bsdp/option_vendor_specific_information_test.go @@ -15,8 +15,6 @@ func TestOptVendorSpecificInformationInterfaceMethods(t *testing.T) { require.Equal(t, 2+messageTypeOpt.Length()+2+versionOpt.Length(), o.Length(), "Length") expectedBytes := []byte{ - 43, // code - 7, // length 1, 1, 1, // List option 2, 2, 1, 1, // Version option } diff --git a/dhcpv4/dhcpv4.go b/dhcpv4/dhcpv4.go index 98f0eae..0f2a5b4 100644 --- a/dhcpv4/dhcpv4.go +++ b/dhcpv4/dhcpv4.go @@ -5,7 +5,6 @@ import ( "encoding/binary" "errors" "fmt" - "log" "net" "strings" @@ -472,29 +471,6 @@ func (d *DHCPv4) Summary() string { return ret } -// ValidateOptions runs sanity checks on the DHCPv4 packet and prints a number -// of warnings if something is incorrect. -func (d *DHCPv4) ValidateOptions() { - // TODO find duplicate options - foundOptionEnd := false - for _, opt := range d.Options { - if foundOptionEnd { - if opt.Code() == OptionEnd { - log.Print("Warning: found duplicate End option") - } - if opt.Code() != OptionEnd && opt.Code() != OptionPad { - log.Printf("Warning: found option %v (%v) after End option", opt.Code(), opt.Code().String()) - } - } - if opt.Code() == OptionEnd { - foundOptionEnd = true - } - } - if !foundOptionEnd { - log.Print("Warning: no End option found") - } -} - // IsOptionRequested returns true if that option is within the requested // options of the DHCPv4 message. func (d *DHCPv4) IsOptionRequested(requested OptionCode) bool { @@ -553,9 +529,7 @@ func (d *DHCPv4) ToBytes() []byte { // The magic cookie. buf.WriteBytes(magicCookie[:]) - - for _, opt := range d.Options { - buf.WriteBytes(opt.ToBytes()) - } + d.Options.Marshal(buf) + buf.Write8(uint8(OptionEnd)) return buf.Data() } diff --git a/dhcpv4/modifiers_test.go b/dhcpv4/modifiers_test.go index 15dbebf..6d3976e 100644 --- a/dhcpv4/modifiers_test.go +++ b/dhcpv4/modifiers_test.go @@ -49,8 +49,6 @@ func TestUserClassModifier(t *testing.T) { userClass := WithUserClass([]byte("linuxboot"), false) d = userClass(d) expected := []byte{ - 77, // OptionUserClass - 9, // length 'l', 'i', 'n', 'u', 'x', 'b', 'o', 'o', 't', } require.Equal(t, "User Class Information -> linuxboot", d.Options[0].String()) @@ -62,8 +60,6 @@ func TestUserClassModifierRFC(t *testing.T) { userClass := WithUserClass([]byte("linuxboot"), true) d = userClass(d) expected := []byte{ - 77, // OptionUserClass - 10, // length 9, 'l', 'i', 'n', 'u', 'x', 'b', 'o', 'o', 't', } require.Equal(t, "User Class Information -> linuxboot", d.Options[0].String()) diff --git a/dhcpv4/option_archtype.go b/dhcpv4/option_archtype.go index f5882bc..f400a21 100644 --- a/dhcpv4/option_archtype.go +++ b/dhcpv4/option_archtype.go @@ -4,7 +4,6 @@ package dhcpv4 // https://tools.ietf.org/html/rfc4578 import ( - "encoding/binary" "fmt" "github.com/insomniacslk/dhcp/iana" @@ -24,13 +23,11 @@ func (o *OptClientArchType) Code() OptionCode { // ToBytes returns a serialized stream of bytes for this option. func (o *OptClientArchType) ToBytes() []byte { - ret := []byte{byte(o.Code()), byte(o.Length())} + buf := uio.NewBigEndianBuffer(nil) for _, at := range o.ArchTypes { - buf := make([]byte, 2) - binary.BigEndian.PutUint16(buf[0:2], uint16(at)) - ret = append(ret, buf...) + buf.Write16(uint16(at)) } - return ret + return buf.Data() } // Length returns the length of the data portion (excluding option code an byte diff --git a/dhcpv4/option_archtype_test.go b/dhcpv4/option_archtype_test.go index 482ebb1..9e0db5b 100644 --- a/dhcpv4/option_archtype_test.go +++ b/dhcpv4/option_archtype_test.go @@ -9,23 +9,19 @@ import ( func TestParseOptClientArchType(t *testing.T) { data := []byte{ - 93, // OptionClientSystemArchitectureType - 2, // Length 0, 6, // EFI_IA32 } - opt, err := ParseOptClientArchType(data[2:]) + opt, err := ParseOptClientArchType(data) require.NoError(t, err) require.Equal(t, opt.ArchTypes[0], iana.EFI_IA32) } func TestParseOptClientArchTypeMultiple(t *testing.T) { data := []byte{ - 93, // OptionClientSystemArchitectureType - 4, // Length 0, 6, // EFI_IA32 0, 2, // EFI_ITANIUM } - opt, err := ParseOptClientArchType(data[2:]) + opt, err := ParseOptClientArchType(data) require.NoError(t, err) require.Equal(t, opt.ArchTypes[0], iana.EFI_IA32) require.Equal(t, opt.ArchTypes[1], iana.EFI_ITANIUM) @@ -39,23 +35,19 @@ func TestParseOptClientArchTypeInvalid(t *testing.T) { func TestOptClientArchTypeParseAndToBytes(t *testing.T) { data := []byte{ - 93, // OptionClientSystemArchitectureType - 2, // Length 0, 8, // EFI_XSCALE } - opt, err := ParseOptClientArchType(data[2:]) + opt, err := ParseOptClientArchType(data) require.NoError(t, err) require.Equal(t, opt.ToBytes(), data) } func TestOptClientArchTypeParseAndToBytesMultiple(t *testing.T) { data := []byte{ - 93, // OptionClientSystemArchitectureType - 4, // Length 0, 8, // EFI_XSCALE 0, 6, // EFI_IA32 } - opt, err := ParseOptClientArchType(data[2:]) + opt, err := ParseOptClientArchType(data) require.NoError(t, err) require.Equal(t, opt.ToBytes(), data) } diff --git a/dhcpv4/option_bootfile_name.go b/dhcpv4/option_bootfile_name.go index e06e4eb..b0b8464 100644 --- a/dhcpv4/option_bootfile_name.go +++ b/dhcpv4/option_bootfile_name.go @@ -19,7 +19,7 @@ func (op *OptBootfileName) Code() OptionCode { // ToBytes serializes the option and returns it as a sequence of bytes func (op *OptBootfileName) ToBytes() []byte { - return append([]byte{byte(op.Code()), byte(op.Length())}, []byte(op.BootfileName)...) + return []byte(op.BootfileName) } // Length returns the option length in bytes diff --git a/dhcpv4/option_bootfile_name_test.go b/dhcpv4/option_bootfile_name_test.go index 2671ac5..76f60f4 100644 --- a/dhcpv4/option_bootfile_name_test.go +++ b/dhcpv4/option_bootfile_name_test.go @@ -17,8 +17,6 @@ func TestOptBootfileNameToBytes(t *testing.T) { } data := opt.ToBytes() expected := []byte{ - 67, // OptionBootfileName - 9, // length 'l', 'i', 'n', 'u', 'x', 'b', 'o', 'o', 't', } require.Equal(t, expected, data) diff --git a/dhcpv4/option_broadcast_address.go b/dhcpv4/option_broadcast_address.go index fdce946..1f6d4b8 100644 --- a/dhcpv4/option_broadcast_address.go +++ b/dhcpv4/option_broadcast_address.go @@ -29,8 +29,7 @@ func (o *OptBroadcastAddress) Code() OptionCode { // ToBytes returns a serialized stream of bytes for this option. func (o *OptBroadcastAddress) ToBytes() []byte { - ret := []byte{byte(o.Code()), byte(o.Length())} - return append(ret, o.BroadcastAddress.To4()...) + return []byte(o.BroadcastAddress.To4()) } // String returns a human-readable string. diff --git a/dhcpv4/option_broadcast_address_test.go b/dhcpv4/option_broadcast_address_test.go index 1feb6cc..7980429 100644 --- a/dhcpv4/option_broadcast_address_test.go +++ b/dhcpv4/option_broadcast_address_test.go @@ -13,7 +13,7 @@ func TestOptBroadcastAddressInterfaceMethods(t *testing.T) { require.Equal(t, OptionBroadcastAddress, o.Code(), "Code") - expectedBytes := []byte{byte(OptionBroadcastAddress), 4, 192, 168, 0, 1} + expectedBytes := []byte{192, 168, 0, 1} require.Equal(t, expectedBytes, o.ToBytes(), "ToBytes") require.Equal(t, 4, o.Length(), "Length") diff --git a/dhcpv4/option_class_identifier.go b/dhcpv4/option_class_identifier.go index 1a49b87..6610df5 100644 --- a/dhcpv4/option_class_identifier.go +++ b/dhcpv4/option_class_identifier.go @@ -25,7 +25,7 @@ func (o *OptClassIdentifier) Code() OptionCode { // ToBytes returns a serialized stream of bytes for this option. func (o *OptClassIdentifier) ToBytes() []byte { - return append([]byte{byte(o.Code()), byte(o.Length())}, []byte(o.Identifier)...) + return []byte(o.Identifier) } // String returns a human-readable string for this option. diff --git a/dhcpv4/option_class_identifier_test.go b/dhcpv4/option_class_identifier_test.go index 289eafe..6ff7888 100644 --- a/dhcpv4/option_class_identifier_test.go +++ b/dhcpv4/option_class_identifier_test.go @@ -10,7 +10,7 @@ func TestOptClassIdentifierInterfaceMethods(t *testing.T) { o := OptClassIdentifier{Identifier: "foo"} require.Equal(t, OptionClassIdentifier, o.Code(), "Code") require.Equal(t, 3, o.Length(), "Length") - require.Equal(t, []byte{byte(OptionClassIdentifier), 3, 'f', 'o', 'o'}, o.ToBytes(), "ToBytes") + require.Equal(t, []byte{'f', 'o', 'o'}, o.ToBytes(), "ToBytes") } func TestParseOptClassIdentifier(t *testing.T) { diff --git a/dhcpv4/option_domain_name.go b/dhcpv4/option_domain_name.go index 673b2a6..689064b 100644 --- a/dhcpv4/option_domain_name.go +++ b/dhcpv4/option_domain_name.go @@ -23,7 +23,7 @@ func (o *OptDomainName) Code() OptionCode { // ToBytes returns a serialized stream of bytes for this option. func (o *OptDomainName) ToBytes() []byte { - return append([]byte{byte(o.Code()), byte(o.Length())}, []byte(o.DomainName)...) + return []byte(o.DomainName) } // String returns a human-readable string. diff --git a/dhcpv4/option_domain_name_server.go b/dhcpv4/option_domain_name_server.go index 8633cc4..59299c3 100644 --- a/dhcpv4/option_domain_name_server.go +++ b/dhcpv4/option_domain_name_server.go @@ -31,23 +31,12 @@ func (o *OptDomainNameServer) Code() OptionCode { // ToBytes returns a serialized stream of bytes for this option. func (o *OptDomainNameServer) ToBytes() []byte { - ret := []byte{byte(o.Code()), byte(o.Length())} - for _, ns := range o.NameServers { - ret = append(ret, ns.To4()...) - } - return ret + return IPsToBytes(o.NameServers) } // String returns a human-readable string. func (o *OptDomainNameServer) String() string { - var servers string - for idx, ns := range o.NameServers { - servers += ns.String() - if idx < len(o.NameServers)-1 { - servers += ", " - } - } - return fmt.Sprintf("Domain Name Servers -> %v", servers) + return fmt.Sprintf("Domain Name Servers -> %s", IPsToString(o.NameServers)) } // Length returns the length of the data portion (excluding option code an byte diff --git a/dhcpv4/option_domain_name_test.go b/dhcpv4/option_domain_name_test.go index e88d87e..473e28d 100644 --- a/dhcpv4/option_domain_name_test.go +++ b/dhcpv4/option_domain_name_test.go @@ -10,7 +10,7 @@ func TestOptDomainNameInterfaceMethods(t *testing.T) { o := OptDomainName{DomainName: "foo"} require.Equal(t, OptionDomainName, o.Code(), "Code") require.Equal(t, 3, o.Length(), "Length") - require.Equal(t, []byte{byte(OptionDomainName), 3, 'f', 'o', 'o'}, o.ToBytes(), "ToBytes") + require.Equal(t, []byte{'f', 'o', 'o'}, o.ToBytes(), "ToBytes") } func TestParseOptDomainName(t *testing.T) { diff --git a/dhcpv4/option_domain_search.go b/dhcpv4/option_domain_search.go index 5fafb6e..55b8bf5 100644 --- a/dhcpv4/option_domain_search.go +++ b/dhcpv4/option_domain_search.go @@ -24,9 +24,7 @@ func (op *OptDomainSearch) Code() OptionCode { // ToBytes returns a serialized stream of bytes for this option. func (op *OptDomainSearch) ToBytes() []byte { - buf := []byte{byte(op.Code()), byte(op.Length())} - buf = append(buf, op.DomainSearch.ToBytes()...) - return buf + return op.DomainSearch.ToBytes() } // Length returns the length of the data portion (excluding option code an byte diff --git a/dhcpv4/option_domain_search_test.go b/dhcpv4/option_domain_search_test.go index 0e5f8e9..677309e 100644 --- a/dhcpv4/option_domain_search_test.go +++ b/dhcpv4/option_domain_search_test.go @@ -9,24 +9,20 @@ import ( func TestParseOptDomainSearch(t *testing.T) { data := []byte{ - 119, // OptionDNSDomainSearchList - 33, // length 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 := ParseOptDomainSearch(data[2:]) + opt, err := ParseOptDomainSearch(data) require.NoError(t, err) require.Equal(t, 2, len(opt.DomainSearch.Labels)) - require.Equal(t, data[2:], opt.DomainSearch.ToBytes()) - require.Equal(t, len(data[2:]), opt.DomainSearch.Length()) + require.Equal(t, data, opt.DomainSearch.ToBytes()) + require.Equal(t, len(data), opt.DomainSearch.Length()) require.Equal(t, opt.DomainSearch.Labels[0], "example.com") require.Equal(t, opt.DomainSearch.Labels[1], "subnet.example.org") } func TestOptDomainSearchToBytes(t *testing.T) { expected := []byte{ - 119, // OptionDNSDomainSearchList - 33, // length 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, } diff --git a/dhcpv4/option_generic.go b/dhcpv4/option_generic.go index 964a655..4afcfe1 100644 --- a/dhcpv4/option_generic.go +++ b/dhcpv4/option_generic.go @@ -29,13 +29,7 @@ func (o OptionGeneric) Code() OptionCode { // ToBytes returns a serialized generic option as a slice of bytes. func (o OptionGeneric) ToBytes() []byte { - ret := []byte{byte(o.OptionCode)} - if o.OptionCode == OptionEnd || o.OptionCode == OptionPad { - return ret - } - ret = append(ret, byte(o.Length())) - ret = append(ret, o.Data...) - return ret + return o.Data } // String returns a human-readable representation of a generic option. diff --git a/dhcpv4/option_generic_test.go b/dhcpv4/option_generic_test.go index 5c34903..8a1acc1 100644 --- a/dhcpv4/option_generic_test.go +++ b/dhcpv4/option_generic_test.go @@ -26,19 +26,7 @@ func TestOptionGenericToBytes(t *testing.T) { Data: []byte{byte(MessageTypeDiscover)}, } serialized := o.ToBytes() - expected := []byte{53, 1, 1} - require.Equal(t, expected, serialized) -} - -func TestOptionGenericToBytesZeroOptions(t *testing.T) { - o := OptionGeneric{OptionCode: OptionEnd} - serialized := o.ToBytes() - expected := []byte{255} - require.Equal(t, expected, serialized) - - o = OptionGeneric{OptionCode: OptionPad} - serialized = o.ToBytes() - expected = []byte{0} + expected := []byte{1} require.Equal(t, expected, serialized) } diff --git a/dhcpv4/option_host_name.go b/dhcpv4/option_host_name.go index decc18b..77ed17c 100644 --- a/dhcpv4/option_host_name.go +++ b/dhcpv4/option_host_name.go @@ -23,7 +23,7 @@ func (o *OptHostName) Code() OptionCode { // ToBytes returns a serialized stream of bytes for this option. func (o *OptHostName) ToBytes() []byte { - return append([]byte{byte(o.Code()), byte(o.Length())}, []byte(o.HostName)...) + return []byte(o.HostName) } // String returns a human-readable string. diff --git a/dhcpv4/option_host_name_test.go b/dhcpv4/option_host_name_test.go index f5e8548..ddae067 100644 --- a/dhcpv4/option_host_name_test.go +++ b/dhcpv4/option_host_name_test.go @@ -10,7 +10,7 @@ func TestOptHostNameInterfaceMethods(t *testing.T) { o := OptHostName{HostName: "foo"} require.Equal(t, OptionHostName, o.Code(), "Code") require.Equal(t, 3, o.Length(), "Length") - require.Equal(t, []byte{byte(OptionHostName), 3, 'f', 'o', 'o'}, o.ToBytes(), "ToBytes") + require.Equal(t, []byte{'f', 'o', 'o'}, o.ToBytes(), "ToBytes") } func TestParseOptHostName(t *testing.T) { diff --git a/dhcpv4/option_ip_address_lease_time.go b/dhcpv4/option_ip_address_lease_time.go index 2f63fb7..c020e6e 100644 --- a/dhcpv4/option_ip_address_lease_time.go +++ b/dhcpv4/option_ip_address_lease_time.go @@ -1,7 +1,6 @@ package dhcpv4 import ( - "encoding/binary" "fmt" "github.com/u-root/u-root/pkg/uio" @@ -30,10 +29,9 @@ func (o *OptIPAddressLeaseTime) Code() OptionCode { // ToBytes returns a serialized stream of bytes for this option. func (o *OptIPAddressLeaseTime) ToBytes() []byte { - serializedTime := make([]byte, 4) - binary.BigEndian.PutUint32(serializedTime, o.LeaseTime) - serializedOpt := []byte{byte(o.Code()), byte(o.Length())} - return append(serializedOpt, serializedTime...) + buf := uio.NewBigEndianBuffer(nil) + buf.Write32(o.LeaseTime) + return buf.Data() } // String returns a human-readable string for this option. diff --git a/dhcpv4/option_ip_address_lease_time_test.go b/dhcpv4/option_ip_address_lease_time_test.go index dafa6e4..fb11cf6 100644 --- a/dhcpv4/option_ip_address_lease_time_test.go +++ b/dhcpv4/option_ip_address_lease_time_test.go @@ -10,7 +10,7 @@ func TestOptIPAddressLeaseTimeInterfaceMethods(t *testing.T) { o := OptIPAddressLeaseTime{LeaseTime: 43200} require.Equal(t, OptionIPAddressLeaseTime, o.Code(), "Code") require.Equal(t, 4, o.Length(), "Length") - require.Equal(t, []byte{51, 4, 0, 0, 168, 192}, o.ToBytes(), "ToBytes") + require.Equal(t, []byte{0, 0, 168, 192}, o.ToBytes(), "ToBytes") } func TestParseOptIPAddressLeaseTime(t *testing.T) { diff --git a/dhcpv4/option_maximum_dhcp_message_size.go b/dhcpv4/option_maximum_dhcp_message_size.go index 26865fd..097ba8f 100644 --- a/dhcpv4/option_maximum_dhcp_message_size.go +++ b/dhcpv4/option_maximum_dhcp_message_size.go @@ -1,7 +1,6 @@ package dhcpv4 import ( - "encoding/binary" "fmt" "github.com/u-root/u-root/pkg/uio" @@ -29,10 +28,9 @@ func (o *OptMaximumDHCPMessageSize) Code() OptionCode { // ToBytes returns a serialized stream of bytes for this option. func (o *OptMaximumDHCPMessageSize) ToBytes() []byte { - serializedSize := make([]byte, 2) - binary.BigEndian.PutUint16(serializedSize, o.Size) - serializedOpt := []byte{byte(o.Code()), byte(o.Length())} - return append(serializedOpt, serializedSize...) + buf := uio.NewBigEndianBuffer(nil) + buf.Write16(o.Size) + return buf.Data() } // String returns a human-readable string for this option. diff --git a/dhcpv4/option_maximum_dhcp_message_size_test.go b/dhcpv4/option_maximum_dhcp_message_size_test.go index 24ba49f..550ea77 100644 --- a/dhcpv4/option_maximum_dhcp_message_size_test.go +++ b/dhcpv4/option_maximum_dhcp_message_size_test.go @@ -10,7 +10,7 @@ func TestOptMaximumDHCPMessageSizeInterfaceMethods(t *testing.T) { o := OptMaximumDHCPMessageSize{Size: 1500} require.Equal(t, OptionMaximumDHCPMessageSize, o.Code(), "Code") require.Equal(t, 2, o.Length(), "Length") - require.Equal(t, []byte{57, 2, 5, 220}, o.ToBytes(), "ToBytes") + require.Equal(t, []byte{5, 220}, o.ToBytes(), "ToBytes") } func TestParseOptMaximumDHCPMessageSize(t *testing.T) { diff --git a/dhcpv4/option_message_type.go b/dhcpv4/option_message_type.go index 3e11a9a..426f35a 100644 --- a/dhcpv4/option_message_type.go +++ b/dhcpv4/option_message_type.go @@ -28,7 +28,7 @@ func (o *OptMessageType) Code() OptionCode { // ToBytes returns a serialized stream of bytes for this option. func (o *OptMessageType) ToBytes() []byte { - return []byte{byte(o.Code()), byte(o.Length()), byte(o.MessageType)} + return []byte{byte(o.MessageType)} } // String returns a human-readable string for this option. diff --git a/dhcpv4/option_message_type_test.go b/dhcpv4/option_message_type_test.go index 091066e..03ce4da 100644 --- a/dhcpv4/option_message_type_test.go +++ b/dhcpv4/option_message_type_test.go @@ -10,7 +10,7 @@ func TestOptMessageTypeInterfaceMethods(t *testing.T) { o := OptMessageType{MessageType: MessageTypeDiscover} require.Equal(t, OptionDHCPMessageType, o.Code(), "Code") require.Equal(t, 1, o.Length(), "Length") - require.Equal(t, []byte{53, 1, 1}, o.ToBytes(), "ToBytes") + require.Equal(t, []byte{1}, o.ToBytes(), "ToBytes") } func TestOptMessageTypeNew(t *testing.T) { diff --git a/dhcpv4/option_ntp_servers.go b/dhcpv4/option_ntp_servers.go index 6d30920..5415c63 100644 --- a/dhcpv4/option_ntp_servers.go +++ b/dhcpv4/option_ntp_servers.go @@ -29,23 +29,12 @@ func (o *OptNTPServers) Code() OptionCode { // ToBytes returns a serialized stream of bytes for this option. func (o *OptNTPServers) ToBytes() []byte { - ret := []byte{byte(o.Code()), byte(o.Length())} - for _, ntp := range o.NTPServers { - ret = append(ret, ntp.To4()...) - } - return ret + return IPsToBytes(o.NTPServers) } // String returns a human-readable string. func (o *OptNTPServers) String() string { - var ntpServers string - for idx, ntp := range o.NTPServers { - ntpServers += ntp.String() - if idx < len(o.NTPServers)-1 { - ntpServers += ", " - } - } - return fmt.Sprintf("NTP Servers -> %v", ntpServers) + return fmt.Sprintf("NTP Servers -> %v", IPsToString(o.NTPServers)) } // Length returns the length of the data portion (excluding option code an byte diff --git a/dhcpv4/option_parameter_request_list.go b/dhcpv4/option_parameter_request_list.go index 8516e3b..e3eb7ff 100644 --- a/dhcpv4/option_parameter_request_list.go +++ b/dhcpv4/option_parameter_request_list.go @@ -33,11 +33,11 @@ func (o *OptParameterRequestList) Code() OptionCode { // ToBytes returns a serialized stream of bytes for this option. func (o *OptParameterRequestList) ToBytes() []byte { - ret := []byte{byte(o.Code()), byte(o.Length())} + buf := uio.NewBigEndianBuffer(nil) for _, req := range o.RequestedOpts { - ret = append(ret, byte(req)) + buf.Write8(uint8(req)) } - return ret + return buf.Data() } // String returns a human-readable string for this option. diff --git a/dhcpv4/option_parameter_request_list_test.go b/dhcpv4/option_parameter_request_list_test.go index d54fc0f..0590da8 100644 --- a/dhcpv4/option_parameter_request_list_test.go +++ b/dhcpv4/option_parameter_request_list_test.go @@ -11,7 +11,7 @@ func TestOptParameterRequestListInterfaceMethods(t *testing.T) { o := &OptParameterRequestList{RequestedOpts: requestedOpts} require.Equal(t, OptionParameterRequestList, o.Code(), "Code") - expectedBytes := []byte{55, 2, 67, 5} + expectedBytes := []byte{67, 5} require.Equal(t, expectedBytes, o.ToBytes(), "ToBytes") expectedString := "Parameter Request List -> [Bootfile Name, Name Server]" diff --git a/dhcpv4/option_relay_agent_information.go b/dhcpv4/option_relay_agent_information.go index d6547fe..624e261 100644 --- a/dhcpv4/option_relay_agent_information.go +++ b/dhcpv4/option_relay_agent_information.go @@ -1,6 +1,10 @@ package dhcpv4 -import "fmt" +import ( + "fmt" + + "github.com/u-root/u-root/pkg/uio" +) // This option implements the relay agent information option // https://tools.ietf.org/html/rfc3046 @@ -28,11 +32,7 @@ func (o *OptRelayAgentInformation) Code() OptionCode { // ToBytes returns a serialized stream of bytes for this option. func (o *OptRelayAgentInformation) ToBytes() []byte { - ret := []byte{byte(o.Code()), byte(o.Length())} - for _, opt := range o.Options { - ret = append(ret, opt.ToBytes()...) - } - return ret + return uio.ToBigEndian(o.Options) } // String returns a human-readable string for this option. diff --git a/dhcpv4/option_relay_agent_information_test.go b/dhcpv4/option_relay_agent_information_test.go index 8722593..d1aeee2 100644 --- a/dhcpv4/option_relay_agent_information_test.go +++ b/dhcpv4/option_relay_agent_information_test.go @@ -8,8 +8,6 @@ import ( func TestParseOptRelayAgentInformation(t *testing.T) { data := []byte{ - byte(OptionRelayAgentInformation), - 13, 1, 5, 'l', 'i', 'n', 'u', 'x', 2, 4, 'b', 'o', 'o', 't', } @@ -22,7 +20,7 @@ func TestParseOptRelayAgentInformation(t *testing.T) { opt, err = ParseOptRelayAgentInformation([]byte{1, 1}) require.Error(t, err) - opt, err = ParseOptRelayAgentInformation(data[2:]) + opt, err = ParseOptRelayAgentInformation(data) require.NoError(t, err) require.Equal(t, len(opt.Options), 2) circuit := opt.Options.GetOne(1).(*OptionGeneric) @@ -34,15 +32,14 @@ func TestParseOptRelayAgentInformation(t *testing.T) { } func TestParseOptRelayAgentInformationToBytes(t *testing.T) { - opt := OptRelayAgentInformation{} - opt1 := &OptionGeneric{OptionCode: 1, Data: []byte("linux")} - opt.Options = append(opt.Options, opt1) - opt2 := &OptionGeneric{OptionCode: 2, Data: []byte("boot")} - opt.Options = append(opt.Options, opt2) + opt := OptRelayAgentInformation{ + Options: Options{ + &OptionGeneric{OptionCode: 1, Data: []byte("linux")}, + &OptionGeneric{OptionCode: 2, Data: []byte("boot")}, + }, + } data := opt.ToBytes() expected := []byte{ - byte(OptionRelayAgentInformation), - 13, 1, 5, 'l', 'i', 'n', 'u', 'x', 2, 4, 'b', 'o', 'o', 't', } diff --git a/dhcpv4/option_requested_ip_address.go b/dhcpv4/option_requested_ip_address.go index 3539278..403f262 100644 --- a/dhcpv4/option_requested_ip_address.go +++ b/dhcpv4/option_requested_ip_address.go @@ -30,8 +30,7 @@ func (o *OptRequestedIPAddress) Code() OptionCode { // ToBytes returns a serialized stream of bytes for this option. func (o *OptRequestedIPAddress) ToBytes() []byte { - ret := []byte{byte(o.Code()), byte(o.Length())} - return append(ret, o.RequestedAddr.To4()...) + return o.RequestedAddr.To4() } // String returns a human-readable string. diff --git a/dhcpv4/option_requested_ip_address_test.go b/dhcpv4/option_requested_ip_address_test.go index 99592ea..84761b5 100644 --- a/dhcpv4/option_requested_ip_address_test.go +++ b/dhcpv4/option_requested_ip_address_test.go @@ -13,7 +13,7 @@ func TestOptRequestedIPAddressInterfaceMethods(t *testing.T) { require.Equal(t, OptionRequestedIPAddress, o.Code(), "Code") - expectedBytes := []byte{byte(OptionRequestedIPAddress), 4, 192, 168, 0, 1} + expectedBytes := []byte{192, 168, 0, 1} require.Equal(t, expectedBytes, o.ToBytes(), "ToBytes") require.Equal(t, 4, o.Length(), "Length") diff --git a/dhcpv4/option_root_path.go b/dhcpv4/option_root_path.go index ba6f03f..350109f 100644 --- a/dhcpv4/option_root_path.go +++ b/dhcpv4/option_root_path.go @@ -25,7 +25,7 @@ func (o *OptRootPath) Code() OptionCode { // ToBytes returns a serialized stream of bytes for this option. func (o *OptRootPath) ToBytes() []byte { - return append([]byte{byte(o.Code()), byte(o.Length())}, []byte(o.Path)...) + return []byte(o.Path) } // String returns a human-readable string for this option. diff --git a/dhcpv4/option_root_path_test.go b/dhcpv4/option_root_path_test.go index 4bc7bc1..0bfbd65 100644 --- a/dhcpv4/option_root_path_test.go +++ b/dhcpv4/option_root_path_test.go @@ -11,8 +11,6 @@ func TestOptRootPathInterfaceMethods(t *testing.T) { require.Equal(t, OptionRootPath, o.Code(), "Code") require.Equal(t, 12, o.Length(), "Length") wantBytes := []byte{ - byte(OptionRootPath), - 12, '/', 'f', 'o', 'o', '/', 'b', 'a', 'r', '/', 'b', 'a', 'z', } require.Equal(t, wantBytes, o.ToBytes(), "ToBytes") diff --git a/dhcpv4/option_router.go b/dhcpv4/option_router.go index 60a96d1..686e3dc 100644 --- a/dhcpv4/option_router.go +++ b/dhcpv4/option_router.go @@ -3,6 +3,7 @@ package dhcpv4 import ( "fmt" "net" + "strings" "github.com/u-root/u-root/pkg/uio" ) @@ -32,6 +33,26 @@ func ParseIPs(data []byte) ([]net.IP, error) { return ips, buf.FinError() } +// IPsToBytes marshals an IPv4 address to a DHCP packet as specified by RFC +// 2132, Section 3.5 et al. +func IPsToBytes(i []net.IP) []byte { + buf := uio.NewBigEndianBuffer(nil) + + for _, ip := range i { + buf.WriteBytes(ip.To4()) + } + return buf.Data() +} + +// IPsToString returns a human-readable representation of a list of IPs. +func IPsToString(i []net.IP) string { + s := make([]string, 0, len(i)) + for _, ip := range i { + s = append(s, ip.String()) + } + return strings.Join(s, ", ") +} + // ParseOptRouter returns a new OptRouter from a byte stream, or error if any. func ParseOptRouter(data []byte) (*OptRouter, error) { ips, err := ParseIPs(data) @@ -48,23 +69,12 @@ func (o *OptRouter) Code() OptionCode { // ToBytes returns a serialized stream of bytes for this option. func (o *OptRouter) ToBytes() []byte { - ret := []byte{byte(o.Code()), byte(o.Length())} - for _, router := range o.Routers { - ret = append(ret, router.To4()...) - } - return ret + return IPsToBytes(o.Routers) } // String returns a human-readable string. func (o *OptRouter) String() string { - var routers string - for idx, router := range o.Routers { - routers += router.String() - if idx < len(o.Routers)-1 { - routers += ", " - } - } - return fmt.Sprintf("Routers -> %v", routers) + return fmt.Sprintf("Routers -> %s", IPsToString(o.Routers)) } // Length returns the length of the data portion (excluding option code an byte diff --git a/dhcpv4/option_server_identifier.go b/dhcpv4/option_server_identifier.go index fd0311a..64185aa 100644 --- a/dhcpv4/option_server_identifier.go +++ b/dhcpv4/option_server_identifier.go @@ -29,8 +29,7 @@ func (o *OptServerIdentifier) Code() OptionCode { // ToBytes returns a serialized stream of bytes for this option. func (o *OptServerIdentifier) ToBytes() []byte { - ret := []byte{byte(o.Code()), byte(o.Length())} - return append(ret, o.ServerID.To4()...) + return o.ServerID.To4() } // String returns a human-readable string. diff --git a/dhcpv4/option_server_identifier_test.go b/dhcpv4/option_server_identifier_test.go index 4a85469..f41c90f 100644 --- a/dhcpv4/option_server_identifier_test.go +++ b/dhcpv4/option_server_identifier_test.go @@ -13,7 +13,7 @@ func TestOptServerIdentifierInterfaceMethods(t *testing.T) { require.Equal(t, OptionServerIdentifier, o.Code(), "Code") - expectedBytes := []byte{54, 4, 192, 168, 0, 1} + expectedBytes := []byte{192, 168, 0, 1} require.Equal(t, expectedBytes, o.ToBytes(), "ToBytes") require.Equal(t, 4, o.Length(), "Length") diff --git a/dhcpv4/option_subnet_mask.go b/dhcpv4/option_subnet_mask.go index 86ce004..7c8a2c1 100644 --- a/dhcpv4/option_subnet_mask.go +++ b/dhcpv4/option_subnet_mask.go @@ -29,8 +29,7 @@ func (o *OptSubnetMask) Code() OptionCode { // ToBytes returns a serialized stream of bytes for this option. func (o *OptSubnetMask) ToBytes() []byte { - ret := []byte{byte(o.Code()), byte(o.Length())} - return append(ret, o.SubnetMask[:4]...) + return o.SubnetMask[:net.IPv4len] } // String returns a human-readable string. diff --git a/dhcpv4/option_subnet_mask_test.go b/dhcpv4/option_subnet_mask_test.go index e3c37bf..beeeda0 100644 --- a/dhcpv4/option_subnet_mask_test.go +++ b/dhcpv4/option_subnet_mask_test.go @@ -13,7 +13,7 @@ func TestOptSubnetMaskInterfaceMethods(t *testing.T) { require.Equal(t, OptionSubnetMask, o.Code(), "Code") - expectedBytes := []byte{1, 4, 255, 255, 255, 0} + expectedBytes := []byte{255, 255, 255, 0} require.Equal(t, expectedBytes, o.ToBytes(), "ToBytes") require.Equal(t, 4, o.Length(), "Length") diff --git a/dhcpv4/option_tftp_server_name.go b/dhcpv4/option_tftp_server_name.go index 2a4af6d..2aa832f 100644 --- a/dhcpv4/option_tftp_server_name.go +++ b/dhcpv4/option_tftp_server_name.go @@ -19,7 +19,7 @@ func (op *OptTFTPServerName) Code() OptionCode { // ToBytes serializes the option and returns it as a sequence of bytes func (op *OptTFTPServerName) ToBytes() []byte { - return append([]byte{byte(op.Code()), byte(op.Length())}, []byte(op.TFTPServerName)...) + return []byte(op.TFTPServerName) } // Length returns the option length in bytes diff --git a/dhcpv4/option_tftp_server_name_test.go b/dhcpv4/option_tftp_server_name_test.go index 54efef8..d85b254 100644 --- a/dhcpv4/option_tftp_server_name_test.go +++ b/dhcpv4/option_tftp_server_name_test.go @@ -17,8 +17,6 @@ func TestOptTFTPServerNameToBytes(t *testing.T) { } data := opt.ToBytes() expected := []byte{ - 66, // OptionTFTPServerName - 9, // length 'l', 'i', 'n', 'u', 'x', 'b', 'o', 'o', 't', } require.Equal(t, expected, data) diff --git a/dhcpv4/option_userclass.go b/dhcpv4/option_userclass.go index 44ce090..560d6ac 100644 --- a/dhcpv4/option_userclass.go +++ b/dhcpv4/option_userclass.go @@ -24,15 +24,16 @@ func (op *OptUserClass) Code() OptionCode { // ToBytes serializes the option and returns it as a sequence of bytes func (op *OptUserClass) ToBytes() []byte { - buf := []byte{byte(op.Code()), byte(op.Length())} + buf := uio.NewBigEndianBuffer(nil) if !op.Rfc3004 { - return append(buf, op.UserClasses[0]...) - } - for _, uc := range op.UserClasses { - buf = append(buf, byte(len(uc))) - buf = append(buf, uc...) + buf.WriteBytes(op.UserClasses[0]) + } else { + for _, uc := range op.UserClasses { + buf.Write8(uint8(len(uc))) + buf.WriteBytes(uc) + } } - return buf + return buf.Data() } // Length returns the option length diff --git a/dhcpv4/option_userclass_test.go b/dhcpv4/option_userclass_test.go index d392ed8..ccbabef 100644 --- a/dhcpv4/option_userclass_test.go +++ b/dhcpv4/option_userclass_test.go @@ -13,8 +13,6 @@ func TestOptUserClassToBytes(t *testing.T) { } data := opt.ToBytes() expected := []byte{ - 77, // OptionUserClass - 10, // length 9, 'l', 'i', 'n', 'u', 'x', 'b', 'o', 'o', 't', } require.Equal(t, expected, data) @@ -26,8 +24,6 @@ func TestOptUserClassMicrosoftToBytes(t *testing.T) { } data := opt.ToBytes() expected := []byte{ - 77, // OptionUserClass - 9, // length 'l', 'i', 'n', 'u', 'x', 'b', 'o', 'o', 't', } require.Equal(t, expected, data) @@ -91,8 +87,6 @@ func TestOptUserClassToBytesMultiple(t *testing.T) { } data := opt.ToBytes() expected := []byte{ - 77, // OptionUserClass - 15, // length 9, 'l', 'i', 'n', 'u', 'x', 'b', 'o', 'o', 't', 4, 't', 'e', 's', 't', } diff --git a/dhcpv4/option_vivc.go b/dhcpv4/option_vivc.go index 4ff42a3..4c278e0 100644 --- a/dhcpv4/option_vivc.go +++ b/dhcpv4/option_vivc.go @@ -2,7 +2,6 @@ package dhcpv4 import ( "bytes" - "encoding/binary" "fmt" "github.com/u-root/u-root/pkg/uio" @@ -44,17 +43,13 @@ func (o *OptVIVC) Code() OptionCode { // ToBytes returns a serialized stream of bytes for this option. func (o *OptVIVC) ToBytes() []byte { - buf := make([]byte, o.Length()+2) - copy(buf[0:], []byte{byte(o.Code()), byte(o.Length())}) - - b := buf[2:] + buf := uio.NewBigEndianBuffer(nil) for _, id := range o.Identifiers { - binary.BigEndian.PutUint32(b[0:4], id.EntID) - b[4] = byte(len(id.Data)) - copy(b[5:], id.Data) - b = b[len(id.Data)+5:] + buf.Write32(id.EntID) + buf.Write8(uint8(len(id.Data))) + buf.WriteBytes(id.Data) } - return buf + return buf.Data() } // String returns a human-readable string for this option. diff --git a/dhcpv4/option_vivc_test.go b/dhcpv4/option_vivc_test.go index 0b4ab7c..855bc2a 100644 --- a/dhcpv4/option_vivc_test.go +++ b/dhcpv4/option_vivc_test.go @@ -14,7 +14,6 @@ var ( }, } sampleVIVCOptRaw = []byte{ - byte(OptionVendorIdentifyingVendorClass), 44, // option header 0x0, 0x0, 0x0, 0x9, // enterprise id 9 0xf, // length 'C', 'i', 's', 'c', 'o', 'I', 'd', 'e', 'n', 't', 'i', 'f', 'i', 'e', 'r', @@ -31,13 +30,13 @@ func TestOptVIVCInterfaceMethods(t *testing.T) { } func TestParseOptVICO(t *testing.T) { - o, err := ParseOptVIVC(sampleVIVCOptRaw[2:]) + o, err := ParseOptVIVC(sampleVIVCOptRaw) require.NoError(t, err) require.Equal(t, &sampleVIVCOpt, o) // Identifier len too long - data := make([]byte, len(sampleVIVCOptRaw[2:])) - copy(data, sampleVIVCOptRaw[2:]) + data := make([]byte, len(sampleVIVCOptRaw)) + copy(data, sampleVIVCOptRaw) data[4] = 40 _, err = ParseOptVIVC(data) require.Error(t, err, "should get error from bad length") diff --git a/dhcpv4/options.go b/dhcpv4/options.go index d5162f4..ce2fdcd 100644 --- a/dhcpv4/options.go +++ b/dhcpv4/options.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "io" + "math" "github.com/u-root/u-root/pkg/uio" ) @@ -208,3 +209,42 @@ func OptionsFromBytesWithParser(data []byte, parser OptionParser, checkEndOption } return opts, nil } + +// Marshal writes options binary representations to b. +func (o Options) Marshal(b *uio.Lexer) { + for _, opt := range o { + code := opt.Code() + + // Even if the End option is in there, don't marshal it until + // the end. + if code == OptionEnd { + continue + } else if code == OptionPad { + // Some DHCPv4 options have fixed length and do not put + // length on the wire. + b.Write8(uint8(code)) + continue + } + + data := opt.ToBytes() + + // RFC 3396: If more than 256 bytes of data are given, the + // option is simply listed multiple times. + for len(data) > 0 { + // 1 byte: option code + b.Write8(uint8(code)) + + n := len(data) + if n > math.MaxUint8 { + n = math.MaxUint8 + } + + // 1 byte: option length + b.Write8(uint8(n)) + + // N bytes: option data + b.WriteBytes(data[:n]) + data = data[n:] + } + } +} diff --git a/dhcpv4/options_test.go b/dhcpv4/options_test.go index 9a2f1c0..02d4018 100644 --- a/dhcpv4/options_test.go +++ b/dhcpv4/options_test.go @@ -7,12 +7,13 @@ import ( "testing" "github.com/stretchr/testify/require" + "github.com/u-root/u-root/pkg/uio" ) func TestParseOption(t *testing.T) { // Generic - option := []byte{5, 4, 192, 168, 1, 254} // DNS option - opt, err := ParseOption(OptionCode(option[0]), option[2:]) + option := []byte{192, 168, 1, 254} // Name server option + opt, err := ParseOption(OptionNameServer, option) require.NoError(t, err) generic := opt.(*OptionGeneric) require.Equal(t, OptionNameServer, generic.Code()) @@ -21,166 +22,223 @@ func TestParseOption(t *testing.T) { require.Equal(t, "Name Server -> [192 168 1 254]", generic.String()) // Option subnet mask - option = []byte{1, 4, 255, 255, 255, 0} - opt, err = ParseOption(OptionCode(option[0]), option[2:]) + option = []byte{255, 255, 255, 0} + opt, err = ParseOption(OptionSubnetMask, option) require.NoError(t, err) require.Equal(t, OptionSubnetMask, opt.Code(), "Code") require.Equal(t, 4, opt.Length(), "Length") require.Equal(t, option, opt.ToBytes(), "ToBytes") // Option router - option = []byte{3, 4, 192, 168, 1, 1} - opt, err = ParseOption(OptionCode(option[0]), option[2:]) + option = []byte{192, 168, 1, 1} + opt, err = ParseOption(OptionRouter, option) require.NoError(t, err) require.Equal(t, OptionRouter, opt.Code(), "Code") require.Equal(t, 4, opt.Length(), "Length") require.Equal(t, option, opt.ToBytes(), "ToBytes") // Option domain name server - option = []byte{6, 4, 192, 168, 1, 1} - opt, err = ParseOption(OptionCode(option[0]), option[2:]) + option = []byte{192, 168, 1, 1} + opt, err = ParseOption(OptionDomainNameServer, option) require.NoError(t, err) require.Equal(t, OptionDomainNameServer, opt.Code(), "Code") require.Equal(t, 4, opt.Length(), "Length") require.Equal(t, option, opt.ToBytes(), "ToBytes") // Option host name - option = []byte{12, 4, 't', 'e', 's', 't'} - opt, err = ParseOption(OptionCode(option[0]), option[2:]) + option = []byte{'t', 'e', 's', 't'} + opt, err = ParseOption(OptionHostName, option) require.NoError(t, err) require.Equal(t, OptionHostName, opt.Code(), "Code") require.Equal(t, 4, opt.Length(), "Length") require.Equal(t, option, opt.ToBytes(), "ToBytes") // Option domain name - option = []byte{15, 4, 't', 'e', 's', 't'} - opt, err = ParseOption(OptionCode(option[0]), option[2:]) + option = []byte{'t', 'e', 's', 't'} + opt, err = ParseOption(OptionDomainName, option) require.NoError(t, err) require.Equal(t, OptionDomainName, opt.Code(), "Code") require.Equal(t, 4, opt.Length(), "Length") require.Equal(t, option, opt.ToBytes(), "ToBytes") // Option root path - option = []byte{17, 4, '/', 'f', 'o', 'o'} - opt, err = ParseOption(OptionCode(option[0]), option[2:]) + option = []byte{'/', 'f', 'o', 'o'} + opt, err = ParseOption(OptionRootPath, option) require.NoError(t, err) require.Equal(t, OptionRootPath, opt.Code(), "Code") require.Equal(t, 4, opt.Length(), "Length") require.Equal(t, option, opt.ToBytes(), "ToBytes") // Option broadcast address - option = []byte{28, 4, 255, 255, 255, 255} - opt, err = ParseOption(OptionCode(option[0]), option[2:]) + option = []byte{255, 255, 255, 255} + opt, err = ParseOption(OptionBroadcastAddress, option) require.NoError(t, err) require.Equal(t, OptionBroadcastAddress, opt.Code(), "Code") require.Equal(t, 4, opt.Length(), "Length") require.Equal(t, option, opt.ToBytes(), "ToBytes") // Option NTP servers - option = []byte{42, 4, 10, 10, 10, 10} - opt, err = ParseOption(OptionCode(option[0]), option[2:]) + option = []byte{10, 10, 10, 10} + opt, err = ParseOption(OptionNTPServers, option) require.NoError(t, err) require.Equal(t, OptionNTPServers, opt.Code(), "Code") require.Equal(t, 4, opt.Length(), "Length") require.Equal(t, option, opt.ToBytes(), "ToBytes") // Requested IP address - option = []byte{50, 4, 1, 2, 3, 4} - opt, err = ParseOption(OptionCode(option[0]), option[2:]) + option = []byte{1, 2, 3, 4} + opt, err = ParseOption(OptionRequestedIPAddress, option) require.NoError(t, err) require.Equal(t, OptionRequestedIPAddress, opt.Code(), "Code") require.Equal(t, 4, opt.Length(), "Length") require.Equal(t, option, opt.ToBytes(), "ToBytes") // Requested IP address lease time - option = []byte{51, 4, 0, 0, 0, 0} - opt, err = ParseOption(OptionCode(option[0]), option[2:]) + option = []byte{0, 0, 0, 0} + opt, err = ParseOption(OptionIPAddressLeaseTime, option) require.NoError(t, err) require.Equal(t, OptionIPAddressLeaseTime, opt.Code(), "Code") require.Equal(t, 4, opt.Length(), "Length") require.Equal(t, option, opt.ToBytes(), "ToBytes") // Message type - option = []byte{53, 1, 1} - opt, err = ParseOption(OptionCode(option[0]), option[2:]) + option = []byte{1} + opt, err = ParseOption(OptionDHCPMessageType, option) require.NoError(t, err) require.Equal(t, OptionDHCPMessageType, opt.Code(), "Code") require.Equal(t, 1, opt.Length(), "Length") require.Equal(t, option, opt.ToBytes(), "ToBytes") // Option server ID - option = []byte{54, 4, 1, 2, 3, 4} - opt, err = ParseOption(OptionCode(option[0]), option[2:]) + option = []byte{1, 2, 3, 4} + opt, err = ParseOption(OptionServerIdentifier, option) require.NoError(t, err) require.Equal(t, OptionServerIdentifier, opt.Code(), "Code") require.Equal(t, 4, opt.Length(), "Length") require.Equal(t, option, opt.ToBytes(), "ToBytes") // Parameter request list - option = []byte{55, 3, 5, 53, 61} - opt, err = ParseOption(OptionCode(option[0]), option[2:]) + option = []byte{5, 53, 61} + opt, err = ParseOption(OptionParameterRequestList, option) require.NoError(t, err) require.Equal(t, OptionParameterRequestList, opt.Code(), "Code") require.Equal(t, 3, opt.Length(), "Length") require.Equal(t, option, opt.ToBytes(), "ToBytes") // Option max message size - option = []byte{57, 2, 1, 2} - opt, err = ParseOption(OptionCode(option[0]), option[2:]) + option = []byte{1, 2} + opt, err = ParseOption(OptionMaximumDHCPMessageSize, option) require.NoError(t, err) require.Equal(t, OptionMaximumDHCPMessageSize, opt.Code(), "Code") require.Equal(t, 2, opt.Length(), "Length") require.Equal(t, option, opt.ToBytes(), "ToBytes") // Option class identifier - option = []byte{60, 4, 't', 'e', 's', 't'} - opt, err = ParseOption(OptionCode(option[0]), option[2:]) + option = []byte{'t', 'e', 's', 't'} + opt, err = ParseOption(OptionClassIdentifier, option) require.NoError(t, err) require.Equal(t, OptionClassIdentifier, opt.Code(), "Code") require.Equal(t, 4, opt.Length(), "Length") require.Equal(t, option, opt.ToBytes(), "ToBytes") // Option TFTP server name - option = []byte{66, 4, 't', 'e', 's', 't'} - opt, err = ParseOption(OptionCode(option[0]), option[2:]) + option = []byte{'t', 'e', 's', 't'} + opt, err = ParseOption(OptionTFTPServerName, option) require.NoError(t, err) require.Equal(t, OptionTFTPServerName, opt.Code(), "Code") require.Equal(t, 4, opt.Length(), "Length") require.Equal(t, option, opt.ToBytes(), "ToBytes") // Option Bootfile name - option = []byte{67, 9, 'l', 'i', 'n', 'u', 'x', 'b', 'o', 'o', 't'} - opt, err = ParseOption(OptionCode(option[0]), option[2:]) + option = []byte{'l', 'i', 'n', 'u', 'x', 'b', 'o', 'o', 't'} + opt, err = ParseOption(OptionBootfileName, option) require.NoError(t, err) require.Equal(t, OptionBootfileName, opt.Code(), "Code") require.Equal(t, 9, opt.Length(), "Length") require.Equal(t, option, opt.ToBytes(), "ToBytes") // Option user class information - option = []byte{77, 5, 4, 't', 'e', 's', 't'} - opt, err = ParseOption(OptionCode(option[0]), option[2:]) + option = []byte{4, 't', 'e', 's', 't'} + opt, err = ParseOption(OptionUserClassInformation, option) require.NoError(t, err) require.Equal(t, OptionUserClassInformation, opt.Code(), "Code") require.Equal(t, 5, opt.Length(), "Length") require.Equal(t, option, opt.ToBytes(), "ToBytes") // Option relay agent information - option = []byte{82, 6, 1, 4, 129, 168, 0, 1} - opt, err = ParseOption(OptionCode(option[0]), option[2:]) + option = []byte{1, 4, 129, 168, 0, 1} + opt, err = ParseOption(OptionRelayAgentInformation, option) require.NoError(t, err) require.Equal(t, OptionRelayAgentInformation, opt.Code(), "Code") require.Equal(t, 6, opt.Length(), "Length") require.Equal(t, option, opt.ToBytes(), "ToBytes") // Option client system architecture type option - option = []byte{93, 4, 't', 'e', 's', 't'} - opt, err = ParseOption(OptionCode(option[0]), option[2:]) + option = []byte{'t', 'e', 's', 't'} + opt, err = ParseOption(OptionClientSystemArchitectureType, option) require.NoError(t, err) require.Equal(t, OptionClientSystemArchitectureType, opt.Code(), "Code") require.Equal(t, 4, opt.Length(), "Length") require.Equal(t, option, opt.ToBytes(), "ToBytes") } +func TestOptionsMarshal(t *testing.T) { + for i, tt := range []struct { + opts Options + want []byte + }{ + { + opts: nil, + want: nil, + }, + { + opts: Options{ + &OptionGeneric{ + OptionCode: 5, + Data: []byte{1, 2, 3, 4}, + }, + }, + want: []byte{ + 5 /* key */, 4 /* length */, 1, 2, 3, 4, + }, + }, + { + // Test sorted key order. + opts: Options{ + &OptionGeneric{ + OptionCode: 5, + Data: []byte{1, 2, 3}, + }, + &OptionGeneric{ + OptionCode: 100, + Data: []byte{101, 102, 103}, + }, + }, + want: []byte{ + 5, 3, 1, 2, 3, + 100, 3, 101, 102, 103, + }, + }, + { + // Test RFC 3396. + opts: Options{ + &OptionGeneric{ + OptionCode: 5, + Data: bytes.Repeat([]byte{10}, math.MaxUint8+1), + }, + }, + want: append(append( + []byte{5, math.MaxUint8}, bytes.Repeat([]byte{10}, math.MaxUint8)...), + 5, 1, 10, + ), + }, + } { + t.Run(fmt.Sprintf("Test %02d", i), func(t *testing.T) { + require.Equal(t, uio.ToBigEndian(tt.opts), tt.want) + }) + } +} + func TestOptionsUnmarshal(t *testing.T) { for i, tt := range []struct { input []byte |