├── .gitignore ├── LICENSE ├── examples ├── ipfs_add.cpp └── ipfs_cat.cpp ├── include └── ipfs_client.h ├── lib └── README ├── library.json ├── platformio.ini ├── readme.md ├── src └── ipfs_client.cpp └── test └── README /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .pioenvs 3 | .piolibdeps 4 | .vscode/.browse.c_cpp.db* 5 | .vscode/c_cpp_properties.json 6 | .vscode/launch.json 7 | .vscode/* 8 | .directory -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /examples/ipfs_add.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "ipfs_client.h" 4 | #include 5 | 6 | const char WIFI_SSID[] = ""; 7 | const char WIFI_PASS[] = ""; 8 | 9 | const char NODE_ADDR[] = ""; 10 | const int NODE_PORT = 5001; 11 | 12 | void print_ipfs_error(IPFSClient::Result result); 13 | 14 | void setup() 15 | { 16 | Serial.begin(115200); 17 | Serial.println(F("IPFS add example")); 18 | 19 | // 20 | // Start WiFi 21 | // 22 | 23 | WiFiClient wifi_client; 24 | 25 | WiFi.begin(WIFI_SSID, WIFI_PASS); 26 | Serial.println("Connecting..."); 27 | while (WiFi.status() != WL_CONNECTED) 28 | {} 29 | Serial.println("WiFi connected."); 30 | 31 | // Init IPFS client object 32 | IPFSClient ipfs_client(wifi_client); 33 | ipfs_client.set_node_address(NODE_ADDR, NODE_PORT); 34 | 35 | // Will hold response info from IPFS 36 | IPFSClient::IPFSFile ipfs_file = {0}; 37 | 38 | // Return value for client functions 39 | IPFSClient::Result result; 40 | 41 | // 42 | // Add plain text to IPFS 43 | // 44 | result = ipfs_client.add(&ipfs_file, "file.txt", "Lorem IPFSum"); 45 | 46 | if(result == IPFSClient::IPFS_CLIENT_OK) 47 | { 48 | Serial.print(F("Text added to IPFS. CID: ")); 49 | Serial.println(ipfs_file.hash); 50 | } 51 | else 52 | { 53 | Serial.println(F("Text could not be added to IPFS.")); 54 | print_ipfs_error(result); 55 | } 56 | 57 | // 58 | // Add file from SPIFFS from IPFS 59 | // 60 | 61 | // Lets create a dummy file in SPIFFS first 62 | SPIFFS.begin(true); // true to format SPIFFS partition in case it hasn't been created yet 63 | 64 | File f = SPIFFS.open("/file_in_spiffs.txt", "w"); 65 | if(!f) 66 | { 67 | Serial.println(F("Could create file in SPIFFS.")); 68 | return; 69 | } 70 | 71 | String contents = 72 | "Lorem ipfsum dolor sit amet, consectetur adipiscing elit, " 73 | "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; 74 | 75 | f.write((uint8_t*)contents.c_str(), contents.length()); 76 | f.close(); 77 | 78 | // 79 | // Now that we have a dummy file lets add it to IPFS 80 | // 81 | f = SPIFFS.open("/file_in_spiffs.txt", "r"); 82 | if(!f) 83 | { 84 | Serial.println(F("Could not open file.")); 85 | return; 86 | } 87 | 88 | result = ipfs_client.add(&ipfs_file, "file_from_spiffs.txt", &f); 89 | 90 | if(result == IPFSClient::IPFS_CLIENT_OK) 91 | { 92 | Serial.print(F("File added to IPFS. CID: ")); 93 | Serial.println(ipfs_file.hash); 94 | } 95 | else 96 | { 97 | Serial.println(F("File could not be added to IPFS.")); 98 | print_ipfs_error(result); 99 | } 100 | 101 | f.close(); 102 | } 103 | 104 | /** 105 | * Print error 106 | */ 107 | void print_ipfs_error(IPFSClient::Result result) 108 | { 109 | switch(result) 110 | { 111 | case IPFSClient::Result::IPFS_CLIENT_CANNOT_CONNECT: 112 | Serial.println(F("Could not connect to IPFS.")); 113 | break; 114 | case IPFSClient::Result::IPFS_CLIENT_INVALID_RESPONSE: 115 | Serial.println(F("Invalid response received.")); 116 | break; 117 | } 118 | } 119 | 120 | void loop() 121 | { 122 | } -------------------------------------------------------------------------------- /examples/ipfs_cat.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "ipfs_client.h" 4 | #include 5 | 6 | const char WIFI_SSID[] = ""; 7 | const char WIFI_PASS[] = ""; 8 | 9 | const char NODE_ADDR[] = ""; 10 | const int NODE_PORT = 5001; 11 | 12 | void print_ipfs_error(IPFSClient::Result result); 13 | 14 | void setup() 15 | { 16 | Serial.begin(115200); 17 | Serial.println(F("IPFS 'cat' example")); 18 | 19 | // 20 | // Start WiFi 21 | // 22 | WiFiClient wifi_client; 23 | 24 | WiFi.begin(WIFI_SSID, WIFI_PASS); 25 | Serial.println("Connecting..."); 26 | while (WiFi.status() != WL_CONNECTED) 27 | {} 28 | Serial.println("WiFi connected."); 29 | 30 | // Init IPFS client object 31 | IPFSClient ipfs_client(wifi_client); 32 | ipfs_client.set_node_address(NODE_ADDR, NODE_PORT); 33 | 34 | // Return value for client functions 35 | IPFSClient::Result result; 36 | 37 | String output; 38 | result = ipfs_client.cat("[put IPFS CID here]", output); 39 | 40 | if(result != IPFSClient::IPFS_CLIENT_OK) 41 | { 42 | print_ipfs_error(result); 43 | } 44 | else 45 | { 46 | Serial.println(F("Returned: ")); 47 | Serial.println(output); 48 | } 49 | } 50 | 51 | /** 52 | * Print error 53 | */ 54 | void print_ipfs_error(IPFSClient::Result result) 55 | { 56 | switch(result) 57 | { 58 | case IPFSClient::Result::IPFS_CLIENT_CANNOT_CONNECT: 59 | Serial.println(F("Could not connect to IPFS.")); 60 | break; 61 | case IPFSClient::Result::IPFS_CLIENT_INVALID_RESPONSE: 62 | Serial.println(F("Invalid response received.")); 63 | break; 64 | } 65 | } 66 | 67 | void loop() 68 | { 69 | } -------------------------------------------------------------------------------- /include/ipfs_client.h: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * IPFS Client ESP32 3 | * EX-MACHINA - exm.gr 4 | * Nikos Ts. 5 | ******************************************************************************/ 6 | 7 | #ifndef IPFS_CLIENT_H 8 | #define IPFS_CLIENT_H 9 | 10 | #include 11 | #include 12 | #include "SPIFFS.h" 13 | 14 | class IPFSClient 15 | { 16 | public: 17 | /** 18 | * Return codes 19 | */ 20 | enum Result 21 | { 22 | IPFS_CLIENT_OK, 23 | IPFS_CLIENT_ERROR, 24 | IPFS_CLIENT_CANNOT_CONNECT, 25 | IPFS_CLIENT_INVALID_RESPONSE 26 | }; 27 | 28 | /** 29 | * File descriptor 30 | */ 31 | struct IPFSFile 32 | { 33 | char name[255]; 34 | char hash[50]; 35 | uint32_t size; 36 | }; 37 | 38 | /** 39 | * Command response as returned by IPFS node 40 | */ 41 | struct IPFSResponse 42 | { 43 | int code; 44 | char type[20]; 45 | char message[200]; 46 | }; 47 | 48 | IPFSClient(WiFiClient client); 49 | 50 | void set_node_address(String addr, uint16_t port); 51 | 52 | Result add(IPFSFile *file_out, String filename, String data); 53 | Result add(IPFSFile *file_out, String filename, File *spiffs_file); 54 | Result cat(String cid, String& output, int max_length = 0); 55 | Result files_cp(String from, String to); 56 | Result files_mv(String from, String to); 57 | Result files_stat(String path, JsonDocument& output_json_doc); 58 | 59 | const IPFSResponse *get_last_response(); 60 | private: 61 | // 62 | // Functions 63 | // 64 | 65 | // Default constructor private 66 | IPFSClient(){}; 67 | 68 | Result add_req(IPFSFile *file_out, String filename, String data, File *spiffs_file); 69 | 70 | Result post(String path, String* output = nullptr); 71 | void parse_last_response(String response); 72 | String build_api_path(String path); 73 | 74 | // 75 | // Vars 76 | // 77 | String _node_addr = ""; 78 | uint16_t _node_port = 0; 79 | 80 | WiFiClient _client; 81 | IPFSResponse _last_response = {0}; 82 | static const char API_PATH[]; 83 | }; 84 | 85 | #endif -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "IPFSClientESP32", 3 | "version": "0.1.1", 4 | "description": "ESP32 library for interacting with IPFS.", 5 | "keywords": "ipfs", 6 | "repository": 7 | { 8 | "type": "git", 9 | "url": "https://github.com/exmgr/ipfs-client-esp32" 10 | }, 11 | "authors": 12 | [ 13 | { 14 | "name": "EX MACHINA Nikos Tsiligaridis", 15 | "email": "nikos@exm.gr", 16 | "url": "https://www.exm.gr", 17 | "maintainer": true 18 | } 19 | ], 20 | "license": "Apache-2.0", 21 | "homepage": "https://www.exm.gr", 22 | "dependencies": { 23 | "bblanchon/ArduinoJson": "^6.17.3" 24 | }, 25 | "frameworks": "*", 26 | "platforms": "espressif32" 27 | } -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:esp32dev] 12 | platform = espressif32 13 | board = esp32dev 14 | framework = arduino 15 | upload_speed = 921600 16 | monitor_speed = 115200 17 | lib_deps = 18 | ArduinoJSON -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # IPFS Client ESP32 Arduino Library 2 | 3 | ESP32 library for interacting with IPFS. 4 | Requires ArduinoJSON. 5 | 6 | ## Usage 7 | 8 | Initialize IPFS client object 9 | ```c++ 10 | WiFiClient wifi_client(); 11 | IPFSClient ipfs_client(wifi_client); 12 | ipfs_client.set_node_address([ipfs node address], 5001); 13 | ``` 14 | 15 | ### IPFS 'add' 16 | 17 | Add text file 18 | 19 | ```c++ 20 | IPFSClient::IPFSFile ipfs_file; // Parsed return parameters 21 | 22 | ipfs_client.add(&ipfs_file, "file.txt", "Lorem IPFSum"); 23 | 24 | // Resulting CID is in ipfs_file.hash 25 | ``` 26 | 27 | Add binary file 28 | ```c++ 29 | IPFSClient::IPFSFile ipfs_file; // Parsed return parameters 30 | 31 | // Open file in SPIFFS 32 | f = SPIFFS.open("/file_in_spiffs.png", "r"); 33 | ipfs_client.add(&ipfs_file, "file.png", &f); 34 | 35 | // Resulting CID is in ipfs_file.hash 36 | ``` 37 | 38 | ### IPFS 'cat' 39 | 40 | Read data from file. 41 | 42 | ```c++ 43 | IPFSClient::Result = ipfs_client.cat("[IPFS CID here]", output); 44 | ``` 45 | 46 | Check /examples for more. 47 | 48 | ### IPFS 'cp' / 'mv/ 49 | 50 | **cp** copy file from IPFS to the Mutable File System, or between MFS directories. 51 | **mv** move file between MFS directories. 52 | 53 | ```c++ 54 | IPFSClient::Result result = ipfs_client.files_mv("/path/to/source/file", "/path/to/destination"); 55 | ``` 56 | 57 | ### IPFS 'stat' 58 | Get file status 59 | 60 | ```c++ 61 | StaticJsonDocument<200> json; 62 | IPFSClient::Result res = ipfs_client.files_stat("/path/to/file", json); 63 | 64 | Serial.print(F("Hash: ")); 65 | Serial.println(doc["Hash"].as()); 66 | Serial.print(F("Size: ")); 67 | Serial.println(doc["Size"].as(), DEC); 68 | Serial.print(F("Type: ")); 69 | Serial.println(doc["Type"].as()); -------------------------------------------------------------------------------- /src/ipfs_client.cpp: -------------------------------------------------------------------------------- 1 | #include "ipfs_client.h" 2 | #include 3 | 4 | const char IPFSClient::API_PATH[] = "/api/v0"; 5 | 6 | /****************************************************************************** 7 | * Constructor, sets wifi client. Default constructor is private. 8 | * @param client WiFi Client to use for requests 9 | ******************************************************************************/ 10 | IPFSClient::IPFSClient(WiFiClient client) : _client(client) 11 | { 12 | } 13 | 14 | /****************************************************************************** 15 | * Set node connection data 16 | * 17 | * @param addr IPFS node address 18 | * @param port IPFS node address 19 | ******************************************************************************/ 20 | void IPFSClient::set_node_address(String addr, uint16_t port) 21 | { 22 | _node_addr = addr; 23 | _node_port = port; 24 | } 25 | 26 | /****************************************************************************** 27 | * Add a plain text file to IPFS 28 | * @param file_out Parsed IPFS response (output) 29 | * @param filename Filename to submit to IFPS 30 | * @param data Data 31 | * @return Result struct 32 | ******************************************************************************/ 33 | IPFSClient::Result IPFSClient::add(IPFSFile *file_out, String filename, String data) 34 | { 35 | return add_req(file_out, filename, data, NULL); 36 | } 37 | 38 | /****************************************************************************** 39 | * Add a file to IPFS from SPIFFS 40 | * @param file_out Parsed IPFS response (output) 41 | * @param filename Filename to submit to IPFS (not related to SPIFFS filename) 42 | * @param spiffs_file Handle to an opened file in SPIFFS 43 | * @return Result struct 44 | ******************************************************************************/ 45 | IPFSClient::Result IPFSClient::add(IPFSFile *file_out, String filename, File *spiffs_file) 46 | { 47 | return add_req(file_out, filename, "", spiffs_file); 48 | } 49 | 50 | /****************************************************************************** 51 | * Add text or file data to IPFS. Used by other add functions 52 | * @param file_out Parsed IPFS response (output) 53 | * @param filename Filename to submit to IPFS (not related to SPIFFS filename) 54 | * @param data Data. Ignored if spiffs_file is set 55 | * @param spiffs_file Handle to an opened file in SPIFFS 56 | * @return Result struct 57 | ******************************************************************************/ 58 | IPFSClient::Result IPFSClient::add_req(IPFSFile *file_out, String filename, String data, File *spiffs_file = NULL) 59 | { 60 | Result ret = IPFS_CLIENT_OK; 61 | 62 | bool is_file = false; 63 | if (spiffs_file != NULL) 64 | { 65 | is_file = true; 66 | 67 | // TODO: check if file valid, eg. size > 0 68 | } 69 | 70 | // Attempt to connect if not connected already 71 | if (!_client.connected()) 72 | { 73 | if (!_client.connect(_node_addr.c_str(), _node_port)) 74 | { 75 | return IPFS_CLIENT_CANNOT_CONNECT; 76 | } 77 | } 78 | 79 | // 80 | // Main request headers 81 | // 82 | String boundary = "EXM-IPFSClient"; // + String(millis()); 83 | String boundary_start = "--" + boundary + "\r\n"; 84 | String boundary_end = "\r\n--" + boundary + "--\r\n\r\n"; 85 | 86 | String headers_main = 87 | "POST " + String(API_PATH) + "/add HTTP/1.1\r\n" 88 | "Host: " + 89 | String(_node_addr) + ":" + _node_port + "\r\n" 90 | "User-Agent: EXM-IPFSClient/1.0\r\n" 91 | "Content-Type: multipart/form-data; boundary=" + 92 | boundary + "\r\n"; 93 | 94 | // 95 | // Data part headers 96 | // 97 | String headers_file = 98 | "Content-Disposition: form-data; name=\"file\"; filename=\"" + filename + "\"\r\n"; 99 | 100 | if (is_file) 101 | { 102 | headers_file += "Content-Type: application/octet-stream\r\n\r\n"; 103 | } 104 | else 105 | { 106 | headers_file += "Content-Type: text/plain\r\n\r\n"; 107 | } 108 | 109 | // 110 | // Boundaries 111 | // 112 | int content_length = headers_file.length() + boundary_start.length() + boundary_end.length(); 113 | 114 | if (is_file) 115 | { 116 | content_length += spiffs_file->size(); 117 | } 118 | else 119 | { 120 | content_length += data.length(); 121 | } 122 | 123 | headers_main += "Content-Length: " + String(content_length) + "\n\n"; 124 | 125 | // Output request 126 | // Serial.println(F("Request: ")); 127 | // Serial.print(headers_main); 128 | // Serial.print(boundary_start); 129 | // Serial.print(headers_file); 130 | // Serial.print(data); 131 | // Serial.print(boundary_end); 132 | 133 | // Serial.println(F("Submitting request.")); 134 | 135 | _client.write(headers_main.c_str()); 136 | _client.write(boundary_start.c_str()); 137 | _client.write(headers_file.c_str()); 138 | 139 | if (is_file) 140 | { 141 | _client.write(*spiffs_file); 142 | } 143 | else 144 | { 145 | _client.write(data.c_str()); 146 | } 147 | 148 | _client.write(boundary_end.c_str()); 149 | _client.write("\r\n\r\n"); 150 | 151 | _client.setTimeout(5); 152 | 153 | // 154 | // Read headers 155 | // 156 | String line = ""; 157 | 158 | bool headers = true; 159 | StaticJsonDocument<255> json_doc; 160 | 161 | int response_code = -1; 162 | 163 | bool failed = true; 164 | while (line = _client.readStringUntil('\n')) 165 | { 166 | if (headers) 167 | { 168 | // Done with headers 169 | if (line.length() <= 1) 170 | { 171 | headers = false; 172 | } 173 | else 174 | { 175 | if (line.startsWith(F("HTTP"))) 176 | { 177 | response_code = line.substring(line.indexOf(" ")).toInt(); 178 | } 179 | } 180 | } 181 | else 182 | { 183 | if (deserializeJson(json_doc, line) == DeserializationError::Ok) 184 | { 185 | JsonObject obj = json_doc.as(); 186 | 187 | if (obj["Name"].isNull() || obj["Hash"].isNull() || obj["Size"].isNull()) 188 | { 189 | Serial.print(F("Invalid JSON object received.")); 190 | ret = IPFS_CLIENT_INVALID_RESPONSE; 191 | break; 192 | } 193 | else 194 | { 195 | failed = false; 196 | 197 | // Serial.println(F("Parsed:")); 198 | // serializeJsonPretty(json_doc, Serial); 199 | 200 | if (file_out != NULL) 201 | { 202 | strncpy(file_out->name, obj["Name"].as(), sizeof(file_out->name)); 203 | strncpy(file_out->hash, obj["Hash"].as(), sizeof(file_out->hash)); 204 | file_out->size = obj["Size"].as(); 205 | } 206 | 207 | ret = IPFS_CLIENT_OK; 208 | break; 209 | } 210 | } 211 | else 212 | { 213 | ret = IPFS_CLIENT_INVALID_RESPONSE; 214 | } 215 | } 216 | } 217 | 218 | _client.stop(); 219 | 220 | return ret; 221 | } 222 | 223 | /****************************************************************************** 224 | * Helper function to build paths to HTTP api functions 225 | * @param path Path to API function 226 | * @return Result API path URL 227 | ******************************************************************************/ 228 | String IPFSClient::build_api_path(String path) 229 | { 230 | return _node_addr + ":" + _node_port + API_PATH + path; 231 | } 232 | 233 | /****************************************************************************** 234 | * Get contents of IPFS file 235 | * @param file_out Parsed IPFS response (output) 236 | * @param filename Filename to submit to IPFS (not related to SPIFFS filename) 237 | * @param data Data. Ignored if spiffs_file is set 238 | * @param spiffs_file Handle to an opened file in SPIFFS 239 | * @return Result struct 240 | ******************************************************************************/ 241 | IPFSClient::Result IPFSClient::cat(String cid, String& output, int max_length) 242 | { 243 | HTTPClient http_client; 244 | 245 | // Build path and append required arguments 246 | String path = "/cat?arg=" + cid; 247 | if(max_length > 0) 248 | { 249 | path += "&length=" + String(max_length); 250 | } 251 | 252 | String full_path = build_api_path(path); 253 | 254 | return post(full_path, &output); 255 | } 256 | 257 | /****************************************************************************** 258 | * Copy file from IPFS/MFS to MFS 259 | * Equivalent to HTTP /files/cp 260 | * @param from Source path 261 | * @param to Destination path 262 | * @return Result struct 263 | ******************************************************************************/ 264 | IPFSClient::Result IPFSClient::files_cp(String from, String to) 265 | { 266 | HTTPClient http_client; 267 | 268 | // Prepare path/params 269 | String path = "/files/cp?arg=" + from + "&arg=" + to; 270 | String full_path = build_api_path(path); 271 | 272 | return post(full_path); 273 | } 274 | 275 | /****************************************************************************** 276 | * Move file in MFS 277 | * Equivalent to HTTP /files/mv 278 | * @param from Source path 279 | * @param to Destination path 280 | * @return Result struct 281 | ******************************************************************************/ 282 | IPFSClient::Result IPFSClient::files_mv(String from, String to) 283 | { 284 | HTTPClient http_client; 285 | 286 | // Prepare path/params 287 | String path = "/files/mv?arg=" + from + "&arg=" + to; 288 | String full_path = build_api_path(path); 289 | 290 | return post(full_path); 291 | } 292 | 293 | /****************************************************************************** 294 | * Display file status 295 | * Equivalent to HTTP /files/stat 296 | * @param path File path 297 | * @param output_json_doc Output JSON document containing the response 298 | * @return Result struct 299 | ******************************************************************************/ 300 | IPFSClient::Result IPFSClient::files_stat(String file_path, JsonDocument& output_json_doc) 301 | { 302 | HTTPClient http_client; 303 | 304 | // Build path and append required arguments 305 | String path = "/files/stat?arg=" + file_path; 306 | 307 | String full_path = build_api_path(path); 308 | 309 | String response; 310 | 311 | Result res = post(full_path, &response); 312 | 313 | if(res == IPFSClient::IPFS_CLIENT_OK) 314 | { 315 | deserializeJson(output_json_doc, response); 316 | } 317 | 318 | return res; 319 | } 320 | 321 | /****************************************************************************** 322 | * Parse response into the last response object 323 | * @param response Response received from request 324 | ******************************************************************************/ 325 | void IPFSClient::parse_last_response(String response) 326 | { 327 | StaticJsonDocument<255> json_doc; 328 | 329 | if(deserializeJson(json_doc, response) == DeserializationError::Ok) 330 | { 331 | _last_response.code = json_doc["Code"].as(); 332 | strncpy(_last_response.message, json_doc["Message"], sizeof(_last_response.message)); 333 | strncpy(_last_response.type, json_doc["Type"], sizeof(_last_response.type)); 334 | } 335 | else 336 | { 337 | memset(&_last_response, 0, sizeof(_last_response)); 338 | } 339 | } 340 | 341 | /****************************************************************************** 342 | * Get response object returned from the last executed command 343 | * @return Ptr to last response struct 344 | ******************************************************************************/ 345 | const IPFSClient::IPFSResponse* IPFSClient::get_last_response() 346 | { 347 | return &_last_response; 348 | } 349 | 350 | /****************************************************************************** 351 | * Helper function for submitting command requests. 352 | * @param Path to use along with querystring of params 353 | * @return Result struct 354 | ******************************************************************************/ 355 | IPFSClient::Result IPFSClient::post(String path, String* output) 356 | { 357 | HTTPClient http_client; 358 | 359 | Serial.print(F("POST to: ")); 360 | Serial.println(path); 361 | 362 | // Do req 363 | if(http_client.begin(path) == false) 364 | { 365 | return IPFS_CLIENT_CANNOT_CONNECT; 366 | } 367 | 368 | int response_code = http_client.POST(""); 369 | 370 | String response = http_client.getString(); 371 | 372 | if(output != nullptr) 373 | *output = response; 374 | 375 | if(response_code != 200) 376 | parse_last_response(response); 377 | 378 | // Serial.print(F("Response code: ")); 379 | // Serial.println(response_code, DEC); 380 | // Serial.print(F("Response")); 381 | // Serial.println(response); 382 | 383 | if (response_code == 200) 384 | { 385 | return IPFS_CLIENT_OK; 386 | } 387 | else if (response_code == 500) 388 | { 389 | return IPFS_CLIENT_ERROR; 390 | } 391 | else 392 | { 393 | return IPFS_CLIENT_INVALID_RESPONSE; 394 | } 395 | } -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Unit Testing and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/page/plus/unit-testing.html 12 | --------------------------------------------------------------------------------