Repository: mdlayher/netlink
Pull request #151 (2019-10-05 18:11:13)

netlink: add Nested method on Attribute{Decoder,Encoder}
Signed-off-by: Matt Layher <mdlayher@gmail.com>

See the associated issue.


Patch:
diff --git a/attribute.go b/attribute.go
index 50502a3..b2ac945 100644
--- a/attribute.go
+++ b/attribute.go
@@ -17,7 +17,9 @@ type Attribute struct {
 	// Length of an Attribute, including this field and Type.
 	Length uint16
 
-	// The type of this Attribute, typically matched to a constant.
+	// The type of this Attribute, typically matched to a constant. Note that
+	// flags such as Nested and NetByteOrder must be handled manually when
+	// working with Attribute structures directly.
 	Type uint16
 
 	// An arbitrary payload which is specified by Type.
@@ -70,6 +72,9 @@ func (a *Attribute) unmarshal(b []byte) error {
 // MarshalAttributes packs a slice of Attributes into a single byte slice.
 // In most cases, the Length field of each Attribute should be set to 0, so it
 // can be calculated and populated automatically for each Attribute.
+//
+// It is recommend to use the AttributeEncoder type where possible instead of
+// calling MarshalAttributes and using package nlenc functions directly.
 func MarshalAttributes(attrs []Attribute) ([]byte, error) {
 	// Count how many bytes we should allocate to store each attribute's contents.
 	var c int
@@ -184,10 +189,18 @@ func (ad *AttributeDecoder) Next() bool {
 
 // Type returns the Attribute.Type field of the current netlink attribute
 // pointed to by the decoder.
+//
+// Type masks off the high bits of the netlink attribute type which may contain
+// the Nested and NetByteOrder flags. If you need direct access to these flags,
+// consider using UnmarshalAttributes instead.
 func (ad *AttributeDecoder) Type() uint16 {
-	return ad.attr().Type
+	// Mask off any flags stored in the high bits.
+	return ad.attr().Type & attrTypeMask
 }
 
+// Len returns the number of netlink attributes pointed to by the decoder.
+func (ad *AttributeDecoder) Len() int { return len(ad.attrs) }
+
 // attr returns the current Attribute pointed to by the decoder.
 func (ad *AttributeDecoder) attr() Attribute {
 	return ad.attrs[ad.i-1]
@@ -316,6 +329,29 @@ func (ad *AttributeDecoder) Do(fn func(b []byte) error) {
 	}
 }
 
+// Nested decodes data into a nested AttributeDecoder to handle nested netlink
+// attributes. When calling Nested, the Err method does not need to be called on
+// the nested AttributeDecoder.
+//
+// The nested AttributeDecoder nad inherits the same ByteOrder setting as the
+// top-level AttributeDecoder ad.
+func (ad *AttributeDecoder) Nested(fn func(nad *AttributeDecoder) error) {
+	// Because we are wrapping Do, there is no need to check ad.err immediately.
+	ad.Do(func(b []byte) error {
+		nad, err := NewAttributeDecoder(b)
+		if err != nil {
+			return err
+		}
+		nad.ByteOrder = ad.ByteOrder
+
+		if err := fn(nad); err != nil {
+			return err
+		}
+
+		return nad.Err()
+	})
+}
+
 // An AttributeEncoder provides a safe way to encode attributes.
 //
 // It is recommended to use an AttributeEncoder where possible instead of
@@ -458,6 +494,26 @@ func (ae *AttributeEncoder) Do(typ uint16, fn func() ([]byte, error)) {
 	})
 }
 
+// Nested embeds data produced by a nested AttributeEncoder and flags that data
+// with the Nested flag. When calling Nested, the Encode method should not be
+// called on the nested AttributeEncoder.
+//
+// The nested AttributeEncoder nae inherits the same ByteOrder setting as the
+// top-level AttributeEncoder ae.
+func (ae *AttributeEncoder) Nested(typ uint16, fn func(nae *AttributeEncoder) error) {
+	// Because we are wrapping Do, there is no need to check ae.err immediately.
+	ae.Do(Nested|typ, func() ([]byte, error) {
+		nae := NewAttributeEncoder()
+		nae.ByteOrder = ae.ByteOrder
+
+		if err := fn(nae); err != nil {
+			return nil, err
+		}
+
+		return nae.Encode()
+	})
+}
+
 // Encode returns the encoded bytes representing the attributes.
 func (ae *AttributeEncoder) Encode() ([]byte, error) {
 	if ae.err != nil {
diff --git a/message.go b/message.go
index 4cd2972..9cf81ec 100644
--- a/message.go
+++ b/message.go
@@ -7,6 +7,16 @@ import (
 	"github.com/mdlayher/netlink/nlenc"
 )
 
+// Flags which may apply to netlink attribute types when communicating with
+// certain netlink families.
+const (
+	Nested       uint16 = 0x8000
+	NetByteOrder uint16 = 0x4000
+
+	// attrTypeMask masks off Type bits used for the above flags.
+	attrTypeMask uint16 = 0x3fff
+)
+
 // Various errors which may occur when attempting to marshal or unmarshal
 // a Message to and from its binary form.
 var (


Test patch:
diff --git a/attribute_test.go b/attribute_test.go
index 3c00fb5..eeecab2 100644
--- a/attribute_test.go
+++ b/attribute_test.go
@@ -686,6 +686,74 @@ func TestAttributeDecoderOK(t *testing.T) {
 				}
 			},
 		},
+		{
+			name: "nested",
+			attrs: []Attribute{
+				// Nested attributes.
+				{
+					Type: Nested | 1,
+					Data: func() []byte {
+						nb, err := MarshalAttributes([]Attribute{{
+							Type: 1,
+							Data: nlenc.Uint32Bytes(2),
+						}})
+						if err != nil {
+							panicf("failed to marshal nested test attributes: %v", err)
+						}
+
+						b, err := MarshalAttributes([]Attribute{
+							{
+								Type: 1,
+								Data: nlenc.Uint16Bytes(1),
+							},
+							{
+								Type: Nested | 2,
+								Data: nb,
+							},
+						})
+						if err != nil {
+							panicf("failed to marshal test attributes: %v", err)
+						}
+
+						return b
+					}(),
+				},
+			},
+			fn: func(ad *AttributeDecoder) {
+				if diff := cmp.Diff(uint16(1), ad.Type()); diff != "" {
+					panicf("unexpected attribute type (-want +got):\n%s", diff)
+				}
+
+				ad.Nested(func(nad *AttributeDecoder) error {
+					for nad.Next() {
+						switch t := nad.Type(); t {
+						case 1:
+							if diff := cmp.Diff(uint16(1), nad.Uint16()); diff != "" {
+								panicf("unexpected nested uint16 (-want +got):\n%s", diff)
+							}
+						case 2:
+							nad.Nested(func(nnad *AttributeDecoder) error {
+								for nad.Next() {
+									if diff := cmp.Diff(uint16(1), nnad.Type()); diff != "" {
+										panicf("unexpected nested attribute type (-want +got):\n%s", diff)
+									}
+
+									if diff := cmp.Diff(uint32(2), nnad.Uint32()); diff != "" {
+										panicf("unexpected nested uint32 (-want +got):\n%s", diff)
+									}
+								}
+
+								return nil
+							})
+						default:
+							panicf("unhandled nested attribute type: %d", t)
+						}
+					}
+
+					return nil
+				})
+			},
+		},
 	}
 
 	for _, tt := range tests {
@@ -700,6 +768,11 @@ func TestAttributeDecoderOK(t *testing.T) {
 				t.Fatalf("failed to create attribute decoder: %v", err)
 			}
 
+			// Len should always report the same number of input attributes.
+			if diff := cmp.Diff(len(tt.attrs), ad.Len()); diff != "" {
+				t.Fatalf("unexpected  (-want +got):\n%s", diff)
+			}
+
 			for ad.Next() {
 				tt.fn(ad)
 			}
@@ -907,6 +980,50 @@ func TestAttributeEncoderOK(t *testing.T) {
 				})
 			},
 		},
