forked from security/hakurei
internal/pipewire: improve protocol error messages
These are mostly small formatting changes, with the biggest change being to UnexpectedEOFError where its kind is now described as part of the error type. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
@@ -87,14 +87,14 @@ type PODMarshaler interface {
|
|||||||
// to encode an unsupported value type.
|
// to encode an unsupported value type.
|
||||||
type UnsupportedTypeError struct{ Type reflect.Type }
|
type UnsupportedTypeError struct{ Type reflect.Type }
|
||||||
|
|
||||||
func (e *UnsupportedTypeError) Error() string { return "unsupported type: " + e.Type.String() }
|
func (e *UnsupportedTypeError) Error() string { return "unsupported type " + e.Type.String() }
|
||||||
|
|
||||||
// An UnsupportedSizeError is returned by [Marshal] when attempting
|
// An UnsupportedSizeError is returned by [Marshal] when attempting
|
||||||
// to encode a value with its encoded size exceeding what could be
|
// to encode a value with its encoded size exceeding what could be
|
||||||
// represented by the format.
|
// represented by the format.
|
||||||
type UnsupportedSizeError int
|
type UnsupportedSizeError int
|
||||||
|
|
||||||
func (e UnsupportedSizeError) Error() string { return "size out of range: " + strconv.Itoa(int(e)) }
|
func (e UnsupportedSizeError) Error() string { return "size " + strconv.Itoa(int(e)) + " out of range" }
|
||||||
|
|
||||||
// Marshal returns the PipeWire POD encoding of v.
|
// Marshal returns the PipeWire POD encoding of v.
|
||||||
func Marshal(v any) ([]byte, error) {
|
func Marshal(v any) ([]byte, error) {
|
||||||
@@ -220,16 +220,43 @@ func (e *InvalidUnmarshalError) Error() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if e.Type.Kind() != reflect.Pointer {
|
if e.Type.Kind() != reflect.Pointer {
|
||||||
return "attempting to unmarshal to non-pointer type: " + e.Type.String()
|
return "attempting to unmarshal to non-pointer type " + e.Type.String()
|
||||||
}
|
}
|
||||||
return "attempting to unmarshal to nil " + e.Type.String()
|
return "attempting to unmarshal to nil " + e.Type.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnexpectedEOFError is returned when EOF was encountered in the middle of decoding POD data.
|
// UnexpectedEOFError describes an unexpected EOF encountered in the middle of decoding POD data.
|
||||||
type UnexpectedEOFError struct{}
|
type UnexpectedEOFError uintptr
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ErrEOFPrefix is returned when unexpectedly encountering EOF
|
||||||
|
// decoding the fixed-size POD prefix.
|
||||||
|
ErrEOFPrefix UnexpectedEOFError = iota
|
||||||
|
// ErrEOFData is returned when unexpectedly encountering EOF
|
||||||
|
// establishing POD data bounds.
|
||||||
|
ErrEOFData
|
||||||
|
// ErrEOFDataString is returned when unexpectedly encountering EOF
|
||||||
|
// establishing POD [String] bounds.
|
||||||
|
ErrEOFDataString
|
||||||
|
)
|
||||||
|
|
||||||
func (UnexpectedEOFError) Unwrap() error { return io.ErrUnexpectedEOF }
|
func (UnexpectedEOFError) Unwrap() error { return io.ErrUnexpectedEOF }
|
||||||
func (UnexpectedEOFError) Error() string { return "unexpected EOF decoding POD data" }
|
func (e UnexpectedEOFError) Error() string {
|
||||||
|
var suffix string
|
||||||
|
switch e {
|
||||||
|
case ErrEOFPrefix:
|
||||||
|
suffix = "decoding fixed-size POD prefix"
|
||||||
|
case ErrEOFData:
|
||||||
|
suffix = "establishing POD data bounds"
|
||||||
|
case ErrEOFDataString:
|
||||||
|
suffix = "establishing POD String bounds"
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "unexpected EOF"
|
||||||
|
}
|
||||||
|
|
||||||
|
return "unexpected EOF " + suffix
|
||||||
|
}
|
||||||
|
|
||||||
// Unmarshal parses the PipeWire POD encoded data and stores the result
|
// Unmarshal parses the PipeWire POD encoded data and stores the result
|
||||||
// in the value pointed to by v. If v is nil or not a pointer,
|
// in the value pointed to by v. If v is nil or not a pointer,
|
||||||
@@ -238,7 +265,7 @@ func Unmarshal(data []byte, v any) error {
|
|||||||
if n, err := UnmarshalNext(data, v); err != nil {
|
if n, err := UnmarshalNext(data, v); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if len(data) > int(n) {
|
} else if len(data) > int(n) {
|
||||||
return &TrailingGarbageError{data[int(n):]}
|
return TrailingGarbageError(data[int(n):])
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -261,25 +288,25 @@ func UnmarshalNext(data []byte, v any) (size Word, err error) {
|
|||||||
// This is likely an unexported struct field.
|
// This is likely an unexported struct field.
|
||||||
type UnmarshalSetError struct{ Type reflect.Type }
|
type UnmarshalSetError struct{ Type reflect.Type }
|
||||||
|
|
||||||
func (u *UnmarshalSetError) Error() string { return "cannot set: " + u.Type.String() }
|
func (u *UnmarshalSetError) Error() string { return "cannot set " + u.Type.String() }
|
||||||
|
|
||||||
// A TrailingGarbageError describes extra bytes after decoding
|
// A TrailingGarbageError describes extra bytes after decoding
|
||||||
// has completed during [Unmarshal].
|
// has completed during [Unmarshal].
|
||||||
type TrailingGarbageError struct{ Data []byte }
|
type TrailingGarbageError []byte
|
||||||
|
|
||||||
func (e *TrailingGarbageError) Error() string {
|
func (e TrailingGarbageError) Error() string {
|
||||||
if len(e.Data) < SizePrefix {
|
if len(e) < SizePrefix {
|
||||||
return "got " + strconv.Itoa(len(e.Data)) + " bytes of trailing garbage"
|
return "got " + strconv.Itoa(len(e)) + " bytes of trailing garbage"
|
||||||
}
|
}
|
||||||
return "data has extra values starting with type " + strconv.Itoa(int(binary.NativeEndian.Uint32(e.Data[SizeSPrefix:])))
|
return "data has extra values starting with " + SPAKind(binary.NativeEndian.Uint32(e[SizeSPrefix:])).String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// A StringTerminationError describes an incorrectly terminated string
|
// A StringTerminationError describes an incorrectly terminated string
|
||||||
// encountered during [Unmarshal].
|
// encountered during [Unmarshal].
|
||||||
type StringTerminationError struct{ Value byte }
|
type StringTerminationError byte
|
||||||
|
|
||||||
func (e StringTerminationError) Error() string {
|
func (e StringTerminationError) Error() string {
|
||||||
return "got byte " + strconv.Itoa(int(e.Value)) + " instead of NUL"
|
return "got byte " + strconv.Itoa(int(e)) + " instead of NUL"
|
||||||
}
|
}
|
||||||
|
|
||||||
// unmarshalValue implements [Unmarshal] on [reflect.Value] without compensating for prefix and padding size.
|
// unmarshalValue implements [Unmarshal] on [reflect.Value] without compensating for prefix and padding size.
|
||||||
@@ -351,13 +378,13 @@ func unmarshalValue(data []byte, v reflect.Value, wireSizeP *Word) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(data) != 0 {
|
if len(data) != 0 {
|
||||||
return &TrailingGarbageError{data}
|
return TrailingGarbageError(data)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
case reflect.Pointer:
|
case reflect.Pointer:
|
||||||
if len(data) < SizePrefix {
|
if len(data) < SizePrefix {
|
||||||
return UnexpectedEOFError{}
|
return ErrEOFPrefix
|
||||||
}
|
}
|
||||||
switch SPAKind(binary.NativeEndian.Uint32(data[SizeSPrefix:])) {
|
switch SPAKind(binary.NativeEndian.Uint32(data[SizeSPrefix:])) {
|
||||||
case SPA_TYPE_None:
|
case SPA_TYPE_None:
|
||||||
@@ -378,12 +405,12 @@ func unmarshalValue(data []byte, v reflect.Value, wireSizeP *Word) error {
|
|||||||
// string size, one extra NUL byte
|
// string size, one extra NUL byte
|
||||||
size := int(*wireSizeP)
|
size := int(*wireSizeP)
|
||||||
if len(data) < size {
|
if len(data) < size {
|
||||||
return UnexpectedEOFError{}
|
return ErrEOFDataString
|
||||||
}
|
}
|
||||||
|
|
||||||
// the serialised strings still include NUL termination
|
// the serialised strings still include NUL termination
|
||||||
if data[size-1] != 0 {
|
if data[size-1] != 0 {
|
||||||
return StringTerminationError{data[size-1]}
|
return StringTerminationError(data[size-1])
|
||||||
}
|
}
|
||||||
|
|
||||||
v.SetString(string(data[:size-1]))
|
v.SetString(string(data[:size-1]))
|
||||||
@@ -398,23 +425,24 @@ func unmarshalValue(data []byte, v reflect.Value, wireSizeP *Word) error {
|
|||||||
// in data passed to [Unmarshal].
|
// in data passed to [Unmarshal].
|
||||||
type InconsistentSizeError struct{ Prefix, Expect Word }
|
type InconsistentSizeError struct{ Prefix, Expect Word }
|
||||||
|
|
||||||
func (e *InconsistentSizeError) Error() string {
|
func (e InconsistentSizeError) Error() string {
|
||||||
return "unexpected size prefix: " + strconv.Itoa(int(e.Prefix)) + ", want " + strconv.Itoa(int(e.Expect))
|
return "prefix claims size " + strconv.Itoa(int(e.Prefix)) +
|
||||||
|
" for a " + strconv.Itoa(int(e.Expect)) + "-byte long segment"
|
||||||
}
|
}
|
||||||
|
|
||||||
// An UnexpectedTypeError describes an unexpected type encountered
|
// An UnexpectedTypeError describes an unexpected type encountered
|
||||||
// in data passed to [Unmarshal].
|
// in data passed to [Unmarshal].
|
||||||
type UnexpectedTypeError struct{ Type, Expect SPAKind }
|
type UnexpectedTypeError struct{ Type, Expect SPAKind }
|
||||||
|
|
||||||
func (u *UnexpectedTypeError) Error() string {
|
func (e UnexpectedTypeError) Error() string {
|
||||||
return "received " + u.Type.String() + " for a value of type " + u.Expect.String()
|
return "received " + e.Type.String() + " for a value of type " + e.Expect.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// unmarshalCheckTypeBounds performs bounds checks on data and validates the type and size prefixes.
|
// unmarshalCheckTypeBounds performs bounds checks on data and validates the type and size prefixes.
|
||||||
// An expected size of zero skips further bounds checks.
|
// An expected size of zero skips further bounds checks.
|
||||||
func unmarshalCheckTypeBounds(data *[]byte, t SPAKind, sizeP *Word) error {
|
func unmarshalCheckTypeBounds(data *[]byte, t SPAKind, sizeP *Word) error {
|
||||||
if len(*data) < SizePrefix {
|
if len(*data) < SizePrefix {
|
||||||
return UnexpectedEOFError{}
|
return ErrEOFPrefix
|
||||||
}
|
}
|
||||||
|
|
||||||
wantSize := *sizeP
|
wantSize := *sizeP
|
||||||
@@ -422,15 +450,15 @@ func unmarshalCheckTypeBounds(data *[]byte, t SPAKind, sizeP *Word) error {
|
|||||||
*sizeP = gotSize
|
*sizeP = gotSize
|
||||||
|
|
||||||
if wantSize != 0 && gotSize != wantSize {
|
if wantSize != 0 && gotSize != wantSize {
|
||||||
return &InconsistentSizeError{gotSize, wantSize}
|
return InconsistentSizeError{gotSize, wantSize}
|
||||||
}
|
}
|
||||||
if len(*data)-SizePrefix < int(gotSize) {
|
if len(*data)-SizePrefix < int(gotSize) {
|
||||||
return UnexpectedEOFError{}
|
return ErrEOFData
|
||||||
}
|
}
|
||||||
|
|
||||||
gotType := SPAKind(binary.NativeEndian.Uint32((*data)[SizeSPrefix:]))
|
gotType := SPAKind(binary.NativeEndian.Uint32((*data)[SizeSPrefix:]))
|
||||||
if gotType != t {
|
if gotType != t {
|
||||||
return &UnexpectedTypeError{gotType, t}
|
return UnexpectedTypeError{gotType, t}
|
||||||
}
|
}
|
||||||
|
|
||||||
*data = (*data)[SizePrefix : gotSize+SizePrefix]
|
*data = (*data)[SizePrefix : gotSize+SizePrefix]
|
||||||
@@ -541,7 +569,7 @@ func (d *SPADict) UnmarshalPOD(data []byte) (Word, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(data) != 0 {
|
if len(data) != 0 {
|
||||||
return wireSize, &TrailingGarbageError{data}
|
return wireSize, TrailingGarbageError(data)
|
||||||
}
|
}
|
||||||
return wireSize, nil
|
return wireSize, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,6 +69,76 @@ func (testCases encodingTestCases[V, S]) run(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPODErrors(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"UnsupportedTypeError", &pipewire.UnsupportedTypeError{
|
||||||
|
Type: reflect.TypeFor[any](),
|
||||||
|
}, "unsupported type interface {}"},
|
||||||
|
|
||||||
|
{"UnsupportedSizeError", pipewire.UnsupportedSizeError(pipewire.SizeMax + 1), "size 16777216 out of range"},
|
||||||
|
|
||||||
|
{"InvalidUnmarshalError untyped nil", new(pipewire.InvalidUnmarshalError), "attempting to unmarshal to nil"},
|
||||||
|
{"InvalidUnmarshalError non-pointer", &pipewire.InvalidUnmarshalError{
|
||||||
|
Type: reflect.TypeFor[uintptr](),
|
||||||
|
}, "attempting to unmarshal to non-pointer type uintptr"},
|
||||||
|
{"InvalidUnmarshalError nil", &pipewire.InvalidUnmarshalError{
|
||||||
|
Type: reflect.TypeFor[*uintptr](),
|
||||||
|
}, "attempting to unmarshal to nil *uintptr"},
|
||||||
|
|
||||||
|
{"UnexpectedEOFError ErrEOFPrefix", pipewire.ErrEOFPrefix, "unexpected EOF decoding fixed-size POD prefix"},
|
||||||
|
{"UnexpectedEOFError ErrEOFData", pipewire.ErrEOFData, "unexpected EOF establishing POD data bounds"},
|
||||||
|
{"UnexpectedEOFError ErrEOFDataString", pipewire.ErrEOFDataString, "unexpected EOF establishing POD String bounds"},
|
||||||
|
{"UnexpectedEOFError invalid", pipewire.UnexpectedEOFError(0xbad), "unexpected EOF"},
|
||||||
|
|
||||||
|
{"UnmarshalSetError", &pipewire.UnmarshalSetError{
|
||||||
|
Type: reflect.TypeFor[*uintptr](),
|
||||||
|
}, "cannot set *uintptr"},
|
||||||
|
|
||||||
|
{"TrailingGarbageError short", make(pipewire.TrailingGarbageError, 1<<3-1), "got 7 bytes of trailing garbage"},
|
||||||
|
{"TrailingGarbageError String", pipewire.TrailingGarbageError{
|
||||||
|
/* size: */ 0, 0, 0, 0,
|
||||||
|
/* type: */ byte(pipewire.SPA_TYPE_String), 0, 0, 0,
|
||||||
|
}, "data has extra values starting with String"},
|
||||||
|
{"TrailingGarbageError invalid", pipewire.TrailingGarbageError{
|
||||||
|
/* size: */ 0, 0, 0, 0,
|
||||||
|
/* type: */ 0xff, 0xff, 0xff, 0xff,
|
||||||
|
/* garbage: */ 0,
|
||||||
|
}, "data has extra values starting with invalid type field 0xffffffff"},
|
||||||
|
|
||||||
|
{"StringTerminationError", pipewire.StringTerminationError(0xff), "got byte 255 instead of NUL"},
|
||||||
|
|
||||||
|
{"InconsistentSizeError", pipewire.InconsistentSizeError{
|
||||||
|
Prefix: 0xbad,
|
||||||
|
Expect: 0xff,
|
||||||
|
}, "prefix claims size 2989 for a 255-byte long segment"},
|
||||||
|
|
||||||
|
{"UnexpectedTypeError zero", pipewire.UnexpectedTypeError{}, "received invalid type field 0x0 for a value of type invalid type field 0x0"},
|
||||||
|
{"UnexpectedTypeError", pipewire.UnexpectedTypeError{
|
||||||
|
Type: pipewire.SPA_TYPE_String,
|
||||||
|
Expect: pipewire.SPA_TYPE_Array,
|
||||||
|
}, "received String for a value of type Array"},
|
||||||
|
{"UnexpectedTypeError invalid", pipewire.UnexpectedTypeError{
|
||||||
|
Type: 0xdeadbeef,
|
||||||
|
Expect: pipewire.SPA_TYPE_Long,
|
||||||
|
}, "received invalid type field 0xdeadbeef for a value of type Long"},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
if got := tc.err.Error(); got != tc.want {
|
||||||
|
t.Errorf("Error: %q, want %q", got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var benchmarkSample = func() (sample pipewire.CoreInfo) {
|
var benchmarkSample = func() (sample pipewire.CoreInfo) {
|
||||||
if err := sample.UnmarshalBinary(samplePWContainer[1][0][1]); err != nil {
|
if err := sample.UnmarshalBinary(samplePWContainer[1][0][1]); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|||||||
Reference in New Issue
Block a user