feat: revert readme
This commit is contained in:
625
README.md
625
README.md
@@ -1,483 +1,238 @@
|
|||||||
# swift-rs
|
# card-cli
|
||||||
|
|
||||||

|
> FIDO(U2F, WebAuthn), YubiKey, OpenPGP command line tool
|
||||||

|
|
||||||
|
|
||||||
Call Swift functions from Rust with ease!
|
Install:
|
||||||
|
```shell
|
||||||
## Setup
|
cargo install --git https://git.hatter.ink/hatter/card-cli.git
|
||||||
|
|
||||||
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:
|
Compile without features:
|
||||||
|
```shell
|
||||||
1. Ensure your swift code is organized into a Swift Package.
|
cargo build --release --no-default-features
|
||||||
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
|
# PGP
|
||||||
use swift_rs::SwiftLinker;
|
|
||||||
|
|
||||||
fn build() {
|
## encrypt & decrypt
|
||||||
// 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
|
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"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
With those steps completed, you should be ready to start using Swift code from Rust!
|
# piv-ecsign
|
||||||
|
|
||||||
If you experience the error `dyld[16008]: Library not loaded: @rpath/libswiftCore.dylib`
|
```shell
|
||||||
when using `swift-rs` with [Tauri](https://tauri.app) ensure you have set your
|
$ card-cli piv-ecsign -s 82 --hash-hex 8f25018489d6fe0dec34a352314c38dc146247b7de65735790f4398a92afa84b --json
|
||||||
[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
|
{
|
||||||
|
"hash_hex": "8f25018489d6fe0dec34a352314c38dc146247b7de65735790f4398a92afa84b",
|
||||||
To allow calling a Swift function from Rust, it must follow some rules:
|
"signed_data_base64": "MEUCICdes5Y0Id7KBNL23ZsTXXXGAzmsWYyDa6szQwjCxhCJAiEAhJotD2dPK/fWNjNrwkrPd0F20MpGgIY3WiKDR7YgJbk=",
|
||||||
|
"signed_data_hex": "30450220275eb3963421deca04d2f6dd9b135d75c60339ac598c836bab334308c2c61089022100849a2d0f674f2bf7d636336bc24acf774176d0ca468086375a228347b62025b9",
|
||||||
1. It must be global
|
"slot": "82"
|
||||||
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.
|
# import private key to PIV card & generate certificate
|
||||||
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
|
```shell
|
||||||
@_cdecl("square_number")
|
$ ykman piv keys import --pin-policy NEVER --touch-policy CACHED 82 private_key.pem
|
||||||
public func squareNumber(number: Int) -> Int {
|
|
||||||
return number * number
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Now that `squareNumber` is properly exposed to Rust, we can start interfacing with it.
|
| Parameter | Description |
|
||||||
This can be done using the `swift!` macro, with the `Int` type helping to provide a similar function signature:
|
| ---- |------------------------------------------------------------------|
|
||||||
|
| --pin-policy | \[ DEFAULT \| NEVER \| ONCE \| ALWAYS \] PIN policy for slot |
|
||||||
|
| --touch-policy | \[ DEFAULT \| NEVER \| ALWAYS \| CACHED \] touch policy for slot |
|
||||||
|
|
||||||
```rust
|
```shell
|
||||||
use swift_rs::swift;
|
$ ykman piv certificates generate 82 public_key.pem -s 'O=age-plugin-yubikey,OU=0.3.3,CN=hatter-yk'
|
||||||
|
|
||||||
swift!(fn square_number(number: Int) -> Int);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Lastly, you can call the function from regular Rust functions.
|
# age
|
||||||
Note that <b>all</b> calls to a Swift function are unsafe,
|
|
||||||
and require wrapping in an `unsafe {}` block or `unsafe fn`.
|
|
||||||
|
|
||||||
```rust
|
## pgp-age-address
|
||||||
fn main() {
|
|
||||||
let input: Int = 4;
|
|
||||||
let output = unsafe { square_number(input) };
|
|
||||||
|
|
||||||
println!("Input: {}, Squared: {}", input, output);
|
```shell
|
||||||
// Prints "Input: 4, Squared: 16"
|
$ 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
|
||||||
```
|
```
|
||||||
|
|
||||||
Check [the documentation](TODO) for all available helper types.
|
# sign-jwt
|
||||||
|
|
||||||
## Returning objects from Swift
|
Sign a JWT:
|
||||||
|
```shell
|
||||||
Let's say that we want our `squareNumber` function to return not only the result, but also the original input.
|
card-cli sign-jwt -s r3 \
|
||||||
A standard way to do this in Swift would be with a struct:
|
-C iss:****** \
|
||||||
|
-C sub:****** \
|
||||||
```swift
|
-C aud:client_gard****** \
|
||||||
struct SquareNumberResult {
|
-K KEY=ID \
|
||||||
var input: Int
|
--jti \
|
||||||
var output: Int
|
--validity 10m --json
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
We are not allowed to do this, though, since structs cannot be represented in Objective-C.
|
# SSH CA
|
||||||
Instead, we must use a class that extends `NSObject`:
|
|
||||||
|
|
||||||
```swift
|
## Generate SSH root CA
|
||||||
class SquareNumberResult: NSObject {
|
|
||||||
var input: Int
|
|
||||||
var output: Int
|
|
||||||
|
|
||||||
init(_ input: Int, _ output: Int) {
|
```shell
|
||||||
self.input = input;
|
card-cli ssh-pub-key --ca -s r15
|
||||||
self.output = output
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
<sub><sup>Yes, this class could contain the squaring logic too, but that is irrelevant for this example
|
Outputs:
|
||||||
|
```
|
||||||
An instance of this class can then be returned from `squareNumber`:
|
cert-authority,principals="root" ecdsa-sha2-nistp384 AAAAE2VjZHNh****** Yubikey-PIV-R15
|
||||||
|
|
||||||
```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.
|
> `principals` can be multiple items, split by `,`, e.g. `root,hatterink`
|
||||||
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<T>`.
|
## Generate SSH user CA
|
||||||
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
|
```shell
|
||||||
use swift_rs::{swift, Int, SRObject};
|
ssh-keygen -f id_user
|
||||||
|
|
||||||
// Any struct that is used in a C function must be annotated
|
card-cli ssh-piv-cert --pub id_user.pub -s r15
|
||||||
// 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<SquareNumberResult>);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Then, using the new return value is just like using `SquareNumberResult` directly:
|
Show SSH CA cert details:
|
||||||
|
```shell
|
||||||
```rust
|
ssh-keygen -L -f id_user-cert.pub
|
||||||
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.
|
SSH to server:
|
||||||
|
```shell
|
||||||
## Optionals
|
ssh -i id_user root@example.com
|
||||||
|
|
||||||
`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<T>` type!
|
|
||||||
|
|
||||||
```rust
|
<br><br>
|
||||||
use swift_rs::{swift, Bool, SRString};
|
|
||||||
|
|
||||||
swift!(optional_string(return_nil: Bool) -> Option<SRString>)
|
Downloads:
|
||||||
```
|
* https://developers.yubico.com/yubikey-manager/
|
||||||
|
|
||||||
Null pointers are actually the reason why a function that returns an optional primitive cannot be represented in C.
|
<br>
|
||||||
If this were to be supported, how could a `nil` be differentiated from a number? It can't!
|
|
||||||
|
|
||||||
## Complex types
|
Related projects:
|
||||||
|
* https://crates.io/crates/openpgp-card-tools
|
||||||
So far we have only looked at using primitive types and structs/classes,
|
* https://github.com/sekey/sekey
|
||||||
but this leaves out some of the most important data structures: arrays (`SRArray<T>`) and strings (`SRString`).
|
* https://github.com/str4d/age-plugin-yubikey
|
||||||
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<T>` 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<Int>
|
|
||||||
|
|
||||||
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<Int>
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since IntArray extends NSObject in its Swift implementation,
|
|
||||||
// it must be wrapped in SRObject on the Rust side
|
|
||||||
swift!(fn get_numbers() -> SRObject<IntArray>);
|
|
||||||
|
|
||||||
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<usize>`,
|
|
||||||
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<SRArray<Int>>);
|
|
||||||
```
|
|
||||||
|
|
||||||
**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<T>` 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<T>`, 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<T> since
|
|
||||||
// SRObjectArray<T> does it automatically
|
|
||||||
swift!(fn get_tuples() -> SRObjectArray<IntTuple>);
|
|
||||||
|
|
||||||
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<T>` you like, just remember to follow the 3 rules!
|
|
||||||
|
|
||||||
## Bonuses
|
|
||||||
|
|
||||||
### SRData
|
|
||||||
|
|
||||||
A wrapper type for `SRArray<T>` 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.
|
|
||||||
Reference in New Issue
Block a user