mirror of
https://github.com/clementine-player/Clementine
synced 2025-01-31 03:27:40 +01:00
Merge branch 'remotecontrol'
This commit is contained in:
commit
51d0664eb6
20
3rdparty/tinysvcmdns/CMakeLists.txt
vendored
Normal file
20
3rdparty/tinysvcmdns/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99")
|
||||
add_definitions(-D_GNU_SOURCE)
|
||||
|
||||
set(TINYSVCMDNS-SOURCES
|
||||
mdns.c
|
||||
mdnsd.c
|
||||
)
|
||||
|
||||
find_library(PTHREAD
|
||||
pthreadGC2
|
||||
)
|
||||
|
||||
add_library(tinysvcmdns STATIC
|
||||
${TINYSVCMDNS-SOURCES}
|
||||
)
|
||||
|
||||
target_link_libraries(tinysvcmdns
|
||||
${PTHREAD}
|
||||
ws2_32
|
||||
)
|
25
3rdparty/tinysvcmdns/LICENSE.txt
vendored
Normal file
25
3rdparty/tinysvcmdns/LICENSE.txt
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
Copyright (c) 2011, Darell Tan
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. The name of the author may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
38
3rdparty/tinysvcmdns/Makefile
vendored
Normal file
38
3rdparty/tinysvcmdns/Makefile
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
#
|
||||
# Makefile for tinysvcmdns
|
||||
#
|
||||
|
||||
CFLAGS += -Wall -pedantic -std=gnu99
|
||||
#CFLAGS += -g
|
||||
CFLAGS += -O2 -DNDEBUG
|
||||
LDLIBS = -lpthread
|
||||
|
||||
ifneq ($(CROSS_COMPILE),)
|
||||
CC = gcc
|
||||
CC := $(CROSS_COMPILE)$(CC)
|
||||
AR := $(CROSS_COMPILE)$(AR)
|
||||
endif
|
||||
|
||||
BIN=testmdnsd
|
||||
|
||||
LIBTINYSVCMDNS_OBJS = mdns.o mdnsd.o
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(BIN) libtinysvcmdns.a
|
||||
|
||||
clean:
|
||||
-$(RM) *.o
|
||||
-$(RM) *.bin
|
||||
-$(RM) mdns
|
||||
-$(RM) $(BIN)
|
||||
-$(RM) libtinysvcmdns.a
|
||||
|
||||
mdns: mdns.o
|
||||
|
||||
mdnsd: mdns.o mdnsd.o
|
||||
|
||||
testmdnsd: testmdnsd.o libtinysvcmdns.a
|
||||
|
||||
libtinysvcmdns.a: $(patsubst %, libtinysvcmdns.a(%), $(LIBTINYSVCMDNS_OBJS))
|
||||
|
68
3rdparty/tinysvcmdns/README.markdown
vendored
Normal file
68
3rdparty/tinysvcmdns/README.markdown
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
tinysvcmdns
|
||||
============
|
||||
tinysvcmdns is a tiny MDNS responder implementation for publishing services.
|
||||
|
||||
This implementation is only concerned with publishing services, without a
|
||||
system-wide daemon like Bonjour or Avahi. Its other goal is to be extremely
|
||||
small, embeddable, and have no external dependencies.
|
||||
|
||||
It only answers queries related to its own hostname (the A record), the
|
||||
service PTRs, and the "_services.dns-sd._udp.local" name, which advertises
|
||||
all services on a particular host.
|
||||
|
||||
Services consist of a single SRV and TXT record.
|
||||
|
||||
Decoding of MDNS packets is only done to retrieve the questions and answer RRs.
|
||||
The purpose for decoding answer RRs is to make sure the service PTR is not
|
||||
sent out if it is already included in the answer RRs.
|
||||
|
||||
It also only utilizes multicast packets, so no "QU" queries are accepted.
|
||||
|
||||
There is no name collision detection, so this means no queries are generated
|
||||
before publishing the services. However compliant responders will avoid using
|
||||
our names, since the implementation will respond to queries that match our
|
||||
name.
|
||||
|
||||
|
||||
TODO
|
||||
-----
|
||||
* better, more stable & complete API
|
||||
* name collision detection
|
||||
|
||||
|
||||
FILES
|
||||
------
|
||||
* mdns.c - provides data structures, parsing & encoding of MDNS packets
|
||||
* mdnsd.c - implements the server socket, communication and thread
|
||||
* testmdnsd.c - an example that creates an instance until terminated
|
||||
|
||||
|
||||
LICENSE
|
||||
--------
|
||||
tinysvcmdns is licensed under the 3-clause ("modified") BSD License.
|
||||
|
||||
Copyright (C) 2011 Darell Tan
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. The name of the author may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
993
3rdparty/tinysvcmdns/mdns.c
vendored
Normal file
993
3rdparty/tinysvcmdns/mdns.c
vendored
Normal file
@ -0,0 +1,993 @@
|
||||
/*
|
||||
* tinysvcmdns - a tiny MDNS implementation for publishing services
|
||||
* Copyright (C) 2011 Darell Tan
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. The name of the author may not be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "mdns.h"
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock.h>
|
||||
#include <in6addr.h>
|
||||
#else
|
||||
#include <netinet/in.h>
|
||||
#endif
|
||||
|
||||
|
||||
#define DEFAULT_TTL 120
|
||||
|
||||
|
||||
struct name_comp {
|
||||
uint8_t *label; // label
|
||||
size_t pos; // position in msg
|
||||
|
||||
struct name_comp *next;
|
||||
};
|
||||
|
||||
// ----- label functions -----
|
||||
|
||||
// duplicates a name
|
||||
inline uint8_t *dup_nlabel(const uint8_t *n) {
|
||||
assert(n[0] <= 63); // prevent mis-use
|
||||
return (uint8_t *) strdup((char *) n);
|
||||
}
|
||||
|
||||
// duplicates a label
|
||||
uint8_t *dup_label(const uint8_t *label) {
|
||||
int len = *label + 1;
|
||||
if (len > 63)
|
||||
return NULL;
|
||||
uint8_t *newlabel = malloc(len + 1);
|
||||
strncpy((char *) newlabel, (char *) label, len);
|
||||
newlabel[len] = '\0';
|
||||
return newlabel;
|
||||
}
|
||||
|
||||
uint8_t *join_nlabel(const uint8_t *n1, const uint8_t *n2) {
|
||||
int len1, len2;
|
||||
uint8_t *s;
|
||||
|
||||
assert(n1[0] <= 63 && n2[0] <= 63); // detect misuse
|
||||
|
||||
len1 = strlen((char *) n1);
|
||||
len2 = strlen((char *) n2);
|
||||
|
||||
s = malloc(len1 + len2 + 1);
|
||||
strncpy((char *) s, (char *) n1, len1);
|
||||
strncpy((char *) s+len1, (char *) n2, len2);
|
||||
s[len1 + len2] = '\0';
|
||||
return s;
|
||||
}
|
||||
|
||||
// returns a human-readable name label in dotted form
|
||||
char *nlabel_to_str(const uint8_t *name) {
|
||||
char *label, *labelp;
|
||||
const uint8_t *p;
|
||||
|
||||
assert(name != NULL);
|
||||
|
||||
label = labelp = malloc(256);
|
||||
|
||||
for (p = name; *p; p++) {
|
||||
strncpy(labelp, (char *) p + 1, *p);
|
||||
labelp += *p;
|
||||
*labelp = '.';
|
||||
labelp++;
|
||||
|
||||
p += *p;
|
||||
}
|
||||
|
||||
*labelp = '\0';
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
// returns the length of a label field
|
||||
// does NOT uncompress the field, so it could be as small as 2 bytes
|
||||
// or 1 for the root
|
||||
static size_t label_len(uint8_t *pkt_buf, size_t pkt_len, size_t off) {
|
||||
uint8_t *p;
|
||||
uint8_t *e = pkt_buf + pkt_len;
|
||||
size_t len = 0;
|
||||
|
||||
for (p = pkt_buf + off; p < e; p++) {
|
||||
if (*p == 0) {
|
||||
return len + 1;
|
||||
} else if ((*p & 0xC0) == 0xC0) {
|
||||
return len + 2;
|
||||
} else {
|
||||
len += *p + 1;
|
||||
p += *p;
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
// creates a label
|
||||
// free() after use
|
||||
uint8_t *create_label(const char *txt) {
|
||||
int len;
|
||||
uint8_t *s;
|
||||
|
||||
assert(txt != NULL);
|
||||
len = strlen(txt);
|
||||
if (len > 63)
|
||||
return NULL;
|
||||
|
||||
s = malloc(len + 2);
|
||||
s[0] = len;
|
||||
strncpy((char *) s + 1, txt, len);
|
||||
s[len + 1] = '\0';
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
// creates a uncompressed name label given a DNS name like "apple.b.com"
|
||||
// free() after use
|
||||
uint8_t *create_nlabel(const char *name) {
|
||||
char *label;
|
||||
char *p, *e, *lenpos;
|
||||
int len = 0;
|
||||
|
||||
assert(name != NULL);
|
||||
|
||||
len = strlen(name);
|
||||
label = malloc(len + 1 + 1);
|
||||
if (label == NULL)
|
||||
return NULL;
|
||||
|
||||
strncpy((char *) label + 1, name, len);
|
||||
label[len + 1] = '\0';
|
||||
|
||||
p = label;
|
||||
e = p + len;
|
||||
lenpos = p;
|
||||
|
||||
while (p < e) {
|
||||
*lenpos = 0;
|
||||
char *dot = memchr(p + 1, '.', e - p - 1);
|
||||
if (dot == NULL)
|
||||
dot = e + 1;
|
||||
*lenpos = dot - p - 1;
|
||||
|
||||
p = dot;
|
||||
lenpos = dot;
|
||||
}
|
||||
|
||||
return (uint8_t *) label;
|
||||
}
|
||||
|
||||
// copies a label from the buffer into a newly-allocated string
|
||||
// free() after use
|
||||
static uint8_t *copy_label(uint8_t *pkt_buf, size_t pkt_len, size_t off) {
|
||||
int len;
|
||||
|
||||
if (off > pkt_len)
|
||||
return NULL;
|
||||
|
||||
len = pkt_buf[off] + 1;
|
||||
if (off + len > pkt_len) {
|
||||
DEBUG_PRINTF("label length exceeds packet buffer\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return dup_label(pkt_buf + off);
|
||||
}
|
||||
|
||||
// uncompresses a name
|
||||
// free() after use
|
||||
static uint8_t *uncompress_nlabel(uint8_t *pkt_buf, size_t pkt_len, size_t off) {
|
||||
uint8_t *p;
|
||||
uint8_t *e = pkt_buf + pkt_len;
|
||||
size_t len = 0;
|
||||
char *str, *sp;
|
||||
if (off >= pkt_len)
|
||||
return NULL;
|
||||
|
||||
// calculate length of uncompressed label
|
||||
for (p = pkt_buf + off; *p && p < e; p++) {
|
||||
size_t llen = 0;
|
||||
if ((*p & 0xC0) == 0xC0) {
|
||||
uint8_t *p2 = pkt_buf + (((p[0] & ~0xC0) << 8) | p[1]);
|
||||
llen = *p2 + 1;
|
||||
p = p2 + llen - 1;
|
||||
} else {
|
||||
llen = *p + 1;
|
||||
p += llen - 1;
|
||||
}
|
||||
len += llen;
|
||||
}
|
||||
|
||||
str = sp = malloc(len + 1);
|
||||
if (str == NULL)
|
||||
return NULL;
|
||||
|
||||
// FIXME: must merge this with above code
|
||||
for (p = pkt_buf + off; *p && p < e; p++) {
|
||||
size_t llen = 0;
|
||||
if ((*p & 0xC0) == 0xC0) {
|
||||
uint8_t *p2 = pkt_buf + (((p[0] & ~0xC0) << 8) | p[1]);
|
||||
llen = *p2 + 1;
|
||||
strncpy(sp, (char *) p2, llen);
|
||||
p = p2 + llen - 1;
|
||||
} else {
|
||||
llen = *p + 1;
|
||||
strncpy(sp, (char *) p, llen);
|
||||
p += llen - 1;
|
||||
}
|
||||
sp += llen;
|
||||
}
|
||||
*sp = '\0';
|
||||
|
||||
return (uint8_t *) str;
|
||||
}
|
||||
|
||||
// ----- RR list & group functions -----
|
||||
|
||||
const char *rr_get_type_name(enum rr_type type) {
|
||||
switch (type) {
|
||||
case RR_A: return "A";
|
||||
case RR_PTR: return "PTR";
|
||||
case RR_TXT: return "TXT";
|
||||
case RR_AAAA: return "AAAA";
|
||||
case RR_SRV: return "SRV";
|
||||
case RR_NSEC: return "NSEC";
|
||||
case RR_ANY: return "ANY";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void rr_entry_destroy(struct rr_entry *rr) {
|
||||
struct rr_data_txt *txt_rec;
|
||||
assert(rr);
|
||||
|
||||
// check rr_type and free data elements
|
||||
switch (rr->type) {
|
||||
case RR_PTR:
|
||||
if (rr->data.PTR.name)
|
||||
free(rr->data.PTR.name);
|
||||
// don't free entry
|
||||
break;
|
||||
|
||||
case RR_TXT:
|
||||
txt_rec = &rr->data.TXT;
|
||||
while (txt_rec) {
|
||||
struct rr_data_txt *next = txt_rec->next;
|
||||
if (txt_rec->txt)
|
||||
free(txt_rec->txt);
|
||||
|
||||
// only free() if it wasn't part of the struct
|
||||
if (txt_rec != &rr->data.TXT)
|
||||
free(txt_rec);
|
||||
|
||||
txt_rec = next;
|
||||
}
|
||||
break;
|
||||
|
||||
case RR_SRV:
|
||||
if (rr->data.SRV.target)
|
||||
free(rr->data.SRV.target);
|
||||
break;
|
||||
|
||||
default:
|
||||
// nothing to free
|
||||
break;
|
||||
}
|
||||
|
||||
free(rr->name);
|
||||
free(rr);
|
||||
}
|
||||
|
||||
// destroys an RR list (and optionally, items)
|
||||
void rr_list_destroy(struct rr_list *rr, char destroy_items) {
|
||||
struct rr_list *rr_next;
|
||||
|
||||
for (; rr; rr = rr_next) {
|
||||
rr_next = rr->next;
|
||||
if (destroy_items)
|
||||
rr_entry_destroy(rr->e);
|
||||
free(rr);
|
||||
}
|
||||
}
|
||||
|
||||
int rr_list_count(struct rr_list *rr) {
|
||||
int i = 0;
|
||||
for (; rr; i++, rr = rr->next);
|
||||
return i;
|
||||
}
|
||||
|
||||
struct rr_entry *rr_list_remove(struct rr_list **rr_head, struct rr_entry *rr) {
|
||||
struct rr_list *le = *rr_head, *pe = NULL;
|
||||
for (; le; le = le->next) {
|
||||
if (le->e == rr) {
|
||||
if (pe == NULL) {
|
||||
*rr_head = le->next;
|
||||
free(le);
|
||||
return rr;
|
||||
} else {
|
||||
pe->next = le->next;
|
||||
free(le);
|
||||
return rr;
|
||||
}
|
||||
}
|
||||
pe = le;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// appends an rr_entry to an RR list
|
||||
// if the RR is already in the list, it will not be added
|
||||
// RRs are compared by memory location - not its contents
|
||||
// return value of 0 means item not added
|
||||
int rr_list_append(struct rr_list **rr_head, struct rr_entry *rr) {
|
||||
struct rr_list *node = malloc(sizeof(struct rr_list));
|
||||
node->e = rr;
|
||||
node->next = NULL;
|
||||
|
||||
if (*rr_head == NULL) {
|
||||
*rr_head = node;
|
||||
} else {
|
||||
struct rr_list *e = *rr_head, *taile;
|
||||
for (; e; e = e->next) {
|
||||
// already in list - don't add
|
||||
if (e->e == rr) {
|
||||
free(node);
|
||||
return 0;
|
||||
}
|
||||
if (e->next == NULL)
|
||||
taile = e;
|
||||
}
|
||||
taile->next = node;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#define FILL_RR_ENTRY(rr, _name, _type) \
|
||||
rr->name = _name; \
|
||||
rr->type = _type; \
|
||||
rr->ttl = DEFAULT_TTL; \
|
||||
rr->cache_flush = 1; \
|
||||
rr->rr_class = 1;
|
||||
|
||||
struct rr_entry *rr_create_a(uint8_t *name, uint32_t addr) {
|
||||
DECL_MALLOC_ZERO_STRUCT(rr, rr_entry);
|
||||
FILL_RR_ENTRY(rr, name, RR_A);
|
||||
rr->data.A.addr = addr;
|
||||
return rr;
|
||||
}
|
||||
|
||||
struct rr_entry *rr_create_aaaa(uint8_t *name, struct in6_addr *addr) {
|
||||
DECL_MALLOC_ZERO_STRUCT(rr, rr_entry);
|
||||
FILL_RR_ENTRY(rr, name, RR_AAAA);
|
||||
rr->data.AAAA.addr = addr;
|
||||
return rr;
|
||||
}
|
||||
|
||||
struct rr_entry *rr_create_srv(uint8_t *name, uint16_t port, uint8_t *target) {
|
||||
DECL_MALLOC_ZERO_STRUCT(rr, rr_entry);
|
||||
FILL_RR_ENTRY(rr, name, RR_SRV);
|
||||
rr->data.SRV.port = port;
|
||||
rr->data.SRV.target = target;
|
||||
return rr;
|
||||
}
|
||||
|
||||
struct rr_entry *rr_create_ptr(uint8_t *name, struct rr_entry *d_rr) {
|
||||
DECL_MALLOC_ZERO_STRUCT(rr, rr_entry);
|
||||
FILL_RR_ENTRY(rr, name, RR_PTR);
|
||||
rr->cache_flush = 0; // PTRs shouldn't have their cache flush bit set
|
||||
rr->data.PTR.entry = d_rr;
|
||||
return rr;
|
||||
}
|
||||
|
||||
struct rr_entry *rr_create(uint8_t *name, enum rr_type type) {
|
||||
DECL_MALLOC_ZERO_STRUCT(rr, rr_entry);
|
||||
FILL_RR_ENTRY(rr, name, type);
|
||||
return rr;
|
||||
}
|
||||
|
||||
void rr_set_nsec(struct rr_entry *rr_nsec, enum rr_type type) {
|
||||
assert(rr_nsec->type = RR_NSEC);
|
||||
assert((type / 8) < sizeof(rr_nsec->data.NSEC.bitmap));
|
||||
|
||||
rr_nsec->data.NSEC.bitmap[ type / 8 ] = 1 << (7 - (type % 8));
|
||||
}
|
||||
|
||||
void rr_add_txt(struct rr_entry *rr_txt, const char *txt) {
|
||||
struct rr_data_txt *txt_rec;
|
||||
assert(rr_txt->type == RR_TXT);
|
||||
|
||||
txt_rec = &rr_txt->data.TXT;
|
||||
|
||||
// is current data filled?
|
||||
if (txt_rec->txt == NULL) {
|
||||
txt_rec->txt = create_label(txt);
|
||||
return;
|
||||
}
|
||||
|
||||
// find the last node
|
||||
for (; txt_rec->next; txt_rec = txt_rec->next);
|
||||
|
||||
// create a new empty node
|
||||
txt_rec->next = malloc(sizeof(struct rr_data_txt));
|
||||
|
||||
txt_rec = txt_rec->next;
|
||||
txt_rec->txt = create_label(txt);
|
||||
txt_rec->next = NULL;
|
||||
}
|
||||
|
||||
// adds a record to an rr_group
|
||||
void rr_group_add(struct rr_group **group, struct rr_entry *rr) {
|
||||
struct rr_group *g;
|
||||
|
||||
assert(rr != NULL);
|
||||
|
||||
if (*group) {
|
||||
g = rr_group_find(*group, rr->name);
|
||||
if (g) {
|
||||
rr_list_append(&g->rr, rr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
MALLOC_ZERO_STRUCT(g, rr_group);
|
||||
g->name = dup_nlabel(rr->name);
|
||||
rr_list_append(&g->rr, rr);
|
||||
|
||||
// prepend to list
|
||||
g->next = *group;
|
||||
*group = g;
|
||||
}
|
||||
|
||||
// finds a rr_group matching the given name
|
||||
struct rr_group *rr_group_find(struct rr_group* g, uint8_t *name) {
|
||||
for (; g; g = g->next) {
|
||||
if (cmp_nlabel(g->name, name) == 0)
|
||||
return g;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct rr_entry *rr_entry_find(struct rr_list *rr_list, uint8_t *name, uint16_t type) {
|
||||
struct rr_list *rr = rr_list;
|
||||
for (; rr; rr = rr->next) {
|
||||
if (rr->e->type == type && cmp_nlabel(rr->e->name, name) == 0)
|
||||
return rr->e;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// looks for a matching entry in rr_list
|
||||
// if entry is a PTR, we need to check if the PTR target also matches
|
||||
struct rr_entry *rr_entry_match(struct rr_list *rr_list, struct rr_entry *entry) {
|
||||
struct rr_list *rr = rr_list;
|
||||
for (; rr; rr = rr->next) {
|
||||
if (rr->e->type == entry->type && cmp_nlabel(rr->e->name, entry->name) == 0) {
|
||||
if (entry->type != RR_PTR) {
|
||||
return rr->e;
|
||||
} else if (cmp_nlabel(MDNS_RR_GET_PTR_NAME(entry), MDNS_RR_GET_PTR_NAME(rr->e)) == 0) {
|
||||
// if it's a PTR, we need to make sure PTR target also matches
|
||||
return rr->e;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void rr_group_destroy(struct rr_group *group) {
|
||||
struct rr_group *g = group;
|
||||
|
||||
while (g) {
|
||||
struct rr_group *nextg = g->next;
|
||||
free(g->name);
|
||||
rr_list_destroy(g->rr, 1);
|
||||
free(g);
|
||||
g = nextg;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t *mdns_write_u16(uint8_t *ptr, const uint16_t v) {
|
||||
*ptr++ = (uint8_t) (v >> 8) & 0xFF;
|
||||
*ptr++ = (uint8_t) (v >> 0) & 0xFF;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
uint8_t *mdns_write_u32(uint8_t *ptr, const uint32_t v) {
|
||||
*ptr++ = (uint8_t) (v >> 24) & 0xFF;
|
||||
*ptr++ = (uint8_t) (v >> 16) & 0xFF;
|
||||
*ptr++ = (uint8_t) (v >> 8) & 0xFF;
|
||||
*ptr++ = (uint8_t) (v >> 0) & 0xFF;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
uint16_t mdns_read_u16(const uint8_t *ptr) {
|
||||
return ((ptr[0] & 0xFF) << 8) |
|
||||
((ptr[1] & 0xFF) << 0);
|
||||
}
|
||||
|
||||
uint32_t mdns_read_u32(const uint8_t *ptr) {
|
||||
return ((ptr[0] & 0xFF) << 24) |
|
||||
((ptr[1] & 0xFF) << 16) |
|
||||
((ptr[2] & 0xFF) << 8) |
|
||||
((ptr[3] & 0xFF) << 0);
|
||||
}
|
||||
|
||||
// initialize the packet for reply
|
||||
// clears the packet of list structures but not its list items
|
||||
void mdns_init_reply(struct mdns_pkt *pkt, uint16_t id) {
|
||||
// copy transaction ID
|
||||
pkt->id = id;
|
||||
|
||||
// response flags
|
||||
pkt->flags = MDNS_FLAG_RESP | MDNS_FLAG_AA;
|
||||
|
||||
rr_list_destroy(pkt->rr_qn, 0);
|
||||
rr_list_destroy(pkt->rr_ans, 0);
|
||||
rr_list_destroy(pkt->rr_auth, 0);
|
||||
rr_list_destroy(pkt->rr_add, 0);
|
||||
|
||||
pkt->rr_qn = NULL;
|
||||
pkt->rr_ans = NULL;
|
||||
pkt->rr_auth = NULL;
|
||||
pkt->rr_add = NULL;
|
||||
|
||||
pkt->num_qn = 0;
|
||||
pkt->num_ans_rr = 0;
|
||||
pkt->num_auth_rr = 0;
|
||||
pkt->num_add_rr = 0;
|
||||
}
|
||||
|
||||
// destroys an mdns_pkt struct, including its contents
|
||||
void mdns_pkt_destroy(struct mdns_pkt *p) {
|
||||
rr_list_destroy(p->rr_qn, 1);
|
||||
rr_list_destroy(p->rr_ans, 1);
|
||||
rr_list_destroy(p->rr_auth, 1);
|
||||
rr_list_destroy(p->rr_add, 1);
|
||||
|
||||
free(p);
|
||||
}
|
||||
|
||||
|
||||
// parse the MDNS questions section
|
||||
// stores the parsed data in the given mdns_pkt struct
|
||||
static size_t mdns_parse_qn(uint8_t *pkt_buf, size_t pkt_len, size_t off,
|
||||
struct mdns_pkt *pkt) {
|
||||
const uint8_t *p = pkt_buf + off;
|
||||
struct rr_entry *rr;
|
||||
uint8_t *name;
|
||||
|
||||
assert(pkt != NULL);
|
||||
|
||||
rr = malloc(sizeof(struct rr_entry));
|
||||
memset(rr, 0, sizeof(struct rr_entry));
|
||||
|
||||
name = uncompress_nlabel(pkt_buf, pkt_len, off);
|
||||
p += label_len(pkt_buf, pkt_len, off);
|
||||
rr->name = name;
|
||||
|
||||
rr->type = mdns_read_u16(p);
|
||||
p += sizeof(uint16_t);
|
||||
|
||||
rr->unicast_query = (*p & 0x80) == 0x80;
|
||||
rr->rr_class = mdns_read_u16(p) & ~0x80;
|
||||
p += sizeof(uint16_t);
|
||||
|
||||
rr_list_append(&pkt->rr_qn, rr);
|
||||
|
||||
return p - (pkt_buf + off);
|
||||
}
|
||||
|
||||
// parse the MDNS RR section
|
||||
// stores the parsed data in the given mdns_pkt struct
|
||||
static size_t mdns_parse_rr(uint8_t *pkt_buf, size_t pkt_len, size_t off,
|
||||
struct mdns_pkt *pkt) {
|
||||
const uint8_t *p = pkt_buf + off;
|
||||
const uint8_t *e = pkt_buf + pkt_len;
|
||||
struct rr_entry *rr;
|
||||
uint8_t *name;
|
||||
size_t rr_data_len = 0;
|
||||
struct rr_data_txt *txt_rec;
|
||||
int parse_error = 0;
|
||||
|
||||
assert(pkt != NULL);
|
||||
|
||||
if (off > pkt_len)
|
||||
return 0;
|
||||
|
||||
rr = malloc(sizeof(struct rr_entry));
|
||||
memset(rr, 0, sizeof(struct rr_entry));
|
||||
|
||||
name = uncompress_nlabel(pkt_buf, pkt_len, off);
|
||||
p += label_len(pkt_buf, pkt_len, off);
|
||||
rr->name = name;
|
||||
|
||||
rr->type = mdns_read_u16(p);
|
||||
p += sizeof(uint16_t);
|
||||
|
||||
rr->cache_flush = (*p & 0x80) == 0x80;
|
||||
rr->rr_class = mdns_read_u16(p) & ~0x80;
|
||||
p += sizeof(uint16_t);
|
||||
|
||||
rr->ttl = mdns_read_u32(p);
|
||||
p += sizeof(uint32_t);
|
||||
|
||||
// RR data
|
||||
rr_data_len = mdns_read_u16(p);
|
||||
p += sizeof(uint16_t);
|
||||
|
||||
if (p + rr_data_len > e) {
|
||||
DEBUG_PRINTF("rr_data_len goes beyond packet buffer: %lu > %lu\n", rr_data_len, e - p);
|
||||
rr_entry_destroy(rr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
e = p + rr_data_len;
|
||||
|
||||
// see if we can parse the RR data
|
||||
switch (rr->type) {
|
||||
case RR_A:
|
||||
if (rr_data_len < sizeof(uint32_t)) {
|
||||
DEBUG_PRINTF("invalid rr_data_len=%lu for A record\n", rr_data_len);
|
||||
parse_error = 1;
|
||||
break;
|
||||
}
|
||||
rr->data.A.addr = ntohl(mdns_read_u32(p)); /* addr already in net order */
|
||||
p += sizeof(uint32_t);
|
||||
break;
|
||||
|
||||
case RR_AAAA:
|
||||
if (rr_data_len < sizeof(struct in6_addr)) {
|
||||
DEBUG_PRINTF("invalid rr_data_len=%lu for AAAA record\n", rr_data_len);
|
||||
parse_error = 1;
|
||||
break;
|
||||
}
|
||||
rr->data.AAAA.addr = malloc(sizeof(struct in6_addr));
|
||||
for (int i = 0; i < sizeof(struct in6_addr); i++)
|
||||
rr->data.AAAA.addr->s6_addr[i] = p[i];
|
||||
p += sizeof(struct in6_addr);
|
||||
break;
|
||||
|
||||
case RR_PTR:
|
||||
rr->data.PTR.name = uncompress_nlabel(pkt_buf, pkt_len, p - pkt_buf);
|
||||
if (rr->data.PTR.name == NULL) {
|
||||
DEBUG_PRINTF("unable to parse/uncompress label for PTR name\n");
|
||||
parse_error = 1;
|
||||
break;
|
||||
}
|
||||
p += rr_data_len;
|
||||
break;
|
||||
|
||||
case RR_TXT:
|
||||
txt_rec = &rr->data.TXT;
|
||||
|
||||
// not supposed to happen, but we should handle it
|
||||
if (rr_data_len == 0) {
|
||||
DEBUG_PRINTF("WARN: rr_data_len for TXT is 0\n");
|
||||
txt_rec->txt = create_label("");
|
||||
break;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
txt_rec->txt = copy_label(pkt_buf, pkt_len, p - pkt_buf);
|
||||
if (txt_rec->txt == NULL) {
|
||||
DEBUG_PRINTF("unable to copy label for TXT record\n");
|
||||
parse_error = 1;
|
||||
break;
|
||||
}
|
||||
p += txt_rec->txt[0] + 1;
|
||||
|
||||
if (p >= e)
|
||||
break;
|
||||
|
||||
// allocate another record
|
||||
txt_rec->next = malloc(sizeof(struct rr_data_txt));
|
||||
txt_rec = txt_rec->next;
|
||||
txt_rec->next = NULL;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// skip to end of RR data
|
||||
p = e;
|
||||
}
|
||||
|
||||
// if there was a parse error, destroy partial rr_entry
|
||||
if (parse_error) {
|
||||
rr_entry_destroy(rr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
rr_list_append(&pkt->rr_ans, rr);
|
||||
|
||||
return p - (pkt_buf + off);
|
||||
}
|
||||
|
||||
// parse a MDNS packet into an mdns_pkt struct
|
||||
struct mdns_pkt *mdns_parse_pkt(uint8_t *pkt_buf, size_t pkt_len) {
|
||||
uint8_t *p = pkt_buf;
|
||||
size_t off;
|
||||
struct mdns_pkt *pkt;
|
||||
int i;
|
||||
|
||||
if (pkt_len < 12)
|
||||
return NULL;
|
||||
|
||||
MALLOC_ZERO_STRUCT(pkt, mdns_pkt);
|
||||
|
||||
// parse header
|
||||
pkt->id = mdns_read_u16(p); p += sizeof(uint16_t);
|
||||
pkt->flags = mdns_read_u16(p); p += sizeof(uint16_t);
|
||||
pkt->num_qn = mdns_read_u16(p); p += sizeof(uint16_t);
|
||||
pkt->num_ans_rr = mdns_read_u16(p); p += sizeof(uint16_t);
|
||||
pkt->num_auth_rr = mdns_read_u16(p); p += sizeof(uint16_t);
|
||||
pkt->num_add_rr = mdns_read_u16(p); p += sizeof(uint16_t);
|
||||
|
||||
off = p - pkt_buf;
|
||||
|
||||
// parse questions
|
||||
for (i = 0; i < pkt->num_qn; i++) {
|
||||
size_t l = mdns_parse_qn(pkt_buf, pkt_len, off, pkt);
|
||||
if (! l) {
|
||||
DEBUG_PRINTF("error parsing question #%d\n", i);
|
||||
mdns_pkt_destroy(pkt);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
off += l;
|
||||
}
|
||||
|
||||
// parse answer RRs
|
||||
for (i = 0; i < pkt->num_ans_rr; i++) {
|
||||
size_t l = mdns_parse_rr(pkt_buf, pkt_len, off, pkt);
|
||||
if (! l) {
|
||||
DEBUG_PRINTF("error parsing answer #%d\n", i);
|
||||
mdns_pkt_destroy(pkt);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
off += l;
|
||||
}
|
||||
|
||||
// TODO: parse the authority and additional RR sections
|
||||
|
||||
return pkt;
|
||||
}
|
||||
|
||||
// encodes a name (label) into a packet using the name compression scheme
|
||||
// encoded names will be added to the compression list for subsequent use
|
||||
static size_t mdns_encode_name(uint8_t *pkt_buf, size_t pkt_len, size_t off,
|
||||
const uint8_t *name, struct name_comp *comp) {
|
||||
struct name_comp *c, *c_tail = NULL;
|
||||
uint8_t *p = pkt_buf + off;
|
||||
size_t len = 0;
|
||||
|
||||
if (name) {
|
||||
while (*name) {
|
||||
// find match for compression
|
||||
for (c = comp; c; c = c->next) {
|
||||
if (cmp_nlabel(name, c->label) == 0) {
|
||||
mdns_write_u16(p, 0xC000 | (c->pos & ~0xC000));
|
||||
return len + sizeof(uint16_t);
|
||||
}
|
||||
|
||||
if (c->next == NULL)
|
||||
c_tail = c;
|
||||
}
|
||||
|
||||
// copy this segment
|
||||
int segment_len = *name + 1;
|
||||
strncpy((char *) p, (char *) name, segment_len);
|
||||
|
||||
// cache the name for subsequent compression
|
||||
DECL_MALLOC_ZERO_STRUCT(new_c, name_comp);
|
||||
|
||||
new_c->label = (uint8_t *) name;
|
||||
new_c->pos = p - pkt_buf;
|
||||
c_tail->next = new_c;
|
||||
|
||||
// advance to next name segment
|
||||
p += segment_len;
|
||||
len += segment_len;
|
||||
name += segment_len;
|
||||
}
|
||||
}
|
||||
|
||||
*p = '\0'; // root "label"
|
||||
len += 1;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
// encodes an RR entry at the given offset
|
||||
// returns the size of the entire RR entry
|
||||
static size_t mdns_encode_rr(uint8_t *pkt_buf, size_t pkt_len, size_t off,
|
||||
struct rr_entry *rr, struct name_comp *comp) {
|
||||
uint8_t *p = pkt_buf + off, *p_data;
|
||||
size_t l;
|
||||
struct rr_data_txt *txt_rec;
|
||||
uint8_t *label;
|
||||
int i;
|
||||
|
||||
assert(off < pkt_len);
|
||||
|
||||
// name
|
||||
l = mdns_encode_name(pkt_buf, pkt_len, off, rr->name, comp);
|
||||
assert(l != 0);
|
||||
p += l;
|
||||
|
||||
// type
|
||||
p = mdns_write_u16(p, rr->type);
|
||||
|
||||
// class & cache flush
|
||||
p = mdns_write_u16(p, (rr->rr_class & ~0x8000) | (rr->cache_flush << 15));
|
||||
|
||||
// TTL
|
||||
p = mdns_write_u32(p, rr->ttl);
|
||||
|
||||
// data length (filled in later)
|
||||
p += sizeof(uint16_t);
|
||||
|
||||
// start of data marker
|
||||
p_data = p;
|
||||
|
||||
switch (rr->type) {
|
||||
case RR_A:
|
||||
/* htonl() needed coz addr already in net order */
|
||||
p = mdns_write_u32(p, htonl(rr->data.A.addr));
|
||||
break;
|
||||
|
||||
case RR_AAAA:
|
||||
for (i = 0; i < sizeof(struct in6_addr); i++)
|
||||
*p++ = rr->data.AAAA.addr->s6_addr[i];
|
||||
break;
|
||||
|
||||
case RR_PTR:
|
||||
label = rr->data.PTR.name ?
|
||||
rr->data.PTR.name :
|
||||
rr->data.PTR.entry->name;
|
||||
p += mdns_encode_name(pkt_buf, pkt_len, p - pkt_buf, label, comp);
|
||||
break;
|
||||
|
||||
case RR_TXT:
|
||||
txt_rec = &rr->data.TXT;
|
||||
for (; txt_rec; txt_rec = txt_rec->next) {
|
||||
int len = txt_rec->txt[0] + 1;
|
||||
strncpy((char *) p, (char *) txt_rec->txt, len);
|
||||
p += len;
|
||||
}
|
||||
break;
|
||||
|
||||
case RR_SRV:
|
||||
p = mdns_write_u16(p, rr->data.SRV.priority);
|
||||
|
||||
p = mdns_write_u16(p, rr->data.SRV.weight);
|
||||
|
||||
p = mdns_write_u16(p, rr->data.SRV.port);
|
||||
|
||||
p += mdns_encode_name(pkt_buf, pkt_len, p - pkt_buf,
|
||||
rr->data.SRV.target, comp);
|
||||
break;
|
||||
|
||||
case RR_NSEC:
|
||||
p += mdns_encode_name(pkt_buf, pkt_len, p - pkt_buf,
|
||||
rr->name, comp);
|
||||
|
||||
*p++ = 0; // bitmap window/block number
|
||||
|
||||
*p++ = sizeof(rr->data.NSEC.bitmap); // bitmap length
|
||||
|
||||
for (i = 0; i < sizeof(rr->data.NSEC.bitmap); i++)
|
||||
*p++ = rr->data.NSEC.bitmap[i];
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG_PRINTF("unhandled rr type 0x%02x\n", rr->type);
|
||||
}
|
||||
|
||||
// calculate data length based on p
|
||||
l = p - p_data;
|
||||
|
||||
// fill in the length
|
||||
mdns_write_u16(p - l - sizeof(uint16_t), l);
|
||||
|
||||
return p - pkt_buf - off;
|
||||
}
|
||||
|
||||
// encodes a MDNS packet from the given mdns_pkt struct into a buffer
|
||||
// returns the size of the entire MDNS packet
|
||||
size_t mdns_encode_pkt(struct mdns_pkt *answer, uint8_t *pkt_buf, size_t pkt_len) {
|
||||
struct name_comp *comp;
|
||||
uint8_t *p = pkt_buf;
|
||||
//uint8_t *e = pkt_buf + pkt_len;
|
||||
size_t off;
|
||||
int i;
|
||||
|
||||
assert(answer != NULL);
|
||||
assert(pkt_len >= 12);
|
||||
|
||||
if (p == NULL)
|
||||
return -1;
|
||||
|
||||
// this is an Answer - number of qns should be zero
|
||||
assert(answer->num_qn == 0);
|
||||
|
||||
p = mdns_write_u16(p, answer->id);
|
||||
p = mdns_write_u16(p, answer->flags);
|
||||
p = mdns_write_u16(p, answer->num_qn);
|
||||
p = mdns_write_u16(p, answer->num_ans_rr);
|
||||
p = mdns_write_u16(p, answer->num_auth_rr);
|
||||
p = mdns_write_u16(p, answer->num_add_rr);
|
||||
|
||||
off = p - pkt_buf;
|
||||
|
||||
// allocate list for name compression
|
||||
comp = malloc(sizeof(struct name_comp));
|
||||
if (comp == NULL)
|
||||
return -1;
|
||||
memset(comp, 0, sizeof(struct name_comp));
|
||||
|
||||
// dummy entry
|
||||
comp->label = (uint8_t *) "";
|
||||
comp->pos = 0;
|
||||
|
||||
// skip encoding of qn
|
||||
|
||||
struct rr_list *rr_set[] = {
|
||||
answer->rr_ans,
|
||||
answer->rr_auth,
|
||||
answer->rr_add
|
||||
};
|
||||
|
||||
// encode answer, authority and additional RRs
|
||||
for (i = 0; i < sizeof(rr_set) / sizeof(rr_set[0]); i++) {
|
||||
struct rr_list *rr = rr_set[i];
|
||||
for (; rr; rr = rr->next) {
|
||||
size_t l = mdns_encode_rr(pkt_buf, pkt_len, off, rr->e, comp);
|
||||
off += l;
|
||||
|
||||
if (off >= pkt_len) {
|
||||
DEBUG_PRINTF("packet buffer too small\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// free name compression list
|
||||
while (comp) {
|
||||
struct name_comp *c = comp->next;
|
||||
free(comp);
|
||||
comp = c;
|
||||
}
|
||||
|
||||
return off;
|
||||
}
|
||||
|
202
3rdparty/tinysvcmdns/mdns.h
vendored
Normal file
202
3rdparty/tinysvcmdns/mdns.h
vendored
Normal file
@ -0,0 +1,202 @@
|
||||
/*
|
||||
* tinysvcmdns - a tiny MDNS implementation for publishing services
|
||||
* Copyright (C) 2011 Darell Tan
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. The name of the author may not be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __MDNS_H__
|
||||
#define __MDNS_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock.h>
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
#define MALLOC_ZERO_STRUCT(x, type) \
|
||||
x = malloc(sizeof(struct type)); \
|
||||
memset(x, 0, sizeof(struct type));
|
||||
|
||||
#define DECL_MALLOC_ZERO_STRUCT(x, type) \
|
||||
struct type * MALLOC_ZERO_STRUCT(x, type)
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define DEBUG_PRINTF(...) printf(__VA_ARGS__)
|
||||
#else
|
||||
#define DEBUG_PRINTF(...) ((void) 0)
|
||||
#endif
|
||||
|
||||
|
||||
struct rr_data_srv {
|
||||
uint16_t priority;
|
||||
uint16_t weight;
|
||||
uint16_t port;
|
||||
uint8_t *target; // host
|
||||
};
|
||||
|
||||
struct rr_data_txt {
|
||||
struct rr_data_txt *next;
|
||||
uint8_t *txt;
|
||||
};
|
||||
|
||||
struct rr_data_nsec {
|
||||
//uint8_t *name; // same as record
|
||||
|
||||
// NSEC occupies the 47th bit, 5 bytes
|
||||
//uint8_t bitmap_len; // = 5
|
||||
uint8_t bitmap[5]; // network order: first byte contains LSB
|
||||
};
|
||||
|
||||
struct rr_data_ptr {
|
||||
uint8_t *name; // NULL if entry is to be used
|
||||
struct rr_entry *entry;
|
||||
};
|
||||
|
||||
struct rr_data_a {
|
||||
uint32_t addr;
|
||||
};
|
||||
|
||||
struct rr_data_aaaa {
|
||||
struct in6_addr *addr;
|
||||
};
|
||||
|
||||
struct rr_entry {
|
||||
uint8_t *name;
|
||||
|
||||
enum rr_type {
|
||||
RR_A = 0x01,
|
||||
RR_PTR = 0x0C,
|
||||
RR_TXT = 0x10,
|
||||
RR_AAAA = 0x1C,
|
||||
RR_SRV = 0x21,
|
||||
RR_NSEC = 0x2F,
|
||||
RR_ANY = 0xFF,
|
||||
} type;
|
||||
|
||||
uint32_t ttl;
|
||||
|
||||
// for use in Questions only
|
||||
char unicast_query;
|
||||
|
||||
// for use in Answers only
|
||||
char cache_flush;
|
||||
|
||||
uint16_t rr_class;
|
||||
|
||||
// RR data
|
||||
union {
|
||||
struct rr_data_nsec NSEC;
|
||||
struct rr_data_srv SRV;
|
||||
struct rr_data_txt TXT;
|
||||
struct rr_data_ptr PTR;
|
||||
struct rr_data_a A;
|
||||
struct rr_data_aaaa AAAA;
|
||||
} data;
|
||||
};
|
||||
|
||||
struct rr_list {
|
||||
struct rr_entry *e;
|
||||
struct rr_list *next;
|
||||
};
|
||||
|
||||
struct rr_group {
|
||||
uint8_t *name;
|
||||
|
||||
struct rr_list *rr;
|
||||
|
||||
struct rr_group *next;
|
||||
};
|
||||
|
||||
#define MDNS_FLAG_RESP (1 << 15) // Query=0 / Response=1
|
||||
#define MDNS_FLAG_AA (1 << 10) // Authoritative
|
||||
#define MDNS_FLAG_TC (1 << 9) // TrunCation
|
||||
#define MDNS_FLAG_RD (1 << 8) // Recursion Desired
|
||||
#define MDNS_FLAG_RA (1 << 7) // Recursion Available
|
||||
#define MDNS_FLAG_Z (1 << 6) // Reserved (zero)
|
||||
|
||||
#define MDNS_FLAG_GET_RCODE(x) (x & 0x0F)
|
||||
#define MDNS_FLAG_GET_OPCODE(x) ((x >> 11) & 0x0F)
|
||||
|
||||
// gets the PTR target name, either from "name" member or "entry" member
|
||||
#define MDNS_RR_GET_PTR_NAME(rr) (rr->data.PTR.name != NULL ? rr->data.PTR.name : rr->data.PTR.entry->name)
|
||||
|
||||
struct mdns_pkt {
|
||||
uint16_t id; // transaction ID
|
||||
uint16_t flags;
|
||||
uint16_t num_qn;
|
||||
uint16_t num_ans_rr;
|
||||
uint16_t num_auth_rr;
|
||||
uint16_t num_add_rr;
|
||||
|
||||
struct rr_list *rr_qn; // questions
|
||||
struct rr_list *rr_ans; // answer RRs
|
||||
struct rr_list *rr_auth; // authority RRs
|
||||
struct rr_list *rr_add; // additional RRs
|
||||
};
|
||||
|
||||
struct mdns_pkt *mdns_parse_pkt(uint8_t *pkt_buf, size_t pkt_len);
|
||||
|
||||
void mdns_init_reply(struct mdns_pkt *pkt, uint16_t id);
|
||||
size_t mdns_encode_pkt(struct mdns_pkt *answer, uint8_t *pkt_buf, size_t pkt_len);
|
||||
|
||||
void mdns_pkt_destroy(struct mdns_pkt *p);
|
||||
void rr_group_destroy(struct rr_group *group);
|
||||
struct rr_group *rr_group_find(struct rr_group *g, uint8_t *name);
|
||||
struct rr_entry *rr_entry_find(struct rr_list *rr_list, uint8_t *name, uint16_t type);
|
||||
struct rr_entry *rr_entry_match(struct rr_list *rr_list, struct rr_entry *entry);
|
||||
void rr_group_add(struct rr_group **group, struct rr_entry *rr);
|
||||
|
||||
int rr_list_count(struct rr_list *rr);
|
||||
int rr_list_append(struct rr_list **rr_head, struct rr_entry *rr);
|
||||
struct rr_entry *rr_list_remove(struct rr_list **rr_head, struct rr_entry *rr);
|
||||
void rr_list_destroy(struct rr_list *rr, char destroy_items);
|
||||
|
||||
struct rr_entry *rr_create_ptr(uint8_t *name, struct rr_entry *d_rr);
|
||||
struct rr_entry *rr_create_srv(uint8_t *name, uint16_t port, uint8_t *target);
|
||||
struct rr_entry *rr_create_aaaa(uint8_t *name, struct in6_addr *addr);
|
||||
struct rr_entry *rr_create_a(uint8_t *name, uint32_t addr);
|
||||
struct rr_entry *rr_create(uint8_t *name, enum rr_type type);
|
||||
void rr_set_nsec(struct rr_entry *rr_nsec, enum rr_type type);
|
||||
void rr_add_txt(struct rr_entry *rr_txt, const char *txt);
|
||||
|
||||
const char *rr_get_type_name(enum rr_type type);
|
||||
|
||||
uint8_t *create_label(const char *txt);
|
||||
uint8_t *create_nlabel(const char *name);
|
||||
char *nlabel_to_str(const uint8_t *name);
|
||||
uint8_t *dup_label(const uint8_t *label);
|
||||
uint8_t *dup_nlabel(const uint8_t *n);
|
||||
uint8_t *join_nlabel(const uint8_t *n1, const uint8_t *n2);
|
||||
|
||||
// compares 2 names
|
||||
static inline int cmp_nlabel(const uint8_t *L1, const uint8_t *L2) {
|
||||
return strcmp((char *) L1, (char *) L2);
|
||||
}
|
||||
|
||||
#endif /*!__MDNS_H__*/
|
614
3rdparty/tinysvcmdns/mdnsd.c
vendored
Normal file
614
3rdparty/tinysvcmdns/mdnsd.c
vendored
Normal file
@ -0,0 +1,614 @@
|
||||
/*
|
||||
* tinysvcmdns - a tiny MDNS implementation for publishing services
|
||||
* Copyright (C) 2011 Darell Tan
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. The name of the author may not be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#define LOG_ERR 3
|
||||
#define pipe(fds) _pipe(fds, 4096, _O_BINARY)
|
||||
#else
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <net/if.h>
|
||||
#include <syslog.h>
|
||||
#endif
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <pthread.h>
|
||||
|
||||
/*
|
||||
* Define a proper IP socket level if not already done.
|
||||
* Required to compile on OS X
|
||||
*/
|
||||
#ifndef SOL_IP
|
||||
#define SOL_IP IPPROTO_IP
|
||||
#endif
|
||||
|
||||
#include "mdns.h"
|
||||
#include "mdnsd.h"
|
||||
|
||||
#define MDNS_ADDR "224.0.0.251"
|
||||
#define MDNS_PORT 5353
|
||||
|
||||
#define PACKET_SIZE 65536
|
||||
|
||||
#define SERVICES_DNS_SD_NLABEL \
|
||||
((uint8_t *) "\x09_services\x07_dns-sd\x04_udp\x05local")
|
||||
|
||||
struct mdnsd {
|
||||
pthread_mutex_t data_lock;
|
||||
int sockfd;
|
||||
int notify_pipe[2];
|
||||
int stop_flag;
|
||||
|
||||
struct rr_group *group;
|
||||
struct rr_list *announce;
|
||||
struct rr_list *services;
|
||||
uint8_t *hostname;
|
||||
};
|
||||
|
||||
struct mdns_service {
|
||||
struct rr_list *entries;
|
||||
};
|
||||
|
||||
/////////////////////////////////
|
||||
|
||||
|
||||
static void log_message(int loglevel, char *fmt_str, ...) {
|
||||
va_list ap;
|
||||
char buf[2048];
|
||||
|
||||
va_start(ap, fmt_str);
|
||||
vsnprintf(buf, 2047, fmt_str, ap);
|
||||
va_end(ap);
|
||||
buf[2047] = 0;
|
||||
|
||||
fprintf(stderr, "%s\n", buf);
|
||||
}
|
||||
|
||||
static int create_recv_sock() {
|
||||
int sd = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (sd < 0) {
|
||||
log_message(LOG_ERR, "recv socket(): %m");
|
||||
return sd;
|
||||
}
|
||||
|
||||
int r = -1;
|
||||
|
||||
int on = 1;
|
||||
if ((r = setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on))) < 0) {
|
||||
log_message(LOG_ERR, "recv setsockopt(SO_REUSEADDR): %m");
|
||||
return r;
|
||||
}
|
||||
|
||||
/* bind to an address */
|
||||
struct sockaddr_in serveraddr;
|
||||
memset(&serveraddr, 0, sizeof(serveraddr));
|
||||
serveraddr.sin_family = AF_INET;
|
||||
serveraddr.sin_port = htons(MDNS_PORT);
|
||||
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); /* receive multicast */
|
||||
if ((r = bind(sd, (struct sockaddr *)&serveraddr, sizeof(serveraddr))) < 0) {
|
||||
log_message(LOG_ERR, "recv bind(): %m");
|
||||
}
|
||||
|
||||
// add membership to receiving socket
|
||||
struct ip_mreq mreq;
|
||||
memset(&mreq, 0, sizeof(struct ip_mreq));
|
||||
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
|
||||
mreq.imr_multiaddr.s_addr = inet_addr(MDNS_ADDR);
|
||||
if ((r = setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *) &mreq, sizeof(mreq))) < 0) {
|
||||
log_message(LOG_ERR, "recv setsockopt(IP_ADD_MEMBERSHIP): %m");
|
||||
return r;
|
||||
}
|
||||
|
||||
// enable loopback in case someone else needs the data
|
||||
if ((r = setsockopt(sd, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &on, sizeof(on))) < 0) {
|
||||
log_message(LOG_ERR, "recv setsockopt(IP_MULTICAST_LOOP): %m");
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
#ifdef IP_PKTINFO
|
||||
if ((r = setsockopt(sd, SOL_IP, IP_PKTINFO, (char *) &on, sizeof(on))) < 0) {
|
||||
log_message(LOG_ERR, "recv setsockopt(IP_PKTINFO): %m");
|
||||
return r;
|
||||
}
|
||||
#endif
|
||||
|
||||
return sd;
|
||||
}
|
||||
|
||||
static ssize_t send_packet(int fd, const void *data, size_t len) {
|
||||
static struct sockaddr_in toaddr;
|
||||
if (toaddr.sin_family != AF_INET) {
|
||||
memset(&toaddr, 0, sizeof(struct sockaddr_in));
|
||||
toaddr.sin_family = AF_INET;
|
||||
toaddr.sin_port = htons(MDNS_PORT);
|
||||
toaddr.sin_addr.s_addr = inet_addr(MDNS_ADDR);
|
||||
}
|
||||
|
||||
return sendto(fd, data, len, 0, (struct sockaddr *) &toaddr, sizeof(struct sockaddr_in));
|
||||
}
|
||||
|
||||
|
||||
// populate the specified list which matches the RR name and type
|
||||
// type can be RR_ANY, which populates all entries EXCEPT RR_NSEC
|
||||
static int populate_answers(struct mdnsd *svr, struct rr_list **rr_head, uint8_t *name, enum rr_type type) {
|
||||
int num_ans = 0;
|
||||
|
||||
// check if we have the records
|
||||
pthread_mutex_lock(&svr->data_lock);
|
||||
struct rr_group *ans_grp = rr_group_find(svr->group, name);
|
||||
if (ans_grp == NULL) {
|
||||
pthread_mutex_unlock(&svr->data_lock);
|
||||
return num_ans;
|
||||
}
|
||||
|
||||
// decide which records should go into answers
|
||||
struct rr_list *n = ans_grp->rr;
|
||||
for (; n; n = n->next) {
|
||||
// exclude NSEC for RR_ANY
|
||||
if (type == RR_ANY && n->e->type == RR_NSEC)
|
||||
continue;
|
||||
|
||||
if ((type == n->e->type || type == RR_ANY) && cmp_nlabel(name, n->e->name) == 0) {
|
||||
num_ans += rr_list_append(rr_head, n->e);
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&svr->data_lock);
|
||||
|
||||
return num_ans;
|
||||
}
|
||||
|
||||
// given a list of RRs, look up related records and add them
|
||||
static void add_related_rr(struct mdnsd *svr, struct rr_list *list, struct mdns_pkt *reply) {
|
||||
for (; list; list = list->next) {
|
||||
struct rr_entry *ans = list->e;
|
||||
|
||||
switch (ans->type) {
|
||||
case RR_PTR:
|
||||
// target host A, AAAA records
|
||||
reply->num_add_rr += populate_answers(svr, &reply->rr_add,
|
||||
MDNS_RR_GET_PTR_NAME(ans), RR_ANY);
|
||||
break;
|
||||
|
||||
case RR_SRV:
|
||||
// target host A, AAAA records
|
||||
reply->num_add_rr += populate_answers(svr, &reply->rr_add,
|
||||
ans->data.SRV.target, RR_ANY);
|
||||
|
||||
// perhaps TXT records of the same name?
|
||||
// if we use RR_ANY, we risk pulling in the same RR_SRV
|
||||
reply->num_add_rr += populate_answers(svr, &reply->rr_add,
|
||||
ans->name, RR_TXT);
|
||||
break;
|
||||
|
||||
case RR_A:
|
||||
case RR_AAAA:
|
||||
reply->num_add_rr += populate_answers(svr, &reply->rr_add,
|
||||
ans->name, RR_NSEC);
|
||||
break;
|
||||
|
||||
default:
|
||||
// nothing to add
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// creates an announce packet given the type name PTR
|
||||
static void announce_srv(struct mdnsd *svr, struct mdns_pkt *reply, uint8_t *name) {
|
||||
mdns_init_reply(reply, 0);
|
||||
|
||||
reply->num_ans_rr += populate_answers(svr, &reply->rr_ans, name, RR_PTR);
|
||||
|
||||
// remember to add the services dns-sd PTR too
|
||||
reply->num_ans_rr += populate_answers(svr, &reply->rr_ans,
|
||||
SERVICES_DNS_SD_NLABEL, RR_PTR);
|
||||
|
||||
// see if we can match additional records for answers
|
||||
add_related_rr(svr, reply->rr_ans, reply);
|
||||
|
||||
// additional records for additional records
|
||||
add_related_rr(svr, reply->rr_add, reply);
|
||||
}
|
||||
|
||||
// processes the incoming MDNS packet
|
||||
// returns >0 if processed, 0 otherwise
|
||||
static int process_mdns_pkt(struct mdnsd *svr, struct mdns_pkt *pkt, struct mdns_pkt *reply) {
|
||||
int i;
|
||||
|
||||
assert(pkt != NULL);
|
||||
|
||||
// is it standard query?
|
||||
if ((pkt->flags & MDNS_FLAG_RESP) == 0 &&
|
||||
MDNS_FLAG_GET_OPCODE(pkt->flags) == 0) {
|
||||
mdns_init_reply(reply, pkt->id);
|
||||
|
||||
DEBUG_PRINTF("flags = %04x, qn = %d, ans = %d, add = %d\n",
|
||||
pkt->flags,
|
||||
pkt->num_qn,
|
||||
pkt->num_ans_rr,
|
||||
pkt->num_add_rr);
|
||||
|
||||
// loop through questions
|
||||
struct rr_list *qnl = pkt->rr_qn;
|
||||
for (i = 0; i < pkt->num_qn; i++, qnl = qnl->next) {
|
||||
struct rr_entry *qn = qnl->e;
|
||||
int num_ans_added = 0;
|
||||
|
||||
char *namestr = nlabel_to_str(qn->name);
|
||||
DEBUG_PRINTF("qn #%d: type %s (%02x) %s - ", i, rr_get_type_name(qn->type), qn->type, namestr);
|
||||
free(namestr);
|
||||
|
||||
// check if it's a unicast query - we ignore those
|
||||
if (qn->unicast_query) {
|
||||
DEBUG_PRINTF("skipping unicast query\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
num_ans_added = populate_answers(svr, &reply->rr_ans, qn->name, qn->type);
|
||||
reply->num_ans_rr += num_ans_added;
|
||||
|
||||
DEBUG_PRINTF("added %d answers\n", num_ans_added);
|
||||
}
|
||||
|
||||
// remove our replies if they were already in their answers
|
||||
struct rr_list *ans = NULL, *prev_ans = NULL;
|
||||
for (ans = reply->rr_ans; ans; ) {
|
||||
struct rr_list *next_ans = ans->next;
|
||||
struct rr_entry *known_ans = rr_entry_match(pkt->rr_ans, ans->e);
|
||||
|
||||
// discard answers that have at least half of the actual TTL
|
||||
if (known_ans != NULL && known_ans->ttl >= ans->e->ttl / 2) {
|
||||
char *namestr = nlabel_to_str(ans->e->name);
|
||||
DEBUG_PRINTF("removing answer for %s\n", namestr);
|
||||
free(namestr);
|
||||
|
||||
// check if list item is head
|
||||
if (prev_ans == NULL)
|
||||
reply->rr_ans = ans->next;
|
||||
else
|
||||
prev_ans->next = ans->next;
|
||||
free(ans);
|
||||
|
||||
ans = prev_ans;
|
||||
|
||||
// adjust answer count
|
||||
reply->num_ans_rr--;
|
||||
}
|
||||
|
||||
prev_ans = ans;
|
||||
ans = next_ans;
|
||||
}
|
||||
|
||||
|
||||
// see if we can match additional records for answers
|
||||
add_related_rr(svr, reply->rr_ans, reply);
|
||||
|
||||
// additional records for additional records
|
||||
add_related_rr(svr, reply->rr_add, reply);
|
||||
|
||||
DEBUG_PRINTF("\n");
|
||||
|
||||
return reply->num_ans_rr;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// main loop to receive, process and send out MDNS replies
|
||||
// also handles MDNS service announces
|
||||
static void main_loop(struct mdnsd *svr) {
|
||||
fd_set sockfd_set;
|
||||
int max_fd = svr->sockfd;
|
||||
char notify_buf[2]; // buffer for reading of notify_pipe
|
||||
|
||||
void *pkt_buffer = malloc(PACKET_SIZE);
|
||||
|
||||
if (svr->notify_pipe[0] > max_fd)
|
||||
max_fd = svr->notify_pipe[0];
|
||||
|
||||
struct mdns_pkt *mdns_reply = malloc(sizeof(struct mdns_pkt));
|
||||
memset(mdns_reply, 0, sizeof(struct mdns_pkt));
|
||||
|
||||
while (! svr->stop_flag) {
|
||||
FD_ZERO(&sockfd_set);
|
||||
FD_SET(svr->sockfd, &sockfd_set);
|
||||
FD_SET(svr->notify_pipe[0], &sockfd_set);
|
||||
select(max_fd + 1, &sockfd_set, NULL, NULL, NULL);
|
||||
|
||||
if (FD_ISSET(svr->notify_pipe[0], &sockfd_set)) {
|
||||
// flush the notify_pipe
|
||||
read(svr->notify_pipe[0], ¬ify_buf, 1);
|
||||
} else if (FD_ISSET(svr->sockfd, &sockfd_set)) {
|
||||
struct sockaddr_in fromaddr;
|
||||
socklen_t sockaddr_size = sizeof(struct sockaddr_in);
|
||||
|
||||
ssize_t recvsize = recvfrom(svr->sockfd, pkt_buffer, PACKET_SIZE, 0,
|
||||
(struct sockaddr *) &fromaddr, &sockaddr_size);
|
||||
if (recvsize < 0) {
|
||||
log_message(LOG_ERR, "recv(): %m");
|
||||
}
|
||||
|
||||
DEBUG_PRINTF("data from=%s size=%ld\n", inet_ntoa(fromaddr.sin_addr), (long) recvsize);
|
||||
struct mdns_pkt *mdns = mdns_parse_pkt(pkt_buffer, recvsize);
|
||||
if (mdns != NULL) {
|
||||
if (process_mdns_pkt(svr, mdns, mdns_reply)) {
|
||||
size_t replylen = mdns_encode_pkt(mdns_reply, pkt_buffer, PACKET_SIZE);
|
||||
send_packet(svr->sockfd, pkt_buffer, replylen);
|
||||
} else if (mdns->num_qn == 0) {
|
||||
DEBUG_PRINTF("(no questions in packet)\n\n");
|
||||
}
|
||||
|
||||
mdns_pkt_destroy(mdns);
|
||||
}
|
||||
}
|
||||
|
||||
// send out announces
|
||||
while (1) {
|
||||
struct rr_entry *ann_e = NULL;
|
||||
|
||||
// extract from head of list
|
||||
pthread_mutex_lock(&svr->data_lock);
|
||||
if (svr->announce)
|
||||
ann_e = rr_list_remove(&svr->announce, svr->announce->e);
|
||||
pthread_mutex_unlock(&svr->data_lock);
|
||||
|
||||
if (! ann_e)
|
||||
break;
|
||||
|
||||
char *namestr = nlabel_to_str(ann_e->name);
|
||||
DEBUG_PRINTF("sending announce for %s\n", namestr);
|
||||
free(namestr);
|
||||
|
||||
announce_srv(svr, mdns_reply, ann_e->name);
|
||||
|
||||
if (mdns_reply->num_ans_rr > 0) {
|
||||
size_t replylen = mdns_encode_pkt(mdns_reply, pkt_buffer, PACKET_SIZE);
|
||||
send_packet(svr->sockfd, pkt_buffer, replylen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// main thread terminating. send out "goodbye packets" for services
|
||||
mdns_init_reply(mdns_reply, 0);
|
||||
|
||||
pthread_mutex_lock(&svr->data_lock);
|
||||
struct rr_list *svc_le = svr->services;
|
||||
for (; svc_le; svc_le = svc_le->next) {
|
||||
// set TTL to zero
|
||||
svc_le->e->ttl = 0;
|
||||
mdns_reply->num_ans_rr += rr_list_append(&mdns_reply->rr_ans, svc_le->e);
|
||||
}
|
||||
pthread_mutex_unlock(&svr->data_lock);
|
||||
|
||||
// send out packet
|
||||
if (mdns_reply->num_ans_rr > 0) {
|
||||
size_t replylen = mdns_encode_pkt(mdns_reply, pkt_buffer, PACKET_SIZE);
|
||||
send_packet(svr->sockfd, pkt_buffer, replylen);
|
||||
}
|
||||
|
||||
// destroy packet
|
||||
mdns_init_reply(mdns_reply, 0);
|
||||
free(mdns_reply);
|
||||
|
||||
free(pkt_buffer);
|
||||
|
||||
#ifdef _WIN32
|
||||
closesocket(svr->sockfd);
|
||||
#else
|
||||
close(svr->sockfd);
|
||||
#endif
|
||||
|
||||
svr->stop_flag = 2;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
|
||||
void mdnsd_set_hostname(struct mdnsd *svr, const char *hostname, uint32_t ip) {
|
||||
struct rr_entry *a_e = NULL,
|
||||
*nsec_e = NULL;
|
||||
|
||||
// currently can't be called twice
|
||||
// dont ask me what happens if the IP changes
|
||||
assert(svr->hostname == NULL);
|
||||
|
||||
a_e = rr_create_a(create_nlabel(hostname), ip);
|
||||
|
||||
nsec_e = rr_create(create_nlabel(hostname), RR_NSEC);
|
||||
rr_set_nsec(nsec_e, RR_A);
|
||||
|
||||
pthread_mutex_lock(&svr->data_lock);
|
||||
svr->hostname = create_nlabel(hostname);
|
||||
rr_group_add(&svr->group, a_e);
|
||||
rr_group_add(&svr->group, nsec_e);
|
||||
pthread_mutex_unlock(&svr->data_lock);
|
||||
}
|
||||
|
||||
void mdnsd_add_rr(struct mdnsd *svr, struct rr_entry *rr) {
|
||||
pthread_mutex_lock(&svr->data_lock);
|
||||
rr_group_add(&svr->group, rr);
|
||||
pthread_mutex_unlock(&svr->data_lock);
|
||||
}
|
||||
|
||||
struct mdns_service *mdnsd_register_svc(struct mdnsd *svr, const char *instance_name,
|
||||
const char *type, uint16_t port, const char *hostname, const char *txt[]) {
|
||||
struct rr_entry *txt_e = NULL,
|
||||
*srv_e = NULL,
|
||||
*ptr_e = NULL,
|
||||
*bptr_e = NULL;
|
||||
uint8_t *target;
|
||||
uint8_t *inst_nlabel, *type_nlabel, *nlabel;
|
||||
struct mdns_service *service = malloc(sizeof(struct mdns_service));
|
||||
memset(service, 0, sizeof(struct mdns_service));
|
||||
|
||||
// combine service name
|
||||
type_nlabel = create_nlabel(type);
|
||||
inst_nlabel = create_nlabel(instance_name);
|
||||
nlabel = join_nlabel(inst_nlabel, type_nlabel);
|
||||
|
||||
// create TXT record
|
||||
if (txt && *txt) {
|
||||
txt_e = rr_create(dup_nlabel(nlabel), RR_TXT);
|
||||
rr_list_append(&service->entries, txt_e);
|
||||
|
||||
// add TXTs
|
||||
for (; *txt; txt++)
|
||||
rr_add_txt(txt_e, *txt);
|
||||
}
|
||||
|
||||
// create SRV record
|
||||
assert(hostname || svr->hostname); // either one as target
|
||||
target = hostname ?
|
||||
create_nlabel(hostname) :
|
||||
dup_nlabel(svr->hostname);
|
||||
|
||||
srv_e = rr_create_srv(dup_nlabel(nlabel), port, target);
|
||||
rr_list_append(&service->entries, srv_e);
|
||||
|
||||
// create PTR record for type
|
||||
ptr_e = rr_create_ptr(type_nlabel, srv_e);
|
||||
|
||||
// create services PTR record for type
|
||||
// this enables the type to show up as a "service"
|
||||
bptr_e = rr_create_ptr(dup_nlabel(SERVICES_DNS_SD_NLABEL), ptr_e);
|
||||
|
||||
// modify lists here
|
||||
pthread_mutex_lock(&svr->data_lock);
|
||||
|
||||
if (txt_e)
|
||||
rr_group_add(&svr->group, txt_e);
|
||||
rr_group_add(&svr->group, srv_e);
|
||||
rr_group_add(&svr->group, ptr_e);
|
||||
rr_group_add(&svr->group, bptr_e);
|
||||
|
||||
// append PTR entry to announce list
|
||||
rr_list_append(&svr->announce, ptr_e);
|
||||
rr_list_append(&svr->services, ptr_e);
|
||||
|
||||
pthread_mutex_unlock(&svr->data_lock);
|
||||
|
||||
// don't free type_nlabel - it's with the PTR record
|
||||
free(nlabel);
|
||||
free(inst_nlabel);
|
||||
|
||||
// notify server
|
||||
write(svr->notify_pipe[1], ".", 1);
|
||||
|
||||
return service;
|
||||
}
|
||||
|
||||
void mdns_service_destroy(struct mdns_service *srv) {
|
||||
assert(srv != NULL);
|
||||
rr_list_destroy(srv->entries, 0);
|
||||
free(srv);
|
||||
}
|
||||
|
||||
struct mdnsd *mdnsd_start() {
|
||||
pthread_t tid;
|
||||
pthread_attr_t attr;
|
||||
|
||||
struct mdnsd *server = malloc(sizeof(struct mdnsd));
|
||||
memset(server, 0, sizeof(struct mdnsd));
|
||||
|
||||
if (pipe(server->notify_pipe) != 0) {
|
||||
log_message(LOG_ERR, "pipe(): %m\n");
|
||||
free(server);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
server->sockfd = create_recv_sock();
|
||||
if (server->sockfd < 0) {
|
||||
log_message(LOG_ERR, "unable to create recv socket");
|
||||
free(server);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pthread_mutex_init(&server->data_lock, NULL);
|
||||
|
||||
// init thread
|
||||
pthread_attr_init(&attr);
|
||||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
||||
|
||||
if (pthread_create(&tid, &attr, (void *(*)(void *)) main_loop, (void *) server) != 0) {
|
||||
pthread_mutex_destroy(&server->data_lock);
|
||||
free(server);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
void mdnsd_stop(struct mdnsd *s) {
|
||||
assert(s != NULL);
|
||||
|
||||
struct timeval tv = {
|
||||
.tv_sec = 0,
|
||||
.tv_usec = 500 * 1000,
|
||||
};
|
||||
|
||||
s->stop_flag = 1;
|
||||
write(s->notify_pipe[1], ".", 1);
|
||||
|
||||
while (s->stop_flag != 2)
|
||||
select(0, NULL, NULL, NULL, &tv);
|
||||
|
||||
#ifdef _WIN32
|
||||
closesocket(s->notify_pipe[0]);
|
||||
closesocket(s->notify_pipe[1]);
|
||||
#else
|
||||
close(s->notify_pipe[0]);
|
||||
close(s->notify_pipe[1]);
|
||||
#endif
|
||||
|
||||
pthread_mutex_destroy(&s->data_lock);
|
||||
rr_group_destroy(s->group);
|
||||
rr_list_destroy(s->announce, 0);
|
||||
rr_list_destroy(s->services, 0);
|
||||
|
||||
if (s->hostname)
|
||||
free(s->hostname);
|
||||
|
||||
free(s);
|
||||
}
|
||||
|
58
3rdparty/tinysvcmdns/mdnsd.h
vendored
Normal file
58
3rdparty/tinysvcmdns/mdnsd.h
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* tinysvcmdns - a tiny MDNS implementation for publishing services
|
||||
* Copyright (C) 2011 Darell Tan
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. The name of the author may not be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __MDNSD_H__
|
||||
#define __MDNSD_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct mdnsd;
|
||||
struct mdns_service;
|
||||
|
||||
// starts a MDNS responder instance
|
||||
// returns NULL if unsuccessful
|
||||
struct mdnsd *mdnsd_start();
|
||||
|
||||
// stops the given MDNS responder instance
|
||||
void mdnsd_stop(struct mdnsd *s);
|
||||
|
||||
// sets the hostname for the given MDNS responder instance
|
||||
void mdnsd_set_hostname(struct mdnsd *svr, const char *hostname, uint32_t ip);
|
||||
|
||||
// adds an additional RR
|
||||
void mdnsd_add_rr(struct mdnsd *svr, struct rr_entry *rr);
|
||||
|
||||
// registers a service with the MDNS responder instance
|
||||
struct mdns_service *mdnsd_register_svc(struct mdnsd *svr, const char *instance_name,
|
||||
const char *type, uint16_t port, const char *hostname, const char *txt[]);
|
||||
|
||||
// destroys the mdns_service struct returned by mdnsd_register_svc()
|
||||
void mdns_service_destroy(struct mdns_service *srv);
|
||||
|
||||
|
||||
#endif/*!__MDNSD_H__*/
|
97
3rdparty/tinysvcmdns/testmdnsd.c
vendored
Normal file
97
3rdparty/tinysvcmdns/testmdnsd.c
vendored
Normal file
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* tinysvcmdns - a tiny MDNS implementation for publishing services
|
||||
* Copyright (C) 2011 Darell Tan
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. The name of the author may not be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <in6addr.h>
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include "mdns.h"
|
||||
#include "mdnsd.h"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
// create host entries
|
||||
char *hostname = "some-random-host.local";
|
||||
|
||||
struct mdnsd *svr = mdnsd_start();
|
||||
if (svr == NULL) {
|
||||
printf("mdnsd_start() error\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("mdnsd_start OK. press ENTER to add hostname & service\n");
|
||||
getchar();
|
||||
|
||||
mdnsd_set_hostname(svr, hostname, inet_addr("192.168.0.29"));
|
||||
|
||||
struct rr_entry *a2_e = NULL;
|
||||
a2_e = rr_create_a(create_nlabel(hostname), inet_addr("192.168.0.31"));
|
||||
mdnsd_add_rr(svr, a2_e);
|
||||
|
||||
struct rr_entry *aaaa_e = NULL;
|
||||
|
||||
struct addrinfo hints;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_INET6;
|
||||
hints.ai_flags = AI_NUMERICHOST;
|
||||
struct addrinfo* results;
|
||||
getaddrinfo(
|
||||
"fe80::e2f8:47ff:fe20:28e0",
|
||||
NULL,
|
||||
&hints,
|
||||
&results);
|
||||
struct sockaddr_in6* addr = (struct sockaddr_in6*)results->ai_addr;
|
||||
struct in6_addr v6addr = addr->sin6_addr;
|
||||
freeaddrinfo(results);
|
||||
|
||||
aaaa_e = rr_create_aaaa(create_nlabel(hostname), &v6addr);
|
||||
|
||||
mdnsd_add_rr(svr, aaaa_e);
|
||||
|
||||
const char *txt[] = {
|
||||
"path=/mywebsite",
|
||||
NULL
|
||||
};
|
||||
struct mdns_service *svc = mdnsd_register_svc(svr, "My Website",
|
||||
"_http._tcp.local", 8080, NULL, txt);
|
||||
mdns_service_destroy(svc);
|
||||
|
||||
printf("added service and hostname. press ENTER to exit\n");
|
||||
getchar();
|
||||
|
||||
mdnsd_stop(svr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -416,6 +416,7 @@ add_subdirectory(3rdparty/qocoa)
|
||||
add_subdirectory(src)
|
||||
if (WIN32)
|
||||
add_subdirectory(3rdparty/qtwin)
|
||||
add_subdirectory(3rdparty/tinysvcmdns)
|
||||
endif (WIN32)
|
||||
add_subdirectory(tests)
|
||||
add_subdirectory(dist)
|
||||
@ -423,6 +424,7 @@ add_subdirectory(tools/ultimate_lyrics_parser)
|
||||
add_subdirectory(ext/libclementine-common)
|
||||
add_subdirectory(ext/libclementine-tagreader)
|
||||
add_subdirectory(ext/clementine-tagreader)
|
||||
add_subdirectory(ext/libclementine-remote)
|
||||
|
||||
option(WITH_DEBIAN OFF)
|
||||
if(WITH_DEBIAN)
|
||||
|
10
debian/copyright
vendored
10
debian/copyright
vendored
@ -116,7 +116,7 @@ License: SPMediaKeyTapLicense
|
||||
|
||||
Files: 3rdparty/sha2/*
|
||||
Copyright: 2000-2001, Aaron D. Gifford
|
||||
License: BSD
|
||||
License: BSD-3-clause
|
||||
|
||||
Files: 3rdparty/chromaprint/*
|
||||
Copyright: 2010, Lukas Lalinsky <lalinsky@gmail.com>
|
||||
@ -130,6 +130,10 @@ Files: 3rdparty/taglib/*
|
||||
Copyright: 2002-2008, Scott Wheeler
|
||||
License: LGPL-2.1
|
||||
|
||||
Files: 3rdparty/tinysvcmdns/*
|
||||
Copyright: 2011, Darell Tan
|
||||
License: BSD-3-clause
|
||||
|
||||
License: LGPL-2.1
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License, version 2.1, as
|
||||
@ -238,7 +242,7 @@ License: BSD-Nokia
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
License: BSD
|
||||
License: BSD-3-clause
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
@ -261,7 +265,7 @@ License: BSD
|
||||
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGE.
|
||||
SUCH DAMAGE.
|
||||
|
||||
License: Qt Commercial
|
||||
Licensees holding valid Qt Commercial licenses may use this file in
|
||||
|
16
ext/libclementine-remote/CMakeLists.txt
Normal file
16
ext/libclementine-remote/CMakeLists.txt
Normal file
@ -0,0 +1,16 @@
|
||||
include_directories(${PROTOBUF_INCLUDE_DIRS})
|
||||
|
||||
set(MESSAGES
|
||||
remotecontrolmessages.proto
|
||||
)
|
||||
|
||||
protobuf_generate_cpp(PROTO_SOURCES PROTO_HEADERS ${MESSAGES})
|
||||
|
||||
add_library(libclementine-remote STATIC
|
||||
${PROTO_SOURCES}
|
||||
)
|
||||
|
||||
target_link_libraries(libclementine-remote
|
||||
libclementine-common
|
||||
)
|
||||
|
157
ext/libclementine-remote/remotecontrolmessages.proto
Normal file
157
ext/libclementine-remote/remotecontrolmessages.proto
Normal file
@ -0,0 +1,157 @@
|
||||
package pb.remote;
|
||||
|
||||
// The supported message types
|
||||
enum MsgType {
|
||||
UNKNOWN = 0;
|
||||
// Messages generally send from client to server
|
||||
CONNECT = 1;
|
||||
DISCONNECT = 2;
|
||||
REQUEST_PLAYLISTS = 3;
|
||||
REQUEST_PLAYLIST_SONGS = 4;
|
||||
CHANGE_SONG = 5;
|
||||
SET_VOLUME = 6;
|
||||
|
||||
// Messages send by both
|
||||
PLAY = 20;
|
||||
PLAYPAUSE = 21;
|
||||
PAUSE = 22;
|
||||
STOP = 23;
|
||||
NEXT = 24;
|
||||
PREVIOUS = 25;
|
||||
SHUFFLE_PLAYLIST = 26;
|
||||
// Messages that contain the repeat or random mode
|
||||
// Either set by client or clementine
|
||||
REPEAT = 27;
|
||||
SHUFFLE = 28;
|
||||
|
||||
// Messages send from server to client
|
||||
INFO = 40;
|
||||
CURRENT_METAINFO = 41;
|
||||
PLAYLISTS = 42;
|
||||
PLAYLIST_SONGS = 43;
|
||||
ENGINE_STATE_CHANGED = 44;
|
||||
KEEP_ALIVE = 45;
|
||||
}
|
||||
|
||||
// Valid Engine states
|
||||
enum EngineState {
|
||||
Empty = 0;
|
||||
Idle = 1;
|
||||
Playing = 2;
|
||||
Paused = 3;
|
||||
}
|
||||
|
||||
// Song Metadata
|
||||
message SongMetadata {
|
||||
optional int32 id = 1; // unique id of the song
|
||||
optional int32 index = 2; // Index of the current row of the active playlist
|
||||
optional string title = 3;
|
||||
optional string album = 4;
|
||||
optional string artist = 5;
|
||||
optional string albumartist = 6;
|
||||
optional int32 track = 7;
|
||||
optional int32 disc = 8;
|
||||
optional string pretty_year = 9;
|
||||
optional string genre = 10;
|
||||
optional int32 playcount = 11;
|
||||
optional string pretty_length = 12;
|
||||
optional bytes art = 13;
|
||||
}
|
||||
|
||||
// Playlist informations
|
||||
message Playlist {
|
||||
optional int32 id = 1;
|
||||
optional string name = 2;
|
||||
optional int32 item_count = 3;
|
||||
optional bool active = 4;
|
||||
}
|
||||
|
||||
// Valid Repeatmodes
|
||||
enum RepeatMode {
|
||||
Repeat_Off = 0;
|
||||
Repeat_Track = 1;
|
||||
Repeat_Album = 2;
|
||||
Repeat_Playlist = 3;
|
||||
}
|
||||
|
||||
// Valid Shuffle modes
|
||||
enum ShuffleMode {
|
||||
Shuffle_Off = 0;
|
||||
Shuffle_All = 1;
|
||||
Shuffle_InsideAlbum = 2;
|
||||
Shuffle_Albums = 3;
|
||||
}
|
||||
|
||||
// A Client requests songs from a specific playlist
|
||||
message RequestPlaylistSongs {
|
||||
optional int32 id = 1;
|
||||
}
|
||||
|
||||
// Client want to change track
|
||||
message RequestChangeSong {
|
||||
// In which playlist is the song?
|
||||
optional int32 playlist_id = 1;
|
||||
// And on which position?
|
||||
optional int32 song_index = 2;
|
||||
}
|
||||
|
||||
// Set the volume
|
||||
message RequestSetVolume {
|
||||
optional int32 volume = 1;
|
||||
}
|
||||
|
||||
// Repeat and Random messages
|
||||
message Repeat {
|
||||
optional RepeatMode repeat_mode = 1;
|
||||
}
|
||||
|
||||
message Shuffle {
|
||||
optional ShuffleMode shuffle_mode = 1;
|
||||
}
|
||||
|
||||
// Response from server
|
||||
// General info
|
||||
message ResponseClementineInfo {
|
||||
optional string version = 1;
|
||||
optional EngineState state = 2;
|
||||
}
|
||||
|
||||
// The current song played
|
||||
message ResponseCurrentMetadata {
|
||||
optional SongMetadata song_metadata = 1;
|
||||
}
|
||||
|
||||
// The playlists in clementine
|
||||
message ResponsePlaylists {
|
||||
repeated Playlist playlist = 1;
|
||||
}
|
||||
|
||||
// A list of songs in a playlist
|
||||
message ResponsePlaylistSongs {
|
||||
optional Playlist requested_playlist = 1;
|
||||
|
||||
// The songs that are in the playlist
|
||||
repeated SongMetadata songs = 2;
|
||||
}
|
||||
|
||||
// The current state of the play engine
|
||||
message ResponseEngineStateChanged {
|
||||
optional EngineState state = 1;
|
||||
}
|
||||
|
||||
// The message itself
|
||||
message Message {
|
||||
optional int32 version = 1 [default=1];
|
||||
optional MsgType type = 2 [default=UNKNOWN]; // What data is in the message?
|
||||
|
||||
optional RequestPlaylistSongs request_playlist_songs = 10;
|
||||
optional RequestChangeSong request_change_song = 11;
|
||||
optional RequestSetVolume request_set_volume = 12;
|
||||
optional Repeat repeat = 13;
|
||||
optional Shuffle shuffle = 14;
|
||||
optional ResponseClementineInfo response_clementine_info = 15;
|
||||
optional ResponseCurrentMetadata response_current_metadata = 16;
|
||||
optional ResponsePlaylists response_playlists = 17;
|
||||
optional ResponsePlaylistSongs response_playlist_songs = 18;
|
||||
optional ResponseEngineStateChanged response_engine_state_changed = 19;
|
||||
}
|
@ -62,6 +62,8 @@ endif(HAVE_BREAKPAD)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/ext/libclementine-common)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/ext/libclementine-tagreader)
|
||||
include_directories(${CMAKE_BINARY_DIR}/ext/libclementine-tagreader)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/ext/libclementine-remote)
|
||||
include_directories(${CMAKE_BINARY_DIR}/ext/libclementine-remote)
|
||||
|
||||
cmake_policy(SET CMP0011 NEW)
|
||||
include(../cmake/ParseArguments.cmake)
|
||||
@ -212,6 +214,13 @@ set(SOURCES
|
||||
musicbrainz/musicbrainzclient.cpp
|
||||
musicbrainz/tagfetcher.cpp
|
||||
|
||||
networkremote/incomingdataparser.cpp
|
||||
networkremote/networkremote.cpp
|
||||
networkremote/networkremotehelper.cpp
|
||||
networkremote/outgoingdatacreator.cpp
|
||||
networkremote/remoteclient.cpp
|
||||
networkremote/zeroconf.cpp
|
||||
|
||||
playlist/dynamicplaylistcontrols.cpp
|
||||
playlist/playlist.cpp
|
||||
playlist/playlistbackend.cpp
|
||||
@ -323,6 +332,7 @@ set(SOURCES
|
||||
ui/iconloader.cpp
|
||||
ui/mainwindow.cpp
|
||||
ui/networkproxysettingspage.cpp
|
||||
ui/networkremotesettingspage.cpp
|
||||
ui/notificationssettingspage.cpp
|
||||
ui/organisedialog.cpp
|
||||
ui/organiseerrordialog.cpp
|
||||
@ -483,6 +493,12 @@ set(HEADERS
|
||||
musicbrainz/acoustidclient.h
|
||||
musicbrainz/musicbrainzclient.h
|
||||
musicbrainz/tagfetcher.h
|
||||
|
||||
networkremote/networkremotehelper.h
|
||||
networkremote/networkremote.h
|
||||
networkremote/incomingdataparser.h
|
||||
networkremote/outgoingdatacreator.h
|
||||
networkremote/remoteclient.h
|
||||
|
||||
playlist/dynamicplaylistcontrols.h
|
||||
playlist/playlist.h
|
||||
@ -580,6 +596,7 @@ set(HEADERS
|
||||
ui/globalshortcutssettingspage.h
|
||||
ui/mainwindow.h
|
||||
ui/networkproxysettingspage.h
|
||||
ui/networkremotesettingspage.h
|
||||
ui/notificationssettingspage.h
|
||||
ui/organisedialog.h
|
||||
ui/organiseerrordialog.h
|
||||
@ -697,6 +714,7 @@ set(UI
|
||||
ui/globalshortcutssettingspage.ui
|
||||
ui/mainwindow.ui
|
||||
ui/networkproxysettingspage.ui
|
||||
ui/networkremotesettingspage.ui
|
||||
ui/notificationssettingspage.ui
|
||||
ui/organisedialog.ui
|
||||
ui/organiseerrordialog.ui
|
||||
@ -825,6 +843,7 @@ optional_source(APPLE
|
||||
core/mac_startup.mm
|
||||
core/scoped_nsautorelease_pool.mm
|
||||
devices/macdevicelister.mm
|
||||
networkremote/bonjour.mm
|
||||
ui/globalshortcutgrabber.mm
|
||||
ui/macscreensaver.cpp
|
||||
ui/macsystemtrayicon.mm
|
||||
@ -837,7 +856,13 @@ optional_source(APPLE
|
||||
)
|
||||
|
||||
# Platform specific - Windows
|
||||
optional_source(WIN32 SOURCES widgets/osd_win.cpp)
|
||||
optional_source(WIN32
|
||||
SOURCES
|
||||
networkremote/tinysvcmdns.cpp
|
||||
widgets/osd_win.cpp
|
||||
INCLUDE_DIRECTORIES
|
||||
${CMAKE_SOURCE_DIR}/3rdparty/tinysvcmdns
|
||||
)
|
||||
|
||||
# Platform specific - X11
|
||||
optional_source(LINUX SOURCES widgets/osd_x11.cpp)
|
||||
@ -888,6 +913,34 @@ if(HAVE_DBUS)
|
||||
dbus/org.gnome.SettingsDaemon.MediaKeys.xml
|
||||
dbus/gnomesettingsdaemon)
|
||||
|
||||
# org.freedesktop.Avahi.Server interface
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver.cpp
|
||||
${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver.h
|
||||
COMMAND ${QT_DBUSXML2CPP_EXECUTABLE}
|
||||
dbus/org.freedesktop.Avahi.Server.xml
|
||||
-p ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver
|
||||
-i dbus/metatypes.h
|
||||
DEPENDS dbus/org.freedesktop.Avahi.Server.xml
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
list(APPEND HEADERS ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver.h)
|
||||
list(APPEND SOURCES ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver.cpp)
|
||||
|
||||
# org.freedesktop.Avahi.EntryGroup interface
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup.cpp
|
||||
${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup.h
|
||||
COMMAND ${QT_DBUSXML2CPP_EXECUTABLE}
|
||||
dbus/org.freedesktop.Avahi.EntryGroup.xml
|
||||
-p ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup
|
||||
-i dbus/metatypes.h
|
||||
DEPENDS dbus/org.freedesktop.Avahi.EntryGroup.xml
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
list(APPEND HEADERS ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup.h)
|
||||
list(APPEND SOURCES ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup.cpp)
|
||||
|
||||
# DeviceKit DBUS interfaces
|
||||
if(HAVE_DEVICEKIT)
|
||||
qt4_add_dbus_interface(SOURCES
|
||||
@ -911,6 +964,7 @@ optional_source(HAVE_DBUS
|
||||
core/mpris.cpp
|
||||
core/mpris1.cpp
|
||||
core/mpris2.cpp
|
||||
networkremote/avahi.cpp
|
||||
ui/dbusscreensaver.cpp
|
||||
HEADERS
|
||||
core/mpris.h
|
||||
@ -1125,6 +1179,7 @@ add_dependencies(clementine_lib pot)
|
||||
target_link_libraries(clementine_lib
|
||||
libclementine-common
|
||||
libclementine-tagreader
|
||||
libclementine-remote
|
||||
${SHA2_LIBRARIES}
|
||||
${TAGLIB_LIBRARIES}
|
||||
${MYGPOQT_LIBRARIES}
|
||||
@ -1243,6 +1298,7 @@ if (WIN32)
|
||||
${ZLIB_LIBRARIES}
|
||||
${MSWMDM_LIBRARIES}
|
||||
${QTSPARKLE_LIBRARIES}
|
||||
tinysvcmdns
|
||||
qtwin
|
||||
)
|
||||
endif (WIN32)
|
||||
|
@ -30,6 +30,8 @@
|
||||
#include "globalsearch/globalsearch.h"
|
||||
#include "library/library.h"
|
||||
#include "library/librarybackend.h"
|
||||
#include "networkremote/networkremote.h"
|
||||
#include "networkremote/networkremotehelper.h"
|
||||
#include "playlist/playlistbackend.h"
|
||||
#include "playlist/playlistmanager.h"
|
||||
#include "podcasts/gpoddersync.h"
|
||||
@ -63,7 +65,9 @@ Application::Application(QObject* parent)
|
||||
podcast_downloader_(NULL),
|
||||
gpodder_sync_(NULL),
|
||||
moodbar_loader_(NULL),
|
||||
moodbar_controller_(NULL)
|
||||
moodbar_controller_(NULL),
|
||||
network_remote_(NULL),
|
||||
network_remote_helper_(NULL)
|
||||
{
|
||||
tag_reader_client_ = new TagReaderClient(this);
|
||||
MoveToNewThread(tag_reader_client_);
|
||||
@ -100,6 +104,13 @@ Application::Application(QObject* parent)
|
||||
moodbar_controller_ = new MoodbarController(this, this);
|
||||
#endif
|
||||
|
||||
// Network Remote
|
||||
network_remote_ = new NetworkRemote(this);
|
||||
MoveToNewThread(network_remote_);
|
||||
|
||||
network_remote_helper_ = new NetworkRemoteHelper(this);
|
||||
network_remote_helper_->StartServer();
|
||||
|
||||
library_->Init();
|
||||
|
||||
DoInAMinuteOrSo(database_, SLOT(DoBackup()));
|
||||
|
@ -36,6 +36,8 @@ class LibraryBackend;
|
||||
class LibraryModel;
|
||||
class MoodbarController;
|
||||
class MoodbarLoader;
|
||||
class NetworkRemote;
|
||||
class NetworkRemoteHelper;
|
||||
class Player;
|
||||
class PlaylistBackend;
|
||||
class PodcastDownloader;
|
||||
@ -73,6 +75,8 @@ public:
|
||||
GPodderSync* gpodder_sync() const { return gpodder_sync_; }
|
||||
MoodbarLoader* moodbar_loader() const { return moodbar_loader_; }
|
||||
MoodbarController* moodbar_controller() const { return moodbar_controller_; }
|
||||
NetworkRemote* network_remote() const { return network_remote_; }
|
||||
NetworkRemoteHelper* network_remote_helper() const { return network_remote_helper_; }
|
||||
|
||||
LibraryBackend* library_backend() const;
|
||||
LibraryModel* library_model() const;
|
||||
@ -111,6 +115,8 @@ private:
|
||||
GPodderSync* gpodder_sync_;
|
||||
MoodbarLoader* moodbar_loader_;
|
||||
MoodbarController* moodbar_controller_;
|
||||
NetworkRemote* network_remote_;
|
||||
NetworkRemoteHelper* network_remote_helper_;
|
||||
|
||||
QList<QObject*> objects_in_threads_;
|
||||
QList<QThread*> threads_;
|
||||
|
110
src/networkremote/avahi.cpp
Normal file
110
src/networkremote/avahi.cpp
Normal file
@ -0,0 +1,110 @@
|
||||
#include "avahi.h"
|
||||
|
||||
#include <QDBusConnection>
|
||||
|
||||
#include "core/closure.h"
|
||||
#include "core/logging.h"
|
||||
#include "dbus/avahientrygroup.h"
|
||||
#include "dbus/avahiserver.h"
|
||||
|
||||
namespace {
|
||||
|
||||
void AddService(
|
||||
const QString domain,
|
||||
const QString type,
|
||||
const QString name,
|
||||
quint16 port,
|
||||
QDBusPendingReply<QDBusObjectPath> path_reply);
|
||||
void Commit(OrgFreedesktopAvahiEntryGroupInterface* interface);
|
||||
void LogCommit(QDBusPendingReply<> reply);
|
||||
|
||||
} // namespace
|
||||
|
||||
void Avahi::Publish(
|
||||
const QString& domain,
|
||||
const QString& type,
|
||||
const QString& name,
|
||||
quint16 port) {
|
||||
OrgFreedesktopAvahiServerInterface server_interface(
|
||||
"org.freedesktop.Avahi",
|
||||
"/",
|
||||
QDBusConnection::systemBus());
|
||||
QDBusPendingReply<QDBusObjectPath> reply = server_interface.EntryGroupNew();
|
||||
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply);
|
||||
NewClosure(
|
||||
watcher,
|
||||
SIGNAL(finished(QDBusPendingCallWatcher*)),
|
||||
&AddService,
|
||||
domain, type, name, port, reply);
|
||||
QObject::connect(
|
||||
watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
|
||||
watcher, SLOT(deleteLater()));
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
void AddService(
|
||||
const QString domain,
|
||||
const QString type,
|
||||
const QString name,
|
||||
quint16 port,
|
||||
QDBusPendingReply<QDBusObjectPath> path_reply) {
|
||||
if (path_reply.isError()) {
|
||||
qLog(Warning)
|
||||
<< "Failed to create Avahi entry group:"
|
||||
<< path_reply.error();
|
||||
qLog(Info)
|
||||
<< "This might be because 'disable-user-service-publishing'"
|
||||
<< "is set to 'yes' in avahi-daemon.conf";
|
||||
return;
|
||||
}
|
||||
qLog(Debug) << path_reply.error();
|
||||
OrgFreedesktopAvahiEntryGroupInterface* entry_group_interface =
|
||||
new OrgFreedesktopAvahiEntryGroupInterface(
|
||||
"org.freedesktop.Avahi",
|
||||
path_reply.value().path(),
|
||||
QDBusConnection::systemBus());
|
||||
QDBusPendingReply<> reply = entry_group_interface->AddService(
|
||||
-1, // Interface (all)
|
||||
-1, // Protocol (v4 & v6)
|
||||
0, // Flags
|
||||
name, // Service name, eg. Clementine
|
||||
type, // Service type, eg. _clementine._tcp
|
||||
domain, // Domain, eg. local
|
||||
QString::null, // Hostname (filled in by Avahi)
|
||||
port, // Port our service is running on
|
||||
QList<QByteArray>()); // TXT record
|
||||
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply);
|
||||
NewClosure(
|
||||
watcher,
|
||||
SIGNAL(finished(QDBusPendingCallWatcher*)),
|
||||
&Commit,
|
||||
entry_group_interface);
|
||||
|
||||
QObject::connect(
|
||||
watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
|
||||
watcher, SLOT(deleteLater()));
|
||||
}
|
||||
|
||||
void Commit(OrgFreedesktopAvahiEntryGroupInterface* interface) {
|
||||
QDBusPendingReply<> reply = interface->Commit();
|
||||
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply);
|
||||
QObject::connect(
|
||||
watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
|
||||
watcher, SLOT(deleteLater()));
|
||||
QObject::connect(
|
||||
watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
|
||||
interface, SLOT(deleteLater()));
|
||||
NewClosure(
|
||||
watcher,
|
||||
SIGNAL(finished(QDBusPendingCallWatcher*)),
|
||||
&LogCommit,
|
||||
reply);
|
||||
}
|
||||
|
||||
void LogCommit(QDBusPendingReply<> reply) {
|
||||
qLog(Debug) << "Remote interface published on Avahi:" << reply.error();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
15
src/networkremote/avahi.h
Normal file
15
src/networkremote/avahi.h
Normal file
@ -0,0 +1,15 @@
|
||||
#ifndef AVAHI_H
|
||||
#define AVAHI_H
|
||||
|
||||
#include "zeroconf.h"
|
||||
|
||||
class Avahi : public Zeroconf {
|
||||
public:
|
||||
virtual void Publish(
|
||||
const QString& domain,
|
||||
const QString& type,
|
||||
const QString& name,
|
||||
quint16 port);
|
||||
};
|
||||
|
||||
#endif // AVAHI_H
|
26
src/networkremote/bonjour.h
Normal file
26
src/networkremote/bonjour.h
Normal file
@ -0,0 +1,26 @@
|
||||
#ifndef BONJOUR_H
|
||||
#define BONJOUR_H
|
||||
|
||||
#include "zeroconf.h"
|
||||
|
||||
#ifdef __OBJC__
|
||||
@class NetServicePublicationDelegate;
|
||||
#else
|
||||
class NetServicePublicationDelegate;
|
||||
#endif // __OBJC__
|
||||
|
||||
class Bonjour : public Zeroconf {
|
||||
public:
|
||||
Bonjour();
|
||||
virtual ~Bonjour();
|
||||
virtual void Publish(
|
||||
const QString& domain,
|
||||
const QString& type,
|
||||
const QString& name,
|
||||
quint16 port);
|
||||
|
||||
private:
|
||||
NetServicePublicationDelegate* delegate_;
|
||||
};
|
||||
|
||||
#endif // BONJOUR_H
|
65
src/networkremote/bonjour.mm
Normal file
65
src/networkremote/bonjour.mm
Normal file
@ -0,0 +1,65 @@
|
||||
#include "bonjour.h"
|
||||
|
||||
#import <Foundation/NSNetServices.h>
|
||||
#import <Foundation/NSString.h>
|
||||
|
||||
#include "core/logging.h"
|
||||
|
||||
@interface NetServicePublicationDelegate : NSObject<NSNetServiceDelegate> {
|
||||
}
|
||||
|
||||
- (void)netServiceWillPublish:(NSNetService*)netService;
|
||||
- (void)netService:(NSNetService*)netService
|
||||
didNotPublish:(NSDictionary*)errorDict;
|
||||
- (void)netServiceDidStop:(NSNetService*)netService;
|
||||
|
||||
@end
|
||||
|
||||
@implementation NetServicePublicationDelegate
|
||||
|
||||
- (void)netServiceWillPublish: (NSNetService*)netService {
|
||||
qLog(Debug) << "Publishing:" << [[netService name] UTF8String];
|
||||
}
|
||||
|
||||
- (void)netService: (NSNetService*)netServie didNotPublish: (NSDictionary*)errorDict {
|
||||
qLog(Debug) << "Failed to publish remote service with Bonjour";
|
||||
NSLog(@"%@", errorDict);
|
||||
}
|
||||
|
||||
- (void)netServiceDidStop: (NSNetService*)netService {
|
||||
qLog(Debug) << "Unpublished:" << [[netService name] UTF8String];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
namespace {
|
||||
|
||||
NSString* NSStringFromQString(const QString& s) {
|
||||
return [[NSString alloc] initWithUTF8String: s.toUtf8().constData()];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Bonjour::Bonjour()
|
||||
: delegate_([[NetServicePublicationDelegate alloc] init]) {
|
||||
}
|
||||
|
||||
Bonjour::~Bonjour() {
|
||||
[delegate_ release];
|
||||
}
|
||||
|
||||
void Bonjour::Publish(
|
||||
const QString& domain,
|
||||
const QString& type,
|
||||
const QString& name,
|
||||
quint16 port) {
|
||||
NSNetService* service = [[NSNetService alloc]
|
||||
initWithDomain: NSStringFromQString(domain)
|
||||
type: NSStringFromQString(type)
|
||||
name: NSStringFromQString(name)
|
||||
port: port];
|
||||
if (service) {
|
||||
[service setDelegate: delegate_];
|
||||
[service publish];
|
||||
}
|
||||
}
|
170
src/networkremote/incomingdataparser.cpp
Normal file
170
src/networkremote/incomingdataparser.cpp
Normal file
@ -0,0 +1,170 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2012, Andreas Muttscheller <asfa194@gmail.com>
|
||||
|
||||
Clementine 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.
|
||||
|
||||
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "incomingdataparser.h"
|
||||
#include "core/logging.h"
|
||||
#include "engines/enginebase.h"
|
||||
#include "playlist/playlistmanager.h"
|
||||
#include "playlist/playlistsequence.h"
|
||||
|
||||
IncomingDataParser::IncomingDataParser(Application* app)
|
||||
:app_(app)
|
||||
{
|
||||
// Connect all the signals
|
||||
// due the player is in a different thread, we cannot access these functions directly
|
||||
connect(this, SIGNAL(Play()),
|
||||
app_->player(), SLOT(Play()));
|
||||
connect(this, SIGNAL(PlayPause()),
|
||||
app_->player(), SLOT(PlayPause()));
|
||||
connect(this, SIGNAL(Pause()),
|
||||
app_->player(), SLOT(Pause()));
|
||||
connect(this, SIGNAL(Stop()),
|
||||
app_->player(), SLOT(Stop()));
|
||||
connect(this, SIGNAL(Next()),
|
||||
app_->player(), SLOT(Next()));
|
||||
connect(this, SIGNAL(Previous()),
|
||||
app_->player(), SLOT(Previous()));
|
||||
connect(this, SIGNAL(SetVolume(int)),
|
||||
app_->player(), SLOT(SetVolume(int)));
|
||||
connect(this, SIGNAL(PlayAt(int,Engine::TrackChangeFlags,bool)),
|
||||
app_->player(), SLOT(PlayAt(int,Engine::TrackChangeFlags,bool)));
|
||||
|
||||
// For some connects we have to wait for the playlistmanager
|
||||
// to be initialized
|
||||
connect(app_->playlist_manager(), SIGNAL(PlaylistManagerInitialized()),
|
||||
this, SLOT(PlaylistManagerInitialized()));
|
||||
}
|
||||
|
||||
void IncomingDataParser::PlaylistManagerInitialized() {
|
||||
connect(this, SIGNAL(SetActivePlaylist(int)),
|
||||
app_->playlist_manager(), SLOT(SetActivePlaylist(int)));
|
||||
connect(this, SIGNAL(ShuffleCurrent()),
|
||||
app_->playlist_manager(), SLOT(ShuffleCurrent()));
|
||||
connect(this, SIGNAL(SetRepeatMode(PlaylistSequence::RepeatMode)),
|
||||
app_->playlist_manager()->sequence(),
|
||||
SLOT(SetRepeatMode(PlaylistSequence::RepeatMode)));
|
||||
connect(this, SIGNAL(SetShuffleMode(PlaylistSequence::ShuffleMode)),
|
||||
app_->playlist_manager()->sequence(),
|
||||
SLOT(SetShuffleMode(PlaylistSequence::ShuffleMode)));
|
||||
}
|
||||
|
||||
IncomingDataParser::~IncomingDataParser() {
|
||||
}
|
||||
|
||||
bool IncomingDataParser::close_connection() {
|
||||
return close_connection_;
|
||||
}
|
||||
|
||||
void IncomingDataParser::Parse(const QByteArray& data) {
|
||||
close_connection_ = false;
|
||||
|
||||
// Parse the incoming data
|
||||
pb::remote::Message msg;
|
||||
if (!msg.ParseFromArray(data.constData(), data.size())) {
|
||||
qLog(Info) << "Couldn't parse data";
|
||||
return;
|
||||
}
|
||||
|
||||
// Now check what's to do
|
||||
switch (msg.type()) {
|
||||
case pb::remote::CONNECT: emit SendClementineInfo();
|
||||
emit SendFirstData();
|
||||
break;
|
||||
case pb::remote::DISCONNECT: close_connection_ = true;
|
||||
break;
|
||||
case pb::remote::REQUEST_PLAYLISTS: emit SendAllPlaylists();
|
||||
break;
|
||||
case pb::remote::REQUEST_PLAYLIST_SONGS: GetPlaylistSongs(msg);
|
||||
break;
|
||||
case pb::remote::SET_VOLUME: emit SetVolume(msg.request_set_volume().volume());
|
||||
break;
|
||||
case pb::remote::PLAY: emit Play();
|
||||
break;
|
||||
case pb::remote::PLAYPAUSE: emit PlayPause();
|
||||
break;
|
||||
case pb::remote::PAUSE: emit Pause();
|
||||
break;
|
||||
case pb::remote::STOP: emit Stop();
|
||||
break;
|
||||
case pb::remote::NEXT: emit Next();
|
||||
break;
|
||||
case pb::remote::PREVIOUS: emit Previous();
|
||||
break;
|
||||
case pb::remote::CHANGE_SONG: ChangeSong(msg);
|
||||
break;
|
||||
case pb::remote::SHUFFLE_PLAYLIST: emit ShuffleCurrent();
|
||||
break;
|
||||
case pb::remote::REPEAT: SetRepeatMode(msg.repeat());
|
||||
break;
|
||||
case pb::remote::SHUFFLE: SetShuffleMode(msg.shuffle());
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
void IncomingDataParser::GetPlaylistSongs(const pb::remote::Message& msg) {
|
||||
emit SendPlaylistSongs(msg.request_playlist_songs().id());
|
||||
}
|
||||
|
||||
void IncomingDataParser::ChangeSong(const pb::remote::Message& msg) {
|
||||
// Get the first entry and check if there is a song
|
||||
const pb::remote::RequestChangeSong& request = msg.request_change_song();
|
||||
|
||||
// Check if we need to change the playlist
|
||||
if (request.playlist_id() != app_->playlist_manager()->active_id()) {
|
||||
emit SetActivePlaylist(request.playlist_id());
|
||||
}
|
||||
|
||||
// Play the selected song
|
||||
emit PlayAt(request.song_index(), Engine::Manual, false);
|
||||
}
|
||||
|
||||
void IncomingDataParser::SetRepeatMode(const pb::remote::Repeat& repeat) {
|
||||
switch (repeat.repeat_mode()) {
|
||||
case pb::remote::Repeat_Off:
|
||||
emit SetRepeatMode(PlaylistSequence::Repeat_Off);
|
||||
break;
|
||||
case pb::remote::Repeat_Track:
|
||||
emit SetRepeatMode(PlaylistSequence::Repeat_Track);
|
||||
break;
|
||||
case pb::remote::Repeat_Album:
|
||||
emit SetRepeatMode(PlaylistSequence::Repeat_Album);
|
||||
break;
|
||||
case pb::remote::Repeat_Playlist:
|
||||
emit SetRepeatMode(PlaylistSequence::Repeat_Playlist);
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
void IncomingDataParser::SetShuffleMode(const pb::remote::Shuffle& shuffle) {
|
||||
switch (shuffle.shuffle_mode()) {
|
||||
case pb::remote::Shuffle_Off:
|
||||
emit SetShuffleMode(PlaylistSequence::Shuffle_Off);
|
||||
break;
|
||||
case pb::remote::Shuffle_All:
|
||||
emit SetShuffleMode(PlaylistSequence::Shuffle_All);
|
||||
break;
|
||||
case pb::remote::Shuffle_InsideAlbum:
|
||||
emit SetShuffleMode(PlaylistSequence::Shuffle_InsideAlbum);
|
||||
break;
|
||||
case pb::remote::Shuffle_Albums:
|
||||
emit SetShuffleMode(PlaylistSequence::Shuffle_Albums);
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
51
src/networkremote/incomingdataparser.h
Normal file
51
src/networkremote/incomingdataparser.h
Normal file
@ -0,0 +1,51 @@
|
||||
#ifndef INCOMINGDATAPARSER_H
|
||||
#define INCOMINGDATAPARSER_H
|
||||
|
||||
#include "core/player.h"
|
||||
#include "core/application.h"
|
||||
#include "remotecontrolmessages.pb.h"
|
||||
|
||||
class IncomingDataParser : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
IncomingDataParser(Application* app);
|
||||
~IncomingDataParser();
|
||||
|
||||
bool close_connection();
|
||||
|
||||
public slots:
|
||||
void Parse(const QByteArray& pb_data);
|
||||
|
||||
signals:
|
||||
void SendClementineInfo();
|
||||
void SendFirstData();
|
||||
void SendAllPlaylists();
|
||||
void SendPlaylistSongs(int id);
|
||||
|
||||
void Play();
|
||||
void PlayPause();
|
||||
void Pause();
|
||||
void Stop();
|
||||
void Next();
|
||||
void Previous();
|
||||
void SetVolume(int volume);
|
||||
void PlayAt(int i, Engine::TrackChangeFlags change, bool reshuffle);
|
||||
void SetActivePlaylist(int id);
|
||||
void ShuffleCurrent();
|
||||
void SetRepeatMode(PlaylistSequence::RepeatMode mode);
|
||||
void SetShuffleMode(PlaylistSequence::ShuffleMode mode);
|
||||
|
||||
private slots:
|
||||
void PlaylistManagerInitialized();
|
||||
|
||||
private:
|
||||
Application* app_;
|
||||
bool close_connection_;
|
||||
|
||||
void GetPlaylistSongs(const pb::remote::Message& msg);
|
||||
void ChangeSong(const pb::remote::Message& msg);
|
||||
void SetRepeatMode(const pb::remote::Repeat& repeat);
|
||||
void SetShuffleMode(const pb::remote::Shuffle& shuffle);
|
||||
};
|
||||
|
||||
#endif // INCOMINGDATAPARSER_H
|
183
src/networkremote/networkremote.cpp
Normal file
183
src/networkremote/networkremote.cpp
Normal file
@ -0,0 +1,183 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2012, Andreas Muttscheller <asfa194@gmail.com>
|
||||
|
||||
Clementine 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.
|
||||
|
||||
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "networkremote.h"
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "covers/currentartloader.h"
|
||||
#include "networkremote/zeroconf.h"
|
||||
#include "playlist/playlistmanager.h"
|
||||
|
||||
|
||||
#include <QDataStream>
|
||||
#include <QSettings>
|
||||
|
||||
const char* NetworkRemote::kSettingsGroup = "NetworkRemote";
|
||||
const quint16 NetworkRemote::kDefaultServerPort = 5500;
|
||||
const int NetworkRemote::kProtocolBufferVersion = 1;
|
||||
|
||||
NetworkRemote::NetworkRemote(Application* app, QObject* parent)
|
||||
: QObject(parent),
|
||||
signals_connected_(false),
|
||||
app_(app) {
|
||||
}
|
||||
|
||||
|
||||
NetworkRemote::~NetworkRemote() {
|
||||
StopServer();
|
||||
}
|
||||
|
||||
void NetworkRemote::ReadSettings() {
|
||||
QSettings s;
|
||||
|
||||
s.beginGroup(NetworkRemote::kSettingsGroup);
|
||||
use_remote_ = s.value("use_remote", false).toBool();
|
||||
port_ = s.value("port", kDefaultServerPort).toInt();
|
||||
|
||||
// Use only non public ips must be true be default
|
||||
only_non_public_ip_ = s.value("only_non_public_ip", true).toBool();
|
||||
|
||||
s.endGroup();
|
||||
}
|
||||
|
||||
void NetworkRemote::SetupServer() {
|
||||
server_.reset(new QTcpServer());
|
||||
server_ipv6_.reset(new QTcpServer());
|
||||
incoming_data_parser_.reset(new IncomingDataParser(app_));
|
||||
outgoing_data_creator_.reset(new OutgoingDataCreator(app_));
|
||||
|
||||
outgoing_data_creator_->SetClients(&clients_);
|
||||
|
||||
connect(app_->current_art_loader(),
|
||||
SIGNAL(ArtLoaded(const Song&, const QString&, const QImage&)),
|
||||
outgoing_data_creator_.get(),
|
||||
SLOT(CurrentSongChanged(const Song&, const QString&, const QImage&)));
|
||||
}
|
||||
|
||||
void NetworkRemote::StartServer() {
|
||||
if (!app_) {
|
||||
qLog(Error) << "Start Server called without having an application!";
|
||||
return;
|
||||
}
|
||||
// Check if user desires to start a network remote server
|
||||
ReadSettings();
|
||||
if (!use_remote_) {
|
||||
qLog(Info) << "Network Remote deactivated";
|
||||
return;
|
||||
}
|
||||
|
||||
qLog(Info) << "Starting network remote";
|
||||
|
||||
connect(server_.get(), SIGNAL(newConnection()), this, SLOT(AcceptConnection()));
|
||||
connect(server_ipv6_.get(), SIGNAL(newConnection()), this, SLOT(AcceptConnection()));
|
||||
|
||||
server_->listen(QHostAddress::Any, port_);
|
||||
server_ipv6_->listen(QHostAddress::AnyIPv6, port_);
|
||||
|
||||
qLog(Info) << "Listening on port " << port_;
|
||||
|
||||
if (Zeroconf::GetZeroconf()) {
|
||||
Zeroconf::GetZeroconf()->Publish(
|
||||
"local", "_clementine._tcp", "Clementine", port_);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkRemote::StopServer() {
|
||||
if (server_->isListening()) {
|
||||
server_->close();
|
||||
server_ipv6_->close();
|
||||
clients_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkRemote::ReloadSettings() {
|
||||
StopServer();
|
||||
StartServer();
|
||||
}
|
||||
|
||||
void NetworkRemote::AcceptConnection() {
|
||||
if (!signals_connected_) {
|
||||
signals_connected_ = true;
|
||||
|
||||
// Setting up the signals, but only once
|
||||
connect(incoming_data_parser_.get(), SIGNAL(SendClementineInfo()),
|
||||
outgoing_data_creator_.get(), SLOT(SendClementineInfo()));
|
||||
connect(incoming_data_parser_.get(), SIGNAL(SendFirstData()),
|
||||
outgoing_data_creator_.get(), SLOT(SendFirstData()));
|
||||
connect(incoming_data_parser_.get(), SIGNAL(SendAllPlaylists()),
|
||||
outgoing_data_creator_.get(), SLOT(SendAllPlaylists()));
|
||||
connect(incoming_data_parser_.get(), SIGNAL(SendPlaylistSongs(int)),
|
||||
outgoing_data_creator_.get(), SLOT(SendPlaylistSongs(int)));
|
||||
|
||||
connect(app_->playlist_manager(), SIGNAL(ActiveChanged(Playlist*)),
|
||||
outgoing_data_creator_.get(), SLOT(ActiveChanged(Playlist*)));
|
||||
connect(app_->playlist_manager(), SIGNAL(PlaylistChanged(Playlist*)),
|
||||
outgoing_data_creator_.get(), SLOT(PlaylistChanged(Playlist*)));
|
||||
|
||||
connect(app_->player(), SIGNAL(VolumeChanged(int)), outgoing_data_creator_.get(),
|
||||
SLOT(VolumeChanged(int)));
|
||||
connect(app_->player()->engine(), SIGNAL(StateChanged(Engine::State)),
|
||||
outgoing_data_creator_.get(), SLOT(StateChanged(Engine::State)));
|
||||
|
||||
connect(app_->playlist_manager()->sequence(),
|
||||
SIGNAL(RepeatModeChanged(PlaylistSequence::RepeatMode)),
|
||||
outgoing_data_creator_.get(),
|
||||
SLOT(SendRepeatMode(PlaylistSequence::RepeatMode)));
|
||||
connect(app_->playlist_manager()->sequence(),
|
||||
SIGNAL(ShuffleModeChanged(PlaylistSequence::ShuffleMode)),
|
||||
outgoing_data_creator_.get(),
|
||||
SLOT(SendShuffleMode(PlaylistSequence::ShuffleMode)));
|
||||
}
|
||||
|
||||
QTcpServer* server = qobject_cast<QTcpServer*>(sender());
|
||||
QTcpSocket* client_socket = server->nextPendingConnection();
|
||||
// Check if our ip is in private scope
|
||||
if (only_non_public_ip_ && !IpIsPrivate(client_socket->peerAddress())) {
|
||||
qLog(Info) << "Got a connection from public ip" <<
|
||||
client_socket->peerAddress().toString();
|
||||
client_socket->close();
|
||||
} else {
|
||||
CreateRemoteClient(client_socket);
|
||||
}
|
||||
}
|
||||
|
||||
bool NetworkRemote::IpIsPrivate(const QHostAddress& address) {
|
||||
return
|
||||
// Link Local v4
|
||||
address.isInSubnet(QHostAddress::parseSubnet("127.0.0.1/8")) ||
|
||||
// Link Local v6
|
||||
address.isInSubnet(QHostAddress::parseSubnet("::1/128")) ||
|
||||
address.isInSubnet(QHostAddress::parseSubnet("fe80::/10")) ||
|
||||
// Private v4 range
|
||||
address.isInSubnet(QHostAddress::parseSubnet("192.168.0.0/16")) ||
|
||||
address.isInSubnet(QHostAddress::parseSubnet("172.16.0.0/12")) ||
|
||||
address.isInSubnet(QHostAddress::parseSubnet("10.0.0.0/8")) ||
|
||||
// Private v6 range
|
||||
address.isInSubnet(QHostAddress::parseSubnet("fc00::/7"));
|
||||
}
|
||||
|
||||
void NetworkRemote::CreateRemoteClient(QTcpSocket *client_socket) {
|
||||
if (client_socket) {
|
||||
// Add the client to the list
|
||||
RemoteClient* client = new RemoteClient(app_, client_socket);
|
||||
clients_.push_back(client);
|
||||
|
||||
// Connect the signal to parse data
|
||||
connect(client, SIGNAL(Parse(QByteArray)),
|
||||
incoming_data_parser_.get(), SLOT(Parse(QByteArray)));
|
||||
}
|
||||
}
|
51
src/networkremote/networkremote.h
Normal file
51
src/networkremote/networkremote.h
Normal file
@ -0,0 +1,51 @@
|
||||
#ifndef NETWORKREMOTE_H
|
||||
#define NETWORKREMOTE_H
|
||||
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
|
||||
#include <QTcpServer>
|
||||
#include <QTcpSocket>
|
||||
|
||||
#include "core/player.h"
|
||||
#include "core/application.h"
|
||||
#include "incomingdataparser.h"
|
||||
#include "outgoingdatacreator.h"
|
||||
#include "remoteclient.h"
|
||||
|
||||
class NetworkRemote : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
static const char* kSettingsGroup;
|
||||
static const quint16 kDefaultServerPort;
|
||||
static const int kProtocolBufferVersion;
|
||||
|
||||
explicit NetworkRemote(Application* app, QObject* parent = 0);
|
||||
~NetworkRemote();
|
||||
|
||||
public slots:
|
||||
void SetupServer();
|
||||
void StartServer();
|
||||
void ReloadSettings();
|
||||
void AcceptConnection();
|
||||
|
||||
private:
|
||||
boost::scoped_ptr<QTcpServer> server_;
|
||||
boost::scoped_ptr<QTcpServer> server_ipv6_;
|
||||
boost::scoped_ptr<IncomingDataParser> incoming_data_parser_;
|
||||
boost::scoped_ptr<OutgoingDataCreator> outgoing_data_creator_;
|
||||
|
||||
quint16 port_;
|
||||
bool use_remote_;
|
||||
bool only_non_public_ip_;
|
||||
bool signals_connected_;
|
||||
Application* app_;
|
||||
|
||||
QList<RemoteClient*> clients_;
|
||||
|
||||
void StopServer();
|
||||
void ReadSettings();
|
||||
void CreateRemoteClient(QTcpSocket* client_socket);
|
||||
bool IpIsPrivate(const QHostAddress& address);
|
||||
};
|
||||
|
||||
#endif // NETWORKREMOTE_H
|
58
src/networkremote/networkremotehelper.cpp
Normal file
58
src/networkremote/networkremotehelper.cpp
Normal file
@ -0,0 +1,58 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2012, Andreas Muttscheller <asfa194@gmail.com>
|
||||
|
||||
Clementine 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.
|
||||
|
||||
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "core/logging.h"
|
||||
|
||||
#include "networkremote.h"
|
||||
#include "networkremotehelper.h"
|
||||
|
||||
NetworkRemoteHelper* NetworkRemoteHelper::sInstance = NULL;
|
||||
|
||||
NetworkRemoteHelper::NetworkRemoteHelper(Application* app)
|
||||
: app_(app)
|
||||
{
|
||||
app_ = app;
|
||||
connect(this, SIGNAL(ReloadSettingsSig()),
|
||||
app_->network_remote(), SLOT(ReloadSettings()));
|
||||
connect(this, SIGNAL(StartServerSig()),
|
||||
app_->network_remote(), SLOT(StartServer()));
|
||||
connect(this, SIGNAL(SetupServerSig()),
|
||||
app_->network_remote(), SLOT(SetupServer()));
|
||||
|
||||
sInstance = this;
|
||||
}
|
||||
|
||||
NetworkRemoteHelper::~NetworkRemoteHelper() {
|
||||
}
|
||||
|
||||
void NetworkRemoteHelper::StartServer() {
|
||||
emit SetupServerSig();
|
||||
emit StartServerSig();
|
||||
}
|
||||
|
||||
void NetworkRemoteHelper::ReloadSettings() {
|
||||
emit ReloadSettingsSig();
|
||||
}
|
||||
|
||||
// For using in Settingsdialog, we haven't the appication there
|
||||
NetworkRemoteHelper* NetworkRemoteHelper::Instance() {
|
||||
if (!sInstance) {
|
||||
// normally he shouldn't go here. Only for safety
|
||||
return NULL;
|
||||
}
|
||||
return sInstance;
|
||||
}
|
29
src/networkremote/networkremotehelper.h
Normal file
29
src/networkremote/networkremotehelper.h
Normal file
@ -0,0 +1,29 @@
|
||||
#ifndef NETWORKREMOTEHELPER_H
|
||||
#define NETWORKREMOTEHELPER_H
|
||||
|
||||
#include <QThread>
|
||||
|
||||
#include "networkremote.h"
|
||||
|
||||
class NetworkRemoteHelper : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
static NetworkRemoteHelper* Instance();
|
||||
|
||||
NetworkRemoteHelper(Application* app);
|
||||
~NetworkRemoteHelper();
|
||||
|
||||
void StartServer();
|
||||
void ReloadSettings();
|
||||
|
||||
signals:
|
||||
void SetupServerSig();
|
||||
void StartServerSig();
|
||||
void ReloadSettingsSig();
|
||||
|
||||
private:
|
||||
static NetworkRemoteHelper* sInstance;
|
||||
Application* app_;
|
||||
};
|
||||
|
||||
#endif // NETWORKREMOTEHELPER_H
|
321
src/networkremote/outgoingdatacreator.cpp
Normal file
321
src/networkremote/outgoingdatacreator.cpp
Normal file
@ -0,0 +1,321 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2012, Andreas Muttscheller <asfa194@gmail.com>
|
||||
|
||||
Clementine 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.
|
||||
|
||||
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "outgoingdatacreator.h"
|
||||
#include "networkremote.h"
|
||||
#include "core/logging.h"
|
||||
|
||||
OutgoingDataCreator::OutgoingDataCreator(Application* app)
|
||||
: app_(app)
|
||||
{
|
||||
// Create Keep Alive Timer
|
||||
keep_alive_timer_ = new QTimer(this);
|
||||
connect(keep_alive_timer_, SIGNAL(timeout()), this, SLOT(SendKeepAlive()));
|
||||
keep_alive_timeout_ = 10000;
|
||||
}
|
||||
|
||||
OutgoingDataCreator::~OutgoingDataCreator() {
|
||||
}
|
||||
|
||||
void OutgoingDataCreator::SetClients(QList<RemoteClient*>* clients) {
|
||||
clients_ = clients;
|
||||
// After we got some clients, start the keep alive timer
|
||||
// Default: every 10 seconds
|
||||
keep_alive_timer_->start(keep_alive_timeout_);
|
||||
}
|
||||
|
||||
void OutgoingDataCreator::SendDataToClients(pb::remote::Message* msg) {
|
||||
// Check if we have clients to send data to
|
||||
if (clients_->empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add the Version number
|
||||
msg->set_version(NetworkRemote::kProtocolBufferVersion);
|
||||
|
||||
RemoteClient* client;
|
||||
foreach(client, *clients_) {
|
||||
// Check if the client is still active
|
||||
if (client->State() == QTcpSocket::ConnectedState) {
|
||||
client->SendData(msg);
|
||||
} else {
|
||||
clients_->removeAt(clients_->indexOf(client));
|
||||
delete client;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OutgoingDataCreator::SendClementineInfo() {
|
||||
// Create the general message and set the message type
|
||||
pb::remote::Message msg;
|
||||
msg.set_type(pb::remote::INFO);
|
||||
|
||||
// Now add the message specific data
|
||||
pb::remote::ResponseClementineInfo* info =
|
||||
msg.mutable_response_clementine_info();
|
||||
SetEngineState(info);
|
||||
|
||||
QString version = QString("%1 %2").arg(QCoreApplication::applicationName(),
|
||||
QCoreApplication::applicationVersion());
|
||||
info->set_version(version.toAscii());
|
||||
SendDataToClients(&msg);
|
||||
}
|
||||
|
||||
void OutgoingDataCreator::SetEngineState(pb::remote::ResponseClementineInfo* msg) {
|
||||
switch(app_->player()->GetState()) {
|
||||
case Engine::Idle: msg->set_state(pb::remote::Idle);
|
||||
break;
|
||||
case Engine::Empty: msg->set_state(pb::remote::Empty);
|
||||
break;
|
||||
case Engine::Playing: msg->set_state(pb::remote::Playing);
|
||||
break;
|
||||
case Engine::Paused: msg->set_state(pb::remote::Paused);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void OutgoingDataCreator::SendAllPlaylists() {
|
||||
// Get all Playlists
|
||||
QList<Playlist*> app_playlists = app_->playlist_manager()->GetAllPlaylists();
|
||||
int active_playlist = app_->playlist_manager()->active_id();
|
||||
|
||||
// Create message
|
||||
pb::remote::Message msg;
|
||||
msg.set_type(pb::remote::PLAYLISTS);
|
||||
|
||||
pb::remote::ResponsePlaylists* playlists = msg.mutable_response_playlists();
|
||||
|
||||
QListIterator<Playlist*> it(app_playlists);
|
||||
while(it.hasNext()) {
|
||||
// Get the next Playlist
|
||||
Playlist* p = it.next();
|
||||
QString playlist_name = app_->playlist_manager()->GetPlaylistName(p->id());
|
||||
|
||||
// Create a new playlist
|
||||
pb::remote::Playlist* playlist = playlists->add_playlist();
|
||||
playlist->set_name(playlist_name.toStdString());
|
||||
playlist->set_id(p->id());
|
||||
playlist->set_active((p->id() == active_playlist));
|
||||
playlist->set_item_count(p->rowCount());
|
||||
}
|
||||
|
||||
SendDataToClients(&msg);
|
||||
}
|
||||
|
||||
void OutgoingDataCreator::ActiveChanged(Playlist*) {
|
||||
// When a playlist was changed, send the new list
|
||||
SendAllPlaylists();
|
||||
}
|
||||
|
||||
void OutgoingDataCreator::SendFirstData() {
|
||||
// First Send the current song
|
||||
PlaylistItemPtr item = app_->player()->GetCurrentItem();
|
||||
if (!item) {
|
||||
qLog(Info) << "No current item found!";
|
||||
}
|
||||
|
||||
CurrentSongChanged(current_song_, current_uri_, current_image_);
|
||||
|
||||
// then the current volume
|
||||
VolumeChanged(app_->player()->GetVolume());
|
||||
}
|
||||
|
||||
void OutgoingDataCreator::CurrentSongChanged(const Song& song, const QString& uri, const QImage& img) {
|
||||
current_song_ = song;
|
||||
current_uri_ = uri;
|
||||
current_image_ = img;
|
||||
|
||||
if (!clients_->empty()) {
|
||||
// Create the message
|
||||
pb::remote::Message msg;
|
||||
msg.set_type(pb::remote::CURRENT_METAINFO);
|
||||
|
||||
// If there is no song, create an empty node, otherwise fill it with data
|
||||
int i = app_->playlist_manager()->active()->current_row();
|
||||
CreateSong(
|
||||
current_song_, uri, i,
|
||||
msg.mutable_response_current_metadata()->mutable_song_metadata());
|
||||
|
||||
SendDataToClients(&msg);
|
||||
}
|
||||
}
|
||||
|
||||
void OutgoingDataCreator::CreateSong(
|
||||
const Song& song,
|
||||
const QString& art_uri,
|
||||
const int index,
|
||||
pb::remote::SongMetadata* song_metadata) {
|
||||
if (song.is_valid()) {
|
||||
song_metadata->set_id(song.id());
|
||||
song_metadata->set_index(index);
|
||||
song_metadata->set_title(DataCommaSizeFromQString(song.PrettyTitle()));
|
||||
song_metadata->set_artist(DataCommaSizeFromQString(song.artist()));
|
||||
song_metadata->set_album(DataCommaSizeFromQString(song.album()));
|
||||
song_metadata->set_albumartist(DataCommaSizeFromQString(song.albumartist()));
|
||||
song_metadata->set_pretty_length(DataCommaSizeFromQString(song.PrettyLength()));
|
||||
song_metadata->set_genre(DataCommaSizeFromQString(song.genre()));
|
||||
song_metadata->set_pretty_year(DataCommaSizeFromQString(song.PrettyYear()));
|
||||
song_metadata->set_track(song.track());
|
||||
song_metadata->set_disc(song.disc());
|
||||
song_metadata->set_playcount(song.playcount());
|
||||
|
||||
// Append coverart
|
||||
if (!art_uri.isEmpty()) {
|
||||
QImage orig(QUrl(art_uri).toLocalFile());
|
||||
QImage small;
|
||||
// Check if we resize the image
|
||||
if (orig.width() > 1000) {
|
||||
small = orig.scaled(1000, 1000, Qt::KeepAspectRatio);
|
||||
} else {
|
||||
small = orig;
|
||||
}
|
||||
|
||||
// Read the image in a buffer and compress it
|
||||
QByteArray data;
|
||||
QBuffer buf(&data);
|
||||
buf.open(QIODevice::WriteOnly);
|
||||
small.save(&buf, "JPG");
|
||||
|
||||
// Append the Data in the protocol buffer
|
||||
song_metadata->set_art(data.constData(), data.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void OutgoingDataCreator::VolumeChanged(int volume) {
|
||||
// Create the message
|
||||
pb::remote::Message msg;
|
||||
msg.set_type(pb::remote::SET_VOLUME);
|
||||
msg.mutable_request_set_volume()->set_volume(volume);
|
||||
SendDataToClients(&msg);
|
||||
}
|
||||
|
||||
void OutgoingDataCreator::SendPlaylistSongs(int id) {
|
||||
// Get the PlaylistQByteArray(data.data(), data.size()
|
||||
Playlist* playlist = app_->playlist_manager()->playlist(id);
|
||||
if(!playlist) {
|
||||
qLog(Info) << "Could not find playlist with id = " << id;
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the message and the playlist
|
||||
pb::remote::Message msg;
|
||||
msg.set_type(pb::remote::PLAYLIST_SONGS);
|
||||
|
||||
// Create the Response message
|
||||
pb::remote::ResponsePlaylistSongs* pb_response_playlist_songs =
|
||||
msg.mutable_response_playlist_songs();
|
||||
|
||||
// Create a new playlist
|
||||
pb::remote::Playlist* pb_playlist =
|
||||
pb_response_playlist_songs->mutable_requested_playlist();
|
||||
pb_playlist->set_id(id);
|
||||
|
||||
// Send all songs
|
||||
int index = 0;
|
||||
SongList song_list = playlist->GetAllSongs();
|
||||
QListIterator<Song> it(song_list);
|
||||
while(it.hasNext()) {
|
||||
Song song = it.next();
|
||||
QString art = song.art_automatic();
|
||||
pb::remote::SongMetadata* pb_song = pb_response_playlist_songs->add_songs();
|
||||
CreateSong(song, art, index, pb_song);
|
||||
++index;
|
||||
}
|
||||
SendDataToClients(&msg);
|
||||
}
|
||||
|
||||
void OutgoingDataCreator::PlaylistChanged(Playlist* playlist) {
|
||||
// If a playlist changed, then send the new songs to the client
|
||||
SendPlaylistSongs(playlist->id());
|
||||
}
|
||||
|
||||
void OutgoingDataCreator::StateChanged(Engine::State state) {
|
||||
// Send state only if it changed
|
||||
// When selecting next song, StateChanged is emitted, but we already know
|
||||
// that we are playing
|
||||
if (state == last_state_) {
|
||||
return;
|
||||
}
|
||||
last_state_ = state;
|
||||
|
||||
pb::remote::Message msg;
|
||||
|
||||
switch (state) {
|
||||
case Engine::Playing: msg.set_type(pb::remote::PLAY);
|
||||
break;
|
||||
case Engine::Paused: msg.set_type(pb::remote::PAUSE);
|
||||
break;
|
||||
case Engine::Empty: msg.set_type(pb::remote::STOP); // Empty is called when player stopped
|
||||
break;
|
||||
default: msg.set_type(pb::remote::STOP);
|
||||
break;
|
||||
};
|
||||
|
||||
SendDataToClients(&msg);
|
||||
}
|
||||
|
||||
void OutgoingDataCreator::SendRepeatMode(PlaylistSequence::RepeatMode mode) {
|
||||
pb::remote::Message msg;
|
||||
msg.set_type(pb::remote::REPEAT);
|
||||
|
||||
switch (mode) {
|
||||
case PlaylistSequence::Repeat_Off:
|
||||
msg.mutable_repeat()->set_repeat_mode(pb::remote::Repeat_Off);
|
||||
break;
|
||||
case PlaylistSequence::Repeat_Track:
|
||||
msg.mutable_repeat()->set_repeat_mode(pb::remote::Repeat_Track);
|
||||
break;
|
||||
case PlaylistSequence::Repeat_Album:
|
||||
msg.mutable_repeat()->set_repeat_mode(pb::remote::Repeat_Album);
|
||||
break;
|
||||
case PlaylistSequence::Repeat_Playlist:
|
||||
msg.mutable_repeat()->set_repeat_mode(pb::remote::Repeat_Playlist);
|
||||
break;
|
||||
}
|
||||
|
||||
SendDataToClients(&msg);
|
||||
}
|
||||
|
||||
void OutgoingDataCreator::SendShuffleMode(PlaylistSequence::ShuffleMode mode) {
|
||||
pb::remote::Message msg;
|
||||
msg.set_type(pb::remote::SHUFFLE);
|
||||
|
||||
switch (mode) {
|
||||
case PlaylistSequence::Shuffle_Off:
|
||||
msg.mutable_shuffle()->set_shuffle_mode(pb::remote::Shuffle_Off);
|
||||
break;
|
||||
case PlaylistSequence::Shuffle_All:
|
||||
msg.mutable_shuffle()->set_shuffle_mode(pb::remote::Shuffle_All);
|
||||
break;
|
||||
case PlaylistSequence::Shuffle_InsideAlbum:
|
||||
msg.mutable_shuffle()->set_shuffle_mode(pb::remote::Shuffle_InsideAlbum);
|
||||
break;
|
||||
case PlaylistSequence::Shuffle_Albums:
|
||||
msg.mutable_shuffle()->set_shuffle_mode(pb::remote::Shuffle_Albums);
|
||||
break;
|
||||
}
|
||||
|
||||
SendDataToClients(&msg);
|
||||
}
|
||||
|
||||
void OutgoingDataCreator::SendKeepAlive() {
|
||||
pb::remote::Message msg;
|
||||
msg.set_type(pb::remote::KEEP_ALIVE);
|
||||
SendDataToClients(&msg);
|
||||
}
|
59
src/networkremote/outgoingdatacreator.h
Normal file
59
src/networkremote/outgoingdatacreator.h
Normal file
@ -0,0 +1,59 @@
|
||||
#ifndef OUTGOINGDATACREATOR_H
|
||||
#define OUTGOINGDATACREATOR_H
|
||||
|
||||
#include <QTcpSocket>
|
||||
#include <QImage>
|
||||
#include <QList>
|
||||
#include <QTimer>
|
||||
|
||||
#include "core/player.h"
|
||||
#include "core/application.h"
|
||||
#include "engines/enginebase.h"
|
||||
#include "engines/engine_fwd.h"
|
||||
#include "playlist/playlist.h"
|
||||
#include "playlist/playlistmanager.h"
|
||||
#include "remotecontrolmessages.pb.h"
|
||||
#include "remoteclient.h"
|
||||
|
||||
class OutgoingDataCreator : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
OutgoingDataCreator(Application* app);
|
||||
~OutgoingDataCreator();
|
||||
|
||||
void SetClients(QList<RemoteClient*>* clients);
|
||||
|
||||
public slots:
|
||||
void SendClementineInfo();
|
||||
void SendAllPlaylists();
|
||||
void SendFirstData();
|
||||
void SendPlaylistSongs(int id);
|
||||
void PlaylistChanged(Playlist*);
|
||||
void VolumeChanged(int volume);
|
||||
void ActiveChanged(Playlist*);
|
||||
void CurrentSongChanged(const Song& song, const QString& uri, const QImage& img);
|
||||
void StateChanged(Engine::State);
|
||||
void SendKeepAlive();
|
||||
void SendRepeatMode(PlaylistSequence::RepeatMode mode);
|
||||
void SendShuffleMode(PlaylistSequence::ShuffleMode mode);
|
||||
|
||||
private:
|
||||
Application* app_;
|
||||
QList<RemoteClient*>* clients_;
|
||||
Song current_song_;
|
||||
QString current_uri_;
|
||||
QImage current_image_;
|
||||
Engine::State last_state_;
|
||||
QTimer* keep_alive_timer_;
|
||||
int keep_alive_timeout_;
|
||||
|
||||
void SendDataToClients(pb::remote::Message* msg);
|
||||
void SetEngineState(pb::remote::ResponseClementineInfo* msg);
|
||||
void CreateSong(
|
||||
const Song& song,
|
||||
const QString& art_uri,
|
||||
const int index,
|
||||
pb::remote::SongMetadata* song_metadata);
|
||||
};
|
||||
|
||||
#endif // OUTGOINGDATACREATOR_H
|
83
src/networkremote/remoteclient.cpp
Normal file
83
src/networkremote/remoteclient.cpp
Normal file
@ -0,0 +1,83 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2013, Andreas Muttscheller <asfa194@gmail.com>
|
||||
|
||||
Clementine 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.
|
||||
|
||||
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "core/logging.h"
|
||||
|
||||
#include "remoteclient.h"
|
||||
|
||||
#include <QDataStream>
|
||||
|
||||
RemoteClient::RemoteClient(Application* app, QTcpSocket* client)
|
||||
: app_(app),
|
||||
client_(client)
|
||||
{
|
||||
// Open the buffer
|
||||
buffer_.setData(QByteArray());
|
||||
buffer_.open(QIODevice::ReadWrite);
|
||||
reading_protobuf_ = false;
|
||||
|
||||
// Connect to the slot IncomingData when receiving data
|
||||
connect(client, SIGNAL(readyRead()), this, SLOT(IncomingData()));
|
||||
}
|
||||
|
||||
|
||||
RemoteClient::~RemoteClient() {
|
||||
}
|
||||
|
||||
void RemoteClient::IncomingData() {
|
||||
while (client_->bytesAvailable()) {
|
||||
if (!reading_protobuf_) {
|
||||
// Read the length of the next message
|
||||
QDataStream s(client_);
|
||||
s >> expected_length_;
|
||||
reading_protobuf_ = true;
|
||||
}
|
||||
|
||||
// Read some of the message
|
||||
buffer_.write(
|
||||
client_->read(expected_length_ - buffer_.size()));
|
||||
|
||||
// Did we get everything?
|
||||
if (buffer_.size() == expected_length_) {
|
||||
// Parse the message
|
||||
emit Parse(buffer_.data());
|
||||
|
||||
// Clear the buffer
|
||||
buffer_.close();
|
||||
buffer_.setData(QByteArray());
|
||||
buffer_.open(QIODevice::ReadWrite);
|
||||
reading_protobuf_ = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteClient::SendData(pb::remote::Message *msg) {
|
||||
// Serialize the message
|
||||
std::string data = msg->SerializeAsString();
|
||||
|
||||
// write the length of the data first
|
||||
QDataStream s(client_);
|
||||
s << qint32(data.length());
|
||||
s.writeRawData(data.data(), data.length());
|
||||
|
||||
// Flush data
|
||||
client_->flush();
|
||||
}
|
||||
|
||||
QAbstractSocket::SocketState RemoteClient::State() {
|
||||
return client_->state();
|
||||
}
|
35
src/networkremote/remoteclient.h
Normal file
35
src/networkremote/remoteclient.h
Normal file
@ -0,0 +1,35 @@
|
||||
#ifndef REMOTECLIENT_H
|
||||
#define REMOTECLIENT_H
|
||||
|
||||
#include <QAbstractSocket>
|
||||
#include <QTcpSocket>
|
||||
#include <QBuffer>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "incomingdataparser.h"
|
||||
|
||||
class RemoteClient : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
RemoteClient(Application* app, QTcpSocket* client);
|
||||
~RemoteClient();
|
||||
|
||||
void SendData(pb::remote::Message* msg);
|
||||
QAbstractSocket::SocketState State();
|
||||
|
||||
private slots:
|
||||
void IncomingData();
|
||||
|
||||
signals:
|
||||
void Parse(const QByteArray& pb_data);
|
||||
|
||||
private:
|
||||
Application* app_;
|
||||
|
||||
QTcpSocket* client_;
|
||||
bool reading_protobuf_;
|
||||
quint32 expected_length_;
|
||||
QBuffer buffer_;
|
||||
};
|
||||
|
||||
#endif // REMOTECLIENT_H
|
64
src/networkremote/tinysvcmdns.cpp
Normal file
64
src/networkremote/tinysvcmdns.cpp
Normal file
@ -0,0 +1,64 @@
|
||||
#include "tinysvcmdns.h"
|
||||
|
||||
extern "C" {
|
||||
#include "mdnsd.h"
|
||||
}
|
||||
|
||||
#include <QHostInfo>
|
||||
#include <QNetworkInterface>
|
||||
|
||||
#include "core/logging.h"
|
||||
|
||||
namespace {
|
||||
|
||||
uint32_t GetLocalIPAddress() {
|
||||
QList<QHostAddress> addresses = QNetworkInterface::allAddresses();
|
||||
foreach (const QHostAddress& address, addresses) {
|
||||
// TODO: Add ipv6 support to tinysvcmdns.
|
||||
if (address.protocol() == QAbstractSocket::IPv4Protocol &&
|
||||
!address.isInSubnet(QHostAddress::parseSubnet("127.0.0.1/8"))) {
|
||||
return address.toIPv4Address();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TinySVCMDNS::TinySVCMDNS()
|
||||
: mdnsd_(NULL) {
|
||||
uint32_t ip_address = GetLocalIPAddress();
|
||||
if (ip_address == 0) {
|
||||
qLog(Warning) << "Could not publish service over mDNS as there is no"
|
||||
<< "non-local IPv4 interface";
|
||||
return;
|
||||
}
|
||||
mdnsd_ = mdnsd_start();
|
||||
mdnsd_set_hostname(
|
||||
mdnsd_,
|
||||
QHostInfo::localHostName().toUtf8().constData(),
|
||||
ip_address);
|
||||
}
|
||||
|
||||
TinySVCMDNS::~TinySVCMDNS() {
|
||||
if (mdnsd_) {
|
||||
mdnsd_stop(mdnsd_);
|
||||
}
|
||||
}
|
||||
|
||||
void TinySVCMDNS::Publish(
|
||||
const QString& domain,
|
||||
const QString& type,
|
||||
const QString& name,
|
||||
quint16 port) {
|
||||
if (!mdnsd_) {
|
||||
return;
|
||||
}
|
||||
mdnsd_register_svc(
|
||||
mdnsd_,
|
||||
name.toUtf8().constData(),
|
||||
QString(type + ".local").toUtf8().constData(),
|
||||
port,
|
||||
NULL,
|
||||
NULL);
|
||||
}
|
23
src/networkremote/tinysvcmdns.h
Normal file
23
src/networkremote/tinysvcmdns.h
Normal file
@ -0,0 +1,23 @@
|
||||
#ifndef TINYSVCMDNS_H
|
||||
#define TINYSVCMDNS_H
|
||||
|
||||
#include "zeroconf.h"
|
||||
|
||||
struct mdnsd;
|
||||
|
||||
class TinySVCMDNS : public Zeroconf {
|
||||
public:
|
||||
TinySVCMDNS();
|
||||
virtual ~TinySVCMDNS();
|
||||
|
||||
virtual void Publish(
|
||||
const QString& domain,
|
||||
const QString& type,
|
||||
const QString& name,
|
||||
quint16 port);
|
||||
|
||||
private:
|
||||
mdnsd* mdnsd_;
|
||||
};
|
||||
|
||||
#endif // TINYSVCMDNS_H
|
37
src/networkremote/zeroconf.cpp
Normal file
37
src/networkremote/zeroconf.cpp
Normal file
@ -0,0 +1,37 @@
|
||||
#include "zeroconf.h"
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
#include "avahi.h"
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_DARWIN
|
||||
#include "bonjour.h"
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
#include "tinysvcmdns.h"
|
||||
#endif
|
||||
|
||||
Zeroconf* Zeroconf::sInstance = NULL;
|
||||
|
||||
Zeroconf::~Zeroconf() {
|
||||
|
||||
}
|
||||
|
||||
Zeroconf* Zeroconf::GetZeroconf() {
|
||||
if (!sInstance) {
|
||||
#ifdef HAVE_DBUS
|
||||
sInstance = new Avahi;
|
||||
#endif // HAVE_DBUS
|
||||
#ifdef Q_OS_DARWIN
|
||||
sInstance = new Bonjour;
|
||||
#endif
|
||||
#ifdef Q_OS_WIN32
|
||||
sInstance = new TinySVCMDNS;
|
||||
#endif
|
||||
}
|
||||
|
||||
return sInstance;
|
||||
}
|
22
src/networkremote/zeroconf.h
Normal file
22
src/networkremote/zeroconf.h
Normal file
@ -0,0 +1,22 @@
|
||||
#ifndef ZEROCONF_H
|
||||
#define ZEROCONF_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
class Zeroconf {
|
||||
public:
|
||||
virtual ~Zeroconf();
|
||||
|
||||
virtual void Publish(
|
||||
const QString& domain,
|
||||
const QString& type,
|
||||
const QString& name,
|
||||
quint16 port) = 0;
|
||||
|
||||
static Zeroconf* GetZeroconf();
|
||||
|
||||
private:
|
||||
static Zeroconf* sInstance;
|
||||
};
|
||||
|
||||
#endif // ZEROCONF_H
|
78
src/ui/networkremotesettingspage.cpp
Normal file
78
src/ui/networkremotesettingspage.cpp
Normal file
@ -0,0 +1,78 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine 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.
|
||||
|
||||
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "iconloader.h"
|
||||
#include "networkremotesettingspage.h"
|
||||
#include "ui_networkremotesettingspage.h"
|
||||
#include "networkremote/networkremote.h"
|
||||
#include "networkremote/networkremotehelper.h"
|
||||
|
||||
#include <QSettings>
|
||||
|
||||
|
||||
NetworkRemoteSettingsPage::NetworkRemoteSettingsPage(SettingsDialog* dialog)
|
||||
: SettingsPage(dialog),
|
||||
ui_(new Ui_NetworkRemoteSettingsPage)
|
||||
{
|
||||
ui_->setupUi(this);
|
||||
setWindowIcon(IconLoader::Load("ipodtouchicon"));
|
||||
}
|
||||
|
||||
NetworkRemoteSettingsPage::~NetworkRemoteSettingsPage() {
|
||||
delete ui_;
|
||||
}
|
||||
|
||||
void NetworkRemoteSettingsPage::Load() {
|
||||
QSettings s;
|
||||
int port;
|
||||
|
||||
s.beginGroup(NetworkRemote::kSettingsGroup);
|
||||
|
||||
port = s.value("port").toInt();
|
||||
if (port == 0) {
|
||||
ui_->remote_port->setValue(NetworkRemote::kDefaultServerPort);
|
||||
}
|
||||
else {
|
||||
ui_->remote_port->setValue(s.value("port").toInt());
|
||||
}
|
||||
|
||||
ui_->use_remote->setChecked(s.value("use_remote").toBool());
|
||||
if (s.contains("only_non_public_ip")) {
|
||||
ui_->only_non_public_ip->setChecked(s.value("only_non_public_ip").toBool());
|
||||
} else {
|
||||
// Default yes
|
||||
ui_->only_non_public_ip->setChecked(true);
|
||||
s.setValue("only_non_public_ip", true);
|
||||
}
|
||||
|
||||
s.endGroup();
|
||||
}
|
||||
|
||||
void NetworkRemoteSettingsPage::Save() {
|
||||
QSettings s;
|
||||
|
||||
s.beginGroup(NetworkRemote::kSettingsGroup);
|
||||
s.setValue("port", ui_->remote_port->value());
|
||||
s.setValue("use_remote", ui_->use_remote->isChecked());
|
||||
s.setValue("only_non_public_ip", ui_->only_non_public_ip->isChecked());
|
||||
|
||||
s.endGroup();
|
||||
|
||||
if (NetworkRemoteHelper::Instance()) {
|
||||
NetworkRemoteHelper::Instance()->ReloadSettings();
|
||||
}
|
||||
}
|
39
src/ui/networkremotesettingspage.h
Normal file
39
src/ui/networkremotesettingspage.h
Normal file
@ -0,0 +1,39 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine 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.
|
||||
|
||||
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef NETWORKREMOTESETTINGSPAGE_H
|
||||
#define NETWORKREMOTESETTINGSPAGE_H
|
||||
|
||||
#include "settingspage.h"
|
||||
|
||||
class Ui_NetworkRemoteSettingsPage;
|
||||
|
||||
class NetworkRemoteSettingsPage : public SettingsPage {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
NetworkRemoteSettingsPage(SettingsDialog* dialog);
|
||||
~NetworkRemoteSettingsPage();
|
||||
|
||||
void Load();
|
||||
void Save();
|
||||
|
||||
private:
|
||||
Ui_NetworkRemoteSettingsPage* ui_;
|
||||
};
|
||||
|
||||
#endif // NETWORKREMOTESETTINGSPAGE_H
|
123
src/ui/networkremotesettingspage.ui
Normal file
123
src/ui/networkremotesettingspage.ui
Normal file
@ -0,0 +1,123 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>NetworkRemoteSettingsPage</class>
|
||||
<widget class="QWidget" name="NetworkRemoteSettingsPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>475</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Network Remote</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="use_remote">
|
||||
<property name="text">
|
||||
<string>Use Remotecontrol</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="use_remote_container" native="true">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<property name="leftMargin">
|
||||
<number>24</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_remote_port">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>171</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::LeftToRight</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Port</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="remote_port">
|
||||
<property name="maximum">
|
||||
<number>65535</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>8080</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="only_non_public_ip">
|
||||
<property name="toolTip">
|
||||
<string>Only accept connections from clients within the ip ranges:
|
||||
10.x.x.x
|
||||
172.16.0.0 - 172.31.255.255
|
||||
192.168.x.x</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Accept non public clients only</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>36</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>use_remote</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
<receiver>use_remote_container</receiver>
|
||||
<slot>setEnabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>199</x>
|
||||
<y>19</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>199</x>
|
||||
<y>60</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
@ -23,6 +23,7 @@
|
||||
#include "iconloader.h"
|
||||
#include "playbacksettingspage.h"
|
||||
#include "networkproxysettingspage.h"
|
||||
#include "networkremotesettingspage.h"
|
||||
#include "notificationssettingspage.h"
|
||||
#include "mainwindow.h"
|
||||
#include "settingsdialog.h"
|
||||
@ -130,6 +131,7 @@ SettingsDialog::SettingsDialog(Application* app, BackgroundStreams* streams, QWi
|
||||
AddPage(Page_Library, new LibrarySettingsPage(this), general);
|
||||
AddPage(Page_Proxy, new NetworkProxySettingsPage(this), general);
|
||||
AddPage(Page_Transcoding, new TranscoderSettingsPage(this), general);
|
||||
AddPage(Page_NetworkRemote, new NetworkRemoteSettingsPage(this), general);
|
||||
|
||||
#ifdef HAVE_WIIMOTEDEV
|
||||
AddPage(Page_Wiimotedev, new WiimoteSettingsPage(this), general);
|
||||
|
@ -63,6 +63,7 @@ public:
|
||||
Page_GlobalShortcuts,
|
||||
Page_GlobalSearch,
|
||||
Page_Appearance,
|
||||
Page_NetworkRemote,
|
||||
Page_Notifications,
|
||||
Page_Library,
|
||||
Page_Lastfm,
|
||||
|
Loading…
x
Reference in New Issue
Block a user