1 /*
2 * libzvbi -- Triggers
3 *
4 * Implementation of EACEM TP 14-99-16 "Data Broadcasting", rev 0.8;
5 * ATVEF "Enhanced Content Specification", v1.1 (www.atvef.com);
6 * and http://developer.webtv.net
7 *
8 * Copyright (C) 2001 Michael H. Schimek
9 *
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Library General Public
12 * License as published by the Free Software Foundation; either
13 * version 2 of the License, or (at your option) any later version.
14 *
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Library General Public License for more details.
19 *
20 * You should have received a copy of the GNU Library General Public
21 * License along with this library; if not, write to the
22 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23 * Boston, MA 02110-1301 USA.
24 */
25
26 /* $Id: trigger.c,v 1.14 2008/02/19 00:35:22 mschimek Exp $ */
27
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31
32 #include <ctype.h>
33 #include <time.h>
34 #include <limits.h>
35 #include <math.h>
36
37 #include "misc.h"
38 #include "trigger.h"
39 #include "tables.h"
40 #include "vbi.h"
41
42 struct vbi_trigger {
43 vbi_trigger * next;
44 vbi_link link;
45 double fire;
46 unsigned char view;
47 vbi_bool _delete;
48 };
49
50 static vbi_bool
verify_checksum(const char * s,int count,int checksum)51 verify_checksum(const char *s, int count, int checksum)
52 {
53 register unsigned long sum2, sum1 = checksum;
54
55 for (; count > 1; count -= 2) {
56 sum1 += (unsigned long) *s++ << 8;
57 sum1 += (unsigned long) *s++;
58 }
59
60 sum2 = sum1;
61
62 /*
63 * There seems to be confusion about how left-over
64 * bytes shall be added, the example C code in
65 * RFC 1071 subclause 4.1 contradicts the definition
66 * in subclause 1 (zero pad to 16 bit).
67 */
68 if (count > 0) {
69 sum1 += (unsigned long) *s << 8; /* correct */
70 sum2 += (unsigned long) *s << 0; /* wrong */
71 }
72
73 while (sum1 >= (1 << 16))
74 sum1 = (sum1 & 0xFFFFUL) + (sum1 >> 16);
75
76 while (sum2 >= (1 << 16))
77 sum2 = (sum2 & 0xFFFFUL) + (sum2 >> 16);
78
79 return sum1 == 0xFFFFUL || sum2 == 0xFFFFUL;
80 }
81
82 static int
parse_dec(const char * s,int digits)83 parse_dec(const char *s, int digits)
84 {
85 int n = 0;
86
87 while (digits-- > 0) {
88 if (!isdigit(*s))
89 return -1;
90
91 n = n * 10 + *s++ - '0';
92 }
93
94 return n;
95 }
96
97 static int
parse_hex(const char * s,int digits)98 parse_hex(const char *s, int digits)
99 {
100 int n = 0;
101
102 while (digits-- > 0) {
103 if (!isxdigit(*s))
104 return -1;
105
106 n = n * 16
107 + (*s & 15) + ((*s > '9') ? 9 : 0);
108 s++;
109 }
110
111 return n;
112 }
113
114 /*
115 * XXX http://developer.webtv.net/itv/tvlink/main.htm adds more ...???
116 */
117 static time_t
parse_date(const char * s)118 parse_date(const char *s)
119 {
120 struct tm tm;
121
122 memset(&tm, 0, sizeof(tm));
123
124 if ((tm.tm_year = parse_dec(s + 0, 4)) < 0
125 || (tm.tm_mon = parse_dec(s + 4, 2)) < 0
126 || (tm.tm_mday = parse_dec(s + 6, 2)) < 0)
127 return (time_t) -1;
128 if (s[8]) {
129 if (s[8] != 'T'
130 || (tm.tm_hour = parse_dec(s + 9, 2)) < 0
131 || (tm.tm_min = parse_dec(s + 11, 2)) < 0)
132 return (time_t) -1;
133 if (s[13] &&
134 (tm.tm_sec = parse_dec(s + 13, 2)) < 0)
135 return (time_t) -1;
136 }
137
138 tm.tm_year -= 1900;
139
140 return mktime(&tm);
141 }
142
143 static int
parse_time(const char * s)144 parse_time(const char *s)
145 {
146 int seconds, frames = 0;
147
148 seconds = strtoul(s, (char **) &s, 10);
149
150 if (*s)
151 if (*s != 'F'
152 || (frames = parse_dec(s + 1, 2)) < 0)
153 return -1;
154
155 return seconds * 25 + frames;
156 }
157
158 static int
parse_bool(char * s)159 parse_bool(char *s)
160 {
161 return (strcmp(s, "1") == 0) || (strcasecmp(s, "true") == 0);
162 }
163
164 static int
keyword(char * s,const char ** keywords,int num)165 keyword(char *s, const char **keywords, int num)
166 {
167 int i;
168
169 if (!s[0])
170 return -1;
171 else if (!s[1]) {
172 for (i = 0; i < num; i++)
173 if (tolower(s[0]) == keywords[i][0])
174 return i;
175 } else
176 for (i = 0; i < num; i++)
177 if (strcasecmp(s, keywords[i]) == 0)
178 return i;
179 return -1;
180 }
181
182 static char *
parse_eacem(vbi_trigger * t,char * s1,unsigned int nuid,double now)183 parse_eacem(vbi_trigger *t, char *s1, unsigned int nuid, double now)
184 {
185 static const char *attributes[] = {
186 "active", "countdown", "delete", "expires",
187 "name", "priority", "script"
188 };
189 char buf[256];
190 char *s, *e, *d, *dx;
191 int active, countdown;
192 int c;
193
194 t->link.url[0] = 0;
195 t->link.name[0] = 0;
196 t->link.script[0] = 0;
197 t->link.priority = 9;
198 t->link.expires = 0.0;
199 t->link.autoload = FALSE;
200 t->_delete = FALSE;
201 t->fire = now;
202 t->view = 'w';
203 t->link.itv_type = 0;
204 active = INT_MAX;
205
206 for (s = s1;; s++) {
207 e = s;
208
209 c = *s;
210
211 if (c == '<') {
212 if (s != s1)
213 return NULL;
214
215 d = (char *) t->link.url;
216 dx = d + sizeof(t->link.url) - 2;
217
218 for (s++; (c = *s) != '>'; s++)
219 if (c && d < dx)
220 *d++ = c;
221 else
222 return NULL;
223 *d++ = 0;
224 } else
225
226 if (c == '[' || c == '(') {
227 int delim = (c == '[') ? ']' : ')';
228 char *attr, *text = "";
229 vbi_bool quote = FALSE;
230
231 attr = d = buf;
232 dx = d + sizeof(buf) - 2;
233
234 for (s++; c = *s, c != ':' && c != delim; s++) {
235 if (c == '%') {
236 if ((c = parse_hex(s + 1, 2)) < 0x20)
237 return NULL;
238 s += 2;
239 }
240
241 if (c && d < dx)
242 *d++ = c;
243 else
244 return NULL;
245 }
246
247 *d++ = 0;
248
249 if (!attr[0])
250 return NULL;
251
252 s++;
253
254 if (c != ':') {
255 if (!verify_checksum(s1, e - s1,
256 strtoul(attr, NULL, 16))) {
257 if (0)
258 fprintf(stderr, "checksum mismatch\n");
259 return NULL;
260 }
261
262 break;
263 }
264
265 for (text = d; quote || (c = *s) != delim; s++) {
266 if (c == '"')
267 quote ^= TRUE;
268 else if (c == '%') {
269 if ((c = parse_hex(s + 1, 2)) < 0x20)
270 return NULL;
271 s += 2;
272 }
273
274 if (c && d < dx)
275 *d++ = c;
276 else
277 return NULL;
278 }
279
280 *d++ = 0;
281
282 switch (keyword(attr, attributes,
283 sizeof(attributes) / sizeof(attributes[0]))) {
284 case 0: /* active */
285 active = parse_time(text);
286 if (active < 0)
287 return NULL;
288 break;
289
290 case 1: /* countdown */
291 countdown = parse_time(text);
292 if (countdown < 0)
293 return NULL;
294 t->fire = now + countdown / 25.0;
295 break;
296
297 case 2: /* delete */
298 t->_delete = TRUE;
299 break;
300
301 case 3: /* expires */
302 t->link.expires = parse_date(text);
303 if (t->link.expires == (time_t) -1)
304 return NULL;
305 break;
306
307 case 4: /* name */
308 strlcpy((char *) t->link.name, text,
309 sizeof(t->link.name) - 1);
310 t->link.name[sizeof(t->link.name) - 1] = 0;
311 break;
312
313 case 5: /* priority */
314 t->link.priority = strtoul(text, NULL, 10);
315 if (t->link.priority > 9)
316 return NULL;
317 break;
318
319 case 6: /* script */
320 strlcpy((char *) t->link.script, text,
321 sizeof(t->link.script) - 1);
322 t->link.script[sizeof(t->link.script) - 1] = 0;
323 break;
324
325 default:
326 /* ignored */
327 break;
328 }
329 } else if (c == 0)
330 break;
331 else
332 return NULL;
333 }
334
335 if (t->link.expires <= 0.0)
336 t->link.expires = t->fire + active / 25.0;
337 /* EACEM eqv PAL/SECAM land, 25 fps */
338
339 if (!t->link.url)
340 return NULL;
341
342 if (strncmp((char *) t->link.url, "http://", 7) == 0)
343 t->link.type = VBI_LINK_HTTP;
344 else if (strncmp((char *) t->link.url, "lid://", 6) == 0)
345 t->link.type = VBI_LINK_LID;
346 else if (strncmp((char *) t->link.url, "tw://", 5) == 0)
347 t->link.type = VBI_LINK_TELEWEB;
348 else if (strncmp((char *) t->link.url, "dummy", 5) == 0) {
349 t->link.pgno = parse_dec((char *) t->link.url + 5, 2);
350 if (!t->link.name || t->link.pgno < 0 || t->link.url[7])
351 return NULL;
352 t->link.type = VBI_LINK_MESSAGE;
353 } else if (strncmp((char *) t->link.url, "ttx://", 6) == 0) {
354 const struct vbi_cni_entry *p;
355 int cni;
356
357 cni = parse_hex((char *) t->link.url + 6, 4);
358 if (cni < 0 || t->link.url[10] != '/')
359 return NULL;
360
361 t->link.pgno = parse_hex((char *) t->link.url + 11, 3);
362 if (t->link.pgno < 0x100 || t->link.url[14] != '/')
363 return NULL;
364
365 t->link.subno = parse_hex((char *) t->link.url + 15, 4);
366 if (t->link.subno < 0)
367 return NULL;
368
369 if (cni > 0) {
370 for (p = vbi_cni_table; p->name; p++)
371 if (p->cni1 == cni || p->cni4 == cni)
372 break;
373 if (!p->name)
374 return NULL;
375 t->link.nuid = p->id;
376 } else
377 t->link.nuid = nuid;
378
379 t->link.type = VBI_LINK_PAGE;
380 } else
381 return NULL;
382
383 return s;
384 }
385
386 static char *
parse_atvef(vbi_trigger * t,char * s1,double now)387 parse_atvef(vbi_trigger *t, char *s1, double now)
388 {
389 static const char *attributes[] = {
390 "auto", "expires", "name", "script",
391 "type" /* "t" */, "time", "tve",
392 "tve-level", "view" /* "v" */
393 };
394 static const char *type_attrs[] = {
395 "program", "network", "station", "sponsor",
396 "operator", "tve"
397 };
398 char buf[256];
399 char *s, *e, *d, *dx;
400 int c;
401
402 t->link.url[0] = 0;
403 t->link.name[0] = 0;
404 t->link.script[0] = 0;
405 t->link.priority = 9;
406 t->fire = now;
407 t->link.expires = 0.0;
408 t->link.autoload = FALSE;
409 t->_delete = FALSE;
410 t->view = 'w';
411 t->link.itv_type = 0;
412
413 for (s = s1;; s++) {
414 e = s;
415 c = *s;
416
417 if (c == '<') {
418 if (s != s1)
419 return NULL;
420
421 d = (char *) t->link.url;
422 dx = (char *) d + sizeof(t->link.url) - 1;
423
424 for (s++; (c = *s) != '>'; s++)
425 if (c && d < dx)
426 *d++ = c;
427 else
428 return NULL;
429 *d++ = 0;
430 } else
431
432 if (c == '[') {
433 char *attr, *text = "";
434 vbi_bool quote = FALSE;
435
436 attr = d = buf;
437 dx = d + sizeof(buf) - 2;
438
439 for (s++; c = *s, c != ':' && c != ']'; s++) {
440 if (c == '%') {
441 if ((c = parse_hex(s + 1, 2)) < 0x20)
442 return NULL;
443 s += 2;
444 }
445
446 if (c && d < dx)
447 *d++ = c;
448 else
449 return NULL;
450 }
451
452 *d++ = 0;
453
454 if (!attr[0])
455 return NULL;
456
457 s++;
458
459 if (c != ':') {
460 unsigned int i;
461
462 for (i = 1; i < (sizeof(type_attrs) / sizeof(type_attrs[0]) - 1); i++)
463 if (strcasecmp(type_attrs[i], attr) == 0)
464 break;
465
466 if (i < (sizeof(type_attrs) / sizeof(type_attrs[0]) - 1)) {
467 t->link.itv_type = i + 1;
468 continue;
469 }
470
471 if (!verify_checksum(s1, e - s1,
472 strtoul(attr, NULL, 16)))
473 return NULL;
474
475 break;
476 }
477
478 for (text = d; quote || (c = *s) != ']'; s++) {
479 if (c == '"')
480 quote ^= TRUE;
481 else if (c == '%') {
482 if ((c = parse_hex(s + 1, 2)) < 0x20)
483 return NULL;
484 s += 2;
485 }
486
487 if (c && d < dx)
488 *d++ = c;
489 else
490 return NULL;
491 }
492
493 *d++ = 0;
494
495 switch (keyword(attr, attributes,
496 sizeof(attributes) / sizeof(attributes[0]))) {
497 case 0: /* auto */
498 t->link.autoload = parse_bool(text);
499 break;
500
501 case 1: /* expires */
502 t->link.expires = parse_date(text);
503 if (t->link.expires < 0.0)
504 return NULL;
505 break;
506
507 case 2: /* name */
508 strlcpy((char *) t->link.name, text,
509 sizeof(t->link.name) - 1);
510 t->link.name[sizeof(t->link.name) - 1] = 0;
511 break;
512
513 case 3: /* script */
514 strlcpy((char *) t->link.script, text,
515 sizeof(t->link.script));
516 t->link.script[sizeof(t->link.script) - 1] = 0;
517 break;
518
519 case 4: /* type */
520 t->link.itv_type = keyword(text, type_attrs,
521 sizeof(type_attrs) / sizeof(type_attrs[0])) + 1;
522 break;
523
524 case 5: /* time */
525 t->fire = parse_date(text);
526 if (t->fire < 0.0)
527 return NULL;
528 break;
529
530 case 6: /* tve */
531 case 7: /* tve-level */
532 /* ignored */
533 break;
534
535 case 8: /* view (tve == v) */
536 t->view = *text;
537 break;
538
539 default:
540 /* ignored */
541 break;
542 }
543
544 } else if (c == 0)
545 break;
546 else
547 return NULL;
548 }
549
550 if (!t->link.url)
551 return NULL;
552
553 if (strncmp((char *) t->link.url, "http://", 7) == 0)
554 t->link.type = VBI_LINK_HTTP;
555 else if (strncmp((char *) t->link.url, "lid://", 6) == 0)
556 t->link.type = VBI_LINK_LID;
557 else
558 return NULL;
559
560 return s;
561 }
562
563 /**
564 * vbi_trigger_flush:
565 * @param vbi Initialized vbi decoding context.
566 *
567 * Discard all triggers stored to fire at a later time. This function
568 * must be called before deleting the @vbi context.
569 **/
570 void
vbi_trigger_flush(vbi_decoder * vbi)571 vbi_trigger_flush(vbi_decoder *vbi)
572 {
573 vbi_trigger *t;
574
575 while ((t = vbi->triggers)) {
576 vbi->triggers = t->next;
577 free(t);
578 }
579 }
580
581 /**
582 * vbi_deferred_trigger:
583 * @param vbi Initialized vbi decoding context.
584 *
585 * This function must be called at regular intervals,
586 * preferably once per video frame, to fire (send a trigger
587 * event) previously received triggers which reached their
588 * fire time. 'Now' is supposed to be @vbi->time.
589 **/
590 void
vbi_deferred_trigger(vbi_decoder * vbi)591 vbi_deferred_trigger(vbi_decoder *vbi)
592 {
593 vbi_trigger *t, **tp;
594
595 for (tp = &vbi->triggers; (t = *tp); tp = &t->next)
596 if (t->fire <= vbi->time) {
597 vbi_event ev;
598
599 ev.type = VBI_EVENT_TRIGGER;
600 ev.ev.trigger = &t->link;
601 vbi_send_event(vbi, &ev);
602
603 *tp = t->next;
604 free(t);
605 } else
606 tp = &t->next;
607 }
608
609 static void
add_trigger(vbi_decoder * vbi,vbi_trigger * a)610 add_trigger(vbi_decoder *vbi, vbi_trigger *a)
611 {
612 vbi_trigger *t;
613
614 if (a->_delete) {
615 vbi_trigger **tp;
616
617 for (tp = &vbi->triggers; (t = *tp); tp = &t->next)
618 if (strcmp((char *) a->link.url, (char *) t->link.url) == 0
619 && fabs(a->fire - t->fire) < 0.1) {
620 *tp = t->next;
621 free(t);
622 } else
623 tp = &t->next;
624
625 return;
626 }
627
628 for (t = vbi->triggers; t; t = t->next)
629 if (strcmp((char *) a->link.url, (char *) t->link.url) == 0
630 && fabs(a->fire - t->fire) < 0.1)
631 return;
632
633 if (a->fire <= vbi->time) {
634 vbi_event ev;
635
636 ev.type = VBI_EVENT_TRIGGER;
637 ev.ev.trigger = &a->link;
638 vbi_send_event(vbi, &ev);
639
640 return;
641 }
642
643 if (!(t = malloc(sizeof(*t))))
644 return;
645
646 t->next = vbi->triggers;
647 vbi->triggers = t;
648 }
649
650 /**
651 * vbi_atvef_trigger:
652 * @param vbi Initialized vbi decoding context.
653 * @param s EACEM string (supposedly ASCII).
654 *
655 * Parse an EACEM string and add it to the trigger list (where it
656 * may fire immediately or at a later time).
657 **/
658 void
vbi_eacem_trigger(vbi_decoder * vbi,unsigned char * s)659 vbi_eacem_trigger(vbi_decoder *vbi, unsigned char *s)
660 {
661 vbi_trigger t;
662 char *r;
663
664 r = (char *) s;
665
666 while ((r = parse_eacem(&t, r,
667 vbi->network.ev.network.nuid, vbi->time))) {
668 if (0)
669 fprintf(stderr, "At %f eacem link type %d '%s', <%s> '%s', "
670 "%08x %03x.%04x, exp %f, pri %d %d, auto %d; "
671 "fire %f view %d del %d\n",
672 vbi->time,
673 t.link.type, t.link.name, t.link.url, t.link.script,
674 t.link.nuid, t.link.pgno, t.link.subno,
675 t.link.expires, t.link.priority, t.link.itv_type,
676 t.link.autoload, t.fire, t.view, t._delete);
677
678 t.link.eacem = TRUE;
679
680 if (t.link.type == VBI_LINK_LID
681 || t.link.type == VBI_LINK_TELEWEB)
682 return;
683
684 add_trigger(vbi, &t);
685 }
686 }
687
688 /**
689 * vbi_atvef_trigger:
690 * @param vbi Initialized vbi context.
691 * @param s ATVEF string (ASCII).
692 *
693 * Parse an ATVEF string and add it to the trigger list (where it
694 * may fire immediately or at a later time).
695 **/
696 void
vbi_atvef_trigger(vbi_decoder * vbi,unsigned char * s)697 vbi_atvef_trigger(vbi_decoder *vbi, unsigned char *s)
698 {
699 vbi_trigger t;
700
701 if (parse_atvef(&t, (char *) s, vbi->time)) {
702 if (0)
703 fprintf(stderr, "At %f atvef link type %d '%s', <%s> '%s', "
704 "%08x %03x.%04x, exp %f, pri %d %d, auto %d; "
705 "fire %f view %d del %d\n",
706 vbi->time,
707 t.link.type, t.link.name, t.link.url, t.link.script,
708 t.link.nuid, t.link.pgno, t.link.subno,
709 t.link.expires, t.link.priority, t.link.itv_type,
710 t.link.autoload, t.fire, t.view, t._delete);
711
712 t.link.eacem = FALSE;
713
714 if (t.view == 't' /* WebTV */
715 || strchr((char *) t.link.url, '*') /* trigger matching */
716 || t.link.type == VBI_LINK_LID)
717 return;
718
719 add_trigger(vbi, &t);
720 }
721 }
722
723
724 /*
725 Local variables:
726 c-set-style: K&R
727 c-basic-offset: 8
728 End:
729 */
730