diff --git a/internal/pipewire/pod.go b/internal/pipewire/pod.go index ff82c72..a99f988 100644 --- a/internal/pipewire/pod.go +++ b/internal/pipewire/pod.go @@ -183,6 +183,14 @@ func (e *TrailingGarbageError) Error() string { return "data has extra values starting with type " + strconv.Itoa(int(binary.NativeEndian.Uint32(e.Data[4:]))) } +// A StringTerminationError describes an incorrectly terminated string +// encountered during [Unmarshal]. +type StringTerminationError struct{ Value byte } + +func (e StringTerminationError) Error() string { + return "got byte " + strconv.Itoa(int(e.Value)) + " instead of NUL" +} + // unmarshalValue implements [Unmarshal] on [reflect.Value]. func unmarshalValue(data []byte, v reflect.Value, sizeP *Word) error { switch v.Kind() { @@ -236,6 +244,25 @@ func unmarshalValue(data []byte, v reflect.Value, sizeP *Word) error { return unmarshalValue(data, v.Elem(), sizeP) } + case reflect.String: + if err := unmarshalCheckTypeBounds(&data, SPA_TYPE_String, sizeP); err != nil { + return err + } + + // string size, one extra NUL byte + size := int(*sizeP) + if len(data) < size { + return io.ErrUnexpectedEOF + } + + // the serialised strings still include NUL termination + if data[size-1] != 0 { + return StringTerminationError{data[size-1]} + } + + v.SetString(string(data[:size-1])) + return nil + default: return &UnsupportedTypeError{v.Type()} } diff --git a/internal/pipewire/pod_test.go b/internal/pipewire/pod_test.go index a798286..a39f961 100644 --- a/internal/pipewire/pod_test.go +++ b/internal/pipewire/pod_test.go @@ -2,6 +2,7 @@ package pipewire_test import ( "encoding" + "encoding/json" "reflect" "testing" ) @@ -37,7 +38,7 @@ func (testCases encodingTestCases[V, S]) run(t *testing.T) { t.Fatalf("UnmarshalBinary: error = %v", err) } if !reflect.DeepEqual(&value, &tc.value) { - t.Fatalf("UnmarshalBinary: %#v, want %#v", value, tc.value) + t.Fatalf("UnmarshalBinary:\n%s\nwant\n%s", mustMarshalJSON(value), mustMarshalJSON(tc.value)) } }) @@ -53,3 +54,12 @@ func (testCases encodingTestCases[V, S]) run(t *testing.T) { }) } } + +// mustMarshalJSON calls [json.Marshal] and returns the result. +func mustMarshalJSON(v any) string { + if data, err := json.Marshal(v); err != nil { + panic(err) + } else { + return string(data) + } +}