summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--dhcpv4/option_vivc.go102
-rw-r--r--dhcpv4/option_vivc_test.go64
-rw-r--r--dhcpv4/options.go2
3 files changed, 168 insertions, 0 deletions
diff --git a/dhcpv4/option_vivc.go b/dhcpv4/option_vivc.go
new file mode 100644
index 0000000..9be8557
--- /dev/null
+++ b/dhcpv4/option_vivc.go
@@ -0,0 +1,102 @@
+package dhcpv4
+
+import (
+ "bytes"
+ "encoding/binary"
+ "fmt"
+)
+
+// This option implements the Vendor-Identifying Vendor Class Option
+// https://tools.ietf.org/html/rfc3925
+
+// VIVCIdentifier represents one Vendor-Identifying vendor class option.
+type VIVCIdentifier struct {
+ EntID uint32
+ Data []byte
+}
+
+// OptVIVC represents the DHCP message type option.
+type OptVIVC struct {
+ Identifiers []VIVCIdentifier
+}
+
+// ParseOptVIVC contructs an OptVIVC tsruct from a sequence of bytes and returns
+// it, or an error.
+func ParseOptVIVC(data []byte) (*OptVIVC, error) {
+ if len(data) < 2 {
+ return nil, ErrShortByteStream
+ }
+ code := OptionCode(data[0])
+ if code != OptionVendorIdentifyingVendorClass {
+ return nil, fmt.Errorf("expected code %v, got %v", OptionVendorIdentifyingVendorClass, code)
+ }
+ length := int(data[1])
+ data = data[2:]
+
+ if length != len(data) {
+ return nil, ErrShortByteStream
+ }
+
+ ids := []VIVCIdentifier{}
+ for len(data) > 5 {
+ entID := binary.BigEndian.Uint32(data[0:4])
+ idLen := int(data[4])
+ data = data[5:]
+
+ if idLen > len(data) {
+ return nil, ErrShortByteStream
+ }
+
+ ids = append(ids, VIVCIdentifier{EntID: entID, Data: data[:idLen]})
+ data = data[idLen:]
+ }
+
+ if len(data) != 0 {
+ return nil, ErrShortByteStream
+ }
+
+ return &OptVIVC{Identifiers: ids}, nil
+}
+
+// Code returns the option code.
+func (o *OptVIVC) Code() OptionCode {
+ return OptionVendorIdentifyingVendorClass
+}
+
+// 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:]
+ 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:]
+ }
+ return buf
+}
+
+// String returns a human-readable string for this option.
+func (o *OptVIVC) String() string {
+ buf := bytes.Buffer{}
+ fmt.Fprintf(&buf, "Vendor-Identifying Vendor Class ->")
+
+ for _, id := range o.Identifiers {
+ fmt.Fprintf(&buf, " %d:'%s',", id.EntID, id.Data)
+ }
+
+ return buf.String()[:buf.Len()-1]
+}
+
+// Length returns the length of the data portion (excluding option code and byte
+// for length, if any).
+func (o *OptVIVC) Length() int {
+ n := 0
+ for _, id := range o.Identifiers {
+ // each class has a header of endID (4 bytes) and length (1 byte)
+ n += 5 + len(id.Data)
+ }
+ return n
+}
diff --git a/dhcpv4/option_vivc_test.go b/dhcpv4/option_vivc_test.go
new file mode 100644
index 0000000..14ded61
--- /dev/null
+++ b/dhcpv4/option_vivc_test.go
@@ -0,0 +1,64 @@
+package dhcpv4
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+var (
+ sampleVIVCOpt = OptVIVC{
+ Identifiers: []VIVCIdentifier{
+ {EntID: 9, Data: []byte("CiscoIdentifier")},
+ {EntID: 18, Data: []byte("WellfleetIdentifier")},
+ },
+ }
+ 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',
+ 0x0, 0x0, 0x0, 0x12, // enterprise id 18
+ 0x13, // length
+ 'W', 'e', 'l', 'l', 'f', 'l', 'e', 'e', 't', 'I', 'd', 'e', 'n', 't', 'i', 'f', 'i', 'e', 'r',
+ }
+)
+
+func TestOptVIVCInterfaceMethods(t *testing.T) {
+ require.Equal(t, OptionVendorIdentifyingVendorClass, sampleVIVCOpt.Code(), "Code")
+ require.Equal(t, 44, sampleVIVCOpt.Length(), "Length")
+ require.Equal(t, sampleVIVCOptRaw, sampleVIVCOpt.ToBytes(), "ToBytes")
+}
+
+func TestParseOptVICO(t *testing.T) {
+ o, err := ParseOptVIVC(sampleVIVCOptRaw)
+ require.NoError(t, err)
+ require.Equal(t, &sampleVIVCOpt, o)
+
+ // Short byte stream
+ data := []byte{byte(OptionVendorIdentifyingVendorClass)}
+ _, err = ParseOptVIVC(data)
+ require.Error(t, err, "should get error from short byte stream")
+
+ // Wrong code
+ data = []byte{54, 2, 1, 1}
+ _, err = ParseOptVIVC(data)
+ require.Error(t, err, "should get error from wrong code")
+
+ // Bad length
+ data = []byte{byte(OptionVendorIdentifyingVendorClass), 6, 1, 1, 1}
+ _, err = ParseOptVIVC(data)
+ require.Error(t, err, "should get error from bad length")
+
+ // Identifier len too long
+ data = make([]byte, len(sampleVIVCOptRaw))
+ copy(data, sampleVIVCOptRaw)
+ data[6] = 40
+ _, err = ParseOptVIVC(data)
+ require.Error(t, err, "should get error from bad length")
+}
+
+func TestOptVIVCString(t *testing.T) {
+ require.Equal(t, "Vendor-Identifying Vendor Class -> 9:'CiscoIdentifier', 18:'WellfleetIdentifier'",
+ sampleVIVCOpt.String())
+}
diff --git a/dhcpv4/options.go b/dhcpv4/options.go
index efbfb74..528c175 100644
--- a/dhcpv4/options.go
+++ b/dhcpv4/options.go
@@ -56,6 +56,8 @@ func ParseOption(data []byte) (Option, error) {
opt, err = ParseOptClassIdentifier(data)
case OptionDomainName:
opt, err = ParseOptDomainName(data)
+ case OptionVendorIdentifyingVendorClass:
+ opt, err = ParseOptVIVC(data)
default:
opt, err = ParseOptionGeneric(data)
}