From 10232bfa7d8ec00d9b67653124ee61050e3194dd Mon Sep 17 00:00:00 2001 From: Ophestra Date: Sat, 29 Nov 2025 16:08:31 +0900 Subject: [PATCH] internal/pipewire: implement SecurityContext::Create This is finally the thing we are after. Signed-off-by: Ophestra --- internal/pipewire/core_test.go | 9 +++ internal/pipewire/header_test.go | 20 +++---- internal/pipewire/pipewire.go | 24 -------- internal/pipewire/pod.go | 25 ++++++++ internal/pipewire/securitycontext.go | 69 +++++++++++++++++++++++ internal/pipewire/securitycontext_test.go | 21 +++++++ 6 files changed, 131 insertions(+), 37 deletions(-) create mode 100644 internal/pipewire/securitycontext.go create mode 100644 internal/pipewire/securitycontext_test.go diff --git a/internal/pipewire/core_test.go b/internal/pipewire/core_test.go index ffea96e..21f84ff 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) } diff --git a/internal/pipewire/header_test.go b/internal/pipewire/header_test.go index 6369276..758d50d 100644 --- a/internal/pipewire/header_test.go +++ b/internal/pipewire/header_test.go @@ -314,19 +314,13 @@ 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_CREATE", samplePWContainer[6][0][0], pipewire.Header{ + ID: 3, + Opcode: pipewire.PW_SECURITY_CONTEXT_METHOD_CREATE, + Size: 0xd8, Sequence: 5, FileCount: 2, + }, nil}, {"PW_SECURITY_CONTEXT_METHOD_NUM", []byte{ // Id 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) +}