pipewire/pinos/client/buffer.c
Wim Taymans 4a5ed1e1f5 Rework how clients connect.
Add buffer flags. The idea is to make it possible to easily check when a
buffer contains control information that we need to parse to update the
port fields.
Make the client create remote nodes and ports and set up proxies for
them.
Make a port base class implementing most of the logic to pass buffers
locally and remotely.
Remove most code from stream.c, it's now in the port.
Make a portsink and portsrc that can write and read to/from any port. We
use these in the server to send and receive data.
Rework format negotiation. The final format is now sent in-line before
the data. The server will select a format on output ports.
2016-05-17 09:38:30 +02:00

843 lines
20 KiB
C

/* Pinos
* Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include <string.h>
#include <gio/gio.h>
#include "pinos/client/properties.h"
#include "pinos/client/context.h"
#include "pinos/client/buffer.h"
#include "pinos/client/private.h"
G_STATIC_ASSERT (sizeof (PinosStackBuffer) <= sizeof (PinosBuffer));
/**
* pinos_buffer_init_data:
* @buffer: a #PinosBuffer
* @data: data
* @size: size of @data
* @fds: file descriptors
* @n_fds: number of file descriptors
*
* Initialize @buffer with @data and @size and @fds and @n_fds.
* The memory pointer to by @data and @fds becomes property of @buffer
* and should not be freed or modified until pinos_buffer_clear() is
* called.
*/
void
pinos_buffer_init_data (PinosBuffer *buffer,
gpointer data,
gsize size,
gint *fds,
gint n_fds)
{
PinosStackBuffer *sb = PSB (buffer);
sb->magic = PSB_MAGIC;
sb->data = data;
sb->size = size;
sb->max_size = size;
sb->free_data = NULL;
sb->fds = fds;
sb->n_fds = n_fds;
sb->max_fds = n_fds;
sb->free_fds = NULL;
}
void
pinos_buffer_clear (PinosBuffer *buffer)
{
PinosStackBuffer *sb = PSB (buffer);
gint i;
g_return_if_fail (is_valid_buffer (buffer));
sb->magic = 0;
g_free (sb->free_data);
for (i = 0; i < sb->n_fds; i++)
close (sb->fds[i]);
g_free (sb->free_fds);
sb->n_fds = 0;
}
/**
* pinos_buffer_get_version
* @buffer: a #PinosBuffer
*
* Get the buffer version
*
* Returns: the buffer version.
*/
guint32
pinos_buffer_get_version (PinosBuffer *buffer)
{
PinosStackBuffer *sb = PSB (buffer);
PinosStackHeader *hdr;
g_return_val_if_fail (is_valid_buffer (buffer), -1);
hdr = sb->data;
return hdr->version;
}
/**
* pinos_buffer_get_flags
* @buffer: a #PinosBuffer
*
* Get the buffer flags
*
* Returns: the buffer flags.
*/
PinosBufferFlags
pinos_buffer_get_flags (PinosBuffer *buffer)
{
PinosStackBuffer *sb = PSB (buffer);
PinosStackHeader *hdr;
g_return_val_if_fail (is_valid_buffer (buffer), -1);
hdr = sb->data;
return hdr->flags;
}
/**
* pinos_buffer_get_fd:
* @buffer: a #PinosBuffer
* @index: an index
*
* Get the file descriptor at @index in @buffer.
*
* Returns: a file descriptor at @index in @buffer. The file descriptor
* is not duplicated in any way. -1 is returned on error.
*/
int
pinos_buffer_get_fd (PinosBuffer *buffer, gint index)
{
PinosStackBuffer *sb = PSB (buffer);
g_return_val_if_fail (is_valid_buffer (buffer), -1);
if (sb->fds == NULL || sb->n_fds < index)
return -1;
return sb->fds[index];
}
/**
* pinos_buffer_steal_data:
* @buffer: a #PinosBuffer
* @size: output size or %NULL to ignore
*
* Take the data from @buffer.
*
* Returns: the data of @buffer.
*/
gpointer
pinos_buffer_steal_data (PinosBuffer *buffer,
gsize *size)
{
PinosStackBuffer *sb = PSB (buffer);
gpointer data;
g_return_val_if_fail (is_valid_buffer (buffer), 0);
data = sb->data;
if (size)
*size = sb->size;
if (sb->data != sb->free_data)
g_free (sb->free_data);
sb->data = NULL;
sb->free_data = NULL;
sb->size = 0;
sb->max_size = 0;
return data;
}
/**
* pinos_buffer_steal_fds:
* @buffer: a #PinosBuffer
* @n_fds: number of fds
*
* Take the fds from @buffer.
*
* Returns: the fds of @buffer.
*/
gint *
pinos_buffer_steal_fds (PinosBuffer *buffer,
gint *n_fds)
{
PinosStackBuffer *sb = PSB (buffer);
gint *fds;
g_return_val_if_fail (is_valid_buffer (buffer), 0);
fds = sb->fds;
if (n_fds)
*n_fds = sb->n_fds;
if (sb->fds != sb->free_fds)
g_free (sb->free_fds);
sb->fds = NULL;
sb->free_fds = NULL;
sb->n_fds = 0;
sb->max_fds = 0;
return fds;
}
/**
* PinosBufferIter:
*
* #PinosBufferIter is an opaque data structure and can only be accessed
* using the following functions.
*/
struct stack_iter {
gsize magic;
guint32 version;
PinosStackBuffer *buffer;
gsize offset;
PinosPacketType type;
gsize size;
gpointer data;
guint item;
};
G_STATIC_ASSERT (sizeof (struct stack_iter) <= sizeof (PinosBufferIter));
#define PPSI(i) ((struct stack_iter *) (i))
#define PPSI_MAGIC ((gsize) 6739527471u)
#define is_valid_iter(i) (i != NULL && \
PPSI(i)->magic == PPSI_MAGIC)
/**
* pinos_buffer_iter_init:
* @iter: a #PinosBufferIter
* @buffer: a #PinosBuffer
*
* Initialize @iter to iterate the packets in @buffer.
*/
void
pinos_buffer_iter_init_full (PinosBufferIter *iter,
PinosBuffer *buffer,
guint32 version)
{
struct stack_iter *si = PPSI (iter);
g_return_if_fail (iter != NULL);
g_return_if_fail (is_valid_buffer (buffer));
si->magic = PPSI_MAGIC;
si->version = version;
si->buffer = PSB (buffer);
si->offset = 0;
si->type = PINOS_PACKET_TYPE_INVALID;
si->size = sizeof (PinosStackHeader);
si->data = NULL;
si->item = 0;
}
static gboolean
read_length (guint8 * data, guint size, gsize * length, gsize * skip)
{
gsize len, offset;
guint8 b;
/* start reading the length, we need this to skip to the data later */
len = offset = 0;
do {
if (offset >= size)
return FALSE;
b = data[offset++];
len = (len << 7) | (b & 0x7f);
} while (b & 0x80);
/* check remaining buffer size */
if (size - offset < len)
return FALSE;
*length = len;
*skip = offset;
return TRUE;
}
/**
* pinos_buffer_iter_next:
* @iter: a #PinosBufferIter
*
* Move to the next packet in @iter.
*
* Returns: %TRUE if more packets are available.
*/
gboolean
pinos_buffer_iter_next (PinosBufferIter *iter)
{
struct stack_iter *si = PPSI (iter);
gsize len, size, skip;
guint8 *data;
g_return_val_if_fail (is_valid_iter (iter), FALSE);
/* move to next packet */
si->offset += si->size;
/* now read packet */
data = si->buffer->data;
size = si->buffer->size;
if (si->offset >= size)
return FALSE;
data += si->offset;
size -= si->offset;
if (size < 1)
return FALSE;
si->type = *data;
data++;
size--;
if (!read_length (data, size, &len, &skip))
return FALSE;
si->size = len;
si->data = data + skip;
si->offset += 1 + skip;
return TRUE;
}
PinosPacketType
pinos_buffer_iter_get_type (PinosBufferIter *iter)
{
struct stack_iter *si = PPSI (iter);
g_return_val_if_fail (is_valid_iter (iter), PINOS_PACKET_TYPE_INVALID);
return si->type;
}
gpointer
pinos_buffer_iter_get_data (PinosBufferIter *iter, gsize *size)
{
struct stack_iter *si = PPSI (iter);
g_return_val_if_fail (is_valid_iter (iter), NULL);
if (size)
*size = si->size;
return si->data;
}
/**
* PinosBufferBuilder:
* @buffer: owner #PinosBuffer
*/
struct stack_builder {
gsize magic;
PinosStackHeader *sh;
PinosStackBuffer buf;
PinosPacketType type;
gsize offset;
};
G_STATIC_ASSERT (sizeof (struct stack_builder) <= sizeof (PinosBufferBuilder));
#define PPSB(b) ((struct stack_builder *) (b))
#define PPSB_MAGIC ((gsize) 8103647428u)
#define is_valid_builder(b) (b != NULL && \
PPSB(b)->magic == PPSB_MAGIC)
/**
* pinos_buffer_builder_init_full:
* @builder: a #PinosBufferBuilder
* @version: a version
* @data: data to build into or %NULL to allocate
* @max_data: allocated size of @data
* @fds: memory for fds
* @max_fds: maximum number of fds in @fds
*
* Initialize a stack allocated @builder and set the @version.
*/
void
pinos_buffer_builder_init_full (PinosBufferBuilder *builder,
guint32 version,
gpointer data,
gsize max_data,
gint *fds,
gint max_fds)
{
struct stack_builder *sb = PPSB (builder);
PinosStackHeader *sh;
g_return_if_fail (builder != NULL);
sb->magic = PPSB_MAGIC;
if (max_data < sizeof (PinosStackHeader) || data == NULL) {
sb->buf.max_size = sizeof (PinosStackHeader) + 128;
sb->buf.data = g_malloc (sb->buf.max_size);
sb->buf.free_data = sb->buf.data;
} else {
sb->buf.max_size = max_data;
sb->buf.data = data;
sb->buf.free_data = NULL;
}
sb->buf.size = sizeof (PinosStackHeader);
sb->buf.fds = fds;
sb->buf.max_fds = max_fds;
sb->buf.n_fds = 0;
sb->buf.free_fds = NULL;
sh = sb->sh = sb->buf.data;
sh->version = version;
sh->flags = 0;
sh->length = 0;
sb->type = 0;
sb->offset = 0;
}
/**
* pinos_buffer_builder_set_flags:
* @builder: a #PinosBufferBuilder
* @flags: flags to set
*
* Set the flags on the buffer from @builder.
*/
void
pinos_buffer_builder_set_flags (PinosBufferBuilder *builder, PinosBufferFlags flags)
{
struct stack_builder *sb = PPSB (builder);
g_return_if_fail (is_valid_builder (builder));
sb->sh->flags = flags;
}
/**
* pinos_buffer_builder_clear:
* @builder: a #PinosBufferBuilder
*
* Clear the memory used by @builder. This can be used to abort building the
* buffer.
*
* @builder becomes invalid after this function and can be reused with
* pinos_buffer_builder_init()
*/
void
pinos_buffer_builder_clear (PinosBufferBuilder *builder)
{
struct stack_builder *sb = PPSB (builder);
g_return_if_fail (is_valid_builder (builder));
sb->magic = 0;
g_free (sb->buf.free_data);
g_free (sb->buf.free_fds);
}
/**
* pinos_buffer_builder_end:
* @builder: a #PinosBufferBuilder
* @buffer: a #PinosBuffer
*
* Ends the building process and fills @buffer with the constructed
* #PinosBuffer.
*
* @builder becomes invalid after this function and can be reused with
* pinos_buffer_builder_init()
*/
void
pinos_buffer_builder_end (PinosBufferBuilder *builder,
PinosBuffer *buffer)
{
struct stack_builder *sb = PPSB (builder);
PinosStackBuffer *sbuf = PSB (buffer);
g_return_if_fail (is_valid_builder (builder));
g_return_if_fail (buffer != NULL);
sb->magic = 0;
sb->sh->length = sb->buf.size - sizeof (PinosStackHeader);
sbuf->magic = PSB_MAGIC;
sbuf->data = sb->buf.data;
sbuf->size = sb->buf.size;
sbuf->max_size = sb->buf.max_size;
sbuf->free_data = sb->buf.free_data;
sbuf->fds = sb->buf.fds;
sbuf->n_fds = sb->buf.n_fds;
sbuf->max_fds = sb->buf.max_fds;
sbuf->free_fds = sb->buf.free_fds;
}
/**
* pinos_buffer_builder_add_fd:
* @builder: a #PinosBufferBuilder
* @fd: a valid fd
*
* Add the file descriptor @fd to @builder.
*
* Returns: the index of the file descriptor in @builder.
*/
gint
pinos_buffer_builder_add_fd (PinosBufferBuilder *builder,
int fd)
{
struct stack_builder *sb = PPSB (builder);
gint index;
g_return_val_if_fail (is_valid_builder (builder), -1);
g_return_val_if_fail (fd > 0, -1);
if (sb->buf.n_fds >= sb->buf.max_fds) {
sb->buf.max_fds += 8;
sb->buf.free_fds = g_realloc (sb->buf.free_fds, sb->buf.max_fds * sizeof (int));
sb->buf.fds = sb->buf.free_fds;
}
index = sb->buf.n_fds;
sb->buf.fds[index] = fd;
sb->buf.n_fds++;
return index;
}
static gpointer
builder_ensure_size (struct stack_builder *sb, gsize size)
{
if (sb->buf.size + size > sb->buf.max_size) {
sb->buf.max_size = sb->buf.size + MAX (size, 1024);
sb->buf.free_data = g_realloc (sb->buf.free_data, sb->buf.max_size);
sb->sh = sb->buf.data = sb->buf.free_data;
}
return (guint8 *) sb->buf.data + sb->buf.size;
}
static gpointer
builder_add_packet (struct stack_builder *sb, PinosPacketType type, gsize size)
{
guint8 *p;
guint plen;
plen = 1;
while (size >> (7 * plen))
plen++;
/* 1 for type, plen for size and size for payload */
p = builder_ensure_size (sb, 1 + plen + size);
sb->type = type;
sb->offset = sb->buf.size;
sb->buf.size += 1 + plen + size;
*p++ = type;
/* write length */
while (plen) {
plen--;
*p++ = ((plen > 0) ? 0x80 : 0) | ((size >> (7 * plen)) & 0x7f);
}
return p;
}
/* header packets */
/**
* pinos_buffer_iter_get_header:
* @iter: a #PinosBufferIter
* @header: a #PinosPacketHeader
*
* Get the #PinosPacketHeader. @iter must be positioned on a packet of
* type #PINOS_PACKET_TYPE_HEADER
*
* Returns: %TRUE if @header contains valid data.
*/
gboolean
pinos_buffer_iter_parse_header (PinosBufferIter *iter,
PinosPacketHeader *header)
{
struct stack_iter *si = PPSI (iter);
g_return_val_if_fail (is_valid_iter (iter), FALSE);
g_return_val_if_fail (si->type == PINOS_PACKET_TYPE_HEADER, FALSE);
if (si->size < sizeof (PinosPacketHeader))
return FALSE;
memcpy (header, si->data, sizeof (*header));
return TRUE;
}
/**
* pinos_buffer_builder_add_header:
* @builder: a #PinosBufferBuilder
* @header: a #PinosPacketHeader
*
* Add a #PINOS_PACKET_TYPE_HEADER to @builder with data from @header.
*
* Returns: %TRUE on success.
*/
gboolean
pinos_buffer_builder_add_header (PinosBufferBuilder *builder,
PinosPacketHeader *header)
{
struct stack_builder *sb = PPSB (builder);
PinosPacketHeader *h;
g_return_val_if_fail (is_valid_builder (builder), FALSE);
h = builder_add_packet (sb, PINOS_PACKET_TYPE_HEADER, sizeof (PinosPacketHeader));
memcpy (h, header, sizeof (*header));
return TRUE;
}
/* fd-payload packets */
/**
* pinos_buffer_iter_get_fd_payload:
* @iter: a #PinosBufferIter
* @payload: a #PinosPacketFDPayload
*
* Get the #PinosPacketFDPayload. @iter must be positioned on a packet of
* type #PINOS_PACKET_TYPE_FD_PAYLOAD
*
* Returns: %TRUE if @payload contains valid data.
*/
gboolean
pinos_buffer_iter_parse_fd_payload (PinosBufferIter *iter,
PinosPacketFDPayload *payload)
{
struct stack_iter *si = PPSI (iter);
g_return_val_if_fail (is_valid_iter (iter), FALSE);
g_return_val_if_fail (si->type == PINOS_PACKET_TYPE_FD_PAYLOAD, FALSE);
if (si->size < sizeof (PinosPacketFDPayload))
return FALSE;
memcpy (payload, si->data, sizeof (*payload));
return TRUE;
}
/**
* pinos_buffer_builder_add_fd_payload:
* @builder: a #PinosBufferBuilder
* @payload: a #PinosPacketFDPayload
*
* Add a #PINOS_PACKET_TYPE_FD_PAYLOAD to @builder with data from @payload.
*
* Returns: %TRUE on success.
*/
gboolean
pinos_buffer_builder_add_fd_payload (PinosBufferBuilder *builder,
PinosPacketFDPayload *payload)
{
struct stack_builder *sb = PPSB (builder);
PinosPacketFDPayload *p;
g_return_val_if_fail (is_valid_builder (builder), FALSE);
g_return_val_if_fail (payload->size > 0, FALSE);
p = builder_add_packet (sb, PINOS_PACKET_TYPE_FD_PAYLOAD, sizeof (PinosPacketFDPayload));
memcpy (p, payload, sizeof (*payload));
return TRUE;
}
/**
* pinos_buffer_iter_parse_release_fd_payload:
* @iter: a #PinosBufferIter
* @payload: a #PinosPacketReleaseFDPayload
*
* Parse a #PINOS_PACKET_TYPE_RELEASE_FD_PAYLOAD packet from @iter into @payload.
*
* Returns: %TRUE on success
*/
gboolean
pinos_buffer_iter_parse_release_fd_payload (PinosBufferIter *iter,
PinosPacketReleaseFDPayload *payload)
{
struct stack_iter *si = PPSI (iter);
g_return_val_if_fail (is_valid_iter (iter), FALSE);
g_return_val_if_fail (si->type == PINOS_PACKET_TYPE_RELEASE_FD_PAYLOAD, FALSE);
if (si->size < sizeof (PinosPacketReleaseFDPayload))
return FALSE;
memcpy (payload, si->data, sizeof (*payload));
return TRUE;
}
/**
* pinos_buffer_builder_add_release_fd_payload:
* @builder: a #PinosBufferBuilder
* @payload: a #PinosPacketReleaseFDPayload
*
* Add a #PINOS_PACKET_TYPE_RELEASE_FD_PAYLOAD payload in @payload to @builder.
*
* Returns: %TRUE on success
*/
gboolean
pinos_buffer_builder_add_release_fd_payload (PinosBufferBuilder *builder,
PinosPacketReleaseFDPayload *payload)
{
struct stack_builder *sb = PPSB (builder);
PinosPacketReleaseFDPayload *p;
g_return_val_if_fail (is_valid_builder (builder), FALSE);
p = builder_add_packet (sb,
PINOS_PACKET_TYPE_RELEASE_FD_PAYLOAD,
sizeof (PinosPacketReleaseFDPayload));
memcpy (p, payload, sizeof (*payload));
return TRUE;
}
/**
* pinos_buffer_iter_parse_format_change:
* @iter: a #PinosBufferIter
* @payload: a #PinosPacketFormatChange
*
* Parse a #PINOS_PACKET_TYPE_FORMAT_CHANGE packet from @iter into @payload.
*
* Returns: %TRUE on success
*/
gboolean
pinos_buffer_iter_parse_format_change (PinosBufferIter *iter,
PinosPacketFormatChange *payload)
{
struct stack_iter *si = PPSI (iter);
char *p;
g_return_val_if_fail (is_valid_iter (iter), FALSE);
g_return_val_if_fail (si->type == PINOS_PACKET_TYPE_FORMAT_CHANGE, FALSE);
if (si->size < 2)
return FALSE;
p = si->data;
payload->id = *p++;
payload->format = p;
return TRUE;
}
/**
* pinos_buffer_builder_add_format_change:
* @builder: a #PinosBufferBuilder
* @payload: a #PinosPacketFormatChange
*
* Add a #PINOS_PACKET_TYPE_FORMAT_CHANGE payload in @payload to @builder.
*
* Returns: %TRUE on success
*/
gboolean
pinos_buffer_builder_add_format_change (PinosBufferBuilder *builder,
PinosPacketFormatChange *payload)
{
struct stack_builder *sb = PPSB (builder);
gsize len;
char *p;
g_return_val_if_fail (is_valid_builder (builder), FALSE);
/* id + format len + zero byte */
len = 1 + strlen (payload->format) + 1;
p = builder_add_packet (sb,
PINOS_PACKET_TYPE_FORMAT_CHANGE,
len);
*p++ = payload->id;
strcpy (p, payload->format);
sb->sh->flags |= PINOS_BUFFER_FLAG_CONTROL;
return TRUE;
}
/**
* pinos_buffer_iter_parse_refresh_request:
* @iter: a #PinosBufferIter
* @payload: a #PinosPacketRefreshRequest
*
* Parse a #PINOS_PACKET_TYPE_REFRESH_REQUEST packet from @iter into @payload.
*
* Returns: %TRUE on success.
*/
gboolean
pinos_buffer_iter_parse_refresh_request (PinosBufferIter *iter,
PinosPacketRefreshRequest *payload)
{
struct stack_iter *si = PPSI (iter);
g_return_val_if_fail (is_valid_iter (iter), FALSE);
g_return_val_if_fail (si->type == PINOS_PACKET_TYPE_REFRESH_REQUEST, FALSE);
if (si->size < sizeof (PinosPacketRefreshRequest))
return FALSE;
memcpy (payload, si->data, sizeof (*payload));
return TRUE;
}
/**
* pinos_buffer_builder_add_refresh_request:
* @builder: a #PinosBufferBuilder
* @payload: a #PinosPacketRefreshRequest
*
* Add a #PINOS_PACKET_TYPE_REFRESH_REQUEST payload in @payload to @builder.
*
* Returns: %TRUE on success
*/
gboolean
pinos_buffer_builder_add_refresh_request (PinosBufferBuilder *builder,
PinosPacketRefreshRequest *payload)
{
struct stack_builder *sb = PPSB (builder);
PinosPacketRefreshRequest *p;
g_return_val_if_fail (is_valid_builder (builder), FALSE);
p = builder_add_packet (sb,
PINOS_PACKET_TYPE_REFRESH_REQUEST,
sizeof (PinosPacketRefreshRequest));
memcpy (p, payload, sizeof (*payload));
return TRUE;
}