aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/pipewire
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-11-29 16:08:31 +0900
committerOphestra <cat@gensokyo.uk>2025-11-29 16:46:39 +0900
commitde3fc7ba381988c06fdbb0be8446b8e8d2e75af4 (patch)
tree10a00dee1a050b59b45ed8c8a875855fb471d169 /internal/pipewire
parent5a5c4705dd47c7e27f49526b4ccf0d4c7c1bab5a (diff)
internal/pipewire: implement SecurityContext::Create
This is finally the thing we are after. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/pipewire')
-rw-r--r--internal/pipewire/core_test.go22
-rw-r--r--internal/pipewire/header_test.go48
-rw-r--r--internal/pipewire/pipewire.go24
-rw-r--r--internal/pipewire/pod.go25
-rw-r--r--internal/pipewire/securitycontext.go69
-rw-r--r--internal/pipewire/securitycontext_test.go21
6 files changed, 157 insertions, 52 deletions
diff --git a/internal/pipewire/core_test.go b/internal/pipewire/core_test.go
index ffea96e7..be3f1f94 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 63692769..f0bd20d0 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 6cc89ff0..cee5c865 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 1abc821a..fe6c43ad 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 00000000..849dc958
--- /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 00000000..397107ba
--- /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)
+}