1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
//! String-manipulation utilities

/// Return the position of one string slice within another.
///
/// If `needle` is indeed part of `haystack`, returns some offset
/// `off`, such that `needle` is the same as
/// `&haystack[off..needle.len()]`.
///
/// Returns None if `needle` is not a part of `haystack`.
///
/// Remember, offsets are in bytes, not in characters.
///
/// # Example
/// ```ignore
/// use tor_netdoc::util::str_offset;
/// let quote = "A rose is a rose is a rose."; // -- Gertrude Stein
/// assert_eq!(&quote[2..6], "rose");
/// assert_eq!(str_offset(quote, &quote[2..6]).unwrap(), 2);
/// assert_eq!(&quote[12..16], "rose");
/// assert_eq!(str_offset(quote, &quote[12..16]).unwrap(), 12);
/// assert_eq!(&quote[22..26], "rose");
/// assert_eq!(str_offset(quote, &quote[22..26]).unwrap(), 22);
///
/// assert_eq!(str_offset(quote, "rose"), None);
///
/// assert_eq!(str_offset(&quote[1..], &quote[2..6]), Some(1));
/// assert_eq!(str_offset(&quote[1..5], &quote[2..6]), None);
/// ```
pub(crate) fn str_offset(haystack: &str, needle: &str) -> Option<usize> {
    let needle_start_u = needle.as_ptr() as usize;
    let needle_end_u = needle_start_u + needle.len();
    let haystack_start_u = haystack.as_ptr() as usize;
    let haystack_end_u = haystack_start_u + haystack.len();
    if haystack_start_u <= needle_start_u && needle_end_u <= haystack_end_u {
        Some(needle_start_u - haystack_start_u)
    } else {
        None
    }
}

/// An extent within a given string slice.
///
/// This whole type is probably naughty and shouldn't exist.  We use
/// it only within this crate, to remember where it was that we found
/// parsed objects within the strings we got them from.
#[derive(Clone, Debug)]
pub(crate) struct Extent {
    /// At what position within the original string is this extent, in bytes?
    offset: usize,
    /// How long is this extend, in bytes?
    length: usize,
    /// What was the original string?
    ///
    /// If this doesn't match, there's been an error.
    sliceptr: *const u8,
    /// How long was the original string?
    ///
    /// If this doesn't match, there's been an error.
    slicelen: usize,
}

impl Extent {
    /// Construct a new extent to represent the position of `needle`
    /// within `haystack`.
    ///
    /// Return None if `needle` is not in fact a slice of `haystack`.
    pub(crate) fn new(haystack: &str, needle: &str) -> Option<Extent> {
        str_offset(haystack, needle).map(|offset| Extent {
            offset,
            length: needle.len(),
            sliceptr: haystack.as_ptr(),
            slicelen: haystack.len(),
        })
    }
    /// Reconstruct the original `needle` within `haystack`.
    ///
    /// Return None if we're sure that the haystack doesn't match the one
    /// we were originally given.
    ///
    /// Note that it is possible for this to give a bogus result if
    /// provided a new haystack that happens to be at the same
    /// position in memory as the original one.
    pub(crate) fn reconstruct<'a>(&self, haystack: &'a str) -> Option<&'a str> {
        if self.sliceptr != haystack.as_ptr() || self.slicelen != haystack.len() {
            None
        } else {
            haystack.get(self.offset..self.offset + self.length)
        }
    }
}

#[cfg(test)]
mod test {
    // @@ begin test lint list maintained by maint/add_warning @@
    #![allow(clippy::bool_assert_comparison)]
    #![allow(clippy::clone_on_copy)]
    #![allow(clippy::dbg_macro)]
    #![allow(clippy::print_stderr)]
    #![allow(clippy::print_stdout)]
    #![allow(clippy::single_char_pattern)]
    #![allow(clippy::unwrap_used)]
    #![allow(clippy::unchecked_duration_subtraction)]
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->

    #[test]
    fn test_str_offset() {
        use super::str_offset;
        let quote = "A rose is a rose is a rose."; // -- Gertrude Stein
        assert_eq!(&quote[2..6], "rose");
        assert_eq!(str_offset(quote, &quote[2..6]).unwrap(), 2);
        assert_eq!(&quote[12..16], "rose");
        assert_eq!(str_offset(quote, &quote[12..16]).unwrap(), 12);
        assert_eq!(&quote[22..26], "rose");
        assert_eq!(str_offset(quote, &quote[22..26]).unwrap(), 22);

        assert_eq!(str_offset(quote, "rose"), None);

        assert_eq!(str_offset(&quote[1..], &quote[2..6]), Some(1));
        assert_eq!(str_offset(&quote[1..5], &quote[2..6]), None);
    }

    #[test]
    fn test_extent() {
        use super::Extent;
        let quote = "What is a winter wedding a winter wedding."; // -- ibid
        assert_eq!(&quote[10..16], "winter");
        let ex = Extent::new(quote, &quote[10..16]).unwrap();
        let s = ex.reconstruct(quote).unwrap();
        assert_eq!(s, "winter");

        assert!(Extent::new(quote, "winter").is_none());
        assert!(ex.reconstruct("Hello world").is_none());
    }
}