Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 30 additions & 5 deletions dhcpv6/option_userclass.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,41 @@ func (op *OptUserClass) String() string {
return fmt.Sprintf("%s: [%s]", op.Code(), strings.Join(ucStrings, ", "))
}

// FromBytes builds an OptUserClass structure from a sequence of bytes. The
// input data does not include option code and length bytes.
// FromBytes builds an OptUserClass structure from a sequence of bytes.
// The input data does not include option code and length bytes.
Comment thread
damyan marked this conversation as resolved.
// It first attempts to parse the data as RFC 8415-compliant (each user class
// preceded by a 16-bit length field). If no complete user class can be
// extracted, it falls back to treating the entire data as a single user class
// (MS-compatible, non-RFC-compliant behavior). Note that ToBytes always
// serializes in RFC 8415-compliant format regardless of how the data was
// originally parsed.
func (op *OptUserClass) FromBytes(data []byte) error {
if len(data) == 0 {
return fmt.Errorf("%w: user class option must not be empty", uio.ErrBufferTooShort)
}
buf := uio.NewBigEndianBuffer(data)
var userClasses [][]byte
var bufferTooShort bool
for buf.Has(2) {
len := buf.Read16()
op.UserClasses = append(op.UserClasses, buf.CopyN(int(len)))
length := buf.Read16()
if int(length) > buf.Len() {
bufferTooShort = true
break
Comment thread
damyan marked this conversation as resolved.
}
uc := buf.CopyN(int(length))
userClasses = append(userClasses, uc)
}
return buf.FinError()
if len(userClasses) > 0 {
if bufferTooShort {
return fmt.Errorf("%w: user class option too short", uio.ErrBufferTooShort)
}
op.UserClasses = userClasses
return buf.FinError()
}
// MS-compatible fallback: no valid length-prefixed classes found,
// treat entire data as a single user class.
// Note: when no valid classes were parsed, we cannot distinguish corrupt
// RFC data from genuine MS-format data, so we treat it as MS-compatible.
op.UserClasses = [][]byte{data}
return nil
}
25 changes: 25 additions & 0 deletions dhcpv6/option_userclass_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,28 @@ func TestOptUserClassString(t *testing.T) {
"String() should contain the list of user classes",
)
}

func TestOptUserClassTruncatedAfterValid(t *testing.T) {
// Valid entry "ABC" followed by a truncated entry (declared length 5, only 1 byte)
data := []byte{0, 3, 'A', 'B', 'C', 0, 5, 'X'}
var opt OptUserClass
err := opt.FromBytes(data)
require.Error(t, err)
require.True(t, errors.Is(err, uio.ErrBufferTooShort), "expected ErrBufferTooShort, got %v", err)
}

func TestOptUserClassMicrosoftFormat(t *testing.T) {
data := []byte{
'l', 'i', 'n', 'u', 'x', 'b', 'o', 'o', 't',
}
var opt OptUserClass
err := opt.FromBytes(data)
require.NoError(t, err)

require.Contains(
t,
opt.String(),
"User Class: [linuxboot]",
"String() should contain the list of user classes",
)
}