mirror of
https://github.com/albfan/miraclecast.git
synced 2025-03-09 23:38:56 +00:00
1432 lines
29 KiB
C
1432 lines
29 KiB
C
/*
|
|
* MiracleCast - Wifi-Display/Miracast Implementation
|
|
*
|
|
* Copyright (c) 2013-2014 David Herrmann <dh.herrmann@gmail.com>
|
|
*
|
|
* MiracleCast is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation; either version 2.1 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* MiracleCast 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with MiracleCast; If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <systemd/sd-bus.h>
|
|
#include "ctl.h"
|
|
#include "shl_dlist.h"
|
|
#include "shl_macro.h"
|
|
#include "shl_util.h"
|
|
#include "util.h"
|
|
|
|
/*
|
|
* Peers
|
|
*/
|
|
|
|
static void ctl_peer_free(struct ctl_peer *p)
|
|
{
|
|
if (!p)
|
|
return;
|
|
|
|
if (shl_dlist_linked(&p->list))
|
|
ctl_fn_peer_free(p);
|
|
|
|
free(p->wfd_subelements);
|
|
free(p->remote_address);
|
|
free(p->local_address);
|
|
free(p->interface);
|
|
free(p->friendly_name);
|
|
free(p->p2p_mac);
|
|
|
|
shl_dlist_unlink(&p->list);
|
|
free(p->label);
|
|
free(p);
|
|
}
|
|
|
|
static void ctl_peer_free_p(struct ctl_peer **p)
|
|
{
|
|
ctl_peer_free(*p);
|
|
}
|
|
|
|
#define _ctl_peer_free_ _shl_cleanup_(ctl_peer_free_p)
|
|
|
|
static int ctl_peer_new(struct ctl_peer **out,
|
|
struct ctl_link *l,
|
|
const char *label)
|
|
{
|
|
struct ctl_peer *p;
|
|
int r;
|
|
|
|
if (!l || !label)
|
|
return cli_EINVAL();
|
|
|
|
p = calloc(1, sizeof(*p));
|
|
if (!p)
|
|
return cli_ENOMEM();
|
|
|
|
p->l = l;
|
|
|
|
p->label = strdup(label);
|
|
if (!p->label) {
|
|
r = cli_ENOMEM();
|
|
goto error;
|
|
}
|
|
|
|
if (out)
|
|
*out = p;
|
|
|
|
return 0;
|
|
|
|
error:
|
|
ctl_peer_free(p);
|
|
return r;
|
|
}
|
|
|
|
static void ctl_peer_link(struct ctl_peer *p)
|
|
{
|
|
if (!p || shl_dlist_linked(&p->list))
|
|
return;
|
|
|
|
shl_dlist_link_tail(&p->l->peers, &p->list);
|
|
ctl_fn_peer_new(p);
|
|
}
|
|
|
|
static int ctl_peer_parse_properties(struct ctl_peer *p,
|
|
sd_bus_message *m)
|
|
{
|
|
const char *t, *p2p_mac = NULL, *friendly_name = NULL;
|
|
const char *interface = NULL, *local_address = NULL;
|
|
const char *remote_address = NULL, *wfd_subelements = NULL;
|
|
bool connected_set = false;
|
|
char *tmp;
|
|
int connected, r;
|
|
|
|
if (!p || !m)
|
|
return cli_EINVAL();
|
|
|
|
r = sd_bus_message_enter_container(m, 'a', "{sv}");
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
|
|
while ((r = sd_bus_message_enter_container(m,
|
|
'e',
|
|
"sv")) > 0) {
|
|
r = sd_bus_message_read(m, "s", &t);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
|
|
if (!strcmp(t, "P2PMac")) {
|
|
r = bus_message_read_basic_variant(m, "s", &p2p_mac);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
} else if (!strcmp(t, "FriendlyName")) {
|
|
r = bus_message_read_basic_variant(m, "s",
|
|
&friendly_name);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
} else if (!strcmp(t, "Connected")) {
|
|
r = bus_message_read_basic_variant(m, "b",
|
|
&connected);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
|
|
connected_set = true;
|
|
} else if (!strcmp(t, "Interface")) {
|
|
r = bus_message_read_basic_variant(m, "s",
|
|
&interface);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
} else if (!strcmp(t, "LocalAddress")) {
|
|
r = bus_message_read_basic_variant(m, "s",
|
|
&local_address);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
} else if (!strcmp(t, "RemoteAddress")) {
|
|
r = bus_message_read_basic_variant(m, "s",
|
|
&remote_address);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
} else if (!strcmp(t, "WfdSubelements")) {
|
|
r = bus_message_read_basic_variant(m, "s",
|
|
&wfd_subelements);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
} else {
|
|
sd_bus_message_skip(m, "v");
|
|
}
|
|
|
|
r = sd_bus_message_exit_container(m);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
}
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
|
|
r = sd_bus_message_exit_container(m);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
|
|
if (p2p_mac) {
|
|
tmp = strdup(p2p_mac);
|
|
if (tmp) {
|
|
free(p->p2p_mac);
|
|
p->p2p_mac = tmp;
|
|
} else {
|
|
cli_vENOMEM();
|
|
}
|
|
}
|
|
|
|
if (friendly_name) {
|
|
tmp = strdup(friendly_name);
|
|
if (tmp) {
|
|
free(p->friendly_name);
|
|
p->friendly_name = tmp;
|
|
} else {
|
|
cli_vENOMEM();
|
|
}
|
|
}
|
|
|
|
if (interface) {
|
|
tmp = strdup(interface);
|
|
if (tmp) {
|
|
free(p->interface);
|
|
p->interface = tmp;
|
|
} else {
|
|
cli_vENOMEM();
|
|
}
|
|
}
|
|
|
|
if (local_address) {
|
|
tmp = strdup(local_address);
|
|
if (tmp) {
|
|
free(p->local_address);
|
|
p->local_address = tmp;
|
|
} else {
|
|
cli_vENOMEM();
|
|
}
|
|
}
|
|
|
|
if (remote_address) {
|
|
tmp = strdup(remote_address);
|
|
if (tmp) {
|
|
free(p->remote_address);
|
|
p->remote_address = tmp;
|
|
} else {
|
|
cli_vENOMEM();
|
|
}
|
|
}
|
|
|
|
if (wfd_subelements) {
|
|
tmp = strdup(wfd_subelements);
|
|
if (tmp) {
|
|
free(p->wfd_subelements);
|
|
p->wfd_subelements = tmp;
|
|
} else {
|
|
cli_vENOMEM();
|
|
}
|
|
}
|
|
|
|
/* do notifications last */
|
|
if (connected_set && p->connected != connected) {
|
|
p->connected = connected;
|
|
if (p->connected)
|
|
ctl_fn_peer_connected(p);
|
|
else
|
|
ctl_fn_peer_disconnected(p);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ctl_peer_connect(struct ctl_peer *p, const char *prov, const char *pin)
|
|
{
|
|
_sd_bus_error_free_ sd_bus_error err = SD_BUS_ERROR_NULL;
|
|
_shl_free_ char *node = NULL;
|
|
int r;
|
|
|
|
if (!p)
|
|
return cli_EINVAL();
|
|
|
|
r = sd_bus_path_encode("/org/freedesktop/miracle/wifi/peer",
|
|
p->label,
|
|
&node);
|
|
if (r < 0)
|
|
return cli_ERR(r);
|
|
|
|
r = sd_bus_call_method(p->l->w->bus,
|
|
"org.freedesktop.miracle.wifi",
|
|
node,
|
|
"org.freedesktop.miracle.wifi.Peer",
|
|
"Connect",
|
|
&err,
|
|
NULL,
|
|
"ss",
|
|
prov ? : "auto",
|
|
pin ? : "");
|
|
if (r < 0) {
|
|
cli_error("cannot connect peer %s: %s",
|
|
p->label, bus_error_message(&err, r));
|
|
return r;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ctl_peer_disconnect(struct ctl_peer *p)
|
|
{
|
|
_sd_bus_error_free_ sd_bus_error err = SD_BUS_ERROR_NULL;
|
|
_shl_free_ char *node = NULL;
|
|
int r;
|
|
|
|
if (!p)
|
|
return cli_EINVAL();
|
|
|
|
r = sd_bus_path_encode("/org/freedesktop/miracle/wifi/peer",
|
|
p->label,
|
|
&node);
|
|
if (r < 0)
|
|
return cli_ERR(r);
|
|
|
|
r = sd_bus_call_method(p->l->w->bus,
|
|
"org.freedesktop.miracle.wifi",
|
|
node,
|
|
"org.freedesktop.miracle.wifi.Peer",
|
|
"Disconnect",
|
|
&err,
|
|
NULL,
|
|
NULL);
|
|
if (r < 0) {
|
|
cli_error("cannot disconnect peer %s: %s",
|
|
p->label, bus_error_message(&err, r));
|
|
return r;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Links
|
|
*/
|
|
|
|
static struct ctl_peer *ctl_link_find_peer(struct ctl_link *l,
|
|
const char *label)
|
|
{
|
|
struct shl_dlist *i;
|
|
struct ctl_peer *p;
|
|
|
|
shl_dlist_for_each(i, &l->peers) {
|
|
p = peer_from_dlist(i);
|
|
if (!strcasecmp(p->label, label))
|
|
return p;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void ctl_link_free(struct ctl_link *l)
|
|
{
|
|
struct ctl_peer *p;
|
|
|
|
if (!l)
|
|
return;
|
|
|
|
while (!shl_dlist_empty(&l->peers)) {
|
|
p = shl_dlist_last_entry(&l->peers, struct ctl_peer, list);
|
|
ctl_peer_free(p);
|
|
}
|
|
|
|
if (shl_dlist_linked(&l->list))
|
|
ctl_fn_link_free(l);
|
|
|
|
free(l->wfd_subelements);
|
|
free(l->friendly_name);
|
|
free(l->ifname);
|
|
|
|
shl_dlist_unlink(&l->list);
|
|
free(l->label);
|
|
free(l);
|
|
}
|
|
|
|
static void ctl_link_free_p(struct ctl_link **l)
|
|
{
|
|
ctl_link_free(*l);
|
|
}
|
|
|
|
#define _ctl_link_free_ _shl_cleanup_(ctl_link_free_p)
|
|
|
|
static int ctl_link_new(struct ctl_link **out,
|
|
struct ctl_wifi *w,
|
|
const char *label)
|
|
{
|
|
struct ctl_link *l;
|
|
int r;
|
|
|
|
if (!w || !label)
|
|
return cli_EINVAL();
|
|
|
|
l = calloc(1, sizeof(*l));
|
|
if (!l)
|
|
return cli_ENOMEM();
|
|
|
|
l->w = w;
|
|
shl_dlist_init(&l->peers);
|
|
|
|
l->label = strdup(label);
|
|
if (!l->label) {
|
|
r = cli_ENOMEM();
|
|
goto error;
|
|
}
|
|
|
|
if (out)
|
|
*out = l;
|
|
|
|
return 0;
|
|
|
|
error:
|
|
ctl_link_free(l);
|
|
return r;
|
|
}
|
|
|
|
static void ctl_link_link(struct ctl_link *l)
|
|
{
|
|
if (!l || shl_dlist_linked(&l->list))
|
|
return;
|
|
|
|
shl_dlist_link_tail(&l->w->links, &l->list);
|
|
ctl_fn_link_new(l);
|
|
}
|
|
|
|
static int ctl_link_parse_properties(struct ctl_link *l,
|
|
sd_bus_message *m)
|
|
{
|
|
const char *t, *interface_name = NULL, *friendly_name = NULL;
|
|
const char *wfd_subelements = NULL;
|
|
unsigned int interface_index = 0;
|
|
bool p2p_scanning_set = false;
|
|
char *tmp;
|
|
int p2p_scanning, r;
|
|
bool managed_set = false;
|
|
int managed;
|
|
|
|
if (!l || !m)
|
|
return cli_EINVAL();
|
|
|
|
r = sd_bus_message_enter_container(m, 'a', "{sv}");
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
|
|
while ((r = sd_bus_message_enter_container(m,
|
|
'e',
|
|
"sv")) > 0) {
|
|
r = sd_bus_message_read(m, "s", &t);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
|
|
if (!strcmp(t, "InterfaceIndex")) {
|
|
r = bus_message_read_basic_variant(m, "u",
|
|
&interface_index);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
} else if (!strcmp(t, "InterfaceName")) {
|
|
r = bus_message_read_basic_variant(m, "s",
|
|
&interface_name);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
} else if (!strcmp(t, "FriendlyName")) {
|
|
r = bus_message_read_basic_variant(m, "s",
|
|
&friendly_name);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
} else if (!strcmp(t, "Managed")) {
|
|
r = bus_message_read_basic_variant(m, "b",
|
|
&managed);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
|
|
managed_set = true;
|
|
} else if (!strcmp(t, "P2PScanning")) {
|
|
r = bus_message_read_basic_variant(m, "b",
|
|
&p2p_scanning);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
|
|
p2p_scanning_set = true;
|
|
} else if (!strcmp(t, "WfdSubelements")) {
|
|
r = bus_message_read_basic_variant(m, "s",
|
|
&wfd_subelements);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
} else {
|
|
sd_bus_message_skip(m, "v");
|
|
}
|
|
|
|
r = sd_bus_message_exit_container(m);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
}
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
|
|
r = sd_bus_message_exit_container(m);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
|
|
if (interface_index)
|
|
l->ifindex = interface_index;
|
|
|
|
if (interface_name) {
|
|
tmp = strdup(interface_name);
|
|
if (tmp) {
|
|
free(l->ifname);
|
|
l->ifname = tmp;
|
|
} else {
|
|
cli_vENOMEM();
|
|
}
|
|
}
|
|
|
|
if (friendly_name) {
|
|
tmp = strdup(friendly_name);
|
|
if (tmp) {
|
|
free(l->friendly_name);
|
|
l->friendly_name = tmp;
|
|
} else {
|
|
cli_vENOMEM();
|
|
}
|
|
}
|
|
|
|
if (managed_set)
|
|
l->managed = managed;
|
|
|
|
if (p2p_scanning_set)
|
|
l->p2p_scanning = p2p_scanning;
|
|
|
|
if (wfd_subelements) {
|
|
tmp = strdup(wfd_subelements);
|
|
if (tmp) {
|
|
free(l->wfd_subelements);
|
|
l->wfd_subelements = tmp;
|
|
} else {
|
|
cli_vENOMEM();
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ctl_link_set_friendly_name(struct ctl_link *l, const char *name)
|
|
{
|
|
_sd_bus_message_unref_ sd_bus_message *m = NULL;
|
|
_sd_bus_error_free_ sd_bus_error err = SD_BUS_ERROR_NULL;
|
|
_shl_free_ char *node = NULL;
|
|
int r;
|
|
|
|
if (!l)
|
|
return cli_EINVAL();
|
|
if (!strcmp(l->friendly_name, name))
|
|
return 0;
|
|
|
|
r = sd_bus_path_encode("/org/freedesktop/miracle/wifi/link",
|
|
l->label,
|
|
&node);
|
|
if (r < 0)
|
|
return cli_ERR(r);
|
|
|
|
r = sd_bus_message_new_method_call(l->w->bus,
|
|
&m,
|
|
"org.freedesktop.miracle.wifi",
|
|
node,
|
|
"org.freedesktop.DBus.Properties",
|
|
"Set");
|
|
if (r < 0)
|
|
return cli_log_create(r);
|
|
|
|
r = sd_bus_message_append(m, "ss",
|
|
"org.freedesktop.miracle.wifi.Link",
|
|
"FriendlyName");
|
|
if (r < 0)
|
|
return cli_log_create(r);
|
|
|
|
r = sd_bus_message_open_container(m, 'v', "s");
|
|
if (r < 0)
|
|
return cli_log_create(r);
|
|
|
|
r = sd_bus_message_append(m, "s", name);
|
|
if (r < 0)
|
|
return cli_log_create(r);
|
|
|
|
r = sd_bus_message_close_container(m);
|
|
if (r < 0)
|
|
return cli_log_create(r);
|
|
|
|
r = sd_bus_call(l->w->bus, m, 0, &err, NULL);
|
|
if (r < 0) {
|
|
cli_error("cannot change friendly-name on link %s to %s: %s",
|
|
l->label, name, bus_error_message(&err, r));
|
|
return r;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ctl_link_set_wfd_subelements(struct ctl_link *l, const char *val)
|
|
{
|
|
_sd_bus_message_unref_ sd_bus_message *m = NULL;
|
|
_sd_bus_error_free_ sd_bus_error err = SD_BUS_ERROR_NULL;
|
|
_shl_free_ char *node = NULL;
|
|
int r;
|
|
|
|
if (!l)
|
|
return cli_EINVAL();
|
|
if (!strcmp(l->wfd_subelements, val))
|
|
return 0;
|
|
|
|
r = sd_bus_path_encode("/org/freedesktop/miracle/wifi/link",
|
|
l->label,
|
|
&node);
|
|
if (r < 0)
|
|
return cli_ERR(r);
|
|
|
|
r = sd_bus_message_new_method_call(l->w->bus,
|
|
&m,
|
|
"org.freedesktop.miracle.wifi",
|
|
node,
|
|
"org.freedesktop.DBus.Properties",
|
|
"Set");
|
|
if (r < 0)
|
|
return cli_log_create(r);
|
|
|
|
r = sd_bus_message_append(m, "ss",
|
|
"org.freedesktop.miracle.wifi.Link",
|
|
"WfdSubelements");
|
|
if (r < 0)
|
|
return cli_log_create(r);
|
|
|
|
r = sd_bus_message_open_container(m, 'v', "s");
|
|
if (r < 0)
|
|
return cli_log_create(r);
|
|
|
|
r = sd_bus_message_append(m, "s", val);
|
|
if (r < 0)
|
|
return cli_log_create(r);
|
|
|
|
r = sd_bus_message_close_container(m);
|
|
if (r < 0)
|
|
return cli_log_create(r);
|
|
|
|
r = sd_bus_call(l->w->bus, m, 0, &err, NULL);
|
|
if (r < 0) {
|
|
cli_error("cannot change WfdSubelements on link %s to %s: %s",
|
|
l->label, val, bus_error_message(&err, r));
|
|
return r;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ctl_link_set_managed(struct ctl_link *l, bool val)
|
|
{
|
|
_sd_bus_message_unref_ sd_bus_message *m = NULL;
|
|
_sd_bus_error_free_ sd_bus_error err = SD_BUS_ERROR_NULL;
|
|
_shl_free_ char *node = NULL;
|
|
int r;
|
|
|
|
if (!l)
|
|
return cli_EINVAL();
|
|
if (l->managed == val)
|
|
return 0;
|
|
|
|
r = sd_bus_path_encode("/org/freedesktop/miracle/wifi/link",
|
|
l->label,
|
|
&node);
|
|
if (r < 0)
|
|
return cli_ERR(r);
|
|
|
|
r = sd_bus_message_new_method_call(l->w->bus,
|
|
&m,
|
|
"org.freedesktop.miracle.wifi",
|
|
node,
|
|
"org.freedesktop.DBus.Properties",
|
|
"Set");
|
|
if (r < 0)
|
|
return cli_log_create(r);
|
|
|
|
r = sd_bus_message_append(m, "ss",
|
|
"org.freedesktop.miracle.wifi.Link",
|
|
"Managed");
|
|
if (r < 0)
|
|
return cli_log_create(r);
|
|
|
|
r = sd_bus_message_open_container(m, 'v', "b");
|
|
if (r < 0)
|
|
return cli_log_create(r);
|
|
|
|
r = sd_bus_message_append(m, "b", val);
|
|
if (r < 0)
|
|
return cli_log_create(r);
|
|
|
|
r = sd_bus_message_close_container(m);
|
|
if (r < 0)
|
|
return cli_log_create(r);
|
|
|
|
r = sd_bus_call(l->w->bus, m, 0, &err, NULL);
|
|
if (r < 0) {
|
|
cli_error("cannot change managed state on link %s to %d: %s",
|
|
l->label, val, bus_error_message(&err, r));
|
|
return r;
|
|
}
|
|
|
|
l->managed = val;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ctl_link_set_p2p_scanning(struct ctl_link *l, bool val)
|
|
{
|
|
_sd_bus_message_unref_ sd_bus_message *m = NULL;
|
|
_sd_bus_error_free_ sd_bus_error err = SD_BUS_ERROR_NULL;
|
|
_shl_free_ char *node = NULL;
|
|
int r;
|
|
|
|
if (!l)
|
|
return cli_EINVAL();
|
|
if (l->p2p_scanning == val)
|
|
return 0;
|
|
|
|
r = sd_bus_path_encode("/org/freedesktop/miracle/wifi/link",
|
|
l->label,
|
|
&node);
|
|
if (r < 0)
|
|
return cli_ERR(r);
|
|
|
|
r = sd_bus_message_new_method_call(l->w->bus,
|
|
&m,
|
|
"org.freedesktop.miracle.wifi",
|
|
node,
|
|
"org.freedesktop.DBus.Properties",
|
|
"Set");
|
|
if (r < 0)
|
|
return cli_log_create(r);
|
|
|
|
r = sd_bus_message_append(m, "ss",
|
|
"org.freedesktop.miracle.wifi.Link",
|
|
"P2PScanning");
|
|
if (r < 0)
|
|
return cli_log_create(r);
|
|
|
|
r = sd_bus_message_open_container(m, 'v', "b");
|
|
if (r < 0)
|
|
return cli_log_create(r);
|
|
|
|
r = sd_bus_message_append(m, "b", val);
|
|
if (r < 0)
|
|
return cli_log_create(r);
|
|
|
|
r = sd_bus_message_close_container(m);
|
|
if (r < 0)
|
|
return cli_log_create(r);
|
|
|
|
r = sd_bus_call(l->w->bus, m, 0, &err, NULL);
|
|
if (r < 0) {
|
|
cli_error("cannot change p2p-scanning state on link %s to %d: %s",
|
|
l->label, val, bus_error_message(&err, r));
|
|
return r;
|
|
}
|
|
|
|
/* Don't set l->p2p_scanning. We get a PropertiesChanged once the value
|
|
* has really changed. There is no synchronous way to get errors as the
|
|
* application couldn't deal with them, anyway. See the system-log if
|
|
* you want detailed information.
|
|
* However, mark the device as managed scan. This way, the app can stop
|
|
* scans during shutdown, if required. */
|
|
|
|
if (val)
|
|
l->have_p2p_scan = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Wifi Management
|
|
*/
|
|
|
|
static int ctl_wifi_parse_link(struct ctl_wifi *w,
|
|
const char *label,
|
|
sd_bus_message *m)
|
|
{
|
|
_ctl_link_free_ struct ctl_link *l = NULL;
|
|
const char *t;
|
|
int r;
|
|
|
|
r = ctl_link_new(&l, w, label);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = sd_bus_message_enter_container(m, 'a', "{sa{sv}}");
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
|
|
while ((r = sd_bus_message_enter_container(m,
|
|
'e',
|
|
"sa{sv}")) > 0) {
|
|
r = sd_bus_message_read(m, "s", &t);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
|
|
if (strcmp(t, "org.freedesktop.miracle.wifi.Link")) {
|
|
r = sd_bus_message_skip(m, "a{sv}");
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
r = sd_bus_message_exit_container(m);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
continue;
|
|
}
|
|
|
|
r = ctl_link_parse_properties(l, m);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = sd_bus_message_exit_container(m);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
}
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
|
|
r = sd_bus_message_exit_container(m);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
|
|
ctl_link_link(l);
|
|
l = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ctl_wifi_parse_peer(struct ctl_wifi *w,
|
|
const char *label,
|
|
sd_bus_message *m)
|
|
{
|
|
_ctl_peer_free_ struct ctl_peer *p = NULL;
|
|
const char *t;
|
|
struct ctl_link *l;
|
|
int r;
|
|
|
|
l = ctl_wifi_find_link_by_peer(w, label);
|
|
if (!l)
|
|
return -EINVAL;
|
|
|
|
r = ctl_peer_new(&p, l, label);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = sd_bus_message_enter_container(m, 'a', "{sa{sv}}");
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
|
|
while ((r = sd_bus_message_enter_container(m,
|
|
'e',
|
|
"sa{sv}")) > 0) {
|
|
r = sd_bus_message_read(m, "s", &t);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
if (strcmp(t, "org.freedesktop.miracle.wifi.Peer")) {
|
|
r = sd_bus_message_skip(m, "a{sv}");
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
r = sd_bus_message_exit_container(m);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
continue;
|
|
}
|
|
|
|
r = ctl_peer_parse_properties(p, m);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = sd_bus_message_exit_container(m);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
}
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
|
|
r = sd_bus_message_exit_container(m);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
|
|
ctl_peer_link(p);
|
|
p = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ctl_wifi_parse_object(struct ctl_wifi *w,
|
|
sd_bus_message *m,
|
|
bool added)
|
|
{
|
|
_shl_free_ char *label = NULL;
|
|
struct ctl_link *l;
|
|
struct ctl_peer *p;
|
|
const char *t;
|
|
int r;
|
|
|
|
r = sd_bus_message_read(m, "o", &t);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
|
|
r = sd_bus_path_decode(t,
|
|
"/org/freedesktop/miracle/wifi/link",
|
|
&label);
|
|
if (r < 0) {
|
|
return cli_ENOMEM();
|
|
} else if (r > 0) {
|
|
l = ctl_wifi_find_link(w, label);
|
|
if (!l && added) {
|
|
return ctl_wifi_parse_link(w, label, m);
|
|
} else if (l && !added) {
|
|
/* We don't do any dynamic interfaces, so if any of
|
|
* them is removed, all of them are removed. */
|
|
ctl_link_free(l);
|
|
}
|
|
}
|
|
|
|
free(label);
|
|
label = NULL;
|
|
|
|
r = sd_bus_path_decode(t,
|
|
"/org/freedesktop/miracle/wifi/peer",
|
|
&label);
|
|
if (r < 0) {
|
|
return cli_ENOMEM();
|
|
} else if (r > 0) {
|
|
p = ctl_wifi_find_peer(w, label);
|
|
if (!p && added) {
|
|
return ctl_wifi_parse_peer(w, label, m);
|
|
} else if (p && !added) {
|
|
/* We don't do any dynamic interfaces, so if any of
|
|
* them is removed, all of them are removed. */
|
|
ctl_peer_free(p);
|
|
}
|
|
}
|
|
|
|
/* skip unhandled payload */
|
|
if (added)
|
|
r = sd_bus_message_skip(m, "a{sa{sv}}");
|
|
else
|
|
r = sd_bus_message_skip(m, "as");
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ctl_wifi_object_fn(sd_bus_message *m,
|
|
void *data,
|
|
sd_bus_error *err)
|
|
{
|
|
struct ctl_wifi *w = data;
|
|
bool added;
|
|
|
|
added = !strcmp(sd_bus_message_get_member(m), "InterfacesAdded");
|
|
|
|
return ctl_wifi_parse_object(w, m, added);
|
|
}
|
|
|
|
static int ctl_wifi_properties_fn(sd_bus_message *m,
|
|
void *data,
|
|
sd_bus_error *err)
|
|
{
|
|
_shl_free_ char *label = NULL;
|
|
struct ctl_wifi *w = data;
|
|
struct ctl_link *l;
|
|
struct ctl_peer *p;
|
|
const char *t;
|
|
int r;
|
|
|
|
if (!sd_bus_message_is_signal(m,
|
|
"org.freedesktop.DBus.Properties",
|
|
"PropertiesChanged"))
|
|
return 0;
|
|
|
|
t = sd_bus_message_get_path(m);
|
|
if (!t)
|
|
return cli_EINVAL();
|
|
|
|
r = sd_bus_path_decode(t,
|
|
"/org/freedesktop/miracle/wifi/link",
|
|
&label);
|
|
if (r < 0) {
|
|
return cli_ENOMEM();
|
|
} else if (r > 0) {
|
|
l = ctl_wifi_find_link(w, label);
|
|
if (!l)
|
|
return 0;
|
|
|
|
r = sd_bus_message_read(m, "s", &t);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
|
|
if (strcmp(t, "org.freedesktop.miracle.wifi.Link"))
|
|
return 0;
|
|
|
|
return ctl_link_parse_properties(l, m);
|
|
}
|
|
|
|
r = sd_bus_path_decode(t,
|
|
"/org/freedesktop/miracle/wifi/peer",
|
|
&label);
|
|
if (r < 0) {
|
|
return cli_ENOMEM();
|
|
} else if (r > 0) {
|
|
p = ctl_wifi_find_peer(w, label);
|
|
if (!p)
|
|
return 0;
|
|
|
|
r = sd_bus_message_read(m, "s", &t);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
|
|
if (strcmp(t, "org.freedesktop.miracle.wifi.Peer"))
|
|
return 0;
|
|
|
|
return ctl_peer_parse_properties(p, m);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ctl_wifi_peer_fn(sd_bus_message *m,
|
|
void *data,
|
|
sd_bus_error *err)
|
|
{
|
|
_shl_free_ char *label = NULL;
|
|
struct ctl_wifi *w = data;
|
|
struct ctl_peer *p;
|
|
const char *t;
|
|
int r;
|
|
|
|
t = sd_bus_message_get_path(m);
|
|
if (!t)
|
|
return cli_EINVAL();
|
|
|
|
r = sd_bus_path_decode(t,
|
|
"/org/freedesktop/miracle/wifi/peer",
|
|
&label);
|
|
if (r < 0) {
|
|
return cli_ERR(r);
|
|
} else if (r == 0) {
|
|
return 0;
|
|
} else if (r > 0) {
|
|
p = ctl_wifi_find_peer(w, label);
|
|
if (!p)
|
|
return 0;
|
|
}
|
|
|
|
if (sd_bus_message_is_signal(m,
|
|
"org.freedesktop.miracle.wifi.Peer",
|
|
"ProvisionDiscovery")) {
|
|
/* provision discovery */
|
|
const char *prov, *pin;
|
|
|
|
r = sd_bus_message_read(m, "ss", &prov, &pin);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
|
|
ctl_fn_peer_provision_discovery(p, prov, pin);
|
|
} else if (sd_bus_message_is_signal(m,
|
|
"org.freedesktop.miracle.wifi.Peer",
|
|
"GoNegRequest")) {
|
|
/* connection request */
|
|
const char *prov, *pin;
|
|
|
|
r = sd_bus_message_read(m, "ss", &prov, &pin);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
|
|
ctl_fn_peer_go_neg_request(p, prov, pin);
|
|
} else if (sd_bus_message_is_signal(m,
|
|
"org.freedesktop.miracle.wifi.Peer",
|
|
"FormationFailure")) {
|
|
/* group formation failure */
|
|
const char *reason;
|
|
|
|
r = sd_bus_message_read(m, "s", &reason);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
|
|
ctl_fn_peer_formation_failure(p, reason);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ctl_wifi_init(struct ctl_wifi *w)
|
|
{
|
|
int r;
|
|
|
|
r = sd_bus_add_match(w->bus, NULL,
|
|
"type='signal',"
|
|
"sender='org.freedesktop.miracle.wifi',"
|
|
"interface='org.freedesktop.DBus.ObjectManager'",
|
|
ctl_wifi_object_fn,
|
|
w);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = sd_bus_add_match(w->bus, NULL,
|
|
"type='signal',"
|
|
"sender='org.freedesktop.miracle.wifi',"
|
|
"interface='org.freedesktop.DBus.Properties'",
|
|
ctl_wifi_properties_fn,
|
|
w);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = sd_bus_add_match(w->bus, NULL,
|
|
"type='signal',"
|
|
"sender='org.freedesktop.miracle.wifi',"
|
|
"interface='org.freedesktop.miracle.wifi.Peer'",
|
|
ctl_wifi_peer_fn,
|
|
w);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ctl_wifi_destroy(struct ctl_wifi *w)
|
|
{
|
|
|
|
}
|
|
|
|
int ctl_wifi_new(struct ctl_wifi **out, sd_bus *bus)
|
|
{
|
|
struct ctl_wifi *w;
|
|
int r;
|
|
|
|
if (!out || !bus)
|
|
return cli_EINVAL();
|
|
|
|
w = calloc(1, sizeof(*w));
|
|
if (!w)
|
|
return cli_ENOMEM();
|
|
|
|
w->bus = sd_bus_ref(bus);
|
|
shl_dlist_init(&w->links);
|
|
|
|
r = ctl_wifi_init(w);
|
|
if (r < 0) {
|
|
cli_error("cannot initialize wifi-dbus objects");
|
|
ctl_wifi_free(w);
|
|
return r;
|
|
}
|
|
|
|
*out = w;
|
|
return 0;
|
|
}
|
|
|
|
void ctl_wifi_free(struct ctl_wifi *w)
|
|
{
|
|
struct ctl_link *l;
|
|
|
|
if (!w)
|
|
return;
|
|
|
|
while (!shl_dlist_empty(&w->links)) {
|
|
l = shl_dlist_last_entry(&w->links, struct ctl_link, list);
|
|
ctl_link_free(l);
|
|
}
|
|
|
|
ctl_wifi_destroy(w);
|
|
sd_bus_unref(w->bus);
|
|
free(w);
|
|
}
|
|
|
|
static int ctl_wifi_parse_objects(struct ctl_wifi *w,
|
|
sd_bus_message *m,
|
|
bool ignore_link_not_found)
|
|
{
|
|
bool again = false;
|
|
int r = sd_bus_message_enter_container(m, 'a', "{oa{sa{sv}}}");
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
|
|
while ((r = sd_bus_message_enter_container(m,
|
|
'e',
|
|
"oa{sa{sv}}")) > 0) {
|
|
r = ctl_wifi_parse_object(w, m, true);
|
|
if(ignore_link_not_found && -EINVAL == r) {
|
|
r = sd_bus_message_skip(m, "a{sa{sv}}");
|
|
if(0 > r)
|
|
return cli_log_parser(r);
|
|
again = true;
|
|
}
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = sd_bus_message_exit_container(m);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
}
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
|
|
r = sd_bus_message_exit_container(m);
|
|
if (r < 0)
|
|
return cli_log_parser(r);
|
|
|
|
return again ? -EAGAIN : 0;
|
|
}
|
|
|
|
int ctl_wifi_fetch(struct ctl_wifi *w)
|
|
{
|
|
_sd_bus_message_unref_ sd_bus_message *m = NULL;
|
|
_sd_bus_error_free_ sd_bus_error err = SD_BUS_ERROR_NULL;
|
|
int r;
|
|
|
|
if (!w)
|
|
return cli_EINVAL();
|
|
|
|
r = sd_bus_call_method(w->bus,
|
|
"org.freedesktop.miracle.wifi",
|
|
"/org/freedesktop/miracle/wifi",
|
|
"org.freedesktop.DBus.ObjectManager",
|
|
"GetManagedObjects",
|
|
&err,
|
|
&m,
|
|
NULL);
|
|
if (r < 0) {
|
|
cli_error("cannot retrieve objects: %s",
|
|
bus_error_message(&err, r));
|
|
return r;
|
|
}
|
|
|
|
r = ctl_wifi_parse_objects(w, m, true);
|
|
if(0 <= r || -EAGAIN != r) {
|
|
goto end;
|
|
}
|
|
|
|
r = sd_bus_message_rewind(m, true);
|
|
if(0 > r) {
|
|
return r;
|
|
}
|
|
|
|
r = ctl_wifi_parse_objects(w, m, false);
|
|
|
|
end:
|
|
return r;
|
|
}
|
|
|
|
struct ctl_link *ctl_wifi_find_link(struct ctl_wifi *w,
|
|
const char *label)
|
|
{
|
|
struct shl_dlist *i;
|
|
struct ctl_link *l;
|
|
|
|
if (!w || shl_isempty(label))
|
|
return NULL;
|
|
|
|
shl_dlist_for_each(i, &w->links) {
|
|
l = link_from_dlist(i);
|
|
if (!strcasecmp(l->label, label))
|
|
return l;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct ctl_link *ctl_wifi_search_link(struct ctl_wifi *w,
|
|
const char *label)
|
|
{
|
|
struct shl_dlist *i;
|
|
struct ctl_link *l;
|
|
|
|
if (!w || shl_isempty(label))
|
|
return NULL;
|
|
|
|
l = ctl_wifi_find_link(w, label);
|
|
if (l)
|
|
return l;
|
|
|
|
/* try matching on interface */
|
|
shl_dlist_for_each(i, &w->links) {
|
|
l = link_from_dlist(i);
|
|
if (l->ifname && !strcasecmp(l->ifname, label))
|
|
return l;
|
|
}
|
|
|
|
/* try matching on friendly-name */
|
|
shl_dlist_for_each(i, &w->links) {
|
|
l = link_from_dlist(i);
|
|
if (l->friendly_name && !strcasecmp(l->friendly_name, label))
|
|
return l;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct ctl_link *ctl_wifi_find_link_by_peer(struct ctl_wifi *w,
|
|
const char *label)
|
|
{
|
|
const char *sep;
|
|
|
|
if (!w || shl_isempty(label))
|
|
return NULL;
|
|
|
|
sep = strchr(label, '@');
|
|
if (!sep)
|
|
return NULL;
|
|
|
|
return ctl_wifi_find_link(w, sep + 1);
|
|
}
|
|
|
|
struct ctl_link *ctl_wifi_search_link_by_peer(struct ctl_wifi *w,
|
|
const char *label)
|
|
{
|
|
const char *sep;
|
|
|
|
if (!w || shl_isempty(label))
|
|
return NULL;
|
|
|
|
sep = strchr(label, '@');
|
|
if (!sep)
|
|
return NULL;
|
|
|
|
return ctl_wifi_search_link(w, sep + 1);
|
|
}
|
|
|
|
struct ctl_peer *ctl_wifi_find_peer(struct ctl_wifi *w,
|
|
const char *label)
|
|
{
|
|
struct ctl_link *l;
|
|
|
|
if (!w || shl_isempty(label))
|
|
return NULL;
|
|
|
|
/* first try exact match */
|
|
l = ctl_wifi_find_link_by_peer(w, label);
|
|
if (!l)
|
|
return NULL;
|
|
|
|
return ctl_link_find_peer(l, label);
|
|
}
|
|
|
|
struct ctl_peer *ctl_wifi_search_peer(struct ctl_wifi *w,
|
|
const char *real_label)
|
|
{
|
|
_shl_free_ char *label = NULL;
|
|
struct shl_dlist *i, *j;
|
|
struct ctl_link *l;
|
|
struct ctl_peer *p;
|
|
const char *next;
|
|
char *sep;
|
|
unsigned int cnt, idx;
|
|
|
|
if (!w || shl_isempty(real_label))
|
|
return NULL;
|
|
|
|
label = strdup(real_label);
|
|
if (!label) {
|
|
cli_vENOMEM();
|
|
return NULL;
|
|
}
|
|
|
|
p = ctl_wifi_find_peer(w, label);
|
|
if (p)
|
|
return p;
|
|
|
|
l = ctl_wifi_search_link_by_peer(w, label);
|
|
if (l) {
|
|
sep = strchr(label, '@');
|
|
if (sep)
|
|
*sep = 0;
|
|
|
|
shl_dlist_for_each(j, &l->peers) {
|
|
p = shl_dlist_entry(j, struct ctl_peer, list);
|
|
next = shl_startswith(p->label, label);
|
|
if (next && *next == '@')
|
|
return p;
|
|
}
|
|
|
|
shl_dlist_for_each(j, &l->peers) {
|
|
p = shl_dlist_entry(j, struct ctl_peer, list);
|
|
if (p->friendly_name &&
|
|
!strcasecmp(p->friendly_name, label))
|
|
return p;
|
|
}
|
|
|
|
shl_dlist_for_each(j, &l->peers) {
|
|
p = shl_dlist_entry(j, struct ctl_peer, list);
|
|
if (p->interface &&
|
|
!strcasecmp(p->interface, label))
|
|
return p;
|
|
}
|
|
|
|
if (shl_atoi_u(label, 10, &next, &idx) >= 0 && !*next) {
|
|
cnt = 0;
|
|
shl_dlist_for_each(j, &l->peers) {
|
|
p = shl_dlist_entry(j, struct ctl_peer, list);
|
|
if (cnt++ == idx)
|
|
return p;
|
|
}
|
|
}
|
|
|
|
if (sep)
|
|
*sep = '@';
|
|
}
|
|
|
|
shl_dlist_for_each(i, &w->links) {
|
|
l = shl_dlist_entry(i, struct ctl_link, list);
|
|
shl_dlist_for_each(j, &l->peers) {
|
|
p = shl_dlist_entry(j, struct ctl_peer, list);
|
|
next = shl_startswith(p->label, label);
|
|
if (next && *next == '@')
|
|
return p;
|
|
}
|
|
}
|
|
|
|
shl_dlist_for_each(i, &w->links) {
|
|
l = shl_dlist_entry(i, struct ctl_link, list);
|
|
shl_dlist_for_each(j, &l->peers) {
|
|
p = shl_dlist_entry(j, struct ctl_peer, list);
|
|
if (p->friendly_name &&
|
|
!strcasecmp(p->friendly_name, label))
|
|
return p;
|
|
}
|
|
}
|
|
|
|
shl_dlist_for_each(i, &w->links) {
|
|
l = shl_dlist_entry(i, struct ctl_link, list);
|
|
shl_dlist_for_each(j, &l->peers) {
|
|
p = shl_dlist_entry(j, struct ctl_peer, list);
|
|
if (p->interface &&
|
|
!strcasecmp(p->interface, label))
|
|
return p;
|
|
}
|
|
}
|
|
|
|
if (shl_atoi_u(label, 10, &next, &idx) < 0 || *next)
|
|
return NULL;
|
|
|
|
cnt = 0;
|
|
shl_dlist_for_each(i, &w->links) {
|
|
l = shl_dlist_entry(i, struct ctl_link, list);
|
|
shl_dlist_for_each(j, &l->peers) {
|
|
p = shl_dlist_entry(j, struct ctl_peer, list);
|
|
if (cnt++ == idx)
|
|
return p;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|