use clap::Args;
use colorful::Colorful;
use miette::{miette, WrapErr};
use serde_json::json;
use tokio::{sync::Mutex, try_join};
use crate::{docs, CommandGlobalOpts};
use ockam::identity::get_default_timeout;
use ockam::identity::models::CredentialAndPurposeKey;
use ockam::{identity::Identifier, route, Context};
use ockam_api::address::extract_address_value;
use ockam_api::colors::OckamColor;
use ockam_api::nodes::models::secure_channel::{
CreateSecureChannelRequest, CreateSecureChannelResponse,
};
use ockam_api::nodes::BackgroundNodeClient;
use ockam_api::{fmt_log, fmt_ok, ReverseLocalConverter};
use ockam_core::api::Request;
use ockam_multiaddr::MultiAddr;
use crate::node::util::initialize_default_node;
use crate::project::util::{
clean_projects_multiaddr, get_projects_secure_channels_from_config_lookup,
};
use crate::shared_args::IdentityOpts;
use crate::util::{clean_nodes_multiaddr, exitcode};
const LONG_ABOUT: &str = include_str!("./static/create/long_about.txt");
const AFTER_LONG_HELP: &str = include_str!("./static/create/after_long_help.txt");
#[derive(Clone, Debug, Args)]
#[command(
arg_required_else_help = true,
long_about = docs::about(LONG_ABOUT),
after_long_help = docs::after_help(AFTER_LONG_HELP),
)]
pub struct CreateCommand {
#[arg(value_name = "NODE", long, display_order = 800, value_parser = extract_address_value)]
pub from: String,
#[arg(value_name = "ROUTE", long, display_order = 800)]
pub to: MultiAddr,
#[arg(value_name = "IDENTIFIER", long, short, display_order = 801)]
pub authorized: Option<Vec<Identifier>>,
#[arg(
short,
long = "credential",
value_name = "CREDENTIAL",
display_order = 802
)]
pub credential: Option<String>,
#[command(flatten)]
identity_opts: IdentityOpts,
}
impl CreateCommand {
pub fn name(&self) -> String {
"secure-channel create".into()
}
async fn parse_to_route(
&self,
opts: &CommandGlobalOpts,
ctx: &Context,
node: &BackgroundNodeClient,
) -> miette::Result<MultiAddr> {
let (to, meta) = clean_nodes_multiaddr(&self.to, &opts.state)
.await
.wrap_err(format!("Could not convert {} into route", &self.to))?;
let identity_name = opts
.state
.get_identity_name_or_default(&self.identity_opts.identity_name)
.await?;
let projects_sc = get_projects_secure_channels_from_config_lookup(
opts,
ctx,
node,
&meta,
Some(identity_name),
Some(get_default_timeout()),
)
.await?;
clean_projects_multiaddr(to, projects_sc).wrap_err("Could not parse projects from route")
}
pub async fn run(&self, ctx: &Context, opts: CommandGlobalOpts) -> miette::Result<()> {
initialize_default_node(ctx, &opts).await?;
let node = BackgroundNodeClient::create_to_node(ctx, &opts.state, &self.from)?;
opts.terminal
.write_line(fmt_log!("Creating Secure Channel...\n"))?;
let is_finished: Mutex<bool> = Mutex::new(false);
let to = self.parse_to_route(&opts, ctx, &node).await?;
let authorized_identifiers = self.authorized.clone();
let credential = match &self.credential {
Some(c) => {
let c = hex::decode(c).map_err(|_| miette!("Invalid credential hex"))?;
let c: CredentialAndPurposeKey =
minicbor::decode(&c).map_err(|_| miette!("Invalid credential"))?;
Some(c)
}
None => None,
};
let create_secure_channel = async {
let identity_name = opts
.state
.get_identity_name_or_default(&self.identity_opts.identity_name)
.await?;
let payload = CreateSecureChannelRequest::new(
&to,
authorized_identifiers,
Some(identity_name),
credential,
);
let request = Request::post("/node/secure_channel").body(payload);
let response: CreateSecureChannelResponse = node.ask(ctx, request).await?;
*is_finished.lock().await = true;
Ok(response.addr)
};
let output_messages = vec!["Creating Secure Channel...".to_string()];
let progress_output = opts.terminal.loop_messages(&output_messages, &is_finished);
let (secure_channel, _) = try_join!(create_secure_channel, progress_output)?;
let route = &route![secure_channel.to_string()];
let multi_addr = ReverseLocalConverter::convert_route(route).map_err(|_| {
crate::Error::new(
exitcode::PROTOCOL,
miette!("Failed to convert route {route} to multi-address"),
)
})?;
let from = format!("/node/{}", node.node_name());
opts.terminal
.to_stdout()
.plain(
fmt_ok!(
"Secure Channel at {} created successfully\n",
multi_addr
.to_string()
.color(OckamColor::PrimaryResource.color())
) + &fmt_log!(
"From {} to {}",
from.color(OckamColor::PrimaryResource.color()),
self.to
.to_string()
.color(OckamColor::PrimaryResource.color())
),
)
.machine(multi_addr.to_string())
.json(json!([{ "address": multi_addr.to_string() }]))
.write_line()?;
Ok(())
}
}