2021-06-18 23:36:35 +02:00
|
|
|
/* PipeWire
|
|
|
|
|
*
|
|
|
|
|
* Copyright © 2020 Wim Taymans
|
|
|
|
|
*
|
|
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
|
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
|
|
|
* to deal in the Software without restriction, including without limitation
|
|
|
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
|
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
|
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
|
|
|
*
|
|
|
|
|
* The above copyright notice and this permission notice (including the next
|
|
|
|
|
* paragraph) shall be included in all copies or substantial portions of the
|
|
|
|
|
* Software.
|
|
|
|
|
*
|
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
|
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
|
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
|
|
|
* DEALINGS IN THE SOFTWARE.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <stdbool.h>
|
|
|
|
|
#include <stdint.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
|
#include <sys/socket.h>
|
|
|
|
|
|
|
|
|
|
#include <spa/utils/defs.h>
|
2021-11-11 23:24:41 +01:00
|
|
|
#include <spa/utils/hook.h>
|
2021-06-18 23:36:35 +02:00
|
|
|
#include <spa/utils/list.h>
|
|
|
|
|
#include <pipewire/core.h>
|
|
|
|
|
#include <pipewire/log.h>
|
|
|
|
|
#include <pipewire/loop.h>
|
|
|
|
|
#include <pipewire/map.h>
|
|
|
|
|
#include <pipewire/properties.h>
|
|
|
|
|
|
|
|
|
|
#include "client.h"
|
|
|
|
|
#include "commands.h"
|
|
|
|
|
#include "defs.h"
|
|
|
|
|
#include "internal.h"
|
2021-09-22 09:28:54 +10:00
|
|
|
#include "log.h"
|
2021-06-18 23:36:35 +02:00
|
|
|
#include "manager.h"
|
|
|
|
|
#include "message.h"
|
|
|
|
|
#include "operation.h"
|
|
|
|
|
#include "pending-sample.h"
|
2021-06-18 23:46:50 +02:00
|
|
|
#include "server.h"
|
2021-06-18 23:36:35 +02:00
|
|
|
#include "stream.h"
|
|
|
|
|
|
2021-11-13 14:14:19 +01:00
|
|
|
#define client_emit_disconnect(c) spa_hook_list_call(&(c)->listener_list, struct client_events, disconnect, 0)
|
|
|
|
|
|
2021-11-11 23:24:41 +01:00
|
|
|
struct client *client_new(struct server *server)
|
|
|
|
|
{
|
|
|
|
|
struct client *client = calloc(1, sizeof(*client));
|
|
|
|
|
if (client == NULL)
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
client->ref = 1;
|
|
|
|
|
client->server = server;
|
|
|
|
|
client->impl = server->impl;
|
|
|
|
|
client->connect_tag = SPA_ID_INVALID;
|
|
|
|
|
|
|
|
|
|
pw_map_init(&client->streams, 16, 16);
|
|
|
|
|
spa_list_init(&client->out_messages);
|
|
|
|
|
spa_list_init(&client->operations);
|
|
|
|
|
spa_list_init(&client->pending_samples);
|
|
|
|
|
spa_list_init(&client->pending_streams);
|
2021-11-13 14:14:19 +01:00
|
|
|
spa_hook_list_init(&client->listener_list);
|
2021-11-11 23:24:41 +01:00
|
|
|
|
|
|
|
|
spa_list_append(&server->clients, &client->link);
|
|
|
|
|
server->n_clients++;
|
|
|
|
|
|
|
|
|
|
return client;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-18 23:36:35 +02:00
|
|
|
static int client_free_stream(void *item, void *data)
|
|
|
|
|
{
|
|
|
|
|
struct stream *s = item;
|
|
|
|
|
|
|
|
|
|
stream_free(s);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* tries to detach the client from the server,
|
|
|
|
|
* but it does not drop the server's reference
|
|
|
|
|
*/
|
|
|
|
|
bool client_detach(struct client *client)
|
|
|
|
|
{
|
|
|
|
|
struct impl *impl = client->impl;
|
|
|
|
|
struct server *server = client->server;
|
|
|
|
|
|
|
|
|
|
if (server == NULL)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
pw_log_debug("client %p: detaching from server %p", client, server);
|
|
|
|
|
|
|
|
|
|
/* remove from the `server->clients` list */
|
|
|
|
|
spa_list_remove(&client->link);
|
2022-01-31 16:12:54 +01:00
|
|
|
spa_list_append(&impl->cleanup_clients, &client->link);
|
2021-06-18 23:36:35 +02:00
|
|
|
|
|
|
|
|
server->n_clients--;
|
|
|
|
|
if (server->wait_clients > 0 && --server->wait_clients == 0) {
|
|
|
|
|
int mask = server->source->mask;
|
|
|
|
|
SPA_FLAG_SET(mask, SPA_IO_IN);
|
|
|
|
|
pw_loop_update_io(impl->loop, server->source, mask);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
client->server = NULL;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void client_disconnect(struct client *client)
|
|
|
|
|
{
|
|
|
|
|
struct impl *impl = client->impl;
|
|
|
|
|
|
|
|
|
|
if (client->disconnect)
|
|
|
|
|
return;
|
|
|
|
|
|
2021-11-13 14:14:19 +01:00
|
|
|
client_emit_disconnect(client);
|
|
|
|
|
|
2021-06-18 23:36:35 +02:00
|
|
|
/* the client must be detached from the server to disconnect */
|
|
|
|
|
spa_assert(client->server == NULL);
|
|
|
|
|
|
|
|
|
|
client->disconnect = true;
|
|
|
|
|
|
|
|
|
|
pw_map_for_each(&client->streams, client_free_stream, client);
|
|
|
|
|
|
2022-01-14 17:59:35 +01:00
|
|
|
if (client->source) {
|
2021-06-18 23:36:35 +02:00
|
|
|
pw_loop_destroy_source(impl->loop, client->source);
|
2022-01-14 17:59:35 +01:00
|
|
|
client->source = NULL;
|
|
|
|
|
}
|
2021-06-18 23:36:35 +02:00
|
|
|
|
2022-01-14 17:59:35 +01:00
|
|
|
if (client->manager) {
|
2021-06-18 23:36:35 +02:00
|
|
|
pw_manager_destroy(client->manager);
|
2022-01-14 17:59:35 +01:00
|
|
|
client->manager = NULL;
|
|
|
|
|
}
|
2021-06-18 23:36:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void client_free(struct client *client)
|
|
|
|
|
{
|
|
|
|
|
struct impl *impl = client->impl;
|
|
|
|
|
struct pending_sample *p;
|
|
|
|
|
struct message *msg;
|
|
|
|
|
struct operation *o;
|
|
|
|
|
|
|
|
|
|
pw_log_debug("client %p: free", client);
|
|
|
|
|
|
|
|
|
|
client_detach(client);
|
|
|
|
|
client_disconnect(client);
|
|
|
|
|
|
|
|
|
|
/* remove from the `impl->cleanup_clients` list */
|
|
|
|
|
spa_list_remove(&client->link);
|
|
|
|
|
|
|
|
|
|
spa_list_consume(p, &client->pending_samples, link)
|
|
|
|
|
pending_sample_free(p);
|
|
|
|
|
|
2021-11-23 18:13:35 +01:00
|
|
|
if (client->message)
|
|
|
|
|
message_free(impl, client->message, false, false);
|
|
|
|
|
|
2021-06-18 23:36:35 +02:00
|
|
|
spa_list_consume(msg, &client->out_messages, link)
|
|
|
|
|
message_free(impl, msg, true, false);
|
|
|
|
|
|
|
|
|
|
spa_list_consume(o, &client->operations, link)
|
|
|
|
|
operation_free(o);
|
|
|
|
|
|
|
|
|
|
if (client->core) {
|
|
|
|
|
client->disconnecting = true;
|
|
|
|
|
pw_core_disconnect(client->core);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pw_map_clear(&client->streams);
|
|
|
|
|
|
2022-01-31 17:30:40 +01:00
|
|
|
pw_work_queue_cancel(impl->work_queue, client, SPA_ID_INVALID);
|
|
|
|
|
|
2021-06-18 23:36:35 +02:00
|
|
|
free(client->default_sink);
|
|
|
|
|
free(client->default_source);
|
|
|
|
|
|
2021-11-16 18:25:32 +01:00
|
|
|
pw_properties_free(client->props);
|
|
|
|
|
pw_properties_free(client->routes);
|
2021-06-18 23:36:35 +02:00
|
|
|
|
2021-11-13 14:14:19 +01:00
|
|
|
spa_hook_list_clean(&client->listener_list);
|
|
|
|
|
|
2021-06-18 23:36:35 +02:00
|
|
|
free(client);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int client_queue_message(struct client *client, struct message *msg)
|
|
|
|
|
{
|
|
|
|
|
struct impl *impl = client->impl;
|
2021-11-11 12:36:43 +01:00
|
|
|
int res;
|
2021-06-18 23:36:35 +02:00
|
|
|
|
|
|
|
|
if (msg == NULL)
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
2021-11-11 11:43:28 +01:00
|
|
|
if (client->disconnect) {
|
|
|
|
|
res = -ENOTCONN;
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-18 23:36:35 +02:00
|
|
|
if (msg->length == 0) {
|
|
|
|
|
res = 0;
|
|
|
|
|
goto error;
|
|
|
|
|
} else if (msg->length > msg->allocated) {
|
|
|
|
|
res = -ENOMEM;
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
msg->offset = 0;
|
|
|
|
|
spa_list_append(&client->out_messages, &msg->link);
|
|
|
|
|
|
2021-11-11 12:36:43 +01:00
|
|
|
uint32_t mask = client->source->mask;
|
2021-06-18 23:36:35 +02:00
|
|
|
if (!SPA_FLAG_IS_SET(mask, SPA_IO_OUT)) {
|
|
|
|
|
SPA_FLAG_SET(mask, SPA_IO_OUT);
|
|
|
|
|
pw_loop_update_io(impl->loop, client->source, mask);
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-11 12:36:43 +01:00
|
|
|
client->new_msg_since_last_flush = true;
|
|
|
|
|
|
2021-06-18 23:36:35 +02:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
error:
|
|
|
|
|
message_free(impl, msg, false, false);
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-11 12:36:43 +01:00
|
|
|
static int client_try_flush_messages(struct client *client)
|
2021-06-18 23:36:35 +02:00
|
|
|
{
|
|
|
|
|
struct impl *impl = client->impl;
|
2021-11-11 12:36:43 +01:00
|
|
|
|
|
|
|
|
pw_log_trace("client %p: flushing", client);
|
2021-06-18 23:36:35 +02:00
|
|
|
|
2021-11-11 11:43:28 +01:00
|
|
|
spa_assert(!client->disconnect);
|
|
|
|
|
|
2021-11-11 12:36:43 +01:00
|
|
|
while (!spa_list_is_empty(&client->out_messages)) {
|
|
|
|
|
struct message *m = spa_list_first(&client->out_messages, struct message, link);
|
2021-06-18 23:36:35 +02:00
|
|
|
struct descriptor desc;
|
2021-11-11 12:36:43 +01:00
|
|
|
const void *data;
|
2021-06-18 23:36:35 +02:00
|
|
|
size_t size;
|
|
|
|
|
|
|
|
|
|
if (client->out_index < sizeof(desc)) {
|
|
|
|
|
desc.length = htonl(m->length);
|
|
|
|
|
desc.channel = htonl(m->channel);
|
|
|
|
|
desc.offset_hi = 0;
|
|
|
|
|
desc.offset_lo = 0;
|
|
|
|
|
desc.flags = 0;
|
|
|
|
|
|
|
|
|
|
data = SPA_PTROFF(&desc, client->out_index, void);
|
|
|
|
|
size = sizeof(desc) - client->out_index;
|
|
|
|
|
} else if (client->out_index < m->length + sizeof(desc)) {
|
|
|
|
|
uint32_t idx = client->out_index - sizeof(desc);
|
|
|
|
|
data = m->data + idx;
|
|
|
|
|
size = m->length - idx;
|
|
|
|
|
} else {
|
|
|
|
|
if (debug_messages && m->channel == SPA_ID_INVALID)
|
|
|
|
|
message_dump(SPA_LOG_LEVEL_INFO, m);
|
|
|
|
|
message_free(impl, m, true, false);
|
|
|
|
|
client->out_index = 0;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (true) {
|
2021-11-11 12:36:43 +01:00
|
|
|
ssize_t sent = send(client->source->fd, data, size, MSG_NOSIGNAL | MSG_DONTWAIT);
|
|
|
|
|
if (sent < 0) {
|
|
|
|
|
int res = -errno;
|
2021-06-18 23:36:35 +02:00
|
|
|
if (res == -EINTR)
|
|
|
|
|
continue;
|
|
|
|
|
if (res != -EAGAIN && res != -EWOULDBLOCK)
|
2021-11-11 12:36:43 +01:00
|
|
|
pw_log_warn("client %p: send channel:%u %zu, error %d: %m",
|
2021-06-18 23:36:35 +02:00
|
|
|
client, m->channel, size, res);
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-11 12:36:43 +01:00
|
|
|
client->out_index += sent;
|
2021-06-18 23:36:35 +02:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-11 12:36:43 +01:00
|
|
|
int client_flush_messages(struct client *client)
|
|
|
|
|
{
|
|
|
|
|
client->new_msg_since_last_flush = false;
|
|
|
|
|
|
|
|
|
|
int res = client_try_flush_messages(client);
|
|
|
|
|
if (res >= 0) {
|
|
|
|
|
uint32_t mask = client->source->mask;
|
|
|
|
|
|
|
|
|
|
if (SPA_FLAG_IS_SET(mask, SPA_IO_OUT)) {
|
|
|
|
|
SPA_FLAG_CLEAR(mask, SPA_IO_OUT);
|
|
|
|
|
pw_loop_update_io(client->impl->loop, client->source, mask);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (res != -EAGAIN && res != -EWOULDBLOCK)
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-13 18:38:00 +01:00
|
|
|
static bool drop_from_out_queue(struct client *client, struct message *m)
|
|
|
|
|
{
|
|
|
|
|
spa_assert(!spa_list_is_empty(&client->out_messages));
|
|
|
|
|
|
|
|
|
|
struct message *first = spa_list_first(&client->out_messages, struct message, link);
|
|
|
|
|
if (m == first && client->out_index > 0)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
message_free(client->impl, m, true, false);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-17 11:28:40 +01:00
|
|
|
/* returns true if an event with the (mask, event, index) triplet should be dropped because it is redundant */
|
|
|
|
|
static bool client_prune_subscribe_events(struct client *client, uint32_t mask, uint32_t event, uint32_t index)
|
2021-06-18 23:36:35 +02:00
|
|
|
{
|
2022-01-13 18:38:00 +01:00
|
|
|
struct message *m, *t;
|
2021-11-11 12:36:43 +01:00
|
|
|
|
|
|
|
|
if ((event & SUBSCRIPTION_EVENT_TYPE_MASK) == SUBSCRIPTION_EVENT_NEW)
|
|
|
|
|
return false;
|
2021-06-18 23:36:35 +02:00
|
|
|
|
2022-01-13 18:38:00 +01:00
|
|
|
/* NOTE: reverse iteration */
|
2021-11-11 12:36:43 +01:00
|
|
|
spa_list_for_each_safe_reverse(m, t, &client->out_messages, link) {
|
|
|
|
|
if (m->extra[0] != COMMAND_SUBSCRIBE_EVENT)
|
|
|
|
|
continue;
|
|
|
|
|
if ((m->extra[1] ^ event) & SUBSCRIPTION_EVENT_FACILITY_MASK)
|
|
|
|
|
continue;
|
2022-01-17 11:28:40 +01:00
|
|
|
if (m->extra[2] != index)
|
2021-11-11 12:36:43 +01:00
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if ((event & SUBSCRIPTION_EVENT_TYPE_MASK) == SUBSCRIPTION_EVENT_REMOVE) {
|
|
|
|
|
/* This object is being removed, hence there is
|
|
|
|
|
* point in keeping the old events regarding
|
|
|
|
|
* entry in the queue. */
|
2021-11-11 12:39:49 +01:00
|
|
|
|
2022-01-13 18:38:00 +01:00
|
|
|
bool is_new = (m->extra[1] & SUBSCRIPTION_EVENT_TYPE_MASK) == SUBSCRIPTION_EVENT_NEW;
|
|
|
|
|
|
|
|
|
|
if (drop_from_out_queue(client, m)) {
|
|
|
|
|
pw_log_debug("client %p: dropped redundant event due to remove event for object %u",
|
2022-01-17 11:28:40 +01:00
|
|
|
client, index);
|
2022-01-13 18:38:00 +01:00
|
|
|
|
|
|
|
|
/* if the NEW event for the current object could successfully be dropped,
|
|
|
|
|
there is no need to deliver the REMOVE event */
|
|
|
|
|
if (is_new)
|
|
|
|
|
goto drop;
|
2021-11-11 12:39:49 +01:00
|
|
|
}
|
2022-01-13 18:38:00 +01:00
|
|
|
|
|
|
|
|
/* stop if the NEW event for the current object is reached */
|
|
|
|
|
if (is_new)
|
|
|
|
|
break;
|
2021-11-11 12:36:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((event & SUBSCRIPTION_EVENT_TYPE_MASK) == SUBSCRIPTION_EVENT_CHANGE) {
|
|
|
|
|
/* This object has changed. If a "new" or "change" event for
|
|
|
|
|
* this object is still in the queue we can exit. */
|
2022-01-13 18:38:00 +01:00
|
|
|
goto drop;
|
2021-11-11 12:36:43 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
2022-01-13 18:38:00 +01:00
|
|
|
|
|
|
|
|
drop:
|
2022-01-17 11:28:40 +01:00
|
|
|
pw_log_debug("client %p: dropped redundant event for object %u", client, index);
|
2022-01-13 18:38:00 +01:00
|
|
|
|
|
|
|
|
return true;
|
2021-11-11 12:36:43 +01:00
|
|
|
}
|
|
|
|
|
|
2022-01-17 11:28:40 +01:00
|
|
|
int client_queue_subscribe_event(struct client *client, uint32_t mask, uint32_t event, uint32_t index)
|
2021-11-11 12:36:43 +01:00
|
|
|
{
|
2021-11-11 11:43:28 +01:00
|
|
|
if (client->disconnect)
|
|
|
|
|
return -ENOTCONN;
|
|
|
|
|
|
2021-06-18 23:36:35 +02:00
|
|
|
if (!(client->subscribed & mask))
|
|
|
|
|
return 0;
|
|
|
|
|
|
2022-01-17 11:28:40 +01:00
|
|
|
pw_log_debug("client %p: SUBSCRIBE event:%08x index:%u", client, event, index);
|
2021-06-18 23:36:35 +02:00
|
|
|
|
2022-01-17 11:28:40 +01:00
|
|
|
if (client_prune_subscribe_events(client, mask, event, index))
|
2021-11-11 12:36:43 +01:00
|
|
|
return 0;
|
2021-06-18 23:36:35 +02:00
|
|
|
|
2021-11-11 12:36:43 +01:00
|
|
|
struct message *reply = message_alloc(client->impl, -1, 0);
|
|
|
|
|
reply->extra[0] = COMMAND_SUBSCRIBE_EVENT;
|
|
|
|
|
reply->extra[1] = event;
|
2022-01-17 11:28:40 +01:00
|
|
|
reply->extra[2] = index;
|
2021-06-18 23:36:35 +02:00
|
|
|
|
|
|
|
|
message_put(reply,
|
|
|
|
|
TAG_U32, COMMAND_SUBSCRIBE_EVENT,
|
|
|
|
|
TAG_U32, -1,
|
|
|
|
|
TAG_U32, event,
|
2022-01-17 11:28:40 +01:00
|
|
|
TAG_U32, index,
|
2021-06-18 23:36:35 +02:00
|
|
|
TAG_INVALID);
|
|
|
|
|
|
|
|
|
|
return client_queue_message(client, reply);
|
|
|
|
|
}
|