use anyhow::{Error, Result};
use ironoxide::prelude::*;
use std::{fs::File, io::Write, path::PathBuf, str::from_utf8};
#[tokio::main]
async fn main() -> Result<()> {
let sdk = initialize_sdk_from_file("examples/example-ironoxide-device.json".into()).await?;
let documents = generate_encrypted_documents(&sdk, 5).await?;
let document_data: Vec<&[u8]> = documents.iter().map(|doc| doc.encrypted_data()).collect();
println!("1. Decrypt as needed");
println!("2. Decrypt in pages");
println!("3. Decrypt in bulk");
let method = read_usize("Choose a method of decryption: ")?;
match method {
1 => decrypt_as_needed(&sdk, &document_data).await?,
2 => decrypt_in_pages(&sdk, &document_data).await?,
3 => decrypt_in_bulk(&sdk, &document_data).await?,
_ => println!("Invalid selection"),
};
Ok(())
}
async fn decrypt_as_needed(sdk: &IronOxide, documents: &[&[u8]]) -> Result<()> {
print_encrypted_documents(sdk, &documents);
let doc_index = read_usize("\nDocument # to decrypt: ")? - 1;
let encrypted_doc = documents.get(doc_index).expect("Index out of range.");
let id = sdk.document_get_id_from_bytes(encrypted_doc)?;
println!("Decrypting document with ID {}\n", id.id());
let decrypted_doc = sdk.document_decrypt(encrypted_doc).await?;
print_decrypted_data(&decrypted_doc)?;
Ok(())
}
async fn decrypt_in_pages(sdk: &IronOxide, documents: &[&[u8]]) -> Result<()> {
let page_size = read_page_size(documents.len())?;
let pages: Vec<_> = documents.chunks(page_size).collect();
let mut maybe_current_page = Some(0);
let mut previous_page = 1;
let mut decrypted_documents = vec![];
while let Some(current_page) = maybe_current_page {
if current_page == previous_page {
println!("Page did not change, so the documents are already decrypted.")
} else {
previous_page = current_page;
decrypted_documents = futures::future::try_join_all(
pages
.get(current_page)
.ok_or_else(|| Error::msg("Invalid document page."))?
.iter()
.map(|doc| decrypt_document(sdk, doc)),
)
.await?;
}
print_decrypted_documents(&decrypted_documents, current_page * page_size);
maybe_current_page =
get_new_current_page(current_page, page_size, pages.len(), &decrypted_documents)?;
}
Ok(())
}
async fn decrypt_in_bulk(sdk: &IronOxide, documents: &[&[u8]]) -> Result<()> {
println!("\nDecrypting all documents in bulk...");
let decrypted_documents =
futures::future::try_join_all(documents.iter().map(|doc| decrypt_document(sdk, doc)))
.await?;
print_decrypted_documents(&decrypted_documents, 0);
let doc_index = read_usize("\nDocument # to view: ")?;
let decrypted_doc = &decrypted_documents[doc_index - 1];
print_decrypted_data(decrypted_doc)?;
Ok(())
}
async fn initialize_sdk_from_file(device_path: PathBuf) -> Result<IronOxide> {
if device_path.is_file() {
let device_context_file = File::open(&device_path)?;
let device_context: DeviceContext = serde_json::from_reader(device_context_file)?;
let ironoxide = ironoxide::initialize(&device_context, &Default::default()).await?;
Ok(ironoxide)
} else {
panic!(
"Couldn't open file {} containing DeviceContext",
device_path.display()
)
}
}
async fn generate_encrypted_documents(
sdk: &IronOxide,
number_of_documents: u8,
) -> ironoxide::Result<Vec<DocumentEncryptResult>> {
println!("Encrypting {} documents...", number_of_documents);
let opts = DocumentEncryptOpts::default();
let encrypted_documents = futures::future::try_join_all(
(0..number_of_documents).map(|_| sdk.document_encrypt(b"foobar", &opts)),
)
.await;
println!("Done\n");
encrypted_documents
}
async fn decrypt_document(sdk: &IronOxide, document: &[u8]) -> Result<DocumentDecryptResult> {
let id = sdk.document_get_id_from_bytes(document)?;
println!("Decrypting document with ID {}", id.id());
let decrypted_document = sdk.document_decrypt(document).await?;
Ok(decrypted_document)
}
fn get_new_current_page(
current_page: usize,
page_size: usize,
pages_len: usize,
decrypted_documents: &[DocumentDecryptResult],
) -> Result<Option<usize>> {
let input = read_stdin(
"\nDocument # to decrypt, 'n' for next page, 'p' for previous page, or 'q' to quit: ",
)?;
println!();
Ok(match input.trim().parse::<usize>() {
Ok(num) => {
let current_min = current_page * page_size + 1;
let current_max = current_min + decrypted_documents.len() - 1;
if num >= current_min && num <= current_max {
let doc = &decrypted_documents[num - current_min];
print_decrypted_data(&doc)?;
None
}
else {
println!("Invalid selection");
Some(current_page)
}
}
Err(_) => match input.trim() {
"n" => Some(std::cmp::min(current_page + 1, pages_len - 1)),
"p" => current_page.checked_sub(1).or(Some(0)),
"q" => None,
_ => {
println!("Invalid selection");
Some(current_page)
}
},
})
}
fn print_decrypted_data(document: &DocumentDecryptResult) -> Result<()> {
println!(
"ID: {}\nDecrypted data: {}",
document.id().id(),
from_utf8(document.decrypted_data())?
);
Ok(())
}
fn print_decrypted_documents(decrypted_documents: &[DocumentDecryptResult], page_offset: usize) {
println!("\nDecrypted documents:");
decrypted_documents
.iter()
.enumerate()
.for_each(|(offset, doc)| {
let doc_number = page_offset + offset + 1;
println!("#{}: {}", doc_number, doc.id().id())
});
}
fn print_encrypted_documents(sdk: &IronOxide, encrypted_documents: &[&[u8]]) {
println!("\nEncrypted documents:");
encrypted_documents
.iter()
.enumerate()
.for_each(|(offset, doc)| {
let id = sdk
.document_get_id_from_bytes(doc)
.expect("Invalid document bytes");
println!("#{}: {}", offset + 1, id.id())
});
}
fn read_page_size(max_size: usize) -> Result<usize> {
let page_size_input = read_usize("Enter page size: ")?;
let page_size = match page_size_input {
0 => panic!("Page size cannot be zero"),
n if n > max_size => max_size,
_ => page_size_input,
};
println!();
Ok(page_size)
}
fn read_usize(text: &str) -> Result<usize> {
Ok(read_stdin(text)?.trim().parse()?)
}
fn read_stdin(text: &str) -> Result<String> {
print!("{}", text);
std::io::stdout().flush()?;
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
Ok(input)
}