use serde::{Deserialize, Serialize}; use std::{ collections::{HashMap, HashSet}, sync::Arc, }; use thiserror::Error; use tokio::sync::RwLock; // // internal message pipes between kernel and runtime modules // // keeps the from address so we know where to pipe error pub type NetworkErrorSender = tokio::sync::mpsc::Sender; pub type NetworkErrorReceiver = tokio::sync::mpsc::Receiver; pub type MessageSender = tokio::sync::mpsc::Sender; pub type MessageReceiver = tokio::sync::mpsc::Receiver; pub type PrintSender = tokio::sync::mpsc::Sender; pub type PrintReceiver = tokio::sync::mpsc::Receiver; pub type DebugSender = tokio::sync::mpsc::Sender; pub type DebugReceiver = tokio::sync::mpsc::Receiver; pub type CapMessageSender = tokio::sync::mpsc::UnboundedSender; pub type CapMessageReceiver = tokio::sync::mpsc::UnboundedReceiver; // // types used for UQI: uqbar's identity system // pub type PKINames = Arc>>; // TODO maybe U256 to String pub type OnchainPKI = Arc>>; #[derive(Debug, Serialize, Deserialize)] pub struct Registration { pub username: String, pub password: String, pub direct: bool, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Identity { pub name: String, pub networking_key: String, pub ws_routing: Option<(String, u16)>, pub allowed_routers: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct IdentityTransaction { pub from: String, pub signature: Option, pub to: String, // contract address pub town_id: u32, pub calldata: Identity, pub nonce: String, } // // process-facing kernel types, used for process // management and message-passing // matches types in uqbar.wit // pub type Context = String; // JSON-string #[derive(Clone, Debug, Eq, Hash, Serialize, Deserialize)] pub enum ProcessId { Id(u64), Name(String), } impl PartialEq for ProcessId { fn eq(&self, other: &Self) -> bool { match (self, other) { (ProcessId::Id(i1), ProcessId::Id(i2)) => i1 == i2, (ProcessId::Name(s1), ProcessId::Name(s2)) => s1 == s2, _ => false, } } } impl PartialEq<&str> for ProcessId { fn eq(&self, other: &&str) -> bool { match self { ProcessId::Id(_) => false, ProcessId::Name(s) => s == other, } } } impl PartialEq for ProcessId { fn eq(&self, other: &u64) -> bool { match self { ProcessId::Id(i) => i == other, ProcessId::Name(_) => false, } } } #[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] pub struct Address { pub node: String, pub process: ProcessId, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Payload { pub mime: Option, // MIME type pub bytes: Vec, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Request { pub inherit: bool, pub expects_response: Option, // number of seconds until timeout pub ipc: Option, // JSON-string pub metadata: Option, // JSON-string } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Response { pub ipc: Option, // JSON-string pub metadata: Option, // JSON-string } #[derive(Clone, Debug, Serialize, Deserialize)] pub enum Message { Request(Request), Response((Response, Option)), } #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct Capability { pub issuer: Address, pub params: String, // JSON-string } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct SignedCapability { pub issuer: Address, pub params: String, // JSON-string pub signature: Vec, // signed by the kernel, so we can verify that the kernel issued it } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct SendError { pub kind: SendErrorKind, pub target: Address, // what the message was trying to reach pub message: Message, pub payload: Option, } #[derive(Clone, Debug, Serialize, Deserialize)] pub enum SendErrorKind { Offline, Timeout, } #[derive(Clone, Debug, Serialize, Deserialize)] pub enum OnPanic { None, Restart, Requests(Vec<(Address, Request, Option)>), } impl OnPanic { pub fn is_restart(&self) -> bool { match self { OnPanic::None => false, OnPanic::Restart => true, OnPanic::Requests(_) => false, } } } // // kernel types that runtime modules use // #[derive(Clone, Debug, Serialize, Deserialize)] pub struct ProcessMetadata { pub our: Address, pub wasm_bytes_handle: u128, pub on_panic: OnPanic, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct KernelMessage { pub id: u64, pub source: Address, pub target: Address, pub rsvp: Rsvp, pub message: Message, pub payload: Option, pub signed_capabilities: Option>, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct WrappedSendError { pub id: u64, pub source: Address, pub error: SendError, } /// A terminal printout. Verbosity level is from low to high, and for /// now, only 0 and 1 are used. Level 0 is always printed, level 1 is /// only printed if the terminal is in verbose mode. Numbers greater /// than 1 are reserved for future use and will be ignored for now. pub struct Printout { pub verbosity: u8, pub content: String, } // kernel sets in case, e.g., // A requests response from B does not request response from C // -> kernel sets `Some(A) = Rsvp` for B's request to C pub type Rsvp = Option
; // // boot/startup specific types??? // #[derive(Debug, Serialize, Deserialize)] #[serde(tag = "type")] pub enum SequentializeRequest { QueueMessage(QueueMessage), RunQueue, } #[derive(Debug, Serialize, Deserialize)] pub struct QueueMessage { pub target: ProcessId, pub request: Request, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct BootOutboundRequest { pub target_process: ProcessId, pub json: Option, pub bytes: Option>, } #[derive(Debug, Serialize, Deserialize)] pub enum DebugCommand { Toggle, Step, } #[derive(Debug, Serialize, Deserialize)] pub enum KernelCommand { StartProcess { name: Option, wasm_bytes_handle: u128, on_panic: OnPanic, initial_capabilities: HashSet, }, KillProcess(ProcessId), // this is extrajudicial killing: we might lose messages! RebootProcess { // kernel only process_id: ProcessId, persisted: PersistedProcess, }, Shutdown, // capabilities creation GrantCapability { to_process: ProcessId, params: String, // JSON-string }, } pub enum CapMessage { Add { on: ProcessId, cap: Capability, }, Drop { // not used yet! on: ProcessId, cap: Capability, }, Has { // a bool is given in response here on: ProcessId, cap: Capability, responder: tokio::sync::oneshot::Sender, }, GetAll { on: ProcessId, responder: tokio::sync::oneshot::Sender>, }, } #[derive(Debug, Serialize, Deserialize)] pub enum KernelResponse { StartedProcess(ProcessMetadata), KilledProcess(ProcessId), } pub type ProcessMap = HashMap; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct PersistedProcess { pub wasm_bytes_handle: u128, pub on_panic: OnPanic, pub capabilities: HashSet, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct ProcessContext { // store ultimate in order to set prompting message if needed pub prompting_message: Option, // can be empty if a request doesn't set context, but still needs to inherit pub context: Option, } // // runtime-module-specific types // // // filesystem.rs types // #[derive(Serialize, Deserialize, Debug)] pub enum FsAction { Write, Replace(u128), WriteOffset((u128, u64)), Append(Option), Read(u128), ReadChunk(ReadChunkRequest), Delete(u128), Length(u128), SetLength((u128, u64)), GetState, SetState, } #[derive(Serialize, Deserialize, Debug)] pub struct ReadChunkRequest { pub file: u128, pub start: u64, pub length: u64, } #[derive(Serialize, Deserialize, Debug)] pub enum FsResponse { Write(u128), Read(u128), ReadChunk(u128), Append(u128), Delete(u128), Length(u64), GetState, SetState, } #[derive(Debug)] pub struct S3Config { pub access_key: String, pub secret_key: String, pub region: String, pub bucket: String, pub endpoint: String, } #[derive(Debug)] pub struct FsConfig { pub s3_config: Option, pub mem_buffer_limit: usize, pub chunk_size: usize, pub flush_to_cold_interval: usize, pub encryption: bool, pub cloud_enabled: bool, // pub flush_to_wal_interval: usize, } impl VfsError { pub fn kind(&self) -> &str { match *self { VfsError::BadIdentifier => "BadIdentifier", VfsError::BadDescriptor => "BadDescriptor", VfsError::NoCap => "NoCap", } } } #[derive(Debug, Serialize, Deserialize)] pub enum VfsError { BadIdentifier, BadDescriptor, NoCap, } impl FileSystemError { pub fn kind(&self) -> &str { match *self { FileSystemError::BadUri { .. } => "BadUri", FileSystemError::BadJson { .. } => "BadJson", FileSystemError::BadBytes { .. } => "BadBytes", FileSystemError::IllegalAccess { .. } => "IllegalAccess", FileSystemError::AlreadyOpen { .. } => "AlreadyOpen", FileSystemError::NotCurrentlyOpen { .. } => "NotCurrentlyOpen", FileSystemError::BadPathJoin { .. } => "BadPathJoin", FileSystemError::CouldNotMakeDir { .. } => "CouldNotMakeDir", FileSystemError::ReadFailed { .. } => "ReadFailed", FileSystemError::WriteFailed { .. } => "WriteFailed", FileSystemError::OpenFailed { .. } => "OpenFailed", FileSystemError::FsError { .. } => "FsError", FileSystemError::LFSError { .. } => "LFSErrror", } } } #[derive(Error, Debug, Serialize, Deserialize)] pub enum FileSystemError { // bad input from user #[error("Malformed URI: {uri}. Problem with {bad_part_name}: {:?}.", bad_part)] BadUri { uri: String, bad_part_name: String, bad_part: Option, }, #[error( "JSON payload could not be parsed to FileSystemRequest: {error}. Got {:?}.", json )] BadJson { json: String, error: String }, #[error("Bytes payload required for {action}.")] BadBytes { action: String }, #[error("{process_name} not allowed to access {attempted_dir}. Process may only access within {sandbox_dir}.")] IllegalAccess { process_name: String, attempted_dir: String, sandbox_dir: String, }, #[error("Already have {path} opened with mode {:?}.", mode)] AlreadyOpen { path: String, mode: FileSystemMode }, #[error("Don't have {path} opened with mode {:?}.", mode)] NotCurrentlyOpen { path: String, mode: FileSystemMode }, // path or underlying fs problems #[error("Failed to join path: base: '{base_path}'; addend: '{addend}'.")] BadPathJoin { base_path: String, addend: String }, #[error("Failed to create dir at {path}: {error}.")] CouldNotMakeDir { path: String, error: String }, #[error("Failed to read {path}: {error}.")] ReadFailed { path: String, error: String }, #[error("Failed to write {path}: {error}.")] WriteFailed { path: String, error: String }, #[error("Failed to open {path} for {:?}: {error}.", mode)] OpenFailed { path: String, mode: FileSystemMode, error: String, }, #[error("Filesystem error while {what} on {path}: {error}.")] FsError { what: String, path: String, error: String, }, #[error("LFS error: {error}.")] LFSError { error: String }, } #[derive(Debug, Serialize, Deserialize)] pub struct FileSystemRequest { pub uri_string: String, pub action: FileSystemAction, } #[derive(Debug, Serialize, Deserialize)] pub enum FileSystemAction { Read, Write, GetMetadata, ReadDir, Open(FileSystemMode), Close(FileSystemMode), Append, ReadChunkFromOpen(u64), SeekWithinOpen(FileSystemSeekFrom), } // copy of std::io::SeekFrom with Serialize/Deserialize #[derive(Debug, Serialize, Deserialize)] pub enum FileSystemSeekFrom { Start(u64), End(i64), Current(i64), } #[derive(Debug, Serialize, Deserialize)] pub enum FileSystemResponse { Read(FileSystemUriHash), Write(String), GetMetadata(FileSystemMetadata), ReadDir(Vec), Open { uri_string: String, mode: FileSystemMode, }, Close { uri_string: String, mode: FileSystemMode, }, Append(String), ReadChunkFromOpen(FileSystemUriHash), SeekWithinOpen(String), } #[derive(Debug, Serialize, Deserialize)] pub struct FileSystemUriHash { pub uri_string: String, pub hash: u64, } #[derive(Debug, Serialize, Deserialize)] pub struct FileSystemMetadata { pub uri_string: String, pub hash: Option, pub entry_type: FileSystemEntryType, pub len: u64, } #[derive(Eq, Hash, PartialEq, Clone, Debug, Serialize, Deserialize)] pub enum FileSystemMode { Read, Append, AppendOverwrite, } #[derive(Debug, Serialize, Deserialize)] pub enum FileSystemEntryType { Symlink, File, Dir, } #[derive(Debug, Serialize, Deserialize)] pub enum VfsRequest { New { identifier: String, }, Add { identifier: String, full_path: String, entry_type: AddEntryType, }, Rename { identifier: String, full_path: String, new_full_path: String, }, Delete { identifier: String, full_path: String, }, WriteOffset { identifier: String, full_path: String, offset: u64, }, SetSize { identifier: String, full_path: String, size: u64, }, GetPath { identifier: String, hash: u128, }, GetEntry { identifier: String, full_path: String, }, GetFileChunk { identifier: String, full_path: String, offset: u64, length: u64, }, GetEntryLength { identifier: String, full_path: String, }, } #[derive(Debug, Serialize, Deserialize)] pub enum AddEntryType { Dir, NewFile, // add a new file to fs and add name in vfs ExistingFile { hash: u128 }, // link an existing file in fs to a new name in vfs // ... // symlinks? } #[derive(Debug, Serialize, Deserialize)] pub enum GetEntryType { Dir, File, } #[derive(Debug, Serialize, Deserialize)] pub enum VfsResponse { New { identifier: String, }, Add { identifier: String, full_path: String, }, Rename { identifier: String, new_full_path: String, }, Delete { identifier: String, full_path: String, }, WriteOffset { identifier: String, full_path: String, offset: u64, }, SetSize { identifier: String, full_path: String, size: u64, }, GetPath { identifier: String, hash: u128, full_path: Option, }, GetEntry { identifier: String, full_path: String, children: Vec, }, GetFileChunk { identifier: String, full_path: String, offset: u64, length: u64, }, GetEntryLength { identifier: String, full_path: String, length: u64, }, } #[derive(Debug, Serialize, Deserialize)] pub enum KeyValueMessage { New { identifier: String }, Write { identifier: String, key: Vec }, Read { identifier: String, key: Vec }, } impl KeyValueError { pub fn kind(&self) -> &str { match *self { KeyValueError::BadIdentifier => "BadIdentifier", KeyValueError::NoCap => "NoCap", KeyValueError::NoBytes => "NoBytes", } } } #[derive(Debug, Serialize, Deserialize)] pub enum KeyValueError { BadIdentifier, NoCap, NoBytes, } // // http_client.rs types // #[derive(Debug, Serialize, Deserialize)] pub struct HttpClientRequest { pub uri: String, pub method: String, pub headers: HashMap, } #[derive(Debug, Serialize, Deserialize)] pub struct HttpClientResponse { pub status: u16, pub headers: HashMap, } #[derive(Error, Debug, Serialize, Deserialize)] pub enum HttpClientError { #[error("http_client: rsvp is None but message is expecting response")] BadRsvp, #[error("http_client: no json in request")] NoJson, #[error( "http_client: JSON payload could not be parsed to HttpClientRequest: {error}. Got {:?}.", json )] BadJson { json: String, error: String }, #[error("http_client: http method not supported: {:?}", method)] BadMethod { method: String }, #[error("http_client: failed to execute request {:?}", error)] RequestFailed { error: String }, } impl HttpClientError { pub fn kind(&self) -> &str { match *self { HttpClientError::BadRsvp { .. } => "BadRsvp", HttpClientError::NoJson { .. } => "NoJson", HttpClientError::BadJson { .. } => "BadJson", HttpClientError::BadMethod { .. } => "BadMethod", HttpClientError::RequestFailed { .. } => "RequestFailed", } } } // // custom kernel displays // impl std::fmt::Display for KernelMessage { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, "{{\n id: {},\n source: {},\n target: {},\n rsvp: {:?},\n message: {}\n}}", self.id, self.source, self.target, self.rsvp, self.message, ) } } impl std::fmt::Display for Message { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { Message::Request(request) => write!( f, "Request(\n inherit: {},\n expects_response: {:#?},\n ipc: {},\n metadata: {}\n)", request.inherit, request.expects_response, &request.ipc.as_ref().unwrap_or(&"None".into()), &request.metadata.as_ref().unwrap_or(&"None".into()), ), Message::Response((response, context)) => write!( f, "Response(\n ipc: {},\n metadata: {},\n context: {}\n)", &response.ipc.as_ref().unwrap_or(&"None".into()), &response.metadata.as_ref().unwrap_or(&"None".into()), &context.as_ref().unwrap_or(&"None".into()), ), } } } // // http_server.rs types // #[derive(Debug, Serialize, Deserialize)] pub struct HttpResponse { pub status: u16, pub headers: HashMap, pub body: Option>, // TODO does this use a lot of memory? } #[derive(Error, Debug, Serialize, Deserialize)] pub enum HttpServerError { #[error("http_server: json is None")] NoJson, #[error("http_server: response not ok")] ResponseError, #[error("http_server: bytes are None")] NoBytes, #[error( "http_server: JSON payload could not be parsed to HttpClientRequest: {error}. Got {:?}.", json )] BadJson { json: String, error: String }, } impl HttpServerError { pub fn kind(&self) -> &str { match *self { HttpServerError::NoJson { .. } => "NoJson", HttpServerError::NoBytes { .. } => "NoBytes", HttpServerError::BadJson { .. } => "BadJson", HttpServerError::ResponseError { .. } => "ResponseError", } } } #[derive(Debug, Serialize, Deserialize)] pub struct JwtClaims { pub username: String, pub expiration: u64, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct WebSocketServerTarget { pub node: String, pub id: Option, } #[derive(Debug, Serialize, Deserialize)] pub struct WebSocketPush { pub target: WebSocketServerTarget, pub is_text: Option, } impl std::fmt::Display for Address { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, "{}", match self { Address { node, process: ProcessId::Id(id), } => format!("{}/{}", node, id), Address { node, process: ProcessId::Name(name), } => format!("{}/{}", node, name), } ) } } #[derive(Debug, Serialize, Deserialize)] pub struct ServerAction { pub action: String, } #[derive(Debug, Serialize, Deserialize)] pub enum HttpServerMessage { WebSocketPush(WebSocketPush), ServerAction(ServerAction), WsRegister(WsRegister), // Coming from a proxy WsProxyDisconnect(WsProxyDisconnect), // Coming from a proxy WsMessage(WsMessage), // Coming from a proxy EncryptedWsMessage(EncryptedWsMessage), // Coming from a proxy } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct WsRegister { pub ws_auth_token: String, pub auth_token: String, pub channel_id: String, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct WsProxyDisconnect { // Doesn't require auth because it's coming from the proxy pub channel_id: String, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct WsMessage { pub ws_auth_token: String, pub auth_token: String, pub channel_id: String, pub target: Address, pub json: Option, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct EncryptedWsMessage { pub ws_auth_token: String, pub auth_token: String, pub channel_id: String, pub target: Address, pub encrypted: String, // Encrypted JSON as hex with the 32-byte authentication tag appended pub nonce: String, // Hex of the 12-byte nonce } #[derive(Clone, Debug, Serialize, Deserialize)] pub enum WebSocketClientMessage { WsRegister(WsRegister), WsMessage(WsMessage), EncryptedWsMessage(EncryptedWsMessage), } // http_server End // encryptor Start #[derive(Clone, Debug, Serialize, Deserialize)] pub struct GetKeyAction { pub channel_id: String, pub public_key_hex: String, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct DecryptAndForwardAction { pub channel_id: String, pub forward_to: Address, // node, process pub json: Option, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct EncryptAndForwardAction { pub channel_id: String, pub forward_to: Address, // node, process pub json: Option, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct DecryptAction { pub channel_id: String, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct EncryptAction { pub channel_id: String, } #[derive(Debug, Serialize, Deserialize)] pub enum EncryptorMessage { GetKeyAction(GetKeyAction), DecryptAndForwardAction(DecryptAndForwardAction), EncryptAndForwardAction(EncryptAndForwardAction), DecryptAction(DecryptAction), EncryptAction(EncryptAction), } // encryptor End