internal/pipewire: use custom marshaler when available
All checks were successful
Test / Create distribution (push) Successful in 37s
Test / Sandbox (push) Successful in 45s
Test / Sandbox (race detector) (push) Successful in 2m19s
Test / Hakurei (push) Successful in 2m27s
Test / Hakurei (race detector) (push) Successful in 3m12s
Test / Hpkg (push) Successful in 3m31s
Test / Flake checks (push) Successful in 1m34s

This reduces special cases. This change also exposes unmarshalled message size on the wire.

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
Ophestra 2025-11-25 04:04:07 +09:00
parent 827dc9e1ba
commit 8f4a3bcf9f
Signed by: cat
SSH Key Fingerprint: SHA256:gQ67O0enBZ7UdZypgtspB2FDM1g3GVw8nX0XSdcFw8Q
2 changed files with 61 additions and 15 deletions

View File

@ -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
}

View File

@ -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
}