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 }