diff options
author | Aram Drevekenin <aram@poor.dev> | 2023-08-09 22:26:00 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-08-09 22:26:00 +0200 |
commit | 1bedfc90021558cb201695444107afe5bddd2c17 (patch) | |
tree | 38dd31b5ab112aa9b1c3a54edb07331013bf7d87 /zellij-server | |
parent | c3e140cb4b3c0897329bf07ee7f51e9fd402b3df (diff) |
feat(plugins): use protocol buffers for serializing across the wasm boundary (#2686)
* work
* almost done with command protobuffers
* done translating command data structures
* mid transferring of every command to protobuff command
* transferred plugin_command.rs, now moving on to shim.rs
* plugin command working with protobufs
* protobuffers in update
* protobuf event tests
* various TODOs and comments
* fix zellij-tile
* clean up prost deps
* remove version mismatch error
* fix panic
* some cleanups
* clean up event protobuffers
* clean up command protobuffers
* clean up various protobufs
* refactor protobufs
* update comments
* some transformation fixes
* use protobufs for workers
* style(fmt): rustfmt
* style(fmt): rustfmt
* chore(build): add protoc
* chore(build): authenticate protoc
Diffstat (limited to 'zellij-server')
7 files changed, 494 insertions, 754 deletions
diff --git a/zellij-server/src/plugins/plugin_loader.rs b/zellij-server/src/plugins/plugin_loader.rs index 73957cf7c..92a4c0480 100644 --- a/zellij-server/src/plugins/plugin_loader.rs +++ b/zellij-server/src/plugins/plugin_loader.rs @@ -1,27 +1,28 @@ use crate::plugins::plugin_map::{PluginEnv, PluginMap, RunningPlugin, Subscriptions}; use crate::plugins::plugin_worker::{plugin_worker, RunningWorker}; -use crate::plugins::zellij_exports::{wasi_read_string, wasi_write_object, zellij_exports}; +use crate::plugins::zellij_exports::{wasi_write_object, zellij_exports}; use crate::plugins::PluginId; use highway::{HighwayHash, PortableHash}; use log::info; -use semver::Version; use std::{ collections::{HashMap, HashSet}, - fmt, fs, + fs, path::PathBuf, sync::{Arc, Mutex}, }; use url::Url; use wasmer::{ChainableNamedResolver, Instance, Module, Store}; use wasmer_wasi::{Pipe, WasiState}; +use zellij_utils::prost::Message; use crate::{ logging_pipe::LoggingPipe, screen::ScreenInstruction, thread_bus::ThreadSenders, ui::loading_indication::LoadingIndication, ClientId, }; +use zellij_utils::plugin_api::action::ProtobufPluginConfiguration; use zellij_utils::{ - consts::{VERSION, ZELLIJ_CACHE_DIR, ZELLIJ_SESSION_CACHE_DIR, ZELLIJ_TMP_DIR}, + consts::{ZELLIJ_CACHE_DIR, ZELLIJ_SESSION_CACHE_DIR, ZELLIJ_TMP_DIR}, data::PluginCapabilities, errors::prelude::*, input::command::TerminalAction, @@ -43,116 +44,6 @@ macro_rules! display_loading_stage { }}; } -/// Custom error for plugin version mismatch. -/// -/// This is thrown when, during starting a plugin, it is detected that the plugin version doesn't -/// match the zellij version. This is treated as a fatal error and leads to instantaneous -/// termination. -#[derive(Debug)] -pub struct VersionMismatchError { - zellij_version: String, - plugin_version: String, - plugin_path: PathBuf, - // true for builtin plugins - builtin: bool, -} - -impl std::error::Error for VersionMismatchError {} - -impl VersionMismatchError { - pub fn new( - zellij_version: &str, - plugin_version: &str, - plugin_path: &PathBuf, - builtin: bool, - ) -> Self { - VersionMismatchError { - zellij_version: zellij_version.to_owned(), - plugin_version: plugin_version.to_owned(), - plugin_path: plugin_path.to_owned(), - builtin, - } - } -} - -impl fmt::Display for VersionMismatchError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let first_line = if self.builtin { - "It seems your version of zellij was built with outdated core plugins." - } else { - "If you're seeing this error a plugin version doesn't match the current -zellij version." - }; - - write!( - f, - "{} -Detected versions: - -- Plugin version: {} -- Zellij version: {} -- Offending plugin: {} - -If you're a user: - Please contact the distributor of your zellij version and report this error - to them. - -If you're a developer: - Please run zellij with updated plugins. The easiest way to achieve this - is to build zellij with `cargo xtask install`. Also refer to the docs: - https://github.com/zellij-org/zellij/blob/main/CONTRIBUTING.md#building -", - first_line, - self.plugin_version.trim_end(), - self.zellij_version.trim_end(), - self.plugin_path.display() - ) - } -} - -// Returns `Ok` if the plugin version matches the zellij version. -// Returns an `Err` otherwise. -fn assert_plugin_version(instance: &Instance, plugin_env: &PluginEnv) -> Result<()> { - let err_context = || { - format!( - "failed to determine plugin version for plugin {}", - plugin_env.plugin.path.display() - ) - }; - - let plugin_version_func = match instance.exports.get_function("plugin_version") { - Ok(val) => val, - Err(_) => { - return Err(anyError::new(VersionMismatchError::new( - VERSION, - "Unavailable", - &plugin_env.plugin.path, - plugin_env.plugin.is_builtin(), - ))) - }, - }; - - let plugin_version = plugin_version_func - .call(&[]) - .map_err(anyError::new) - .and_then(|_| wasi_read_string(&plugin_env.wasi_env)) - .and_then(|string| Version::parse(&string).context("failed to parse plugin version")) - .with_context(err_context)?; - let zellij_version = Version::parse(VERSION) - .context("failed to parse zellij version") - .with_context(err_context)?; - if plugin_version != zellij_version { - return Err(anyError::new(VersionMismatchError::new( - VERSION, - &plugin_version.to_string(), - &plugin_env.plugin.path, - plugin_env.plugin.is_builtin(), - ))); - } - - Ok(()) -} - pub struct PluginLoader<'a> { plugin_cache: Arc<Mutex<HashMap<PathBuf, Module>>>, plugin_path: PathBuf, @@ -645,10 +536,8 @@ impl<'a> PluginLoader<'a> { &mut self, module: Module, ) -> Result<(Instance, PluginEnv, Arc<Mutex<Subscriptions>>)> { - let err_context = || format!("Failed to create environment for plugin"); let (instance, plugin_env, subscriptions) = self.create_plugin_instance_env_and_subscriptions(&module)?; - assert_plugin_version(&instance, &plugin_env).with_context(err_context)?; // Only do an insert when everything went well! let cloned_plugin = self.plugin.clone(); self.plugin_cache @@ -724,9 +613,17 @@ impl<'a> PluginLoader<'a> { } start_function.call(&[]).with_context(err_context)?; + let protobuf_plugin_configuration: ProtobufPluginConfiguration = self + .plugin + .userspace_configuration + .clone() + .try_into() + .map_err(|e| anyhow!("Failed to serialize user configuration: {:?}", e))?; + let protobuf_bytes = protobuf_plugin_configuration.encode_to_vec(); wasi_write_object( &plugin_env.wasi_env, - &self.plugin.userspace_configuration.inner(), + &protobuf_bytes, + // &self.plugin.userspace_configuration.inner(), ) .with_context(err_context)?; load_function.call(&[]).with_context(err_context)?; diff --git a/zellij-server/src/plugins/plugin_worker.rs b/zellij-server/src/plugins/plugin_worker.rs index bc7303c7c..9aae0bab8 100644 --- a/zellij-server/src/plugins/plugin_worker.rs +++ b/zellij-server/src/plugins/plugin_worker.rs @@ -1,4 +1,3 @@ -use crate::plugins::plugin_loader::VersionMismatchError; use crate::plugins::plugin_map::PluginEnv; use crate::plugins::zellij_exports::wasi_write_object; use wasmer::Instance; @@ -6,7 +5,9 @@ use wasmer::Instance; use zellij_utils::async_channel::{unbounded, Receiver, Sender}; use zellij_utils::async_std::task; use zellij_utils::errors::prelude::*; -use zellij_utils::{consts::VERSION, input::plugins::PluginConfig}; +use zellij_utils::input::plugins::PluginConfig; +use zellij_utils::plugin_api::message::ProtobufMessage; +use zellij_utils::prost::Message; pub struct RunningWorker { pub instance: Instance, @@ -31,29 +32,19 @@ impl RunningWorker { } pub fn send_message(&self, message: String, payload: String) -> Result<()> { let err_context = || format!("Failed to send message to worker"); - + let protobuf_message = ProtobufMessage { + name: message, + payload, + ..Default::default() + }; + let protobuf_bytes = protobuf_message.encode_to_vec(); let work_function = self .instance .exports .get_function(&self.name) .with_context(err_context)?; - wasi_write_object(&self.plugin_env.wasi_env, &(message, payload)) - .with_context(err_context)?; - work_function.call(&[]).or_else::<anyError, _>(|e| { - match e.downcast::<serde_json::Error>() { - Ok(_) => panic!( - "{}", - anyError::new(VersionMismatchError::new( - VERSION, - "Unavailable", - &self.plugin_config.path, - self.plugin_config.is_builtin(), - )) - ), - Err(e) => Err(e).with_context(err_context), - } - })?; - + wasi_write_object(&self.plugin_env.wasi_env, &protobuf_bytes).with_context(err_context)?; + work_function.call(&[]).with_context(err_context)?; Ok(()) } } diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__go_to_tab_name_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__go_to_tab_name_plugin_command.snap index 7934131f4..a66449c60 100644 --- a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__go_to_tab_name_plugin_command.snap +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__go_to_tab_name_plugin_command.snap @@ -1,11 +1,11 @@ --- source: zellij-server/src/plugins/./unit/plugin_tests.rs -assertion_line: 2334 +assertion_line: 2709 expression: "format!(\"{:#?}\", new_tab_event)" --- Some( GoToTabName( - "my tab name\n\r", + "my tab name", ( [], [], diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__send_configuration_to_plugins.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__send_configuration_to_plugins.snap index 01cb4b6b4..cfc94db2a 100644 --- a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__send_configuration_to_plugins.snap +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__send_configuration_to_plugins.snap @@ -1,11 +1,11 @@ --- source: zellij-server/src/plugins/./unit/plugin_tests.rs -assertion_line: 4220 +assertion_line: 4225 expression: "format!(\"{:#?}\", go_to_tab_event)" --- Some( GoToTabName( - "{\"fake_config_key_1\": \"fake_config_value_1\", \"fake_config_key_2\": \"fake_config_value_2\"}\n\r", + "{\"fake_config_key_1\": \"fake_config_value_1\", \"fake_config_key_2\": \"fake_config_value_2\"}", ( [], [], diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__write_chars_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__write_chars_plugin_command.snap index 504134d01..8c881def6 100644 --- a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__write_chars_plugin_command.snap +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__write_chars_plugin_command.snap @@ -1,6 +1,6 @@ --- source: zellij-server/src/plugins/./unit/plugin_tests.rs -assertion_line: 1171 +assertion_line: 1449 expression: "format!(\"{:#?}\", new_tab_event)" --- Some( @@ -9,8 +9,6 @@ Some( 102, 111, 111, - 10, - 13, ], 1, ), diff --git a/zellij-server/src/plugins/wasm_bridge.rs b/zellij-server/src/plugins/wasm_bridge.rs index 5ae23be4f..d6fcf1fc6 100644 --- a/zellij-server/src/plugins/wasm_bridge.rs +++ b/zellij-server/src/plugins/wasm_bridge.rs @@ -1,5 +1,5 @@ use super::{PluginId, PluginInstruction}; -use crate::plugins::plugin_loader::{PluginLoader, VersionMismatchError}; +use crate::plugins::plugin_loader::PluginLoader; use crate::plugins::plugin_map::{AtomicEvent, PluginEnv, PluginMap, RunningPlugin, Subscriptions}; use crate::plugins::plugin_worker::MessageToWorker; use crate::plugins::watch_filesystem::watch_filesystem; @@ -14,13 +14,15 @@ use std::{ use wasmer::{Instance, Module, Store, Value}; use zellij_utils::async_std::task::{self, JoinHandle}; use zellij_utils::notify_debouncer_full::{notify::RecommendedWatcher, Debouncer, FileIdMap}; +use zellij_utils::plugin_api::event::ProtobufEvent; + +use zellij_utils::prost::Message; use crate::{ background_jobs::BackgroundJob, screen::ScreenInstruction, thread_bus::ThreadSenders, ui::loading_indication::LoadingIndication, ClientId, }; use zellij_utils::{ - consts::VERSION, data::{Event, EventType, PluginCapabilities}, errors::prelude::*, input::{ @@ -737,26 +739,17 @@ pub fn apply_event_to_plugin( plugin_bytes: &mut Vec<(PluginId, ClientId, Vec<u8>)>, ) -> Result<()> { let err_context = || format!("Failed to apply event to plugin {plugin_id}"); + let protobuf_event: ProtobufEvent = event + .clone() + .try_into() + .map_err(|e| anyhow!("Failed to convert to protobuf: {:?}", e))?; let update = instance .exports .get_function("update") .with_context(err_context)?; - wasi_write_object(&plugin_env.wasi_env, &event).with_context(err_context)?; - let update_return = - update - .call(&[]) - .or_else::<anyError, _>(|e| match e.downcast::<serde_json::Error>() { - Ok(_) => panic!( - "{}", - anyError::new(VersionMismatchError::new( - VERSION, - "Unavailable", - &plugin_env.plugin.path, - plugin_env.plugin.is_builtin(), - )) - ), - Err(e) => Err(e).with_context(err_context), - })?; + wasi_write_object(&plugin_env.wasi_env, &protobuf_event.encode_to_vec()) + .with_context(err_context)?; + let update_return = update.call(&[]).with_context(err_context)?; let should_render = match update_return.get(0) { Some(Value::I32(n)) => *n == 1, _ => false, diff --git a/zellij-server/src/plugins/zellij_exports.rs b/zellij-server/src/plugins/zellij_exports.rs index c73068470..275de13e6 100644 --- a/zellij-server/src/plugins/zellij_exports.rs +++ b/zellij-server/src/plugins/zellij_exports.rs @@ -3,7 +3,7 @@ use crate::plugins::plugin_map::{PluginEnv, Subscriptions}; use crate::plugins::wasm_bridge::handle_plugin_crash; use crate::route::route_action; use log::{debug, warn}; -use serde::{de::DeserializeOwned, Serialize}; +use serde::Serialize; use std::{ collections::{BTreeMap, HashSet}, path::PathBuf, @@ -21,7 +21,10 @@ use crate::{panes::PaneId, screen::ScreenInstruction}; use zellij_utils::{ consts::VERSION, - data::{Direction, Event, EventType, InputMode, PluginIds, Resize}, + data::{ + CommandToRun, Direction, Event, EventType, FileToOpen, InputMode, PluginCommand, PluginIds, + PluginMessage, Resize, ResizeStrategy, + }, errors::prelude::*, input::{ actions::Action, @@ -29,6 +32,11 @@ use zellij_utils::{ layout::{Layout, PluginUserConfiguration, RunPlugin, RunPluginLocation}, plugins::PluginType, }, + plugin_api::{ + plugin_command::ProtobufPluginCommand, + plugin_ids::{ProtobufPluginIds, ProtobufZellijVersion}, + }, + prost::Message, serde, }; @@ -53,87 +61,13 @@ pub fn zellij_exports( plugin_env: &PluginEnv, subscriptions: &Arc<Mutex<Subscriptions>>, ) -> ImportObject { - macro_rules! zellij_export { - ($($host_function:ident),+ $(,)?) => { - imports! { - "zellij" => { - $(stringify!($host_function) => - Function::new_native_with_env(store, ForeignFunctionEnv::new(plugin_env, subscriptions), $host_function),)+ - } - } + imports! { + "zellij" => { + "host_run_plugin_command" => { + Function::new_native_with_env(store, ForeignFunctionEnv::new(plugin_env, subscriptions), host_run_plugin_command) + } } } - - zellij_export! { - host_subscribe, - host_unsubscribe, - host_set_selectable, - host_get_plugin_ids, - host_get_zellij_version, - host_open_file, - host_open_file_floating, - host_open_file_with_line, - host_open_file_with_line_floating, - host_open_terminal, - host_open_terminal_floating, - host_open_command_pane, - host_open_command_pane_floating, - host_switch_tab_to, - host_set_timeout, - host_exec_cmd, - host_report_panic, - host_post_message_to, - host_post_message_to_plugin, - host_hide_self, - host_show_self, - host_switch_to_mode, - host_new_tabs_with_layout, - host_new_tab, - host_go_to_next_tab, - host_go_to_previous_tab, - host_resize, - host_resize_with_direction, - host_focus_next_pane, - host_focus_previous_pane, - host_move_focus, - host_move_focus_or_tab, - host_detach, - host_edit_scrollback, - host_write, - host_write_chars, - host_toggle_tab, - host_move_pane, - host_move_pane_with_direction, - host_clear_screen, - host_scroll_up, - host_scroll_down, - host_scroll_to_top, - host_scroll_to_bottom, - host_page_scroll_up, - host_page_scroll_down, - host_toggle_focus_fullscreen, - host_toggle_pane_frames, - host_toggle_pane_embed_or_eject, - host_undo_rename_pane, - host_close_focus, - host_toggle_active_tab_sync, - host_close_focused_tab, - host_undo_rename_tab, - host_quit_zellij, - host_previous_swap_layout, - host_next_swap_layout, - host_go_to_tab_name, - host_focus_or_create_tab, - host_go_to_tab, - host_start_or_reload_plugin, - host_close_terminal_pane, - host_close_plugin_pane, - host_focus_terminal_pane, - host_focus_plugin_pane, - host_rename_terminal_pane, - host_rename_plugin_pane, - host_rename_tab, - } } #[derive(WasmerEnv, Clone)] @@ -151,42 +85,151 @@ impl ForeignFunctionEnv { } } -fn host_subscribe(env: &ForeignFunctionEnv) { - wasi_read_object::<HashSet<EventType>>(&env.plugin_env.wasi_env) - .and_then(|new| { - env.subscriptions.lock().to_anyhow()?.extend(new.clone()); - Ok(new) - }) - .and_then(|new| { - env.plugin_env - .senders - .send_to_plugin(PluginInstruction::PluginSubscribedToEvents( - env.plugin_env.plugin_id, - env.plugin_env.client_id, - new, - )) +fn host_run_plugin_command(env: &ForeignFunctionEnv) { + wasi_read_bytes(&env.plugin_env.wasi_env) + .and_then(|bytes| { + let command: ProtobufPluginCommand = ProtobufPluginCommand::decode(bytes.as_slice())?; + let command: PluginCommand = command + .try_into() + .map_err(|e| anyhow!("failed to convert serialized command: {}", e))?; + match command { + PluginCommand::Subscribe(event_list) => subscribe(env, event_list)?, + PluginCommand::Unsubscribe(event_list) => unsubscribe(env, event_list)?, + PluginCommand::SetSelectable(selectable) => set_selectable(env, selectable), + PluginCommand::GetPluginIds => get_plugin_ids(env), + PluginCommand::GetZellijVersion => get_zellij_version(env), + PluginCommand::OpenFile(file_to_open) => open_file(env, file_to_open), + PluginCommand::OpenFileFloating(file_to_open) => { + open_file_floating(env, file_to_open) + }, + PluginCommand::OpenTerminal(cwd) => open_terminal(env, cwd.path.try_into()?), + PluginCommand::OpenTerminalFloating(cwd) => { + open_terminal_floating(env, cwd.path.try_into()?) + }, + PluginCommand::OpenCommandPane(command_to_run) => { + open_command_pane(env, command_to_run) + }, + PluginCommand::OpenCommandPaneFloating(command_to_run) => { + open_command_pane_floating(env, command_to_run) + }, + PluginCommand::SwitchTabTo(tab_index) => switch_tab_to(env, tab_index), + PluginCommand::SetTimeout(seconds) => set_timeout(env, seconds), + PluginCommand::ExecCmd(command_line) => exec_cmd(env, command_line), + PluginCommand::PostMessageTo(plugin_message) => { + post_message_to(env, plugin_message)? + }, + PluginCommand::PostMessageToPlugin(plugin_message) => { + post_message_to_plugin(env, plugin_message)? + }, + PluginCommand::HideSelf => hide_self(env)?, + PluginCommand::ShowSelf(should_float_if_hidden) => { + show_self(env, should_float_if_hidden) + }, + PluginCommand::SwitchToMode(input_mode) => { + switch_to_mode(env, input_mode.try_into()?) + }, + PluginCommand::NewTabsWithLayout(raw_layout) => { + new_tabs_with_layout(env, &raw_layout)? + }, + PluginCommand::NewTab => new_tab(env), + PluginCommand::GoToNextTab => go_to_next_tab(env), + PluginCommand::GoToPreviousTab => go_to_previous_tab(env), + PluginCommand::Resize(resize_payload) => resize(env, resize_payload), + PluginCommand::ResizeWithDirection(resize_strategy) => { + resize_with_direction(env, resize_strategy) + }, + PluginCommand::FocusNextPane => focus_next_pane(env), + PluginCommand::FocusPreviousPane => focus_previous_pane(env), + PluginCommand::MoveFocus(direction) => move_focus(env, direction), + PluginCommand::MoveFocusOrTab(direction) => move_focus_or_tab(env, direction), + PluginCommand::Detach => detach(env), + PluginCommand::EditScrollback => edit_scrollback(env), + PluginCommand::Write(bytes) => write(env, bytes), + PluginCommand::WriteChars(chars) => write_chars(env, chars), + PluginCommand::ToggleTab => toggle_tab(env), + PluginCommand::MovePane => move_pane(env), + PluginCommand::MovePaneWithDirection(direction) => { + move_pane_with_direction(env, direction) + }, + PluginCommand::ClearScreen => clear_screen(env), + PluginCommand::ScrollUp => scroll_up(env), + PluginCommand::ScrollDown => scroll_down(env), + PluginCommand::ScrollToTop => scroll_to_top(env), + PluginCommand::ScrollToBottom => scroll_to_bottom(env), + PluginCommand::PageScrollUp => page_scroll_up(env), + PluginCommand::PageScrollDown => page_scroll_down(env), + PluginCommand::ToggleFocusFullscreen => toggle_focus_fullscreen(env), + PluginCommand::TogglePaneFrames => toggle_pane_frames(env), + PluginCommand::TogglePaneEmbedOrEject => toggle_pane_embed_or_eject(env), + PluginCommand::UndoRenamePane => undo_rename_pane(env), + PluginCommand::CloseFocus => close_focus(env), + PluginCommand::ToggleActiveTabSync => toggle_active_tab_sync(env), + PluginCommand::CloseFocusedTab => close_focused_tab(env), + PluginCommand::UndoRenameTab => undo_rename_tab(env), + PluginCommand::QuitZellij => quit_zellij(env), + PluginCommand::PreviousSwapLayout => previous_swap_layout(env), + PluginCommand::NextSwapLayout => next_swap_layout(env), |