/*
*
* (c) Philippe 2019, philippe_44@outlook.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
*/
#include
#include "platform.h"
#ifdef WIN32
#include
#include
#include
#include
#include
#include "mdns.h"
#include "mdnsd.h"
#include "mdnssd-itf.h"
#else
#include "esp_pthread.h"
#include "mdns.h"
#include "mbedtls/version.h"
#include
#endif
#include "util.h"
#include "raop.h"
#include "rtp.h"
#include "dmap_parser.h"
#include "log_util.h"
#define RTSP_STACK_SIZE (8*1024)
#define SEARCH_STACK_SIZE (2*1048)
typedef struct raop_ctx_s {
#ifdef WIN32
struct mdns_service *svc;
struct mdnsd *svr;
#endif
struct in_addr host; // IP of bridge
short unsigned port; // RTSP port for AirPlay
int sock; // socket of the above
struct in_addr peer; // IP of the iDevice (airplay sender)
bool running;
#ifdef WIN32
pthread_t thread, search_thread;
#else
TaskHandle_t thread, search_thread, joiner;
StaticTask_t *xTaskBuffer;
StackType_t xStack[RTSP_STACK_SIZE] __attribute__ ((aligned (4)));
#endif
unsigned char mac[6];
int latency;
struct {
char *aesiv, *aeskey;
char *fmtp;
} rtsp;
struct rtp_s *rtp;
raop_cmd_cb_t cmd_cb;
raop_data_cb_t data_cb;
struct {
char DACPid[32], id[32];
struct in_addr host;
u16_t port;
#ifdef WIN32
struct mDNShandle_s *handle;
#else
bool running;
TaskHandle_t thread, joiner;
StaticTask_t *xTaskBuffer;
StackType_t xStack[SEARCH_STACK_SIZE] __attribute__ ((aligned (4)));;
#endif
} active_remote;
void *owner;
} raop_ctx_t;
extern struct mdnsd* glmDNSServer;
extern log_level raop_loglevel;
static log_level *loglevel = &raop_loglevel;
static void* rtsp_thread(void *arg);
static bool handle_rtsp(raop_ctx_t *ctx, int sock);
static char* rsa_apply(unsigned char *input, int inlen, int *outlen, int mode);
static int base64_pad(char *src, char **padded);
static int base64_encode(const void *data, int size, char **str);
static int base64_decode(const char *str, void *data);
static void* search_remote(void *args);
extern char private_key[];
enum { RSA_MODE_KEY, RSA_MODE_AUTH };
static void on_dmap_string(void *ctx, const char *code, const char *name, const char *buf, size_t len);
/*----------------------------------------------------------------------------*/
struct raop_ctx_s *raop_create(struct in_addr host, char *name,
unsigned char mac[6], int latency,
raop_cmd_cb_t cmd_cb, raop_data_cb_t data_cb) {
struct raop_ctx_s *ctx = malloc(sizeof(struct raop_ctx_s));
struct sockaddr_in addr;
char id[64];
#ifdef WIN32
socklen_t nlen = sizeof(struct sockaddr);
char *txt[] = { "am=airesp32", "tp=UDP", "sm=false", "sv=false", "ek=1",
"et=0,1", "md=0,1,2", "cn=0,1", "ch=2",
"ss=16", "sr=44100", "vn=3", "txtvers=1",
NULL };
#else
mdns_txt_item_t txt[] = {
{"am", "airesp32"},
{"tp", "UDP"},
{"sm","false"},
{"sv","false"},
{"ek","1"},
{"et","0,1"},
{"md","0,1,2"},
{"cn","0,1"},
{"ch","2"},
{"ss","16"},
{"sr","44100"},
{"vn","3"},
{"txtvers","1"},
};
#endif
if (!ctx) return NULL;
// make sure we have a clean context
memset(ctx, 0, sizeof(raop_ctx_t));
#ifdef WIN32
ctx->svr = glmDNSServer;
#endif
ctx->host = host;
ctx->sock = socket(AF_INET, SOCK_STREAM, 0);
ctx->cmd_cb = cmd_cb;
ctx->data_cb = data_cb;
ctx->latency = min(latency, 88200);
if (ctx->sock == -1) {
LOG_ERROR("Cannot create listening socket", NULL);
free(ctx);
return NULL;
}
memset(&addr, 0, sizeof(addr));
addr.sin_addr.s_addr = host.s_addr;
addr.sin_family = AF_INET;
#ifdef WIN32
ctx->port = 0;
addr.sin_port = htons(ctx->port);
#else
ctx->port = 5000;
addr.sin_port = htons(ctx->port);
#endif
if (bind(ctx->sock, (struct sockaddr *) &addr, sizeof(addr)) < 0 || listen(ctx->sock, 1)) {
LOG_ERROR("Cannot bind or listen RTSP listener: %s", strerror(errno));
free(ctx);
closesocket(ctx->sock);
return NULL;
}
#ifdef WIN32
getsockname(ctx->sock, (struct sockaddr *) &addr, &nlen);
ctx->port = ntohs(addr.sin_port);
#endif
ctx->running = true;
memcpy(ctx->mac, mac, 6);
snprintf(id, 64, "%02X%02X%02X%02X%02X%02X@%s", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], name);
#ifdef WIN32
// seems that Windows snprintf does not add NULL char if actual size > max
id[63] = '\0';
ctx->svc = mdnsd_register_svc(ctx->svr, id, "_raop._tcp.local", ctx->port, NULL, (const char**) txt);
pthread_create(&ctx->thread, NULL, &rtsp_thread, ctx);
#else
LOG_INFO("starting mDNS with %s", id);
ESP_ERROR_CHECK( mdns_service_add(id, "_raop", "_tcp", ctx->port, txt, sizeof(txt) / sizeof(mdns_txt_item_t)) );
ctx->xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
ctx->thread = xTaskCreateStatic( (TaskFunction_t) rtsp_thread, "RTSP_thread", RTSP_STACK_SIZE, ctx, ESP_TASK_PRIO_MIN + 1, ctx->xStack, ctx->xTaskBuffer);
#endif
return ctx;
}
/*----------------------------------------------------------------------------*/
void raop_delete(struct raop_ctx_s *ctx) {
#ifdef WIN32
int sock;
struct sockaddr addr;
socklen_t nlen = sizeof(struct sockaddr);
#endif
if (!ctx) return;
#ifdef WIN32
ctx->running = false;
// wake-up thread by connecting socket, needed for freeBSD
sock = socket(AF_INET, SOCK_STREAM, 0);
getsockname(ctx->sock, (struct sockaddr *) &addr, &nlen);
connect(sock, (struct sockaddr*) &addr, sizeof(addr));
closesocket(sock);
pthread_join(ctx->thread, NULL);
rtp_end(ctx->rtp);
shutdown(ctx->sock, SD_BOTH);
closesocket(ctx->sock);
// terminate search, but do not reclaim memory of pthread if never launched
if (ctx->active_remote.handle) {
close_mDNS(ctx->active_remote.handle);
pthread_join(ctx->search_thread, NULL);
}
// stop broadcasting devices
mdns_service_remove(ctx->svr, ctx->svc);
mdnsd_stop(ctx->svr);
#else
// first stop the search task if any
if (ctx->active_remote.running) {
ctx->active_remote.joiner = xTaskGetCurrentTaskHandle();
ctx->active_remote.running = false;
vTaskResume(ctx->active_remote.thread);
ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
vTaskDelete(ctx->active_remote.thread);
heap_caps_free(ctx->active_remote.xTaskBuffer);
}
// then the RTSP task
ctx->joiner = xTaskGetCurrentTaskHandle();
ctx->running = false;
ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
vTaskDelete(ctx->thread);
heap_caps_free(ctx->xTaskBuffer);
rtp_end(ctx->rtp);
shutdown(ctx->sock, SHUT_RDWR);
closesocket(ctx->sock);
mdns_service_remove("_raop", "_tcp");
#endif
NFREE(ctx->rtsp.aeskey);
NFREE(ctx->rtsp.aesiv);
NFREE(ctx->rtsp.fmtp);
free(ctx);
}
/*----------------------------------------------------------------------------*/
void raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) {
struct sockaddr_in addr;
int sock;
char *command = NULL;
// first notify the remote controller (if any)
switch(event) {
case RAOP_REW:
command = strdup("beginrew");
break;
case RAOP_FWD:
command = strdup("beginff");
break;
case RAOP_PREV:
command = strdup("previtem");
break;
case RAOP_NEXT:
command = strdup("nextitem");
break;
case RAOP_TOGGLE:
command = strdup("playpause");
break;
case RAOP_PAUSE:
command = strdup("pause");
break;
case RAOP_PLAY:
command = strdup("play");
break;
case RAOP_RESUME:
command = strdup("playresume");
break;
case RAOP_STOP:
command = strdup("stop");
break;
case RAOP_VOLUME_UP:
command = strdup("volumeup");
break;
case RAOP_VOLUME_DOWN:
command = strdup("volumedown");
break;
case RAOP_VOLUME: {
float Volume = *((float*) param);
Volume = Volume ? (Volume - 1) * 30 : -144;
asprintf(&command,"setproperty?dmcp.device-volume=%0.4lf", Volume);
break;
}
default:
break;
}
// no command to send to remote or no remote found yet
if (!command || !ctx->active_remote.port) {
NFREE(command);
return;
}
sock = socket(AF_INET, SOCK_STREAM, 0);
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = S_ADDR(ctx->active_remote.host);
addr.sin_port = htons(ctx->active_remote.port);
if (!connect(sock, (struct sockaddr*) &addr, sizeof(addr))) {
char *method, *buf, resp[512] = "";
int len;
key_data_t headers[4] = { {NULL, NULL} };
asprintf(&method, "GET /ctrl-int/1/%s HTTP/1.0", command);
kd_add(headers, "Active-Remote", ctx->active_remote.id);
kd_add(headers, "Connection", "close");
buf = http_send(sock, method, headers);
len = recv(sock, resp, 512, 0);
if (len > 0) resp[len-1] = '\0';
LOG_INFO("[%p]: sending airplay remote\n%s<== received ==>\n%s", ctx, buf, resp);
NFREE(method);
NFREE(buf);
kd_free(headers);
}
free(command);
closesocket(sock);
}
/*----------------------------------------------------------------------------*/
static void *rtsp_thread(void *arg) {
raop_ctx_t *ctx = (raop_ctx_t*) arg;
int sock = -1;
while (ctx->running) {
fd_set rfds;
struct timeval timeout = {0, 100*1000};
int n;
bool res = false;
if (sock == -1) {
struct sockaddr_in peer;
socklen_t addrlen = sizeof(struct sockaddr_in);
sock = accept(ctx->sock, (struct sockaddr*) &peer, &addrlen);
ctx->peer.s_addr = peer.sin_addr.s_addr;
if (sock != -1 && ctx->running) {
LOG_INFO("got RTSP connection %u", sock);
} else continue;
}
FD_ZERO(&rfds);
FD_SET(sock, &rfds);
n = select(sock + 1, &rfds, NULL, NULL, &timeout);
if (!n) continue;
if (n > 0) res = handle_rtsp(ctx, sock);
if (n < 0 || !res) {
closesocket(sock);
LOG_INFO("RTSP close %u", sock);
sock = -1;
}
}
if (sock != -1) closesocket(sock);
#ifndef WIN32
xTaskNotifyGive(ctx->joiner);
vTaskSuspend(NULL);
#endif
return NULL;
}
/*----------------------------------------------------------------------------*/
static bool handle_rtsp(raop_ctx_t *ctx, int sock)
{
char *buf = NULL, *body = NULL, method[16] = "";
key_data_t headers[16], resp[8] = { {NULL, NULL} };
int len;
bool success = true;
if (!http_parse(sock, method, headers, &body, &len)) {
NFREE(body);
kd_free(headers);
return false;
}
if (strcmp(method, "OPTIONS")) {
LOG_INFO("[%p]: received %s", ctx, method);
}
if ((buf = kd_lookup(headers, "Apple-Challenge")) != NULL) {
int n;
char *buf_pad, *p, *data_b64 = NULL, data[32];
LOG_INFO("[%p]: challenge %s", ctx, buf);
// need to pad the base64 string as apple device don't
base64_pad(buf, &buf_pad);
p = data + min(base64_decode(buf_pad, data), 32-10);
p = (char*) memcpy(p, &S_ADDR(ctx->host), 4) + 4;
p = (char*) memcpy(p, ctx->mac, 6) + 6;
memset(p, 0, 32 - (p - data));
p = rsa_apply((unsigned char*) data, 32, &n, RSA_MODE_AUTH);
n = base64_encode(p, n, &data_b64);
// remove padding as well (seems to be optional now)
for (n = strlen(data_b64) - 1; n > 0 && data_b64[n] == '='; data_b64[n--] = '\0');
kd_add(resp, "Apple-Response", data_b64);
NFREE(p);
NFREE(buf_pad);
NFREE(data_b64);
}
if (!strcmp(method, "OPTIONS")) {
kd_add(resp, "Public", "ANNOUNCE, SETUP, RECORD, PAUSE, FLUSH, TEARDOWN, OPTIONS, GET_PARAMETER, SET_PARAMETER");
} else if (!strcmp(method, "ANNOUNCE")) {
char *padded, *p;
NFREE(ctx->rtsp.aeskey);
NFREE(ctx->rtsp.aesiv);
NFREE(ctx->rtsp.fmtp);
// LMS might has taken over the player, leaving us with a running RTP session (should not happen)
if (ctx->rtp) {
LOG_WARN("[%p]: closing unfinished RTP session", ctx);
rtp_end(ctx->rtp);
}
// same, should not happen unless we have missed a teardown ...
if (ctx->active_remote.running) {
ctx->active_remote.joiner = xTaskGetCurrentTaskHandle();
ctx->active_remote.running = false;
vTaskResume(ctx->active_remote.thread);
ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
vTaskDelete(ctx->active_remote.thread);
heap_caps_free(ctx->active_remote.xTaskBuffer);
memset(&ctx->active_remote, 0, sizeof(ctx->active_remote));
LOG_WARN("[%p]: closing unfinished mDNS search", ctx);
}
if ((p = strcasestr(body, "rsaaeskey")) != NULL) {
unsigned char *aeskey;
int len, outlen;
p = strextract(p, ":", "\r\n");
base64_pad(p, &padded);
aeskey = malloc(strlen(padded));
len = base64_decode(padded, aeskey);
ctx->rtsp.aeskey = rsa_apply(aeskey, len, &outlen, RSA_MODE_KEY);
NFREE(p);
NFREE(aeskey);
NFREE(padded);
}
if ((p = strcasestr(body, "aesiv")) != NULL) {
p = strextract(p, ":", "\r\n");
base64_pad(p, &padded);
ctx->rtsp.aesiv = malloc(strlen(padded));
base64_decode(padded, ctx->rtsp.aesiv);
NFREE(p);
NFREE(padded);
}
if ((p = strcasestr(body, "fmtp")) != NULL) {
p = strextract(p, ":", "\r\n");
ctx->rtsp.fmtp = strdup(p);
NFREE(p);
}
// on announce, search remote
if ((buf = kd_lookup(headers, "DACP-ID")) != NULL) strcpy(ctx->active_remote.DACPid, buf);
if ((buf = kd_lookup(headers, "Active-Remote")) != NULL) strcpy(ctx->active_remote.id, buf);
#ifdef WIN32
ctx->active_remote.handle = init_mDNS(false, ctx->host);
pthread_create(&ctx->search_thread, NULL, &search_remote, ctx);
#else
ctx->active_remote.running = true;
ctx->active_remote.xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
ctx->active_remote.thread = xTaskCreateStatic( (TaskFunction_t) search_remote, "search_remote", SEARCH_STACK_SIZE, ctx, ESP_TASK_PRIO_MIN + 1, ctx->active_remote.xStack, ctx->active_remote.xTaskBuffer);
#endif
} else if (!strcmp(method, "SETUP") && ((buf = kd_lookup(headers, "Transport")) != NULL)) {
char *p;
rtp_resp_t rtp = { 0 };
short unsigned tport = 0, cport = 0;
// we are about to stream, do something if needed
success = ctx->cmd_cb(RAOP_SETUP);
if ((p = strcasestr(buf, "timing_port")) != NULL) sscanf(p, "%*[^=]=%hu", &tport);
if ((p = strcasestr(buf, "control_port")) != NULL) sscanf(p, "%*[^=]=%hu", &cport);
rtp = rtp_init(ctx->peer, ctx->latency, ctx->rtsp.aeskey, ctx->rtsp.aesiv,
ctx->rtsp.fmtp, cport, tport, ctx->cmd_cb, ctx->data_cb);
ctx->rtp = rtp.ctx;
if ( (cport * tport * rtp.cport * rtp.tport * rtp.aport) != 0 && rtp.ctx) {
char *transport;
asprintf(&transport, "RTP/AVP/UDP;unicast;mode=record;control_port=%u;timing_port=%u;server_port=%u", rtp.cport, rtp.tport, rtp.aport);
LOG_DEBUG("[%p]: audio=(%hu:%hu), timing=(%hu:%hu), control=(%hu:%hu)", ctx, 0, rtp.aport, tport, rtp.tport, cport, rtp.cport);
kd_add(resp, "Transport", transport);
kd_add(resp, "Session", "DEADBEEF");
free(transport);
} else {
success = false;
LOG_INFO("[%p]: cannot start session, missing ports", ctx);
}
} else if (!strcmp(method, "RECORD")) {
unsigned short seqno = 0;
unsigned rtptime = 0;
char *p;
if (ctx->latency) {
char latency[6];
snprintf(latency, 6, "%u", ctx->latency);
kd_add(resp, "Audio-Latency", latency);
}
buf = kd_lookup(headers, "RTP-Info");
if (buf && (p = strcasestr(buf, "seq")) != NULL) sscanf(p, "%*[^=]=%hu", &seqno);
if (buf && (p = strcasestr(buf, "rtptime")) != NULL) sscanf(p, "%*[^=]=%u", &rtptime);
if (ctx->rtp) rtp_record(ctx->rtp, seqno, rtptime);
success = ctx->cmd_cb(RAOP_STREAM);
} else if (!strcmp(method, "FLUSH")) {
unsigned short seqno = 0;
unsigned rtptime = 0;
char *p;
buf = kd_lookup(headers, "RTP-Info");
if ((p = strcasestr(buf, "seq")) != NULL) sscanf(p, "%*[^=]=%hu", &seqno);
if ((p = strcasestr(buf, "rtptime")) != NULL) sscanf(p, "%*[^=]=%u", &rtptime);
// only send FLUSH if useful (discards frames above buffer head and top)
if (ctx->rtp && rtp_flush(ctx->rtp, seqno, rtptime))
success = ctx->cmd_cb(RAOP_FLUSH);
} else if (!strcmp(method, "TEARDOWN")) {
rtp_end(ctx->rtp);
ctx->rtp = NULL;
// need to make sure no search is on-going and reclaim pthread memory
#ifdef WIN32
if (ctx->active_remote.handle) close_mDNS(ctx->active_remote.handle);
pthread_join(ctx->search_thread, NULL);
#else
ctx->active_remote.joiner = xTaskGetCurrentTaskHandle();
ctx->active_remote.running = false;
// task might not need to be resumed anyway
vTaskResume(ctx->active_remote.thread);
ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
vTaskDelete(ctx->active_remote.thread);
heap_caps_free(ctx->active_remote.xTaskBuffer);
LOG_INFO("[%p]: mDNS search task terminated", ctx);
#endif
memset(&ctx->active_remote, 0, sizeof(ctx->active_remote));
NFREE(ctx->rtsp.aeskey);
NFREE(ctx->rtsp.aesiv);
NFREE(ctx->rtsp.fmtp);
success = ctx->cmd_cb(RAOP_STOP);
} else if (!strcmp(method, "SET_PARAMETER")) {
char *p;
if (body && (p = strcasestr(body, "volume")) != NULL) {
float volume;
sscanf(p, "%*[^:]:%f", &volume);
LOG_INFO("[%p]: SET PARAMETER volume %f", ctx, volume);
volume = (volume == -144.0) ? 0 : (1 + volume / 30);
success = ctx->cmd_cb(RAOP_VOLUME, volume);
} else if (body && (p = strcasestr(body, "progress")) != NULL) {
int start, current, stop = 0;
// we want ms, not s
sscanf(p, "%*[^:]:%u/%u/%u", &start, ¤t, &stop);
current = ((current - start) / 44100) * 1000;
if (stop) stop = ((stop - start) / 44100) * 1000;
else stop = -1;
LOG_INFO("[%p]: SET PARAMETER progress %u/%u %s", ctx, current, stop, p);
success = ctx->cmd_cb(RAOP_PROGRESS, current, stop);
}
if (body && ((p = kd_lookup(headers, "Content-Type")) != NULL) && !strcasecmp(p, "application/x-dmap-tagged")) {
struct metadata_s metadata;
dmap_settings settings = {
NULL, NULL, NULL, NULL, NULL, NULL, NULL, on_dmap_string, NULL,
NULL
};
LOG_INFO("[%p]: received metadata");
settings.ctx = &metadata;
memset(&metadata, 0, sizeof(struct metadata_s));
if (!dmap_parse(&settings, body, len)) {
LOG_INFO("[%p]: received metadata\n\tartist: %s\n\talbum: %s\n\ttitle: %s",
ctx, metadata.artist, metadata.album, metadata.title);
success = ctx->cmd_cb(RAOP_METADATA, metadata.artist, metadata.album, metadata.title);
free_metadata(&metadata);
}
}
}
// don't need to free "buf" because kd_lookup return a pointer, not a strdup
kd_add(resp, "Audio-Jack-Status", "connected; type=analog");
kd_add(resp, "CSeq", kd_lookup(headers, "CSeq"));
if (success) {
buf = http_send(sock, "RTSP/1.0 200 OK", resp);
} else {
buf = http_send(sock, "RTSP/1.0 503 ERROR", NULL);
closesocket(sock);
}
if (strcmp(method, "OPTIONS")) {
LOG_INFO("[%p]: responding:\n%s", ctx, buf ? buf : "");
}
NFREE(body);
NFREE(buf);
kd_free(resp);
kd_free(headers);
return true;
}
/*----------------------------------------------------------------------------*/
#ifdef WIN32
bool search_remote_cb(mDNSservice_t *slist, void *cookie, bool *stop) {
mDNSservice_t *s;
raop_ctx_t *ctx = (raop_ctx_t*) cookie;
// see if we have found an active remote for our ID
for (s = slist; s; s = s->next) {
if (strcasestr(s->name, ctx->active_remote.DACPid)) {
ctx->active_remote.host = s->addr;
ctx->active_remote.port = s->port;
LOG_INFO("[%p]: found ActiveRemote for %s at %s:%u", ctx, ctx->active_remote.DACPid,
inet_ntoa(ctx->active_remote.host), ctx->active_remote.port);
*stop = true;
break;
}
}
// let caller clear list
return false;
}
/*----------------------------------------------------------------------------*/
static void* search_remote(void *args) {
raop_ctx_t *ctx = (raop_ctx_t*) args;
query_mDNS(ctx->active_remote.handle, "_dacp._tcp.local", 0, 0, &search_remote_cb, (void*) ctx);
return NULL;
}
#else
/*----------------------------------------------------------------------------*/
static void* search_remote(void *args) {
raop_ctx_t *ctx = (raop_ctx_t*) args;
bool found = false;
LOG_INFO("starting remote search");
while (ctx->active_remote.running && !found) {
mdns_result_t *results = NULL;
mdns_result_t *r;
mdns_ip_addr_t *a;
if (mdns_query_ptr("_dacp", "_tcp", 3000, 32, &results)) {
LOG_ERROR("mDNS active remote query Failed");
continue;
}
for (r = results; r && !strcasestr(r->instance_name, ctx->active_remote.DACPid); r = r->next);
if (r) {
for (a = r->addr; a && a->addr.type != IPADDR_TYPE_V4; a = a->next);
if (a) {
found = true;
ctx->active_remote.host.s_addr = a->addr.u_addr.ip4.addr;
ctx->active_remote.port = r->port;
LOG_INFO("found remote %s %s:%hu", r->instance_name, inet_ntoa(ctx->active_remote.host), ctx->active_remote.port);
}
}
mdns_query_results_free(results);
}
/*
for some reason which is beyond me, if that tasks gives the semaphore
before the RTSP tasks waits for it, then freeRTOS crashes in queue
management caused by LWIP stack once the RTSP socket is closed. I have
no clue why, but so we'll suspend the tasks as soon as we're done with
search and wait for the resume then give the semaphore
*/
// PS: I know this is not fully race-condition free
if (ctx->active_remote.running) vTaskSuspend(NULL);
xTaskNotifyGive(ctx->active_remote.joiner);
// now our context will be deleted
vTaskSuspend(NULL);
return NULL;
}
#endif
/*----------------------------------------------------------------------------*/
static char *rsa_apply(unsigned char *input, int inlen, int *outlen, int mode)
{
static char super_secret_key[] =
"-----BEGIN RSA PRIVATE KEY-----\n"
"MIIEpQIBAAKCAQEA59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUt\n"
"wC5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDRKSKv6kDqnw4U\n"
"wPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuBOitnZ/bDzPHrTOZz0Dew0uowxf\n"
"/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJQ+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/\n"
"UAaHqn9JdsBWLUEpVviYnhimNVvYFZeCXg/IdTQ+x4IRdiXNv5hEewIDAQABAoIBAQDl8Axy9XfW\n"
"BLmkzkEiqoSwF0PsmVrPzH9KsnwLGH+QZlvjWd8SWYGN7u1507HvhF5N3drJoVU3O14nDY4TFQAa\n"
"LlJ9VM35AApXaLyY1ERrN7u9ALKd2LUwYhM7Km539O4yUFYikE2nIPscEsA5ltpxOgUGCY7b7ez5\n"
"NtD6nL1ZKauw7aNXmVAvmJTcuPxWmoktF3gDJKK2wxZuNGcJE0uFQEG4Z3BrWP7yoNuSK3dii2jm\n"
"lpPHr0O/KnPQtzI3eguhe0TwUem/eYSdyzMyVx/YpwkzwtYL3sR5k0o9rKQLtvLzfAqdBxBurciz\n"
"aaA/L0HIgAmOit1GJA2saMxTVPNhAoGBAPfgv1oeZxgxmotiCcMXFEQEWflzhWYTsXrhUIuz5jFu\n"
"a39GLS99ZEErhLdrwj8rDDViRVJ5skOp9zFvlYAHs0xh92ji1E7V/ysnKBfsMrPkk5KSKPrnjndM\n"
"oPdevWnVkgJ5jxFuNgxkOLMuG9i53B4yMvDTCRiIPMQ++N2iLDaRAoGBAO9v//mU8eVkQaoANf0Z\n"
"oMjW8CN4xwWA2cSEIHkd9AfFkftuv8oyLDCG3ZAf0vrhrrtkrfa7ef+AUb69DNggq4mHQAYBp7L+\n"
"k5DKzJrKuO0r+R0YbY9pZD1+/g9dVt91d6LQNepUE/yY2PP5CNoFmjedpLHMOPFdVgqDzDFxU8hL\n"
"AoGBANDrr7xAJbqBjHVwIzQ4To9pb4BNeqDndk5Qe7fT3+/H1njGaC0/rXE0Qb7q5ySgnsCb3DvA\n"
"cJyRM9SJ7OKlGt0FMSdJD5KG0XPIpAVNwgpXXH5MDJg09KHeh0kXo+QA6viFBi21y340NonnEfdf\n"
"54PX4ZGS/Xac1UK+pLkBB+zRAoGAf0AY3H3qKS2lMEI4bzEFoHeK3G895pDaK3TFBVmD7fV0Zhov\n"
"17fegFPMwOII8MisYm9ZfT2Z0s5Ro3s5rkt+nvLAdfC/PYPKzTLalpGSwomSNYJcB9HNMlmhkGzc\n"
"1JnLYT4iyUyx6pcZBmCd8bD0iwY/FzcgNDaUmbX9+XDvRA0CgYEAkE7pIPlE71qvfJQgoA9em0gI\n"
"LAuE4Pu13aKiJnfft7hIjbK+5kyb3TysZvoyDnb3HOKvInK7vXbKuU4ISgxB2bB3HcYzQMGsz1qJ\n"
"2gG0N5hvJpzwwhbhXqFKA4zaaSrw622wDniAK5MlIE0tIAKKP4yxNGjoD2QYjhBGuhvkWKY=\n"
"-----END RSA PRIVATE KEY-----";
#ifdef WIN32
unsigned char *out;
RSA *rsa;
BIO *bmem = BIO_new_mem_buf(super_secret_key, -1);
rsa = PEM_read_bio_RSAPrivateKey(bmem, NULL, NULL, NULL);
BIO_free(bmem);
out = malloc(RSA_size(rsa));
switch (mode) {
case RSA_MODE_AUTH:
*outlen = RSA_private_encrypt(inlen, input, out, rsa,
RSA_PKCS1_PADDING);
break;
case RSA_MODE_KEY:
*outlen = RSA_private_decrypt(inlen, input, out, rsa,
RSA_PKCS1_OAEP_PADDING);
break;
}
RSA_free(rsa);
return (char*) out;
#else
mbedtls_pk_context pkctx;
mbedtls_rsa_context *trsa;
size_t olen;
/*
we should do entropy initialization & pass a rng function but this
consumes a ton of stack and there is no security concern here. Anyway,
mbedtls takes a lot of stack, unfortunately ...
*/
mbedtls_pk_init(&pkctx);
mbedtls_pk_parse_key(&pkctx, (unsigned char *)super_secret_key,
sizeof(super_secret_key), NULL, 0);
uint8_t *outbuf = NULL;
trsa = mbedtls_pk_rsa(pkctx);
switch (mode) {
case RSA_MODE_AUTH:
mbedtls_rsa_set_padding(trsa, MBEDTLS_RSA_PKCS_V15, MBEDTLS_MD_NONE);
outbuf = malloc(trsa->len);
mbedtls_rsa_pkcs1_encrypt(trsa, NULL, NULL, MBEDTLS_RSA_PRIVATE, inlen, input, outbuf);
*outlen = trsa->len;
break;
case RSA_MODE_KEY:
mbedtls_rsa_set_padding(trsa, MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA1);
outbuf = malloc(trsa->len);
mbedtls_rsa_pkcs1_decrypt(trsa, NULL, NULL, MBEDTLS_RSA_PRIVATE, &olen, input, outbuf, trsa->len);
*outlen = olen;
break;
}
mbedtls_pk_free(&pkctx);
return (char*) outbuf;
#endif
}
#define DECODE_ERROR 0xffffffff
static char base64_chars[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/*----------------------------------------------------------------------------*/
static int base64_pad(char *src, char **padded)
{
int n;
n = strlen(src) + strlen(src) % 4;
*padded = malloc(n + 1);
memset(*padded, '=', n);
memcpy(*padded, src, strlen(src));
(*padded)[n] = '\0';
return strlen(*padded);
}
/*----------------------------------------------------------------------------*/
static int pos(char c)
{
char *p;
for (p = base64_chars; *p; p++)
if (*p == c)
return p - base64_chars;
return -1;
}
/*----------------------------------------------------------------------------*/
static int base64_encode(const void *data, int size, char **str)
{
char *s, *p;
int i;
int c;
const unsigned char *q;
p = s = (char *) malloc(size * 4 / 3 + 4);
if (p == NULL) return -1;
q = (const unsigned char *) data;
i = 0;
for (i = 0; i < size;) {
c = q[i++];
c *= 256;
if (i < size) c += q[i];
i++;
c *= 256;
if (i < size) c += q[i];
i++;
p[0] = base64_chars[(c & 0x00fc0000) >> 18];
p[1] = base64_chars[(c & 0x0003f000) >> 12];
p[2] = base64_chars[(c & 0x00000fc0) >> 6];
p[3] = base64_chars[(c & 0x0000003f) >> 0];
if (i > size) p[3] = '=';
if (i > size + 1) p[2] = '=';
p += 4;
}
*p = 0;
*str = s;
return strlen(s);
}
/*----------------------------------------------------------------------------*/
static unsigned int token_decode(const char *token)
{
int i;
unsigned int val = 0;
int marker = 0;
if (strlen(token) < 4)
return DECODE_ERROR;
for (i = 0; i < 4; i++) {
val *= 64;
if (token[i] == '=')
marker++;
else if (marker > 0)
return DECODE_ERROR;
else
val += pos(token[i]);
}
if (marker > 2)
return DECODE_ERROR;
return (marker << 24) | val;
}
/*----------------------------------------------------------------------------*/
static int base64_decode(const char *str, void *data)
{
const char *p;
unsigned char *q;
q = data;
for (p = str; *p && (*p == '=' || strchr(base64_chars, *p)); p += 4) {
unsigned int val = token_decode(p);
unsigned int marker = (val >> 24) & 0xff;
if (val == DECODE_ERROR)
return -1;
*q++ = (val >> 16) & 0xff;
if (marker < 2)
*q++ = (val >> 8) & 0xff;
if (marker < 1)
*q++ = val & 0xff;
}
return q - (unsigned char *) data;
}
/*----------------------------------------------------------------------------*/
static void on_dmap_string(void *ctx, const char *code, const char *name, const char *buf, size_t len) {
struct metadata_s *metadata = (struct metadata_s *) ctx;
if (!strcasecmp(code, "asar")) metadata->artist = strndup(buf, len);
else if (!strcasecmp(code, "asal")) metadata->album = strndup(buf, len);
else if (!strcasecmp(code, "minm")) metadata->title = strndup(buf, len);
}