Install Rust and Cargo [Rust-lang official link]
curl --proto '=https' --tlsv1.2 -sSf <https://sh.rustup.rs> | sh
# To reload the PATH env variables, after Rustup installation
source "$HOME/.cargo/env"
Initializing a new project
cargo new colink-example
cd colink-example
Add CoLink dependencies, in Cargo.toml
, add colink = "0.3.3"
[crates.io entry] under [dependencies]
. Let’s also add tokio
as we need to mark the main function to be asynchronous. The dependency section should look like the following.
[dependencies]
colink = "0.3.3"
tokio = "1.18"
As the first step to introduce CoLink into your code, add use colink;
at the beginning of the generated ./src/main.rs
hello world example. The complete code should look like the following.
use colink;
fn main() {
println!("Hello, world!");
}
Try building and running the hello world program for a sanity check. The first building procedure is going to take a while. Also since we hasn’t used colink
in the code, there will be a related warning.
cargo run
<aside>
💡 Note that prost-build
requires cmake as a dependency. If there is any error related to cmake, try installing build-essential and cmake before building again.
</aside>
After seeing the familiar Hello, world!
without any errors, congratulations! You are all set!
Setup: You will need chrono
for calculating expiration timestamp in this example, so please update your Cargo.toml
.
[dependencies]
chrono = "0.4"
colink = "0.3.3"
tokio = "1.18"
Step-by-step user import example.
<aside> 💡 Note that we are using a testing server with its host JWT. For more convenient experiment, please consider setting up your own CoLink server. You can refer to CoLink Server Setup for detailed instructions.
</aside>
use colink;
// To asynchronously reach out to the colink server for various requests
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
// Step 1. Generate a key pair for a new user.
let (pk, sk) = colink::generate_user();
println!("Key pair: {:?}\\n", (pk, sk));
// Step 2. Connect to a running colink server
// and get its public key for later usage.
// Plz refer to the documentation for how to set up a colink server.
// Here we use one of our testing server.
let addr = "<https://test.colink-server.colearn.cloud>";
let jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwcml2aWxlZ2UiOiJob3N0IiwidXNlcl9pZCI6IjAzNzY4N2NkYzhiMGE2ZmI3NzhjYzgwNjVmMzM5MTg3OGVjZmEwY2QxYzhlNzk2NGY1YzY0ODNlY2QzM2FjZWE3MCIsImV4cCI6MTY5NjM4MjkyNH0.vi2e7-Dn449oZq8Qpq_ihPDeohXzdLN1pAMxmx5jSkM";
let cl = colink::CoLink::new(addr, jwt);
let (_, core_pub_key) = cl.request_core_info().await?;
println!("Core pub key: {:?}\\n", core_pub_key);
// Step 3. To show that you agree the server to act on behalf of you
// and also prove that you are who you claim to be,
// you need to sign a "consent" message with your secret key.
let expiration_timestamp = chrono::Utc::now().timestamp() + 86400 * 31; // A default expiration timestamp at 31 days later
let (signature_timestamp, sig) =
colink::prepare_import_user_signature(&pk, &sk, &core_pub_key, expiration_timestamp);
println!("Sig: {:?}\\n", sig);
// Step 4. Import the user to the colink server.
// Note that you will need a `cl` session with a host JWT for this operation.
// You will get a new user JWT for future authentication.
let result_jwt = cl
.import_user(&pk, signature_timestamp, expiration_timestamp, &sig)
.await?;
println!("Result JWT: {:?}\\n", result_jwt);
Ok(())
}
<aside> 💡 Note that the user JWT in this step is generated from the previous steps.
</aside>
use colink;
// To asynchronously reach out to the colink server for various requests
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
// Use the user JWT to connect to a running colink server.
let addr = "<https://test.colink-server.colearn.cloud>";
let jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwcml2aWxlZ2UiOiJ1c2VyIiwidXNlcl9pZCI6IjAzNDlmODljZTg5OWE3Nzg5ZWM4MGE3OWVjZDNlNGU2ZTRhNjlkOTdiNjVjYTA1YjQzMGRmOTUzYTkwOGRmZjZjYyIsImV4cCI6MTY2NzU1NDM2OX0.XenARSb9fMbgCRyS8XJouQPFyWB-6FB6OFt3GvGVUAk";
let cl = colink::CoLink::new(addr, jwt);
// Then basically it is all set: now you can interact with the private storage.
// Create
let key_name = "a";
let key_path = cl.create_entry(key_name, "v".as_bytes()).await?;
println!("Create -> {:?}\\n", key_path);
// Read (using key name)
println!("Read (with key name) -> {:?}", cl.read_entry(key_name).await?);
// Read (using key path)
println!("Read (with key path) -> {:?}\\n", cl.read_entry(&key_path).await?);
// Update
let new_key_path = cl.update_entry(key_name, "vv".as_bytes()).await?;
println!("Update -> {:?}", new_key_path);
println!("Read again (with key name) -> {:?}", cl.read_entry(key_name).await?);
println!("Read old keypath -> {:?}\\n", cl.read_entry(&key_path).await?);
// Delete
println!("Delete -> {:?}", cl.delete_entry(key_name).await?);
println!("Read again (with key name) - return the msg -> {:?}", cl.read_entry(key_name).await); // notice: no "?"
println!("Read old keypath -> {:?}\\n", cl.read_entry(&key_path).await?);
// List (get all entries with some prefix)
let user_id = cl.get_user_id()?;
cl.update_entry("b", "example_value".as_bytes()).await?;
cl.update_entry("b:c", "example_value".as_bytes()).await?;
cl.update_entry("b:d", "example_value".as_bytes()).await?;
println!("List '<user_id>::*' -> {:?}", cl.read_keys(&format!("{}:", user_id), false).await?);
println!("List '<user_id>::*' (include history) -> {:?}", cl.read_keys(&format!("{}:", user_id), true).await?);
println!("List '<user_id>::b:*' -> {:?}", cl.read_keys(&format!("{}::b", user_id), false).await?);
println!("List internal '<user_id>::_internal' -> {:?}\\n", cl.read_keys(&format!("{}::_internal", user_id), false).await?);
// More advanced usage:
// Subscribe: you can also "subscribe" to the changes of a key
let queue_name = cl.subscribe(key_name, None).await?;
println!("Subscription queue name -> {:?}\\n", queue_name);
// You can use:
// let mut sub = self.cl.new_subscriber(&queue_name).await?;
// To get a new subscriber and then use
// let data = subscriber.get_next().await?;
// To get the next change.
// After you are done, you can use:
cl.unsubscribe(&queue_name).await?;
// To unsubscribe and offload from the colink server.
// Read or wait
// One convenient extension to subscription is the ability to read a key
// before it is even created.
// If the programmer is unsure whether the key would be ready during the execution
// and want the program to wait until the key is ready and get the value,
// they can use "read_or_wait" function.
// For how this is related to subscription, see the implementation of "read_or_wait".
cl.create_entry(key_name, "vvv".as_bytes()).await?;
println!("Read or wait -> {:?}", cl.read_or_wait(key_name).await?);
Ok(())
}
<aside> 💡 Note that the user JWT in this step is generated from the previous steps.
</aside>
use colink;
// To asynchronously reach out to the colink server for various requests
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
// Use the user JWT to connect to a running colink server.
let addr = "<https://test.colink-server.colearn.cloud>";
let jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwcml2aWxlZ2UiOiJ1c2VyIiwidXNlcl9pZCI6IjAzNDlmODljZTg5OWE3Nzg5ZWM4MGE3OWVjZDNlNGU2ZTRhNjlkOTdiNjVjYTA1YjQzMGRmOTUzYTkwOGRmZjZjYyIsImV4cCI6MTY2NzU1NDM2OX0.XenARSb9fMbgCRyS8XJouQPFyWB-6FB6OFt3GvGVUAk";
let cl = colink::CoLink::new(addr, jwt);
// Then basically it is all set: now you can run protocols as tasks.
// In this demonstration, we will use one default protocol that is already started -
// the "policy manager" protocol - to demonstrate how to instantiate protocols as tasks.
let protocol_name = "policy_module.stop"; // let's try to pause the policy manager for a few seconds
let protocol_param = "".as_bytes(); // not public parameter is needed for this protocol
let participants = vec![colink::Participant {
user_id: cl.get_user_id()?,
role: "local".to_string(),
}]; // in this example, the current user is required as the only participant
let require_agreement = false; // local protocols do not require agreements
let task_id = cl.run_task(protocol_name, protocol_param, &participants, require_agreement).await?;
println!("[Policy Module Stop] Task ID: {:?}\\n", task_id);
// Note that the system is completely asynchronous,
// so getting the task id does not imply that the task is finished or even already running.
tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;
let task_id = cl.run_task("policy_module.start", protocol_param, &participants, require_agreement).await?;
println!("[Policy Module Start] Task ID: {:?}\\n", task_id);
Ok(())
}