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!
|
||||
|
||||
## 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"] }
|
||||
Install:
|
||||
```shell
|
||||
cargo install --git https://git.hatter.ink/hatter/card-cli.git
|
||||
```
|
||||
|
||||
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"
|
||||
)
|
||||
],
|
||||
)
|
||||
]
|
||||
)
|
||||
Compile without features:
|
||||
```shell
|
||||
cargo build --release --no-default-features
|
||||
```
|
||||
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;
|
||||
# PGP
|
||||
|
||||
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();
|
||||
## encrypt & decrypt
|
||||
|
||||
// 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`
|
||||
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`.
|
||||
```shell
|
||||
$ card-cli piv-ecsign -s 82 --hash-hex 8f25018489d6fe0dec34a352314c38dc146247b7de65735790f4398a92afa84b --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
|
||||
{
|
||||
"hash_hex": "8f25018489d6fe0dec34a352314c38dc146247b7de65735790f4398a92afa84b",
|
||||
"signed_data_base64": "MEUCICdes5Y0Id7KBNL23ZsTXXXGAzmsWYyDa6szQwjCxhCJAiEAhJotD2dPK/fWNjNrwkrPd0F20MpGgIY3WiKDR7YgJbk=",
|
||||
"signed_data_hex": "30450220275eb3963421deca04d2f6dd9b135d75c60339ac598c836bab334308c2c61089022100849a2d0f674f2bf7d636336bc24acf774176d0ca468086375a228347b62025b9",
|
||||
"slot": "82"
|
||||
}
|
||||
```
|
||||
|
||||
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`.
|
||||
# import private key to PIV card & generate certificate
|
||||
|
||||
```swift
|
||||
@_cdecl("square_number")
|
||||
public func squareNumber(number: Int) -> Int {
|
||||
return number * number
|
||||
}
|
||||
```shell
|
||||
$ ykman piv keys import --pin-policy NEVER --touch-policy CACHED 82 private_key.pem
|
||||
```
|
||||
|
||||
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:
|
||||
| Parameter | Description |
|
||||
| ---- |------------------------------------------------------------------|
|
||||
| --pin-policy | \[ DEFAULT \| NEVER \| ONCE \| ALWAYS \] PIN policy for slot |
|
||||
| --touch-policy | \[ DEFAULT \| NEVER \| ALWAYS \| CACHED \] touch policy for slot |
|
||||
|
||||
```rust
|
||||
use swift_rs::swift;
|
||||
|
||||
swift!(fn square_number(number: Int) -> Int);
|
||||
```shell
|
||||
$ ykman piv certificates generate 82 public_key.pem -s 'O=age-plugin-yubikey,OU=0.3.3,CN=hatter-yk'
|
||||
```
|
||||
|
||||
Lastly, you can call the function from regular Rust functions.
|
||||
Note that <b>all</b> calls to a Swift function are unsafe,
|
||||
and require wrapping in an `unsafe {}` block or `unsafe fn`.
|
||||
# age
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let input: Int = 4;
|
||||
let output = unsafe { square_number(input) };
|
||||
## pgp-age-address
|
||||
|
||||
println!("Input: {}, Squared: {}", input, output);
|
||||
// Prints "Input: 4, Squared: 16"
|
||||
}
|
||||
```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
|
||||
```
|
||||
|
||||
Check [the documentation](TODO) for all available helper types.
|
||||
# sign-jwt
|
||||
|
||||
## 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
|
||||
}
|
||||
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
|
||||
```
|
||||
|
||||
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`:
|
||||
# SSH CA
|
||||
|
||||
```swift
|
||||
class SquareNumberResult: NSObject {
|
||||
var input: Int
|
||||
var output: Int
|
||||
## Generate SSH root CA
|
||||
|
||||
init(_ input: Int, _ output: Int) {
|
||||
self.input = input;
|
||||
self.output = output
|
||||
}
|
||||
}
|
||||
```shell
|
||||
card-cli ssh-pub-key --ca -s r15
|
||||
```
|
||||
|
||||
<sub><sup>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)
|
||||
}
|
||||
Outputs:
|
||||
```
|
||||
cert-authority,principals="root" ecdsa-sha2-nistp384 AAAAE2VjZHNh****** Yubikey-PIV-R15
|
||||
```
|
||||
|
||||
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.
|
||||
> `principals` can be multiple items, split by `,`, e.g. `root,hatterink`
|
||||
|
||||
This may sound daunting, but it's not actually a problem thanks to `SRObject<T>`.
|
||||
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:
|
||||
## Generate SSH user CA
|
||||
|
||||
```rust
|
||||
use swift_rs::{swift, Int, SRObject};
|
||||
```shell
|
||||
ssh-keygen -f id_user
|
||||
|
||||
// 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<SquareNumberResult>);
|
||||
card-cli ssh-piv-cert --pub id_user.pub -s r15
|
||||
```
|
||||
|
||||
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
|
||||
}
|
||||
Show SSH CA cert details:
|
||||
```shell
|
||||
ssh-keygen -L -f id_user-cert.pub
|
||||
```
|
||||
|
||||
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")
|
||||
}
|
||||
SSH to server:
|
||||
```shell
|
||||
ssh -i id_user root@example.com
|
||||
```
|
||||
|
||||
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
|
||||
use swift_rs::{swift, Bool, SRString};
|
||||
<br><br>
|
||||
|
||||
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.
|
||||
If this were to be supported, how could a `nil` be differentiated from a number? It can't!
|
||||
<br>
|
||||
|
||||
## 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<T>`) 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<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.
|
||||
Related projects:
|
||||
* https://crates.io/crates/openpgp-card-tools
|
||||
* https://github.com/sekey/sekey
|
||||
* https://github.com/str4d/age-plugin-yubikey
|
||||
Reference in New Issue
Block a user