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