xref: /illumos-gate/usr/src/cmd/picl/plugins/sun4u/lw2plus/fcal_leds/fc_led_parse.c (revision b210e77709da8e42dfe621e10ccf4be504206058)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright (c) 2001 by Sun Microsystems, Inc.
24  * All rights reserved.
25  */
26 
27 #include <stdio.h>
28 #include <unistd.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <ctype.h>
32 #include <errno.h>
33 #include <libintl.h>
34 #include <syslog.h>
35 #include "fcal_leds.h"
36 
37 /*
38  * function templates for static functions
39  */
40 static token_t get_token(char **pptr, int lineNo, actfun_t *fun);
41 static int get_cstr(str *p_str, cstr *p_cstr_res);
42 static int get_assert(str *p_str, int *assert);
43 static int get_pnz(str *p_str, int *pnz);
44 static int get_mask(str *p_str, int n_disks, int *p_intarray);
45 
46 /*
47  * Templates for functions which may be returned by get_token().
48  * These functions are all called with a pointer to the position just
49  * beyond the token being actioned.
50  */
51 static int act_version(str *p_str, led_dtls_t *dtls);
52 static int act_leds_board(str *p_str, led_dtls_t *dtls);
53 static int act_status_board(str *p_str, led_dtls_t *dtls);
54 static int act_disk_driver(str *p_str, led_dtls_t *dtls);
55 static int act_n_disks(str *p_str, led_dtls_t *dtls);
56 static int act_asrt_pres(str *p_str, led_dtls_t *dtls);
57 static int act_asrt_fault(str *p_str, led_dtls_t *dtls);
58 static int act_led_on(str *p_str, led_dtls_t *dtls);
59 static int act_disk_present(str *p_str, led_dtls_t *dtls);
60 static int act_disk_fault(str *p_str, led_dtls_t *dtls);
61 static int act_led_id(str *p_str, led_dtls_t *dtls);
62 static int act_slow_poll(str *p_str, led_dtls_t *dtls);
63 static int act_fast_poll(str *p_str, led_dtls_t *dtls);
64 static int act_relax_interval(str *p_str, led_dtls_t *dtls);
65 static int act_test_interval(str *p_str, led_dtls_t *dtls);
66 static int act_disk_parent(str *p_str, led_dtls_t *dtls);
67 static int act_unit_parent(str *p_str, led_dtls_t *dtls);
68 static int act_led_nodes(str *p_str, led_dtls_t *dtls);
69 
70 /*
71  * The table below is used to lookup .conf file keywords to yield either
72  * a corresponding enum or a function to process the keyword.
73  */
74 static lookup_t table[] = {
75 	{ FCAL_VERSION,		"VERSION", 		act_version	},
76 	{ FCAL_REMOK_LED,	"REMOK",		NULL		},
77 	{ FCAL_FAULT_LED,	"FAULT",		NULL		},
78 	{ FCAL_READY_LED,	"READY",		NULL		},
79 	{ FCAL_LEDS_BOARD,	"FCAL-LEDS",		act_leds_board	},
80 	{ FCAL_STATUS_BOARD,	"FCAL-STATUS",		act_status_board },
81 	{ FCAL_DISK_DRIVER,	"FCAL-DISK-DRIVER",	act_disk_driver },
82 	{ FCAL_N_DISKS,		"N-DISKS",		act_n_disks	},
83 	{ FCAL_ASSERT_PRESENT,	"ASSERT-PRESENT",	act_asrt_pres	},
84 	{ FCAL_ASSERT_FAULT,	"ASSERT-FAULT",		act_asrt_fault	},
85 	{ FCAL_LED_ON,		"LED-ON",		act_led_on	},
86 	{ FCAL_DISK_PRESENT,	"DISK-PRESENT",		act_disk_present },
87 	{ FCAL_DISK_FAULT,	"DISK-FAULT",		act_disk_fault	},
88 	{ FCAL_LED_ID,		"LED",			act_led_id	},
89 	{ FCAL_SLOW_POLL,	"SLOW-POLL",		act_slow_poll	},
90 	{ FCAL_FAST_POLL,	"FAST-POLL",		act_fast_poll	},
91 	{ FCAL_RELAX_INTERVAL,	"RELAX-INTERVAL",	act_relax_interval },
92 	{ FCAL_TEST_INTERVAL,	"LED-TEST-INTERVAL",	act_test_interval },
93 	{ FCAL_DISK_PARENT,	"FCAL-DISK-PARENT",	act_disk_parent	},
94 	{ FCAL_UNIT_PARENT,	"DISK-UNIT-PARENT",	act_unit_parent	},
95 	{ FCAL_LED_NODES,	"DISK-LED-NODES",	act_led_nodes	}
96 };
97 
98 /*
99  * length of longest string in table (with space for null terminator)
100  */
101 #define	MAX_FCAL_TOKEN_LEN	18
102 
103 static const int tab_len = (sizeof (table))/sizeof (table[0]);
104 
105 /*
106  * get_token
107  * Parses the current line of data and returns the next token.
108  * If there are no significant characters in the line, NO_TOKEN is returned.
109  * If a syntax error is encountered, TOKEN_ERROR is returned.
110  * Pointer to position in current line is updated to point to the terminator
111  * of the token, unless TOKEN_ERROR is returned.
112  */
113 static token_t
114 get_token(
115 	char **pptr,	/* pointer to pointer to position in current line */
116 			/* *ptr is updated by the function */
117 	int lineNo,	/* current line number, used for syslog. If set to */
118 			/* zero, syslogging is supressed */
119 	actfun_t *fun)	/* pointer to function variable to receive action */
120 			/* pointer for the token found. NULL may be returned */
121 {
122 	char		*ptr;
123 	char		*token_start;
124 	int		toklen;
125 	int		i;
126 	int		ch;
127 
128 	*fun = NULL;
129 	ptr = *pptr;
130 
131 	/* strip leading white space */
132 	do {
133 		ch = (unsigned)(*ptr++);
134 
135 	} while (isspace(ch));
136 
137 	if ((ch == '\0') || (ch == '#')) {
138 		*pptr = ptr;
139 		return (NO_TOKEN);	/* empty line or comment */
140 	}
141 
142 	if (!isalpha(ch)) {
143 		if (lineNo != 0)
144 			SYSLOG(LOG_ERR, EM_NONALF_TOK, lineNo);
145 		return (TOKEN_ERROR);
146 	}
147 	token_start = ptr - 1;
148 	toklen = strcspn(token_start, ",: \t");
149 	*pptr = token_start + toklen;
150 	/*
151 	 * got token, now look it up
152 	 */
153 	for (i = 0; i < tab_len; i++) {
154 		if ((strncasecmp(token_start, table[i].tok_str,
155 		    toklen) == 0) && (table[i].tok_str[toklen] == '\0')) {
156 			*fun = table[i].action;
157 			return (table[i].tok);
158 		}
159 	}
160 	if (lineNo != 0)
161 		SYSLOG(LOG_ERR, EM_UNKN_TOK, lineNo);
162 	return (TOKEN_ERROR);
163 }
164 
165 static int
166 act_version(str *p_str, led_dtls_t *dtls)
167 {
168 	dtls->ver_maj = strtoul(*p_str, p_str, 0);
169 	if (*(*p_str)++ != '.') {
170 		SYSLOG(LOG_ERR, EM_VER_FRMT);
171 		return (-1);
172 	}
173 	dtls->ver_min = strtoul(*p_str, p_str, 0);
174 	if ((**p_str != '\0') && !isspace(**p_str)) {
175 		SYSLOG(LOG_ERR, EM_VER_FRMT);
176 		return (-1);
177 	}
178 	if ((dtls->ver_maj != 1) || (dtls->ver_min != 0)) {
179 		SYSLOG(LOG_ERR, EM_WRNGVER, dtls->ver_maj, dtls->ver_min);
180 		return (-1);
181 	}
182 	return (0);
183 }
184 
185 /*
186  * get space to hold white-space terminated string at *p_str
187  * advance *p_str to point to terminator
188  * return copy of string, null terminated
189  */
190 static int
191 get_cstr(str *p_str, cstr *p_cstr_res)
192 {
193 	int ch;
194 	int len;
195 	char *ptr;
196 
197 	while (isspace(**p_str))
198 		(*p_str)++;
199 	ptr = *p_str;
200 
201 	do {
202 		ch = *++ptr;
203 	} while ((ch != '\0') && (!isspace(ch)));
204 
205 	len = ptr - *p_str;
206 	if (*p_cstr_res != NULL)
207 		free((void *)(*p_cstr_res));
208 	ptr = malloc(len + 1);
209 	*p_cstr_res = ptr;
210 	if (ptr == NULL) {
211 		return (ENOMEM);
212 	}
213 	(void) memcpy(ptr, *p_str, len);
214 	ptr[len] = '\0';
215 	(*p_str) += len;
216 	return (0);
217 }
218 
219 static int
220 act_leds_board(str *p_str, led_dtls_t *dtls)
221 {
222 	int res = get_cstr(p_str, &dtls->fcal_leds);
223 	if (res == 0) {
224 		if (dtls->fcal_leds[0] != '/') {
225 			free((void *)dtls->fcal_leds);
226 			dtls->fcal_leds = NULL;
227 			SYSLOG(LOG_ERR, EM_REL_PATH);
228 			return (-1);
229 		}
230 	}
231 	return (res);
232 }
233 
234 static int
235 act_status_board(str *p_str, led_dtls_t *dtls)
236 {
237 	int res = get_cstr(p_str, &dtls->fcal_status);
238 	if (res == 0) {
239 		if (dtls->fcal_status[0] != '/') {
240 			free((void *)dtls->fcal_status);
241 			dtls->fcal_status = NULL;
242 			SYSLOG(LOG_ERR, EM_REL_PATH);
243 			return (-1);
244 		}
245 	}
246 	return (res);
247 }
248 
249 static int
250 act_disk_driver(str *p_str, led_dtls_t *dtls)
251 {
252 	return (get_cstr(p_str, &dtls->fcal_driver));
253 }
254 
255 static int
256 act_disk_parent(str *p_str, led_dtls_t *dtls)
257 {
258 	return (get_cstr(p_str, &dtls->fcal_disk_parent));
259 }
260 
261 static int
262 act_unit_parent(str *p_str, led_dtls_t *dtls)
263 {
264 	return (get_cstr(p_str, &dtls->disk_unit_parent));
265 }
266 
267 static int
268 act_led_nodes(str *p_str, led_dtls_t *dtls)
269 {
270 	return (get_cstr(p_str, &dtls->disk_led_nodes));
271 }
272 
273 /*
274  * A number of fields in the led_dtls_t structure have per-disk copies.
275  * This action routine creates the space for all such fields.
276  * Following any failure, an error is returned and the calling routine
277  * must handle the fact that only a subset of these fields are populated.
278  * In practice, this function is only called by get_token() on behalf of
279  * fc_led_parse(). fc_led_parse calls free_led_dtls() after any error.
280  */
281 static int
282 act_n_disks(str *p_str, led_dtls_t *dtls)
283 {
284 	int i;
285 
286 	if (dtls->n_disks != 0) {
287 		SYSLOG(LOG_ERR, EM_NDISKS_DBL);
288 		return (-1);
289 	}
290 	dtls->n_disks = strtoul(*p_str, p_str, 0);
291 	if ((**p_str != '\0') && !isspace(**p_str)) {
292 		SYSLOG(LOG_ERR, EM_NUM_TERM);
293 		return (-1);
294 	}
295 	if (dtls->n_disks < 1) {
296 		SYSLOG(LOG_ERR, EM_NO_DISKS);
297 		return (-1);
298 	}
299 	dtls->presence = calloc(dtls->n_disks, sizeof (int));
300 	if (dtls->presence == NULL)
301 		return (ENOMEM);
302 	dtls->faults = calloc(dtls->n_disks, sizeof (int));
303 	if (dtls->faults == NULL)
304 		return (ENOMEM);
305 	dtls->disk_detected = calloc(dtls->n_disks, sizeof (int));
306 	if (dtls->disk_detected == NULL)
307 		return (ENOMEM);
308 	dtls->disk_ready = calloc(dtls->n_disks, sizeof (int));
309 	if (dtls->disk_ready == NULL)
310 		return (ENOMEM);
311 	dtls->disk_prev = calloc(dtls->n_disks, sizeof (int));
312 	if (dtls->disk_prev == NULL)
313 		return (ENOMEM);
314 	dtls->led_test_end = calloc(dtls->n_disks, sizeof (int));
315 	if (dtls->led_test_end == NULL)
316 		return (ENOMEM);
317 	dtls->picl_retry = calloc(dtls->n_disks, sizeof (boolean_t));
318 	if (dtls->picl_retry == NULL)
319 		return (ENOMEM);
320 	dtls->disk_port = calloc(dtls->n_disks, sizeof (char *));
321 	if (dtls->disk_port == NULL) {
322 		return (ENOMEM);
323 	}
324 	for (i = 0; i < FCAL_LED_CNT; i++) {
325 		dtls->led_addr[i] = calloc(dtls->n_disks, sizeof (int));
326 		if (dtls->led_addr[i] == NULL)
327 			return (ENOMEM);
328 		dtls->led_state[i] = calloc(dtls->n_disks,
329 		    sizeof (led_state_t));
330 		if (dtls->led_state[i] == NULL)
331 			return (ENOMEM);
332 	}
333 	return (0);
334 }
335 
336 static int
337 get_assert(str *p_str, int *assert)
338 {
339 	int i = strtoul(*p_str, p_str, 0);
340 	if ((**p_str != '\0') && !isspace(**p_str)) {
341 		SYSLOG(LOG_ERR, EM_NUM_TERM);
342 		return (-1);
343 	}
344 	if ((i != 0) && (i != 1)) {
345 		SYSLOG(LOG_ERR, EM_LOGIC_LVL);
346 		return (-1);
347 	}
348 	*assert = i;
349 	return (0);
350 }
351 
352 static int
353 get_pnz(str *p_str, int *pnz)
354 {
355 	int i = strtoul(*p_str, p_str, 0);
356 	if ((**p_str != '\0') && !isspace(**p_str)) {
357 		SYSLOG(LOG_ERR, EM_NUM_TERM);
358 		return (-1);
359 	}
360 	if (i < 1) {
361 		SYSLOG(LOG_ERR, EM_NOTPOS);
362 		return (-1);
363 	}
364 	*pnz = i;
365 	return (0);
366 }
367 
368 static int
369 act_asrt_pres(str *p_str, led_dtls_t *dtls)
370 {
371 	return (get_assert(p_str, &dtls->assert_presence));
372 }
373 
374 static int
375 act_asrt_fault(str *p_str, led_dtls_t *dtls)
376 {
377 	return (get_assert(p_str, &dtls->assert_fault));
378 }
379 
380 static int
381 act_led_on(str *p_str, led_dtls_t *dtls)
382 {
383 	return (get_assert(p_str, &dtls->assert_led_on));
384 }
385 
386 static int
387 get_mask(str *p_str, int n_disks, int *p_intarray)
388 {
389 	int i;
390 	int j = strtoul(*p_str, p_str, 0);
391 	if (*(*p_str)++ != ',') {
392 		SYSLOG(LOG_ERR, EM_NUM_TERM);
393 		return (-1);
394 	}
395 	if ((j < 0) || (j > n_disks)) {
396 		SYSLOG(LOG_ERR, EM_DISK_RANGE);
397 		return (-1);
398 	}
399 	i = strtoul(*p_str, p_str, 0);
400 	if ((**p_str != '\0') && !isspace(**p_str)) {
401 		SYSLOG(LOG_ERR, EM_NUM_TERM);
402 		return (-1);
403 	}
404 	p_intarray[j] = i;
405 	return (0);
406 }
407 
408 static int
409 act_disk_present(str *p_str, led_dtls_t *dtls)
410 {
411 	return (get_mask(p_str, dtls->n_disks, dtls->presence));
412 }
413 
414 static int
415 act_disk_fault(str *p_str, led_dtls_t *dtls)
416 {
417 	return (get_mask(p_str, dtls->n_disks, dtls->faults));
418 }
419 
420 static int
421 act_led_id(str *p_str, led_dtls_t *dtls)
422 {
423 	token_t		tok;
424 	actfun_t	action;
425 	int		i;
426 	int		j = strtoul(*p_str, p_str, 0);
427 
428 	if (*(*p_str)++ != ',') {
429 		SYSLOG(LOG_ERR, EM_NUM_TERM);
430 		return (-1);
431 	}
432 	if ((j < 0) || (j >= dtls->n_disks)) {
433 		SYSLOG(LOG_ERR, EM_DISK_RANGE);
434 		return (-1);
435 	}
436 	tok = get_token(p_str, 0, &action);
437 	if ((tok <= LED_PROPS_START) || (tok >= LED_PROPS_END)) {
438 		SYSLOG(LOG_ERR, EM_NO_LED_PROP);
439 		return (-1);
440 	}
441 	if (*(*p_str)++ != ',') {
442 		SYSLOG(LOG_ERR, EM_PROP_TERM);
443 		return (-1);
444 	}
445 	i = strtoul(*p_str, p_str, 0);
446 	if ((**p_str != '\0') && !isspace(**p_str)) {
447 		SYSLOG(LOG_ERR, EM_NUM_TERM);
448 		return (-1);
449 	}
450 	dtls->led_addr[tok - FCAL_REMOK_LED][j] = i;
451 	return (0);
452 }
453 
454 static int
455 act_slow_poll(str *p_str, led_dtls_t *dtls)
456 {
457 	return (get_pnz(p_str, &dtls->slow_poll_ticks));
458 }
459 
460 static int
461 act_fast_poll(str *p_str, led_dtls_t *dtls)
462 {
463 	return (get_pnz(p_str, &dtls->fast_poll));
464 }
465 
466 static int
467 act_relax_interval(str *p_str, led_dtls_t *dtls)
468 {
469 	return (get_pnz(p_str, &dtls->relax_time_ticks));
470 }
471 
472 static int
473 act_test_interval(str *p_str, led_dtls_t *dtls)
474 {
475 	return (get_pnz(p_str, &dtls->led_test_time));
476 }
477 
478 /*
479  * Create a led_dtls_t structure
480  * Parse configuration file and populate the led_dtls_t
481  * In the event of an error, free the structure and return an error
482  */
483 int
484 fc_led_parse(FILE *fp, led_dtls_t **p_dtls)
485 {
486 	int		lineNo = 0;
487 	int		err = 0;
488 	char		linebuf[160];
489 	char		*ptr;
490 	led_dtls_t	*dtls = calloc(1, sizeof (led_dtls_t));
491 	actfun_t	action;
492 	token_t		tok;
493 
494 	*p_dtls = dtls;
495 	if (dtls == NULL) {
496 		return (ENOMEM);
497 	}
498 	dtls->ver_min = -1;	/* mark as version unknown */
499 
500 	while ((ptr = fgets(linebuf, sizeof (linebuf), fp)) != NULL) {
501 		lineNo++;
502 		tok = get_token(&ptr, lineNo, &action);
503 		if (tok == NO_TOKEN)
504 			continue;
505 		if (tok == TOKEN_ERROR) {
506 			err = -1;
507 			break;
508 		}
509 		if (tok == FCAL_VERSION) {
510 			if ((err = (*action)(&ptr, dtls)) != 0)
511 				break;
512 			else
513 				continue;
514 		}
515 		if (dtls->ver_min < 0) {
516 			SYSLOG(LOG_ERR, EM_NOVERS);
517 			err = -1;
518 			break;
519 		}
520 		if (tok <= LINE_DEFS) {
521 			SYSLOG(LOG_ERR, EM_INVAL_TOK, lineNo);
522 			err = -1;
523 			break;
524 		}
525 		if (*ptr++ != ':') {
526 			SYSLOG(LOG_ERR, EM_NOCOLON, lineNo);
527 			err = -1;
528 			break;
529 		}
530 		if ((err = (*action)(&ptr, dtls)) != 0) {
531 			SYSLOG(LOG_ERR, EM_ERRLINE, lineNo);
532 			break;
533 		}
534 		else
535 			continue;
536 	}
537 
538 	if (err == 0) {
539 		err = -1;	/* just in case */
540 		if (dtls->ver_min < 0) {
541 			SYSLOG(LOG_ERR, EM_NOVERS);
542 		} else if (dtls->n_disks == 0) {
543 			SYSLOG(LOG_ERR, EM_NO_DISKS);
544 		} else if (dtls->fcal_leds == NULL) {
545 			SYSLOG(LOG_ERR, EM_STR_NOT_SET, "fcal-leds");
546 		} else if (dtls->fcal_status == NULL) {
547 			SYSLOG(LOG_ERR, EM_STR_NOT_SET, "fcal-status");
548 		} else if (dtls->fcal_driver == NULL) {
549 			SYSLOG(LOG_ERR, EM_STR_NOT_SET, "fcal-driver");
550 		} else
551 			err = 0;
552 	}
553 
554 	if (err != 0) {
555 		/*
556 		 * clean up after error detected
557 		 */
558 		free_led_dtls(dtls);
559 		*p_dtls = NULL;
560 		return (err);
561 	}
562 
563 	/*
564 	 * set any unset timers to default time
565 	 */
566 	if (dtls->slow_poll_ticks == 0)
567 		dtls->slow_poll_ticks = DFLT_SLOW_POLL;
568 	if (dtls->fast_poll == 0)
569 		dtls->fast_poll = DFLT_FAST_POLL;
570 	if (dtls->relax_time_ticks == 0)
571 		dtls->relax_time_ticks = DFLT_RELAX_TIME;
572 	if (dtls->led_test_time == 0)
573 		dtls->led_test_time = DFLT_TEST_TIME;
574 
575 	/*
576 	 * set polling flag to avoid a start-up glitch
577 	 * it will be cleared again if the poll thread fails
578 	 */
579 	dtls->polling = B_TRUE;
580 
581 	/*
582 	 * convert derived timers to multiples of fast poll time
583 	 */
584 	dtls->slow_poll_ticks += dtls->fast_poll - 1;	/* for round up */
585 	dtls->slow_poll_ticks /= dtls->fast_poll;
586 	dtls->relax_time_ticks += dtls->fast_poll - 1;
587 	dtls->relax_time_ticks /= dtls->fast_poll;
588 	dtls->led_test_time += dtls->fast_poll - 1;
589 	dtls->led_test_time /= dtls->fast_poll;
590 	return (0);
591 }
592 
593 void
594 free_led_dtls(led_dtls_t *dtls)
595 {
596 	int	i;
597 
598 	if (dtls == NULL)
599 		return;
600 	if (dtls->fcal_leds != NULL)
601 		free((void *)dtls->fcal_leds);
602 	if (dtls->fcal_status != NULL)
603 		free((void *)dtls->fcal_status);
604 	if (dtls->fcal_driver != NULL)
605 		free((void *)dtls->fcal_driver);
606 	if (dtls->presence != NULL)
607 		free((void *)dtls->presence);
608 	if (dtls->faults != NULL)
609 		free((void *)dtls->faults);
610 	if (dtls->disk_detected != NULL)
611 		free((void *)dtls->disk_detected);
612 	if (dtls->disk_ready != NULL)
613 		free((void *)dtls->disk_ready);
614 	if (dtls->disk_prev != NULL)
615 		free((void *)dtls->disk_prev);
616 	if (dtls->led_test_end != NULL)
617 		free((void *)dtls->led_test_end);
618 	if (dtls->picl_retry != NULL)
619 		free((void *)dtls->picl_retry);
620 	if (dtls->disk_port != NULL) {
621 		for (i = 0; i < dtls->n_disks; i++) {
622 			if (dtls->disk_port[i] != NULL)
623 				free(dtls->disk_port[i]);
624 		}
625 		free(dtls->disk_port);
626 	}
627 	for (i = 0; i < FCAL_LED_CNT; i++) {
628 		if (dtls->led_addr[i] != NULL)
629 			free((void *)dtls->led_addr[i]);
630 		if (dtls->led_state[i] != NULL)
631 			free((void *)dtls->led_state[i]);
632 	}
633 
634 	free(dtls);
635 }
636