Skip to content

Commit

Permalink
Fixed a an issue with memory being written to after it was freed, lea…
Browse files Browse the repository at this point in the history
…ding to abort signals from malloc (#565)

* Fixed a memory scribble issue in rust binding, leading to abort signals coming from malloc
  • Loading branch information
bokbok committed Oct 21, 2022
1 parent ece814f commit ed7ec7b
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 54 deletions.
1 change: 1 addition & 0 deletions rust_package/brainflow/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ thiserror = "1.0.29"

[dev-dependencies]
approx = "0.5.0"
regex = "1.6.0"

[build-dependencies]
bindgen = { version = "0.59.1", optional = true }
Expand Down
124 changes: 89 additions & 35 deletions rust_package/brainflow/src/board_shim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ use ndarray::Array2;
use paste::paste;
use std::{
ffi::CString,
ffi::CStr,
os::raw::{c_double, c_int},
};
use std::os::raw::c_char;

use crate::{
brainflow_input_params::BrainFlowInputParams, check_brainflow_exit_code, BoardIds, LogLevels,
Expand Down Expand Up @@ -216,26 +218,25 @@ impl BoardShim {
pub fn config_board<S: AsRef<str>>(&self, config: S) -> Result<String> {
let config = CString::new(config.as_ref())?;
let mut response_len = 0;
let response = CString::new(Vec::with_capacity(8192))?;
let response = response.into_raw();
let mut result_char_buffer: [c_char; 8192] = [0; 8192];

let config = config.into_raw();
let (res, response) = unsafe {
let res = board_controller::config_board(
config,
response,
result_char_buffer.as_mut_ptr(),
&mut response_len,
self.board_id as c_int,
self.json_brainflow_input_params.as_ptr(),
);
let _ = CString::from_raw(config);
let response = CString::from_raw(response);
(res, response)
let resp = CStr::from_ptr(result_char_buffer.as_ptr());

(res, resp)
};
check_brainflow_exit_code(res)?;
Ok(response
.to_str()?
.split_at(response_len as usize)
.0
.to_string())
}

Expand Down Expand Up @@ -356,34 +357,30 @@ pub fn log_message<S: AsRef<str>>(log_level: LogLevels, message: S) -> Result<()

/// Get board description as json.
pub fn get_board_descr(board_id: BoardIds, preset: BrainFlowPresets) -> Result<String> {
const MAX_CHARS: usize = 16000;
let mut response_len = 0;
let response = CString::new(Vec::with_capacity(16000))?;
let response = response.into_raw();
let mut result_char_buffer: [c_char; MAX_CHARS] = [0; MAX_CHARS];
let (res, response) = unsafe {
let res = board_controller::get_board_descr(board_id as c_int, preset as c_int, response, &mut response_len);
let response = CString::from_raw(response);
let res = board_controller::get_board_descr(board_id as c_int, preset as c_int, result_char_buffer.as_mut_ptr(), &mut response_len);
let response = CStr::from_ptr(result_char_buffer.as_ptr());
(res, response)
};
check_brainflow_exit_code(res)?;
Ok(response
.to_str()?
.split_at(response_len as usize)
.0
.to_string())
Ok(response.to_str()?.to_string())
}

/// Get names of EEG channels in 10-20 system if their location is fixed.
pub fn get_eeg_names(board_id: BoardIds, preset: BrainFlowPresets) -> Result<Vec<String>> {
const MAX_CHARS: usize = 16000;
let mut response_len = 0;
let response = CString::new(Vec::with_capacity(16000))?;
let response = response.into_raw();
let mut result_char_buffer: [c_char; MAX_CHARS] = [0; MAX_CHARS];
let (res, response) = unsafe {
let res = board_controller::get_eeg_names(board_id as c_int, preset as c_int, response, &mut response_len);
let response = CString::from_raw(response);
let res = board_controller::get_eeg_names(board_id as c_int, preset as c_int, result_char_buffer.as_mut_ptr(), &mut response_len);
let response = CStr::from_ptr(result_char_buffer.as_ptr());
(res, response)
};
check_brainflow_exit_code(res)?;
let names = response.to_str()?.split_at(response_len as usize).0;
let names = response.to_str()?;

Ok(names
.split(',')
Expand All @@ -410,36 +407,34 @@ pub fn get_board_presets(board_id: BoardIds) -> Result<Vec<usize>> {

/// Get BoardShim version.
pub fn get_version() -> Result<String> {
const MAX_CHARS: usize = 64;
let mut response_len = 0;
let response = CString::new(Vec::with_capacity(64))?;
let response = response.into_raw();
let mut result_char_buffer: [c_char; MAX_CHARS] = [0; MAX_CHARS];

let (res, response) = unsafe {
let res = board_controller::get_version_board_controller(response, &mut response_len, 64);
let response = CString::from_raw(response);
let res = board_controller::get_version_board_controller(result_char_buffer.as_mut_ptr(), &mut response_len, MAX_CHARS as i32);
let response = CStr::from_ptr(result_char_buffer.as_ptr());
(res, response)
};
check_brainflow_exit_code(res)?;
let version = response.to_str()?.split_at(response_len as usize).0;
let version = response.to_str()?;

Ok(version.to_string())
}

/// Get device name.
pub fn get_device_name(board_id: BoardIds, preset: BrainFlowPresets) -> Result<String> {
const MAX_CHARS: usize = 4096;
let mut response_len = 0;
let response = CString::new(Vec::with_capacity(4096))?;
let response = response.into_raw();
let mut result_char_buffer: [c_char; MAX_CHARS] = [0; MAX_CHARS];

let (res, response) = unsafe {
let res = board_controller::get_device_name(board_id as c_int, preset as c_int, response, &mut response_len);
let response = CString::from_raw(response);
let res = board_controller::get_device_name(board_id as c_int, preset as c_int, result_char_buffer.as_mut_ptr(), &mut response_len);
let response = CStr::from_ptr(result_char_buffer.as_ptr());
(res, response)
};
check_brainflow_exit_code(res)?;
Ok(response
.to_str()?
.split_at(response_len as usize)
.0
.to_string())
Ok(response.to_str()?.to_string())
}

macro_rules! gen_vec_fn {
Expand Down Expand Up @@ -518,3 +513,62 @@ gen_vec_fn!(
resistance_channels,
"Get list of resistance channels in resulting data table for a board."
);

#[cfg(test)]
mod tests {
#[cfg(test)]
mod functions {
use crate::test_helpers::assertions::assert_regex_matches;
use crate::test_helpers::consts::VERSION_PATTERN;
use crate::board_shim::{get_version, get_device_name, get_eeg_names, get_board_descr};
use crate::{BoardIds, BrainFlowPresets};

#[test]
fn test_it_gets_the_version() {
assert_regex_matches(VERSION_PATTERN, get_version().unwrap().as_str());
}

#[test]
fn test_get_device_name() {
assert_eq!("Synthetic", get_device_name(BoardIds::SyntheticBoard, BrainFlowPresets::DefaultPreset).unwrap());
}

#[test]
fn test_get_eeg_names() {
assert_eq!(vec!["Fz", "C3", "Cz", "C4", "Pz", "PO7", "Oz", "PO8", "F5", "F7", "F3", "F1", "F2", "F4", "F6", "F8"],
get_eeg_names(BoardIds::SyntheticBoard, BrainFlowPresets::DefaultPreset).unwrap());

}

#[test]
fn test_get_board_descr() {
let pattern = r#"^.*"name"\w*:\w*"Synthetic".*$"#;

assert_regex_matches(pattern,
get_board_descr(BoardIds::SyntheticBoard, BrainFlowPresets::DefaultPreset).unwrap().as_str());
}
}


#[cfg(test)]
mod board_shim {
use crate::board_shim::BoardShim;
use crate::BoardIds;
use crate::brainflow_input_params::BrainFlowInputParams;

fn board_shim() -> BoardShim {
BoardShim::new(BoardIds::SyntheticBoard,
BrainFlowInputParams::default()).unwrap()
}

#[test]
fn test_config_board() {
let board = board_shim();
board.prepare_session();
// synthetic board doesnt send any message back as it ignores config calls
assert_eq!("", board.config_board("x123456X").unwrap());

}
}

}
26 changes: 15 additions & 11 deletions rust_package/brainflow/src/data_filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ use ndarray::{Array1, Array2, Array3, ArrayBase};
use num::Complex;
use num_complex::Complex64;
use std::os::raw::c_int;
use std::{ffi::CString, os::raw::c_double};
use std::{ffi::CString, ffi::CStr, os::raw::c_double};
use std::os::raw::c_char;

use crate::error::{BrainFlowError, Error};
use crate::ffi::data_handler;
Expand Down Expand Up @@ -807,30 +808,33 @@ where

/// Get DataFilter version.
pub fn get_version() -> Result<String> {
const MAX_CHARS: usize = 64;
let mut response_len = 0;
let response = CString::new(Vec::with_capacity(64))?;
let response = response.into_raw();
let mut result_char_buffer: [c_char; MAX_CHARS] = [0; MAX_CHARS];
let (res, response) = unsafe {
let res = data_handler::get_version_data_handler(response, &mut response_len, 64);
let response = CString::from_raw(response);
let res = data_handler::get_version_data_handler(result_char_buffer.as_mut_ptr(), &mut response_len, MAX_CHARS as i32);
let response = CStr::from_ptr(result_char_buffer.as_mut_ptr());
(res, response)
};
check_brainflow_exit_code(res)?;
let version = response.to_str()?.split_at(response_len as usize).0;

Ok(version.to_string())
Ok(response.to_str()?.to_string())
}

#[cfg(test)]
mod tests {
use std::{env, f64::consts::PI, fs};

use ndarray::array;

use crate::ffi::constants::WindowOperations;

use crate::test_helpers::assertions::assert_regex_matches;
use crate::test_helpers::consts::VERSION_PATTERN;
use super::*;

#[test]
fn test_it_gets_the_version() {
assert_regex_matches(VERSION_PATTERN, get_version().unwrap().as_str());
}

#[test]
fn wavelet_inverse_transform_equals_input_data() {
let step = 2.0 * PI / 256.0;
Expand All @@ -846,7 +850,7 @@ mod tests {
println!("{:?}", restored_fft);

println!("{:?}", data);
let wavelet_data = perform_wavelet_transform(&mut data, "db3", 3).unwrap();
let wavelet_data = perform_wavelet_transform(&mut data, WaveletTypes::Db3, 3, WaveletExtensionTypes::Periodic).unwrap();
let restored_wavelet = perform_inverse_wavelet_transform(wavelet_data).unwrap();
println!("{:?}", restored_wavelet);
for (d, r) in data.iter().zip(restored_wavelet) {
Expand Down
1 change: 1 addition & 0 deletions rust_package/brainflow/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ mod ffi;
/// Used to calculate derivative metrics from raw data.
pub mod ml_model;

mod test_helpers;
/// Store all supported BrainFlow Errors.
pub use error::BrainFlowError;
/// Enum to store all supported Agg Operations.
Expand Down
26 changes: 18 additions & 8 deletions rust_package/brainflow/src/ml_model.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{
ffi::CString,
os::raw::{c_double, c_int},
ffi::{CString, CStr},
os::raw::{c_double, c_int, c_char},
};

use crate::{
Expand Down Expand Up @@ -105,16 +105,26 @@ pub fn release_all() -> Result<()> {

/// Get DataFilter version.
pub fn get_version() -> Result<String> {
const MAX_CHARS: usize = 64;
let mut response_len = 0;
let response = CString::new(Vec::with_capacity(64))?;
let response = response.into_raw();
let mut result_char_buffer: [c_char; MAX_CHARS] = [0; MAX_CHARS];
let (res, response) = unsafe {
let res = ml_module::get_version_ml_module(response, &mut response_len, 64);
let response = CString::from_raw(response);
let res = ml_module::get_version_ml_module(result_char_buffer.as_mut_ptr(), &mut response_len, MAX_CHARS as i32);
let response = CStr::from_ptr(result_char_buffer.as_ptr());
(res, response)
};
check_brainflow_exit_code(res)?;
let version = response.to_str()?.split_at(response_len as usize).0;
Ok(response.to_str()?.to_string())
}

#[cfg(test)]
mod tests {
use crate::ml_model::get_version;
use crate::test_helpers::assertions::assert_regex_matches;
use crate::test_helpers::consts::VERSION_PATTERN;

Ok(version.to_string())
#[test]
fn test_it_gets_the_version() {
assert_regex_matches(VERSION_PATTERN, get_version().unwrap().as_str());
}
}
14 changes: 14 additions & 0 deletions rust_package/brainflow/src/test_helpers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#[cfg(test)]
pub(crate) mod assertions {
use regex::Regex;

pub(crate) fn assert_regex_matches(regex: &str, value: &str) {
let compiled_regex = Regex::new(regex).unwrap();
assert!(compiled_regex.is_match(value), "Expected to match {}, got {}", regex, value);
}
}

#[cfg(test)]
pub mod consts {
pub(crate) const VERSION_PATTERN: &str = r"^\d+\.\d+\.\d+$";
}

0 comments on commit ed7ec7b

Please sign in to comment.