1use std::io::{Error as IoError, ErrorKind};
2
3use serde::de::DeserializeOwned;
4use serde::{Deserialize, Serialize};
5use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
6
7use crate::state::persistence::BashStateSnapshot;
8use crate::types::BashCommand;
9
10pub const PROTOCOL_MAJOR: u16 = 1;
11pub const PROTOCOL_MINOR: u16 = 0;
12pub const MAX_FRAME_BYTES: usize = 8 * 1024 * 1024;
13
14#[derive(Debug, Serialize, Deserialize)]
15pub(crate) struct RpcRequest {
16 pub jsonrpc: String,
17 pub id: u64,
18 pub method: String,
19 pub params: serde_json::Value,
20}
21
22#[derive(Debug, Serialize, Deserialize)]
23pub(crate) struct RpcResponse {
24 pub jsonrpc: String,
25 pub id: u64,
26 #[serde(skip_serializing_if = "Option::is_none")]
27 pub result: Option<serde_json::Value>,
28 #[serde(skip_serializing_if = "Option::is_none")]
29 pub error: Option<RpcError>,
30}
31
32#[derive(Debug, Serialize, Deserialize)]
33pub(crate) struct RpcError {
34 pub code: i32,
35 pub message: String,
36}
37
38#[derive(Clone, Debug, Serialize, Deserialize)]
39pub struct HelloResult {
40 pub protocol_major: u16,
41 pub protocol_minor: u16,
42 pub capabilities: Vec<String>,
43 pub max_frame_bytes: usize,
44 pub daemon_epoch: String,
45 pub daemon_pid: u32,
46}
47
48#[derive(Debug, Serialize, Deserialize)]
49pub(crate) struct RunActionParams {
50 pub snapshot: BashStateSnapshot,
51 pub command: BashCommand,
52 pub request_key: String,
53 #[serde(default)]
54 pub consumer_id: String,
55}
56
57#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
58#[serde(rename_all = "snake_case")]
59pub(crate) enum ConfigureSessionTransition {
60 FirstCall,
61 ModeChange,
62 Reset,
63 WorkspaceChange,
64}
65
66#[derive(Debug, Serialize, Deserialize)]
67pub(crate) struct ConfigureSessionParams {
68 pub snapshot: BashStateSnapshot,
69 pub transition: ConfigureSessionTransition,
70}
71
72#[derive(Clone, Debug, Serialize, Deserialize)]
73pub(crate) struct ConfigureSessionResult {
74 pub attach_hint: Option<String>,
75}
76
77#[derive(Clone, Debug, Serialize, Deserialize)]
78pub(crate) struct RunActionResult {
79 pub output: Option<String>,
80 pub snapshot: BashStateSnapshot,
81 pub error: Option<WireShellError>,
82}
83
84#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
85pub struct JournalRead {
86 pub output: String,
87 pub next_seq: u64,
88 pub gap: bool,
89}
90
91#[derive(Debug, Serialize, Deserialize)]
92pub(crate) struct JournalReadParams {
93 pub thread_id: String,
94 pub consumer_id: String,
95}
96
97#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
98pub struct SessionInfo {
99 pub thread_id: String,
100 pub cwd: String,
101 pub shell_pid: Option<u32>,
102 pub command_id: Option<String>,
103 pub running: bool,
104 pub background_command_ids: Vec<String>,
105}
106
107#[derive(Debug, Serialize, Deserialize)]
108pub(crate) struct SessionParams {
109 pub thread_id: String,
110}
111
112#[derive(Clone, Debug, Serialize, Deserialize)]
113#[serde(tag = "kind", content = "message", rename_all = "snake_case")]
114pub(crate) enum WireShellError {
115 BashStateNotInitialized,
116 ShellInitialization(String),
117 CommandExecution(String),
118 CommandNotAllowed(String),
119 ThreadIdMismatch(String),
120 Other(String),
121}
122
123pub(crate) async fn write_json_frame<W, T>(writer: &mut W, value: &T) -> std::io::Result<()>
124where
125 W: AsyncWrite + Unpin,
126 T: Serialize,
127{
128 let payload = serde_json::to_vec(value)
129 .map_err(|error| IoError::new(ErrorKind::InvalidData, error.to_string()))?;
130 if payload.len() > MAX_FRAME_BYTES {
131 return Err(IoError::new(
132 ErrorKind::InvalidData,
133 format!("JSON-RPC frame exceeds {MAX_FRAME_BYTES} bytes"),
134 ));
135 }
136 let length = u32::try_from(payload.len())
137 .map_err(|_| IoError::new(ErrorKind::InvalidData, "JSON-RPC frame is too large"))?;
138 writer.write_all(&length.to_be_bytes()).await?;
139 writer.write_all(&payload).await?;
140 writer.flush().await
141}
142
143pub(crate) async fn read_json_frame<R, T>(reader: &mut R) -> std::io::Result<T>
144where
145 R: AsyncRead + Unpin,
146 T: DeserializeOwned,
147{
148 let mut length = [0_u8; 4];
149 reader.read_exact(&mut length).await?;
150 let length = u32::from_be_bytes(length) as usize;
151 if length == 0 || length > MAX_FRAME_BYTES {
152 return Err(IoError::new(
153 ErrorKind::InvalidData,
154 format!("invalid JSON-RPC frame length: {length}"),
155 ));
156 }
157 let mut payload = vec![0_u8; length];
158 reader.read_exact(&mut payload).await?;
159 serde_json::from_slice(&payload)
160 .map_err(|error| IoError::new(ErrorKind::InvalidData, error.to_string()))
161}