aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-12-04 02:27:45 +0900
committerOphestra <cat@gensokyo.uk>2025-12-04 02:29:27 +0900
commit69b1131d6622108f37c85014035f71cf4f2986e5 (patch)
tree368cc8d0659d9925c9df8153398ca5038313e160
parent2c0b92771a95693af7f14a70ddeddc5371e73996 (diff)
internal/pipewire: use type name in error strings
This provides more useful messages for protocol errors. Signed-off-by: Ophestra <cat@gensokyo.uk>
-rw-r--r--internal/pipewire/core_test.go12
-rw-r--r--internal/pipewire/pod.go26
-rw-r--r--internal/pipewire/spa.go70
-rw-r--r--internal/pipewire/spa_test.go22
4 files changed, 110 insertions, 20 deletions
diff --git a/internal/pipewire/core_test.go b/internal/pipewire/core_test.go
index 9d19be4a..0c52c7ee 100644
--- a/internal/pipewire/core_test.go
+++ b/internal/pipewire/core_test.go
@@ -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{
diff --git a/internal/pipewire/pod.go b/internal/pipewire/pod.go
index 13d2dc25..582d0a16 100644
--- a/internal/pipewire/pod.go
+++ b/internal/pipewire/pod.go
@@ -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
}
diff --git a/internal/pipewire/spa.go b/internal/pipewire/spa.go
index 32d6986f..a1014da9 100644
--- a/internal/pipewire/spa.go
+++ b/internal/pipewire/spa.go
@@ -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
diff --git a/internal/pipewire/spa_test.go b/internal/pipewire/spa_test.go
index 6bf7794b..0a7d79aa 100644
--- a/internal/pipewire/spa_test.go
+++ b/internal/pipewire/spa_test.go
@@ -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.