Testing a small http-client on Linux using no_std & embedded reqwless.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

add no_std reqwless linux downloader

rektide b613864f 7fe37c3b

+982 -150
-150
AGENTS.md
··· 1 - # Agent Instructions 2 - 3 - This project uses **bd** (beads) for issue tracking. Run `bd onboard` to get started. 4 - 5 - ## Quick Reference 6 - 7 - ```bash 8 - bd ready # Find available work 9 - bd show <id> # View issue details 10 - bd update <id> --claim # Claim work atomically 11 - bd close <id> # Complete work 12 - bd sync # Sync with git 13 - ``` 14 - 15 - ## Non-Interactive Shell Commands 16 - 17 - **ALWAYS use non-interactive flags** with file operations to avoid hanging on confirmation prompts. 18 - 19 - Shell commands like `cp`, `mv`, and `rm` may be aliased to include `-i` (interactive) mode on some systems, causing the agent to hang indefinitely waiting for y/n input. 20 - 21 - **Use these forms instead:** 22 - ```bash 23 - # Force overwrite without prompting 24 - cp -f source dest # NOT: cp source dest 25 - mv -f source dest # NOT: mv source dest 26 - rm -f file # NOT: rm file 27 - 28 - # For recursive operations 29 - rm -rf directory # NOT: rm -r directory 30 - cp -rf source dest # NOT: cp -r source dest 31 - ``` 32 - 33 - **Other commands that may prompt:** 34 - - `scp` - use `-o BatchMode=yes` for non-interactive 35 - - `ssh` - use `-o BatchMode=yes` to fail instead of prompting 36 - - `apt-get` - use `-y` flag 37 - - `brew` - use `HOMEBREW_NO_AUTO_UPDATE=1` env var 38 - 39 - <!-- BEGIN BEADS INTEGRATION --> 40 - ## Issue Tracking with bd (beads) 41 - 42 - **IMPORTANT**: This project uses **bd (beads)** for ALL issue tracking. Do NOT use markdown TODOs, task lists, or other tracking methods. 43 - 44 - ### Why bd? 45 - 46 - - Dependency-aware: Track blockers and relationships between issues 47 - - Version-controlled: Built on Dolt with cell-level merge 48 - - Agent-optimized: JSON output, ready work detection, discovered-from links 49 - - Prevents duplicate tracking systems and confusion 50 - 51 - ### Quick Start 52 - 53 - **Check for ready work:** 54 - 55 - ```bash 56 - bd ready --json 57 - ``` 58 - 59 - **Create new issues:** 60 - 61 - ```bash 62 - bd create "Issue title" --description="Detailed context" -t bug|feature|task -p 0-4 --json 63 - bd create "Issue title" --description="What this issue is about" -p 1 --deps discovered-from:bd-123 --json 64 - ``` 65 - 66 - **Claim and update:** 67 - 68 - ```bash 69 - bd update <id> --claim --json 70 - bd update bd-42 --priority 1 --json 71 - ``` 72 - 73 - **Complete work:** 74 - 75 - ```bash 76 - bd close bd-42 --reason "Completed" --json 77 - ``` 78 - 79 - ### Issue Types 80 - 81 - - `bug` - Something broken 82 - - `feature` - New functionality 83 - - `task` - Work item (tests, docs, refactoring) 84 - - `epic` - Large feature with subtasks 85 - - `chore` - Maintenance (dependencies, tooling) 86 - 87 - ### Priorities 88 - 89 - - `0` - Critical (security, data loss, broken builds) 90 - - `1` - High (major features, important bugs) 91 - - `2` - Medium (default, nice-to-have) 92 - - `3` - Low (polish, optimization) 93 - - `4` - Backlog (future ideas) 94 - 95 - ### Workflow for AI Agents 96 - 97 - 1. **Check ready work**: `bd ready` shows unblocked issues 98 - 2. **Claim your task atomically**: `bd update <id> --claim` 99 - 3. **Work on it**: Implement, test, document 100 - 4. **Discover new work?** Create linked issue: 101 - - `bd create "Found bug" --description="Details about what was found" -p 1 --deps discovered-from:<parent-id>` 102 - 5. **Complete**: `bd close <id> --reason "Done"` 103 - 104 - ### Auto-Sync 105 - 106 - bd automatically syncs with git: 107 - 108 - - Exports to `.beads/issues.jsonl` after changes (5s debounce) 109 - - Imports from JSONL when newer (e.g., after `git pull`) 110 - - No manual export/import needed! 111 - 112 - ### Important Rules 113 - 114 - - ✅ Use bd for ALL task tracking 115 - - ✅ Always use `--json` flag for programmatic use 116 - - ✅ Link discovered work with `discovered-from` dependencies 117 - - ✅ Check `bd ready` before asking "what should I work on?" 118 - - ❌ Do NOT create markdown TODO lists 119 - - ❌ Do NOT use external issue trackers 120 - - ❌ Do NOT duplicate tracking systems 121 - 122 - For more details, see README.md and docs/QUICKSTART.md. 123 - 124 - ## Landing the Plane (Session Completion) 125 - 126 - **When ending a work session**, you MUST complete ALL steps below. Work is NOT complete until `git push` succeeds. 127 - 128 - **MANDATORY WORKFLOW:** 129 - 130 - 1. **File issues for remaining work** - Create issues for anything that needs follow-up 131 - 2. **Run quality gates** (if code changed) - Tests, linters, builds 132 - 3. **Update issue status** - Close finished work, update in-progress items 133 - 4. **PUSH TO REMOTE** - This is MANDATORY: 134 - ```bash 135 - git pull --rebase 136 - bd sync 137 - git push 138 - git status # MUST show "up to date with origin" 139 - ``` 140 - 5. **Clean up** - Clear stashes, prune remote branches 141 - 6. **Verify** - All changes committed AND pushed 142 - 7. **Hand off** - Provide context for next session 143 - 144 - **CRITICAL RULES:** 145 - - Work is NOT complete until `git push` succeeds 146 - - NEVER stop before pushing - that leaves work stranded locally 147 - - NEVER say "ready to push when you are" - YOU must push 148 - - If push fails, resolve and retry until it succeeds 149 - 150 - <!-- END BEADS INTEGRATION -->
+496
Cargo.lock
··· 1 + # This file is automatically @generated by Cargo. 2 + # It is not intended for manual editing. 3 + version = 4 4 + 5 + [[package]] 6 + name = "base16ct" 7 + version = "0.2.0" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" 10 + 11 + [[package]] 12 + name = "base64" 13 + version = "0.21.7" 14 + source = "registry+https://github.com/rust-lang/crates.io-index" 15 + checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 16 + 17 + [[package]] 18 + name = "block-buffer" 19 + version = "0.10.4" 20 + source = "registry+https://github.com/rust-lang/crates.io-index" 21 + checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 22 + dependencies = [ 23 + "generic-array", 24 + ] 25 + 26 + [[package]] 27 + name = "buffered-io" 28 + version = "0.6.0" 29 + source = "registry+https://github.com/rust-lang/crates.io-index" 30 + checksum = "6a4372fa7a38648577b85b4a0c2a559be9fbc131ca27f6f2f2cf250a4bd45a03" 31 + dependencies = [ 32 + "embedded-io", 33 + "embedded-io-async", 34 + ] 35 + 36 + [[package]] 37 + name = "byteorder" 38 + version = "1.5.0" 39 + source = "registry+https://github.com/rust-lang/crates.io-index" 40 + checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 41 + 42 + [[package]] 43 + name = "cfg-if" 44 + version = "1.0.4" 45 + source = "registry+https://github.com/rust-lang/crates.io-index" 46 + checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 47 + 48 + [[package]] 49 + name = "const-oid" 50 + version = "0.9.6" 51 + source = "registry+https://github.com/rust-lang/crates.io-index" 52 + checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" 53 + 54 + [[package]] 55 + name = "cpufeatures" 56 + version = "0.2.17" 57 + source = "registry+https://github.com/rust-lang/crates.io-index" 58 + checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 59 + dependencies = [ 60 + "libc", 61 + ] 62 + 63 + [[package]] 64 + name = "crypto-bigint" 65 + version = "0.5.5" 66 + source = "registry+https://github.com/rust-lang/crates.io-index" 67 + checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" 68 + dependencies = [ 69 + "generic-array", 70 + "rand_core", 71 + "subtle", 72 + "zeroize", 73 + ] 74 + 75 + [[package]] 76 + name = "crypto-common" 77 + version = "0.1.6" 78 + source = "registry+https://github.com/rust-lang/crates.io-index" 79 + checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 80 + dependencies = [ 81 + "generic-array", 82 + "typenum", 83 + ] 84 + 85 + [[package]] 86 + name = "der" 87 + version = "0.7.10" 88 + source = "registry+https://github.com/rust-lang/crates.io-index" 89 + checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" 90 + dependencies = [ 91 + "const-oid", 92 + "zeroize", 93 + ] 94 + 95 + [[package]] 96 + name = "digest" 97 + version = "0.10.7" 98 + source = "registry+https://github.com/rust-lang/crates.io-index" 99 + checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 100 + dependencies = [ 101 + "block-buffer", 102 + "const-oid", 103 + "crypto-common", 104 + "subtle", 105 + ] 106 + 107 + [[package]] 108 + name = "ecdsa" 109 + version = "0.16.9" 110 + source = "registry+https://github.com/rust-lang/crates.io-index" 111 + checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" 112 + dependencies = [ 113 + "der", 114 + "digest", 115 + "elliptic-curve", 116 + "rfc6979", 117 + "signature", 118 + ] 119 + 120 + [[package]] 121 + name = "elliptic-curve" 122 + version = "0.13.8" 123 + source = "registry+https://github.com/rust-lang/crates.io-index" 124 + checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" 125 + dependencies = [ 126 + "base16ct", 127 + "crypto-bigint", 128 + "digest", 129 + "ff", 130 + "generic-array", 131 + "group", 132 + "hkdf", 133 + "rand_core", 134 + "sec1", 135 + "subtle", 136 + "zeroize", 137 + ] 138 + 139 + [[package]] 140 + name = "embedded-io" 141 + version = "0.7.1" 142 + source = "registry+https://github.com/rust-lang/crates.io-index" 143 + checksum = "9eb1aa714776b75c7e67e1da744b81a129b3ff919c8712b5e1b32252c1f07cc7" 144 + 145 + [[package]] 146 + name = "embedded-io-async" 147 + version = "0.7.0" 148 + source = "registry+https://github.com/rust-lang/crates.io-index" 149 + checksum = "2564b9f813c544241430e147d8bc454815ef9ac998878d30cc3055449f7fd4c0" 150 + dependencies = [ 151 + "embedded-io", 152 + ] 153 + 154 + [[package]] 155 + name = "embedded-nal" 156 + version = "0.9.0" 157 + source = "registry+https://github.com/rust-lang/crates.io-index" 158 + checksum = "c56a28be191a992f28f178ec338a0bf02f63d7803244add736d026a471e6ed77" 159 + dependencies = [ 160 + "nb", 161 + ] 162 + 163 + [[package]] 164 + name = "embedded-nal-async" 165 + version = "0.9.0" 166 + source = "registry+https://github.com/rust-lang/crates.io-index" 167 + checksum = "eb5a1bd585135d302f8f6d7de329310938093da6271b37a6c94b8798795c0c6d" 168 + dependencies = [ 169 + "embedded-io-async", 170 + "embedded-nal", 171 + ] 172 + 173 + [[package]] 174 + name = "ff" 175 + version = "0.13.1" 176 + source = "registry+https://github.com/rust-lang/crates.io-index" 177 + checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" 178 + dependencies = [ 179 + "rand_core", 180 + "subtle", 181 + ] 182 + 183 + [[package]] 184 + name = "generic-array" 185 + version = "0.14.9" 186 + source = "registry+https://github.com/rust-lang/crates.io-index" 187 + checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" 188 + dependencies = [ 189 + "typenum", 190 + "version_check", 191 + "zeroize", 192 + ] 193 + 194 + [[package]] 195 + name = "group" 196 + version = "0.13.0" 197 + source = "registry+https://github.com/rust-lang/crates.io-index" 198 + checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" 199 + dependencies = [ 200 + "ff", 201 + "rand_core", 202 + "subtle", 203 + ] 204 + 205 + [[package]] 206 + name = "hash32" 207 + version = "0.3.1" 208 + source = "registry+https://github.com/rust-lang/crates.io-index" 209 + checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" 210 + dependencies = [ 211 + "byteorder", 212 + ] 213 + 214 + [[package]] 215 + name = "heapless" 216 + version = "0.9.2" 217 + source = "registry+https://github.com/rust-lang/crates.io-index" 218 + checksum = "2af2455f757db2b292a9b1768c4b70186d443bcb3b316252d6b540aec1cd89ed" 219 + dependencies = [ 220 + "hash32", 221 + "stable_deref_trait", 222 + ] 223 + 224 + [[package]] 225 + name = "hex" 226 + version = "0.4.3" 227 + source = "registry+https://github.com/rust-lang/crates.io-index" 228 + checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 229 + 230 + [[package]] 231 + name = "hkdf" 232 + version = "0.12.4" 233 + source = "registry+https://github.com/rust-lang/crates.io-index" 234 + checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" 235 + dependencies = [ 236 + "hmac", 237 + ] 238 + 239 + [[package]] 240 + name = "hmac" 241 + version = "0.12.1" 242 + source = "registry+https://github.com/rust-lang/crates.io-index" 243 + checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 244 + dependencies = [ 245 + "digest", 246 + ] 247 + 248 + [[package]] 249 + name = "httparse" 250 + version = "1.10.1" 251 + source = "registry+https://github.com/rust-lang/crates.io-index" 252 + checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 253 + 254 + [[package]] 255 + name = "libc" 256 + version = "0.2.183" 257 + source = "registry+https://github.com/rust-lang/crates.io-index" 258 + checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" 259 + 260 + [[package]] 261 + name = "nb" 262 + version = "1.1.0" 263 + source = "registry+https://github.com/rust-lang/crates.io-index" 264 + checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" 265 + 266 + [[package]] 267 + name = "nourl" 268 + version = "0.1.4" 269 + source = "registry+https://github.com/rust-lang/crates.io-index" 270 + checksum = "aa07b0722c63805057dec824444fdc814bdfd30d1c782a3a8f63bbcf67c4ed1c" 271 + 272 + [[package]] 273 + name = "p256" 274 + version = "0.13.2" 275 + source = "registry+https://github.com/rust-lang/crates.io-index" 276 + checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" 277 + dependencies = [ 278 + "ecdsa", 279 + "elliptic-curve", 280 + "primeorder", 281 + "sha2", 282 + ] 283 + 284 + [[package]] 285 + name = "pkcs8" 286 + version = "0.10.2" 287 + source = "registry+https://github.com/rust-lang/crates.io-index" 288 + checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" 289 + dependencies = [ 290 + "der", 291 + "spki", 292 + ] 293 + 294 + [[package]] 295 + name = "ppv-lite86" 296 + version = "0.2.21" 297 + source = "registry+https://github.com/rust-lang/crates.io-index" 298 + checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 299 + dependencies = [ 300 + "zerocopy", 301 + ] 302 + 303 + [[package]] 304 + name = "primeorder" 305 + version = "0.13.6" 306 + source = "registry+https://github.com/rust-lang/crates.io-index" 307 + checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" 308 + dependencies = [ 309 + "elliptic-curve", 310 + ] 311 + 312 + [[package]] 313 + name = "proc-macro2" 314 + version = "1.0.106" 315 + source = "registry+https://github.com/rust-lang/crates.io-index" 316 + checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" 317 + dependencies = [ 318 + "unicode-ident", 319 + ] 320 + 321 + [[package]] 322 + name = "quote" 323 + version = "1.0.45" 324 + source = "registry+https://github.com/rust-lang/crates.io-index" 325 + checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" 326 + dependencies = [ 327 + "proc-macro2", 328 + ] 329 + 330 + [[package]] 331 + name = "rand_chacha" 332 + version = "0.3.1" 333 + source = "registry+https://github.com/rust-lang/crates.io-index" 334 + checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 335 + dependencies = [ 336 + "ppv-lite86", 337 + "rand_core", 338 + ] 339 + 340 + [[package]] 341 + name = "rand_core" 342 + version = "0.6.4" 343 + source = "registry+https://github.com/rust-lang/crates.io-index" 344 + checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 345 + 346 + [[package]] 347 + name = "reqwless" 348 + version = "0.14.0" 349 + source = "registry+https://github.com/rust-lang/crates.io-index" 350 + checksum = "836acee7034acc981677b98cd9e6cf123c7d9174be376281c86d8248fe5169eb" 351 + dependencies = [ 352 + "base64", 353 + "buffered-io", 354 + "embedded-io", 355 + "embedded-io-async", 356 + "embedded-nal-async", 357 + "heapless", 358 + "hex", 359 + "httparse", 360 + "nourl", 361 + "p256", 362 + "pkcs8", 363 + "rand_chacha", 364 + "rand_core", 365 + ] 366 + 367 + [[package]] 368 + name = "reqwless-linux" 369 + version = "0.1.0" 370 + dependencies = [ 371 + "embedded-io", 372 + "embedded-io-async", 373 + "embedded-nal-async", 374 + "libc", 375 + "reqwless", 376 + ] 377 + 378 + [[package]] 379 + name = "rfc6979" 380 + version = "0.4.0" 381 + source = "registry+https://github.com/rust-lang/crates.io-index" 382 + checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" 383 + dependencies = [ 384 + "hmac", 385 + "subtle", 386 + ] 387 + 388 + [[package]] 389 + name = "sec1" 390 + version = "0.7.3" 391 + source = "registry+https://github.com/rust-lang/crates.io-index" 392 + checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" 393 + dependencies = [ 394 + "base16ct", 395 + "der", 396 + "generic-array", 397 + "subtle", 398 + "zeroize", 399 + ] 400 + 401 + [[package]] 402 + name = "sha2" 403 + version = "0.10.9" 404 + source = "registry+https://github.com/rust-lang/crates.io-index" 405 + checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" 406 + dependencies = [ 407 + "cfg-if", 408 + "cpufeatures", 409 + "digest", 410 + ] 411 + 412 + [[package]] 413 + name = "signature" 414 + version = "2.2.0" 415 + source = "registry+https://github.com/rust-lang/crates.io-index" 416 + checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" 417 + dependencies = [ 418 + "digest", 419 + "rand_core", 420 + ] 421 + 422 + [[package]] 423 + name = "spki" 424 + version = "0.7.3" 425 + source = "registry+https://github.com/rust-lang/crates.io-index" 426 + checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" 427 + dependencies = [ 428 + "der", 429 + ] 430 + 431 + [[package]] 432 + name = "stable_deref_trait" 433 + version = "1.2.1" 434 + source = "registry+https://github.com/rust-lang/crates.io-index" 435 + checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" 436 + 437 + [[package]] 438 + name = "subtle" 439 + version = "2.6.1" 440 + source = "registry+https://github.com/rust-lang/crates.io-index" 441 + checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 442 + 443 + [[package]] 444 + name = "syn" 445 + version = "2.0.117" 446 + source = "registry+https://github.com/rust-lang/crates.io-index" 447 + checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" 448 + dependencies = [ 449 + "proc-macro2", 450 + "quote", 451 + "unicode-ident", 452 + ] 453 + 454 + [[package]] 455 + name = "typenum" 456 + version = "1.19.0" 457 + source = "registry+https://github.com/rust-lang/crates.io-index" 458 + checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" 459 + 460 + [[package]] 461 + name = "unicode-ident" 462 + version = "1.0.24" 463 + source = "registry+https://github.com/rust-lang/crates.io-index" 464 + checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" 465 + 466 + [[package]] 467 + name = "version_check" 468 + version = "0.9.5" 469 + source = "registry+https://github.com/rust-lang/crates.io-index" 470 + checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 471 + 472 + [[package]] 473 + name = "zerocopy" 474 + version = "0.8.47" 475 + source = "registry+https://github.com/rust-lang/crates.io-index" 476 + checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" 477 + dependencies = [ 478 + "zerocopy-derive", 479 + ] 480 + 481 + [[package]] 482 + name = "zerocopy-derive" 483 + version = "0.8.47" 484 + source = "registry+https://github.com/rust-lang/crates.io-index" 485 + checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" 486 + dependencies = [ 487 + "proc-macro2", 488 + "quote", 489 + "syn", 490 + ] 491 + 492 + [[package]] 493 + name = "zeroize" 494 + version = "1.8.2" 495 + source = "registry+https://github.com/rust-lang/crates.io-index" 496 + checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
+17
Cargo.toml
··· 1 + [package] 2 + name = "reqwless-linux" 3 + version = "0.1.0" 4 + edition = "2024" 5 + 6 + [dependencies] 7 + embedded-io = { version = "0.7", default-features = false } 8 + embedded-io-async = { version = "0.7", default-features = false } 9 + embedded-nal-async = { version = "0.9", default-features = false } 10 + libc = { version = "0.2", default-features = false } 11 + reqwless = { version = "0.14", default-features = false } 12 + 13 + [profile.dev] 14 + panic = "abort" 15 + 16 + [profile.release] 17 + panic = "abort"
+18
README.md
··· 1 + # reqwless-linux 2 + 3 + > A demonstration of a no_std reqwless http downloader for linux 4 + 5 + ## Usage 6 + 7 + ```bash 8 + cargo run -- http://example.com 9 + ``` 10 + 11 + This prints the HTTP response body to stdout. 12 + 13 + ## Notes 14 + 15 + - `no_std` + `no_main` binary using Linux libc syscalls 16 + - Uses `reqwless` with `default-features = false` (HTTP only) 17 + - Uses fixed-size stack buffers for request/response and output chunks 18 + - Performs best-effort low-allocation operation (no `String`/`Vec` in app code)
+451
src/main.rs
··· 1 + #![no_std] 2 + #![no_main] 3 + 4 + // pattern: Imperative Shell 5 + 6 + use core::ffi::{CStr, c_char, c_int, c_void}; 7 + use core::future::Future; 8 + use core::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; 9 + use core::panic::PanicInfo; 10 + use core::pin::Pin; 11 + use core::ptr; 12 + use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; 13 + 14 + use embedded_io::ErrorKind; 15 + use embedded_io_async::{ErrorType, Read, Write}; 16 + use embedded_nal_async::{AddrType, Dns, TcpConnect}; 17 + use reqwless::TryBufRead; 18 + use reqwless::client::HttpClient; 19 + use reqwless::request::Method; 20 + 21 + const EXIT_OK: c_int = 0; 22 + const EXIT_USAGE: c_int = 2; 23 + const EXIT_FAILURE: c_int = 1; 24 + 25 + const DNS_HOST_MAX: usize = 255; 26 + 27 + struct LinuxTcp; 28 + 29 + struct LinuxDns; 30 + 31 + struct LinuxConnection { 32 + fd: c_int, 33 + } 34 + 35 + #[derive(Debug)] 36 + enum AppError { 37 + MissingUrl, 38 + InvalidUtf8, 39 + UrlTooLong, 40 + NulInHost, 41 + DnsLookupFailed, 42 + AddrTypeMismatch, 43 + Syscall(i32), 44 + Http(reqwless::Error), 45 + } 46 + 47 + impl embedded_io::Error for AppError { 48 + fn kind(&self) -> ErrorKind { 49 + match self { 50 + Self::MissingUrl => ErrorKind::InvalidInput, 51 + Self::InvalidUtf8 => ErrorKind::InvalidData, 52 + Self::UrlTooLong => ErrorKind::InvalidInput, 53 + Self::NulInHost => ErrorKind::InvalidInput, 54 + Self::DnsLookupFailed => ErrorKind::NotFound, 55 + Self::AddrTypeMismatch => ErrorKind::AddrNotAvailable, 56 + Self::Syscall(code) => match *code { 57 + libc::EINTR => ErrorKind::Interrupted, 58 + libc::EINVAL => ErrorKind::InvalidInput, 59 + libc::ENOMEM => ErrorKind::OutOfMemory, 60 + libc::ENOTCONN => ErrorKind::NotConnected, 61 + libc::ECONNRESET => ErrorKind::ConnectionReset, 62 + libc::ECONNABORTED => ErrorKind::ConnectionAborted, 63 + libc::ECONNREFUSED => ErrorKind::ConnectionRefused, 64 + libc::EADDRINUSE => ErrorKind::AddrInUse, 65 + libc::EADDRNOTAVAIL => ErrorKind::AddrNotAvailable, 66 + libc::EPIPE => ErrorKind::BrokenPipe, 67 + libc::EWOULDBLOCK => ErrorKind::Other, 68 + libc::ETIMEDOUT => ErrorKind::TimedOut, 69 + _ => ErrorKind::Other, 70 + }, 71 + Self::Http(err) => err.kind(), 72 + } 73 + } 74 + } 75 + 76 + impl core::fmt::Display for AppError { 77 + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 78 + let message = match self { 79 + Self::MissingUrl => "missing url argument", 80 + Self::InvalidUtf8 => "url argument must be valid utf-8", 81 + Self::UrlTooLong => "hostname is too long", 82 + Self::NulInHost => "hostname contains NUL byte", 83 + Self::DnsLookupFailed => "dns lookup failed", 84 + Self::AddrTypeMismatch => "address type mismatch", 85 + Self::Syscall(_) => "linux syscall failed", 86 + Self::Http(_) => "http request failed", 87 + }; 88 + 89 + f.write_str(message) 90 + } 91 + } 92 + 93 + impl core::error::Error for AppError {} 94 + 95 + impl ErrorType for LinuxConnection { 96 + type Error = AppError; 97 + } 98 + 99 + impl Read for LinuxConnection { 100 + async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> { 101 + if buf.is_empty() { 102 + return Ok(0); 103 + } 104 + 105 + let rc = unsafe { libc::recv(self.fd, buf.as_mut_ptr().cast::<c_void>(), buf.len(), 0) }; 106 + if rc < 0 { 107 + return Err(AppError::Syscall(last_errno())); 108 + } 109 + 110 + Ok(rc as usize) 111 + } 112 + } 113 + 114 + impl Write for LinuxConnection { 115 + async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> { 116 + if buf.is_empty() { 117 + return Ok(0); 118 + } 119 + 120 + let rc = unsafe { libc::send(self.fd, buf.as_ptr().cast::<c_void>(), buf.len(), 0) }; 121 + if rc < 0 { 122 + return Err(AppError::Syscall(last_errno())); 123 + } 124 + 125 + Ok(rc as usize) 126 + } 127 + 128 + async fn flush(&mut self) -> Result<(), Self::Error> { 129 + Ok(()) 130 + } 131 + } 132 + 133 + impl Drop for LinuxConnection { 134 + fn drop(&mut self) { 135 + unsafe { 136 + let _ = libc::close(self.fd); 137 + } 138 + } 139 + } 140 + 141 + impl TryBufRead for LinuxConnection {} 142 + 143 + impl TcpConnect for LinuxTcp { 144 + type Error = AppError; 145 + type Connection<'m> = LinuxConnection; 146 + 147 + async fn connect<'m>(&'m self, remote: SocketAddr) -> Result<Self::Connection<'m>, Self::Error> { 148 + let (domain, storage, len) = sockaddr_from_socket_addr(remote); 149 + 150 + let fd = unsafe { libc::socket(domain, libc::SOCK_STREAM, 0) }; 151 + if fd < 0 { 152 + return Err(AppError::Syscall(last_errno())); 153 + } 154 + 155 + let rc = unsafe { libc::connect(fd, &storage as *const libc::sockaddr_storage as *const libc::sockaddr, len) }; 156 + if rc < 0 { 157 + let error = AppError::Syscall(last_errno()); 158 + unsafe { 159 + let _ = libc::close(fd); 160 + } 161 + return Err(error); 162 + } 163 + 164 + Ok(LinuxConnection { fd }) 165 + } 166 + } 167 + 168 + impl Dns for LinuxDns { 169 + type Error = AppError; 170 + 171 + async fn get_host_by_name(&self, host: &str, addr_type: AddrType) -> Result<IpAddr, Self::Error> { 172 + if let Ok(parsed) = host.parse::<IpAddr>() { 173 + if matches_addr_type(parsed, addr_type) { 174 + return Ok(parsed); 175 + } 176 + return Err(AppError::AddrTypeMismatch); 177 + } 178 + 179 + resolve_host(host, addr_type) 180 + } 181 + 182 + async fn get_host_by_address(&self, _addr: IpAddr, _result: &mut [u8]) -> Result<usize, Self::Error> { 183 + Err(AppError::DnsLookupFailed) 184 + } 185 + } 186 + 187 + #[unsafe(no_mangle)] 188 + pub extern "C" fn main(argc: c_int, argv: *const *const c_char) -> c_int { 189 + let url = match read_url_argument(argc, argv) { 190 + Ok(url) => url, 191 + Err(AppError::MissingUrl) => { 192 + write_stderr(b"usage: reqwless-linux <http://url>\n"); 193 + return EXIT_USAGE; 194 + } 195 + Err(error) => { 196 + report_error(error); 197 + return EXIT_FAILURE; 198 + } 199 + }; 200 + 201 + match block_on(download_to_stdout(url)) { 202 + Ok(()) => EXIT_OK, 203 + Err(error) => { 204 + report_error(error); 205 + EXIT_FAILURE 206 + } 207 + } 208 + } 209 + 210 + async fn download_to_stdout(url: &str) -> Result<(), AppError> { 211 + let tcp = LinuxTcp; 212 + let dns = LinuxDns; 213 + let mut client = HttpClient::new(&tcp, &dns); 214 + let mut rx_buf = [0_u8; 8192]; 215 + 216 + let mut request = client.request(Method::GET, url).await.map_err(AppError::Http)?; 217 + let response = request.send(&mut rx_buf).await.map_err(AppError::Http)?; 218 + let mut body = response.body().reader(); 219 + 220 + let mut chunk = [0_u8; 1024]; 221 + loop { 222 + let read = body.read(&mut chunk).await.map_err(AppError::Http)?; 223 + if read == 0 { 224 + break; 225 + } 226 + write_all_fd(libc::STDOUT_FILENO, &chunk[..read])?; 227 + } 228 + 229 + Ok(()) 230 + } 231 + 232 + fn read_url_argument<'a>(argc: c_int, argv: *const *const c_char) -> Result<&'a str, AppError> { 233 + if argc != 2 || argv.is_null() { 234 + return Err(AppError::MissingUrl); 235 + } 236 + 237 + let url_ptr = unsafe { *argv.add(1) }; 238 + if url_ptr.is_null() { 239 + return Err(AppError::MissingUrl); 240 + } 241 + 242 + let cstr = unsafe { CStr::from_ptr(url_ptr) }; 243 + cstr.to_str().map_err(|_| AppError::InvalidUtf8) 244 + } 245 + 246 + fn resolve_host(host: &str, addr_type: AddrType) -> Result<IpAddr, AppError> { 247 + if host.len() > DNS_HOST_MAX { 248 + return Err(AppError::UrlTooLong); 249 + } 250 + if host.as_bytes().contains(&0) { 251 + return Err(AppError::NulInHost); 252 + } 253 + 254 + let mut host_buf = [0_u8; DNS_HOST_MAX + 1]; 255 + host_buf[..host.len()].copy_from_slice(host.as_bytes()); 256 + host_buf[host.len()] = 0; 257 + 258 + let mut hints: libc::addrinfo = unsafe { core::mem::zeroed() }; 259 + hints.ai_socktype = libc::SOCK_STREAM; 260 + hints.ai_family = match addr_type { 261 + AddrType::IPv4 => libc::AF_INET, 262 + AddrType::IPv6 => libc::AF_INET6, 263 + AddrType::Either => libc::AF_UNSPEC, 264 + }; 265 + 266 + let mut result: *mut libc::addrinfo = ptr::null_mut(); 267 + let lookup_rc = unsafe { 268 + libc::getaddrinfo( 269 + host_buf.as_ptr().cast::<c_char>(), 270 + ptr::null(), 271 + &hints, 272 + &mut result, 273 + ) 274 + }; 275 + if lookup_rc != 0 { 276 + return Err(AppError::DnsLookupFailed); 277 + } 278 + 279 + let mut cursor = result; 280 + let mut found = None; 281 + while !cursor.is_null() { 282 + let family = unsafe { (*cursor).ai_family }; 283 + if family == libc::AF_INET { 284 + let sockaddr = unsafe { &*((*cursor).ai_addr as *const libc::sockaddr_in) }; 285 + let octets = sockaddr.sin_addr.s_addr.to_ne_bytes(); 286 + found = Some(IpAddr::V4(Ipv4Addr::from(octets))); 287 + break; 288 + } 289 + if family == libc::AF_INET6 { 290 + let sockaddr = unsafe { &*((*cursor).ai_addr as *const libc::sockaddr_in6) }; 291 + found = Some(IpAddr::V6(Ipv6Addr::from(sockaddr.sin6_addr.s6_addr))); 292 + break; 293 + } 294 + cursor = unsafe { (*cursor).ai_next }; 295 + } 296 + 297 + unsafe { 298 + libc::freeaddrinfo(result); 299 + } 300 + 301 + found.ok_or(AppError::DnsLookupFailed) 302 + } 303 + 304 + fn sockaddr_from_socket_addr(remote: SocketAddr) -> (c_int, libc::sockaddr_storage, libc::socklen_t) { 305 + match remote { 306 + SocketAddr::V4(addr) => { 307 + let mut storage: libc::sockaddr_storage = unsafe { core::mem::zeroed() }; 308 + let in_addr = libc::sockaddr_in { 309 + sin_family: libc::AF_INET as u16, 310 + sin_port: addr.port().to_be(), 311 + sin_addr: libc::in_addr { 312 + s_addr: u32::from_ne_bytes(addr.ip().octets()), 313 + }, 314 + sin_zero: [0; 8], 315 + }; 316 + 317 + unsafe { 318 + ptr::write((&mut storage as *mut libc::sockaddr_storage).cast::<libc::sockaddr_in>(), in_addr); 319 + } 320 + 321 + ( 322 + libc::AF_INET, 323 + storage, 324 + core::mem::size_of::<libc::sockaddr_in>() as libc::socklen_t, 325 + ) 326 + } 327 + SocketAddr::V6(addr) => { 328 + let mut storage: libc::sockaddr_storage = unsafe { core::mem::zeroed() }; 329 + let in6_addr = libc::sockaddr_in6 { 330 + sin6_family: libc::AF_INET6 as u16, 331 + sin6_port: addr.port().to_be(), 332 + sin6_flowinfo: addr.flowinfo(), 333 + sin6_addr: libc::in6_addr { 334 + s6_addr: addr.ip().octets(), 335 + }, 336 + sin6_scope_id: addr.scope_id(), 337 + }; 338 + 339 + unsafe { 340 + ptr::write((&mut storage as *mut libc::sockaddr_storage).cast::<libc::sockaddr_in6>(), in6_addr); 341 + } 342 + 343 + ( 344 + libc::AF_INET6, 345 + storage, 346 + core::mem::size_of::<libc::sockaddr_in6>() as libc::socklen_t, 347 + ) 348 + } 349 + } 350 + } 351 + 352 + fn matches_addr_type(addr: IpAddr, addr_type: AddrType) -> bool { 353 + match (addr, addr_type) { 354 + (IpAddr::V4(_), AddrType::IPv4) => true, 355 + (IpAddr::V6(_), AddrType::IPv6) => true, 356 + (_, AddrType::Either) => true, 357 + _ => false, 358 + } 359 + } 360 + 361 + fn last_errno() -> i32 { 362 + unsafe { *libc::__errno_location() } 363 + } 364 + 365 + fn write_all_fd(fd: c_int, mut buf: &[u8]) -> Result<(), AppError> { 366 + while !buf.is_empty() { 367 + let written = unsafe { libc::write(fd, buf.as_ptr().cast::<c_void>(), buf.len()) }; 368 + if written < 0 { 369 + let errno = last_errno(); 370 + if errno == libc::EINTR { 371 + continue; 372 + } 373 + return Err(AppError::Syscall(errno)); 374 + } 375 + 376 + let written = written as usize; 377 + if written == 0 { 378 + return Err(AppError::Syscall(libc::EIO)); 379 + } 380 + buf = &buf[written..]; 381 + } 382 + 383 + Ok(()) 384 + } 385 + 386 + fn write_stderr(msg: &[u8]) { 387 + let _ = write_all_fd(libc::STDERR_FILENO, msg); 388 + } 389 + 390 + fn report_error(error: AppError) { 391 + match error { 392 + AppError::MissingUrl => write_stderr(b"missing url argument\n"), 393 + AppError::InvalidUtf8 => write_stderr(b"url argument must be valid utf-8\n"), 394 + AppError::UrlTooLong => write_stderr(b"hostname is too long\n"), 395 + AppError::NulInHost => write_stderr(b"hostname contains NUL byte\n"), 396 + AppError::DnsLookupFailed => write_stderr(b"dns lookup failed\n"), 397 + AppError::AddrTypeMismatch => write_stderr(b"address type mismatch\n"), 398 + AppError::Syscall(_) => write_stderr(b"linux syscall failed\n"), 399 + AppError::Http(reqwless::Error::InvalidUrl(_)) => write_stderr(b"invalid url\n"), 400 + AppError::Http(reqwless::Error::Dns) => write_stderr(b"http dns error\n"), 401 + AppError::Http(reqwless::Error::BufferTooSmall) => write_stderr(b"response buffer too small\n"), 402 + AppError::Http(_) => write_stderr(b"http request failed\n"), 403 + } 404 + } 405 + 406 + fn block_on<F>(future: F) -> F::Output 407 + where 408 + F: Future, 409 + { 410 + let mut future = future; 411 + let mut future = unsafe { Pin::new_unchecked(&mut future) }; 412 + let waker = unsafe { Waker::from_raw(noop_raw_waker()) }; 413 + let mut context = Context::from_waker(&waker); 414 + 415 + loop { 416 + match future.as_mut().poll(&mut context) { 417 + Poll::Ready(output) => return output, 418 + Poll::Pending => unsafe { 419 + let _ = libc::sched_yield(); 420 + }, 421 + } 422 + } 423 + } 424 + 425 + const NOOP_WAKER_VTABLE: RawWakerVTable = 426 + RawWakerVTable::new(noop_waker_clone, noop_waker_wake, noop_waker_wake_by_ref, noop_waker_drop); 427 + 428 + const fn noop_raw_waker() -> RawWaker { 429 + RawWaker::new(ptr::null(), &NOOP_WAKER_VTABLE) 430 + } 431 + 432 + unsafe fn noop_waker_clone(_: *const ()) -> RawWaker { 433 + noop_raw_waker() 434 + } 435 + 436 + unsafe fn noop_waker_wake(_: *const ()) {} 437 + 438 + unsafe fn noop_waker_wake_by_ref(_: *const ()) {} 439 + 440 + unsafe fn noop_waker_drop(_: *const ()) {} 441 + 442 + #[panic_handler] 443 + fn panic(_: &PanicInfo<'_>) -> ! { 444 + write_stderr(b"panic\n"); 445 + unsafe { 446 + libc::_exit(101); 447 + } 448 + } 449 + 450 + #[unsafe(no_mangle)] 451 + extern "C" fn rust_eh_personality() {}