Skip to content

Commit

Permalink
Switch SerializeBuffer from struct to interface.
Browse files Browse the repository at this point in the history
This modifies the API for layer serialization slightly to open up the
opportunity for users to create their own SerializeBuffer implementations,
should they have a specific need to do so.
  • Loading branch information
gconnell committed Oct 3, 2013
1 parent 4ce5169 commit 63984d3
Show file tree
Hide file tree
Showing 11 changed files with 144 additions and 73 deletions.
8 changes: 6 additions & 2 deletions base.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,12 @@ func (p *Payload) DecodeFromBytes(data []byte, df DecodeFeedback) error {
// SerializeTo writes the serialized form of this layer into the
// SerializationBuffer, implementing gopacket.SerializableLayer.
// See the docs for gopacket.SerializableLayer for more info.
func (p *Payload) SerializeTo(b *SerializeBuffer, opts SerializeOptions) error {
copy(b.PrependBytes(len(*p)), *p)
func (p *Payload) SerializeTo(b SerializeBuffer, opts SerializeOptions) error {
bytes, err := b.PrependBytes(len(*p))
if err != nil {
return err
}
copy(bytes, *p)
return nil
}

Expand Down
25 changes: 25 additions & 0 deletions doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,31 @@ create DecodingLayers that are not themselves Layers... see
layers.IPv6ExtensionSkipper for an example of this.
Creating Packet Data
As well as offering the ability to decode packet data, gopacket will allow you
to create packets from scratch, as well. A number of gopacket layers implement
the SerializableLayer interface; these layers can be serialized to a []byte in
the following manner:
ip := &layers.IPv4{
SrcIP: net.IP{1, 2, 3, 4},
DstIP: net.IP{5, 6, 7, 8},
// etc...
}
buf := gopacket.NewSerializeBuffer()
opts := gopacket.SerializeOptions{} // See SerializeOptions for more details.
err := ip.SerializeTo(&buf, opts)
if err != nil { panic(err) }
fmt.Println(buf.Bytes()) // prints out a byte slice containing the serialized IPv4 layer.
SerializeTo PREPENDS the given layer onto the SerializeBuffer, and they treat
the current buffer's Bytes() slice as the payload of the serializing layer.
Therefore, you can serialize an entire packet by serializing a set of layers in
reverse order (Payload, then TCP, then IP, then Ethernet, for example). The
SerializeBuffer's SerializeLayers function is a helper that does exactly that.
A Final Note
If you use gopacket, you'll almost definitely want to make sure gopacket/layers
Expand Down
24 changes: 12 additions & 12 deletions layers/decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,37 +121,37 @@ func getSerializeLayers() []gopacket.SerializableLayer {

func BenchmarkSerializeTcpNoOptions(b *testing.B) {
slayers := getSerializeLayers()
var buf gopacket.SerializeBuffer
buf := gopacket.NewSerializeBuffer()
opts := gopacket.SerializeOptions{}
for i := 0; i < b.N; i++ {
buf.SerializeLayers(opts, slayers...)
gopacket.SerializeLayers(buf, opts, slayers...)
}
}

func BenchmarkSerializeTcpFixLengths(b *testing.B) {
slayers := getSerializeLayers()
var buf gopacket.SerializeBuffer
buf := gopacket.NewSerializeBuffer()
opts := gopacket.SerializeOptions{FixLengths: true}
for i := 0; i < b.N; i++ {
buf.SerializeLayers(opts, slayers...)
gopacket.SerializeLayers(buf, opts, slayers...)
}
}

func BenchmarkSerializeTcpComputeChecksums(b *testing.B) {
slayers := getSerializeLayers()
var buf gopacket.SerializeBuffer
buf := gopacket.NewSerializeBuffer()
opts := gopacket.SerializeOptions{ComputeChecksums: true}
for i := 0; i < b.N; i++ {
buf.SerializeLayers(opts, slayers...)
gopacket.SerializeLayers(buf, opts, slayers...)
}
}

func BenchmarkSerializeTcpFixLengthsComputeChecksums(b *testing.B) {
slayers := getSerializeLayers()
var buf gopacket.SerializeBuffer
buf := gopacket.NewSerializeBuffer()
opts := gopacket.SerializeOptions{FixLengths: true, ComputeChecksums: true}
for i := 0; i < b.N; i++ {
buf.SerializeLayers(opts, slayers...)
gopacket.SerializeLayers(buf, opts, slayers...)
}
}

Expand Down Expand Up @@ -435,8 +435,8 @@ func TestDecodeSimpleTCPPacket(t *testing.T) {
gopacket.SerializeOptions{ComputeChecksums: true},
gopacket.SerializeOptions{FixLengths: true, ComputeChecksums: true},
} {
var buf gopacket.SerializeBuffer
err := buf.SerializeLayers(opts, slayers...)
buf := gopacket.NewSerializeBuffer()
err := gopacket.SerializeLayers(buf, opts, slayers...)
if err != nil {
t.Errorf("unable to reserialize layers with opts %#v: %v", opts, err)
} else if !bytes.Equal(buf.Bytes(), testSimpleTCPPacket) {
Expand Down Expand Up @@ -474,8 +474,8 @@ func TestDecodeSmallTCPPacketHasEmptyPayload(t *testing.T) {
gopacket.SerializeOptions{ComputeChecksums: true},
gopacket.SerializeOptions{FixLengths: true, ComputeChecksums: true},
} {
var buf gopacket.SerializeBuffer
err := buf.SerializeLayers(opts, slayers...)
buf := gopacket.NewSerializeBuffer()
err := gopacket.SerializeLayers(buf, opts, slayers...)
if err != nil {
t.Errorf("unable to reserialize layers with opts %#v: %v", opts, err)
} else if !bytes.Equal(buf.Bytes(), smallPacket) {
Expand Down
7 changes: 5 additions & 2 deletions layers/dot1q.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,11 @@ func decodeDot1Q(data []byte, p gopacket.PacketBuilder) error {
// SerializeTo writes the serialized form of this layer into the
// SerializationBuffer, implementing gopacket.SerializableLayer.
// See the docs for gopacket.SerializableLayer for more info.
func (d *Dot1Q) SerializeTo(b *gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error {
bytes := b.PrependBytes(4)
func (d *Dot1Q) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error {
bytes, err := b.PrependBytes(4)
if err != nil {
return err
}
if d.VLANIdentifier > 0xFFF {
return fmt.Errorf("vlan identifier %v is too high", d.VLANIdentifier)
}
Expand Down
15 changes: 11 additions & 4 deletions layers/ethernet.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,18 @@ func (eth *Ethernet) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) er
// SerializeTo writes the serialized form of this layer into the
// SerializationBuffer, implementing gopacket.SerializableLayer.
// See the docs for gopacket.SerializableLayer for more info.
func (eth *Ethernet) SerializeTo(b *gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error {
payload := b.Bytes()
bytes := b.PrependBytes(14)
func (eth *Ethernet) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error {
if len(eth.DstMAC) != 6 {
return fmt.Errorf("invalid dst MAC: %v", eth.DstMAC)
}
if len(eth.SrcMAC) != 6 {
return fmt.Errorf("invalid src MAC: %v", eth.SrcMAC)
}
payload := b.Bytes()
bytes, err := b.PrependBytes(14)
if err != nil {
return err
}
copy(bytes, eth.DstMAC)
copy(bytes[6:], eth.SrcMAC)
if eth.Length != 0 || eth.EthernetType == EthernetTypeLLC {
Expand All @@ -87,7 +90,11 @@ func (eth *Ethernet) SerializeTo(b *gopacket.SerializeBuffer, opts gopacket.Seri
length := len(b.Bytes())
if length < 60 {
// Pad out to 60 bytes.
copy(b.AppendBytes(60-length), lotsOfZeros[:])
padding, err := b.AppendBytes(60 - length)
if err != nil {
return err
}
copy(padding, lotsOfZeros[:])
}
return nil
}
Expand Down
7 changes: 5 additions & 2 deletions layers/ip4.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,11 @@ func (i IPv4Option) String() string {

// SerializeTo writes the serialized form of this layer into the
// SerializationBuffer, implementing gopacket.SerializableLayer.
func (ip *IPv4) SerializeTo(b *gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error {
bytes := b.PrependBytes(20)
func (ip *IPv4) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error {
bytes, err := b.PrependBytes(20)
if err != nil {
return err
}
bytes[0] = (ip.Version << 4) | ip.IHL
bytes[1] = ip.TOS
if opts.FixLengths {
Expand Down
7 changes: 5 additions & 2 deletions layers/ip6.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,15 @@ const (
// SerializeTo writes the serialized form of this layer into the
// SerializationBuffer, implementing gopacket.SerializableLayer.
// See the docs for gopacket.SerializableLayer for more info.
func (ip6 *IPv6) SerializeTo(b *gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error {
func (ip6 *IPv6) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error {
payload := b.Bytes()
if ip6.HopByHop != nil {
return fmt.Errorf("unable to serialize hopbyhop for now")
}
bytes := b.PrependBytes(40)
bytes, err := b.PrependBytes(40)
if err != nil {
return err
}
bytes[0] = (ip6.Version << 4) | (ip6.TrafficClass >> 4)
bytes[1] = (ip6.TrafficClass << 4) | uint8(ip6.FlowLabel>>16)
binary.BigEndian.PutUint16(bytes[2:], uint16(ip6.FlowLabel))
Expand Down
7 changes: 5 additions & 2 deletions layers/tcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func (t *TCP) LayerType() gopacket.LayerType { return LayerTypeTCP }
// SerializeTo writes the serialized form of this layer into the
// SerializationBuffer, implementing gopacket.SerializableLayer.
// See the docs for gopacket.SerializableLayer for more info.
func (t *TCP) SerializeTo(b *gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error {
func (t *TCP) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error {
var optionLength int
for _, o := range t.Options {
switch o.OptionType {
Expand All @@ -71,7 +71,10 @@ func (t *TCP) SerializeTo(b *gopacket.SerializeBuffer, opts gopacket.SerializeOp
if opts.FixLengths {
t.Padding = lotsOfZeros[:optionLength%4]
}
bytes := b.PrependBytes(20 + optionLength + len(t.Padding))
bytes, err := b.PrependBytes(20 + optionLength + len(t.Padding))
if err != nil {
return err
}
binary.BigEndian.PutUint16(bytes, uint16(t.SrcPort))
binary.BigEndian.PutUint16(bytes[2:], uint16(t.DstPort))
binary.BigEndian.PutUint32(bytes[4:], t.Seq)
Expand Down
7 changes: 5 additions & 2 deletions layers/udp.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,12 @@ func (udp *UDP) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error {
// SerializeTo writes the serialized form of this layer into the
// SerializationBuffer, implementing gopacket.SerializableLayer.
// See the docs for gopacket.SerializableLayer for more info.
func (u *UDP) SerializeTo(b *gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error {
func (u *UDP) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error {
payload := b.Bytes()
bytes := b.PrependBytes(8)
bytes, err := b.PrependBytes(8)
if err != nil {
return err
}
binary.BigEndian.PutUint16(bytes, uint16(u.SrcPort))
binary.BigEndian.PutUint16(bytes[2:], uint16(u.DstPort))
if opts.FixLengths {
Expand Down
89 changes: 52 additions & 37 deletions writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ type SerializableLayer interface {
// SerializeTo calls SHOULD entirely ignore LayerContents and
// LayerPayload. It just serializes based on struct fields, neither
// modifying nor using contents/payload.
SerializeTo(b *SerializeBuffer, opts SerializeOptions) error
SerializeTo(b SerializeBuffer, opts SerializeOptions) error
}

// SerializeOptions provides options for behaviors that SerializableLayers may want to
Expand Down Expand Up @@ -61,12 +61,12 @@ type SerializeOptions struct {
// typical writes to byte slices using append(), where we only write at the end
// of the buffer.
//
// As seen with the Clear method in the example above, a write buffer can be
// reused by clearing it. Note, however, that a Clear call will invalidate the
// It can be reused via Clear. Note, however, that a Clear call will invalidate the
// byte slices returned by any previous Bytes() call (the same buffer is
// reused). This means that:
// reused).
//
// 1) Reusing a write buffer is extremely fast and should be efficient.
// 1) Reusing a write buffer is generally much faster than creating a new one,
// and with the default implementation it avoids additional memory allocations.
// 2) If a byte slice from a previous Bytes() call will continue to be used,
// it's better to create a new SerializeBuffer.
//
Expand All @@ -75,35 +75,57 @@ type SerializeOptions struct {
// Prepend/Append calls, then clear, then make the same calls with the same
// sizes, the second round (and all future similar rounds) shouldn't allocate
// any new memory.
//
// A SerializeBuffer may be used simply by declaring it (its zero value is fully
// functional). Use of NewSerializeBuffer is just an optimization.
type SerializeBuffer struct {
type SerializeBuffer interface {
// Bytes returns the contiguous set of bytes collected so far by Prepend/Append
// calls. The slice returned by Bytes will be modified by future Clear calls,
// so if you're planning on clearing this SerializeBuffer, you may want to copy
// Bytes somewhere safe first.
Bytes() []byte
// PrependBytes returns a set of bytes which prepends the current bytes in this
// buffer. These bytes start in an indeterminate state, so they should be
// overwritten by the caller. The caller must only call PrependBytes if they
// know they're going to immediately overwrite all bytes returned.
PrependBytes(num int) ([]byte, error)
// AppendBytes returns a set of bytes which prepends the current bytes in this
// buffer. These bytes start in an indeterminate state, so they should be
// overwritten by the caller. The caller must only call AppendBytes if they
// know they're going to immediately overwrite all bytes returned.
AppendBytes(num int) ([]byte, error)
// Clear resets the SerializeBuffer to a new, empty buffer. After a call to clear,
// the byte slice returned by any previous call to Bytes() for this buffer
// should be considered invalidated.
Clear() error
}

type serializeBuffer struct {
data []byte
start int
prepended, appended int
}

func NewSerializeBuffer(expectedPrependLength, expectedAppendLength int) *SerializeBuffer {
return &SerializeBuffer{
// NewSerializeBuffer creates a new instance of the default implementation of
// the SerializeBuffer interface.
func NewSerializeBuffer() SerializeBuffer {
return &serializeBuffer{}
}

// NewSerializeBufferExpectedSize creates a new buffer for serialization, optimized for an
// expected number of bytes prepended/appended. This tends to decrease the
// number of memory allocations made by the buffer during writes.
func NewSerializeBufferExpectedSize(expectedPrependLength, expectedAppendLength int) SerializeBuffer {
return &serializeBuffer{
data: make([]byte, expectedPrependLength, expectedPrependLength+expectedAppendLength),
start: expectedPrependLength,
prepended: expectedPrependLength,
appended: expectedAppendLength,
}
}

// Bytes returns the contiguous set of bytes collected so far by Prepend/Append
// calls.
func (w *SerializeBuffer) Bytes() []byte {
func (w *serializeBuffer) Bytes() []byte {
return w.data[w.start:]
}

// PrependBytes returns a set of bytes which prepends the current bytes in this
// buffer. These bytes start in an indeterminate state, so they should be
// overwritten by the caller. The caller must only call PrependBytes if they
// know they're going to immediately overwrite all bytes returned.
func (w *SerializeBuffer) PrependBytes(num int) []byte {
func (w *serializeBuffer) PrependBytes(num int) ([]byte, error) {
if num < 0 {
panic("num < 0")
}
Expand All @@ -121,14 +143,10 @@ func (w *SerializeBuffer) PrependBytes(num int) []byte {
w.data = newData[:toPrepend+len(w.data)]
}
w.start -= num
return w.data[w.start : w.start+num]
return w.data[w.start : w.start+num], nil
}

// AppendBytes returns a set of bytes which prepends the current bytes in this
// buffer. These bytes start in an indeterminate state, so they should be
// overwritten by the caller. The caller must only call AppendBytes if they
// know they're going to immediately overwrite all bytes returned.
func (w *SerializeBuffer) AppendBytes(num int) []byte {
func (w *serializeBuffer) AppendBytes(num int) ([]byte, error) {
if num < 0 {
panic("num < 0")
}
Expand All @@ -145,30 +163,27 @@ func (w *SerializeBuffer) AppendBytes(num int) []byte {
}
// Grow the buffer. We know it'll be under capacity given above.
w.data = w.data[:initialLength+num]
return w.data[initialLength:]
return w.data[initialLength:], nil
}

// Clear resets the SerializeBuffer to a new, empty buffer. After a call to clear,
// the byte slice returned by any previous call to Bytes() for this buffer
// should be considered invalidated.
func (w *SerializeBuffer) Clear() {
func (w *serializeBuffer) Clear() error {
w.start = w.prepended
w.data = w.data[:w.start]
return nil
}

// SerializeLayers clears the given write buffer, then writes all layers into it so
// they correctly wrap each other. Note that by clearing the buffer, it
// invalidates all slices previously returned by w.Bytes()
//
// Example:
// var buf SerializeBuffer
// var opts SerializeOptions
// buf.SerializeLayers([]SerializableLayer{a, b, c}, opts)
// buf := gopacket.NewSerializeBuffer()
// opts := gopacket.SerializeOptions{}
// gopacket.SerializeLayers(buf, []SerializableLayer{a, b, c}, opts)
// firstPayload := buf.Bytes() // contains byte representation of a(b(c))
// buf.SerializeLayers([]SerializableLayer{d, e, f}, opts)
// secondPayload := buf.Bytes() // contains byte representation of d(e(f)).
// firstPayload is now invalidated, since the SerializeLayers call Clears buf.
func (w *SerializeBuffer) SerializeLayers(opts SerializeOptions, layers ...SerializableLayer) error {
// gopacket.SerializeLayers(buf, []SerializableLayer{d, e, f}, opts)
// secondPayload := buf.Bytes() // contains byte representation of d(e(f)). firstPayload is now invalidated, since the SerializeLayers call Clears buf.
func SerializeLayers(w SerializeBuffer, opts SerializeOptions, layers ...SerializableLayer) error {
w.Clear()
for i := len(layers) - 1; i >= 0; i-- {
layer := layers[i]
Expand Down
Loading

0 comments on commit 63984d3

Please sign in to comment.