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