remote installs?

This commit is contained in:
dr-frmr 2023-10-16 15:56:26 -04:00
parent b85413339a
commit ae766b4e9a
No known key found for this signature in database
16 changed files with 567 additions and 364 deletions

View File

@ -126,7 +126,7 @@ fn main() {
}
// only execute if one of the modules has source code changes
const WASI_APPS: [&str; 9] = [
"app_tracker",
"app_store",
"chess",
"homepage",
"http_bindings",

View File

@ -9,7 +9,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
[[package]]
name = "app_tracker"
name = "app_store"
version = "0.1.0"
dependencies = [
"anyhow",

View File

@ -1,5 +1,5 @@
[package]
name = "app_tracker"
name = "app_store"
version = "0.1.0"
edition = "2021"

View File

@ -1,7 +1,7 @@
[
{
"process_name": "app_tracker",
"process_wasm_path": "/app_tracker.wasm",
"process_name": "main",
"process_wasm_path": "/app_store.wasm",
"on_panic": "Restart",
"request_networking": true,
"request_messaging": [

View File

@ -0,0 +1,5 @@
{
"package": "app_store",
"publisher": "uqbar",
"description": "A package manager + app store. This JSON field is optional and you can add whatever you want in addition to this."
}

View File

@ -0,0 +1,489 @@
cargo_component_bindings::generate!();
use bindings::{
component::uq_process::types::*, get_capability, get_payload, print_to_terminal, receive,
send_request, send_response, Guest,
};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
#[allow(dead_code)]
mod kernel_types;
use kernel_types as kt;
#[allow(dead_code)]
mod process_lib;
use process_lib::PackageId;
mod transfer_lib;
struct Component;
// #[derive(Serialize, Deserialize)]
// struct AppState {
// // TODO this should mirror onchain listing
// pub name: String,
// pub owner: NodeId,
// pub desc: String,
// pub website: Option<String>,
// pub versions: Vec<(u32, String)>, // TODO
// }
#[derive(Serialize, Deserialize)]
struct AppTrackerState {
pub mirrored_packages: HashSet<PackageId>,
pub requested_packages: HashMap<PackageId, NodeId>, // who we're expecting it from
}
#[derive(Debug, Serialize, Deserialize)]
pub enum AppTrackerRequest {
New {
package: String,
mirror: bool,
},
NewFromRemote {
package_id: PackageId,
install_from: NodeId,
},
Install {
package: String,
},
}
#[derive(Debug, Serialize, Deserialize)]
pub enum AppTrackerResponse {
New { package: String },
NewFromRemote { package_id: PackageId },
Install { package: String },
Error { error: String },
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PackageMetadata {
pub package: String,
pub publisher: String,
pub desc: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PackageManifestEntry {
pub process_name: String,
pub process_wasm_path: String,
pub on_panic: kt::OnPanic,
pub request_networking: bool,
pub request_messaging: Vec<String>,
pub public: bool,
}
fn parse_command(
our: &Address,
source: &Address,
request_string: String,
state: &mut AppTrackerState,
) -> anyhow::Result<Option<AppTrackerResponse>> {
match serde_json::from_str(&request_string)? {
// create a new package based on local payload
AppTrackerRequest::New { package, mirror } => {
if our.node != source.node {
return Err(anyhow::anyhow!("new package request from non-local node"));
}
let Some(payload) = get_payload() else {
return Err(anyhow::anyhow!("no payload"));
};
let vfs_address = Address {
node: our.node.clone(),
process: ProcessId::from_str("vfs:sys:uqbar")?,
};
let _ = process_lib::send_and_await_response(
&vfs_address,
false,
Some(serde_json::to_string(&kt::VfsRequest {
drive: package.clone(),
action: kt::VfsAction::New,
})?),
None,
None,
5,
)?;
// add zip bytes
let _ = process_lib::send_and_await_response(
&vfs_address,
true,
Some(serde_json::to_string(&kt::VfsRequest {
drive: package.clone(),
action: kt::VfsAction::Add {
full_path: package.clone().into(),
entry_type: kt::AddEntryType::ZipArchive,
},
})?),
None,
Some(&payload),
5,
)?;
// save the zip file itself in VFS for sharing with other nodes
// call it <package>.zip
let _ = process_lib::send_and_await_response(
&vfs_address,
true,
Some(serde_json::to_string(&kt::VfsRequest {
drive: package.clone(),
action: kt::VfsAction::Add {
full_path: format!("/{}.zip", package),
entry_type: kt::AddEntryType::NewFile,
},
})?),
None,
Some(&payload),
5,
)?;
// if mirror, save in our state
if mirror {
let _ = process_lib::send_and_await_response(
&vfs_address,
false,
Some(serde_json::to_string(&kt::VfsRequest {
drive: package.clone(),
action: kt::VfsAction::GetEntry("/metadata.json".into()),
})?),
None,
None,
5,
)?;
let Some(payload) = get_payload() else {
return Err(anyhow::anyhow!("no metadata payload"));
};
let metadata = String::from_utf8(payload.bytes)?;
let metadata = serde_json::from_str::<PackageMetadata>(&metadata)?;
state
.mirrored_packages
.insert(PackageId::new(&metadata.package, &metadata.publisher));
process_lib::set_state::<AppTrackerState>(&state);
}
Ok(Some(AppTrackerResponse::New { package }))
}
// if we are the source, forward to install_from target.
// if we install_from, respond with package if we have it
AppTrackerRequest::NewFromRemote {
package_id,
install_from,
} => {
if our.node == source.node {
let _ = send_request(
&Address {
node: install_from.clone(),
process: our.process.clone(),
},
&Request {
inherit: true,
expects_response: Some(5), // TODO
ipc: Some(serde_json::to_string(&AppTrackerRequest::NewFromRemote {
package_id,
install_from,
})?),
metadata: None,
},
None,
None,
);
Ok(None)
} else if our.node == install_from {
let Some(_mirror) = state.mirrored_packages.get(&package_id) else {
return Ok(Some(AppTrackerResponse::Error { error: "package not mirrored here!".into() }))
};
// get the .zip from VFS and attach as payload to response
let vfs_address = Address {
node: our.node.clone(),
process: ProcessId::from_str("vfs:sys:uqbar")?,
};
let _ = process_lib::send_and_await_response(
&vfs_address,
false,
Some(serde_json::to_string(&kt::VfsRequest {
drive: package_id.to_string(),
action: kt::VfsAction::GetEntry(format!("/{}.zip", package_id.to_string())),
})?),
None,
None,
5,
)?;
Ok(Some(AppTrackerResponse::NewFromRemote { package_id }))
} else {
// TODO what to do here?
Ok(None)
}
}
AppTrackerRequest::Install { package } => {
if our.node != source.node {
return Err(anyhow::anyhow!("install request from non-local node"));
}
let vfs_address = Address {
node: our.node.clone(),
process: ProcessId::from_str("vfs:sys:uqbar")?,
};
let _ = process_lib::send_and_await_response(
&vfs_address,
false,
Some(serde_json::to_string(&kt::VfsRequest {
drive: package.clone(),
action: kt::VfsAction::GetEntry("/manifest.json".into()),
})?),
None,
None,
5,
)?;
let Some(payload) = get_payload() else {
return Err(anyhow::anyhow!("no payload"));
};
let manifest = String::from_utf8(payload.bytes)?;
let manifest = serde_json::from_str::<Vec<PackageManifestEntry>>(&manifest)?;
for entry in manifest {
let path = if entry.process_wasm_path.starts_with("/") {
entry.process_wasm_path
} else {
format!("/{}", entry.process_wasm_path)
};
let (_, hash_response) = process_lib::send_and_await_response(
&vfs_address,
false,
Some(serde_json::to_string(&kt::VfsRequest {
drive: package.clone(),
action: kt::VfsAction::GetHash(path.clone()),
})?),
None,
None,
5,
)?;
let Message::Response((Response { ipc: Some(ipc), .. }, _)) = hash_response else {
return Err(anyhow::anyhow!("bad vfs response"));
};
let kt::VfsResponse::GetHash(Some(hash)) = serde_json::from_str(&ipc)? else {
return Err(anyhow::anyhow!("no hash in vfs"));
};
// build initial caps
let mut initial_capabilities: HashSet<kt::SignedCapability> = HashSet::new();
if entry.request_networking {
let Some(networking_cap) = get_capability(
&Address {
node: our.node.clone(),
process: ProcessId::from_str("kernel:sys:uqbar")?,
},
&"\"network\"".to_string(),
) else {
return Err(anyhow::anyhow!("app-store: no net cap"));
};
initial_capabilities.insert(kt::de_wit_signed_capability(networking_cap));
}
let Some(read_cap) = get_capability(
&vfs_address.clone(),
&serde_json::to_string(&serde_json::json!({
"kind": "read",
"drive": package,
}))?,
) else {
return Err(anyhow::anyhow!("app-store: no read cap"));
};
initial_capabilities.insert(kt::de_wit_signed_capability(read_cap));
let Some(write_cap) = get_capability(
&vfs_address.clone(),
&serde_json::to_string(&serde_json::json!({
"kind": "write",
"drive": package,
}))?,
) else {
return Err(anyhow::anyhow!("app-store: no write cap"));
};
initial_capabilities.insert(kt::de_wit_signed_capability(write_cap));
for process_name in &entry.request_messaging {
let Ok(parsed_process_id) = ProcessId::from_str(&process_name) else {
// TODO handle arbitrary caps here
continue;
};
let Some(messaging_cap) = get_capability(
&Address {
node: our.node.clone(),
process: parsed_process_id.clone(),
},
&"\"messaging\"".into()
) else {
print_to_terminal(0, &format!("app-store: no cap for {} to give away!", process_name));
continue;
};
initial_capabilities.insert(kt::de_wit_signed_capability(messaging_cap));
}
let process_id = format!("{}:{}", entry.process_name, package.clone());
let Ok(parsed_new_process_id) = ProcessId::from_str(&process_id) else {
return Err(anyhow::anyhow!("app-store: invalid process id!"));
};
let _ = process_lib::send_request(
&Address {
node: our.node.clone(),
process: ProcessId::from_str("kernel:sys:uqbar")?,
},
false,
Some(serde_json::to_string(&kt::KernelCommand::KillProcess(
kt::ProcessId::de_wit(parsed_new_process_id.clone()),
))?),
None,
None,
None,
);
// kernel start process takes bytes as payload + wasm_bytes_handle...
// reconsider perhaps
let (_, _bytes_response) = process_lib::send_and_await_response(
&vfs_address,
false,
Some(serde_json::to_string(&kt::VfsRequest {
drive: package.clone(),
action: kt::VfsAction::GetEntry(path),
})?),
None,
None,
5,
)?;
let Some(payload) = get_payload() else {
return Err(anyhow::anyhow!("no wasm bytes payload."));
};
let _ = process_lib::send_and_await_response(
&Address {
node: our.node.clone(),
process: ProcessId::from_str("kernel:sys:uqbar")?,
},
false,
Some(serde_json::to_string(&kt::KernelCommand::StartProcess {
id: kt::ProcessId::de_wit(parsed_new_process_id),
wasm_bytes_handle: hash,
on_panic: entry.on_panic,
initial_capabilities,
public: entry.public,
})?),
None,
Some(&payload),
5,
)?;
}
Ok(Some(AppTrackerResponse::Install { package }))
}
}
}
impl Guest for Component {
fn init(our: Address) {
assert_eq!(our.process.to_string(), "main:app_store:uqbar");
print_to_terminal(0, &format!("app_store main proc: start"));
let mut state = process_lib::get_state::<AppTrackerState>().unwrap_or(AppTrackerState {
mirrored_packages: HashSet::new(),
requested_packages: HashMap::new(),
});
loop {
let (source, message) = match receive() {
Ok((source, message)) => (source, message),
Err((error, _context)) => {
print_to_terminal(0, &format!("net error: {:?}", error.kind));
continue;
}
};
match message {
Message::Request(Request {
ipc,
expects_response,
metadata,
..
}) => {
let Some(command) = ipc else {
continue;
};
match parse_command(&our, &source, command, &mut state) {
Ok(response) => {
if let Some(_) = expects_response {
let _ = send_response(
&Response {
ipc: Some(serde_json::to_string(&response).unwrap()),
metadata,
},
None, // payload will be attached here if created in parse_command
);
};
}
Err(e) => {
print_to_terminal(0, &format!("app-store: got error {}", e));
if let Some(_) = expects_response {
let error = AppTrackerResponse::Error {
error: format!("{}", e),
};
let _ = send_response(
&Response {
ipc: Some(serde_json::to_string(&error).unwrap()),
metadata,
},
None,
);
};
}
}
}
Message::Response((response, _)) => {
// only expecting NewFromRemote for apps we've requested
match serde_json::from_str(&response.ipc.unwrap_or_default()) {
Ok(AppTrackerResponse::NewFromRemote { package_id }) => {
if let Some(install_from) = state.requested_packages.remove(&package_id)
{
if install_from == source.node {
// auto-take zip from payload and request ourself with New
let _ = send_request(
&our,
&Request {
inherit: true, // will inherit payload!
expects_response: None,
ipc: Some(
serde_json::to_string(&AppTrackerRequest::New {
package: package_id.package().into(),
mirror: true,
})
.unwrap(),
),
metadata: None,
},
None,
None,
);
} else {
print_to_terminal(
0,
&format!(
"app-store: got install response from bad source: {}",
install_from
),
);
}
}
}
err => {
print_to_terminal(
0,
&format!("app-store: got unexpected response {:?}", err),
);
}
}
}
}
}
}
}

View File

@ -1,5 +0,0 @@
{
"package": "app_tracker",
"publisher": "uqbar",
"desc": "A package manager + app store. This JSON field is optional and you can add whatever you want in addition to this."
}

View File

@ -1,15 +0,0 @@
### App Tracker: our built-in package manager that lives in userspace
*note: 'app' and 'package' will be used interchangeably, but they are the same thing. generally, end users should see 'apps', and developers and the system itself should see 'packages'*
### Backend
Tracker requires full read-write to filesystem, along with caps for every other distro app and runtime module. It takes all the caps because it needs the ability to grant them to packages we install!
In order to load in the currently installed packages, Tracker will access the VFS and read from a hardcoded set of
### Frontend
Tracker will present a frontend that shows all the apps you currently have installed. You can see some metadata about them, and uninstall them from this list.
Tracker will contain a "store" to browse for new apps to install. TODO

View File

@ -1,336 +0,0 @@
cargo_component_bindings::generate!();
use bindings::{
component::uq_process::types::*, get_capability, get_payload, print_to_terminal, receive,
send_request, send_response, Guest,
};
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
#[allow(dead_code)]
mod kernel_types;
use kernel_types as kt;
#[allow(dead_code)]
mod process_lib;
mod transfer_lib;
struct Component;
#[derive(Debug, Serialize, Deserialize)]
pub enum AppTrackerRequest {
New { package: String },
Install { package: String },
}
#[derive(Debug, Serialize, Deserialize)]
pub enum AppTrackerResponse {
New { package: String },
Install { package: String },
Error { error: String },
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PackageManifestEntry {
pub process_name: String,
pub process_wasm_path: String,
pub on_panic: kt::OnPanic,
pub request_networking: bool,
pub request_messaging: Vec<String>,
pub public: bool,
}
fn parse_command(our: &Address, request_string: String) -> anyhow::Result<AppTrackerResponse> {
match serde_json::from_str(&request_string)? {
AppTrackerRequest::New { package } => {
let Some(payload) = get_payload() else {
return Err(anyhow::anyhow!("no payload"));
};
let vfs_address = Address {
node: our.node.clone(),
process: ProcessId::from_str("vfs:sys:uqbar").unwrap(),
};
let _ = process_lib::send_and_await_response(
&vfs_address,
false,
Some(
serde_json::to_string(&kt::VfsRequest {
drive: package.clone(),
action: kt::VfsAction::New,
})
.unwrap(),
),
None,
None,
5,
)?;
// add zip bytes
let _ = process_lib::send_and_await_response(
&vfs_address,
true,
Some(
serde_json::to_string(&kt::VfsRequest {
drive: package.clone(),
action: kt::VfsAction::Add {
full_path: package.clone().into(),
entry_type: kt::AddEntryType::ZipArchive,
},
})
.unwrap(),
),
None,
Some(&payload),
5,
)?;
Ok(AppTrackerResponse::New { package })
}
AppTrackerRequest::Install { package } => {
let vfs_address = Address {
node: our.node.clone(),
process: ProcessId::from_str("vfs:sys:uqbar").unwrap(),
};
let _ = process_lib::send_and_await_response(
&vfs_address,
false,
Some(
serde_json::to_string(&kt::VfsRequest {
drive: package.clone(),
action: kt::VfsAction::GetEntry("/manifest.json".into()),
})
.unwrap(),
),
None,
None,
5,
)?;
let Some(payload) = get_payload() else {
return Err(anyhow::anyhow!("no payload"));
};
let manifest = String::from_utf8(payload.bytes)?;
let manifest = serde_json::from_str::<Vec<PackageManifestEntry>>(&manifest).unwrap();
for entry in manifest {
let path = if entry.process_wasm_path.starts_with("/") {
entry.process_wasm_path
} else {
format!("/{}", entry.process_wasm_path)
};
let (_, hash_response) = process_lib::send_and_await_response(
&vfs_address,
false,
Some(
serde_json::to_string(&kt::VfsRequest {
drive: package.clone(),
action: kt::VfsAction::GetHash(path.clone()),
})
.unwrap(),
),
None,
None,
5,
)?;
let Message::Response((Response { ipc: Some(ipc), .. }, _)) = hash_response else {
return Err(anyhow::anyhow!("bad vfs response"));
};
let kt::VfsResponse::GetHash(Some(hash)) = serde_json::from_str(&ipc).unwrap() else {
return Err(anyhow::anyhow!("no hash in vfs"));
};
// build initial caps
let mut initial_capabilities: HashSet<kt::SignedCapability> = HashSet::new();
if entry.request_networking {
let Some(networking_cap) = get_capability(
&Address {
node: our.node.clone(),
process: ProcessId::from_str("kernel:sys:uqbar").unwrap(),
},
&"\"network\"".to_string(),
) else {
return Err(anyhow::anyhow!("app_tracker: no net cap"));
};
initial_capabilities.insert(kt::de_wit_signed_capability(networking_cap));
}
let Some(read_cap) = get_capability(
&vfs_address.clone(),
&serde_json::to_string(&serde_json::json!({
"kind": "read",
"drive": package,
})).unwrap(),
) else {
return Err(anyhow::anyhow!("app_tracker: no read cap"));
};
initial_capabilities.insert(kt::de_wit_signed_capability(read_cap));
let Some(write_cap) = get_capability(
&vfs_address.clone(),
&serde_json::to_string(&serde_json::json!({
"kind": "write",
"drive": package,
})).unwrap(),
) else {
return Err(anyhow::anyhow!("app_tracker: no write cap"));
};
initial_capabilities.insert(kt::de_wit_signed_capability(write_cap));
let mut public = false;
let entry_process_id = match ProcessId::from_str(
&[entry.process_name.clone(), ":".into(), package.clone()].concat(),
) {
Ok(process_id) => process_id,
Err(_) => {
return Err(anyhow::anyhow!("app_tracker: invalid process id!"));
}
};
for process_name in &entry.request_messaging {
let Ok(parsed_process_id) = ProcessId::from_str(&process_name) else {
// TODO handle arbitrary caps here
continue;
};
let Some(messaging_cap) = get_capability(
&Address {
node: our.node.clone(),
process: parsed_process_id.clone(),
},
&"\"messaging\"".into()
) else {
print_to_terminal(0, &format!("app_tracker: no cap for {} to give away!", process_name));
continue;
};
initial_capabilities.insert(kt::de_wit_signed_capability(messaging_cap));
}
let process_id = format!("{}:{}", entry.process_name, package.clone());
let Ok(parsed_new_process_id) = ProcessId::from_str(&process_id) else {
return Err(anyhow::anyhow!("app_tracker: invalid process id!"));
};
let _ = process_lib::send_request(
&Address {
node: our.node.clone(),
process: ProcessId::from_str("kernel:sys:uqbar").unwrap(),
},
false,
Some(
serde_json::to_string(&kt::KernelCommand::KillProcess(
kt::ProcessId::de_wit(parsed_new_process_id.clone()),
))
.unwrap(),
),
None,
None,
None,
);
// kernel start process takes bytes as payload + wasm_bytes_handle...
// reconsider perhaps
let (_, _bytes_response) = process_lib::send_and_await_response(
&vfs_address,
false,
Some(
serde_json::to_string(&kt::VfsRequest {
drive: package.clone(),
action: kt::VfsAction::GetEntry(path),
})
.unwrap(),
),
None,
None,
5,
)?;
let Some(payload) = get_payload() else {
return Err(anyhow::anyhow!("no wasm bytes payload."));
};
let _ = process_lib::send_and_await_response(
&Address {
node: our.node.clone(),
process: ProcessId::from_str("kernel:sys:uqbar").unwrap(),
},
false,
Some(
serde_json::to_string(&kt::KernelCommand::StartProcess {
id: kt::ProcessId::de_wit(parsed_new_process_id),
wasm_bytes_handle: hash,
on_panic: entry.on_panic,
initial_capabilities,
public,
})
.unwrap(),
),
None,
Some(&payload),
5,
)?;
}
Ok(AppTrackerResponse::Install { package })
}
}
}
impl Guest for Component {
fn init(our: Address) {
assert_eq!(our.process.to_string(), "app_tracker:app_tracker:uqbar");
print_to_terminal(0, &format!("app_tracker: start"));
loop {
let message = match receive() {
Ok((source, message)) => {
if our.node != source.node {
continue;
}
message
}
Err((error, _context)) => {
print_to_terminal(0, &format!("net error: {:?}!", error.kind));
continue;
}
};
match message {
Message::Request(Request {
ipc,
expects_response,
metadata,
..
}) => {
let Some(command) = ipc else {
continue;
};
match parse_command(&our, command) {
Ok(response) => {
if let Some(_) = expects_response {
let _ = send_response(
&Response {
ipc: Some(serde_json::to_string(&response).unwrap()),
metadata,
},
None,
);
};
}
Err(e) => {
print_to_terminal(0, &format!("app_tracker: got error {}", e));
if let Some(_) = expects_response {
let error = AppTrackerResponse::Error {
error: format!("{}", e),
};
let _ = send_response(
&Response {
ipc: Some(serde_json::to_string(&error).unwrap()),
metadata,
},
None,
);
};
}
}
}
_ => continue,
}
}
}
}

View File

@ -6,7 +6,7 @@
"request_networking": false,
"request_messaging": [
"http_bindings:http_bindings:uqbar",
"app_tracker:app_tracker:uqbar",
"main:app_store:uqbar",
"http_server:sys:uqbar"
],
"public": false

View File

@ -1971,7 +1971,7 @@ async fn make_event_loop(
t::Printout {
verbosity: 0,
content: format!(
"event loop: process {:?} doesn't have capability to send networked messages",
"event loop: process {} doesn't have capability to send networked messages",
kernel_message.source.process
)
}
@ -2007,7 +2007,7 @@ async fn make_event_loop(
t::Printout {
verbosity: 0,
content: format!(
"event loop: process {:?} doesn't have capability to message process {:?}",
"event loop: process {} doesn't have capability to message process {}",
kernel_message.source.process, kernel_message.target.process
)
}

View File

@ -3,6 +3,49 @@ use serde::{Deserialize, Serialize};
use super::bindings::component::uq_process::types::*;
use super::bindings::{Address, Payload, ProcessId, SendError};
#[derive(Hash, Eq, PartialEq, Debug, Clone, Serialize, Deserialize)]
pub struct PackageId {
pub package_name: String,
pub publisher_node: String,
}
impl PackageId {
pub fn new(package_name: &str, publisher_node: &str) -> Self {
PackageId {
package_name: package_name.into(),
publisher_node: publisher_node.into(),
}
}
pub fn from_str(input: &str) -> Result<Self, ProcessIdParseError> {
// split string on colons into 2 segments
let mut segments = input.split(':');
let package_name = segments
.next()
.ok_or(ProcessIdParseError::MissingField)?
.to_string();
let publisher_node = segments
.next()
.ok_or(ProcessIdParseError::MissingField)?
.to_string();
if segments.next().is_some() {
return Err(ProcessIdParseError::TooManyColons);
}
Ok(PackageId {
package_name,
publisher_node,
})
}
pub fn to_string(&self) -> String {
[self.package_name.as_str(), self.publisher_node.as_str()].join(":")
}
pub fn package(&self) -> &str {
&self.package_name
}
pub fn publisher_node(&self) -> &str {
&self.publisher_node
}
}
#[allow(dead_code)]
impl ProcessId {
/// generates a random u64 number if process_name is not declared
@ -80,6 +123,28 @@ pub enum ProcessIdParseError {
MissingField,
}
impl std::fmt::Display for ProcessIdParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
ProcessIdParseError::TooManyColons => "Too many colons in ProcessId string",
ProcessIdParseError::MissingField => "Missing field in ProcessId string",
}
)
}
}
impl std::error::Error for ProcessIdParseError {
fn description(&self) -> &str {
match self {
ProcessIdParseError::TooManyColons => "Too many colons in ProcessId string",
ProcessIdParseError::MissingField => "Missing field in ProcessId string",
}
}
}
pub fn send_and_await_response(
target: &Address,
inherit: bool,