flipper_http.c 50 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544
  1. // Description: Flipper HTTP API (For use with Flipper Zero and the FlipperHTTP flash: https://github.com/jblanked/FlipperHTTP)
  2. // License: MIT
  3. // Author: JBlanked
  4. // File: flipper_http.c
  5. #include <flipper_http/flipper_http.h>
  6. /**
  7. * @brief Worker thread to handle UART data asynchronously.
  8. * @return 0
  9. * @param context The FlipperHTTP context.
  10. * @note This function will handle received data asynchronously via the callback.
  11. */
  12. static int32_t flipper_http_worker(void *context)
  13. {
  14. if (!context)
  15. {
  16. FURI_LOG_E(HTTP_TAG, "Failed to get context.");
  17. return -1;
  18. }
  19. FlipperHTTP *fhttp = (FlipperHTTP *)context;
  20. if (!fhttp)
  21. {
  22. FURI_LOG_E(HTTP_TAG, "Failed to get context.");
  23. return -1;
  24. }
  25. size_t rx_line_pos = 0;
  26. while (1)
  27. {
  28. uint32_t events = furi_thread_flags_wait(
  29. WorkerEvtStop | WorkerEvtRxDone, FuriFlagWaitAny, FuriWaitForever);
  30. if (events & WorkerEvtStop)
  31. {
  32. break;
  33. }
  34. if (events & WorkerEvtRxDone)
  35. {
  36. // Continuously read from the stream buffer until it's empty
  37. while (!furi_stream_buffer_is_empty(fhttp->flipper_http_stream))
  38. {
  39. // Read one byte at a time
  40. char c = 0;
  41. size_t received = furi_stream_buffer_receive(fhttp->flipper_http_stream, &c, 1, 0);
  42. if (received == 0)
  43. {
  44. // No more data to read
  45. break;
  46. }
  47. fhttp->bytes_received += received;
  48. // print amount of bytes received
  49. // FURI_LOG_I(HTTP_TAG, "Bytes received: %d", fhttp->bytes_received);
  50. // Append the received byte to the file if saving is enabled
  51. if (fhttp->save_bytes)
  52. {
  53. // Add byte to the buffer
  54. fhttp->file_buffer[fhttp->file_buffer_len++] = c;
  55. // Write to file if buffer is full
  56. if (fhttp->file_buffer_len >= FILE_BUFFER_SIZE)
  57. {
  58. if (!flipper_http_append_to_file(
  59. fhttp->file_buffer,
  60. fhttp->file_buffer_len,
  61. fhttp->just_started_bytes,
  62. fhttp->file_path))
  63. {
  64. FURI_LOG_E(HTTP_TAG, "Failed to append data to file");
  65. }
  66. fhttp->file_buffer_len = 0;
  67. fhttp->just_started_bytes = false;
  68. }
  69. }
  70. // Handle line buffering only if callback is set (text data)
  71. if (fhttp->handle_rx_line_cb)
  72. {
  73. // Handle line buffering
  74. if (c == '\n' || rx_line_pos >= RX_LINE_BUFFER_SIZE - 1)
  75. {
  76. fhttp->rx_line_buffer[rx_line_pos] = '\0'; // Null-terminate the line
  77. // Invoke the callback with the complete line
  78. fhttp->handle_rx_line_cb(fhttp->rx_line_buffer, fhttp->callback_context);
  79. // Reset the line buffer position
  80. rx_line_pos = 0;
  81. }
  82. else
  83. {
  84. fhttp->rx_line_buffer[rx_line_pos++] = c; // Add character to the line buffer
  85. }
  86. }
  87. }
  88. }
  89. }
  90. return 0;
  91. }
  92. // UART RX Handler Callback (Interrupt Context)
  93. /**
  94. * @brief A private callback function to handle received data asynchronously.
  95. * @return void
  96. * @param handle The UART handle.
  97. * @param event The event type.
  98. * @param context The FlipperHTTP context.
  99. * @note This function will handle received data asynchronously via the callback.
  100. */
  101. static void _flipper_http_rx_callback(
  102. FuriHalSerialHandle *handle,
  103. FuriHalSerialRxEvent event,
  104. void *context)
  105. {
  106. FlipperHTTP *fhttp = (FlipperHTTP *)context;
  107. if (!fhttp)
  108. {
  109. FURI_LOG_E(HTTP_TAG, "Failed to get context.");
  110. return;
  111. }
  112. if (event == FuriHalSerialRxEventData)
  113. {
  114. uint8_t data = furi_hal_serial_async_rx(handle);
  115. furi_stream_buffer_send(fhttp->flipper_http_stream, &data, 1, 0);
  116. furi_thread_flags_set(fhttp->rx_thread_id, WorkerEvtRxDone);
  117. }
  118. }
  119. // Timer callback function
  120. /**
  121. * @brief Callback function for the GET timeout timer.
  122. * @return 0
  123. * @param context The FlipperHTTP context.
  124. * @note This function will be called when the GET request times out.
  125. */
  126. static void get_timeout_timer_callback(void *context)
  127. {
  128. FlipperHTTP *fhttp = (FlipperHTTP *)context;
  129. if (!fhttp)
  130. {
  131. FURI_LOG_E(HTTP_TAG, "Failed to get context.");
  132. return;
  133. }
  134. FURI_LOG_E(HTTP_TAG, "Timeout reached without receiving the end.");
  135. // Reset the state
  136. fhttp->started_receiving = false;
  137. // Update UART state
  138. fhttp->state = ISSUE;
  139. }
  140. static void flipper_http_rx_callback(const char *line, void *context); // forward declaration
  141. // Instead of two globals, we use a single static pointer to the active instance.
  142. static FlipperHTTP *active_fhttp = NULL;
  143. FlipperHTTP *flipper_http_alloc()
  144. {
  145. // If an active instance already exists, free it first.
  146. if (active_fhttp != NULL)
  147. {
  148. FURI_LOG_E(HTTP_TAG, "Existing UART instance detected, freeing previous instance.");
  149. flipper_http_free(active_fhttp);
  150. active_fhttp = NULL;
  151. }
  152. FlipperHTTP *fhttp = malloc(sizeof(FlipperHTTP));
  153. if (!fhttp)
  154. {
  155. FURI_LOG_E(HTTP_TAG, "Failed to allocate FlipperHTTP.");
  156. return NULL;
  157. }
  158. memset(fhttp, 0, sizeof(FlipperHTTP));
  159. fhttp->flipper_http_stream = furi_stream_buffer_alloc(RX_BUF_SIZE, 1);
  160. if (!fhttp->flipper_http_stream)
  161. {
  162. FURI_LOG_E(HTTP_TAG, "Failed to allocate UART stream buffer.");
  163. free(fhttp);
  164. return NULL;
  165. }
  166. fhttp->rx_thread = furi_thread_alloc();
  167. if (!fhttp->rx_thread)
  168. {
  169. FURI_LOG_E(HTTP_TAG, "Failed to allocate UART thread.");
  170. furi_stream_buffer_free(fhttp->flipper_http_stream);
  171. free(fhttp);
  172. return NULL;
  173. }
  174. furi_thread_set_name(fhttp->rx_thread, "FlipperHTTP_RxThread");
  175. furi_thread_set_stack_size(fhttp->rx_thread, 1024);
  176. furi_thread_set_context(fhttp->rx_thread, fhttp);
  177. furi_thread_set_callback(fhttp->rx_thread, flipper_http_worker);
  178. fhttp->handle_rx_line_cb = flipper_http_rx_callback;
  179. fhttp->callback_context = fhttp;
  180. furi_thread_start(fhttp->rx_thread);
  181. fhttp->rx_thread_id = furi_thread_get_id(fhttp->rx_thread);
  182. // Acquire UART control
  183. fhttp->serial_handle = furi_hal_serial_control_acquire(UART_CH);
  184. if (!fhttp->serial_handle)
  185. {
  186. FURI_LOG_E(HTTP_TAG, "Failed to acquire UART control - handle is NULL");
  187. furi_thread_flags_set(fhttp->rx_thread_id, WorkerEvtStop);
  188. furi_thread_join(fhttp->rx_thread);
  189. furi_thread_free(fhttp->rx_thread);
  190. furi_stream_buffer_free(fhttp->flipper_http_stream);
  191. free(fhttp);
  192. return NULL;
  193. }
  194. // Initialize and enable UART
  195. furi_hal_serial_init(fhttp->serial_handle, BAUDRATE);
  196. furi_hal_serial_enable_direction(fhttp->serial_handle, FuriHalSerialDirectionRx);
  197. furi_hal_serial_async_rx_start(fhttp->serial_handle, _flipper_http_rx_callback, fhttp, false);
  198. furi_hal_serial_tx_wait_complete(fhttp->serial_handle);
  199. // Allocate the timeout timer
  200. fhttp->get_timeout_timer = furi_timer_alloc(get_timeout_timer_callback, FuriTimerTypeOnce, fhttp);
  201. if (!fhttp->get_timeout_timer)
  202. {
  203. FURI_LOG_E(HTTP_TAG, "Failed to allocate HTTP request timeout timer.");
  204. furi_hal_serial_async_rx_stop(fhttp->serial_handle);
  205. furi_hal_serial_disable_direction(fhttp->serial_handle, FuriHalSerialDirectionRx);
  206. furi_hal_serial_control_release(fhttp->serial_handle);
  207. furi_hal_serial_deinit(fhttp->serial_handle);
  208. furi_thread_flags_set(fhttp->rx_thread_id, WorkerEvtStop);
  209. furi_thread_join(fhttp->rx_thread);
  210. furi_thread_free(fhttp->rx_thread);
  211. furi_stream_buffer_free(fhttp->flipper_http_stream);
  212. free(fhttp);
  213. return NULL;
  214. }
  215. furi_timer_set_thread_priority(FuriTimerThreadPriorityElevated);
  216. fhttp->last_response = malloc(RX_BUF_SIZE);
  217. if (!fhttp->last_response)
  218. {
  219. FURI_LOG_E(HTTP_TAG, "Failed to allocate memory for last_response.");
  220. furi_timer_free(fhttp->get_timeout_timer);
  221. furi_hal_serial_async_rx_stop(fhttp->serial_handle);
  222. furi_hal_serial_disable_direction(fhttp->serial_handle, FuriHalSerialDirectionRx);
  223. furi_hal_serial_control_release(fhttp->serial_handle);
  224. furi_hal_serial_deinit(fhttp->serial_handle);
  225. furi_thread_flags_set(fhttp->rx_thread_id, WorkerEvtStop);
  226. furi_thread_join(fhttp->rx_thread);
  227. furi_thread_free(fhttp->rx_thread);
  228. furi_stream_buffer_free(fhttp->flipper_http_stream);
  229. free(fhttp);
  230. return NULL;
  231. }
  232. memset(fhttp->last_response, 0, RX_BUF_SIZE);
  233. fhttp->state = IDLE;
  234. // Track the active instance globally.
  235. active_fhttp = fhttp;
  236. return fhttp;
  237. }
  238. void flipper_http_free(FlipperHTTP *fhttp)
  239. {
  240. if (!fhttp)
  241. {
  242. FURI_LOG_E(HTTP_TAG, "Failed to get context.");
  243. return;
  244. }
  245. if (fhttp->serial_handle == NULL)
  246. {
  247. FURI_LOG_E(HTTP_TAG, "UART handle is NULL. Already deinitialized?");
  248. return;
  249. }
  250. // Stop asynchronous RX and clean up UART
  251. furi_hal_serial_async_rx_stop(fhttp->serial_handle);
  252. furi_hal_serial_disable_direction(fhttp->serial_handle, FuriHalSerialDirectionRx);
  253. furi_hal_serial_deinit(fhttp->serial_handle);
  254. furi_hal_serial_control_release(fhttp->serial_handle);
  255. // Signal and free the worker thread
  256. furi_thread_flags_set(fhttp->rx_thread_id, WorkerEvtStop);
  257. furi_thread_join(fhttp->rx_thread);
  258. furi_thread_free(fhttp->rx_thread);
  259. // Free the stream buffer
  260. furi_stream_buffer_free(fhttp->flipper_http_stream);
  261. // Free the timer, if allocated
  262. if (fhttp->get_timeout_timer)
  263. {
  264. furi_timer_free(fhttp->get_timeout_timer);
  265. fhttp->get_timeout_timer = NULL;
  266. }
  267. // Free the last_response buffer
  268. if (fhttp->last_response)
  269. {
  270. free(fhttp->last_response);
  271. fhttp->last_response = NULL;
  272. }
  273. // If this instance is the active instance, clear the static pointer.
  274. if (active_fhttp == fhttp)
  275. {
  276. free(active_fhttp);
  277. active_fhttp = NULL;
  278. }
  279. free(fhttp);
  280. }
  281. /**
  282. * @brief Append received data to a file.
  283. * @return true if the data was appended successfully, false otherwise.
  284. * @param data The data to append to the file.
  285. * @param data_size The size of the data to append.
  286. * @param start_new_file Flag to indicate if a new file should be created.
  287. * @param file_path The path to the file.
  288. * @note Make sure to initialize the file path before calling this function.
  289. */
  290. bool flipper_http_append_to_file(
  291. const void *data,
  292. size_t data_size,
  293. bool start_new_file,
  294. char *file_path)
  295. {
  296. Storage *storage = furi_record_open(RECORD_STORAGE);
  297. File *file = storage_file_alloc(storage);
  298. if (start_new_file)
  299. {
  300. // Delete the file if it already exists
  301. if (storage_file_exists(storage, file_path))
  302. {
  303. if (!storage_simply_remove_recursive(storage, file_path))
  304. {
  305. FURI_LOG_E(HTTP_TAG, "Failed to delete file: %s", file_path);
  306. storage_file_free(file);
  307. furi_record_close(RECORD_STORAGE);
  308. return false;
  309. }
  310. }
  311. // Open the file in write mode
  312. if (!storage_file_open(file, file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS))
  313. {
  314. FURI_LOG_E(HTTP_TAG, "Failed to open file for writing: %s", file_path);
  315. storage_file_free(file);
  316. furi_record_close(RECORD_STORAGE);
  317. return false;
  318. }
  319. }
  320. else
  321. {
  322. // Open the file in append mode
  323. if (!storage_file_open(file, file_path, FSAM_WRITE, FSOM_OPEN_APPEND))
  324. {
  325. FURI_LOG_E(HTTP_TAG, "Failed to open file for appending: %s", file_path);
  326. storage_file_free(file);
  327. furi_record_close(RECORD_STORAGE);
  328. return false;
  329. }
  330. }
  331. // Write the data to the file
  332. if (storage_file_write(file, data, data_size) != data_size)
  333. {
  334. FURI_LOG_E(HTTP_TAG, "Failed to append data to file");
  335. storage_file_close(file);
  336. storage_file_free(file);
  337. furi_record_close(RECORD_STORAGE);
  338. return false;
  339. }
  340. storage_file_close(file);
  341. storage_file_free(file);
  342. furi_record_close(RECORD_STORAGE);
  343. return true;
  344. }
  345. /**
  346. * @brief Load data from a file.
  347. * @return The loaded data as a FuriString.
  348. * @param file_path The path to the file to load.
  349. */
  350. FuriString *flipper_http_load_from_file(char *file_path)
  351. {
  352. // Open the storage record
  353. Storage *storage = furi_record_open(RECORD_STORAGE);
  354. if (!storage)
  355. {
  356. FURI_LOG_E(HTTP_TAG, "Failed to open storage record");
  357. return NULL;
  358. }
  359. // Allocate a file handle
  360. File *file = storage_file_alloc(storage);
  361. if (!file)
  362. {
  363. FURI_LOG_E(HTTP_TAG, "Failed to allocate storage file");
  364. furi_record_close(RECORD_STORAGE);
  365. return NULL;
  366. }
  367. // Open the file for reading
  368. if (!storage_file_open(file, file_path, FSAM_READ, FSOM_OPEN_EXISTING))
  369. {
  370. storage_file_free(file);
  371. furi_record_close(RECORD_STORAGE);
  372. FURI_LOG_E(HTTP_TAG, "Failed to open file for reading: %s", file_path);
  373. return NULL;
  374. }
  375. size_t file_size = storage_file_size(file);
  376. // final memory check
  377. if (memmgr_heap_get_max_free_block() < file_size)
  378. {
  379. FURI_LOG_E(HTTP_TAG, "Not enough heap to read file.");
  380. storage_file_close(file);
  381. storage_file_free(file);
  382. furi_record_close(RECORD_STORAGE);
  383. return NULL;
  384. }
  385. // Allocate a buffer to hold the read data
  386. uint8_t *buffer = (uint8_t *)malloc(file_size);
  387. if (!buffer)
  388. {
  389. FURI_LOG_E(HTTP_TAG, "Failed to allocate buffer");
  390. storage_file_close(file);
  391. storage_file_free(file);
  392. furi_record_close(RECORD_STORAGE);
  393. return NULL;
  394. }
  395. // Allocate a FuriString to hold the received data
  396. FuriString *str_result = furi_string_alloc();
  397. if (!str_result)
  398. {
  399. FURI_LOG_E(HTTP_TAG, "Failed to allocate FuriString");
  400. storage_file_close(file);
  401. storage_file_free(file);
  402. furi_record_close(RECORD_STORAGE);
  403. return NULL;
  404. }
  405. // Reset the FuriString to ensure it's empty before reading
  406. furi_string_reset(str_result);
  407. // Read data into the buffer
  408. size_t read_count = storage_file_read(file, buffer, MAX_FILE_SHOW);
  409. if (storage_file_get_error(file) != FSE_OK)
  410. {
  411. FURI_LOG_E(HTTP_TAG, "Error reading from file.");
  412. furi_string_free(str_result);
  413. storage_file_close(file);
  414. storage_file_free(file);
  415. furi_record_close(RECORD_STORAGE);
  416. return NULL;
  417. }
  418. // Append each byte to the FuriString
  419. for (size_t i = 0; i < read_count; i++)
  420. {
  421. furi_string_push_back(str_result, buffer[i]);
  422. }
  423. // Clean up
  424. storage_file_close(file);
  425. storage_file_free(file);
  426. furi_record_close(RECORD_STORAGE);
  427. free(buffer);
  428. return str_result;
  429. }
  430. /**
  431. * @brief Load data from a file with a size limit.
  432. * @return The loaded data as a FuriString.
  433. * @param file_path The path to the file to load.
  434. * @param limit The size limit for loading data.
  435. */
  436. FuriString *flipper_http_load_from_file_with_limit(char *file_path, size_t limit)
  437. {
  438. if (memmgr_heap_get_max_free_block() < limit)
  439. {
  440. FURI_LOG_E(HTTP_TAG, "Not enough heap to read file.");
  441. return NULL;
  442. }
  443. // Open the storage record
  444. Storage *storage = furi_record_open(RECORD_STORAGE);
  445. if (!storage)
  446. {
  447. FURI_LOG_E(HTTP_TAG, "Failed to open storage record");
  448. return NULL;
  449. }
  450. // Allocate a file handle
  451. File *file = storage_file_alloc(storage);
  452. if (!file)
  453. {
  454. FURI_LOG_E(HTTP_TAG, "Failed to allocate storage file");
  455. furi_record_close(RECORD_STORAGE);
  456. return NULL;
  457. }
  458. // Open the file for reading
  459. if (!storage_file_open(file, file_path, FSAM_READ, FSOM_OPEN_EXISTING))
  460. {
  461. storage_file_free(file);
  462. furi_record_close(RECORD_STORAGE);
  463. FURI_LOG_E(HTTP_TAG, "Failed to open file for reading: %s", file_path);
  464. return NULL;
  465. }
  466. size_t file_size = storage_file_size(file);
  467. if (file_size > limit)
  468. {
  469. FURI_LOG_E(HTTP_TAG, "File size exceeds limit: %d > %d", file_size, limit);
  470. storage_file_close(file);
  471. storage_file_free(file);
  472. furi_record_close(RECORD_STORAGE);
  473. return NULL;
  474. }
  475. // final memory check
  476. if (memmgr_heap_get_max_free_block() < file_size)
  477. {
  478. FURI_LOG_E(HTTP_TAG, "Not enough heap to read file.");
  479. storage_file_close(file);
  480. storage_file_free(file);
  481. furi_record_close(RECORD_STORAGE);
  482. return NULL;
  483. }
  484. // Allocate a buffer to hold the read data
  485. uint8_t *buffer = (uint8_t *)malloc(file_size);
  486. if (!buffer)
  487. {
  488. FURI_LOG_E(HTTP_TAG, "Failed to allocate buffer");
  489. storage_file_close(file);
  490. storage_file_free(file);
  491. furi_record_close(RECORD_STORAGE);
  492. return NULL;
  493. }
  494. // Allocate a FuriString with preallocated capacity
  495. FuriString *str_result = furi_string_alloc();
  496. if (!str_result)
  497. {
  498. FURI_LOG_E(HTTP_TAG, "Failed to allocate FuriString");
  499. free(buffer);
  500. storage_file_close(file);
  501. storage_file_free(file);
  502. furi_record_close(RECORD_STORAGE);
  503. return NULL;
  504. }
  505. furi_string_reserve(str_result, file_size);
  506. // Read data into the buffer
  507. size_t read_count = storage_file_read(file, buffer, file_size);
  508. if (storage_file_get_error(file) != FSE_OK)
  509. {
  510. FURI_LOG_E(HTTP_TAG, "Error reading from file.");
  511. furi_string_free(str_result);
  512. free(buffer);
  513. storage_file_close(file);
  514. storage_file_free(file);
  515. furi_record_close(RECORD_STORAGE);
  516. return NULL;
  517. }
  518. if (read_count == 0)
  519. {
  520. FURI_LOG_E(HTTP_TAG, "No data read from file.");
  521. furi_string_free(str_result);
  522. free(buffer);
  523. storage_file_close(file);
  524. storage_file_free(file);
  525. furi_record_close(RECORD_STORAGE);
  526. return NULL;
  527. }
  528. // Append the entire buffer to FuriString in one operation
  529. furi_string_cat_str(str_result, (char *)buffer);
  530. // Clean up
  531. storage_file_close(file);
  532. storage_file_free(file);
  533. furi_record_close(RECORD_STORAGE);
  534. free(buffer);
  535. return str_result;
  536. }
  537. /**
  538. * @brief Perform a task while displaying a loading screen
  539. * @param fhttp The FlipperHTTP context
  540. * @param http_request The function to send the request
  541. * @param parse_response The function to parse the response
  542. * @param success_view_id The view ID to switch to on success
  543. * @param failure_view_id The view ID to switch to on failure
  544. * @param view_dispatcher The view dispatcher to use
  545. * @return
  546. */
  547. void flipper_http_loading_task(FlipperHTTP *fhttp,
  548. bool (*http_request)(void),
  549. bool (*parse_response)(void),
  550. uint32_t success_view_id,
  551. uint32_t failure_view_id,
  552. ViewDispatcher **view_dispatcher)
  553. {
  554. if (!fhttp)
  555. {
  556. FURI_LOG_E(HTTP_TAG, "Failed to get context.");
  557. return;
  558. }
  559. if (fhttp->state == INACTIVE)
  560. {
  561. view_dispatcher_switch_to_view(*view_dispatcher, failure_view_id);
  562. return;
  563. }
  564. Loading *loading;
  565. int32_t loading_view_id = 987654321; // Random ID
  566. loading = loading_alloc();
  567. if (!loading)
  568. {
  569. FURI_LOG_E(HTTP_TAG, "Failed to allocate loading");
  570. view_dispatcher_switch_to_view(*view_dispatcher, failure_view_id);
  571. return;
  572. }
  573. view_dispatcher_add_view(*view_dispatcher, loading_view_id, loading_get_view(loading));
  574. // Switch to the loading view
  575. view_dispatcher_switch_to_view(*view_dispatcher, loading_view_id);
  576. // Make the request
  577. if (!flipper_http_process_response_async(fhttp, http_request, parse_response))
  578. {
  579. FURI_LOG_E(HTTP_TAG, "Failed to make request");
  580. view_dispatcher_switch_to_view(*view_dispatcher, failure_view_id);
  581. view_dispatcher_remove_view(*view_dispatcher, loading_view_id);
  582. loading_free(loading);
  583. return;
  584. }
  585. // Switch to the success view
  586. view_dispatcher_switch_to_view(*view_dispatcher, success_view_id);
  587. view_dispatcher_remove_view(*view_dispatcher, loading_view_id);
  588. loading_free(loading); // comment this out if you experience a freeze
  589. }
  590. /**
  591. * @brief Parse JSON data.
  592. * @return true if the JSON data was parsed successfully, false otherwise.
  593. * @param fhttp The FlipperHTTP context
  594. * @param key The key to parse from the JSON data.
  595. * @param json_data The JSON data to parse.
  596. * @note The received data will be handled asynchronously via the callback.
  597. */
  598. bool flipper_http_parse_json(FlipperHTTP *fhttp, const char *key, const char *json_data)
  599. {
  600. if (!fhttp)
  601. {
  602. FURI_LOG_E(HTTP_TAG, "Failed to get context.");
  603. return false;
  604. }
  605. if (!key || !json_data)
  606. {
  607. FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_parse_json.");
  608. return false;
  609. }
  610. char buffer[512];
  611. int ret =
  612. snprintf(buffer, sizeof(buffer), "[PARSE]{\"key\":\"%s\",\"json\":%s}", key, json_data);
  613. if (ret < 0 || ret >= (int)sizeof(buffer))
  614. {
  615. FURI_LOG_E("FlipperHTTP", "Failed to format JSON parse command.");
  616. return false;
  617. }
  618. return flipper_http_send_data(fhttp, buffer);
  619. }
  620. /**
  621. * @brief Parse JSON array data.
  622. * @return true if the JSON array data was parsed successfully, false otherwise.
  623. * @param fhttp The FlipperHTTP context
  624. * @param key The key to parse from the JSON array data.
  625. * @param index The index to parse from the JSON array data.
  626. * @param json_data The JSON array data to parse.
  627. * @note The received data will be handled asynchronously via the callback.
  628. */
  629. bool flipper_http_parse_json_array(FlipperHTTP *fhttp, const char *key, int index, const char *json_data)
  630. {
  631. if (!fhttp)
  632. {
  633. FURI_LOG_E(HTTP_TAG, "Failed to get context.");
  634. return false;
  635. }
  636. if (!key || !json_data)
  637. {
  638. FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_parse_json_array.");
  639. return false;
  640. }
  641. char buffer[512];
  642. int ret = snprintf(
  643. buffer,
  644. sizeof(buffer),
  645. "[PARSE/ARRAY]{\"key\":\"%s\",\"index\":%d,\"json\":%s}",
  646. key,
  647. index,
  648. json_data);
  649. if (ret < 0 || ret >= (int)sizeof(buffer))
  650. {
  651. FURI_LOG_E("FlipperHTTP", "Failed to format JSON parse array command.");
  652. return false;
  653. }
  654. return flipper_http_send_data(fhttp, buffer);
  655. }
  656. /**
  657. * @brief Process requests and parse JSON data asynchronously
  658. * @param fhttp The FlipperHTTP context
  659. * @param http_request The function to send the request
  660. * @param parse_json The function to parse the JSON
  661. * @return true if successful, false otherwise
  662. */
  663. bool flipper_http_process_response_async(FlipperHTTP *fhttp, bool (*http_request)(void), bool (*parse_json)(void))
  664. {
  665. if (!fhttp)
  666. {
  667. FURI_LOG_E(HTTP_TAG, "Failed to get context.");
  668. return false;
  669. }
  670. if (http_request()) // start the async request
  671. {
  672. furi_timer_start(fhttp->get_timeout_timer, TIMEOUT_DURATION_TICKS);
  673. fhttp->state = RECEIVING;
  674. }
  675. else
  676. {
  677. FURI_LOG_E(HTTP_TAG, "Failed to send request");
  678. return false;
  679. }
  680. while (fhttp->state == RECEIVING && furi_timer_is_running(fhttp->get_timeout_timer) > 0)
  681. {
  682. // Wait for the request to be received
  683. furi_delay_ms(100);
  684. }
  685. furi_timer_stop(fhttp->get_timeout_timer);
  686. if (!parse_json()) // parse the JSON before switching to the view (synchonous)
  687. {
  688. FURI_LOG_E(HTTP_TAG, "Failed to parse the JSON...");
  689. return false;
  690. }
  691. return true;
  692. }
  693. /**
  694. * @brief Send a request to the specified URL.
  695. * @return true if the request was successful, false otherwise.
  696. * @param fhttp The FlipperHTTP context
  697. * @param method The HTTP method to use.
  698. * @param url The URL to send the request to.
  699. * @param headers The headers to send with the request.
  700. * @param payload The data to send with the request.
  701. * @note The received data will be handled asynchronously via the callback.
  702. */
  703. bool flipper_http_request(FlipperHTTP *fhttp, HTTPMethod method, const char *url, const char *headers, const char *payload)
  704. {
  705. if (!fhttp)
  706. {
  707. FURI_LOG_E("FlipperHTTP", "flipper_http_request: Failed to get context.");
  708. return false;
  709. }
  710. if (!url)
  711. {
  712. FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_request.");
  713. return false;
  714. }
  715. // Prepare request command
  716. char command[512];
  717. int ret = 0;
  718. switch (method)
  719. {
  720. case GET:
  721. if (headers && strlen(headers) > 0)
  722. ret = snprintf(command, sizeof(command), "[GET/HTTP]{\"url\":\"%s\",\"headers\":%s}", url, headers);
  723. else
  724. ret = snprintf(command, sizeof(command), "[GET]%s", url);
  725. break;
  726. case POST:
  727. if (!headers || !payload)
  728. {
  729. FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_request.");
  730. return false;
  731. }
  732. ret = snprintf(command, sizeof(command), "[POST/HTTP]{\"url\":\"%s\",\"headers\":%s,\"payload\":%s}", url, headers, payload);
  733. break;
  734. case PUT:
  735. if (!headers || !payload)
  736. {
  737. FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_request.");
  738. return false;
  739. }
  740. ret = snprintf(command, sizeof(command), "[PUT/HTTP]{\"url\":\"%s\",\"headers\":%s,\"payload\":%s}", url, headers, payload);
  741. break;
  742. case DELETE:
  743. if (!headers || !payload)
  744. {
  745. FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_request.");
  746. return false;
  747. }
  748. ret = snprintf(command, sizeof(command), "[DELETE/HTTP]{\"url\":\"%s\",\"headers\":%s,\"payload\":%s}", url, headers, payload);
  749. break;
  750. case BYTES:
  751. if (!headers)
  752. {
  753. FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_request.");
  754. return false;
  755. }
  756. if (strlen(fhttp->file_path) == 0)
  757. {
  758. FURI_LOG_E("FlipperHTTP", "File path is not set.");
  759. return false;
  760. }
  761. fhttp->save_received_data = false;
  762. fhttp->is_bytes_request = true;
  763. ret = snprintf(command, sizeof(command), "[GET/BYTES]{\"url\":\"%s\",\"headers\":%s}", url, headers);
  764. break;
  765. case BYTES_POST:
  766. if (!headers || !payload)
  767. {
  768. FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_request.");
  769. return false;
  770. }
  771. if (strlen(fhttp->file_path) == 0)
  772. {
  773. FURI_LOG_E("FlipperHTTP", "File path is not set.");
  774. return false;
  775. }
  776. fhttp->save_received_data = false;
  777. fhttp->is_bytes_request = true;
  778. ret = snprintf(command, sizeof(command), "[POST/BYTES]{\"url\":\"%s\",\"headers\":%s,\"payload\":%s}", url, headers, payload);
  779. break;
  780. }
  781. // check if ret is valid
  782. if (ret < 0 || ret >= (int)sizeof(command))
  783. {
  784. FURI_LOG_E("FlipperHTTP", "Failed to format request command.");
  785. return false;
  786. }
  787. // set method
  788. fhttp->method = method;
  789. // Send request via UART
  790. return flipper_http_send_data(fhttp, command);
  791. }
  792. /**
  793. * @brief Send a command to save WiFi settings.
  794. * @return true if the request was successful, false otherwise.
  795. * @param fhttp The FlipperHTTP context
  796. * @note The received data will be handled asynchronously via the callback.
  797. */
  798. bool flipper_http_save_wifi(FlipperHTTP *fhttp, const char *ssid, const char *password)
  799. {
  800. if (!fhttp)
  801. {
  802. FURI_LOG_E(HTTP_TAG, "Failed to get context.");
  803. return false;
  804. }
  805. if (!ssid || !password)
  806. {
  807. FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_save_wifi.");
  808. return false;
  809. }
  810. char buffer[256];
  811. int ret = snprintf(
  812. buffer, sizeof(buffer), "[WIFI/SAVE]{\"ssid\":\"%s\",\"password\":\"%s\"}", ssid, password);
  813. if (ret < 0 || ret >= (int)sizeof(buffer))
  814. {
  815. FURI_LOG_E("FlipperHTTP", "Failed to format WiFi save command.");
  816. return false;
  817. }
  818. return flipper_http_send_data(fhttp, buffer);
  819. }
  820. /**
  821. * @brief Send a command.
  822. * @return true if the request was successful, false otherwise.
  823. * @param fhttp The FlipperHTTP context
  824. * @param command The command to send.
  825. * @note The received data will be handled asynchronously via the callback.
  826. */
  827. bool flipper_http_send_command(FlipperHTTP *fhttp, HTTPCommand command)
  828. {
  829. if (!fhttp)
  830. {
  831. FURI_LOG_E(HTTP_TAG, "flipper_http_send_command: Failed to get context.");
  832. return false;
  833. }
  834. switch (command)
  835. {
  836. case HTTP_CMD_WIFI_CONNECT:
  837. return flipper_http_send_data(fhttp, "[WIFI/CONNECT]");
  838. case HTTP_CMD_WIFI_DISCONNECT:
  839. return flipper_http_send_data(fhttp, "[WIFI/DISCONNECT]");
  840. case HTTP_CMD_IP_ADDRESS:
  841. return flipper_http_send_data(fhttp, "[IP/ADDRESS]");
  842. case HTTP_CMD_IP_WIFI:
  843. return flipper_http_send_data(fhttp, "[WIFI/IP]");
  844. case HTTP_CMD_SCAN:
  845. return flipper_http_send_data(fhttp, "[WIFI/SCAN]");
  846. case HTTP_CMD_LIST_COMMANDS:
  847. return flipper_http_send_data(fhttp, "[LIST]");
  848. case HTTP_CMD_LED_ON:
  849. return flipper_http_send_data(fhttp, "[LED/ON]");
  850. case HTTP_CMD_LED_OFF:
  851. return flipper_http_send_data(fhttp, "[LED/OFF]");
  852. case HTTP_CMD_PING:
  853. fhttp->state = INACTIVE; // set state as INACTIVE to be made IDLE if PONG is received
  854. return flipper_http_send_data(fhttp, "[PING]");
  855. case HTTP_CMD_REBOOT:
  856. return flipper_http_send_data(fhttp, "[REBOOT]");
  857. default:
  858. FURI_LOG_E(HTTP_TAG, "Invalid command.");
  859. return false;
  860. }
  861. }
  862. /**
  863. * @brief Send data over UART with newline termination.
  864. * @return true if the data was sent successfully, false otherwise.
  865. * @param fhttp The FlipperHTTP context
  866. * @param data The data to send over UART.
  867. * @note The data will be sent over UART with a newline character appended.
  868. */
  869. bool flipper_http_send_data(FlipperHTTP *fhttp, const char *data)
  870. {
  871. if (!fhttp)
  872. {
  873. FURI_LOG_E(HTTP_TAG, "flipper_http_send_data: Failed to get context.");
  874. return false;
  875. }
  876. size_t data_length = strlen(data);
  877. if (data_length == 0)
  878. {
  879. FURI_LOG_E("FlipperHTTP", "Attempted to send empty data.");
  880. return false;
  881. }
  882. // Create a buffer with data + '\n'
  883. size_t send_length = data_length + 1; // +1 for '\n'
  884. if (send_length > 512)
  885. { // Ensure buffer size is sufficient
  886. FURI_LOG_E("FlipperHTTP", "Data too long to send over FHTTP->");
  887. return false;
  888. }
  889. char send_buffer[513]; // 512 + 1 for safety
  890. strncpy(send_buffer, data, 512);
  891. send_buffer[data_length] = '\n'; // Append newline
  892. send_buffer[data_length + 1] = '\0'; // Null-terminate
  893. if (fhttp->state == INACTIVE && ((strstr(send_buffer, "[PING]") == NULL) &&
  894. (strstr(send_buffer, "[WIFI/CONNECT]") == NULL)))
  895. {
  896. FURI_LOG_E("FlipperHTTP", "Cannot send data while INACTIVE.");
  897. fhttp->last_response = "Cannot send data while INACTIVE.";
  898. return false;
  899. }
  900. fhttp->state = SENDING;
  901. furi_hal_serial_tx(fhttp->serial_handle, (const uint8_t *)send_buffer, send_length);
  902. // FURI_LOG_I("FlipperHTTP", "Sent data over UART: %s", send_buffer);
  903. fhttp->state = IDLE;
  904. return true;
  905. }
  906. // Function to set content length and status code
  907. static void set_header(FlipperHTTP *fhttp)
  908. {
  909. // example response: [GET/SUCCESS]{"Status-Code":200,"Content-Length":12528}
  910. if (!fhttp)
  911. {
  912. FURI_LOG_E(HTTP_TAG, "Invalid arguments provided to set_header.");
  913. return;
  914. }
  915. size_t error_size = -1;
  916. // reset values
  917. fhttp->content_length = 0;
  918. fhttp->status_code = 0;
  919. fhttp->bytes_received = 0;
  920. FuriString *furi_string = furi_string_alloc_set_str(fhttp->last_response);
  921. if (!furi_string)
  922. {
  923. FURI_LOG_E(HTTP_TAG, "Failed to allocate memory for furi_string.");
  924. return;
  925. }
  926. size_t status_code_start = furi_string_search_str(furi_string, "\"Status-Code\":", 0);
  927. if (status_code_start != error_size)
  928. {
  929. // trim everything, including the status code and colon
  930. furi_string_right(furi_string, status_code_start + strlen("\"Status-Code\":"));
  931. // find comma (we have this currently: 200,"Content-Length":12528})
  932. size_t comma = furi_string_search_str(furi_string, ",\"Content-Length\":", 0);
  933. if (comma == error_size)
  934. {
  935. FURI_LOG_E(HTTP_TAG, "Failed to find comma in furi_string.");
  936. furi_string_free(furi_string);
  937. return;
  938. }
  939. // set status code
  940. FuriString *status_code_str = furi_string_alloc();
  941. // dest, src, start, length
  942. furi_string_set_n(status_code_str, furi_string, 0, comma);
  943. fhttp->status_code = atoi(furi_string_get_cstr(status_code_str));
  944. furi_string_free(status_code_str);
  945. // trim left to remove everything before the content length
  946. furi_string_right(furi_string, comma + strlen(",\"Content-Length\":"));
  947. // find closing brace (we have this currently: 12528})
  948. size_t closing_brace = furi_string_search_str(furi_string, "}", 0);
  949. if (closing_brace == error_size)
  950. {
  951. FURI_LOG_E(HTTP_TAG, "Failed to find closing brace in furi_string.");
  952. furi_string_free(furi_string);
  953. return;
  954. }
  955. // set content length
  956. FuriString *content_length_str = furi_string_alloc();
  957. // dest, src, start, length
  958. furi_string_set_n(content_length_str, furi_string, 0, closing_brace);
  959. fhttp->content_length = atoi(furi_string_get_cstr(content_length_str));
  960. furi_string_free(content_length_str);
  961. }
  962. // print results
  963. // FURI_LOG_I(HTTP_TAG, "Status Code: %d", fhttp->status_code);
  964. // FURI_LOG_I(HTTP_TAG, "Content Length: %d", fhttp->content_length);
  965. // free the furi_string
  966. furi_string_free(furi_string);
  967. }
  968. // Function to trim leading and trailing spaces and newlines from a constant string
  969. static char *trim(const char *str)
  970. {
  971. const char *end;
  972. char *trimmed_str;
  973. size_t len;
  974. // Trim leading space
  975. while (isspace((unsigned char)*str))
  976. str++;
  977. // All spaces?
  978. if (*str == 0)
  979. return strdup(""); // Return an empty string if all spaces
  980. // Trim trailing space
  981. end = str + strlen(str) - 1;
  982. while (end > str && isspace((unsigned char)*end))
  983. end--;
  984. // Set length for the trimmed string
  985. len = end - str + 1;
  986. // Allocate space for the trimmed string and null terminator
  987. trimmed_str = (char *)malloc(len + 1);
  988. if (trimmed_str == NULL)
  989. {
  990. return NULL; // Handle memory allocation failure
  991. }
  992. // Copy the trimmed part of the string into trimmed_str
  993. strncpy(trimmed_str, str, len);
  994. trimmed_str[len] = '\0'; // Null terminate the string
  995. return trimmed_str;
  996. }
  997. /**
  998. * @brief Callback function to handle received data asynchronously.
  999. * @return void
  1000. * @param line The received line.
  1001. * @param context The FlipperHTTP context.
  1002. * @note The received data will be handled asynchronously via the callback and handles the state of the UART.
  1003. */
  1004. static void flipper_http_rx_callback(const char *line, void *context)
  1005. {
  1006. FlipperHTTP *fhttp = (FlipperHTTP *)context;
  1007. if (!fhttp)
  1008. {
  1009. FURI_LOG_E(HTTP_TAG, "flipper_http_rx_callback: Failed to get context.");
  1010. return;
  1011. }
  1012. if (!line)
  1013. {
  1014. FURI_LOG_E(HTTP_TAG, "Invalid arguments provided to flipper_http_rx_callback.");
  1015. return;
  1016. }
  1017. // Trim the received line to check if it's empty
  1018. char *trimmed_line = trim(line);
  1019. if (trimmed_line != NULL && trimmed_line[0] != '\0')
  1020. {
  1021. // if the line is not [GET/END] or [POST/END] or [PUT/END] or [DELETE/END]
  1022. if (strstr(trimmed_line, "[GET/END]") == NULL &&
  1023. strstr(trimmed_line, "[POST/END]") == NULL &&
  1024. strstr(trimmed_line, "[PUT/END]") == NULL &&
  1025. strstr(trimmed_line, "[DELETE/END]") == NULL)
  1026. {
  1027. strncpy(fhttp->last_response, trimmed_line, RX_BUF_SIZE);
  1028. }
  1029. }
  1030. free(trimmed_line); // Free the allocated memory for trimmed_line
  1031. if (fhttp->state != INACTIVE && fhttp->state != ISSUE)
  1032. {
  1033. fhttp->state = RECEIVING;
  1034. }
  1035. // Uncomment below line to log the data received over UART
  1036. // FURI_LOG_I(HTTP_TAG, "Received UART line: %s", line);
  1037. // Check if we've started receiving data from a GET request
  1038. if (fhttp->started_receiving && (fhttp->method == GET || fhttp->method == BYTES))
  1039. {
  1040. // Restart the timeout timer each time new data is received
  1041. furi_timer_restart(fhttp->get_timeout_timer, TIMEOUT_DURATION_TICKS);
  1042. if (strstr(line, "[GET/END]") != NULL)
  1043. {
  1044. FURI_LOG_I(HTTP_TAG, "GET request completed.");
  1045. // Stop the timer since we've completed the GET request
  1046. furi_timer_stop(fhttp->get_timeout_timer);
  1047. fhttp->started_receiving = false;
  1048. fhttp->just_started = false;
  1049. fhttp->state = IDLE;
  1050. fhttp->save_bytes = false;
  1051. fhttp->save_received_data = false;
  1052. if (fhttp->is_bytes_request)
  1053. {
  1054. // Search for the binary marker `[GET/END]` in the file buffer
  1055. const char marker[] = "[GET/END]";
  1056. const size_t marker_len = sizeof(marker) - 1; // Exclude null terminator
  1057. for (size_t i = 0; i <= fhttp->file_buffer_len - marker_len; i++)
  1058. {
  1059. // Check if the marker is found
  1060. if (memcmp(&fhttp->file_buffer[i], marker, marker_len) == 0)
  1061. {
  1062. // Remove the marker by shifting the remaining data left
  1063. size_t remaining_len = fhttp->file_buffer_len - (i + marker_len);
  1064. memmove(&fhttp->file_buffer[i], &fhttp->file_buffer[i + marker_len], remaining_len);
  1065. fhttp->file_buffer_len -= marker_len;
  1066. break;
  1067. }
  1068. }
  1069. // If there is data left in the buffer, append it to the file
  1070. if (fhttp->file_buffer_len > 0)
  1071. {
  1072. if (!flipper_http_append_to_file(fhttp->file_buffer, fhttp->file_buffer_len, false, fhttp->file_path))
  1073. {
  1074. FURI_LOG_E(HTTP_TAG, "Failed to append data to file.");
  1075. }
  1076. fhttp->file_buffer_len = 0;
  1077. }
  1078. }
  1079. fhttp->is_bytes_request = false;
  1080. return;
  1081. }
  1082. // Append the new line to the existing data
  1083. if (fhttp->save_received_data &&
  1084. !flipper_http_append_to_file(
  1085. line, strlen(line), !fhttp->just_started, fhttp->file_path))
  1086. {
  1087. FURI_LOG_E(HTTP_TAG, "Failed to append data to file.");
  1088. fhttp->started_receiving = false;
  1089. fhttp->just_started = false;
  1090. fhttp->state = IDLE;
  1091. return;
  1092. }
  1093. if (!fhttp->just_started)
  1094. {
  1095. fhttp->just_started = true;
  1096. }
  1097. return;
  1098. }
  1099. // Check if we've started receiving data from a POST request
  1100. else if (fhttp->started_receiving && (fhttp->method == POST || fhttp->method == BYTES_POST))
  1101. {
  1102. // Restart the timeout timer each time new data is received
  1103. furi_timer_restart(fhttp->get_timeout_timer, TIMEOUT_DURATION_TICKS);
  1104. if (strstr(line, "[POST/END]") != NULL)
  1105. {
  1106. FURI_LOG_I(HTTP_TAG, "POST request completed.");
  1107. // Stop the timer since we've completed the POST request
  1108. furi_timer_stop(fhttp->get_timeout_timer);
  1109. fhttp->started_receiving = false;
  1110. fhttp->just_started = false;
  1111. fhttp->state = IDLE;
  1112. fhttp->save_bytes = false;
  1113. fhttp->save_received_data = false;
  1114. if (fhttp->is_bytes_request)
  1115. {
  1116. // Search for the binary marker `[POST/END]` in the file buffer
  1117. const char marker[] = "[POST/END]";
  1118. const size_t marker_len = sizeof(marker) - 1; // Exclude null terminator
  1119. for (size_t i = 0; i <= fhttp->file_buffer_len - marker_len; i++)
  1120. {
  1121. // Check if the marker is found
  1122. if (memcmp(&fhttp->file_buffer[i], marker, marker_len) == 0)
  1123. {
  1124. // Remove the marker by shifting the remaining data left
  1125. size_t remaining_len = fhttp->file_buffer_len - (i + marker_len);
  1126. memmove(&fhttp->file_buffer[i], &fhttp->file_buffer[i + marker_len], remaining_len);
  1127. fhttp->file_buffer_len -= marker_len;
  1128. break;
  1129. }
  1130. }
  1131. // If there is data left in the buffer, append it to the file
  1132. if (fhttp->file_buffer_len > 0)
  1133. {
  1134. if (!flipper_http_append_to_file(fhttp->file_buffer, fhttp->file_buffer_len, false, fhttp->file_path))
  1135. {
  1136. FURI_LOG_E(HTTP_TAG, "Failed to append data to file.");
  1137. }
  1138. fhttp->file_buffer_len = 0;
  1139. }
  1140. }
  1141. fhttp->is_bytes_request = false;
  1142. return;
  1143. }
  1144. // Append the new line to the existing data
  1145. if (fhttp->save_received_data &&
  1146. !flipper_http_append_to_file(
  1147. line, strlen(line), !fhttp->just_started, fhttp->file_path))
  1148. {
  1149. FURI_LOG_E(HTTP_TAG, "Failed to append data to file.");
  1150. fhttp->started_receiving = false;
  1151. fhttp->just_started = false;
  1152. fhttp->state = IDLE;
  1153. return;
  1154. }
  1155. if (!fhttp->just_started)
  1156. {
  1157. fhttp->just_started = true;
  1158. }
  1159. return;
  1160. }
  1161. // Check if we've started receiving data from a PUT request
  1162. else if (fhttp->started_receiving && fhttp->method == PUT)
  1163. {
  1164. // Restart the timeout timer each time new data is received
  1165. furi_timer_restart(fhttp->get_timeout_timer, TIMEOUT_DURATION_TICKS);
  1166. if (strstr(line, "[PUT/END]") != NULL)
  1167. {
  1168. FURI_LOG_I(HTTP_TAG, "PUT request completed.");
  1169. // Stop the timer since we've completed the PUT request
  1170. furi_timer_stop(fhttp->get_timeout_timer);
  1171. fhttp->started_receiving = false;
  1172. fhttp->just_started = false;
  1173. fhttp->state = IDLE;
  1174. fhttp->save_bytes = false;
  1175. fhttp->is_bytes_request = false;
  1176. fhttp->save_received_data = false;
  1177. return;
  1178. }
  1179. // Append the new line to the existing data
  1180. if (fhttp->save_received_data &&
  1181. !flipper_http_append_to_file(
  1182. line, strlen(line), !fhttp->just_started, fhttp->file_path))
  1183. {
  1184. FURI_LOG_E(HTTP_TAG, "Failed to append data to file.");
  1185. fhttp->started_receiving = false;
  1186. fhttp->just_started = false;
  1187. fhttp->state = IDLE;
  1188. return;
  1189. }
  1190. if (!fhttp->just_started)
  1191. {
  1192. fhttp->just_started = true;
  1193. }
  1194. return;
  1195. }
  1196. // Check if we've started receiving data from a DELETE request
  1197. else if (fhttp->started_receiving && fhttp->method == DELETE)
  1198. {
  1199. // Restart the timeout timer each time new data is received
  1200. furi_timer_restart(fhttp->get_timeout_timer, TIMEOUT_DURATION_TICKS);
  1201. if (strstr(line, "[DELETE/END]") != NULL)
  1202. {
  1203. FURI_LOG_I(HTTP_TAG, "DELETE request completed.");
  1204. // Stop the timer since we've completed the DELETE request
  1205. furi_timer_stop(fhttp->get_timeout_timer);
  1206. fhttp->started_receiving = false;
  1207. fhttp->just_started = false;
  1208. fhttp->state = IDLE;
  1209. fhttp->save_bytes = false;
  1210. fhttp->is_bytes_request = false;
  1211. fhttp->save_received_data = false;
  1212. return;
  1213. }
  1214. // Append the new line to the existing data
  1215. if (fhttp->save_received_data &&
  1216. !flipper_http_append_to_file(
  1217. line, strlen(line), !fhttp->just_started, fhttp->file_path))
  1218. {
  1219. FURI_LOG_E(HTTP_TAG, "Failed to append data to file.");
  1220. fhttp->started_receiving = false;
  1221. fhttp->just_started = false;
  1222. fhttp->state = IDLE;
  1223. return;
  1224. }
  1225. if (!fhttp->just_started)
  1226. {
  1227. fhttp->just_started = true;
  1228. }
  1229. return;
  1230. }
  1231. // Handle different types of responses
  1232. if (strstr(line, "[SUCCESS]") != NULL || strstr(line, "[CONNECTED]") != NULL)
  1233. {
  1234. FURI_LOG_I(HTTP_TAG, "Operation succeeded.");
  1235. }
  1236. else if (strstr(line, "[INFO]") != NULL)
  1237. {
  1238. FURI_LOG_I(HTTP_TAG, "Received info: %s", line);
  1239. if (fhttp->state == INACTIVE && strstr(line, "[INFO] Already connected to Wifi.") != NULL)
  1240. {
  1241. fhttp->state = IDLE;
  1242. }
  1243. }
  1244. else if (strstr(line, "[GET/SUCCESS]") != NULL)
  1245. {
  1246. FURI_LOG_I(HTTP_TAG, "GET request succeeded.");
  1247. furi_timer_start(fhttp->get_timeout_timer, TIMEOUT_DURATION_TICKS);
  1248. fhttp->started_receiving = true;
  1249. fhttp->state = RECEIVING;
  1250. // for GET request, save data only if it's a bytes request
  1251. fhttp->save_bytes = fhttp->is_bytes_request;
  1252. fhttp->just_started_bytes = true;
  1253. fhttp->file_buffer_len = 0;
  1254. // set header
  1255. set_header(fhttp);
  1256. return;
  1257. }
  1258. else if (strstr(line, "[POST/SUCCESS]") != NULL)
  1259. {
  1260. FURI_LOG_I(HTTP_TAG, "POST request succeeded.");
  1261. furi_timer_start(fhttp->get_timeout_timer, TIMEOUT_DURATION_TICKS);
  1262. fhttp->started_receiving = true;
  1263. fhttp->state = RECEIVING;
  1264. // for POST request, save data only if it's a bytes request
  1265. fhttp->save_bytes = fhttp->is_bytes_request;
  1266. fhttp->just_started_bytes = true;
  1267. fhttp->file_buffer_len = 0;
  1268. // set header
  1269. set_header(fhttp);
  1270. return;
  1271. }
  1272. else if (strstr(line, "[PUT/SUCCESS]") != NULL)
  1273. {
  1274. FURI_LOG_I(HTTP_TAG, "PUT request succeeded.");
  1275. furi_timer_start(fhttp->get_timeout_timer, TIMEOUT_DURATION_TICKS);
  1276. fhttp->started_receiving = true;
  1277. fhttp->state = RECEIVING;
  1278. // set header
  1279. set_header(fhttp);
  1280. return;
  1281. }
  1282. else if (strstr(line, "[DELETE/SUCCESS]") != NULL)
  1283. {
  1284. FURI_LOG_I(HTTP_TAG, "DELETE request succeeded.");
  1285. furi_timer_start(fhttp->get_timeout_timer, TIMEOUT_DURATION_TICKS);
  1286. fhttp->started_receiving = true;
  1287. fhttp->state = RECEIVING;
  1288. // set header
  1289. set_header(fhttp);
  1290. return;
  1291. }
  1292. else if (strstr(line, "[DISCONNECTED]") != NULL)
  1293. {
  1294. FURI_LOG_I(HTTP_TAG, "WiFi disconnected successfully.");
  1295. }
  1296. else if (strstr(line, "[ERROR]") != NULL)
  1297. {
  1298. FURI_LOG_E(HTTP_TAG, "Received error: %s", line);
  1299. fhttp->state = ISSUE;
  1300. return;
  1301. }
  1302. else if (strstr(line, "[PONG]") != NULL)
  1303. {
  1304. FURI_LOG_I(HTTP_TAG, "Received PONG response: Wifi Dev Board is still alive.");
  1305. // send command to connect to WiFi
  1306. if (fhttp->state == INACTIVE)
  1307. {
  1308. fhttp->state = IDLE;
  1309. return;
  1310. }
  1311. }
  1312. if (fhttp->state == INACTIVE && strstr(line, "[PONG]") != NULL)
  1313. {
  1314. fhttp->state = IDLE;
  1315. }
  1316. else if (fhttp->state == INACTIVE && strstr(line, "[PONG]") == NULL)
  1317. {
  1318. fhttp->state = INACTIVE;
  1319. }
  1320. else
  1321. {
  1322. fhttp->state = IDLE;
  1323. }
  1324. }
  1325. /**
  1326. * @brief Send a request to the specified URL to start a WebSocket connection.
  1327. * @return true if the request was successful, false otherwise.
  1328. * @param fhttp The FlipperHTTP context
  1329. * @param url The URL to send the WebSocket request to.
  1330. * @param port The port to connect to
  1331. * @param headers The headers to send with the WebSocket request
  1332. * @note The received data will be handled asynchronously via the callback.
  1333. */
  1334. bool flipper_http_websocket_start(FlipperHTTP *fhttp, const char *url, uint16_t port, const char *headers)
  1335. {
  1336. if (!fhttp)
  1337. {
  1338. FURI_LOG_E(HTTP_TAG, "flipper_http_websocket_start: Failed to get context.");
  1339. return false;
  1340. }
  1341. if (!url || !headers)
  1342. {
  1343. FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_websocket_start.");
  1344. return false;
  1345. }
  1346. // Prepare WebSocket request command with headers
  1347. char command[512];
  1348. int ret = snprintf(
  1349. command,
  1350. sizeof(command),
  1351. "[SOCKET/START]{\"url\":\"%s\",\"port\":%d,\"headers\":%s}",
  1352. url,
  1353. port,
  1354. headers);
  1355. if (ret < 0 || ret >= (int)sizeof(command))
  1356. {
  1357. FURI_LOG_E("FlipperHTTP", "Failed to format WebSocket start command with headers.");
  1358. return false;
  1359. }
  1360. // Send WebSocket request via UART
  1361. return flipper_http_send_data(fhttp, command);
  1362. }
  1363. /**
  1364. * @brief Send a request to stop the WebSocket connection.
  1365. * @return true if the request was successful, false otherwise.
  1366. * @param fhttp The FlipperHTTP context
  1367. * @note The received data will be handled asynchronously via the callback.
  1368. */
  1369. bool flipper_http_websocket_stop(FlipperHTTP *fhttp)
  1370. {
  1371. if (!fhttp)
  1372. {
  1373. FURI_LOG_E(HTTP_TAG, "flipper_http_websocket_stop: Failed to get context.");
  1374. return false;
  1375. }
  1376. return flipper_http_send_data(fhttp, "[SOCKET/STOP]");
  1377. }