| package sysenc |
| |
| import ( |
| "bytes" |
| "encoding" |
| "encoding/binary" |
| "errors" |
| "fmt" |
| "reflect" |
| "sync" |
| "unsafe" |
| |
| "github.com/cilium/ebpf/internal" |
| |
| "golang.org/x/exp/slices" |
| ) |
| |
| // Marshal turns data into a byte slice using the system's native endianness. |
| // |
| // If possible, avoids allocations by directly using the backing memory |
| // of data. This means that the variable must not be modified for the lifetime |
| // of the returned [Buffer]. |
| // |
| // Returns an error if the data can't be turned into a byte slice according to |
| // the behaviour of [binary.Write]. |
| func Marshal(data any, size int) (Buffer, error) { |
| if data == nil { |
| return Buffer{}, errors.New("can't marshal a nil value") |
| } |
| |
| var buf []byte |
| var err error |
| switch value := data.(type) { |
| case encoding.BinaryMarshaler: |
| buf, err = value.MarshalBinary() |
| case string: |
| buf = unsafe.Slice(unsafe.StringData(value), len(value)) |
| case []byte: |
| buf = value |
| case int16: |
| buf = internal.NativeEndian.AppendUint16(make([]byte, 0, 2), uint16(value)) |
| case uint16: |
| buf = internal.NativeEndian.AppendUint16(make([]byte, 0, 2), value) |
| case int32: |
| buf = internal.NativeEndian.AppendUint32(make([]byte, 0, 4), uint32(value)) |
| case uint32: |
| buf = internal.NativeEndian.AppendUint32(make([]byte, 0, 4), value) |
| case int64: |
| buf = internal.NativeEndian.AppendUint64(make([]byte, 0, 8), uint64(value)) |
| case uint64: |
| buf = internal.NativeEndian.AppendUint64(make([]byte, 0, 8), value) |
| default: |
| if buf := unsafeBackingMemory(data); len(buf) == size { |
| return newBuffer(buf), nil |
| } |
| |
| wr := internal.NewBuffer(make([]byte, 0, size)) |
| defer internal.PutBuffer(wr) |
| |
| err = binary.Write(wr, internal.NativeEndian, value) |
| buf = wr.Bytes() |
| } |
| if err != nil { |
| return Buffer{}, err |
| } |
| |
| if len(buf) != size { |
| return Buffer{}, fmt.Errorf("%T doesn't marshal to %d bytes", data, size) |
| } |
| |
| return newBuffer(buf), nil |
| } |
| |
| var bytesReaderPool = sync.Pool{ |
| New: func() interface{} { |
| return new(bytes.Reader) |
| }, |
| } |
| |
| // Unmarshal a byte slice in the system's native endianness into data. |
| // |
| // Returns an error if buf can't be unmarshalled according to the behaviour |
| // of [binary.Read]. |
| func Unmarshal(data interface{}, buf []byte) error { |
| switch value := data.(type) { |
| case encoding.BinaryUnmarshaler: |
| return value.UnmarshalBinary(buf) |
| |
| case *string: |
| *value = string(buf) |
| return nil |
| |
| case *[]byte: |
| // Backwards compat: unmarshaling into a slice replaces the whole slice. |
| *value = slices.Clone(buf) |
| return nil |
| |
| default: |
| if dataBuf := unsafeBackingMemory(data); len(dataBuf) == len(buf) { |
| copy(dataBuf, buf) |
| return nil |
| } |
| |
| rd := bytesReaderPool.Get().(*bytes.Reader) |
| defer bytesReaderPool.Put(rd) |
| |
| rd.Reset(buf) |
| |
| if err := binary.Read(rd, internal.NativeEndian, value); err != nil { |
| return err |
| } |
| |
| if rd.Len() != 0 { |
| return fmt.Errorf("unmarshaling %T doesn't consume all data", data) |
| } |
| |
| return nil |
| } |
| } |
| |
| // unsafeBackingMemory returns the backing memory of data if it can be used |
| // instead of calling into package binary. |
| // |
| // Returns nil if the value is not a pointer or a slice, or if it contains |
| // padding or unexported fields. |
| func unsafeBackingMemory(data any) []byte { |
| if data == nil { |
| return nil |
| } |
| |
| value := reflect.ValueOf(data) |
| var valueSize int |
| switch value.Kind() { |
| case reflect.Pointer: |
| if value.IsNil() { |
| return nil |
| } |
| |
| if elemType := value.Type().Elem(); elemType.Kind() != reflect.Slice { |
| valueSize = int(elemType.Size()) |
| break |
| } |
| |
| // We're dealing with a pointer to a slice. Dereference and |
| // handle it like a regular slice. |
| value = value.Elem() |
| fallthrough |
| |
| case reflect.Slice: |
| valueSize = int(value.Type().Elem().Size()) * value.Len() |
| |
| default: |
| // Prevent Value.UnsafePointer from panicking. |
| return nil |
| } |
| |
| // Some nil pointer types currently crash binary.Size. Call it after our own |
| // code so that the panic isn't reachable. |
| // See https://github.com/golang/go/issues/60892 |
| if size := binary.Size(data); size == -1 || size != valueSize { |
| // The type contains padding or unsupported types. |
| return nil |
| } |
| |
| if hasUnexportedFields(reflect.TypeOf(data)) { |
| return nil |
| } |
| |
| // Reinterpret the pointer as a byte slice. This violates the unsafe.Pointer |
| // rules because it's very unlikely that the source data has "an equivalent |
| // memory layout". However, we can make it safe-ish because of the |
| // following reasons: |
| // - There is no alignment mismatch since we cast to a type with an |
| // alignment of 1. |
| // - There are no pointers in the source type so we don't upset the GC. |
| // - The length is verified at runtime. |
| return unsafe.Slice((*byte)(value.UnsafePointer()), valueSize) |
| } |