diff --git a/internal/pipewire/core_test.go b/internal/pipewire/core_test.go index ffea96e..be3f1f9 100644 --- a/internal/pipewire/core_test.go +++ b/internal/pipewire/core_test.go @@ -10,6 +10,7 @@ func TestFooterCoreGeneration(t *testing.T) { t.Parallel() encodingTestCases[pipewire.Footer[pipewire.FooterCoreGeneration], *pipewire.Footer[pipewire.FooterCoreGeneration]]{ + /* recvmsg 0 */ {"sample0", samplePWContainer[1][0][2], pipewire.Footer[pipewire.FooterCoreGeneration]{ @@ -29,6 +30,7 @@ func TestFooterCoreGeneration(t *testing.T) { }.run(t) encodingTestCases[pipewire.Footer[pipewire.FooterClientGeneration], *pipewire.Footer[pipewire.FooterClientGeneration]]{ + /* sendmsg 1 */ {"sample0", samplePWContainer[3][0][2], pipewire.Footer[pipewire.FooterClientGeneration]{ @@ -36,6 +38,13 @@ func TestFooterCoreGeneration(t *testing.T) { // why does this not match FooterCoreGeneration sample2? Payload: pipewire.FooterClientGeneration{ClientGeneration: 0x23}, }, nil}, + + /* sendmsg 2 */ + + {"sample1", samplePWContainer[6][0][2], pipewire.Footer[pipewire.FooterClientGeneration]{ + Opcode: pipewire.FOOTER_CORE_OPCODE_GENERATION, + Payload: pipewire.FooterClientGeneration{ClientGeneration: 0x24}, + }, nil}, }.run(t) } @@ -101,6 +110,12 @@ func TestCoreDone(t *testing.T) { ID: 0, Sequence: pipewire.CoreSyncSequenceOffset + 3, }, nil}, + + // matches the second Core::Sync sample + {"sample2", samplePWContainer[7][0][1], pipewire.CoreDone{ + ID: 0, + Sequence: pipewire.CoreSyncSequenceOffset + 6, + }, nil}, }.run(t) } @@ -150,10 +165,15 @@ func TestCoreSync(t *testing.T) { t.Parallel() encodingTestCases[pipewire.CoreSync, *pipewire.CoreSync]{ - {"sample", samplePWContainer[0][3][1], pipewire.CoreSync{ + {"sample0", samplePWContainer[0][3][1], pipewire.CoreSync{ ID: 0, Sequence: pipewire.CoreSyncSequenceOffset + 3, }, nil}, + + {"sample1", samplePWContainer[6][1][1], pipewire.CoreSync{ + ID: 0, + Sequence: pipewire.CoreSyncSequenceOffset + 6, + }, nil}, }.run(t) } diff --git a/internal/pipewire/header_test.go b/internal/pipewire/header_test.go index 6369276..f0bd20d 100644 --- a/internal/pipewire/header_test.go +++ b/internal/pipewire/header_test.go @@ -32,7 +32,7 @@ func TestHeader(t *testing.T) { Size: 0x28, Sequence: 2, FileCount: 0, }, nil}, - {"PW_CORE_METHOD_SYNC", samplePWContainer[0][3][0], pipewire.Header{ + {"PW_CORE_METHOD_SYNC 0", samplePWContainer[0][3][0], pipewire.Header{ ID: pipewire.PW_ID_CORE, Opcode: pipewire.PW_CORE_METHOD_SYNC, Size: 0x28, Sequence: 3, FileCount: 0, @@ -314,33 +314,27 @@ func TestHeader(t *testing.T) { Size: 0x68, Sequence: 43, FileCount: 0, }, nil}, - {"PW_SECURITY_CONTEXT_METHOD_CREATE", []byte{ - // Id - 3, 0, 0, 0, - // size - 0xd8, 0, 0, - // opcode - 1, - // seq - 5, 0, 0, 0, - // n_fds - 2, 0, 0, 0, - }, pipewire.Header{ID: 3, Opcode: pipewire.PW_SECURITY_CONTEXT_METHOD_CREATE, - Size: 0xd8, Sequence: 5, FileCount: 2}, nil}, + /* sendmsg 2 */ - {"PW_SECURITY_CONTEXT_METHOD_NUM", []byte{ - // Id - 0, 0, 0, 0, - // size - 0x28, 0, 0, - // opcode - 2, - // seq - 6, 0, 0, 0, - // n_fds - 0, 0, 0, 0, - }, pipewire.Header{ID: 0, Opcode: pipewire.PW_SECURITY_CONTEXT_METHOD_NUM, - Size: 0x28, Sequence: 6, FileCount: 0}, nil}, + {"PW_SECURITY_CONTEXT_METHOD_CREATE", samplePWContainer[6][0][0], pipewire.Header{ + ID: 3, + Opcode: pipewire.PW_SECURITY_CONTEXT_METHOD_CREATE, + Size: 0xd8, Sequence: 5, FileCount: 2, + }, nil}, + + {"PW_CORE_METHOD_SYNC 1", samplePWContainer[6][1][0], pipewire.Header{ + ID: pipewire.PW_ID_CORE, + Opcode: pipewire.PW_CORE_METHOD_SYNC, + Size: 0x28, Sequence: 6, FileCount: 0, + }, nil}, + + /* recvmsg 2 */ + + {"PW_CORE_EVENT_DONE 2", samplePWContainer[7][0][0], pipewire.Header{ + ID: pipewire.PW_ID_CORE, + Opcode: pipewire.PW_CORE_EVENT_DONE, + Size: 0x28, Sequence: 44, FileCount: 0, + }, nil}, }.run(t) t.Run("size range", func(t *testing.T) { diff --git a/internal/pipewire/pipewire.go b/internal/pipewire/pipewire.go index 6cc89ff..cee5c86 100644 --- a/internal/pipewire/pipewire.go +++ b/internal/pipewire/pipewire.go @@ -356,30 +356,6 @@ const ( PW_KEY_PROFILER_NAME = "profiler.name" ) -/* pipewire/extensions/security-context.h */ - -const ( - PW_TYPE_INTERFACE_SecurityContext = PW_TYPE_INFO_INTERFACE_BASE + "SecurityContext" - PW_SECURITY_CONTEXT_PERM_MASK = PW_PERM_RWX - PW_VERSION_SECURITY_CONTEXT = 3 - - PW_EXTENSION_MODULE_SECURITY_CONTEXT = PIPEWIRE_MODULE_PREFIX + "module-security-context" -) - -const ( - PW_SECURITY_CONTEXT_EVENT_NUM = iota - - PW_VERSION_SECURITY_CONTEXT_EVENTS = 0 -) - -const ( - PW_SECURITY_CONTEXT_METHOD_ADD_LISTENER = iota - PW_SECURITY_CONTEXT_METHOD_CREATE - PW_SECURITY_CONTEXT_METHOD_NUM - - PW_VERSION_SECURITY_CONTEXT_METHODS = 0 -) - /* pipewire/type.h */ const ( diff --git a/internal/pipewire/pod.go b/internal/pipewire/pod.go index 1abc821..fe6c43a 100644 --- a/internal/pipewire/pod.go +++ b/internal/pipewire/pod.go @@ -30,6 +30,9 @@ type ( String = string // Bytes is a byte slice representing SPA_TYPE_Bytes. Bytes = []byte + + // A Fd is a signed integer value representing SPA_TYPE_Fd. + Fd Long ) const ( @@ -49,6 +52,9 @@ const ( SizeInt Word = 4 // SizeLong is the fixed, unpadded size of a [SPA_TYPE_Long] value. SizeLong Word = 8 + + // SizeFd is the fixed, unpadded size of a [SPA_TYPE_Fd] value. + SizeFd = SizeLong ) /* Basic types */ @@ -176,6 +182,15 @@ func marshalValueAppend(data []byte, v reflect.Value) ([]byte, error) { // marshalValueAppendRaw implements [MarshalAppend] on [reflect.Value] without the size prefix. 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 = binary.NativeEndian.AppendUint64(data, uint64(c)) + return data, nil + } + } + switch v.Kind() { case reflect.Uint32: data = binary.NativeEndian.AppendUint32(data, SPA_TYPE_Id) @@ -312,6 +327,16 @@ func unmarshalValue(data []byte, v reflect.Value, wireSizeP *Word) error { *wireSizeP, err = u.UnmarshalPOD(data) return err } + + switch v.Interface().(type) { + case Fd: + *wireSizeP = SizeFd + if err := unmarshalCheckTypeBounds(&data, SPA_TYPE_Fd, wireSizeP); err != nil { + return err + } + v.SetInt(int64(binary.NativeEndian.Uint64(data))) + return nil + } } switch v.Kind() { diff --git a/internal/pipewire/securitycontext.go b/internal/pipewire/securitycontext.go new file mode 100644 index 0000000..849dc95 --- /dev/null +++ b/internal/pipewire/securitycontext.go @@ -0,0 +1,69 @@ +package pipewire + +/* pipewire/extensions/security-context.h */ + +const ( + PW_TYPE_INTERFACE_SecurityContext = PW_TYPE_INFO_INTERFACE_BASE + "SecurityContext" + PW_SECURITY_CONTEXT_PERM_MASK = PW_PERM_RWX + PW_VERSION_SECURITY_CONTEXT = 3 + + PW_EXTENSION_MODULE_SECURITY_CONTEXT = PIPEWIRE_MODULE_PREFIX + "module-security-context" +) + +const ( + PW_SECURITY_CONTEXT_EVENT_NUM = iota + + PW_VERSION_SECURITY_CONTEXT_EVENTS = 0 +) + +const ( + PW_SECURITY_CONTEXT_METHOD_ADD_LISTENER = iota + PW_SECURITY_CONTEXT_METHOD_CREATE + PW_SECURITY_CONTEXT_METHOD_NUM + + PW_VERSION_SECURITY_CONTEXT_METHODS = 0 +) + +// SecurityContextCreate is sent to create a new security context. +// +// Creates a new security context with a socket listening FD. +// PipeWire will accept new client connections on listen_fd. +// +// listen_fd must be ready to accept new connections when this +// request is sent by the client. In other words, the client must +// call bind(2) and listen(2) before sending the FD. +// +// close_fd is a FD closed by the client when PipeWire should stop +// accepting new connections on listen_fd. +// +// PipeWire must continue to accept connections on listen_fd when +// the client which created the security context disconnects. +// +// After sending this request, closing listen_fd and close_fd +// remains the only valid operation on them. +type SecurityContextCreate struct { + // The offset in the SCM_RIGHTS msg_control message to + // the fd to listen on for new connections. + ListenFd Fd + // The offset in the SCM_RIGHTS msg_control message to + // the fd used to stop listening. + CloseFd Fd + + // Extra properties. These will be copied on the client + // that connects through this context. + Properties *SPADict `json:"props"` +} + +// Size satisfies [KnownSize] with a value computed at runtime. +func (c *SecurityContextCreate) Size() Word { + return SizePrefix + + Size(SizeFd) + + Size(SizeFd) + + c.Properties.Size() +} + +// MarshalBinary satisfies [encoding.BinaryMarshaler] via [Marshal]. +func (c *SecurityContextCreate) MarshalBinary() ([]byte, error) { return Marshal(c) } + +// UnmarshalBinary satisfies [encoding.BinaryUnmarshaler] via [Unmarshal]. +func (c *SecurityContextCreate) UnmarshalBinary(data []byte) error { return Unmarshal(data, c) } diff --git a/internal/pipewire/securitycontext_test.go b/internal/pipewire/securitycontext_test.go new file mode 100644 index 0000000..397107b --- /dev/null +++ b/internal/pipewire/securitycontext_test.go @@ -0,0 +1,21 @@ +package pipewire_test + +import ( + "testing" + + "hakurei.app/internal/pipewire" +) + +func TestSecurityContextCreate(t *testing.T) { + t.Parallel() + + encodingTestCases[pipewire.SecurityContextCreate, *pipewire.SecurityContextCreate]{ + {"sample", samplePWContainer[6][0][1], pipewire.SecurityContextCreate{ + ListenFd: 1 /* 21: duplicated from listen_fd */, CloseFd: 0, /* 20: duplicated from close_fd */ + Properties: &pipewire.SPADict{ + {Key: "pipewire.sec.engine", Value: "org.flatpak"}, + {Key: "pipewire.access", Value: "restricted"}, + }, + }, nil}, + }.run(t) +}