Setup

  1. 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"
    
  2. Initializing a new project

    cargo new colink-example
    cd colink-example
    
  3. 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"
    
  4. 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!");
    }
    
  5. 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>

  6. After seeing the familiar Hello, world! without any errors, congratulations! You are all set!

Generate and import new users

  1. 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"
    
  2. 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(())
    }
    

Private Storage CRUD and more

<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(())
}

Run an existing protocol

<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(())
}

Implement your own protocols