Merge "stasis: No need to keep a stasis type ref in a stasis msg or cache object."
[asterisk/asterisk.git] / main / pbx_timing.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2016, CFWare, LLC
5  *
6  * Corey Farrell <git@cfware.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*! \file
20  *
21  * \brief PBX timing routines.
22  *
23  * \author Corey Farrell <git@cfware.com>
24  */
25
26 /*** MODULEINFO
27         <support_level>core</support_level>
28  ***/
29
30 #include "asterisk.h"
31
32 #include "asterisk/localtime.h"
33 #include "asterisk/logger.h"
34 #include "asterisk/pbx.h"
35 #include "asterisk/strings.h"
36 #include "asterisk/utils.h"
37
38 /*! \brief Helper for get_range.
39  * return the index of the matching entry, starting from 1.
40  * If names is not supplied, try numeric values.
41  */
42 static int lookup_name(const char *s, const char * const names[], int max)
43 {
44         int i;
45
46         if (names && *s > '9') {
47                 for (i = 0; names[i]; i++) {
48                         if (!strcasecmp(s, names[i])) {
49                                 return i;
50                         }
51                 }
52         }
53
54         /* Allow months and weekdays to be specified as numbers, as well */
55         if (sscanf(s, "%2d", &i) == 1 && i >= 1 && i <= max) {
56                 /* What the array offset would have been: "1" would be at offset 0 */
57                 return i - 1;
58         }
59         return -1; /* error return */
60 }
61
62 /*! \brief helper function to return a range up to max (7, 12, 31 respectively).
63  * names, if supplied, is an array of names that should be mapped to numbers.
64  */
65 static unsigned get_range(char *src, int max, const char * const names[], const char *msg)
66 {
67         int start, end; /* start and ending position */
68         unsigned int mask = 0;
69         char *part;
70
71         /* Check for whole range */
72         if (ast_strlen_zero(src) || !strcmp(src, "*")) {
73                 return (1 << max) - 1;
74         }
75
76         while ((part = strsep(&src, "&"))) {
77                 /* Get start and ending position */
78                 char *endpart = strchr(part, '-');
79                 if (endpart) {
80                         *endpart++ = '\0';
81                 }
82                 /* Find the start */
83                 if ((start = lookup_name(part, names, max)) < 0) {
84                         ast_log(LOG_WARNING, "Invalid %s '%s', skipping element\n", msg, part);
85                         continue;
86                 }
87                 if (endpart) { /* find end of range */
88                         if ((end = lookup_name(endpart, names, max)) < 0) {
89                                 ast_log(LOG_WARNING, "Invalid end %s '%s', skipping element\n", msg, endpart);
90                                 continue;
91                         }
92                 } else {
93                         end = start;
94                 }
95                 /* Fill the mask. Remember that ranges are cyclic */
96                 mask |= (1 << end);   /* initialize with last element */
97                 while (start != end) {
98                         mask |= (1 << start);
99                         if (++start >= max) {
100                                 start = 0;
101                         }
102                 }
103         }
104         return mask;
105 }
106
107 /*! \brief store a bitmask of valid times, one bit each 1 minute */
108 static void get_timerange(struct ast_timing *i, char *times)
109 {
110         char *endpart, *part;
111         int x;
112         int st_h, st_m;
113         int endh, endm;
114         int minute_start, minute_end;
115
116         /* start disabling all times, fill the fields with 0's, as they may contain garbage */
117         memset(i->minmask, 0, sizeof(i->minmask));
118
119         /* 1-minute per bit */
120         /* Star is all times */
121         if (ast_strlen_zero(times) || !strcmp(times, "*")) {
122                 /* 48, because each hour takes 2 integers; 30 bits each */
123                 for (x = 0; x < 48; x++) {
124                         i->minmask[x] = 0x3fffffff; /* 30 bits */
125                 }
126                 return;
127         }
128         /* Otherwise expect a range */
129         while ((part = strsep(&times, "&"))) {
130                 if (!(endpart = strchr(part, '-'))) {
131                         if (sscanf(part, "%2d:%2d", &st_h, &st_m) != 2 || st_h < 0 || st_h > 23 || st_m < 0 || st_m > 59) {
132                                 ast_log(LOG_WARNING, "%s isn't a valid time.\n", part);
133                                 continue;
134                         }
135                         i->minmask[st_h * 2 + (st_m >= 30 ? 1 : 0)] |= (1 << (st_m % 30));
136                         continue;
137                 }
138                 *endpart++ = '\0';
139                 /* why skip non digits? Mostly to skip spaces */
140                 while (*endpart && !isdigit(*endpart)) {
141                         endpart++;
142                 }
143                 if (!*endpart) {
144                         ast_log(LOG_WARNING, "Invalid time range starting with '%s-'.\n", part);
145                         continue;
146                 }
147                 if (sscanf(part, "%2d:%2d", &st_h, &st_m) != 2 || st_h < 0 || st_h > 23 || st_m < 0 || st_m > 59) {
148                         ast_log(LOG_WARNING, "'%s' isn't a valid start time.\n", part);
149                         continue;
150                 }
151                 if (sscanf(endpart, "%2d:%2d", &endh, &endm) != 2 || endh < 0 || endh > 23 || endm < 0 || endm > 59) {
152                         ast_log(LOG_WARNING, "'%s' isn't a valid end time.\n", endpart);
153                         continue;
154                 }
155                 minute_start = st_h * 60 + st_m;
156                 minute_end = endh * 60 + endm;
157                 /* Go through the time and enable each appropriate bit */
158                 for (x = minute_start; x != minute_end; x = (x + 1) % (24 * 60)) {
159                         i->minmask[x / 30] |= (1 << (x % 30));
160                 }
161                 /* Do the last one */
162                 i->minmask[x / 30] |= (1 << (x % 30));
163         }
164         /* All done */
165         return;
166 }
167
168 static const char * const days[] =
169 {
170         "sun",
171         "mon",
172         "tue",
173         "wed",
174         "thu",
175         "fri",
176         "sat",
177         NULL,
178 };
179
180 static const char * const months[] =
181 {
182         "jan",
183         "feb",
184         "mar",
185         "apr",
186         "may",
187         "jun",
188         "jul",
189         "aug",
190         "sep",
191         "oct",
192         "nov",
193         "dec",
194         NULL,
195 };
196
197 /*! /brief Build timing
198  *
199  * /param i info
200  * /param info_in
201  *
202  */
203 int ast_build_timing(struct ast_timing *i, const char *info_in)
204 {
205         char *info;
206         int j, num_fields, last_sep = -1;
207
208         i->timezone = NULL;
209
210         /* Check for empty just in case */
211         if (ast_strlen_zero(info_in)) {
212                 return 0;
213         }
214
215         /* make a copy just in case we were passed a static string */
216         info = ast_strdupa(info_in);
217
218         /* count the number of fields in the timespec */
219         for (j = 0, num_fields = 1; info[j] != '\0'; j++) {
220                 if (info[j] == ',') {
221                         last_sep = j;
222                         num_fields++;
223                 }
224         }
225
226         /* save the timezone, if it is specified */
227         if (num_fields == 5) {
228                 i->timezone = ast_strdup(info + last_sep + 1);
229         }
230
231         /* Assume everything except time */
232         i->monthmask = 0xfff;   /* 12 bits */
233         i->daymask = 0x7fffffffU; /* 31 bits */
234         i->dowmask = 0x7f; /* 7 bits */
235         /* on each call, use strsep() to move info to the next argument */
236         get_timerange(i, strsep(&info, "|,"));
237         if (info)
238                 i->dowmask = get_range(strsep(&info, "|,"), 7, days, "day of week");
239         if (info)
240                 i->daymask = get_range(strsep(&info, "|,"), 31, NULL, "day");
241         if (info)
242                 i->monthmask = get_range(strsep(&info, "|,"), 12, months, "month");
243         return 1;
244 }
245
246 int ast_check_timing(const struct ast_timing *i)
247 {
248         return ast_check_timing2(i, ast_tvnow());
249 }
250
251 int ast_check_timing2(const struct ast_timing *i, const struct timeval tv)
252 {
253         struct ast_tm tm;
254
255         ast_localtime(&tv, &tm, i->timezone);
256
257         /* If it's not the right month, return */
258         if (!(i->monthmask & (1 << tm.tm_mon)))
259                 return 0;
260
261         /* If it's not that time of the month.... */
262         /* Warning, tm_mday has range 1..31! */
263         if (!(i->daymask & (1 << (tm.tm_mday-1))))
264                 return 0;
265
266         /* If it's not the right day of the week */
267         if (!(i->dowmask & (1 << tm.tm_wday)))
268                 return 0;
269
270         /* Sanity check the hour just to be safe */
271         if ((tm.tm_hour < 0) || (tm.tm_hour > 23)) {
272                 ast_log(LOG_WARNING, "Insane time...\n");
273                 return 0;
274         }
275
276         /* Now the tough part, we calculate if it fits
277            in the right time based on min/hour */
278         if (!(i->minmask[tm.tm_hour * 2 + (tm.tm_min >= 30 ? 1 : 0)] & (1 << (tm.tm_min >= 30 ? tm.tm_min - 30 : tm.tm_min))))
279                 return 0;
280
281         /* If we got this far, then we're good */
282         return 1;
283 }
284
285 int ast_destroy_timing(struct ast_timing *i)
286 {
287         if (i->timezone) {
288                 ast_free(i->timezone);
289                 i->timezone = NULL;
290         }
291         return 0;
292 }