// Copyright 2012 Google, Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file in the root of the source
// tree.

package gopacket

import (
	"fmt"
)

// SerializableLayer allows its implementations to be written out as a set of bytes,
// so those bytes may be sent on the wire or otherwise used by the caller.
// SerializableLayer is implemented by certain Layer types, and can be encoded to
// bytes using the LayerWriter object.
type SerializableLayer interface {
	// SerializeTo writes this layer to a slice, growing that slice if necessary
	// to make it fit the layer's data.
	//  Args:
	//   b:  SerializeBuffer to write this layer on to.  When called, b.Bytes()
	//     is the payload this layer should wrap, if any.  Note that this
	//     layer can either prepend itself (common), append itself
	//     (uncommon), or both (sometimes padding or footers are required at
	//     the end of packet data). It's also possible (though probably very
	//     rarely needed) to overwrite any bytes in the current payload.
	//     After this call, b.Bytes() should return the byte encoding of
	//     this layer wrapping the original b.Bytes() payload.
	//   opts:  options to use while writing out data.
	//  Returns:
	//   error if a problem was encountered during encoding.  If an error is
	//   returned, the bytes in data should be considered invalidated, and
	//   not used.
	//
	// 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
}

// SerializeOptions provides options for behaviors that SerializableLayers may want to
// implement.
type SerializeOptions struct {
	// FixLengths determines whether, during serialization, layers should fix
	// the values for any length field that depends on the payload.
	FixLengths bool
	// ComputeChecksums determines whether, during serialization, layers
	// should recompute checksums based on their payloads.
	ComputeChecksums bool
}

// SerializeBuffer is a helper used by gopacket for writing out packet layers.
// SerializeBuffer starts off as an empty []byte.  Subsequent calls to PrependBytes
// return byte slices before the current Bytes(), AppendBytes returns byte
// slices after.
//
// Byte slices returned by PrependBytes/AppendBytes are NOT zero'd out, so if
// you want to make sure they're all zeros, set them as such.
//
// SerializeBuffer is specifically designed to handle packet writing, where unlike
// with normal writes it's easier to start writing at the inner-most layer and
// work out, meaning that we often need to prepend bytes.  This runs counter to
// typical writes to byte slices using append(), where we only write at the end
// of the buffer.
//
// 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).
//
//  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.
//
// The Clear method is specifically designed to minimize memory allocations for
// similar later workloads on the SerializeBuffer.  IE: if you make a set of
// 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.
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 appends 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
}

// 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,
	}
}

func (w *serializeBuffer) Bytes() []byte {
	return w.data[w.start:]
}

func (w *serializeBuffer) PrependBytes(num int) ([]byte, error) {
	if num < 0 {
		panic("num < 0")
	}
	if w.start < num {
		toPrepend := w.prepended
		if toPrepend < num {
			toPrepend = num
		}
		w.prepended += toPrepend
		length := cap(w.data) + toPrepend
		newData := make([]byte, length)
		newStart := w.start + toPrepend
		copy(newData[newStart:], w.data[w.start:])
		w.start = newStart
		w.data = newData[:toPrepend+len(w.data)]
	}
	w.start -= num
	return w.data[w.start : w.start+num], nil
}

func (w *serializeBuffer) AppendBytes(num int) ([]byte, error) {
	if num < 0 {
		panic("num < 0")
	}
	initialLength := len(w.data)
	if cap(w.data)-initialLength < num {
		toAppend := w.appended
		if toAppend < num {
			toAppend = num
		}
		w.appended += toAppend
		newData := make([]byte, cap(w.data)+toAppend)
		copy(newData[w.start:], w.data[w.start:])
		w.data = newData[:initialLength]
	}
	// Grow the buffer.  We know it'll be under capacity given above.
	w.data = w.data[:initialLength+num]
	return w.data[initialLength:], nil
}

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:
//   buf := gopacket.NewSerializeBuffer()
//   opts := gopacket.SerializeOptions{}
//   gopacket.SerializeLayers(buf, opts, a, b, c)
//   firstPayload := buf.Bytes()  // contains byte representation of a(b(c))
//   gopacket.SerializeLayers(buf, opts, d, e, f)
//   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]
		err := layer.SerializeTo(w, opts)
		if err != nil {
			return err
		}
	}
	return nil
}

// SerializePacket is a convenience function that calls SerializeLayers
// on packet's Layers().
// It returns an error if one of the packet layers is not a SerializebleLayer.
func SerializePacket(buf SerializeBuffer, opts SerializeOptions, packet Packet) error {
	sls := []SerializableLayer{}
	for _, layer := range packet.Layers() {
		sl, ok := layer.(SerializableLayer)
		if !ok {
			return fmt.Errorf("layer %s is not serializable", layer.LayerType().String())
		}
		sls = append(sls, sl)
	}
	return SerializeLayers(buf, opts, sls...)
}
