From 8f4a3bcf9f3d2dc0d8363b2f66ba41f5bc9f7c6a Mon Sep 17 00:00:00 2001 From: Ophestra Date: Tue, 25 Nov 2025 04:04:07 +0900 Subject: [PATCH] internal/pipewire: use custom marshaler when available This reduces special cases. This change also exposes unmarshalled message size on the wire. Signed-off-by: Ophestra --- internal/pipewire/core.go | 5 ++- internal/pipewire/pod.go | 71 +++++++++++++++++++++++++++++++-------- 2 files changed, 61 insertions(+), 15 deletions(-) diff --git a/internal/pipewire/core.go b/internal/pipewire/core.go index a833009..9a408e2 100644 --- a/internal/pipewire/core.go +++ b/internal/pipewire/core.go @@ -76,4 +76,7 @@ type CoreHello struct { func (c *CoreHello) MarshalBinary() ([]byte, error) { return MarshalAppend(make([]byte, 0, 24), c) } // UnmarshalBinary satisfies [encoding.BinaryUnmarshaler] via [Unmarshal]. -func (c *CoreHello) UnmarshalBinary(data []byte) error { return Unmarshal(data, c) } +func (c *CoreHello) UnmarshalBinary(data []byte) error { + _, err := Unmarshal(data, c) + return err +} diff --git a/internal/pipewire/pod.go b/internal/pipewire/pod.go index cf018cc..4ba4c6c 100644 --- a/internal/pipewire/pod.go +++ b/internal/pipewire/pod.go @@ -65,6 +65,13 @@ const ( _SPA_TYPE_LAST // not part of ABI ) +// PODMarshaler is the interface implemented by an object that can +// marshal itself into PipeWire POD encoding. +type PODMarshaler interface { + // MarshalPOD encodes the receiver into PipeWire POD encoding and returns the result. + MarshalPOD() ([]byte, error) +} + // An UnsupportedTypeError is returned by [Marshal] when attempting // to encode an unsupported value type. type UnsupportedTypeError struct{ Type reflect.Type } @@ -86,11 +93,12 @@ func MarshalAppend(data []byte, v any) ([]byte, error) { return marshalValueAppend(data, reflect.ValueOf(v)) } -// marshalValueAppendRaw implements [MarshalAppend] on [reflect.Value]. -func marshalValueAppend(data []byte, v reflect.Value) ([]byte, error) { +// appendInner calls f and handles size prefix and padding around the appended data. +// f must only append to data. +func appendInner(data []byte, f func(data []byte) ([]byte, error)) ([]byte, error) { data = append(data, make([]byte, 4)...) - rData, err := marshalValueAppendRaw(data, v) + rData, err := f(data) if err != nil { return data, err } @@ -108,6 +116,18 @@ func marshalValueAppend(data []byte, v reflect.Value) ([]byte, error) { return rData, nil } +// marshalValueAppendRaw implements [MarshalAppend] on [reflect.Value]. +func marshalValueAppend(data []byte, v reflect.Value) ([]byte, error) { + if v.CanInterface() { + if m, ok := v.Interface().(PODMarshaler); ok { + extraData, err := m.MarshalPOD() + return append(data, extraData...), err + } + } + + return appendInner(data, func(data []byte) ([]byte, error) { return marshalValueAppendRaw(data, v) }) +} + // marshalValueAppendRaw implements [MarshalAppend] on [reflect.Value] without the size prefix. func marshalValueAppendRaw(data []byte, v reflect.Value) ([]byte, error) { switch v.Kind() { @@ -146,6 +166,15 @@ func marshalValueAppendRaw(data []byte, v reflect.Value) ([]byte, error) { } } +// PODUnmarshaler is the interface implemented by an object that can +// unmarshal a PipeWire POD encoding representation of itself. +type PODUnmarshaler interface { + // UnmarshalPOD must be able to decode the form generated by MarshalPOD. + // UnmarshalPOD must copy the data if it wishes to retain the data + // after returning. + UnmarshalPOD(data []byte) (Word, error) +} + // 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 } @@ -164,12 +193,17 @@ func (e *InvalidUnmarshalError) Error() 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 { +func Unmarshal(data []byte, v any) (size Word, err error) { rv := reflect.ValueOf(v) if rv.Kind() != reflect.Pointer || rv.IsNil() { - return &InvalidUnmarshalError{reflect.TypeOf(v)} + return 0, &InvalidUnmarshalError{reflect.TypeOf(v)} } - return unmarshalValue(data, rv.Elem(), new(Word)) + err = unmarshalValue(data, rv.Elem(), &size) + // size and type prefix + size += 8 + // padding + size += (8 - (size)%8) % 8 + return } // UnmarshalSetError describes a value that cannot be set during [Unmarshal]. @@ -199,6 +233,22 @@ func (e StringTerminationError) Error() string { // unmarshalValue implements [Unmarshal] on [reflect.Value]. func unmarshalValue(data []byte, v reflect.Value, sizeP *Word) error { + if !v.CanSet() { + return &UnmarshalSetError{v.Type()} + } + + if v.CanInterface() { + if v.Kind() == reflect.Pointer { + v.Set(reflect.New(v.Type().Elem())) + } + + if u, ok := v.Interface().(PODUnmarshaler); ok { + var err error + *sizeP, err = u.UnmarshalPOD(data) + return err + } + } + switch v.Kind() { case reflect.Int32: @@ -206,9 +256,6 @@ func unmarshalValue(data []byte, v reflect.Value, sizeP *Word) error { 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 @@ -223,7 +270,7 @@ func unmarshalValue(data []byte, v reflect.Value, sizeP *Word) error { return err } paddingSize := (8 - (fieldWireSize)%8) % 8 - // already bounds checked by the successful unmarshalValue call + // bounds check completed in successful call to unmarshalValue data = data[8+fieldWireSize+paddingSize:] } @@ -233,10 +280,6 @@ func unmarshalValue(data []byte, v reflect.Value, sizeP *Word) error { return nil case reflect.Pointer: - if !v.CanSet() { - return &UnmarshalSetError{v.Type()} - } - if len(data) < 8 { return io.ErrUnexpectedEOF }