73663ce4b5984a51596e4a4b89708c39c93214cd
[iotivity.git] / resource / csdk / security / src / iotvticalendar.c
1 //******************************************************************
2 //
3 // Copyright 2015 Intel Mobile Communications GmbH All Rights Reserved.
4 //
5 //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
6 //
7 // Licensed under the Apache License, Version 2.0 (the "License");
8 // you may not use this file except in compliance with the License.
9 // You may obtain a copy of the License at
10 //
11 //      http://www.apache.org/licenses/LICENSE-2.0
12 //
13 // Unless required by applicable law or agreed to in writing, software
14 // distributed under the License is distributed on an "AS IS" BASIS,
15 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 // See the License for the specific language governing permissions and
17 // limitations under the License.
18 //
19 //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
20
21 //Not supported on Arduino due lack of absolute time need to implement iCalendar
22 #if !defined(WITH_ARDUINO)
23
24 #define _XOPEN_SOURCE  //Needed by strptime
25 #include "iotivity_config.h"
26 #include <string.h>
27 #include <assert.h>
28 #include "iotvticalendar.h"
29 #include "oic_string.h"
30
31 #ifndef HAVE_STRPTIME
32 char *strptime(const char *buf, const char *fmt, struct tm *tm);
33 #endif
34
35 static char dtFormat[] =  "%Y%m%dT%H%M%S"; //date-time format
36 static char dFormat[] =  "%Y%m%d";         // date format
37
38 static const char FREQ[]  = "FREQ";
39 static const char UNTIL[] = "UNTIL";
40 static const char BYDAY[] = "BYDAY";
41 static const char DAILY[] = "DAILY";
42
43 static size_t CalculateElementLength(const char* startPosition, const char* endPosition)
44 {
45     assert((NULL != startPosition) &&
46             ((NULL == endPosition) || (endPosition >= startPosition)));
47     return (NULL != endPosition) ? (size_t) (endPosition - startPosition) : strlen(startPosition);
48 }
49
50 IotvtICalResult_t ParsePeriod(const char *periodStr, IotvtICalPeriod_t *period)
51 {
52     if ((NULL == periodStr) || (NULL == period))
53     {
54         return IOTVTICAL_INVALID_PARAMETER;
55     }
56
57     char *endDTPos;
58     char *fmt = "";
59     int   startDTLen;
60     int   endDTLen;
61
62     //Finding length of startDateTime and endDateTime in period
63     //startDateTime and endDateTime can have form YYYYmmdd or YYYYmmddTHHMMSS
64     //startDateTime and endDateTime must be same form
65     //Eg: periodStr = "20150629T153050/20150630T203055"
66     //    periodStr = "20150629/20150630"
67     if (NULL == (endDTPos = strchr(periodStr, '/')))
68     {
69         return IOTVTICAL_INVALID_PERIOD;
70     }
71     endDTPos += 1;
72     startDTLen = endDTPos - periodStr - 1;
73     endDTLen   = strlen(endDTPos);
74
75     //Checking if both startDateTime and endDateTime are of same form
76     if (startDTLen == endDTLen)
77     {
78         if (8 == startDTLen) //YYYYmmdd
79         {
80             fmt = dFormat;
81         }
82         else if (15 == startDTLen) //YYYYmmddTHHMMSS
83         {
84             fmt = dtFormat;
85         }
86         else
87         {
88             return IOTVTICAL_INVALID_PERIOD;
89         }
90     }
91     else
92     {
93         return IOTVTICAL_INVALID_PERIOD;
94     }
95
96     //Checking if startDateTime has right format
97     if (NULL != strptime(periodStr, fmt, &period->startDateTime))
98     {
99         //Checking if endDateTime has right format
100         if (NULL != strptime(endDTPos, fmt, &period->endDateTime))
101         {
102             //Checking if endDateTime is after startDateTime
103             if ((period->startDateTime.tm_year > period->endDateTime.tm_year)
104                 || ((period->startDateTime.tm_year == period->endDateTime.tm_year)
105                     && (period->startDateTime.tm_mon > period->endDateTime.tm_mon))
106                 || ((period->startDateTime.tm_year == period->endDateTime.tm_year)
107                     && (period->startDateTime.tm_mon == period->endDateTime.tm_mon)
108                     && (period->startDateTime.tm_mday > period->endDateTime.tm_mday))
109                 || (( fmt == dtFormat) && (period->startDateTime.tm_year == period->endDateTime.tm_year)
110                     && (period->startDateTime.tm_mon == period->endDateTime.tm_mon)
111                     && (period->startDateTime.tm_mday == period->endDateTime.tm_mday)
112                     && (period->startDateTime.tm_hour > period->endDateTime.tm_hour))
113                 || (( fmt == dtFormat) && (period->startDateTime.tm_year == period->endDateTime.tm_year)
114                     && (period->startDateTime.tm_mon == period->endDateTime.tm_mon)
115                     && (period->startDateTime.tm_mday == period->endDateTime.tm_mday)
116                     && (period->startDateTime.tm_hour == period->endDateTime.tm_hour)
117                     && (period->startDateTime.tm_min > period->endDateTime.tm_min))
118                 || (( fmt == dtFormat) && (period->startDateTime.tm_year == period->endDateTime.tm_year)
119                     && (period->startDateTime.tm_mon == period->endDateTime.tm_mon)
120                     && (period->startDateTime.tm_mday == period->endDateTime.tm_mday)
121                     && (period->startDateTime.tm_hour == period->endDateTime.tm_hour)
122                     && (period->startDateTime.tm_min == period->endDateTime.tm_min)
123                     && (period->startDateTime.tm_sec > period->endDateTime.tm_sec)))
124             {
125                 return IOTVTICAL_INVALID_PERIOD;
126             }
127             else
128             {
129                 return IOTVTICAL_SUCCESS;
130             }
131         }
132     }
133     return IOTVTICAL_INVALID_PERIOD;
134 }
135
136 /**
137  * Parses untilRule and populate "until" field of struct IotvtICalRecur_t.
138  *
139  * @param untilRule is a string to be parsed.
140  * @param recur is the reference to the @ref IotvtICalRecur_t to be populated.
141  *
142  * @return ::IOTVTICAL_SUCCESS is succesful, else in case of error
143  * ::IOTVTICAL_ERROR, if untilRule has invalid format or ::IOTVTICAL_INVALID_SUCCESS,
144  * if no error while parsing.
145  */
146 static IotvtICalResult_t ParseDate(char *untilRule, IotvtICalRecur_t *recur)
147 {
148     char *date = strchr(untilRule, '=');
149
150     if (NULL == date)
151     {
152         return IOTVTICAL_ERROR;
153     }
154     date += 1;
155
156     if (strlen(date) == 8) //YYYYmmdd
157     {
158         if (NULL != strptime(date, dFormat, &recur->until))
159         {
160             return IOTVTICAL_SUCCESS;
161         }
162     }
163     return IOTVTICAL_ERROR;
164 }
165
166 /**
167  * Parses bydayRule and populate "byDay" field of struct @ref IotvtICalRecur_t.
168  *
169  * @param bydayRule is a string to be parsed.
170  * @param recur is a reference to @ref IotvtICalRecur_t struct to be populated.
171  *
172  * @return ::IOTVTICAL_SUCCESS is succesful, else in case of error ::IOTVTICAL_ERROR,
173  * if bydayRule has empty weekday list or invalid weekdays.
174  */
175 static IotvtICalResult_t  ParseByDay(char *bydayRule, IotvtICalRecur_t *recur)
176 {
177     if (strstr(bydayRule, "SU"))
178     {
179         recur->byDay = recur->byDay | SUNDAY;
180     }
181     if (strstr(bydayRule, "MO"))
182     {
183         recur->byDay = recur->byDay | MONDAY;
184     }
185     if (strstr(bydayRule, "TU"))
186     {
187         recur->byDay = recur->byDay | TUESDAY;
188     }
189     if (strstr(bydayRule, "WE"))
190     {
191         recur->byDay = recur->byDay | WEDNESDAY;
192     }
193     if (strstr(bydayRule, "TH"))
194     {
195         recur->byDay = recur->byDay | THURSDAY;
196     }
197     if (strstr(bydayRule, "FR"))
198     {
199         recur->byDay = recur->byDay | FRIDAY;
200     }
201     if (strstr(bydayRule, "SA"))
202     {
203         recur->byDay = recur->byDay | SATURDAY;
204     }
205
206     //Checking if byDay list is empty or has inValid weekdays
207     if (recur->byDay == NO_WEEKDAY)
208     {
209         return IOTVTICAL_ERROR;
210     }
211
212     return IOTVTICAL_SUCCESS;
213 }
214
215 IotvtICalResult_t ParseRecur(const char *recurStr, IotvtICalRecur_t *recur)
216 {
217     if ((NULL == recurStr) || (NULL == recur))
218     {
219         return IOTVTICAL_INVALID_PARAMETER;
220     }
221
222     const char *startPos = recurStr;
223     const char *endPos;
224     char        buf[50];
225     int         freqFlag = 0; //valid RRULE must have "FREQ" parameter.
226                               //flag to track if RRULE has "FREQ" or not
227
228     //Iterates though recurrence rule
229     //E.g., RRULE: FREQ=DAILY; UNTIL=20150703; BYDAY=MO, WE, FR
230     while (NULL != startPos)
231     {
232         endPos = strchr(startPos, ';');
233         size_t elementLength = CalculateElementLength(startPos, endPos);
234         if (elementLength >= sizeof(buf))
235         {
236             return IOTVTICAL_INVALID_RRULE;
237         }
238
239         OICStrcpyPartial(buf, sizeof(buf), startPos, elementLength);
240         if (NULL != strstr(buf, FREQ))
241         {
242             if (NULL != strstr(buf, DAILY))
243             {
244                 recur->freq = FREQ_DAILY;
245                 freqFlag = 1;
246             }
247             else
248             {
249                 return IOTVTICAL_INVALID_RRULE;
250             }
251         }
252         else if (NULL != strstr(buf, UNTIL))
253         {
254             if (IOTVTICAL_SUCCESS != ParseDate(buf, recur))
255             {
256                 return IOTVTICAL_INVALID_RRULE;
257             }
258         }
259         else if (NULL != strstr(buf, BYDAY))
260         {
261             if (IOTVTICAL_SUCCESS != ParseByDay(buf, recur))
262             {
263                 return IOTVTICAL_INVALID_RRULE;
264             };
265         }
266
267         startPos = (NULL != endPos) ? (endPos + 1) : NULL;
268     }
269
270     if (1 != freqFlag)
271     {
272         return IOTVTICAL_INVALID_RRULE;
273     }
274
275     return IOTVTICAL_SUCCESS;
276 }
277
278 /**
279  * Computes number of days between two dates.
280  *
281  * @param date1 earlier date.
282  * @param date2 later date.
283  *
284  * @return  number of days between date1 & date2.
285  */
286 static int DiffDays(IotvtICalDateTime_t *date1, IotvtICalDateTime_t *date2)
287 {
288     int days;
289     int leapDays=0;
290
291     if (date2->tm_year > date1->tm_year)
292     {
293         for (int y = date1->tm_year; y < date2->tm_year; y++)
294         {
295             y += TM_YEAR_OFFSET;
296             if (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0))
297             {
298                leapDays += 1;
299             }
300         }
301     }
302
303     days = (365 * date2->tm_year + date2->tm_yday + leapDays) -
304            (365 * date1->tm_year + date1->tm_yday);
305
306     return days;
307 }
308
309 /**
310  * Computes number of seconds between two time.
311  *
312  * @param time1 earlier time.
313  * @param date2 later time.
314  *
315  * @return  number of seconds between time1 and time2.
316  */
317 static int DiffSecs(IotvtICalDateTime_t *time1, IotvtICalDateTime_t *time2)
318 {
319     return (3600 * time2->tm_hour + 60 * time2->tm_min + time2->tm_sec) -
320            (3600 * time1->tm_hour + 60 * time1->tm_min + time1->tm_sec);
321 }
322
323 /**
324  * Validates if the @param currentTime is with in allowable period.
325  *
326  * @param period allowable period.
327  * @param currentTime the time that need to be validated against allowable time.
328  *
329  * @return ::IOTVTICAL_VALID_ACCESS, if the request is within valid time period.
330  * ::IOTVTICAL_INVALID_ACCESS, if the request is not within valid time period.
331  * ::IOTVTICAL_INVALID_PARAMETER, if parameter are invalid.
332  */
333 static IotvtICalResult_t ValidatePeriod(IotvtICalPeriod_t *period, IotvtICalDateTime_t *currentTime)
334 {
335     if (NULL == period || NULL == currentTime)
336     {
337         return IOTVTICAL_INVALID_PARAMETER;
338     }
339
340     bool validStartTime = true;
341     bool validEndTime = true;
342     bool validDay = false;
343     bool todayIsStartDay = (0 == DiffDays(&period->startDateTime, currentTime)) ? true : false;
344     bool todayIsEndDay = (0 == DiffDays(currentTime, &period->endDateTime)) ? true : false;
345
346     //If today is the start day of the allowable period then check
347     //currentTime > allowable period startTime
348     if (todayIsStartDay)
349     {
350         validStartTime = (0 <= DiffSecs(&period->startDateTime, currentTime)) ? true : false;
351     }
352
353     //If today is the end day of allowable period then check
354     //currentTime < allowable period endTime
355     if (todayIsEndDay)
356     {
357         validEndTime = (0 <= DiffSecs(currentTime, &period->endDateTime)) ? true :false;
358     }
359
360     //Check if today is valid day between startDate and EndDate inclusive
361     if ((0 <= DiffDays(&period->startDateTime, currentTime)) &&
362        (0 <= DiffDays(currentTime, &period->endDateTime)))
363     {
364         validDay = true;
365     }
366
367     if (validDay && validStartTime && validEndTime)
368     {
369         return IOTVTICAL_VALID_ACCESS;
370     }
371     else
372     {
373         return IOTVTICAL_INVALID_ACCESS;
374     }
375 }
376
377 IotvtICalResult_t IsRequestWithinValidTime(const char *periodStr, const char *recurStr)
378 {
379     //NULL recur rule means no recurring patter exist.
380     //Period can't be null. Period is used with or without
381     //recur rule to compute allowable access time.
382     if (NULL == periodStr)
383     {
384         return IOTVTICAL_INVALID_PARAMETER;
385     }
386
387     IotvtICalPeriod_t period = {.startDateTime={.tm_sec=0}};
388     IotvtICalRecur_t recur = {.freq=0};
389     IotvtICalResult_t ret = IOTVTICAL_INVALID_ACCESS;
390
391     time_t rawTime = time(0);
392     IotvtICalDateTime_t *currentTime = localtime(&rawTime);
393
394     ret  = ParsePeriod(periodStr, &period);
395     if (ret != IOTVTICAL_SUCCESS)
396     {
397         return ret;
398     }
399
400     //If recur is NULL then the access time is between period's startDateTime and endDateTime
401     if (NULL == recurStr)
402     {
403         ret = ValidatePeriod(&period, currentTime);
404     }
405
406     //If recur is not NULL then the access time is between period's startTime and
407     //endTime on days specified in "BYDAY" list. The first instance of recurrence
408     //is computed from period's startDate and the last instance is computed from
409     //"UNTIL". If "UNTIL" is not specified then the recurrence goes for forever.
410     //Eg, RRULE: FREQ=DAILY; UNTIL=20150703; BYDAY=MO, WE, FR
411     if (NULL != recurStr)
412     {
413         ret = ParseRecur(recurStr, &recur);
414         if (ret != IOTVTICAL_SUCCESS)
415         {
416             return ret;
417         }
418
419         if ((0 <= DiffSecs(&period.startDateTime, currentTime))&&
420            (0 <= DiffSecs(currentTime, &period.endDateTime)) &&
421            (0 <= DiffDays(&period.startDateTime, currentTime)))
422         {
423             IotvtICalDateTime_t emptyDT = {.tm_sec=0};
424             ret = IOTVTICAL_VALID_ACCESS;
425
426             //"UNTIL" is an optional parameter of RRULE, checking if until present in recur
427             if (0 != memcmp(&recur.until, &emptyDT, sizeof(IotvtICalDateTime_t)))
428             {
429                 if(0 > DiffDays(currentTime, &recur.until))
430                 {
431                     ret = IOTVTICAL_INVALID_ACCESS;
432                 }
433             }
434
435             //"BYDAY" is an optional parameter of RRULE, checking if byday present in recur
436             if (NO_WEEKDAY != recur.byDay)
437             {
438
439                 int isValidWD = (0x1 << currentTime->tm_wday) & recur.byDay; //Valid weekdays
440                 if (!isValidWD)
441                 {
442                     ret = IOTVTICAL_INVALID_ACCESS;
443                 }
444              }
445         }
446         else
447         {
448             ret = IOTVTICAL_INVALID_ACCESS;
449         }
450     }
451     return ret;
452 }
453 #endif