A messenger application for Raspberry Pi Zerofor A.U.TH (Real time Embedded systems).
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

700 lines
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. * @example
  33. * @code{.c}
  34. * devAEM_t dev = addr2devAEM (ntohl(clntAddr.sin_addr.s_addr));
  35. * @endcode
  36. */
  37. devAEM_t addr2devAEM (in_addr_t in_addr) {
  38. return (in_addr & 0x000000FF) + ((in_addr >> 8) & 0x000000FF) * 100;
  39. }
  40. /*!
  41. * devAEM_t to in_addr_t conversion utility function
  42. * @param dev devAEM_t address
  43. * @return Internet address (host byte order)
  44. * @example
  45. * @code{.c}
  46. * sockaddr_in_t srvAddr;
  47. * srvAddr.sin_addr.s_addr = htonl (devAEM2addr (dev));
  48. * @endcode
  49. */
  50. in_addr_t devAEM2addr (devAEM_t dev) {
  51. uint32_t add = _ADHOC_SUBNET (ADHOC_NET_A, ADHOC_NET_B, ADHOC_NET_C, ADHOC_NET_D);
  52. add |= (dev % 100) & 0x000000FF;
  53. add |= ((dev / 100) & 0x000000FF) << 8;
  54. return add;
  55. }
  56. /*!
  57. * DevIP_t to devAEM_t conversion utility function
  58. * @param ip pointer to devIP_t type address
  59. * @return devAEM_t representation of the address
  60. * @note
  61. * We discard the first 2 fields
  62. */
  63. devAEM_t ip2AEM (devIP_t* ip) {
  64. return ip->C*100 + ip->D;
  65. }
  66. /*!
  67. * devAEM_t to DevIP_t conversion utility function
  68. * @param dev devAEM_t representation of the address
  69. * @return devIP_t type address
  70. */
  71. devIP_t AEM2ip (devAEM_t dev) {
  72. devIP_t ip = {
  73. .A =ADHOC_NET_A, .B=ADHOC_NET_B, .C=dev/100, .D=dev%100
  74. };
  75. return ip;
  76. }
  77. //! @}
  78. /*
  79. * Log related local data
  80. */
  81. static char_t* _frm_msg_in = "In from dev=%d, message: from=%d, to=%d, timestamp=%lld, text=%s\n";
  82. static char_t* _frm_msg_out = "Out to dev=%d, message: from=%d, to=%d, timestamp=%lld, text=%s\n";
  83. static char_t* _frm_msg_new = "New message: from=%d, to=%d, timestamp=%lld, text=%s\n";
  84. //! log API
  85. //! @{
  86. #define _HEAD_SIZE 25
  87. /*!
  88. * Initialize log functionality
  89. * @return The status of the operation
  90. */
  91. status_t log_init (void) {
  92. // Try to initialize pthreads for logging
  93. if (pthread_mutex_init(&lock_stderr, NULL) != 0) {
  94. fprintf (stderr, "Error %s: mutex init has failed\n", __FUNCTION__ );
  95. return MSG_ERROR;
  96. }
  97. if (pthread_mutex_init(&lock_stdout, NULL) != 0) {
  98. fprintf (stderr, "Error %s: mutex init has failed\n", __FUNCTION__ );
  99. return MSG_ERROR;
  100. }
  101. return MSG_OK;
  102. }
  103. /*!
  104. * Logs incoming messages
  105. * @param msg Pointer to msg to log
  106. * @note
  107. * This function depends on \sa settings.outLevel
  108. */
  109. void log_msg_in (msg_t* msg) {
  110. if (settings.outLevel >= OUTLEVEL_1) {
  111. pthread_mutex_lock(&lock_stdout); // lock stdout
  112. fprintf (stdout, _frm_msg_in, // print and flush
  113. msg->sender,
  114. msg->cMsg.from,
  115. msg->cMsg.to,
  116. msg->cMsg.ts,
  117. msg->cMsg.text
  118. );
  119. fflush(stdout);
  120. pthread_mutex_unlock(&lock_stdout); // unlock stdout
  121. }
  122. }
  123. /*!
  124. * Logs outgoing messages
  125. * @param msg Pointer to msg to log
  126. * @param dev device that accepts the message
  127. * @note
  128. * This function depends on \sa settings.outLevel
  129. */
  130. void log_msg_out (msg_t* msg, devAEM_t dev) {
  131. if (settings.outLevel >= OUTLEVEL_1) {
  132. pthread_mutex_lock(&lock_stdout); // lock stdout
  133. fprintf (stdout, _frm_msg_out, // print and flush
  134. dev,
  135. msg->cMsg.from,
  136. msg->cMsg.to,
  137. msg->cMsg.ts,
  138. msg->cMsg.text
  139. );
  140. fflush(stdout);
  141. pthread_mutex_unlock(&lock_stdout); // unlock stdout
  142. }
  143. }
  144. /*!
  145. * Logs new messages
  146. * @param msg Pointer to msg to log
  147. * @note
  148. * This function depends on \sa settings.outLevel
  149. */
  150. void log_msg_new (msg_t* msg) {
  151. if (settings.outLevel >= OUTLEVEL_1) {
  152. pthread_mutex_lock(&lock_stdout); // lock stdout
  153. fprintf (stdout, _frm_msg_new, // print and flush
  154. msg->cMsg.from,
  155. msg->cMsg.to,
  156. msg->cMsg.ts,
  157. msg->cMsg.text
  158. );
  159. fflush(stdout);
  160. pthread_mutex_unlock(&lock_stdout); // unlock stdout
  161. }
  162. }
  163. /*!
  164. * Debug message log to stdout
  165. * Variadic formating print
  166. * @param fmt Pointer to printf like format string
  167. */
  168. void log_debug (const char *fmt, ...) {
  169. if (settings.outLevel >= OUTLEVEL_2) {
  170. va_list ap;
  171. va_start(ap, fmt);
  172. pthread_mutex_lock(&lock_stdout);
  173. vfprintf (stdout, fmt, ap);
  174. fflush(stdout);
  175. pthread_mutex_unlock(&lock_stdout);
  176. va_end(ap);
  177. }
  178. }
  179. /*!
  180. * Debug error message log to stderr
  181. * Variadic formating print
  182. * @param fmt Pointer to printf like format string
  183. */
  184. void log_error (const char *fmt, ...) {
  185. va_list ap;
  186. va_start(ap, fmt);
  187. pthread_mutex_lock(&lock_stderr);
  188. vfprintf (stderr, fmt, ap);
  189. fflush(stderr);
  190. pthread_mutex_unlock(&lock_stderr);
  191. va_end(ap);
  192. }
  193. //! @}
  194. //! cMsg_t API
  195. //! @{
  196. /*!
  197. * Make a new message
  198. * @param msg Pointer to message to create
  199. */
  200. void cMsg_make (cMsg_t* msg) {
  201. static int msgID =0; // unique msg ID
  202. msg->from = settings.me; // from me
  203. do {
  204. // randomly select recipient device
  205. msg->to = devList[rand() % AEMLIST_SIZE].dev;
  206. } while (msg->to == settings.me);
  207. msg->ts = time(NULL);
  208. // stream the first fields and take the quote text iterator
  209. sprintf (msg->text, "%s #%d", MESSAGE_BODY, msgID++);
  210. }
  211. /*!
  212. * Message concatenation
  213. * This function synthesize the ascii message for transmission
  214. * @param msg Pointer to message to serialize
  215. * @param buffer Pointer to output buffer
  216. * @return The size of the resulting message
  217. */
  218. size_t cMsg_serialize (cMsg_t* msg, char_t* buffer) {
  219. return sprintf (buffer, "%d_%d_%lld_%s",
  220. msg->from,
  221. msg->to,
  222. msg->ts,
  223. msg->text
  224. );
  225. }
  226. /*!
  227. * Custom strtok functionality
  228. * @param str Pointer to string to parse
  229. * @param max The size of string
  230. * @param c Single delimiter character (non compatible with strtok())
  231. * @return Pointer to token (non compatible with strtok())
  232. * @note
  233. * This function alters the given string by adding null termination in the places
  234. * of delimiters
  235. */
  236. char_t* _strtok (char_t* str, size_t max, char_t c) {
  237. static char_t* last = NULL;
  238. char_t* ret = str;
  239. // init last
  240. if (str != NULL) last = str;
  241. // loop
  242. for (size_t i=0 ; i<max ; ++i) {
  243. if (*last == c) {
  244. *last++ = 0;
  245. return ret;
  246. }
  247. ++last;
  248. }
  249. return NULL;
  250. }
  251. /*!
  252. * Parse an incoming message
  253. *
  254. * @param cMsg Pointer to cMsg object to store the parsed data
  255. * @param rawMsg Pointer to raw message
  256. * @param size The size f raw message buffer
  257. * @return The status of the operation
  258. * @arg MSG_OK Success
  259. * @arg MSG_ERROR Parse failure, incoming message format error
  260. */
  261. status_t cMsg_parse (cMsg_t* cMsg, char_t* rawMsg, size_t size) {
  262. // Check message integrity
  263. if (size > MSG_TEXT_SIZE)
  264. return MSG_ERROR;
  265. // Parse message
  266. char_t* rest = rawMsg;
  267. char_t* tok[4];
  268. bool_t done = true;
  269. for (size_t i =0; i < 3; ++i) {
  270. tok[i] = _strtok (rest, size, MSG_DELIMITER);
  271. if (tok[i] == NULL) {
  272. done = false;
  273. break;
  274. }
  275. else {
  276. int l = strlen(rest);
  277. rest += l + 1;
  278. size -= l + 1;
  279. }
  280. }
  281. tok[3] = rest;
  282. if (done) {
  283. cMsg->from = atoi (tok[0]);
  284. cMsg->to = atoi (tok[1]);
  285. cMsg->ts = atoi (tok[2]);
  286. strcpy (cMsg->text, tok[3]);
  287. return MSG_OK;
  288. }
  289. return MSG_ERROR;
  290. }
  291. /*! getter for cMsg_t member fromAEM */
  292. uint32_t cMsg_getFrom(cMsg_t* cMsg) { return cMsg->from; }
  293. /*! getter for cMsg_t member toAEM */
  294. uint32_t cMsg_getTo(cMsg_t* cMsg) { return cMsg->to; }
  295. /*! getter for cMsg_t member fromAEM */
  296. uint64_t cMsg_getTs(cMsg_t* cMsg) { return cMsg->ts; }
  297. /*! getter for payload text member */
  298. char_t* cMsg_getText(cMsg_t* cMsg) { return cMsg->text; }
  299. /*!
  300. * Predicate to check core message equality
  301. * @param m1 Pointer to message 1
  302. * @param m2 Pointer to message 2
  303. * @return Equality result (true, false)
  304. */
  305. bool_t cMsg_equal (cMsg_t* m1, cMsg_t* m2) {
  306. if (m1->from != m2->from) return false;
  307. if (m1->to != m2->to) return false;
  308. if (m1->ts != m2->ts) return false;
  309. if (strncmp (m1->text, m2->text, strlen(m1->text)))
  310. return false;
  311. return true;
  312. }
  313. //! @}
  314. /*!
  315. * mgs_t API
  316. */
  317. //! @{
  318. /*!
  319. * message initialization
  320. * @param msg
  321. */
  322. void msg_init (msg_t* msg) {
  323. memset ((void*)msg, 0, sizeof(msg_t)); // init to 0
  324. }
  325. //! @}
  326. //! devList API
  327. //! @{
  328. /*!
  329. * Initialize the devList
  330. * @param msgList Pointer to mesList t initialize
  331. * @return The status of the operation
  332. */
  333. status_t devList_init (devList_t* devList) {
  334. devAEM_t l[] = AEMLIST;
  335. if (pthread_mutex_init(&lock_devList, NULL) != 0) {
  336. log_error ("Error: mutex init has failed\n");
  337. return MSG_ERROR;
  338. }
  339. memset ((void*)devList, 0, sizeof(devList_t));
  340. for (size_t i =0 ; i<AEMLIST_SIZE ; ++i)
  341. devList[i].dev = l[i];
  342. return MSG_OK;
  343. }
  344. /*!
  345. * Returns an iterator for \sa devList AND \sa msg_t.recipients
  346. * @param dev The device to search
  347. * @return The iterator, namely the index to devList array in which is the \p dev
  348. */
  349. dIter_t devList_getIter (devAEM_t dev) {
  350. for (dIter_t i =0 ; i<AEMLIST_SIZE ; ++i) {
  351. if (devList[i].dev == dev)
  352. return i;
  353. }
  354. return -1; // return end()
  355. }
  356. //! Acquires devList resources
  357. void devList_acquire (void) { pthread_mutex_lock(&lock_devList); }
  358. //! Releases devList resources
  359. void devList_release (void) { pthread_mutex_unlock(&lock_devList); }
  360. //! @}
  361. //! msgList API
  362. //! @{
  363. /*! Macro helper to saturate increased values */
  364. #define _top_saturate(test, apply, value) do { \
  365. if (test >= value) apply = value; \
  366. } while (0)
  367. /*! Macro helper to saturate decreased values */
  368. #define _btm_saturate(test, apply, value) do { \
  369. if (test < value) apply = value; \
  370. while (0)
  371. /*!
  372. * Initialize the msgList
  373. * @param msgList Pointer to mesList t initialize
  374. * @return The status of the operation
  375. */
  376. status_t msgList_init (msgList_t* msgList) {
  377. if (pthread_mutex_init(&lock_msgList, NULL) != 0) {
  378. log_error ("Error: mutex init has failed\n");
  379. return MSG_ERROR;
  380. }
  381. memset((void*)msgList, 0, sizeof(msgList_t));
  382. msgList->first =-1;
  383. msgList->last =-1;
  384. srand (time(NULL));
  385. return MSG_OK;
  386. }
  387. /*!
  388. * @brief msgList iterator pre-increment in the msg_t direction
  389. *
  390. * This iterator force a ring buffer behavior. This function takes pointer
  391. * to iterator to alter but return the altered value so it can be directly
  392. * used in expressions
  393. *
  394. * @param it Pointer to iterator to increase
  395. * @return The iterator values
  396. */
  397. mIter_t msgList_preInc (mIter_t* it) {
  398. if (++*it >= MSG_LIST_SIZE) *it = 0;
  399. return *it;
  400. }
  401. /*!
  402. * @brief msgList iterator pre-decrement in the msg_t direction
  403. *
  404. * This iterator force a ring buffer behavior. This function takes pointer
  405. * to iterator to alter but return the altered value so it can be directly
  406. * used in expressions
  407. *
  408. * @param it Pointer to iterator to decrease
  409. * @return The iterator values
  410. */
  411. mIter_t msgList_preDec (mIter_t* it) {
  412. if (--*it < 0) *it = MSG_LIST_SIZE;
  413. return *it;
  414. }
  415. /*!
  416. * @param this Pointer to msgList to use
  417. * @return An iterator to the first message of \sa MSG_LIST_SIZE
  418. * of msgList.m[]
  419. * @note
  420. * As the msgList is a ring buffer we can not have a sensible end() iterator.
  421. * end() will eventually merge with begin() when the size reaches \sa MSG_LIST_SIZE
  422. * For that reason in all of our loops through msgList we shall take a begin()
  423. * iterator and loop using size as sentinel
  424. * @example
  425. * @code{.c}
  426. * mIter_t it = msgList_begin (&msgList); // get a message iterator
  427. * size_t size= msgList_size(&msgList); // get current msgList size
  428. * for (size_t i=0 ; i<size ; ++i, msgList_preInc (&it)) { // don't forget to increase iterator
  429. * // use msgList[it]
  430. * }
  431. * @endcode
  432. */
  433. mIter_t msgList_begin (msgList_t* this) {
  434. return this->first;
  435. }
  436. /*!
  437. * @param this Pointer to msgList to use
  438. * @return An iterator to the last inserted message of \sa MSG_LIST_SIZE
  439. * of msgList.m[]
  440. */
  441. mIter_t msgList_last (msgList_t* this) {
  442. return this->last;
  443. }
  444. /*!
  445. * @param this Pointer to msgList to use
  446. * @return The current used slots of msgList
  447. * @note
  448. * As the msgList is a ring buffer we can not have a sensible end() iterator.
  449. * end() will eventually merge with begin() when the size reaches \sa MSG_LIST_SIZE
  450. * For that reason in all of our loops through msgList we shall take a begin()
  451. * iterator and loop using size as sentinel
  452. * @example
  453. * @code{.c}
  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 \sa 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. //! @}