rpc_test.c 53 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488
  1. #include "flipper.pb.h"
  2. #include "furi_hal_delay.h"
  3. #include "furi/check.h"
  4. #include "furi/record.h"
  5. #include "pb_decode.h"
  6. #include "rpc/rpc_i.h"
  7. #include "storage.pb.h"
  8. #include "storage/filesystem_api_defines.h"
  9. #include "storage/storage.h"
  10. #include <furi.h>
  11. #include "../minunit.h"
  12. #include <stdint.h>
  13. #include <stream_buffer.h>
  14. #include <pb.h>
  15. #include <pb_encode.h>
  16. #include <m-list.h>
  17. #include <lib/toolbox/md5.h>
  18. #include <cli/cli.h>
  19. #include <loader/loader.h>
  20. #include <protobuf_version.h>
  21. LIST_DEF(MsgList, PB_Main, M_POD_OPLIST)
  22. #define M_OPL_MsgList_t() LIST_OPLIST(MsgList)
  23. /* MinUnit test framework doesn't allow passing context into tests,
  24. * so we have to use global variables
  25. */
  26. static Rpc* rpc = NULL;
  27. static RpcSession* session = NULL;
  28. static StreamBufferHandle_t output_stream = NULL;
  29. static uint32_t command_id = 0;
  30. #define TAG "UnitTestsRpc"
  31. #define MAX_RECEIVE_OUTPUT_TIMEOUT 3000
  32. #define MAX_NAME_LENGTH 255
  33. #define MAX_DATA_SIZE 512 // have to be exact as in rpc_storage.c
  34. #define TEST_DIR TEST_DIR_NAME "/"
  35. #define TEST_DIR_NAME "/ext/unit_tests_tmp"
  36. #define MD5SUM_SIZE 16
  37. #define PING_REQUEST 0
  38. #define PING_RESPONSE 1
  39. #define WRITE_REQUEST 0
  40. #define READ_RESPONSE 1
  41. #define DEBUG_PRINT 0
  42. #define BYTES(x) (x), sizeof(x)
  43. static void output_bytes_callback(void* ctx, uint8_t* got_bytes, size_t got_size);
  44. static void clean_directory(Storage* fs_api, const char* clean_dir);
  45. static void
  46. test_rpc_add_empty_to_list(MsgList_t msg_list, PB_CommandStatus status, uint32_t command_id);
  47. static void test_rpc_encode_and_feed(MsgList_t msg_list);
  48. static void test_rpc_encode_and_feed_one(PB_Main* request);
  49. static void test_rpc_compare_messages(PB_Main* result, PB_Main* expected);
  50. static void test_rpc_decode_and_compare(MsgList_t expected_msg_list);
  51. static void test_rpc_free_msg_list(MsgList_t msg_list);
  52. static void test_rpc_setup(void) {
  53. furi_assert(!rpc);
  54. furi_assert(!session);
  55. furi_assert(!output_stream);
  56. rpc = furi_record_open("rpc");
  57. for(int i = 0; !session && (i < 10000); ++i) {
  58. session = rpc_session_open(rpc);
  59. delay(1);
  60. }
  61. furi_assert(session);
  62. output_stream = xStreamBufferCreate(1000, 1);
  63. mu_assert(session, "failed to start session");
  64. rpc_session_set_send_bytes_callback(session, output_bytes_callback);
  65. rpc_session_set_context(session, output_stream);
  66. }
  67. static void test_rpc_teardown(void) {
  68. rpc_session_close(session);
  69. furi_record_close("rpc");
  70. vStreamBufferDelete(output_stream);
  71. ++command_id;
  72. output_stream = NULL;
  73. rpc = NULL;
  74. session = NULL;
  75. }
  76. static void test_rpc_storage_setup(void) {
  77. test_rpc_setup();
  78. Storage* fs_api = furi_record_open("storage");
  79. clean_directory(fs_api, TEST_DIR_NAME);
  80. furi_record_close("storage");
  81. }
  82. static void test_rpc_storage_teardown(void) {
  83. Storage* fs_api = furi_record_open("storage");
  84. clean_directory(fs_api, TEST_DIR_NAME);
  85. furi_record_close("storage");
  86. test_rpc_teardown();
  87. }
  88. static void clean_directory(Storage* fs_api, const char* clean_dir) {
  89. furi_assert(fs_api);
  90. furi_assert(clean_dir);
  91. File* dir = storage_file_alloc(fs_api);
  92. if(storage_dir_open(dir, clean_dir)) {
  93. FileInfo fileinfo;
  94. char* name = furi_alloc(MAX_NAME_LENGTH + 1);
  95. while(storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH)) {
  96. char* fullname = furi_alloc(strlen(clean_dir) + strlen(name) + 1 + 1);
  97. sprintf(fullname, "%s/%s", clean_dir, name);
  98. if(fileinfo.flags & FSF_DIRECTORY) {
  99. clean_directory(fs_api, fullname);
  100. }
  101. FS_Error error = storage_common_remove(fs_api, fullname);
  102. furi_check(error == FSE_OK);
  103. free(fullname);
  104. }
  105. free(name);
  106. } else {
  107. FS_Error error = storage_common_mkdir(fs_api, clean_dir);
  108. (void)error;
  109. furi_assert(error == FSE_OK);
  110. }
  111. storage_dir_close(dir);
  112. storage_file_free(dir);
  113. }
  114. static void test_rpc_print_message_list(MsgList_t msg_list) {
  115. #if DEBUG_PRINT
  116. MsgList_reverse(msg_list);
  117. for
  118. M_EACH(msg, msg_list, MsgList_t) {
  119. rpc_print_message(msg);
  120. }
  121. MsgList_reverse(msg_list);
  122. #endif
  123. }
  124. static PB_CommandStatus test_rpc_storage_get_file_error(File* file) {
  125. FS_Error fs_error = storage_file_get_error(file);
  126. PB_CommandStatus pb_error;
  127. switch(fs_error) {
  128. case FSE_OK:
  129. pb_error = PB_CommandStatus_OK;
  130. break;
  131. case FSE_INVALID_NAME:
  132. pb_error = PB_CommandStatus_ERROR_STORAGE_INVALID_NAME;
  133. break;
  134. case FSE_INVALID_PARAMETER:
  135. pb_error = PB_CommandStatus_ERROR_STORAGE_INVALID_PARAMETER;
  136. break;
  137. case FSE_INTERNAL:
  138. pb_error = PB_CommandStatus_ERROR_STORAGE_INTERNAL;
  139. break;
  140. case FSE_ALREADY_OPEN:
  141. pb_error = PB_CommandStatus_ERROR_STORAGE_ALREADY_OPEN;
  142. break;
  143. case FSE_DENIED:
  144. pb_error = PB_CommandStatus_ERROR_STORAGE_DENIED;
  145. break;
  146. case FSE_EXIST:
  147. pb_error = PB_CommandStatus_ERROR_STORAGE_EXIST;
  148. break;
  149. case FSE_NOT_EXIST:
  150. pb_error = PB_CommandStatus_ERROR_STORAGE_NOT_EXIST;
  151. break;
  152. case FSE_NOT_READY:
  153. pb_error = PB_CommandStatus_ERROR_STORAGE_NOT_READY;
  154. break;
  155. case FSE_NOT_IMPLEMENTED:
  156. pb_error = PB_CommandStatus_ERROR_STORAGE_NOT_IMPLEMENTED;
  157. break;
  158. default:
  159. pb_error = PB_CommandStatus_ERROR;
  160. break;
  161. }
  162. return pb_error;
  163. }
  164. static void output_bytes_callback(void* ctx, uint8_t* got_bytes, size_t got_size) {
  165. StreamBufferHandle_t stream_buffer = ctx;
  166. size_t bytes_sent = xStreamBufferSend(stream_buffer, got_bytes, got_size, osWaitForever);
  167. (void)bytes_sent;
  168. furi_assert(bytes_sent == got_size);
  169. }
  170. static void test_rpc_add_ping_to_list(MsgList_t msg_list, bool request, uint32_t command_id) {
  171. PB_Main* response = MsgList_push_new(msg_list);
  172. response->command_id = command_id;
  173. response->command_status = PB_CommandStatus_OK;
  174. response->cb_content.funcs.encode = NULL;
  175. response->has_next = false;
  176. response->which_content = (request == PING_REQUEST) ? PB_Main_system_ping_request_tag :
  177. PB_Main_system_ping_response_tag;
  178. }
  179. static void test_rpc_create_simple_message(
  180. PB_Main* message,
  181. uint16_t tag,
  182. const char* str,
  183. uint32_t command_id) {
  184. furi_assert(message);
  185. char* str_copy = NULL;
  186. if(str) {
  187. str_copy = strdup(str);
  188. }
  189. message->command_id = command_id;
  190. message->command_status = PB_CommandStatus_OK;
  191. message->cb_content.funcs.encode = NULL;
  192. message->which_content = tag;
  193. message->has_next = false;
  194. switch(tag) {
  195. case PB_Main_storage_info_request_tag:
  196. message->content.storage_info_request.path = str_copy;
  197. break;
  198. case PB_Main_storage_stat_request_tag:
  199. message->content.storage_stat_request.path = str_copy;
  200. break;
  201. case PB_Main_storage_list_request_tag:
  202. message->content.storage_list_request.path = str_copy;
  203. break;
  204. case PB_Main_storage_mkdir_request_tag:
  205. message->content.storage_mkdir_request.path = str_copy;
  206. break;
  207. case PB_Main_storage_read_request_tag:
  208. message->content.storage_read_request.path = str_copy;
  209. break;
  210. case PB_Main_storage_delete_request_tag:
  211. message->content.storage_delete_request.path = str_copy;
  212. break;
  213. case PB_Main_storage_md5sum_request_tag:
  214. message->content.storage_md5sum_request.path = str_copy;
  215. break;
  216. case PB_Main_storage_md5sum_response_tag: {
  217. char* md5sum = message->content.storage_md5sum_response.md5sum;
  218. size_t md5sum_size = sizeof(message->content.storage_md5sum_response.md5sum);
  219. furi_assert((strlen(str) + 1) <= md5sum_size);
  220. memcpy(md5sum, str_copy, md5sum_size);
  221. free(str_copy);
  222. break;
  223. }
  224. default:
  225. furi_assert(0);
  226. break;
  227. }
  228. }
  229. static void test_rpc_add_read_or_write_to_list(
  230. MsgList_t msg_list,
  231. bool write,
  232. const char* path,
  233. const uint8_t* pattern,
  234. size_t pattern_size,
  235. size_t pattern_repeats,
  236. uint32_t command_id) {
  237. furi_assert(pattern_repeats > 0);
  238. do {
  239. PB_Main* request = MsgList_push_new(msg_list);
  240. PB_Storage_File* msg_file = NULL;
  241. request->command_id = command_id;
  242. request->command_status = PB_CommandStatus_OK;
  243. if(write == WRITE_REQUEST) {
  244. request->content.storage_write_request.path = strdup(path);
  245. request->which_content = PB_Main_storage_write_request_tag;
  246. request->content.storage_write_request.has_file = true;
  247. msg_file = &request->content.storage_write_request.file;
  248. } else {
  249. request->which_content = PB_Main_storage_read_response_tag;
  250. request->content.storage_read_response.has_file = true;
  251. msg_file = &request->content.storage_read_response.file;
  252. }
  253. msg_file->data = furi_alloc(PB_BYTES_ARRAY_T_ALLOCSIZE(pattern_size));
  254. msg_file->data->size = pattern_size;
  255. memcpy(msg_file->data->bytes, pattern, pattern_size);
  256. --pattern_repeats;
  257. request->has_next = (pattern_repeats > 0);
  258. } while(pattern_repeats);
  259. }
  260. static void test_rpc_encode_and_feed_one(PB_Main* request) {
  261. furi_assert(request);
  262. pb_ostream_t ostream = PB_OSTREAM_SIZING;
  263. bool result = pb_encode_ex(&ostream, &PB_Main_msg, request, PB_ENCODE_DELIMITED);
  264. furi_check(result && ostream.bytes_written);
  265. uint8_t* buffer = furi_alloc(ostream.bytes_written);
  266. ostream = pb_ostream_from_buffer(buffer, ostream.bytes_written);
  267. pb_encode_ex(&ostream, &PB_Main_msg, request, PB_ENCODE_DELIMITED);
  268. size_t bytes_left = ostream.bytes_written;
  269. uint8_t* buffer_ptr = buffer;
  270. do {
  271. size_t bytes_sent = rpc_session_feed(session, buffer_ptr, bytes_left, 1000);
  272. mu_check(bytes_sent > 0);
  273. bytes_left -= bytes_sent;
  274. buffer_ptr += bytes_sent;
  275. } while(bytes_left);
  276. free(buffer);
  277. pb_release(&PB_Main_msg, request);
  278. }
  279. static void test_rpc_encode_and_feed(MsgList_t msg_list) {
  280. MsgList_reverse(msg_list);
  281. for
  282. M_EACH(request, msg_list, MsgList_t) {
  283. test_rpc_encode_and_feed_one(request);
  284. }
  285. MsgList_reverse(msg_list);
  286. }
  287. static void
  288. test_rpc_compare_file(PB_Storage_File* result_msg_file, PB_Storage_File* expected_msg_file) {
  289. mu_check(!result_msg_file->name == !expected_msg_file->name);
  290. if(result_msg_file->name) {
  291. mu_check(!strcmp(result_msg_file->name, expected_msg_file->name));
  292. }
  293. mu_check(result_msg_file->size == expected_msg_file->size);
  294. mu_check(result_msg_file->type == expected_msg_file->type);
  295. mu_check(!result_msg_file->data == !expected_msg_file->data);
  296. mu_check(result_msg_file->data->size == expected_msg_file->data->size);
  297. for(int i = 0; i < result_msg_file->data->size; ++i) {
  298. mu_check(result_msg_file->data->bytes[i] == expected_msg_file->data->bytes[i]);
  299. }
  300. }
  301. static void test_rpc_compare_messages(PB_Main* result, PB_Main* expected) {
  302. mu_check(result->command_id == expected->command_id);
  303. mu_check(result->command_status == expected->command_status);
  304. mu_check(result->has_next == expected->has_next);
  305. mu_check(result->which_content == expected->which_content);
  306. if(result->command_status != PB_CommandStatus_OK) {
  307. mu_check(result->which_content == PB_Main_empty_tag);
  308. }
  309. switch(result->which_content) {
  310. case PB_Main_empty_tag:
  311. case PB_Main_system_ping_response_tag:
  312. /* nothing to check */
  313. break;
  314. case PB_Main_system_ping_request_tag:
  315. case PB_Main_storage_list_request_tag:
  316. case PB_Main_storage_read_request_tag:
  317. case PB_Main_storage_write_request_tag:
  318. case PB_Main_storage_delete_request_tag:
  319. case PB_Main_storage_mkdir_request_tag:
  320. case PB_Main_storage_md5sum_request_tag:
  321. /* rpc doesn't send it */
  322. mu_check(0);
  323. break;
  324. case PB_Main_app_lock_status_response_tag: {
  325. bool result_locked = result->content.app_lock_status_response.locked;
  326. bool expected_locked = expected->content.app_lock_status_response.locked;
  327. mu_check(result_locked == expected_locked);
  328. break;
  329. }
  330. case PB_Main_storage_info_response_tag: {
  331. uint64_t result_total_space = result->content.storage_info_response.total_space;
  332. uint64_t expected_total_space = expected->content.storage_info_response.total_space;
  333. mu_check(result_total_space == expected_total_space);
  334. uint64_t result_free_space = result->content.storage_info_response.free_space;
  335. uint64_t expected_free_space = expected->content.storage_info_response.free_space;
  336. mu_check(result_free_space == expected_free_space);
  337. } break;
  338. case PB_Main_storage_stat_response_tag: {
  339. bool result_has_msg_file = result->content.storage_stat_response.has_file;
  340. bool expected_has_msg_file = expected->content.storage_stat_response.has_file;
  341. mu_check(result_has_msg_file == expected_has_msg_file);
  342. if(result_has_msg_file) {
  343. PB_Storage_File* result_msg_file = &result->content.storage_stat_response.file;
  344. PB_Storage_File* expected_msg_file = &expected->content.storage_stat_response.file;
  345. test_rpc_compare_file(result_msg_file, expected_msg_file);
  346. } else {
  347. mu_check(0);
  348. }
  349. } break;
  350. case PB_Main_storage_read_response_tag: {
  351. bool result_has_msg_file = result->content.storage_read_response.has_file;
  352. bool expected_has_msg_file = expected->content.storage_read_response.has_file;
  353. mu_check(result_has_msg_file == expected_has_msg_file);
  354. if(result_has_msg_file) {
  355. PB_Storage_File* result_msg_file = &result->content.storage_read_response.file;
  356. PB_Storage_File* expected_msg_file = &expected->content.storage_read_response.file;
  357. test_rpc_compare_file(result_msg_file, expected_msg_file);
  358. } else {
  359. mu_check(0);
  360. }
  361. } break;
  362. case PB_Main_storage_list_response_tag: {
  363. size_t expected_msg_files = expected->content.storage_list_response.file_count;
  364. size_t result_msg_files = result->content.storage_list_response.file_count;
  365. mu_check(result_msg_files == expected_msg_files);
  366. for(int i = 0; i < expected_msg_files; ++i) {
  367. PB_Storage_File* result_msg_file = &result->content.storage_list_response.file[i];
  368. PB_Storage_File* expected_msg_file = &expected->content.storage_list_response.file[i];
  369. test_rpc_compare_file(result_msg_file, expected_msg_file);
  370. }
  371. break;
  372. }
  373. case PB_Main_storage_md5sum_response_tag: {
  374. char* result_md5sum = result->content.storage_md5sum_response.md5sum;
  375. char* expected_md5sum = expected->content.storage_md5sum_response.md5sum;
  376. mu_check(!strcmp(result_md5sum, expected_md5sum));
  377. break;
  378. }
  379. case PB_Main_system_protobuf_version_response_tag: {
  380. uint32_t major_version_expected = expected->content.system_protobuf_version_response.major;
  381. uint32_t minor_version_expected = expected->content.system_protobuf_version_response.minor;
  382. uint32_t major_version_result = result->content.system_protobuf_version_response.major;
  383. uint32_t minor_version_result = result->content.system_protobuf_version_response.minor;
  384. mu_check(major_version_expected == major_version_result);
  385. mu_check(minor_version_expected == minor_version_result);
  386. break;
  387. }
  388. default:
  389. furi_assert(0);
  390. break;
  391. }
  392. }
  393. static bool test_rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) {
  394. StreamBufferHandle_t stream_buffer = istream->state;
  395. size_t bytes_received = 0;
  396. bytes_received = xStreamBufferReceive(stream_buffer, buf, count, MAX_RECEIVE_OUTPUT_TIMEOUT);
  397. return (count == bytes_received);
  398. }
  399. static void
  400. test_rpc_storage_list_create_expected_list_root(MsgList_t msg_list, uint32_t command_id) {
  401. PB_Main* message = MsgList_push_new(msg_list);
  402. message->has_next = false;
  403. message->cb_content.funcs.encode = NULL;
  404. message->command_id = command_id;
  405. message->which_content = PB_Main_storage_list_response_tag;
  406. message->content.storage_list_response.file_count = 3;
  407. message->content.storage_list_response.file[0].data = NULL;
  408. message->content.storage_list_response.file[1].data = NULL;
  409. message->content.storage_list_response.file[2].data = NULL;
  410. message->content.storage_list_response.file[0].size = 0;
  411. message->content.storage_list_response.file[1].size = 0;
  412. message->content.storage_list_response.file[2].size = 0;
  413. message->content.storage_list_response.file[0].type = PB_Storage_File_FileType_DIR;
  414. message->content.storage_list_response.file[1].type = PB_Storage_File_FileType_DIR;
  415. message->content.storage_list_response.file[2].type = PB_Storage_File_FileType_DIR;
  416. char* str = furi_alloc(4);
  417. strcpy(str, "any");
  418. message->content.storage_list_response.file[0].name = str;
  419. str = furi_alloc(4);
  420. strcpy(str, "int");
  421. message->content.storage_list_response.file[1].name = str;
  422. str = furi_alloc(4);
  423. strcpy(str, "ext");
  424. message->content.storage_list_response.file[2].name = str;
  425. }
  426. static void test_rpc_storage_list_create_expected_list(
  427. MsgList_t msg_list,
  428. const char* path,
  429. uint32_t command_id) {
  430. Storage* fs_api = furi_record_open("storage");
  431. File* dir = storage_file_alloc(fs_api);
  432. PB_Main response = {
  433. .command_id = command_id,
  434. .has_next = false,
  435. .which_content = PB_Main_storage_list_response_tag,
  436. /* other fields (e.g. msg_files ptrs) explicitly initialized by 0 */
  437. };
  438. PB_Storage_ListResponse* list = &response.content.storage_list_response;
  439. bool finish = false;
  440. int i = 0;
  441. if(storage_dir_open(dir, path)) {
  442. response.command_status = PB_CommandStatus_OK;
  443. } else {
  444. response.command_status = test_rpc_storage_get_file_error(dir);
  445. response.which_content = PB_Main_empty_tag;
  446. finish = true;
  447. }
  448. while(!finish) {
  449. FileInfo fileinfo;
  450. char* name = furi_alloc(MAX_NAME_LENGTH + 1);
  451. if(storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH)) {
  452. if(i == COUNT_OF(list->file)) {
  453. list->file_count = i;
  454. response.has_next = true;
  455. MsgList_push_back(msg_list, response);
  456. i = 0;
  457. }
  458. list->file[i].type = (fileinfo.flags & FSF_DIRECTORY) ? PB_Storage_File_FileType_DIR :
  459. PB_Storage_File_FileType_FILE;
  460. list->file[i].size = fileinfo.size;
  461. list->file[i].data = NULL;
  462. /* memory free inside rpc_encode_and_send() -> pb_release() */
  463. list->file[i].name = name;
  464. ++i;
  465. } else {
  466. finish = true;
  467. free(name);
  468. }
  469. }
  470. list->file_count = i;
  471. response.has_next = false;
  472. MsgList_push_back(msg_list, response);
  473. storage_dir_close(dir);
  474. storage_file_free(dir);
  475. furi_record_close("storage");
  476. }
  477. static void test_rpc_decode_and_compare(MsgList_t expected_msg_list) {
  478. furi_assert(!MsgList_empty_p(expected_msg_list));
  479. pb_istream_t istream = {
  480. .callback = test_rpc_pb_stream_read,
  481. .state = output_stream,
  482. .errmsg = NULL,
  483. .bytes_left = 0x7FFFFFFF,
  484. };
  485. /* other fields explicitly initialized by 0 */
  486. PB_Main result = {.cb_content.funcs.decode = NULL};
  487. /* mlib adds msg_files into start of list, so reverse it */
  488. MsgList_reverse(expected_msg_list);
  489. for
  490. M_EACH(expected_msg, expected_msg_list, MsgList_t) {
  491. if(!pb_decode_ex(&istream, &PB_Main_msg, &result, PB_DECODE_DELIMITED)) {
  492. mu_assert(
  493. 0,
  494. "not all expected messages decoded (maybe increase MAX_RECEIVE_OUTPUT_TIMEOUT)");
  495. break;
  496. }
  497. test_rpc_compare_messages(&result, expected_msg);
  498. pb_release(&PB_Main_msg, &result);
  499. }
  500. MsgList_reverse(expected_msg_list);
  501. }
  502. static void test_rpc_free_msg_list(MsgList_t msg_list) {
  503. for
  504. M_EACH(it, msg_list, MsgList_t) {
  505. pb_release(&PB_Main_msg, it);
  506. }
  507. MsgList_clear(msg_list);
  508. }
  509. static void test_rpc_storage_list_run(const char* path, uint32_t command_id) {
  510. PB_Main request;
  511. MsgList_t expected_msg_list;
  512. MsgList_init(expected_msg_list);
  513. test_rpc_create_simple_message(&request, PB_Main_storage_list_request_tag, path, command_id);
  514. if(!strcmp(path, "/")) {
  515. test_rpc_storage_list_create_expected_list_root(expected_msg_list, command_id);
  516. } else {
  517. test_rpc_storage_list_create_expected_list(expected_msg_list, path, command_id);
  518. }
  519. test_rpc_encode_and_feed_one(&request);
  520. test_rpc_decode_and_compare(expected_msg_list);
  521. pb_release(&PB_Main_msg, &request);
  522. test_rpc_free_msg_list(expected_msg_list);
  523. }
  524. MU_TEST(test_storage_list) {
  525. test_rpc_storage_list_run("/", ++command_id);
  526. test_rpc_storage_list_run("/ext/nfc", ++command_id);
  527. test_rpc_storage_list_run("/int", ++command_id);
  528. test_rpc_storage_list_run("/ext", ++command_id);
  529. test_rpc_storage_list_run("/ext/irda", ++command_id);
  530. test_rpc_storage_list_run("/ext/ibutton", ++command_id);
  531. test_rpc_storage_list_run("/ext/lfrfid", ++command_id);
  532. test_rpc_storage_list_run("error_path", ++command_id);
  533. }
  534. static void
  535. test_rpc_add_empty_to_list(MsgList_t msg_list, PB_CommandStatus status, uint32_t command_id) {
  536. PB_Main* response = MsgList_push_new(msg_list);
  537. response->command_id = command_id;
  538. response->command_status = status;
  539. response->cb_content.funcs.encode = NULL;
  540. response->has_next = false;
  541. response->which_content = PB_Main_empty_tag;
  542. }
  543. static void test_rpc_add_read_to_list_by_reading_real_file(
  544. MsgList_t msg_list,
  545. const char* path,
  546. uint32_t command_id) {
  547. furi_assert(MsgList_empty_p(msg_list));
  548. Storage* fs_api = furi_record_open("storage");
  549. File* file = storage_file_alloc(fs_api);
  550. bool result = false;
  551. if(storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING)) {
  552. size_t size_left = storage_file_size(file);
  553. do {
  554. PB_Main* response = MsgList_push_new(msg_list);
  555. response->command_id = command_id;
  556. response->command_status = PB_CommandStatus_OK;
  557. response->has_next = false;
  558. response->which_content = PB_Main_storage_read_response_tag;
  559. response->content.storage_read_response.has_file = true;
  560. response->content.storage_read_response.file.data =
  561. furi_alloc(PB_BYTES_ARRAY_T_ALLOCSIZE(MIN(size_left, MAX_DATA_SIZE)));
  562. uint8_t* buffer = response->content.storage_read_response.file.data->bytes;
  563. uint16_t* read_size_msg = &response->content.storage_read_response.file.data->size;
  564. size_t read_size = MIN(size_left, MAX_DATA_SIZE);
  565. *read_size_msg = storage_file_read(file, buffer, read_size);
  566. size_left -= read_size;
  567. result = (*read_size_msg == read_size);
  568. if(result) {
  569. response->has_next = (size_left > 0);
  570. }
  571. } while((size_left != 0) && result);
  572. if(!result) {
  573. test_rpc_add_empty_to_list(
  574. msg_list, test_rpc_storage_get_file_error(file), command_id);
  575. }
  576. } else {
  577. test_rpc_add_empty_to_list(msg_list, test_rpc_storage_get_file_error(file), command_id);
  578. }
  579. storage_file_close(file);
  580. storage_file_free(file);
  581. furi_record_close("storage");
  582. }
  583. static void test_storage_read_run(const char* path, uint32_t command_id) {
  584. PB_Main request;
  585. MsgList_t expected_msg_list;
  586. MsgList_init(expected_msg_list);
  587. test_rpc_add_read_to_list_by_reading_real_file(expected_msg_list, path, command_id);
  588. test_rpc_create_simple_message(&request, PB_Main_storage_read_request_tag, path, command_id);
  589. test_rpc_encode_and_feed_one(&request);
  590. test_rpc_decode_and_compare(expected_msg_list);
  591. pb_release(&PB_Main_msg, &request);
  592. test_rpc_free_msg_list(expected_msg_list);
  593. }
  594. static bool test_is_exists(const char* path) {
  595. Storage* fs_api = furi_record_open("storage");
  596. FileInfo fileinfo;
  597. FS_Error result = storage_common_stat(fs_api, path, &fileinfo);
  598. furi_check((result == FSE_OK) || (result == FSE_NOT_EXIST));
  599. furi_record_close("storage");
  600. return result == FSE_OK;
  601. }
  602. static void test_create_dir(const char* path) {
  603. Storage* fs_api = furi_record_open("storage");
  604. FS_Error error = storage_common_mkdir(fs_api, path);
  605. (void)error;
  606. furi_assert((error == FSE_OK) || (error == FSE_EXIST));
  607. furi_record_close("storage");
  608. furi_check(test_is_exists(path));
  609. }
  610. static void test_create_file(const char* path, size_t size) {
  611. Storage* fs_api = furi_record_open("storage");
  612. File* file = storage_file_alloc(fs_api);
  613. if(storage_file_open(file, path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
  614. uint8_t buf[128] = {0};
  615. for(int i = 0; i < sizeof(buf); ++i) {
  616. buf[i] = '0' + (i % 10);
  617. }
  618. while(size) {
  619. size_t written = storage_file_write(file, buf, MIN(size, sizeof(buf)));
  620. furi_assert(written);
  621. size -= written;
  622. }
  623. }
  624. storage_file_close(file);
  625. storage_file_free(file);
  626. furi_record_close("storage");
  627. furi_check(test_is_exists(path));
  628. }
  629. static void test_rpc_storage_info_run(const char* path, uint32_t command_id) {
  630. PB_Main request;
  631. MsgList_t expected_msg_list;
  632. MsgList_init(expected_msg_list);
  633. test_rpc_create_simple_message(&request, PB_Main_storage_info_request_tag, path, command_id);
  634. PB_Main* response = MsgList_push_new(expected_msg_list);
  635. response->command_id = command_id;
  636. Storage* fs_api = furi_record_open("storage");
  637. FS_Error error = storage_common_fs_info(
  638. fs_api,
  639. path,
  640. &response->content.storage_info_response.total_space,
  641. &response->content.storage_info_response.free_space);
  642. response->command_status = rpc_system_storage_get_error(error);
  643. if(error == FSE_OK) {
  644. response->which_content = PB_Main_storage_info_response_tag;
  645. } else {
  646. response->which_content = PB_Main_empty_tag;
  647. }
  648. test_rpc_encode_and_feed_one(&request);
  649. test_rpc_decode_and_compare(expected_msg_list);
  650. pb_release(&PB_Main_msg, &request);
  651. test_rpc_free_msg_list(expected_msg_list);
  652. }
  653. static void test_rpc_storage_stat_run(const char* path, uint32_t command_id) {
  654. PB_Main request;
  655. MsgList_t expected_msg_list;
  656. MsgList_init(expected_msg_list);
  657. test_rpc_create_simple_message(&request, PB_Main_storage_stat_request_tag, path, command_id);
  658. Storage* fs_api = furi_record_open("storage");
  659. FileInfo fileinfo;
  660. FS_Error error = storage_common_stat(fs_api, path, &fileinfo);
  661. furi_record_close("storage");
  662. PB_Main* response = MsgList_push_new(expected_msg_list);
  663. response->command_id = command_id;
  664. response->command_status = rpc_system_storage_get_error(error);
  665. response->has_next = false;
  666. response->which_content = PB_Main_empty_tag;
  667. if(error == FSE_OK) {
  668. response->which_content = PB_Main_storage_stat_response_tag;
  669. response->content.storage_stat_response.has_file = true;
  670. response->content.storage_stat_response.file.type = (fileinfo.flags & FSF_DIRECTORY) ?
  671. PB_Storage_File_FileType_DIR :
  672. PB_Storage_File_FileType_FILE;
  673. response->content.storage_stat_response.file.size = fileinfo.size;
  674. }
  675. test_rpc_encode_and_feed_one(&request);
  676. test_rpc_decode_and_compare(expected_msg_list);
  677. pb_release(&PB_Main_msg, &request);
  678. test_rpc_free_msg_list(expected_msg_list);
  679. }
  680. MU_TEST(test_storage_info) {
  681. test_rpc_storage_info_run("/any", ++command_id);
  682. test_rpc_storage_info_run("/int", ++command_id);
  683. test_rpc_storage_info_run("/ext", ++command_id);
  684. }
  685. #define TEST_DIR_STAT_NAME TEST_DIR "stat_dir"
  686. #define TEST_DIR_STAT TEST_DIR_STAT_NAME "/"
  687. MU_TEST(test_storage_stat) {
  688. test_create_dir(TEST_DIR_STAT_NAME);
  689. test_create_file(TEST_DIR_STAT "empty.txt", 0);
  690. test_create_file(TEST_DIR_STAT "l33t.txt", 1337);
  691. test_rpc_storage_stat_run("/", ++command_id);
  692. test_rpc_storage_stat_run("/int", ++command_id);
  693. test_rpc_storage_stat_run("/ext", ++command_id);
  694. test_rpc_storage_stat_run(TEST_DIR_STAT "empty.txt", ++command_id);
  695. test_rpc_storage_stat_run(TEST_DIR_STAT "l33t.txt", ++command_id);
  696. test_rpc_storage_stat_run(TEST_DIR_STAT "missing", ++command_id);
  697. test_rpc_storage_stat_run(TEST_DIR_STAT_NAME, ++command_id);
  698. test_rpc_storage_stat_run(TEST_DIR_STAT, ++command_id);
  699. }
  700. MU_TEST(test_storage_read) {
  701. test_create_file(TEST_DIR "empty.txt", 0);
  702. test_create_file(TEST_DIR "file1.txt", 1);
  703. test_create_file(TEST_DIR "file2.txt", MAX_DATA_SIZE);
  704. test_create_file(TEST_DIR "file3.txt", MAX_DATA_SIZE + 1);
  705. test_create_file(TEST_DIR "file4.txt", (MAX_DATA_SIZE * 2) + 1);
  706. test_storage_read_run(TEST_DIR "empty.txt", ++command_id);
  707. test_storage_read_run(TEST_DIR "file1.txt", ++command_id);
  708. test_storage_read_run(TEST_DIR "file2.txt", ++command_id);
  709. test_storage_read_run(TEST_DIR "file3.txt", ++command_id);
  710. test_storage_read_run(TEST_DIR "file4.txt", ++command_id);
  711. }
  712. static void test_storage_write_run(
  713. const char* path,
  714. size_t write_size,
  715. size_t write_count,
  716. uint32_t command_id,
  717. PB_CommandStatus status) {
  718. MsgList_t input_msg_list;
  719. MsgList_init(input_msg_list);
  720. MsgList_t expected_msg_list;
  721. MsgList_init(expected_msg_list);
  722. uint8_t* buf = furi_alloc(write_size);
  723. for(int i = 0; i < write_size; ++i) {
  724. buf[i] = '0' + (i % 10);
  725. }
  726. test_rpc_add_read_or_write_to_list(
  727. input_msg_list, WRITE_REQUEST, path, buf, write_size, write_count, command_id);
  728. test_rpc_add_empty_to_list(expected_msg_list, status, command_id);
  729. test_rpc_encode_and_feed(input_msg_list);
  730. test_rpc_decode_and_compare(expected_msg_list);
  731. test_rpc_free_msg_list(input_msg_list);
  732. test_rpc_free_msg_list(expected_msg_list);
  733. free(buf);
  734. }
  735. static void test_storage_write_read_run(
  736. const char* path,
  737. const uint8_t* pattern,
  738. size_t pattern_size,
  739. size_t pattern_repeats,
  740. uint32_t* command_id) {
  741. MsgList_t input_msg_list;
  742. MsgList_init(input_msg_list);
  743. MsgList_t expected_msg_list;
  744. MsgList_init(expected_msg_list);
  745. test_rpc_add_read_or_write_to_list(
  746. input_msg_list, WRITE_REQUEST, path, pattern, pattern_size, pattern_repeats, ++*command_id);
  747. test_rpc_add_empty_to_list(expected_msg_list, PB_CommandStatus_OK, *command_id);
  748. test_rpc_create_simple_message(
  749. MsgList_push_raw(input_msg_list), PB_Main_storage_read_request_tag, path, ++*command_id);
  750. test_rpc_add_read_or_write_to_list(
  751. expected_msg_list,
  752. READ_RESPONSE,
  753. path,
  754. pattern,
  755. pattern_size,
  756. pattern_repeats,
  757. *command_id);
  758. test_rpc_print_message_list(input_msg_list);
  759. test_rpc_print_message_list(expected_msg_list);
  760. test_rpc_encode_and_feed(input_msg_list);
  761. test_rpc_decode_and_compare(expected_msg_list);
  762. test_rpc_free_msg_list(input_msg_list);
  763. test_rpc_free_msg_list(expected_msg_list);
  764. }
  765. MU_TEST(test_storage_write_read) {
  766. uint8_t pattern1[] = "abcdefgh";
  767. test_storage_write_read_run(TEST_DIR "test1.txt", pattern1, sizeof(pattern1), 1, &command_id);
  768. test_storage_write_read_run(TEST_DIR "test2.txt", pattern1, 1, 1, &command_id);
  769. test_storage_write_read_run(TEST_DIR "test3.txt", pattern1, 0, 1, &command_id);
  770. }
  771. MU_TEST(test_storage_write) {
  772. test_storage_write_run(
  773. TEST_DIR "afaefo/aefaef/aef/aef/test1.txt",
  774. 1,
  775. 1,
  776. ++command_id,
  777. PB_CommandStatus_ERROR_STORAGE_NOT_EXIST);
  778. test_storage_write_run(TEST_DIR "test1.txt", 100, 1, ++command_id, PB_CommandStatus_OK);
  779. test_storage_write_run(TEST_DIR "test2.txt", 100, 3, ++command_id, PB_CommandStatus_OK);
  780. test_storage_write_run(TEST_DIR "test1.txt", 100, 3, ++command_id, PB_CommandStatus_OK);
  781. test_storage_write_run(TEST_DIR "test2.txt", 100, 3, ++command_id, PB_CommandStatus_OK);
  782. test_storage_write_run(
  783. TEST_DIR "afaefo/aefaef/aef/aef/test1.txt",
  784. 1,
  785. 1,
  786. ++command_id,
  787. PB_CommandStatus_ERROR_STORAGE_NOT_EXIST);
  788. test_storage_write_run(TEST_DIR "test2.txt", 1, 50, ++command_id, PB_CommandStatus_OK);
  789. test_storage_write_run(TEST_DIR "test2.txt", 512, 3, ++command_id, PB_CommandStatus_OK);
  790. }
  791. MU_TEST(test_storage_interrupt_continuous_same_system) {
  792. MsgList_t input_msg_list;
  793. MsgList_init(input_msg_list);
  794. MsgList_t expected_msg_list;
  795. MsgList_init(expected_msg_list);
  796. uint8_t pattern[16] = {0};
  797. test_rpc_add_read_or_write_to_list(
  798. input_msg_list,
  799. WRITE_REQUEST,
  800. TEST_DIR "test1.txt",
  801. pattern,
  802. sizeof(pattern),
  803. 3,
  804. command_id);
  805. /* replace last packet (has_next == false) with another command */
  806. PB_Main message_to_remove;
  807. MsgList_pop_back(&message_to_remove, input_msg_list);
  808. pb_release(&PB_Main_msg, &message_to_remove);
  809. test_rpc_create_simple_message(
  810. MsgList_push_new(input_msg_list),
  811. PB_Main_storage_mkdir_request_tag,
  812. TEST_DIR "dir1",
  813. command_id + 1);
  814. test_rpc_add_read_or_write_to_list(
  815. input_msg_list,
  816. WRITE_REQUEST,
  817. TEST_DIR "test2.txt",
  818. pattern,
  819. sizeof(pattern),
  820. 3,
  821. command_id);
  822. test_rpc_add_empty_to_list(
  823. expected_msg_list, PB_CommandStatus_ERROR_CONTINUOUS_COMMAND_INTERRUPTED, command_id);
  824. test_rpc_add_empty_to_list(expected_msg_list, PB_CommandStatus_OK, command_id + 1);
  825. test_rpc_encode_and_feed(input_msg_list);
  826. test_rpc_decode_and_compare(expected_msg_list);
  827. test_rpc_free_msg_list(input_msg_list);
  828. test_rpc_free_msg_list(expected_msg_list);
  829. }
  830. MU_TEST(test_storage_interrupt_continuous_another_system) {
  831. MsgList_t input_msg_list;
  832. MsgList_init(input_msg_list);
  833. MsgList_t expected_msg_list;
  834. MsgList_init(expected_msg_list);
  835. uint8_t pattern[16] = {0};
  836. test_rpc_add_read_or_write_to_list(
  837. input_msg_list,
  838. WRITE_REQUEST,
  839. TEST_DIR "test1.txt",
  840. pattern,
  841. sizeof(pattern),
  842. 3,
  843. command_id);
  844. PB_Main message = {
  845. .command_id = command_id + 1,
  846. .command_status = PB_CommandStatus_OK,
  847. .cb_content.funcs.encode = NULL,
  848. .has_next = false,
  849. .which_content = PB_Main_system_ping_request_tag,
  850. };
  851. MsgList_it_t it;
  852. MsgList_it(it, input_msg_list);
  853. MsgList_next(it);
  854. MsgList_insert(input_msg_list, it, message);
  855. test_rpc_add_read_or_write_to_list(
  856. input_msg_list,
  857. WRITE_REQUEST,
  858. TEST_DIR "test2.txt",
  859. pattern,
  860. sizeof(pattern),
  861. 3,
  862. command_id + 2);
  863. test_rpc_add_ping_to_list(expected_msg_list, PING_RESPONSE, command_id + 1);
  864. test_rpc_add_empty_to_list(expected_msg_list, PB_CommandStatus_OK, command_id);
  865. test_rpc_add_empty_to_list(expected_msg_list, PB_CommandStatus_OK, command_id + 2);
  866. test_rpc_encode_and_feed(input_msg_list);
  867. test_rpc_decode_and_compare(expected_msg_list);
  868. test_rpc_free_msg_list(input_msg_list);
  869. test_rpc_free_msg_list(expected_msg_list);
  870. }
  871. static void test_storage_delete_run(
  872. const char* path,
  873. size_t command_id,
  874. PB_CommandStatus status,
  875. bool recursive) {
  876. PB_Main request;
  877. MsgList_t expected_msg_list;
  878. MsgList_init(expected_msg_list);
  879. test_rpc_create_simple_message(&request, PB_Main_storage_delete_request_tag, path, command_id);
  880. request.content.storage_delete_request.recursive = recursive;
  881. test_rpc_add_empty_to_list(expected_msg_list, status, command_id);
  882. test_rpc_encode_and_feed_one(&request);
  883. test_rpc_decode_and_compare(expected_msg_list);
  884. pb_release(&PB_Main_msg, &request);
  885. test_rpc_free_msg_list(expected_msg_list);
  886. }
  887. #define TEST_DIR_RMRF_NAME TEST_DIR "rmrf_test"
  888. #define TEST_DIR_RMRF TEST_DIR_RMRF_NAME "/"
  889. MU_TEST(test_storage_delete_recursive) {
  890. test_create_dir(TEST_DIR_RMRF_NAME);
  891. test_create_dir(TEST_DIR_RMRF "dir1");
  892. test_create_file(TEST_DIR_RMRF "dir1/file1", 1);
  893. test_create_dir(TEST_DIR_RMRF "dir1/dir1");
  894. test_create_dir(TEST_DIR_RMRF "dir1/dir2");
  895. test_create_file(TEST_DIR_RMRF "dir1/dir2/file1", 1);
  896. test_create_file(TEST_DIR_RMRF "dir1/dir2/file2", 1);
  897. test_create_dir(TEST_DIR_RMRF "dir1/dir3");
  898. test_create_dir(TEST_DIR_RMRF "dir1/dir3/dir1");
  899. test_create_dir(TEST_DIR_RMRF "dir1/dir3/dir1/dir1");
  900. test_create_dir(TEST_DIR_RMRF "dir1/dir3/dir1/dir1/dir1");
  901. test_create_dir(TEST_DIR_RMRF "dir1/dir3/dir1/dir1/dir1/dir1");
  902. test_create_dir(TEST_DIR_RMRF "dir2");
  903. test_create_dir(TEST_DIR_RMRF "dir2/dir1");
  904. test_create_dir(TEST_DIR_RMRF "dir2/dir2");
  905. test_create_file(TEST_DIR_RMRF "dir2/dir2/file1", 1);
  906. test_create_dir(TEST_DIR_RMRF "dir2/dir2/dir1");
  907. test_create_dir(TEST_DIR_RMRF "dir2/dir2/dir1/dir1");
  908. test_create_dir(TEST_DIR_RMRF "dir2/dir2/dir1/dir1/dir1");
  909. test_create_file(TEST_DIR_RMRF "dir2/dir2/dir1/dir1/dir1/file1", 1);
  910. test_storage_delete_run(
  911. TEST_DIR_RMRF_NAME, ++command_id, PB_CommandStatus_ERROR_STORAGE_DIR_NOT_EMPTY, false);
  912. mu_check(test_is_exists(TEST_DIR_RMRF_NAME));
  913. test_storage_delete_run(TEST_DIR_RMRF_NAME, ++command_id, PB_CommandStatus_OK, true);
  914. mu_check(!test_is_exists(TEST_DIR_RMRF_NAME));
  915. test_storage_delete_run(TEST_DIR_RMRF_NAME, ++command_id, PB_CommandStatus_OK, false);
  916. mu_check(!test_is_exists(TEST_DIR_RMRF_NAME));
  917. test_create_dir(TEST_DIR_RMRF_NAME);
  918. test_storage_delete_run(TEST_DIR_RMRF_NAME, ++command_id, PB_CommandStatus_OK, true);
  919. mu_check(!test_is_exists(TEST_DIR_RMRF_NAME));
  920. test_create_dir(TEST_DIR "file1");
  921. test_storage_delete_run(TEST_DIR "file1", ++command_id, PB_CommandStatus_OK, true);
  922. mu_check(!test_is_exists(TEST_DIR "file1"));
  923. }
  924. MU_TEST(test_storage_delete) {
  925. test_storage_delete_run(NULL, ++command_id, PB_CommandStatus_ERROR_INVALID_PARAMETERS, false);
  926. furi_check(!test_is_exists(TEST_DIR "empty.txt"));
  927. test_storage_delete_run(TEST_DIR "empty.txt", ++command_id, PB_CommandStatus_OK, false);
  928. mu_check(!test_is_exists(TEST_DIR "empty.txt"));
  929. test_create_file(TEST_DIR "empty.txt", 0);
  930. test_storage_delete_run(TEST_DIR "empty.txt", ++command_id, PB_CommandStatus_OK, false);
  931. mu_check(!test_is_exists(TEST_DIR "empty.txt"));
  932. furi_check(!test_is_exists(TEST_DIR "dir1"));
  933. test_create_dir(TEST_DIR "dir1");
  934. test_storage_delete_run(TEST_DIR "dir1", ++command_id, PB_CommandStatus_OK, false);
  935. mu_check(!test_is_exists(TEST_DIR "dir1"));
  936. test_storage_delete_run(TEST_DIR "dir1", ++command_id, PB_CommandStatus_OK, false);
  937. mu_check(!test_is_exists(TEST_DIR "dir1"));
  938. }
  939. static void test_storage_mkdir_run(const char* path, size_t command_id, PB_CommandStatus status) {
  940. PB_Main request;
  941. MsgList_t expected_msg_list;
  942. MsgList_init(expected_msg_list);
  943. test_rpc_create_simple_message(&request, PB_Main_storage_mkdir_request_tag, path, command_id);
  944. test_rpc_add_empty_to_list(expected_msg_list, status, command_id);
  945. test_rpc_encode_and_feed_one(&request);
  946. test_rpc_decode_and_compare(expected_msg_list);
  947. pb_release(&PB_Main_msg, &request);
  948. test_rpc_free_msg_list(expected_msg_list);
  949. }
  950. MU_TEST(test_storage_mkdir) {
  951. furi_check(!test_is_exists(TEST_DIR "dir1"));
  952. test_storage_mkdir_run(TEST_DIR "dir1", ++command_id, PB_CommandStatus_OK);
  953. mu_check(test_is_exists(TEST_DIR "dir1"));
  954. test_storage_mkdir_run(TEST_DIR "dir1", ++command_id, PB_CommandStatus_ERROR_STORAGE_EXIST);
  955. mu_check(test_is_exists(TEST_DIR "dir1"));
  956. furi_check(!test_is_exists(TEST_DIR "dir2"));
  957. test_create_dir(TEST_DIR "dir2");
  958. test_storage_mkdir_run(TEST_DIR "dir2", ++command_id, PB_CommandStatus_ERROR_STORAGE_EXIST);
  959. mu_check(test_is_exists(TEST_DIR "dir2"));
  960. }
  961. static void test_storage_calculate_md5sum(const char* path, char* md5sum) {
  962. Storage* api = furi_record_open("storage");
  963. File* file = storage_file_alloc(api);
  964. if(storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING)) {
  965. const uint16_t once_read_size = 512;
  966. const uint8_t hash_size = MD5SUM_SIZE;
  967. uint8_t* data = malloc(once_read_size);
  968. uint8_t* hash = malloc(sizeof(uint8_t) * hash_size);
  969. md5_context* md5_ctx = malloc(sizeof(md5_context));
  970. md5_starts(md5_ctx);
  971. while(true) {
  972. uint16_t read_size = storage_file_read(file, data, once_read_size);
  973. if(read_size == 0) break;
  974. md5_update(md5_ctx, data, read_size);
  975. }
  976. md5_finish(md5_ctx, hash);
  977. free(md5_ctx);
  978. for(uint8_t i = 0; i < hash_size; i++) {
  979. md5sum += sprintf(md5sum, "%02x", hash[i]);
  980. }
  981. free(hash);
  982. free(data);
  983. } else {
  984. furi_assert(0);
  985. }
  986. storage_file_close(file);
  987. storage_file_free(file);
  988. furi_record_close("storage");
  989. }
  990. static void test_storage_md5sum_run(
  991. const char* path,
  992. uint32_t command_id,
  993. const char* md5sum,
  994. PB_CommandStatus status) {
  995. PB_Main request;
  996. MsgList_t expected_msg_list;
  997. MsgList_init(expected_msg_list);
  998. test_rpc_create_simple_message(&request, PB_Main_storage_md5sum_request_tag, path, command_id);
  999. if(status == PB_CommandStatus_OK) {
  1000. PB_Main* response = MsgList_push_new(expected_msg_list);
  1001. test_rpc_create_simple_message(
  1002. response, PB_Main_storage_md5sum_response_tag, md5sum, command_id);
  1003. response->command_status = status;
  1004. } else {
  1005. test_rpc_add_empty_to_list(expected_msg_list, status, command_id);
  1006. }
  1007. test_rpc_encode_and_feed_one(&request);
  1008. test_rpc_decode_and_compare(expected_msg_list);
  1009. pb_release(&PB_Main_msg, &request);
  1010. test_rpc_free_msg_list(expected_msg_list);
  1011. }
  1012. MU_TEST(test_storage_md5sum) {
  1013. char md5sum1[MD5SUM_SIZE * 2 + 1] = {0};
  1014. char md5sum2[MD5SUM_SIZE * 2 + 1] = {0};
  1015. char md5sum3[MD5SUM_SIZE * 2 + 1] = {0};
  1016. test_storage_md5sum_run(
  1017. TEST_DIR "test1.txt", ++command_id, "", PB_CommandStatus_ERROR_STORAGE_NOT_EXIST);
  1018. test_create_file(TEST_DIR "file1.txt", 0);
  1019. test_create_file(TEST_DIR "file2.txt", 1);
  1020. test_create_file(TEST_DIR "file3.txt", 512);
  1021. test_storage_calculate_md5sum(TEST_DIR "file1.txt", md5sum1);
  1022. test_storage_calculate_md5sum(TEST_DIR "file2.txt", md5sum2);
  1023. test_storage_calculate_md5sum(TEST_DIR "file3.txt", md5sum3);
  1024. test_storage_md5sum_run(TEST_DIR "file1.txt", ++command_id, md5sum1, PB_CommandStatus_OK);
  1025. test_storage_md5sum_run(TEST_DIR "file1.txt", ++command_id, md5sum1, PB_CommandStatus_OK);
  1026. test_storage_md5sum_run(TEST_DIR "file2.txt", ++command_id, md5sum2, PB_CommandStatus_OK);
  1027. test_storage_md5sum_run(TEST_DIR "file2.txt", ++command_id, md5sum2, PB_CommandStatus_OK);
  1028. test_storage_md5sum_run(TEST_DIR "file3.txt", ++command_id, md5sum3, PB_CommandStatus_OK);
  1029. test_storage_md5sum_run(TEST_DIR "file3.txt", ++command_id, md5sum3, PB_CommandStatus_OK);
  1030. test_storage_md5sum_run(TEST_DIR "file2.txt", ++command_id, md5sum2, PB_CommandStatus_OK);
  1031. test_storage_md5sum_run(TEST_DIR "file3.txt", ++command_id, md5sum3, PB_CommandStatus_OK);
  1032. test_storage_md5sum_run(TEST_DIR "file1.txt", ++command_id, md5sum1, PB_CommandStatus_OK);
  1033. test_storage_md5sum_run(TEST_DIR "file2.txt", ++command_id, md5sum2, PB_CommandStatus_OK);
  1034. }
  1035. static void test_rpc_storage_rename_run(
  1036. const char* old_path,
  1037. const char* new_path,
  1038. uint32_t command_id,
  1039. PB_CommandStatus status) {
  1040. PB_Main request;
  1041. MsgList_t expected_msg_list;
  1042. MsgList_init(expected_msg_list);
  1043. char* str_old_path = strdup(old_path);
  1044. char* str_new_path = strdup(new_path);
  1045. request.command_id = command_id;
  1046. request.command_status = PB_CommandStatus_OK;
  1047. request.cb_content.funcs.encode = NULL;
  1048. request.which_content = PB_Main_storage_rename_request_tag;
  1049. request.has_next = false;
  1050. request.content.storage_rename_request.old_path = str_old_path;
  1051. request.content.storage_rename_request.new_path = str_new_path;
  1052. test_rpc_add_empty_to_list(expected_msg_list, status, command_id);
  1053. test_rpc_encode_and_feed_one(&request);
  1054. test_rpc_decode_and_compare(expected_msg_list);
  1055. pb_release(&PB_Main_msg, &request);
  1056. test_rpc_free_msg_list(expected_msg_list);
  1057. }
  1058. MU_TEST(test_storage_rename) {
  1059. test_rpc_storage_rename_run(
  1060. NULL, NULL, ++command_id, PB_CommandStatus_ERROR_STORAGE_INVALID_NAME);
  1061. furi_check(!test_is_exists(TEST_DIR "empty.txt"));
  1062. test_create_file(TEST_DIR "empty.txt", 0);
  1063. test_rpc_storage_rename_run(
  1064. TEST_DIR "empty.txt", TEST_DIR "empty2.txt", ++command_id, PB_CommandStatus_OK);
  1065. mu_check(!test_is_exists(TEST_DIR "empty.txt"));
  1066. mu_check(test_is_exists(TEST_DIR "empty2.txt"));
  1067. furi_check(!test_is_exists(TEST_DIR "dir1"));
  1068. test_create_dir(TEST_DIR "dir1");
  1069. test_rpc_storage_rename_run(
  1070. TEST_DIR "dir1", TEST_DIR "dir2", ++command_id, PB_CommandStatus_OK);
  1071. mu_check(!test_is_exists(TEST_DIR "dir1"));
  1072. mu_check(test_is_exists(TEST_DIR "dir2"));
  1073. }
  1074. MU_TEST(test_ping) {
  1075. MsgList_t input_msg_list;
  1076. MsgList_init(input_msg_list);
  1077. MsgList_t expected_msg_list;
  1078. MsgList_init(expected_msg_list);
  1079. test_rpc_add_ping_to_list(input_msg_list, PING_REQUEST, 0);
  1080. test_rpc_add_ping_to_list(input_msg_list, PING_REQUEST, 1);
  1081. test_rpc_add_ping_to_list(input_msg_list, PING_REQUEST, 0);
  1082. test_rpc_add_ping_to_list(input_msg_list, PING_REQUEST, 500);
  1083. test_rpc_add_ping_to_list(input_msg_list, PING_REQUEST, (uint32_t)-1);
  1084. test_rpc_add_ping_to_list(input_msg_list, PING_REQUEST, 700);
  1085. test_rpc_add_ping_to_list(input_msg_list, PING_REQUEST, 1);
  1086. test_rpc_add_ping_to_list(expected_msg_list, PING_RESPONSE, 0);
  1087. test_rpc_add_ping_to_list(expected_msg_list, PING_RESPONSE, 1);
  1088. test_rpc_add_ping_to_list(expected_msg_list, PING_RESPONSE, 0);
  1089. test_rpc_add_ping_to_list(expected_msg_list, PING_RESPONSE, 500);
  1090. test_rpc_add_ping_to_list(expected_msg_list, PING_RESPONSE, (uint32_t)-1);
  1091. test_rpc_add_ping_to_list(expected_msg_list, PING_RESPONSE, 700);
  1092. test_rpc_add_ping_to_list(expected_msg_list, PING_RESPONSE, 1);
  1093. test_rpc_encode_and_feed(input_msg_list);
  1094. test_rpc_decode_and_compare(expected_msg_list);
  1095. test_rpc_free_msg_list(input_msg_list);
  1096. test_rpc_free_msg_list(expected_msg_list);
  1097. }
  1098. MU_TEST(test_system_protobuf_version) {
  1099. MsgList_t expected_msg_list;
  1100. MsgList_init(expected_msg_list);
  1101. PB_Main request;
  1102. request.command_id = ++command_id;
  1103. request.command_status = PB_CommandStatus_OK;
  1104. request.cb_content.funcs.decode = NULL;
  1105. request.has_next = false;
  1106. request.which_content = PB_Main_system_protobuf_version_request_tag;
  1107. PB_Main* response = MsgList_push_new(expected_msg_list);
  1108. response->command_id = command_id;
  1109. response->command_status = PB_CommandStatus_OK;
  1110. response->cb_content.funcs.encode = NULL;
  1111. response->has_next = false;
  1112. response->which_content = PB_Main_system_protobuf_version_response_tag;
  1113. response->content.system_protobuf_version_response.major = PROTOBUF_MAJOR_VERSION;
  1114. response->content.system_protobuf_version_response.minor = PROTOBUF_MINOR_VERSION;
  1115. test_rpc_encode_and_feed_one(&request);
  1116. test_rpc_decode_and_compare(expected_msg_list);
  1117. test_rpc_free_msg_list(expected_msg_list);
  1118. }
  1119. // TODO: 1) test for rubbish data
  1120. // 2) test for unexpected end of packet
  1121. // 3) test for one push of several packets
  1122. // 4) test for fill buffer till end (great varint) and close connection
  1123. MU_TEST_SUITE(test_rpc_system) {
  1124. MU_SUITE_CONFIGURE(&test_rpc_setup, &test_rpc_teardown);
  1125. MU_RUN_TEST(test_ping);
  1126. MU_RUN_TEST(test_system_protobuf_version);
  1127. }
  1128. MU_TEST_SUITE(test_rpc_storage) {
  1129. MU_SUITE_CONFIGURE(&test_rpc_storage_setup, &test_rpc_storage_teardown);
  1130. MU_RUN_TEST(test_storage_info);
  1131. MU_RUN_TEST(test_storage_stat);
  1132. MU_RUN_TEST(test_storage_list);
  1133. MU_RUN_TEST(test_storage_read);
  1134. MU_RUN_TEST(test_storage_write_read);
  1135. MU_RUN_TEST(test_storage_write);
  1136. MU_RUN_TEST(test_storage_delete);
  1137. MU_RUN_TEST(test_storage_delete_recursive);
  1138. MU_RUN_TEST(test_storage_mkdir);
  1139. MU_RUN_TEST(test_storage_md5sum);
  1140. MU_RUN_TEST(test_storage_rename);
  1141. MU_RUN_TEST(test_storage_interrupt_continuous_same_system);
  1142. MU_RUN_TEST(test_storage_interrupt_continuous_another_system);
  1143. }
  1144. static void test_app_create_request(
  1145. PB_Main* request,
  1146. const char* app_name,
  1147. const char* app_args,
  1148. uint32_t command_id) {
  1149. request->command_id = command_id;
  1150. request->command_status = PB_CommandStatus_OK;
  1151. request->cb_content.funcs.encode = NULL;
  1152. request->which_content = PB_Main_app_start_request_tag;
  1153. request->has_next = false;
  1154. if(app_name) {
  1155. char* msg_app_name = strdup(app_name);
  1156. request->content.app_start_request.name = msg_app_name;
  1157. } else {
  1158. request->content.app_start_request.name = NULL;
  1159. }
  1160. if(app_args) {
  1161. char* msg_app_args = strdup(app_args);
  1162. request->content.app_start_request.args = msg_app_args;
  1163. } else {
  1164. request->content.app_start_request.args = NULL;
  1165. }
  1166. }
  1167. static void test_app_start_run(
  1168. const char* app_name,
  1169. const char* app_args,
  1170. PB_CommandStatus status,
  1171. uint32_t command_id) {
  1172. PB_Main request;
  1173. MsgList_t expected_msg_list;
  1174. MsgList_init(expected_msg_list);
  1175. test_app_create_request(&request, app_name, app_args, command_id);
  1176. test_rpc_add_empty_to_list(expected_msg_list, status, command_id);
  1177. test_rpc_encode_and_feed_one(&request);
  1178. test_rpc_decode_and_compare(expected_msg_list);
  1179. pb_release(&PB_Main_msg, &request);
  1180. test_rpc_free_msg_list(expected_msg_list);
  1181. }
  1182. static void test_app_get_status_lock_run(bool locked_expected, uint32_t command_id) {
  1183. PB_Main request = {
  1184. .command_id = command_id,
  1185. .command_status = PB_CommandStatus_OK,
  1186. .which_content = PB_Main_app_lock_status_request_tag,
  1187. .has_next = false,
  1188. };
  1189. MsgList_t expected_msg_list;
  1190. MsgList_init(expected_msg_list);
  1191. PB_Main* response = MsgList_push_new(expected_msg_list);
  1192. response->command_id = command_id;
  1193. response->command_status = PB_CommandStatus_OK;
  1194. response->which_content = PB_Main_app_lock_status_response_tag;
  1195. response->has_next = false;
  1196. response->content.app_lock_status_response.locked = locked_expected;
  1197. test_rpc_encode_and_feed_one(&request);
  1198. test_rpc_decode_and_compare(expected_msg_list);
  1199. pb_release(&PB_Main_msg, &request);
  1200. test_rpc_free_msg_list(expected_msg_list);
  1201. }
  1202. MU_TEST(test_app_start_and_lock_status) {
  1203. test_app_get_status_lock_run(false, ++command_id);
  1204. test_app_start_run(NULL, "/ext/file", PB_CommandStatus_ERROR_INVALID_PARAMETERS, ++command_id);
  1205. test_app_start_run(NULL, NULL, PB_CommandStatus_ERROR_INVALID_PARAMETERS, ++command_id);
  1206. test_app_get_status_lock_run(false, ++command_id);
  1207. test_app_start_run(
  1208. "skynet_destroy_world_app", NULL, PB_CommandStatus_ERROR_INVALID_PARAMETERS, ++command_id);
  1209. test_app_get_status_lock_run(false, ++command_id);
  1210. test_app_start_run("Delay Test", "0", PB_CommandStatus_OK, ++command_id);
  1211. delay(100);
  1212. test_app_get_status_lock_run(false, ++command_id);
  1213. test_app_start_run("Delay Test", "200", PB_CommandStatus_OK, ++command_id);
  1214. test_app_get_status_lock_run(true, ++command_id);
  1215. delay(100);
  1216. test_app_get_status_lock_run(true, ++command_id);
  1217. test_app_start_run("Delay Test", "0", PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED, ++command_id);
  1218. delay(200);
  1219. test_app_get_status_lock_run(false, ++command_id);
  1220. test_app_start_run("Delay Test", "500", PB_CommandStatus_OK, ++command_id);
  1221. delay(100);
  1222. test_app_get_status_lock_run(true, ++command_id);
  1223. test_app_start_run("Infrared", "0", PB_CommandStatus_ERROR_APP_SYSTEM_LOCKED, ++command_id);
  1224. delay(100);
  1225. test_app_get_status_lock_run(true, ++command_id);
  1226. test_app_start_run(
  1227. "2_girls_1_app", "0", PB_CommandStatus_ERROR_INVALID_PARAMETERS, ++command_id);
  1228. delay(100);
  1229. test_app_get_status_lock_run(true, ++command_id);
  1230. delay(500);
  1231. test_app_get_status_lock_run(false, ++command_id);
  1232. }
  1233. MU_TEST_SUITE(test_rpc_app) {
  1234. MU_SUITE_CONFIGURE(&test_rpc_setup, &test_rpc_teardown);
  1235. MU_RUN_TEST(test_app_start_and_lock_status);
  1236. }
  1237. int run_minunit_test_rpc() {
  1238. Storage* storage = furi_record_open("storage");
  1239. furi_record_close("storage");
  1240. if(storage_sd_status(storage) != FSE_OK) {
  1241. FURI_LOG_E(TAG, "SD card not mounted - skip storage tests");
  1242. } else {
  1243. MU_RUN_SUITE(test_rpc_storage);
  1244. }
  1245. MU_RUN_SUITE(test_rpc_system);
  1246. MU_RUN_SUITE(test_rpc_app);
  1247. return MU_EXIT_CODE;
  1248. }
  1249. int32_t delay_test_app(void* p) {
  1250. int timeout = atoi((const char*)p);
  1251. if(timeout > 0) {
  1252. delay(timeout);
  1253. }
  1254. return 0;
  1255. }