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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
// string_collection.rs
//
// This file is part of the Eclipse Paho MQTT Rust Client library.
//
// A string_collection is a helper to bridge between a collection of
// strings in Rust to an array of NUL terminated char string pointers
// that  the C library expects.
//
// It is useful when a C API takes a `const char *arg[]` parameter.
//

/*******************************************************************************
 * Copyright (c) 2017-2020 Frank Pagliughi <fpagliughi@mindspring.com>
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Eclipse Distribution License v1.0 which accompany this distribution.
 *
 * The Eclipse Public License is available at
 *    http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at
 *   http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *    Frank Pagliughi - initial implementation and documentation
 *******************************************************************************/

use std::{ffi::CString, os::raw::c_char, pin::Pin};

/// A collection of C-compatible (NUL-terminated) strings that is useful
/// with C API's that require an array of strings, normally specified as:
/// `const char* arr[]` or  `const char** arr`
#[derive(Debug)]
pub struct StringCollection {
    /// A vector cache of pointers into the data `coll`
    /// This must be updated any time the data is modified.
    c_coll: Vec<*const c_char>,
    /// A vector cache of mut pointers into the data `coll`
    /// This must be updated any time the data is modified.
    c_mut_coll: Vec<*mut c_char>,
    /// The pinned data cache
    data: Pin<Box<StringCollectionData>>,
}

#[derive(Debug, Default, Clone)]
struct StringCollectionData {
    /// The owned NUL-terminated strings
    coll: Vec<CString>,
}

impl StringCollection {
    /// Creates a StringCollection from a vector of string references.
    ///
    /// # Arguments
    ///
    /// `coll` A collection of string references.
    ///
    pub fn new<T>(coll: &[T]) -> Self
    where
        T: AsRef<str>,
    {
        let data = StringCollectionData {
            coll: Self::to_cstring(coll),
        };
        Self::from_data(data)
    }

    // Convert a collection of string references to a vector of CStrings.
    fn to_cstring<T>(coll: &[T]) -> Vec<CString>
    where
        T: AsRef<str>,
    {
        coll.iter()
            .map(|s| CString::new(s.as_ref()).unwrap())
            .collect()
    }

    // Convert a collection of CString's to a vector of C char pointers.
    //
    // Note that the pointers are invalidated if the original vector or
    // any of the strings in it change.
    fn to_c_vec(sv: &[CString]) -> Vec<*const c_char> {
        sv.iter().map(|cs| cs.as_ptr()).collect()
    }

    // Convert a collection of CString's to a vector of C char pointers.
    //
    // Note that the pointers are invalidated if the original vector or
    // any of the strings in it change.
    fn to_c_mut_vec(sv: &[CString]) -> Vec<*mut c_char> {
        sv.iter().map(|cs| cs.as_ptr() as *mut c_char).collect()
    }

    // Updates the cached vector to correspond to the string.
    fn from_data(data: StringCollectionData) -> Self {
        let data = Box::pin(data);
        let c_coll = Self::to_c_vec(&data.coll);
        let c_mut_coll = Self::to_c_mut_vec(&data.coll);
        Self {
            c_coll,
            c_mut_coll,
            data,
        }
    }

    /// Returns true if the collection contains elements.
    pub fn is_empty(&self) -> bool {
        self.data.coll.is_empty()
    }

    /// Gets the number of strings in the collection.
    pub fn len(&self) -> usize {
        self.data.coll.len()
    }

    /// Gets the collection as a pointer to const C string pointers.
    ///
    /// This returns a pointer that can be sent to a C API that takes a
    /// pointer to an array of char pointers, like `const char* arr[]`
    /// This function is inherently unsafe. The pointer it returns is only
    /// valid while the collection remains unmodified. In general, it
    /// should be requested when needed and not stored for future use.
    pub fn as_c_arr_ptr(&self) -> *const *const c_char {
        self.c_coll.as_ptr()
    }

    /// Gets the collection as a pointer to mutable C string pointers.
    ///
    /// This returns a pointer that can be sent to a C API that takes a
    /// pointer to an array of mutable char pointers, like `char* arr[]`
    /// This function is inherently unsafe. The pointer it returns is only
    /// valid while the collection remains unmodified. In general, it
    /// should be requested when needed and not stored for future use.
    ///
    /// This function is required due to the lax nature of the use of
    /// const strings in the C API. Hopefully the API will be fixed and
    /// this function can be removed
    pub fn as_c_arr_mut_ptr(&self) -> *const *mut c_char {
        self.c_mut_coll.as_ptr()
    }
}

