1 /*
2 	Linphone
3 	Copyright (C) 2014  Belledonne Communications SARL
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 2 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 #ifndef _XOPEN_SOURCE
20 	#define _XOPEN_SOURCE 700 // To have definition of strptime, snprintf and getline
21 #endif
22 #include <time.h>
23 #include "linphone/core.h"
24 #include "private.h"
25 #include "liblinphone_tester.h"
26 
27 #ifdef HAVE_ZLIB
28 #include <zlib.h>
29 #endif
30 
31 
32 /*getline is POSIX 2008, not available on many systems.*/
33 #if (defined(__ANDROID__) && !defined(__LP64__)) || defined(_WIN32) || defined(__QNX__)
34 /* This code is public domain -- Will Hartung 4/9/09 */
getline(char ** lineptr,size_t * n,FILE * stream)35 static ssize_t getline(char **lineptr, size_t *n, FILE *stream) {
36 	char *bufptr = NULL;
37 	char *p = bufptr;
38 	size_t size;
39 	int c;
40 
41 	if (lineptr == NULL) {
42 		return -1;
43 	}
44 	if (stream == NULL) {
45 		return -1;
46 	}
47 	if (n == NULL) {
48 		return -1;
49 	}
50 	bufptr = *lineptr;
51 	size = *n;
52 
53 	c = fgetc(stream);
54 	if (c == EOF) {
55 		return -1;
56 	}
57 	if (bufptr == NULL) {
58 		bufptr = malloc(128);
59 		if (bufptr == NULL) {
60 			return -1;
61 		}
62 		size = 128;
63 	}
64 	p = bufptr;
65 	while(c != EOF) {
66 		size_t curpos = p-bufptr;
67 
68 		if (curpos > (size - 1)) {
69 			size = size + 128;
70 			bufptr = realloc(bufptr, size);
71 			p = bufptr + curpos;
72 			if (bufptr == NULL) {
73 				return -1;
74 			}
75 		}
76 		*p++ = c;
77 		if (c == '\n') {
78 			break;
79 		}
80 		c = fgetc(stream);
81 	}
82 
83 	*p++ = '\0';
84 	*lineptr = bufptr;
85 	*n = size;
86 
87 	return (ssize_t)(p - bufptr) - 1;
88 }
89 #endif
90 
91 static LinphoneLogCollectionState old_collection_state;
collect_init(void)92 static void collect_init(void)  {
93 	old_collection_state = linphone_core_log_collection_enabled();
94 	linphone_core_set_log_collection_path(bc_tester_get_writable_dir_prefix());
95 }
96 
collect_cleanup(LinphoneCoreManager * marie)97 static void collect_cleanup(LinphoneCoreManager *marie)  {
98 	linphone_core_manager_destroy(marie);
99 
100 	linphone_core_enable_log_collection(old_collection_state);
101 	linphone_core_reset_log_collection();
102 }
103 
setup(LinphoneLogCollectionState log_collection_state)104 static LinphoneCoreManager* setup(LinphoneLogCollectionState log_collection_state)  {
105 	LinphoneCoreManager *marie;
106 	int timeout = 300;
107 
108 	collect_init();
109 	linphone_core_enable_log_collection(log_collection_state);
110 
111 	marie = linphone_core_manager_new2("marie_rc", 0);
112 	// wait a few seconds to generate some traffic
113 	while (--timeout){
114 		// Generate some logs - error logs because we must ensure that
115 		// even if user did not enable logs, we will see them
116 		ms_error("(test error)Timeout in %d...", timeout);
117 	}
118 	return marie;
119 }
120 
121 #if HAVE_ZLIB
122 
123 /*returns uncompressed log file*/
gzuncompress(const char * filepath)124 static FILE* gzuncompress(const char* filepath) {
125 		gzFile file = gzopen(filepath, "rb");
126 		FILE *output = NULL;
127 		FILE *ret;
128 		char *newname = ms_strdup_printf("%s.txt", filepath);
129 		char buffer[512]={0};
130 		output = fopen(newname, "wb");
131 		while (gzread(file, buffer, 511) > 0) {
132 			fputs(buffer, output);
133 			memset(buffer, 0, strlen(buffer));
134 		}
135 		fclose(output);
136 		BC_ASSERT_EQUAL(gzclose(file), Z_OK, int, "%d");
137 		ret=fopen(newname, "rb");
138 		ms_free(newname);
139 		return ret;
140 }
141 #endif
142 
get_current_time(void)143 static time_t get_current_time(void) {
144 	struct timeval tp;
145 	struct tm *lt;
146 #ifndef _WIN32
147 	struct tm tmbuf;
148 #endif
149 	time_t tt;
150 	ortp_gettimeofday(&tp,NULL);
151 	tt = (time_t)tp.tv_sec;
152 
153 #ifdef _WIN32
154 	lt = localtime(&tt);
155 #else
156 	lt = localtime_r(&tt,&tmbuf);
157 #endif
158 	return mktime(lt);
159 }
160 
check_file(LinphoneCoreManager * mgr)161 static time_t check_file(LinphoneCoreManager* mgr)  {
162 
163 	time_t cur_time = get_current_time();
164 	char*    filepath = linphone_core_compress_log_collection();
165 	time_t  log_time = -1;
166 	uint32_t timediff = 0;
167 	FILE *file = NULL;
168 
169 	BC_ASSERT_PTR_NOT_NULL(filepath);
170 
171 	if (filepath != NULL) {
172 		int line_count = 0;
173 		char *line = NULL;
174 		size_t line_size = 256;
175 #ifndef _WIN32
176 		struct tm tm_curr = {0};
177 		time_t time_prev = 0;
178 #endif
179 
180 #if HAVE_ZLIB
181 		// 0) if zlib is enabled, we must decompress the file first
182 		file = gzuncompress(filepath);
183 #else
184 		file = fopen(filepath, "rb");
185 #endif
186 		BC_ASSERT_PTR_NOT_NULL(file);
187 		if (!file) return 0;
188 		// 1) expect to find folder name in filename path
189 		BC_ASSERT_PTR_NOT_NULL(strstr(filepath, bc_tester_get_writable_dir_prefix()));
190 
191 		// 2) check file contents
192 		while (getline(&line, &line_size, file) != -1) {
193 			// a) there should be at least 25 lines
194 			++line_count;
195 #ifndef _WIN32
196 			// b) logs should be ordered by date (format: 2014-11-04 15:22:12:606)
197 			if (strlen(line) > 24) {
198 				char date[24] = {'\0'};
199 				memcpy(date, line, 23);
200 				/*reset tm_curr to reset milliseconds and below fields*/
201 				memset(&tm_curr, 0, sizeof(struct tm));
202 				if (strptime(date, "%Y-%m-%d %H:%M:%S", &tm_curr) != NULL) {
203 					tm_curr.tm_isdst = -1; // LOL
204 					log_time = mktime(&tm_curr);
205 					BC_ASSERT_GREATER(log_time , time_prev, long int, "%ld");
206 					time_prev = log_time;
207 				}
208 			}
209 #endif
210 		}
211 		BC_ASSERT_GREATER(line_count , 25, int, "%d");
212 		free(line);
213 		fclose(file);
214 		ms_free(filepath);
215 
216 
217 		timediff = labs((long int)log_time - (long int)cur_time);
218 #ifndef _WIN32
219 		BC_ASSERT_LOWER(timediff, 1, unsigned, "%u");
220 		if( !(timediff <= 1) ){
221 			char buffers[2][128] = {{0}};
222 			strftime(buffers[0], sizeof(buffers[0]), "%Y-%m-%d %H:%M:%S", localtime(&log_time));
223 			strftime(buffers[1], sizeof(buffers[1]), "%Y-%m-%d %H:%M:%S", localtime(&cur_time));
224 
225 			ms_error("log_time: %ld (%s), cur_time: %ld (%s) timediff: %u"
226 				, (long int)log_time, buffers[0]
227 				, (long int)cur_time, buffers[1]
228 				, timediff
229 			);
230 		}
231 #else
232 		(void)timediff;
233 		ms_warning("strptime() not available for this platform, test is incomplete.");
234 #endif
235 	}
236 	// return latest time in file
237 	return log_time;
238 }
239 
collect_files_disabled(void)240 static void collect_files_disabled(void)  {
241 	LinphoneCoreManager* marie = setup(LinphoneLogCollectionDisabled);
242 	BC_ASSERT_PTR_NULL(linphone_core_compress_log_collection());
243 	collect_cleanup(marie);
244 }
245 
collect_files_filled(void)246 static void collect_files_filled(void) {
247 	LinphoneCoreManager* marie = setup(LinphoneLogCollectionEnabled);
248 	check_file(marie);
249 	collect_cleanup(marie);
250 }
251 
collect_files_small_size(void)252 static void collect_files_small_size(void)  {
253 	LinphoneCoreManager* marie = setup(LinphoneLogCollectionEnabled);
254 	linphone_core_set_log_collection_max_file_size(5000);
255 	check_file(marie);
256 	collect_cleanup(marie);
257 }
258 
collect_files_changing_size(void)259 static void collect_files_changing_size(void)  {
260 	LinphoneCoreManager* marie = setup(LinphoneLogCollectionEnabled);
261 	int waiting = 100;
262 
263 	check_file(marie);
264 
265 	linphone_core_set_log_collection_max_file_size(5000);
266 	// Generate some logs
267 	while (--waiting) ms_error("(test error)Waiting %d...", waiting);
268 
269 	check_file(marie);
270 
271 	collect_cleanup(marie);
272 }
logCollectionUploadStateChangedCb(LinphoneCore * lc,LinphoneCoreLogCollectionUploadState state,const char * info)273 static void logCollectionUploadStateChangedCb(LinphoneCore *lc, LinphoneCoreLogCollectionUploadState state, const char *info) {
274 
275 	stats* counters = get_stats(lc);
276 	ms_message("lc [%p], logCollectionUploadStateChanged to [%s], info [%s]",lc
277 																			,linphone_core_log_collection_upload_state_to_string(state)
278 																			,info);
279 	switch(state) {
280 		case LinphoneCoreLogCollectionUploadStateInProgress:
281 			counters->number_of_LinphoneCoreLogCollectionUploadStateInProgress++;
282 			break;
283 		case LinphoneCoreLogCollectionUploadStateDelivered:
284 			counters->number_of_LinphoneCoreLogCollectionUploadStateDelivered++;
285 			BC_ASSERT_GREATER((int)strlen(info), 0, int, "%d");
286 			break;
287 		case LinphoneCoreLogCollectionUploadStateNotDelivered:
288 			counters->number_of_LinphoneCoreLogCollectionUploadStateNotDelivered++;
289 			break;
290 	}
291 }
upload_collected_traces(void)292 static void upload_collected_traces(void)  {
293 	if (transport_supported(LinphoneTransportTls)) {
294 		LinphoneCoreManager* marie = setup(LinphoneLogCollectionEnabled);
295 		int waiting = 100;
296 		LinphoneCoreCbs *cbs = linphone_factory_create_core_cbs(linphone_factory_get());
297 		linphone_core_cbs_set_log_collection_upload_state_changed(cbs, logCollectionUploadStateChangedCb);
298 		linphone_core_add_callbacks(marie->lc, cbs);
299 		linphone_core_cbs_unref(cbs);
300 
301 		linphone_core_set_log_collection_max_file_size(5000);
302 		linphone_core_set_log_collection_upload_server_url(marie->lc,"https://www.linphone.org:444/lft.php");
303 		// Generate some logs
304 		while (--waiting) ms_error("(test error)Waiting %d...", waiting);
305 		ms_free(linphone_core_compress_log_collection());
306 		linphone_core_upload_log_collection(marie->lc);
307 		BC_ASSERT_TRUE(wait_for_until(marie->lc,marie->lc,&marie->stat.number_of_LinphoneCoreLogCollectionUploadStateDelivered,1, 10000));
308 
309 		/*try 2 times*/
310 		waiting=100;
311 		linphone_core_reset_log_collection();
312 		while (--waiting) ms_error("(test error)Waiting %d...", waiting);
313 		ms_free(linphone_core_compress_log_collection());
314 		linphone_core_upload_log_collection(marie->lc);
315 		BC_ASSERT_TRUE(wait_for_until(marie->lc,marie->lc,&marie->stat.number_of_LinphoneCoreLogCollectionUploadStateDelivered,2, 10000));
316 		collect_cleanup(marie);
317 	}
318 }
319 
320 test_t log_collection_tests[] = {
321 	TEST_NO_TAG("No file when disabled", collect_files_disabled),
322 	TEST_NO_TAG("Collect files filled when enabled", collect_files_filled),
323 	TEST_NO_TAG("Logs collected into small file", collect_files_small_size),
324 	TEST_NO_TAG("Logs collected when decreasing max size", collect_files_changing_size),
325 	TEST_NO_TAG("Upload collected traces", upload_collected_traces)
326 };
327 
328 test_suite_t log_collection_test_suite = {"LogCollection", NULL, NULL, liblinphone_tester_before_each, liblinphone_tester_after_each,
329 										  sizeof(log_collection_tests) / sizeof(log_collection_tests[0]),
330 										  log_collection_tests};
331