puffin/algebra/
bitstrings.rs

1use std::fmt::Display;
2
3use libafl::inputs::{BytesInput, HasMutatorBytes};
4use serde::{Deserialize, Serialize};
5
6use crate::algebra::dynamic_function::TypeShape;
7use crate::algebra::{ConcreteMessage, DYTerm, Term, TermType};
8use crate::error::Error;
9use crate::error::Error::TermBug;
10use crate::fuzzer::utils::TermPath;
11use crate::protocol::{EvaluatedTerm, ProtocolBehavior, ProtocolTypes};
12use crate::trace::{Source, TraceContext};
13
14/// Constants governing heuristic for finding payloads in term evaluations
15const THRESHOLD_SIZE: usize = 3; // minimum size of a payload to be directly searched in root_eval
16const THRESHOLD_RATIO: usize = 100; // maximum ratio for root_eval/too_search for a direct search
17
18/// `TermMetadata` stores some metadata about terms.
19#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash, Default)]
20pub struct PayloadMetadata {
21    pub(crate) readable: bool, // true when the payload is readable and whole term should be read
22    pub(crate) has_changed: bool, // true when the payload has been modified at least once
23}
24
25/// `Term`s are `Term`s equipped with optional `Payloads` when they no longer are treated as
26/// symbolic terms.
27#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)]
28pub struct Payloads {
29    pub payload_0: BytesInput, // initially both are equal and correspond to the term evaluation
30    pub payload: BytesInput,   // this one will later be subject to bit-level mutation
31    pub(crate) metadata: PayloadMetadata, // stores metadata
32}
33
34impl Payloads {
35    #[must_use]
36    pub fn len(&self) -> usize {
37        self.payload_0.mutator_bytes().len()
38    }
39
40    pub fn is_empty(&self) -> bool {
41        self.len() == 0
42    }
43
44    #[must_use]
45    pub fn has_changed(&self) -> bool {
46        self.metadata.has_changed
47    }
48
49    pub fn set_changed(&mut self) {
50        self.metadata.has_changed = true
51    }
52}
53
54impl Display for Payloads {
55    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56        write!(
57            f,
58            "{{\n    payload_0: {:?}\n    payload:   {:?}\n}}",
59            self.payload_0.mutator_bytes(),
60            self.payload.mutator_bytes()
61        )
62    }
63}
64/// Payload with the context related to the term it originates from
65#[derive(Clone, Debug, PartialEq, Eq, Hash)]
66pub struct PayloadContext<'a, PT: ProtocolTypes> {
67    // not used if no payload to replace
68    of_term: &'a Term<PT>,  // point to the corresponding term
69    payloads: &'a Payloads, // point to the corresponding term.payload
70    path: TermPath,         // path of the sub-term from which this payload originates
71}
72
73impl<'a, PT: ProtocolTypes> Display for PayloadContext<'a, PT> {
74    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75        write!(
76            f,
77            "\n{{ of_term:\n{}\n, payloads: {}, path: {:?}\n}}",
78            self.of_term, self.payloads, self.path
79        )
80    }
81}
82
83/// A tree of evaluated term, linked to the term structure itself. Created while evaluating a term.
84#[derive(Clone, Debug, PartialEq, Eq, Hash)]
85pub struct EvalTree {
86    // not used (only the root) if no payload to replace
87    encode: Option<ConcreteMessage>, // encoding, if it was necessary and could be computed
88    path: TermPath,                  // path of the sub-term from which this payload originates
89    args: Vec<EvalTree>,             // tree structure
90}
91impl EvalTree {
92    #[must_use]
93    pub const fn empty() -> Self {
94        Self {
95            encode: None, /* will contain the bitstring encoding when sub-terms have payloads and
96                           * when evaluation succeeds */
97            path: TermPath::new(),
98            args: vec![], /* will be modified while evaluating `term` if there are payloads to
99                           * replace in this sub-tree */
100        }
101    }
102
103    #[must_use]
104    pub fn with_path(path: TermPath) -> Self {
105        let mut e_t = Self::empty();
106        e_t.path = path;
107        e_t
108    }
109
110    #[allow(dead_code)]
111    fn get(&self, path: &[usize]) -> Result<&Self, Error> {
112        if path.is_empty() {
113            return Ok(self);
114        }
115
116        let nb = path[0];
117        let path = &path[1..];
118        if self.args.len() <= nb {
119            return Err(Error::TermBug(format!(
120                "[replace_payloads] [get] Should never happen! self.args.len() <= nb. EvalTree: {self:?}\n, path: {path:?}"
121            )));
122        }
123        self.args[nb].get(path)
124    }
125}
126
127/// Checks if a get symbol is between the root and the path_to_search
128pub fn is_get_in_path<PT: ProtocolTypes>(term: &Term<PT>, path_to_search: &[usize]) -> bool {
129    for i in 0..path_to_search.len() {
130        // excluding the target
131        if term.get(&path_to_search[..i]).unwrap().is_get() {
132            return true;
133        }
134    }
135    false
136}
137
138/// Search and locate `to_search := eval_tree[path_to_search].encode` in
139/// `root_eval:=eval_tree`[vec![]].encode (=`whole_term.encode(ctx)`) such that the match
140/// corresponds to `path_to_search` (when `to_search` occurs multiple times). Also returns
141/// a boolean flag envcountered_get_symbol indicating whether a get symbol is between the root and
142/// the path_to_search, in which case, inconsistencies might occur.
143pub fn find_unique_match<PT: ProtocolTypes>(
144    path_to_search: &[usize],
145    eval_tree: &EvalTree,
146    whole_term: &Term<PT>,
147    is_to_search_in_list: bool,
148) -> Result<(usize, bool), Error> {
149    find_unique_match_rec(path_to_search, eval_tree, whole_term, is_to_search_in_list)
150}
151
152/// Goal: locate byte position of `to_search := eval_tree[path_to_search].encode` in
153/// `root_eval:=eval_tree`[vec![]].encode (=`whole_term.encode(ctx)`) by traversing `eval_tree`
154/// until reaching node at `pat_to_search`. Assumptions:
155/// - encoded arguments of a term can be found in this same order in the evaluation of the father
156///   node
157/// - there can be headers of arbitrary length
158/// - no trailer (no bytes added after the last argument encoding)
159/// Also returns a boolean flag envcountered_get_symbol indicating whether a get symbol is between
160/// the root and the path_to_search, in which case, inconsistencies might occur.
161pub fn find_unique_match_rec<PT: ProtocolTypes>(
162    path_to_search: &[usize],
163    eval_tree: &EvalTree,
164    whole_term: &Term<PT>,
165    is_to_search_in_list: bool,
166) -> Result<(usize, bool), Error> {
167    log::debug!("[find_unique_match_rec] --- [STARTING] with {path_to_search:?}");
168    log::trace!("[find_unique_match_rec] --- [STARTING] with {path_to_search:?},\n - eval_tree: {eval_tree:?},\n - to_search: {:?}", eval_tree.get(path_to_search)?.encode.as_ref().unwrap());
169    // We traverse the eval_tree and update the followings until we reach the node at
170    // `path_to_search`
171    let mut path_to_search = path_to_search;
172    let mut eval_tree = eval_tree;
173    let mut term = whole_term;
174    let mut start_pos = 0; // Current byte position of `term` in the original root term (encoded evaluations thereof)
175    let eval_to_search = eval_tree
176        .get(path_to_search)?
177        .encode
178        .as_ref()
179        .expect("[find_unique_match_rec] path_to_search should exist in eval_tree");
180    // Whether there is a get symbol above the target
181    let encountered_get_symbol = is_get_in_path(whole_term, path_to_search);
182
183    // For later debugging
184    #[cfg(any(debug_assertions, feature = "debug"))]
185    let eval_root_orig: Vec<u8>;
186    #[cfg(any(debug_assertions, feature = "debug"))]
187    let eval_to_search_orig: Vec<u8>;
188    #[cfg(any(debug_assertions, feature = "debug"))]
189    {
190        eval_root_orig = eval_tree.encode.as_ref().unwrap().clone();
191        eval_to_search_orig = eval_to_search.clone();
192    }
193
194    // [WHILE LOOP TRAVERSAL] We traverse eval_tree towards reaching the node at path_to_search
195    while !path_to_search.is_empty() {
196        // Getting child information
197        let parent_tp = term.get_type_shape();
198        let parent_is_get = term.is_get();
199        let parent_is_list = term.is_list();
200        let nb_children = eval_tree.args.len();
201        let child_arg_number = path_to_search[0];
202        log::debug!("[find_unique_match_rec] while step: {path_to_search:?}, nb_children: {nb_children}, child_arg: {child_arg_number}");
203        let eval_parent = eval_tree
204            .encode
205            .as_ref()
206            .expect("[find_unique_match_rec] EvalTree should have been computed");
207        let children = &eval_tree.args;
208
209        // Updating the term, eval_tree, path_to_search to the child
210        eval_tree = eval_tree.get(&[child_arg_number])?; // we move to the next child for the future while iteration
211        let eval_child = eval_tree
212            .encode
213            .as_ref()
214            .expect("[find_unique_match_rec] EvalTree should have been computed");
215        log::trace!("[find_unique_match_rec] --- [Rec call] with path_to_search (from parent):{path_to_search:?}, start_pos: {start_pos},\n - term_parent: {term}\n - eval_parent: {eval_parent:?}\n - eval_child: {eval_child:?}\n - eval_to_search: {eval_to_search:?}");
216        path_to_search = &path_to_search[1..];
217        term = term
218            .get(&[child_arg_number])
219            .expect("[find_unique_match_rec] term does not match eval_tree");
220
221        // [STEP 1: DIRECT SEARCH] Directly search for eval_too_search in root_eval
222        // We apply this fast heuristics if the likelyhood of a unique match seems high enough
223        if !eval_to_search.is_empty()
224            && (eval_to_search.len() > THRESHOLD_SIZE
225                || eval_parent.len() / eval_to_search.len() < THRESHOLD_RATIO)
226            && !encountered_get_symbol
227        // otherwise, we might find a match, which is not the right one
228        // and yet unique because the target is simply not present in the encoding.
229        {
230            if let Some(unique_pos) = first_sub_vec_unique(eval_parent, eval_to_search) {
231                log::debug!(
232                    "[find_unique_match_rec] [S1] Directly found unique match in root: {unique_pos}"
233                );
234                start_pos += unique_pos;
235                break;
236            } else {
237                log::debug!("[find_unique_match_rec] [S1] Direct found is not unique!");
238            }
239        }
240
241        // [STEP 2: SEARCH CHILD IN PARENT] Searching for the child encoding in the parent
242        // [2:1] [CASE EMPTY] Child encoding is empty, we will position relatively to the siblings
243        // This assumes no trailer and no intermediate header (i.e., not present of the children
244        // encodings.
245        if eval_child.is_empty() {
246            let mut eval_right_siblings = Vec::new();
247            for right_sibling in (child_arg_number + 1)..nb_children {
248                eval_right_siblings.extend_from_slice(
249                    children[right_sibling].encode.as_ref().expect(
250                        "[find_unique_match_rec] [S2:1] EvalTree should have been computed",
251                    ),
252                );
253            }
254            let pos_child = eval_parent.len() - eval_right_siblings.len();
255            log::debug!("[S2:1] eval_child has an empty encoding, we use right siblings to position the child: pos = {pos_child}");
256
257            start_pos += pos_child;
258            continue;
259        }
260
261        // [2:special-list] If the parent and the target is a list symbol, we use a dedicated
262        // heuristic to avoid paying the cost of searching the right position of the target in the
263        // list. This could be extremely costly when the list is long, e.g., same element is
264        // repeated many times. We assume there is no nested list of same type: that is if to_search
265        // and parents are in a list of the same type, it should be the same list.
266        if parent_is_list && is_to_search_in_list && !is_get_in_path(term, path_to_search) {
267            // do not proceed with the heuristics if there is a get symbol above the target
268            if child_arg_number == 0 && path_to_search.iter().all(|x| *x == 0) {
269                log::debug!("[find_unique_match_rec] [S2:special-list] [Nil*] Child is the NIL starting the list, pos is 0!");
270                break;
271            }
272            log::debug!("[S2:special-list] Child is a list, we use a special heuristic to find the position of the target in the parent");
273            if path_to_search.is_empty() {
274                // Based on last two conditions: we know child_arg_number == 1
275                #[cfg(any(debug_assertions, feature = "debug"))]
276                {
277                    assert_eq!(child_arg_number, 1);
278                }
279                // We know the target is the last element of the parent list
280                let pos = eval_parent.len() - eval_to_search.len();
281                log::debug!(
282                    "[find_unique_match_rec] [S2:special-list] [pos=1] Found position: {pos}"
283                );
284                start_pos += pos;
285                break;
286            }
287
288            // Else: path_to_search is not empty
289            // Since MakeMessage cannot be applied to is_list symbols, we know the target is a
290            // "leaf" (single element stored in the list). Therefore, we can locate the position
291            // of the target by computing the length of the parent of the target: we know this is
292            // located at the beginning of the parent encoding and we know the target is at the end
293            // of this encoding. This works because list elements are neither prefxed nor suffixed
294            // with neither headers nor trailers.
295            let eval_parent_to_search = eval_tree
296                .get(&path_to_search[..path_to_search.len() - 1])?
297                .encode
298                .as_ref()
299                .expect(
300                    "[find_unique_match_rec] path_to_search[0..len-1] should exist in eval_tree",
301                );
302            let parent_to_search_tp = term
303                .get(&path_to_search[..path_to_search.len() - 1])
304                .expect("[find_unique_match_rec] path_to_search[0..len-1] should exist in term")
305                .get_type_shape();
306            if parent_tp != parent_to_search_tp {
307                log::debug!(
308                    "[S2:special-list] [S2:1] Parent and target are not the same type, we cannot use the special heuristic. Continue..."
309                );
310            } else {
311                log::debug!(
312                    "[S2:special-list] Searching encoding of parent target (at {:?}) in parent... \n  - eval_parent_to_search:{eval_parent_to_search:?}\n  - eval_to_search: {eval_to_search:?}",
313                    path_to_search[0..path_to_search.len() - 1].to_vec()
314                );
315                let pos = eval_parent_to_search.len() - eval_to_search.len();
316                log::debug!(
317                    "[find_unique_match_rec] [S2:special-list] [skip] Found position: {pos}"
318                );
319                start_pos += pos;
320                break;
321            }
322        }
323
324        // [2:2] [CASE NON-EMPTY] We compute all matching positions of child in parent
325        let all_matches = search_sub_vec_all(eval_parent, eval_child);
326        log::debug!("[S2:2] Searched child #{child_arg_number} encoding in parent and found positions: {all_matches:?}. (for path {path_to_search:?})");
327
328        //   - If match is unique: we found the (unique) right position
329        if all_matches.len() == 1 {
330            // We found the child encoding in the root
331            log::debug!(
332                "[S2:2] Found position is unique: {}. Continue...",
333                all_matches[0]
334            );
335            start_pos += all_matches[0];
336            continue;
337        }
338        //   - No matching case: either parent is a `get` symbol --> could happen, or critical error
339        if all_matches.is_empty() {
340            let ft = format!(
341                "--> [find_unique_match_rec] [S2:2] Child {child_arg_number} encoding not found in root for path {path_to_search:?}\n - eval_parent:\n  {eval_parent:?}\n  - eval_child:\n  {eval_child:?}",
342            );
343            return if parent_is_get {
344                // This case is to be expected: we are looking for a child encoding that might just
345                // not been present in the encoding because the function symbol is a `get` symbol.
346                // No relevant payload replacement is possible --> We returns a simple error in that
347                // case.
348                Err(Error::Term(format!(
349                    "{}\n--> [S2:2] [symbol above was a get symbol so this is not a critical error]",
350                    ft
351                )))
352            } else {
353                Err(Error::TermBug(format!(
354                    "{}\n--> [S2:2] [symbol above was not a get symbol so this is a critical error]",
355                    ft
356                )))
357            };
358        }
359
360        // [STEP 2:2: POSITION CHILD RELATIVELY TO SIBLINGS] If no unique match, we must find which
361        // matching position of child relatively to the position of the evaluation of all
362        // right siblings.
363        if parent_is_get {
364            // do not proceed with the heuristics if parent is a get symbol
365            let ft = format!("[[find_unique_match_rec] [S2:2] Skips last resorting heuristics since the parant is a get symbol. Failed to find the target.");
366            log::warn!("{ft}");
367            return Err(Error::Term(format!("{ft}")));
368        }
369        // We first compute evaluation of all right siblings
370        let mut eval_right_siblings = Vec::new();
371        for right_sibling in (child_arg_number + 1)..nb_children {
372            eval_right_siblings.extend_from_slice(
373                children[right_sibling]
374                    .encode
375                    .as_ref()
376                    .expect("[find_unique_match_rec] [S2:2] EvalTree should have been computed"),
377            );
378        }
379
380        if let Some(pos_right_siblings) = last_sub_vec(eval_parent, &eval_right_siblings) {
381            //   - We found at least some match. We assume the real match is the last one:
382            //     pos_right_siblings
383            // We assume the right child position is the last match (idx) for which the end of the
384            // match would not overlap with the right siblings:
385            // all_matches[idx] + eval_child.len() <= pos_right_siblings
386            log::debug!("[find_unique_match_rec] [S2:2] [sib] Found last matching position of eval_right_siblings in eval_parent: pos_right_siblings= {pos_right_siblings}");
387            if let Some(pos_child) = all_matches
388                .iter()
389                .rev()
390                .find(|p| **p + eval_child.len() <= pos_right_siblings)
391            {
392                log::debug!("[find_unique_match_rec] [S2:2] Found pos_child: {pos_child} by comparing with pos_right_siblings: {pos_right_siblings}. Continue....");
393                start_pos += pos_child;
394                continue;
395            } else {
396                let ft = format!("[find_unique_match_rec] [S2:2] [sib] Could not find at least one appropriate idx for all_matches: {all_matches:?} and eval_child.len: {}, eval_parent.len(): {}, pos_right_siblings: {pos_right_siblings}. Continue....\n\
397                  - eval_right_siblings: {eval_right_siblings:?}\n\
398                  - eval_parent: {eval_parent:?}", eval_child.len(), eval_parent.len());
399                if !parent_is_get {
400                    return Err(Error::TermBug(ft));
401                } else {
402                    return Err(Error::Term(ft));
403                }
404            }
405        } else {
406            // right_sibling could not be found --> warning
407            #[cfg(any(debug_assertions, feature = "debug"))]
408            {
409                let ft = format!("[[find_unique_match_rec] [S2:2] [not-sib] Could not find right siblings encoding in eval_parent: {eval_parent:?} for path {path_to_search:?}. eval_right_siblings: {eval_right_siblings:?}");
410                return if parent_is_get {
411                    // This case is to be expected: we are looking for a child encoding that might
412                    // just not been present in the encoding because the
413                    // function symbol is a `get` symbol. No relevant payload
414                    // replacement is possible --> We returns a simple error in that
415                    // case.
416                    Err(Error::Term(format!("{ft}")))
417                } else {
418                    Err(Error::TermBug(format!("{ft}")))
419                };
420            }
421        }
422    }
423
424    log::debug!("[find_unique_match_rec] End of while, returning {start_pos}");
425
426    // Check consistencies in debug mode
427    #[cfg(any(debug_assertions, feature = "debug"))]
428    {
429        if !encountered_get_symbol {
430            log::debug!("[find_unique_match_rec] [DEBUG] Checking consistencies...");
431            assert!(start_pos + eval_to_search_orig.len() <= eval_root_orig.len());
432            assert_eq!(
433                eval_root_orig[start_pos..(start_pos + eval_to_search_orig.len())],
434                eval_to_search_orig
435            );
436        }
437    }
438
439    Ok((start_pos, encountered_get_symbol))
440}
441
442/// Operate the payloads replacements in `eval_tree.encode`[vec![]] and returns the modified
443/// bitstring by "splicing". `@payloads` follows this order: deeper terms first, left-to-right,
444/// assuming no overlap (no two terms one being a sub-term of the other).
445pub fn replace_payloads<PT: ProtocolTypes>(
446    term: &Term<PT>,
447    eval_tree: &mut EvalTree,
448    payloads: Vec<PayloadContext<PT>>,
449) -> Result<ConcreteMessage, Error> {
450    log::trace!("[replace_payload] --------> START");
451    let mut shift = 0_isize; // Number of bytes we need to shift on the right to operate the
452                             // replacement (i.e., apply the splicing, taking into account previous payloads replacements).
453                             // We assume the aforementioned invariant.
454    let mut to_modify: Vec<u8> = eval_tree.encode.as_ref().unwrap().clone(); // unwrap: eval_until_opaque
455                                                                             // returned an error if it cannot compute the encoding of the root having payloads
456    log::trace!("to_modify before: {to_modify:?}");
457    for payload_context in &payloads {
458        let path_payload = &payload_context.path;
459        log::trace!("[replace_payload] --------> treating {} at path {:?} on message of length = {}. Shift = {shift}", payload_context.payloads, payload_context.path, to_modify.len());
460        let old_bitstring = if term.has_variable() {
461            // We prefer looking for the payload in the term evaluation in case it has changed since
462            // MakeMessage because of variables (that might contain different values now)
463            eval_tree.get(path_payload)?.encode.as_ref().unwrap()
464        } else {
465            payload_context.payloads.payload_0.mutator_bytes()
466        };
467
468        // Consistency checks in debug mode, except when failures are to be expected
469        #[cfg(any(debug_assertions, feature = "debug"))]
470        if !term.has_variable() && !term.has_no_det() {
471            assert_eq!(
472                eval_tree.get(path_payload)?.encode.as_ref().unwrap(),
473                payload_context.payloads.payload_0.mutator_bytes()
474            );
475        }
476
477        // Goal: compute byte position of path_payload in eval_tree: `pos_start`
478        let is_to_search_in_list = if path_payload.is_empty() {
479            false
480        } else {
481            let path_parent = &path_payload[0..path_payload.len() - 1];
482            term.get(path_parent)
483                .expect("[replace_payload] Should never happen")
484                .is_list()
485        };
486        let (pos_start, encountered_get_symbol) =
487            find_unique_match(path_payload, eval_tree, term, is_to_search_in_list)?;
488        let old_bitstring_len = old_bitstring.len();
489        let new_bitstring = payload_context.payloads.payload.mutator_bytes();
490
491        let start = (pos_start as isize + shift) as usize; // taking previous replacements into account, we need to shift the start
492        let end = start + old_bitstring_len;
493
494        if (pos_start as isize + shift) < 0 || start + old_bitstring_len > to_modify.len() {
495            let ft = format!(
496                "--> [replace_payload] Impossible to splice because of incompatible indices.\n\
497                 - start = (pos_start as isize + shift) < 0: {start} = ({pos_start} + {shift}) < 0\n\
498                 - end = start + old_bitstring_len > to_modify.len(): {end} = ({pos_start} + {shift} + {old_bitstring_len}) as usize > {}\n\
499                 - payload_context: {payload_context}",
500                to_modify.len());
501            if !encountered_get_symbol && !term.has_no_det() {
502                log::error!("{ft}");
503                return Err(Error::TermBug(ft));
504            } else {
505                log::debug!("(with encountered_get_symbol) {ft}");
506                return Err(Error::Term(ft));
507            }
508        }
509
510        log::trace!("[replace_payload] About to splice for indices to_replace.len={}, range={start}..{end} (shift={shift})\n  - to_modify[start..end]={:?}\n  - old_bitstring={old_bitstring:?}\n  - to_modify={to_modify:?}",
511                to_modify.len(), &to_modify[start..end]);
512        #[cfg(any(debug_assertions, feature = "debug"))]
513        if to_modify[start..end] != *old_bitstring {
514            let ft = format!(
515                "--> [replace_payloads] Payloads returned by eval_until_opaque were inconsistent!\n\
516                   - payload_path: {:?}\n\
517                   - term: {term}\n\
518                   - payload_0.bytes() != to_modify[{start}..{end}].to_vec()\n\
519                   - old_bistring = payload_0.bytes()\n= {:?}\n\
520                   - to_modify[{start}..{end}].to_vec()\n= {:?}\n\
521                   - payload_context: {payload_context}\
522                   - payload: {:?}",
523                payload_context.path,
524                old_bitstring,
525                to_modify[start..end].to_vec(),
526                payload_context.payloads,
527            );
528            if !encountered_get_symbol {
529                log::error!("{ft}");
530                return Err(Error::TermBug(ft));
531            } else {
532                log::debug!("(encountered_get_symbol) {ft}");
533                return Err(Error::Term(ft));
534            }
535        }
536        // Performing the bytes replaceents through splicing
537        let to_remove: Vec<u8> = to_modify
538            .splice(start..end, new_bitstring.to_vec())
539            .collect();
540        log::trace!(
541            "[replace_payload] Removed elements (len={}): {:?}",
542            to_remove.len(),
543            &to_remove
544        );
545        log::trace!("[replace_payload] Shift update!: New_b: {}, old_b_len: {old_bitstring_len}, old_shift: {shift}, new_shift:{} ", new_bitstring.len(), shift + (new_bitstring.len() as isize - old_bitstring_len as isize));
546        shift += new_bitstring.len() as isize - old_bitstring_len as isize;
547    }
548    log::trace!("to_modify afterwards: {to_modify:?}");
549    Ok(to_modify)
550}
551
552impl<PT: ProtocolTypes> Term<PT> {
553    /// Evaluate a term without replacing the payloads (returning the payloads with the
554    /// corresponding paths instead, i.e., a `Vec<PayloadContext>` accumulator), except when
555    /// reaching an opaque term with payloads as strict sub-terms. In the latter case, fully
556    /// evaluate each of the arguments (and performing the payload replacements) before
557    /// evaluating the opaque function, which then needs to be read to convert it back to a
558    /// `Box<dyn EvaluatedTerm<PT>>`. @path: current path of &self in the overall recipe (initially
559    /// []). Invariant: Returns the payloads to replace in this order: deeper first, left-most
560    /// arguments first.
561    /// When `with_payloads` is false, then this should be equivalent to `evaluate_lazy_test` and it
562    /// always return empty `PayloadContext` vectors.
563    pub(crate) fn eval_until_opaque<PB>(
564        &self,
565        eval_tree: &mut EvalTree,
566        ctx: &TraceContext<PB>,
567        with_payloads: bool,
568        sibling_has_payloads: bool,
569        type_term: &TypeShape<PT>,
570    ) -> Result<(Box<dyn EvaluatedTerm<PT>>, Vec<PayloadContext<'_, PT>>), Error>
571    where
572        PB: ProtocolBehavior<ProtocolTypes = PT>,
573    {
574        log::trace!("[eval_until_opaque] [START]: Eval term:\n {self}");
575        // We optimize here by bypassing evaluation and directly read over the payload when the term
576        // has no variable. Indeed, variables may contain different values since when we ran
577        // MakeMessage
578        if let (true, Some(payload)) = (with_payloads && !self.has_variable(), &self.payloads) {
579            log::trace!(
580                "[eval_until_opaque] Trying to read payload_0 to skip further computations... payload_0: {:?}",
581                payload.payload_0.mutator_bytes(),
582            );
583            if let Ok(di) =
584                PB::try_read_bytes(payload.payload_0.mutator_bytes(), type_term.clone().into())
585            {
586                // We must make sure that we read correctly and avoided cases where read and encode
587                // are not inverse of each other. Otherwise, later payload replacements will fail.
588                if &di.get_encoding()[..] != &payload.payload_0.mutator_bytes()[..] {
589                    log::warn!(
590                                "--> [eval_until_opaque] [argument is symbolic: {}] [Skipping eval bypass] Failed consistency check for read.encode a type {}:\n\
591                                - bi (first eval)  : {:?}\n\
592                                - read.encode:     : {:?}",
593                                self.is_symbolic(),
594                                <TypeShape<PT> as Clone>::clone(type_term),
595                                payload.payload_0.mutator_bytes(),
596                                di.get_encoding(),
597                            );
598                    log::trace!("Read EvaluatedTerm was: {di:?}");
599                } else {
600                    log::trace!("[eval_until_opaque] Successfully read term: {:?}", di);
601                    let p_c = vec![PayloadContext {
602                        of_term: self,
603                        payloads: payload,
604                        path: eval_tree.path.clone(),
605                    }];
606                    eval_tree.encode = Some(payload.payload_0.mutator_bytes().to_vec());
607                    return Ok((di, p_c));
608                }
609            } else {
610                // We expect this might fail because encode and read are not fully inverse of each
611                // other. For example: read(encode(PayloadU8)) != PayloadU8 when its size exceeds
612                // 255 because: encode will write it all, while read will only read
613                // the announced length. Note: we don't want to make encode only write the announced
614                // length since we precisely want to be able to "lie" about the announced lengths...
615                log::trace!("[eval_until_opaque] Attempt to skip evaluation failed, fall back to normal evaluation...");
616            }
617        }
618
619        // Specific case of readable terms (originated from ReadMessage): if the term is a readable
620        // term, we MUST read it directly from payload!
621        if with_payloads && self.is_readable() {
622            let payload = self.payloads.as_ref().ok_or(TermBug(format!("[eval_until_opaque] Try to read a readable term without payload, should never happen!")))?;
623            log::trace!(
624                "[eval_until_opaque] Trying to read payload (originated from ReadMessage): {:?}",
625                payload.payload.mutator_bytes(),
626            );
627            if let Ok(di) = PB::try_read_bytes(
628                payload.payload.mutator_bytes(),
629                self.get_type_shape().clone().into(),
630            ) {
631                log::trace!("[eval_until_opaque] Successfully read term: {:?}", di);
632                // We have set payload to di.get_encoding() when performing ReadMessage, so we can
633                // use it here
634                eval_tree.encode = Some(payload.payload.mutator_bytes().to_vec());
635                return Ok((di, vec![]));
636            } else {
637                log::error!("[eval_until_opaque] Fail to read a readable term (originated from ReadMessage), should never happen! payload:\n{:?}\n payload_0:\n{:?}. Has changed: {}", payload.payload.mutator_bytes(), payload.payload_0.mutator_bytes(), payload.has_changed());
638                return Err(TermBug(format!("[eval_until_opaque] Fail to read a readable term (originated from ReadMessage), should never happen!")));
639            }
640        }
641
642        match &self.term {
643            DYTerm::Variable(variable) => {
644                let d = ctx
645                    .find_variable(variable.typ.clone(), &variable.query)
646                    .map(|data| data.boxed())
647                    .or_else(|| {
648                        if let Some(Source::Agent(agent_name)) = &variable.query.source {
649                            ctx.find_claim(*agent_name, variable.typ.clone())
650                        } else {
651                            // Claims doesn't have precomputations as source
652                            None
653                        }
654                    })
655                    .ok_or_else(|| {
656                        Error::Term(format!("--> Unable to find variable {variable}!"))
657                    })?;
658                if with_payloads {
659                    // TODO: we might want to relax this a bit and only do this for nodes that are
660                    // in the paths towards a payload or a right sibling of such a node. Not sure
661                    // this woulf save us a lot of computations though.
662                    let eval = PB::any_get_encoding(d.as_ref());
663                    log::trace!("        / Variable evaluated into: {eval:?}");
664                    eval_tree.encode = Some(eval);
665                    if self.payloads.is_some() {
666                        log::trace!("[eval_until_opaque] [Var] Add a payload for a leaf at path: {:?}, payload is: {:?} and eval is: {:?}", eval_tree.path, self.payloads.as_ref().unwrap(), PB::any_get_encoding(d.as_ref()));
667                        return Ok((
668                            d,
669                            vec![PayloadContext {
670                                of_term: self,
671                                payloads: self.payloads.as_ref().unwrap(),
672                                path: eval_tree.path.clone(),
673                            }],
674                        ));
675                    }
676                }
677                log::trace!("[eval_until_opaque] [Var] Did not add a payload for a leaf at path: {:?} and eval is: {:?}", eval_tree.path, PB::any_get_encoding(d.as_ref()));
678                Ok((d, vec![]))
679            }
680            DYTerm::Application(func, args) => {
681                log::trace!(
682                    "[eval_until_opaque] [App]: Application from path={:?}",
683                    eval_tree.path
684                );
685                let mut dynamic_args: Vec<Box<dyn EvaluatedTerm<PT>>> = Vec::new(); // will contain all the arguments on which to call the function symbol
686                                                                                    // implementation
687                let mut all_payloads = vec![]; // will collect all payloads contexts of arguments (except those under opaque
688                                               // function symbols)
689                let mut eval_tree_args = vec![]; // will collect the eval tree of the sub-terms, if `with_payloads`
690                let self_has_payloads_wo_root = self.has_payload_to_replace_wo_root();
691                for (i, ti) in args.iter().enumerate() {
692                    log::trace!(
693                        "  + Treating argument # {i} from path {:?}...",
694                        eval_tree.path
695                    );
696                    if with_payloads && self.is_opaque() && ti.has_payload_to_replace() {
697                        // Fully evaluate this sub-term and consume the payloads
698                        log::trace!("    * [eval_until_opaque] Opaque and has payloads: Inner call of eval on term: {}\n with #{} payloads", ti, ti.payloads_to_replace().len());
699                        let typei = ti.get_type_shape();
700                        let bi = ti.evaluate(ctx)?; // payloads in ti are consumed here!
701                        let di = PB::try_read_bytes(&bi, typei.clone().into()) // TODO: to make this more robust, we might want to relax this when payloads are in deeper terms, then read there!
702                            .map_err(|e| {
703                                if !ti.is_symbolic() {
704                                    log::warn!("[eval_until_opaque] [Argument has payload, might explain why] Warn: {}", e);
705                                } else {
706                                    log::warn!("[eval_until_opaque] [Argument is symbolic!] Err: {}", e);
707                                }
708                                e
709                            })?; // This may fail for good or bad reasons, we don't distinguish for now
710                                 // We must make sure that we read correctly and avoided cases where read and
711                                 // encode are not inverse of each other.
712                                 // Otherwise, later payload replacements will fail.
713                        if &di.get_encoding()[..] != &bi[..] {
714                            return Err(Error::Term(format!(
715                                "--> [eval_until_opaque] [argument is symbolic: {}] [1] Failed consistency check for read.encode a type {}:\n\
716                                - bi (first eval)  : {bi:?}\n\
717                                - read.encode:     : {:?}",
718                                ti.is_symbolic(),
719                                typei,
720                                di.get_encoding(),
721                            )));
722                        }
723
724                        dynamic_args.push(di); // no need to add payloads to all_p as they were
725                                               // consumed (opaque function symbol)
726                    } else {
727                        let mut path_i = eval_tree.path.clone();
728                        path_i.push(i); // adding `i` for i-th argument
729                        let mut eval_tree_i = if with_payloads {
730                            EvalTree::with_path(path_i.clone())
731                        } else {
732                            EvalTree::with_path(vec![]) // dummy eval_tree
733                        };
734                        let (di, mut p_s) = ti.eval_until_opaque(
735                            &mut eval_tree_i,
736                            ctx,
737                            with_payloads,
738                            self_has_payloads_wo_root,
739                            &ti.get_type_shape(),
740                        )?;
741                        dynamic_args.push(di); // add the evaluation (Boc<dyn Any>) to the list of arguments
742                        if with_payloads {
743                            eval_tree_args.push(eval_tree_i);
744                            all_payloads.append(p_s.as_mut()); // collect the payloads
745                        }
746                        log::trace!(
747                            "  + Ending treating argument # {i} from path {:?}...",
748                            eval_tree.path
749                        );
750                    }
751                }
752                log::trace!("[eval_until_opaque] Now calling the function symbol {} implementation and then updating payloads...", func.name());
753                let dynamic_fn = &func.dynamic_fn();
754                let result: Box<dyn EvaluatedTerm<PT>> = dynamic_fn(&dynamic_args)?; // evaluation of the function symbol implementation
755
756                if with_payloads && self.payloads.is_some() {
757                    all_payloads.push(PayloadContext {
758                        of_term: self,
759                        payloads: self.payloads.as_ref().unwrap(),
760                        path: eval_tree.path.clone(),
761                    });
762                }
763
764                // Sanity check!
765                #[cfg(any(debug_assertions, feature = "debug"))]
766                if let (true, Some(payload)) = (with_payloads, &self.payloads) {
767                    log::trace!("[eval_until_opaque] Checking consistency of new evaluation!");
768                    let new_payload_0 = PB::any_get_encoding(result.as_ref());
769                    if new_payload_0 != payload.payload_0.mutator_bytes() {
770                        let ft = format!("--> [eval_until_opaque] [term has variable:{}] Failed consistency check payload_0 versus new encoding.\n\
771                                    - term: {}\n\
772                                    - payload_0:    {:?}\n\
773                                    - payload:    {:?}\n\
774                                    - new_eval:     {new_payload_0:?}\n\
775                                    - result: {result:?}",
776                                         self.has_variable(), self, &payload.payload_0.mutator_bytes(), &payload.payload.mutator_bytes());
777                        if self.has_variable() || self.has_no_det() {
778                            // Some mismatches are to be expected when there are variables or no
779                            // deterministic function symbols
780                            log::trace!("[term has variables or no det function symbols --> only a log::trace] {}", ft);
781                        } else {
782                            return Err(Error::TermBug(ft));
783                        }
784                    }
785                }
786
787                // If there are payloads to replace in self or siblings, then we will *likely* have
788                // to know the encoding of self, we save it for later in eval_tree
789                if with_payloads && (!all_payloads.is_empty() || sibling_has_payloads) {
790                    eval_tree.args = eval_tree_args;
791                    let eval = PB::any_get_encoding(result.as_ref());
792                    log::trace!("        / We successfully evaluated the term into: {eval:?}");
793                    eval_tree.encode = Some(eval);
794                }
795
796                Ok((result, all_payloads))
797            }
798        }
799    }
800}
801
802/// Return all the matching positions
803#[must_use]
804pub fn search_sub_vec_all(haystack: &[u8], needle: &[u8]) -> Vec<usize> {
805    let mut matches = Vec::new();
806    if haystack.len() < needle.len() {
807        // log::trace!("search_sub_vec_double: length");
808        return matches;
809    }
810    for i in 0..=(haystack.len() - needle.len()) {
811        if haystack[i..i + needle.len()] == needle[..] {
812            // log::trace!("search_sub_vec_double: found for i:{i}");
813            matches.push(i);
814        }
815    }
816    // log::trace!("search_sub_vec_double: not found");
817    matches
818}
819
820/// Return the last matching positions, if any
821#[must_use]
822pub fn last_sub_vec(haystack: &[u8], needle: &[u8]) -> Option<usize> {
823    if haystack.len() < needle.len() {
824        return None;
825    }
826    for i in (0..=(haystack.len() - needle.len())).rev() {
827        if haystack[i..i + needle.len()] == needle[..] {
828            return Some(i);
829        }
830    }
831    None
832}
833
834/// Return the first matching position when it is unique, and None otherwise
835pub fn first_sub_vec_unique(haystack: &[u8], needle: &[u8]) -> Option<usize> {
836    if haystack.len() < needle.len() {
837        // log::trace!("search_sub_vec_double: length");
838        return None;
839    }
840    let mut first_found = false;
841    let mut pos = 0;
842    for i in 0..=(haystack.len() - needle.len()) {
843        if haystack[i..i + needle.len()] == needle[..] {
844            // log::trace!("search_sub_vec_double: found for i:{i}");
845            if !first_found {
846                pos = i;
847                first_found = true;
848            } else {
849                // Double matches
850                return None;
851            }
852        }
853    }
854    // log::trace!("search_sub_vec_double: not found");
855    if first_found {
856        Some(pos)
857    } else {
858        None
859    }
860}