From 5f39158f45a6859bbc37d8e000ed18eae42249eb Mon Sep 17 00:00:00 2001 From: Derek Dai Date: Wed, 14 Sep 2016 16:24:14 +0800 Subject: [PATCH] init copy & modify --- src/ctl/CMakeLists.txt | 26 ++ src/ctl/ctl-src.c | 609 +++++++++++++++++++++++++++ src/ctl/ctl-src.h | 98 +++++ src/ctl/ctl-wifi.c | 9 + src/ctl/ctl.h | 17 + src/ctl/srcctl.c | 915 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 1674 insertions(+) create mode 100644 src/ctl/ctl-src.c create mode 100644 src/ctl/ctl-src.h create mode 100644 src/ctl/srcctl.c diff --git a/src/ctl/CMakeLists.txt b/src/ctl/CMakeLists.txt index d6d98e4..c8b291b 100644 --- a/src/ctl/CMakeLists.txt +++ b/src/ctl/CMakeLists.txt @@ -46,6 +46,32 @@ target_link_libraries(miracle-sinkctl miracle-shared) INCLUDE_DIRECTORIES(${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src/shared) +########### next target ############### + +set(miracle-srcctl_SRCS ctl.h + ctl-cli.c + ctl-src.h + ctl-src.c + ctl-wifi.c + srcctl.c + wfd.c) + +add_executable(miracle-srcctl ${miracle-srcctl_SRCS}) + +install(TARGETS miracle-srcctl DESTINATION bin) + +if(READLINE_FOUND) + message(STATUS "Compiling with Readline support") + set_property(TARGET miracle-srcctl + APPEND + PROPERTY COMPILE_DEFINITIONS HAVE_READLINE) + target_link_libraries(miracle-srcctl ${READLINE_LIBRARY}) +endif(READLINE_FOUND) + +target_link_libraries(miracle-srcctl miracle-shared) + +INCLUDE_DIRECTORIES(${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src/shared) + ########### install files ############### diff --git a/src/ctl/ctl-src.c b/src/ctl/ctl-src.c new file mode 100644 index 0000000..e1892a9 --- /dev/null +++ b/src/ctl/ctl-src.c @@ -0,0 +1,609 @@ +/* + * MiracleCast - Wifi-Display/Miracast Implementation + * + * Copyright (c) 2013-2014 David Herrmann + * + * 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 . + */ + +#include "ctl-src.h" +#include "util.h" + +///* +// * RTSP Session +// */ +// +//static int sink_req_fn(struct rtsp *bus, struct rtsp_message *m, void *data) +//{ +// cli_debug("INCOMING: %s\n", rtsp_message_get_raw(m)); +// return 0; +//} +// +//static void sink_handle_options(struct ctl_sink *s, +// struct rtsp_message *m) +//{ +// _rtsp_message_unref_ struct rtsp_message *rep = NULL; +// int r; +// +// r = rtsp_message_new_reply_for(m, &rep, RTSP_CODE_OK, NULL); +// if (r < 0) +// return cli_vERR(r); +// +// r = rtsp_message_append(rep, "", +// "Public", +// "org.wfa.wfd1.0, GET_PARAMETER, SET_PARAMETER"); +// if (r < 0) +// return cli_vERR(r); +// +// rtsp_message_seal(rep); +// cli_debug("OUTGOING: %s\n", rtsp_message_get_raw(rep)); +// +// r = rtsp_send(s->rtsp, rep); +// if (r < 0) +// return cli_vERR(r); +// +// rtsp_message_unref(rep); +// rep = NULL; +// +// r = rtsp_message_new_request(s->rtsp, +// &rep, +// "OPTIONS", +// "*"); +// if (r < 0) +// return cli_vERR(r); +// +// r = rtsp_message_append(rep, "", +// "Require", +// "org.wfa.wfd1.0"); +// if (r < 0) +// return cli_vERR(r); +// +// rtsp_message_seal(rep); +// cli_debug("OUTGOING: %s\n", rtsp_message_get_raw(rep)); +// +// r = rtsp_call_async(s->rtsp, rep, sink_req_fn, NULL, 0, NULL); +// if (r < 0) +// return cli_vERR(r); +//} +// +//static void sink_handle_get_parameter(struct ctl_sink *s, +// struct rtsp_message *m) +//{ +// _rtsp_message_unref_ struct rtsp_message *rep = NULL; +// int r; +// +// r = rtsp_message_new_reply_for(m, &rep, RTSP_CODE_OK, NULL); +// if (r < 0) +// return cli_vERR(r); +// +// /* wfd_content_protection */ +// if (rtsp_message_read(m, "{<>}", "wfd_content_protection") >= 0) { +// r = rtsp_message_append(rep, "{&}", +// "wfd_content_protection: none"); +// if (r < 0) +// return cli_vERR(r); +// } +// /* wfd_video_formats */ +// if (rtsp_message_read(m, "{<>}", "wfd_video_formats") >= 0) { +// char wfd_video_formats[128]; +// sprintf(wfd_video_formats, +// "wfd_video_formats: 00 00 03 10 %08x %08x %08x 00 0000 0000 10 none none", +// s->resolutions_cea, s->resolutions_vesa, s->resolutions_hh); +// r = rtsp_message_append(rep, "{&}", wfd_video_formats); +// if (r < 0) +// return cli_vERR(r); +// } +// /* wfd_audio_codecs */ +// if (rtsp_message_read(m, "{<>}", "wfd_audio_codecs") >= 0) { +// r = rtsp_message_append(rep, "{&}", +// "wfd_audio_codecs: AAC 00000007 00"); +// if (r < 0) +// return cli_vERR(r); +// } +// /* wfd_client_rtp_ports */ +// if (rtsp_message_read(m, "{<>}", "wfd_client_rtp_ports") >= 0) { +// char wfd_client_rtp_ports[128]; +// sprintf(wfd_client_rtp_ports, +// "wfd_client_rtp_ports: RTP/AVP/UDP;unicast %d 0 mode=play", rstp_port); +// r = rtsp_message_append(rep, "{&}", +// wfd_client_rtp_ports); +// if (r < 0) +// return cli_vERR(r); +// } +// +// /* wfd_uibc_capability */ +// if (rtsp_message_read(m, "{<>}", "wfd_uibc_capability") >= 0 && uibc) { +// char wfd_uibc_capability[512]; +// sprintf(wfd_uibc_capability, +// "wfd_uibc_capability: input_category_list=GENERIC;" +// "generic_cap_list=Mouse,SingleTouch;" +// "hidc_cap_list=none;port=none"); +// r = rtsp_message_append(rep, "{&}", wfd_uibc_capability); +// if (r < 0) +// return cli_vERR(r); +// } +// rtsp_message_seal(rep); +// cli_debug("OUTGOING: %s\n", rtsp_message_get_raw(rep)); +// +// r = rtsp_send(s->rtsp, rep); +// if (r < 0) +// return cli_vERR(r); +//} +// +//static int sink_setup_fn(struct rtsp *bus, struct rtsp_message *m, void *data) +//{ +// _rtsp_message_unref_ struct rtsp_message *rep = NULL; +// struct ctl_sink *s = data; +// const char *session; +// char *ns, *next; +// int r; +// +// cli_debug("INCOMING: %s\n", rtsp_message_get_raw(m)); +// +// r = rtsp_message_read(m, "", "Session", &session); +// if (r < 0) +// return cli_ERR(r); +// +// ns = strdup(session); +// if (!ns) +// return cli_ENOMEM(); +// +// next = strchr(ns, ';'); +// if (next) +// *next = '\0'; +// +// free(s->session); +// s->session = ns; +// +// r = rtsp_message_new_request(s->rtsp, +// &rep, +// "PLAY", +// s->url); +// if (r < 0) +// return cli_ERR(r); +// +// r = rtsp_message_append(rep, "", "Session", s->session); +// if (r < 0) +// return cli_ERR(r); +// +// rtsp_message_seal(rep); +// cli_debug("OUTGOING: %s\n", rtsp_message_get_raw(rep)); +// +// r = rtsp_call_async(s->rtsp, rep, sink_req_fn, NULL, 0, NULL); +// if (r < 0) +// return cli_ERR(r); +// +// return 0; +//} +// +//static int sink_set_format(struct ctl_sink *s, +// unsigned int cea_res, +// unsigned int vesa_res, +// unsigned int hh_res) +//{ +// int hres, vres; +// +// if ((vfd_get_cea_resolution(cea_res, &hres, &vres) == 0) || +// (vfd_get_vesa_resolution(vesa_res, &hres, &vres) == 0) || +// (vfd_get_hh_resolution(hh_res, &hres, &vres) == 0)) { +// if (hres && vres) { +// s->hres = hres; +// s->vres = vres; +// ctl_fn_sink_resolution_set(s); +// return 0; +// } +// } +// +// return -EINVAL; +//} +// +//static void sink_handle_set_parameter(struct ctl_sink *s, +// struct rtsp_message *m) +//{ +// _rtsp_message_unref_ struct rtsp_message *rep = NULL; +// const char *trigger; +// const char *url; +// const char *uibc_config; +// const char *uibc_setting; +// char *nu; +// unsigned int cea_res, vesa_res, hh_res; +// int r; +// +// r = rtsp_message_new_reply_for(m, &rep, RTSP_CODE_OK, NULL); +// if (r < 0) +// return cli_vERR(r); +// +// rtsp_message_seal(rep); +// cli_debug("OUTGOING: %s\n", rtsp_message_get_raw(rep)); +// +// r = rtsp_send(s->rtsp, rep); +// if (r < 0) +// return cli_vERR(r); +// +// rtsp_message_unref(rep); +// rep = NULL; +// +// /* M4 (or any other) can pass presentation URLs */ +// r = rtsp_message_read(m, "{}", "wfd_presentation_URL", &url); +// if (r >= 0) { +// if (!s->url || strcmp(s->url, url)) { +// nu = strdup(url); +// if (!nu) +// return cli_vENOMEM(); +// +// free(s->url); +// s->url = nu; +// cli_debug("Got URL: %s\n", s->url); +// } +// } +// +// /* M4 (or any other) can pass presentation URLs */ +// r = rtsp_message_read(m, "{}", "wfd_uibc_capability", &uibc_config); +// if (r >= 0) { +// if (!s->uibc_config || strcmp(s->uibc_config, uibc_config)) { +// nu = strdup(uibc_config); +// if (!nu) +// return cli_vENOMEM(); +// +// free(s->uibc_config); +// s->uibc_config = nu; +// +// char* token = strtok(uibc_config, ";"); +// +// while (token) { +// if (sscanf(token, "port=%d", &uibc_port)) { +// break; +// } +// token = strtok(0, ";"); +// } +// +// cli_debug("Got URL: %s\n", s->url); +// } +// } +// +// /* M4 (or any other) can pass presentation URLs */ +// r = rtsp_message_read(m, "{}", "wfd_uibc_setting", &uibc_setting); +// if (r >= 0) { +// if (!s->uibc_setting || strcmp(s->uibc_setting, uibc_setting)) { +// nu = strdup(uibc_setting); +// if (!nu) +// return cli_vENOMEM(); +// +// free(s->uibc_setting); +// s->uibc_setting = nu; +// cli_debug("uibc setting: %s\n", s->uibc_setting); +// } +// } +// /* M4 again */ +// r = rtsp_message_read(m, "{<****hhh>}", "wfd_video_formats", +// &cea_res, &vesa_res, &hh_res); +// if (r == 0) { +// r = sink_set_format(s, cea_res, vesa_res, hh_res); +// if (r) +// return cli_vERR(r); +// } +// +// /* M5 */ +// r = rtsp_message_read(m, "{}", "wfd_trigger_method", &trigger); +// if (r < 0) +// return; +// +// if (!strcmp(trigger, "SETUP")) { +// if (!s->url) { +// cli_error("No valid wfd_presentation_URL\n"); +// return; +// } +// +// r = rtsp_message_new_request(s->rtsp, +// &rep, +// "SETUP", +// s->url); +// if (r < 0) +// return cli_vERR(r); +// +// char rtsp_setup[128]; +// sprintf(rtsp_setup, "RTP/AVP/UDP;unicast;client_port=%d", rstp_port); +// r = rtsp_message_append(rep, "", "Transport", rtsp_setup); +// if (r < 0) +// return cli_vERR(r); +// +// rtsp_message_seal(rep); +// cli_debug("OUTGOING: %s\n", rtsp_message_get_raw(rep)); +// +// r = rtsp_call_async(s->rtsp, rep, sink_setup_fn, s, 0, NULL); +// if (r < 0) +// return cli_vERR(r); +// } +//} +// +//static void sink_handle(struct ctl_sink *s, +// struct rtsp_message *m) +//{ +// const char *method; +// +// cli_debug("INCOMING: %s\n", rtsp_message_get_raw(m)); +// +// method = rtsp_message_get_method(m); +// if (!method) +// return; +// +// if (!strcmp(method, "OPTIONS")) { +// sink_handle_options(s, m); +// } else if (!strcmp(method, "GET_PARAMETER")) { +// sink_handle_get_parameter(s, m); +// } else if (!strcmp(method, "SET_PARAMETER")) { +// sink_handle_set_parameter(s, m); +// } +//} + +static int src_rtsp_fn(struct rtsp *bus, + struct rtsp_message *m, + void *data) +{ +// struct ctl_sink *s = data; +// +// if (!m) +// s->hup = true; +// else +// sink_handle(s, m); +// +// if (s->hup) { +// ctl_sink_close(s); +// ctl_fn_sink_disconnected(s); +// } +// + return 0; +} + +/* + * Source I/O + */ + +static void src_connected(struct ctl_src *s) +{ + int r, val; + socklen_t len; + + cli_printf("got incomming connection request\n"); + + if (s->connected || s->hup) + return; + + sd_event_source_set_enabled(s->fd_source, SD_EVENT_OFF); + + len = sizeof(val); + r = getsockopt(s->fd, SOL_SOCKET, SO_ERROR, &val, &len); + if (r < 0) { + s->hup = true; + cli_vERRNO(); + return; + } else if (val) { + s->hup = true; + errno = val; + cli_error("cannot connect to remote host (%d): %m", + errno); + return; + } + + cli_debug("connection established"); + + r = rtsp_open(&s->rtsp, s->fd); + if (r < 0) + goto error; + + r = rtsp_attach_event(s->rtsp, s->event, 0); + if (r < 0) + goto error; + + r = rtsp_add_match(s->rtsp, src_rtsp_fn, s); + if (r < 0) + goto error; + + s->connected = true; + ctl_fn_src_connected(s); + return; + +error: + s->hup = true; + cli_vERR(r); +} + +static void src_io(struct ctl_src *s, uint32_t mask) +{ + cli_notice("src_io: %u", mask); + + if (mask & EPOLLIN) { + src_connected(s); + } + + if (mask & EPOLLERR) { + cli_notice("ERR on socket"); + s->hup = true; + } + + if (s->hup) { + ctl_src_close(s); + ctl_fn_src_disconnected(s); + } +} + +static int src_io_fn(sd_event_source *source, + int fd, + uint32_t mask, + void *data) +{ + src_io(data, mask); + return 0; +} + +static int src_listen(struct ctl_src *s) +{ + int fd, r; + + if (!s) + return cli_EINVAL(); + if (s->fd >= 0) + return 0; + if (!s->addr.ss_family || !s->addr_size) + return cli_EINVAL(); + + fd = socket(s->addr.ss_family, + SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, + 0); + if (fd < 0) + return cli_ERRNO(); + + cli_printf("RTSP server socket created\n"); + + r = bind(fd, (struct sockaddr*)&s->addr, s->addr_size); + if (fd < 0) { + r = -errno; + cli_vERR(r); + goto err_close; + } + + cli_printf("RTSP server socket bound\n"); + + r = listen(fd, 1); + if (r < 0) { + r = -errno; + if (r != -EINPROGRESS) { + cli_vERR(r); + goto err_close; + } + } + + cli_printf("RTSP server socket listening\n"); + + r = sd_event_add_io(s->event, + &s->fd_source, + fd, + EPOLLERR | EPOLLIN | EPOLLET, + src_io_fn, + s); + if (r < 0) { + cli_vERR(r); + goto err_close; + } + + s->fd = fd; + return 0; + +err_close: + close(fd); + return r; +} + +static void src_close(struct ctl_src *s) +{ + if (!s || s->fd < 0) + return; + + rtsp_remove_match(s->rtsp, src_rtsp_fn, s); + rtsp_detach_event(s->rtsp); + rtsp_unref(s->rtsp); + s->rtsp = NULL; + sd_event_source_unref(s->fd_source); + s->fd_source = NULL; + close(s->fd); + s->fd = -1; + s->connected = false; +// s->hup = false; +} + +/* + * Source Management + */ + +int ctl_src_new(struct ctl_src **out, + sd_event *event) +{ + struct ctl_src *s; + + if (!out || !event) + return cli_EINVAL(); + + s = calloc(1, sizeof(*s)); + if (!s) + return cli_ENOMEM(); + + s->event = sd_event_ref(event); + s->fd = -1; + + *out = s; + return 0; +} + +void ctl_src_free(struct ctl_src *s) +{ + if (!s) + return; + + ctl_src_close(s); + free(s->local); + free(s->session); + sd_event_unref(s->event); + free(s); +} + +int ctl_src_listen(struct ctl_src *s, const char *local) +{ + struct sockaddr_in addr = { }; + char *l; + int r; + + if (!s || !local || s->fd >= 0) + return cli_EINVAL(); + + addr.sin_family = AF_INET; + addr.sin_port = htons(7236); + r = inet_pton(AF_INET, local, &addr.sin_addr); + if (r != 1) + return cli_EINVAL(); + + l = strdup(local); + if (!l) + return cli_ENOMEM(); + + free(s->local); + s->local = l; + + memcpy(&s->addr, &addr, sizeof(addr)); + s->addr_size = sizeof(addr); + + return src_listen(s); +} + +void ctl_src_close(struct ctl_src *s) +{ + if (!s) + return; + + src_close(s); +} + +bool ctl_src_is_connecting(struct ctl_src *s) +{ + return s && s->fd >= 0 && !s->connected; +} + +bool ctl_src_is_connected(struct ctl_src *s) +{ + return s && s->connected; +} + +bool ctl_src_is_closed(struct ctl_src *s) +{ + return !s || s->fd < 0; +} diff --git a/src/ctl/ctl-src.h b/src/ctl/ctl-src.h new file mode 100644 index 0000000..e8bb06a --- /dev/null +++ b/src/ctl/ctl-src.h @@ -0,0 +1,98 @@ +/* + * MiracleCast - Wifi-Display/Miracast Implementation + * + * 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 . + */ + +#ifndef CTL_SRC_H +#define CTL_SRC_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ctl.h" + +#include "rtsp.h" +#include "shl_macro.h" +#include "shl_util.h" +#include "wfd.h" + +struct ctl_src { + sd_event *event; + + char *local; + char *session; +// char *url; +// char *uibc_config; +// char *uibc_setting; + struct sockaddr_storage addr; + size_t addr_size; + int fd; + sd_event_source *fd_source; + + struct rtsp *rtsp; + + bool connected : 1; + bool hup : 1; +// +// uint32_t resolutions_cea; +// uint32_t resolutions_vesa; +// uint32_t resolutions_hh; +// +// int hres; +// int vres; +}; + +//extern int rstp_port; +//extern bool uibc; +//extern int uibc_port; +// +//struct ctl_sink { +// sd_event *event; +// +// char *target; +// char *session; +// char *url; +// char *uibc_config; +// char *uibc_setting; +// struct sockaddr_storage addr; +// size_t addr_size; +// int fd; +// sd_event_source *fd_source; +// +// struct rtsp *rtsp; +// +// bool connected : 1; +// bool hup : 1; +// +// uint32_t resolutions_cea; +// uint32_t resolutions_vesa; +// uint32_t resolutions_hh; +// +// int hres; +// int vres; +//}; + +#endif /* CTL_SRC_H */ diff --git a/src/ctl/ctl-wifi.c b/src/ctl/ctl-wifi.c index 6efca2c..876a39e 100644 --- a/src/ctl/ctl-wifi.c +++ b/src/ctl/ctl-wifi.c @@ -121,6 +121,7 @@ static int ctl_peer_parse_properties(struct ctl_peer *p, 'e', "sv")) > 0) { r = sd_bus_message_read(m, "s", &t); + log_info("==== sd_bus_message name: %s", t); if (r < 0) return cli_log_parser(r); @@ -760,6 +761,8 @@ static int ctl_wifi_parse_peer(struct ctl_wifi *w, if (r < 0) return r; + log_info("======= peer created: p=%p, l=%p, label=%s", p, l, label); + r = sd_bus_message_enter_container(m, 'a', "{sa{sv}}"); if (r < 0) return cli_log_parser(r); @@ -771,6 +774,8 @@ static int ctl_wifi_parse_peer(struct ctl_wifi *w, if (r < 0) return cli_log_parser(r); + log_info("ctl_wifi_parse_peer: %p, %t", m, t); + if (strcmp(t, "org.freedesktop.miracle.wifi.Peer")) { r = sd_bus_message_skip(m, "a{sv}"); if (r < 0) @@ -812,6 +817,8 @@ static int ctl_wifi_parse_object(struct ctl_wifi *w, const char *t; int r; + log_info("======== ctl_wifi_parse_object"); + r = sd_bus_message_read(m, "o", &t); if (r < 0) return cli_log_parser(r); @@ -866,6 +873,8 @@ static int ctl_wifi_object_fn(sd_bus_message *m, struct ctl_wifi *w = data; bool added; + log_info("ooooooo"); + added = !strcmp(sd_bus_message_get_member(m), "InterfacesAdded"); return ctl_wifi_parse_object(w, m, added); diff --git a/src/ctl/ctl.h b/src/ctl/ctl.h index 75cc0a3..dc9e7a0 100644 --- a/src/ctl/ctl.h +++ b/src/ctl/ctl.h @@ -104,6 +104,20 @@ struct ctl_peer *ctl_wifi_find_peer(struct ctl_wifi *w, struct ctl_peer *ctl_wifi_search_peer(struct ctl_wifi *w, const char *real_label); +/* source handling */ + +struct ctl_src; + +int ctl_src_new(struct ctl_src **out, + sd_event *event); +void ctl_src_free(struct ctl_src *s); + +int ctl_src_listen(struct ctl_src *s, const char *local); +void ctl_src_close(struct ctl_src *s); +bool ctl_src_is_connecting(struct ctl_src *s); +bool ctl_src_is_connected(struct ctl_src *s); +bool ctl_src_is_closed(struct ctl_src *s); + /* sink handling */ struct ctl_sink; @@ -239,6 +253,9 @@ void ctl_fn_peer_disconnected(struct ctl_peer *p); void ctl_fn_link_new(struct ctl_link *l); void ctl_fn_link_free(struct ctl_link *l); +void ctl_fn_src_connected(struct ctl_src *s); +void ctl_fn_src_disconnected(struct ctl_src *s); + void ctl_fn_sink_connected(struct ctl_sink *s); void ctl_fn_sink_disconnected(struct ctl_sink *s); void ctl_fn_sink_resolution_set(struct ctl_sink *s); diff --git a/src/ctl/srcctl.c b/src/ctl/srcctl.c new file mode 100644 index 0000000..5abe9b9 --- /dev/null +++ b/src/ctl/srcctl.c @@ -0,0 +1,915 @@ +/* + * MiracleCast - Wifi-Display/Miracast Implementation + * + * Copyright (c) 2013-2014 David Herrmann + * + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ctl.h" +//#include "ctl-sink.h" +#include "wfd.h" +#include "shl_macro.h" +#include "shl_util.h" +#include "config.h" + +static sd_bus *bus; +static struct ctl_wifi *wifi; +static struct ctl_src *src; +static sd_event_source *scan_timeout; +static sd_event_source *src_timeout; +static unsigned int src_timeout_time; +static bool src_connected; +//static pid_t sink_pid; +// +//static char *selected_ link; +static struct ctl_link *running_link; +static struct ctl_peer *running_peer; +static struct ctl_peer *pending_peer; +// +//void launch_player(struct ctl_sink *s); +// +//char *gst_scale_res; +int gst_audio_en = 1; +static const int DEFAULT_RSTP_PORT = 1991; +//bool uibc; +int rstp_port; +//int uibc_port; +// +//unsigned int wfd_supported_res_cea = 0x0000001f; /* up to 720x576 */ +//unsigned int wfd_supported_res_vesa = 0x00000003; /* up to 800x600 */ +//unsigned int wfd_supported_res_hh = 0x00000000; /* not supported */ + +/* + * cmd: select + */ + +static int cmd_select(char **args, unsigned int n) +{ + struct ctl_link *l; + + if (!n) { + if (running_link) { + cli_printf("link %s deselected\n", + running_link->label); + running_link = NULL; + } + + return 0; + } + + l = ctl_wifi_search_link(wifi, args[0]); + if (!l) { + cli_error("unknown link %s", args[0]); + return 0; + } + + running_link = l; + cli_printf("link %s selected\n", running_link->label); + + return 0; +} + +/* + * cmd list + */ + +static int cmd_list(char **args, unsigned int n) +{ + size_t link_cnt = 0, peer_cnt = 0; + struct shl_dlist *i, *j; + struct ctl_link *l; + struct ctl_peer *p; + + /* list links */ + + cli_printf("%6s %-24s %-30s\n", + "LINK", "INTERFACE", "FRIENDLY-NAME"); + + shl_dlist_for_each(i, &wifi->links) { + l = link_from_dlist(i); + ++link_cnt; + + cli_printf("%6s %-24s %-30s\n", + l->label, + shl_isempty(l->ifname) ? + "" : l->ifname, + shl_isempty(l->friendly_name) ? + "" : l->friendly_name); + } + + cli_printf("\n"); + + /* list peers */ + + cli_printf("%6s %-24s %-30s %-10s\n", + "LINK", "PEER-ID", "FRIENDLY-NAME", "CONNECTED"); + + shl_dlist_for_each(i, &wifi->links) { + l = link_from_dlist(i); + + shl_dlist_for_each(j, &l->peers) { + p = peer_from_dlist(j); + ++peer_cnt; + + cli_printf("%6s %-24s %-30s %-10s\n", + p->l->label, + p->label, + shl_isempty(p->friendly_name) ? + "" : p->friendly_name, + p->connected ? "yes" : "no"); + } + } + + cli_printf("\n %u peers and %u links listed.\n", peer_cnt, link_cnt); + + return 0; +} + +/* + * cmd: show + */ + +static int cmd_show(char **args, unsigned int n) +{ + struct ctl_link *l = NULL; + struct ctl_peer *p = NULL; + + if (n > 0) { + if (!(l = ctl_wifi_find_link(wifi, args[0])) && + !(p = ctl_wifi_find_peer(wifi, args[0])) && + !(l = ctl_wifi_search_link(wifi, args[0])) && + !(p = ctl_wifi_search_peer(wifi, args[0]))) { + cli_error("unknown link or peer %s", args[0]); + return 0; + } + } + + if (l) { + cli_printf("Link=%s\n", l->label); + if (l->ifindex > 0) + cli_printf("InterfaceIndex=%u\n", l->ifindex); + if (l->ifname && *l->ifname) + cli_printf("InterfaceName=%s\n", l->ifname); + if (l->friendly_name && *l->friendly_name) + cli_printf("FriendlyName=%s\n", l->friendly_name); + cli_printf("P2PScanning=%d\n", l->p2p_scanning); + if (l->wfd_subelements && *l->wfd_subelements) + cli_printf("WfdSubelements=%s\n", l->wfd_subelements); + } else if (p) { + cli_printf("Peer=%s\n", p->label); + if (p->p2p_mac && *p->p2p_mac) + cli_printf("P2PMac=%s\n", p->p2p_mac); + if (p->friendly_name && *p->friendly_name) + cli_printf("FriendlyName=%s\n", p->friendly_name); + cli_printf("Connected=%d\n", p->connected); + if (p->interface && *p->interface) + cli_printf("Interface=%s\n", p->interface); + if (p->local_address && *p->local_address) + cli_printf("LocalAddress=%s\n", p->local_address); + if (p->remote_address && *p->remote_address) + cli_printf("RemoteAddress=%s\n", p->remote_address); + if (p->wfd_subelements && *p->wfd_subelements) + cli_printf("WfdSubelements=%s\n", p->wfd_subelements); + } else { + cli_printf("Show what?\n"); + return 0; + } + + return 0; +} + +/* + * cmd: set-friendly-name + */ + +static int cmd_set_friendly_name(char **args, unsigned int n) +{ + struct ctl_link *l = NULL; + const char *name; + + if (n < 1) { + cli_printf("To what?\n"); + return 0; + } + + if (n > 1) { + l = ctl_wifi_search_link(wifi, args[0]); + if (!l) { + cli_error("unknown link %s", args[0]); + return 0; + } + + name = args[1]; + } else { + name = args[0]; + } + + l = l ? : running_link; + if (!l) { + cli_error("no link selected"); + return 0; + } + + return ctl_link_set_friendly_name(l, name); +} + +/* + * cmd: p2p-scan + */ + +static int cmd_p2p_scan(char **args, unsigned int n) +{ + struct ctl_link *l = NULL; + unsigned int i; + bool stop = false; + int r; + + for (i = 0; i < n; ++i) { + if (!strcmp(args[i], "stop")) { + stop = true; + } else { + l = ctl_wifi_search_link(wifi, args[i]); + if (!l) { + cli_error("unknown link %s", args[i]); + return 0; + } + } + } + + l = l ? : running_link; + if (!l) { + cli_error("no link selected"); + return 0; + } + + ctl_link_set_wfd_subelements(l, "00000600101c440032"); + r = ctl_link_set_p2p_scanning(l, !stop); + if(!r && !running_link) { + running_link = l; + } + + return r; +} + +/* + * cmd: connect + */ + +static bool is_valid_prov(const char *prov) +{ + return prov && (!strcmp(prov, "auto") || + !strcmp(prov, "pbc") || + !strcmp(prov, "display") || + !strcmp(prov, "pin")); +} + +static int cmd_connect(char **args, unsigned int n) +{ + struct ctl_peer *p; + const char *prov, *pin; + + if (n < 1) { + cli_printf("To whom?\n"); + return 0; + } + + p = ctl_wifi_search_peer(wifi, args[0]); + if (!p) { + cli_error("unknown peer %s", args[0]); + return 0; + } + + if (n > 2) { + prov = args[1]; + pin = args[2]; + } else if (n > 1) { + if (is_valid_prov(args[1])) { + prov = args[1]; + pin = ""; + } else { + prov = "auto"; + pin = args[1]; + } + } else { + prov = "auto"; + pin = ""; + } + + return ctl_peer_connect(p, prov, pin); +} + +/* + * cmd: disconnect + */ + +static int cmd_disconnect(char **args, unsigned int n) +{ + struct ctl_peer *p; + + if (n < 1) { + cli_printf("From whom?\n"); + return 0; + } + + p = ctl_wifi_search_peer(wifi, args[0]); + if (!p) { + cli_error("unknown peer %s", args[0]); + return 0; + } + + return ctl_peer_disconnect(p); +} + +/* + * cmd: quit/exit + */ + +static int cmd_quit(char **args, unsigned int n) +{ + cli_exit(); + return 0; +} + +/* + * main + */ + +static void schedule_timeout(sd_event_source **out, + uint64_t rel_usec, + sd_event_time_handler_t timeout_fn, + void *data) +{ + int r; + + rel_usec += shl_now(CLOCK_MONOTONIC); + + if (*out) { + r = sd_event_source_set_time(*out, rel_usec); + if (r < 0) + cli_vERR(r); + } else { + r = sd_event_add_time(cli_event, + out, + CLOCK_MONOTONIC, + rel_usec, + 0, + timeout_fn, + data); + if (r < 0) + cli_vERR(r); + } +} + +static void stop_timeout(sd_event_source **out) +{ + if (*out) { + sd_event_source_set_enabled(*out, SD_EVENT_OFF); + sd_event_source_unref(*out); + *out = NULL; + } +} + +static int scan_timeout_fn(sd_event_source *s, uint64_t usec, void *data) +{ + stop_timeout(&scan_timeout); + + if (pending_peer) { + if (cli_running()) { + cli_printf("[" CLI_RED "TIMEOUT" CLI_DEFAULT "] waiting for %s\n", + pending_peer->friendly_name); + } + pending_peer = NULL; + } + + if (running_link) + ctl_link_set_p2p_scanning(running_link, true); + + return 0; +} + +static int src_timeout_fn(sd_event_source *s, uint64_t usec, void *data) +{ + int r; + + stop_timeout(&src_timeout); + + cli_printf("src_timeout_fn(): %p, %d, %d\n", + running_peer, + running_peer ? running_peer->connected : 0, + ctl_src_is_closed(src)); + + if (running_peer && + running_peer->connected && + ctl_src_is_closed(src)) { + r = ctl_src_listen(src, running_peer->local_address); + if (r < 0) { + if (src_timeout_time++ >= 3) + cli_vERR(r); + else + schedule_timeout(&src_timeout, + src_timeout_time * 1000ULL * 1000ULL, + src_timeout_fn, + NULL); + } + + log_info("listening on %s", running_peer->local_address); + } + + return 0; +} + +static const struct cli_cmd cli_cmds[] = { + { "list", NULL, CLI_M, CLI_LESS, 0, cmd_list, "List all objects" }, + { "select", "[link]", CLI_Y, CLI_LESS, 1, cmd_select, "Select default link" }, + { "show", "", CLI_M, CLI_LESS, 1, cmd_show, "Show detailed object information" }, + { "set-friendly-name", "[link] ", CLI_M, CLI_LESS, 2, cmd_set_friendly_name, "Set friendly name of an object" }, + { "p2p-scan", "[link] [stop]", CLI_Y, CLI_LESS, 2, cmd_p2p_scan, "Control neighborhood P2P scanning" }, + { "connect", " [provision] [pin]", CLI_M, CLI_LESS, 3, cmd_connect, "Connect to peer" }, + { "disconnect", "", CLI_M, CLI_EQUAL, 1, cmd_disconnect, "Disconnect from peer" }, + { "quit", NULL, CLI_Y, CLI_MORE, 0, cmd_quit, "Quit program" }, + { "exit", NULL, CLI_Y, CLI_MORE, 0, cmd_quit, NULL }, + { "help", NULL, CLI_M, CLI_MORE, 0, NULL, "Print help" }, + { }, +}; +// +//static void spawn_gst(struct ctl_sink *s) +//{ +// pid_t pid; +// int fd_journal; +// sigset_t mask; +// +// if (sink_pid > 0) +// return; +// +// pid = fork(); +// if (pid < 0) { +// return cli_vERRNO(); +// } else if (!pid) { +// /* child */ +// +// sigemptyset(&mask); +// sigprocmask(SIG_SETMASK, &mask, NULL); +// +// /* redirect stdout/stderr to journal */ +// fd_journal = sd_journal_stream_fd("miracle-sinkctl-gst", +// LOG_DEBUG, +// false); +// if (fd_journal >= 0) { +// /* dup journal-fd to stdout and stderr */ +// dup2(fd_journal, 1); +// dup2(fd_journal, 2); +// } else { +// /* no journal? redirect stdout to parent's stderr */ +// dup2(2, 1); +// } +// +// launch_player(s); +// _exit(1); +// } else { +// sink_pid = pid; +// } +//} +// +//void launch_player(struct ctl_sink *s) { +// char *argv[64]; +// char resolution[64]; +// char port[64]; +// char uibc_portStr[64]; +// int i = 0; +// char* player; +// if (uibc) { +// player = "uibc-viewer"; +// } else { +// player = "miracle-gst"; +// } +// +// argv[i++] = player; +// if (uibc) { +// argv[i++] = s->target; +// sprintf(uibc_portStr, "%d", uibc_port); +// argv[i++] = uibc_portStr; +// } +// if (cli_max_sev >= 7) +// argv[i++] = "-d 3"; +// if (gst_audio_en) +// argv[i++] = "-a"; +// if (gst_scale_res) { +// argv[i++] = "-s"; +// argv[i++] = gst_scale_res; +// } +// argv[i++] = "-p"; +// sprintf(port, "%d", rstp_port); +// argv[i++] = port; +// +// if (s->hres && s->vres) { +// sprintf(resolution, "%dx%d", s->hres, s->vres); +// argv[i++] = "-r"; +// argv[i++] = resolution; +// } +// +// argv[i] = NULL; +// +// i = 0; +// int size = 0; +// while (argv[i]) { +// size += strlen(argv[i++] + 1); +// } +// +// char* player_command = malloc(size); +// i = 0; +// strcpy(player_command, argv[i++]); +// while (argv[i]) { +// strcat(player_command, " "); +// strcat(player_command, argv[i++]); +// } +// log_debug("player command: %s", player_command); +// //free(player_command); +// if (execvpe(argv[0], argv, environ) < 0) { +// cli_debug("stream player failed (%d): %m", errno); +// int i = 0; +// cli_debug("printing environment: "); +// while (environ[i]) { +// cli_debug("%s", environ[i++]); +// } +// } +//} +// +//void launch_uibc_daemon(int port) { +// char *argv[64]; +// char portStr[64]; +// int i = 0; +// argv[i++] = "miracle-uibcctl"; +// argv[i++] = "localhost"; +// sprintf(portStr, "%d", port); +// argv[i++] = portStr; +// argv[i] = NULL; +// +// cli_debug("uibc daemon: %s", argv[0]); +// execvpe(argv[0], argv, environ); +//} +// +//static void kill_gst(void) +//{ +// if (sink_pid <= 0) +// return; +// +// kill(sink_pid, SIGTERM); +// sink_pid = 0; +//} + +void ctl_fn_src_connected(struct ctl_src *s) +{ + cli_notice("SOURCE connected"); + src_connected = true; +} + +void ctl_fn_src_disconnected(struct ctl_src *s) +{ + if (!src_connected) { + /* treat HUP as timeout */ + src_timeout_fn(src_timeout, 0, NULL); + } else { + cli_notice("SRC disconnected"); + src_connected = false; + } +} + +//void ctl_fn_sink_resolution_set(struct ctl_sink *s) +//{ +// cli_printf("SINK set resolution %dx%d\n", s->hres, s->vres); +// if (sink_connected) +// spawn_gst(s); +//} + +void ctl_fn_peer_new(struct ctl_peer *p) +{ + if (p->l != running_link || shl_isempty(p->wfd_subelements)) + return; + + if (cli_running()) + cli_printf("[" CLI_GREEN "ADD" CLI_DEFAULT "] Peer: %s\n", + p->label); +} + +void ctl_fn_peer_free(struct ctl_peer *p) +{ + if (p->l != running_link || shl_isempty(p->wfd_subelements)) + return; + + if (p == pending_peer) { + cli_printf("no longer waiting for peer %s (%s)\n", + p->friendly_name, p->label); + pending_peer = NULL; + stop_timeout(&scan_timeout); + ctl_link_set_p2p_scanning(p->l, true); + } + + if (p == running_peer) { + cli_printf("no longer running on peer %s\n", + running_peer->label); + stop_timeout(&src_timeout); + //kill_gst(); + ctl_src_close(src); + running_peer = NULL; + stop_timeout(&scan_timeout); + ctl_link_set_p2p_scanning(p->l, true); + } + + if (cli_running()) + cli_printf("[" CLI_RED "REMOVE" CLI_DEFAULT "] Peer: %s\n", + p->label); +} +// +void ctl_fn_peer_provision_discovery(struct ctl_peer *p, + const char *prov, + const char *pin) +{ + if (p->l != running_link || shl_isempty(p->wfd_subelements)) + return; + + if (cli_running()) + cli_printf("[" CLI_YELLOW "PROV" CLI_DEFAULT "] Peer: %s Type: %s PIN: %s\n", + p->label, prov, pin); +} + +void ctl_fn_peer_go_neg_request(struct ctl_peer *p, + const char *prov, + const char *pin) +{ + if (p->l != running_link || shl_isempty(p->wfd_subelements)) + return; + + if (cli_running()) + cli_printf("[" CLI_YELLOW "GO NEG" CLI_DEFAULT "] Peer: %s Type: %s PIN: %s\n", + p->label, prov, pin); + + if (!running_peer) { + /* auto accept any incoming connection attempt */ + ctl_peer_connect(p, "auto", ""); + pending_peer = p; + + /* 60s timeout in case the connect fails. Yes, stupid wpas does + * not catch this and notify us.. and as it turns out, DHCP + * negotiation with some proprietary devices can take up to 30s + * so lets be safe. */ + schedule_timeout(&scan_timeout, + 60 * 1000ULL * 1000ULL, + scan_timeout_fn, + NULL); + } +} + +void ctl_fn_peer_formation_failure(struct ctl_peer *p, const char *reason) +{ + if (p->l != running_link || shl_isempty(p->wfd_subelements)) + return; + + if (cli_running()) + cli_printf("[" CLI_YELLOW "FAIL" CLI_DEFAULT "] Peer: %s Reason: %s\n", + p->label, reason); + + if (!running_peer) { + stop_timeout(&scan_timeout); + ctl_link_set_p2p_scanning(p->l, true); + } +} + +void ctl_fn_peer_connected(struct ctl_peer *p) +{ + if (p->l != running_link || shl_isempty(p->wfd_subelements)) + return; + + if (cli_running()) + cli_printf("[" CLI_GREEN "CONNECT" CLI_DEFAULT "] Peer: %s\n", + p->label); + + pending_peer = NULL; + + if (!running_peer) { + running_peer = p; + cli_printf("now running on peer %s\n", running_peer->label); + stop_timeout(&scan_timeout); + + src_connected = false; + src_timeout_time = 1; + cli_printf("src_timeout_time=%llu\n", src_timeout_time); + schedule_timeout(&src_timeout, + src_timeout_time * 1000ULL * 1000ULL, + src_timeout_fn, + NULL); + } +} +// +void ctl_fn_peer_disconnected(struct ctl_peer *p) +{ + if (p->l != running_link || shl_isempty(p->wfd_subelements)) + return; + + if (p == running_peer) { + cli_printf("no longer running on peer %s\n", + running_peer->label); + stop_timeout(&src_timeout); + //kill_gst(); + ctl_src_close(src); + running_peer = NULL; + stop_timeout(&scan_timeout); + ctl_link_set_p2p_scanning(p->l, true); + } + + if (cli_running()) + cli_printf("[" CLI_YELLOW "DISCONNECT" CLI_DEFAULT "] Peer: %s\n", + p->label); +} +// +void ctl_fn_link_new(struct ctl_link *l) +{ + if (cli_running()) + cli_printf("[" CLI_GREEN "ADD" CLI_DEFAULT "] Link: %s\n", + l->label); +} + +void ctl_fn_link_free(struct ctl_link *l) +{ + if (l == running_link) { + cli_printf("no longer running on link %s\n", + running_link->label); + running_link = NULL; + stop_timeout(&scan_timeout); + } + + if (cli_running()) + cli_printf("[" CLI_RED "REMOVE" CLI_DEFAULT "] Link: %s\n", + l->label); +} + +void cli_fn_help() +{ + /* + * 80-char barrier: + * 01234567890123456789012345678901234567890123456789012345678901234567890123456789 + */ + printf("%s [OPTIONS...] ...\n\n" + "Control a dedicated local sink via MiracleCast.\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --log-level Maximum level for log messages\n" + " --log-journal-level Maximum level for journal log messages\n" + " --audio <0/1> Enable audio support (default %d)\n" + "\n" + , program_invocation_short_name, gst_audio_en + ); + /* + * 80-char barrier: + * 01234567890123456789012345678901234567890123456789012345678901234567890123456789 + */ +} + +static int ctl_interactive(char **argv, int argc) +{ + int r; + + r = cli_init(bus, cli_cmds); + if (r < 0) + return r; + + r = ctl_src_new(&src, cli_event); + if (r < 0) + goto error; + + r = ctl_wifi_fetch(wifi); + if (r < 0) + goto error; + + if (argc > 0) { + r = cli_do(cli_cmds, argv, argc); + if (r == -EAGAIN) + cli_error("unknown operation %s", argv[0]); + } + + r = cli_run(); + +error: + ctl_src_free(src); + cli_destroy(); + return r; +} + +static int ctl_main(int argc, char *argv[]) +{ + struct shl_dlist *i; + struct ctl_link *l; + int r, left; + + r = ctl_wifi_new(&wifi, bus); + if (r < 0) + return r; + + left = argc - optind; + r = ctl_interactive(argv + optind, left <= 0 ? 0 : left); + + /* stop all scans */ + shl_dlist_for_each(i, &wifi->links) { + l = link_from_dlist(i); + if (l->have_p2p_scan) + ctl_link_set_p2p_scanning(l, false); + } + + ctl_wifi_free(wifi); + return r; +} + +static int parse_argv(int argc, char *argv[]) +{ + enum { + ARG_VERSION = 0x100, + ARG_LOG_LEVEL, + ARG_JOURNAL_LEVEL, + ARG_AUDIO, + }; + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, + { "log-journal-level", required_argument, NULL, ARG_JOURNAL_LEVEL }, + { "audio", required_argument, NULL, ARG_AUDIO }, + {} + }; + int c; + + rstp_port = DEFAULT_RSTP_PORT; + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { + switch (c) { + case 'h': + return cli_help(cli_cmds); + case ARG_VERSION: + puts(PACKAGE_STRING); + return 0; + case ARG_LOG_LEVEL: + cli_max_sev = log_parse_arg(optarg); + break; + case ARG_JOURNAL_LEVEL: + log_max_sev = log_parse_arg(optarg); + break; + case ARG_AUDIO: + gst_audio_en = atoi(optarg); + break; + case '?': + return -EINVAL; + } + } + + return 1; +} + +int main(int argc, char **argv) +{ + int r; + + setlocale(LC_ALL, ""); + + r = parse_argv(argc, argv); + if (r < 0) + return EXIT_FAILURE; + if (!r) + return EXIT_SUCCESS; + + r = sd_bus_default_system(&bus); + if (r < 0) { + cli_error("cannot connect to system bus: %s", strerror(-r)); + return EXIT_FAILURE; + } + + r = ctl_main(argc, argv); + sd_bus_unref(bus); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +}