diff --git a/internal/pipewire/core.go b/internal/pipewire/core.go new file mode 100644 index 0000000..a833009 --- /dev/null +++ b/internal/pipewire/core.go @@ -0,0 +1,79 @@ +package pipewire + +/* pipewire/core.h */ + +const ( + PW_TYPE_INTERFACE_Core = PW_TYPE_INFO_INTERFACE_BASE + "Core" + PW_TYPE_INTERFACE_Registry = PW_TYPE_INFO_INTERFACE_BASE + "Registry" + PW_CORE_PERM_MASK = PW_PERM_R | PW_PERM_X | PW_PERM_M + PW_VERSION_CORE = 4 + PW_VERSION_REGISTRY = 3 + + PW_DEFAULT_REMOTE = "pipewire-0" + PW_ID_CORE = 0 + PW_ID_ANY = Word(0xffffffff) +) + +const ( + PW_CORE_CHANGE_MASK_PROPS = 1 << iota + + PW_CORE_CHANGE_MASK_ALL = 1< math.MaxUint32 { + return data, UnsupportedSizeError(wireSize) + } + binary.NativeEndian.PutUint32(rData[len(data)-4:len(data)], Word(wireSize)) + rData = append(rData, make([]byte, paddingSize)...) + + return rData, nil +} + +// marshalValueAppendRaw implements [MarshalAppend] on [reflect.Value] without the size prefix. +func marshalValueAppendRaw(data []byte, v reflect.Value) ([]byte, error) { + switch v.Kind() { + + case reflect.Int32: + data = binary.NativeEndian.AppendUint32(data, SPA_TYPE_Int) + data = binary.NativeEndian.AppendUint32(data, Word(v.Int())) + return data, nil + + case reflect.Struct: + data = binary.NativeEndian.AppendUint32(data, SPA_TYPE_Struct) + var err error + for i := 0; i < v.NumField(); i++ { + data, err = marshalValueAppend(data, v.Field(i)) + if err != nil { + return data, err + } + } + return data, nil + + case reflect.Pointer: + if v.IsNil() { + data = binary.NativeEndian.AppendUint32(data, SPA_TYPE_None) + return data, nil + } + return marshalValueAppendRaw(data, v.Elem()) + + default: + return data, &UnsupportedTypeError{v.Type()} + } +} + +// An InvalidUnmarshalError describes an invalid argument passed to [Unmarshal]. +// (The argument to [Unmarshal] must be a non-nil pointer.) +type InvalidUnmarshalError struct{ Type reflect.Type } + +func (e *InvalidUnmarshalError) Error() string { + if e.Type == nil { + return "attempting to unmarshal to nil" + } + + if e.Type.Kind() != reflect.Pointer { + return "attempting to unmarshal to non-pointer type: " + e.Type.String() + } + return "attempting to unmarshal to nil " + e.Type.String() +} + +// Unmarshal parses the JSON-encoded data and stores the result +// in the value pointed to by v. If v is nil or not a pointer, +// Unmarshal returns an [InvalidUnmarshalError]. +func Unmarshal(data []byte, v any) error { + rv := reflect.ValueOf(v) + if rv.Kind() != reflect.Pointer || rv.IsNil() { + return &InvalidUnmarshalError{reflect.TypeOf(v)} + } + return unmarshalValue(data, rv.Elem(), new(Word)) +} + +// UnmarshalSetError describes a value that cannot be set during [Unmarshal]. +// This is likely an unexported struct field. +type UnmarshalSetError struct{ Type reflect.Type } + +func (u *UnmarshalSetError) Error() string { return "cannot set: " + u.Type.String() } + +// unmarshalValue implements [Unmarshal] on [reflect.Value]. +func unmarshalValue(data []byte, v reflect.Value, sizeP *Word) error { + switch v.Kind() { + + case reflect.Int32: + *sizeP = 4 + if err := unmarshalCheckTypeBounds(&data, SPA_TYPE_Int, sizeP); err != nil { + return err + } + if !v.CanSet() { + return &UnmarshalSetError{v.Type()} + } + v.SetInt(int64(binary.NativeEndian.Uint32(data))) + return nil + + case reflect.Struct: + if err := unmarshalCheckTypeBounds(&data, SPA_TYPE_Struct, sizeP); err != nil { + return err + } + + var fieldWireSize Word + for i := 0; i < v.NumField(); i++ { + if err := unmarshalValue(data, v.Field(i), &fieldWireSize); err != nil { + return err + } + paddingSize := (8 - (fieldWireSize)%8) % 8 + // already bounds checked by the successful unmarshalValue call + data = data[8+fieldWireSize+paddingSize:] + } + return nil + + case reflect.Pointer: + if !v.CanSet() { + return &UnmarshalSetError{v.Type()} + } + + if len(data) < 8 { + return io.ErrUnexpectedEOF + } + switch binary.NativeEndian.Uint32(data[4:]) { + case SPA_TYPE_None: + v.SetZero() + return nil + + default: + v.Set(reflect.New(v.Type().Elem())) + return unmarshalValue(data, v.Elem(), sizeP) + } + + default: + return &UnsupportedTypeError{v.Type()} + } +} + +// An InconsistentSizeError describes an inconsistent size prefix encountered +// in data passed to [Unmarshal]. +type InconsistentSizeError struct{ Prefix, Expect Word } + +func (e *InconsistentSizeError) Error() string { + return "unexpected size prefix: " + strconv.Itoa(int(e.Prefix)) + ", want " + strconv.Itoa(int(e.Expect)) +} + +// An UnexpectedTypeError describes an unexpected type encountered +// in data passed to [Unmarshal]. +type UnexpectedTypeError struct{ Type, Expect Word } + +func (u *UnexpectedTypeError) Error() string { + return "unexpected type: " + strconv.Itoa(int(u.Type)) + ", want " + strconv.Itoa(int(u.Expect)) +} + +// unmarshalCheckTypeBounds performs bounds checks on data and validates the type and size prefixes. +// An expected size of zero skips further bounds checks. +func unmarshalCheckTypeBounds(data *[]byte, t Word, sizeP *Word) error { + if len(*data) < 8 { + return io.ErrUnexpectedEOF + } + + wantSize := *sizeP + gotSize := binary.NativeEndian.Uint32(*data) + *sizeP = gotSize + + if wantSize != 0 && gotSize != wantSize { + return &InconsistentSizeError{gotSize, wantSize} + } + if len(*data)-8 < int(wantSize) { + return io.ErrUnexpectedEOF + } + + gotType := binary.NativeEndian.Uint32((*data)[4:]) + if gotType != t { + return &UnexpectedTypeError{gotType, t} + } + + *data = (*data)[8:] + return nil +} + /* Pointers */ const ( SPA_TYPE_POINTER_START = 0x10000 + iota