running_process_protocol/
lib.rs1#[allow(missing_docs)]
9pub mod independent_spawn {
10 include!(concat!(
11 env!("OUT_DIR"),
12 "/running_process.independent_spawn.v1.rs"
13 ));
14}
15
16#[allow(missing_docs)]
18pub mod daemon {
19 include!(concat!(env!("OUT_DIR"), "/running_process.daemon.v1.rs"));
20}
21
22pub mod broker {
24 #[allow(missing_docs)]
26 pub mod v1 {
27 include!(concat!(env!("OUT_DIR"), "/running_process.broker.v1.rs"));
28 }
29
30 #[allow(missing_docs)]
32 pub mod v2 {
33 include!(concat!(env!("OUT_DIR"), "/running_process.broker.v2.rs"));
34 }
35}
36
37#[derive(Debug, thiserror::Error, PartialEq, Eq)]
39pub enum EndpointNameError {
40 #[error("endpoint name must not be empty")]
42 Empty,
43 #[error(
47 "windows pipe name must be bare (no \\\\.\\pipe\\ prefix), got {got:?}: \\
48 running-process prepends the prefix when resolving the endpoint"
49 )]
50 PrefixedPipeName {
51 got: String,
53 },
54}
55
56pub trait SessionStartEnvironmentPolicy {
64 fn session_start_wire_fields(self) -> (i32, bool);
66}
67
68impl broker::v1::Frame {
69 pub fn request(payload_protocol: u32, payload: Vec<u8>) -> Self {
71 Self {
72 envelope_version: 1,
73 kind: broker::v1::FrameKind::Request as i32,
74 payload_protocol,
75 payload,
76 request_id: 0,
77 payload_encoding: broker::v1::PayloadEncoding::None as i32,
78 deadline_unix_ms: 0,
79 traceparent: String::new(),
80 tracestate: String::new(),
81 }
82 }
83
84 pub fn response_to(request: &Self, payload: Vec<u8>) -> Self {
86 Self {
87 envelope_version: 1,
88 kind: broker::v1::FrameKind::Response as i32,
89 payload_protocol: request.payload_protocol,
90 payload,
91 request_id: request.request_id,
92 payload_encoding: broker::v1::PayloadEncoding::None as i32,
93 deadline_unix_ms: 0,
94 traceparent: request.traceparent.clone(),
95 tracestate: request.tracestate.clone(),
96 }
97 }
98
99 #[must_use]
101 pub fn with_request_id(mut self, request_id: u64) -> Self {
102 self.request_id = request_id;
103 self
104 }
105}
106
107impl broker::v1::Endpoint {
108 pub fn windows_pipe(
110 namespace_id: impl Into<String>,
111 pipe_name: impl Into<String>,
112 ) -> Result<Self, EndpointNameError> {
113 let pipe_name = pipe_name.into();
114 if pipe_name.is_empty() {
115 return Err(EndpointNameError::Empty);
116 }
117 let lowered = pipe_name.to_ascii_lowercase().replace('/', "\\");
118 if lowered.starts_with("\\\\.\\pipe\\") {
119 return Err(EndpointNameError::PrefixedPipeName { got: pipe_name });
120 }
121 Ok(Self {
122 namespace_id: namespace_id.into(),
123 path: pipe_name,
124 })
125 }
126
127 pub fn unix_socket(
129 namespace_id: impl Into<String>,
130 socket_path: impl Into<String>,
131 ) -> Result<Self, EndpointNameError> {
132 let socket_path = socket_path.into();
133 if socket_path.is_empty() {
134 return Err(EndpointNameError::Empty);
135 }
136 Ok(Self {
137 namespace_id: namespace_id.into(),
138 path: socket_path,
139 })
140 }
141}
142
143impl broker::v2::SessionStart {
144 pub fn from_current_process(
148 program: impl Into<String>,
149 args: impl IntoIterator<Item = impl Into<String>>,
150 cwd: impl Into<String>,
151 ) -> Self {
152 Self {
153 program: program.into(),
154 args: args.into_iter().map(Into::into).collect(),
155 cwd: cwd.into(),
156 env: std::env::vars()
157 .map(|(key, value)| broker::v2::SessionEnvVar { key, value })
158 .collect(),
159 clear_inherited_env: true,
160 environment_policy: 3,
161 }
162 }
163
164 #[must_use]
170 pub fn with_environment_policy(mut self, policy: impl SessionStartEnvironmentPolicy) -> Self {
171 (self.environment_policy, self.clear_inherited_env) = policy.session_start_wire_fields();
172 self
173 }
174}
175
176#[cfg(test)]
177mod compatibility_tests {
178 use super::broker::v1::{Endpoint, Frame, FrameKind, PayloadEncoding};
179 use super::EndpointNameError;
180
181 #[test]
182 fn frame_constructors_keep_the_frozen_v1_defaults() {
183 let mut request = Frame::request(0x7A63, b"ping".to_vec()).with_request_id(42);
184 assert_eq!(request.envelope_version, 1);
185 assert_eq!(request.kind, FrameKind::Request as i32);
186 assert_eq!(request.payload_encoding, PayloadEncoding::None as i32);
187 assert_eq!(request.request_id, 42);
188
189 request.traceparent = "00-abc-def-01".to_owned();
190 request.tracestate = "vendor=1".to_owned();
191 let response = Frame::response_to(&request, b"pong".to_vec());
192 assert_eq!(response.kind, FrameKind::Response as i32);
193 assert_eq!(response.payload_protocol, request.payload_protocol);
194 assert_eq!(response.request_id, request.request_id);
195 assert_eq!(response.traceparent, request.traceparent);
196 assert_eq!(response.tracestate, request.tracestate);
197 }
198
199 #[test]
200 fn endpoint_constructors_keep_the_public_validation_contract() {
201 let pipe = Endpoint::windows_pipe("svc", "svc-pipe").expect("bare pipe name");
202 assert_eq!(pipe.namespace_id, "svc");
203 assert_eq!(pipe.path, "svc-pipe");
204 assert_eq!(
205 Endpoint::windows_pipe("svc", r"\\.\pipe\svc-pipe"),
206 Err(EndpointNameError::PrefixedPipeName {
207 got: r"\\.\pipe\svc-pipe".to_owned(),
208 })
209 );
210 assert_eq!(
216 Endpoint::windows_pipe("svc", "//./pipe/svc-pipe"),
217 Err(EndpointNameError::PrefixedPipeName {
218 got: "//./pipe/svc-pipe".to_owned(),
219 }),
220 "forward-slash spelling of the prefix must be rejected too"
221 );
222 assert_eq!(
223 Endpoint::windows_pipe("svc", r"\\.\PIPE\svc-pipe"),
224 Err(EndpointNameError::PrefixedPipeName {
225 got: r"\\.\PIPE\svc-pipe".to_owned(),
226 }),
227 "the prefix check is case-insensitive"
228 );
229 assert_eq!(
230 Endpoint::windows_pipe("svc", ""),
231 Err(EndpointNameError::Empty)
232 );
233 assert_eq!(
234 Endpoint::unix_socket("svc", ""),
235 Err(EndpointNameError::Empty)
236 );
237 }
238}