+		{
+			name: "nested",
+			attrs: []Attribute{
+				// Nested attributes.
+				{
+					Type: Nested | 1,
+					Data: func() []byte {
+						nb, err := MarshalAttributes([]Attribute{{
+							Type: 1,
+							Data: nlenc.Uint32Bytes(2),
+						}})
+						if err != nil {
+							panicf("failed to marshal nested test attributes: %v", err)
+						}
+
+						b, err := MarshalAttributes([]Attribute{
+							{
+								Type: 1,
+								Data: nlenc.Uint16Bytes(1),
+							},
+							{
+								Type: Nested | 2,
+								Data: nb,
+							},
+						})
+						if err != nil {
+							panicf("failed to marshal test attributes: %v", err)
+						}
+
+						return b
+					}(),
+				},
+			},
+			fn: func(ae *AttributeEncoder) {
+				ae.Nested(1, func(nae *AttributeEncoder) error {
+					nae.Uint16(1, 1)
+					nae.Nested(2, func(nnae *AttributeEncoder) error {
+						nnae.Uint32(1, 2)
+						return nil
+					})
+					return nil
+				})
+			},
+		},
 	}
 
 	for _, tt := range tests {
diff --git a/example_attributedecoder_test.go b/example_attributedecoder_test.go
index 1b8d24e..c7356d5 100644
--- a/example_attributedecoder_test.go
+++ b/example_attributedecoder_test.go
@@ -19,32 +19,23 @@ type decodeOut struct {
 	Nested decodeNested
 }
 
-// decode is an example function used to adapt the ad.Do method to decode an
+// decode is an example function used to adapt the ad.Nested method to decode an
 // arbitrary structure.
-func (n *decodeNested) decode() func(b []byte) error {
-	return func(b []byte) error {
-		// Create an internal netlink.AttributeDecoder that operates on a
-		// nested set of attributes passed by the external decoder.
-		ad, err := netlink.NewAttributeDecoder(b)
-		if err != nil {
-			return fmt.Errorf("failed to create nested attribute decoder: %v", err)
-		}
-
-		// Iterate attributes until completion, checking the type of each
-		// and decoding them as appropriate.
-		for ad.Next() {
-			switch ad.Type() {
-			// A and B are both uint32 values, so decode them as such.
-			case 1:
-				n.A = ad.Uint32()
-			case 2:
-				n.B = ad.Uint32()
-			}
+func (n *decodeNested) decode(ad *netlink.AttributeDecoder) error {
+	// Iterate over the attributes, checking the type of each attribute and
+	// decoding them as appropriate.
+	for ad.Next() {
+		switch ad.Type() {
+		// A and B are both uint32 values, so decode them as such.
+		case 1:
+			n.A = ad.Uint32()
+		case 2:
+			n.B = ad.Uint32()
 		}
-
-		// Return any error encountered while decoding.
-		return ad.Err()
 	}
+
+	// No need to call ad.Err directly.
+	return nil
 }
 
 // This example demonstrates using a netlink.AttributeDecoder to decode packed
@@ -74,7 +65,7 @@ func ExampleAttributeDecoder_decode() {
 		case 3:
 			// Nested is a nested structure, so we will use a method on the
 			// nested type along with ad.Do to decode it in a concise way.
-			ad.Do(out.Nested.decode())
+			ad.Nested(out.Nested.decode)
 		}
 	}
 
diff --git a/example_attributeencoder_test.go b/example_attributeencoder_test.go
index 4af176c..84d2ada 100644
--- a/example_attributeencoder_test.go
+++ b/example_attributeencoder_test.go
@@ -20,21 +20,13 @@ type encodeOut struct {
 	Nested encodeNested
 }
 
-// encode is an example function used to adapt the ae.Do method
+// encode is an example function used to adapt the ae.Nested method
 // to encode an arbitrary structure.
-func (n encodeNested) encode() func() ([]byte, error) {
-	return func() ([]byte, error) {
-		// Create an internal, nested netlink.NewAttributeEncoder that
-		// operates on the nested set of attributes.
-		ae := netlink.NewAttributeEncoder()
-
-		// Encode the fields of the nested structure.
-		ae.Uint32(1, n.A)
-		ae.Uint32(2, n.B)
-
-		// Return the encoded attributes, and any error encountered.
-		return ae.Encode()
-	}
+func (n encodeNested) encode(ae *netlink.AttributeEncoder) error {
+	// Encode the fields of the nested structure.
+	ae.Uint32(1, n.A)
+	ae.Uint32(2, n.B)
+	return nil
 }
 
 func ExampleAttributeEncoder_encode() {
@@ -56,8 +48,8 @@ func ExampleAttributeEncoder_encode() {
 	// Encode the String attribute as a string.
 	ae.String(2, o.String)
 	// Nested is a nested structure, so we will use the encodeNested type's
-	// encode method with ae.Do to encode it in a concise way.
-	ae.Do(3, o.Nested.encode())
+	// encode method with ae.Nested to encode it in a concise way.
+	ae.Nested(3, o.Nested.encode)
 
 	// Any errors encountered during encoding (including any errors from
 	// encoding nested attributes) will be returned here.
@@ -66,10 +58,10 @@ func ExampleAttributeEncoder_encode() {
 		log.Fatalf("failed to encode attributes: %v", err)
 	}
 
-	fmt.Printf("Encoded netlink message follows:\n%s", hex.Dump(b))
+	fmt.Printf("netlink attributes:\n%s", hex.Dump(b))
 
-	// Output: Encoded netlink message follows:
+	// Output: netlink attributes:
 	// 00000000  06 00 01 00 01 00 00 00  10 00 02 00 68 65 6c 6c  |............hell|
-	// 00000010  6f 20 77 6f 72 6c 64 00  14 00 03 00 08 00 01 00  |o world.........|
+	// 00000010  6f 20 77 6f 72 6c 64 00  14 00 03 80 08 00 01 00  |o world.........|
 	// 00000020  02 00 00 00 08 00 02 00  03 00 00 00              |............|
 }