> uploadtext_

v1.0.0 - Secure text sharing node

Encapsulating Unsafe C FFI Calls in Rust for Memory Safety

Owner: SnippetBot Created: 2026-11-01 00:01:22 Size: 6.50 KB Expires: Never
[ RAW ] [ NEW ]
tty1
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
use std::ffi::{CStr, CString};
use std::os::raw::c_char;

// For this snippet to run directly, you would need to:
// 1. Create a C source file (e.g., `src/lib_example.c`):
//    ```c
//    #include <string.h>
//    #include <stdlib.h>
//    // A simple C function to concatenate two strings into a buffer.
//    // Caller's responsibility to ensure 'dest' is large enough.
//    // Returns the length of the new string (excluding null), or -1 if buffer is too small or invalid args.
//    int concatenate_strings(char* dest, const char* str1, const char* str2, size_t dest_size) {
//        if (!dest || !str1 || !str2 || dest_size == 0) {
//            return -1; // Invalid arguments
//        }
//        size_t len1 = strlen(str1);
//        size_t len2 = strlen(str2);
//        size_t required_len = len1 + len2 + 1; // +1 for null terminator
//        if (dest_size < required_len) {
//            return -1; // Buffer too small
//        }
//        strcpy(dest, str1);
//        strcat(dest, str2);
//        return (int)(required_len - 1); // Return actual string length
//    }
//    ```
// 2. Create a C header file (e.g., `src/lib_example.h`):
//    ```c
//    #ifndef LIB_EXAMPLE_H
//    #define LIB_EXAMPLE_H
//    #include <stddef.h> // For size_t
//    int concatenate_strings(char* dest, const char* str1, const char* str2, size_t dest_size);
//    #endif // LIB_EXAMPLE_H
//    ```
// 3. Create a `build.rs` file in your project root to compile the C code:
//    ```rust
//    fn main() {
//        cc::Build::new()
//            .file("src/lib_example.c") // Path to your C source file
//            .compile("lib_example");    // Output library name
//        println!("cargo:rustc-link-lib=static=lib_example"); // Link against static library
//    }
//    ```
// 4. Add `libc = "0.2"` to your Cargo.toml and `cc = "1.0"` to your Cargo.toml `[build-dependencies]` section.

// Define the foreign function interface.
// This block is inherently `unsafe` because Rust cannot verify the C function's correctness.
#[link(name = "lib_example", kind = "static")] // Link to the compiled C library
extern "C" {
    fn concatenate_strings(
        dest: *mut c_char,
        str1: *const c_char,
        str2: *const c_char,
        dest_size: libc::size_t, // Using `libc::size_t` for C's size_t type
    ) -> libc::c_int;
}

/// Safely concatenates two Rust strings using a C FFI function.
///
/// This function encapsulates the `unsafe` FFI call, providing a safe
/// Rust interface and handling potential errors from the C function.
/// It ensures that the buffer for the C function is correctly sized and
/// that CStrings are properly managed, preventing memory corruption.
fn safe_ffi_concatenate(s1: &str, s2: &str) -> Result<String, String> {
    // Convert Rust strings to null-terminated CStrings.
    // `CString::new` checks for internal null bytes, preventing premature termination in C.
    let c_str1 = CString::new(s1).map_err(|e| format!("Invalid string 1 (contains null byte): {}", e))?;
    let c_str2 = CString::new(s2).map_err(|e| format!("Invalid string 2 (contains null byte): {}", e))?;

    // Calculate required buffer size for C function (includes null terminator).
    let max_len = s1.len() + s2.len();
    let buffer_size = max_len + 1;

    // Allocate a buffer. `Vec<u8>` guarantees contiguous memory and can be safely
    // cast to a mutable raw pointer. `with_capacity` reserves space but doesn't set length.
    let mut buffer = Vec::<u8>::with_capacity(buffer_size);

    // The `unsafe` block explicitly marks code that requires careful reasoning about memory safety.
    // All interactions with FFI are inherently `unsafe`.
    let result_code = unsafe {
        // SAFETY CRITERIA FOR THIS `unsafe` BLOCK:
        // 1. `buffer.as_mut_ptr()` provides a valid mutable pointer to `buffer_size` bytes
        //    of allocated, owned memory. We cast `*mut u8` to `*mut c_char` which is valid
        //    as `c_char` is typically `i8` or `u8`.
        // 2. `c_str1.as_ptr()` and `c_str2.as_ptr()` provide valid const pointers to
        //    null-terminated C strings. Their lifetimes are managed by `CString`.
        // 3. `buffer_size` is passed, ensuring the C function knows the maximum writable length.
        // 4. The C function `concatenate_strings` is assumed to correctly write a null-terminated
        //    string into `dest` without exceeding `dest_size`.
        // 5. The return value from the C function is checked for error conditions.
        concatenate_strings(
            buffer.as_mut_ptr() as *mut c_char, // Cast `*mut u8` to `*mut c_char`
            c_str1.as_ptr(),
            c_str2.as_ptr(),
            buffer_size as libc::size_t, // Cast `usize` to `libc::size_t`
        )
    };

    if result_code >= 0 {
        // SAFETY CRITERIA FOR THIS `unsafe` BLOCK:
        // 1. The C function guarantees that `result_code` bytes (excluding null terminator)
        //    have been written and that the buffer is null-terminated at this length.
        // 2. We can safely set the vector's length to `result_code` because valid data
        //    has been written up to that point.
        // 3. `CStr::from_ptr` creates a safe CStr slice from the buffer's contents,
        //    which is then converted to a Rust `String`.
        unsafe {
            buffer.set_len(result_code as usize); // Set the vector's length to the actual string length
            let c_string = CStr::from_ptr(buffer.as_ptr() as *const c_char);
            Ok(c_string.to_string_lossy().into_owned()) // Convert CStr to owned Rust String
        }
    } else {
        Err(format!("C function 'concatenate_strings' failed with code: {}", result_code))
    }
}

fn main() {
    println!("--- Demonstrating Safe FFI --- ");

    match safe_ffi_concatenate("Hello, ", "Rust!") {
        Ok(s) => println!("FFI Concatenated (success): '{}'", s),
        Err(e) => println!("FFI Concatenated (error): {}", e),
    }

    // Simulate a case where the C function might detect buffer overflow
    match safe_ffi_concatenate(
        "ThisIsAVeryLongStringPartOne",
        "AndThisIsAnotherVeryLongStringPartTwoTryingToOverflowSmallBuffer"
    ) {
        Ok(s) => println!("FFI Concatenated (success): '{}'", s),
        Err(e) => println!("FFI Concatenated (error): {}", e),
    }

    // Example of a Rust string with an internal null byte, which CString prevents.
    match safe_ffi_concatenate("Hello\0World", "!") {
        Ok(s) => println!("FFI Concatenated (success): '{}'", s),
        Err(e) => println!("FFI Concatenated (error): {}", e), // This will trigger the CString error
    }
}