Repository: INTO-CPS-Association/unifmu (language: python)

Issue:
error using -z flag
The code:
`./target/unifmu generate python test-py`
succesfully worked. However using the `-z` flag as in
`./target/unifmu generate -z python test-py`
produced the error:
`[2021-08-27T11:20:40Z INFO  unifmu] Generating FMU for language 'Python' with tmpdir "/var/folders/g6/kltshxrn3198s863_j7l4b_m0000gp/T/.tmpNHQ2H0" and final output path "test-py"
[2021-08-27T11:20:40Z INFO  unifmu] "/var/folders/g6/kltshxrn3198s863_j7l4b_m0000gp/T/.tmpNHQ2H0/binaries/win64/unifmu.dll"
[2021-08-27T11:20:40Z INFO  unifmu] copying resource "python/backend.py" to "/var/folders/g6/kltshxrn3198s863_j7l4b_m0000gp/T/.tmpNHQ2H0/resources/backend.py"
[2021-08-27T11:20:40Z INFO  unifmu] copying resource "python/model.py" to "/var/folders/g6/kltshxrn3198s863_j7l4b_m0000gp/T/.tmpNHQ2H0/resources/model.py"
[2021-08-27T11:20:40Z INFO  unifmu] copying resource "auto_generated/unifmu_fmi2_pb2.py" to "/var/folders/g6/kltshxrn3198s863_j7l4b_m0000gp/T/.tmpNHQ2H0/resources/schemas/unifmu_fmi2_pb2.py"
[2021-08-27T11:20:40Z INFO  unifmu] copying resource "python/launch.toml" to "/var/folders/g6/kltshxrn3198s863_j7l4b_m0000gp/T/.tmpNHQ2H0/resources/launch.toml"
[2021-08-27T11:20:40Z INFO  unifmu] copying resource "python/README.md" to "/var/folders/g6/kltshxrn3198s863_j7l4b_m0000gp/T/.tmpNHQ2H0/resources/README.md"
thread 'main' panicked at 'not yet implemented', cli/src/lib.rs:163:17
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
bash-3.2$ `
I am on an Apple machine. 


Discussion / hints:

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index 4c79ac5..b5bb64b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,3 +1,3 @@
 [workspace]
 
-members = ["fmi2api", "integration-tests", "cli"]
+members = ["common", "fmi2api", "integration-tests", "cli"]
diff --git a/README.md b/README.md
index 2c1e35e..bbaaaa4 100644
--- a/README.md
+++ b/README.md
@@ -116,7 +116,12 @@ docker run --name builder -it -v ${pwd}:/workdir unifmu-build   # powershell
 
 **Note: On windows you may have to enable the use of shared folders through the dockers interface, otherwise the container fails to start.**
 
