1use super::{CoreError, ErrorCode};
2macro_rules! uuid_ids { ($($name:ident),*) => { $(#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct $name(pub [u8;16]);)* }; }
3uuid_ids!(
4 WorkspaceID,
5 DocumentID,
6 OperationID,
7 TerminalSessionID,
8 TerminalTabID,
9 ClientID,
10 WorkspaceLeaseID,
11 TransferID
12);
13#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug, PartialEq, Eq, Hash)]
14pub struct RequestId(pub u64);
15#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug, PartialEq, Eq, Hash)]
16pub struct ViewID(pub u64);
17#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
18pub enum RouteKind {
19 Local,
20 Ssh,
21 LocalContainer,
22 SshContainer,
23}
24#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
25pub enum ContainerEngine {
26 Docker,
27 Podman,
28}
29#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
30pub struct EnvironmentLocator {
31 pub route: RouteKind,
32 pub ssh_profile: Option<String>,
33 pub engine: Option<ContainerEngine>,
34 pub engine_context: Option<String>,
35 pub container_selector: Option<String>,
36 pub effective_user: Option<String>,
37}
38#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
39pub struct WorkspaceLocator {
40 pub environment: EnvironmentLocator,
41 pub root: TargetPath,
42}
43#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq, Hash)]
44pub struct ContainerIdentity {
45 pub engine_endpoint: Vec<u8>,
46 pub immutable_id: Vec<u8>,
47}
48#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq, Hash)]
49pub struct TargetIdentity {
50 pub security_binding: Vec<u8>,
51 pub effective_user: Vec<u8>,
52 pub filesystem_namespace: Vec<u8>,
53 pub container: Option<ContainerIdentity>,
54}
55#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq, Hash)]
56pub struct RuntimeGeneration {
57 pub supervisor_boot: [u8; 16],
58 pub container_start: Option<Vec<u8>>,
59}
60#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
61pub struct WorkspaceLease {
62 pub client: ClientID,
63 pub workspace: WorkspaceID,
64 pub lease: WorkspaceLeaseID,
65 pub runtime: RuntimeGeneration,
66}
67#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq, Hash)]
68pub enum TargetPath {
69 PosixBytes(Vec<u8>),
70 WindowsWide(Vec<u16>),
71}
72impl TargetPath {
73 pub fn windows(units: Vec<u16>) -> Result<Self, CoreError> {
75 fn ascii_eq(units: &[u16], ascii: &str) -> bool {
76 units.len() == ascii.len()
77 && units.iter().zip(ascii.bytes()).all(|(u, b)| {
78 *u == u16::from(b.to_ascii_uppercase())
79 || *u == u16::from(b.to_ascii_lowercase())
80 })
81 }
82 fn trimmed(mut units: &[u16]) -> &[u16] {
83 while units.last().is_some_and(|u| *u == 32 || *u == 46) {
84 units = &units[..units.len() - 1];
85 }
86 units
87 }
88 fn dos_device(component: &[u16]) -> bool {
89 let base = trimmed(
90 component
91 .split(|u| *u == 46 || *u == 58)
92 .next()
93 .unwrap_or_default(),
94 );
95 ["CON", "PRN", "AUX", "NUL", "CONIN$", "CONOUT$"]
96 .iter()
97 .any(|name| ascii_eq(base, name))
98 || (base.len() == 4
99 && (ascii_eq(&base[..3], "COM") || ascii_eq(&base[..3], "LPT"))
100 && ((49..=57).contains(&base[3]) || [0xb9, 0xb2, 0xb3].contains(&base[3])))
101 }
102 let separator = |c: &u16| *c == 92 || *c == 47;
103 let drive = |p: &[u16]| {
104 p.len() >= 3
105 && ((65..=90).contains(&p[0]) || (97..=122).contains(&p[0]))
106 && p[1] == 58
107 && separator(&p[2])
108 };
109 let unc = |p: &[u16]| {
110 let mut parts = p.split(separator);
111 let server = parts.next().unwrap_or_default();
112 let share = parts.next().unwrap_or_default();
113 !server.is_empty()
114 && !share.is_empty()
115 && ![&[46][..], &[46, 46][..], &[63][..]].contains(&server)
116 && ![&[46][..], &[46, 46][..]].contains(&share)
117 && !ascii_eq(trimmed(share), "pipe")
118 && !ascii_eq(trimmed(share), "IPC$")
119 };
120 let absolute = if let Some(p) = units.strip_prefix(&[92, 92, 63, 92]) {
121 !p.contains(&47)
122 && (drive(p)
123 || (p.len() >= 4
124 && p[..3]
125 .iter()
126 .zip([85, 78, 67])
127 .all(|(c, upper)| *c == upper || *c == upper + 32)
128 && p[3] == 92
129 && unc(&p[4..])))
130 } else {
131 drive(&units)
132 || (units.len() >= 2
133 && separator(&units[0])
134 && separator(&units[1])
135 && unc(&units[2..]))
136 };
137 let device = !units.starts_with(&[92, 92, 63, 92])
139 && if drive(&units) {
140 units[3..].split(separator).any(dos_device)
141 } else {
142 units
143 .get(2..)
144 .unwrap_or_default()
145 .split(separator)
146 .skip(2)
147 .any(dos_device)
148 };
149 if units.contains(&0) || !absolute || device {
150 return Err(CoreError::new(
151 ErrorCode::InvalidArgument,
152 "expected absolute NUL-free Windows filesystem path",
153 ));
154 }
155 Ok(Self::WindowsWide(units))
156 }
157
158 pub fn posix(bytes: Vec<u8>) -> Result<Self, CoreError> {
159 if bytes.first() != Some(&b'/') || bytes.contains(&0) {
160 return Err(CoreError::new(
161 ErrorCode::InvalidArgument,
162 "expected absolute NUL-free POSIX path",
163 ));
164 }
165 Ok(Self::PosixBytes(bytes))
166 }
167 pub fn display(&self) -> String {
168 match self {
169 Self::PosixBytes(v) => String::from_utf8_lossy(v).into_owned(),
170 Self::WindowsWide(v) => String::from_utf16_lossy(v),
171 }
172 }
173}
174#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
175pub enum Capability {
176 Files,
177 Watch,
178 Search,
179 Git,
180 Terminal,
181 Operations,
182 Leases,
183 Language,
184}
185#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
186pub struct Capabilities(pub Vec<Capability>);
187#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
188pub enum ConnectionState {
189 Resolving,
190 Authenticating,
191 PreparingHelper,
192 Negotiating,
193 Ready,
194 Reconnecting,
195 Disconnected,
196 Failed(CoreError),
197 TargetChanged {
198 expected: TargetIdentity,
199 observed: TargetIdentity,
200 },
201}
202#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
203pub enum ConnectionCloseReason {
204 WindowDetachment,
205 ResolvedFinalClose,
206 ApplicationShutdown,
207}
208#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
209pub enum LeaseReleaseIntent {
210 ViewDetachment,
211 ResolvedWorkspaceClosure,
212}
213
214#[derive(Clone, Debug, PartialEq, Eq)]
216pub enum LeasePresence {
217 Active,
218 Detached { remaining_millis: u64 },
219 Absent,
220 RuntimeLost,
221}
222#[derive(Clone, Debug, PartialEq, Eq)]
223pub struct LeaseObservation {
224 pub owner: WorkspaceLease,
225 pub target: TargetIdentity,
226 pub observed_runtime: RuntimeGeneration,
227 pub presence: LeasePresence,
228 pub workers_retired: bool,
229}