impl Default for StringCollection {
    fn default() -> Self {
        Self::from_data(StringCollectionData::default())
    }
}

impl Clone for StringCollection {
    fn clone(&self) -> Self {
        Self::from_data((*self.data).clone())
    }
}

/////////////////////////////////////////////////////////////////////////////
//                              Unit Tests
/////////////////////////////////////////////////////////////////////////////

#[cfg(test)]
mod tests {
    use super::*;

    macro_rules! vec_of_strings {
        ($($x:expr),*) => (vec![$($x.to_string()),*]);
    }

    #[test]
    fn test_default() {
        let sc = StringCollection::default();
        assert_eq!(0, sc.len());
    }

    #[test]
    fn test_new() {
        let v = ["string0", "string1", "string2"];
        let n = v.len();

        let sc = StringCollection::new(&v);

        assert_eq!(n, sc.len());
        assert_eq!(n, sc.c_coll.len());
        assert_eq!(n, sc.c_mut_coll.len());
        assert_eq!(n, sc.data.coll.len());

        assert_eq!(v[0].as_bytes(), sc.data.coll[0].as_bytes());
        assert_eq!(v[1].as_bytes(), sc.data.coll[1].as_bytes());
        assert_eq!(v[2].as_bytes(), sc.data.coll[2].as_bytes());

        assert_eq!(sc.data.coll[0].as_ptr(), sc.c_coll[0]);
        assert_eq!(sc.data.coll[1].as_ptr(), sc.c_coll[1]);
        assert_eq!(sc.data.coll[2].as_ptr(), sc.c_coll[2]);
    }

    #[test]
    fn test_new_from_vec_strings() {
        let v = vec_of_strings!["string0", "string1", "string2"];
        let n = v.len();

        let sc = StringCollection::new(&v);

        assert_eq!(n, sc.len());
        assert_eq!(n, sc.c_coll.len());
        assert_eq!(n, sc.c_mut_coll.len());
        assert_eq!(n, sc.data.coll.len());

        assert_eq!(v[0].as_bytes(), sc.data.coll[0].as_bytes());
        assert_eq!(v[1].as_bytes(), sc.data.coll[1].as_bytes());
        assert_eq!(v[2].as_bytes(), sc.data.coll[2].as_bytes());

        assert_eq!(sc.data.coll[0].as_ptr(), sc.c_coll[0]);
        assert_eq!(sc.data.coll[1].as_ptr(), sc.c_coll[1]);
        assert_eq!(sc.data.coll[2].as_ptr(), sc.c_coll[2]);
    }

    #[test]
    fn test_assign() {
        let v = ["string0", "string1", "string2"];
        let n = v.len();

        let org_sc = StringCollection::new(&v);

        let sc = org_sc;

        assert_eq!(n, sc.len());
        assert_eq!(n, sc.c_coll.len());
        assert_eq!(n, sc.c_mut_coll.len());
        assert_eq!(n, sc.data.coll.len());

        assert_eq!(v[0].as_bytes(), sc.data.coll[0].as_bytes());
        assert_eq!(v[1].as_bytes(), sc.data.coll[1].as_bytes());
        assert_eq!(v[2].as_bytes(), sc.data.coll[2].as_bytes());

        assert_eq!(sc.data.coll[0].as_ptr(), sc.c_coll[0]);
        assert_eq!(sc.data.coll[1].as_ptr(), sc.c_coll[1]);
        assert_eq!(sc.data.coll[2].as_ptr(), sc.c_coll[2]);
    }

    #[test]
    fn test_clone() {
        let v = ["string0", "string1", "string2"];
        let n = v.len();

        let sc = {
            let org_sc = StringCollection::new(&v);
            org_sc.clone()
        };

        assert_eq!(n, sc.len());
        assert_eq!(n, sc.c_coll.len());
        assert_eq!(n, sc.c_mut_coll.len());
        assert_eq!(n, sc.data.coll.len());

        assert_eq!(v[0].as_bytes(), sc.data.coll[0].as_bytes());
        assert_eq!(v[1].as_bytes(), sc.data.coll[1].as_bytes());
        assert_eq!(v[2].as_bytes(), sc.data.coll[2].as_bytes());

        assert_eq!(sc.data.coll[0].as_ptr(), sc.c_coll[0]);
        assert_eq!(sc.data.coll[1].as_ptr(), sc.c_coll[1]);
        assert_eq!(sc.data.coll[2].as_ptr(), sc.c_coll[2]);
    }
}