-To build the code invoke the script `docker-build/build_all.sh` in the `workdir` of the container.
+To build the code invoke the script `docker-build/build_all.sh` in the `workdir` of the container:
+
+``` bash
+bash ./docker-build/build_all.sh
+```
+
 This generates and copies all relevant build artifacts into the `assets/auto_generated` directory:
 
 ```
@@ -129,7 +134,7 @@ This generates and copies all relevant build artifacts into the `assets/auto_gen
  ┗ 📜unifmu_fmi2_pb2.py
 ```
 
-**Note: On windows Git may be configured to replace LF line-endings with CRLF. **
+**Note: On windows Git may be configured to replace LF line-endings with CRLF, which are not compatible with bash.**
 
 Following this the cli is compiled for each platform, including the assets that were just compiled.
 The final standalone executables can be found in the target folder, under the host tripple:
@@ -138,8 +143,6 @@ The final standalone executables can be found in the target folder, under the ho
 - windows: unifmu-x86_64-pc-windows-gnu-0.0.4.zip
 - macOS: unifmu-x86_64-apple-darwin-0.0.4.zip
 
-**Note: The executable for any platform embeds implementations of the FMI api for all other platforms. In other words the windows executable can generate FMUs that run on all other platforms.**
-
 ## Environment Variables
 
 In addition to the systems environment variables, UniFMU defines the following variables in the process created during instantiation of a slave.
diff --git a/cli/Cargo.toml b/cli/Cargo.toml
index 06f8e55..3e1d7c0 100644
--- a/cli/Cargo.toml
+++ b/cli/Cargo.toml
@@ -2,7 +2,7 @@
 authors = ["Christian Legaard <clegaard@outlook.com>"]
 edition = "2018"
 name = "unifmu"
-version = "0.0.4"
+version = "0.0.5"
 
 [dependencies]
 
@@ -11,8 +11,15 @@ env_logger = "*"
 fs_extra = "*"
 lazy_static = "*"
 libc = "*"
+dlopen = "*"
+dlopen_derive = "*"
 log = "*"
 num_enum = "*"
 rust-embed = "6.0.0"
 structopt = "*"
 tempfile = "*"
+url = "*"
+walkdir = "*"
+zip = { version = "*", default-features = false, features = ["deflate"] }
+
+common = { path = "../common" }
diff --git a/cli/src/benchmark.rs b/cli/src/benchmark.rs
new file mode 100644
index 0000000..7a612df
--- /dev/null
+++ b/cli/src/benchmark.rs
@@ -0,0 +1,13 @@
+use std::path::Path;
+
+pub struct BenchmarkConfig {}
+
+impl Default for BenchmarkConfig {
+    fn default() -> Self {
+        Self {}
+    }
+}
+
+pub fn benchmark(rootdir: &Path, config: &BenchmarkConfig) {
+    todo!();
+}
diff --git a/cli/src/lib.rs b/cli/src/lib.rs
index 78daa50..77a4fef 100644
--- a/cli/src/lib.rs
+++ b/cli/src/lib.rs
@@ -1,5 +1,7 @@
 use clap::arg_enum;
-use std::path::Path;
+use std::{fs::File, path::Path};
+use walkdir::WalkDir;
+use zip::{result::ZipError, CompressionMethod};
 
 use fs_extra::dir::CopyOptions;
 use lazy_static::lazy_static;
@@ -7,6 +9,11 @@ use log::info;
 use rust_embed::RustEmbed;
 use tempfile::TempDir;
 
+use crate::utils::zip_dir;
+
+#[macro_use]
+extern crate dlopen_derive;
+
 arg_enum! {
     #[derive(Debug)]
 pub enum Language {
@@ -20,6 +27,10 @@ pub enum Language {
 #[folder = "../assets"]
 struct Assets;
 
+pub mod benchmark;
+pub mod utils;
+pub mod validation;
+
 struct LanguageAssets {
     resources: Vec<(&'static str, &'static str)>,
     docker: Vec<(&'static str, &'static str)>,
@@ -67,6 +78,8 @@ lazy_static! {
 #[derive(Debug)]
 pub enum GenerateError {
     Error,
+    FileExists,
+    ZipError(ZipError),
 }
 
 pub fn generate(
@@ -160,10 +173,38 @@ pub fn generate(
 
     match zipped {
         // zip to temporary, change extension from 'zip' to 'fmu', then copy to output directory
-        true => todo!(),
+        true => {
+            info!("Compressing contents into archive with path {:?}", outpath);
+
+            let file = match File::create(&outpath) {
+                Ok(f) => f,
+                Err(_) => return Err(GenerateError::FileExists),
+            };
+
+            let walkdir = WalkDir::new(tmpdir.path());
+            let it = walkdir.into_iter();
+
+            let method = CompressionMethod::Deflated;
+
+            match zip_dir(
+                &mut it.filter_map(|e| e.ok()),
+                tmpdir.path().to_str().unwrap(),
+                file,
+                method,
+            ) {
+                Ok(_) => (),
+                Err(e) => return Err(GenerateError::ZipError(e)),
+            }
+            Ok(())
+        }
 
         // copy from temporary directory to output directory
         false => {
+            info!(
+                "copying temporary dir from {:?} to output {:?}",
+                tmpdir.path(),
+                outpath,
+            );
             let mut options = CopyOptions::default();
             options.copy_inside = true;
             options.content_only = true;
diff --git a/cli/src/main.rs b/cli/src/main.rs
index 832fb2e..b0eb60e 100644
--- a/cli/src/main.rs
+++ b/cli/src/main.rs
@@ -1,15 +1,27 @@
 use env_logger::Builder;
-use log::info;
-use std::path::PathBuf;
+use log::{error, info};
+use std::{path::PathBuf, process::exit};
 use structopt::StructOpt;
-use unifmu::{generate, Language};
+use unifmu::{
+    benchmark::{benchmark, BenchmarkConfig},
+    generate,
+    validation::{validate, ValidationConfig},
+    Language,
+};
 
 #[derive(Debug, StructOpt)]
 #[structopt(
     name = "UniFMU",
     about = "Implement 'Functional Mock-up units' (FMUs) in various source languages."
 )]
-enum Subcommands {
+struct Arguments {
+    #[structopt(subcommand)]
+    cmd: Command,
+}
+
+#[derive(Debug, StructOpt)]
+
+enum Command {
     /// Create a new FMU using the specified source language
     Generate {
         /// Source language of the generated FMU
@@ -28,28 +40,75 @@ enum Subcommands {
         dockerize: bool,
     },
 
-    Validate {},
+    /// Run a suite of checks to detect potential defects of the FMU
+    Validate {
+        /// Path to FMU directory or archive
+        path: PathBuf,
+    },
+
+    /// Benchmark the performance of the FMU
+    Benchmark {
+        /// Path to FMU directory or archive
+        path: PathBuf,
+    },
 }
 
 fn main() {
-    let opt = Subcommands::from_args();
+    let opt = Arguments::from_args();
 
     let mut b = Builder::new();
-    b.filter_level(log::LevelFilter::Info);
-    b.init();
+    b.filter_level(log::LevelFilter::Info)
+        .format_timestamp(None)
+        .format_target(false)
+        .format_module_path(false)
+        .init();
 
-    match opt {
-        Subcommands::Generate {
+    match opt.cmd {
+        Command::Generate {
             language,
             outpath,
             zipped,
             dockerize,
         } => match generate(&language, &outpath, zipped, dockerize) {
             Ok(_) => {
-                info!("The FMU was generated succesfully");
+                info!("the FMU was generated succesfully");
+            }
+            Err(e) => {
+                error!("an error ocurred during the generation of the FMU: {:?}", e);
+                exit(-1);
             }
-            Err(_) => todo!(),
         },
-        Subcommands::Validate {} => todo!(),
+        Command::Validate { path } => {
+            let config = ValidationConfig::default();
+
+            let path = match path.is_absolute() {
+                true => path,
+                false => std::env::current_dir().unwrap().join(path),
+            };
+
+            if !path.exists() {
+                error!("Unable to open FMU, the specified path is neither a directory or a file.");
+                exit(-1);
+            }
+
+            // let path = path.canonicalize().unwrap();
+
+            info!(
+                "validating the following FMU {:?} with the following checks {:?}",
+                path, config
+            );
+
+            match validate(&path, &config) {
+                Ok(_) => info!("no errors detected during validation of the FMU"),
+                Err(e) => {
+                    error!(
+                        "a defect was detected during the validation of the FMU: {:?} ",
+                        e
+                    );
+                    exit(-1);
+                }
+            }
+        }
+        Command::Benchmark { path } => benchmark(&path, &BenchmarkConfig::default()),
     }
 }
diff --git a/cli/src/utils.rs b/cli/src/utils.rs
new file mode 100644
index 0000000..7754037
--- /dev/null
+++ b/cli/src/utils.rs
@@ -0,0 +1,51 @@
+use log::info;
+use std::fs::File;
+use std::io::prelude::*;
+use std::io::{Seek, Write};
+use std::iter::Iterator;
+use std::path::Path;
+use walkdir::{DirEntry, WalkDir};
+use zip::result::ZipError;
+use zip::write::FileOptions;
+
+pub fn zip_dir<T>(
+    it: &mut dyn Iterator<Item = DirEntry>,
+    prefix: &str,
+    writer: T,
+    method: zip::CompressionMethod,
+) -> zip::result::ZipResult<()>
+where
+    T: Write + Seek,
+{
+    let mut zip = zip::ZipWriter::new(writer);
+    let options = FileOptions::default()
+        .compression_method(method)
+        .unix_permissions(0o755);
+
+    let mut buffer = Vec::new();
+    for entry in it {
+        let path = entry.path();
+        let name = path.strip_prefix(Path::new(prefix)).unwrap();
+
+        // Write file or directory explicitly
+        // Some unzip tools unzip files with directory paths correctly, some do not!
+        if path.is_file() {
+            info!("adding file {:?} as {:?} ...", path, name);
+            #[allow(deprecated)]
+            zip.start_file_from_path(name, options)?;
+            let mut f = File::open(path)?;
+
+            f.read_to_end(&mut buffer)?;
+            zip.write_all(&*buffer)?;
+            buffer.clear();
+        } else if name.as_os_str().len() != 0 {
+            // Only if not root! Avoids path spec / warning
+            // and mapname conversion failed error on unzip
+            info!("adding dir {:?} as {:?} ...", path, name);
+            #[allow(deprecated)]
+            zip.add_directory_from_path(name, options)?;
+        }
+    }
+    zip.finish()?;
+    Result::Ok(())
+}
diff --git a/cli/src/validation.rs b/cli/src/validation.rs
new file mode 100644
index 0000000..6acd929
--- /dev/null
+++ b/cli/src/validation.rs
@@ -0,0 +1,466 @@
+#![allow(non_snake_case, non_camel_case_types)]
+use std::{
+    ffi::CString,
+    path::{Path, PathBuf},
+};
+
+extern crate dlopen;
+
+use dlopen::wrapper::{Container, WrapperApi};
+
+use common::md::{Fmi2ModelDescription, Fmi2Variable};
+use libc::{c_char, c_double, c_int, c_void, size_t};
+// use libloading::{Library, Symbol};
+
+use log::{error, info};
+use std::ptr::null;
+use url::Url;
+
+type fmi2CallbackFunctions = *const c_void;
+
+// type fmi2InstantiateType = unsafe extern "C" fn(
+//     *const c_char,
+//     c_int,
+//     *const c_char,
+//     *const c_char,
+//     fmi2CallbackFunctions,
+//     c_int,
+//     c_int,
+// ) -> *mut c_void;
+
+// type fmi2SetupExperimentType =
+//     unsafe extern "C" fn(*mut c_void, c_int, c_double, c_double, c_int, c_double) -> c_int;
+// type fmi2EnterInitializationModeType = unsafe extern "C" fn(*mut c_void) -> c_int;
+// type fmi2ExitInitializationModeType = unsafe extern "C" fn(*mut c_void) -> c_int;
+// type fmi2TerminateType = unsafe extern "C" fn(*mut c_void) -> c_int;
+// type fmi2ResetType = unsafe extern "C" fn(*mut c_void) -> c_int;
+// type fmi2FreeInstanceType = unsafe extern "C" fn(*mut c_void);
+// type fmi2DoStepType = unsafe extern "C" fn(*mut c_void, c_double, c_double, c_int) -> c_int;
+
+#[derive(WrapperApi)]
+struct Fmi2CoSimulationApi {
+    fmi2Instantiate: unsafe extern "C" fn(
+        name: *const c_char,
+        fmu_type: c_int,
+        guid: *const c_char,
+        path: *const c_char,
+        callbacks: fmi2CallbackFunctions,
+        visible: c_int,
+        logging: c_int,
+    ) -> *mut c_void,
+
+    fmi2SetupExperiment: unsafe extern "C" fn(
+        instance: *mut c_void,
+        tolerance_defined: c_int,
+        tolerance: c_double,
+        start_time: c_double,
+        stop_time_defined: c_int,
+        stop_time: c_double,
+    ) -> c_int,
+    fmi2EnterInitializationMode: unsafe extern "C" fn(instance: *mut c_void) -> c_int,
+    fmi2ExitInitializationMode: unsafe extern "C" fn(instance: *mut c_void) -> c_int,
+    fmi2Terminate: unsafe extern "C" fn(instance: *mut c_void) -> c_int,
+    fmi2Reset: unsafe extern "C" fn(instance: *mut c_void) -> c_int,
+    fmi2FreeInstance: unsafe extern "C" fn(instance: *mut c_void),
+    fmi2DoStep: unsafe extern "C" fn(
+        instance: *mut c_void,
+        current_time: c_double,
+        step_size: c_double,
+        no_step_prior: c_int,
+    ) -> c_int,
+
+    fmi2GetReal: unsafe extern "C" fn(
+        instance: *mut c_void,
+        vrefs: *const u32,
+        values: *mut f64,
+        nvr: size_t,
+    ) -> i32,
+
+    fmi2GetInteger: unsafe extern "C" fn(
+        instance: *mut c_void,
+        vrefs: *const u32,
+        values: *mut i32,
+        nvr: size_t,
+    ) -> i32,
+
+    fmi2GetBoolean: unsafe extern "C" fn(
+        instance: *mut c_void,
+        vrefs: *const u32,
+        values: *mut i32,
+        nvr: size_t,
+    ) -> i32,
+
+    fmi2GetString: unsafe extern "C" fn(
+        instance: *mut c_void,
+        vrefs: *const u32,
+        values: *mut *const c_char,
+        nvr: size_t,
+    ) -> i32,
+}
+
+#[derive(Debug)]
+pub enum ValidateError {
+    /// Unable to locate the binary for the current host operating system
+    BinaryNotFound,
+    ModelDescriptionNotFound,
+    ModelDescriptionUnableToRead,
+    ModelDescriptionUnableToParse,
+    MissingMethods {
+        methods: Vec<String>,
+    },
+
+    Fmi2InstantiateFailed,
+    Fmi2SetupExperimentFailed(i32),
+    Fmi2EnterInitializationModeFailed(i32),
+    Fmi2ExitInitializationModeFailed(i32),
+    Fmi2TerminateFailed(i32),
+    Fmi2ResetFailed(i32),
+}
+
+#[derive(Debug)]
+pub struct ValidationConfig {
+    check_instantiate: bool,
+
+    /// ensure that the start values specified by the modelDescription.xml file are identical to the values defined by the FMU.
+    start_values: bool,
+}
+
+impl Default for ValidationConfig {
+    fn default() -> Self {
+        Self {
+            check_instantiate: true,
+            start_values: true,
+        }
+    }
+}
+
+pub struct FmuDescription {
+    md: Fmi2ModelDescription,
+}
+
+/// Convenience wrapper for a single instance of an FMI2 FMU.
+struct Fmi2CoSimulationFMU {
+    api: Container<Fmi2CoSimulationApi>,
+    handle: *mut c_void,
+}
+
+impl Fmi2CoSimulationFMU {
+    pub unsafe fn fmi2Instantate(
+        api: Container<Fmi2CoSimulationApi>,
+        instance_name: &str,
+        guid: &str,
+        resources_uri: &str,
+        visible: bool,
+        logging: bool,
+    ) -> Result<Self, ValidateError> {
+        let instance_name = CString::new(instance_name).unwrap();
+        let guid = CString::new(guid).unwrap();
+
+        let resources_uri = CString::new(resources_uri).unwrap();
+        let fmu_type: c_int = 1;
+        let callbacks = null();
+
+        let visible: c_int = 1;
+        let logging_on: c_int = 1;
+
+        let handle = api.fmi2Instantiate(
+            instance_name.as_ptr(),
+            fmu_type,
+            guid.as_ptr(),
+            resources_uri.as_ptr(),
+            callbacks,
+            visible,
+            logging_on,
+        );
+
+        if handle.is_null() {
+            return Err(ValidateError::Fmi2InstantiateFailed);
+        }
+
+        Ok(Self { api, handle })
+    }
+
+    pub fn fmi2EnterInitializationMode(&self) -> i32 {
+        unsafe { self.api.fmi2EnterInitializationMode(self.handle) }
+    }
+    pub fn fmi2ExitInitializationMode(&self) -> i32 {
+        unsafe { self.api.fmi2ExitInitializationMode(self.handle) }
+    }
+
+    pub fn fmi2SetupExperiment(
+        &self,
+        tolerance: Option<f64>,
+        start_time: f64,
+        stop_time: Option<f64>,
+    ) -> i32 {
+        let tolerance_defined = match tolerance {
+            Some(_) => 1,
+            None => 0,
+        };
+        let tolerance = tolerance.unwrap_or_else(|| 0.0);
+        let stop_time_defined = match stop_time {
+            Some(_) => 1,
+            None => 0,
+        };
+        let stop_time = stop_time.unwrap_or_else(|| 0.0);
+        unsafe {
+            self.api.fmi2SetupExperiment(
+                self.handle,
+                tolerance_defined,
+                tolerance,
+                start_time,
+                stop_time_defined,
+                stop_time,
+            )
+        }
+    }
+
+    pub fn fmi2DoStep(&self, current_time: f64, step_size: f64, no_step_prior: i32) -> i32 {
+        unsafe {
+            self.api
+                .fmi2DoStep(self.handle, current_time, step_size, no_step_prior)
+        }
+    }
+
+    pub fn fmi2Terminate(&self) -> i32 {
+        unsafe { self.api.fmi2Terminate(self.handle) }
+    }
+
+    pub fn fmi2Reset(&self) -> i32 {
+        unsafe { self.api.fmi2Reset(self.handle) }
+    }
+
+    pub fn fmi2FreeInstance(&self) -> () {
+        unsafe { self.api.fmi2FreeInstance(self.handle) }
+    }
+
+    pub fn fmi2GetReal(&self, vrefs: &[u32], values: &mut [f64]) -> i32 {
+        unsafe {
+            self.api.fmi2GetReal(
+                self.handle,
+                vrefs.as_ptr(),
+                values.as_mut_ptr(),
+                values.len(),
+            )
+        }
+    }
+
+    pub fn fmi2GetInteger(&self, vrefs: &[u32], values: &mut [i32]) -> i32 {
+        unsafe {
+            self.api.fmi2GetInteger(
+                self.handle,
+                vrefs.as_ptr(),
+                values.as_mut_ptr(),
+                values.len(),
+            )
+        }
+    }
+
+    pub fn fmi2GetBoolean(&self, vrefs: &[u32], values: &mut [bool]) -> i32 {
+        let mut c_values: Vec<i32> = values.into_iter().map(|v| *v as i32).collect();
+
+        let status = unsafe {
+            self.api.fmi2GetBoolean(
+                self.handle,
+                vrefs.as_ptr(),
+                c_values.as_mut_ptr(),
+                values.len(),
+            )
+        };
+
+        for (b, i) in values.into_iter().zip(c_values) {
+            *b = i != 0;
+        }
+
+        status
+    }
+
+    pub fn fmi2GetString(&self, vrefs: &[u32], values: &mut [String]) -> i32 {
+        let c_values: Vec<CString> = values
+            .into_iter()
+            .map(|v| CString::new(v.as_str()).unwrap())
+            .collect();
+
+        let mut c_values_ptr: Vec<*const c_char> = c_values.iter().map(|v| v.as_ptr()).collect();
+
+        let ptrs = c_values_ptr.as_mut_ptr();
+
+        let status = unsafe {
+            self.api
+                .fmi2GetString(self.handle, vrefs.as_ptr(), ptrs, values.len())
+        };
+
+        for (c, cs) in values.into_iter().zip(c_values) {
+            *c = cs.to_str().unwrap().to_owned();
+        }
+
+        status
+    }
+}
+
+pub fn validate(rootdir: &Path, config: &ValidationConfig) -> Result<(), ValidateError> {
+    let md_path = rootdir.join("modelDescription.xml");
+
+    if !md_path.exists() {
+        return Err(ValidateError::ModelDescriptionNotFound);
+    }
+
+    let md = match std::fs::read_to_string(md_path) {
+        Ok(s) => match Fmi2ModelDescription::from_slice(s.as_bytes()) {
+            Ok(md) => md,
+            Err(_) => return Err(ValidateError::ModelDescriptionUnableToParse),
+        },
+        Err(_) => return Err(ValidateError::ModelDescriptionUnableToRead),
+    };
+
+    if md.cosimulation.is_none() {
+        todo!("Only validation of FMI2 cosimulation FMUs are currently supported.")
+    }
+
+    let model_identifier = md.cosimulation.unwrap().model_identifier;
+
+    // load library
+    unsafe {
+        let binary_path = rootdir.join("binaries");
+
+        let binary_path = match std::env::consts::OS {
+            "windows" => binary_path
+                .join("win64")
+                .join(format!("{}.dll", model_identifier)),
+            "macos" => binary_path
+                .join("darwin645")
+                .join(format!("{}.dylib", model_identifier)),
+
+            _other => binary_path
+                .join("linux64")
+                .join(format!("{}.so", model_identifier)),
+        };
+
+        info!("attempting to load binary from {:?}", binary_path);
+
+        let api: Container<Fmi2CoSimulationApi> = match Container::load(binary_path) {
+            Ok(l) => l,
+            Err(_) => return Err(ValidateError::BinaryNotFound),
+        };
+
+        info!("binary loaded successfully");
+
+        // instantiate slave
+        let instance_name = CString::new("a").unwrap();
+        let resources_path = rootdir.join("resources");
+
+        info!(
+            "attempting to generate URI for based on path to resources directory {:?}",
+            resources_path
+        );
+
+        let url = Url::from_file_path(resources_path).unwrap();
+        let resources_uri = CString::new(url.as_str()).unwrap();
+        let fmu_type: c_int = 1;
+        let callbacks: *const c_void = null();
+
+        let guid = CString::new(md.guid).unwrap();
+
+        let visible: c_int = 1;
+        let logging_on: c_int = 1;
+
+        info!(
+                "instantiating slave with instance name {:?} with type {:?}, guid {:?}, resources uri {:?}, callbacks: {:?}, visible {:?} and logging {:?}",
+                instance_name, fmu_type, guid, resources_uri, callbacks, visible, logging_on
+            );
+
+        let s = match Fmi2CoSimulationFMU::fmi2Instantate(
+            api,
+            &"abc",
+            &"1234",
+            &url.as_str(),
+            true,
+            false,
+        ) {
+            Ok(s) => s,
+            Err(e) => return Err(e),
+        };
+
+        // Basic run of lifecycle
+
+        match s.fmi2SetupExperiment(None, 0.001, None) {
+            0 => (),
+            s => return Err(ValidateError::Fmi2SetupExperimentFailed(s)),
+        }
+
+        match s.fmi2EnterInitializationMode() {
+            0 => (),
+            s => return Err(ValidateError::Fmi2EnterInitializationModeFailed(s)),
+        }
+
+        match s.fmi2ExitInitializationMode() {
+            0 => (),
+            s => return Err(ValidateError::Fmi2EnterInitializationModeFailed(s)),
+        }
+
+        let mut current_time = 0.0;
+        let step_size = 0.01;
+        let n_steps = 100;
+        for _ in 0..n_steps {
+            let status = s.fmi2DoStep(current_time, step_size, 0);
+            current_time += step_size;
+        }
+
+        match s.fmi2Terminate() {
+            0 => (),
+            s => return Err(ValidateError::Fmi2TerminateFailed(s)),
+        }
+
+        match s.fmi2Reset() {
+            0 => (),
+            s => return Err(ValidateError::Fmi2ResetFailed(s)),
+        }
+
+        // check start values
+        if config.start_values {
+            let mut real_vrefs: Vec<(u32, f64)> = Vec::new();
+            let mut integer_vrefs: Vec<(u32, i32)> = Vec::new();
+            let mut boolean_vrefs: Vec<(u32, bool)> = Vec::new();
+            let mut string_vrefs: Vec<(u32, String)> = Vec::new();
+            for v in md.model_variables.variables {
+                match v.var {
+                    Fmi2Variable::Real { start } => match start {
+                        Some(s) => real_vrefs.push((v.value_reference, s)),
+                        None => (),
+                    },
+                    Fmi2Variable::Integer { start } => match start {
+                        Some(s) => integer_vrefs.push((v.value_reference, s)),
+                        None => (),
+                    },
+                    Fmi2Variable::Boolean { start } => match start {
+                        Some(s) => boolean_vrefs.push((v.value_reference, s)),
+                        None => (),
+                    },
+                    Fmi2Variable::String { start } => match start {
+                        Some(s) => string_vrefs.push((v.value_reference, s)),
+                        None => (),
+                    },
+                };
+            }
+
+            match s.fmi2EnterInitializationMode() {
+                0 => (),
+                s => return Err(ValidateError::Fmi2EnterInitializationModeFailed(s)),
+            }
+
+            match s.fmi2ExitInitializationMode() {
+                0 => (),
+                s => return Err(ValidateError::Fmi2EnterInitializationModeFailed(s)),
+            }
+        }
+
+        //
+        s.fmi2FreeInstance();
+
+        Ok(())
+    }
+}
+
+fn validate_common() {}
+
+fn validate_cosimulation() {}
diff --git a/common/Cargo.toml b/common/Cargo.toml
new file mode 100644
index 0000000..2737424
--- /dev/null
+++ b/common/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+edition = "2018"
+name = "common"
+version = "0.1.0"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+quick-xml = {version = "*", features = ["serialize"]}
+safer-ffi = {version = "*", features = ["proc_macros"]}
+serde = {version = "1.0.127", features = ["derive"]}
diff --git a/common/src/lib.rs b/common/src/lib.rs
new file mode 100644
index 0000000..3c45b4b
--- /dev/null
+++ b/common/src/lib.rs
@@ -0,0 +1,92 @@
+pub mod md;
+use serde::{Deserialize, Deserializer};
+
+#[cfg(test)]
+mod tests {
+    use crate::md::Fmi2ModelDescription;
+
+    #[test]
+    fn it_works() {
+        let md = r#"
+    <?xml version='1.0' encoding='utf-8'?>
+    <fmiModelDescription fmiVersion="2.0" modelName="unifmu" guid="77236337-210e-4e9c-8f2c-c1a0677db21b" author="Christian Møldrup Legaard" generationDateAndTime="2020-10-23T19:51:25Z" variableNamingConvention="flat" generationTool="unifmu">
+    <CoSimulation modelIdentifier="unifmu" needsExecutionTool="true" canNotUseMemoryManagementFunctions="false" canHandleVariableCommunicationStepSize="true" />
+    <LogCategories>
+        <Category name="logStatusWarning" />
+        <Category name="logStatusDiscard" />
+        <Category name="logStatusError" />
+        <Category name="logStatusFatal" />
+        <Category name="logStatusPending" />
+        <Category name="logAll" />
+    </LogCategories>
+    <ModelVariables>
+        <!--Index of variable = "1"-->
+        <ScalarVariable name="real_a" valueReference="0" variability="continuous" causality="input">
+        <Real start="0.0" />
+        </ScalarVariable>
+        <!--Index of variable = "2"-->
+        <ScalarVariable name="real_b" valueReference="1" variability="continuous" causality="input">
+        <Real start="0.0" />
+        </ScalarVariable>
+        <!--Index of variable = "3"-->
+        <ScalarVariable name="real_c" valueReference="2" variability="continuous" causality="output" initial="calculated">
+        <Real />
+        </ScalarVariable>
+        <!--Index of variable = "4"-->
+        <ScalarVariable name="integer_a" valueReference="3" variability="discrete" causality="input">
+        <Integer start="0" />
+        </ScalarVariable>
+        <!--Index of variable = "5"-->
+        <ScalarVariable name="integer_b" valueReference="4" variability="discrete" causality="input">
+        <Integer start="0" />
+        </ScalarVariable>
+        <!--Index of variable = "6"-->
+        <ScalarVariable name="integer_c" valueReference="5" variability="discrete" causality="output" initial="calculated">
+        <Integer />
+        </ScalarVariable>
+        <!--Index of variable = "7"-->
+        <ScalarVariable name="boolean_a" valueReference="6" variability="discrete" causality="input">
+        <Boolean start="false" />
+        </ScalarVariable>
+        <!--Index of variable = "8"-->
+        <ScalarVariable name="boolean_b" valueReference="7" variability="discrete" causality="input">
+        <Boolean start="false" />
+        </ScalarVariable>
+        <!--Index of variable = "9"-->
+        <ScalarVariable name="boolean_c" valueReference="8" variability="discrete" causality="output" initial="calculated">
+        <Boolean />
+        </ScalarVariable>
+        <!--Index of variable = "10"-->
+        <ScalarVariable name="string_a" valueReference="9" variability="discrete" causality="input">
+        <String start="" />
+        </ScalarVariable>
+        <!--Index of variable = "11"-->
+        <ScalarVariable name="string_b" valueReference="10" variability="discrete" causality="input">
+        <String start="" />
+        </ScalarVariable>
+        <!--Index of variable = "12"-->
+        <ScalarVariable name="string_c" valueReference="11" variability="discrete" causality="output" initial="calculated">
+        <String />
+        </ScalarVariable>
+
+    </ModelVariables>
+    <ModelStructure>
+        <Outputs>
+        <Unknown index="3" dependencies="" />
+        <Unknown index="6" dependencies="" />
+        <Unknown index="9" dependencies="" />
+        <Unknown index="12" dependencies="" />
+        </Outputs>
+        <InitialUnknowns>
+        <Unknown index="3" dependencies="" />
+        <Unknown index="6" dependencies="" />
+        <Unknown index="9" dependencies="" />
+        <Unknown index="12" dependencies="" />
+        </InitialUnknowns>
+    </ModelStructure>
+    </fmiModelDescription>"#;
+
+        let md: Fmi2ModelDescription = quick_xml::de::from_str(md).unwrap();
+        println!("{:?}", md);
+    }
+}
diff --git a/common/src/md.rs b/common/src/md.rs
new file mode 100644
index 0000000..10fbad5
--- /dev/null
+++ b/common/src/md.rs
@@ -0,0 +1,74 @@
+use std::{fs, path::Path};
+
+use quick_xml::de::from_str;
+use serde::Deserialize;
+
+#[derive(Debug, Deserialize, PartialEq)]
+pub struct Fmi2CoSimulation {
+    #[serde(rename = "modelIdentifier")]
+    pub model_identifier: String,
+
+    #[serde(rename = "canRunAsynchronuously")]
+    pub can_run_asynchronously: Option<bool>,
+}
+
+#[derive(Debug, Deserialize, PartialEq)]
+pub struct Fmi2ScalarVariable {
+    #[serde(rename = "valueReference")]
+    pub value_reference: u32,
+    pub name: String,
+    #[serde(rename = "$value")]
+    pub var: Fmi2Variable,
+}
+
+#[derive(Debug, Deserialize, PartialEq)]
+pub struct Fmi2ModelVariables {
+    #[serde(rename = "ScalarVariable")]
+    pub variables: Vec<Fmi2ScalarVariable>,
+}
+
+#[derive(Debug, Deserialize, PartialEq)]
+pub struct Fmi2ModelDescription {
+    pub guid: String,
+
+    #[serde(rename = "CoSimulation")]
+    pub cosimulation: Option<Fmi2CoSimulation>,
+
+    #[serde(rename = "ModelVariables")]
+    pub model_variables: Fmi2ModelVariables,
+}
+
+#[derive(Debug, Deserialize, PartialEq)]
+
+pub enum Fmi2Variable {
+    Real { start: Option<f64> },
+    Integer { start: Option<i32> },
+    Boolean { start: Option<bool> },
+
+    String { start: Option<String> },
+}
+
+pub enum Fmi2ModelDescriptionError {
+    UnableToRead,
+    UnableToParse,
+}
+
+pub fn parse_model_description(
+    md_path: &Path,
+) -> Result<Fmi2ModelDescription, Fmi2ModelDescriptionError> {
+    match fs::read_to_string(md_path) {
+        Ok(contents) => match from_str::<Fmi2ModelDescription>(&contents) {
+            Ok(md) => Ok(md),
+            Err(e) => Err(Fmi2ModelDescriptionError::UnableToParse),
+        },
+        Err(e) => Err(Fmi2ModelDescriptionError::UnableToRead),
+    }
+}
+
+impl Fmi2ModelDescription {
+    pub fn from_slice(buf: &[u8]) -> Result<Self, String> {
+        let md: Result<Fmi2ModelDescription, _> = quick_xml::de::from_reader(buf);
+
+        md.map_err(|_| "whoops".to_string())
+    }
+}
diff --git a/docker-build/build_all.sh b/docker-build/build_all.sh
index 36195dc..c7a143b 100644
--- a/docker-build/build_all.sh
+++ b/docker-build/build_all.sh
@@ -2,7 +2,7 @@
 # ------------------------------ fmi2api ------------------------------
 tgt=fmi2api
 echo "building fmi2api for linux"
-cargo build --package ${tgt} --release
+cargo build --package ${tgt} --target x86_64-unknown-linux-gnu --release
 
 echo "building fmi2api for windows"
 cargo build --package ${tgt} --target x86_64-pc-windows-gnu --release
@@ -13,7 +13,7 @@ export CARGO_TARGET_X86_64_APPLE_DARWIN_LINKER=/usr/osxcross/target/bin/x86_64-a
 cargo build --package ${tgt} --target x86_64-apple-darwin --release
 
 echo "copying fmi2api into cli assets"
-cp ./target/release/lib${tgt}.so ./assets/auto_generated/unifmu.so
+cp ./target/x86_64-unknown-linux-gnu/release/lib${tgt}.so ./assets/auto_generated/unifmu.so
 cp ./target/x86_64-pc-windows-gnu/release/${tgt}.dll ./assets/auto_generated/unifmu.dll
 cp ./target/x86_64-apple-darwin/release/lib${tgt}.dylib ./assets/auto_generated/unifmu.dylib
 
diff --git a/fmi2api/Cargo.toml b/fmi2api/Cargo.toml
index e332cf7..739dbfc 100644
--- a/fmi2api/Cargo.toml
+++ b/fmi2api/Cargo.toml
@@ -13,7 +13,6 @@ bytes = "*"
 libc = "*"
 num_enum = "*"
 prost = "*"
-quick-xml = {version = "*", features = ["serialize"]}
 safer-ffi = {version = "*", features = ["proc_macros"]}
 serde = {version = "1.0.127", features = ["derive"]}
 serde_bytes = "*"
@@ -28,5 +27,7 @@ zeromq = {git = "https://github.com/zeromq/zmq.rs.git", default-features = false
   "tcp-transport",
 ]}
 
+common = {path = "../common"}
+
 [build-dependencies]
 prost-build = "0.8.0"
diff --git a/fmi2api/src/lib.rs b/fmi2api/src/lib.rs
index b570acb..7c63d3e 100644
--- a/fmi2api/src/lib.rs
+++ b/fmi2api/src/lib.rs
@@ -6,9 +6,9 @@
 pub mod config;
 pub mod dispatcher;
 pub mod fmi2_proto;
-pub mod md;
 pub mod socket_dispatcher;
 
+use common::md;
 use dispatcher::{Fmi2CommandDispatcher, Fmi2CommandDispatcherError};
 use libc::c_double;
 use libc::size_t;
@@ -39,8 +39,8 @@ use std::slice::from_raw_parts;
 use std::slice::from_raw_parts_mut;
 
 use crate::config::LaunchConfig;
-use crate::md::parse_model_description;
 use crate::socket_dispatcher::Fmi2SocketDispatcher;
+use common::md::parse_model_description;
 
 ///
 /// Represents the function signature of the logging callback function passsed
@@ -285,13 +285,12 @@ pub fn fmi2Instantiate(
                     OsString::from("UNIFMU_REFS_TO_ATTRS"),
                     OsString::from(serde_json::to_string(&ref_to_attr).unwrap()),
                 ));
-                println!("the 'modelDescription.xml' was succefully parsed and it's results used to populate 'UNIFMU_REFS_TO_ATTRS'")
             }
             Err(e) => match e {
-                md::ModelDescriptionError::UnableToRead => {
+                md::Fmi2ModelDescriptionError::UnableToRead => {
                     println!("the 'modelDescription.xml' file was not found")
                 }
-                md::ModelDescriptionError::UnableToParse => {
+                md::Fmi2ModelDescriptionError::UnableToParse => {
                     println!("the 'modelDescription.xml' file was found but could not be parsed")
                 }
             },
@@ -332,7 +331,7 @@ pub fn fmi2Instantiate(
     };
 
     match dispatcher.await_handshake() {
-        Ok(handshake) => println!("Received handshake"),
+        Ok(handshake) => (),
         Err(e) => {
             eprint!("Error ocurred during handshake");
             return None;
diff --git a/fmi2api/src/md.rs b/fmi2api/src/md.rs
deleted file mode 100644
index 8296cc6..0000000
--- a/fmi2api/src/md.rs
+++ /dev/null
@@ -1,49 +0,0 @@
-use std::{fs, path::Path};
-
-use quick_xml::de::from_str;
-use serde::Deserialize;
-
-#[derive(Debug, Deserialize, PartialEq)]
-pub struct MdCoSimulation {
-    #[serde(rename = "canRunAsynchronuously")]
-    pub can_run_asynchronously: bool,
-}
-
-#[derive(Debug, Deserialize, PartialEq)]
-pub struct MdScalarVariable {
-    #[serde(rename = "valueReference")]
-    pub value_reference: u32,
-    pub name: String,
-}
-
-#[derive(Debug, Deserialize, PartialEq)]
-pub struct MdModelVariables {
-    #[serde(rename = "ScalarVariable")]
-    pub variables: Vec<MdScalarVariable>,
-}
-
-#[derive(Debug, Deserialize, PartialEq)]
-pub struct MdModelDescription {
-    pub guid: String,
-    // #[serde(rename = "CoSimulation")]
-    // cosimulation: MdCoSimulation,
-    #[serde(rename = "ModelVariables")]
-    pub model_variables: MdModelVariables,
-}
-
-pub enum ModelDescriptionError {
-    UnableToRead,
-    UnableToParse,
-}
-
-pub fn parse_model_description(
-    md_path: &Path,
-) -> Result<MdModelDescription, ModelDescriptionError> {
-    match fs::read_to_string(md_path) {
-        Ok(contents) => match from_str::<MdModelDescription>(&contents) {
-            Ok(md) => Ok(md),
-            Err(e) => Err(ModelDescriptionError::UnableToParse),
-        },
-        Err(e) => Err(ModelDescriptionError::UnableToRead),
-    }
-}


Test patch:
diff --git a/integration-tests/tests/integration_tests.rs b/integration-tests/tests/integration_tests.rs
index 1ac617f..cd90203 100644
--- a/integration-tests/tests/integration_tests.rs
+++ b/integration-tests/tests/integration_tests.rs
@@ -6,7 +6,7 @@ use unifmu::generate;
 mod tests {
 
     use fmi2api::{
-        fmi2DeSerializeFMUstate, fmi2DoStep, fmi2EnterInitializationMode,
+        config, fmi2DeSerializeFMUstate, fmi2DoStep, fmi2EnterInitializationMode,
         fmi2ExitInitializationMode, fmi2FreeFMUstate, fmi2FreeInstance, fmi2GetBoolean,
         fmi2GetFMUstate, fmi2GetInteger, fmi2GetReal, fmi2GetString, fmi2GetTypesPlatform,
         fmi2GetVersion, fmi2Instantiate, fmi2Reset, fmi2SerializeFMUstate,
@@ -27,7 +27,10 @@ mod tests {
         time::{SystemTime, UNIX_EPOCH},
     };
     use tempfile::TempDir;
-    use unifmu::Language;
+    use unifmu::{
+        validation::{validate, ValidationConfig},
+        Language,
+    };
     use url::Url;
 
     use super::*;
@@ -36,7 +39,12 @@ mod tests {
     fn test_placeholder_python() {
         let tmpdir = TempDir::new().unwrap();
         generate(&Language::Python, tmpdir.path(), false, false).unwrap();
-        test_placeholder_functionality(tmpdir.path());
+
+        let res = validate(tmpdir.path(), &ValidationConfig::default());
+
+        res.unwrap();
+
+        // test_placeholder_functionality(tmpdir.path());
     }
 
     // #[test]
@@ -50,7 +58,11 @@ mod tests {
     fn test_placeholder_csharp() {
         let tmpdir = TempDir::new().unwrap();
         generate(&Language::CSharp, tmpdir.path(), false, false).unwrap();
-        test_placeholder_functionality(tmpdir.path());
+
+        let res = validate(tmpdir.path(), &ValidationConfig::default());
+
+        res.unwrap()
+        // test_placeholder_functionality(tmpdir.path());
     }
 
     fn test_placeholder_functionality(rootdir: &Path) {