github.com/cilium/ebpf@v0.15.1-0.20240517100537-8079b37aa138/internal/sysenc/marshal.go (about)

     1  package sysenc
     2  
     3  import (
     4  	"bytes"
     5  	"encoding"
     6  	"encoding/binary"
     7  	"errors"
     8  	"fmt"
     9  	"reflect"
    10  	"slices"
    11  	"sync"
    12  	"unsafe"
    13  
    14  	"github.com/cilium/ebpf/internal"
    15  )
    16  
    17  // Marshal turns data into a byte slice using the system's native endianness.
    18  //
    19  // If possible, avoids allocations by directly using the backing memory
    20  // of data. This means that the variable must not be modified for the lifetime
    21  // of the returned [Buffer].
    22  //
    23  // Returns an error if the data can't be turned into a byte slice according to
    24  // the behaviour of [binary.Write].
    25  func Marshal(data any, size int) (Buffer, error) {
    26  	if data == nil {
    27  		return Buffer{}, errors.New("can't marshal a nil value")
    28  	}
    29  
    30  	var buf []byte
    31  	var err error
    32  	switch value := data.(type) {
    33  	case encoding.BinaryMarshaler:
    34  		buf, err = value.MarshalBinary()
    35  	case string:
    36  		buf = unsafe.Slice(unsafe.StringData(value), len(value))
    37  	case []byte:
    38  		buf = value
    39  	case int16:
    40  		buf = internal.NativeEndian.AppendUint16(make([]byte, 0, 2), uint16(value))
    41  	case uint16:
    42  		buf = internal.NativeEndian.AppendUint16(make([]byte, 0, 2), value)
    43  	case int32:
    44  		buf = internal.NativeEndian.AppendUint32(make([]byte, 0, 4), uint32(value))
    45  	case uint32:
    46  		buf = internal.NativeEndian.AppendUint32(make([]byte, 0, 4), value)
    47  	case int64:
    48  		buf = internal.NativeEndian.AppendUint64(make([]byte, 0, 8), uint64(value))
    49  	case uint64:
    50  		buf = internal.NativeEndian.AppendUint64(make([]byte, 0, 8), value)
    51  	default:
    52  		if buf := unsafeBackingMemory(data); len(buf) == size {
    53  			return newBuffer(buf), nil
    54  		}
    55  
    56  		wr := internal.NewBuffer(make([]byte, 0, size))
    57  		defer internal.PutBuffer(wr)
    58  
    59  		err = binary.Write(wr, internal.NativeEndian, value)
    60  		buf = wr.Bytes()
    61  	}
    62  	if err != nil {
    63  		return Buffer{}, err
    64  	}
    65  
    66  	if len(buf) != size {
    67  		return Buffer{}, fmt.Errorf("%T doesn't marshal to %d bytes", data, size)
    68  	}
    69  
    70  	return newBuffer(buf), nil
    71  }
    72  
    73  var bytesReaderPool = sync.Pool{
    74  	New: func() interface{} {
    75  		return new(bytes.Reader)
    76  	},
    77  }
    78  
    79  // Unmarshal a byte slice in the system's native endianness into data.
    80  //
    81  // Returns an error if buf can't be unmarshalled according to the behaviour
    82  // of [binary.Read].
    83  func Unmarshal(data interface{}, buf []byte) error {
    84  	switch value := data.(type) {
    85  	case encoding.BinaryUnmarshaler:
    86  		return value.UnmarshalBinary(buf)
    87  
    88  	case *string:
    89  		*value = string(buf)
    90  		return nil
    91  
    92  	case *[]byte:
    93  		// Backwards compat: unmarshaling into a slice replaces the whole slice.
    94  		*value = slices.Clone(buf)
    95  		return nil
    96  
    97  	default:
    98  		if dataBuf := unsafeBackingMemory(data); len(dataBuf) == len(buf) {
    99  			copy(dataBuf, buf)
   100  			return nil
   101  		}
   102  
   103  		rd := bytesReaderPool.Get().(*bytes.Reader)
   104  		defer bytesReaderPool.Put(rd)
   105  
   106  		rd.Reset(buf)
   107  
   108  		if err := binary.Read(rd, internal.NativeEndian, value); err != nil {
   109  			return err
   110  		}
   111  
   112  		if rd.Len() != 0 {
   113  			return fmt.Errorf("unmarshaling %T doesn't consume all data", data)
   114  		}
   115  
   116  		return nil
   117  	}
   118  }
   119  
   120  // unsafeBackingMemory returns the backing memory of data if it can be used
   121  // instead of calling into package binary.
   122  //
   123  // Returns nil if the value is not a pointer or a slice, or if it contains
   124  // padding or unexported fields.
   125  func unsafeBackingMemory(data any) []byte {
   126  	if data == nil {
   127  		return nil
   128  	}
   129  
   130  	value := reflect.ValueOf(data)
   131  	var valueSize int
   132  	switch value.Kind() {
   133  	case reflect.Pointer:
   134  		if value.IsNil() {
   135  			return nil
   136  		}
   137  
   138  		if elemType := value.Type().Elem(); elemType.Kind() != reflect.Slice {
   139  			valueSize = int(elemType.Size())
   140  			break
   141  		}
   142  
   143  		// We're dealing with a pointer to a slice. Dereference and
   144  		// handle it like a regular slice.
   145  		value = value.Elem()
   146  		fallthrough
   147  
   148  	case reflect.Slice:
   149  		valueSize = int(value.Type().Elem().Size()) * value.Len()
   150  
   151  	default:
   152  		// Prevent Value.UnsafePointer from panicking.
   153  		return nil
   154  	}
   155  
   156  	// Some nil pointer types currently crash binary.Size. Call it after our own
   157  	// code so that the panic isn't reachable.
   158  	// See https://github.com/golang/go/issues/60892
   159  	if size := binary.Size(data); size == -1 || size != valueSize {
   160  		// The type contains padding or unsupported types.
   161  		return nil
   162  	}
   163  
   164  	if hasUnexportedFields(reflect.TypeOf(data)) {
   165  		return nil
   166  	}
   167  
   168  	// Reinterpret the pointer as a byte slice. This violates the unsafe.Pointer
   169  	// rules because it's very unlikely that the source data has "an equivalent
   170  	// memory layout". However, we can make it safe-ish because of the
   171  	// following reasons:
   172  	//  - There is no alignment mismatch since we cast to a type with an
   173  	//    alignment of 1.
   174  	//  - There are no pointers in the source type so we don't upset the GC.
   175  	//  - The length is verified at runtime.
   176  	return unsafe.Slice((*byte)(value.UnsafePointer()), valueSize)
   177  }