shellmatta_ymodem.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. /*
  2. * Copyright (c) 2023 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  3. *
  4. * This Source Code Form is subject to the terms of the Mozilla Public
  5. * License, v. 2.0. If a copy of the MPL was not distributed with this
  6. * file, You can obtain one at https://mozilla.org/MPL/2.0/.
  7. */
  8. /**
  9. * @file shellmatta_ymodem.c
  10. * @brief ymodem functions of shellmatta
  11. * @author Simon Fischer <dev@s-fischer.net>
  12. */
  13. #include "shellmatta_ymodem.h"
  14. #include "shellmatta_crc.h"
  15. #include "shellmatta_utils.h"
  16. #include <stddef.h>
  17. #include <string.h>
  18. /** @brief symbols needed for ymodem protocol */
  19. typedef enum {
  20. YMODEM_NULL = 0x00, /**< NULL-terminator */
  21. YMODEM_SOH = 0x01, /**< Start of header */
  22. YMODEM_STX = 0x02, /**< Start of header for 1k packet */
  23. YMODEM_EOT = 0x04, /**< End of transmission */
  24. YMODEM_ACK = 0x06, /**< Acknowledge */
  25. YMODEM_NAK = 0x15, /**< Negative acknowledge */
  26. YMODEM_CA = 0x18, /**< Cancel */
  27. YMODEM_SPACE = 0x20, /**< Space */
  28. YMODEM_CRC = 0x43 /**< 'C' to indicate CRC type */
  29. } YMODEM_SYMBOLS;
  30. #define YMODEM_PACKET_SIZE 128u /**< default packet size of ymodem transmission */
  31. #define YMODEM_PACKET_SIZE_1K 1024u /**< extended packet size of ymodem transmission */
  32. #define YMODEM_CRC_SIZE 2u /**< CRC size of the ymodem packet */
  33. #define YMODEM_POLL_NUMBER 2u /**< Number of polls to be received */
  34. /**
  35. * @brief forwards the given character to write-function without formatting
  36. * @param[in] handle shellmatta handle of the instance
  37. * @param[in] c character to be sent
  38. */
  39. static void shellmatta_ymodem_control(shellmatta_handle_t handle, const uint8_t c)
  40. {
  41. shellmatta_instance_t *inst = (shellmatta_instance_t*)handle;
  42. SHELLMATTA_WRITE((char*)&c, 1);
  43. }
  44. /**
  45. * @brief reset function for the ymodem module
  46. * @param[in, out] handle shellmatta instance handle
  47. * @param[in] doCancel flag to execute the cancel-callback
  48. */
  49. static void shellmatta_ymodem_reset(shellmatta_handle_t handle, bool doCancel)
  50. {
  51. shellmatta_instance_t *inst = (shellmatta_instance_t *)handle;
  52. /** -# call cancel callback function */
  53. if (doCancel)
  54. {
  55. /* send cancel symbol */
  56. shellmatta_ymodem_control(handle, YMODEM_CA);
  57. if (NULL != inst->ymodem.cancelCallback)
  58. {
  59. inst->ymodem.cancelCallback(handle);
  60. }
  61. }
  62. /** -# reset instance variables */
  63. inst->ymodem.state = SHELLMATTA_YMODEM_INACTIVE;
  64. inst->ymodem.byteCounter = 0u;
  65. inst->ymodem.packetCounter = 0u;
  66. inst->ymodem.totalBytesReceived = 0u;
  67. inst->ymodem.fileSize = 0u;
  68. inst->ymodem.pauseRequested = false;
  69. inst->ymodem.pollCyclesLeft = 0u;
  70. #ifdef SHELLMATTA_TRANSPORT
  71. /** .-# reenable transport layer optional mode */
  72. inst->transportLayer.suspendOptional = false;
  73. #endif
  74. (void)memset((void *)&inst->ymodem.packet, 0, sizeof(shellmatta_ymodem_packet_t));
  75. }
  76. shellmatta_retCode_t processPacket(shellmatta_handle_t handle)
  77. {
  78. shellmatta_retCode_t ret = SHELLMATTA_OK;
  79. shellmatta_instance_t *inst = (shellmatta_instance_t *)handle;
  80. char *fileName;
  81. uint32_t packetSize;
  82. /** -# read filesize and name from first packet - ignore rest */
  83. if(0u == inst->ymodem.packetCounter)
  84. {
  85. fileName = (char*)inst->ymodem.packet.packetData;
  86. ret = utils_shellAsciiToUInt32((char*)&inst->ymodem.packet.packetData[strlen(fileName) + 1u],
  87. &inst->ymodem.fileSize,
  88. 10u);
  89. /** -# pass filename and size to the callback */
  90. inst->ymodem.recvHeaderCallback(handle, inst->ymodem.fileSize, fileName);
  91. }
  92. else
  93. {
  94. /** -# calculate packet size - when it is the last packet this is limited by the file size */
  95. if((inst->ymodem.totalBytesReceived + inst->ymodem.packet.size) > inst->ymodem.fileSize)
  96. {
  97. packetSize = inst->ymodem.fileSize % inst->ymodem.packet.size;
  98. }
  99. else
  100. {
  101. packetSize = inst->ymodem.packet.size;
  102. }
  103. /** -# pass data to the application using the callback */
  104. inst->ymodem.recvPacketCallback(handle, inst->ymodem.packet.packetData, packetSize, inst->ymodem.packetCounter);
  105. }
  106. return ret;
  107. }
  108. static shellmatta_retCode_t ymodem_stateMachine(shellmatta_handle_t handle, uint8_t byte)
  109. {
  110. shellmatta_retCode_t ret = SHELLMATTA_OK;
  111. shellmatta_instance_t *inst = (shellmatta_instance_t *)handle;
  112. uint16_t computedCrc;
  113. switch(inst->ymodem.state)
  114. {
  115. case SHELLMATTA_YMODEM_INACTIVE:
  116. /** -# skip state machine if inactive */
  117. break;
  118. case SHELLMATTA_YMODEM_WAIT_FOR_START:
  119. /** -# Wait for start character */
  120. inst->ymodem.byteCounter = 0u;
  121. switch(byte)
  122. {
  123. case YMODEM_SOH:
  124. inst->ymodem.packet.size = YMODEM_PACKET_SIZE;
  125. inst->ymodem.state = SHELLMATTA_YMODEM_RECEIVE_HEADER;
  126. break;
  127. case YMODEM_STX:
  128. inst->ymodem.packet.size = YMODEM_PACKET_SIZE_1K;
  129. inst->ymodem.state = SHELLMATTA_YMODEM_RECEIVE_HEADER;
  130. break;
  131. case YMODEM_EOT:
  132. /** -# ACK the successful file reception */
  133. shellmatta_ymodem_control(handle, YMODEM_ACK);
  134. shellmatta_ymodem_control(handle, YMODEM_CRC);
  135. /** -# handle additional EOTs in WAIT FOR END state */
  136. inst->ymodem.state = SHELLMATTA_YMODEM_WAIT_FOR_END;
  137. inst->ymodem.pollCyclesLeft = YMODEM_POLL_NUMBER;
  138. break;
  139. default:
  140. /** -# ignore unexpected characters on start */
  141. break;
  142. }
  143. break;
  144. case SHELLMATTA_YMODEM_RECEIVE_HEADER:
  145. if(0u == inst->ymodem.byteCounter)
  146. {
  147. inst->ymodem.packet.packetNumber = byte;
  148. inst->ymodem.byteCounter ++;
  149. }
  150. else
  151. {
  152. if(((0xffu - byte) != inst->ymodem.packet.packetNumber) ||
  153. (inst->ymodem.packetCounter % 256u != inst->ymodem.packet.packetNumber))
  154. {
  155. /** -# return error on packet number mismatch or on unexpected packet numbers */
  156. inst->ymodem.state = SHELLMATTA_YMODEM_WAIT_FOR_START;
  157. shellmatta_ymodem_control(handle, YMODEM_NAK);
  158. ret = SHELLMATTA_ERROR;
  159. }
  160. else
  161. {
  162. /** -# start receiving the payload */
  163. inst->ymodem.state = SHELLMATTA_YMODEM_RECEIVE_DATA;
  164. inst->ymodem.byteCounter = 0u;
  165. }
  166. }
  167. break;
  168. case SHELLMATTA_YMODEM_RECEIVE_DATA:
  169. inst->ymodem.packet.packetData[inst->ymodem.byteCounter] = byte;
  170. inst->ymodem.byteCounter ++;
  171. /** -# load payload until the packet is full */
  172. if(inst->ymodem.byteCounter >= inst->ymodem.packet.size)
  173. {
  174. inst->ymodem.state = SHELLMATTA_YMODEM_RECEIVE_CRC;
  175. inst->ymodem.byteCounter = 0u;
  176. inst->ymodem.packet.crc = 0u;
  177. }
  178. break;
  179. case SHELLMATTA_YMODEM_RECEIVE_CRC:
  180. inst->ymodem.byteCounter ++;
  181. inst->ymodem.packet.crc |= (uint16_t)byte << (8u * (YMODEM_CRC_SIZE - inst->ymodem.byteCounter));
  182. if(inst->ymodem.byteCounter >= YMODEM_CRC_SIZE)
  183. {
  184. inst->ymodem.state = SHELLMATTA_YMODEM_WAIT_FOR_START;
  185. /** -# check CRC */
  186. computedCrc = crc16Calc((const char*)inst->ymodem.packet.packetData,
  187. inst->ymodem.packet.size);
  188. if(computedCrc != inst->ymodem.packet.crc)
  189. {
  190. shellmatta_ymodem_control(handle, YMODEM_NAK);
  191. ret = SHELLMATTA_ERROR;
  192. }
  193. else
  194. {
  195. ret = processPacket(handle);
  196. if(SHELLMATTA_OK == ret)
  197. {
  198. if(0u != inst->ymodem.packetCounter)
  199. {
  200. /** -# Calculate the total bytes received */
  201. inst->ymodem.totalBytesReceived += inst->ymodem.packet.size;
  202. }
  203. if(true != inst->ymodem.pauseRequested)
  204. {
  205. /** -# ACK the successful packet reception */
  206. shellmatta_ymodem_control(handle, YMODEM_ACK);
  207. if(0u == inst->ymodem.packetCounter)
  208. {
  209. /** -# send addional CRC flag after packet 0 */
  210. shellmatta_ymodem_control(handle, YMODEM_CRC);
  211. }
  212. }
  213. inst->ymodem.packetCounter ++;
  214. }
  215. }
  216. }
  217. break;
  218. case SHELLMATTA_YMODEM_WAIT_FOR_END:
  219. inst->ymodem.pollCyclesLeft = YMODEM_POLL_NUMBER;
  220. if(YMODEM_EOT == byte)
  221. {
  222. /** -# ACK the successful file reception */
  223. shellmatta_ymodem_control(handle, YMODEM_ACK);
  224. shellmatta_ymodem_control(handle, YMODEM_CRC);
  225. }
  226. break;
  227. default:
  228. /** -# unexpected state - should never happen */
  229. break;
  230. }
  231. return ret;
  232. }
  233. shellmatta_retCode_t shellmatta_ymodem_processByte(shellmatta_handle_t handle, char byte)
  234. {
  235. shellmatta_instance_t *inst = (shellmatta_instance_t*)handle;
  236. shellmatta_retCode_t ret;
  237. /** -# check if session is cancelled -accept only if ymodem is between packets */
  238. if(('\x03' == byte) && (inst->ymodem.state == SHELLMATTA_YMODEM_WAIT_FOR_START))
  239. {
  240. /** -# explicitly reset ymodem with cancel, if it was active */
  241. shellmatta_ymodem_reset(handle, true);
  242. utils_terminateInput(inst);
  243. ret = SHELLMATTA_ERROR;
  244. }
  245. else
  246. {
  247. ret = ymodem_stateMachine(handle, (uint8_t)byte);
  248. }
  249. return ret;
  250. }
  251. shellmatta_retCode_t shellmatta_ymodem_poll(shellmatta_handle_t handle)
  252. {
  253. shellmatta_retCode_t ret = SHELLMATTA_ERROR;
  254. shellmatta_instance_t *inst = (shellmatta_instance_t*)handle;
  255. switch (inst->ymodem.state)
  256. {
  257. case SHELLMATTA_YMODEM_WAIT_FOR_START:
  258. /** -# send ymodem symbol to start transmission */
  259. shellmatta_ymodem_control(handle, YMODEM_CRC);
  260. ret = SHELLMATTA_OK;
  261. break;
  262. case SHELLMATTA_YMODEM_WAIT_FOR_END:
  263. if(inst->ymodem.pollCyclesLeft > 1u)
  264. {
  265. inst->ymodem.pollCyclesLeft --;
  266. }
  267. else
  268. {
  269. ret = SHELLMATTA_OK;
  270. /** -# check if the received data matches the file size */
  271. if((inst->ymodem.totalBytesReceived < inst->ymodem.fileSize) ||
  272. ((inst->ymodem.totalBytesReceived - inst->ymodem.fileSize) >= YMODEM_PACKET_SIZE_1K))
  273. {
  274. ret = SHELLMATTA_ERROR;
  275. }
  276. #ifdef SHELLMATTA_TRANSPORT
  277. /** .-# reenable transport layer optional mode */
  278. inst->transportLayer.suspendOptional = false;
  279. #endif
  280. shellmatta_ymodem_reset(handle, false);
  281. inst->ymodem.transmissionCompleteCallback(handle, ret);
  282. (void)utils_terminateInput(inst);
  283. }
  284. break;
  285. default:
  286. /* nothing to do */
  287. break;
  288. }
  289. return ret;
  290. }
  291. /**
  292. * @brief Initialise the ymodem prior to actually receiving data
  293. * @param[in, out] handle shellmatta instance handle
  294. * @param[in] recvBuffer pointer to the buffer to save the received payload in
  295. * @param[in] recvPacketCallback pointer to the file size variable
  296. * @param[in] recvPacketCallback pointer to the packet size variable
  297. * @param[in] transmissionCompleteCallback callback functions for the ymodem module
  298. * @return errorcode #SHELLMATTA_OK
  299. * #SHELLMATTA_USE_FAULT (param err)
  300. * @note Disables the tranport layer if inactive or sets it to mandatory if active
  301. */
  302. shellmatta_retCode_t shellmatta_ymodem_init(shellmatta_handle_t handle,
  303. uint8_t* recvBuffer,
  304. shellmatta_ymodem_cancel_t cancelCallback,
  305. shellmatta_ymodem_recvHeader_t recvHeaderCallback,
  306. shellmatta_ymodem_recvPacket_t recvPacketCallback,
  307. shellmatta_ymodem_complete_t transmissionCompleteCallback)
  308. {
  309. shellmatta_retCode_t ret = SHELLMATTA_OK;
  310. shellmatta_instance_t *inst = (shellmatta_instance_t *)handle;
  311. /** -# check parameters for plausibility */
  312. if( (NULL != inst)
  313. && (SHELLMATTA_MAGIC == inst->magic)
  314. && (NULL != cancelCallback)
  315. && (NULL != recvHeaderCallback)
  316. && (NULL != recvPacketCallback)
  317. && (NULL != transmissionCompleteCallback))
  318. {
  319. /** -# use instances buffer if no buffer is given */
  320. if(NULL == recvBuffer)
  321. {
  322. if(inst->bufferSize <= YMODEM_PACKET_SIZE_1K)
  323. {
  324. /** -# return use fault if buffer is too small */
  325. ret = SHELLMATTA_USE_FAULT;
  326. }
  327. else
  328. {
  329. inst->ymodem.packet.packetData = (uint8_t*)inst->buffer;
  330. }
  331. }
  332. (void)memset((void *)&inst->ymodem, 0, sizeof(shellmatta_ymodem_t));
  333. /** -# store buffer */
  334. inst->ymodem.packet.packetData = recvBuffer;
  335. /** -# init callbacks */
  336. inst->ymodem.cancelCallback = cancelCallback;
  337. inst->ymodem.recvHeaderCallback = recvHeaderCallback;
  338. inst->ymodem.recvPacketCallback = recvPacketCallback;
  339. inst->ymodem.transmissionCompleteCallback = transmissionCompleteCallback;
  340. inst->ymodem.state = SHELLMATTA_YMODEM_WAIT_FOR_START;
  341. #ifdef SHELLMATTA_TRANSPORT
  342. /** -# suspend the transport layer being optional while ymodem is running */
  343. inst->transportLayer.suspendOptional = true;
  344. #endif
  345. /** -# send initial ymodem symbol to start transmission */
  346. shellmatta_ymodem_control(handle, YMODEM_CRC);
  347. }
  348. else
  349. {
  350. ret = SHELLMATTA_USE_FAULT;
  351. }
  352. return ret;
  353. }
  354. /**
  355. * @brief pauses the shellmatta reception until #shellmatta_ymodem_resume resume is called
  356. * @param[in, out] handle shellmatta instance handle
  357. * @return errorcode #SHELLMATTA_OK
  358. * #SHELLMATTA_USE_FAULT (param err)
  359. * @note This can be used when an application needs processing time before accepting the next packet
  360. */
  361. shellmatta_retCode_t shellmatta_ymodem_pause(shellmatta_handle_t handle)
  362. {
  363. shellmatta_retCode_t ret = SHELLMATTA_OK;
  364. shellmatta_instance_t *inst = (shellmatta_instance_t *)handle;
  365. /** -# check parameters for plausibility */
  366. if( (NULL != inst)
  367. && (SHELLMATTA_MAGIC == inst->magic))
  368. {
  369. inst->ymodem.pauseRequested = true;
  370. }
  371. else
  372. {
  373. ret = SHELLMATTA_USE_FAULT;
  374. }
  375. return ret;
  376. }
  377. /**
  378. * @brief Resume the ymodem module
  379. * @param[in, out] handle shellmatta instance handle
  380. * @return errorcode #SHELLMATTA_OK
  381. * #SHELLMATTA_USE_FAULT (param err)
  382. * @note This can be used when an application needs processing time before accepting the next packet
  383. */
  384. shellmatta_retCode_t shellmatta_ymodem_resume(shellmatta_handle_t handle)
  385. {
  386. shellmatta_retCode_t ret = SHELLMATTA_OK;
  387. shellmatta_instance_t *inst = (shellmatta_instance_t *)handle;
  388. /** -# check parameters for plausibility */
  389. if( (NULL != inst)
  390. && (SHELLMATTA_MAGIC == inst->magic))
  391. {
  392. /** -# ACK the successful packet reception */
  393. shellmatta_ymodem_control(handle, YMODEM_ACK);
  394. if(1u == inst->ymodem.packetCounter)
  395. {
  396. /** -# send addional CRC flag after packet 0 */
  397. shellmatta_ymodem_control(handle, YMODEM_CRC);
  398. }
  399. inst->ymodem.pauseRequested = false;
  400. }
  401. else
  402. {
  403. ret = SHELLMATTA_USE_FAULT;
  404. }
  405. return ret;
  406. }
  407. /**
  408. * @brief Resets the ymodem module
  409. * @param[in] doCancel Set this flag to execute the cancel-callback function within the ymodem-reset function
  410. * @return errorcode #SHELLMATTA_OK
  411. * #SHELLMATTA_USE_FAULT (param err)
  412. * @note call this function after file transmission is done or cancelled
  413. */
  414. shellmatta_retCode_t shellmatta_ymodem_cancel(shellmatta_handle_t handle, bool doCancel)
  415. {
  416. shellmatta_retCode_t ret = SHELLMATTA_OK;
  417. shellmatta_instance_t *inst = (shellmatta_instance_t *)handle;
  418. /** -# check parameters for plausibility */
  419. if( (NULL != inst)
  420. && (SHELLMATTA_MAGIC == inst->magic))
  421. {
  422. shellmatta_ymodem_reset(handle, doCancel);
  423. /* clear any possibly leftover inputs */
  424. utils_clearInput((shellmatta_instance_t*)handle);
  425. }
  426. else
  427. {
  428. ret = SHELLMATTA_USE_FAULT;
  429. }
  430. return ret;
  431. }