running_process/broker/server/handoff/
wire.rs1use std::io::{Read, Write};
42use std::time::Instant;
43
44use prost::Message;
45
46use crate::broker::protocol::{
47 read_frame, validate_frame_envelope, write_frame, Frame, FrameKind, FrameValidationError,
48 HandoffAck, HandoffOffer, PayloadEncoding, PROTOCOL_VERSION,
49};
50use crate::broker::server::handoff::handoff_token::HandoffToken;
51use crate::broker::server::handoff::orchestrate::{HandoffDelivery, HandoffDeliveryError};
52use crate::broker::server::handoff::windows::WindowsHandleValue;
53use crate::broker::server::deadline_stream::DeadlineStream;
54
55pub use crate::broker::protocol::registry::HANDOFF_PAYLOAD_PROTOCOL;
63
64pub fn handoff_offer_frame(offer: &HandoffOffer) -> Frame {
66 let mut payload = Vec::with_capacity(64);
67 offer.encode(&mut payload).expect(
68 "prost encoding HandoffOffer into Vec cannot fail because Vec writes are infallible",
69 );
70 Frame {
71 envelope_version: PROTOCOL_VERSION,
72 kind: FrameKind::Request as i32,
73 payload_protocol: HANDOFF_PAYLOAD_PROTOCOL,
74 payload,
75 request_id: offer.correlation_id,
76 payload_encoding: PayloadEncoding::None as i32,
77 deadline_unix_ms: 0,
78 traceparent: String::new(),
79 tracestate: String::new(),
80 }
81}
82
83pub fn handoff_ack_frame(ack: &HandoffAck) -> Frame {
85 let mut payload = Vec::with_capacity(64);
86 ack.encode(&mut payload)
87 .expect("prost encoding HandoffAck into Vec cannot fail because Vec writes are infallible");
88 Frame {
89 envelope_version: PROTOCOL_VERSION,
90 kind: FrameKind::Response as i32,
91 payload_protocol: HANDOFF_PAYLOAD_PROTOCOL,
92 payload,
93 request_id: ack.correlation_id,
94 payload_encoding: PayloadEncoding::None as i32,
95 deadline_unix_ms: 0,
96 traceparent: String::new(),
97 tracestate: String::new(),
98 }
99}
100
101pub fn handoff_ready_frame(ack: &HandoffAck) -> Frame {
114 let mut payload = Vec::with_capacity(64);
115 ack.encode(&mut payload)
116 .expect("prost encoding HandoffAck into Vec cannot fail because Vec writes are infallible");
117 Frame {
118 envelope_version: PROTOCOL_VERSION,
119 kind: FrameKind::Event as i32,
120 payload_protocol: HANDOFF_PAYLOAD_PROTOCOL,
121 payload,
122 request_id: ack.correlation_id,
123 payload_encoding: PayloadEncoding::None as i32,
124 deadline_unix_ms: 0,
125 traceparent: String::new(),
126 tracestate: String::new(),
127 }
128}
129
130pub fn validate_handoff_frame(frame: &Frame, expected_kind: FrameKind) -> Result<(), &'static str> {
135 validate_frame_envelope(frame, expected_kind, HANDOFF_PAYLOAD_PROTOCOL).map_err(|error| {
136 match error {
137 FrameValidationError::EnvelopeVersion { .. } => "envelope_version is not v1",
138 FrameValidationError::Kind { .. } => match expected_kind {
139 FrameKind::Request => "kind is not REQUEST",
140 FrameKind::Event => "kind is not EVENT",
141 _ => "kind is not RESPONSE",
142 },
143 FrameValidationError::PayloadProtocol { .. } => "payload_protocol is not handoff",
144 FrameValidationError::PayloadEncoding { .. } => "payload is compressed",
145 }
146 })
147}
148
149#[derive(Debug)]
158pub struct WireHandoffDelivery<S> {
159 stream: S,
160 service_name: String,
161 correlation_id: u64,
162 configure_ack_bounded_io: Option<fn(&S, bool) -> std::io::Result<()>>,
163 ack_setup_error: Option<String>,
164 io_deadline: Instant,
165}
166
167impl<S> WireHandoffDelivery<S> {
168 pub fn new_preconfigured_nonblocking(
173 stream: S,
174 service_name: impl Into<String>,
175 correlation_id: u64,
176 io_deadline: Instant,
177 ) -> Self {
178 Self {
179 stream,
180 service_name: service_name.into(),
181 correlation_id,
182 configure_ack_bounded_io: None,
183 ack_setup_error: None,
184 io_deadline,
185 }
186 }
187
188 #[deprecated(
194 note = "use new_preconfigured_nonblocking, or new_local_socket for production sockets"
195 )]
196 pub fn new(stream: S, service_name: impl Into<String>, correlation_id: u64) -> Self {
197 Self {
198 stream,
199 service_name: service_name.into(),
200 correlation_id,
201 configure_ack_bounded_io: None,
202 ack_setup_error: None,
203 io_deadline: Instant::now() + std::time::Duration::from_secs(30),
204 }
205 }
206
207 pub fn correlation_id(&self) -> u64 {
209 self.correlation_id
210 }
211
212 pub fn stream(&self) -> &S {
215 &self.stream
216 }
217
218 pub fn into_stream(self) -> S {
221 self.stream
222 }
223}
224
225impl WireHandoffDelivery<interprocess::local_socket::Stream> {
226 pub fn new_local_socket(
229 stream: interprocess::local_socket::Stream,
230 service_name: impl Into<String>,
231 correlation_id: u64,
232 io_deadline: Instant,
233 ) -> Self {
234 use interprocess::local_socket::traits::Stream as _;
235 let ack_setup_error = stream
236 .set_nonblocking(true)
237 .err()
238 .map(|error| format!("failed to enable bounded HandoffAck reads: {error}"));
239 Self {
240 stream,
241 service_name: service_name.into(),
242 correlation_id,
243 configure_ack_bounded_io: Some(|stream, bounded| {
244 use interprocess::local_socket::traits::Stream as _;
245 stream.set_nonblocking(bounded)
246 }),
247 ack_setup_error,
248 io_deadline,
249 }
250 }
251}
252
253impl<S: Read + Write> HandoffDelivery for WireHandoffDelivery<S> {
254 fn deliver(
255 &mut self,
256 handle: WindowsHandleValue,
257 token: &HandoffToken,
258 ) -> Result<(), HandoffDeliveryError> {
259 if let Some(error) = self.ack_setup_error.as_ref() {
260 return Err(HandoffDeliveryError::DeliveryFailed {
261 detail: error.clone(),
262 });
263 }
264 let offer = HandoffOffer {
265 handle_value: handle.get() as u64,
266 token: token.as_bytes().to_vec(),
267 service_name: self.service_name.clone(),
268 correlation_id: self.correlation_id,
269 };
270 let frame = handoff_offer_frame(&offer);
271 let mut bytes = Vec::with_capacity(64);
272 frame
273 .encode(&mut bytes)
274 .expect("prost encoding Frame into Vec cannot fail because Vec writes are infallible");
275 let mut deadline_stream = DeadlineStream::new(&mut self.stream, self.io_deadline);
276 if let Err(write_error) = write_frame(&mut deadline_stream, &bytes) {
277 let restore_error = self
278 .configure_ack_bounded_io
279 .and_then(|configure| configure(&self.stream, false).err());
280 let detail = match restore_error {
281 Some(restore_error) => format!(
282 "failed to write HandoffOffer frame: {write_error}; additionally failed to \
283 restore blocking mode: {restore_error}"
284 ),
285 None => format!("failed to write HandoffOffer frame: {write_error}"),
286 };
287 return Err(HandoffDeliveryError::DeliveryFailed { detail });
288 }
289 Ok(())
290 }
291
292 fn await_backend_ack(
293 &mut self,
294 token: &HandoffToken,
295 deadline: Instant,
296 ) -> Result<Instant, HandoffDeliveryError> {
297 if let Some(error) = self.ack_setup_error.take() {
298 return Err(ack_not_observed(error));
299 }
300 let read_result = {
301 let mut deadline_stream = DeadlineStream::new(&mut self.stream, deadline);
302 read_frame(&mut deadline_stream)
303 };
304 let restore_result = self
305 .configure_ack_bounded_io
306 .map(|configure| configure(&self.stream, false))
307 .unwrap_or(Ok(()));
308 let bytes = match (read_result, restore_result) {
309 (Ok(bytes), Ok(())) => bytes,
310 (Err(read_error), Ok(())) => {
311 return Err(ack_not_observed(format!(
312 "failed to read HandoffAck frame: {read_error}"
313 )));
314 }
315 (Ok(_), Err(restore_error)) => {
316 return Err(ack_not_observed(format!(
317 "failed to restore blocking HandoffAck stream: {restore_error}"
318 )));
319 }
320 (Err(read_error), Err(restore_error)) => {
321 return Err(ack_not_observed(format!(
322 "failed to read HandoffAck frame: {read_error}; additionally failed to \
323 restore blocking mode: {restore_error}"
324 )));
325 }
326 };
327 let observed_at = Instant::now();
328 let frame = Frame::decode(bytes.as_slice()).map_err(|error| {
329 ack_not_observed(format!("failed to decode HandoffAck Frame: {error}"))
330 })?;
331 validate_handoff_frame(&frame, FrameKind::Response)
332 .map_err(|detail| ack_not_observed(format!("unexpected HandoffAck frame: {detail}")))?;
333 if frame.request_id != self.correlation_id {
334 return Err(ack_not_observed(format!(
335 "HandoffAck frame request_id {} does not match correlation id {}",
336 frame.request_id, self.correlation_id
337 )));
338 }
339 let ack = HandoffAck::decode(frame.payload.as_slice()).map_err(|error| {
340 ack_not_observed(format!("failed to decode HandoffAck payload: {error}"))
341 })?;
342 if ack.correlation_id != self.correlation_id {
343 return Err(ack_not_observed(format!(
344 "HandoffAck correlation id {} does not match offer correlation id {}",
345 ack.correlation_id, self.correlation_id
346 )));
347 }
348 if ack.token != token.as_bytes() {
349 return Err(ack_not_observed(
350 "HandoffAck token echo does not match the offered token".to_string(),
351 ));
352 }
353 if !ack.accepted {
354 return Err(ack_not_observed(format!(
355 "backend refused the handoff: {}",
356 if ack.error_detail.is_empty() {
357 "no detail provided"
358 } else {
359 ack.error_detail.as_str()
360 }
361 )));
362 }
363 if observed_at > deadline {
364 return Err(ack_not_observed(
365 "backend HandoffAck arrived after the ACK deadline".to_string(),
366 ));
367 }
368 Ok(observed_at)
369 }
370}
371
372fn ack_not_observed(detail: String) -> HandoffDeliveryError {
373 HandoffDeliveryError::AckNotObserved { detail }
374}