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.
|
||||
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
|
||||
// to encode a value with its encoded size exceeding what could be
|
||||
// represented by the format.
|
||||
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.
|
||||
func Marshal(v any) ([]byte, error) {
|
||||
@@ -220,16 +220,43 @@ func (e *InvalidUnmarshalError) Error() string {
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
// UnexpectedEOFError is returned when EOF was encountered in the middle of decoding POD data.
|
||||
type UnexpectedEOFError struct{}
|
||||
// UnexpectedEOFError describes an unexpected EOF encountered in the middle of decoding POD data.
|
||||
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) 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
|
||||
// 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 {
|
||||
return err
|
||||
} else if len(data) > int(n) {
|
||||
return &TrailingGarbageError{data[int(n):]}
|
||||
return TrailingGarbageError(data[int(n):])
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -261,25 +288,25 @@ func UnmarshalNext(data []byte, v any) (size Word, err error) {
|
||||
// This is likely an unexported struct field.
|
||||
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
|
||||
// has completed during [Unmarshal].
|
||||
type TrailingGarbageError struct{ Data []byte }
|
||||
type TrailingGarbageError []byte
|
||||
|
||||
func (e *TrailingGarbageError) Error() string {
|
||||
if len(e.Data) < SizePrefix {
|
||||
return "got " + strconv.Itoa(len(e.Data)) + " bytes of trailing garbage"
|
||||
func (e TrailingGarbageError) Error() string {
|
||||
if len(e) < SizePrefix {
|
||||
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
|
||||
// encountered during [Unmarshal].
|
||||
type StringTerminationError struct{ Value byte }
|
||||
type StringTerminationError byte
|
||||
|
||||
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.
|
||||
@@ -351,13 +378,13 @@ func unmarshalValue(data []byte, v reflect.Value, wireSizeP *Word) error {
|
||||
}
|
||||
|
||||
if len(data) != 0 {
|
||||
return &TrailingGarbageError{data}
|
||||
return TrailingGarbageError(data)
|
||||
}
|
||||
return nil
|
||||
|
||||
case reflect.Pointer:
|
||||
if len(data) < SizePrefix {
|
||||
return UnexpectedEOFError{}
|
||||
return ErrEOFPrefix
|
||||
}
|
||||
switch SPAKind(binary.NativeEndian.Uint32(data[SizeSPrefix:])) {
|
||||
case SPA_TYPE_None:
|
||||
@@ -378,12 +405,12 @@ func unmarshalValue(data []byte, v reflect.Value, wireSizeP *Word) error {
|
||||
// string size, one extra NUL byte
|
||||
size := int(*wireSizeP)
|
||||
if len(data) < size {
|
||||
return UnexpectedEOFError{}
|
||||
return ErrEOFDataString
|
||||
}
|
||||
|
||||
// the serialised strings still include NUL termination
|
||||
if data[size-1] != 0 {
|
||||
return StringTerminationError{data[size-1]}
|
||||
return StringTerminationError(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].
|
||||
type InconsistentSizeError struct{ Prefix, Expect Word }
|
||||
|
||||
func (e *InconsistentSizeError) Error() string {
|
||||
return "unexpected size prefix: " + strconv.Itoa(int(e.Prefix)) + ", want " + strconv.Itoa(int(e.Expect))
|
||||
func (e InconsistentSizeError) Error() string {
|
||||
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
|
||||
// in data passed to [Unmarshal].
|
||||
type UnexpectedTypeError struct{ Type, Expect SPAKind }
|
||||
|
||||
func (u *UnexpectedTypeError) Error() string {
|
||||
return "received " + u.Type.String() + " for a value of type " + u.Expect.String()
|
||||
func (e UnexpectedTypeError) Error() 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.
|
||||
// An expected size of zero skips further bounds checks.
|
||||
func unmarshalCheckTypeBounds(data *[]byte, t SPAKind, sizeP *Word) error {
|
||||
if len(*data) < SizePrefix {
|
||||
return UnexpectedEOFError{}
|
||||
return ErrEOFPrefix
|
||||
}
|
||||
|
||||
wantSize := *sizeP
|
||||
@@ -422,15 +450,15 @@ func unmarshalCheckTypeBounds(data *[]byte, t SPAKind, sizeP *Word) error {
|
||||
*sizeP = gotSize
|
||||
|
||||
if wantSize != 0 && gotSize != wantSize {
|
||||
return &InconsistentSizeError{gotSize, wantSize}
|
||||
return InconsistentSizeError{gotSize, wantSize}
|
||||
}
|
||||
if len(*data)-SizePrefix < int(gotSize) {
|
||||
return UnexpectedEOFError{}
|
||||
return ErrEOFData
|
||||
}
|
||||
|
||||
gotType := SPAKind(binary.NativeEndian.Uint32((*data)[SizeSPrefix:]))
|
||||
if gotType != t {
|
||||
return &UnexpectedTypeError{gotType, t}
|
||||
return UnexpectedTypeError{gotType, t}
|
||||
}
|
||||
|
||||
*data = (*data)[SizePrefix : gotSize+SizePrefix]
|
||||
@@ -541,7 +569,7 @@ func (d *SPADict) UnmarshalPOD(data []byte) (Word, error) {
|
||||
}
|
||||
|
||||
if len(data) != 0 {
|
||||
return wireSize, &TrailingGarbageError{data}
|
||||
return wireSize, TrailingGarbageError(data)
|
||||
}
|
||||
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) {
|
||||
if err := sample.UnmarshalBinary(samplePWContainer[1][0][1]); err != nil {
|
||||
panic(err)
|
||||
|
||||
Reference in New Issue
Block a user