#![deny(missing_docs)]
#![forbid(unsafe_code)]
pub mod attach;
pub mod capabilities;
pub mod codec;
pub mod control;
pub mod envelope;
pub mod error;
pub mod frame_kind;
pub mod identity;
pub mod request;
pub mod response;
pub mod types;
pub use attach::{
decode_attach_data_frame, decode_attach_data_frame_with_limit, encode_attach_data,
encode_attach_data_into_slice, encode_attach_message, AttachDataFrame, AttachFrameDecoder,
AttachMessage, AttachShellCommand, AttachedKeystroke, AttachedWindowsConsoleKey, KeyDispatched,
ATTACH_DATA_HEADER_LEN,
};
pub use capabilities::{
capabilities_for_features, HandshakeRequest, HandshakeResponse, CAPABILITY_ATTACH_RENDER,
CAPABILITY_ATTACH_RESIZE_GEOMETRY, CAPABILITY_ATTACH_STREAM,
CAPABILITY_ATTACH_WINDOWS_CONSOLE_KEY, CAPABILITY_CLI_CAPTURE_TARGET_ACTION,
CAPABILITY_CLI_LIST_WINDOWS_ALL_QUEUE, CAPABILITY_CLI_RUNTIME_COMMAND_EXPANSION,
CAPABILITY_CLI_TARGET_ACTIONS, CAPABILITY_CONTROL_STREAM, CAPABILITY_DAEMON_SHUTDOWN,
CAPABILITY_DAEMON_SHUTDOWN_IF_IDLE, CAPABILITY_DAEMON_STATUS, CAPABILITY_DETACHED_RPC,
CAPABILITY_FRAMED_ERRORS, CAPABILITY_HANDSHAKE, CAPABILITY_SDK_OWNED_SESSION_STABLE_IDENTITY,
CAPABILITY_SDK_PANE_BROADCAST, CAPABILITY_SDK_PANE_BY_ID, CAPABILITY_SDK_PANE_FOREGROUND,
CAPABILITY_SDK_PANE_OPTIONS, CAPABILITY_SDK_PANE_SPLIT_IDENTITY,
CAPABILITY_SDK_PANE_STATE_EVENTS, CAPABILITY_SDK_PROCESS_COMMAND, CAPABILITY_SDK_SESSION_LEASE,
CAPABILITY_SDK_SESSION_LEASE_BY_ID, CAPABILITY_SDK_SESSION_LEASE_BY_ID_V2,
CAPABILITY_SDK_WAITS, CAPABILITY_SDK_WAITS_ARMED, CAPABILITY_TARGET_CLIENT_COMMANDS,
CAPABILITY_WEB_SHARE, SUPPORTED_CAPABILITIES,
};
#[cfg(feature = "fuzzing")]
pub use codec::fuzz_detached_frame_decoder;
pub use codec::{
decode_frame, encode_frame, FrameDecoder, DEFAULT_MAX_DETACHED_FRAME_LENGTH,
DEFAULT_MAX_FRAME_LENGTH,
};
pub use control::{
format_continue_line, format_exit_line, format_extended_output_line, format_guard_line,
format_output_line, format_pause_line, octal_escape, ClientTerminalContext, ControlGuardKind,
ControlMode, ControlModeRequest, ControlModeResponse, CONTROL_BUFFER_HIGH, CONTROL_BUFFER_LOW,
CONTROL_CONTROL_END, CONTROL_CONTROL_START, CONTROL_MAXIMUM_AGE_MS, CONTROL_STDIN_EOF_MARKER,
CONTROL_WRITE_MINIMUM, MAX_INITIAL_CONTROL_COMMANDS,
};
pub use envelope::{RMUX_FRAME_MAGIC, RMUX_WIRE_VERSION};
pub use error::{
RmuxError, OWNED_SESSION_LEASE_LOST_MESSAGE_PREFIX, PANE_STILL_ACTIVE_MESSAGE,
PROCESS_COMMAND_EMPTY_MESSAGE, SPAWN_FAILED_MESSAGE_PREFIX,
};
pub use frame_kind::{
frame_kind_for_request, frame_kind_for_response, ledger_entry_for, FrameDirection,
FrameFeature, FrameKind, FrameLedgerEntry, FrameStatus, V1_FRAME_LEDGER,
};
pub use identity::{PaneId, SessionId, SessionName, WindowId};
pub use request::*;
pub use response::*;
pub use types::*;
pub use types::{
OptionScopeSelector, PaneOutputSubscriptionId, PaneStateSubscriptionId, SdkWaitId,
SdkWaitOwnerId,
};
pub const PROTOCOL_VERSION: u16 = RMUX_WIRE_VERSION as u16;
pub const INTERNAL_RUNTIME_COMMAND_EXPANSION_PATH: &str = "\0rmux-runtime-command-expansion-v1";
pub const INTERNAL_PARSE_TIME_ASSIGNMENTS_PATH: &str = "\0rmux-parse-time-assignments-v1";
pub const INTERNAL_CANONICAL_COMMAND_EXECUTION_PATH: &str = "\0rmux-canonical-command-execution-v1";
pub const INTERNAL_LIST_WINDOWS_ALL_EXECUTION_PATH: &str = "\0rmux-list-windows-all-execution-v1";
pub const INTERNAL_PANE_EXIT_PROBE_PREFIX: &str = "\0rmux-pane-exit-probe-v1:";
#[must_use]
pub fn encode_internal_pane_exit_probe(session_id: SessionId, pane_id: PaneId) -> String {
format!(
"{INTERNAL_PANE_EXIT_PROBE_PREFIX}{}:{}",
session_id.as_u32(),
pane_id.as_u32()
)
}
#[must_use]
pub fn decode_internal_pane_exit_probe(value: &str) -> Option<(SessionId, PaneId)> {
let payload = value.strip_prefix(INTERNAL_PANE_EXIT_PROBE_PREFIX)?;
let (session_id, pane_id) = payload.split_once(':')?;
if pane_id.contains(':') {
return None;
}
Some((
SessionId::new(session_id.parse().ok()?),
PaneId::new(pane_id.parse().ok()?),
))
}
pub fn encode_internal_runtime_command_arguments(
arguments: &[String],
) -> Result<String, serde_json::Error> {
serde_json::to_string(arguments)
}
pub fn decode_internal_runtime_command_arguments(
payload: &str,
) -> Result<Vec<String>, serde_json::Error> {
serde_json::from_str(payload)
}
#[must_use]
pub fn decode_internal_list_windows_all_arguments(payload: &str) -> Option<Vec<String>> {
let arguments = decode_internal_runtime_command_arguments(payload).ok()?;
internal_list_windows_all_arguments_are_valid(&arguments).then_some(arguments)
}
#[must_use]
pub fn internal_list_windows_all_arguments_are_valid(arguments: &[String]) -> bool {
if arguments.first().map(String::as_str) != Some("list-windows") {
return false;
}
let mut seen_flags = 0_u8;
let mut index = 1;
while index < arguments.len() {
let (bit, takes_value) = match arguments[index].as_str() {
"-a" => (1 << 0, false),
"-r" => (1 << 1, false),
"-t" => (1 << 2, true),
"-F" => (1 << 3, true),
"-f" => (1 << 4, true),
"-O" => (1 << 5, true),
_ => return false,
};
if seen_flags & bit != 0 {
return false;
}
seen_flags |= bit;
index += 1;
if takes_value {
if index == arguments.len() {
return false;
}
index += 1;
}
}
seen_flags & 1 != 0
}
pub const MIN_SESSION_LEASE_TTL_MILLIS: u64 = 500;
#[cfg(test)]
mod internal_pane_exit_probe_tests {
use super::*;
#[test]
fn pane_exit_probe_round_trips_stable_identities() {
let encoded = encode_internal_pane_exit_probe(SessionId::new(17), PaneId::new(42));
assert_eq!(
decode_internal_pane_exit_probe(&encoded),
Some((SessionId::new(17), PaneId::new(42)))
);
}
#[test]
fn pane_exit_probe_rejects_public_and_malformed_formats() {
for value in [
"#{pane_id}",
"\0rmux-pane-exit-probe-v1:1",
"\0rmux-pane-exit-probe-v1:1:2:3",
"\0rmux-pane-exit-probe-v1:session:2",
] {
assert_eq!(decode_internal_pane_exit_probe(value), None);
}
}
#[test]
fn list_windows_all_arguments_accept_only_the_read_only_internal_shape() {
let valid = vec![
"list-windows".to_owned(),
"-a".to_owned(),
"-t".to_owned(),
"alpha".to_owned(),
"-F".to_owned(),
"#{window_name}".to_owned(),
"-r".to_owned(),
];
let encoded = encode_internal_runtime_command_arguments(&valid).expect("argv encodes");
assert_eq!(
decode_internal_list_windows_all_arguments(&encoded),
Some(valid)
);
for invalid in [
vec!["list-windows".to_owned()],
vec!["list-windows".to_owned(), "-aFname".to_owned()],
vec![
"list-windows".to_owned(),
"-a".to_owned(),
"; kill-server".to_owned(),
],
vec!["kill-server".to_owned(), "-a".to_owned()],
] {
let encoded =
encode_internal_runtime_command_arguments(&invalid).expect("argv encodes");
assert_eq!(decode_internal_list_windows_all_arguments(&encoded), None);
}
}
}