rockusb-0.1.3/.cargo_vcs_info.json0000644000000001450000000000100125050ustar { "git": { "sha1": "bd76dff24272a31637d78757a68424606fbbf7ed" }, "path_in_vcs": "rockusb" }rockusb-0.1.3/Cargo.lock0000644000000513420000000000100104650ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "anstream" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" dependencies = [ "anstyle", "windows-sys", ] [[package]] name = "anyhow" version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "async-trait" version = "0.1.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", "syn 2.0.39", ] [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "bmap-parser" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57de8a2f656fe34cdd24bef631afab588a16cf3705aaa0c950328f9a817a0301" dependencies = [ "async-trait", "digest", "flate2", "futures", "quick-xml", "serde", "sha2", "strum", "thiserror", ] [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cc" version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "libc", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" version = "4.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap-num" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "488557e97528174edaa2ee268b23a809e0c598213a4bbcb4f34575a46fda147e" dependencies = [ "num-traits", ] [[package]] name = "clap_builder" version = "4.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", ] [[package]] name = "clap_derive" version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" dependencies = [ "heck", "proc-macro2", "quote", "syn 2.0.39", ] [[package]] name = "clap_lex" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "colorchoice" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "cpufeatures" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" dependencies = [ "libc", ] [[package]] name = "crc" version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" dependencies = [ "crc-catalog", ] [[package]] name = "crc-catalog" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if", ] [[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", ] [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", ] [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "fastrand" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ "instant", ] [[package]] name = "flate2" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ "crc32fast", "miniz_oxide", ] [[package]] name = "futures" version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" [[package]] name = "futures-executor" version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" [[package]] name = "futures-macro" version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", "syn 2.0.39", ] [[package]] name = "futures-sink" version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" [[package]] name = "futures-task" version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" [[package]] name = "futures-util" version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", ] [[package]] name = "hashbrown" version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" [[package]] name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "indexmap" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "instant" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", ] [[package]] name = "libc" version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "libusb1-sys" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d0e2afce4245f2c9a418511e5af8718bcaf2fa408aefb259504d1a9cb25f27" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "memchr" version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "miniz_oxide" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", ] [[package]] name = "nbd" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae36b0aee27117ae0bc775511be136ac58692704b8650da1e6afee816394a11a" dependencies = [ "byteorder", ] [[package]] name = "num-traits" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] [[package]] name = "num_enum" version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "once_cell" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "pin-project-lite" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "proc-macro-crate" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", "toml_edit", ] [[package]] name = "proc-macro2" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] [[package]] name = "quick-xml" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd" dependencies = [ "memchr", "serde", ] [[package]] name = "quote" version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] [[package]] name = "rockfile" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e89194db9752ec1f052472debd3990f77bec4ca0732d9dd28163f95fed1455a" dependencies = [ "bytes", ] [[package]] name = "rockusb" version = "0.1.3" dependencies = [ "anyhow", "bmap-parser", "bytes", "clap", "clap-num", "crc", "fastrand", "flate2", "nbd", "num_enum", "rockfile", "rusb", "thiserror", ] [[package]] name = "rusb" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45fff149b6033f25e825cbb7b2c625a11ee8e6dac09264d49beb125e39aa97bf" dependencies = [ "libc", "libusb1-sys", ] [[package]] name = "rustversion" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "serde" version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" dependencies = [ "proc-macro2", "quote", "syn 2.0.39", ] [[package]] name = "sha2" version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", "digest", "sha2-asm", ] [[package]] name = "sha2-asm" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27ba7066011e3fb30d808b51affff34f0a66d3a03a58edd787c6e420e40e44e" dependencies = [ "cc", ] [[package]] name = "slab" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "strum" version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ "heck", "proc-macro2", "quote", "rustversion", "syn 1.0.109", ] [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "thiserror" version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", "syn 2.0.39", ] [[package]] name = "toml_datetime" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" [[package]] name = "toml_edit" version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap", "toml_datetime", "winnow", ] [[package]] name = "typenum" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "utf8parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winnow" version = "0.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" dependencies = [ "memchr", ] rockusb-0.1.3/Cargo.toml0000644000000030460000000000100105060ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "rockusb" version = "0.1.3" authors = ["Sjoerd Simons "] description = "Rockchip usb protocol host implementation" homepage = "https://github.com/collabora/rockchiprs" readme = "README.md" license = "MIT OR Apache-2.0" repository = "https://github.com/collabora/rockchiprs" resolver = "1" [[example]] name = "rockusb" required-features = ["libusb"] [dependencies.bytes] version = "1.4.0" [dependencies.crc] version = "3.0.1" [dependencies.fastrand] version = "1.9.0" [dependencies.num_enum] version = "0.5.9" [dependencies.rusb] version = "0.9.1" optional = true [dependencies.thiserror] version = "1.0.38" [dev-dependencies.anyhow] version = "1.0.69" [dev-dependencies.bmap-parser] version = "0.1.0" [dev-dependencies.clap] version = "4.2" features = ["derive"] [dev-dependencies.clap-num] version = "1.0" [dev-dependencies.flate2] version = "1.0.25" [dev-dependencies.nbd] version = "0.2.3" [dev-dependencies.rockfile] version = "0.1.1" [dev-dependencies.rusb] version = "0.9.1" [features] default = ["libusb"] libusb = ["dep:rusb"] rockusb-0.1.3/Cargo.toml.orig000064400000000000000000000016311046102023000141650ustar 00000000000000[package] name = "rockusb" version = "0.1.3" edition = "2021" authors = ["Sjoerd Simons "] license = "MIT OR Apache-2.0" description = "Rockchip usb protocol host implementation" homepage = "https://github.com/collabora/rockchiprs" repository = "https://github.com/collabora/rockchiprs" readme = "README.md" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] default = ["libusb"] libusb = ["dep:rusb"] [dependencies] bytes = "1.4.0" crc = "3.0.1" fastrand = "1.9.0" num_enum = "0.5.9" thiserror = "1.0.38" rusb = { version = "0.9.1", optional = true } [dev-dependencies] anyhow = "1.0.69" bmap-parser = "0.1.0" clap = { version = "4.2", features = ["derive"] } clap-num = "1.0" flate2 = "1.0.25" nbd = "0.2.3" rockfile = { path = "../rockfile", version = "0.1.1" } rusb = "0.9.1" [[example]] name="rockusb" required-features = ["libusb"] rockusb-0.1.3/README.md000064400000000000000000000011221046102023000125500ustar 00000000000000# Rockchip usb protocol host implementation Rockchip bootroms and early loaders implement an USB protocol to help loader early firmware, flashing persistant storage etc. This crate contains a sans-io implementation of that protocol as well as an optional (enabled by default) implementation of IO using libusb ```rust,no_run fn main() -> anyhow::Result<()> { let devices = rockusb::libusb::Devices::new()?; let mut transport = devices.iter().next() .ok_or_else(|| anyhow::anyhow!("No Device found"))??; println!("Chip Info: {:0x?}", transport.chip_info()?); Ok(()) } ``` rockusb-0.1.3/examples/rockusb.rs000064400000000000000000000250021046102023000151300ustar 00000000000000use std::{ ffi::OsStr, fs::File, io::{BufWriter, Read, Seek, SeekFrom, Write}, net::TcpListener, path::{Path, PathBuf}, thread::sleep, time::Duration, }; use anyhow::{anyhow, Result}; use bmap_parser::Bmap; use clap::{Parser, ValueEnum}; use clap_num::maybe_hex; use flate2::read::GzDecoder; use rockfile::boot::{ RkBootEntry, RkBootEntryBytes, RkBootHeader, RkBootHeaderBytes, RkBootHeaderEntry, }; use rockusb::libusb::{DeviceUnavalable, Transport}; use rockusb::protocol::ResetOpcode; fn read_flash_info(mut transport: Transport) -> Result<()> { let info = transport.flash_info()?; println!("Raw Flash Info: {:0x?}", info); println!( "Flash size: {} MB ({} sectors)", info.sectors() / 2048, info.sectors() ); Ok(()) } fn reset_device(mut transport: Transport, opcode: ResetOpcode) -> Result<()> { transport.reset_device(opcode)?; Ok(()) } fn read_chip_info(mut transport: Transport) -> Result<()> { println!("Chip Info: {:0x?}", transport.chip_info()?); Ok(()) } fn read_lba(mut transport: Transport, offset: u32, length: u16, path: &Path) -> Result<()> { let mut data = Vec::new(); data.resize(length as usize * 512, 0); transport.read_lba(offset, &mut data)?; let mut file = std::fs::OpenOptions::new() .create(true) .write(true) .truncate(true) .open(path)?; file.write_all(&data)?; Ok(()) } fn write_lba(mut transport: Transport, offset: u32, length: u16, path: &Path) -> Result<()> { let mut data = Vec::new(); data.resize(length as usize * 512, 0); let mut file = File::open(path)?; file.read_exact(&mut data)?; transport.write_lba(offset, &data)?; Ok(()) } fn write_file(mut transport: Transport, offset: u32, path: &Path) -> Result<()> { let mut file = File::open(path)?; let mut io = transport.io()?; io.seek(SeekFrom::Start(offset as u64 * 512))?; std::io::copy(&mut file, &mut io)?; Ok(()) } fn find_bmap(img: &Path) -> Option { fn append(path: PathBuf) -> PathBuf { let mut p = path.into_os_string(); p.push(".bmap"); p.into() } let mut bmap = img.to_path_buf(); loop { bmap = append(bmap); if bmap.exists() { return Some(bmap); } // Drop .bmap bmap.set_extension(""); bmap.extension()?; // Drop existing orignal extension part bmap.set_extension(""); } } fn write_bmap(transport: Transport, path: &Path) -> Result<()> { let bmap_path = find_bmap(path).ok_or_else(|| anyhow!("Failed to find bmap"))?; println!("Using bmap file: {}", path.display()); let mut bmap_file = File::open(bmap_path)?; let mut xml = String::new(); bmap_file.read_to_string(&mut xml)?; let bmap = Bmap::from_xml(&xml)?; // HACK to minimize small writes let mut writer = BufWriter::with_capacity(16 * 1024 * 1024, transport.into_io()?); let mut file = File::open(path)?; match path.extension().and_then(OsStr::to_str) { Some("gz") => { let gz = GzDecoder::new(file); let mut gz = bmap_parser::Discarder::new(gz); bmap_parser::copy(&mut gz, &mut writer, &bmap)?; } _ => { bmap_parser::copy(&mut file, &mut writer, &bmap)?; } } Ok(()) } fn download_entry( header: RkBootHeaderEntry, code: u16, file: &mut File, transport: &mut Transport, ) -> Result<()> { for i in 0..header.count { let mut entry: RkBootEntryBytes = [0; 57]; file.seek(SeekFrom::Start( header.offset as u64 + (header.size * i) as u64, ))?; file.read_exact(&mut entry)?; let entry = RkBootEntry::from_bytes(&entry); println!("{} Name: {}", i, String::from_utf16(entry.name.as_slice())?); let mut data = Vec::new(); data.resize(entry.data_size as usize, 0); file.seek(SeekFrom::Start(entry.data_offset as u64))?; file.read_exact(&mut data)?; transport.write_maskrom_area(code, &data)?; println!("Done!... waiting {}ms", entry.data_delay); if entry.data_delay > 0 { sleep(Duration::from_millis(entry.data_delay as u64)); } } Ok(()) } fn download_boot(mut transport: Transport, path: &Path) -> Result<()> { let mut file = File::open(path)?; let mut header: RkBootHeaderBytes = [0; 102]; file.read_exact(&mut header)?; let header = RkBootHeader::from_bytes(&header).ok_or_else(|| anyhow!("Failed to parse header"))?; download_entry(header.entry_471, 0x471, &mut file, &mut transport)?; download_entry(header.entry_472, 0x472, &mut file, &mut transport)?; Ok(()) } fn run_nbd(transport: Transport) -> Result<()> { let listener = TcpListener::bind("127.0.0.1:10809").unwrap(); println!( "Listening for nbd connection on: {:?}", listener.local_addr()? ); let mut stream = listener .incoming() .next() .transpose()? .ok_or_else(|| anyhow!("Connection failure"))?; // Stop listening for new connections drop(listener); let io = transport.into_io()?; let export = nbd::Export { size: io.size(), readonly: false, ..Default::default() }; println!("Connection!"); nbd::server::handshake(&mut stream, &export)?; println!("Shook hands!"); nbd::server::transmission(&mut stream, io)?; println!("nbd client disconnected"); Ok(()) } #[derive(Debug, clap::Parser)] enum Command { List, DownloadBoot { path: PathBuf, }, Read { #[clap(value_parser=maybe_hex::)] offset: u32, #[clap(value_parser=maybe_hex::)] length: u16, path: PathBuf, }, Write { #[clap(value_parser=maybe_hex::)] offset: u32, #[clap(value_parser=maybe_hex::)] length: u16, path: PathBuf, }, WriteFile { #[clap(value_parser=maybe_hex::)] offset: u32, path: PathBuf, }, WriteBmap { path: PathBuf, }, ChipInfo, FlashId, FlashInfo, ResetDevice { #[clap(value_enum, default_value_t=ArgResetOpcode::Reset)] opcode: ArgResetOpcode, }, // Run/expose device as a network block device Nbd, } #[derive(ValueEnum, Clone, Debug)] pub enum ArgResetOpcode { /// Reset Reset, /// Reset to USB mass-storage device class MSC, /// Powers the SOC off PowerOff, /// Reset to maskrom mode Maskrom, /// Disconnect from USB Disconnect, } impl From for ResetOpcode { fn from(arg: ArgResetOpcode) -> ResetOpcode { match arg { ArgResetOpcode::Reset => ResetOpcode::Reset, ArgResetOpcode::MSC => ResetOpcode::MSC, ArgResetOpcode::PowerOff => ResetOpcode::PowerOff, ArgResetOpcode::Maskrom => ResetOpcode::Maskrom, ArgResetOpcode::Disconnect => ResetOpcode::Disconnect, } } } #[derive(Debug, Clone)] struct DeviceArg { bus_number: u8, address: u8, } fn parse_device(device: &str) -> Result { let mut parts = device.split(':'); let bus_number = parts .next() .ok_or_else(|| anyhow!("No bus number: use :
"))? .parse() .map_err(|_| anyhow!("Bus should be a number"))?; let address = parts .next() .ok_or_else(|| anyhow!("No address: use :
"))? .parse() .map_err(|_| anyhow!("Address should be a numbrer"))?; if parts.next().is_some() { return Err(anyhow!("Too many parts")); } Ok(DeviceArg { bus_number, address, }) } #[derive(clap::Parser)] struct Opts { #[arg(short, long, value_parser = parse_device)] /// Device type specified as :
device: Option, #[command(subcommand)] command: Command, } fn list_available_devices() -> Result<()> { let devices = rockusb::libusb::Devices::new()?; println!("Available rockchip devices"); for d in devices.iter() { match d { Ok(mut d) => println!("* {:?}", d.handle().device()), Err(DeviceUnavalable { device, error }) => { println!("* {:?} - Unavailable: {}", device, error) } } } Ok(()) } fn main() -> Result<()> { let opt = Opts::parse(); // Commands that don't talk a device if matches!(opt.command, Command::List) { return list_available_devices(); } let devices = rockusb::libusb::Devices::new()?; let mut transport = if let Some(dev) = opt.device { devices .iter() .find(|d| match d { Ok(device) => { device.bus_number() == dev.bus_number && device.address() == dev.address } Err(DeviceUnavalable { device, .. }) => { device.bus_number() == dev.bus_number && device.address() == dev.address } }) .ok_or_else(|| anyhow!("Specified device not found"))? } else { let mut devices: Vec<_> = devices.iter().collect(); match devices.len() { 0 => Err(anyhow!("No devices found")), 1 => Ok(devices.pop().unwrap()), _ => { drop(devices); let _ = list_available_devices(); println!(); Err(anyhow!( "Please select a specific device using the -d option" )) } }? }?; match opt.command { Command::List => unreachable!(), Command::DownloadBoot { path } => download_boot(transport, &path), Command::Read { offset, length, path, } => read_lba(transport, offset, length, &path), Command::Write { offset, length, path, } => write_lba(transport, offset, length, &path), Command::WriteFile { offset, path } => write_file(transport, offset, &path), Command::WriteBmap { path } => write_bmap(transport, &path), Command::ChipInfo => read_chip_info(transport), Command::FlashId => { let id = transport.flash_id()?; println!("Flash id: {}", id.to_str()); println!("raw: {:?}", id); Ok(()) } Command::FlashInfo => read_flash_info(transport), Command::ResetDevice { opcode } => reset_device(transport, opcode.into()), Command::Nbd => run_nbd(transport), } } rockusb-0.1.3/src/lib.rs000064400000000000000000000007051046102023000132020ustar 00000000000000#![doc = include_str!("../README.md")] /// libusb transport implementation #[cfg(feature = "libusb")] pub mod libusb; /// sans-io protocol implementationsss /// /// This module contains all protocol logic; Each operation implements the [operation::OperationSteps] /// trait which gives a transport a series of [operation::UsbStep] to execute to complete an /// operation. pub mod operation; /// low-level usb protocol data structures pub mod protocol; rockusb-0.1.3/src/libusb.rs000064400000000000000000000357401046102023000137230ustar 00000000000000use std::{ borrow::BorrowMut, io::{Read, Seek, SeekFrom, Write}, time::Duration, }; use crate::{ operation::{OperationSteps, UsbStep}, protocol::{ChipInfo, FlashId, FlashInfo, ResetOpcode, SECTOR_SIZE}, }; use rusb::{DeviceHandle, GlobalContext}; use thiserror::Error; /// Error indicate a device is not available #[derive(Debug, Clone, Eq, PartialEq, Error)] #[error("Device is not available: {device:?} {error}")] pub struct DeviceUnavalable { pub device: rusb::Device, #[source] pub error: rusb::Error, } #[derive(Debug, Clone, Eq, PartialEq, Error)] pub enum Error { #[error("Usb error: {0}")] UsbError(#[from] rusb::Error), #[error("Operation error: {0}")] OperationError(#[from] crate::operation::UsbOperationError), } type Result = std::result::Result; /// Rockchip devices pub struct Devices { devices: rusb::DeviceList, } impl Devices { pub fn new() -> Result { let devices = rusb::DeviceList::new()?; Ok(Self { devices }) } /// Create an Iterator over found Rockchip device pub fn iter(&self) -> DevicesIter { let iter = self.devices.iter(); DevicesIter { iter } } } /// Iterator over found Rockchip device pub struct DevicesIter<'a> { iter: rusb::Devices<'a, GlobalContext>, } impl Iterator for DevicesIter<'_> { type Item = std::result::Result; fn next(&mut self) -> Option { for device in self.iter.by_ref() { let desc = match device.device_descriptor() { Ok(desc) => desc, _ => continue, }; if desc.vendor_id() != 0x2207 { continue; } let handle = match device.open() { Ok(handle) => handle, Err(error) => return Some(Err(DeviceUnavalable { device, error })), }; return Some(Transport::from_usb_device(handle)); } None } } /// libusb based Transport for rockusb operation pub struct Transport { handle: DeviceHandle, ep_in: u8, ep_out: u8, } impl Transport { fn new( mut handle: DeviceHandle, interface: u8, ep_in: u8, ep_out: u8, ) -> std::result::Result { handle .claim_interface(interface) .map_err(|error| DeviceUnavalable { device: handle.device(), error, })?; Ok(Self { handle, ep_in, ep_out, }) } /// Create a new transport from an exist device handle pub fn from_usb_device( handle: rusb::DeviceHandle, ) -> std::result::Result { let device = handle.device(); let desc = device .device_descriptor() .map_err(|error| DeviceUnavalable { device: device.clone(), error, })?; for c in 0..desc.num_configurations() { let config = device .config_descriptor(c) .map_err(|error| DeviceUnavalable { device: device.clone(), error, })?; for i in config.interfaces() { for i_desc in i.descriptors() { let output = i_desc.endpoint_descriptors().find(|e| { e.direction() == rusb::Direction::Out && e.transfer_type() == rusb::TransferType::Bulk }); let input = i_desc.endpoint_descriptors().find(|e| { e.direction() == rusb::Direction::In && e.transfer_type() == rusb::TransferType::Bulk }); if let (Some(input), Some(output)) = (input, output) { return Transport::new( handle, i_desc.setting_number(), input.address(), output.address(), ); } } } } Err(DeviceUnavalable { device, error: rusb::Error::NotFound, }) } /// Create an IO object which implements [Read](std::io::Read), [Write](std::io::Write) and /// [Seek](std::io::Seek) pub fn io(&mut self) -> Result> { TransportIO::new(self) } /// Convert into an IO object which implements [Read](std::io::Read), [Write](std::io::Write) and /// [Seek](std::io::Seek) pub fn into_io(self) -> Result> { TransportIO::new(self) } /// Get a reference to the underlying device handle pub fn handle(&mut self) -> &mut DeviceHandle { &mut self.handle } /// Get the bus number of the current device pub fn bus_number(&self) -> u8 { self.handle.device().bus_number() } /// Get the bus address of the current device pub fn address(&self) -> u8 { self.handle.device().address() } fn handle_operation(&mut self, mut operation: O) -> Result where O: OperationSteps, { loop { let step = operation.step(); match step { UsbStep::WriteBulk { data } => { let _written = self.handle .write_bulk(self.ep_out, data, Duration::from_secs(5))?; } UsbStep::ReadBulk { data } => { let _read = self .handle .read_bulk(self.ep_in, data, Duration::from_secs(5))?; } UsbStep::Finished(r) => break r.map_err(|e| e.into()), UsbStep::WriteControl { request_type, request, value, index, data, } => { self.handle.write_control( request_type, request, value, index, data, Duration::from_secs(5), )?; } } } } /// retrieve SoC flash identifier pub fn flash_id(&mut self) -> Result { self.handle_operation(crate::operation::flash_id()) } /// retrieve SoC flash info pub fn flash_info(&mut self) -> Result { self.handle_operation(crate::operation::flash_info()) } /// retrieve SoC chip info pub fn chip_info(&mut self) -> Result { self.handle_operation(crate::operation::chip_info()) } /// read from the flash /// /// start_sector with [SECTOR_SIZE](crate::protocol::SECTOR_SIZE) sectors. the data to be read /// must be a multiple of [SECTOR_SIZE](crate::protocol::SECTOR_SIZE) bytes pub fn read_lba(&mut self, start_sector: u32, read: &mut [u8]) -> Result { self.handle_operation(crate::operation::read_lba(start_sector, read)) .map(|t| t.into()) } /// Create operation to read an lba from the flash /// /// start_sector based on [SECTOR_SIZE](crate::protocol::SECTOR_SIZE) sectors. the data to be /// written must be a multiple of [SECTOR_SIZE](crate::protocol::SECTOR_SIZE) bytes pub fn write_lba(&mut self, start_sector: u32, write: &[u8]) -> Result { self.handle_operation(crate::operation::write_lba(start_sector, write)) .map(|t| t.into()) } /// Write a specific area while in maskrom mode; typically 0x471 or 0x472 data as retrieved from a /// rockchip boot file pub fn write_maskrom_area(&mut self, area: u16, data: &[u8]) -> Result<()> { self.handle_operation(crate::operation::write_area(area, data)) } /// Reset the device pub fn reset_device(&mut self, opcode: ResetOpcode) -> Result<()> { self.handle_operation(crate::operation::reset_device(opcode)) } } /// IO object which implements [Read](std::io::Read), [Write](std::io::Write) and /// [Seek](std::io::Seek) pub struct TransportIO { transport: T, size: u64, // Read/Write offset in bytes offset: u64, buffer: [u8; 512], // Whether or not the buffer is dirty state: BufferState, } impl TransportIO where T: BorrowMut, { const MAXIO_SIZE: u64 = 128 * crate::protocol::SECTOR_SIZE; /// Create a new IO object around a given transport pub fn new(mut transport: T) -> Result { let info = transport.borrow_mut().flash_info()?; Ok(Self { transport, size: info.size(), offset: 0, buffer: [0u8; 512], state: BufferState::Invalid, }) } /// Get a reference to the inner transport pub fn inner(&mut self) -> &mut Transport { self.transport.borrow_mut() } /// Convert into the inner transport pub fn into_inner(self) -> T { self.transport } /// Size of the flash in bytes pub fn size(&self) -> u64 { self.size } fn current_sector(&self) -> u64 { self.offset / SECTOR_SIZE } // Want to start an i/o operation with a given maximum length fn pre_io(&mut self, len: u64) -> std::result::Result { if self.offset >= self.size { return Ok(IOOperation::Eof); } // Offset inside the current sector let sector_offset = self.offset % SECTOR_SIZE; // bytes left from current position to end of current sector let sector_remaining = SECTOR_SIZE - sector_offset; // If the I/O operation is starting at a sector edge and encompasses at least one sector // then direct I/O can be done if sector_offset == 0 && len >= SECTOR_SIZE { // At most read the amount of bytes left let left = self.size - self.offset; let io_len = len.min(left) / SECTOR_SIZE * SECTOR_SIZE; Ok(IOOperation::Direct { len: io_len.min(Self::MAXIO_SIZE) as usize, }) } else { if self.state == BufferState::Invalid { let sector = self.current_sector() as u32; self.transport .borrow_mut() .read_lba(sector, &mut self.buffer) .map_err(|e| std::io::Error::new(std::io::ErrorKind::BrokenPipe, e))?; self.state = BufferState::Valid; } Ok(IOOperation::Buffered { offset: sector_offset as usize, len: len.min(sector_remaining) as usize, }) } } fn post_io(&mut self, len: u64) -> std::result::Result { // Offset inside the current sector let sector_offset = self.offset % SECTOR_SIZE; // bytes left from current position to end of current sector let sector_remaining = SECTOR_SIZE - sector_offset; // If going over the sector edge flush the current buffer and invalidate it if len >= sector_remaining { self.flush_buffer()?; self.state = BufferState::Invalid; } self.offset += len; Ok(len as usize) } fn flush_buffer(&mut self) -> std::io::Result<()> { if self.state == BufferState::Dirty { let sector = self.current_sector() as u32; self.transport .borrow_mut() .write_lba(sector, &self.buffer) .map_err(|e| std::io::Error::new(std::io::ErrorKind::BrokenPipe, e))?; self.state = BufferState::Valid; } Ok(()) } fn do_read(&mut self, buf: &mut [u8]) -> std::io::Result { let sector = self.current_sector() as u32; self.transport .borrow_mut() .read_lba(sector, buf) .map_err(|e| std::io::Error::new(std::io::ErrorKind::BrokenPipe, e))?; Ok(buf.len()) } fn do_write(&mut self, buf: &[u8]) -> std::io::Result { let sector = self.current_sector() as u32; self.transport .borrow_mut() .write_lba(sector, buf) .map_err(|e| std::io::Error::new(std::io::ErrorKind::BrokenPipe, e))?; Ok(buf.len()) } } enum IOOperation { Direct { len: usize }, Buffered { offset: usize, len: usize }, Eof, } #[derive(Clone, Copy, Eq, PartialEq)] enum BufferState { // Buffer content doesn't match current offset Invalid, // Buffer content matches offset and device-side Valid, // Buffer content matches offset and has outstanding data Dirty, } impl Write for TransportIO where T: BorrowMut, { fn write(&mut self, buf: &[u8]) -> std::io::Result { let r = match self.pre_io(buf.len() as u64)? { IOOperation::Direct { len } => self.do_write(&buf[..len])?, IOOperation::Buffered { offset, len } => { self.buffer[offset..offset + len].copy_from_slice(&buf[0..len]); self.state = BufferState::Dirty; len } IOOperation::Eof => { return Err(std::io::Error::new( std::io::ErrorKind::Other, "Trying to write past end of area", )) } }; self.post_io(r as u64) } fn flush(&mut self) -> std::io::Result<()> { self.flush_buffer() } } impl Read for TransportIO where T: BorrowMut, { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { let r = match self.pre_io(buf.len() as u64)? { IOOperation::Direct { len } => self.do_read(&mut buf[..len])?, IOOperation::Buffered { offset, len } => { buf[0..len].copy_from_slice(&self.buffer[offset..offset + len]); len } IOOperation::Eof => 0, }; self.post_io(r as u64) } } impl Seek for TransportIO where T: BorrowMut, { fn seek(&mut self, pos: SeekFrom) -> std::io::Result { self.offset = match pos { SeekFrom::Start(offset) => self.size.min(offset), SeekFrom::End(offset) => { if offset > 0 { self.size } else { let offset = offset.unsigned_abs(); self.size.saturating_sub(offset) } } SeekFrom::Current(offset) => { if offset > 0 { let offset = offset as u64; self.offset.saturating_add(offset).min(self.size) } else { let offset = offset.unsigned_abs(); self.offset.saturating_sub(offset) } } }; Ok(self.offset) } } rockusb-0.1.3/src/operation.rs000064400000000000000000000350531046102023000144400ustar 00000000000000use std::marker::PhantomData; use crate::protocol::{ self, ChipInfo, CommandBlock, CommandStatus, CommandStatusParseError, Direction, FlashId, FlashInfo, ResetOpcode, }; use thiserror::Error; /// Errors for usb operations #[derive(Debug, Clone, Eq, PartialEq, Error)] pub enum UsbOperationError { #[error("Tag mismatch between command and status")] TagMismatch, #[error("Incorrect status Signature receveived: {0:?}")] InvalidStatusSignature([u8; 4]), #[error("Invalid status status: {0}")] InvalidStatusStatus(u8), #[error("Invalid status data length")] InvalidStatusLength, #[error("Failed to parse reply")] ReplyParseFailure, #[error("Device indicated operation failed")] FailedStatus, } impl From for UsbOperationError { fn from(e: CommandStatusParseError) -> Self { match e { CommandStatusParseError::InvalidSignature(s) => { UsbOperationError::InvalidStatusSignature(s) } CommandStatusParseError::InvalidLength(_) => UsbOperationError::InvalidStatusLength, CommandStatusParseError::InvalidStatus(s) => UsbOperationError::InvalidStatusStatus(s), } } } /// Step to take by the transport implementation #[derive(Debug, Eq, PartialEq)] pub enum UsbStep<'a, T> { /// Write USB data using a control transfer WriteControl { request_type: u8, request: u8, value: u16, index: u16, data: &'a [u8], }, /// Write USB data using a bulk transfer WriteBulk { data: &'a [u8] }, /// Read USB data using a bulk transfer ReadBulk { data: &'a mut [u8] }, /// Operation is finished with a given result or failure Finished(Result), } /// steps to take to finish an operation pub trait OperationSteps { /// Next step to execute by a transport fn step(&mut self) -> UsbStep; } enum MaskRomSteps { Writing(crc::Digest<'static, u16>), Dummy, Done, } /// Operations that can be executed when the SoC is in MaskRom mode pub struct MaskRomOperation<'a> { written: usize, block: [u8; 4096], data: &'a [u8], area: u16, steps: MaskRomSteps, } const CRC: crc::Crc = crc::Crc::::new(&crc::CRC_16_IBM_3740); impl<'a> MaskRomOperation<'a> { fn new(area: u16, data: &'a [u8]) -> Self { Self { written: 0, block: [0; 4096], data, area, steps: MaskRomSteps::Writing(CRC.digest()), } } } impl OperationSteps<()> for MaskRomOperation<'_> { fn step(&mut self) -> UsbStep<()> { let mut current = MaskRomSteps::Done; std::mem::swap(&mut self.steps, &mut current); match current { MaskRomSteps::Writing(mut crc) => { let chunksize = 4096.min(self.data.len() - self.written); self.block[..chunksize] .copy_from_slice(&self.data[self.written..self.written + chunksize]); self.written += chunksize; let chunk = match chunksize { 4096 => { crc.update(&self.block); self.steps = MaskRomSteps::Writing(crc); &self.block[..] } 4095 => { // Add extra 0 to avoid splitting crc over two blocks self.block[4095] = 0; crc.update(&self.block); self.steps = MaskRomSteps::Writing(crc); &self.block[..] } mut end => { crc.update(&self.block[0..end]); let crc = crc.finalize(); self.block[end] = (crc >> 8) as u8; self.block[end + 1] = (crc & 0xff) as u8; end += 2; if end == 4096 { self.steps = MaskRomSteps::Dummy; } else { self.steps = MaskRomSteps::Done; } &self.block[0..end] } }; UsbStep::WriteControl { request_type: 0x40, request: 0xc, value: 0, index: self.area, data: chunk, } } MaskRomSteps::Dummy => { self.steps = MaskRomSteps::Done; self.block[0] = 0; UsbStep::WriteControl { request_type: 0x40, request: 0xc, value: 0, index: self.area, data: &self.block[0..1], } } MaskRomSteps::Done => UsbStep::Finished(Ok(())), } } } /// Write a specific area; typically 0x471 or 0x472 data as retrieved from a rockchip boot file pub fn write_area(area: u16, data: &[u8]) -> MaskRomOperation { MaskRomOperation::new(area, data) } trait FromOperation { fn from_operation(io: &[u8], status: &CommandStatus) -> Result where Self: Sized; } enum Operation { CommandBlock, IO, CommandStatus, Finish, } enum IOBytes<'a> { // Biggest transfer that's not a data read/write Inband([u8; 16]), Read(&'a mut [u8]), Write(&'a [u8]), } /// Operation to execute using the "full" USB protocol pub struct UsbOperation<'a, T> { command: CommandBlock, command_bytes: [u8; 31], data: IOBytes<'a>, next: Operation, _result: PhantomData, } impl<'a, T> UsbOperation<'a, T> { fn new(command: CommandBlock) -> Self { Self { command, command_bytes: [0u8; protocol::COMMAND_BLOCK_BYTES], data: IOBytes::Inband([0u8; 16]), next: Operation::CommandBlock, _result: PhantomData, } } fn new_write(command: CommandBlock, data: &'a [u8]) -> Self { Self { command, command_bytes: [0u8; protocol::COMMAND_BLOCK_BYTES], data: IOBytes::Write(data), next: Operation::CommandBlock, _result: PhantomData, } } fn new_read(command: CommandBlock, data: &'a mut [u8]) -> Self { Self { command, command_bytes: [0u8; protocol::COMMAND_BLOCK_BYTES], data: IOBytes::Read(data), next: Operation::CommandBlock, _result: PhantomData, } } fn io_data_mut(&mut self) -> &mut [u8] { match &mut self.data { IOBytes::Inband(ref mut data) => data, IOBytes::Read(ref mut data) => data, IOBytes::Write(_) => unreachable!(), } } fn io_data(&mut self) -> &[u8] { match self.data { IOBytes::Inband(ref data) => data, IOBytes::Read(ref data) => data, IOBytes::Write(data) => data, } } } impl OperationSteps for UsbOperation<'_, T> where T: FromOperation, T: std::fmt::Debug, { fn step(&mut self) -> UsbStep { let mut next = Operation::CommandBlock; std::mem::swap(&mut self.next, &mut next); match next { Operation::CommandBlock => { let len = self.command.to_bytes(&mut self.command_bytes); // If there is no transfer to be made (e.g. a command) just skip // sending data. if self.command.transfer_length() == 0 { self.next = Operation::CommandStatus; } else { self.next = Operation::IO; } UsbStep::WriteBulk { data: &self.command_bytes[..len], } } Operation::IO => { self.next = Operation::CommandStatus; let len = self.command.transfer_length() as usize; match self.command.direction() { Direction::Out => UsbStep::WriteBulk { data: &self.io_data()[..len], }, Direction::In => UsbStep::ReadBulk { data: &mut self.io_data_mut()[..len], }, } } Operation::CommandStatus => { self.next = Operation::Finish; UsbStep::ReadBulk { data: &mut self.command_bytes[..protocol::COMMAND_STATUS_BYTES], } } Operation::Finish => { let r = CommandStatus::from_bytes(&self.command_bytes) .map_err(UsbOperationError::from) .and_then(|csw| { if csw.status == protocol::Status::FAILED { Err(UsbOperationError::FailedStatus) } else if csw.tag == self.command.tag() { let transfer = self.command.transfer_length() as usize; T::from_operation(&self.io_data()[..transfer], &csw) } else { Err(UsbOperationError::TagMismatch) } }); UsbStep::Finished(r) } } } } impl FromOperation for ChipInfo { fn from_operation(io: &[u8], _status: &CommandStatus) -> Result where Self: Sized, { let data = io .try_into() .map_err(|_e| UsbOperationError::ReplyParseFailure)?; Ok(ChipInfo::from_bytes(data)) } } /// Create operation to retrieve SoC Chip information pub fn chip_info() -> UsbOperation<'static, ChipInfo> { UsbOperation::new(CommandBlock::chip_info()) } impl FromOperation for FlashId { fn from_operation(io: &[u8], _status: &CommandStatus) -> Result where Self: Sized, { let data = io .try_into() .map_err(|_e| UsbOperationError::ReplyParseFailure)?; Ok(FlashId::from_bytes(data)) } } /// Create operation to retrieve SoC flash identifier pub fn flash_id() -> UsbOperation<'static, FlashId> { UsbOperation::new(CommandBlock::flash_id()) } impl FromOperation for FlashInfo { fn from_operation(io: &[u8], _status: &CommandStatus) -> Result where Self: Sized, { let data = io .try_into() .map_err(|_e| UsbOperationError::ReplyParseFailure)?; Ok(FlashInfo::from_bytes(data)) } } /// Create operation to retrieve SoC flash information pub fn flash_info() -> UsbOperation<'static, FlashInfo> { UsbOperation::new(CommandBlock::flash_info()) } impl FromOperation for () { fn from_operation(_io: &[u8], _status: &CommandStatus) -> Result where Self: Sized, { Ok(()) } } /// Create operation to reset the SoC pub fn reset_device(opcode: ResetOpcode) -> UsbOperation<'static, ()> { UsbOperation::new(CommandBlock::reset_device(opcode)) } /// Bytes transferred #[derive(Debug, Clone, Copy)] pub struct Transferred(u32); impl FromOperation for Transferred { fn from_operation(io: &[u8], status: &CommandStatus) -> Result where Self: Sized, { let totransfer = io.len() as u32; if status.residue > totransfer { Err(UsbOperationError::ReplyParseFailure) } else { Ok(Transferred(totransfer - status.residue)) } } } impl From for u32 { fn from(t: Transferred) -> Self { t.0 } } /// Create operation to read an lba from the flash /// /// start_sector with [protocol::SECTOR_SIZE] sectors. the data to be read must be a multiple of /// [protocol::SECTOR_SIZE] bytes pub fn read_lba(start_sector: u32, read: &mut [u8]) -> UsbOperation<'_, Transferred> { assert_eq!(read.len() % 512, 0, "Not a multiple of 512: {}", read.len()); UsbOperation::new_read( CommandBlock::read_lba(start_sector, (read.len() / 512) as u16), read, ) } /// Create operation to read an lba from the flash /// /// start_sector with [protocol::SECTOR_SIZE] sectors. the data to be written must be a multiple of /// [protocol::SECTOR_SIZE] bytes pub fn write_lba(start_sector: u32, write: &[u8]) -> UsbOperation<'_, Transferred> { assert_eq!( write.len() % 512, 0, "Not a multiple of 512: {}", write.len() ); UsbOperation::new_write( CommandBlock::write_lba(start_sector, (write.len() / 512) as u16), write, ) } #[cfg(test)] mod test { use super::*; #[test] fn chip_info_operation() { let mut o = chip_info(); let cb = CommandBlock::chip_info(); let mut cb_bytes = [0u8; protocol::COMMAND_BLOCK_BYTES]; cb.to_bytes(&mut cb_bytes); // Write the command block let tag = match o.step() { UsbStep::WriteBulk { data } if data.len() == protocol::COMMAND_BLOCK_BYTES => { [data[4], data[5], data[6], data[7]] } o => panic!("Unexpected step: {:?}", o), }; // Reading the chip info match o.step() { UsbStep::ReadBulk { data } if data.len() as u32 == cb.transfer_length() => { data.fill(0); /* 3588 */ data[0] = 0x38; data[1] = 0x38; data[2] = 0x35; data[3] = 0x33; } o => panic!("Unexpected step: {:?}", o), } // reading status match o.step() { UsbStep::ReadBulk { data } if data.len() == protocol::COMMAND_STATUS_BYTES => { data.fill(0); /* signature */ data[0] = b'U'; data[1] = b'S'; data[2] = b'B'; data[3] = b'S'; /* tag */ data[4] = tag[0]; data[5] = tag[1]; data[6] = tag[2]; data[7] = tag[3]; // status good data[12] = 0; } o => panic!("Unexpected step: {:?}", o), } match o.step() { UsbStep::Finished(Ok(info)) => { assert_eq!( info.inner(), [ 0x38u8, 0x38, 0x35, 0x33, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 ] ) } o => panic!("Unexpected step: {:?}", o), } } } rockusb-0.1.3/src/protocol.rs000064400000000000000000000237211046102023000143000ustar 00000000000000use std::borrow::Cow; use bytes::{Buf, BufMut}; use num_enum::{IntoPrimitive, TryFromPrimitive}; pub const SECTOR_SIZE: u64 = 512; #[repr(u8)] #[derive(Debug, Eq, PartialEq, Clone, Copy, IntoPrimitive, TryFromPrimitive)] pub enum Direction { In = 0x80, Out = 0x0, } #[non_exhaustive] #[repr(u8)] #[derive(Debug, Eq, PartialEq, Clone, Copy, IntoPrimitive, TryFromPrimitive)] enum CommandCode { TestUnitReady = 0, ReadFlashId = 0x01, TestBadBlock = 0x03, ReadSector = 0x04, WriteSector = 0x05, EraseNormal = 0x06, EraseForce = 0x0B, ReadLBA = 0x14, WriteLBA = 0x15, EraseSystemDisk = 0x16, ReadSDram = 0x17, WriteSDram = 0x18, ExecuteSDram = 0x19, ReadFlashInfo = 0x1A, ReadChipInfo = 0x1B, SetResetFlag = 0x1E, WriteEFuse = 0x1F, ReadEFuse = 0x20, ReadSPIFlash = 0x21, WriteSPIFlash = 0x22, WriteNewEfuse = 0x23, ReadNewEfuse = 0x24, EraseLBA = 0x25, ReadCapability = 0xAA, DeviceReset = 0xFF, } #[repr(u8)] #[derive(Debug, Eq, PartialEq, Clone, Copy, IntoPrimitive, TryFromPrimitive)] pub enum ResetOpcode { /// Reset Reset = 0, /// Reset to USB mass-storage device class MSC, /// Powers the SOC off PowerOff, /// Reset to maskrom mode Maskrom, /// Disconnect from USB Disconnect, } #[derive(Debug, thiserror::Error, Clone)] pub enum CommandStatusParseError { #[error("Invalid signature: {0:x?}")] InvalidSignature([u8; 4]), #[error("Invalid length: {0}")] InvalidLength(usize), #[error("Invalid status: {0}")] InvalidStatus(u8), } #[repr(u8)] #[derive(Debug, Eq, PartialEq, Clone, Copy, IntoPrimitive, TryFromPrimitive)] pub enum Status { SUCCESS = 0, FAILED = 1, } pub const COMMAND_STATUS_BYTES: usize = 13; #[derive(Debug, Clone, PartialEq, Eq)] pub struct CommandStatus { pub tag: u32, pub residue: u32, pub status: Status, } impl CommandStatus { pub fn to_bytes(&self, bytes: &mut [u8]) -> usize { let mut bytes = &mut bytes[..]; bytes.put_slice(b"USBS"); bytes.put_u32(self.tag); bytes.put_u32_le(self.residue); bytes.put_u8(self.status.into()); COMMAND_STATUS_BYTES } pub fn from_bytes(mut bytes: &[u8]) -> Result { if bytes.len() < COMMAND_STATUS_BYTES { return Err(CommandStatusParseError::InvalidLength(bytes.len())); } let mut magic = [0u8; 4]; bytes.copy_to_slice(&mut magic); if &magic != b"USBS" { return Err(CommandStatusParseError::InvalidSignature(magic)); } let tag = bytes.get_u32(); let residue = bytes.get_u32_le(); let status = Status::try_from(bytes.get_u8()) .map_err(|e| CommandStatusParseError::InvalidStatus(e.number))?; Ok(CommandStatus { tag, residue, status, }) } } #[derive(Debug, Clone, Copy)] pub struct ChipInfo([u8; 16]); impl ChipInfo { pub fn from_bytes(data: [u8; 16]) -> Self { ChipInfo(data) } pub fn inner(&self) -> &[u8] { &self.0 } } #[derive(Debug, Clone, Copy)] pub struct FlashId([u8; 5]); impl FlashId { pub fn from_bytes(data: [u8; 5]) -> Self { FlashId(data) } pub fn to_str(&self) -> Cow<'_, str> { String::from_utf8_lossy(&self.0) } } #[derive(Debug, Clone, Copy)] pub struct FlashInfo([u8; 11]); impl FlashInfo { pub fn from_bytes(data: [u8; 11]) -> Self { FlashInfo(data) } /// flash size in 512 byte sectors pub fn sectors(&self) -> u32 { self.0.as_slice().get_u32_le() } /// flash size in bytes pub fn size(&self) -> u64 { u64::from(self.sectors()) * SECTOR_SIZE } /// Block size in 512 bytes sectors pub fn block_size_sectors(&self) -> u16 { (&self.0[4..]).get_u16_le() } pub fn inner(&self) -> &[u8] { &self.0 } } #[derive(Debug, thiserror::Error, Clone)] pub enum CommandBlockParseError { #[error("Invalid Command block signature: {0:x?}")] InvalidSignature([u8; 4]), #[error("Unknown Command code : {0:x}")] UnknownCommandCode(u8), #[error("Unknown flags: {0:x}")] UnknownFlags(u8), #[error("Invalid command block length: {0}")] InvalidLength(usize), } /// Total size of a CBW command pub const COMMAND_BLOCK_BYTES: usize = 31; /// This structure represents a CBW command block according the USB Mass /// Storage class specification. It carries a SCSI command inside the 'CBWCB' /// bytes that is referred to in the code as 'command data block'. #[derive(Debug, Clone, PartialEq, Eq)] pub struct CommandBlock { tag: u32, transfer_length: u32, flags: Direction, lun: u8, // Length of command data block cdb_length: u8, // Command data block fields cd_code: CommandCode, cd_opcode: u8, cd_address: u32, cd_length: u16, } impl CommandBlock { pub fn flash_id() -> CommandBlock { CommandBlock { tag: fastrand::u32(..), transfer_length: 5, flags: Direction::In, lun: 0, cdb_length: 0x6, cd_code: CommandCode::ReadFlashId, cd_opcode: 0, cd_address: 0, cd_length: 0x0, } } pub fn flash_info() -> CommandBlock { CommandBlock { tag: fastrand::u32(..), transfer_length: 11, flags: Direction::In, lun: 0, cdb_length: 0x6, cd_code: CommandCode::ReadFlashInfo, cd_opcode: 0, cd_address: 0, cd_length: 0x0, } } pub fn chip_info() -> CommandBlock { CommandBlock { tag: fastrand::u32(..), transfer_length: 16, flags: Direction::In, lun: 0, cdb_length: 0x6, cd_code: CommandCode::ReadChipInfo, cd_opcode: 0, cd_address: 0, cd_length: 0x0, } } pub fn read_lba(start_sector: u32, sectors: u16) -> CommandBlock { CommandBlock { tag: fastrand::u32(..), transfer_length: u32::from(sectors) * SECTOR_SIZE as u32, flags: Direction::In, lun: 0, cdb_length: 0xa, cd_code: CommandCode::ReadLBA, cd_opcode: 0, cd_address: start_sector, cd_length: sectors, } } pub fn write_lba(start_sector: u32, sectors: u16) -> CommandBlock { CommandBlock { tag: fastrand::u32(..), transfer_length: u32::from(sectors) * SECTOR_SIZE as u32, flags: Direction::Out, lun: 0, cdb_length: 0xa, cd_code: CommandCode::WriteLBA, cd_opcode: 0, cd_address: start_sector, cd_length: sectors, } } pub fn reset_device(opcode: ResetOpcode) -> CommandBlock { CommandBlock { tag: fastrand::u32(..), transfer_length: 0, flags: Direction::Out, lun: 0, cdb_length: 0x6, cd_code: CommandCode::DeviceReset, cd_opcode: opcode.into(), cd_address: 0, cd_length: 0x0, } } pub fn tag(&self) -> u32 { self.tag } pub fn direction(&self) -> Direction { self.flags } pub fn transfer_length(&self) -> u32 { self.transfer_length } pub fn to_bytes(&self, mut bytes: &mut [u8]) -> usize { bytes.put_slice(b"USBC"); bytes.put_u32(self.tag); bytes.put_u32_le(self.transfer_length); bytes.put_u8(self.flags.into()); bytes.put_u8(self.lun); bytes.put_u8(self.cdb_length); bytes.put_u8(self.cd_code.into()); bytes.put_u8(self.cd_opcode); bytes.put_u32(self.cd_address); bytes.put_u8(0); bytes.put_u16(self.cd_length); COMMAND_BLOCK_BYTES } pub fn from_bytes(mut bytes: &[u8]) -> Result { if bytes.len() < COMMAND_BLOCK_BYTES { return Err(CommandBlockParseError::InvalidLength(bytes.len())); } let mut magic = [0u8; 4]; bytes.copy_to_slice(&mut magic); if &magic != b"USBC" { return Err(CommandBlockParseError::InvalidSignature(magic)); } let tag = bytes.get_u32(); let transfer_length = bytes.get_u32_le(); let flags = Direction::try_from(bytes.get_u8()) .map_err(|e| CommandBlockParseError::UnknownFlags(e.number))?; let lun = bytes.get_u8(); let cdb_length = bytes.get_u8(); let cd_code = CommandCode::try_from(bytes.get_u8()) .map_err(|e| CommandBlockParseError::UnknownCommandCode(e.number))?; let cd_opcode = bytes.get_u8(); let cd_address = bytes.get_u32(); bytes.advance(1); let cd_length = bytes.get_u16(); Ok(CommandBlock { tag, transfer_length, flags, lun, cdb_length, cd_code, cd_opcode, cd_address, cd_length, }) } } #[cfg(test)] mod test { use super::*; #[test] fn csw() { let c = CommandStatus { tag: 0x11223344, residue: 0x55667788, status: Status::SUCCESS, }; let mut b = [0u8; 13]; c.to_bytes(&mut b); let c2 = CommandStatus::from_bytes(&b).unwrap(); assert_eq!(c, c2); } #[test] fn cbw() { let c = CommandBlock { tag: 0xdead, transfer_length: 0x11223344, flags: Direction::Out, lun: 0x66, cdb_length: 0x77, cd_code: CommandCode::EraseForce, cd_opcode: 0x10, cd_address: 0x11223344, cd_length: 0x5566, }; let mut b = [0u8; 31]; c.to_bytes(&mut b); let c2 = CommandBlock::from_bytes(&b).unwrap(); assert_eq!(c, c2); } }