Skip to main content

libinject/
wrappers.rs

1use crate::Boolean;
2use crate::connections::Connection;
3use crate::drcore::log;
4use crate::drwrap::Retval;
5use crate::socket::{SocketData, WSABufRaw, WSAOverlappedRaw};
6use crate::utils::FromBuf;
7use crate::utils::socketaddr_from_socket_id;
8use crate::{cli, connections, drcore, drwrap, ffi, instrument, pipe, trajectory};
9use enum_iterator::Sequence;
10use navigator::callstack::CallStack;
11use navigator::netgraph::core::{
12    Attempt, ConnectAttempt, ConnectReact, React, RecvAttempt, RecvReact, SendAttempt, SendReact,
13};
14use navigator::netgraph::trajectory::GraphStep;
15use navigator::pipe_event::PipeEvent;
16use navigator::socket::{HalfDuplexExchange, HalfDuplexResponse, RecvBuffer};
17use navigator::trajectory::PlanningStep;
18use std::sync::Arc;
19use std::{net::SocketAddr, os::raw::c_void};
20
21const SOCKET_ERROR: i32 = -1;
22const WSAEHOSTUNREACH: i32 = 10065;
23const WSA_IO_PENDING: i32 = 997;
24const WSAECONNRESET: i32 = 10054;
25const SIO_GET_EXTENSION_FUNCTION_POINTER: u32 = 0xC8000006;
26// WSAID_CONNECTEX GUID: {25a207b9-ddf3-4660-8ee9-76e58c74063e}
27const WSAID_CONNECTEX: [u8; 16] = [
28    0xb9, 0x07, 0xa2, 0x25, 0xf3, 0xdd, 0x60, 0x46, 0x8e, 0xe9, 0x76, 0xe5, 0x8c, 0x74, 0x06, 0x3e,
29];
30
31#[derive(Debug, PartialEq, Sequence)]
32pub enum Symbols {
33    WSAioctl,
34    Connect,
35    Send,
36    WSASend,
37    Recv,
38    WSARecv,
39}
40
41impl Symbols {
42    pub fn pre_fn(
43        &self,
44    ) -> unsafe extern "C" fn(wrapctx: *mut c_void, _user_data: *mut *mut c_void) {
45        match self {
46            Symbols::WSAioctl => pre_wsaioctl,
47            Symbols::Connect => pre_connect,
48            Symbols::Send => pre_send,
49            Symbols::WSASend => pre_wsasend,
50            Symbols::Recv => pre_recv,
51            Symbols::WSARecv => pre_wsarecv,
52        }
53    }
54    pub fn post_fn(
55        &self,
56    ) -> Option<unsafe extern "C" fn(wrapctx: *mut c_void, _user_data: *mut c_void)> {
57        match self {
58            Symbols::WSAioctl => Some(post_wsaioctl),
59            _ => None,
60        }
61    }
62    pub fn to_str(&self) -> &str {
63        match self {
64            Symbols::WSAioctl => "wsaioctl",
65            Symbols::Connect => "connect",
66            Symbols::Send => "send",
67            Symbols::WSASend => "wsasend",
68            Symbols::Recv => "recv",
69            Symbols::WSARecv => "wsarecv",
70        }
71    }
72}
73
74/// Pre-callback for WSAIoctl - save output buffer pointer to wrap ConnectEx in post
75pub unsafe extern "C" fn pre_wsaioctl(wrapcxt: *mut c_void, user_data: *mut *mut c_void) {
76    log("[WSAIoctl] Pre-callback called");
77
78    // Get all the WSAIoctl arguments
79    let io_control_code = unsafe { ffi::drwrap_get_arg(wrapcxt, 1) as u32 };
80    let lpv_in = unsafe { ffi::drwrap_get_arg(wrapcxt, 2) as *const u8 };
81    let cb_in = unsafe { ffi::drwrap_get_arg(wrapcxt, 3) as u32 };
82    let lpv_out = unsafe { ffi::drwrap_get_arg(wrapcxt, 4) as *mut usize };
83
84    log(&format!("[WSAIoctl] Control code: 0x{:X}", io_control_code));
85
86    // Check if this is a SIO_GET_EXTENSION_FUNCTION_POINTER request for ConnectEx
87    if io_control_code == SIO_GET_EXTENSION_FUNCTION_POINTER {
88        if !lpv_in.is_null() && cb_in >= 16 {
89            let guid_slice = unsafe { std::slice::from_raw_parts(lpv_in, 16) };
90
91            if guid_slice == &WSAID_CONNECTEX {
92                log("[WSAIoctl] Detected ConnectEx request - will wrap in post-callback");
93
94                // Store the output buffer pointer in user_data so we can access it in post-callback
95                unsafe {
96                    *user_data = lpv_out as *mut c_void;
97                }
98                // Let the real WSAIoctl proceed to get the real ConnectEx pointer
99                return;
100            }
101        }
102    }
103
104    // For all other cases, let WSAIoctl proceed normally
105    log("[WSAIoctl] Not a ConnectEx request - letting WSAIoctl proceed");
106}
107
108/// Post-callback for WSAIoctl - wrap the real ConnectEx that was returned
109pub unsafe extern "C" fn post_wsaioctl(_wrapcxt: *mut c_void, user_data: *mut c_void) {
110    if user_data.is_null() {
111        // Not a ConnectEx request, nothing to do
112        return;
113    }
114
115    log("[WSAIoctl] Post-callback called");
116
117    // user_data contains the lpv_out pointer
118    let lpv_out = user_data as *mut usize;
119
120    if !lpv_out.is_null() {
121        // Read the real ConnectEx pointer that Windows returned
122        let real_connectex_ptr = unsafe { *lpv_out };
123        log(&format!(
124            "[WSAIoctl] Real ConnectEx pointer: 0x{:X}",
125            real_connectex_ptr
126        ));
127
128        if real_connectex_ptr != 0 {
129            // Now wrap the real ConnectEx function
130            unsafe {
131                match ffi::drwrap_wrap(real_connectex_ptr as *mut u8, Some(pre_connectex), None)
132                    .as_bool()
133                {
134                    true => log(&format!(
135                        "[WSAIoctl] Successfully wrapped real ConnectEx @ 0x{:X}",
136                        real_connectex_ptr
137                    )),
138                    false => log(&format!(
139                        "[WSAIoctl] FAILED to wrap real ConnectEx @ 0x{:X}",
140                        real_connectex_ptr
141                    )),
142                }
143            }
144        }
145    }
146}
147
148#[unsafe(no_mangle)]
149pub unsafe extern "C" fn pre_connectex(wrapctx: *mut c_void, _user_data: *mut *mut c_void) {
150    unsafe {
151        let _send_buffer = drwrap::get_arg(wrapctx, 3);
152        let send_data_length = drwrap::get_arg(wrapctx, 4) as u32;
153
154        if send_data_length != 0 {
155            log(
156                "[connectex] send_data_length is not 0! Implementation required to parse the send payload accompanying this connect",
157            );
158            unimplemented!(
159                "[connectex] send_data_length is not 0! Implementation required to parse the send payload accompanying this connect"
160            );
161        }
162
163        // Required for `skip_call` on 32-bit.
164        let stdcall_args_size = if cfg!(target_pointer_width = "32") {
165            // 7 args, all ptr sized = 7 * 4 = 28
166            28
167        } else {
168            0
169        };
170
171        let (socket_id, socket_addr) = parse_socket(wrapctx, "connectex");
172        let graph_step = parse_connect_graph_step(socket_addr, "connectex");
173        pipe::send(&PipeEvent::NetGraph(graph_step.clone()));
174
175        // Assume that calls to connectex are using AsyncOverlapped sockets.
176        if let GraphStep::Connect(_, ConnectReact { success: true }) = graph_step {
177            let socket_data = SocketData::ServerManaged;
178            connections::insert(socket_id, socket_addr, socket_data);
179            // Allow the connectex call through to the OS, fakenet will handle the socket.
180        } else {
181            let retval = Retval::Int(false as i32);
182            let error_code = WSAEHOSTUNREACH;
183            unimplemented!("TODO: handle connection rejection");
184            log(&format!(
185                "[connectex] Stored error code {} in TLS for WSAGetLastError hook",
186                error_code
187            ));
188            skip_call_helper(wrapctx, retval, "connectex", stdcall_args_size);
189        }
190    }
191}
192
193/// Windows docs for "connect": https://learn.microsoft.com/en-us/windows/win32/api/Winsock2/nf-winsock2-connect
194#[unsafe(no_mangle)]
195pub unsafe extern "C" fn pre_connect(wrapctx: *mut c_void, _user_data: *mut *mut c_void) {
196    unsafe {
197        // Required for `skip_call` on 32-bit.
198        let stdcall_args_size = if cfg!(target_pointer_width = "32") {
199            // socket_id pointer (4) + sockaddr_ptr (4) + int (4)
200            12
201        } else {
202            0
203        };
204
205        let (socket_id, socket_addr) = parse_socket(wrapctx, "connect");
206        let graph_step = parse_connect_graph_step(socket_addr, "connect");
207        pipe::send(&PipeEvent::NetGraph(graph_step.clone()));
208
209        // Assume that calls to connect are using Sync sockets.
210        if let GraphStep::Connect(_, ConnectReact { success: true }) = graph_step {
211            if cli::get_args()
212                .expect("Args parsed in Libinject init")
213                .server_managed
214            {
215                let socket_data = SocketData::ServerManaged;
216                connections::insert(socket_id, socket_addr, socket_data);
217            } else {
218                let socket_data = SocketData::Sync(RecvBuffer::new());
219                connections::insert(socket_id, socket_addr, socket_data);
220                let retval = Retval::Int(0);
221                skip_call_helper(wrapctx, retval, "connect", stdcall_args_size);
222            };
223        } else {
224            let retval = Retval::Int(SOCKET_ERROR);
225            skip_call_helper(wrapctx, retval, "connect", stdcall_args_size);
226        }
227    }
228}
229
230unsafe fn connection_of_socket(socket_id: usize, wrapper_name: &str) -> Arc<Connection> {
231    unsafe {
232        connections::get(socket_id).unwrap_or_else(|| {
233            log(&format!("[{wrapper_name}] Binary tried to call {wrapper_name} with a socket that we have not recorded a connection for! Trying to parse the socket address from the socket_id..."));
234            match socketaddr_from_socket_id(socket_id) {
235                Ok(socket_addr) => {
236                    log(&format!("[{wrapper_name}] Successfully parsed socket address from socket_id. Recording this connection."));
237                    connections::insert(socket_id, socket_addr, SocketData::ServerManaged);
238                    connections::get(socket_id).unwrap()
239                },
240                Err(e) => {
241                    log(&format!("[{wrapper_name} Failed parsing SocketAddr from socket_id (opaque OS handle): {e}"));
242                    log(&format!("[{wrapper_name}] If there is only one connection, we will assume the socket_id has changed but the IP address remains the same."));
243                    connections::exactly_one().expect("[{wrapper_name}] There is not exactly one connection")
244                }
245            }
246        })
247    }
248}
249
250#[unsafe(no_mangle)]
251pub unsafe extern "C" fn pre_wsasend(wrapctx: *mut c_void, _user_data: *mut *mut c_void) {
252    unsafe {
253        log("[WSASend] Pre-callback called *********");
254        let stdcall_args_size = 28;
255
256        let socket_id = drwrap::get_arg(wrapctx, 0) as usize;
257        let connection = connection_of_socket(socket_id, "wsasend");
258
259        let buffers_ptr = drwrap::get_arg(wrapctx, 1) as *mut WSABufRaw;
260        let buffer_count = drwrap::get_arg(wrapctx, 2) as usize;
261        log(&format!("[WSASend] buffer count: {buffer_count}"));
262
263        if buffers_ptr.is_null() {
264            panic!("[WSASend] buffer_ptr is null!");
265        }
266
267        let overlapped_ptr = drwrap::get_arg(wrapctx, 5) as *mut WSAOverlappedRaw;
268        let completion_routine_ptr = drwrap::get_arg(wrapctx, 6);
269        log(&format!(
270            "[WSASend] overlapper ptr is null?: {}",
271            overlapped_ptr.is_null()
272        ));
273        log(&format!(
274            "[WSASend] completion routine ptr is null?: {}",
275            completion_routine_ptr.is_null()
276        ));
277
278        let mut payloads = Vec::new();
279
280        for i in 0..buffer_count {
281            let wsabuf = buffers_ptr.add(i).read();
282            let length = wsabuf.len as usize;
283            let buf_ptr = wsabuf.buf;
284
285            if !buf_ptr.is_null() && length > 0 {
286                let data = std::slice::from_raw_parts(buf_ptr, length);
287                payloads.push(data.to_vec());
288                let snippet =
289                    std::str::from_utf8(&data[..data.len().min(100)]).unwrap_or("<non-utf8>");
290                log(&format!("[WSASend] Buffer {i} (len {length}): {snippet}"));
291            } else {
292                log(&format!("[WSASend] Buffer {i}: null or zero-length"));
293            }
294        }
295
296        if payloads.len() > 1 {
297            unimplemented!("handle multiple WSABUFs arriving in WSASend")
298        }
299
300        let request = payloads
301            .into_iter()
302            .next()
303            .expect("At least one WSABUF is expected when WSASend is called");
304
305        log(&format!("[WSASend parsed request: {:?}", request.clone()));
306
307        connections::record_request(connection.socket.id, request.clone());
308        let graph_step = parse_send_graph_step(request.clone(), "WSASend");
309        pipe::send(&PipeEvent::NetGraph(graph_step.clone()));
310
311        // Match on the socket type.
312        match &connection.socket.data {
313            SocketData::ServerManaged => {
314                // Emit a callstack on WSASend for async sockets.
315                capture_and_send_callstack("WSASend");
316
317                // If blocking the send, return SOCKET_ERROR and set WSAECONNRESET; else generate a response to the request and
318                // push the response to the socket.
319                if let GraphStep::Send(_, SendReact { success: false }) = graph_step {
320                    let retval = Retval::Int(SOCKET_ERROR);
321                    let error_code = WSAECONNRESET;
322                    unimplemented!("TODO: handle rejecting the send");
323                    // skip_call_helper(wrapctx, retval, "WSASend", stdcall_args_size);
324                } else {
325                    let exchange = parse_exchange(request, "WSASend");
326                    connection.socket.push_exchange(exchange.clone());
327                }
328            }
329            _ => unimplemented!("Only server managed sockets are implemented for WSASend"),
330        }
331    }
332}
333
334/// Windows docs for "send": https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-send
335#[unsafe(no_mangle)]
336pub unsafe extern "C" fn pre_send(wrapctx: *mut c_void, _user_data: *mut *mut c_void) {
337    unsafe {
338        // Opaque handle for the socket (provided by the OS).
339        let socket_id = drwrap::get_arg(wrapctx, 0) as usize;
340        let payload_ptr = drwrap::get_arg(wrapctx, 1);
341        let payload_size = drwrap::get_arg(wrapctx, 2) as usize;
342
343        // Required for `skip_call` on 32-bit.
344        let stdcall_args_size = if cfg!(target_pointer_width = "32") {
345            // socket_id pointer (4) + payload_ptr (4) + payload_size int (4) + flags int (4)
346            16
347        } else {
348            0
349        };
350
351        let connection = connection_of_socket(socket_id, "send");
352
353        let request = match drcore::safe_read(payload_ptr, payload_size) {
354            Ok(payload) => {
355                log(&format!(
356                    "[send] target: {}, payload: {:?}",
357                    connection.to_string(),
358                    payload
359                ));
360                payload
361            }
362            Err(read_err) => {
363                panic!(
364                    "[send] Failed to read the payload sent by the binary: {:?}",
365                    read_err
366                );
367            }
368        };
369
370        connections::record_request(connection.socket.id, request.clone());
371        let graph_step = parse_send_graph_step(request.clone(), "send");
372        pipe::send(&PipeEvent::NetGraph(graph_step.clone()));
373
374        // If blocking the send, return SOCKET_ERROR; else generate a response to the request and
375        // push the response to the socket.
376        if let GraphStep::Send(_, SendReact { success: false }) = graph_step {
377            let retval = Retval::Int(SOCKET_ERROR);
378            skip_call_helper(wrapctx, retval, "send", stdcall_args_size);
379        } else {
380            let exchange = parse_exchange(request.clone(), "send");
381            connection.socket.push_exchange(exchange);
382            match connection.socket.data {
383                SocketData::Sync(_) => {
384                    let retval = Retval::Int(request.len() as i32);
385                    skip_call_helper(wrapctx, retval, "send", stdcall_args_size);
386                }
387                SocketData::ServerManaged => (),
388            }
389        }
390    }
391}
392
393#[unsafe(no_mangle)]
394pub unsafe extern "C" fn pre_wsarecv(_wrapctx: *mut c_void, _user_data: *mut *mut c_void) {
395    unsafe {
396        log("[WSARecv] Pre-callback called *********");
397        //capture_and_send_callstack("WSARecv");
398    }
399}
400
401/// Windows docs for "recv": https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-recv
402#[unsafe(no_mangle)]
403pub extern "C" fn pre_recv(wrapctx: *mut c_void, _user_data: *mut *mut c_void) {
404    unsafe {
405        // Opaque handle for the socket (provided by the OS).
406        let socket_id = drwrap::get_arg(wrapctx, 0) as usize;
407
408        // Required for `skip_call` on 32-bit.
409        let stdcall_args_size = if cfg!(target_pointer_width = "32") {
410            // socket_id pointer (4) + payload_ptr (4) + payload_size int (4) + flags int (4)
411            16
412        } else {
413            0
414        };
415
416        let connection = connection_of_socket(socket_id, "recv");
417
418        log(&format!(
419            "[recv] called for: {}",
420            connection.addr.to_string()
421        ));
422        // Pointer to the recv buffer that the exe will read from.
423        let buf_ptr = drwrap::get_arg(wrapctx, 1);
424        // Must write <= buf_size to buf_ptr.
425        let buf_size = drwrap::get_arg(wrapctx, 2) as usize;
426
427        capture_and_send_callstack("recv");
428
429        match &connection.socket.data {
430            SocketData::Sync(recv_buf) => {
431                // 100 * 100ms = 10 second countdown timer
432                let mut counter = 100;
433                while recv_buf.is_empty() {
434                    counter -= 1;
435                    ffi::dr_sleep(100);
436                    if counter <= 0 {
437                        // Timeout reached, send a socket error.
438                        let retval = Retval::Int(SOCKET_ERROR);
439                        skip_call_helper(wrapctx, retval, "recv", stdcall_args_size);
440                        return ();
441                    }
442                }
443                let mut exch_slot_guard = recv_buf
444                    .slot
445                    .lock()
446                    .expect("Recv queue mutex should not be poisoned");
447
448                let exch = exch_slot_guard
449                    .take()
450                    .expect("while loop exits when recv_buf.is_some");
451
452                match &exch.response {
453                    HalfDuplexResponse::SocketError => {
454                        let graph_step = GraphStep::Recv(
455                            RecvAttempt {
456                                payload: exch.request.clone(),
457                            },
458                            RecvReact::SocketError,
459                        );
460                        let retval = Retval::Int(SOCKET_ERROR);
461                        pipe::send(&PipeEvent::NetGraph(graph_step));
462                        skip_call_helper(wrapctx, retval, "recv", stdcall_args_size);
463                    }
464                    HalfDuplexResponse::Payload(response) => {
465                        assert!(response.len() <= buf_size);
466                        // Write the response payload to the recv buffer.
467                        match drcore::safe_write(buf_ptr, response.clone()) {
468                            Ok(()) => log("[recv] successfully wrote to recv buffer"),
469                            Err(write_err) => {
470                                panic!("[recv] Failed writing payload: {:?}", write_err);
471                            }
472                        };
473                        let graph_step = GraphStep::Recv(
474                            RecvAttempt {
475                                payload: exch.request.clone(),
476                            },
477                            RecvReact::Payload(response.clone()),
478                        );
479                        pipe::send(&PipeEvent::NetGraph(graph_step));
480                        connections::record_response(socket_id, response.clone());
481                        let retval = Retval::Int(response.len() as i32);
482                        skip_call_helper(wrapctx, retval, "recv", stdcall_args_size);
483                    }
484                };
485            }
486            // Let the `recv` call hit the OS.
487            SocketData::ServerManaged => (),
488        };
489    }
490}
491
492fn parse_socket(wrapctx: *mut c_void, wrapper_name: &str) -> (usize, SocketAddr) {
493    // Opaque handle for the socket (provided by the OS).
494    let socket_id = drwrap::get_arg(wrapctx, 0) as usize;
495    // Pointer to the sockaddr struct.
496    let sockaddr_ptr = drwrap::get_arg(wrapctx, 1);
497    // Size of the sockaddr struct in bytes.
498    let sockaddr_size = drwrap::get_arg(wrapctx, 2) as usize;
499
500    if sockaddr_ptr.is_null() || sockaddr_size == 0 {
501        todo!("[{wrapper_name}] Couldn't parse an IP address during Connect, ticket #8")
502    }
503
504    // Try to parse a std::net::SocketAddr from the pointer.
505    let socket_addr = match drcore::safe_read(sockaddr_ptr, sockaddr_size) {
506        Ok(buf) => SocketAddr::from_buf(buf),
507        Err(read_err) => {
508            log(&format!(
509                "[{wrapper_name}] Failed reading sockaddr:\n {:?}",
510                read_err
511            ));
512            None
513        }
514    };
515
516    let socket_addr = socket_addr.unwrap_or_else(|| {
517        todo!("[{wrapper_name}] Couldn't parse an IP address during Connect, ticket #8")
518    });
519    log(&format!(
520        "[{wrapper_name}] attempt to connect to {}",
521        socket_addr.to_string()
522    ));
523    (socket_id, socket_addr)
524}
525
526fn parse_connect_graph_step(socket_addr: SocketAddr, wrapper_name: &str) -> GraphStep {
527    let observed_attempt = Attempt::Connect(ConnectAttempt { addr: socket_addr });
528    match parse_planned_step(observed_attempt, wrapper_name) {
529        PlanningStep::Graph(graph_step) => graph_step,
530        _ => unreachable!("PlanningStep will be a GraphStep::Connect"),
531    }
532}
533
534fn parse_exchange(request: Vec<u8>, wrapper_name: &str) -> HalfDuplexExchange {
535    let observed_attempt = Attempt::Recv(RecvAttempt {
536        payload: request.clone(),
537    });
538    let planned_step = parse_planned_step(observed_attempt, wrapper_name);
539
540    match planned_step {
541        PlanningStep::Graph(GraphStep::Recv(recv_attempt, recv_react)) => match recv_react {
542            RecvReact::SocketError => HalfDuplexExchange {
543                request: recv_attempt.payload,
544                response: HalfDuplexResponse::SocketError,
545            },
546            RecvReact::Payload(p) => HalfDuplexExchange {
547                request: recv_attempt.payload,
548                response: HalfDuplexResponse::Payload(p),
549            },
550        },
551        PlanningStep::Fuzz(recv_attempt) => HalfDuplexExchange {
552            request: recv_attempt.payload.clone(),
553            response: HalfDuplexResponse::Payload(
554                navigator::protocol::get().fuzz_response(&recv_attempt.payload, Some(log)),
555            ),
556        },
557        _ => unreachable!(
558            "GraphStep will be the Recv variant when an Attempt::Recv is the argument to parse_planning_step"
559        ),
560    }
561}
562
563fn parse_send_graph_step(request: Vec<u8>, wrapper_name: &str) -> GraphStep {
564    let observed_attempt = Attempt::Send(SendAttempt { payload: request });
565    let planned_step = parse_planned_step(observed_attempt, wrapper_name);
566    match planned_step {
567        PlanningStep::Graph(graph_step @ GraphStep::Send(_, _)) => graph_step,
568        _ => unreachable!(
569            "Planned step will be Send when parse_planned_step is called with an Attempt::Send"
570        ),
571    }
572}
573
574unsafe fn capture_and_send_callstack(wrapper_name: &str) {
575    unsafe {
576        // Capture in-module call stack for this Recv attempt, if available.
577        let drctx = ffi::dr_get_current_drcontext();
578        let callstack = instrument::stack_frames(drctx).map(|frames| {
579            let (base, size) = instrument::module_bounds();
580            CallStack::from_raw_frames(base, size, frames)
581        });
582        log(&format!(
583            "[{wrapper_name}] callstack recorded: {}",
584            if callstack.is_some() { "Some" } else { "None" }
585        ));
586        if callstack.is_some() {
587            pipe::send(&PipeEvent::CallStack(callstack.clone().unwrap()));
588        }
589    }
590}
591
592unsafe fn skip_call_helper(
593    wrapctx: *mut c_void,
594    retval: Retval,
595    wrapper_name: &str,
596    args_size: usize,
597) {
598    unsafe {
599        match drwrap::skip_call(wrapctx, retval.clone(), args_size) {
600            true => {
601                log(&format!(
602                    "[{wrapper_name}] Successfully returned {:?}",
603                    retval
604                ));
605            }
606            false => panic!("[{wrapper_name}] skip_call failed"),
607        };
608    }
609}
610
611/// Check the current trajectory for a planned reaction to the `observed_attempt`. If not present,
612/// create the default `PlanningStep` for the `observed_attempt`.
613fn parse_planned_step(observed_attempt: Attempt, wrapper_name: &str) -> PlanningStep {
614    // Get the reaction from the configured trajectory (or default) and create the current step.
615    match trajectory::next(&observed_attempt) {
616        Ok(Some(current_step)) => {
617            log(&format!(
618                "[{wrapper_name}] following configured trajectory, current step: {:?}",
619                current_step
620            ));
621            current_step
622        }
623        // If the end of the trajectory has been reached, or if we are off trajectory, do the default behaviour.
624        Ok(None) | Err(_) => {
625            let default_react = React::default(&observed_attempt, Some(log));
626            let default_step =
627                PlanningStep::Graph(GraphStep::new(observed_attempt, default_react).expect(
628                    "React::default ensures that the default_react matches the attempt type",
629                ));
630            default_step
631        }
632    }
633}