A messenger application for Raspberry Pi Zerofor A.U.TH (Real time Embedded systems).
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 
 

700 linhas
20 KiB

  1. /*!
  2. * \file core.c
  3. * Core messenger functionality
  4. * \author Christos Choutouridis AEM:8997 <cchoutou@ece.auth.gr>
  5. */
  6. #include <stdio.h>
  7. #include <stdlib.h>
  8. #include <string.h>
  9. #include <stdarg.h>
  10. #include <pthread.h>
  11. #include "core.h"
  12. //! Global data
  13. //! @{
  14. msgList_t msgList; //!< The message list for our application.
  15. //! @}
  16. /*
  17. * Local data types
  18. */
  19. static pthread_mutex_t lock_msgList; //!< mutex for msgList locking
  20. static pthread_mutex_t lock_devList; //!< mutex for devList locking
  21. static pthread_mutex_t lock_stderr; //!< mutex for stderr locking
  22. static pthread_mutex_t lock_stdout; //!< mutex for stderr locking
  23. static pthread_mutex_t lock_stats; //!< mutex for stats locking
  24. //! Helper API
  25. //! @{
  26. //! Macro to create a in_addr_t
  27. #define _ADHOC_SUBNET(A, B, C, D) (((A)<<24) | ((B)<<16) | ((C)<<8) | (D))
  28. /*!
  29. * in_addr_t to devAEM_t conversion utility function
  30. * @param in_addr Internet address (host byte order)
  31. * @return devAEM_t representation of the address
  32. * @note
  33. * for example:
  34. * @code
  35. * devAEM_t dev = addr2devAEM (ntohl(clntAddr.sin_addr.s_addr));
  36. * @endcode
  37. */
  38. devAEM_t addr2devAEM (in_addr_t in_addr) {
  39. return (in_addr & 0x000000FF) + ((in_addr >> 8) & 0x000000FF) * 100;
  40. }
  41. /*!
  42. * devAEM_t to in_addr_t conversion utility function
  43. * @param dev devAEM_t address
  44. * @return Internet address (host byte order)
  45. * @note
  46. * for example:
  47. * @code
  48. * sockaddr_in_t srvAddr;
  49. * srvAddr.sin_addr.s_addr = htonl (devAEM2addr (dev));
  50. * @endcode
  51. */
  52. in_addr_t devAEM2addr (devAEM_t dev) {
  53. uint32_t add = _ADHOC_SUBNET (ADHOC_NET_A, ADHOC_NET_B, ADHOC_NET_C, ADHOC_NET_D);
  54. add |= (dev % 100) & 0x000000FF;
  55. add |= ((dev / 100) & 0x000000FF) << 8;
  56. return add;
  57. }
  58. /*!
  59. * DevIP_t to devAEM_t conversion utility function
  60. * @param ip pointer to devIP_t type address
  61. * @return devAEM_t representation of the address
  62. * @note
  63. * We discard the first 2 fields
  64. */
  65. devAEM_t ip2AEM (devIP_t* ip) {
  66. return ip->C*100 + ip->D;
  67. }
  68. /*!
  69. * devAEM_t to DevIP_t conversion utility function
  70. * @param dev devAEM_t representation of the address
  71. * @return devIP_t type address
  72. */
  73. devIP_t AEM2ip (devAEM_t dev) {
  74. devIP_t ip = {
  75. .A =ADHOC_NET_A, .B=ADHOC_NET_B, .C=dev/100, .D=dev%100
  76. };
  77. return ip;
  78. }
  79. //! @}
  80. /*
  81. * Log related local data
  82. */
  83. static char_t* _frm_msg_in = "In from dev=%d, message: from=%d, to=%d, timestamp=%lld, text=%s\n";
  84. static char_t* _frm_msg_out = "Out to dev=%d, message: from=%d, to=%d, timestamp=%lld, text=%s\n";
  85. static char_t* _frm_msg_new = "New message: from=%d, to=%d, timestamp=%lld, text=%s\n";
  86. //! log API
  87. //! @{
  88. #define _HEAD_SIZE 25
  89. /*!
  90. * Initialize log functionality
  91. * @return The status of the operation
  92. */
  93. status_t log_init (void) {
  94. // Try to initialize pthreads for logging
  95. if (pthread_mutex_init(&lock_stderr, NULL) != 0) {
  96. fprintf (stderr, "Error %s: mutex init has failed\n", __FUNCTION__ );
  97. return MSG_ERROR;
  98. }
  99. if (pthread_mutex_init(&lock_stdout, NULL) != 0) {
  100. fprintf (stderr, "Error %s: mutex init has failed\n", __FUNCTION__ );
  101. return MSG_ERROR;
  102. }
  103. return MSG_OK;
  104. }
  105. /*!
  106. * Logs incoming messages
  107. * @param msg Pointer to msg to log
  108. * @note
  109. * This function depends on settings.outLevel
  110. */
  111. void log_msg_in (msg_t* msg) {
  112. if (settings.outLevel >= OUTLEVEL_1) {
  113. pthread_mutex_lock(&lock_stdout); // lock stdout
  114. fprintf (stdout, _frm_msg_in, // print and flush
  115. msg->sender,
  116. msg->cMsg.from,
  117. msg->cMsg.to,
  118. msg->cMsg.ts,
  119. msg->cMsg.text
  120. );
  121. fflush(stdout);
  122. pthread_mutex_unlock(&lock_stdout); // unlock stdout
  123. }
  124. }
  125. /*!
  126. * Logs outgoing messages
  127. * @param msg Pointer to msg to log
  128. * @param dev device that accepts the message
  129. * @note
  130. * This function depends on settings.outLevel
  131. */
  132. void log_msg_out (msg_t* msg, devAEM_t dev) {
  133. if (settings.outLevel >= OUTLEVEL_1) {
  134. pthread_mutex_lock(&lock_stdout); // lock stdout
  135. fprintf (stdout, _frm_msg_out, // print and flush
  136. dev,
  137. msg->cMsg.from,
  138. msg->cMsg.to,
  139. msg->cMsg.ts,
  140. msg->cMsg.text
  141. );
  142. fflush(stdout);
  143. pthread_mutex_unlock(&lock_stdout); // unlock stdout
  144. }
  145. }
  146. /*!
  147. * Logs new messages
  148. * @param msg Pointer to msg to log
  149. * @note
  150. * This function depends on settings.outLevel
  151. */
  152. void log_msg_new (msg_t* msg) {
  153. if (settings.outLevel >= OUTLEVEL_1) {
  154. pthread_mutex_lock(&lock_stdout); // lock stdout
  155. fprintf (stdout, _frm_msg_new, // print and flush
  156. msg->cMsg.from,
  157. msg->cMsg.to,
  158. msg->cMsg.ts,
  159. msg->cMsg.text
  160. );
  161. fflush(stdout);
  162. pthread_mutex_unlock(&lock_stdout); // unlock stdout
  163. }
  164. }
  165. /*!
  166. * Debug message log to stdout
  167. * Variadic formating print
  168. * @param fmt Pointer to printf like format string
  169. */
  170. void log_debug (const char *fmt, ...) {
  171. if (settings.outLevel >= OUTLEVEL_2) {
  172. va_list ap;
  173. va_start(ap, fmt);
  174. pthread_mutex_lock(&lock_stdout);
  175. vfprintf (stdout, fmt, ap);
  176. fflush(stdout);
  177. pthread_mutex_unlock(&lock_stdout);
  178. va_end(ap);
  179. }
  180. }
  181. /*!
  182. * Debug error message log to stderr
  183. * Variadic formating print
  184. * @param fmt Pointer to printf like format string
  185. */
  186. void log_error (const char *fmt, ...) {
  187. va_list ap;
  188. va_start(ap, fmt);
  189. pthread_mutex_lock(&lock_stderr);
  190. vfprintf (stderr, fmt, ap);
  191. fflush(stderr);
  192. pthread_mutex_unlock(&lock_stderr);
  193. va_end(ap);
  194. }
  195. //! @}
  196. //! cMsg_t API
  197. //! @{
  198. /*!
  199. * Make a new message
  200. * @param msg Pointer to message to create
  201. */
  202. void cMsg_make (cMsg_t* msg) {
  203. static int msgID =0; // unique msg ID
  204. msg->from = settings.me; // from me
  205. do {
  206. // randomly select recipient device
  207. msg->to = devList[rand() % AEMLIST_SIZE].dev;
  208. } while (msg->to == settings.me);
  209. msg->ts = time(NULL);
  210. // stream the first fields and take the quote text iterator
  211. sprintf (msg->text, "%s #%d", MESSAGE_BODY, msgID++);
  212. }
  213. /*!
  214. * Message concatenation
  215. * This function synthesize the ascii message for transmission
  216. * @param msg Pointer to message to serialize
  217. * @param buffer Pointer to output buffer
  218. * @return The size of the resulting message
  219. */
  220. size_t cMsg_serialize (cMsg_t* msg, char_t* buffer) {
  221. return sprintf (buffer, "%d_%d_%lld_%s",
  222. msg->from,
  223. msg->to,
  224. msg->ts,
  225. msg->text
  226. );
  227. }
  228. /*!
  229. * Custom strtok functionality
  230. * @param str Pointer to string to parse
  231. * @param max The size of string
  232. * @param c Single delimiter character (non compatible with strtok())
  233. * @return Pointer to token (non compatible with strtok())
  234. * @note
  235. * This function alters the given string by adding null termination in the places
  236. * of delimiters
  237. */
  238. char_t* _strtok (char_t* str, size_t max, char_t c) {
  239. static char_t* last = NULL;
  240. char_t* ret = str;
  241. // init last
  242. if (str != NULL) last = str;
  243. // loop
  244. for (size_t i=0 ; i<max ; ++i) {
  245. if (*last == c) {
  246. *last++ = 0;
  247. return ret;
  248. }
  249. ++last;
  250. }
  251. return NULL;
  252. }
  253. /*!
  254. * Parse an incoming message
  255. *
  256. * @param cMsg Pointer to cMsg object to store the parsed data
  257. * @param rawMsg Pointer to raw message
  258. * @param size The size f raw message buffer
  259. * @return The status of the operation
  260. * @arg MSG_OK Success
  261. * @arg MSG_ERROR Parse failure, incoming message format error
  262. */
  263. status_t cMsg_parse (cMsg_t* cMsg, char_t* rawMsg, size_t size) {
  264. // Check message integrity
  265. if (size > MSG_TEXT_SIZE)
  266. return MSG_ERROR;
  267. // Parse message
  268. char_t* rest = rawMsg;
  269. char_t* tok[4];
  270. bool_t done = true;
  271. for (size_t i =0; i < 3; ++i) {
  272. tok[i] = _strtok (rest, size, MSG_DELIMITER);
  273. if (tok[i] == NULL) {
  274. done = false;
  275. break;
  276. }
  277. else {
  278. int l = strlen(rest);
  279. rest += l + 1;
  280. size -= l + 1;
  281. }
  282. }
  283. tok[3] = rest;
  284. if (done) {
  285. cMsg->from = atoi (tok[0]);
  286. cMsg->to = atoi (tok[1]);
  287. cMsg->ts = atoi (tok[2]);
  288. strcpy (cMsg->text, tok[3]);
  289. return MSG_OK;
  290. }
  291. return MSG_ERROR;
  292. }
  293. /*! getter for cMsg_t member fromAEM */
  294. uint32_t cMsg_getFrom(cMsg_t* cMsg) { return cMsg->from; }
  295. /*! getter for cMsg_t member toAEM */
  296. uint32_t cMsg_getTo(cMsg_t* cMsg) { return cMsg->to; }
  297. /*! getter for cMsg_t member fromAEM */
  298. uint64_t cMsg_getTs(cMsg_t* cMsg) { return cMsg->ts; }
  299. /*! getter for payload text member */
  300. char_t* cMsg_getText(cMsg_t* cMsg) { return cMsg->text; }
  301. /*!
  302. * Predicate to check core message equality
  303. * @param m1 Pointer to message 1
  304. * @param m2 Pointer to message 2
  305. * @return Equality result (true, false)
  306. */
  307. bool_t cMsg_equal (cMsg_t* m1, cMsg_t* m2) {
  308. if (m1->from != m2->from) return false;
  309. if (m1->to != m2->to) return false;
  310. if (m1->ts != m2->ts) return false;
  311. if (strncmp (m1->text, m2->text, strlen(m1->text)))
  312. return false;
  313. return true;
  314. }
  315. //! @}
  316. /*!
  317. * mgs_t API
  318. */
  319. //! @{
  320. /*!
  321. * message initialization
  322. * @param msg
  323. */
  324. void msg_init (msg_t* msg) {
  325. memset ((void*)msg, 0, sizeof(msg_t)); // init to 0
  326. }
  327. //! @}
  328. //! devList API
  329. //! @{
  330. /*!
  331. * Initialize the devList
  332. * @param msgList Pointer to mesList t initialize
  333. * @return The status of the operation
  334. */
  335. status_t devList_init (devList_t* devList) {
  336. devAEM_t l[] = AEMLIST;
  337. if (pthread_mutex_init(&lock_devList, NULL) != 0) {
  338. log_error ("Error: mutex init has failed\n");
  339. return MSG_ERROR;
  340. }
  341. memset ((void*)devList, 0, sizeof(devList_t));
  342. for (size_t i =0 ; i<AEMLIST_SIZE ; ++i)
  343. devList[i].dev = l[i];
  344. return MSG_OK;
  345. }
  346. /*!
  347. * Returns an iterator for devList AND msg_t.recipients
  348. * @param dev The device to search
  349. * @return The iterator, namely the index to devList array in which is the \p dev
  350. */
  351. dIter_t devList_getIter (devAEM_t dev) {
  352. for (dIter_t i =0 ; i<AEMLIST_SIZE ; ++i) {
  353. if (devList[i].dev == dev)
  354. return i;
  355. }
  356. return -1; // return end()
  357. }
  358. //! Acquires devList resources
  359. void devList_acquire (void) { pthread_mutex_lock(&lock_devList); }
  360. //! Releases devList resources
  361. void devList_release (void) { pthread_mutex_unlock(&lock_devList); }
  362. //! @}
  363. //! msgList API
  364. //! @{
  365. /*! Macro helper to saturate increased values */
  366. #define _top_saturate(test, apply, value) do { \
  367. if (test >= value) apply = value; \
  368. } while (0)
  369. /*! Macro helper to saturate decreased values */
  370. #define _btm_saturate(test, apply, value) do { \
  371. if (test < value) apply = value; \
  372. while (0)
  373. /*!
  374. * Initialize the msgList
  375. * @param msgList Pointer to mesList t initialize
  376. * @return The status of the operation
  377. */
  378. status_t msgList_init (msgList_t* msgList) {
  379. if (pthread_mutex_init(&lock_msgList, NULL) != 0) {
  380. log_error ("Error: mutex init has failed\n");
  381. return MSG_ERROR;
  382. }
  383. memset((void*)msgList, 0, sizeof(msgList_t));
  384. msgList->first =-1;
  385. msgList->last =-1;
  386. srand (time(NULL));
  387. return MSG_OK;
  388. }
  389. /*!
  390. * @brief msgList iterator pre-increment in the msg_t direction
  391. *
  392. * This iterator force a ring buffer behavior. This function takes pointer
  393. * to iterator to alter but return the altered value so it can be directly
  394. * used in expressions
  395. *
  396. * @param it Pointer to iterator to increase
  397. * @return The iterator values
  398. */
  399. mIter_t msgList_preInc (mIter_t* it) {
  400. if (++*it >= MSG_LIST_SIZE) *it = 0;
  401. return *it;
  402. }
  403. /*!
  404. * @brief msgList iterator pre-decrement in the msg_t direction
  405. *
  406. * This iterator force a ring buffer behavior. This function takes pointer
  407. * to iterator to alter but return the altered value so it can be directly
  408. * used in expressions
  409. *
  410. * @param it Pointer to iterator to decrease
  411. * @return The iterator values
  412. */
  413. mIter_t msgList_preDec (mIter_t* it) {
  414. if (--*it < 0) *it = MSG_LIST_SIZE-1;
  415. return *it;
  416. }
  417. /*!
  418. * @param this Pointer to msgList to use
  419. * @return An iterator to the first message of MSG_LIST_SIZE
  420. * of msgList.m[]
  421. * @note
  422. * As the msgList is a ring buffer we can not have a sensible end() iterator.
  423. * end() will eventually merge with begin() when the size reaches MSG_LIST_SIZE
  424. * For that reason in all of our loops through msgList we shall take a begin()
  425. * iterator and loop using size as sentinel for example:
  426. * @code
  427. * mIter_t it = msgList_begin (&msgList); // get a message iterator
  428. * size_t size= msgList_size(&msgList); // get current msgList size
  429. * for (size_t i=0 ; i<size ; ++i, msgList_preInc (&it)) { // don't forget to increase iterator
  430. * // use msgList[it]
  431. * }
  432. * @endcode
  433. */
  434. mIter_t msgList_begin (msgList_t* this) {
  435. return this->first;
  436. }
  437. /*!
  438. * @param this Pointer to msgList to use
  439. * @return An iterator to the last inserted message of MSG_LIST_SIZE
  440. * of msgList.m[]
  441. */
  442. mIter_t msgList_last (msgList_t* this) {
  443. return this->last;
  444. }
  445. /*!
  446. * @param this Pointer to msgList to use
  447. * @return The current used slots of msgList
  448. * @note
  449. * As the msgList is a ring buffer we can not have a sensible end() iterator.
  450. * end() will eventually merge with begin() when the size reaches MSG_LIST_SIZE
  451. * For that reason in all of our loops through msgList we shall take a begin()
  452. * iterator and loop using size as sentinel
  453. * @code
  454. * mIter_t it = msgList_begin (&msgList); // get a message iterator
  455. * size_t size= msgList_size(&msgList); // get current msgList size
  456. * for (size_t i=0 ; i<size ; ++i, msgList_preInc (&it)) { // don't forget to increase iterator
  457. * // use msgList[it]
  458. * }
  459. * @endcode
  460. */
  461. size_t msgList_size (msgList_t* this) {
  462. return this->size;
  463. }
  464. /*!
  465. * Searches for a message in the message list.
  466. *
  467. * @param this The msgList object to work with
  468. * @param msg Pointer to message to search
  469. * @return Iterator to message if found, or -1 if not
  470. */
  471. mIter_t msgList_find (msgList_t* this, msg_t* msg) {
  472. mIter_t it =this->last; // get iterator
  473. // search from end to start to find msg, return on success
  474. for (size_t i=0 ; i < this->size ; ++i) {
  475. if (cMsg_equal (&this->m[it].cMsg, &msg->cMsg))
  476. return it;
  477. msgList_preDec(&it);
  478. // We start from the end as we think, its more possible
  479. // to find msg in the recent messages.
  480. }
  481. return (mIter_t)-1; // fail to find
  482. }
  483. /*!
  484. * Add a new message in the message list
  485. *
  486. * @param this The msgList object to work with
  487. * @param msg Pointer to message
  488. * @return Iterator to the added item (last)
  489. */
  490. mIter_t msgList_add (msgList_t* this, msg_t* msg) {
  491. if (this->first == -1) // if its first time init "first" iterator
  492. this->first = 0;
  493. this->m[msgList_preInc(&this->last)] = *msg; // store data *++it = *msg;
  494. _top_saturate(++this->size, this->size, MSG_LIST_SIZE); // count the items with saturation
  495. // if we reacher full capacity, move along first also
  496. if ((this->first == this->last) && (this->size > 1))
  497. msgList_preInc(&this->first);
  498. return this->last; // return the iterator to newly created slot
  499. }
  500. //! Acquires msgList resources
  501. void msgList_acquire () { pthread_mutex_lock (&lock_msgList); }
  502. //! releases msgList resources
  503. void msgList_release () { pthread_mutex_unlock (&lock_msgList); }
  504. //! @}
  505. //! Statistics API
  506. //! @{
  507. /*!
  508. * Initialize statistics
  509. * @param s Pointer to stat_t struct to initialize
  510. * @return The status of the operation
  511. */
  512. status_t stats_init (stats_t* s) {
  513. memset ((void*)s, 0, sizeof (stats_t));
  514. if (pthread_mutex_init(&lock_stats, NULL) != 0) {
  515. log_error ("Error: mutex init has failed\n");
  516. return MSG_ERROR;
  517. }
  518. return MSG_OK;
  519. }
  520. /*!
  521. * Update statistics for newly created messages
  522. * @param msg Pointer to msg
  523. */
  524. void statsUpdateCreate (msg_t* msg) {
  525. pthread_mutex_lock (&lock_stats);
  526. ++stats.totalMsg;
  527. ++stats.myMsg;
  528. // average message size
  529. int32_t saved = stats.totalMsg - stats.duplicateMsg;
  530. if ((saved-1) > 0) {
  531. // Append to average
  532. int32_t l = strlen(msg->cMsg.text);
  533. stats.avMsgSize += l / (fpdata_t)(saved -1);
  534. stats.avMsgSize *= (fpdata_t)(saved-1)/saved;
  535. }
  536. pthread_mutex_unlock (&lock_stats);
  537. }
  538. /*!
  539. * Update statistics for incoming message
  540. * @param msg Pointer to incoming message
  541. * @param dup Flag to indicate if the message was duplicate
  542. */
  543. void statsUpdateIn (msg_t* msg, bool_t dup) {
  544. pthread_mutex_lock (&lock_stats);
  545. bool_t forMe = msg->cMsg.to == settings.me;
  546. stats.totalMsg++;
  547. stats.duplicateMsg += (dup) ? 1:0;
  548. stats.forMeMsg += (forMe) ? 1:0;
  549. stats.inDirectMsg += (forMe && (msg->cMsg.from == msg->sender)) ? 1:0;
  550. // averages
  551. int32_t saved = stats.totalMsg - stats.duplicateMsg;
  552. if ((saved-1) > 0) {
  553. // Append to message size average
  554. int32_t l = strlen(msg->cMsg.text);
  555. stats.avMsgSize += l / (fpdata_t)(saved -1);
  556. stats.avMsgSize *= (fpdata_t)(saved-1)/saved;
  557. if (settings.trackTime) {
  558. // append to time to me average
  559. tstamp_t dt = (tstamp_t)time(NULL) - msg->cMsg.ts;
  560. if (dt < 0)
  561. dt = 0;
  562. stats.avTimeToMe += dt / (fpdata_t)(saved -1);
  563. stats.avTimeToMe *= (fpdata_t)(saved-1)/saved;
  564. }
  565. }
  566. pthread_mutex_unlock (&lock_stats);
  567. }
  568. /*!
  569. * Update statistics for outgoing message
  570. * @param msg Pointer to message
  571. * @param dev The recipient device
  572. */
  573. void statsUpdateOut (msg_t* msg, devAEM_t dev) {
  574. pthread_mutex_lock (&lock_stats);
  575. stats.outDirectMsg += (msg->cMsg.to == dev) ? 1:0;
  576. pthread_mutex_unlock (&lock_stats);
  577. }
  578. /*!
  579. * Statistics print functionality
  580. * @param stats Pointer to stats to print
  581. * @return The status of the operation
  582. */
  583. status_t statsPrint (stats_t* stats) {
  584. FILE* fp = fopen ("statistics.txt", "w");
  585. if (fp == NULL) {
  586. fclose (fp);
  587. return MSG_ERROR;
  588. }
  589. fprintf (fp, "\n Statistics\n============\n");
  590. fprintf (fp, "Total messages: %d\n", stats->totalMsg);
  591. fprintf (fp, "Duplicate messages: %d\n", stats->duplicateMsg);
  592. fprintf (fp, "Messages for me: %d\n", stats->forMeMsg);
  593. fprintf (fp, "Messages by me: %d\n",stats->myMsg);
  594. fprintf (fp, "In messages direct for me: %d\n", stats->inDirectMsg);
  595. fprintf (fp, "Out direct messages: %d\n", stats->outDirectMsg);
  596. fprintf (fp, "Average message size: %g\n", stats->avMsgSize);
  597. fprintf (fp, "Average time to me: %g\n", stats->avTimeToMe);
  598. fclose (fp);
  599. return MSG_OK;
  600. }
  601. /*!
  602. * Device online timing print functionality
  603. * @param devList Pointer to devList to print
  604. * @return The status of the operation
  605. */
  606. status_t statsTimesPrint (devList_t *devList) {
  607. FILE* fp = fopen ("devices.txt", "w");
  608. if (fp == NULL) {
  609. fclose (fp);
  610. return MSG_ERROR;
  611. }
  612. fprintf (fp, "\n Device timings\n================\n");
  613. for (size_t i =0 ; i<AEMLIST_SIZE ; ++i) {
  614. fprintf (fp, "Device %u found on %lld, last: %lld\n",
  615. devList[i].dev, devList[i].begin, devList[i].end);
  616. }
  617. fclose (fp);
  618. return MSG_OK;
  619. }
  620. //! @}