diff options
Diffstat (limited to 'src/rust')
-rw-r--r-- | src/rust/Cargo.lock | 10 | ||||
-rw-r--r-- | src/rust/Cargo.toml | 3 | ||||
-rw-r--r-- | src/rust/include.am | 3 | ||||
-rw-r--r-- | src/rust/protover/Cargo.toml | 6 | ||||
-rw-r--r-- | src/rust/protover/ffi.rs | 34 | ||||
-rw-r--r-- | src/rust/protover/lib.rs | 1 | ||||
-rw-r--r-- | src/rust/protover/protover.rs | 89 | ||||
-rw-r--r-- | src/rust/tor_allocate/tor_allocate.rs | 19 | ||||
-rw-r--r-- | src/rust/tor_log/Cargo.toml | 18 | ||||
-rw-r--r-- | src/rust/tor_log/lib.rs | 16 | ||||
-rw-r--r-- | src/rust/tor_log/tor_log.rs | 270 | ||||
-rw-r--r-- | src/rust/tor_util/Cargo.toml | 3 | ||||
-rw-r--r-- | src/rust/tor_util/ffi.rs | 13 | ||||
-rw-r--r-- | src/rust/tor_util/lib.rs | 3 | ||||
-rw-r--r-- | src/rust/tor_util/strings.rs | 154 |
15 files changed, 500 insertions, 142 deletions
diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 4f918c0221..91c0502c60 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -18,6 +18,7 @@ dependencies = [ "libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)", "smartlist 0.0.1", "tor_allocate 0.0.1", + "tor_log 0.1.0", "tor_util 0.0.1", ] @@ -36,6 +37,14 @@ dependencies = [ ] [[package]] +name = "tor_log" +version = "0.1.0" +dependencies = [ + "libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)", + "tor_allocate 0.0.1", +] + +[[package]] name = "tor_rust" version = "0.1.0" dependencies = [ @@ -49,6 +58,7 @@ version = "0.0.1" dependencies = [ "libc 0.2.39 (registry+https://github.com/rust-lang/crates.io-index)", "tor_allocate 0.0.1", + "tor_log 0.1.0", ] [metadata] diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index 953c9b96b7..4ae8033eb3 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -1,5 +1,6 @@ [workspace] -members = ["tor_util", "protover", "smartlist", "external", "tor_allocate", "tor_rust"] +members = ["tor_util", "protover", "smartlist", "external", "tor_allocate", +"tor_rust", "tor_log"] [profile.release] debug = true diff --git a/src/rust/include.am b/src/rust/include.am index 7a0181e373..f1aa0bd5ac 100644 --- a/src/rust/include.am +++ b/src/rust/include.am @@ -20,6 +20,9 @@ EXTRA_DIST +=\ src/rust/tor_allocate/Cargo.toml \ src/rust/tor_allocate/lib.rs \ src/rust/tor_allocate/tor_allocate.rs \ + src/rust/tor_log/Cargo.toml \ + src/rust/tor_log/lib.rs \ + src/rust/tor_log/tor_log.rs \ src/rust/tor_rust/Cargo.toml \ src/rust/tor_rust/include.am \ src/rust/tor_rust/lib.rs \ diff --git a/src/rust/protover/Cargo.toml b/src/rust/protover/Cargo.toml index 86301b8787..af1089c914 100644 --- a/src/rust/protover/Cargo.toml +++ b/src/rust/protover/Cargo.toml @@ -3,6 +3,9 @@ authors = ["The Tor Project"] version = "0.0.1" name = "protover" +[features] +testing = ["tor_log/testing"] + [dependencies] libc = "=0.2.39" @@ -18,6 +21,9 @@ path = "../tor_util" [dependencies.tor_allocate] path = "../tor_allocate" +[dependencies.tor_log] +path = "../tor_log" + [lib] name = "protover" path = "lib.rs" diff --git a/src/rust/protover/ffi.rs b/src/rust/protover/ffi.rs index a40353eb13..2dfeda87b2 100644 --- a/src/rust/protover/ffi.rs +++ b/src/rust/protover/ffi.rs @@ -11,9 +11,6 @@ use std::ffi::CString; use smartlist::*; use tor_allocate::allocate_and_copy_string; -use tor_util::strings::byte_slice_is_c_like; -use tor_util::strings::empty_static_cstr; - use errors::ProtoverError; use protover::*; @@ -158,18 +155,7 @@ pub extern "C" fn protocol_list_supports_protocol_or_later( pub extern "C" fn protover_get_supported_protocols() -> *const c_char { let supported: &'static CStr; - // If we're going to pass it to C, there cannot be any intermediate NUL - // bytes. An assert is okay here, since changing the const byte slice - // in protover.rs to contain a NUL byte somewhere in the middle would be a - // programming error. - assert!(byte_slice_is_c_like(SUPPORTED_PROTOCOLS)); - - // It's okay to unwrap the result of this function because - // we can see that the bytes we're passing into it 1) are valid UTF-8, - // 2) have no intermediate NUL bytes, and 3) are terminated with a NUL - // byte. - supported = CStr::from_bytes_with_nul(SUPPORTED_PROTOCOLS).unwrap(); - + supported = get_supported_protocols_cstr(); supported.as_ptr() } @@ -228,10 +214,9 @@ pub extern "C" fn protover_is_supported_here( #[no_mangle] pub extern "C" fn protover_compute_for_old_tor(version: *const c_char) -> *const c_char { let supported: &'static CStr; - let elder_protocols: &'static [u8]; let empty: &'static CStr; - empty = empty_static_cstr(); + empty = cstr!(""); if version.is_null() { return empty.as_ptr(); @@ -246,19 +231,6 @@ pub extern "C" fn protover_compute_for_old_tor(version: *const c_char) -> *const Err(_) => return empty.as_ptr(), }; - elder_protocols = compute_for_old_tor_cstr(&version); - - // If we're going to pass it to C, there cannot be any intermediate NUL - // bytes. An assert is okay here, since changing the const byte slice - // in protover.rs to contain a NUL byte somewhere in the middle would be a - // programming error. - assert!(byte_slice_is_c_like(elder_protocols)); - - // It's okay to unwrap the result of this function because - // we can see that the bytes we're passing into it 1) are valid UTF-8, - // 2) have no intermediate NUL bytes, and 3) are terminated with a NUL - // byte. - supported = CStr::from_bytes_with_nul(elder_protocols).unwrap_or(empty); - + supported = compute_for_old_tor_cstr(&version); supported.as_ptr() } diff --git a/src/rust/protover/lib.rs b/src/rust/protover/lib.rs index 483260bca8..ce964196fd 100644 --- a/src/rust/protover/lib.rs +++ b/src/rust/protover/lib.rs @@ -28,6 +28,7 @@ extern crate libc; extern crate smartlist; extern crate external; extern crate tor_allocate; +#[macro_use] extern crate tor_util; pub mod errors; diff --git a/src/rust/protover/protover.rs b/src/rust/protover/protover.rs index 5e5a31cd33..514aeffc58 100644 --- a/src/rust/protover/protover.rs +++ b/src/rust/protover/protover.rs @@ -3,12 +3,12 @@ use std::collections::HashMap; use std::collections::hash_map; +use std::ffi::CStr; use std::fmt; use std::str; use std::str::FromStr; use std::string::String; -use tor_util::strings::NUL_BYTE; use external::c_tor_version_as_new_as; use errors::ProtoverError; @@ -28,30 +28,6 @@ const FIRST_TOR_VERSION_TO_ADVERTISE_PROTOCOLS: &'static str = "0.2.9.3-alpha"; /// C_RUST_COUPLED: src/or/protover.c `MAX_PROTOCOLS_TO_EXPAND` const MAX_PROTOCOLS_TO_EXPAND: usize = (1<<16); -/// Currently supported protocols and their versions, as a byte-slice. -/// -/// # Warning -/// -/// This byte-slice ends in a NUL byte. This is so that we can directly convert -/// it to an `&'static CStr` in the FFI code, in order to hand the static string -/// to C in a way that is compatible with C static strings. -/// -/// Rust code which wishes to accesses this string should use -/// `protover::get_supported_protocols()` instead. -/// -/// C_RUST_COUPLED: src/or/protover.c `protover_get_supported_protocols` -pub(crate) const SUPPORTED_PROTOCOLS: &'static [u8] = - b"Cons=1-2 \ - Desc=1-2 \ - DirCache=1-2 \ - HSDir=1-2 \ - HSIntro=3-4 \ - HSRend=1-2 \ - Link=1-5 \ - LinkAuth=1,3 \ - Microdesc=1-2 \ - Relay=1-2\0"; - /// Known subprotocols in Tor. Indicates which subprotocol a relay supports. /// /// C_RUST_COUPLED: src/or/protover.h `protocol_type_t` @@ -124,21 +100,33 @@ impl From<Protocol> for UnknownProtocol { } } -/// Get the string representation of current supported protocols +/// Get a CStr representation of current supported protocols, for +/// passing to C, or for converting to a `&str` for Rust. /// /// # Returns /// -/// A `String` whose value is the existing protocols supported by tor. +/// An `&'static CStr` whose value is the existing protocols supported by tor. /// Returned data is in the format as follows: /// /// "HSDir=1-1 LinkAuth=1" /// -pub fn get_supported_protocols() -> &'static str { - // The `len() - 1` is to remove the NUL byte. - // The `unwrap` is safe becauase we SUPPORTED_PROTOCOLS is under - // our control. - str::from_utf8(&SUPPORTED_PROTOCOLS[..SUPPORTED_PROTOCOLS.len() - 1]) - .unwrap_or("") +/// # Note +/// +/// Rust code can use the `&'static CStr` as a normal `&'a str` by +/// calling `protover::get_supported_protocols`. +/// +// C_RUST_COUPLED: src/or/protover.c `protover_get_supported_protocols` +pub(crate) fn get_supported_protocols_cstr() -> &'static CStr { + cstr!("Cons=1-2 \ + Desc=1-2 \ + DirCache=1-2 \ + HSDir=1-2 \ + HSIntro=3-4 \ + HSRend=1-2 \ + Link=1-5 \ + LinkAuth=1,3 \ + Microdesc=1-2 \ + Relay=1-2") } /// A map of protocol names to the versions of them which are supported. @@ -161,7 +149,8 @@ impl ProtoEntry { /// ProtoEntry, which is useful when looking up a specific /// subprotocol. pub fn supported() -> Result<Self, ProtoverError> { - let supported: &'static str = get_supported_protocols(); + let supported_cstr: &'static CStr = get_supported_protocols_cstr(); + let supported: &str = supported_cstr.to_str().unwrap_or(""); supported.parse() } @@ -627,7 +616,7 @@ pub fn is_supported_here(proto: &Protocol, vers: &Version) -> bool { /// /// # Returns /// -/// A `&'static [u8]` encoding a list of protocol names and supported +/// A `&'static CStr` encoding a list of protocol names and supported /// versions. The string takes the following format: /// /// "HSDir=1-1 LinkAuth=1" @@ -643,24 +632,25 @@ pub fn is_supported_here(proto: &Protocol, vers: &Version) -> bool { /// like to use this code in Rust, please see `compute_for_old_tor()`. // // C_RUST_COUPLED: src/rust/protover.c `compute_for_old_tor` -pub(crate) fn compute_for_old_tor_cstr(version: &str) -> &'static [u8] { +pub(crate) fn compute_for_old_tor_cstr(version: &str) -> &'static CStr { + let empty: &'static CStr = cstr!(""); + if c_tor_version_as_new_as(version, FIRST_TOR_VERSION_TO_ADVERTISE_PROTOCOLS) { - return NUL_BYTE; + return empty; } if c_tor_version_as_new_as(version, "0.2.9.1-alpha") { - return b"Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1-2 \ - Link=1-4 LinkAuth=1 Microdesc=1-2 Relay=1-2\0"; + return cstr!("Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1-2 \ + Link=1-4 LinkAuth=1 Microdesc=1-2 Relay=1-2"); } if c_tor_version_as_new_as(version, "0.2.7.5") { - return b"Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 \ - Link=1-4 LinkAuth=1 Microdesc=1-2 Relay=1-2\0"; + return cstr!("Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 \ + Link=1-4 LinkAuth=1 Microdesc=1-2 Relay=1-2"); } if c_tor_version_as_new_as(version, "0.2.4.19") { - return b"Cons=1 Desc=1 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 \ - Link=1-4 LinkAuth=1 Microdesc=1 Relay=1-2\0"; + return cstr!("Cons=1 Desc=1 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 \ + Link=1-4 LinkAuth=1 Microdesc=1 Relay=1-2"); } - - NUL_BYTE + empty } /// Since older versions of Tor cannot infer their own subprotocols, @@ -691,14 +681,9 @@ pub(crate) fn compute_for_old_tor_cstr(version: &str) -> &'static [u8] { // // C_RUST_COUPLED: src/rust/protover.c `compute_for_old_tor` pub fn compute_for_old_tor(version: &str) -> Result<&'static str, ProtoverError> { - let mut computed: &'static [u8] = compute_for_old_tor_cstr(version); - - // Remove the NULL byte at the end. - computed = &computed[..computed.len() - 1]; - - // .from_utf8() fails with a Utf8Error if it couldn't validate the + // .to_str() fails with a Utf8Error if it couldn't validate the // utf-8, so convert that here into an Unparseable ProtoverError. - str::from_utf8(computed).or(Err(ProtoverError::Unparseable)) + compute_for_old_tor_cstr(version).to_str().or(Err(ProtoverError::Unparseable)) } #[cfg(test)] diff --git a/src/rust/tor_allocate/tor_allocate.rs b/src/rust/tor_allocate/tor_allocate.rs index 359df1cd7a..3c0037f139 100644 --- a/src/rust/tor_allocate/tor_allocate.rs +++ b/src/rust/tor_allocate/tor_allocate.rs @@ -1,12 +1,17 @@ // Copyright (c) 2016-2017, 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_char, c_void}; +#[cfg(not(feature = "testing"))] use std::{ptr, slice, mem}; +use libc::c_void; -#[cfg(not(test))] -extern "C" { - fn tor_malloc_(size: usize) -> *mut c_void; +// Define a no-op implementation for testing Rust modules without linking to C +#[cfg(feature = "testing")] +pub fn allocate_and_copy_string(s: &String) -> *mut c_char { + use std::ffi::CString; + CString::new(s.as_str()).unwrap().into_raw() } // Defined only for tests, used for testing purposes, so that we don't need @@ -17,6 +22,11 @@ unsafe extern "C" fn tor_malloc_(size: usize) -> *mut c_void { 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. /// @@ -28,6 +38,7 @@ unsafe extern "C" fn tor_malloc_(size: usize) -> *mut c_void { /// /// A `*mut c_char` that should be freed by tor_free in C /// +#[cfg(not(feature = "testing"))] pub fn allocate_and_copy_string(src: &String) -> *mut c_char { let bytes: &[u8] = src.as_bytes(); diff --git a/src/rust/tor_log/Cargo.toml b/src/rust/tor_log/Cargo.toml new file mode 100644 index 0000000000..971cd658b1 --- /dev/null +++ b/src/rust/tor_log/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "tor_log" +version = "0.1.0" +authors = ["The Tor Project"] + +[lib] +name = "tor_log" +path = "lib.rs" +crate_type = ["rlib", "staticlib"] + +[features] +testing = [] + +[dependencies] +libc = "0.2.39" + +[dependencies.tor_allocate] +path = "../tor_allocate" diff --git a/src/rust/tor_log/lib.rs b/src/rust/tor_log/lib.rs new file mode 100644 index 0000000000..72f9e38339 --- /dev/null +++ b/src/rust/tor_log/lib.rs @@ -0,0 +1,16 @@ +//! Copyright (c) 2016-2017, The Tor Project, Inc. */ +//! See LICENSE for licensing information */ + +//! Logging wrapper for Rust to utilize Tor's logger, found at +//! src/common/log.c and src/common/torlog.h +//! +//! Exposes different interfaces depending on whether we are running in test +//! or non-test mode. When testing, we use a no-op implementation, +//! otherwise we link directly to C. + +extern crate libc; +extern crate tor_allocate; + +mod tor_log; + +pub use tor_log::*; diff --git a/src/rust/tor_log/tor_log.rs b/src/rust/tor_log/tor_log.rs new file mode 100644 index 0000000000..1fdc0026bf --- /dev/null +++ b/src/rust/tor_log/tor_log.rs @@ -0,0 +1,270 @@ +// Copyright (c) 2016-2017, The Tor Project, Inc. */ +// See LICENSE for licensing information */ + +// Note that these functions are untested due to the fact that there are no +// return variables to test and they are calling into a C API. + +/// The related domain which the logging message is relevant. For example, +/// log messages relevant to networking would use LogDomain::LdNet, whereas +/// general messages can use LdGeneral. +#[derive(Eq, PartialEq)] +pub enum LogDomain { + Net, + General, +} + +/// The severity level at which to log messages. +#[derive(Eq, PartialEq)] +pub enum LogSeverity { + Notice, + Warn, +} + +/// Main entry point for Rust modules to log messages. +/// +/// # Inputs +/// +/// * A `severity` of type LogSeverity, which defines the level of severity the +/// message will be logged. +/// * A `domain` of type LogDomain, which defines the domain the log message +/// will be associated with. +/// * A `function` of type &str, which defines the name of the function where +/// the message is being logged. There is a current RFC for a macro that +/// defines function names. When it is, we should use it. See +/// https://github.com/rust-lang/rfcs/pull/1719 +/// * A `message` of type &str, which is the log message itself. +#[macro_export] +macro_rules! tor_log_msg { + ($severity: path, + $domain: path, + $function: expr, + $($message:tt)*) => + { + { + let msg = format!($($message)*); + $crate::tor_log_msg_impl($severity, $domain, $function, msg) + } + }; +} + +#[inline] +pub fn tor_log_msg_impl( + severity: LogSeverity, + domain: LogDomain, + function: &str, + message: String, +) { + use std::ffi::CString; + + /// Default function name to log in case of errors when converting + /// a function name to a CString + const ERR_LOG_FUNCTION: &str = "tor_log_msg"; + + /// Default message to log in case of errors when converting a log + /// message to a CString + const ERR_LOG_MSG: &str = "Unable to log message from Rust \ + module due to error when converting to CString"; + + let func = match CString::new(function) { + Ok(n) => n, + Err(_) => CString::new(ERR_LOG_FUNCTION).unwrap(), + }; + + let msg = match CString::new(message) { + Ok(n) => n, + Err(_) => CString::new(ERR_LOG_MSG).unwrap(), + }; + + // Bind to a local variable to preserve ownership. This is essential so + // that ownership is guaranteed until these local variables go out of scope + let func_ptr = func.as_ptr(); + let msg_ptr = msg.as_ptr(); + + let c_severity = unsafe { log::translate_severity(severity) }; + let c_domain = unsafe { log::translate_domain(domain) }; + + unsafe { log::tor_log_string(c_severity, c_domain, func_ptr, msg_ptr) } +} + +/// This implementation is used when compiling for actual use, as opposed to +/// testing. +#[cfg(all(not(test), not(feature = "testing")))] +pub mod log { + use libc::{c_char, c_int}; + use super::LogDomain; + use super::LogSeverity; + + /// Severity log types. These mirror definitions in /src/common/torlog.h + /// C_RUST_COUPLED: src/common/log.c, log domain types + extern "C" { + static LOG_WARN_: c_int; + static LOG_NOTICE_: c_int; + } + + /// Domain log types. These mirror definitions in /src/common/torlog.h + /// C_RUST_COUPLED: src/common/log.c, log severity types + extern "C" { + static LD_NET_: u32; + static LD_GENERAL_: u32; + } + + /// Translate Rust defintions of log domain levels to C. This exposes a 1:1 + /// mapping between types. + #[inline] + pub unsafe fn translate_domain(domain: LogDomain) -> u32 { + match domain { + LogDomain::Net => LD_NET_, + LogDomain::General => LD_GENERAL_, + } + } + + /// Translate Rust defintions of log severity levels to C. This exposes a + /// 1:1 mapping between types. + #[inline] + pub unsafe fn translate_severity(severity: LogSeverity) -> c_int { + match severity { + LogSeverity::Warn => LOG_WARN_, + LogSeverity::Notice => LOG_NOTICE_, + } + } + + /// The main entry point into Tor's logger. When in non-test mode, this + /// will link directly with `tor_log_string` in /src/or/log.c + extern "C" { + pub fn tor_log_string( + severity: c_int, + domain: u32, + function: *const c_char, + string: *const c_char, + ); + } +} + +/// This module exposes no-op functionality for testing other Rust modules +/// without linking to C. +#[cfg(any(test, feature = "testing"))] +pub mod log { + use libc::{c_char, c_int}; + use super::LogDomain; + use super::LogSeverity; + + pub static mut LAST_LOGGED_FUNCTION: *mut String = 0 as *mut String; + pub static mut LAST_LOGGED_MESSAGE: *mut String = 0 as *mut String; + + pub unsafe fn tor_log_string( + _severity: c_int, + _domain: u32, + function: *const c_char, + message: *const c_char, + ) { + use std::ffi::CStr; + + let f = CStr::from_ptr(function); + let fct = match f.to_str() { + Ok(n) => n, + Err(_) => "", + }; + LAST_LOGGED_FUNCTION = Box::into_raw(Box::new(String::from(fct))); + + let m = CStr::from_ptr(message); + let msg = match m.to_str() { + Ok(n) => n, + Err(_) => "", + }; + LAST_LOGGED_MESSAGE = Box::into_raw(Box::new(String::from(msg))); + } + + pub unsafe fn translate_domain(_domain: LogDomain) -> u32 { + 1 + } + + pub unsafe fn translate_severity(_severity: LogSeverity) -> c_int { + 1 + } +} + +#[cfg(test)] +mod test { + use tor_log::*; + use tor_log::log::{LAST_LOGGED_FUNCTION, LAST_LOGGED_MESSAGE}; + + #[test] + fn test_get_log_message() { + { + fn test_macro() { + tor_log_msg!( + LogSeverity::Warn, + LogDomain::Net, + "test_macro", + "test log message {}", + "a", + ); + } + + test_macro(); + + let function = unsafe { Box::from_raw(LAST_LOGGED_FUNCTION) }; + assert_eq!("test_macro", *function); + + let message = unsafe { Box::from_raw(LAST_LOGGED_MESSAGE) }; + assert_eq!("test log message a", *message); + } + + // test multiple inputs into the log message + { + fn test_macro() { + tor_log_msg!( + LogSeverity::Warn, + LogDomain::Net, + "next_test_macro", + "test log message {} {} {} {} {}", + 1, + 2, + 3, + 4, + 5 + ); + } + + test_macro(); + + let function = unsafe { Box::from_raw(LAST_LOGGED_FUNCTION) }; + assert_eq!("next_test_macro", *function); + + let message = unsafe { Box::from_raw(LAST_LOGGED_MESSAGE) }; + assert_eq!("test log message 1 2 3 4 5", *message); + } + + // test how a long log message will be formatted + { + fn test_macro() { + tor_log_msg!( + LogSeverity::Warn, + LogDomain::Net, + "test_macro", + "{}", + "All the world's a stage, and all the men and women \ + merely players: they have their exits and their \ + entrances; and one man in his time plays many parts, his \ + acts being seven ages." + ); + } + + test_macro(); + + let expected_string = "All the world's a \ + stage, and all the men \ + and women merely players: \ + they have their exits and \ + their entrances; and one man \ + in his time plays many parts, \ + his acts being seven ages."; + + let function = unsafe { Box::from_raw(LAST_LOGGED_FUNCTION) }; + assert_eq!("test_macro", *function); + + let message = unsafe { Box::from_raw(LAST_LOGGED_MESSAGE) }; + assert_eq!(expected_string, *message); + } + } +} diff --git a/src/rust/tor_util/Cargo.toml b/src/rust/tor_util/Cargo.toml index b540d8c847..a606a280b2 100644 --- a/src/rust/tor_util/Cargo.toml +++ b/src/rust/tor_util/Cargo.toml @@ -11,6 +11,9 @@ crate_type = ["rlib", "staticlib"] [dependencies.tor_allocate] path = "../tor_allocate" +[dependencies.tor_log] +path = "../tor_log" + [dependencies] libc = "=0.2.39" diff --git a/src/rust/tor_util/ffi.rs b/src/rust/tor_util/ffi.rs index 5c3cdba4be..32779ed476 100644 --- a/src/rust/tor_util/ffi.rs +++ b/src/rust/tor_util/ffi.rs @@ -5,8 +5,7 @@ //! called from C. //! -use libc::c_char; -use tor_allocate::allocate_and_copy_string; +use tor_log::{LogSeverity, LogDomain}; /// Returns a short string to announce Rust support during startup. /// @@ -17,10 +16,12 @@ use tor_allocate::allocate_and_copy_string; /// tor_free(rust_str); /// ``` #[no_mangle] -pub extern "C" fn rust_welcome_string() -> *mut c_char { - let rust_welcome = String::from( +pub extern "C" fn rust_log_welcome_string() { + tor_log_msg!( + LogSeverity::Notice, + LogDomain::General, + "rust_log_welcome_string", "Tor is running with Rust integration. Please report \ - any bugs you encounter.", + any bugs you encounter." ); - allocate_and_copy_string(&rust_welcome) } diff --git a/src/rust/tor_util/lib.rs b/src/rust/tor_util/lib.rs index 12cb3896b6..94697b6069 100644 --- a/src/rust/tor_util/lib.rs +++ b/src/rust/tor_util/lib.rs @@ -7,5 +7,8 @@ extern crate libc; extern crate tor_allocate; +#[macro_use] +extern crate tor_log; + pub mod ffi; pub mod strings; diff --git a/src/rust/tor_util/strings.rs b/src/rust/tor_util/strings.rs index 9321ce4f85..505191d913 100644 --- a/src/rust/tor_util/strings.rs +++ b/src/rust/tor_util/strings.rs @@ -3,80 +3,138 @@ //! Utilities for working with static strings. -use std::ffi::CStr; - -/// A byte-array containing a single NUL byte (`b"\0"`). -pub const NUL_BYTE: &'static [u8] = b"\0"; - -/// Determine if a byte slice is a C-like string. -/// -/// These checks guarantee that: -/// -/// 1. there are no intermediate NUL bytes -/// 2. the last byte *is* a NUL byte +/// Create a `CStr` from a literal byte slice, appending a NUL byte to it first. /// /// # Warning /// -/// This function does _not_ guarantee that the bytes represent any valid -/// encoding such as ASCII or UTF-8. +/// The literal byte slice which is taken as an argument *MUST NOT* have any NUL +/// bytes (`b"\0"`) in it, anywhere, or else an empty string will be returned +/// (`CStr::from_bytes_with_nul_unchecked(b"\0")`) so as to avoid `panic!()`ing. /// /// # Examples /// /// ``` -/// # use tor_util::strings::byte_slice_is_c_like; -/// # -/// let bytes: &[u8] = b"foo bar baz"; +/// #[macro_use] +/// extern crate tor_util; /// -/// assert!(byte_slice_is_c_like(&bytes) == false); +/// use std::ffi::CStr; /// -/// let bytes: &[u8] = b"foo\0bar baz"; +/// # fn do_test() -> Result<&'static CStr, &'static str> { +/// let message: &'static str = "This is a test of the tsunami warning system."; +/// let tuesday: &'static CStr; +/// let original: &str; /// -/// assert!(byte_slice_is_c_like(&bytes) == false); +/// tuesday = cstr!("This is a test of the tsunami warning system."); +/// original = tuesday.to_str().or(Err("Couldn't unwrap CStr!"))?; /// -/// let bytes: &[u8] = b"foo bar baz\0"; +/// assert!(original == message); +/// # +/// # Ok(tuesday) +/// # } +/// # fn main() { +/// # do_test(); // so that we can use the ? operator in the test +/// # } +/// ``` +/// It is also possible to pass several string literals to this macro. They +/// will be concatenated together in the order of the arguments, unmodified, +/// before finally being suffixed with a NUL byte: /// -/// assert!(byte_slice_is_c_like(&bytes) == true); /// ``` -pub fn byte_slice_is_c_like(bytes: &[u8]) -> bool { - if !bytes[..bytes.len() - 1].contains(&0x00) && bytes[bytes.len() - 1] == 0x00 { - return true; - } - false -} - -/// Get a static `CStr` containing a single `NUL_BYTE`. +/// #[macro_use] +/// extern crate tor_util; +/// # +/// # use std::ffi::CStr; +/// # +/// # fn do_test() -> Result<&'static CStr, &'static str> { /// -/// # Examples +/// let quux: &'static CStr = cstr!("foo", "bar", "baz"); +/// let orig: &'static str = quux.to_str().or(Err("Couldn't unwrap CStr!"))?; /// -/// When used as follows in a Rust FFI function, which could be called -/// from C: +/// assert!(orig == "foobarbaz"); +/// # Ok(quux) +/// # } +/// # fn main() { +/// # do_test(); // so that we can use the ? operator in the test +/// # } +/// ``` +/// This is useful for passing static strings to C from Rust FFI code. To do so +/// so, use the `.as_ptr()` method on the resulting `&'static CStr` to convert +/// it to the Rust equivalent of a C `const char*`: /// /// ``` -/// # extern crate libc; -/// # extern crate tor_util; -/// # -/// # use tor_util::strings::empty_static_cstr; -/// use libc::c_char; +/// #[macro_use] +/// extern crate tor_util; +/// /// use std::ffi::CStr; +/// use std::os::raw::c_char; /// -/// pub extern "C" fn give_c_code_an_empty_static_string() -> *const c_char { -/// let empty: &'static CStr = empty_static_cstr(); +/// pub extern "C" fn give_static_borrowed_string_to_c() -> *const c_char { +/// let hello: &'static CStr = cstr!("Hello, language my parents wrote."); /// -/// empty.as_ptr() +/// hello.as_ptr() /// } -/// /// # fn main() { -/// # give_c_code_an_empty_static_string(); +/// # let greetings = give_static_borrowed_string_to_c(); /// # } /// ``` +/// Note that the C code this static borrowed string is passed to *MUST NOT* +/// attempt to free the memory for the string. +/// +/// # Note +/// +/// An unfortunate limitation of the rustc compiler (as of 1.25.0-nightly), is +/// that the first example above compiles, but if we were to change the +/// assignment of `tuesday` as follows, it will fail to compile, because Rust +/// macros are expanded at parse time, and at parse time there is no symbol +/// table available. /// -/// This equates to an "empty" `const char*` static string in C. -pub fn empty_static_cstr() -> &'static CStr { - let empty: &'static CStr; +/// ```ignore +/// tuesday = cstr!(message); +/// ``` +/// with the error message `error: expected a literal`. +/// +/// # Returns +/// +/// If the string literals passed as arguments contain no NUL bytes anywhere, +/// then an `&'static CStr` containing the (concatenated) bytes of the string +/// literal(s) passed as arguments, with a NUL byte appended, is returned. +/// Otherwise, an `&'static CStr` containing a single NUL byte is returned (an +/// "empty" string in C). +#[macro_export] +macro_rules! cstr { + ($($bytes:expr),*) => ( + ::std::ffi::CStr::from_bytes_with_nul( + concat!($($bytes),*, "\0").as_bytes() + ).unwrap_or( + unsafe{ + ::std::ffi::CStr::from_bytes_with_nul_unchecked(b"\0") + } + ) + ) +} + +#[cfg(test)] +mod test { + use std::ffi::CStr; + + #[test] + fn cstr_macro() { + let _: &'static CStr = cstr!("boo"); + } + + #[test] + fn cstr_macro_multi_input() { + let quux: &'static CStr = cstr!("foo", "bar", "baz"); - unsafe { - empty = CStr::from_bytes_with_nul_unchecked(NUL_BYTE); + assert!(quux.to_str().unwrap() == "foobarbaz"); } - empty + #[test] + fn cstr_macro_bad_input() { + let waving: &'static CStr = cstr!("waving not drowning o/"); + let drowning: &'static CStr = cstr!("\0 drowning not waving"); + + assert!(waving.to_str().unwrap() == "waving not drowning o/"); + assert!(drowning.to_str().unwrap() == "") + } } |