polylineffi/
lib.rs

1#![doc(
2    html_logo_url = "https://cdn.rawgit.com/urschrei/polyline-ffi/master/line.svg",
3    html_root_url = "https://docs.rs/polyline-ffi/"
4)]
5//! This module exposes functions for accessing the Polyline encoding and decoding functions via FFI
6//!
7//!
8//! ## A Note on Coordinate Order
9//! This crate uses `Coordinate` and `LineString` types from the `geo-types` crate, which encodes coordinates in `(x, y)` order. The Polyline algorithm and first-party documentation assumes the _opposite_ coordinate order. It is thus advisable to pay careful attention to the order of the coordinates you use for encoding and decoding.
10
11#![deny(
12    clippy::cast_slice_from_raw_parts,
13    clippy::cast_slice_different_sizes,
14    clippy::invalid_null_ptr_usage,
15    clippy::ptr_as_ptr,
16    clippy::transmute_ptr_to_ref
17)]
18
19use polyline::{decode_polyline, encode_coordinates};
20use std::ffi::{CStr, CString};
21use std::slice;
22use std::{f64, ptr};
23
24use geo_types::{CoordFloat, LineString};
25use libc::c_char;
26
27// we only want to allow 5 or 6, but we need the previous values for the cast to work
28#[allow(dead_code)]
29enum Precision {
30    Zero,
31    One,
32    Two,
33    Three,
34    Four,
35    Five,
36    Six,
37}
38
39// We currently only allow 5 or 6
40fn get_precision(input: u32) -> Option<u32> {
41    match input {
42        5 => Some(Precision::Five as u32),
43        6 => Some(Precision::Six as u32),
44        _ => None,
45    }
46}
47
48/// A C-compatible `struct` originating **outside** Rust
49/// used for passing arrays across the FFI boundary
50#[repr(C)]
51pub struct ExternalArray {
52    pub data: *const libc::c_void,
53    pub len: libc::size_t,
54}
55
56/// A C-compatible `struct` originating **inside** Rust
57/// used for passing arrays across the FFI boundary
58#[repr(C)]
59pub struct InternalArray {
60    pub data: *mut libc::c_void,
61    pub len: libc::size_t,
62}
63
64impl Drop for InternalArray {
65    fn drop(&mut self) {
66        if self.data.is_null() {
67            return;
68        }
69        unsafe {
70            // we originated this data, so pointer-to-slice -> box -> vec
71            let p = ptr::slice_from_raw_parts_mut(self.data.cast::<[f64; 2]>(), self.len);
72            drop(Box::from_raw(p));
73        };
74    }
75}
76
77// Build an InternalArray from a LineString, so it can be leaked across the FFI boundary
78impl<T> From<LineString<T>> for InternalArray
79where
80    T: CoordFloat,
81{
82    fn from(sl: LineString<T>) -> Self {
83        let v: Vec<[T; 2]> = sl.0.iter().map(|p| [p.x, p.y]).collect();
84        let boxed = v.into_boxed_slice();
85        let blen = boxed.len();
86        let rawp = Box::into_raw(boxed);
87        InternalArray {
88            data: rawp.cast::<libc::c_void>(),
89            len: blen as libc::size_t,
90        }
91    }
92}
93
94// Build a LineString from an InternalArray
95impl From<InternalArray> for LineString<f64> {
96    fn from(arr: InternalArray) -> Self {
97        // we originated this data, so pointer-to-slice -> box -> vec
98        unsafe {
99            let p = ptr::slice_from_raw_parts_mut(arr.data.cast::<[f64; 2]>(), arr.len);
100            let v = Box::from_raw(p).to_vec();
101            v.into()
102        }
103    }
104}
105
106// Build an InternalArray from a LineString, so it can be leaked across the FFI boundary
107impl From<Vec<[f64; 2]>> for InternalArray {
108    fn from(v: Vec<[f64; 2]>) -> Self {
109        let boxed = v.into_boxed_slice();
110        let blen = boxed.len();
111        let rawp = Box::into_raw(boxed);
112        InternalArray {
113            data: rawp.cast::<libc::c_void>(),
114            len: blen as libc::size_t,
115        }
116    }
117}
118
119// Build an InternalArray from a LineString, so it can be leaked across the FFI boundary
120impl From<Vec<[f64; 2]>> for ExternalArray {
121    fn from(v: Vec<[f64; 2]>) -> Self {
122        let boxed = v.into_boxed_slice();
123        let blen = boxed.len();
124        let rawp = Box::into_raw(boxed);
125        ExternalArray {
126            data: rawp.cast::<libc::c_void>(),
127            len: blen as libc::size_t,
128        }
129    }
130}
131
132// Build a LineString from an ExternalArray
133impl From<ExternalArray> for LineString<f64> {
134    fn from(arr: ExternalArray) -> Self {
135        // we need to take ownership of this data, so slice -> vec
136        unsafe {
137            let v = slice::from_raw_parts(arr.data as *mut [f64; 2], arr.len).to_vec();
138            v.into()
139        }
140    }
141}
142
143// Decode a Polyline into an InternalArray
144fn arr_from_string(incoming: &str, precision: u32) -> InternalArray {
145    let result: InternalArray = if get_precision(precision).is_some() {
146        match decode_polyline(incoming, precision) {
147            Ok(res) => res.into(),
148            // should be easy to check for
149            Err(_) => vec![[f64::NAN, f64::NAN]].into(),
150        }
151    } else {
152        // bad precision parameter
153        vec![[f64::NAN, f64::NAN]].into()
154    };
155    result
156}
157
158// Decode an Array into a Polyline
159fn string_from_arr(incoming: ExternalArray, precision: u32) -> String {
160    let inc: LineString<_> = incoming.into();
161    if get_precision(precision).is_some() {
162        match encode_coordinates(Into::<LineString<_>>::into(inc), precision) {
163            Ok(res) => res,
164            // we don't need to adapt the error
165            Err(res) => res,
166        }
167    } else {
168        "Bad precision parameter supplied".to_string()
169    }
170}
171
172/// Convert a Polyline into an array of coordinates
173///
174/// Callers must pass two arguments:
175///
176/// - a pointer to `NUL`-terminated characters (`char*`)
177/// - an unsigned 32-bit `int` for precision (5 for Google Polylines, 6 for
178/// OSRM and Valhalla Polylines)
179///
180/// A decoding failure will return an [Array](struct.Array.html) whose `data` field is `[[NaN, NaN]]`, and whose `len` field is `1`.
181///
182/// Implementations calling this function **must** call [`drop_float_array`](fn.drop_float_array.html)
183/// with the returned [Array](struct.Array.html), in order to free the memory it allocates.
184///
185/// # Safety
186///
187/// This function is unsafe because it accesses a raw pointer which could contain arbitrary data
188#[no_mangle]
189pub unsafe extern "C" fn decode_polyline_ffi(pl: *const c_char, precision: u32) -> InternalArray {
190    let s = CStr::from_ptr(pl).to_str();
191    if let Ok(unwrapped) = s {
192        arr_from_string(unwrapped, precision)
193    } else {
194        vec![[f64::NAN, f64::NAN]].into()
195    }
196}
197
198/// Convert an array of coordinates into a Polyline
199///
200/// Callers must pass two arguments:
201///
202/// - a [Struct](struct.Array.html) with two fields:
203///     - `data`, a void pointer to an array of floating-point lat, lon coordinates: `[[1.0, 2.0]]`
204///     - `len`, the length of the array being passed. Its type must be `size_t`: `1`
205/// - an unsigned 32-bit `int` for precision (5 for Google Polylines, 6 for
206/// OSRM and Valhalla Polylines)
207///
208/// A decoding failure will return one of the following:
209///
210/// - a `char*` beginning with "Longitude error:" if invalid longitudes are passed
211/// - a `char*` beginning with "Latitude error:" if invalid latitudes are passed
212///
213/// Implementations calling this function **must** call [`drop_cstring`](fn.drop_cstring.html)
214/// with the returned `c_char` pointer, in order to free the memory it allocates.
215///
216/// # Safety
217///
218/// This function is unsafe because it accesses a raw pointer which could contain arbitrary data
219#[no_mangle]
220pub extern "C" fn encode_coordinates_ffi(coords: ExternalArray, precision: u32) -> *mut c_char {
221    let s: String = string_from_arr(coords, precision);
222    match CString::new(s) {
223        Ok(res) => res.into_raw(),
224        // It's arguably better to fail noisily, but this is robust
225        Err(_) => CString::new("Couldn't decode Polyline".to_string())
226            .unwrap()
227            .into_raw(),
228    }
229}
230
231/// Free Array memory which Rust has allocated across the FFI boundary
232///
233/// # Safety
234///
235/// This function is unsafe because it accesses a raw pointer which could contain arbitrary data
236#[no_mangle]
237pub extern "C" fn drop_float_array(_: InternalArray) {}
238
239/// Free `CString` memory which Rust has allocated across the FFI boundary
240///
241/// # Safety
242///
243/// This function is unsafe because it accesses a raw pointer which could contain arbitrary data
244#[no_mangle]
245pub unsafe extern "C" fn drop_cstring(p: *mut c_char) {
246    drop(CString::from_raw(p));
247}
248
249#[cfg(test)]
250mod tests {
251    use super::*;
252    use std::ptr;
253
254    #[test]
255    fn test_drop_empty_float_array() {
256        let original: LineString<_> = vec![[2.0, 1.0], [4.0, 3.0]].into();
257        // move into an Array, and leak it
258        let mut arr: InternalArray = original.into();
259        // zero Array contents
260        arr.data = ptr::null_mut();
261        drop_float_array(arr);
262    }
263
264    #[test]
265    fn test_coordinate_conversion() {
266        let input = vec![[2.0, 1.0], [4.0, 3.0]];
267        let output = "_ibE_seK_seK_seK";
268        let input_arr: ExternalArray = input.into();
269        let transformed: String = super::string_from_arr(input_arr, 5);
270        assert_eq!(transformed, output);
271    }
272
273    #[test]
274    fn test_string_conversion() {
275        let input = "_ibE_seK_seK_seK";
276        let output = vec![[2.0, 1.0], [4.0, 3.0]];
277        // String to Array
278        let transformed: InternalArray = super::arr_from_string(input, 5);
279        // Array to LS via slice, as we want to take ownership of a copy for testing purposes
280        let v = unsafe {
281            slice::from_raw_parts(transformed.data as *mut [f64; 2], transformed.len).to_vec()
282        };
283        let ls: LineString<_> = v.into();
284        assert_eq!(ls, output.into());
285    }
286
287    #[test]
288    #[should_panic]
289    fn test_bad_string_conversion() {
290        let input = "_p~iF~ps|U_u🗑lLnnqC_mqNvxq`@";
291        let output = vec![[1.0, 2.0], [3.0, 4.0]];
292        // String to Array
293        let transformed: InternalArray = super::arr_from_string(input, 5);
294        // Array to LS via slice, as we want to take ownership of a copy for testing purposes
295        let v = unsafe {
296            slice::from_raw_parts(transformed.data as *mut [f64; 2], transformed.len).to_vec()
297        };
298        let ls: LineString<_> = v.into();
299        assert_eq!(ls, output.into());
300    }
301
302    #[test]
303    fn test_long_vec() {
304        use std::clone::Clone;
305        let arr = include!("../test_fixtures/berlin.rs");
306        let s = include!("../test_fixtures/berlin_decoded.rs");
307        for _ in 0..9999 {
308            let a = arr.clone();
309            let n = 5;
310            let input_ls: ExternalArray = a.into();
311            let transformed: String = super::string_from_arr(input_ls, n);
312            assert_eq!(transformed, s);
313        }
314    }
315}