From 827dc9e1ba600622fd93f12f0e96de67e4b1347b Mon Sep 17 00:00:00 2001 From: Ophestra Date: Tue, 25 Nov 2025 03:33:57 +0900 Subject: [PATCH] internal/pipewire: implement string type This is still NUL terminated strings, and an extra NUL character on an 8-byte string does cause an extra 7 bytes of padding. Signed-off-by: Ophestra --- internal/pipewire/pod.go | 33 +++++++++++++++++++++++++++++++++ internal/pipewire/pod_test.go | 12 +++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/internal/pipewire/pod.go b/internal/pipewire/pod.go index ff82c72..cf018cc 100644 --- a/internal/pipewire/pod.go +++ b/internal/pipewire/pod.go @@ -135,6 +135,12 @@ func marshalValueAppendRaw(data []byte, v reflect.Value) ([]byte, error) { } return marshalValueAppendRaw(data, v.Elem()) + case reflect.String: + data = binary.NativeEndian.AppendUint32(data, SPA_TYPE_String) + data = append(data, []byte(v.String())...) + data = append(data, 0) + return data, nil + default: return data, &UnsupportedTypeError{v.Type()} } @@ -183,6 +189,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 +250,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) + } +}