From bf9f22896739ecd8e159a31520d09b12590a11a5 Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Sun, 15 Dec 2024 00:56:51 +0800 Subject: [PATCH] feat: v1.10.9, add se supprot --- Cargo.lock | 250 +++++++------ Cargo.toml | 9 +- README.md | 625 +++++++++++++++++++++---------- build.rs | 9 + src/cmd_se.rs | 25 ++ src/cmd_se_generate.rs | 73 ++++ src/main.rs | 100 ++--- src/seutil.rs | 46 +++ swift-lib/Package.swift | 30 ++ swift-lib/src/lib.swift | 84 +++++ swift-rs/Cargo.lock | 417 +++++++++++++++++++++ swift-rs/Cargo.toml | 34 ++ swift-rs/LICENSE-APACHE | 201 ++++++++++ swift-rs/LICENSE-MIT | 19 + swift-rs/Package.swift | 30 ++ swift-rs/README.md | 483 ++++++++++++++++++++++++ swift-rs/src-rs/autorelease.rs | 26 ++ swift-rs/src-rs/build.rs | 326 ++++++++++++++++ swift-rs/src-rs/dark_magic.rs | 90 +++++ swift-rs/src-rs/lib.rs | 20 + swift-rs/src-rs/swift.rs | 101 +++++ swift-rs/src-rs/swift_arg.rs | 75 ++++ swift-rs/src-rs/swift_ret.rs | 55 +++ swift-rs/src-rs/test-build.rs | 20 + swift-rs/src-rs/types/array.rs | 110 ++++++ swift-rs/src-rs/types/data.rs | 75 ++++ swift-rs/src-rs/types/mod.rs | 11 + swift-rs/src-rs/types/object.rs | 75 ++++ swift-rs/src-rs/types/scalars.rs | 34 ++ swift-rs/src-rs/types/string.rs | 84 +++++ swift-rs/src-swift/lib.swift | 94 +++++ 31 files changed, 3276 insertions(+), 355 deletions(-) create mode 100644 build.rs create mode 100644 src/cmd_se.rs create mode 100644 src/cmd_se_generate.rs create mode 100644 src/seutil.rs create mode 100644 swift-lib/Package.swift create mode 100644 swift-lib/src/lib.swift create mode 100644 swift-rs/Cargo.lock create mode 100644 swift-rs/Cargo.toml create mode 100644 swift-rs/LICENSE-APACHE create mode 100644 swift-rs/LICENSE-MIT create mode 100644 swift-rs/Package.swift create mode 100644 swift-rs/README.md create mode 100644 swift-rs/src-rs/autorelease.rs create mode 100644 swift-rs/src-rs/build.rs create mode 100644 swift-rs/src-rs/dark_magic.rs create mode 100644 swift-rs/src-rs/lib.rs create mode 100644 swift-rs/src-rs/swift.rs create mode 100644 swift-rs/src-rs/swift_arg.rs create mode 100644 swift-rs/src-rs/swift_ret.rs create mode 100644 swift-rs/src-rs/test-build.rs create mode 100644 swift-rs/src-rs/types/array.rs create mode 100644 swift-rs/src-rs/types/data.rs create mode 100644 swift-rs/src-rs/types/mod.rs create mode 100644 swift-rs/src-rs/types/object.rs create mode 100644 swift-rs/src-rs/types/scalars.rs create mode 100644 swift-rs/src-rs/types/string.rs create mode 100644 swift-rs/src-swift/lib.swift diff --git a/Cargo.lock b/Cargo.lock index 9656c58..91946bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,9 +71,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" +checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" [[package]] name = "arrayvec" @@ -103,7 +103,7 @@ dependencies = [ "num-traits", "rusticata-macros 4.1.0", "thiserror", - "time 0.3.36", + "time 0.3.37", ] [[package]] @@ -420,9 +420,9 @@ dependencies = [ [[package]] name = "buffered-reader" -version = "1.3.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd098763fdb64579407a8c83cf0d751e6d4a7e161d0114c89cc181a2ca760ec8" +checksum = "fabd1c5e55587a8e8526172d63ad2ba665fa18c8acb39ec9a77af1708c982b9b" dependencies = [ "bzip2", "flate2", @@ -460,15 +460,15 @@ dependencies = [ [[package]] name = "bytes" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "bzip2" -version = "0.4.4" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +checksum = "bafdbf26611df8c14810e268ddceda071c297570a5fb360ceddf617fe417ef58" dependencies = [ "bzip2-sys", "libc", @@ -487,7 +487,7 @@ dependencies = [ [[package]] name = "card-cli" -version = "1.10.8" +version = "1.10.9" dependencies = [ "authenticator 0.3.1", "base64 0.21.7", @@ -523,6 +523,7 @@ dependencies = [ "spki 0.7.3", "ssh-agent", "sshcerts", + "swift-rs", "tabled", "u2f", "x509", @@ -533,9 +534,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.37" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40545c26d092346d8a8dab71ee48e7685a7a9cba76e634790c215b41a4a7b4cf" +checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf" dependencies = [ "shlex", ] @@ -563,9 +564,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "android-tzdata", "iana-time-zone", @@ -662,9 +663,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -1078,19 +1079,19 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "fastrand" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "ff" @@ -1126,9 +1127,9 @@ checksum = "b3ea1ec5f8307826a5b71094dd91fc04d4ae75d5709b20ad351c7fb4815c86ec" [[package]] name = "flate2" -version = "1.0.34" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", "miniz_oxide 0.8.0", @@ -1294,9 +1295,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96512db27971c2c3eece70a1e106fbe6c87760234e31e8f7e5634912fe52794a" +checksum = "2cb8bc4c28d15ade99c7e90b219f30da4be5c88e586277e8cbe886beeb868ab2" dependencies = [ "typenum", ] @@ -1365,7 +1366,7 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ - "bytes 1.8.0", + "bytes 1.9.0", "fnv", "futures-core", "futures-sink", @@ -1373,7 +1374,7 @@ dependencies = [ "http", "indexmap", "slab", - "tokio 1.41.1", + "tokio 1.42.0", "tokio-util", "tracing", ] @@ -1386,9 +1387,9 @@ checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" [[package]] name = "hashbrown" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "heck" @@ -1472,7 +1473,7 @@ version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ - "bytes 1.8.0", + "bytes 1.9.0", "fnv", "itoa", ] @@ -1483,7 +1484,7 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ - "bytes 1.8.0", + "bytes 1.9.0", "http", "pin-project-lite", ] @@ -1512,7 +1513,7 @@ version = "0.14.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" dependencies = [ - "bytes 1.8.0", + "bytes 1.9.0", "futures-channel", "futures-core", "futures-util", @@ -1524,7 +1525,7 @@ dependencies = [ "itoa", "pin-project-lite", "socket2", - "tokio 1.41.1", + "tokio 1.42.0", "tower-service", "tracing", "want", @@ -1536,10 +1537,10 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ - "bytes 1.8.0", + "bytes 1.9.0", "hyper", "native-tls", - "tokio 1.41.1", + "tokio 1.42.0", "tokio-native-tls", ] @@ -1707,9 +1708,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", "hashbrown", @@ -1776,10 +1777,11 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -1865,15 +1867,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.162" +version = "0.2.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" +checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" [[package]] name = "libloading" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if 1.0.0", "windows-targets 0.52.6", @@ -1935,9 +1937,9 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "litemap" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "lock_api" @@ -2058,11 +2060,10 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "hermit-abi 0.3.9", "libc", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", @@ -2832,7 +2833,7 @@ checksum = "52c4f3084aa3bc7dfbba4eff4fab2a54db4324965d8872ab933565e6fbd83bc6" dependencies = [ "pem", "ring 0.16.20", - "time 0.3.36", + "time 0.3.37", "yasna", ] @@ -2892,7 +2893,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ "base64 0.21.7", - "bytes 1.8.0", + "bytes 1.9.0", "encoding_rs", "futures-core", "futures-util", @@ -2915,7 +2916,7 @@ dependencies = [ "serde_urlencoded", "sync_wrapper", "system-configuration", - "tokio 1.41.1", + "tokio 1.42.0", "tokio-native-tls", "tower-service", "url", @@ -3030,9 +3031,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.6" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519" dependencies = [ "const-oid", "digest 0.10.7", @@ -3129,15 +3130,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.39" +version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375116bee2be9ed569afe2154ea6a99dfdffd257f533f187498c2a8f5feaf4ee" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3172,9 +3173,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ "windows-sys 0.59.0", ] @@ -3262,9 +3263,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "sequoia-openpgp" -version = "1.21.2" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13261ee216b44d932ef93b2d4a75d45199bef77864bcc5b77ecfc7bc0ecb02d6" +checksum = "e858e4e9e48ff079cede92e1b45c942a5466ce9a4e3cc0c2a7e66586a718ef59" dependencies = [ "anyhow", "base64 0.22.1", @@ -3291,9 +3292,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] @@ -3319,9 +3320,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote 1.0.37", @@ -3330,9 +3331,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.132" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "itoa", "memchr", @@ -3383,7 +3384,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f606421e4a6012877e893c399822a4ed4b089164c5969424e1b9d1e66e6964b" dependencies = [ "digest 0.10.7", - "generic-array 1.1.0", + "generic-array 1.1.1", ] [[package]] @@ -3490,9 +3491,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", @@ -3627,6 +3628,17 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +[[package]] +name = "swift-rs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + [[package]] name = "syn" version = "1.0.109" @@ -3783,18 +3795,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote 1.0.37", @@ -3814,9 +3826,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", @@ -3835,9 +3847,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", @@ -3909,14 +3921,14 @@ dependencies = [ [[package]] name = "tokio" -version = "1.41.1" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" dependencies = [ "backtrace", - "bytes 1.8.0", + "bytes 1.9.0", "libc", - "mio 1.0.2", + "mio 1.0.3", "pin-project-lite", "socket2", "windows-sys 0.52.0", @@ -3982,7 +3994,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ "native-tls", - "tokio 1.41.1", + "tokio 1.42.0", ] [[package]] @@ -4092,15 +4104,15 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ - "bytes 1.8.0", + "bytes 1.9.0", "futures-core", "futures-sink", "pin-project-lite", - "tokio 1.41.1", + "tokio 1.42.0", ] [[package]] @@ -4111,9 +4123,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-core", @@ -4121,9 +4133,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", ] @@ -4188,9 +4200,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.3" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", @@ -4275,9 +4287,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if 1.0.0", "once_cell", @@ -4286,13 +4298,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote 1.0.37", "syn 2.0.87", @@ -4301,21 +4312,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.45" +version = "0.4.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" dependencies = [ "cfg-if 1.0.0", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote 1.0.37", "wasm-bindgen-macro-support", @@ -4323,9 +4335,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote 1.0.37", @@ -4336,15 +4348,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "web-sys" -version = "0.3.72" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" dependencies = [ "js-sys", "wasm-bindgen", @@ -4639,7 +4651,7 @@ dependencies = [ "oid-registry", "rusticata-macros 4.1.0", "thiserror", - "time 0.3.36", + "time 0.3.37", ] [[package]] @@ -4657,7 +4669,7 @@ dependencies = [ "ring 0.16.20", "rusticata-macros 4.1.0", "thiserror", - "time 0.3.36", + "time 0.3.37", ] [[package]] @@ -4672,14 +4684,14 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" dependencies = [ - "time 0.3.36", + "time 0.3.37", ] [[package]] name = "yoke" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ "serde", "stable_deref_trait", @@ -4689,9 +4701,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote 1.0.37", @@ -4771,7 +4783,7 @@ dependencies = [ "pbkdf2 0.12.2", "pcsc", "rand_core 0.6.4", - "rsa 0.9.6", + "rsa 0.9.7", "secrecy", "sha1", "sha2 0.10.8", @@ -4805,18 +4817,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote 1.0.37", diff --git a/Cargo.toml b/Cargo.toml index 258b7fb..56b8994 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "card-cli" -version = "1.10.8" +version = "1.10.9" authors = ["Hatter Jiang "] edition = "2018" [features] -default = ["with-sequoia-openpgp"] +default = ["with-sequoia-openpgp", "with-secure-enclave"] with-sequoia-openpgp = ["sequoia-openpgp", "openpgp-card-sequoia"] +with-secure-enclave = ["swift-rs"] [dependencies] authenticator = "0.3" @@ -49,9 +50,13 @@ rpassword = "7.3" secrecy = "0.8" der-parser = "9.0" sshcerts = "0.13" +swift-rs = { version = "1.0.7", optional = true } #lazy_static = "1.4.0" #ssh-key = "0.4.0" #ctap-hid-fido2 = "2.1.3" +[build-dependencies] +swift-rs = { version = "1.0.7", features = ["build"], optional = true } + [patch.crates-io] u2f = { git = "https://github.com/jht5945/u2f-rs.git" } diff --git a/README.md b/README.md index 275b164..2e83a2d 100644 --- a/README.md +++ b/README.md @@ -1,238 +1,483 @@ -# card-cli +# swift-rs -> FIDO(U2F, WebAuthn), YubiKey, OpenPGP command line tool +![Crates.io](https://img.shields.io/crates/v/swift-rs?color=blue&style=flat-square) +![docs.rs](https://img.shields.io/docsrs/swift-rs?color=blue&style=flat-square) -Install: -```shell -cargo install --git https://git.hatter.ink/hatter/card-cli.git +Call Swift functions from Rust with ease! + +## Setup + +Add `swift-rs` to your project's `dependencies` and `build-dependencies`: + +```toml +[dependencies] +swift-rs = "1.0.5" + +[build-dependencies] +swift-rs = { version = "1.0.5", features = ["build"] } ``` -Compile without features: -```shell -cargo build --release --no-default-features +Next, some setup work must be done: + +1. Ensure your swift code is organized into a Swift Package. +This can be done in XCode by selecting File -> New -> Project -> Multiplatform -> Swift Package and importing your existing code. +2. Add `SwiftRs` as a dependency to your Swift package and make the build type `.static`. +```swift +let package = Package( + dependencies: [ + .package(url: "https://github.com/Brendonovich/swift-rs", from: "1.0.5") + ], + products: [ + .library( + type: .static, + ), + ], + targets: [ + .target( + // Must specify swift-rs as a dependency of your target + dependencies: [ + .product( + name: "SwiftRs", + package: "swift-rs" + ) + ], + ) + ] +) ``` +3. Create a `build.rs` file in your project's root folder, if you don't have one already. +4. Use `SwiftLinker` in your `build.rs` file to link both the Swift runtime and your Swift package. +The package name should be the same as is specified in your `Package.swift` file, +and the path should point to your Swift project's root folder relative to your crate's root folder. -# PGP +```rust +use swift_rs::SwiftLinker; -## encrypt & decrypt +fn build() { + // swift-rs has a minimum of macOS 10.13 + // Ensure the same minimum supported macOS version is specified as in your `Package.swift` file. + SwiftLinker::new("10.13") + // Only if you are also targetting iOS + // Ensure the same minimum supported iOS version is specified as in your `Package.swift` file + .with_ios("11") + .with_package(PACKAGE_NAME, PACKAGE_PATH) + .link(); -sample encrypt public key -``` ------BEGIN PUBLIC KEY----- -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEApUM8M+QRMUw0dIvXISFx -43j4h9CK38Y9HD6kPcc3Z0dCGPiFy7Ze0OQebPWHyUZ2YmqsdyzFuOQuV9P2pxxj -/WLIgRqZV8Jk8tWhtAjOOvm0MTc2rg+EJHfa+zhX4eFEMsj4DvQBMJDXiKnpXTM/ -j7oMKpIUQHqfXBwsEJHLmHZTLeEBEYKcZXTAmuu3WdxK5jvEc02Xt2hZ1fBs0M9e -/2EMe3t69aH4/rabiBjF2h9Jde15wrJMxXaCCWJqYhbBS0CJ3BdjkAqOIpcqPXva -xiJN1pNpK8ejA9Q4Nmx7pxnvfv+hCPkWXZS3r/BWZ9lFZc8uErQEbB4gLgko8jOl -fQF7cYqtZEs69qY8nnIUBsqZYfAp+bQd2xCFSbEZAl+OrtGzfVjD9YFMPy02+xRg -v2N3KT3KHHvuU7WxrvffrshP2fwDuG2MBlmcq1suAKxA0cYPSyajceEqw/3ogSp7 -7SYx41rT8EWLmTvU0CHzCsuf/O7sDWZRfxatAzWhBBhnKCPqzizpOQOqm8XhCt74 -FfnabPpHM9XUjoQIPrTssyS3eWqynzJiAqez6v2LK2fhL7IkcLtvt5p59Y+KY4I6 -YQ09iUh7lKJHRhkgTomUurJHieVHMWFGIHofEC+nU6pGIUh0P7Nr0Gz45GJTwWGd -hW53WfImja+b5kwwyqUikyMCAwEAAQ== ------END PUBLIC KEY----- -``` - -encrypt -```shell -$ openssl rsautl -encrypt -pubin -inkey enc_key.pem -in test.txt -out enc.txt -pkcs - -OR - -$ openssl pkeyutl -encrypt -inkey enc_key.pem -pubin -in a.txt -out enc.txt -``` - -decrypt -```shell -$ card-cli pgp-card-decrypt -c $(cat enc.txt | xxd -ps -c 11111) - -OR - -$ card-cli piv-decrypt -s r3 -c "$(cat enc.txt | base64)" -``` - -## sign & verify - -sign -```shell -$ card-cli pgp-card-sign -2 $(shasum -a 256 test.txt | awk '{print $1}') - -OR - -$ card-cli pgp-card-sign --in test.txt --use-sha256 -``` - -verify -```shell -$ openssl dgst -sha256 -verify sign_key.pem -signature sig test.txt -Verified OK -``` - -# sample public keys - -``` -[INFO ] Authentication fingerprint: EB0A43A10BFC6E58323F7650BA42AE533FDCE10E -[INFO ] Authentication public key sha256: ac97c7f9f500f3fbab635536096311c62698f8c22abd9e9687de7893932bc15b -[INFO ] Authentication public key: -----BEGIN PUBLIC KEY----- -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA8Kg5fg47YilT/xFOZ7xK -17T47cfwzS6L/4IRtTjcygvmOVSdOISihQxVfsygpxhThRQ3pjqhFGqH9LUIpry/ -a8hWfPMZYolYywBvdx5S6UGDUeRf2zLcRYrQo+Fs9oxdhxPE05HhWl9L5ORn4HWz -RZSkNfh7PDKPJRUaJV85uB6Fyvt0GGY14pmINZ7NRLLi2ubYBlp3CLSh7XdleVE8 -/Q6gya501INhXUksuwHXdPYtcXF3l+VIdMc6YJTxivFLtujqiEAfEwauuv+1GzsN -ZDOg6JfSc+1d7iZMixU4RrKtzM57ZwGX0bAK3MQdP6iT20DOYq/BDJTXJuhQBWgE -6pIDiTJF4q/If0ZLxU+kxstAEg0fuD+wOg/+4W1BSn5D3hSdvVOxgj3hWtPudAVp -QucP8LKnq5B0oy4LdGqXXAQYJ2Q+ln0N9By2T8N/P37HOsR7yJLl8cM2FptCoo4x -ViGzmIbir8EyZ6VQmoi8fqOP4x9nH5XeNA2JCVLEc0o6n5PJ4IitYYCb0NGOPTHV -FEz2qzxkQDJxS5oC7GddWQB/pa4Jq0EL9dEabB2oPyvYBAmmE0HzZWLl3T1kR1dJ -fAXuqgShFcZLXa1SFUpLzlJi3jARuxoaUeHnKP3xeAd8o5WPBwzXM7LL47nTueNa -uFZKwHs/e9x4EszQ/qFo2uECAwEAAQ== ------END PUBLIC KEY----- -[INFO ] Encryption fingerprint: E48EC98FE6CAE85AAFD5A68AC37A909EAF1BFB00 -[INFO ] Encryption public key sha256: de5a99c239a82adf039982cb6319abcb95f44cfc76a5027ae6f7819cfc5fde7c -[INFO ] Encryption public key: -----BEGIN PUBLIC KEY----- -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEApUM8M+QRMUw0dIvXISFx -43j4h9CK38Y9HD6kPcc3Z0dCGPiFy7Ze0OQebPWHyUZ2YmqsdyzFuOQuV9P2pxxj -/WLIgRqZV8Jk8tWhtAjOOvm0MTc2rg+EJHfa+zhX4eFEMsj4DvQBMJDXiKnpXTM/ -j7oMKpIUQHqfXBwsEJHLmHZTLeEBEYKcZXTAmuu3WdxK5jvEc02Xt2hZ1fBs0M9e -/2EMe3t69aH4/rabiBjF2h9Jde15wrJMxXaCCWJqYhbBS0CJ3BdjkAqOIpcqPXva -xiJN1pNpK8ejA9Q4Nmx7pxnvfv+hCPkWXZS3r/BWZ9lFZc8uErQEbB4gLgko8jOl -fQF7cYqtZEs69qY8nnIUBsqZYfAp+bQd2xCFSbEZAl+OrtGzfVjD9YFMPy02+xRg -v2N3KT3KHHvuU7WxrvffrshP2fwDuG2MBlmcq1suAKxA0cYPSyajceEqw/3ogSp7 -7SYx41rT8EWLmTvU0CHzCsuf/O7sDWZRfxatAzWhBBhnKCPqzizpOQOqm8XhCt74 -FfnabPpHM9XUjoQIPrTssyS3eWqynzJiAqez6v2LK2fhL7IkcLtvt5p59Y+KY4I6 -YQ09iUh7lKJHRhkgTomUurJHieVHMWFGIHofEC+nU6pGIUh0P7Nr0Gz45GJTwWGd -hW53WfImja+b5kwwyqUikyMCAwEAAQ== ------END PUBLIC KEY----- -[INFO ] Signature fingerprint: 6FAFC0E0170985AA71545483C794B1646A886CD6 -[INFO ] Signature public key sha256: d65831b0316a03828eeb31fe6a51e6eec59e7092eb6d3477404ad2f5fa08e903 -[INFO ] Signature public key: -----BEGIN PUBLIC KEY----- -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr7kVYTHxFjZD9kT+w97B -GiHfqlyoulJ10cRqaWwX3/mZKfoeGJkBDglFnLfgtHhXivPqRSn73sCX6M0HCzSq -9M/drkms/H8cecM08SoZdZTM0TVr/c8w0ZA7Ipoder9K/9LdGpIgoc3qa8hdY2nH -TwGYJ53aQv32neOcg3p/vzqdzgmwbk4JLjcIMhOuTUj4xM8OMnkxRpyy9+Ghi22X -oZXDxu8meI2Pc8jM+zpRYb0wd06dd231m03CK80LAwSvIn7dGFAr+xTF5XKopXHY -vuT+9SshszbP4+pSqbEHZhJOX1/os+Uo8KKfysifJBKfKCVvVWho8QCWoXgiNuOJ -3cYoThfWwUpIS1S51el/fPPSk3K295jlZAON9yEszdzKHGVGOrtJ7e9XSxKIXqhG -us2XA14eMvhQdaOgd/bscXIYe4YzqvaqvVRiDUP8bzA+4w0ctB0w9HRFGK5lajTn -/QQvkKP9JQXm6Tb2GB+wjuU3wPXhKRWscEzbHVwMq2WiaYH5vWVhHI6lbqXcWkvZ -i2gZXQPyrAKzUau1Z2lBN2xi2cv5+9JJth5pHebuLOWbuf1WV4nR1fdSNdG7GGmj -G951w/1bTqIlzN4Vl6kdore4u45U4kO4Xf7Hq8b8k8ys107ENpgO7lB9KLoMMFKS -vjG+EPEF3g8ywKaS8mZQX+sCAwEAAQ== ------END PUBLIC KEY----- -``` - -# piv-ecdh - -```shell -$ card-cli piv-ecdh --public-256 --public-key-point-hex 04dd3eebd906c9cf00b08ec29f7ed61804d1cc1d1352d9257b628191e08fc3717c4fae3298cd5c4829cec8bf3a946e7db60b7857e1287f6a0bae6b3f2342f007d0 --json -{ - "epk_point_hex": "04bbb6a458e81d2c646587118abfb029ff715db366f92a1d0468887f9947f176c11961eccebd5b9cbbb8b67e33fa8d3f0010a4aaf5010d0f419f1f99b4c2d7aa56", - "pk_point_hex": "04dd3eebd906c9cf00b08ec29f7ed61804d1cc1d1352d9257b628191e08fc3717c4fae3298cd5c4829cec8bf3a946e7db60b7857e1287f6a0bae6b3f2342f007d0", - "shared_secret_hex": "58069f1b2ce85c4f2232070567bef99f71b45f69ab321c4c782e599813b56f25" -} - -$ card-cli piv-ecdh --private --slot 82 --epk 04bbb6a458e81d2c646587118abfb029ff715db366f92a1d0468887f9947f176c11961eccebd5b9cbbb8b67e33fa8d3f0010a4aaf5010d0f419f1f99b4c2d7aa56 --json -[WARN ] Get slot: 82 meta data failed -{ - "shared_secret_hex": "58069f1b2ce85c4f2232070567bef99f71b45f69ab321c4c782e599813b56f25" + // Other build steps } ``` -# piv-ecsign +With those steps completed, you should be ready to start using Swift code from Rust! -```shell -$ card-cli piv-ecsign -s 82 --hash-hex 8f25018489d6fe0dec34a352314c38dc146247b7de65735790f4398a92afa84b --json +If you experience the error `dyld[16008]: Library not loaded: @rpath/libswiftCore.dylib` +when using `swift-rs` with [Tauri](https://tauri.app) ensure you have set your +[Tauri minimum system version](https://tauri.app/v1/guides/building/macos#setting-a-minimum-system-version) +to `10.15` or higher in your `tauri.config.json`. -{ - "hash_hex": "8f25018489d6fe0dec34a352314c38dc146247b7de65735790f4398a92afa84b", - "signed_data_base64": "MEUCICdes5Y0Id7KBNL23ZsTXXXGAzmsWYyDa6szQwjCxhCJAiEAhJotD2dPK/fWNjNrwkrPd0F20MpGgIY3WiKDR7YgJbk=", - "signed_data_hex": "30450220275eb3963421deca04d2f6dd9b135d75c60339ac598c836bab334308c2c61089022100849a2d0f674f2bf7d636336bc24acf774176d0ca468086375a228347b62025b9", - "slot": "82" +## Calling basic functions + +To allow calling a Swift function from Rust, it must follow some rules: + +1. It must be global +2. It must be annotated with `@_cdecl`, so that it is callable from C +3. It must only use types that can be represented in Objective-C, +so only classes that derive `NSObject`, as well as scalars such as Int and Bool. +This excludes strings, arrays, generics (though all of these can be sent with workarounds) +and structs (which are strictly forbidden). + +For this example we will use a function that simply squares a number: + +```swift +public func squareNumber(number: Int) -> Int { + return number * number } ``` -# import private key to PIV card & generate certificate +So far, this function meets requirements 1 and 3: it is global and public, and only uses the Int type, which is Objective-C compatible. +However, it is not annotated with `@_cdecl`. +To fix this, we must call `@_cdecl` before the function's declaration and specify the name that the function is exposed to Rust with as its only argument. +To keep with Rust's naming conventions, we will export this function in snake case as `square_number`. -```shell -$ ykman piv keys import --pin-policy NEVER --touch-policy CACHED 82 private_key.pem +```swift +@_cdecl("square_number") +public func squareNumber(number: Int) -> Int { + return number * number +} ``` -| Parameter | Description | -| ---- |------------------------------------------------------------------| -| --pin-policy | \[ DEFAULT \| NEVER \| ONCE \| ALWAYS \] PIN policy for slot | -| --touch-policy | \[ DEFAULT \| NEVER \| ALWAYS \| CACHED \] touch policy for slot | +Now that `squareNumber` is properly exposed to Rust, we can start interfacing with it. +This can be done using the `swift!` macro, with the `Int` type helping to provide a similar function signature: -```shell -$ ykman piv certificates generate 82 public_key.pem -s 'O=age-plugin-yubikey,OU=0.3.3,CN=hatter-yk' +```rust +use swift_rs::swift; + +swift!(fn square_number(number: Int) -> Int); ``` -# age +Lastly, you can call the function from regular Rust functions. +Note that all calls to a Swift function are unsafe, +and require wrapping in an `unsafe {}` block or `unsafe fn`. -## pgp-age-address +```rust +fn main() { + let input: Int = 4; + let output = unsafe { square_number(input) }; -```shell -$ card-cli pgp-age-address -[INFO ] Found 1 card(s) -[OK ] Found card #0: Ok(ApplicationIdentifier { application: 1, version: 772, manufacturer: 6, serial: 370378374 }) -[OK ] Age address: age10l464vxcpnkjguctvylnmp5jg4swhncn4quda0qxta3ud8pycc0qeaj2te + println!("Input: {}, Squared: {}", input, output); + // Prints "Input: 4, Squared: 16" +} ``` -# sign-jwt +Check [the documentation](TODO) for all available helper types. -Sign a JWT: -```shell -card-cli sign-jwt -s r3 \ - -C iss:****** \ - -C sub:****** \ - -C aud:client_gard****** \ - -K KEY=ID \ - --jti \ - --validity 10m --json +## Returning objects from Swift + +Let's say that we want our `squareNumber` function to return not only the result, but also the original input. +A standard way to do this in Swift would be with a struct: + +```swift +struct SquareNumberResult { + var input: Int + var output: Int +} ``` -# SSH CA +We are not allowed to do this, though, since structs cannot be represented in Objective-C. +Instead, we must use a class that extends `NSObject`: -## Generate SSH root CA +```swift +class SquareNumberResult: NSObject { + var input: Int + var output: Int -```shell -card-cli ssh-pub-key --ca -s r15 + init(_ input: Int, _ output: Int) { + self.input = input; + self.output = output + } +} ``` -Outputs: -``` -cert-authority,principals="root" ecdsa-sha2-nistp384 AAAAE2VjZHNh****** Yubikey-PIV-R15 +Yes, this class could contain the squaring logic too, but that is irrelevant for this example + +An instance of this class can then be returned from `squareNumber`: + +```swift +@_cdecl("square_number") +public func squareNumber(input: Int) -> SquareNumberResult { + let output = input * input + return SquareNumberResult(input, output) +} ``` -> `principals` can be multiple items, split by `,`, e.g. `root,hatterink` +As you can see, returning an `NSObject` from Swift isn't too difficult. +The same can't be said for the Rust implementation, though. +`squareNumber` doesn't actually return a struct containing `input` and `output`, +but instead a pointer to a `SquareNumberResult` stored somewhere in memory. +Additionally, this value contains more data than just `input` and `output`: +Since it is an `NSObject`, it contains extra data that must be accounted for when using it in Rust. -## Generate SSH user CA +This may sound daunting, but it's not actually a problem thanks to `SRObject`. +This type manages the pointer internally, and takes a generic argument for a struct that we can access the data through. +Let's see how we'd implement `SquareNumberResult` in Rust: -```shell -ssh-keygen -f id_user +```rust +use swift_rs::{swift, Int, SRObject}; -card-cli ssh-piv-cert --pub id_user.pub -s r15 +// Any struct that is used in a C function must be annotated +// with this, and since our Swift function is exposed as a +// C function with @_cdecl, this is necessary here +#[repr(C)] +// Struct matches the class declaration in Swift +struct SquareNumberResult { + input: Int, + output: Int +} + +// SRObject abstracts away the underlying pointer and will automatically deref to +// &SquareNumberResult through the Deref trait +swift!(fn square_number(input: Int) -> SRObject); ``` -Show SSH CA cert details: -```shell -ssh-keygen -L -f id_user-cert.pub +Then, using the new return value is just like using `SquareNumberResult` directly: + +```rust +fn main() { + let input = 4; + let result = unsafe { square_number(input) }; + + let result_input = result.input; // 4 + let result_output = result.output; // 16 +} ``` -SSH to server: -```shell -ssh -i id_user root@example.com +Creating objects in Rust and then passing them to Swift is not supported. + +## Optionals + +`swift-rs` also supports Swift's `nil` type, but only for functions that return optional `NSObject`s. +Functions returning optional primitives cannot be represented in Objective C, and thus are not supported. + +Let's say we have a function returning an optional `SRString`: + +```swift +@_cdecl("optional_string") +func optionalString(returnNil: Bool) -> SRString? { + if (returnNil) return nil + else return SRString("lorem ipsum") +} ``` +Thanks to Rust's [null pointer optimisation](https://doc.rust-lang.org/std/option/index.html#representation), +the optional nature of `SRString?` can be represented by wrapping `SRString` in Rust's `Option` type! -

+```rust +use swift_rs::{swift, Bool, SRString}; -Downloads: -* https://developers.yubico.com/yubikey-manager/ +swift!(optional_string(return_nil: Bool) -> Option) +``` -
+Null pointers are actually the reason why a function that returns an optional primitive cannot be represented in C. +If this were to be supported, how could a `nil` be differentiated from a number? It can't! -Related projects: -* https://crates.io/crates/openpgp-card-tools -* https://github.com/sekey/sekey -* https://github.com/str4d/age-plugin-yubikey +## Complex types + +So far we have only looked at using primitive types and structs/classes, +but this leaves out some of the most important data structures: arrays (`SRArray`) and strings (`SRString`). +These types must be treated with caution, however, and are not as flexible as their native Swift & Rust counterparts. + +### Strings + +Strings can be passed between Rust and Swift through `SRString`, which can be created from native strings in either language. + +**As an argument** + +```swift +import SwiftRs + +@_cdecl("swift_print") +public func swiftPrint(value: SRString) { + // .to_string() converts the SRString to a Swift String + print(value.to_string()) +} +``` + +```rust +use swift_rs::{swift, SRString, SwiftRef}; + +swift!(fn swift_print(value: &SRString)); + +fn main() { + // SRString can be created by simply calling .into() on any string reference. + // This will allocate memory in Swift and copy the string + let value: SRString = "lorem ipsum".into(); + + unsafe { swift_print(&value) }; // Will print "lorem ipsum" to the console +} +``` + +**As a return value** + +```swift +import SwiftRs + +@_cdecl("get_string") +public func getString() -> SRString { + let value = "lorem ipsum" + + // SRString can be created from a regular String + return SRString(value) +} +``` + +```rust +use swift_rs::{swift, SRString}; + +swift!(fn get_string() -> SRString); + +fn main() { + let value_srstring = unsafe { get_string() }; + + // SRString can be converted to an &str using as_str()... + let value_str: &str = value_srstring.as_str(); + // or though the Deref trait + let value_str: &str = &*value_srstring; + + // SRString also implements Display + println!("{}", value_srstring); // Will print "lorem ipsum" to the console +} +``` + +### Arrays + +**Primitive Arrays** + +Representing arrays properly is tricky, since we cannot use generics as Swift arguments or return values according to rule 3. +Instead, `swift-rs` provides a generic `SRArray` that can be embedded inside another class that extends `NSObject` that is not generic, +but is restricted to a single element type. + +```swift +import SwiftRs + +// Argument/Return values can contain generic types, but cannot be generic themselves. +// This includes extending generic types. +class IntArray: NSObject { + var data: SRArray + + init(_ data: [Int]) { + self.data = SRArray(data) + } +} + +@_cdecl("get_numbers") +public func getNumbers() -> IntArray { + let numbers = [1, 2, 3, 4] + + return IntArray(numbers) +} +``` + +```rust +use swift_rs::{Int, SRArray, SRObject}; + +#[repr(C)] +struct IntArray { + data: SRArray +} + +// Since IntArray extends NSObject in its Swift implementation, +// it must be wrapped in SRObject on the Rust side +swift!(fn get_numbers() -> SRObject); + +fn main() { + let numbers = unsafe { get_numbers() }; + + // SRArray can be accessed as a slice via as_slice + let numbers_slice: &[Int] = numbers.data.as_slice(); + + assert_eq!(numbers_slice, &[1, 2, 3, 4]); +} +``` + +To simplify things on the rust side, we can actually do away with the `IntArray` struct. +Since `IntArray` only has one field, its memory layout is identical to that of `SRArray`, +so our Rust implementation can be simplified at the cost of equivalence with our Swift code: + +```rust +// We still need to wrap the array in SRObject since +// the wrapper class in Swift is an NSObject +swift!(fn get_numbers() -> SRObject>); +``` + +**NSObject Arrays** + +What if we want to return an `NSObject` array? There are two options on the Swift side: + +1. Continue using `SRArray` and a custom wrapper type, or +2. Use `SRObjectArray`, a wrapper type provided by `swift-rs` that accepts any `NSObject` as its elements. +This can be easier than continuing to create wrapper types, but sacrifices some type safety. + +There is also `SRObjectArray` for Rust, which is compatible with any single-element Swift wrapper type (and of course `SRObjectArray` in Swift), +and automatically wraps its elements in `SRObject`, so there's very little reason to not use it unless you _really_ like custom wrapper types. + +Using `SRObjectArray` in both Swift and Rust with a basic custom class/struct can be done like this: + +```swift +import SwiftRs + +class IntTuple: NSObject { + var item1: Int + var item2: Int + + init(_ item1: Int, _ item2: Int) { + self.item1 = item1 + self.item2 = item2 + } +} + +@_cdecl("get_tuples") +public func getTuples() -> SRObjectArray { + let tuple1 = IntTuple(0,1), + tuple2 = IntTuple(2,3), + tuple3 = IntTuple(4,5) + + let tupleArray: [IntTuple] = [ + tuple1, + tuple2, + tuple3 + ] + + // Type safety is only lost when the Swift array is converted to an SRObjectArray + return SRObjectArray(tupleArray) +} +``` + +```rust +use swift_rs::{swift, Int, SRObjectArray}; + +#[repr(C)] +struct IntTuple { + item1: Int, + item2: Int +} + +// No need to wrap IntTuple in SRObject since +// SRObjectArray does it automatically +swift!(fn get_tuples() -> SRObjectArray); + +fn main() { + let tuples = unsafe { get_tuples() }; + + for tuple in tuples.as_slice() { + // Will print each tuple's contents to the console + println!("Item 1: {}, Item 2: {}", tuple.item1, tuple.item2); + } +} +``` + +Complex types can contain whatever combination of primitives and `SRObject` you like, just remember to follow the 3 rules! + +## Bonuses + +### SRData + +A wrapper type for `SRArray` designed for storing `u8`s - essentially just a byte buffer. + +### Tighter Memory Control with `autoreleasepool!` + +If you've come to Swift from an Objective-C background, you likely know the utility of `@autoreleasepool` blocks. +`swift-rs` has your back on this too, just wrap your block of code with a `autoreleasepool!`, and that block of code now executes with its own autorelease pool! + +```rust +use swift_rs::autoreleasepool; + +for _ in 0..10000 { + autoreleasepool!({ + // do some memory intensive thing here + }); +} +``` + +## Limitations + +Currently, the only types that can be created from Rust are number types, boolean, `SRString`, and `SRData`. +This is because those types are easy to allocate memory for, either on the stack or on the heap via calling out to swift, +whereas other types are not. This may be implemented in the future, though. + +Mutating values across Swift and Rust is not currently an aim for this library, it is purely for providing arguments and returning values. +Besides, this would go against Rust's programming model, potentially allowing for multiple shared references to a value instead of interior mutability via something like a Mutex. + +## License + +Licensed under either of + + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the Apache-2.0 +license, shall be dual licensed as above, without any additional terms or +conditions. diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..2640416 --- /dev/null +++ b/build.rs @@ -0,0 +1,9 @@ +fn main() { + // Ensure this matches the versions set in your `Package.swift` file. + #[cfg(feature = "with-secure-enclave")] + swift_rs::SwiftLinker::new("11") + .with_ios("11") + .with_package("swift-lib", "./swift-lib/") + .link(); +} + diff --git a/src/cmd_se.rs b/src/cmd_se.rs new file mode 100644 index 0000000..61aee35 --- /dev/null +++ b/src/cmd_se.rs @@ -0,0 +1,25 @@ +use crate::seutil; +use clap::{App, ArgMatches, SubCommand}; +use rust_util::util_clap::{Command, CommandError}; + +pub struct CommandImpl; + +impl Command for CommandImpl { + fn name(&self) -> &str { + "se" + } + + fn subcommand<'a>(&self) -> App<'a, 'a> { + SubCommand::with_name(self.name()).about("Secure Enclave subcommand") + // .arg(Arg::with_name("json").long("json").help("JSON output")) + } + + fn run(&self, _arg_matches: &ArgMatches, _sub_arg_matches: &ArgMatches) -> CommandError { + if seutil::is_support_se() { + success!("Secure Enclave is supported.") + } else { + failure!("Secure Enclave is NOT supported.") + } + Ok(None) + } +} diff --git a/src/cmd_se_generate.rs b/src/cmd_se_generate.rs new file mode 100644 index 0000000..2fa34c1 --- /dev/null +++ b/src/cmd_se_generate.rs @@ -0,0 +1,73 @@ +use crate::pkiutil::bytes_to_pem; +use crate::seutil; +use clap::{App, Arg, ArgMatches, SubCommand}; +use rust_util::util_clap::{Command, CommandError}; +use rust_util::util_msg; +use std::collections::BTreeMap; + +pub struct CommandImpl; + +impl Command for CommandImpl { + fn name(&self) -> &str { + "se-generate" + } + + fn subcommand<'a>(&self) -> App<'a, 'a> { + SubCommand::with_name(self.name()) + .about("Secure Enclave subcommand") + .arg( + Arg::with_name("type") + .long("type") + .required(true) + .takes_value(true) + .help("Type signing or key_agreement"), + ) + .arg( + Arg::with_name("host") + .long("host") + .required(false) + .takes_value(true) + .help("Host name"), + ) + .arg(Arg::with_name("json").long("json").help("JSON output")) + } + + fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError { + if !seutil::is_support_se() { + return simple_error!("Secure Enclave is NOT supported."); + } + let ty = sub_arg_matches.value_of("type").unwrap(); + let host = sub_arg_matches.value_of("host").unwrap_or("macbook"); + let json_output = sub_arg_matches.is_present("json"); + if json_output { + util_msg::set_logger_std_out(false); + } + + let sign = match ty { + "signing" | "ecsign" | "sign" => true, + "key_agreement" | "ecdh" | "dh" => false, + _ => return simple_error!("Invalie type: {}", ty), + }; + + let (public_key_point, public_key_der, private_key) = + seutil::generate_secure_enclave_p256_keypair(sign)?; + + let public_key_point_hex = hex::encode(&public_key_point); + let public_key_pem = bytes_to_pem("PUBLIC KEY", &*public_key_der); + let key = format!("key://{}:se/p256:{}:{}", + host, iff!(sign, "signing", "key_agreement"), private_key, + ); + if json_output { + let mut json = BTreeMap::<&'_ str, String>::new(); + json.insert("public_key_point", public_key_point_hex); + json.insert("public_key_pem", public_key_pem); + json.insert("key", key); + } else { + success!("Public key(point): {}", public_key_point_hex); + success!("Public key PEM: {}", public_key_pem); + success!("Key: {}", key); + } + + Ok(None) + } +} diff --git a/src/main.rs b/src/main.rs index db31526..94de46b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,56 +4,62 @@ extern crate rust_util; use clap::{App, AppSettings, ArgMatches}; use rust_util::util_clap::{Command, CommandError}; -mod util; -mod sshutil; -mod fido; -mod digest; -mod pivutil; -mod rsautil; -mod pkiutil; -mod hmacutil; -mod ecdsautil; mod argsutil; -mod pgpcardutil; -mod cmd_list; -mod cmd_u2fregister; -mod cmd_u2fsign; -mod cmd_rsaencrypt; -mod cmd_rsadecrypt; -mod cmd_rsaverify; -#[cfg(feature = "with-sequoia-openpgp")] -mod cmd_pgp; -mod cmd_pgpcardadmin; -mod cmd_pgpcardlist; -mod cmd_pgpcardsign; -mod cmd_pgpcarddecrypt; -#[cfg(feature = "with-sequoia-openpgp")] -mod cmd_pgpcardmake; -mod cmd_piv; -mod cmd_pivsummary; -mod cmd_pivmeta; -mod cmd_pivverify; -mod cmd_pivrsasign; -mod cmd_pivecdh; -mod cmd_pivecsign; -mod cmd_pivdecrypt; -mod cmd_pivgenerate; -mod cmd_hmac_sha1; mod cmd_chall; mod cmd_challconfig; -mod cmd_sshagent; -mod cmd_sshparsesign; -mod cmd_sshpivsign; -mod cmd_sshpivcert; -mod cmd_sshpubkey; -mod cmd_sshparse; +mod cmd_hmac_sha1; +mod cmd_list; +#[cfg(feature = "with-sequoia-openpgp")] +mod cmd_pgp; mod cmd_pgpageaddress; -mod cmd_signjwt; +mod cmd_pgpcardadmin; +mod cmd_pgpcarddecrypt; +mod cmd_pgpcardlist; +#[cfg(feature = "with-sequoia-openpgp")] +mod cmd_pgpcardmake; +mod cmd_pgpcardsign; +mod cmd_piv; +mod cmd_pivdecrypt; +mod cmd_pivecdh; +mod cmd_pivecsign; +mod cmd_pivgenerate; +mod cmd_pivmeta; +mod cmd_pivrsasign; +mod cmd_pivsummary; +mod cmd_pivverify; +mod cmd_rsadecrypt; +mod cmd_rsaencrypt; +mod cmd_rsaverify; +#[cfg(feature = "with-secure-enclave")] +mod cmd_se; +#[cfg(feature = "with-secure-enclave")] +mod cmd_se_generate; mod cmd_signfile; +mod cmd_signjwt; +mod cmd_sshagent; +mod cmd_sshparse; +mod cmd_sshparsesign; +mod cmd_sshpivcert; +mod cmd_sshpivsign; +mod cmd_sshpubkey; +mod cmd_u2fregister; +mod cmd_u2fsign; mod cmd_verifyfile; -mod signfile; +mod digest; mod ecdhutil; +mod ecdsautil; +mod fido; +mod hmacutil; +mod pgpcardutil; mod pinutil; +mod pivutil; +mod pkiutil; +mod rsautil; +#[cfg(feature = "with-secure-enclave")] +mod seutil; +mod signfile; +mod sshutil; +mod util; pub struct DefaultCommandImpl; @@ -117,11 +123,17 @@ fn inner_main() -> CommandError { Box::new(cmd_signjwt::CommandImpl), Box::new(cmd_signfile::CommandImpl), Box::new(cmd_verifyfile::CommandImpl), + #[cfg(feature = "with-secure-enclave")] + Box::new(cmd_se::CommandImpl), + #[cfg(feature = "with-secure-enclave")] + Box::new(cmd_se_generate::CommandImpl), ]; let mut features: Vec<&str> = vec![]; #[cfg(feature = "with-sequoia-openpgp")] - features.push("with-sequoia-openpgp"); + features.push("sequoia-openpgp"); + #[cfg(feature = "with-secure-enclave")] + features.push("secure-enclave"); let about = format!( "{}, features: [{}]", "Card Cli is a command tool for WebAuthn, OpenPGP, YubiKey ... smart cards", @@ -144,4 +156,4 @@ fn inner_main() -> CommandError { } } DefaultCommandImpl::run(&matches) -} \ No newline at end of file +} diff --git a/src/seutil.rs b/src/seutil.rs new file mode 100644 index 0000000..ebd7533 --- /dev/null +++ b/src/seutil.rs @@ -0,0 +1,46 @@ +use crate::util::base64_decode; +use rust_util::XResult; +use swift_rs::swift; +use swift_rs::{Bool, SRString}; + +swift!(fn is_support_secure_enclave() -> Bool); +swift!(fn generate_secure_enclave_p256_ecdh_keypair() -> SRString); +swift!(fn generate_secure_enclave_p256_ecsign_keypair() -> SRString); +swift!(fn compute_secure_enclave_p256_ecdh(private_key_base64: SRString, ephemera_public_key_base64: SRString) -> SRString); + +pub fn is_support_se() -> bool { + unsafe { is_support_secure_enclave() } +} + +pub fn generate_secure_enclave_p256_keypair(sign: bool) -> XResult<(Vec, Vec, String)> { + let p256_keypair_result = if sign { + unsafe { generate_secure_enclave_p256_ecsign_keypair() } + } else { + unsafe { generate_secure_enclave_p256_ecdh_keypair() } + }; + let p256_keypair_result_str = p256_keypair_result.as_str(); + if !p256_keypair_result_str.starts_with("ok:") { + return simple_error!( + "Generate P256 in secure enclave failed: {}", + p256_keypair_result_str + ); + } + let public_key_and_private_key = p256_keypair_result_str.chars().skip(3).collect::(); + let public_key_and_private_keys = public_key_and_private_key.split(',').collect::>(); + if public_key_and_private_keys.len() != 3 { + return simple_error!( + "Generate P256 in secure enclave result is bad: {}", + public_key_and_private_key + ); + } + let public_key_point = opt_result!( + base64_decode(public_key_and_private_keys[0]), + "Public key point is not base64 encoded: {}" + ); + let public_key_der = opt_result!( + base64_decode(public_key_and_private_keys[1]), + "Public key der is not base64 encoded: {}" + ); + let private_key = public_key_and_private_keys[2].to_string(); + Ok((public_key_point, public_key_der, private_key)) +} diff --git a/swift-lib/Package.swift b/swift-lib/Package.swift new file mode 100644 index 0000000..997dad6 --- /dev/null +++ b/swift-lib/Package.swift @@ -0,0 +1,30 @@ +// swift-tools-version:5.3 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "swift-lib", + platforms: [ + .macOS(.v11), // macOS Catalina. Earliest version that is officially supported by Apple. + ], + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "swift-lib", + type: .static, + targets: ["swift-lib"]), + ], + dependencies: [ + // Dependencies declare other packages that this package depends on. + .package(name: "SwiftRs", path: "../swift-rs") + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "swift-lib", + dependencies: [.product(name: "SwiftRs", package: "SwiftRs")], + path: "src") + ] +) \ No newline at end of file diff --git a/swift-lib/src/lib.swift b/swift-lib/src/lib.swift new file mode 100644 index 0000000..0c4fa23 --- /dev/null +++ b/swift-lib/src/lib.swift @@ -0,0 +1,84 @@ +import SwiftRs +import CryptoKit +import LocalAuthentication + +// reference: +// https://zenn.dev/iceman/scraps/380f69137c7ea2 +// https://www.andyibanez.com/posts/cryptokit-secure-enclave/ +@_cdecl("is_support_secure_enclave") +func isSupportSecureEnclave() -> Bool { + return SecureEnclave.isAvailable +} + +@_cdecl("generate_secure_enclave_p256_ecdh_keypair") +func generateSecureEnclaveP256KeyPairEcdh() -> SRString { + return generateSecureEnclaveP256KeyPair(sign: false); +} + +@_cdecl("generate_secure_enclave_p256_ecsign_keypair") +func generateSecureEnclaveP256KeyPairEcsign() -> SRString { + return generateSecureEnclaveP256KeyPair(sign: true); +} + +func generateSecureEnclaveP256KeyPair(sign: Bool) -> SRString { + var error: Unmanaged? = nil; + guard let accessCtrl = SecAccessControlCreateWithFlags( + nil, + kSecAttrAccessibleWhenUnlockedThisDeviceOnly, + [.privateKeyUsage, .biometryCurrentSet], + &error + ) else { + return SRString("err:\(error.debugDescription)") + } + do { + if (sign) { + let privateKeyReference = try SecureEnclave.P256.Signing.PrivateKey.init( + accessControl: accessCtrl + ); + let publicKeyBase64 = privateKeyReference.publicKey.x963Representation.base64EncodedString() + let publicKeyPem = privateKeyReference.publicKey.derRepresentation.base64EncodedString() + let dataRepresentationBase64 = privateKeyReference.dataRepresentation.base64EncodedString() + return SRString("ok:\(publicKeyBase64),\(publicKeyPem),\(dataRepresentationBase64)") + } else { + let privateKeyReference = try SecureEnclave.P256.KeyAgreement.PrivateKey.init( + accessControl: accessCtrl + ); + let publicKeyBase64 = privateKeyReference.publicKey.x963Representation.base64EncodedString() + let publicKeyPem = privateKeyReference.publicKey.derRepresentation.base64EncodedString() + let dataRepresentationBase64 = privateKeyReference.dataRepresentation.base64EncodedString() + return SRString("ok:\(publicKeyBase64),\(publicKeyPem),\(dataRepresentationBase64)") + } + } catch { + return SRString("err:\(error)") + } +} + +@_cdecl("compute_secure_enclave_p256_ecdh") +func computeSecureEnclaveP256Ecdh(privateKeyDataRepresentation: SRString, ephemeraPublicKey: SRString) -> SRString { + guard let privateKeyDataRepresentation = Data( + base64Encoded: privateKeyDataRepresentation.toString() + ) else { + return SRString("err:private key base64 decode failed") + } + guard let ephemeralPublicKeyRepresentation = Data( + base64Encoded: ephemeraPublicKey.toString() + ) else { + return SRString("err:ephemeral public key base64 decode failed") + } + do { + let context = LAContext(); + let p = try SecureEnclave.P256.KeyAgreement.PrivateKey( + dataRepresentation: privateKeyDataRepresentation, + authenticationContext: context + ) + + let ephemeralPublicKey = try P256.KeyAgreement.PublicKey.init(derRepresentation: ephemeralPublicKeyRepresentation) + + let sharedSecret = try p.sharedSecretFromKeyAgreement( + with: ephemeralPublicKey) + + return SRString("ok:\(sharedSecret.description)") + } catch { + return SRString("err:\(error)") + } +} \ No newline at end of file diff --git a/swift-rs/Cargo.lock b/swift-rs/Cargo.lock new file mode 100644 index 0000000..bd75097 --- /dev/null +++ b/swift-rs/Cargo.lock @@ -0,0 +1,417 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.164" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro2" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "serde_json" +version = "1.0.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serial_test" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c789ec87f4687d022a2405cf46e0cd6284889f1839de292cadeb6c6019506f2" +dependencies = [ + "dashmap", + "futures", + "lazy_static", + "log", + "parking_lot", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b64f9e531ce97c88b4778aad0ceee079216071cffec6ac9b904277f8f92e7fe3" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "swift-rs-hatter-fork" +version = "1.0.6" +dependencies = [ + "base64", + "serde", + "serde_json", + "serial_test", +] + +[[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.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/swift-rs/Cargo.toml b/swift-rs/Cargo.toml new file mode 100644 index 0000000..a466285 --- /dev/null +++ b/swift-rs/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "swift-rs" +version = "1.0.6" +description = "Call Swift from Rust with ease!" +authors = ["The swift-rs contributors"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/Brendonovich/swift-rs" +edition = "2021" +exclude=["/src-swift", "*.swift"] +build = "src-rs/test-build.rs" + +# /bin/sh RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features +[package.metadata."docs.rs"] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[lib] +path = "src-rs/lib.rs" + +[dependencies] +base64 = "0.22" +serde = { version = "1.0", features = ["derive"], optional = true} +serde_json = { version = "1.0", optional = true } + +[build-dependencies] +serde = { version = "1.0", features = ["derive"]} +serde_json = { version = "1.0" } + +[dev-dependencies] +serial_test = "0.10" + +[features] +default = [] +build = ["serde", "serde_json"] diff --git a/swift-rs/LICENSE-APACHE b/swift-rs/LICENSE-APACHE new file mode 100644 index 0000000..6e7334b --- /dev/null +++ b/swift-rs/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2023 The swift-rs developers + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/swift-rs/LICENSE-MIT b/swift-rs/LICENSE-MIT new file mode 100644 index 0000000..dd89749 --- /dev/null +++ b/swift-rs/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) 2023 The swift-rs Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/swift-rs/Package.swift b/swift-rs/Package.swift new file mode 100644 index 0000000..eb1c07d --- /dev/null +++ b/swift-rs/Package.swift @@ -0,0 +1,30 @@ +// swift-tools-version:5.3 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "SwiftRs", + platforms: [ + .macOS(.v10_13), + .iOS(.v11), + ], + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "SwiftRs", + targets: ["SwiftRs"]), + ], + dependencies: [ + // Dependencies declare other packages that this package depends on. + // .package(url: /* package url */, from: "1.0.0"), + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "SwiftRs", + dependencies: [], + path: "src-swift") + ] +) diff --git a/swift-rs/README.md b/swift-rs/README.md new file mode 100644 index 0000000..2e83a2d --- /dev/null +++ b/swift-rs/README.md @@ -0,0 +1,483 @@ +# swift-rs + +![Crates.io](https://img.shields.io/crates/v/swift-rs?color=blue&style=flat-square) +![docs.rs](https://img.shields.io/docsrs/swift-rs?color=blue&style=flat-square) + +Call Swift functions from Rust with ease! + +## Setup + +Add `swift-rs` to your project's `dependencies` and `build-dependencies`: + +```toml +[dependencies] +swift-rs = "1.0.5" + +[build-dependencies] +swift-rs = { version = "1.0.5", features = ["build"] } +``` + +Next, some setup work must be done: + +1. Ensure your swift code is organized into a Swift Package. +This can be done in XCode by selecting File -> New -> Project -> Multiplatform -> Swift Package and importing your existing code. +2. Add `SwiftRs` as a dependency to your Swift package and make the build type `.static`. +```swift +let package = Package( + dependencies: [ + .package(url: "https://github.com/Brendonovich/swift-rs", from: "1.0.5") + ], + products: [ + .library( + type: .static, + ), + ], + targets: [ + .target( + // Must specify swift-rs as a dependency of your target + dependencies: [ + .product( + name: "SwiftRs", + package: "swift-rs" + ) + ], + ) + ] +) +``` +3. Create a `build.rs` file in your project's root folder, if you don't have one already. +4. Use `SwiftLinker` in your `build.rs` file to link both the Swift runtime and your Swift package. +The package name should be the same as is specified in your `Package.swift` file, +and the path should point to your Swift project's root folder relative to your crate's root folder. + +```rust +use swift_rs::SwiftLinker; + +fn build() { + // swift-rs has a minimum of macOS 10.13 + // Ensure the same minimum supported macOS version is specified as in your `Package.swift` file. + SwiftLinker::new("10.13") + // Only if you are also targetting iOS + // Ensure the same minimum supported iOS version is specified as in your `Package.swift` file + .with_ios("11") + .with_package(PACKAGE_NAME, PACKAGE_PATH) + .link(); + + // Other build steps +} +``` + +With those steps completed, you should be ready to start using Swift code from Rust! + +If you experience the error `dyld[16008]: Library not loaded: @rpath/libswiftCore.dylib` +when using `swift-rs` with [Tauri](https://tauri.app) ensure you have set your +[Tauri minimum system version](https://tauri.app/v1/guides/building/macos#setting-a-minimum-system-version) +to `10.15` or higher in your `tauri.config.json`. + +## Calling basic functions + +To allow calling a Swift function from Rust, it must follow some rules: + +1. It must be global +2. It must be annotated with `@_cdecl`, so that it is callable from C +3. It must only use types that can be represented in Objective-C, +so only classes that derive `NSObject`, as well as scalars such as Int and Bool. +This excludes strings, arrays, generics (though all of these can be sent with workarounds) +and structs (which are strictly forbidden). + +For this example we will use a function that simply squares a number: + +```swift +public func squareNumber(number: Int) -> Int { + return number * number +} +``` + +So far, this function meets requirements 1 and 3: it is global and public, and only uses the Int type, which is Objective-C compatible. +However, it is not annotated with `@_cdecl`. +To fix this, we must call `@_cdecl` before the function's declaration and specify the name that the function is exposed to Rust with as its only argument. +To keep with Rust's naming conventions, we will export this function in snake case as `square_number`. + +```swift +@_cdecl("square_number") +public func squareNumber(number: Int) -> Int { + return number * number +} +``` + +Now that `squareNumber` is properly exposed to Rust, we can start interfacing with it. +This can be done using the `swift!` macro, with the `Int` type helping to provide a similar function signature: + +```rust +use swift_rs::swift; + +swift!(fn square_number(number: Int) -> Int); +``` + +Lastly, you can call the function from regular Rust functions. +Note that all calls to a Swift function are unsafe, +and require wrapping in an `unsafe {}` block or `unsafe fn`. + +```rust +fn main() { + let input: Int = 4; + let output = unsafe { square_number(input) }; + + println!("Input: {}, Squared: {}", input, output); + // Prints "Input: 4, Squared: 16" +} +``` + +Check [the documentation](TODO) for all available helper types. + +## Returning objects from Swift + +Let's say that we want our `squareNumber` function to return not only the result, but also the original input. +A standard way to do this in Swift would be with a struct: + +```swift +struct SquareNumberResult { + var input: Int + var output: Int +} +``` + +We are not allowed to do this, though, since structs cannot be represented in Objective-C. +Instead, we must use a class that extends `NSObject`: + +```swift +class SquareNumberResult: NSObject { + var input: Int + var output: Int + + init(_ input: Int, _ output: Int) { + self.input = input; + self.output = output + } +} +``` + +Yes, this class could contain the squaring logic too, but that is irrelevant for this example + +An instance of this class can then be returned from `squareNumber`: + +```swift +@_cdecl("square_number") +public func squareNumber(input: Int) -> SquareNumberResult { + let output = input * input + return SquareNumberResult(input, output) +} +``` + +As you can see, returning an `NSObject` from Swift isn't too difficult. +The same can't be said for the Rust implementation, though. +`squareNumber` doesn't actually return a struct containing `input` and `output`, +but instead a pointer to a `SquareNumberResult` stored somewhere in memory. +Additionally, this value contains more data than just `input` and `output`: +Since it is an `NSObject`, it contains extra data that must be accounted for when using it in Rust. + +This may sound daunting, but it's not actually a problem thanks to `SRObject`. +This type manages the pointer internally, and takes a generic argument for a struct that we can access the data through. +Let's see how we'd implement `SquareNumberResult` in Rust: + +```rust +use swift_rs::{swift, Int, SRObject}; + +// Any struct that is used in a C function must be annotated +// with this, and since our Swift function is exposed as a +// C function with @_cdecl, this is necessary here +#[repr(C)] +// Struct matches the class declaration in Swift +struct SquareNumberResult { + input: Int, + output: Int +} + +// SRObject abstracts away the underlying pointer and will automatically deref to +// &SquareNumberResult through the Deref trait +swift!(fn square_number(input: Int) -> SRObject); +``` + +Then, using the new return value is just like using `SquareNumberResult` directly: + +```rust +fn main() { + let input = 4; + let result = unsafe { square_number(input) }; + + let result_input = result.input; // 4 + let result_output = result.output; // 16 +} +``` + +Creating objects in Rust and then passing them to Swift is not supported. + +## Optionals + +`swift-rs` also supports Swift's `nil` type, but only for functions that return optional `NSObject`s. +Functions returning optional primitives cannot be represented in Objective C, and thus are not supported. + +Let's say we have a function returning an optional `SRString`: + +```swift +@_cdecl("optional_string") +func optionalString(returnNil: Bool) -> SRString? { + if (returnNil) return nil + else return SRString("lorem ipsum") +} +``` + +Thanks to Rust's [null pointer optimisation](https://doc.rust-lang.org/std/option/index.html#representation), +the optional nature of `SRString?` can be represented by wrapping `SRString` in Rust's `Option` type! + +```rust +use swift_rs::{swift, Bool, SRString}; + +swift!(optional_string(return_nil: Bool) -> Option) +``` + +Null pointers are actually the reason why a function that returns an optional primitive cannot be represented in C. +If this were to be supported, how could a `nil` be differentiated from a number? It can't! + +## Complex types + +So far we have only looked at using primitive types and structs/classes, +but this leaves out some of the most important data structures: arrays (`SRArray`) and strings (`SRString`). +These types must be treated with caution, however, and are not as flexible as their native Swift & Rust counterparts. + +### Strings + +Strings can be passed between Rust and Swift through `SRString`, which can be created from native strings in either language. + +**As an argument** + +```swift +import SwiftRs + +@_cdecl("swift_print") +public func swiftPrint(value: SRString) { + // .to_string() converts the SRString to a Swift String + print(value.to_string()) +} +``` + +```rust +use swift_rs::{swift, SRString, SwiftRef}; + +swift!(fn swift_print(value: &SRString)); + +fn main() { + // SRString can be created by simply calling .into() on any string reference. + // This will allocate memory in Swift and copy the string + let value: SRString = "lorem ipsum".into(); + + unsafe { swift_print(&value) }; // Will print "lorem ipsum" to the console +} +``` + +**As a return value** + +```swift +import SwiftRs + +@_cdecl("get_string") +public func getString() -> SRString { + let value = "lorem ipsum" + + // SRString can be created from a regular String + return SRString(value) +} +``` + +```rust +use swift_rs::{swift, SRString}; + +swift!(fn get_string() -> SRString); + +fn main() { + let value_srstring = unsafe { get_string() }; + + // SRString can be converted to an &str using as_str()... + let value_str: &str = value_srstring.as_str(); + // or though the Deref trait + let value_str: &str = &*value_srstring; + + // SRString also implements Display + println!("{}", value_srstring); // Will print "lorem ipsum" to the console +} +``` + +### Arrays + +**Primitive Arrays** + +Representing arrays properly is tricky, since we cannot use generics as Swift arguments or return values according to rule 3. +Instead, `swift-rs` provides a generic `SRArray` that can be embedded inside another class that extends `NSObject` that is not generic, +but is restricted to a single element type. + +```swift +import SwiftRs + +// Argument/Return values can contain generic types, but cannot be generic themselves. +// This includes extending generic types. +class IntArray: NSObject { + var data: SRArray + + init(_ data: [Int]) { + self.data = SRArray(data) + } +} + +@_cdecl("get_numbers") +public func getNumbers() -> IntArray { + let numbers = [1, 2, 3, 4] + + return IntArray(numbers) +} +``` + +```rust +use swift_rs::{Int, SRArray, SRObject}; + +#[repr(C)] +struct IntArray { + data: SRArray +} + +// Since IntArray extends NSObject in its Swift implementation, +// it must be wrapped in SRObject on the Rust side +swift!(fn get_numbers() -> SRObject); + +fn main() { + let numbers = unsafe { get_numbers() }; + + // SRArray can be accessed as a slice via as_slice + let numbers_slice: &[Int] = numbers.data.as_slice(); + + assert_eq!(numbers_slice, &[1, 2, 3, 4]); +} +``` + +To simplify things on the rust side, we can actually do away with the `IntArray` struct. +Since `IntArray` only has one field, its memory layout is identical to that of `SRArray`, +so our Rust implementation can be simplified at the cost of equivalence with our Swift code: + +```rust +// We still need to wrap the array in SRObject since +// the wrapper class in Swift is an NSObject +swift!(fn get_numbers() -> SRObject>); +``` + +**NSObject Arrays** + +What if we want to return an `NSObject` array? There are two options on the Swift side: + +1. Continue using `SRArray` and a custom wrapper type, or +2. Use `SRObjectArray`, a wrapper type provided by `swift-rs` that accepts any `NSObject` as its elements. +This can be easier than continuing to create wrapper types, but sacrifices some type safety. + +There is also `SRObjectArray` for Rust, which is compatible with any single-element Swift wrapper type (and of course `SRObjectArray` in Swift), +and automatically wraps its elements in `SRObject`, so there's very little reason to not use it unless you _really_ like custom wrapper types. + +Using `SRObjectArray` in both Swift and Rust with a basic custom class/struct can be done like this: + +```swift +import SwiftRs + +class IntTuple: NSObject { + var item1: Int + var item2: Int + + init(_ item1: Int, _ item2: Int) { + self.item1 = item1 + self.item2 = item2 + } +} + +@_cdecl("get_tuples") +public func getTuples() -> SRObjectArray { + let tuple1 = IntTuple(0,1), + tuple2 = IntTuple(2,3), + tuple3 = IntTuple(4,5) + + let tupleArray: [IntTuple] = [ + tuple1, + tuple2, + tuple3 + ] + + // Type safety is only lost when the Swift array is converted to an SRObjectArray + return SRObjectArray(tupleArray) +} +``` + +```rust +use swift_rs::{swift, Int, SRObjectArray}; + +#[repr(C)] +struct IntTuple { + item1: Int, + item2: Int +} + +// No need to wrap IntTuple in SRObject since +// SRObjectArray does it automatically +swift!(fn get_tuples() -> SRObjectArray); + +fn main() { + let tuples = unsafe { get_tuples() }; + + for tuple in tuples.as_slice() { + // Will print each tuple's contents to the console + println!("Item 1: {}, Item 2: {}", tuple.item1, tuple.item2); + } +} +``` + +Complex types can contain whatever combination of primitives and `SRObject` you like, just remember to follow the 3 rules! + +## Bonuses + +### SRData + +A wrapper type for `SRArray` designed for storing `u8`s - essentially just a byte buffer. + +### Tighter Memory Control with `autoreleasepool!` + +If you've come to Swift from an Objective-C background, you likely know the utility of `@autoreleasepool` blocks. +`swift-rs` has your back on this too, just wrap your block of code with a `autoreleasepool!`, and that block of code now executes with its own autorelease pool! + +```rust +use swift_rs::autoreleasepool; + +for _ in 0..10000 { + autoreleasepool!({ + // do some memory intensive thing here + }); +} +``` + +## Limitations + +Currently, the only types that can be created from Rust are number types, boolean, `SRString`, and `SRData`. +This is because those types are easy to allocate memory for, either on the stack or on the heap via calling out to swift, +whereas other types are not. This may be implemented in the future, though. + +Mutating values across Swift and Rust is not currently an aim for this library, it is purely for providing arguments and returning values. +Besides, this would go against Rust's programming model, potentially allowing for multiple shared references to a value instead of interior mutability via something like a Mutex. + +## License + +Licensed under either of + + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the Apache-2.0 +license, shall be dual licensed as above, without any additional terms or +conditions. diff --git a/swift-rs/src-rs/autorelease.rs b/swift-rs/src-rs/autorelease.rs new file mode 100644 index 0000000..bb1007d --- /dev/null +++ b/swift-rs/src-rs/autorelease.rs @@ -0,0 +1,26 @@ +/// Run code with its own autorelease pool. Semantically, this is identical +/// to [`@autoreleasepool`](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html) +/// in Objective-C +/// +/// +/// ```no_run +/// use swift_rs::autoreleasepool; +/// +/// autoreleasepool!({ +/// // do something memory intensive stuff +/// }) +/// ``` +#[macro_export] +macro_rules! autoreleasepool { + ( $expr:expr ) => {{ + extern "C" { + fn objc_autoreleasePoolPush() -> *mut std::ffi::c_void; + fn objc_autoreleasePoolPop(context: *mut std::ffi::c_void); + } + + let pool = unsafe { objc_autoreleasePoolPush() }; + let r = { $expr }; + unsafe { objc_autoreleasePoolPop(pool) }; + r + }}; +} diff --git a/swift-rs/src-rs/build.rs b/swift-rs/src-rs/build.rs new file mode 100644 index 0000000..394b9eb --- /dev/null +++ b/swift-rs/src-rs/build.rs @@ -0,0 +1,326 @@ +#![allow(dead_code)] +use std::{env, fmt::Display, path::Path, path::PathBuf, process::Command}; + +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct SwiftTarget { + triple: String, + unversioned_triple: String, + module_triple: String, + //pub swift_runtime_compatibility_version: String, + #[serde(rename = "librariesRequireRPath")] + libraries_require_rpath: bool, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct SwiftPaths { + runtime_library_paths: Vec, + runtime_library_import_paths: Vec, + runtime_resource_path: String, +} + +#[derive(Deserialize)] +struct SwiftEnv { + target: SwiftTarget, + paths: SwiftPaths, +} + +impl SwiftEnv { + fn new(minimum_macos_version: &str, minimum_ios_version: Option<&str>) -> Self { + let rust_target = RustTarget::from_env(); + let target = rust_target.swift_target_triple(minimum_macos_version, minimum_ios_version); + + let swift_target_info_str = Command::new("swift") + .args(["-target", &target, "-print-target-info"]) + .output() + .unwrap() + .stdout; + + serde_json::from_slice(&swift_target_info_str).unwrap() + } +} + +#[allow(clippy::upper_case_acronyms)] +enum RustTargetOS { + MacOS, + IOS, +} + +impl RustTargetOS { + fn from_env() -> Self { + match env::var("CARGO_CFG_TARGET_OS").unwrap().as_str() { + "macos" => RustTargetOS::MacOS, + "ios" => RustTargetOS::IOS, + _ => panic!("unexpected target operating system"), + } + } + + fn to_swift(&self) -> &'static str { + match self { + Self::MacOS => "macosx", + Self::IOS => "ios", + } + } +} + +impl Display for RustTargetOS { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::MacOS => write!(f, "macos"), + Self::IOS => write!(f, "ios"), + } + } +} + +#[allow(clippy::upper_case_acronyms)] +enum SwiftSDK { + MacOS, + IOS, + IOSSimulator, +} + +impl SwiftSDK { + fn from_os(os: &RustTargetOS) -> Self { + let target = env::var("TARGET").unwrap(); + let simulator = target.ends_with("ios-sim") + || (target.starts_with("x86_64") && target.ends_with("ios")); + + match os { + RustTargetOS::MacOS => Self::MacOS, + RustTargetOS::IOS if simulator => Self::IOSSimulator, + RustTargetOS::IOS => Self::IOS, + } + } + + fn clang_lib_extension(&self) -> &'static str { + match self { + Self::MacOS => "osx", + Self::IOS => "ios", + Self::IOSSimulator => "iossim", + } + } +} + +impl Display for SwiftSDK { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::MacOS => write!(f, "macosx"), + Self::IOSSimulator => write!(f, "iphonesimulator"), + Self::IOS => write!(f, "iphoneos"), + } + } +} + +struct RustTarget { + arch: String, + os: RustTargetOS, + sdk: SwiftSDK, +} + +impl RustTarget { + fn from_env() -> Self { + let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); + let os = RustTargetOS::from_env(); + let sdk = SwiftSDK::from_os(&os); + + Self { arch, os, sdk } + } + + fn swift_target_triple( + &self, + minimum_macos_version: &str, + minimum_ios_version: Option<&str>, + ) -> String { + let unversioned = self.unversioned_swift_target_triple(); + format!( + "{unversioned}{}{}", + match (&self.os, minimum_ios_version) { + (RustTargetOS::MacOS, _) => minimum_macos_version, + (RustTargetOS::IOS, Some(version)) => version, + _ => "", + }, + // simulator suffix + matches!(self.sdk, SwiftSDK::IOSSimulator) + .then(|| "-simulator".to_string()) + .unwrap_or_default() + ) + } + + fn unversioned_swift_target_triple(&self) -> String { + format!( + "{}-apple-{}", + match self.arch.as_str() { + "aarch64" => "arm64", + a => a, + }, + self.os.to_swift(), + ) + } +} + +struct SwiftPackage { + name: String, + path: PathBuf, +} + +/// Builder for linking the Swift runtime and custom packages. +#[cfg(feature = "build")] +pub struct SwiftLinker { + packages: Vec, + macos_min_version: String, + ios_min_version: Option, +} + +impl SwiftLinker { + /// Creates a new [`SwiftLinker`] with a minimum macOS verison. + /// + /// Minimum macOS version must be at least 10.13. + pub fn new(macos_min_version: &str) -> Self { + Self { + packages: vec![], + macos_min_version: macos_min_version.to_string(), + ios_min_version: None, + } + } + + /// Instructs the [`SwiftLinker`] to also compile for iOS + /// using the specified minimum iOS version. + /// + /// Minimum iOS version must be at least 11. + pub fn with_ios(mut self, min_version: &str) -> Self { + self.ios_min_version = Some(min_version.to_string()); + self + } + + /// Adds a package to be linked against. + /// `name` should match the `name` field in your `Package.swift`, + /// and `path` should point to the root of your Swift package relative + /// to your crate's root. + pub fn with_package(mut self, name: &str, path: impl AsRef) -> Self { + self.packages.extend([SwiftPackage { + name: name.to_string(), + path: path.as_ref().into(), + }]); + + self + } + + /// Links the Swift runtime, then builds and links the provided packages. + /// This does not (yet) automatically rebuild your Swift files when they are modified, + /// you'll need to modify/save your `build.rs` file for that. + pub fn link(self) { + let swift_env = SwiftEnv::new(&self.macos_min_version, self.ios_min_version.as_deref()); + + #[allow(clippy::uninlined_format_args)] + for path in swift_env.paths.runtime_library_paths { + println!("cargo:rustc-link-search=native={path}"); + } + + let debug = env::var("DEBUG").unwrap() == "true"; + let configuration = if debug { "debug" } else { "release" }; + let rust_target = RustTarget::from_env(); + + link_clang_rt(&rust_target); + + for package in self.packages { + let package_path = + Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()).join(&package.path); + let out_path = Path::new(&env::var("OUT_DIR").unwrap()) + .join("swift-rs") + .join(&package.name); + + let sdk_path_output = Command::new("xcrun") + .args(["--sdk", &rust_target.sdk.to_string(), "--show-sdk-path"]) + .output() + .unwrap(); + if !sdk_path_output.status.success() { + panic!( + "Failed to get SDK path with `xcrun --sdk {} --show-sdk-path`", + rust_target.sdk + ); + } + + let sdk_path = String::from_utf8_lossy(&sdk_path_output.stdout); + + let mut command = Command::new("swift"); + command.current_dir(&package.path); + + let arch = match std::env::consts::ARCH { + "aarch64" => "arm64", + arch => arch, + }; + + command + // Build the package (duh) + .args(["build"]) + // SDK path for regular compilation (idk) + .args(["--sdk", sdk_path.trim()]) + // Release/Debug configuration + .args(["-c", configuration]) + .args(["--arch", arch]) + // Where the artifacts will be generated to + .args(["--build-path", &out_path.display().to_string()]) + // Override SDK path for each swiftc instance. + // Necessary for iOS compilation. + .args(["-Xswiftc", "-sdk"]) + .args(["-Xswiftc", sdk_path.trim()]) + // Override target triple for each swiftc instance. + // Necessary for iOS compilation. + .args(["-Xswiftc", "-target"]) + .args([ + "-Xswiftc", + &rust_target.swift_target_triple( + &self.macos_min_version, + self.ios_min_version.as_deref(), + ), + ]); + + if !command.status().unwrap().success() { + panic!("Failed to compile swift package {}", package.name); + } + + let search_path = out_path + // swift build uses this output folder no matter what is the target + .join(format!( + "{}-apple-macosx", + arch + )) + .join(configuration); + + println!("cargo:rerun-if-changed={}", package_path.display()); + println!("cargo:rustc-link-search=native={}", search_path.display()); + println!("cargo:rustc-link-lib=static={}", package.name); + } + } +} + +fn link_clang_rt(rust_target: &RustTarget) { + println!( + "cargo:rustc-link-lib=clang_rt.{}", + rust_target.sdk.clang_lib_extension() + ); + println!("cargo:rustc-link-search={}", clang_link_search_path()); +} + +fn clang_link_search_path() -> String { + let output = std::process::Command::new( + std::env::var("SWIFT_RS_CLANG").unwrap_or_else(|_| "/usr/bin/clang".to_string()), + ) + .arg("--print-search-dirs") + .output() + .unwrap(); + if !output.status.success() { + panic!("Can't get search paths from clang"); + } + let stdout = String::from_utf8_lossy(&output.stdout); + for line in stdout.lines() { + if line.contains("libraries: =") { + let path = line.split('=').nth(1).unwrap(); + return format!("{}/lib/darwin", path); + } + } + panic!("clang is missing search paths"); +} diff --git a/swift-rs/src-rs/dark_magic.rs b/swift-rs/src-rs/dark_magic.rs new file mode 100644 index 0000000..7cb8c36 --- /dev/null +++ b/swift-rs/src-rs/dark_magic.rs @@ -0,0 +1,90 @@ +/// This retain-balancing algorithm is cool but likely isn't required. +/// I'm keeping it around in case it's necessary one day. + +// #[derive(Clone, Copy, Debug)] +// enum ValueArity { +// Reference, +// Value, +// } + +// pub unsafe fn balance_ptrs(args: Vec<(*const c_void, bool)>, ret: Vec<(*const c_void, bool)>) { +// fn collect_references( +// v: Vec<(*const c_void, bool)>, +// ) -> BTreeMap<*const c_void, Vec> { +// v.into_iter().fold( +// BTreeMap::<_, Vec>::new(), +// |mut map, (ptr, is_ref)| { +// map.entry(ptr).or_default().push(if is_ref { +// ValueArity::Reference +// } else { +// ValueArity::Value +// }); +// map +// }, +// ) +// } + +// let mut args = collect_references(args); +// let mut ret = collect_references(ret); + +// let both_counts = args +// .clone() +// .into_iter() +// .flat_map(|(arg, values)| { +// ret.remove(&arg).map(|ret| { +// args.remove(&arg); + +// let ret_values = ret +// .iter() +// .filter(|v| matches!(v, ValueArity::Value)) +// .count() as isize; + +// let arg_references = values +// .iter() +// .filter(|v| matches!(v, ValueArity::Reference)) +// .count() as isize; + +// let ref_in_value_out_retains = min(ret_values, arg_references); + +// (arg, ref_in_value_out_retains) +// }) +// }) +// .collect::>(); + +// let arg_counts = args.into_iter().map(|(ptr, values)| { +// let count = values +// .into_iter() +// .filter(|v| matches!(v, ValueArity::Value)) +// .count() as isize; +// (ptr, count) +// }); + +// let ret_counts = ret +// .into_iter() +// .map(|(ptr, values)| { +// let count = values +// .into_iter() +// .filter(|v| matches!(v, ValueArity::Value)) +// .count() as isize; +// (ptr, count) +// }) +// .collect::>(); + +// both_counts +// .into_iter() +// .chain(arg_counts) +// .chain(ret_counts) +// .for_each(|(ptr, count)| match count { +// 0 => {} +// n if n > 0 => { +// for _ in 0..n { +// retain_object(ptr) +// } +// } +// n => { +// for _ in n..0 { +// release_object(ptr) +// } +// } +// }); +// } diff --git a/swift-rs/src-rs/lib.rs b/swift-rs/src-rs/lib.rs new file mode 100644 index 0000000..9bbf1c6 --- /dev/null +++ b/swift-rs/src-rs/lib.rs @@ -0,0 +1,20 @@ +//! Call Swift functions from Rust with ease! +#![cfg_attr(docsrs, feature(doc_cfg))] + +mod autorelease; +mod swift; +mod swift_arg; +mod swift_ret; +mod types; + +// pub use autorelease::*; +pub use swift::*; +pub use swift_arg::*; +pub use swift_ret::*; +pub use types::*; + +#[cfg(feature = "build")] +#[cfg_attr(docsrs, doc(cfg(feature = "build")))] +mod build; +#[cfg(feature = "build")] +pub use build::*; diff --git a/swift-rs/src-rs/swift.rs b/swift-rs/src-rs/swift.rs new file mode 100644 index 0000000..0b4f370 --- /dev/null +++ b/swift-rs/src-rs/swift.rs @@ -0,0 +1,101 @@ +use std::ffi::c_void; + +use crate::*; + +/// Reference to an `NSObject` for internal use by [`swift!`]. +#[must_use = "A Ref MUST be sent over to the Swift side"] +#[repr(transparent)] +pub struct SwiftRef<'a, T: SwiftObject>(&'a SRObjectImpl); + +impl<'a, T: SwiftObject> SwiftRef<'a, T> { + pub(crate) unsafe fn retain(&self) { + retain_object(self.0 as *const _ as *const c_void) + } +} + +/// A type that is represented as an `NSObject` in Swift. +pub trait SwiftObject { + type Shape; + + /// Gets a reference to the `SRObject` at the root of a `SwiftObject` + fn get_object(&self) -> &SRObject; + + /// Creates a [`SwiftRef`] for an object which can be used when calling a Swift function. + /// This function should never be called manually, + /// instead you should rely on the [`swift!`] macro to call it for you. + /// + /// # Safety + /// This function converts the [`NonNull`](std::ptr::NonNull) + /// inside an [`SRObject`] into a reference, + /// implicitly assuming that the pointer is still valid. + /// The inner pointer is private, + /// and the returned [`SwiftRef`] is bound to the lifetime of the original [`SRObject`], + /// so if you use `swift-rs` as normal this function should be safe. + unsafe fn swift_ref(&self) -> SwiftRef + where + Self: Sized, + { + SwiftRef(self.get_object().0.as_ref()) + } + + /// Adds a retain to an object. + /// + /// # Safety + /// Just don't call this, let [`swift!`] handle it for you. + unsafe fn retain(&self) + where + Self: Sized, + { + self.swift_ref().retain() + } +} + +swift!(pub(crate) fn retain_object(obj: *const c_void)); +swift!(pub(crate) fn release_object(obj: *const c_void)); +swift!(pub(crate) fn data_from_bytes(data: *const u8, size: Int) -> SRData); +swift!(pub(crate) fn string_from_bytes(data: *const u8, size: Int) -> SRString); + +/// Declares a function defined in a swift library. +/// As long as this macro is used, retain counts of arguments +/// and return values will be correct. +/// +/// Use this macro as if the contents were going directly +/// into an `extern "C"` block. +/// +/// ``` +/// use swift_rs::*; +/// +/// swift!(fn echo(string: &SRString) -> SRString); +/// +/// let string: SRString = "test".into(); +/// let result = unsafe { echo(&string) }; +/// +/// assert_eq!(result.as_str(), string.as_str()) +/// ``` +/// +/// # Details +/// +/// Internally this macro creates a wrapping function around an `extern "C"` block +/// that represents the actual Swift function. This is done in order to restrict the types +/// that can be used as arguments and return types, and to ensure that retain counts of returned +/// values are appropriately balanced. +#[macro_export] +macro_rules! swift { + ($vis:vis fn $name:ident $(<$($lt:lifetime),+>)? ($($arg:ident: $arg_ty:ty),*) $(-> $ret:ty)?) => { + $vis unsafe fn $name $(<$($lt),*>)? ($($arg: $arg_ty),*) $(-> $ret)? { + extern "C" { + fn $name $(<$($lt),*>)? ($($arg: <$arg_ty as $crate::SwiftArg>::ArgType),*) $(-> $ret)?; + } + + let res = { + $(let $arg = $crate::SwiftArg::as_arg(&$arg);)* + + $name($($arg),*) + }; + + $crate::SwiftRet::retain(&res); + + res + } + }; +} diff --git a/swift-rs/src-rs/swift_arg.rs b/swift-rs/src-rs/swift_arg.rs new file mode 100644 index 0000000..689650e --- /dev/null +++ b/swift-rs/src-rs/swift_arg.rs @@ -0,0 +1,75 @@ +use std::ffi::c_void; + +use crate::{swift::SwiftObject, *}; + +/// Identifies a type as being a valid argument in a Swift function. +pub trait SwiftArg<'a> { + type ArgType; + + /// Creates a swift-compatible version of the argument. + /// For primitives this just returns `self`, + /// but for [`SwiftObject`] types it wraps them in [`SwiftRef`]. + /// + /// This function is called within the [`swift!`] macro. + /// + /// # Safety + /// + /// Creating a [`SwiftRef`] is inherently unsafe, + /// but is reliable if using the [`swift!`] macro, + /// so it is not advised to call this function manually. + unsafe fn as_arg(&'a self) -> Self::ArgType; +} + +macro_rules! primitive_impl { + ($($t:ty),+) => { + $(impl<'a> SwiftArg<'a> for $t { + type ArgType = $t; + + unsafe fn as_arg(&'a self) -> Self::ArgType { + *self + } + })+ + }; +} + +primitive_impl!( + Bool, + Int, + Int8, + Int16, + Int32, + Int64, + UInt, + UInt8, + UInt16, + UInt32, + UInt64, + Float32, + Float64, + *const c_void, + *mut c_void, + *const u8, + () +); + +macro_rules! ref_impl { + ($($t:ident $(<$($gen:ident),+>)?),+) => { + $(impl<'a $($(, $gen: 'a),+)?> SwiftArg<'a> for $t$(<$($gen),+>)? { + type ArgType = SwiftRef<'a, $t$(<$($gen),+>)?>; + + unsafe fn as_arg(&'a self) -> Self::ArgType { + self.swift_ref() + } + })+ + }; +} + +ref_impl!(SRObject, SRArray, SRData, SRString); + +impl<'a, T: SwiftArg<'a>> SwiftArg<'a> for &T { + type ArgType = T::ArgType; + + unsafe fn as_arg(&'a self) -> Self::ArgType { + (*self).as_arg() + } +} diff --git a/swift-rs/src-rs/swift_ret.rs b/swift-rs/src-rs/swift_ret.rs new file mode 100644 index 0000000..d853d12 --- /dev/null +++ b/swift-rs/src-rs/swift_ret.rs @@ -0,0 +1,55 @@ +use crate::{swift::SwiftObject, *}; +use std::ffi::c_void; + +/// Identifies a type as being a valid return type from a Swift function. +/// For types that are objects which need extra retains, +/// the [`retain`](SwiftRet::retain) function will be re-implemented. +pub trait SwiftRet { + /// Adds a retain to the value if possible + /// + /// # Safety + /// Just don't use this. + /// Let [`swift!`] handle it. + unsafe fn retain(&self) {} +} + +macro_rules! primitive_impl { + ($($t:ty),+) => { + $(impl SwiftRet for $t { + })+ + }; +} + +primitive_impl!( + Bool, + Int, + Int8, + Int16, + Int32, + Int64, + UInt, + UInt8, + UInt16, + UInt32, + UInt64, + Float32, + Float64, + *const c_void, + *mut c_void, + *const u8, + () +); + +impl SwiftRet for Option { + unsafe fn retain(&self) { + if let Some(v) = self { + v.retain() + } + } +} + +impl SwiftRet for T { + unsafe fn retain(&self) { + (*self).retain() + } +} diff --git a/swift-rs/src-rs/test-build.rs b/swift-rs/src-rs/test-build.rs new file mode 100644 index 0000000..da43c63 --- /dev/null +++ b/swift-rs/src-rs/test-build.rs @@ -0,0 +1,20 @@ +//! Build script for swift-rs that is a no-op for normal builds, but can be enabled +//! to include test swift library based on env var `TEST_SWIFT_RS=true` with the +//! `build` feature being enabled. + +#[cfg(feature = "build")] +mod build; + +fn main() { + println!("cargo:rerun-if-env-changed=TEST_SWIFT_RS"); + + #[cfg(feature = "build")] + if std::env::var("TEST_SWIFT_RS").unwrap_or_else(|_| "false".into()) == "true" { + use build::SwiftLinker; + + SwiftLinker::new("10.15") + .with_ios("11") + .with_package("test-swift", "tests/swift-pkg") + .link(); + } +} diff --git a/swift-rs/src-rs/types/array.rs b/swift-rs/src-rs/types/array.rs new file mode 100644 index 0000000..fc69069 --- /dev/null +++ b/swift-rs/src-rs/types/array.rs @@ -0,0 +1,110 @@ +use std::{ops::Deref, ptr::NonNull}; + +use crate::swift::SwiftObject; + +use super::SRObject; + +/// Wrapper of [`SRArray`] exclusively for arrays of objects. +/// Equivalent to `SRObjectArray` in Swift. +// SRArray is wrapped in SRObject since the Swift implementation extends NSObject +pub type SRObjectArray = SRObject>>; + +#[doc(hidden)] +#[repr(C)] +pub struct SRArrayImpl { + data: NonNull, + length: usize, +} + +/// General array type for objects and scalars. +/// +/// ## Returning Directly +/// +/// When returning an `SRArray` from a Swift function, +/// you will need to wrap it in an `NSObject` class since +/// Swift doesn't permit returning generic types from `@_cdecl` functions. +/// To account for the wrapping `NSObject`, the array must be wrapped +/// in `SRObject` on the Rust side. +/// +/// ```rust +/// use swift_rs::{swift, SRArray, SRObject, Int}; +/// +/// swift!(fn get_int_array() -> SRObject>); +/// +/// let array = unsafe { get_int_array() }; +/// +/// assert_eq!(array.as_slice(), &[1, 2, 3]) +/// ``` +/// [_corresponding Swift code_](https://github.com/Brendonovich/swift-rs/blob/07269e511f1afb71e2fcfa89ca5d7338bceb20e8/tests/swift-pkg/doctests.swift#L19) +/// +/// ## Returning in a Struct fIeld +/// +/// When returning an `SRArray` from a custom struct that is itself an `NSObject`, +/// the above work is already done for you. +/// Assuming your custom struct is already wrapped in `SRObject` in Rust, +/// `SRArray` will work normally. +/// +/// ```rust +/// use swift_rs::{swift, SRArray, SRObject, Int}; +/// +/// #[repr(C)] +/// struct ArrayStruct { +/// array: SRArray +/// } +/// +/// swift!(fn get_array_struct() -> SRObject); +/// +/// let data = unsafe { get_array_struct() }; +/// +/// assert_eq!(data.array.as_slice(), &[4, 5, 6]); +/// ``` +/// [_corresponding Swift code_](https://github.com/Brendonovich/swift-rs/blob/07269e511f1afb71e2fcfa89ca5d7338bceb20e8/tests/swift-pkg/doctests.swift#L32) +#[repr(transparent)] +pub struct SRArray(SRObject>); + +impl SRArray { + pub fn as_slice(&self) -> &[T] { + self.0.as_slice() + } +} + +impl SwiftObject for SRArray { + type Shape = SRArrayImpl; + + fn get_object(&self) -> &SRObject { + &self.0 + } +} + +impl Deref for SRArray { + type Target = [T]; + + fn deref(&self) -> &Self::Target { + self.0.as_slice() + } +} + +impl SRArrayImpl { + pub fn as_slice(&self) -> &[T] { + unsafe { std::slice::from_raw_parts(self.data.as_ref(), self.length) } + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for SRArray +where + T: serde::Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + use serde::ser::SerializeSeq; + + let mut seq = serializer.serialize_seq(Some(self.len()))?; + for item in self.iter() { + seq.serialize_element(item)?; + } + seq.end() + } +} diff --git a/swift-rs/src-rs/types/data.rs b/swift-rs/src-rs/types/data.rs new file mode 100644 index 0000000..e982235 --- /dev/null +++ b/swift-rs/src-rs/types/data.rs @@ -0,0 +1,75 @@ +use crate::{ + swift::{self, SwiftObject}, + Int, +}; + +use super::{array::SRArray, SRObject}; + +use std::ops::Deref; + +type Data = SRArray; + +/// Convenience type for working with byte buffers, +/// analagous to `SRData` in Swift. +/// +/// ```rust +/// use swift_rs::{swift, SRData}; +/// +/// swift!(fn get_data() -> SRData); +/// +/// let data = unsafe { get_data() }; +/// +/// assert_eq!(data.as_ref(), &[1, 2, 3]) +/// ``` +/// [_corresponding Swift code_](https://github.com/Brendonovich/swift-rs/blob/07269e511f1afb71e2fcfa89ca5d7338bceb20e8/tests/swift-pkg/doctests.swift#L68) +#[repr(transparent)] +pub struct SRData(SRObject); + +impl SRData { + /// + pub fn as_slice(&self) -> &[u8] { + self + } + + pub fn to_vec(&self) -> Vec { + self.as_slice().to_vec() + } +} + +impl SwiftObject for SRData { + type Shape = Data; + + fn get_object(&self) -> &SRObject { + &self.0 + } +} + +impl Deref for SRData { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl AsRef<[u8]> for SRData { + fn as_ref(&self) -> &[u8] { + self + } +} + +impl From<&[u8]> for SRData { + fn from(value: &[u8]) -> Self { + unsafe { swift::data_from_bytes(value.as_ptr(), value.len() as Int) } + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for SRData { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_bytes(self) + } +} diff --git a/swift-rs/src-rs/types/mod.rs b/swift-rs/src-rs/types/mod.rs new file mode 100644 index 0000000..90d9465 --- /dev/null +++ b/swift-rs/src-rs/types/mod.rs @@ -0,0 +1,11 @@ +mod array; +mod data; +mod object; +mod scalars; +mod string; + +pub use array::*; +pub use data::*; +pub use object::*; +pub use scalars::*; +pub use string::*; diff --git a/swift-rs/src-rs/types/object.rs b/swift-rs/src-rs/types/object.rs new file mode 100644 index 0000000..49748a7 --- /dev/null +++ b/swift-rs/src-rs/types/object.rs @@ -0,0 +1,75 @@ +use crate::swift::{self, SwiftObject}; +use std::{ffi::c_void, ops::Deref, ptr::NonNull}; + +#[doc(hidden)] +#[repr(C)] +pub struct SRObjectImpl { + _nsobject_offset: u8, + data: T, +} + +/// Wrapper for arbitrary `NSObject` types. +/// +/// When returning an `NSObject`, its Rust type must be wrapped in `SRObject`. +/// The type must also be annotated with `#[repr(C)]` to ensure its memory layout +/// is identical to its Swift counterpart's. +/// +/// ```rust +/// use swift_rs::{swift, SRObject, Int, Bool}; +/// +/// #[repr(C)] +/// struct CustomObject { +/// a: Int, +/// b: Bool +/// } +/// +/// swift!(fn get_custom_object() -> SRObject); +/// +/// let value = unsafe { get_custom_object() }; +/// +/// let reference: &CustomObject = value.as_ref(); +/// ``` +/// [_corresponding Swift code_](https://github.com/Brendonovich/swift-rs/blob/07269e511f1afb71e2fcfa89ca5d7338bceb20e8/tests/swift-pkg/doctests.swift#L49) +#[repr(transparent)] +pub struct SRObject(pub(crate) NonNull>); + +impl SwiftObject for SRObject { + type Shape = T; + + fn get_object(&self) -> &SRObject { + self + } +} + +impl Deref for SRObject { + type Target = T; + + fn deref(&self) -> &T { + unsafe { &self.0.as_ref().data } + } +} + +impl AsRef for SRObject { + fn as_ref(&self) -> &T { + self + } +} + +impl Drop for SRObject { + fn drop(&mut self) { + unsafe { swift::release_object(self.0.as_ref() as *const _ as *const c_void) } + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for SRObject +where + T: serde::Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.deref().serialize(serializer) + } +} diff --git a/swift-rs/src-rs/types/scalars.rs b/swift-rs/src-rs/types/scalars.rs new file mode 100644 index 0000000..226f3bd --- /dev/null +++ b/swift-rs/src-rs/types/scalars.rs @@ -0,0 +1,34 @@ +/// Swift's [`Bool`](https://developer.apple.com/documentation/swift/bool) type +pub type Bool = bool; + +/// Swift's [`Int`](https://developer.apple.com/documentation/swift/int) type +pub type Int = isize; +/// Swift's [`Int8`](https://developer.apple.com/documentation/swift/int8) type +pub type Int8 = i8; +/// Swift's [`Int16`](https://developer.apple.com/documentation/swift/int16) type +pub type Int16 = i16; +/// Swift's [`Int32`](https://developer.apple.com/documentation/swift/int32) type +pub type Int32 = i32; +/// Swift's [`Int64`](https://developer.apple.com/documentation/swift/int64) type +pub type Int64 = i64; + +/// Swift's [`UInt`](https://developer.apple.com/documentation/swift/uint) type +pub type UInt = usize; +/// Swift's [`UInt8`](https://developer.apple.com/documentation/swift/uint8) type +pub type UInt8 = u8; +/// Swift's [`UInt16`](https://developer.apple.com/documentation/swift/uint16) type +pub type UInt16 = u16; +/// Swift's [`UInt32`](https://developer.apple.com/documentation/swift/uint32) type +pub type UInt32 = u32; +/// Swift's [`UInt64`](https://developer.apple.com/documentation/swift/uint64) type +pub type UInt64 = u64; + +/// Swift's [`Float`](https://developer.apple.com/documentation/swift/float) type +pub type Float = f32; +/// Swift's [`Double`](https://developer.apple.com/documentation/swift/double) type +pub type Double = f64; + +/// Swift's [`Float32`](https://developer.apple.com/documentation/swift/float32) type +pub type Float32 = f32; +/// Swift's [`Float64`](https://developer.apple.com/documentation/swift/float64) type +pub type Float64 = f64; diff --git a/swift-rs/src-rs/types/string.rs b/swift-rs/src-rs/types/string.rs new file mode 100644 index 0000000..3f6f86f --- /dev/null +++ b/swift-rs/src-rs/types/string.rs @@ -0,0 +1,84 @@ +use std::{ + fmt::{Display, Error, Formatter}, + ops::Deref, +}; + +use crate::{ + swift::{self, SwiftObject}, + Int, SRData, SRObject, +}; + +/// String type that can be shared between Swift and Rust. +/// +/// ```rust +/// use swift_rs::{swift, SRString}; +/// +/// swift!(fn get_greeting(name: &SRString) -> SRString); +/// +/// let greeting = unsafe { get_greeting(&"Brendan".into()) }; +/// +/// assert_eq!(greeting.as_str(), "Hello Brendan!"); +/// ``` +/// [_corresponding Swift code_](https://github.com/Brendonovich/swift-rs/blob/07269e511f1afb71e2fcfa89ca5d7338bceb20e8/tests/swift-pkg/doctests.swift#L56) +#[repr(transparent)] +pub struct SRString(SRData); + +impl SRString { + pub fn as_str(&self) -> &str { + unsafe { std::str::from_utf8_unchecked(&self.0) } + } +} + +impl SwiftObject for SRString { + type Shape = ::Shape; + + fn get_object(&self) -> &SRObject { + self.0.get_object() + } +} + +impl Deref for SRString { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.as_str() + } +} + +impl AsRef<[u8]> for SRString { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl From<&str> for SRString { + fn from(string: &str) -> Self { + unsafe { swift::string_from_bytes(string.as_ptr(), string.len() as Int) } + } +} + +impl Display for SRString { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + self.as_str().fmt(f) + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for SRString { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} +#[cfg(feature = "serde")] +impl<'a> serde::Deserialize<'a> for SRString { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'a>, + { + let string = String::deserialize(deserializer)?; + Ok(SRString::from(string.as_str())) + } +} diff --git a/swift-rs/src-swift/lib.swift b/swift-rs/src-swift/lib.swift new file mode 100644 index 0000000..7bee3ad --- /dev/null +++ b/swift-rs/src-swift/lib.swift @@ -0,0 +1,94 @@ +import Foundation + +public class SRArray: NSObject { + // Used by Rust + let pointer: UnsafePointer + let length: Int; + + // Actual array, deallocates objects inside automatically + let array: [T]; + + public override init() { + self.array = []; + self.pointer = UnsafePointer(self.array); + self.length = 0; + } + + public init(_ data: [T]) { + self.array = data; + self.pointer = UnsafePointer(self.array) + self.length = data.count + } + + public func toArray() -> [T] { + return Array(self.array) + } +} + +public class SRObjectArray: NSObject { + let data: SRArray + + public init(_ data: [NSObject]) { + self.data = SRArray(data) + } +} + +public class SRData: NSObject { + let data: SRArray + + public override init() { + self.data = SRArray() + } + + public init(_ data: [UInt8]) { + self.data = SRArray(data) + } + + public init (_ srArray: SRArray) { + self.data = srArray + } + + public func toArray() -> [UInt8] { + return self.data.toArray() + } +} + +public class SRString: SRData { + public override init() { + super.init([]) + } + + public init(_ string: String) { + super.init(Array(string.utf8)) + } + + init(_ data: SRData) { + super.init(data.data) + } + + public func toString() -> String { + return String(bytes: self.data.array, encoding: .utf8)! + } +} + +@_cdecl("retain_object") +func retainObject(ptr: UnsafeMutableRawPointer) { + let _ = Unmanaged.fromOpaque(ptr).retain() +} + +@_cdecl("release_object") +func releaseObject(ptr: UnsafeMutableRawPointer) { + let _ = Unmanaged.fromOpaque(ptr).release() +} + +@_cdecl("data_from_bytes") +func dataFromBytes(data: UnsafePointer, size: Int) -> SRData { + let buffer = UnsafeBufferPointer(start: data, count: size) + return SRData(Array(buffer)) +} + +@_cdecl("string_from_bytes") +func stringFromBytes(data: UnsafePointer, size: Int) -> SRString { + let data = dataFromBytes(data: data, size: size); + return SRString(data) +}