aboutsummaryrefslogtreecommitdiff
path: root/src/rust/tor_allocate/tor_allocate.rs
blob: 7b35e2451ffe583ec9e292e9e54eddc0977ca5e8 (plain)
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
// Copyright (c) 2016-2019, The Tor Project, Inc. */
// See LICENSE for licensing information */
// No-op defined purely for testing at the module level
use libc::c_char;

use libc::c_void;
#[cfg(not(feature = "testing"))]
use std::{mem, ptr, slice};

// Define a no-op implementation for testing Rust modules without linking to C
#[cfg(feature = "testing")]
pub fn allocate_and_copy_string(s: &str) -> *mut c_char {
    use std::ffi::CString;
    CString::new(s).unwrap().into_raw()
}

// Defined only for tests, used for testing purposes, so that we don't need
// to link to tor C files. Uses the system allocator
#[cfg(test)]
unsafe extern "C" fn tor_malloc_(size: usize) -> *mut c_void {
    use libc::malloc;
    malloc(size)
}

#[cfg(all(not(test), not(feature = "testing")))]
extern "C" {
    fn tor_malloc_(size: usize) -> *mut c_void;
}

/// Allocate memory using tor_malloc_ and copy an existing string into the
/// allocated buffer, returning a pointer that can later be called in C.
///
/// # Inputs
///
/// * `src`, a reference to a String.
///
/// # Returns
///
/// A `*mut c_char` that should be freed by tor_free in C
///
#[cfg(not(feature = "testing"))]
pub fn allocate_and_copy_string(src: &str) -> *mut c_char {
    let bytes: &[u8] = src.as_bytes();

    let size = mem::size_of_val::<[u8]>(bytes);
    let size_one_byte = mem::size_of::<u8>();

    // handle integer overflow when adding one to the calculated length
    let size_with_null_byte = match size.checked_add(size_one_byte) {
        Some(n) => n,
        None => return ptr::null_mut(),
    };

    let dest = unsafe { tor_malloc_(size_with_null_byte) as *mut u8 };

    if dest.is_null() {
        return ptr::null_mut();
    }

    unsafe { ptr::copy_nonoverlapping(bytes.as_ptr(), dest, size) };

    // set the last byte as null, using the ability to index into a slice
    // rather than doing pointer arithmetic
    let slice = unsafe { slice::from_raw_parts_mut(dest, size_with_null_byte) };
    slice[size] = 0; // add a null terminator

    dest as *mut c_char
}

#[cfg(test)]
mod test {

    #[test]
    fn test_allocate_and_copy_string_with_empty() {
        use libc::{c_void, free};
        use std::ffi::CStr;

        use tor_allocate::allocate_and_copy_string;

        let allocated_empty = allocate_and_copy_string("");

        let allocated_empty_rust = unsafe { CStr::from_ptr(allocated_empty).to_str().unwrap() };

        assert_eq!("", allocated_empty_rust);

        unsafe { free(allocated_empty as *mut c_void) };
    }

    #[test]
    fn test_allocate_and_copy_string_with_not_empty_string() {
        use libc::{c_void, free};
        use std::ffi::CStr;

        use tor_allocate::allocate_and_copy_string;

        let allocated_empty = allocate_and_copy_string("foo bar biz");

        let allocated_empty_rust = unsafe { CStr::from_ptr(allocated_empty).to_str().unwrap() };

        assert_eq!("foo bar biz", allocated_empty_rust);

        unsafe { free(allocated_empty as *mut c_void) };
    }
}