internal/pipewire: use type name in error strings
All checks were successful
Test / Create distribution (push) Successful in 35s
Test / Sandbox (push) Successful in 2m44s
Test / Sandbox (race detector) (push) Successful in 4m39s
Test / Hakurei (push) Successful in 4m52s
Test / Hpkg (push) Successful in 4m53s
Test / Hakurei (race detector) (push) Successful in 6m28s
Test / Flake checks (push) Successful in 1m33s

This provides more useful messages for protocol errors.

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
Ophestra 2025-12-04 02:27:45 +09:00
parent 2c0b92771a
commit 69b1131d66
Signed by: cat
SSH Key Fingerprint: SHA256:gQ67O0enBZ7UdZypgtspB2FDM1g3GVw8nX0XSdcFw8Q
4 changed files with 110 additions and 20 deletions

View File

@ -128,15 +128,15 @@ func TestCorePing(t *testing.T) {
// handmade sample
{"sample", []byte{
/* size: rest of data */ 0x20, 0, 0, 0,
/* type: Struct */ pipewire.SPA_TYPE_Struct, 0, 0, 0,
/* type: Struct */ byte(pipewire.SPA_TYPE_Struct), 0, 0, 0,
/* size: 4 bytes */ 4, 0, 0, 0,
/* type: Int */ pipewire.SPA_TYPE_Int, 0, 0, 0,
/* type: Int */ byte(pipewire.SPA_TYPE_Int), 0, 0, 0,
/* value: -1 */ 0xff, 0xff, 0xff, 0xff,
/* padding */ 0, 0, 0, 0,
/* size: 4 bytes */ 4, 0, 0, 0,
/* type: Int */ pipewire.SPA_TYPE_Int, 0, 0, 0,
/* type: Int */ byte(pipewire.SPA_TYPE_Int), 0, 0, 0,
/* value: 0 */ 0, 0, 0, 0,
/* padding */ 0, 0, 0, 0,
}, pipewire.CorePing{
@ -257,15 +257,15 @@ func TestCorePong(t *testing.T) {
// handmade sample
{"sample", []byte{
/* size: rest of data */ 0x20, 0, 0, 0,
/* type: Struct */ pipewire.SPA_TYPE_Struct, 0, 0, 0,
/* type: Struct */ byte(pipewire.SPA_TYPE_Struct), 0, 0, 0,
/* size: 4 bytes */ 4, 0, 0, 0,
/* type: Int */ pipewire.SPA_TYPE_Int, 0, 0, 0,
/* type: Int */ byte(pipewire.SPA_TYPE_Int), 0, 0, 0,
/* value: -1 */ 0xff, 0xff, 0xff, 0xff,
/* padding */ 0, 0, 0, 0,
/* size: 4 bytes */ 4, 0, 0, 0,
/* type: Int */ pipewire.SPA_TYPE_Int, 0, 0, 0,
/* type: Int */ byte(pipewire.SPA_TYPE_Int), 0, 0, 0,
/* value: 0 */ 0, 0, 0, 0,
/* padding */ 0, 0, 0, 0,
}, pipewire.CorePong{

View File

@ -150,7 +150,7 @@ func marshalValueAppendRaw(data []byte, v reflect.Value) ([]byte, error) {
if v.CanInterface() {
switch c := v.Interface().(type) {
case Fd:
data = binary.NativeEndian.AppendUint32(data, SPA_TYPE_Fd)
data = SPA_TYPE_Fd.append(data)
data = binary.NativeEndian.AppendUint64(data, uint64(c))
return data, nil
}
@ -158,22 +158,22 @@ func marshalValueAppendRaw(data []byte, v reflect.Value) ([]byte, error) {
switch v.Kind() {
case reflect.Uint32:
data = binary.NativeEndian.AppendUint32(data, SPA_TYPE_Id)
data = SPA_TYPE_Id.append(data)
data = binary.NativeEndian.AppendUint32(data, Word(v.Uint()))
return data, nil
case reflect.Int32:
data = binary.NativeEndian.AppendUint32(data, SPA_TYPE_Int)
data = SPA_TYPE_Int.append(data)
data = binary.NativeEndian.AppendUint32(data, Word(v.Int()))
return data, nil
case reflect.Int64:
data = binary.NativeEndian.AppendUint32(data, SPA_TYPE_Long)
data = SPA_TYPE_Long.append(data)
data = binary.NativeEndian.AppendUint64(data, uint64(v.Int()))
return data, nil
case reflect.Struct:
data = binary.NativeEndian.AppendUint32(data, SPA_TYPE_Struct)
data = SPA_TYPE_Struct.append(data)
var err error
for i := 0; i < v.NumField(); i++ {
data, err = marshalValueAppend(data, v.Field(i))
@ -185,13 +185,13 @@ func marshalValueAppendRaw(data []byte, v reflect.Value) ([]byte, error) {
case reflect.Pointer:
if v.IsNil() {
data = binary.NativeEndian.AppendUint32(data, SPA_TYPE_None)
data = SPA_TYPE_None.append(data)
return data, nil
}
return marshalValueAppendRaw(data, v.Elem())
case reflect.String:
data = binary.NativeEndian.AppendUint32(data, SPA_TYPE_String)
data = SPA_TYPE_String.append(data)
data = append(data, []byte(v.String())...)
data = append(data, 0)
return data, nil
@ -359,7 +359,7 @@ func unmarshalValue(data []byte, v reflect.Value, wireSizeP *Word) error {
if len(data) < SizePrefix {
return UnexpectedEOFError{}
}
switch binary.NativeEndian.Uint32(data[SizeSPrefix:]) {
switch SPAKind(binary.NativeEndian.Uint32(data[SizeSPrefix:])) {
case SPA_TYPE_None:
v.SetZero()
return nil
@ -404,15 +404,15 @@ func (e *InconsistentSizeError) Error() string {
// An UnexpectedTypeError describes an unexpected type encountered
// in data passed to [Unmarshal].
type UnexpectedTypeError struct{ Type, Expect Word }
type UnexpectedTypeError struct{ Type, Expect SPAKind }
func (u *UnexpectedTypeError) Error() string {
return "unexpected type: " + strconv.Itoa(int(u.Type)) + ", want " + strconv.Itoa(int(u.Expect))
return "received " + u.Type.String() + " for a value of type " + u.Expect.String()
}
// 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 {
func unmarshalCheckTypeBounds(data *[]byte, t SPAKind, sizeP *Word) error {
if len(*data) < SizePrefix {
return UnexpectedEOFError{}
}
@ -428,7 +428,7 @@ func unmarshalCheckTypeBounds(data *[]byte, t Word, sizeP *Word) error {
return UnexpectedEOFError{}
}
gotType := binary.NativeEndian.Uint32((*data)[SizeSPrefix:])
gotType := SPAKind(binary.NativeEndian.Uint32((*data)[SizeSPrefix:]))
if gotType != t {
return &UnexpectedTypeError{gotType, t}
}
@ -491,7 +491,7 @@ func (d *SPADict) Size() Word {
// MarshalPOD satisfies [PODMarshaler] as [SPADict] violates the POD type system.
func (d *SPADict) MarshalPOD(data []byte) ([]byte, error) {
return appendInner(data, func(dataPrefix []byte) (data []byte, err error) {
data = binary.NativeEndian.AppendUint32(dataPrefix, SPA_TYPE_Struct)
data = SPA_TYPE_Struct.append(dataPrefix)
if data, err = MarshalAppend(data, Int(len(*d))); err != nil {
return
}

View File

@ -1,10 +1,24 @@
package pipewire
import (
"encoding/binary"
"fmt"
)
// A SPAKind describes the kind of data being encoded right after it.
//
// These do not always follow the same rules, and encoding/decoding
// is very much context-dependent. Callers should therefore not
// attempt to use these values directly and rely on [Marshal] and
// [Unmarshal] and their variants instead.
type SPAKind Word
/* Basic types */
const (
/* POD's can contain a number of basic SPA types: */
SPA_TYPE_START = 0x00000 + iota
SPA_TYPE_START SPAKind = 0x00000 + iota
SPA_TYPE_None // No value or a NULL pointer.
SPA_TYPE_Bool // A boolean value.
SPA_TYPE_Id // An enumerated value.
@ -35,6 +49,60 @@ const (
_SPA_TYPE_LAST // not part of ABI
)
// append appends the representation of [SPAKind] to data and returns the appended slice.
func (kind SPAKind) append(data []byte) []byte {
return binary.NativeEndian.AppendUint32(data, Word(kind))
}
// String returns the name of the [SPAKind] for basic types.
func (kind SPAKind) String() string {
switch kind {
case SPA_TYPE_None:
return "None"
case SPA_TYPE_Bool:
return "Bool"
case SPA_TYPE_Id:
return "Id"
case SPA_TYPE_Int:
return "Int"
case SPA_TYPE_Long:
return "Long"
case SPA_TYPE_Float:
return "Float"
case SPA_TYPE_Double:
return "Double"
case SPA_TYPE_String:
return "String"
case SPA_TYPE_Bytes:
return "Bytes"
case SPA_TYPE_Rectangle:
return "Rectangle"
case SPA_TYPE_Fraction:
return "Fraction"
case SPA_TYPE_Bitmap:
return "Bitmap"
case SPA_TYPE_Array:
return "Array"
case SPA_TYPE_Struct:
return "Struct"
case SPA_TYPE_Object:
return "Object"
case SPA_TYPE_Sequence:
return "Sequence"
case SPA_TYPE_Pointer:
return "Pointer"
case SPA_TYPE_Fd:
return "Fd"
case SPA_TYPE_Choice:
return "Choice"
case SPA_TYPE_Pod:
return "Pod"
default:
return fmt.Sprintf("invalid type field %#x", Word(kind))
}
}
/* Pointers */
const (
SPA_TYPE_POINTER_START = 0x10000 + iota

View File

@ -3,10 +3,32 @@ package pipewire_test
import (
_ "embed"
"encoding/binary"
"testing"
"hakurei.app/internal/pipewire"
)
func TestSPAKind(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
v pipewire.SPAKind
want string
}{
{"invalid", 0xdeadbeef, "invalid type field 0xdeadbeef"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
if got := tc.v.String(); got != tc.want {
t.Errorf("String: %q, want %q", got, tc.want)
}
})
}
}
// splitMessages splits concatenated messages into groups of
// header, payload, footer of each individual message.
// splitMessages panics on any decoding error.