1 /*
2  *  tvheadend, HTSP interface
3  *  Copyright (C) 2007 Andreas Öman
4  *
5  *  This program is free software: you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation, either version 3 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "tvheadend.h"
20 #include "atomic.h"
21 #include "config.h"
22 #include "api.h"
23 #include "channels.h"
24 #include "subscriptions.h"
25 #include "tcp.h"
26 #include "packet.h"
27 #include "access.h"
28 #include "htsp_server.h"
29 #include "streaming.h"
30 #include "htsmsg_binary.h"
31 #include "epg.h"
32 #include "plumbing/tsfix.h"
33 #include "imagecache.h"
34 #include "descrambler.h"
35 #include "descrambler/caid.h"
36 #include "notify.h"
37 #include "htsmsg_json.h"
38 #include "lang_codes.h"
39 #if ENABLE_TIMESHIFT
40 #include "timeshift.h"
41 #endif
42 
43 #include <pthread.h>
44 #include <assert.h>
45 #include <stdio.h>
46 #include <unistd.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include <stdarg.h>
50 #include <fcntl.h>
51 #include <errno.h>
52 #include <signal.h>
53 #include <netinet/in.h>
54 #include <netinet/tcp.h>
55 #include <arpa/inet.h>
56 #include <sys/types.h>
57 #include <sys/socket.h>
58 #include <sys/stat.h>
59 #include <sys/time.h>
60 #include <limits.h>
61 #include "settings.h"
62 
63 /* **************************************************************************
64  * Datatypes and variables
65  * *************************************************************************/
66 
67 static void *htsp_server, *htsp_server_2;
68 
69 #define HTSP_PROTO_VERSION 27
70 
71 #define HTSP_ASYNC_OFF  0x00
72 #define HTSP_ASYNC_ON   0x01
73 #define HTSP_ASYNC_EPG  0x02
74 
75 #define HTSP_ASYNC_AUX_CH        0x01
76 #define HTSP_ASYNC_AUX_CHTAG     0x02
77 #define HTSP_ASYNC_AUX_CHTAG_DEL 0x03
78 #define HTSP_ASYNC_AUX_DVR       0x04
79 #define HTSP_ASYNC_AUX_AUTOREC   0x05
80 #define HTSP_ASYNC_AUX_TIMEREC   0x06
81 #define HTSP_ASYNC_AUX_EPG       0x07
82 
83 #define HTSP_ASYNC_EPG_INTERVAL 30
84 
85 #define HTSP_PRIV_MASK (ACCESS_HTSP_STREAMING)
86 
87 extern char *dvr_storage;
88 
89 LIST_HEAD(htsp_connection_list, htsp_connection);
90 LIST_HEAD(htsp_subscription_list, htsp_subscription);
91 LIST_HEAD(htsp_file_list, htsp_file);
92 
93 TAILQ_HEAD(htsp_msg_queue, htsp_msg);
94 TAILQ_HEAD(htsp_msg_q_queue, htsp_msg_q);
95 
96 static struct htsp_connection_list htsp_async_connections;
97 static struct htsp_connection_list htsp_connections;
98 
99 static void htsp_streaming_input(void *opaque, streaming_message_t *sm);
100 static htsmsg_t *htsp_streaming_input_info(void *opaque, htsmsg_t *list);
101 const char * _htsp_get_subscription_status(int smcode);
102 static void htsp_epg_send_waiting(struct htsp_connection *, int64_t mintime);
103 
104 static streaming_ops_t htsp_streaming_input_ops = {
105   .st_cb   = htsp_streaming_input,
106   .st_info = htsp_streaming_input_info
107 };
108 
109 /**
110  *
111  */
112 typedef struct htsp_msg {
113   TAILQ_ENTRY(htsp_msg) hm_link;
114 
115   htsmsg_t *hm_msg;
116   int hm_payloadsize;         /* For maintaining stats about streaming
117 				 buffer depth */
118 
119   pktbuf_t *hm_pb;      /* For keeping reference to packet payload.
120 			   hm_msg can contain messages that points
121 			   to packet payload so to avoid copy we
122 			   keep a reference here */
123 } htsp_msg_t;
124 
125 
126 /**
127  *
128  */
129 typedef struct htsp_msg_q {
130   struct htsp_msg_queue hmq_q;
131 
132   TAILQ_ENTRY(htsp_msg_q) hmq_link;
133   int hmq_strict_prio;      /* Serve this queue 'til it's empty */
134   int hmq_length;
135   int hmq_payload;          /* Bytes of streaming payload that's enqueued */
136   int hmq_dead;
137 } htsp_msg_q_t;
138 
139 /**
140  *
141  */
142 typedef struct htsp_connection {
143   LIST_ENTRY(htsp_connection) htsp_link;
144 
145   int htsp_fd;
146   struct sockaddr_storage *htsp_peer;
147 
148   uint32_t htsp_version;
149 
150   char *htsp_logname;
151   char *htsp_peername;
152   char *htsp_username;
153   char *htsp_clientname;
154   char *htsp_language; // for async updates
155 
156   int64_t  htsp_epg_window;      // only send async epg updates within this window (seconds)
157   int64_t  htsp_epg_lastupdate;  // last update time for async epg events
158   mtimer_t htsp_epg_timer;       // timer for async epg updates
159 
160   /**
161    * Async mode
162    */
163   int htsp_async_mode;
164   LIST_ENTRY(htsp_connection) htsp_async_link;
165 
166   /**
167    * Writer thread
168    */
169   pthread_t htsp_writer_thread;
170 
171   int htsp_writer_run;
172 
173   struct htsp_msg_q_queue htsp_active_output_queues;
174 
175   pthread_mutex_t htsp_out_mutex;
176   tvh_cond_t htsp_out_cond;
177 
178   htsp_msg_q_t htsp_hmq_ctrl;
179   htsp_msg_q_t htsp_hmq_epg;
180   htsp_msg_q_t htsp_hmq_qstatus;
181 
182   struct htsp_subscription_list htsp_subscriptions;
183   struct htsp_subscription_list htsp_dead_subscriptions;
184   struct htsp_file_list htsp_files;
185   int htsp_file_id;
186 
187   access_t *htsp_granted_access;
188 
189   uint8_t htsp_challenge[32];
190 
191 } htsp_connection_t;
192 
193 
194 /**
195  *
196  */
197 typedef struct htsp_subscription {
198   htsp_connection_t *hs_htsp;
199 
200   LIST_ENTRY(htsp_subscription) hs_link;
201 
202   int hs_sid;  /* Subscription ID (set by client) */
203 
204   th_subscription_t *hs_s; // Temporary
205   int                hs_s_bytes_out;
206   mtimer_t           hs_s_bytes_out_timer;
207 
208   streaming_target_t hs_input;
209   profile_chain_t    hs_prch;
210 
211   htsp_msg_q_t hs_q;
212 
213   int64_t hs_last_report; /* Last queue status report sent */
214 
215   int hs_dropstats[PKT_NTYPES];
216 
217   int hs_wait_for_video;
218 
219   int hs_90khz;
220 
221   int hs_queue_depth;
222 
223 #define NUM_FILTERED_STREAMS (64*8)
224 
225   uint64_t hs_filtered_streams[8]; // one bit per stream
226 
227   int hs_first;
228 
229   uint32_t hs_data_errors;
230 
231 } htsp_subscription_t;
232 
233 
234 /**
235  *
236  */
237 typedef struct htsp_file {
238   LIST_ENTRY(htsp_file) hf_link;
239   int hf_id;          // ID sent to client
240   int hf_fd;          // Our file descriptor
241   char *hf_path;      // For logging
242   uint32_t  hf_de_id; // Associated dvr entry
243   th_subscription_t *hf_subscription;
244 } htsp_file_t;
245 
246 #define HTSP_DEFAULT_QUEUE_DEPTH 500000
247 
248 /* **************************************************************************
249  * Support routines
250  * *************************************************************************/
251 
252 static void
htsp_trace(htsp_connection_t * htsp,int subsystem,const char * prefix,htsmsg_t * m)253 htsp_trace(htsp_connection_t *htsp, int subsystem,
254            const char *prefix, htsmsg_t *m)
255 {
256   htsbuf_queue_t q;
257   char *s;
258   htsbuf_queue_init(&q, 0);
259   htsmsg_json_serialize(m, &q, 0);
260   s = htsbuf_to_string(&q);
261   htsbuf_queue_flush(&q);
262   tvhtrace(subsystem, "%s - %s '%s'", htsp->htsp_logname, prefix, s);
263   free(s);
264 }
265 
266 static void
htsp_disable_stream(htsp_subscription_t * hs,unsigned int id)267 htsp_disable_stream(htsp_subscription_t *hs, unsigned int id)
268 {
269   if(id < NUM_FILTERED_STREAMS)
270     hs->hs_filtered_streams[id / 64] |= 1 << (id & 63);
271 }
272 
273 
274 static void
htsp_enable_stream(htsp_subscription_t * hs,unsigned int id)275 htsp_enable_stream(htsp_subscription_t *hs, unsigned int id)
276 {
277   if(id < NUM_FILTERED_STREAMS)
278     hs->hs_filtered_streams[id / 64] &= ~(1 << (id & 63));
279 }
280 
281 
282 static inline int
htsp_is_stream_enabled(htsp_subscription_t * hs,unsigned int id)283 htsp_is_stream_enabled(htsp_subscription_t *hs, unsigned int id)
284 {
285   if(id < NUM_FILTERED_STREAMS)
286     return !(hs->hs_filtered_streams[id / 64] & (1 << (id & 63)));
287   return 1;
288 }
289 
290 static inline int
htsp_anonymize(htsp_connection_t * htsp)291 htsp_anonymize(htsp_connection_t *htsp)
292 {
293   return (htsp->htsp_granted_access->aa_rights & ACCESS_HTSP_ANONYMIZE) != 0;
294 }
295 
296 /**
297  *
298  */
299 static void
htsp_update_logname(htsp_connection_t * htsp)300 htsp_update_logname(htsp_connection_t *htsp)
301 {
302   char buf[100];
303 
304   snprintf(buf, sizeof(buf), "%s%s%s%s%s%s",
305 	   htsp->htsp_peername,
306 	   htsp->htsp_username || htsp->htsp_clientname ? " [ " : "",
307 	   htsp->htsp_username ?: "",
308 	   htsp->htsp_username && htsp->htsp_clientname ? " | " : "",
309 	   htsp->htsp_clientname ?: "",
310 	   htsp->htsp_username || htsp->htsp_clientname ? " ]" : "");
311 
312   tvh_str_update(&htsp->htsp_logname, buf);
313 }
314 
315 /**
316  *
317  */
318 static void
htsp_msg_destroy(htsp_msg_t * hm)319 htsp_msg_destroy(htsp_msg_t *hm)
320 {
321   htsmsg_destroy(hm->hm_msg);
322   if(hm->hm_pb != NULL)
323     pktbuf_ref_dec(hm->hm_pb);
324   free(hm);
325 }
326 
327 /**
328  *
329  */
330 static void
htsp_init_queue(htsp_msg_q_t * hmq,int strict_prio)331 htsp_init_queue(htsp_msg_q_t *hmq, int strict_prio)
332 {
333   TAILQ_INIT(&hmq->hmq_q);
334   hmq->hmq_length = 0;
335   hmq->hmq_strict_prio = strict_prio;
336 }
337 
338 /**
339  *
340  */
341 static void
htsp_flush_queue(htsp_connection_t * htsp,htsp_msg_q_t * hmq,int dead)342 htsp_flush_queue(htsp_connection_t *htsp, htsp_msg_q_t *hmq, int dead)
343 {
344   htsp_msg_t *hm;
345 
346   pthread_mutex_lock(&htsp->htsp_out_mutex);
347 
348   if(hmq->hmq_length)
349     TAILQ_REMOVE(&htsp->htsp_active_output_queues, hmq, hmq_link);
350 
351   while((hm = TAILQ_FIRST(&hmq->hmq_q)) != NULL) {
352     TAILQ_REMOVE(&hmq->hmq_q, hm, hm_link);
353     htsp_msg_destroy(hm);
354   }
355 
356   // reset
357   hmq->hmq_length = 0;
358   hmq->hmq_payload = 0;
359   hmq->hmq_dead = dead;
360   pthread_mutex_unlock(&htsp->htsp_out_mutex);
361 }
362 
363 /**
364  *
365  */
366 static void
htsp_subscription_destroy(htsp_connection_t * htsp,htsp_subscription_t * hs)367 htsp_subscription_destroy(htsp_connection_t *htsp, htsp_subscription_t *hs)
368 {
369   th_subscription_t *ts = hs->hs_s;
370 
371   hs->hs_s = NULL;
372   mtimer_disarm(&hs->hs_s_bytes_out_timer);
373 
374   LIST_REMOVE(hs, hs_link);
375   LIST_INSERT_HEAD(&htsp->htsp_dead_subscriptions, hs, hs_link);
376 
377   subscription_unsubscribe(ts, UNSUBSCRIBE_FINAL);
378 
379   if(hs->hs_prch.prch_st != NULL)
380     profile_chain_close(&hs->hs_prch);
381 
382   htsp_flush_queue(htsp, &hs->hs_q, 1);
383 }
384 
385 /**
386  *
387  */
388 static void
htsp_subscription_free(htsp_connection_t * htsp,htsp_subscription_t * hs)389 htsp_subscription_free(htsp_connection_t *htsp, htsp_subscription_t *hs)
390 {
391   LIST_REMOVE(hs, hs_link);
392   htsp_flush_queue(htsp, &hs->hs_q, 1);
393   free(hs);
394 }
395 
396 /**
397  *
398  */
399 static void
htsp_send(htsp_connection_t * htsp,htsmsg_t * m,pktbuf_t * pb,htsp_msg_q_t * hmq,int payloadsize)400 htsp_send(htsp_connection_t *htsp, htsmsg_t *m, pktbuf_t *pb,
401 	  htsp_msg_q_t *hmq, int payloadsize)
402 {
403   htsp_msg_t *hm = malloc(sizeof(htsp_msg_t));
404 
405   hm->hm_msg = m;
406   hm->hm_pb = pb;
407   if(pb != NULL)
408     pktbuf_ref_inc(pb);
409   hm->hm_payloadsize = payloadsize;
410 
411   pthread_mutex_lock(&htsp->htsp_out_mutex);
412 
413   assert(!hmq->hmq_dead);
414 
415   TAILQ_INSERT_TAIL(&hmq->hmq_q, hm, hm_link);
416 
417   if(hmq->hmq_length == 0) {
418     /* Activate queue */
419 
420     if(hmq->hmq_strict_prio) {
421       TAILQ_INSERT_HEAD(&htsp->htsp_active_output_queues, hmq, hmq_link);
422     } else {
423       TAILQ_INSERT_TAIL(&htsp->htsp_active_output_queues, hmq, hmq_link);
424     }
425   }
426 
427   hmq->hmq_length++;
428   hmq->hmq_payload += payloadsize;
429   tvh_cond_signal(&htsp->htsp_out_cond, 0);
430   pthread_mutex_unlock(&htsp->htsp_out_mutex);
431 }
432 
433 /**
434  *
435  */
436 static void
htsp_send_subscription(htsp_connection_t * htsp,htsmsg_t * m,pktbuf_t * pb,htsp_subscription_t * hs,int payloadsize)437 htsp_send_subscription(htsp_connection_t *htsp, htsmsg_t *m, pktbuf_t *pb,
438 	               htsp_subscription_t *hs, int payloadsize)
439 {
440   if (tvhtrace_enabled()) {
441     char buf[64];
442     size_t l = 0;
443     tvh_strlcatf(buf, sizeof(buf), l, "subscription %i", hs->hs_sid);
444     if (payloadsize)
445       tvh_strlcatf(buf, sizeof(buf), l, " (payload %d)", payloadsize);
446     htsp_trace(htsp, LS_HTSP_SUB, buf, m);
447   }
448 
449   htsp_send(htsp, m, pb, &hs->hs_q, payloadsize);
450 }
451 
452 /**
453  *
454  */
455 static void
htsp_send_message(htsp_connection_t * htsp,htsmsg_t * m,htsp_msg_q_t * hmq)456 htsp_send_message(htsp_connection_t *htsp, htsmsg_t *m, htsp_msg_q_t *hmq)
457 {
458   if (tvhtrace_enabled()) {
459     const char *qname = "answer";
460     if (hmq == &htsp->htsp_hmq_qstatus)
461       qname = "status";
462     htsp_trace(htsp, LS_HTSP_ANS, qname, m);
463   }
464 
465   htsp_send(htsp, m, NULL, hmq ?: &htsp->htsp_hmq_ctrl, 0);
466 }
467 
468 /**
469  * Simple function to respond with an error
470  */
471 static htsmsg_t *
htsp_error(htsp_connection_t * htsp,const char * errstr)472 htsp_error(htsp_connection_t *htsp, const char *errstr)
473 {
474   htsmsg_t *r = htsmsg_create_map();
475   htsmsg_add_str(r, "error", tvh_gettext_lang(htsp->htsp_language, errstr));
476   return r;
477 }
478 
479 /**
480  * Simple function to respond with an success
481  */
482 static htsmsg_t *
htsp_success(void)483 htsp_success(void)
484 {
485   htsmsg_t *r = htsmsg_create_map();
486   htsmsg_add_u32(r, "success", 1);
487   return r;
488 }
489 
490 /**
491  *
492  */
493 static void
htsp_reply(htsp_connection_t * htsp,htsmsg_t * in,htsmsg_t * out)494 htsp_reply(htsp_connection_t *htsp, htsmsg_t *in, htsmsg_t *out)
495 {
496   uint32_t seq;
497 
498   if(!htsmsg_get_u32(in, "seq", &seq))
499     htsmsg_add_u32(out, "seq", seq);
500 
501   htsp_send_message(htsp, out, NULL);
502 }
503 
504 /**
505  * Update challenge
506  */
507 static int
htsp_generate_challenge(htsp_connection_t * htsp)508 htsp_generate_challenge(htsp_connection_t *htsp)
509 {
510   int fd, n;
511 
512   if((fd = tvh_open("/dev/urandom", O_RDONLY, 0)) < 0)
513     return -1;
514 
515   n = read(fd, &htsp->htsp_challenge, 32);
516   close(fd);
517   return n != 32;
518 }
519 
520 /**
521  * Check if user can access the channel
522  */
523 static inline int
htsp_user_access_channel(htsp_connection_t * htsp,channel_t * ch)524 htsp_user_access_channel(htsp_connection_t *htsp, channel_t *ch)
525 {
526   if (!ch || !ch->ch_enabled || LIST_FIRST(&ch->ch_services) == NULL) /* Don't pass unplayable channels to clients */
527     return 0;
528   if (!htsp)
529     return 1;
530   return channel_access(ch, htsp->htsp_granted_access, 0);
531 }
532 
533 static const char *
htsp_dvr_config_name(htsp_connection_t * htsp,const char * config_name)534 htsp_dvr_config_name( htsp_connection_t *htsp, const char *config_name )
535 {
536   dvr_config_t *cfg = NULL, *cfg2;
537   access_t *perm = htsp->htsp_granted_access;
538   htsmsg_field_t *f;
539   const char *uuid;
540   static char ubuf[UUID_HEX_SIZE];
541 
542   lock_assert(&global_lock);
543 
544   config_name = config_name ?: "";
545 
546   if (perm->aa_dvrcfgs == NULL)
547     return config_name; /* no change */
548 
549   HTSMSG_FOREACH(f, perm->aa_dvrcfgs) {
550     uuid = htsmsg_field_get_str(f) ?: "";
551     if (strcmp(uuid, config_name) == 0)
552       return config_name;
553     cfg2 = dvr_config_find_by_uuid(uuid);
554     if (cfg2 && strcmp(cfg2->dvr_config_name, config_name) == 0)
555       return uuid;
556     if (!cfg)
557       cfg = cfg2;
558   }
559 
560   if (!cfg && perm->aa_username)
561     tvhinfo(LS_HTSP, "User '%s' has no valid dvr config in ACL, using default...", perm->aa_username);
562 
563   return cfg ? idnode_uuid_as_str(&cfg->dvr_id, ubuf) : NULL;
564 }
565 
566 /**
567  * Converts htsp input to internal API
568  * @param htsp, current htsp connection
569  * @param in, the htsp message input from client
570  * @param autorec, true for autorecs, false for timerecs
571  * @param add, true for new instances, false for update calls
572  * @return the htsmsg_t config to be added or updated with idnode
573  */
574 static htsmsg_t *
htsp_serierec_convert(htsp_connection_t * htsp,htsmsg_t * in,channel_t * ch,int autorec,int add)575 htsp_serierec_convert(htsp_connection_t *htsp, htsmsg_t *in, channel_t *ch, int autorec, int add)
576 {
577   htsmsg_t *conf,*days;
578   uint32_t u32;
579   int64_t s64;
580   int32_t approx_time, start, start_window, s32;
581   int retval;
582   const char *str;
583   char ubuf[UUID_HEX_SIZE];
584 
585   conf = htsmsg_create_map();
586   days = htsmsg_create_list();
587 
588   if (autorec) { // autorec specific
589     if (!(retval = htsmsg_get_u32(in, "minduration", &u32)) || add)
590       htsmsg_add_u32(conf, "minduration", !retval ? u32 : 0);  // 0 = any
591     if (!(retval = htsmsg_get_u32(in, "maxduration", &u32)) || add)
592       htsmsg_add_u32(conf, "maxduration", !retval ? u32 : 0);  // 0 = any
593     if (!(retval = htsmsg_get_u32(in, "fulltext", &u32)) || add)
594       htsmsg_add_u32(conf, "fulltext", !retval ? u32 : 0);     // 0 = off
595     if (!(retval = htsmsg_get_u32(in, "dupDetect", &u32)) || add)
596       htsmsg_add_u32(conf, "record", !retval ? u32 : DVR_AUTOREC_RECORD_ALL);
597     if (!(retval = htsmsg_get_u32(in, "maxCount", &u32)) || add)
598       htsmsg_add_u32(conf, "maxcount", !retval ? u32 : 0);     // 0 = unlimited
599     if (!(retval = htsmsg_get_s64(in, "startExtra", &s64)) || add)
600       htsmsg_add_s64(conf, "start_extra", !retval ? (s64 < 0 ? 0 : s64)  : 0); // 0 = dvr config
601     if (!(retval = htsmsg_get_s64(in, "stopExtra", &s64)) || add)
602       htsmsg_add_s64(conf, "stop_extra", !retval ? (s64 < 0 ? 0 : s64) : 0);   // 0 = dvr config
603 
604     if (add) { // for add, stay compatible with older "approxTime
605       if(htsmsg_get_s32(in, "approxTime", &approx_time))
606         approx_time = -1;
607       if(htsmsg_get_s32(in, "start", &start))
608         start = -1;
609       if(htsmsg_get_s32(in, "startWindow", &start_window))
610         start_window = -1;
611       if (start < 0 || start_window < 0)
612         start = start_window = -1;
613       if (start < 0 && approx_time >= 0) {
614         start = approx_time - 15;
615         if (start < 0)
616           start += 24 * 60;
617         start_window = start + 30;
618         if (start_window >= 24 * 60)
619           start_window -= 24 * 60;
620       }
621       htsmsg_add_s32(conf, "start", start >= 0 ? start : -1); // -1 = any time
622       htsmsg_add_s32(conf, "start_window", start_window >= 0 ? start_window : -1); // -1 = any duration
623     }
624     else { // for update, we don't care about "approxTime"
625       if(!htsmsg_get_s32(in, "start", &s32))
626         htsmsg_add_s32(conf, "start", s32 >= 0 ? s32 : -1);        // -1 = any time
627       if(!htsmsg_get_s32(in, "startWindow", &s32))
628         htsmsg_add_s32(conf, "start_window", s32 >= 0 ? s32 : -1); // -1 = any duration
629     }
630   }
631   else { //timerec specific
632     if (!(retval = htsmsg_get_u32(in, "start", &u32)) || add)
633       htsmsg_add_u32(conf, "start", !retval ? u32 : 0);
634     if (!(retval = htsmsg_get_u32(in, "stop", &u32)) || add)
635       htsmsg_add_u32(conf, "stop", !retval ? u32 : 0);
636   }
637 
638   if (!(retval = htsmsg_get_u32(in, "enabled", &u32)) || add)
639     htsmsg_add_u32(conf, "enabled", !retval ? (u32 > 0 ? 1 : 0) : 1); // default on
640   if (!(retval = htsmsg_get_u32(in, "retention", &u32)) || add)
641     htsmsg_add_u32(conf, "retention", !retval ? u32 : DVR_RET_REM_DVRCONFIG);
642   if (!(retval = htsmsg_get_u32(in, "removal", &u32)) || add)
643     htsmsg_add_u32(conf, "removal", !retval ? u32 : DVR_RET_REM_DVRCONFIG);
644   if(!(retval = htsmsg_get_u32(in, "priority", &u32)) || add)
645     htsmsg_add_u32(conf, "pri", !retval ? u32 : DVR_PRIO_NORMAL);
646   if ((str = htsmsg_get_str(in, "name")) || add)
647     htsmsg_add_str(conf, "name", str ?: "");
648   if ((str = htsmsg_get_str(in, "comment")) || add)
649     htsmsg_add_str(conf, "comment", str ?: "");
650   if ((str = htsmsg_get_str(in, "directory")) || add)
651     htsmsg_add_str(conf, "directory", str ?: "");
652   if((str = htsmsg_get_str(in, "title")) || add)
653     htsmsg_add_str(conf, "title", str ?: "");
654 
655   /* Only on creation */
656   if (add) {
657     str = htsp_dvr_config_name(htsp, htsmsg_get_str(in, "configName"));
658     htsmsg_add_str(conf, "config_name", str ?: "");
659     htsmsg_add_str(conf, "owner",   htsp->htsp_granted_access->aa_username ?: "");
660     htsmsg_add_str(conf, "creator", htsp->htsp_granted_access->aa_representative ?: "");
661   } else {
662     str = htsmsg_get_str(in, "configName");
663     if (str) {
664       str = htsp_dvr_config_name(htsp, str);
665       htsmsg_add_str(conf, "config_name", str ?: "");
666     }
667   }
668 
669   /* Weekdays only if present */
670   if(!(retval = htsmsg_get_u32(in, "daysOfWeek", &u32))) {
671     int i;
672     for (i = 0; i < 7; i++)
673       if (u32 & (1 << i))
674         htsmsg_add_u32(days, NULL, i + 1);
675     htsmsg_add_msg(conf, "weekdays", days);  // not set = all days
676   }
677 
678   /* Allow channel to be cleared on update -> any channel */
679   if (ch || !add) {
680     htsmsg_add_str(conf, "channel", ch ? idnode_uuid_as_str(&ch->ch_id, ubuf) : "");
681   }
682   return conf;
683 }
684 
685 /* **************************************************************************
686  * File helpers
687  * *************************************************************************/
688 
689 static uint32_t
htsp_channel_tag_get_identifier(channel_tag_t * ct)690 htsp_channel_tag_get_identifier(channel_tag_t *ct)
691 {
692   static int prev = 0;
693   if (ct->ct_htsp_id == 0)
694     ct->ct_htsp_id = ++prev;
695   return ct->ct_htsp_id;
696 }
697 
698 static channel_tag_t *
htsp_channel_tag_find_by_identifier(htsp_connection_t * htsp,uint32_t id)699 htsp_channel_tag_find_by_identifier(htsp_connection_t *htsp, uint32_t id)
700 {
701   channel_tag_t *ct;
702 
703   TAILQ_FOREACH(ct, &channel_tags, ct_link) {
704     if (!channel_tag_access(ct, htsp->htsp_granted_access, 0))
705       continue;
706     if (id == ct->ct_htsp_id)
707       return ct;
708   }
709   return NULL;
710 }
711 
712 /**
713  *
714  */
715 static htsmsg_t *
htsp_file_open(htsp_connection_t * htsp,const char * path,int fd,dvr_entry_t * de)716 htsp_file_open(htsp_connection_t *htsp, const char *path, int fd, dvr_entry_t *de)
717 {
718   struct stat st;
719 
720   if (fd <= 0) {
721     pthread_mutex_unlock(&global_lock);
722     fd = tvh_open(path, O_RDONLY, 0);
723     tvhdebug(LS_HTSP, "Opening file %s -- %s", path, fd < 0 ? strerror(errno) : "OK");
724     pthread_mutex_lock(&global_lock);
725     if(fd == -1)
726       return htsp_error(htsp, N_("Unable to open file"));
727   }
728 
729   htsp_file_t *hf = calloc(1, sizeof(htsp_file_t));
730   hf->hf_fd = fd;
731   hf->hf_id = ++htsp->htsp_file_id;
732   hf->hf_path = strdup(path);
733   hf->hf_de_id = de ? idnode_get_short_uuid(&de->de_id) : 0;
734 
735   if (de) {
736     const char *charset = de->de_config ? de->de_config->dvr_charset_id : NULL;
737 
738     hf->hf_subscription =
739       subscription_create_from_file("HTSP", charset, path,
740                                     htsp->htsp_peername,
741                                     htsp->htsp_granted_access->aa_representative,
742                                     htsp->htsp_clientname);
743   }
744 
745   LIST_INSERT_HEAD(&htsp->htsp_files, hf, hf_link);
746 
747   htsmsg_t *rep = htsmsg_create_map();
748   htsmsg_add_u32(rep, "id", hf->hf_id);
749 
750   if(!fstat(hf->hf_fd, &st)) {
751     htsmsg_add_s64(rep, "size", st.st_size);
752     htsmsg_add_s64(rep, "mtime", st.st_mtime);
753   }
754 
755   return rep;
756 }
757 
758 /**
759  *
760  */
761 static htsp_file_t *
htsp_file_find(const htsp_connection_t * htsp,htsmsg_t * in)762 htsp_file_find(const htsp_connection_t *htsp, htsmsg_t *in)
763 {
764   htsp_file_t *hf;
765 
766   int id = htsmsg_get_u32_or_default(in, "id", 0);
767 
768   LIST_FOREACH(hf, &htsp->htsp_files, hf_link) {
769     if(hf->hf_id == id)
770       return hf;
771   }
772   return NULL;
773 }
774 
775 /**
776  *
777  */
778 static void
htsp_file_destroy(htsp_file_t * hf)779 htsp_file_destroy(htsp_file_t *hf)
780 {
781   tvhdebug(LS_HTSP, "Closed opened file %s", hf->hf_path);
782   LIST_REMOVE(hf, hf_link);
783   if (hf->hf_subscription) {
784     pthread_mutex_lock(&global_lock);
785     subscription_unsubscribe(hf->hf_subscription, UNSUBSCRIBE_FINAL);
786     pthread_mutex_unlock(&global_lock);
787   }
788   free(hf->hf_path);
789   close(hf->hf_fd);
790   free(hf);
791 }
792 
793 static void
htsp_file_update_stats(htsp_file_t * hf,size_t len)794 htsp_file_update_stats(htsp_file_t *hf, size_t len)
795 {
796   th_subscription_t *ts = hf->hf_subscription;
797   if (ts) {
798     subscription_add_bytes_in(ts, len);
799     subscription_add_bytes_out(ts, len);
800   }
801 }
802 
803 /* **************************************************************************
804  * Output message generators
805  * *************************************************************************/
806 
807 /**
808  *
809  */
810 static htsmsg_t *
htsp_build_channel(channel_t * ch,const char * method,htsp_connection_t * htsp)811 htsp_build_channel(channel_t *ch, const char *method, htsp_connection_t *htsp)
812 {
813   idnode_list_mapping_t *ilm;
814   channel_tag_t *ct;
815   service_t *t;
816   epg_broadcast_t *now, *next = NULL;
817   int64_t chnum = channel_get_number(ch);
818   const char *icon;
819   char buf[64];
820 
821   htsmsg_t *out = htsmsg_create_map();
822   htsmsg_t *tags = htsmsg_create_list();
823   htsmsg_t *services = htsmsg_create_list();
824 
825   htsmsg_add_u32(out, "channelId", channel_get_id(ch));
826   htsmsg_add_u32(out, "channelNumber", channel_get_major(chnum));
827   if (channel_get_minor(chnum))
828     htsmsg_add_u32(out, "channelNumberMinor", channel_get_minor(chnum));
829 
830   htsmsg_add_str(out, "channelName", channel_get_name(ch));
831   if ((icon = channel_get_icon(ch))) {
832 
833     /* Handle older clients */
834     if ((strstr(icon, "imagecache") == icon) && htsp->htsp_version < 8) {
835       struct sockaddr_storage addr;
836       socklen_t addrlen;
837       char url[256];
838       char buf[50];
839       addrlen = sizeof(addr);
840       getsockname(htsp->htsp_fd, (struct sockaddr*)&addr, &addrlen);
841       tcp_get_str_from_ip(&addr, buf, 50);
842       snprintf(url, sizeof(url), "http://%s%s%s:%d%s/%s",
843                     (addr.ss_family == AF_INET6)?"[":"",
844                     buf,
845                     (addr.ss_family == AF_INET6)?"]":"",
846                     tvheadend_webui_port,
847                     tvheadend_webroot ?: "",
848                     icon);
849       htsmsg_add_str(out, "channelIcon", url);
850     } else {
851       if (htsp->htsp_version < 15) {
852         /* older clients expects '/imagecache/' */
853         if (strncmp(icon, "imagecache/", 11) == 0) {
854           snprintf(buf, sizeof(buf), "/%s", icon);
855           icon = buf;
856         }
857       }
858       htsmsg_add_str(out, "channelIcon", icon);
859     }
860   }
861 
862   now  = ch->ch_epg_now;
863   next = ch->ch_epg_next;
864   htsmsg_add_u32(out, "eventId", now ? now->id : 0);
865   htsmsg_add_u32(out, "nextEventId", next ? next->id : 0);
866 
867   LIST_FOREACH(ilm, &ch->ch_ctms, ilm_in2_link) {
868     ct = (channel_tag_t *)ilm->ilm_in1;
869     if(channel_tag_access(ct, htsp->htsp_granted_access, 0))
870       htsmsg_add_u32(tags, NULL, htsp_channel_tag_get_identifier(ct));
871   }
872 
873   LIST_FOREACH(ilm, &ch->ch_services, ilm_in2_link) {
874     t = (service_t *)ilm->ilm_in1;
875     htsmsg_t *svcmsg = htsmsg_create_map();
876     htsmsg_add_str(svcmsg, "name", service_nicename(t));
877 
878     /* Service type string, i.e. UHD, HD, Radio,... */
879     htsmsg_add_str(svcmsg, "type", service_servicetype_txt(t));
880 
881     /* Service content, other = 0x00, tv = 0x01, radio = 0x02 */
882     htsmsg_add_u32(svcmsg, "content", service_is_tv(t) ? 0x01 : (service_is_radio(t) ? 0x02 : 0x00));
883 
884     if (service_is_encrypted(t)) {
885       htsmsg_add_u32(svcmsg, "caid", 65535);
886       htsmsg_add_str(svcmsg, "caname", tvh_gettext_lang(htsp->htsp_language, N_("Encrypted service")));
887     }
888     htsmsg_add_msg(services, NULL, svcmsg);
889   }
890 
891   htsmsg_add_msg(out, "services", services);
892   htsmsg_add_msg(out, "tags", tags);
893   if (method)
894     htsmsg_add_str(out, "method", method);
895   return out;
896 }
897 
898 /**
899  *
900  */
901 static htsmsg_t *
htsp_build_tag(channel_tag_t * ct,const char * method,int include_channels)902 htsp_build_tag(channel_tag_t *ct, const char *method, int include_channels)
903 {
904   idnode_list_mapping_t *ilm;
905   htsmsg_t *out = htsmsg_create_map();
906   htsmsg_t *members = include_channels ? htsmsg_create_list() : NULL;
907 
908   htsmsg_add_u32(out, "tagId", htsp_channel_tag_get_identifier(ct));
909   htsmsg_add_u32(out, "tagIndex", ct->ct_index);
910 
911   htsmsg_add_str(out, "tagName", ct->ct_name);
912   htsmsg_add_str(out, "tagIcon", channel_tag_get_icon(ct));
913   htsmsg_add_u32(out, "tagTitledIcon", ct->ct_titled_icon);
914 
915   if(members != NULL) {
916     LIST_FOREACH(ilm, &ct->ct_ctms, ilm_in1_link)
917       htsmsg_add_u32(members, NULL, channel_get_id((channel_t *)ilm->ilm_in2));
918     htsmsg_add_msg(out, "members", members);
919   }
920 
921   htsmsg_add_str(out, "method", method);
922   return out;
923 }
924 
925 /**
926  *
927  */
928 static htsmsg_t *
htsp_build_dvrentry(htsp_connection_t * htsp,dvr_entry_t * de,const char * method,const char * lang,int statsonly)929 htsp_build_dvrentry(htsp_connection_t *htsp, dvr_entry_t *de, const char *method, const char *lang, int statsonly)
930 {
931   htsmsg_t *out = htsmsg_create_map(), *l, *m, *e, *info;
932   htsmsg_field_t *f;
933   const char *s = NULL, *error = NULL, *subscriptionError = NULL;
934   const char *p, *last;
935   int64_t fsize = -1, start, stop;
936   uint32_t u32;
937   char ubuf[UUID_HEX_SIZE];
938 
939   htsmsg_add_u32(out, "id", idnode_get_short_uuid(&de->de_id));
940 
941   if (!statsonly) {
942     htsmsg_add_u32(out, "enabled", de->de_enabled >= 1 ? 1 : 0);
943     if (de->de_channel)
944       htsmsg_add_u32(out, "channel", channel_get_id(de->de_channel));
945     if (de->de_channel_name) /* stays valid after channel deletion */
946       htsmsg_add_str(out, "channelName", de->de_channel_name);
947 
948     if (de->de_bcast)
949       htsmsg_add_u32(out, "eventId",  de->de_bcast->id);
950 
951     if (de->de_autorec)
952       htsmsg_add_str(out, "autorecId", idnode_uuid_as_str(&de->de_autorec->dae_id, ubuf));
953 
954     if (de->de_timerec)
955       htsmsg_add_str(out, "timerecId", idnode_uuid_as_str(&de->de_timerec->dte_id, ubuf));
956 
957     htsmsg_add_s64(out, "start",       de->de_start);
958     htsmsg_add_s64(out, "stop",        de->de_stop);
959     htsmsg_add_s64(out, "startExtra",  dvr_entry_get_extra_time_pre(de)/60);
960     htsmsg_add_s64(out, "stopExtra",   dvr_entry_get_extra_time_post(de)/60);
961 
962     if (htsp->htsp_version > 24)
963       htsmsg_add_u32(out, "retention",   dvr_entry_get_retention_days(de));
964     else
965       htsmsg_add_u32(out, "retention",   dvr_entry_get_retention_days(de) == DVR_RET_ONREMOVE ?
966           dvr_entry_get_removal_days(de) : dvr_entry_get_retention_days(de));
967 
968     htsmsg_add_u32(out, "removal",     dvr_entry_get_removal_days(de));
969     u32 = de->de_pri;
970     if (u32 >= DVR_PRIO_NOTSET)
971       u32 = DVR_PRIO_NORMAL;
972     htsmsg_add_u32(out, "priority",    u32);
973     htsmsg_add_u32(out, "contentType", de->de_content_type);
974 
975     if (de->de_sched_state == DVR_RECORDING || de->de_sched_state == DVR_COMPLETED) {
976       htsmsg_add_u32(out, "playcount",    de->de_playcount);
977       htsmsg_add_u32(out, "playposition", de->de_playposition);
978     }
979 
980     if(de->de_title && (s = lang_str_get(de->de_title, lang)))
981       htsmsg_add_str(out, "title", s);
982     if(de->de_subtitle && (s = lang_str_get(de->de_subtitle, lang)))
983       htsmsg_add_str(out, "subtitle", s);
984     if(de->de_desc && (s = lang_str_get(de->de_desc, lang)))
985       htsmsg_add_str(out, "description", s);
986     if(de->de_episode)
987       htsmsg_add_str(out, "episode", de->de_episode);
988     if(de->de_owner)
989       htsmsg_add_str(out, "owner",   de->de_owner);
990     if(de->de_creator)
991       htsmsg_add_str(out, "creator", de->de_creator);
992     if(de->de_comment)
993       htsmsg_add_str(out, "comment", de->de_comment);
994 
995     last = NULL;
996     if (!htsmsg_is_empty(de->de_files) && de->de_config) {
997       l = htsmsg_create_list();
998       HTSMSG_FOREACH(f, de->de_files) {
999         m = htsmsg_field_get_map(f);
1000         if (m == NULL) continue;
1001         s = last = htsmsg_get_str(m, "filename");
1002         if (s && (p = tvh_strbegins(s, de->de_config->dvr_storage)) != NULL) {
1003           e = htsmsg_copy(m);
1004           htsmsg_set_str(e, "filename", p);
1005           info = htsmsg_get_list(m, "info");
1006           if (info)
1007             htsmsg_set_msg(e, "info", htsmsg_copy(info));
1008           if (!htsmsg_get_s64(m, "start", &start))
1009             htsmsg_set_s64(e, "start", start);
1010           if (!htsmsg_get_s64(m, "stop", &stop))
1011             htsmsg_set_s64(e, "stop", stop);
1012 
1013           htsmsg_add_msg(l, NULL, e);
1014         }
1015       }
1016       htsmsg_add_msg(out, "files", l);
1017     }
1018 
1019     if(last && de->de_config)
1020       if ((p = tvh_strbegins(last, de->de_config->dvr_storage)))
1021         htsmsg_add_str(out, "path", p);
1022   }
1023 
1024   switch(de->de_sched_state) {
1025   case DVR_SCHEDULED:
1026     s = "scheduled";
1027     break;
1028   case DVR_RECORDING:
1029     s = "recording";
1030     fsize = dvr_get_filesize(de, DVR_FILESIZE_UPDATE);
1031     if (de->de_rec_state == DVR_RS_ERROR ||
1032        (de->de_rec_state == DVR_RS_PENDING && de->de_last_error != SM_CODE_OK))
1033     {
1034       error = streaming_code2txt(de->de_last_error);
1035       subscriptionError = _htsp_get_subscription_status(de->de_last_error);
1036     }
1037     break;
1038   case DVR_COMPLETED:
1039     s = "completed";
1040     fsize = dvr_get_filesize(de, DVR_FILESIZE_UPDATE);
1041     if (fsize < 0)
1042       error = "File missing";
1043     else if(de->de_last_error != SM_CODE_OK &&
1044             de->de_last_error != SM_CODE_FORCE_OK)
1045       error = streaming_code2txt(de->de_last_error);
1046     break;
1047   case DVR_MISSED_TIME:
1048     s = "missed";
1049     break;
1050   case DVR_NOSTATE:
1051     s = "invalid";
1052     break;
1053   }
1054 
1055   htsmsg_add_str(out, "state", s);
1056   if(error)
1057     htsmsg_add_str(out, "error", error);
1058   if (subscriptionError)
1059     htsmsg_add_str(out, "subscriptionError", subscriptionError);
1060   if (de->de_errors)
1061     htsmsg_add_u32(out, "streamErrors", de->de_errors);
1062   if (de->de_data_errors)
1063     htsmsg_add_u32(out, "dataErrors", de->de_data_errors);
1064   if (fsize >= 0)
1065     htsmsg_add_s64(out, "dataSize", fsize);
1066   htsmsg_add_str(out, "method", method);
1067   return out;
1068 }
1069 
1070 /**
1071  *
1072  */
1073 static htsmsg_t *
htsp_build_autorecentry(htsp_connection_t * htsp,dvr_autorec_entry_t * dae,const char * method)1074 htsp_build_autorecentry(htsp_connection_t *htsp, dvr_autorec_entry_t *dae, const char *method)
1075 {
1076   htsmsg_t *out = htsmsg_create_map();
1077   char ubuf[UUID_HEX_SIZE];
1078   int tad;
1079 
1080   htsmsg_add_str(out, "id",          idnode_uuid_as_str(&dae->dae_id, ubuf));
1081   htsmsg_add_u32(out, "enabled",     dae->dae_enabled >= 1 ? 1 : 0);
1082   htsmsg_add_u32(out, "maxDuration", dae->dae_maxduration);
1083   htsmsg_add_u32(out, "minDuration", dae->dae_minduration);
1084 
1085   if (htsp->htsp_version > 24)
1086     htsmsg_add_u32(out, "retention",   dvr_autorec_get_retention_days(dae));
1087   else
1088     htsmsg_add_u32(out, "retention",   dvr_autorec_get_retention_days(dae) == DVR_RET_ONREMOVE ?
1089         dvr_autorec_get_removal_days(dae) : dvr_autorec_get_retention_days(dae));
1090 
1091   htsmsg_add_u32(out, "removal",     dvr_autorec_get_removal_days(dae));
1092   htsmsg_add_u32(out, "daysOfWeek",  dae->dae_weekdays);
1093   if (dae->dae_start >= 0 && dae->dae_start_window >= 0) {
1094     if (dae->dae_start > dae->dae_start_window)
1095       tad = 24 * 60 - dae->dae_start + dae->dae_start_window;
1096     else
1097       tad = dae->dae_start_window - dae->dae_start;
1098   } else {
1099     tad = -1;
1100   }
1101   htsmsg_add_s32(out, "approxTime",
1102                  dae->dae_start >= 0 && tad >= 0 ?
1103                    ((dae->dae_start + tad / 2) % (24 * 60)) : -1);
1104   htsmsg_add_s32(out, "start",       dae->dae_start >= 0 ? dae->dae_start : -1);
1105   htsmsg_add_s32(out, "startWindow", dae->dae_start_window >= 0 ? dae->dae_start_window : -1);
1106   htsmsg_add_u32(out, "priority",    dae->dae_pri);
1107   htsmsg_add_s64(out, "startExtra",  dvr_autorec_get_extra_time_pre(dae));
1108   htsmsg_add_s64(out, "stopExtra",   dvr_autorec_get_extra_time_post(dae));
1109   htsmsg_add_u32(out, "dupDetect",   dae->dae_record);
1110   htsmsg_add_u32(out, "maxCount",    dae->dae_max_count);
1111 
1112   if(dae->dae_title) {
1113     htsmsg_add_str(out, "title",     dae->dae_title);
1114     htsmsg_add_u32(out, "fulltext",  dae->dae_fulltext >= 1 ? 1 : 0);
1115   }
1116   if(dae->dae_name)
1117     htsmsg_add_str(out, "name",      dae->dae_name);
1118   if(dae->dae_directory)
1119     htsmsg_add_str(out, "directory", dae->dae_directory);
1120   if(dae->dae_owner)
1121     htsmsg_add_str(out, "owner",     dae->dae_owner);
1122   if(dae->dae_creator)
1123     htsmsg_add_str(out, "creator",   dae->dae_creator);
1124   if(dae->dae_channel)
1125     htsmsg_add_u32(out, "channel",   channel_get_id(dae->dae_channel));
1126 
1127   htsmsg_add_str(out, "method", method);
1128 
1129   return out;
1130 }
1131 
1132 /**
1133  *
1134  */
1135 static htsmsg_t *
htsp_build_timerecentry(htsp_connection_t * htsp,dvr_timerec_entry_t * dte,const char * method)1136 htsp_build_timerecentry(htsp_connection_t *htsp, dvr_timerec_entry_t *dte, const char *method)
1137 {
1138   htsmsg_t *out = htsmsg_create_map();
1139   char ubuf[UUID_HEX_SIZE];
1140 
1141   htsmsg_add_str(out, "id",          idnode_uuid_as_str(&dte->dte_id, ubuf));
1142   htsmsg_add_u32(out, "enabled",     dte->dte_enabled >= 1 ? 1 : 0);
1143   htsmsg_add_u32(out, "daysOfWeek",  dte->dte_weekdays);
1144 
1145   if (htsp->htsp_version > 24)
1146     htsmsg_add_u32(out, "retention",   dvr_timerec_get_retention_days(dte));
1147   else
1148     htsmsg_add_u32(out, "retention",   dvr_timerec_get_retention_days(dte) == DVR_RET_ONREMOVE ?
1149         dvr_timerec_get_removal_days(dte) : dvr_timerec_get_retention_days(dte));
1150 
1151   htsmsg_add_u32(out, "removal",     dvr_timerec_get_removal_days(dte));
1152   htsmsg_add_u32(out, "priority",    dte->dte_pri);
1153   htsmsg_add_s32(out, "start",       dte->dte_start);
1154   htsmsg_add_s32(out, "stop",        dte->dte_stop);
1155 
1156   if(dte->dte_title)
1157     htsmsg_add_str(out, "title",     dte->dte_title);
1158   if(dte->dte_name)
1159     htsmsg_add_str(out, "name",      dte->dte_name);
1160   if(dte->dte_directory)
1161     htsmsg_add_str(out, "directory", dte->dte_directory);
1162   if(dte->dte_owner)
1163     htsmsg_add_str(out, "owner",     dte->dte_owner);
1164   if(dte->dte_creator)
1165     htsmsg_add_str(out, "creator",   dte->dte_creator);
1166   if(dte->dte_channel)
1167     htsmsg_add_u32(out, "channel",   channel_get_id(dte->dte_channel));
1168 
1169   htsmsg_add_str(out, "method", method);
1170 
1171   return out;
1172 }
1173 
1174 /**
1175  *
1176  */
1177 static htsmsg_t *
htsp_build_event(epg_broadcast_t * e,const char * method,const char * lang,time_t update,htsp_connection_t * htsp)1178 htsp_build_event
1179   (epg_broadcast_t *e, const char *method, const char *lang, time_t update,
1180    htsp_connection_t *htsp )
1181 {
1182   htsmsg_t *out;
1183   epg_broadcast_t *n;
1184   dvr_entry_t *de;
1185   epg_genre_t *g;
1186   epg_episode_num_t epnum;
1187   const char *str;
1188   epg_episode_t *ee = e->episode;
1189 
1190   /* Ignore? */
1191   if (update) {
1192     int ignore = 1;
1193     if      (e->updated > update) ignore = 0;
1194     else if (e->serieslink && e->serieslink->updated > update) ignore = 0;
1195     else if (ee) {
1196            if (ee->updated > update) ignore = 0;
1197       else if (ee->brand  && ee->brand->updated > update)  ignore = 0;
1198       else if (ee->season && ee->season->updated > update) ignore = 0;
1199     }
1200     if (ignore) return NULL;
1201   }
1202 
1203   out = htsmsg_create_map();
1204 
1205   if (method)
1206     htsmsg_add_str(out, "method", method);
1207 
1208   htsmsg_add_u32(out, "eventId", e->id);
1209   htsmsg_add_u32(out, "channelId", channel_get_id(e->channel));
1210   htsmsg_add_s64(out, "start", e->start);
1211   htsmsg_add_s64(out, "stop", e->stop);
1212   if ((str = epg_broadcast_get_title(e, lang)))
1213     htsmsg_add_str(out, "title", str);
1214   if ((str = epg_broadcast_get_subtitle(e, lang)))
1215     htsmsg_add_str(out, "subtitle", str);
1216   if ((str = epg_broadcast_get_description(e, lang))) {
1217     htsmsg_add_str(out, "description", str);
1218     if ((str = epg_broadcast_get_summary(e, lang)))
1219       htsmsg_add_str(out, "summary", str);
1220   } else if((str = epg_broadcast_get_summary(e, lang)))
1221     htsmsg_add_str(out, "description", str);
1222   if (e->serieslink) {
1223     htsmsg_add_u32(out, "serieslinkId", e->serieslink->id);
1224     if (e->serieslink->uri)
1225       htsmsg_add_str(out, "serieslinkUri", e->serieslink->uri);
1226   }
1227 
1228   if (ee) {
1229     htsmsg_add_u32(out, "episodeId", ee->id);
1230     if (ee->uri && strncasecmp(ee->uri,"tvh://",6))  /* tvh:// uris are internal */
1231       htsmsg_add_str(out, "episodeUri", ee->uri);
1232     if (ee->brand)
1233       htsmsg_add_u32(out, "brandId", ee->brand->id);
1234     if (ee->season)
1235       htsmsg_add_u32(out, "seasonId", ee->season->id);
1236     if((g = LIST_FIRST(&ee->genre))) {
1237       uint32_t code = g->code;
1238       if (htsp->htsp_version < 6) code = (code >> 4) & 0xF;
1239       htsmsg_add_u32(out, "contentType", code);
1240     }
1241     if (ee->age_rating)
1242       htsmsg_add_u32(out, "ageRating", ee->age_rating);
1243     if (ee->star_rating)
1244       htsmsg_add_u32(out, "starRating", ee->star_rating);
1245     if (ee->first_aired)
1246       htsmsg_add_s64(out, "firstAired", ee->first_aired);
1247     epg_episode_get_epnum(ee, &epnum);
1248     if (epnum.s_num) {
1249       htsmsg_add_u32(out, "seasonNumber", epnum.s_num);
1250       if (epnum.s_cnt)
1251         htsmsg_add_u32(out, "seasonCount", epnum.s_cnt);
1252     }
1253     if (epnum.e_num) {
1254       htsmsg_add_u32(out, "episodeNumber", epnum.e_num);
1255       if (epnum.e_cnt)
1256         htsmsg_add_u32(out, "episodeCount", epnum.e_cnt);
1257     }
1258     if (epnum.p_num) {
1259       htsmsg_add_u32(out, "partNumber", epnum.p_num);
1260       if (epnum.p_cnt)
1261         htsmsg_add_u32(out, "partCount", epnum.p_cnt);
1262     }
1263     if (epnum.text)
1264       htsmsg_add_str(out, "episodeOnscreen", epnum.text);
1265     if (ee->image)
1266       htsmsg_add_str(out, "image", ee->image);
1267   }
1268 
1269   if (e->channel) {
1270     LIST_FOREACH(de, &e->channel->ch_dvrs, de_channel_link) {
1271       if (de->de_bcast != e)
1272         continue;
1273       if (dvr_entry_verify(de, htsp->htsp_granted_access, 1))
1274         continue;
1275       htsmsg_add_u32(out, "dvrId", idnode_get_short_uuid(&de->de_id));
1276       break;
1277     }
1278   }
1279 
1280   if ((n = epg_broadcast_get_next(e)))
1281     htsmsg_add_u32(out, "nextEventId", n->id);
1282 
1283   return out;
1284 }
1285 
1286 /* **************************************************************************
1287  * Message handlers
1288  * *************************************************************************/
1289 
1290 /**
1291  * Hello, for protocol version negotiation
1292  */
1293 static htsmsg_t *
htsp_method_hello(htsp_connection_t * htsp,htsmsg_t * in)1294 htsp_method_hello(htsp_connection_t *htsp, htsmsg_t *in)
1295 {
1296   htsmsg_t *r;
1297   uint32_t v;
1298   const char *name, *lang;
1299 
1300   if(htsmsg_get_u32(in, "htspversion", &v))
1301     return htsp_error(htsp, N_("Invalid arguments"));
1302 
1303   if((name = htsmsg_get_str(in, "clientname")) == NULL)
1304     return htsp_error(htsp, N_("Invalid arguments"));
1305 
1306   r = htsmsg_create_map();
1307 
1308   tvh_str_update(&htsp->htsp_clientname, htsmsg_get_str(in, "clientname"));
1309 
1310   tvhinfo(LS_HTSP, "%s: Welcomed client software: %s (HTSPv%d)",
1311 	  htsp->htsp_logname, name, v);
1312 
1313   htsmsg_add_u32(r, "htspversion", HTSP_PROTO_VERSION);
1314   htsmsg_add_str(r, "servername", config_get_server_name());
1315   htsmsg_add_str(r, "serverversion", tvheadend_version);
1316   htsmsg_add_bin(r, "challenge", htsp->htsp_challenge, 32);
1317   if (tvheadend_webroot)
1318     htsmsg_add_str(r, "webroot", tvheadend_webroot);
1319   lang = config_get_language();
1320   if (lang)
1321     htsmsg_add_str(r, "language", lang);
1322 
1323   /* Capabilities */
1324   htsmsg_add_msg(r, "servercapability", tvheadend_capabilities_list(1));
1325   htsmsg_add_u32(r, "api_version", TVH_API_VERSION);
1326 
1327   /* Set version to lowest num */
1328   htsp->htsp_version = MIN(HTSP_PROTO_VERSION, v);
1329 
1330   htsp_update_logname(htsp);
1331   return r;
1332 }
1333 
1334 /**
1335  * Try to authenticate
1336  */
1337 static htsmsg_t *
htsp_method_authenticate(htsp_connection_t * htsp,htsmsg_t * in)1338 htsp_method_authenticate(htsp_connection_t *htsp, htsmsg_t *in)
1339 {
1340   htsmsg_t *r = htsmsg_create_map();
1341 
1342   if(!(htsp->htsp_granted_access->aa_rights & HTSP_PRIV_MASK))
1343     htsmsg_add_u32(r, "noaccess", 1);
1344   else if (htsp->htsp_version > 25) {
1345     htsmsg_add_u32(r, "admin",          htsp->htsp_granted_access->aa_rights & ACCESS_ADMIN ? 1 : 0);
1346     htsmsg_add_u32(r, "streaming",      htsp->htsp_granted_access->aa_rights & ACCESS_HTSP_STREAMING ? 1 : 0);
1347     htsmsg_add_u32(r, "dvr",            htsp->htsp_granted_access->aa_rights & ACCESS_HTSP_RECORDER ? 1 : 0);
1348     htsmsg_add_u32(r, "faileddvr",      htsp->htsp_granted_access->aa_rights & ACCESS_FAILED_RECORDER ? 1 : 0);
1349     htsmsg_add_u32(r, "anonymous",      htsp->htsp_granted_access->aa_rights & ACCESS_HTSP_ANONYMIZE ? 1 : 0);
1350     htsmsg_add_u32(r, "limitall",       htsp->htsp_granted_access->aa_conn_limit);
1351     htsmsg_add_u32(r, "limitdvr",       htsp->htsp_granted_access->aa_conn_limit_dvr);
1352     htsmsg_add_u32(r, "limitstreaming", htsp->htsp_granted_access->aa_conn_limit_streaming);
1353     htsmsg_add_u32(r, "uilevel",        htsp->htsp_granted_access->aa_uilevel == UILEVEL_DEFAULT ?
1354         config.uilevel : htsp->htsp_granted_access->aa_uilevel);
1355     htsmsg_add_str(r, "uilanguage",     htsp->htsp_granted_access->aa_lang_ui ?
1356         htsp->htsp_granted_access->aa_lang_ui : (config.language_ui ? config.language_ui : ""));
1357   }
1358 
1359   return r;
1360 }
1361 
1362 /**
1363  * Try to authenticate
1364  */
1365 static htsmsg_t *
htsp_method_api(htsp_connection_t * htsp,htsmsg_t * in)1366 htsp_method_api(htsp_connection_t *htsp, htsmsg_t *in)
1367 {
1368   htsmsg_t *resp = NULL, *ret = htsmsg_create_map();
1369   htsmsg_t *args, *args2 = NULL;
1370   const char *remain;
1371   int r;
1372 
1373   pthread_mutex_unlock(&global_lock);
1374 
1375   args   = htsmsg_get_map(in, "args");
1376   remain = htsmsg_get_str(in, "path");
1377 
1378   if (args == NULL)
1379     args = args2 = htsmsg_create_map();
1380 
1381   /* Call */
1382   r = api_exec(htsp->htsp_granted_access, remain, args, &resp);
1383 
1384   /* Convert error */
1385   if (r) {
1386     switch (r) {
1387       case EPERM:
1388       case EACCES:
1389         htsmsg_add_u32(ret, "noaccess", 1);
1390         break;
1391       case ENOENT:
1392       case ENOSYS:
1393         break;
1394       default:
1395         htsmsg_destroy(args2);
1396         htsmsg_destroy(ret);
1397         return htsp_error(htsp, N_("Bad request"));
1398     }
1399   } else if (resp) {
1400     /* Output response */
1401     htsmsg_add_msg(ret, "response", resp);
1402   }
1403 
1404   htsmsg_destroy(args2);
1405 
1406   pthread_mutex_lock(&global_lock);
1407   return ret;
1408 }
1409 
1410 /**
1411  * Get total and free disk space on configured path
1412  */
1413 static htsmsg_t *
htsp_method_getDiskSpace(htsp_connection_t * htsp,htsmsg_t * in)1414 htsp_method_getDiskSpace(htsp_connection_t *htsp, htsmsg_t *in)
1415 {
1416   htsmsg_t *out;
1417   int64_t bfree, bused, btotal;
1418 
1419   if (dvr_get_disk_space(&bfree, &bused, &btotal))
1420     return htsp_error(htsp, N_("Unable to stat path"));
1421 
1422   out = htsmsg_create_map();
1423   htsmsg_add_s64(out, "freediskspace", bfree);
1424   htsmsg_add_s64(out, "useddiskspace", bused);
1425   htsmsg_add_s64(out, "totaldiskspace", btotal);
1426   return out;
1427 }
1428 
1429 /**
1430  * Get system time and diff to GMT
1431  */
1432 static htsmsg_t *
htsp_method_getSysTime(htsp_connection_t * htsp,htsmsg_t * in)1433 htsp_method_getSysTime(htsp_connection_t *htsp, htsmsg_t *in)
1434 {
1435   htsmsg_t *out;
1436   struct timeval tv;
1437   struct timezone tz;
1438   int tz_offset;
1439   struct tm serverLocalTime;
1440 
1441   if(gettimeofday(&tv, &tz) == -1)
1442     return htsp_error(htsp, N_("Unable to get system time"));
1443 
1444   if (!localtime_r(&tv.tv_sec, &serverLocalTime))
1445     return htsp_error(htsp, N_("Unable to get system local time"));
1446 #if defined(HAS_GMTOFF)
1447   tz_offset = - serverLocalTime.tm_gmtoff / (60);
1448 #else
1449   // NB: This will be a day out when GMT offsets >= 13hrs or <11 hrs apply
1450   struct tm serverGmTime;
1451   if (!gmtime_r(&tv.tv_sec, &serverGmTime))
1452     return htsp_error(htsp, N_("Unable to get system UTC time"));
1453   tz_offset = (serverGmTime.tm_hour - serverLocalTime.tm_hour) * 60;
1454   tz_offset += serverGmTime.tm_min - serverLocalTime.tm_min;
1455   if (tz_offset > 11 * 60)
1456     tz_offset -= 24 * 60;
1457   if (tz_offset <= -13 * 60)
1458     tz_offset += 24 * 60;
1459 #endif
1460 
1461   out = htsmsg_create_map();
1462   htsmsg_add_s32(out, "time", tv.tv_sec);
1463   htsmsg_add_s32(out, "timezone", tz_offset/60);
1464   htsmsg_add_s32(out, "gmtoffset", -tz_offset);
1465   return out;
1466 }
1467 
1468 /**
1469  * Switch the HTSP connection into async mode
1470  */
1471 static htsmsg_t *
htsp_method_async(htsp_connection_t * htsp,htsmsg_t * in)1472 htsp_method_async(htsp_connection_t *htsp, htsmsg_t *in)
1473 {
1474   channel_t *ch;
1475   channel_tag_t *ct;
1476   dvr_entry_t *de;
1477   dvr_autorec_entry_t *dae;
1478   dvr_timerec_entry_t *dte;
1479   htsmsg_t *m;
1480   uint32_t epg = 0;
1481   int64_t lastUpdate = -1;
1482   int64_t epgMaxTime = 0;
1483   const char *lang;
1484 
1485   /* Get optional flags, allow updating them if already in async mode */
1486   if (htsmsg_get_u32(in, "epg", &epg))
1487     epg = (htsp->htsp_async_mode & HTSP_ASYNC_EPG) ? 1 : 0;
1488   if (!htsmsg_get_s64(in, "lastUpdate", &lastUpdate))   // 0 = never
1489     htsp->htsp_epg_lastupdate = lastUpdate;
1490   else if (htsp->htsp_async_mode & HTSP_ASYNC_EPG)
1491     lastUpdate = htsp->htsp_epg_lastupdate;
1492   if (!htsmsg_get_s64(in, "epgMaxTime", &epgMaxTime)) { // 0 = unlimited window
1493     if (htsp->htsp_async_mode & HTSP_ASYNC_EPG) {
1494       /* Only allow to change the window in the correct range */
1495       if (htsp->htsp_epg_window && epgMaxTime > htsp->htsp_epg_lastupdate)
1496         htsp->htsp_epg_window = epgMaxTime-gclk();
1497     } else if (epgMaxTime > gclk()) {
1498       htsp->htsp_epg_window = epgMaxTime-gclk();
1499     } else {
1500       htsp->htsp_epg_window = 0;
1501     }
1502     if (htsp->htsp_epg_window)
1503       htsp->htsp_epg_window = MAX(htsp->htsp_epg_window, 600);
1504   }
1505   if ((lang = htsmsg_get_str(in, "language")) != NULL) {
1506     if (lang[0]) {
1507       htsp->htsp_language = strdup(lang);
1508     } else {
1509       free(htsp->htsp_language);
1510       htsp->htsp_language = NULL;
1511     }
1512   }
1513 
1514   /* First, just OK the async request */
1515   htsp_reply(htsp, in, htsmsg_create_map());
1516 
1517   /* Set epg */
1518   if(epg)
1519     htsp->htsp_async_mode |= HTSP_ASYNC_EPG;
1520   else
1521     htsp->htsp_async_mode &= ~HTSP_ASYNC_EPG;
1522 
1523   if(htsp->htsp_async_mode & HTSP_ASYNC_ON) {
1524     /* Sync epg on demand */
1525     if (epg)
1526       htsp_epg_send_waiting(htsp, lastUpdate);
1527     return NULL;
1528   }
1529 
1530   htsp->htsp_async_mode |= HTSP_ASYNC_ON;
1531 
1532   /* Send all enabled and external tags */
1533   TAILQ_FOREACH(ct, &channel_tags, ct_link)
1534     if(channel_tag_access(ct, htsp->htsp_granted_access, 0))
1535       htsp_send_message(htsp, htsp_build_tag(ct, "tagAdd", 0), NULL);
1536 
1537   /* Send all channels */
1538   CHANNEL_FOREACH(ch)
1539     if (htsp_user_access_channel(htsp,ch))
1540       htsp_send_message(htsp, htsp_build_channel(ch, "channelAdd", htsp), NULL);
1541 
1542   /* Send all enabled and external tags (now with channel mappings) */
1543   TAILQ_FOREACH(ct, &channel_tags, ct_link)
1544     if(channel_tag_access(ct, htsp->htsp_granted_access, 0))
1545       htsp_send_message(htsp, htsp_build_tag(ct, "tagUpdate", 1), NULL);
1546 
1547   /* Send all autorecs */
1548   TAILQ_FOREACH(dae, &autorec_entries, dae_link)
1549     if (!dvr_autorec_entry_verify(dae, htsp->htsp_granted_access, 1))
1550       htsp_send_message(htsp, htsp_build_autorecentry(htsp, dae, "autorecEntryAdd"), NULL);
1551 
1552   /* Send all timerecs */
1553   TAILQ_FOREACH(dte, &timerec_entries, dte_link)
1554     if (!dvr_timerec_entry_verify(dte, htsp->htsp_granted_access, 1))
1555       htsp_send_message(htsp, htsp_build_timerecentry(htsp, dte, "timerecEntryAdd"), NULL);
1556 
1557   /* Send all DVR entries */
1558   LIST_FOREACH(de, &dvrentries, de_global_link)
1559     if (!dvr_entry_verify(de, htsp->htsp_granted_access, 1))
1560       htsp_send_message(htsp, htsp_build_dvrentry(htsp, de, "dvrEntryAdd", htsp->htsp_language, 0), NULL);
1561 
1562   /* Send EPG updates */
1563   if (epg)
1564     htsp_epg_send_waiting(htsp, -1);
1565 
1566   /* Notify that initial sync has been completed */
1567   m = htsmsg_create_map();
1568   htsmsg_add_str(m, "method", "initialSyncCompleted");
1569   htsp_send_message(htsp, m, NULL);
1570 
1571   /* Insert in list so it will get all updates */
1572   LIST_INSERT_HEAD(&htsp_async_connections, htsp, htsp_async_link);
1573 
1574   return NULL;
1575 }
1576 
1577 /**
1578  * Get information about the given event
1579  */
1580 static htsmsg_t *
htsp_method_getChannel(htsp_connection_t * htsp,htsmsg_t * in)1581 htsp_method_getChannel(htsp_connection_t *htsp, htsmsg_t *in)
1582 {
1583   uint32_t channelId;
1584   channel_t *ch = NULL;
1585 
1586   if (htsmsg_get_u32(in, "channelId", &channelId))
1587     return htsp_error(htsp, N_("Invalid arguments"));
1588   if (!(ch = channel_find_by_id(channelId)))
1589     return htsp_error(htsp, N_("Channel does not exist"));
1590 
1591   return htsp_build_channel(ch, NULL, htsp);
1592 }
1593 
1594 /**
1595  * Get information about the given event
1596  */
1597 static htsmsg_t *
htsp_method_getEvent(htsp_connection_t * htsp,htsmsg_t * in)1598 htsp_method_getEvent(htsp_connection_t *htsp, htsmsg_t *in)
1599 {
1600   uint32_t eventId;
1601   epg_broadcast_t *e;
1602   const char *lang;
1603 
1604   if(htsmsg_get_u32(in, "eventId", &eventId))
1605     return htsp_error(htsp, N_("Invalid arguments"));
1606   lang = htsmsg_get_str(in, "language") ?: htsp->htsp_language;
1607 
1608   if((e = epg_broadcast_find_by_id(eventId)) == NULL)
1609     return htsp_error(htsp, N_("Event does not exist"));
1610 
1611   return htsp_build_event(e, NULL, lang, 0, htsp);
1612 }
1613 
1614 /**
1615  * Get information about the given event +
1616  * n following events
1617  */
1618 static htsmsg_t *
htsp_method_getEvents(htsp_connection_t * htsp,htsmsg_t * in)1619 htsp_method_getEvents(htsp_connection_t *htsp, htsmsg_t *in)
1620 {
1621   uint32_t u32, numFollowing;
1622   int64_t maxTime = 0;
1623   htsmsg_t *out, *events;
1624   epg_broadcast_t *e = NULL;
1625   channel_t *ch = NULL;
1626   const char *lang;
1627 
1628   /* Optional fields */
1629   if (!htsmsg_get_u32(in, "channelId", &u32))
1630     if (!(ch = channel_find_by_id(u32)))
1631       return htsp_error(htsp, N_("Channel does not exist"));
1632   if (!htsmsg_get_u32(in, "eventId", &u32))
1633     if (!(e = epg_broadcast_find_by_id(u32)))
1634       return htsp_error(htsp, N_("Event does not exist"));
1635 
1636   /* Check access */
1637 
1638   numFollowing = htsmsg_get_u32_or_default(in, "numFollowing", 0);
1639   maxTime      = htsmsg_get_s64_or_default(in, "maxTime", 0);
1640   lang         = htsmsg_get_str(in, "language") ?: htsp->htsp_language;
1641 
1642   /* Use event as starting point */
1643   if (e || ch) {
1644 
1645     if (!e) e = ch->ch_epg_now ?: ch->ch_epg_next;
1646 
1647     if (e && !htsp_user_access_channel(htsp, e->channel))
1648       return htsp_error(htsp, N_("User does not have access"));
1649 
1650     /* Output */
1651     events = htsmsg_create_list();
1652     while (e) {
1653       if (maxTime && e->start > maxTime) break;
1654       htsmsg_add_msg(events, NULL, htsp_build_event(e, NULL, lang, 0, htsp));
1655       if (numFollowing == 1) break;
1656       if (numFollowing) numFollowing--;
1657       e = epg_broadcast_get_next(e);
1658     }
1659 
1660   /* All channels */
1661   } else {
1662 
1663     events = htsmsg_create_list();
1664     CHANNEL_FOREACH(ch) {
1665       int num = numFollowing;
1666       if (!htsp_user_access_channel(htsp, ch))
1667         continue;
1668       RB_FOREACH(e, &ch->ch_epg_schedule, sched_link) {
1669         if (maxTime && e->start > maxTime) break;
1670         htsmsg_add_msg(events, NULL, htsp_build_event(e, NULL, lang, 0, htsp));
1671         if (num == 1) break;
1672         if (num) num--;
1673       }
1674     }
1675 
1676   }
1677 
1678   /* Send */
1679   out = htsmsg_create_map();
1680   htsmsg_add_msg(out, "events", events);
1681   return out;
1682 }
1683 
1684 /**
1685  *
1686  * do an epg query
1687  */
1688 static htsmsg_t *
htsp_method_epgQuery(htsp_connection_t * htsp,htsmsg_t * in)1689 htsp_method_epgQuery(htsp_connection_t *htsp, htsmsg_t *in)
1690 {
1691   htsmsg_t *out, *array;
1692   const char *query;
1693   int i;
1694   uint32_t u32, full;
1695   channel_t *ch = NULL;
1696   channel_tag_t *ct = NULL;
1697   epg_query_t eq;
1698   const char *lang;
1699   int min_duration;
1700   int max_duration;
1701   char ubuf[UUID_HEX_SIZE];
1702 
1703   /* Required */
1704   if( (query = htsmsg_get_str(in, "query")) == NULL )
1705     return htsp_error(htsp, N_("Invalid arguments"));
1706 
1707   memset(&eq, 0, sizeof(eq));
1708 
1709   if(htsmsg_get_bool_or_default(in, "fulltext", 0))
1710     eq.fulltext = 1;
1711   eq.stitle = strdup(query);
1712 
1713   /* Optional */
1714   if(!(htsmsg_get_u32(in, "channelId", &u32))) {
1715     if (!(ch = channel_find_by_id(u32)))
1716       return htsp_error(htsp, N_("Channel does not exist"));
1717     else
1718       eq.channel = strdup(idnode_uuid_as_str(&ch->ch_id, ubuf));
1719   }
1720   if(!(htsmsg_get_u32(in, "tagId", &u32))) {
1721     if (!(ct = htsp_channel_tag_find_by_identifier(htsp, u32)))
1722       return htsp_error(htsp, N_("Channel tag does not exist"));
1723     else
1724       eq.channel_tag = strdup(idnode_uuid_as_str(&ct->ct_id, ubuf));
1725   }
1726   if (!htsmsg_get_u32(in, "contentType", &u32)) {
1727     if(htsp->htsp_version < 6) u32 <<= 4;
1728     eq.genre_count = 1;
1729     eq.genre = eq.genre_static;
1730     eq.genre[0] = u32;
1731   }
1732   lang = htsmsg_get_str(in, "language") ?: htsp->htsp_language;
1733   eq.lang = lang ? strdup(lang) : NULL;
1734   full = htsmsg_get_u32_or_default(in, "full", 0);
1735 
1736   min_duration = htsmsg_get_u32_or_default(in, "minduration", 0);
1737   max_duration = htsmsg_get_u32_or_default(in, "maxduration", INT_MAX);
1738   eq.duration.comp = EC_RG;
1739   eq.duration.val1 = min_duration;
1740   eq.duration.val2 = max_duration;
1741   tvhtrace(LS_HTSP, "min_duration %d and max_duration %d", min_duration, max_duration);
1742 
1743   /* Check access */
1744   if (ch && !htsp_user_access_channel(htsp, ch))
1745     return htsp_error(htsp, N_("User does not have access"));
1746 
1747   /* Query */
1748   epg_query(&eq, htsp->htsp_granted_access);
1749 
1750   /* Create Reply */
1751   out = htsmsg_create_map();
1752   if( eq.entries ) {
1753     array = htsmsg_create_list();
1754     for(i = 0; i < eq.entries; ++i) {
1755       if (full)
1756         htsmsg_add_msg(array, NULL,
1757                        htsp_build_event(eq.result[i], NULL, lang, 0, htsp));
1758       else
1759         htsmsg_add_u32(array, NULL, eq.result[i]->id);
1760     }
1761     htsmsg_add_msg(out, full ? "events" : "eventIds", array);
1762   }
1763 
1764   epg_query_free(&eq);
1765 
1766   return out;
1767 }
1768 
1769 static htsmsg_t *
htsp_method_getEpgObject(htsp_connection_t * htsp,htsmsg_t * in)1770 htsp_method_getEpgObject(htsp_connection_t *htsp, htsmsg_t *in)
1771 {
1772   uint32_t id, u32;
1773   epg_object_type_t type;
1774   epg_object_t *eo;
1775   htsmsg_t *out;
1776 
1777   // TODO: should really block access based on channel, but actually
1778   //       that's really hard to do here!
1779 
1780   /* Required fields */
1781   if (htsmsg_get_u32(in, "id", &id))
1782     return htsp_error(htsp, N_("Invalid arguments"));
1783 
1784   /* Optional fields */
1785   if (!htsmsg_get_u32(in, "type", &u32) && (u32 <= EPG_TYPEMAX))
1786     type = u32;
1787   else
1788     type = EPG_UNDEF;
1789 
1790   /* Get object */
1791   if (!(eo = epg_object_find_by_id(id, type)))
1792     return htsp_error(htsp, N_("Invalid EPG object request"));
1793 
1794   /* Serialize */
1795   if (!(out = epg_object_serialize(eo)))
1796     return htsp_error(htsp, N_("Internal error"));
1797 
1798   return out;
1799 }
1800 
1801 /**
1802  *
1803  */
1804 static htsmsg_t *
htsp_method_getDvrConfigs(htsp_connection_t * htsp,htsmsg_t * in)1805 htsp_method_getDvrConfigs(htsp_connection_t *htsp, htsmsg_t *in)
1806 {
1807   htsmsg_t *out, *l, *c;
1808   htsmsg_field_t *f;
1809   dvr_config_t *cfg;
1810   const char *uuid, *s;
1811   char ubuf[UUID_HEX_SIZE];
1812 
1813   l = htsmsg_create_list();
1814 
1815   LIST_FOREACH(cfg, &dvrconfigs, config_link)
1816     if (cfg->dvr_enabled) {
1817       uuid = idnode_uuid_as_str(&cfg->dvr_id, ubuf);
1818       if (htsp->htsp_granted_access->aa_dvrcfgs) {
1819         HTSMSG_FOREACH(f, htsp->htsp_granted_access->aa_dvrcfgs) {
1820           if (!(s = htsmsg_field_get_str(f)))
1821             continue;
1822           if (strcmp(s, uuid) == 0)
1823             break;
1824         }
1825         if (f == NULL)
1826           continue;
1827       }
1828       c = htsmsg_create_map();
1829       htsmsg_add_str(c, "uuid", uuid);
1830       htsmsg_add_str(c, "name", cfg->dvr_config_name ?: "");
1831       htsmsg_add_str(c, "comment", cfg->dvr_comment ?: "");
1832       htsmsg_add_msg(l, NULL, c);
1833     }
1834 
1835   out = htsmsg_create_map();
1836 
1837   htsmsg_add_msg(out, "dvrconfigs", l);
1838 
1839   return out;
1840 }
1841 
1842 /**
1843  * add a Dvrentry
1844  */
1845 static htsmsg_t *
htsp_method_addDvrEntry(htsp_connection_t * htsp,htsmsg_t * in)1846 htsp_method_addDvrEntry(htsp_connection_t *htsp, htsmsg_t *in)
1847 {
1848   htsmsg_t *out;
1849   uint32_t eventid;
1850   epg_broadcast_t *e = NULL;
1851   dvr_entry_t *de;
1852   dvr_entry_sched_state_t dvr_status;
1853   const char *dvr_config_name, *title, *desc, *subtitle, *lang, *comment;
1854   int64_t start, stop, start_extra, stop_extra;
1855   uint32_t u32, priority, retention, removal;
1856   channel_t *ch = NULL;
1857   int enabled;
1858 
1859   /* Options */
1860   enabled = htsmsg_get_u32_or_default(in, "enabled", 1);
1861   dvr_config_name = htsp_dvr_config_name(htsp, htsmsg_get_str(in, "configName"));
1862   if(htsmsg_get_s64(in, "startExtra", &start_extra))
1863     start_extra = 0;
1864   if(htsmsg_get_s64(in, "stopExtra", &stop_extra))
1865     stop_extra  = 0;
1866   if(!htsmsg_get_u32(in, "channelId", &u32))
1867     ch = channel_find_by_id(u32);
1868   if(!htsmsg_get_u32(in, "eventId", &eventid)) {
1869     e = epg_broadcast_find_by_id(eventid);
1870     ch = e ? e->channel : ch;
1871   }
1872   if(htsmsg_get_u32(in, "priority", &priority))
1873     priority = DVR_PRIO_NORMAL;
1874   if(htsmsg_get_u32(in, "retention", &retention))
1875     retention = DVR_RET_REM_DVRCONFIG;
1876   if(htsmsg_get_u32(in, "removal", &removal))
1877     removal = DVR_RET_REM_DVRCONFIG;
1878   comment = htsmsg_get_str(in, "comment");
1879   if (!(lang  = htsmsg_get_str(in, "language")))
1880     lang = htsp->htsp_language;
1881 
1882   /* Check access */
1883   if (!htsp_user_access_channel(htsp, ch))
1884     return htsp_error(htsp, N_("User does not have access"));
1885   if (!ch)
1886     return htsp_error(htsp, N_("Channel does not exist"));
1887 
1888   /* Manual timer */
1889   if (!e) {
1890 
1891     /* Required attributes */
1892     if (htsmsg_get_s64(in, "start", &start) ||
1893         htsmsg_get_s64(in, "stop", &stop) ||
1894         !(title = htsmsg_get_str(in, "title")))
1895       return htsp_error(htsp, N_("Invalid arguments"));
1896 
1897     /* Optional attributes */
1898     if (!(subtitle = htsmsg_get_str(in, "subtitle")))
1899       subtitle = "";
1900 
1901     /* Optional attributes */
1902     if (!(desc = htsmsg_get_str(in, "description")))
1903       desc = "";
1904 
1905     /* Create the dvr entry */
1906     de = dvr_entry_create_htsp(enabled, dvr_config_name, ch, start, stop,
1907                                start_extra, stop_extra,
1908                                title, subtitle, desc, lang, 0,
1909                                htsp->htsp_granted_access->aa_username,
1910                                htsp->htsp_granted_access->aa_representative,
1911                                NULL, priority, retention, removal, comment);
1912 
1913   /* Event timer */
1914   } else {
1915 
1916     de = dvr_entry_create_by_event(enabled, dvr_config_name, e,
1917                                    start_extra, stop_extra,
1918                                    htsp->htsp_granted_access->aa_username,
1919                                    htsp->htsp_granted_access->aa_representative,
1920                                    NULL, priority, retention, removal, comment);
1921 
1922   }
1923 
1924   dvr_status = de != NULL ? de->de_sched_state : DVR_NOSTATE;
1925 
1926   /* Create response */
1927   out = htsmsg_create_map();
1928 
1929   switch(dvr_status) {
1930   case DVR_SCHEDULED:
1931   case DVR_RECORDING:
1932   case DVR_MISSED_TIME:
1933   case DVR_COMPLETED:
1934     htsmsg_add_u32(out, "id", idnode_get_short_uuid(&de->de_id));
1935     htsmsg_add_u32(out, "success", 1);
1936     break;
1937   case DVR_NOSTATE:
1938     htsmsg_add_str(out, "error", "Could not add dvrEntry");
1939     htsmsg_add_u32(out, "success", 0);
1940     break;
1941   }
1942   return out;
1943 }
1944 
1945 /**
1946  * Find DVR entry
1947  */
1948 static dvr_entry_t *
htsp_findDvrEntry(htsp_connection_t * htsp,htsmsg_t * in,htsmsg_t ** out,int readonly)1949 htsp_findDvrEntry(htsp_connection_t *htsp, htsmsg_t *in, htsmsg_t **out, int readonly)
1950 {
1951   uint32_t dvrEntryId;
1952   dvr_entry_t *de;
1953 
1954   if(htsmsg_get_u32(in, "id", &dvrEntryId)) {
1955     *out = htsp_error(htsp, N_("Invalid arguments"));
1956     return NULL;
1957   }
1958 
1959   if((de = dvr_entry_find_by_id(dvrEntryId)) == NULL) {
1960     *out = htsp_error(htsp, N_("DVR entry not found"));
1961     return NULL;
1962   }
1963 
1964   if(dvr_entry_verify(de, htsp->htsp_granted_access, readonly)) {
1965     *out = htsp_error(htsp, N_("User does not have access"));
1966     return NULL;
1967   }
1968 
1969   return de;
1970 }
1971 
1972 
1973 /**
1974  * update a Dvrentry
1975  */
1976 static htsmsg_t *
htsp_method_updateDvrEntry(htsp_connection_t * htsp,htsmsg_t * in)1977 htsp_method_updateDvrEntry(htsp_connection_t *htsp, htsmsg_t *in)
1978 {
1979   htsmsg_t *out = NULL;
1980   uint32_t u32;
1981   dvr_entry_t *de;
1982   time_t start, stop, start_extra, stop_extra, priority;
1983   const char *dvr_config_name, *title, *subtitle, *desc, *lang;
1984   channel_t *channel = NULL;
1985   int enabled, retention, removal, playcount = -1, playposition = -1;
1986 
1987   de = htsp_findDvrEntry(htsp, in, &out, 0);
1988   if (de == NULL)
1989     return out;
1990 
1991   if(!htsmsg_get_u32(in, "channelId", &u32))
1992     channel = channel_find_by_id(u32);
1993   if (!channel)
1994     channel = de->de_channel;
1995 
1996   /* Check access new channel */
1997   if (channel && !htsp_user_access_channel(htsp, channel))
1998     return htsp_error(htsp, N_("User does not have access to channel"));
1999 
2000   enabled     = htsmsg_get_s64_or_default(in, "enabled",    -1);
2001   dvr_config_name = htsp_dvr_config_name(htsp, htsmsg_get_str(in, "configName"));
2002   start       = htsmsg_get_s64_or_default(in, "start",      0);
2003   stop        = htsmsg_get_s64_or_default(in, "stop",       0);
2004   start_extra = htsmsg_get_s64_or_default(in, "startExtra", 0);
2005   stop_extra  = htsmsg_get_s64_or_default(in, "stopExtra",  0);
2006   retention   = htsmsg_get_u32_or_default(in, "retention",  DVR_RET_REM_DVRCONFIG);
2007   removal     = htsmsg_get_u32_or_default(in, "removal",    DVR_RET_REM_DVRCONFIG);
2008   priority    = htsmsg_get_u32_or_default(in, "priority",   DVR_PRIO_NOTSET);
2009   title       = htsmsg_get_str(in, "title");
2010   subtitle    = htsmsg_get_str(in, "subtitle");
2011   desc        = htsmsg_get_str(in, "description");
2012   lang        = htsmsg_get_str(in, "language") ?: htsp->htsp_language;
2013 
2014   if(!htsmsg_get_u32(in, "playcount", &u32)) {
2015     if (u32 > INT_MAX)
2016       u32 = HTSP_DVR_PLAYCOUNT_INCR;
2017     switch (u32) {
2018       case HTSP_DVR_PLAYCOUNT_INCR:
2019         playcount = de->de_playcount + 1;
2020         break;
2021       case HTSP_DVR_PLAYCOUNT_SET:
2022         if (!de->de_playcount)
2023           playcount = 1;
2024         break;
2025       case HTSP_DVR_PLAYCOUNT_RESET:
2026         playcount = 0;
2027         break;
2028       case HTSP_DVR_PLAYCOUNT_KEEP:
2029         break;
2030       default:
2031         playcount = u32;
2032         break;
2033     }
2034   }
2035   if(!htsmsg_get_u32(in, "playposition", &u32))
2036     playposition = u32 > INT_MAX ? INT_MAX : u32;
2037 
2038   de = dvr_entry_update(de, enabled, dvr_config_name, channel, title, subtitle,
2039                         desc, lang, start, stop, start_extra, stop_extra,
2040                         priority, retention, removal, playcount, playposition);
2041 
2042   return htsp_success();
2043 }
2044 
2045 /**
2046  * stop a Dvrentry
2047  */
2048 static htsmsg_t *
htsp_method_stopDvrEntry(htsp_connection_t * htsp,htsmsg_t * in)2049 htsp_method_stopDvrEntry(htsp_connection_t *htsp, htsmsg_t *in)
2050 {
2051   htsmsg_t *out = NULL;
2052   dvr_entry_t *de;
2053 
2054   de = htsp_findDvrEntry(htsp, in, &out, 0);
2055   if (de == NULL)
2056     return out;
2057 
2058   dvr_entry_stop(de);
2059 
2060   return htsp_success();
2061 }
2062 
2063 /**
2064  * cancel a Dvrentry
2065  */
2066 static htsmsg_t *
htsp_method_cancelDvrEntry(htsp_connection_t * htsp,htsmsg_t * in)2067 htsp_method_cancelDvrEntry(htsp_connection_t *htsp, htsmsg_t *in)
2068 {
2069   htsmsg_t *out = NULL;
2070   dvr_entry_t *de;
2071 
2072   de = htsp_findDvrEntry(htsp, in, &out, 0);
2073   if (de == NULL)
2074     return out;
2075 
2076   dvr_entry_cancel(de, 0);
2077 
2078   return htsp_success();
2079 }
2080 
2081 /**
2082  * delete a Dvrentry
2083  */
2084 static htsmsg_t *
htsp_method_deleteDvrEntry(htsp_connection_t * htsp,htsmsg_t * in)2085 htsp_method_deleteDvrEntry(htsp_connection_t *htsp, htsmsg_t *in)
2086 {
2087   htsmsg_t *out;
2088   dvr_entry_t *de;
2089 
2090   de = htsp_findDvrEntry(htsp, in, &out, 0);
2091   if (de == NULL)
2092     return out;
2093 
2094   dvr_entry_cancel_remove(de, 0);
2095 
2096   return htsp_success();
2097 }
2098 
2099 /**
2100  * add a Dvr autorec entry
2101  */
2102 static htsmsg_t *
htsp_method_addAutorecEntry(htsp_connection_t * htsp,htsmsg_t * in)2103 htsp_method_addAutorecEntry(htsp_connection_t *htsp, htsmsg_t *in)
2104 {
2105   htsmsg_t *out;
2106   dvr_autorec_entry_t *dae;
2107   const char *str;
2108   uint32_t u32;
2109   int64_t s64;
2110   channel_t *ch = NULL;
2111   char ubuf[UUID_HEX_SIZE];
2112 
2113   /* Options */
2114   if(!(str = htsmsg_get_str(in, "title")))
2115     return htsp_error(htsp, N_("Invalid arguments"));
2116 
2117   if (htsp->htsp_version > 24) {
2118     if (!htsmsg_get_s64(in, "channelId", &s64)) { // not sending or -1 = any channel
2119       if (s64 >= 0)
2120         ch = channel_find_by_id((uint32_t)s64);
2121     }
2122   }
2123   else {
2124     if (!htsmsg_get_u32(in, "channelId", &u32))   // not sending = any channel
2125       ch = channel_find_by_id(u32);
2126   }
2127 
2128   /* Check access channel */
2129   if (ch && !htsp_user_access_channel(htsp, ch))
2130     return htsp_error(htsp, N_("User does not have access"));
2131 
2132   /* Create autorec config from htsp and add */
2133   dae = dvr_autorec_create_htsp(htsp_serierec_convert(htsp, in, ch, 1, 1));
2134 
2135   /* create response */
2136   out = htsmsg_create_map();
2137 
2138   if (dae) {
2139     htsmsg_add_str(out, "id", idnode_uuid_as_str(&dae->dae_id, ubuf));
2140     htsmsg_add_u32(out, "success", 1);
2141   }
2142   else {
2143     htsmsg_add_str(out, "error", "Could not add autorec entry");
2144     htsmsg_add_u32(out, "success", 0);
2145   }
2146   return out;
2147 }
2148 
2149 
2150 /**
2151  * update a Dvr autorec entry
2152  */
2153 static htsmsg_t *
htsp_method_updateAutorecEntry(htsp_connection_t * htsp,htsmsg_t * in)2154 htsp_method_updateAutorecEntry(htsp_connection_t *htsp, htsmsg_t *in)
2155 {
2156   const char *daeId;
2157   dvr_autorec_entry_t *dae;
2158   int64_t s64;
2159   channel_t *ch = NULL;
2160 
2161   if (!(daeId = htsmsg_get_str(in, "id")))
2162     return htsp_error(htsp, N_("Invalid arguments"));
2163 
2164   if((dae = dvr_autorec_find_by_uuid(daeId)) == NULL)
2165     return htsp_error(htsp, N_("Automatic schedule entry not found"));
2166 
2167   if(dvr_autorec_entry_verify(dae, htsp->htsp_granted_access, 0))
2168     return htsp_error(htsp, N_("User does not have access"));
2169 
2170   /* Do we have a channel? No = keep old one */
2171   if (!htsmsg_get_s64(in, "channelId", &s64)) //s64 -> -1 = any channel
2172   {
2173     if (s64 >= 0)
2174       ch = channel_find_by_id((uint32_t)s64);
2175 
2176     /* Check access new channel */
2177     if (ch && !htsp_user_access_channel(htsp, ch))
2178       return htsp_error(htsp, N_("User does not have access"));
2179   }
2180 
2181   /* Update autorec config from htsp and save */
2182   dvr_autorec_update_htsp(dae, htsp_serierec_convert(htsp, in, ch, 1, 0));
2183 
2184   return htsp_success();
2185 }
2186 
2187 
2188 /**
2189  * delete a Dvr autorec entry
2190  */
2191 static htsmsg_t *
htsp_method_deleteAutorecEntry(htsp_connection_t * htsp,htsmsg_t * in)2192 htsp_method_deleteAutorecEntry(htsp_connection_t *htsp, htsmsg_t *in)
2193 {
2194   const char *daeId;
2195   dvr_autorec_entry_t *dae;
2196 
2197   if (!(daeId = htsmsg_get_str(in, "id")))
2198     return htsp_error(htsp, N_("Invalid arguments"));
2199 
2200   if((dae = dvr_autorec_find_by_uuid(daeId)) == NULL)
2201     return htsp_error(htsp, N_("Automatic schedule entry not found"));
2202 
2203   if(dvr_autorec_entry_verify(dae, htsp->htsp_granted_access, 0))
2204     return htsp_error(htsp, N_("User does not have access"));
2205 
2206   autorec_destroy_by_id(daeId, 1);
2207 
2208   return htsp_success();
2209 }
2210 
2211 /**
2212  * add a Dvr timerec entry
2213  */
2214 static htsmsg_t *
htsp_method_addTimerecEntry(htsp_connection_t * htsp,htsmsg_t * in)2215 htsp_method_addTimerecEntry(htsp_connection_t *htsp, htsmsg_t *in)
2216 {
2217   htsmsg_t *out;
2218   dvr_timerec_entry_t *dte;
2219   const char *str;
2220   channel_t *ch = NULL;
2221   uint32_t u32;
2222   int64_t s64;
2223   char ubuf[UUID_HEX_SIZE];
2224 
2225   /* Options */
2226   if(!(str = htsmsg_get_str(in, "title")))
2227     return htsp_error(htsp, N_("Invalid arguments"));
2228 
2229   if (htsp->htsp_version > 24) {
2230     if (!htsmsg_get_s64(in, "channelId", &s64)) { // not sending or -1 = any channel
2231       if (s64 >= 0)
2232         ch = channel_find_by_id((uint32_t)s64);
2233     }
2234   }
2235   else {
2236     if (!htsmsg_get_u32(in, "channelId", &u32))   // not sending = any channel
2237       ch = channel_find_by_id(u32);
2238   }
2239 
2240   /* Check access channel */
2241   if (ch && !htsp_user_access_channel(htsp, ch))
2242     return htsp_error(htsp, N_("User does not have access"));
2243 
2244   /* Create timerec config from htsp and add */
2245   dte = dvr_timerec_create_htsp(htsp_serierec_convert(htsp, in, ch, 0, 1));
2246 
2247   /* create response */
2248   out = htsmsg_create_map();
2249 
2250   if (dte) {
2251     htsmsg_add_str(out, "id", idnode_uuid_as_str(&dte->dte_id, ubuf));
2252     htsmsg_add_u32(out, "success", 1);
2253   }
2254   else {
2255     htsmsg_add_str(out, "error", "Could not add timerec entry");
2256     htsmsg_add_u32(out, "success", 0);
2257   }
2258   return out;
2259 }
2260 
2261 /**
2262  * update a Dvr timerec entry
2263  */
2264 static htsmsg_t *
htsp_method_updateTimerecEntry(htsp_connection_t * htsp,htsmsg_t * in)2265 htsp_method_updateTimerecEntry(htsp_connection_t *htsp, htsmsg_t *in)
2266 {
2267   const char *dteId;
2268   dvr_timerec_entry_t *dte;
2269   int64_t s64;
2270   channel_t *ch = NULL;
2271 
2272   if (!(dteId = htsmsg_get_str(in, "id")))
2273     return htsp_error(htsp, N_("Invalid arguments"));
2274 
2275   if((dte = dvr_timerec_find_by_uuid(dteId)) == NULL)
2276     return htsp_error(htsp, N_("Automatic time scheduler entry not found"));
2277 
2278   if(dvr_timerec_entry_verify(dte, htsp->htsp_granted_access, 0))
2279     return htsp_error(htsp, N_("User does not have access"));
2280 
2281   /* Do we have a channel? No = keep old one */
2282   if (!htsmsg_get_s64(in, "channelId", &s64)) //s64 -> -1 = any channel
2283   {
2284     if (s64 >= 0)
2285       ch = channel_find_by_id((uint32_t)s64);
2286 
2287     /* Check access new channel */
2288     if (ch && !htsp_user_access_channel(htsp, ch))
2289       return htsp_error(htsp, N_("User does not have access"));
2290   }
2291 
2292   /* Update timerec config from htsp and save */
2293   dvr_timerec_update_htsp(dte, htsp_serierec_convert(htsp, in, ch, 0, 0));
2294 
2295   return htsp_success();
2296 }
2297 
2298 /**
2299  * delete a Dvr timerec entry
2300  */
2301 static htsmsg_t *
htsp_method_deleteTimerecEntry(htsp_connection_t * htsp,htsmsg_t * in)2302 htsp_method_deleteTimerecEntry(htsp_connection_t *htsp, htsmsg_t *in)
2303 {
2304   const char *dteId;
2305   dvr_timerec_entry_t *dte;
2306 
2307   if (!(dteId = htsmsg_get_str(in, "id")))
2308     return htsp_error(htsp, N_("Invalid arguments"));
2309 
2310   if((dte = dvr_timerec_find_by_uuid(dteId)) == NULL)
2311     return htsp_error(htsp, N_("Automatic time scheduler entry not found"));
2312 
2313   if(dvr_timerec_entry_verify(dte, htsp->htsp_granted_access, 0))
2314     return htsp_error(htsp, N_("User does not have access"));
2315 
2316   timerec_destroy_by_id(dteId, 1);
2317 
2318   return htsp_success();
2319 }
2320 
2321 /**
2322  * Return cutpoint data for a recording (if present).
2323  *
2324  * Request message fields:
2325  * id                 u32    required   DVR entry id
2326  *
2327  * Result message fields:
2328  * cutpoints          msg[]  optional   List of cutpoint entries, if a file is
2329  *                                      found and has some valid data.
2330  *
2331  * Cutpoint fields:
2332  * start              u32    required   Cut start time in ms.
2333  * end                u32    required   Cut end time in ms.
2334  * type               u32    required   Action type:
2335  *                                      0=Cut, 1=Mute, 2=Scene,
2336  *                                      3=Commercial break.
2337  **/
2338 static htsmsg_t *
htsp_method_getDvrCutpoints(htsp_connection_t * htsp,htsmsg_t * in)2339 htsp_method_getDvrCutpoints(htsp_connection_t *htsp, htsmsg_t *in)
2340 {
2341   uint32_t dvrEntryId;
2342   dvr_entry_t *de;
2343   if (htsmsg_get_u32(in, "id", &dvrEntryId))
2344     return htsp_error(htsp, N_("Invalid arguments"));
2345 
2346   if((de = dvr_entry_find_by_id(dvrEntryId)) == NULL)
2347     return htsp_error(htsp, N_("DVR schedule not found"));
2348 
2349   if(dvr_entry_verify(de, htsp->htsp_granted_access, 1))
2350     return htsp_error(htsp, N_("User does not have access"));
2351 
2352   htsmsg_t *msg = htsmsg_create_map();
2353 
2354   dvr_cutpoint_list_t *list = dvr_get_cutpoint_list(de);
2355 
2356   if (list != NULL) {
2357     htsmsg_t *cutpoint_list = htsmsg_create_list();
2358     dvr_cutpoint_t *cp;
2359     TAILQ_FOREACH(cp, list, dc_link) {
2360       htsmsg_t *cutpoint = htsmsg_create_map();
2361       htsmsg_add_u32(cutpoint, "start", cp->dc_start_ms);
2362       htsmsg_add_u32(cutpoint, "end", cp->dc_end_ms);
2363       htsmsg_add_u32(cutpoint, "type", cp->dc_type);
2364 
2365       htsmsg_add_msg(cutpoint_list, NULL, cutpoint);
2366     }
2367     htsmsg_add_msg(msg, "cutpoints", cutpoint_list);
2368   }
2369 
2370   // Cleanup...
2371   dvr_cutpoint_list_destroy(list);
2372 
2373   return msg;
2374 }
2375 
2376 /**
2377  * Request a ticket for a http url pointing to a channel or dvr
2378  */
2379 static htsmsg_t *
htsp_method_getTicket(htsp_connection_t * htsp,htsmsg_t * in)2380 htsp_method_getTicket(htsp_connection_t *htsp, htsmsg_t *in)
2381 {
2382   htsmsg_t *out;
2383   uint32_t id;
2384   char path[255];
2385   const char *ticket = NULL;
2386   channel_t *ch;
2387   dvr_entry_t *de;
2388 
2389   if(!htsmsg_get_u32(in, "channelId", &id)) {
2390     if (!(ch = channel_find_by_id(id)))
2391       return htsp_error(htsp, N_("Channel not found"));
2392     if (!htsp_user_access_channel(htsp, ch))
2393       return htsp_error(htsp, N_("User does not have access"));
2394 
2395     snprintf(path, sizeof(path), "/stream/channelid/%d", id);
2396     ticket = access_ticket_create(path, htsp->htsp_granted_access);
2397   } else if(!htsmsg_get_u32(in, "dvrId", &id)) {
2398     if (!(de = dvr_entry_find_by_id(id)))
2399       return htsp_error(htsp, N_("DVR schedule does not exist"));
2400     if (!htsp_user_access_channel(htsp, de->de_channel))
2401       return htsp_error(htsp, N_("User does not have access"));
2402 
2403     snprintf(path, sizeof(path), "/dvrfile/%d", id);
2404     ticket = access_ticket_create(path, htsp->htsp_granted_access);
2405   } else {
2406     return htsp_error(htsp, N_("Invalid arguments"));
2407   }
2408 
2409   out = htsmsg_create_map();
2410 
2411   htsmsg_add_str(out, "path", path);
2412   htsmsg_add_str(out, "ticket", ticket);
2413 
2414   return out;
2415 }
2416 
2417 /*
2418  *
2419  */
_bytes_out_cb(void * aux)2420 static void _bytes_out_cb(void *aux)
2421 {
2422   htsp_subscription_t *hs = aux;
2423   if (hs->hs_s) {
2424     subscription_add_bytes_out(hs->hs_s, atomic_exchange(&hs->hs_s_bytes_out, 0));
2425     mtimer_arm_rel(&hs->hs_s_bytes_out_timer, _bytes_out_cb, hs, ms2mono(200));
2426   }
2427 }
2428 
2429 /**
2430  * Request subscription for a channel
2431  */
2432 static htsmsg_t *
htsp_method_subscribe(htsp_connection_t * htsp,htsmsg_t * in)2433 htsp_method_subscribe(htsp_connection_t *htsp, htsmsg_t *in)
2434 {
2435   uint32_t chid, sid, weight, req90khz, timeshiftPeriod = 0;
2436   const char *str, *profile_id;
2437   channel_t *ch;
2438   htsp_subscription_t *hs;
2439   profile_t *pro;
2440 
2441   if(htsmsg_get_u32(in, "subscriptionId", &sid))
2442     return htsp_error(htsp, N_("Invalid arguments"));
2443 
2444   if(!htsmsg_get_u32(in, "channelId", &chid)) {
2445     if((ch = channel_find_by_id(chid)) == NULL)
2446       return htsp_error(htsp, N_("Channel does not exist"));
2447   } else if((str = htsmsg_get_str(in, "channelName")) != NULL) {
2448     if((ch = channel_find_by_name(str)) == NULL)
2449       return htsp_error(htsp, N_("Channel does not exist"));
2450   } else {
2451     return htsp_error(htsp, N_("Invalid arguments"));
2452   }
2453   if (!htsp_user_access_channel(htsp, ch))
2454     return htsp_error(htsp, N_("User does not have access"));
2455 
2456   weight = htsmsg_get_u32_or_default(in, "weight", 0);
2457   req90khz = htsmsg_get_u32_or_default(in, "90khz", 0);
2458 
2459   profile_id = htsmsg_get_str(in, "profile");
2460 
2461 #if ENABLE_TIMESHIFT
2462   if (timeshift_conf.enabled) {
2463     timeshiftPeriod = htsmsg_get_u32_or_default(in, "timeshiftPeriod", 0);
2464     if (!timeshift_conf.unlimited_period)
2465       timeshiftPeriod = MIN(timeshiftPeriod, timeshift_conf.max_period * 60);
2466   }
2467 #endif
2468 
2469   /* Initialize the HTSP subscription structure */
2470 
2471   hs = calloc(1, sizeof(htsp_subscription_t));
2472 
2473   hs->hs_htsp = htsp;
2474   hs->hs_90khz = req90khz;
2475   hs->hs_queue_depth = htsmsg_get_u32_or_default(in, "queueDepth",
2476 						 HTSP_DEFAULT_QUEUE_DEPTH);
2477   htsp_init_queue(&hs->hs_q, 0);
2478 
2479   hs->hs_sid = sid;
2480   streaming_target_init(&hs->hs_input, &htsp_streaming_input_ops, hs, 0);
2481 
2482 #if ENABLE_TIMESHIFT
2483   if (timeshiftPeriod != 0) {
2484     if (timeshiftPeriod == ~0)
2485       tvhdebug(LS_HTSP, "using timeshift buffer (unlimited)");
2486     else
2487       tvhdebug(LS_HTSP, "using timeshift buffer (%u mins)", timeshiftPeriod / 60);
2488   }
2489 #endif
2490 
2491   pro = profile_find_by_list(htsp->htsp_granted_access->aa_profiles, profile_id,
2492                              "htsp", SUBSCRIPTION_PACKET | SUBSCRIPTION_HTSP);
2493   profile_chain_init(&hs->hs_prch, pro, ch, 1);
2494   if (profile_chain_work(&hs->hs_prch, &hs->hs_input, timeshiftPeriod, 0)) {
2495     tvherror(LS_HTSP, "unable to create profile chain '%s'", profile_get_name(pro));
2496     profile_chain_close(&hs->hs_prch);
2497     free(hs);
2498     return htsp_error(htsp, N_("Stream setup error"));
2499   }
2500 
2501   /*
2502    * We send the reply now to avoid the user getting the 'subscriptionStart'
2503    * async message before the reply to 'subscribe'.
2504    *
2505    * Send some optional boolean flags back to the subscriber so it can infer
2506    * if we support those
2507    *
2508    */
2509   htsmsg_t *rep = htsmsg_create_map();
2510   if(req90khz)
2511     htsmsg_add_u32(rep, "90khz", 1);
2512   if(hs->hs_prch.prch_sharer->prsh_tsfix)
2513     htsmsg_add_u32(rep, "normts", 1);
2514   if(hs->hs_s)
2515     htsmsg_add_u32(rep, "weight", hs->hs_s->ths_weight >= 0 ? hs->hs_s->ths_weight : 0);
2516 
2517 #if ENABLE_TIMESHIFT
2518   if(timeshiftPeriod)
2519     htsmsg_add_u32(rep, "timeshiftPeriod", timeshiftPeriod);
2520 #endif
2521 
2522   htsp_reply(htsp, in, rep);
2523 
2524   /*
2525    * subscribe now...
2526    */
2527   LIST_INSERT_HEAD(&htsp->htsp_subscriptions, hs, hs_link);
2528 
2529   tvhdebug(LS_HTSP, "%s - subscribe to %s using profile %s",
2530            htsp->htsp_logname, channel_get_name(ch), profile_get_name(pro));
2531   hs->hs_s = subscription_create_from_channel(&hs->hs_prch, NULL, weight,
2532 					      htsp->htsp_logname,
2533 					      SUBSCRIPTION_PACKET |
2534 					      SUBSCRIPTION_STREAMING,
2535 					      htsp->htsp_peername,
2536 					      htsp->htsp_granted_access->aa_representative,
2537 					      htsp->htsp_clientname,
2538 					      NULL);
2539   if (hs->hs_s)
2540     mtimer_arm_rel(&hs->hs_s_bytes_out_timer, _bytes_out_cb, hs, ms2mono(200));
2541   return NULL;
2542 }
2543 
2544 /**
2545  * Request unsubscription for a channel
2546  */
2547 static htsmsg_t *
htsp_method_unsubscribe(htsp_connection_t * htsp,htsmsg_t * in)2548 htsp_method_unsubscribe(htsp_connection_t *htsp, htsmsg_t *in)
2549 {
2550   htsp_subscription_t *s;
2551   uint32_t sid;
2552 
2553   if(htsmsg_get_u32(in, "subscriptionId", &sid))
2554     return htsp_error(htsp, N_("Invalid arguments"));
2555 
2556   LIST_FOREACH(s, &htsp->htsp_subscriptions, hs_link)
2557     if(s->hs_sid == sid)
2558       break;
2559 
2560   /*
2561    * We send the reply here or the user will get the 'subscriptionStop'
2562    * async message before the reply to 'unsubscribe'.
2563    */
2564   htsp_reply(htsp, in, htsmsg_create_map());
2565 
2566   if(s == NULL)
2567     return NULL; /* Subscription did not exist, but we don't really care */
2568 
2569   htsp_subscription_destroy(htsp, s);
2570   return NULL;
2571 }
2572 
2573 /**
2574  * Change weight for a subscription
2575  */
2576 static htsmsg_t *
htsp_method_change_weight(htsp_connection_t * htsp,htsmsg_t * in)2577 htsp_method_change_weight(htsp_connection_t *htsp, htsmsg_t *in)
2578 {
2579   htsp_subscription_t *hs;
2580   uint32_t sid, weight;
2581 
2582   if(htsmsg_get_u32(in, "subscriptionId", &sid))
2583     return htsp_error(htsp, N_("Invalid arguments"));
2584 
2585   weight = htsmsg_get_u32_or_default(in, "weight", 0);
2586 
2587   LIST_FOREACH(hs, &htsp->htsp_subscriptions, hs_link)
2588     if(hs->hs_sid == sid)
2589       break;
2590 
2591   if(hs == NULL)
2592     return htsp_error(htsp, N_("Subscription does not exist"));
2593 
2594   htsp_reply(htsp, in, htsmsg_create_map());
2595 
2596   subscription_change_weight(hs->hs_s, weight);
2597   return NULL;
2598 }
2599 
2600 /**
2601  * Skip stream
2602  */
2603 static htsmsg_t *
htsp_method_skip(htsp_connection_t * htsp,htsmsg_t * in)2604 htsp_method_skip(htsp_connection_t *htsp, htsmsg_t *in)
2605 {
2606   htsp_subscription_t *hs;
2607   uint32_t sid, abs;
2608   int64_t s64;
2609   streaming_skip_t skip;
2610 
2611   if(htsmsg_get_u32(in, "subscriptionId", &sid))
2612     return htsp_error(htsp, N_("Invalid arguments"));
2613 
2614   LIST_FOREACH(hs, &htsp->htsp_subscriptions, hs_link)
2615     if(hs->hs_sid == sid)
2616       break;
2617 
2618   if(hs == NULL)
2619     return htsp_error(htsp, N_("Subscription does not exist"));
2620 
2621   abs = htsmsg_get_u32_or_default(in, "absolute", 0);
2622 
2623   memset(&skip, 0, sizeof(skip));
2624   if(!htsmsg_get_s64(in, "time", &s64)) {
2625     skip.type = abs ? SMT_SKIP_ABS_TIME : SMT_SKIP_REL_TIME;
2626     skip.time = hs->hs_90khz ? s64 : ts_rescale_inv(s64, 1000000);
2627     tvhtrace(LS_HTSP_SUB, "skip: %s %"PRId64" (%s)", abs ? "abs" : "rel",
2628              skip.time, hs->hs_90khz ? "90kHz" : "1MHz");
2629   } else if (!htsmsg_get_s64(in, "size", &s64)) {
2630     skip.type = abs ? SMT_SKIP_ABS_SIZE : SMT_SKIP_REL_SIZE;
2631     skip.size = s64;
2632     tvhtrace(LS_HTSP_SUB, "skip: %s by size %"PRId64, abs ? "abs" : "rel", s64);
2633   } else {
2634     return htsp_error(htsp, N_("Invalid arguments"));
2635   }
2636 
2637   subscription_set_skip(hs->hs_s, &skip);
2638 
2639   htsp_reply(htsp, in, htsmsg_create_map());
2640   return NULL;
2641 }
2642 
2643 /*
2644  * Set stream speed
2645  */
2646 static htsmsg_t *
htsp_method_speed(htsp_connection_t * htsp,htsmsg_t * in)2647 htsp_method_speed(htsp_connection_t *htsp, htsmsg_t *in)
2648 {
2649   htsp_subscription_t *hs;
2650   uint32_t sid;
2651   int32_t speed;
2652 
2653   if(htsmsg_get_u32(in, "subscriptionId", &sid))
2654     return htsp_error(htsp, N_("Invalid arguments"));
2655   if(htsmsg_get_s32(in, "speed", &speed))
2656     return htsp_error(htsp, N_("Invalid arguments"));
2657 
2658   LIST_FOREACH(hs, &htsp->htsp_subscriptions, hs_link)
2659     if(hs->hs_sid == sid)
2660       break;
2661 
2662   if(hs == NULL)
2663     return htsp_error(htsp, N_("Subscription does not exist"));
2664 
2665   tvhtrace(LS_HTSP_SUB, "speed: %d", speed);
2666   subscription_set_speed(hs->hs_s, speed);
2667 
2668   htsp_reply(htsp, in, htsmsg_create_map());
2669   return NULL;
2670 }
2671 
2672 /*
2673  * Revert to live
2674  */
2675 static htsmsg_t *
htsp_method_live(htsp_connection_t * htsp,htsmsg_t * in)2676 htsp_method_live(htsp_connection_t *htsp, htsmsg_t *in)
2677 {
2678   htsp_subscription_t *hs;
2679   uint32_t sid;
2680   streaming_skip_t skip;
2681 
2682   if(htsmsg_get_u32(in, "subscriptionId", &sid))
2683     return htsp_error(htsp, N_("Invalid arguments"));
2684 
2685   LIST_FOREACH(hs, &htsp->htsp_subscriptions, hs_link)
2686     if(hs->hs_sid == sid)
2687       break;
2688 
2689   if(hs == NULL)
2690     return htsp_error(htsp, N_("Subscription does not exist"));
2691 
2692   memset(&skip, 0, sizeof(skip));
2693   skip.type = SMT_SKIP_LIVE;
2694   tvhtrace(LS_HTSP_SUB, "live");
2695   subscription_set_skip(hs->hs_s, &skip);
2696 
2697   htsp_reply(htsp, in, htsmsg_create_map());
2698   return NULL;
2699 }
2700 
2701 /**
2702  * Change filters for a subscription
2703  */
2704 static htsmsg_t *
htsp_method_filter_stream(htsp_connection_t * htsp,htsmsg_t * in)2705 htsp_method_filter_stream(htsp_connection_t *htsp, htsmsg_t *in)
2706 {
2707   htsp_subscription_t *hs;
2708   uint32_t sid;
2709   htsmsg_t *l;
2710   if(htsmsg_get_u32(in, "subscriptionId", &sid))
2711     return htsp_error(htsp, N_("Invalid arguments"));
2712 
2713   LIST_FOREACH(hs, &htsp->htsp_subscriptions, hs_link)
2714     if(hs->hs_sid == sid)
2715       break;
2716 
2717   if(hs == NULL)
2718     return htsp_error(htsp, N_("Subscription does not exist"));
2719 
2720   if((l = htsmsg_get_list(in, "enable")) != NULL) {
2721     htsmsg_field_t *f;
2722     HTSMSG_FOREACH(f, l) {
2723       if(f->hmf_type == HMF_S64)
2724         htsp_enable_stream(hs, f->hmf_s64);
2725     }
2726   }
2727 
2728   if((l = htsmsg_get_list(in, "disable")) != NULL) {
2729     htsmsg_field_t *f;
2730     HTSMSG_FOREACH(f, l) {
2731       if(f->hmf_type == HMF_S64)
2732         htsp_disable_stream(hs, f->hmf_s64);
2733     }
2734   }
2735   return htsmsg_create_map();
2736 }
2737 
2738 
2739 /**
2740  * Open file
2741  */
2742 static htsmsg_t *
htsp_method_file_open(htsp_connection_t * htsp,htsmsg_t * in)2743 htsp_method_file_open(htsp_connection_t *htsp, htsmsg_t *in)
2744 {
2745   const char *str, *s2;
2746   const char *filename = NULL;
2747   char buf[PATH_MAX];
2748 
2749   if((str = htsmsg_get_str(in, "file")) == NULL)
2750     return htsp_error(htsp, N_("Invalid arguments"));
2751 
2752   // optional leading slash
2753   if (*str == '/')
2754     str++;
2755 
2756   if((s2 = tvh_strbegins(str, "dvr/")) != NULL ||
2757      (s2 = tvh_strbegins(str, "dvrfile/")) != NULL) {
2758     dvr_entry_t *de = dvr_entry_find_by_id(atoi(s2));
2759     if(de == NULL)
2760       return htsp_error(htsp, N_("DVR schedule does not exist"));
2761 
2762     if (dvr_entry_verify(de, htsp->htsp_granted_access, 1))
2763       return htsp_error(htsp, N_("User does not have access"));
2764 
2765     filename = dvr_get_filename(de);
2766 
2767     if (filename == NULL)
2768       return htsp_error(htsp, N_("DVR schedule does not have a file yet"));
2769 
2770     return htsp_file_open(htsp, filename, 0, de);
2771 
2772   } else if ((s2 = tvh_strbegins(str, "imagecache/")) != NULL) {
2773     int fd = -1;
2774     if (!imagecache_filename(atoi(s2), buf, sizeof(buf)))
2775       fd = tvh_open(buf, O_RDONLY, 0);
2776     if (fd < 0)
2777       return htsp_error(htsp, N_("Failed to open image"));
2778     return htsp_file_open(htsp, str, fd, NULL);
2779 
2780   } else {
2781     return htsp_error(htsp, N_("Unknown file"));
2782   }
2783 }
2784 
2785 /**
2786  *
2787  */
2788 static htsmsg_t *
htsp_method_file_read(htsp_connection_t * htsp,htsmsg_t * in)2789 htsp_method_file_read(htsp_connection_t *htsp, htsmsg_t *in)
2790 {
2791   htsp_file_t *hf = htsp_file_find(htsp, in);
2792   htsmsg_t *rep = NULL;
2793   const char *e = NULL;
2794   int64_t off;
2795   int64_t size;
2796   int fd;
2797 
2798   if(hf == NULL)
2799     return htsp_error(htsp, N_("Invalid file"));
2800 
2801   if(htsmsg_get_s64(in, "size", &size))
2802     return htsp_error(htsp, N_("Invalid parameters"));
2803 
2804   fd = hf->hf_fd;
2805 
2806   pthread_mutex_unlock(&global_lock);
2807 
2808   /* Seek (optional) */
2809   if (!htsmsg_get_s64(in, "offset", &off))
2810     if(lseek(fd, off, SEEK_SET) != off) {
2811       e = "Seek error";
2812       goto error;
2813     }
2814 
2815   /* Read */
2816   void *m = malloc(size);
2817   if(m == NULL) {
2818     e = N_("Not enough memory");
2819     goto error;
2820   }
2821 
2822   int r = read(fd, m, size);
2823   if(r < 0) {
2824     free(m);
2825     e = N_("Read error");
2826     goto error;
2827   }
2828 
2829   htsp_file_update_stats(hf, r);
2830 
2831   rep = htsmsg_create_map();
2832   htsmsg_add_bin(rep, "data", m, r);
2833   free(m);
2834 
2835 error:
2836   pthread_mutex_lock(&global_lock);
2837   return e ? htsp_error(htsp, e) : rep;
2838 }
2839 
2840 /**
2841  *
2842  */
2843 static htsmsg_t *
htsp_method_file_close(htsp_connection_t * htsp,htsmsg_t * in)2844 htsp_method_file_close(htsp_connection_t *htsp, htsmsg_t *in)
2845 {
2846   htsp_file_t *hf = htsp_file_find(htsp, in);
2847   uint32_t u32;
2848   dvr_entry_t *de;
2849 
2850   if(hf == NULL)
2851     return htsp_error(htsp, N_("Invalid file"));
2852 
2853   if (hf->hf_de_id > 0 && (de = dvr_entry_find_by_id(hf->hf_de_id)))
2854   {
2855     int save = 0;
2856     /* Only allow incrementing playcount on file close, the rest can be done with "updateDvrEntry" */
2857     if (htsp->htsp_version < 27 || htsmsg_get_u32_or_default(in, "playcount", HTSP_DVR_PLAYCOUNT_INCR) == HTSP_DVR_PLAYCOUNT_INCR) {
2858       de->de_playcount++;
2859       save = 1;
2860     }
2861     if(htsp->htsp_version >= 27 && !htsmsg_get_u32(in, "playposition", &u32)) {
2862       de->de_playposition = u32;
2863       save = 1;
2864     }
2865     if (save)
2866       dvr_entry_changed_notify(de);
2867   }
2868 
2869   pthread_mutex_unlock(&global_lock);
2870   htsp_file_destroy(hf);
2871   pthread_mutex_lock(&global_lock);
2872   return htsmsg_create_map();
2873 }
2874 
2875 /**
2876  *
2877  */
2878 static htsmsg_t *
htsp_method_file_stat(htsp_connection_t * htsp,htsmsg_t * in)2879 htsp_method_file_stat(htsp_connection_t *htsp, htsmsg_t *in)
2880 {
2881   htsp_file_t *hf = htsp_file_find(htsp, in);
2882   htsmsg_t *rep;
2883   struct stat st;
2884   int fd;
2885 
2886   if(hf == NULL)
2887     return htsp_error(htsp, N_("Invalid file"));
2888 
2889   fd = hf->hf_fd;
2890 
2891   pthread_mutex_unlock(&global_lock);
2892   rep = htsmsg_create_map();
2893   if(!fstat(fd, &st)) {
2894     htsmsg_add_s64(rep, "size", st.st_size);
2895     htsmsg_add_s64(rep, "mtime", st.st_mtime);
2896   }
2897   pthread_mutex_lock(&global_lock);
2898 
2899   return rep;
2900 }
2901 
2902 /**
2903  *
2904  */
2905 static htsmsg_t *
htsp_method_file_seek(htsp_connection_t * htsp,htsmsg_t * in)2906 htsp_method_file_seek(htsp_connection_t *htsp, htsmsg_t *in)
2907 {
2908   htsp_file_t *hf = htsp_file_find(htsp, in);
2909   htsmsg_t *rep;
2910   const char *str;
2911   int64_t off;
2912   int fd, whence;
2913 
2914   if(hf == NULL)
2915     return htsp_error(htsp, N_("Invalid file"));
2916 
2917   if (htsmsg_get_s64(in, "offset", &off))
2918     return htsp_error(htsp, N_("Invalid parameters"));
2919 
2920   if ((str = htsmsg_get_str(in, "whence"))) {
2921     if (!strcmp(str, "SEEK_SET"))
2922       whence = SEEK_SET;
2923     else if (!strcmp(str, "SEEK_CUR"))
2924       whence = SEEK_CUR;
2925     else if (!strcmp(str, "SEEK_END"))
2926       whence = SEEK_END;
2927     else
2928       return htsp_error(htsp, N_("Invalid parameters"));
2929   } else {
2930     whence = SEEK_SET;
2931   }
2932 
2933   fd = hf->hf_fd;
2934   pthread_mutex_unlock(&global_lock);
2935 
2936   if ((off = lseek(fd, off, whence)) < 0) {
2937     pthread_mutex_lock(&global_lock);
2938     return htsp_error(htsp, N_("Seek error"));
2939   }
2940 
2941   rep = htsmsg_create_map();
2942   htsmsg_add_s64(rep, "offset", off);
2943 
2944   pthread_mutex_lock(&global_lock);
2945   return rep;
2946 }
2947 
2948 /**
2949  *
2950  */
2951 static htsmsg_t *
htsp_method_getProfiles(htsp_connection_t * htsp,htsmsg_t * in)2952 htsp_method_getProfiles(htsp_connection_t *htsp, htsmsg_t *in)
2953 {
2954   htsmsg_t *out, *l;
2955 
2956   l = htsmsg_create_list();
2957   profile_get_htsp_list(l, htsp->htsp_granted_access->aa_profiles);
2958 
2959   out = htsmsg_create_map();
2960 
2961   htsmsg_add_msg(out, "profiles", l);
2962 
2963   return out;
2964 }
2965 
2966 /**
2967  * HTSP methods
2968  */
2969 struct {
2970   const char *name;
2971   htsmsg_t *(*fn)(htsp_connection_t *htsp, htsmsg_t *in);
2972   int privmask;
2973 } htsp_methods[] = {
2974   { "hello",                    htsp_method_hello,              ACCESS_ANONYMOUS},
2975   { "authenticate",             htsp_method_authenticate,       ACCESS_ANONYMOUS},
2976   { "api",                      htsp_method_api,                ACCESS_ANONYMOUS},
2977   { "getDiskSpace",             htsp_method_getDiskSpace,       ACCESS_HTSP_STREAMING},
2978   { "getSysTime",               htsp_method_getSysTime,         ACCESS_HTSP_STREAMING},
2979   { "enableAsyncMetadata",      htsp_method_async,              ACCESS_HTSP_STREAMING},
2980   { "getChannel",               htsp_method_getChannel,         ACCESS_HTSP_STREAMING},
2981   { "getEvent",                 htsp_method_getEvent,           ACCESS_HTSP_STREAMING},
2982   { "getEvents",                htsp_method_getEvents,          ACCESS_HTSP_STREAMING},
2983   { "epgQuery",                 htsp_method_epgQuery,           ACCESS_HTSP_STREAMING},
2984   { "getEpgObject",             htsp_method_getEpgObject,       ACCESS_HTSP_STREAMING},
2985   { "getDvrConfigs",            htsp_method_getDvrConfigs,      ACCESS_HTSP_RECORDER},
2986   { "addDvrEntry",              htsp_method_addDvrEntry,        ACCESS_HTSP_RECORDER},
2987   { "updateDvrEntry",           htsp_method_updateDvrEntry,     ACCESS_HTSP_RECORDER},
2988   { "stopDvrEntry",             htsp_method_stopDvrEntry,       ACCESS_HTSP_RECORDER},
2989   { "cancelDvrEntry",           htsp_method_cancelDvrEntry,     ACCESS_HTSP_RECORDER},
2990   { "deleteDvrEntry",           htsp_method_deleteDvrEntry,     ACCESS_HTSP_RECORDER},
2991   { "addAutorecEntry",          htsp_method_addAutorecEntry,    ACCESS_HTSP_RECORDER},
2992   { "updateAutorecEntry",       htsp_method_updateAutorecEntry, ACCESS_HTSP_RECORDER},
2993   { "deleteAutorecEntry",       htsp_method_deleteAutorecEntry, ACCESS_HTSP_RECORDER},
2994   { "addTimerecEntry",          htsp_method_addTimerecEntry,    ACCESS_HTSP_RECORDER},
2995   { "updateTimerecEntry",       htsp_method_updateTimerecEntry, ACCESS_HTSP_RECORDER},
2996   { "deleteTimerecEntry",       htsp_method_deleteTimerecEntry, ACCESS_HTSP_RECORDER},
2997   { "getDvrCutpoints",          htsp_method_getDvrCutpoints,    ACCESS_HTSP_RECORDER},
2998   { "getTicket",                htsp_method_getTicket,          ACCESS_HTSP_STREAMING},
2999   { "subscribe",                htsp_method_subscribe,          ACCESS_HTSP_STREAMING},
3000   { "unsubscribe",              htsp_method_unsubscribe,        ACCESS_HTSP_STREAMING},
3001   { "subscriptionChangeWeight", htsp_method_change_weight,      ACCESS_HTSP_STREAMING},
3002   { "subscriptionSeek",         htsp_method_skip,               ACCESS_HTSP_STREAMING},
3003   { "subscriptionSkip",         htsp_method_skip,               ACCESS_HTSP_STREAMING},
3004   { "subscriptionSpeed",        htsp_method_speed,              ACCESS_HTSP_STREAMING},
3005   { "subscriptionLive",         htsp_method_live,               ACCESS_HTSP_STREAMING},
3006   { "subscriptionFilterStream", htsp_method_filter_stream,      ACCESS_HTSP_STREAMING},
3007   { "getProfiles",              htsp_method_getProfiles,        ACCESS_HTSP_STREAMING},
3008   { "fileOpen",                 htsp_method_file_open,          ACCESS_HTSP_RECORDER},
3009   { "fileRead",                 htsp_method_file_read,          ACCESS_HTSP_RECORDER},
3010   { "fileClose",                htsp_method_file_close,         ACCESS_HTSP_RECORDER},
3011   { "fileStat",                 htsp_method_file_stat,          ACCESS_HTSP_RECORDER},
3012   { "fileSeek",                 htsp_method_file_seek,          ACCESS_HTSP_RECORDER},
3013 };
3014 
3015 #define NUM_METHODS (sizeof(htsp_methods) / sizeof(htsp_methods[0]))
3016 
3017 /* **************************************************************************
3018  * Message processing
3019  * *************************************************************************/
3020 
3021 /**
3022  *
3023  */
3024 struct htsp_verify_struct {
3025   const uint8_t *digest;
3026   const uint8_t *challenge;
3027 };
3028 
3029 static int
htsp_verify_callback(void * aux,const char * passwd)3030 htsp_verify_callback(void *aux, const char *passwd)
3031 {
3032   struct htsp_verify_struct *v = aux;
3033   uint8_t d[20];
3034 
3035   if (v->digest == NULL || v->challenge == NULL) return 0;
3036   sha1_calc(d, (uint8_t *)passwd, strlen(passwd), v->challenge, 32);
3037   return memcmp(d, v->digest, 20) == 0;
3038 }
3039 
3040 /**
3041  * Raise privs by field in message
3042  */
3043 static int
htsp_authenticate(htsp_connection_t * htsp,htsmsg_t * m)3044 htsp_authenticate(htsp_connection_t *htsp, htsmsg_t *m)
3045 {
3046   struct htsp_verify_struct vs;
3047   const char *username;
3048   const void *digest;
3049   size_t digestlen;
3050   access_t *rights;
3051   int privgain = 0;
3052 
3053   if((username = htsmsg_get_str(m, "username")) == NULL)
3054     return 0;
3055 
3056   if(!htsmsg_get_bin(m, "digest", &digest, &digestlen)) {
3057 
3058     vs.digest = digest;
3059     vs.challenge = htsp->htsp_challenge;
3060     rights = access_get(htsp->htsp_peer, username,
3061                         htsp_verify_callback, &vs);
3062 
3063     if (rights->aa_rights == 0) {
3064       tvhinfo(LS_HTSP, "%s: Unauthorized access", htsp->htsp_logname);
3065       access_destroy(rights);
3066       return 0;
3067     }
3068 
3069     rights->aa_rights |= ACCESS_HTSP_INTERFACE;
3070     privgain = (rights->aa_rights |
3071                 htsp->htsp_granted_access->aa_rights) !=
3072                   htsp->htsp_granted_access->aa_rights;
3073 
3074     tvhinfo(LS_HTSP, "%s: Identified as user '%s'",
3075 	    htsp->htsp_logname, username);
3076     tvh_str_update(&htsp->htsp_username, username);
3077     htsp_update_logname(htsp);
3078     if(privgain)
3079       tvhinfo(LS_HTSP, "%s: Privileges updated", htsp->htsp_logname);
3080 
3081     access_destroy(htsp->htsp_granted_access);
3082     htsp->htsp_granted_access = rights;
3083 
3084     if (htsp->htsp_language == NULL && rights->aa_lang)
3085       htsp->htsp_language = strdup(rights->aa_lang);
3086 
3087   } else {
3088 
3089     tvhinfo(LS_HTSP, "%s: Identified as user '%s' (unverified)",
3090 	    htsp->htsp_logname, username);
3091     tvh_str_update(&htsp->htsp_username, username);
3092     htsp_update_logname(htsp);
3093 
3094   }
3095 
3096   notify_reload("connections");
3097 
3098   return privgain;
3099 }
3100 
3101 /**
3102  * timeout is in ms, 0 means infinite timeout
3103  */
3104 static int
htsp_read_message(htsp_connection_t * htsp,htsmsg_t ** mp,int timeout)3105 htsp_read_message(htsp_connection_t *htsp, htsmsg_t **mp, int timeout)
3106 {
3107   int v;
3108   uint32_t len;
3109   uint8_t data[4];
3110   void *buf;
3111 
3112   v = timeout ? tcp_read_timeout(htsp->htsp_fd, data, 4, timeout) :
3113                 tcp_read(htsp->htsp_fd, data, 4);
3114 
3115   if(v != 0)
3116     return v;
3117 
3118   len = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
3119   if(len > 1024 * 1024)
3120     return EMSGSIZE;
3121   if((buf = malloc(len)) == NULL)
3122     return ENOMEM;
3123 
3124   v = timeout ? tcp_read_timeout(htsp->htsp_fd, buf, len, timeout) :
3125                 tcp_read(htsp->htsp_fd, buf, len);
3126 
3127   if(v != 0) {
3128     free(buf);
3129     return v;
3130   }
3131 
3132   /* buf will be tied to the message (on success) */
3133   /* bellow fcn calls free(buf) (on failure) */
3134   *mp = htsmsg_binary_deserialize(buf, len, buf);
3135   if(*mp == NULL)
3136     return EBADMSG;
3137 
3138   return 0;
3139 }
3140 
3141 /*
3142  * Status callback
3143  */
3144 static void
htsp_server_status(void * opaque,htsmsg_t * m)3145 htsp_server_status ( void *opaque, htsmsg_t *m )
3146 {
3147   htsp_connection_t *htsp = opaque;
3148   access_t *aa;
3149   char buf[128];
3150   htsmsg_add_str(m, "type", "HTSP");
3151   if (htsp->htsp_username) {
3152     aa = htsp->htsp_granted_access;
3153     if (!strcmp(htsp->htsp_username, aa->aa_username ?: ""))
3154       snprintf(buf, sizeof(buf), "%s", htsp->htsp_username);
3155     else
3156       snprintf(buf, sizeof(buf), "[%s]", htsp->htsp_username);
3157     htsmsg_add_str(m, "user", buf);
3158   }
3159 }
3160 
3161 /**
3162  *
3163  */
3164 static int
htsp_read_loop(htsp_connection_t * htsp)3165 htsp_read_loop(htsp_connection_t *htsp)
3166 {
3167   htsmsg_t *m = NULL, *reply;
3168   int r = 0, i;
3169   const char *method;
3170   void *tcp_id = NULL;;
3171 
3172   if(htsp_generate_challenge(htsp)) {
3173     tvherror(LS_HTSP, "%s: Unable to generate challenge",
3174 	     htsp->htsp_logname);
3175     return 1;
3176   }
3177 
3178   pthread_mutex_lock(&global_lock);
3179 
3180   htsp->htsp_granted_access = access_get_by_addr(htsp->htsp_peer);
3181   htsp->htsp_granted_access->aa_rights |= ACCESS_HTSP_INTERFACE;
3182 
3183   tcp_id = tcp_connection_launch(htsp->htsp_fd, htsp_server_status,
3184                                  htsp->htsp_granted_access);
3185 
3186   pthread_mutex_unlock(&global_lock);
3187 
3188   if (tcp_id == NULL)
3189     return 0;
3190 
3191   tvhinfo(LS_HTSP, "Got connection from %s", htsp->htsp_logname);
3192 
3193   /* Session main loop */
3194 
3195   while(tvheadend_is_running()) {
3196 readmsg:
3197     reply = NULL;
3198 
3199     if((r = htsp_read_message(htsp, &m, 0)) != 0)
3200       break;
3201 
3202     pthread_mutex_lock(&global_lock);
3203     if (htsp_authenticate(htsp, m)) {
3204       tcp_connection_land(tcp_id);
3205       tcp_id = tcp_connection_launch(htsp->htsp_fd, htsp_server_status,
3206                                      htsp->htsp_granted_access);
3207       if (tcp_id == NULL) {
3208         htsmsg_destroy(m);
3209         pthread_mutex_unlock(&global_lock);
3210         return 1;
3211       }
3212     }
3213 
3214     if((method = htsmsg_get_str(m, "method")) != NULL) {
3215       tvhtrace(LS_HTSP, "%s - method %s", htsp->htsp_logname, method);
3216       if (tvhtrace_enabled())
3217         htsp_trace(htsp, LS_HTSP_REQ, "request", m);
3218       for(i = 0; i < NUM_METHODS; i++) {
3219         if(!strcmp(method, htsp_methods[i].name)) {
3220 
3221           if((htsp->htsp_granted_access->aa_rights &
3222               htsp_methods[i].privmask) !=
3223                 htsp_methods[i].privmask) {
3224 
3225       	    pthread_mutex_unlock(&global_lock);
3226             /* Classic authentication failed delay */
3227             tvh_safe_usleep(250000);
3228 
3229             reply = htsmsg_create_map();
3230             htsmsg_add_u32(reply, "noaccess", 1);
3231             htsp_reply(htsp, m, reply);
3232 
3233             htsmsg_destroy(m);
3234             goto readmsg;
3235 
3236           } else {
3237             reply = htsp_methods[i].fn(htsp, m);
3238           }
3239           break;
3240         }
3241       }
3242 
3243       if(i == NUM_METHODS) {
3244         reply = htsp_error(htsp, N_("Method not found"));
3245       }
3246 
3247     } else {
3248       reply = htsp_error(htsp, N_("Invalid arguments"));
3249     }
3250 
3251     pthread_mutex_unlock(&global_lock);
3252 
3253     if(reply != NULL) /* Methods can do all the replying inline */
3254       htsp_reply(htsp, m, reply);
3255 
3256     htsmsg_destroy(m);
3257   }
3258 
3259   pthread_mutex_lock(&global_lock);
3260   tcp_connection_land(tcp_id);
3261   pthread_mutex_unlock(&global_lock);
3262   return tvheadend_is_running() ? r : 0;
3263 }
3264 
3265 /**
3266  *
3267  */
3268 static void *
htsp_write_scheduler(void * aux)3269 htsp_write_scheduler(void *aux)
3270 {
3271   htsp_connection_t *htsp = aux;
3272   htsp_msg_q_t *hmq;
3273   htsp_msg_t *hm;
3274   void *dptr;
3275   size_t dlen;
3276   int r;
3277 
3278   pthread_mutex_lock(&htsp->htsp_out_mutex);
3279 
3280   while(htsp->htsp_writer_run) {
3281 
3282     if((hmq = TAILQ_FIRST(&htsp->htsp_active_output_queues)) == NULL) {
3283       /* Nothing to be done, go to sleep */
3284       tvh_cond_wait(&htsp->htsp_out_cond, &htsp->htsp_out_mutex);
3285       continue;
3286     }
3287 
3288     hm = TAILQ_FIRST(&hmq->hmq_q);
3289     TAILQ_REMOVE(&hmq->hmq_q, hm, hm_link);
3290     hmq->hmq_length--;
3291     hmq->hmq_payload -= hm->hm_payloadsize;
3292 
3293     TAILQ_REMOVE(&htsp->htsp_active_output_queues, hmq, hmq_link);
3294     if(hmq->hmq_length) {
3295       /* Still messages to be sent, put back in active queues */
3296       if(hmq->hmq_strict_prio) {
3297         TAILQ_INSERT_HEAD(&htsp->htsp_active_output_queues, hmq, hmq_link);
3298       } else {
3299         TAILQ_INSERT_TAIL(&htsp->htsp_active_output_queues, hmq, hmq_link);
3300       }
3301     }
3302 
3303     pthread_mutex_unlock(&htsp->htsp_out_mutex);
3304 
3305     if (htsmsg_binary_serialize(hm->hm_msg, &dptr, &dlen, INT32_MAX) != 0) {
3306       tvhwarn(LS_HTSP, "%s: failed to serialize data", htsp->htsp_logname);
3307       htsp_msg_destroy(hm);
3308       pthread_mutex_lock(&htsp->htsp_out_mutex);
3309       continue;
3310     }
3311 
3312     htsp_msg_destroy(hm);
3313 
3314     r = tvh_write(htsp->htsp_fd, dptr, dlen);
3315     free(dptr);
3316     pthread_mutex_lock(&htsp->htsp_out_mutex);
3317 
3318     if (r) {
3319       tvhinfo(LS_HTSP, "%s: Write error -- %s",
3320               htsp->htsp_logname, strerror(errno));
3321       break;
3322     }
3323   }
3324   // Shutdown socket to make receive thread terminate entire HTSP connection
3325 
3326   shutdown(htsp->htsp_fd, SHUT_RDWR);
3327   pthread_mutex_unlock(&htsp->htsp_out_mutex);
3328   return NULL;
3329 }
3330 
3331 /**
3332  *
3333  */
3334 static void
htsp_serve(int fd,void ** opaque,struct sockaddr_storage * source,struct sockaddr_storage * self)3335 htsp_serve(int fd, void **opaque, struct sockaddr_storage *source,
3336 	   struct sockaddr_storage *self)
3337 {
3338   htsp_connection_t htsp;
3339   char buf[50];
3340   htsp_subscription_t *s;
3341 
3342   // Note: global_lock held on entry
3343 
3344   if (config.dscp >= 0)
3345     socket_set_dscp(fd, config.dscp, NULL, 0);
3346 
3347   tcp_get_str_from_ip(source, buf, 50);
3348 
3349   memset(&htsp, 0, sizeof(htsp_connection_t));
3350   *opaque = &htsp;
3351 
3352   TAILQ_INIT(&htsp.htsp_active_output_queues);
3353 
3354   htsp_init_queue(&htsp.htsp_hmq_ctrl, 0);
3355   htsp_init_queue(&htsp.htsp_hmq_qstatus, 1);
3356   htsp_init_queue(&htsp.htsp_hmq_epg, 0);
3357 
3358   htsp.htsp_peername = strdup(buf);
3359   htsp_update_logname(&htsp);
3360 
3361   htsp.htsp_fd = fd;
3362   htsp.htsp_peer = source;
3363   htsp.htsp_writer_run = 1;
3364 
3365   pthread_mutex_init(&htsp.htsp_out_mutex, NULL);
3366 
3367   LIST_INSERT_HEAD(&htsp_connections, &htsp, htsp_link);
3368   pthread_mutex_unlock(&global_lock);
3369 
3370   tvhthread_create(&htsp.htsp_writer_thread, NULL,
3371                    htsp_write_scheduler, &htsp, "htsp-write");
3372 
3373   /**
3374    * Reader loop
3375    */
3376 
3377   htsp_read_loop(&htsp);
3378 
3379   tvhinfo(LS_HTSP, "%s: Disconnected", htsp.htsp_logname);
3380 
3381   /**
3382    * Ok, we're back, other end disconnected. Clean up stuff.
3383    */
3384 
3385   pthread_mutex_lock(&global_lock);
3386 
3387   /* no async notifications from now */
3388   if(htsp.htsp_async_mode)
3389     LIST_REMOVE(&htsp, htsp_async_link);
3390 
3391   mtimer_disarm(&htsp.htsp_epg_timer);
3392 
3393   /* deregister this client */
3394   LIST_REMOVE(&htsp, htsp_link);
3395 
3396   /* Beware! Closing subscriptions will invoke a lot of callbacks
3397      down in the streaming code. So we do this as early as possible
3398      to avoid any weird lockups */
3399   while((s = LIST_FIRST(&htsp.htsp_subscriptions)) != NULL)
3400     htsp_subscription_destroy(&htsp, s);
3401 
3402   pthread_mutex_unlock(&global_lock);
3403 
3404   pthread_mutex_lock(&htsp.htsp_out_mutex);
3405   htsp.htsp_writer_run = 0;
3406   tvh_cond_signal(&htsp.htsp_out_cond, 0);
3407   pthread_mutex_unlock(&htsp.htsp_out_mutex);
3408 
3409   pthread_join(htsp.htsp_writer_thread, NULL);
3410 
3411   while((s = LIST_FIRST(&htsp.htsp_dead_subscriptions)) != NULL)
3412     htsp_subscription_free(&htsp, s);
3413 
3414   htsp_msg_q_t *hmq;
3415 
3416   TAILQ_FOREACH(hmq, &htsp.htsp_active_output_queues, hmq_link) {
3417     htsp_msg_t *hm;
3418     while((hm = TAILQ_FIRST(&hmq->hmq_q)) != NULL) {
3419       TAILQ_REMOVE(&hmq->hmq_q, hm, hm_link);
3420       htsp_msg_destroy(hm);
3421     }
3422   }
3423 
3424   htsp_file_t *hf;
3425   while((hf = LIST_FIRST(&htsp.htsp_files)) != NULL)
3426     htsp_file_destroy(hf);
3427 
3428   close(fd);
3429 
3430   /* Free memory (leave lock in place, for parent method) */
3431   pthread_mutex_lock(&global_lock);
3432   free(htsp.htsp_logname);
3433   free(htsp.htsp_peername);
3434   free(htsp.htsp_username);
3435   free(htsp.htsp_clientname);
3436   free(htsp.htsp_language);
3437   access_destroy(htsp.htsp_granted_access);
3438   *opaque = NULL;
3439 }
3440 
3441 /*
3442  * Cancel callback
3443  */
3444 static void
htsp_server_cancel(void * opaque)3445 htsp_server_cancel ( void *opaque )
3446 {
3447   htsp_connection_t *htsp = opaque;
3448 
3449   if (htsp)
3450     shutdown(htsp->htsp_fd, SHUT_RDWR);
3451 }
3452 
3453 /**
3454  *  Fire up HTSP server
3455  */
3456 void
htsp_init(const char * bindaddr)3457 htsp_init(const char *bindaddr)
3458 {
3459   extern int tvheadend_htsp_port_extra;
3460   static tcp_server_ops_t ops = {
3461     .start  = htsp_serve,
3462     .stop   = NULL,
3463     .cancel = htsp_server_cancel
3464   };
3465   htsp_server = tcp_server_create(LS_HTSP, "HTSP", bindaddr, tvheadend_htsp_port, &ops, NULL);
3466   if(tvheadend_htsp_port_extra)
3467     htsp_server_2 = tcp_server_create(LS_HTSP, "HTSP2", bindaddr, tvheadend_htsp_port_extra, &ops, NULL);
3468 }
3469 
3470 /*
3471  *
3472  */
3473 void
htsp_register(void)3474 htsp_register(void)
3475 {
3476   tcp_server_register(htsp_server);
3477   if (htsp_server_2)
3478     tcp_server_register(htsp_server_2);
3479 }
3480 
3481 /**
3482  *  Fire down HTSP server
3483  */
3484 void
htsp_done(void)3485 htsp_done(void)
3486 {
3487   if (htsp_server_2)
3488     tcp_server_delete(htsp_server_2);
3489   if (htsp_server)
3490     tcp_server_delete(htsp_server);
3491 }
3492 
3493 /* **************************************************************************
3494  * Asynchronous updates
3495  * *************************************************************************/
3496 
3497 /**
3498  *
3499  */
3500 static void
htsp_async_send(htsmsg_t * m,int mode,int aux_type,void * aux)3501 htsp_async_send(htsmsg_t *m, int mode, int aux_type, void *aux)
3502 {
3503   htsp_connection_t *htsp;
3504 
3505   lock_assert(&global_lock);
3506   LIST_FOREACH(htsp, &htsp_async_connections, htsp_async_link)
3507     if (htsp->htsp_async_mode & mode) {
3508       if (aux_type == HTSP_ASYNC_AUX_CHTAG &&
3509           !channel_tag_access(aux, htsp->htsp_granted_access, 0))
3510         continue;
3511       htsp_send_message(htsp, htsmsg_copy(m), NULL);
3512     }
3513   htsmsg_destroy(m);
3514 }
3515 
3516 /**
3517  * Called from channel.c when a new channel is created
3518  */
3519 static void
_htsp_channel_update(channel_t * ch,const char * method,htsmsg_t * msg)3520 _htsp_channel_update(channel_t *ch, const char *method, htsmsg_t *msg)
3521 {
3522   htsp_connection_t *htsp;
3523   LIST_FOREACH(htsp, &htsp_async_connections, htsp_async_link) {
3524     if (htsp->htsp_async_mode & HTSP_ASYNC_ON)
3525       if (htsp_user_access_channel(htsp,ch)) {
3526         htsmsg_t *m = msg ? htsmsg_copy(msg)
3527                         : htsp_build_channel(ch, method, htsp);
3528         htsp_send_message(htsp, m, NULL);
3529       }
3530   }
3531   htsmsg_destroy(msg);
3532 }
3533 
3534 /**
3535  * EPG subsystem calls this function when the current/next event
3536  * changes for a channel, e may be NULL if there is no current event.
3537  *
3538  * global_lock is held
3539  */
3540 void
htsp_channel_update_nownext(channel_t * ch)3541 htsp_channel_update_nownext(channel_t *ch)
3542 {
3543   epg_broadcast_t *now, *next;
3544   htsmsg_t *m;
3545 
3546   m = htsmsg_create_map();
3547   htsmsg_add_str(m, "method", "channelUpdate");
3548   htsmsg_add_u32(m, "channelId", channel_get_id(ch));
3549 
3550   now  = ch->ch_epg_now;
3551   next = ch->ch_epg_next;
3552   htsmsg_add_u32(m, "eventId",     now  ? now->id : 0);
3553   htsmsg_add_u32(m, "nextEventId", next ? next->id : 0);
3554   _htsp_channel_update(ch, NULL, m);
3555 }
3556 
3557 void
htsp_channel_add(channel_t * ch)3558 htsp_channel_add(channel_t *ch)
3559 {
3560   _htsp_channel_update(ch, "channelAdd", NULL);
3561 }
3562 
3563 /**
3564  * Called from channel.c when a channel is updated
3565  */
3566 void
htsp_channel_update(channel_t * ch)3567 htsp_channel_update(channel_t *ch)
3568 {
3569   if (htsp_user_access_channel(NULL, ch))
3570     _htsp_channel_update(ch, "channelUpdate", NULL);
3571   else // in case the channel was ever sent to the client
3572     htsp_channel_delete(ch);
3573 }
3574 
3575 /**
3576  * Called from channel.c when a channel is deleted
3577  */
3578 void
htsp_channel_delete(channel_t * ch)3579 htsp_channel_delete(channel_t *ch)
3580 {
3581   htsmsg_t *m = htsmsg_create_map();
3582   htsmsg_add_u32(m, "channelId", channel_get_id(ch));
3583   htsmsg_add_str(m, "method", "channelDelete");
3584   htsp_async_send(m, HTSP_ASYNC_ON, HTSP_ASYNC_AUX_CH, ch);
3585 }
3586 
3587 
3588 /**
3589  * Called from channel.c when a tag is exported
3590  */
3591 void
htsp_tag_add(channel_tag_t * ct)3592 htsp_tag_add(channel_tag_t *ct)
3593 {
3594   htsp_async_send(htsp_build_tag(ct, "tagAdd", 1), HTSP_ASYNC_ON,
3595                   HTSP_ASYNC_AUX_CHTAG, ct);
3596 }
3597 
3598 
3599 /**
3600  * Called from channel.c when an exported tag is changed
3601  */
3602 void
htsp_tag_update(channel_tag_t * ct)3603 htsp_tag_update(channel_tag_t *ct)
3604 {
3605   if (ct->ct_enabled && !ct->ct_internal) {
3606     htsp_async_send(htsp_build_tag(ct, "tagUpdate", 1), HTSP_ASYNC_ON,
3607                     HTSP_ASYNC_AUX_CHTAG, ct);
3608   }
3609   else // in case the tag was ever sent to the client
3610     htsp_tag_delete(ct);
3611 }
3612 
3613 
3614 /**
3615  * Called from channel.c when an exported tag is deleted
3616  */
3617 void
htsp_tag_delete(channel_tag_t * ct)3618 htsp_tag_delete(channel_tag_t *ct)
3619 {
3620   htsmsg_t *m = htsmsg_create_map();
3621   htsmsg_add_u32(m, "tagId", htsp_channel_tag_get_identifier(ct));
3622   htsmsg_add_str(m, "method", "tagDelete");
3623   htsp_async_send(m, HTSP_ASYNC_ON, HTSP_ASYNC_AUX_CHTAG_DEL, ct);
3624 }
3625 
3626 /**
3627  * Called when a DVR entry is updated/added
3628  */
3629 static void
_htsp_dvr_entry_update(dvr_entry_t * de,const char * method,htsmsg_t * msg)3630 _htsp_dvr_entry_update(dvr_entry_t *de, const char *method, htsmsg_t *msg)
3631 {
3632   htsp_connection_t *htsp;
3633   LIST_FOREACH(htsp, &htsp_async_connections, htsp_async_link) {
3634     if (htsp->htsp_async_mode & HTSP_ASYNC_ON)
3635       if (!dvr_entry_verify(de, htsp->htsp_granted_access, 1)) {
3636         htsmsg_t *m = msg ? htsmsg_copy(msg)
3637                         : htsp_build_dvrentry(htsp, de, method, htsp->htsp_language, 0);
3638         htsp_send_message(htsp, m, NULL);
3639       }
3640   }
3641   htsmsg_destroy(msg);
3642 }
3643 
3644 /**
3645  * Called from dvr_db.c when a DVR entry is created
3646  */
3647 void
htsp_dvr_entry_add(dvr_entry_t * de)3648 htsp_dvr_entry_add(dvr_entry_t *de)
3649 {
3650   _htsp_dvr_entry_update(de, "dvrEntryAdd", NULL);
3651 }
3652 
3653 
3654 /**
3655  * Called from dvr_db.c when a DVR entry is updated
3656  */
3657 void
htsp_dvr_entry_update(dvr_entry_t * de)3658 htsp_dvr_entry_update(dvr_entry_t *de)
3659 {
3660   _htsp_dvr_entry_update(de, "dvrEntryUpdate", NULL);
3661 }
3662 
3663 /**
3664  * Called from dvr_rec.c when a DVR entry is recording
3665  */
3666 void
htsp_dvr_entry_update_stats(dvr_entry_t * de)3667 htsp_dvr_entry_update_stats(dvr_entry_t *de)
3668 {
3669   htsp_connection_t *htsp;
3670   LIST_FOREACH(htsp, &htsp_async_connections, htsp_async_link) {
3671     if (htsp->htsp_async_mode & HTSP_ASYNC_ON){
3672       if (!dvr_entry_verify(de, htsp->htsp_granted_access, 1)) {
3673         htsmsg_t *m = htsp_build_dvrentry(htsp, de, "dvrEntryUpdate", htsp->htsp_language, htsp->htsp_version <= 25 ? 0 : 1);
3674         htsp_send_message(htsp, m, NULL);
3675       }
3676     }
3677   }
3678 }
3679 
3680 
3681 /**
3682  * Called from dvr_db.c when a DVR entry is deleted
3683  */
3684 void
htsp_dvr_entry_delete(dvr_entry_t * de)3685 htsp_dvr_entry_delete(dvr_entry_t *de)
3686 {
3687   htsmsg_t *m = htsmsg_create_map();
3688   htsmsg_add_u32(m, "id", idnode_get_short_uuid(&de->de_id));
3689   htsmsg_add_str(m, "method", "dvrEntryDelete");
3690   htsp_async_send(m, HTSP_ASYNC_ON, HTSP_ASYNC_AUX_DVR, de);
3691 }
3692 
3693 /**
3694  * Called when a autorec entry is updated/added
3695  */
3696 static void
_htsp_autorec_entry_update(dvr_autorec_entry_t * dae,const char * method,htsmsg_t * msg)3697 _htsp_autorec_entry_update(dvr_autorec_entry_t *dae, const char *method, htsmsg_t *msg)
3698 {
3699   htsp_connection_t *htsp;
3700   LIST_FOREACH(htsp, &htsp_async_connections, htsp_async_link) {
3701     if (htsp->htsp_async_mode & HTSP_ASYNC_ON) {
3702       if (!dvr_autorec_entry_verify(dae, htsp->htsp_granted_access, 1)) {
3703         htsmsg_t *m = msg ? htsmsg_copy(msg)
3704                           : htsp_build_autorecentry(htsp, dae, method);
3705         htsp_send_message(htsp, m, NULL);
3706       }
3707     }
3708   }
3709   htsmsg_destroy(msg);
3710 }
3711 
3712 /**
3713  * Called from dvr_autorec.c when a autorec entry is added
3714  */
3715 void
htsp_autorec_entry_add(dvr_autorec_entry_t * dae)3716 htsp_autorec_entry_add(dvr_autorec_entry_t *dae)
3717 {
3718   _htsp_autorec_entry_update(dae, "autorecEntryAdd", NULL);
3719 }
3720 
3721 /**
3722  * Called from dvr_autorec.c when a autorec entry is updated
3723  */
3724 void
htsp_autorec_entry_update(dvr_autorec_entry_t * dae)3725 htsp_autorec_entry_update(dvr_autorec_entry_t *dae)
3726 {
3727   _htsp_autorec_entry_update(dae, "autorecEntryUpdate", NULL);
3728 }
3729 
3730 /**
3731  * Called from dvr_autorec.c when a autorec entry is deleted
3732  */
3733 void
htsp_autorec_entry_delete(dvr_autorec_entry_t * dae)3734 htsp_autorec_entry_delete(dvr_autorec_entry_t *dae)
3735 {
3736   char ubuf[UUID_HEX_SIZE];
3737 
3738   if(dae == NULL)
3739     return;
3740 
3741   htsmsg_t *m = htsmsg_create_map();
3742   htsmsg_add_str(m, "id", idnode_uuid_as_str(&dae->dae_id, ubuf));
3743   htsmsg_add_str(m, "method", "autorecEntryDelete");
3744   htsp_async_send(m, HTSP_ASYNC_ON, HTSP_ASYNC_AUX_AUTOREC, dae);
3745 }
3746 
3747 /**
3748  * Called when a timerec entry is updated/added
3749  */
3750 static void
_htsp_timerec_entry_update(dvr_timerec_entry_t * dte,const char * method,htsmsg_t * msg)3751 _htsp_timerec_entry_update(dvr_timerec_entry_t *dte, const char *method, htsmsg_t *msg)
3752 {
3753   htsp_connection_t *htsp;
3754   LIST_FOREACH(htsp, &htsp_async_connections, htsp_async_link) {
3755     if (htsp->htsp_async_mode & HTSP_ASYNC_ON) {
3756       if (!dvr_timerec_entry_verify(dte, htsp->htsp_granted_access, 1)) {
3757         htsmsg_t *m = msg ? htsmsg_copy(msg)
3758                           : htsp_build_timerecentry(htsp, dte, method);
3759         htsp_send_message(htsp, m, NULL);
3760       }
3761     }
3762   }
3763   htsmsg_destroy(msg);
3764 }
3765 
3766 /**
3767  * Called from dvr_timerec.c when a timerec entry is added
3768  */
3769 void
htsp_timerec_entry_add(dvr_timerec_entry_t * dte)3770 htsp_timerec_entry_add(dvr_timerec_entry_t *dte)
3771 {
3772   _htsp_timerec_entry_update(dte, "timerecEntryAdd", NULL);
3773 }
3774 
3775 /**
3776  * Called from dvr_timerec.c when a timerec entry is updated
3777  */
3778 void
htsp_timerec_entry_update(dvr_timerec_entry_t * dte)3779 htsp_timerec_entry_update(dvr_timerec_entry_t *dte)
3780 {
3781   _htsp_timerec_entry_update(dte, "timerecEntryUpdate", NULL);
3782 }
3783 
3784 /**
3785  * Called from dvr_timerec.c when a timerec entry is deleted
3786  */
3787 void
htsp_timerec_entry_delete(dvr_timerec_entry_t * dte)3788 htsp_timerec_entry_delete(dvr_timerec_entry_t *dte)
3789 {
3790   char ubuf[UUID_HEX_SIZE];
3791 
3792   if(dte == NULL)
3793     return;
3794 
3795   htsmsg_t *m = htsmsg_create_map();
3796   htsmsg_add_str(m, "id", idnode_uuid_as_str(&dte->dte_id, ubuf));
3797   htsmsg_add_str(m, "method", "timerecEntryDelete");
3798   htsp_async_send(m, HTSP_ASYNC_ON, HTSP_ASYNC_AUX_TIMEREC, dte);
3799 }
3800 
3801 /**
3802  * Called every "HTSP_ASYNC_EPG_INTERVAL" seconds
3803  * Keep the async epg window up to date
3804  */
3805 static void
htsp_epg_window_cb(void * aux)3806 htsp_epg_window_cb(void *aux)
3807 {
3808   htsp_connection_t *htsp = aux;
3809   htsp_epg_send_waiting(htsp, htsp->htsp_epg_lastupdate);
3810 }
3811 
3812 /**
3813  * Send all waiting EPG events
3814  */
3815 static void
htsp_epg_send_waiting(htsp_connection_t * htsp,int64_t mintime)3816 htsp_epg_send_waiting(htsp_connection_t *htsp, int64_t mintime)
3817 {
3818   epg_broadcast_t *ebc;
3819   channel_t *ch;
3820   int64_t maxtime;
3821 
3822   maxtime = gclk() + htsp->htsp_epg_window;
3823   htsp->htsp_epg_lastupdate = maxtime;
3824 
3825   /* Push new events */
3826   CHANNEL_FOREACH(ch) {
3827     if (!htsp_user_access_channel(htsp, ch)) continue;
3828     RB_FOREACH(ebc, &ch->ch_epg_schedule, sched_link) {
3829       if (ebc->start <= mintime) continue;
3830       if (htsp->htsp_epg_window && ebc->start > maxtime) break;
3831       htsmsg_t *e = htsp_build_event(ebc, "eventAdd", htsp->htsp_language, 0, htsp);
3832       if (e) htsp_send_message(htsp, e, NULL);
3833     }
3834   }
3835 
3836   /* Keep the epg window up to date */
3837   if (htsp->htsp_epg_window)
3838     mtimer_arm_rel(&htsp->htsp_epg_timer, htsp_epg_window_cb,
3839                    htsp, sec2mono(HTSP_ASYNC_EPG_INTERVAL));
3840 }
3841 
3842 /**
3843  * Called when a event entry is updated/added
3844  */
3845 static void
_htsp_event_update(epg_broadcast_t * ebc,const char * method,htsmsg_t * msg)3846 _htsp_event_update(epg_broadcast_t *ebc, const char *method, htsmsg_t *msg)
3847 {
3848   htsp_connection_t *htsp;
3849   LIST_FOREACH(htsp, &htsp_async_connections, htsp_async_link) {
3850     if (htsp->htsp_async_mode & HTSP_ASYNC_EPG) {
3851       /* Use last update instead of window time as we do not want to push an update
3852        * for an event we still have to send with "htsp_epg_window_cb" */
3853       if (!htsp->htsp_epg_window || ebc->start <= htsp->htsp_epg_lastupdate) {
3854         if (htsp_user_access_channel(htsp,ebc->channel)) {
3855           htsmsg_t *m = msg ? htsmsg_copy(msg)
3856                           : htsp_build_event(ebc, method, htsp->htsp_language, 0, htsp);
3857           htsp_send_message(htsp, m, NULL);
3858         }
3859       }
3860     }
3861   }
3862   htsmsg_destroy(msg);
3863 }
3864 
3865 /**
3866  * Event added
3867  */
3868 void
htsp_event_add(epg_broadcast_t * ebc)3869 htsp_event_add(epg_broadcast_t *ebc)
3870 {
3871   _htsp_event_update(ebc, "eventAdd", NULL);
3872 }
3873 
3874 /**
3875  * Event updated
3876  */
3877 void
htsp_event_update(epg_broadcast_t * ebc)3878 htsp_event_update(epg_broadcast_t *ebc)
3879 {
3880   _htsp_event_update(ebc, "eventUpdate", NULL);
3881 }
3882 
3883 /**
3884  * Event deleted
3885  */
3886 void
htsp_event_delete(epg_broadcast_t * ebc)3887 htsp_event_delete(epg_broadcast_t *ebc)
3888 {
3889   htsmsg_t *m = htsmsg_create_map();
3890   htsmsg_add_str(m, "method", "eventDelete");
3891   htsmsg_add_u32(m, "eventId", ebc->id);
3892   htsp_async_send(m, HTSP_ASYNC_EPG, HTSP_ASYNC_AUX_EPG, ebc);
3893 }
3894 
3895 static const char frametypearray[PKT_NTYPES] = {
3896   [PKT_I_FRAME] = 'I',
3897   [PKT_P_FRAME] = 'P',
3898   [PKT_B_FRAME] = 'B',
3899 };
3900 
3901 /**
3902  * Build a htsmsg from a th_pkt and enqueue it on our HTSP service
3903  */
3904 static void
htsp_stream_deliver(htsp_subscription_t * hs,th_pkt_t * pkt)3905 htsp_stream_deliver(htsp_subscription_t *hs, th_pkt_t *pkt)
3906 {
3907   htsmsg_t *m;
3908   htsp_msg_t *hm;
3909   htsp_connection_t *htsp = hs->hs_htsp;
3910   int64_t ts;
3911   int qlen = hs->hs_q.hmq_payload;
3912   int video = SCT_ISVIDEO(pkt->pkt_type);
3913   size_t payloadlen;
3914 
3915   if (pkt->pkt_err)
3916     hs->hs_data_errors += pkt->pkt_err;
3917   if(pkt->pkt_payload == NULL) {
3918     return;
3919   }
3920 
3921   if(!htsp_is_stream_enabled(hs, pkt->pkt_componentindex)) {
3922     pkt_ref_dec(pkt);
3923     return;
3924   }
3925 
3926   if(video &&
3927      ((qlen > hs->hs_queue_depth     && pkt->v.pkt_frametype == PKT_B_FRAME) ||
3928       (qlen > hs->hs_queue_depth * 2 && pkt->v.pkt_frametype == PKT_P_FRAME) ||
3929       (qlen > hs->hs_queue_depth * 3))) {
3930 
3931     hs->hs_dropstats[pkt->v.pkt_frametype]++;
3932 
3933     /* Queue size protection */
3934     pkt_ref_dec(pkt);
3935     return;
3936   }
3937 
3938   m = htsmsg_create_map();
3939 
3940   htsmsg_add_str(m, "method", "muxpkt");
3941   htsmsg_add_u32(m, "subscriptionId", hs->hs_sid);
3942   if (video)
3943     htsmsg_add_u32(m, "frametype", frametypearray[pkt->v.pkt_frametype]);
3944 
3945   htsmsg_add_u32(m, "stream", pkt->pkt_componentindex);
3946   htsmsg_add_u32(m, "com", pkt->pkt_commercial);
3947 
3948   if(pkt->pkt_pts != PTS_UNSET) {
3949     int64_t pts = hs->hs_90khz ? pkt->pkt_pts : ts_rescale(pkt->pkt_pts, 1000000);
3950     htsmsg_add_s64(m, "pts", pts);
3951   }
3952 
3953   if(pkt->pkt_dts != PTS_UNSET) {
3954     int64_t dts = hs->hs_90khz ? pkt->pkt_dts : ts_rescale(pkt->pkt_dts, 1000000);
3955     htsmsg_add_s64(m, "dts", dts);
3956   }
3957 
3958   uint32_t dur = hs->hs_90khz ? pkt->pkt_duration : ts_rescale(pkt->pkt_duration, 1000000);
3959   htsmsg_add_u32(m, "duration", dur);
3960 
3961   /**
3962    * Since we will serialize directly we use 'binptr' which is a binary
3963    * object that just points to data, thus avoiding a copy.
3964    */
3965   payloadlen = pktbuf_len(pkt->pkt_payload);
3966   htsmsg_add_binptr(m, "payload", pktbuf_ptr(pkt->pkt_payload), payloadlen);
3967   htsp_send_subscription(htsp, m, pkt->pkt_payload, hs, payloadlen);
3968   atomic_add(&hs->hs_s_bytes_out, payloadlen);
3969 
3970   if(mono2sec(hs->hs_last_report) != mono2sec(mclk())) {
3971 
3972     /* Send a queue and signal status report every second */
3973 
3974     hs->hs_last_report = mclk();
3975 
3976     m = htsmsg_create_map();
3977     htsmsg_add_str(m, "method", "queueStatus");
3978     htsmsg_add_u32(m, "subscriptionId", hs->hs_sid);
3979     htsmsg_add_u32(m, "packets", hs->hs_q.hmq_length);
3980     htsmsg_add_u32(m, "bytes", hs->hs_q.hmq_payload);
3981     if (hs->hs_data_errors)
3982       htsmsg_add_u32(m, "errors", hs->hs_data_errors);
3983 
3984     /**
3985      * Figure out real time queue delay
3986      */
3987 
3988     pthread_mutex_lock(&htsp->htsp_out_mutex);
3989 
3990     int64_t min_dts = PTS_UNSET;
3991     int64_t max_dts = PTS_UNSET;
3992     TAILQ_FOREACH(hm, &hs->hs_q.hmq_q, hm_link) {
3993       if(!hm->hm_msg)
3994 	continue;
3995       if(htsmsg_get_s64(hm->hm_msg, "dts", &ts))
3996 	continue;
3997       if(ts == PTS_UNSET)
3998 	continue;
3999 
4000       if(min_dts == PTS_UNSET)
4001 	min_dts = ts;
4002       else
4003 	min_dts = MIN(ts, min_dts);
4004 
4005       if(max_dts == PTS_UNSET)
4006 	max_dts = ts;
4007       else
4008 	max_dts = MAX(ts, max_dts);
4009     }
4010 
4011     htsmsg_add_s64(m, "delay", max_dts - min_dts);
4012 
4013     pthread_mutex_unlock(&htsp->htsp_out_mutex);
4014 
4015     htsmsg_add_u32(m, "Bdrops", hs->hs_dropstats[PKT_B_FRAME]);
4016     htsmsg_add_u32(m, "Pdrops", hs->hs_dropstats[PKT_P_FRAME]);
4017     htsmsg_add_u32(m, "Idrops", hs->hs_dropstats[PKT_I_FRAME]);
4018 
4019     /* We use a special queue for queue status message so they're not
4020        blocked by anything else */
4021     htsp_send_message(hs->hs_htsp, m, &hs->hs_htsp->htsp_hmq_qstatus);
4022   }
4023   pkt_ref_dec(pkt);
4024 }
4025 
4026 /**
4027  * Send a 'subscriptionStart' message to client informing about
4028  * delivery start and all components.
4029  */
4030 static void
htsp_subscription_start(htsp_subscription_t * hs,const streaming_start_t * ss)4031 htsp_subscription_start(htsp_subscription_t *hs, const streaming_start_t *ss)
4032 {
4033   htsmsg_t *m,*streams, *c, *sourceinfo;
4034   const char *type;
4035   tvh_uuid_t hex;
4036   int i;
4037   const source_info_t *si;
4038 
4039   tvhdebug(LS_HTSP, "%s - subscription start", hs->hs_htsp->htsp_logname);
4040 
4041   for(i = 0; i < ss->ss_num_components; i++) {
4042     const streaming_start_component_t *ssc = &ss->ss_components[i];
4043     if (ssc->ssc_disabled) continue;
4044     if (SCT_ISVIDEO(ssc->ssc_type)) {
4045       if (ssc->ssc_width == 0 || ssc->ssc_height == 0) {
4046         hs->hs_wait_for_video = 1;
4047         return;
4048       }
4049       break;
4050     }
4051   }
4052   hs->hs_wait_for_video = 0;
4053 
4054   m = htsmsg_create_map();
4055   streams = htsmsg_create_list();
4056   sourceinfo = htsmsg_create_map();
4057   for(i = 0; i < ss->ss_num_components; i++) {
4058     const streaming_start_component_t *ssc = &ss->ss_components[i];
4059     if(ssc->ssc_disabled) continue;
4060 
4061     c = htsmsg_create_map();
4062     htsmsg_add_u32(c, "index", ssc->ssc_index);
4063     if (ssc->ssc_type == SCT_MP4A)
4064       type = "AAC"; /* override */
4065     else
4066       type = streaming_component_type2txt(ssc->ssc_type);
4067     htsmsg_add_str(c, "type", type);
4068     if(ssc->ssc_lang[0])
4069       htsmsg_add_str(c, "language", ssc->ssc_lang);
4070 
4071     if(ssc->ssc_type == SCT_DVBSUB) {
4072       htsmsg_add_u32(c, "composition_id", ssc->ssc_composition_id);
4073       htsmsg_add_u32(c, "ancillary_id", ssc->ssc_ancillary_id);
4074     }
4075 
4076     if(SCT_ISVIDEO(ssc->ssc_type)) {
4077       if(ssc->ssc_width)
4078         htsmsg_add_u32(c, "width", ssc->ssc_width);
4079       if(ssc->ssc_height)
4080         htsmsg_add_u32(c, "height", ssc->ssc_height);
4081       if(ssc->ssc_frameduration)
4082         htsmsg_add_u32(c, "duration", hs->hs_90khz ? ssc->ssc_frameduration :
4083                        ts_rescale(ssc->ssc_frameduration, 1000000));
4084       if (ssc->ssc_aspect_num)
4085         htsmsg_add_u32(c, "aspect_num", ssc->ssc_aspect_num);
4086       if (ssc->ssc_aspect_den)
4087         htsmsg_add_u32(c, "aspect_den", ssc->ssc_aspect_den);
4088     }
4089 
4090     if (SCT_ISAUDIO(ssc->ssc_type))
4091     {
4092       htsmsg_add_u32(c, "audio_type", ssc->ssc_audio_type);
4093       if (ssc->ssc_audio_version)
4094         htsmsg_add_u32(c, "audio_version", ssc->ssc_audio_version);
4095       if (ssc->ssc_channels)
4096         htsmsg_add_u32(c, "channels", ssc->ssc_channels);
4097       if (ssc->ssc_sri)
4098         htsmsg_add_u32(c, "rate", ssc->ssc_sri);
4099     }
4100 
4101     if (ssc->ssc_gh)
4102       htsmsg_add_binptr(m, "meta", pktbuf_ptr(ssc->ssc_gh),
4103 		        pktbuf_len(ssc->ssc_gh));
4104 
4105     htsmsg_add_msg(streams, NULL, c);
4106   }
4107 
4108   htsmsg_add_msg(m, "streams", streams);
4109 
4110   si = &ss->ss_si;
4111   if(!uuid_empty(&si->si_adapter_uuid)) {
4112     uuid_bin2hex(&si->si_adapter_uuid, &hex);
4113     htsmsg_add_str(sourceinfo, "adapter_uuid", hex.hex);
4114   }
4115   if(!uuid_empty(&si->si_mux_uuid)) {
4116     uuid_bin2hex(&si->si_mux_uuid, &hex);
4117     htsmsg_add_str(sourceinfo, "mux_uuid", hex.hex);
4118   }
4119   if(!uuid_empty(&si->si_network_uuid)) {
4120     uuid_bin2hex(&si->si_network_uuid, &hex);
4121     htsmsg_add_str(sourceinfo, "network_uuid", hex.hex);
4122   }
4123   if (!htsp_anonymize(hs->hs_htsp)) {
4124     htsmsg_add_str2(sourceinfo, "adapter",      si->si_adapter     );
4125     htsmsg_add_str2(sourceinfo, "mux",          si->si_mux         );
4126     htsmsg_add_str2(sourceinfo, "network",      si->si_network     );
4127     htsmsg_add_str2(sourceinfo, "network_type", si->si_network_type);
4128     htsmsg_add_str2(sourceinfo, "provider",     si->si_provider    );
4129     htsmsg_add_str2(sourceinfo, "service",      si->si_service     );
4130     htsmsg_add_str2(sourceinfo, "satpos",       si->si_satpos      );
4131   }
4132 
4133   htsmsg_add_msg(m, "sourceinfo", sourceinfo);
4134 
4135   htsmsg_add_str(m, "method", "subscriptionStart");
4136   htsmsg_add_u32(m, "subscriptionId", hs->hs_sid);
4137   htsp_send_subscription(hs->hs_htsp, m, NULL, hs, 0);
4138 }
4139 
4140 /**
4141  * Send a 'subscriptionStop' stop
4142  */
4143 static void
htsp_subscription_stop(htsp_subscription_t * hs,const char * err,const char * subscriptionErr)4144 htsp_subscription_stop(htsp_subscription_t *hs, const char *err, const char *subscriptionErr)
4145 {
4146   htsmsg_t *m = htsmsg_create_map();
4147   htsmsg_add_str(m, "method", "subscriptionStop");
4148   htsmsg_add_u32(m, "subscriptionId", hs->hs_sid);
4149   tvhdebug(LS_HTSP, "%s - subscription stop", hs->hs_htsp->htsp_logname);
4150 
4151   if(err != NULL)
4152     htsmsg_add_str(m, "status", err);
4153 
4154   if(subscriptionErr != NULL)
4155     htsmsg_add_str(m, "subscriptionError", subscriptionErr);
4156 
4157   htsp_send_subscription(hs->hs_htsp, m, NULL, hs, 0);
4158 }
4159 
4160 /**
4161  * Send a 'subscriptionGrace' message
4162  */
4163 static void
htsp_subscription_grace(htsp_subscription_t * hs,int grace)4164 htsp_subscription_grace(htsp_subscription_t *hs, int grace)
4165 {
4166   htsmsg_t *m = htsmsg_create_map();
4167   htsmsg_add_str(m, "method", "subscriptionGrace");
4168   htsmsg_add_u32(m, "subscriptionId", hs->hs_sid);
4169   htsmsg_add_u32(m, "graceTimeout", grace);
4170   tvhdebug(LS_HTSP, "%s - subscription grace %i seconds", hs->hs_htsp->htsp_logname, grace);
4171 
4172   htsp_send_subscription(hs->hs_htsp, m, NULL, hs, 0);
4173 }
4174 
4175 /**
4176  * Send a 'subscriptionStatus' message
4177  */
4178 static void
htsp_subscription_status(htsp_subscription_t * hs,const char * err,const char * subscriptionErr)4179 htsp_subscription_status(htsp_subscription_t *hs, const char *err, const char *subscriptionErr)
4180 {
4181   htsmsg_t *m = htsmsg_create_map();
4182   htsmsg_add_str(m, "method", "subscriptionStatus");
4183   htsmsg_add_u32(m, "subscriptionId", hs->hs_sid);
4184 
4185   if(err != NULL)
4186     htsmsg_add_str(m, "status", err);
4187 
4188   if(subscriptionErr != NULL)
4189     htsmsg_add_str(m, "subscriptionError", subscriptionErr);
4190 
4191   htsp_send_subscription(hs->hs_htsp, m, NULL, hs, 0);
4192 }
4193 
4194 /**
4195  * Convert the SM_CODE to an understandable string
4196  */
4197 const char *
_htsp_get_subscription_status(int smcode)4198 _htsp_get_subscription_status(int smcode)
4199 {
4200   switch (smcode)
4201   {
4202   case SM_CODE_NOT_FREE:
4203   case SM_CODE_NO_FREE_ADAPTER:
4204   case SM_CODE_NO_ADAPTERS:
4205     return "noFreeAdapter";
4206   case SM_CODE_NO_ACCESS:
4207   case SM_CODE_NO_DESCRAMBLER:
4208     return "scrambled";
4209   case SM_CODE_NO_INPUT:
4210   case SM_CODE_BAD_SIGNAL:
4211     return "badSignal";
4212   case SM_CODE_TUNING_FAILED:
4213     return "tuningFailed";
4214   case SM_CODE_SUBSCRIPTION_OVERRIDDEN:
4215     return "subscriptionOverridden";
4216   case SM_CODE_MUX_NOT_ENABLED:
4217     return "muxNotEnabled";
4218   case SM_CODE_INVALID_TARGET:
4219     return "invalidTarget";
4220   case SM_CODE_USER_ACCESS:
4221     return "userAccess";
4222   case SM_CODE_USER_LIMIT:
4223     return "userLimit";
4224   case SM_CODE_WEAK_STREAM:
4225     return "weakStream";
4226   case SM_CODE_NO_SPACE:
4227     return "noDiskSpace";
4228   default:
4229     return streaming_code2txt(smcode);
4230   }
4231 }
4232 
4233 /**
4234  *
4235  */
4236 static void
htsp_subscription_service_status(htsp_subscription_t * hs,int status)4237 htsp_subscription_service_status(htsp_subscription_t *hs, int status)
4238 {
4239   if(status & TSS_PACKETS) {
4240     htsp_subscription_status(hs, NULL, NULL);
4241   } else if(status & TSS_ERRORS) {
4242     htsp_subscription_status(hs, service_tss2text(status), NULL);
4243   }
4244 }
4245 
4246 /**
4247  *
4248  */
4249 static void
htsp_subscription_signal_status(htsp_subscription_t * hs,signal_status_t * sig)4250 htsp_subscription_signal_status(htsp_subscription_t *hs, signal_status_t *sig)
4251 {
4252   htsmsg_t *m = htsmsg_create_map();
4253   htsmsg_add_str(m, "method", "signalStatus");
4254   htsmsg_add_u32(m, "subscriptionId", hs->hs_sid);
4255   if (!htsp_anonymize(hs->hs_htsp)) {
4256     htsmsg_add_str(m, "feStatus",   sig->status_text);
4257     if((sig->snr != -2) && (sig->snr_scale == SIGNAL_STATUS_SCALE_RELATIVE))
4258       htsmsg_add_u32(m, "feSNR",    sig->snr);
4259     if((sig->signal != -2) && (sig->signal_scale == SIGNAL_STATUS_SCALE_RELATIVE))
4260       htsmsg_add_u32(m, "feSignal", sig->signal);
4261     if(sig->ber != -2)
4262       htsmsg_add_u32(m, "feBER",    sig->ber);
4263     if(sig->unc != -2)
4264       htsmsg_add_u32(m, "feUNC",    sig->unc);
4265   } else {
4266     htsmsg_add_str(m, "feStatus", "");
4267   }
4268   htsp_send_message(hs->hs_htsp, m, &hs->hs_htsp->htsp_hmq_qstatus);
4269 }
4270 
4271 /**
4272  *
4273  */
4274 static void
htsp_subscription_descramble_info(htsp_subscription_t * hs,descramble_info_t * di)4275 htsp_subscription_descramble_info(htsp_subscription_t *hs, descramble_info_t *di)
4276 {
4277   /* don't bother old clients */
4278   if (hs->hs_htsp->htsp_version < 24)
4279     return;
4280   if (htsp_anonymize(hs->hs_htsp))
4281     return;
4282 
4283   htsmsg_t *m = htsmsg_create_map();
4284   htsmsg_add_str(m, "method", "descrambleInfo");
4285   htsmsg_add_u32(m, "subscriptionId", hs->hs_sid);
4286   htsmsg_add_u32(m, "pid", di->pid);
4287   htsmsg_add_u32(m, "caid", di->caid);
4288   htsmsg_add_u32(m, "provid", di->provid);
4289   htsmsg_add_u32(m, "ecmtime", di->ecmtime);
4290   htsmsg_add_u32(m, "hops", di->hops);
4291   if (di->cardsystem[0])
4292     htsmsg_add_str(m, "cardsystem", di->cardsystem);
4293   if (di->reader[0])
4294     htsmsg_add_str(m, "reader", di->reader);
4295   if (di->from[0])
4296     htsmsg_add_str(m, "from", di->from);
4297   if (di->protocol[0])
4298     htsmsg_add_str(m, "protocol", di->protocol);
4299   htsp_send_message(hs->hs_htsp, m, &hs->hs_htsp->htsp_hmq_qstatus);
4300 }
4301 
4302 /**
4303  *
4304  */
4305 static void
htsp_subscription_speed(htsp_subscription_t * hs,int speed)4306 htsp_subscription_speed(htsp_subscription_t *hs, int speed)
4307 {
4308   htsmsg_t *m = htsmsg_create_map();
4309   tvhdebug(LS_HTSP, "%s - subscription speed", hs->hs_htsp->htsp_logname);
4310   htsmsg_add_str(m, "method", "subscriptionSpeed");
4311   htsmsg_add_u32(m, "subscriptionId", hs->hs_sid);
4312   htsmsg_add_s32(m, "speed", speed);
4313   htsp_send_subscription(hs->hs_htsp, m, NULL, hs, 0);
4314 }
4315 
4316 /**
4317  *
4318  */
4319 #if ENABLE_TIMESHIFT
4320 static void
htsp_subscription_timeshift_status(htsp_subscription_t * hs,timeshift_status_t * status)4321 htsp_subscription_timeshift_status(htsp_subscription_t *hs, timeshift_status_t *status)
4322 {
4323   htsmsg_t *m = htsmsg_create_map();
4324   htsmsg_add_str(m, "method", "timeshiftStatus");
4325   htsmsg_add_u32(m, "subscriptionId", hs->hs_sid);
4326   htsmsg_add_u32(m, "full", status->full);
4327   htsmsg_add_s64(m, "shift", hs->hs_90khz ? status->shift : ts_rescale(status->shift, 1000000));
4328   if (status->pts_start != PTS_UNSET)
4329     htsmsg_add_s64(m, "start", hs->hs_90khz ? status->pts_start : ts_rescale(status->pts_start, 1000000)) ;
4330   if (status->pts_end != PTS_UNSET)
4331     htsmsg_add_s64(m, "end", hs->hs_90khz ? status->pts_end : ts_rescale(status->pts_end, 1000000)) ;
4332   htsp_send_subscription(hs->hs_htsp, m, NULL, hs, 0);
4333 }
4334 #endif
4335 
4336 /**
4337  *
4338  */
4339 static void
htsp_subscription_skip(htsp_subscription_t * hs,streaming_skip_t * skip)4340 htsp_subscription_skip(htsp_subscription_t *hs, streaming_skip_t *skip)
4341 {
4342   htsmsg_t *m = htsmsg_create_map();
4343   tvhdebug(LS_HTSP, "%s - subscription skip", hs->hs_htsp->htsp_logname);
4344   htsmsg_add_str(m, "method", "subscriptionSkip");
4345   htsmsg_add_u32(m, "subscriptionId", hs->hs_sid);
4346 
4347   /* Flush pkt buffers */
4348 #if ENABLE_TIMESHIFT
4349   if (skip->type != SMT_SKIP_ERROR && timeshift_conf.enabled) {
4350     htsp_flush_queue(hs->hs_htsp, &hs->hs_q, 0);
4351     htsp_subscription_timeshift_status(hs, &skip->timeshift);
4352   }
4353 #endif
4354 
4355   if (skip->type == SMT_SKIP_ABS_TIME || skip->type == SMT_SKIP_ABS_SIZE)
4356     htsmsg_add_u32(m, "absolute", 1);
4357   if (skip->type == SMT_SKIP_ERROR)
4358     htsmsg_add_u32(m, "error", 1);
4359   else if (skip->type == SMT_SKIP_ABS_TIME || skip->type == SMT_SKIP_REL_TIME)
4360     htsmsg_add_s64(m, "time", hs->hs_90khz ? skip->time : ts_rescale(skip->time, 1000000));
4361   else if (skip->type == SMT_SKIP_ABS_SIZE || skip->type == SMT_SKIP_REL_SIZE)
4362     htsmsg_add_s64(m, "size", skip->size);
4363   htsp_send_subscription(hs->hs_htsp, m, NULL, hs, 0);
4364 }
4365 
4366 /**
4367  *
4368  */
4369 static void
htsp_streaming_input(void * opaque,streaming_message_t * sm)4370 htsp_streaming_input(void *opaque, streaming_message_t *sm)
4371 {
4372   htsp_subscription_t *hs = opaque;
4373 
4374   switch(sm->sm_type) {
4375   case SMT_PACKET:
4376     if (hs->hs_wait_for_video)
4377       break;
4378     if (!hs->hs_first)
4379       tvhdebug(LS_HTSP, "%s - first packet", hs->hs_htsp->htsp_logname);
4380     hs->hs_first = 1;
4381     htsp_stream_deliver(hs, sm->sm_data);
4382     // reference is transfered
4383     sm->sm_data = NULL;
4384     break;
4385 
4386   case SMT_START:
4387     htsp_subscription_start(hs, sm->sm_data);
4388     break;
4389 
4390   case SMT_STOP:
4391     htsp_subscription_stop(hs, streaming_code2txt(sm->sm_code),
4392         sm->sm_code ? _htsp_get_subscription_status(sm->sm_code) : NULL);
4393     break;
4394 
4395   case SMT_GRACE:
4396     htsp_subscription_grace(hs, sm->sm_code);
4397     break;
4398 
4399   case SMT_SERVICE_STATUS:
4400     htsp_subscription_service_status(hs, sm->sm_code);
4401     break;
4402 
4403   case SMT_SIGNAL_STATUS:
4404     htsp_subscription_signal_status(hs, sm->sm_data);
4405     break;
4406 
4407   case SMT_DESCRAMBLE_INFO:
4408     htsp_subscription_descramble_info(hs, sm->sm_data);
4409     break;
4410 
4411   case SMT_NOSTART:
4412   case SMT_NOSTART_WARN:
4413     htsp_subscription_status(hs,  streaming_code2txt(sm->sm_code),
4414         sm->sm_code ? _htsp_get_subscription_status(sm->sm_code) : NULL);
4415     break;
4416 
4417   case SMT_MPEGTS:
4418     break;
4419 
4420   case SMT_EXIT:
4421     abort();
4422 
4423   case SMT_SKIP:
4424     htsp_subscription_skip(hs, sm->sm_data);
4425     break;
4426 
4427   case SMT_SPEED:
4428     htsp_subscription_speed(hs, sm->sm_code);
4429     break;
4430 
4431   case SMT_TIMESHIFT_STATUS:
4432 #if ENABLE_TIMESHIFT
4433     htsp_subscription_timeshift_status(hs, sm->sm_data);
4434 #endif
4435     break;
4436   }
4437   streaming_msg_free(sm);
4438 }
4439 
4440 static htsmsg_t *
htsp_streaming_input_info(void * opaque,htsmsg_t * list)4441 htsp_streaming_input_info(void *opaque, htsmsg_t *list)
4442 {
4443   char buf[512];
4444   htsp_subscription_t *hs = opaque;
4445   snprintf(buf, sizeof(buf), "htsp input: %s", hs->hs_htsp->htsp_logname);
4446   htsmsg_add_str(list, NULL, buf);
4447   return list;
4448 }
4449