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