miniupnpc-libevent: subscribe and receive UPNP events !
enable with -DENABLE_UPNP_EVENTS libevent need to support SUBSCRIBE and NOTIFY requests see https://github.com/miniupnp/libevent/tree/upnp_ext
This commit is contained in:
parent
64578041bd
commit
7f47555c66
|
@ -13,6 +13,8 @@ CFLAGS += -D_BSD_SOURCE
|
||||||
CFLAGS += -D_POSIX_C_SOURCE=200112L
|
CFLAGS += -D_POSIX_C_SOURCE=200112L
|
||||||
CFLAGS += -I/usr/local/include
|
CFLAGS += -I/usr/local/include
|
||||||
|
|
||||||
|
#CFLAGS += -DENABLE_UPNP_EVENTS
|
||||||
|
|
||||||
LDFLAGS = -levent
|
LDFLAGS = -levent
|
||||||
LDFLAGS += -L/usr/local/lib
|
LDFLAGS += -L/usr/local/lib
|
||||||
|
|
||||||
|
|
|
@ -411,6 +411,30 @@ static void upnpc_desc_received(struct evhttp_request * req, void * pvoid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef ENABLE_UPNP_EVENTS
|
||||||
|
static void upnpc_subscribe_response(struct evhttp_request * req, void * pvoid)
|
||||||
|
{
|
||||||
|
size_t len;
|
||||||
|
unsigned char * data;
|
||||||
|
struct evbuffer * input_buffer;
|
||||||
|
upnpc_device_t * d = (upnpc_device_t *)pvoid;
|
||||||
|
|
||||||
|
input_buffer = evhttp_request_get_input_buffer(req);
|
||||||
|
len = evbuffer_get_length(input_buffer);
|
||||||
|
data = evbuffer_pullup(input_buffer, len);
|
||||||
|
debug_printf("%s %d (%d bytes)\n", __func__, evhttp_request_get_response_code(req), (int)len);
|
||||||
|
d->state &= ~UPNPC_DEVICE_SOAP_REQ;
|
||||||
|
if(evhttp_request_get_response_code(req) != HTTP_OK) {
|
||||||
|
/* TODO ERROR */
|
||||||
|
} else {
|
||||||
|
const char * sid;
|
||||||
|
struct evkeyvalq * headers = evhttp_request_get_input_headers(req);
|
||||||
|
sid = evhttp_find_header(headers, "sid");
|
||||||
|
debug_printf("SID=%s\n", sid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif /* ENABLE_UPNP_EVENTS */
|
||||||
|
|
||||||
static void upnpc_soap_response(struct evhttp_request * req, void * pvoid)
|
static void upnpc_soap_response(struct evhttp_request * req, void * pvoid)
|
||||||
{
|
{
|
||||||
size_t len;
|
size_t len;
|
||||||
|
@ -606,6 +630,52 @@ static int upnpc_send_soap_request(upnpc_device_t * p, const char * url,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef ENABLE_UPNP_EVENTS
|
||||||
|
void upnpc_event_conn_req(struct evhttp_request * req, void * data)
|
||||||
|
{
|
||||||
|
size_t len;
|
||||||
|
char * xml_data;
|
||||||
|
struct evbuffer * input_buffer;
|
||||||
|
struct evkeyvalq * headers;
|
||||||
|
const char * sid;
|
||||||
|
const char * nts;
|
||||||
|
const char * seq;
|
||||||
|
struct NameValueParserData parsed_data;
|
||||||
|
struct NameValue * nv;
|
||||||
|
upnpc_device_t * d = (upnpc_device_t *)data;
|
||||||
|
|
||||||
|
debug_printf("%s(%p, %p)\n", __func__, req, d);
|
||||||
|
input_buffer = evhttp_request_get_input_buffer(req);
|
||||||
|
len = evbuffer_get_length(input_buffer);
|
||||||
|
if(len == 0) {
|
||||||
|
evhttp_send_reply(req, 406, "Not Acceptable", NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
xml_data = (char *)evbuffer_pullup(input_buffer, len);
|
||||||
|
headers = evhttp_request_get_input_headers(req);
|
||||||
|
sid = evhttp_find_header(headers, "sid");
|
||||||
|
nts = evhttp_find_header(headers, "nts");
|
||||||
|
seq = evhttp_find_header(headers, "seq");
|
||||||
|
debug_printf("SID=%s NTS=%s SEQ=%s\n", sid, nts, seq);
|
||||||
|
if(sid == NULL || nts == NULL || seq == NULL) {
|
||||||
|
evhttp_send_reply(req, 412, "Precondition Failed", NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/*debug_printf("%.*s\n", len, xml_data);*/
|
||||||
|
ParseNameValue(xml_data, len, &parsed_data);
|
||||||
|
for(nv = parsed_data.l_head; nv != NULL; nv = nv->l_next) {
|
||||||
|
if(d->parent->value_changed_cb) {
|
||||||
|
d->parent->value_changed_cb(d->parent, d, d->parent->cb_data, d->conn_service_type, nv->name, nv->value);
|
||||||
|
} else {
|
||||||
|
debug_printf("%s=%s\n", nv->name, nv->value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClearNameValueList(&parsed_data);
|
||||||
|
/* response : 200 OK */
|
||||||
|
evhttp_send_reply(req, 200, "OK", NULL);
|
||||||
|
}
|
||||||
|
#endif /* ENABLE_UPNP_EVENTS */
|
||||||
|
|
||||||
/* public functions */
|
/* public functions */
|
||||||
int upnpc_init(upnpc_t * p, struct event_base * base, const char * multicastif,
|
int upnpc_init(upnpc_t * p, struct event_base * base, const char * multicastif,
|
||||||
upnpc_callback_fn ready_cb, upnpc_callback_fn soap_cb, void * cb_data)
|
upnpc_callback_fn ready_cb, upnpc_callback_fn soap_cb, void * cb_data)
|
||||||
|
@ -693,6 +763,15 @@ int upnpc_set_local_address(upnpc_t * p, const char * address, uint16_t port)
|
||||||
return UPNPC_OK;
|
return UPNPC_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef ENABLE_UPNP_EVENTS
|
||||||
|
int upnpc_set_event_callback(upnpc_t * p, upnpc_event_callback_fn cb)
|
||||||
|
{
|
||||||
|
if(!p || !cb) return UPNPC_ERR_INVALID_ARGS;
|
||||||
|
p->value_changed_cb = cb;
|
||||||
|
return UPNPC_OK;
|
||||||
|
}
|
||||||
|
#endif /* ENABLE_UPNP_EVENTS */
|
||||||
|
|
||||||
static void upnpc_device_finalize(upnpc_device_t * d)
|
static void upnpc_device_finalize(upnpc_device_t * d)
|
||||||
{
|
{
|
||||||
d->state = 0;
|
d->state = 0;
|
||||||
|
@ -743,9 +822,71 @@ int upnpc_finalize(upnpc_t * p)
|
||||||
p->devices = d->next;
|
p->devices = d->next;
|
||||||
free(d);
|
free(d);
|
||||||
}
|
}
|
||||||
|
free(p->local_address);
|
||||||
|
p->local_address = NULL;
|
||||||
|
#ifdef ENABLE_UPNP_EVENTS
|
||||||
|
if(p->http_server) {
|
||||||
|
evhttp_free(p->http_server);
|
||||||
|
p->http_server = NULL;
|
||||||
|
}
|
||||||
|
#endif /* ENABLE_UPNP_EVENTS */
|
||||||
return UPNPC_OK;
|
return UPNPC_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef ENABLE_UPNP_EVENTS
|
||||||
|
int upnpc_event_subscribe(upnpc_device_t * p)
|
||||||
|
{
|
||||||
|
char hostname[MAXHOSTNAMELEN+1];
|
||||||
|
char hostname_port[MAXHOSTNAMELEN+1+6];
|
||||||
|
unsigned short port;
|
||||||
|
char * path;
|
||||||
|
unsigned int scope_id;
|
||||||
|
struct evhttp_request * req;
|
||||||
|
struct evkeyvalq * headers;
|
||||||
|
char callback_header[7+15+1+5+9+2+1];
|
||||||
|
|
||||||
|
if(p->parent->http_server == NULL) {
|
||||||
|
/* HTTP server to receive event notifications */
|
||||||
|
p->parent->http_server = evhttp_new(p->parent->base);
|
||||||
|
if(p->parent->http_server == NULL) {
|
||||||
|
debug_printf("evhttp_new() FAILED\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
evhttp_set_allowed_methods(p->parent->http_server, EVHTTP_REQ_NOTIFY);
|
||||||
|
evhttp_set_cb(p->parent->http_server, "/evt_conn", upnpc_event_conn_req, p);
|
||||||
|
if(evhttp_bind_socket(p->parent->http_server, p->parent->local_address, p->parent->local_port) < 0) {
|
||||||
|
debug_printf("evhttp_bind_socket() FAILED\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*if(!parseURL(p->event_cif_url, hostname, &port, &path, &scope_id)) {*/
|
||||||
|
if(!parseURL(p->event_conn_url, hostname, &port, &path, &scope_id)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if(port != 80)
|
||||||
|
snprintf(hostname_port, sizeof(hostname_port), "%s:%hu", hostname, port);
|
||||||
|
else
|
||||||
|
strncpy(hostname_port, hostname, sizeof(hostname_port));
|
||||||
|
if(p->soap_conn == NULL) {
|
||||||
|
p->soap_conn = evhttp_connection_base_new(p->parent->base, NULL, hostname, port);
|
||||||
|
}
|
||||||
|
req = evhttp_request_new(upnpc_subscribe_response, p);
|
||||||
|
headers = evhttp_request_get_output_headers(req);
|
||||||
|
/*buffer = evhttp_request_get_output_buffer(req);*/
|
||||||
|
evhttp_add_header(headers, "Host", hostname_port);
|
||||||
|
/*evhttp_add_header(headers, "User-Agent", "***");*/
|
||||||
|
snprintf(callback_header, sizeof(callback_header), "<http://%s:%hu/evt_conn>", p->parent->local_address, p->parent->local_port);
|
||||||
|
evhttp_add_header(headers, "Callback", callback_header);
|
||||||
|
evhttp_add_header(headers, "NT", "upnp:event");
|
||||||
|
/*evhttp_add_header(headers, "NTS", "");*/
|
||||||
|
evhttp_add_header(headers, "Timeout", "3600");
|
||||||
|
/*evbuffer_add(buffer, body, body_len);*/
|
||||||
|
evhttp_make_request(p->soap_conn, req, EVHTTP_REQ_SUBSCRIBE, path);
|
||||||
|
p->state |= UPNPC_DEVICE_SOAP_REQ;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif /* ENABLE_UPNP_EVENTS */
|
||||||
|
|
||||||
int upnpc_get_external_ip_address(upnpc_device_t * p)
|
int upnpc_get_external_ip_address(upnpc_device_t * p)
|
||||||
{
|
{
|
||||||
return upnpc_send_soap_request(p, p->control_conn_url,
|
return upnpc_send_soap_request(p, p->control_conn_url,
|
||||||
|
|
|
@ -49,6 +49,9 @@ typedef struct upnpc_device upnpc_device_t;
|
||||||
typedef struct upnpc upnpc_t;
|
typedef struct upnpc upnpc_t;
|
||||||
|
|
||||||
typedef void(* upnpc_callback_fn)(int, upnpc_t *, upnpc_device_t *, void *);
|
typedef void(* upnpc_callback_fn)(int, upnpc_t *, upnpc_device_t *, void *);
|
||||||
|
#ifdef ENABLE_UPNP_EVENTS
|
||||||
|
typedef void(* upnpc_event_callback_fn)(upnpc_t *, upnpc_device_t *, void *, const char *, const char *, const char *);
|
||||||
|
#endif /* ENABLE_UPNP_EVENTS */
|
||||||
|
|
||||||
struct upnpc_device {
|
struct upnpc_device {
|
||||||
upnpc_t * parent;
|
upnpc_t * parent;
|
||||||
|
@ -76,6 +79,10 @@ struct upnpc {
|
||||||
upnpc_callback_fn ready_cb;
|
upnpc_callback_fn ready_cb;
|
||||||
upnpc_callback_fn soap_cb;
|
upnpc_callback_fn soap_cb;
|
||||||
void * cb_data;
|
void * cb_data;
|
||||||
|
#ifdef ENABLE_UPNP_EVENTS
|
||||||
|
struct evhttp * http_server;
|
||||||
|
upnpc_event_callback_fn value_changed_cb;
|
||||||
|
#endif /* ENABLE_UPNP_EVENTS */
|
||||||
char * local_address;
|
char * local_address;
|
||||||
uint16_t local_port;
|
uint16_t local_port;
|
||||||
};
|
};
|
||||||
|
@ -85,10 +92,18 @@ int upnpc_init(upnpc_t * p, struct event_base * base, const char * multicastif,
|
||||||
|
|
||||||
int upnpc_set_local_address(upnpc_t * p, const char * address, uint16_t port);
|
int upnpc_set_local_address(upnpc_t * p, const char * address, uint16_t port);
|
||||||
|
|
||||||
|
#ifdef ENABLE_UPNP_EVENTS
|
||||||
|
int upnpc_set_event_callback(upnpc_t * p, upnpc_event_callback_fn cb);
|
||||||
|
#endif /* ENABLE_UPNP_EVENTS */
|
||||||
|
|
||||||
int upnpc_start(upnpc_t * p);
|
int upnpc_start(upnpc_t * p);
|
||||||
|
|
||||||
int upnpc_finalize(upnpc_t * p);
|
int upnpc_finalize(upnpc_t * p);
|
||||||
|
|
||||||
|
#ifdef ENABLE_UPNP_EVENTS
|
||||||
|
int upnpc_event_subscribe(upnpc_device_t * p);
|
||||||
|
#endif /* ENABLE_UPNP_EVENTS */
|
||||||
|
|
||||||
int upnpc_get_external_ip_address(upnpc_device_t * p);
|
int upnpc_get_external_ip_address(upnpc_device_t * p);
|
||||||
|
|
||||||
int upnpc_get_link_layer_max_rate(upnpc_device_t * p);
|
int upnpc_get_link_layer_max_rate(upnpc_device_t * p);
|
||||||
|
|
|
@ -45,7 +45,11 @@ static void ready(int code, upnpc_t * p, upnpc_device_t * d, void * data)
|
||||||
printf("READY ! %d\n", code);
|
printf("READY ! %d\n", code);
|
||||||
printf(" root_desc_location='%s'\n", d->root_desc_location);
|
printf(" root_desc_location='%s'\n", d->root_desc_location);
|
||||||
/* 1st request */
|
/* 1st request */
|
||||||
|
#ifdef ENABLE_UPNP_EVENTS
|
||||||
|
upnpc_event_subscribe(d);
|
||||||
|
#else
|
||||||
upnpc_get_status_info(d);
|
upnpc_get_status_info(d);
|
||||||
|
#endif /* ENABLE_UPNP_EVENTS */
|
||||||
} else {
|
} else {
|
||||||
printf("DISCOVER ERROR : %d\n", code);
|
printf("DISCOVER ERROR : %d\n", code);
|
||||||
switch(code) {
|
switch(code) {
|
||||||
|
@ -122,6 +126,15 @@ static void soap(int code, upnpc_t * p, upnpc_device_t * d, void * data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef ENABLE_UPNP_EVENTS
|
||||||
|
/* event callback */
|
||||||
|
static void event_callback(upnpc_t * p, upnpc_device_t * d, void * data,
|
||||||
|
const char * service_id, const char * property_name, const char * property_value)
|
||||||
|
{
|
||||||
|
printf("PROPERTY VALUE CHANGE (service=%s): %s=%s\n", service_id, property_name, property_value);
|
||||||
|
}
|
||||||
|
#endif /* ENABLE_UPNP_EVENTS */
|
||||||
|
|
||||||
/* use a UDP "connection" to 8.8.8.8
|
/* use a UDP "connection" to 8.8.8.8
|
||||||
* to retrieve local address */
|
* to retrieve local address */
|
||||||
int find_local_address(void)
|
int find_local_address(void)
|
||||||
|
@ -216,6 +229,9 @@ int main(int argc, char * * argv)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
upnpc_set_local_address(&upnp, local_address, 50000);
|
upnpc_set_local_address(&upnp, local_address, 50000);
|
||||||
|
#ifdef ENABLE_UPNP_EVENTS
|
||||||
|
upnpc_set_event_callback(&upnp, event_callback);
|
||||||
|
#endif /* ENABLE_UPNP_EVENTS */
|
||||||
if(upnpc_start(&upnp) != UPNPC_OK) {
|
if(upnpc_start(&upnp) != UPNPC_OK) {
|
||||||
fprintf(stderr, "upnp_start() failed\n");
|
fprintf(stderr, "upnp_start() failed\n");
|
||||||
return 1;
|
return 1;
|
||||||
|
|
Loading…
Reference in New Issue