<commit_before>use std::ptr::null_mut;

use gdk_pixbuf_sys::GdkPixbuf;
use gdk_pixbuf_sys::{
    GdkPixbufFormat, GdkPixbufModule, GdkPixbufModulePattern, GdkPixbufModulePreparedFunc,
    GdkPixbufModuleSizeFunc, GdkPixbufModuleUpdatedFunc, GDK_PIXBUF_FORMAT_SCALABLE,
    GDK_PIXBUF_FORMAT_THREADSAFE,
};

use libc::{c_char, c_int, c_uint};

use glib::translate::{IntoGlib, ToGlibPtr};
use glib::Bytes;
use glib_sys::{gboolean, GError};

use gio::prelude::MemoryInputStreamExt;
use gio::MemoryInputStream;
use gobject_sys::GObject;

use librsvg::rsvg_convert_only::LegacySize;
use librsvg::Loader;

use cstr::cstr;

struct SvgContext {
    size_func: GdkPixbufModuleSizeFunc,
    prep_func: GdkPixbufModulePreparedFunc,
    update_func: GdkPixbufModuleUpdatedFunc,
    user_data: glib_sys::gpointer,
    stream: MemoryInputStream,
}

#[no_mangle]
unsafe extern "C" fn begin_load(
    size_func: GdkPixbufModuleSizeFunc,
    prep_func: GdkPixbufModulePreparedFunc,
    update_func: GdkPixbufModuleUpdatedFunc,
    user_data: glib_sys::gpointer,
    error: *mut *mut GError,
) -> glib_sys::gpointer {
    if error != null_mut() {
        *error = null_mut();
    }

    let stream = MemoryInputStream::new();
    let ctx = Box::new(SvgContext {
        size_func,
        prep_func,
        update_func,
        user_data,
        stream,
    });

    Box::into_raw(ctx) as glib_sys::gpointer
}

#[no_mangle]
unsafe extern "C" fn load_increment(
    user_data: glib_sys::gpointer,
    buffer: *const u8,
    size: c_uint,
    error: *mut *mut GError,
) -> gboolean {
    if error != null_mut() {
        *error = null_mut();
    }

    let ctx = user_data as *mut SvgContext;

    let data = std::slice::from_raw_parts(buffer, size as usize);
    (&*ctx).stream.add_bytes(&Bytes::from(data));
    true.into_glib()
}

#[no_mangle]
unsafe extern "C" fn stop_load(user_data: glib_sys::gpointer, error: *mut *mut GError) -> gboolean {
    let ctx = Box::from_raw(user_data as *mut SvgContext);
    if error != null_mut() {
        *error = null_mut();
    }

    fn _inner_stop_load(ctx: &Box<SvgContext>) -> Result<gdk_pixbuf::Pixbuf, String> {
        let handle = Loader::new()
            .read_stream::<_, gio::File, gio::Cancellable>(&ctx.stream, None, None)
            .map_err(|e| e.to_string())?;

        let renderer = librsvg::CairoRenderer::new(&handle);
        let (w, h) = renderer.legacy_document_size().map_err(|e| e.to_string())?;
        let mut w = w.ceil() as c_int;
        let mut h = h.ceil() as c_int;

        if let Some(size_func) = ctx.size_func {
            let mut tmp_w: c_int = w;
            let mut tmp_h: c_int = h;
            unsafe {
                size_func(
                    &mut tmp_w as *mut c_int,
                    &mut tmp_h as *mut c_int,
                    ctx.user_data,
                )
            };
            if tmp_w != 0 && tmp_h != 0 {
                w = tmp_w;
                h = tmp_h;
            }
        }

        let pb = librsvg::c_api::pixbuf_utils::render_to_pixbuf_at_size(
            &renderer, w as f64, h as f64, w as f64, h as f64,
        )
        .map_err(|e| e.to_string())?;

        Ok(pb)
    }

    let pixbuf = match _inner_stop_load(&ctx) {
        Ok(r) => r,
        Err(e) => {
            if error != null_mut() {
                let gerr = glib::Error::new(gdk_pixbuf::PixbufError::Failed, &e);
                *error = gerr.to_glib_full() as *mut GError;
            }
            return false.into_glib();
        }
    };

    let w = pixbuf.width();
    let h = pixbuf.height();
    let pixbuf: *mut GdkPixbuf = pixbuf.to_glib_full();

    if let Some(prep_func) = ctx.prep_func {
        prep_func(pixbuf, null_mut(), ctx.user_data);
    }
    if let Some(update_func) = ctx.update_func {
        update_func(pixbuf, 0, 0, w, h, ctx.user_data);
    }

    // The module loader increases a ref so we drop the pixbuf here
    gobject_sys::g_object_unref(pixbuf as *mut GObject);

    true.into_glib()
}

#[no_mangle]
extern "C" fn fill_vtable(module: &mut GdkPixbufModule) {
    module.begin_load = Some(begin_load);
    module.stop_load = Some(stop_load);
    module.load_increment = Some(load_increment);
}

const SIGNATURE: [GdkPixbufModulePattern; 3] = [
    GdkPixbufModulePattern {
        prefix: cstr!(" <svg").as_ptr() as *mut c_char,
        mask: cstr!("*    ").as_ptr() as *mut c_char,
        relevance: 100,
    },
    GdkPixbufModulePattern {
        prefix: cstr!(" <!DOCTYPE svg").as_ptr() as *mut c_char,
        mask: cstr!("*             ").as_ptr() as *mut c_char,
        relevance: 100,
    },
    GdkPixbufModulePattern {
        prefix: std::ptr::null_mut(),
        mask: std::ptr::null_mut(),
        relevance: 0,
    },
];

const MIME_TYPES: [*const c_char; 7] = [
    cstr!("image/svg+xml").as_ptr(),
    cstr!("image/svg").as_ptr(),
    cstr!("image/svg-xml").as_ptr(),
    cstr!("image/vnd.adobe.svg+xml").as_ptr(),
    cstr!("text/xml-svg").as_ptr(),
    cstr!("image/svg+xml-compressed").as_ptr(),
    std::ptr::null(),
];

const EXTENSIONS: [*const c_char; 4] = [
    cstr!("svg").as_ptr(),
    cstr!("svgz").as_ptr(),
    cstr!("svg.gz").as_ptr(),
    std::ptr::null(),
];

#[no_mangle]
extern "C" fn fill_info(info: &mut GdkPixbufFormat) {
    info.name = cstr!("svg").as_ptr() as *mut c_char;
    info.signature = SIGNATURE.as_ptr() as *mut GdkPixbufModulePattern;
    info.description = cstr!("Scalable Vector Graphics").as_ptr() as *mut c_char; //TODO: Gettext this
    info.mime_types = MIME_TYPES.as_ptr() as *mut *mut c_char;
    info.extensions = EXTENSIONS.as_ptr() as *mut *mut c_char;
    info.flags = GDK_PIXBUF_FORMAT_SCALABLE | GDK_PIXBUF_FORMAT_THREADSAFE;
    info.license = cstr!("LGPL").as_ptr() as *mut c_char;
}

#[cfg(test)]
mod tests {
    use gdk_pixbuf_sys::{
        GdkPixbufFormat, GDK_PIXBUF_FORMAT_SCALABLE, GDK_PIXBUF_FORMAT_THREADSAFE,
    };
    use glib::translate::IntoGlib;

    use crate::{EXTENSIONS, MIME_TYPES};
    use libc::c_char;
    use std::ptr::{null, null_mut};

    fn pb_format_new() -> GdkPixbufFormat {
        let mut info = super::GdkPixbufFormat {
            name: null_mut(),
            signature: null_mut(),
            description: null_mut(),
            mime_types: null_mut(),
            extensions: null_mut(),
            flags: 0,
            license: null_mut(),
            disabled: false.into_glib(),
            domain: null_mut(),
        };

        super::fill_info(&mut info);
        info
    }

    #[test]
    fn fill_info() {
        let info = pb_format_new();

        assert_ne!(info.name, null_mut());
        assert_ne!(info.signature, null_mut());
        assert_ne!(info.description, null_mut());
        assert_ne!(info.mime_types, null_mut());
        assert_ne!(info.extensions, null_mut());
        assert_eq!(
            info.flags,
            GDK_PIXBUF_FORMAT_SCALABLE | GDK_PIXBUF_FORMAT_THREADSAFE
        );
        assert_ne!(info.license, null_mut());
    }

    fn check_null_terminated_arr_cstrings(arr: &[*const c_char]) {
        let n_strings = arr
            .iter()
            .filter(|e| e != &&null())
            .map(|e| {
                if e != &null() {
                    // We use strlen in all of them to ensure some safety
                    // We could use CStr instead but it'd be a bit more cumbersome
                    assert!(unsafe { libc::strlen(*e as *const c_char) } > 0)
                }
            })
            .count(); // Count all non_null items

        // Ensure last item is null and is the only null item
        assert_eq!(n_strings, arr.len() - 1);
        assert!(arr.last().unwrap() == &null());
    }

    #[test]
    fn extensions_bounds() {
        check_null_terminated_arr_cstrings(&EXTENSIONS);
    }

    #[test]
    fn mime_bounds() {
        check_null_terminated_arr_cstrings(&MIME_TYPES)
    }

    #[test]
    fn signature() {
        let info = pb_format_new();

        for i in 0..2 {
            let ptr = unsafe { info.signature.offset(i) };
            if i == 2 {
                assert_eq!(unsafe { (*ptr).prefix }, null_mut());
                continue;
            } else {
                assert_ne!(unsafe { (*ptr).prefix }, null_mut());
                if unsafe { (*ptr).mask } != null_mut() {
                    // Mask can be null
                    assert_eq!(
                        unsafe { libc::strlen((*ptr).prefix as *mut c_char) },
                        unsafe { libc::strlen((*ptr).mask as *mut c_char) }
                    );
                }
                // Relevance must be 0 to 100
                assert!(unsafe { (*ptr).relevance } >= 0);
                assert!(unsafe { (*ptr).relevance } <= 100);
            }
        }
    }

    const SVG_DATA: &'static str = r#"<?xml version="1.0" encoding="UTF-8" standalone="no"?>
                                    <svg
                                    width="100px"
                                    height="150px"
                                    viewBox="0 0 26.458333 26.458333"
                                    version="1.1"
                                    id="svg5"
                                    xmlns="http://www.w3.org/2000/svg"
                                    xmlns:svg="http://www.w3.org/2000/svg">
                                    <rect
                                        style="fill:#aa1144;stroke-width:0.0344347"
                                        width="26.458332"
                                        height="39.6875"
                                        x="4.691162e-07"
                                        y="-6.6145835"
                                        id="rect2" />
                                    </svg>
    "#;

    #[test]
    fn minimal_svg() {
        unsafe extern "C" fn prep_cb(
            pb: *mut gdk_pixbuf_sys::GdkPixbuf,
            pba: *mut gdk_pixbuf_sys::GdkPixbufAnimation,
            user_data: *mut libc::c_void,
        ) {
            assert_eq!(user_data, null_mut());
            assert_eq!(pba, null_mut());

            let w = gdk_pixbuf_sys::gdk_pixbuf_get_width(pb);
            let h = gdk_pixbuf_sys::gdk_pixbuf_get_height(pb);
            let stride = gdk_pixbuf_sys::gdk_pixbuf_get_rowstride(pb);
            assert_eq!(w, 100);
            assert_eq!(h, 150);

            let pixels = gdk_pixbuf_sys::gdk_pixbuf_get_pixels(pb);

            // Upper left pixel, black with full opacity #000000ff
            assert_eq!(*pixels, 0xaa);
            assert_eq!(*pixels.offset(1), 0x11);
            assert_eq!(*pixels.offset(2), 0x44);
            assert_eq!(*pixels.offset(3), 0xff);

            // Bottom left pixel
            assert_eq!(*pixels.offset((stride * (h - 1)) as isize), 0xaa);
            assert_eq!(*pixels.offset((stride * (h - 1)) as isize + 1), 0x11);
            assert_eq!(*pixels.offset((stride * (h - 1)) as isize + 2), 0x44);
            assert_eq!(*pixels.offset((stride * (h - 1)) as isize + 3), 0xff);

            // Bottom right pixel
            assert_eq!(
                *pixels.offset((stride * (h - 1)) as isize + (w as isize - 1) * 4),
                0xaa
            );
            assert_eq!(
                *pixels.offset((stride * (h - 1)) as isize + (w as isize - 1) * 4 + 1),
                0x11
            );
            assert_eq!(
                *pixels.offset((stride * (h - 1)) as isize + (w as isize - 1) * 4 + 2),
                0x44
            );
            assert_eq!(
                *pixels.offset((stride * (h - 1)) as isize + (w as isize - 1) * 4 + 3),
                0xff
            );
        }

        let ctx = unsafe { crate::begin_load(None, Some(prep_cb), None, null_mut(), null_mut()) };
        assert_ne!(ctx, null_mut());

        let inc = unsafe {
            crate::load_increment(ctx, SVG_DATA.as_ptr(), SVG_DATA.len() as u32, null_mut())
        };
        assert_ne!(inc, 0);

        unsafe { crate::stop_load(ctx, null_mut()) };
    }
}
<commit_msg>pixbuf-loader: fix comment about color<commit_after>use std::ptr::null_mut;

use gdk_pixbuf_sys::GdkPixbuf;
use gdk_pixbuf_sys::{
    GdkPixbufFormat, GdkPixbufModule, GdkPixbufModulePattern, GdkPixbufModulePreparedFunc,
    GdkPixbufModuleSizeFunc, GdkPixbufModuleUpdatedFunc, GDK_PIXBUF_FORMAT_SCALABLE,
    GDK_PIXBUF_FORMAT_THREADSAFE,
};

use libc::{c_char, c_int, c_uint};

use glib::translate::{IntoGlib, ToGlibPtr};
use glib::Bytes;
use glib_sys::{gboolean, GError};

use gio::prelude::MemoryInputStreamExt;
use gio::MemoryInputStream;
use gobject_sys::GObject;

use librsvg::rsvg_convert_only::LegacySize;
use librsvg::Loader;

use cstr::cstr;

struct SvgContext {
    size_func: GdkPixbufModuleSizeFunc,
    prep_func: GdkPixbufModulePreparedFunc,
    update_func: GdkPixbufModuleUpdatedFunc,
    user_data: glib_sys::gpointer,
    stream: MemoryInputStream,
}

#[no_mangle]
unsafe extern "C" fn begin_load(
    size_func: GdkPixbufModuleSizeFunc,
    prep_func: GdkPixbufModulePreparedFunc,
    update_func: GdkPixbufModuleUpdatedFunc,
    user_data: glib_sys::gpointer,
    error: *mut *mut GError,
) -> glib_sys::gpointer {
    if error != null_mut() {
        *error = null_mut();
    }

    let stream = MemoryInputStream::new();
    let ctx = Box::new(SvgContext {
        size_func,
        prep_func,
        update_func,
        user_data,
        stream,
    });

    Box::into_raw(ctx) as glib_sys::gpointer
}

#[no_mangle]
unsafe extern "C" fn load_increment(
    user_data: glib_sys::gpointer,
    buffer: *const u8,
    size: c_uint,
    error: *mut *mut GError,
) -> gboolean {
    if error != null_mut() {
        *error = null_mut();
    }

    let ctx = user_data as *mut SvgContext;

    let data = std::slice::from_raw_parts(buffer, size as usize);
    (&*ctx).stream.add_bytes(&Bytes::from(data));
    true.into_glib()
}

#[no_mangle]
unsafe extern "C" fn stop_load(user_data: glib_sys::gpointer, error: *mut *mut GError) -> gboolean {
    let ctx = Box::from_raw(user_data as *mut SvgContext);
    if error != null_mut() {
        *error = null_mut();
    }

    fn _inner_stop_load(ctx: &Box<SvgContext>) -> Result<gdk_pixbuf::Pixbuf, String> {
        let handle = Loader::new()
            .read_stream::<_, gio::File, gio::Cancellable>(&ctx.stream, None, None)
            .map_err(|e| e.to_string())?;

        let renderer = librsvg::CairoRenderer::new(&handle);
        let (w, h) = renderer.legacy_document_size().map_err(|e| e.to_string())?;
        let mut w = w.ceil() as c_int;
        let mut h = h.ceil() as c_int;

        if let Some(size_func) = ctx.size_func {
            let mut tmp_w: c_int = w;
            let mut tmp_h: c_int = h;
            unsafe {
                size_func(
                    &mut tmp_w as *mut c_int,
                    &mut tmp_h as *mut c_int,
                    ctx.user_data,
                )
            };
            if tmp_w != 0 && tmp_h != 0 {
                w = tmp_w;
                h = tmp_h;
            }
        }

        let pb = librsvg::c_api::pixbuf_utils::render_to_pixbuf_at_size(
            &renderer, w as f64, h as f64, w as f64, h as f64,
        )
        .map_err(|e| e.to_string())?;

        Ok(pb)
    }

    let pixbuf = match _inner_stop_load(&ctx) {
        Ok(r) => r,
        Err(e) => {
            if error != null_mut() {
                let gerr = glib::Error::new(gdk_pixbuf::PixbufError::Failed, &e);
                *error = gerr.to_glib_full() as *mut GError;
            }
            return false.into_glib();
        }
    };

    let w = pixbuf.width();
    let h = pixbuf.height();
    let pixbuf: *mut GdkPixbuf = pixbuf.to_glib_full();

    if let Some(prep_func) = ctx.prep_func {
        prep_func(pixbuf, null_mut(), ctx.user_data);
    }
    if let Some(update_func) = ctx.update_func {
        update_func(pixbuf, 0, 0, w, h, ctx.user_data);
    }

    // The module loader increases a ref so we drop the pixbuf here
    gobject_sys::g_object_unref(pixbuf as *mut GObject);

    true.into_glib()
}

#[no_mangle]
extern "C" fn fill_vtable(module: &mut GdkPixbufModule) {
    module.begin_load = Some(begin_load);
    module.stop_load = Some(stop_load);
    module.load_increment = Some(load_increment);
}

const SIGNATURE: [GdkPixbufModulePattern; 3] = [
    GdkPixbufModulePattern {
        prefix: cstr!(" <svg").as_ptr() as *mut c_char,
        mask: cstr!("*    ").as_ptr() as *mut c_char,
        relevance: 100,
    },
    GdkPixbufModulePattern {
        prefix: cstr!(" <!DOCTYPE svg").as_ptr() as *mut c_char,
        mask: cstr!("*             ").as_ptr() as *mut c_char,
        relevance: 100,
    },
    GdkPixbufModulePattern {
        prefix: std::ptr::null_mut(),
        mask: std::ptr::null_mut(),
        relevance: 0,
    },
];

const MIME_TYPES: [*const c_char; 7] = [
    cstr!("image/svg+xml").as_ptr(),
    cstr!("image/svg").as_ptr(),
    cstr!("image/svg-xml").as_ptr(),
    cstr!("image/vnd.adobe.svg+xml").as_ptr(),
    cstr!("text/xml-svg").as_ptr(),
    cstr!("image/svg+xml-compressed").as_ptr(),
    std::ptr::null(),
];

const EXTENSIONS: [*const c_char; 4] = [
    cstr!("svg").as_ptr(),
    cstr!("svgz").as_ptr(),
    cstr!("svg.gz").as_ptr(),
    std::ptr::null(),
];

#[no_mangle]
extern "C" fn fill_info(info: &mut GdkPixbufFormat) {
    info.name = cstr!("svg").as_ptr() as *mut c_char;
    info.signature = SIGNATURE.as_ptr() as *mut GdkPixbufModulePattern;
    info.description = cstr!("Scalable Vector Graphics").as_ptr() as *mut c_char; //TODO: Gettext this
    info.mime_types = MIME_TYPES.as_ptr() as *mut *mut c_char;
    info.extensions = EXTENSIONS.as_ptr() as *mut *mut c_char;
    info.flags = GDK_PIXBUF_FORMAT_SCALABLE | GDK_PIXBUF_FORMAT_THREADSAFE;
    info.license = cstr!("LGPL").as_ptr() as *mut c_char;
}

#[cfg(test)]
mod tests {
    use gdk_pixbuf_sys::{
        GdkPixbufFormat, GDK_PIXBUF_FORMAT_SCALABLE, GDK_PIXBUF_FORMAT_THREADSAFE,
    };
    use glib::translate::IntoGlib;

    use crate::{EXTENSIONS, MIME_TYPES};
    use libc::c_char;
    use std::ptr::{null, null_mut};

    fn pb_format_new() -> GdkPixbufFormat {
        let mut info = super::GdkPixbufFormat {
            name: null_mut(),
            signature: null_mut(),
            description: null_mut(),
            mime_types: null_mut(),
            extensions: null_mut(),
            flags: 0,
            license: null_mut(),
            disabled: false.into_glib(),
            domain: null_mut(),
        };

        super::fill_info(&mut info);
        info
    }

    #[test]
    fn fill_info() {
        let info = pb_format_new();

        assert_ne!(info.name, null_mut());
        assert_ne!(info.signature, null_mut());
        assert_ne!(info.description, null_mut());
        assert_ne!(info.mime_types, null_mut());
        assert_ne!(info.extensions, null_mut());
        assert_eq!(
            info.flags,
            GDK_PIXBUF_FORMAT_SCALABLE | GDK_PIXBUF_FORMAT_THREADSAFE
        );
        assert_ne!(info.license, null_mut());
    }

    fn check_null_terminated_arr_cstrings(arr: &[*const c_char]) {
        let n_strings = arr
            .iter()
            .filter(|e| e != &&null())
            .map(|e| {
                if e != &null() {
                    // We use strlen in all of them to ensure some safety
                    // We could use CStr instead but it'd be a bit more cumbersome
                    assert!(unsafe { libc::strlen(*e as *const c_char) } > 0)
                }
            })
            .count(); // Count all non_null items

        // Ensure last item is null and is the only null item
        assert_eq!(n_strings, arr.len() - 1);
        assert!(arr.last().unwrap() == &null());
    }

    #[test]
    fn extensions_bounds() {
        check_null_terminated_arr_cstrings(&EXTENSIONS);
    }

    #[test]
    fn mime_bounds() {
        check_null_terminated_arr_cstrings(&MIME_TYPES)
    }

    #[test]
    fn signature() {
        let info = pb_format_new();

        for i in 0..2 {
            let ptr = unsafe { info.signature.offset(i) };
            if i == 2 {
                assert_eq!(unsafe { (*ptr).prefix }, null_mut());
                continue;
            } else {
                assert_ne!(unsafe { (*ptr).prefix }, null_mut());
                if unsafe { (*ptr).mask } != null_mut() {
                    // Mask can be null
                    assert_eq!(
                        unsafe { libc::strlen((*ptr).prefix as *mut c_char) },
                        unsafe { libc::strlen((*ptr).mask as *mut c_char) }
                    );
                }
                // Relevance must be 0 to 100
                assert!(unsafe { (*ptr).relevance } >= 0);
                assert!(unsafe { (*ptr).relevance } <= 100);
            }
        }
    }

    const SVG_DATA: &'static str = r#"<?xml version="1.0" encoding="UTF-8" standalone="no"?>
                                    <svg
                                    width="100px"
                                    height="150px"
                                    viewBox="0 0 26.458333 26.458333"
                                    version="1.1"
                                    id="svg5"
                                    xmlns="http://www.w3.org/2000/svg"
                                    xmlns:svg="http://www.w3.org/2000/svg">
                                    <rect
                                        style="fill:#aa1144;stroke-width:0.0344347"
                                        width="26.458332"
                                        height="39.6875"
                                        x="4.691162e-07"
                                        y="-6.6145835"
                                        id="rect2" />
                                    </svg>
    "#;

    #[test]
    fn minimal_svg() {
        unsafe extern "C" fn prep_cb(
            pb: *mut gdk_pixbuf_sys::GdkPixbuf,
            pba: *mut gdk_pixbuf_sys::GdkPixbufAnimation,
            user_data: *mut libc::c_void,
        ) {
            assert_eq!(user_data, null_mut());
            assert_eq!(pba, null_mut());

            let w = gdk_pixbuf_sys::gdk_pixbuf_get_width(pb);
            let h = gdk_pixbuf_sys::gdk_pixbuf_get_height(pb);
            let stride = gdk_pixbuf_sys::gdk_pixbuf_get_rowstride(pb);
            assert_eq!(w, 100);
            assert_eq!(h, 150);

            let pixels = gdk_pixbuf_sys::gdk_pixbuf_get_pixels(pb);

            // Upper left pixel #aa1144ff
            assert_eq!(*pixels, 0xaa);
            assert_eq!(*pixels.offset(1), 0x11);
            assert_eq!(*pixels.offset(2), 0x44);
            assert_eq!(*pixels.offset(3), 0xff);

            // Bottom left pixel
            assert_eq!(*pixels.offset((stride * (h - 1)) as isize), 0xaa);
            assert_eq!(*pixels.offset((stride * (h - 1)) as isize + 1), 0x11);
            assert_eq!(*pixels.offset((stride * (h - 1)) as isize + 2), 0x44);
            assert_eq!(*pixels.offset((stride * (h - 1)) as isize + 3), 0xff);

            // Bottom right pixel
            assert_eq!(
                *pixels.offset((stride * (h - 1)) as isize + (w as isize - 1) * 4),
                0xaa
            );
            assert_eq!(
                *pixels.offset((stride * (h - 1)) as isize + (w as isize - 1) * 4 + 1),
                0x11
            );
            assert_eq!(
                *pixels.offset((stride * (h - 1)) as isize + (w as isize - 1) * 4 + 2),
                0x44
            );
            assert_eq!(
                *pixels.offset((stride * (h - 1)) as isize + (w as isize - 1) * 4 + 3),
                0xff
            );
        }

        let ctx = unsafe { crate::begin_load(None, Some(prep_cb), None, null_mut(), null_mut()) };
        assert_ne!(ctx, null_mut());

        let inc = unsafe {
            crate::load_increment(ctx, SVG_DATA.as_ptr(), SVG_DATA.len() as u32, null_mut())
        };
        assert_ne!(inc, 0);

        unsafe { crate::stop_load(ctx, null_mut()) };
    }
}
<|endoftext|>


<commit_before>//
//  SOS: the Stupid Operating System
//  by Eliza Weisman (hi@hawkweisman.me)
//
//  Copyright (c) 2015-2017 Eliza Weisman
//  Released under the terms of the MIT license. See `LICENSE` in the root
//  directory of this repository for more information.
//
use super::{ElfResult, ElfWord, Section, section};

use core::{fmt, mem};

pub trait Header {
    type Word: ElfWord;

    /// Attempt to extract an ELF file header from a slice of bytes.
    /// TODO: should this be a From impl maybe?
    //          - eliza, 03/08/2017
    fn from_slice<'a>(input: &'a [u8]) -> ElfResult<&'a Self>;

    /// Attempt to extract a section header from a slice of bytes.
    /// TODO: can/should the index be `usize`?
    //          - eliza, 03/08/2017
    fn parse_section<'a>(&'a self, input: &'a [u8], idx: u16)
                            -> ElfResult<&'a Section>;

    // Field accessors -------------------------------------------
    fn ident(&self) -> Ident;
    fn get_type(&self) -> Type;
    fn machine(&self) -> Machine;
    /// Offset of the program entry point
    fn entry_point(&self) -> usize;
    /// Offset of the start of program headers
    fn ph_offset(&self) -> usize;
    /// Offset of the start of [section header]s.
    ///
    /// [section header]: ../section/struct.Header.html
    fn sh_offset(&self) -> usize;
    /// TODO: can this return the flags type?
    //          - eliza, 03/08/2017
    fn flags(&self) -> u32;
    fn sh_str_idx(&self) -> usize;

}

/// An ELF file header
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)]
pub struct HeaderRepr<W: ElfWord> {
    /// the ELF [file identifier](struct.Ident.html)
    pub ident: Ident
  , elftype: TypeRepr
  , pub machine: Machine
  , /// Program entry point
    /// TODO: getters for turning these into `usize`?
    //          - eliza, 03/08/2017
    entry_point: W
  , /// Offset for start of program headers
    ph_offset: W
  , /// Offset for start of [section header]s.
    /// [section header]: ../section/struct.Header.html
    sh_offset: W
  , pub flags: u32
  , pub header_size: u16
  , pub ph_entry_size: u16
  , pub ph_count: u16
  , pub sh_entry_size: u16
  , pub sh_count: u16
  , /// Index of the section header string table
    sh_str_idx: u16
}

/// FIXME(style): generate more stuff with macros/use `macro_attr` to derive
///               these...
//                  - eliza, 03/08/2017
macro_rules! impl_getters {
    ($(#[$attr:meta])* pub fn $name:ident(&self) -> $ty:ident; $($rest:tt)*) => {
        $(#[$attr])* #[inline] pub fn $name(&self) -> $ty { self.$name as $ty }
        impl_getters!{ $( $rest )* }
    };
    ($(#[$attr:meta])* fn $name:ident(&self) -> $ty:ident; $($rest:tt)*) => {
        $(#[$attr])* #[inline] fn $name(&self) -> $ty { self.$name as $ty }
        impl_getters!{ $( $rest )* }
    };
    ( $(#[$attr:meta])* pub fn $name: ident (&self)-> $ty: ident; ) => {
        $(#[$attr])* #[inline] pub fn $name(&self) -> $ty { self.$name as $ty }
    };
    ( $(#[$attr:meta])* fn $name: ident (&self)-> $ty: ident; ) => {
        $(#[$attr])* #[inline] fn $name(&self) -> $ty { self.$name as $ty }
    };
    () => {};
}

impl Header for HeaderRepr<u64> {
    type Word = u64;

    /// Attempt to extract an ELF file header from a slice of bytes.
    /// TODO: can this also be macro-generated?
    ///         - eliza, 03/08/2017
    fn from_slice<'a>(input: &'a [u8]) -> ElfResult<&'a Self> {
        if input.len() < mem::size_of::<Self>() {
            Err("Input too short to extract ELF header")
        } else {
            unsafe { Ok(&super::extract_from_slice::<Self>(input, 0, 1)[0]) }
        }
    }

    /// Attempt to extract a section header from a slice of bytes.
    /// TODO: can this also be macro-generated?
    ///         - eliza, 03/08/2017
    fn parse_section<'a>(&'a self, input: &'a [u8], idx: u16)
                            -> ElfResult<&'a Section>
    {
        if idx < section::SHN_LORESERVE {
            Err("Cannot parse reserved section.")
        } else {
            let start: u64// start offset for section
                = self.sh_offset + idx as u64 * self.sh_entry_size as u64;
            let end: u64 // end offset for section
                = start + self.sh_entry_size as u64;
            let raw
                = &input[start as usize .. end as usize];

            match self.ident.class {
                Class::None => Err("Invalid ELF class (ELFCLASSNONE).")
              , Class::Elf32 => Err("Cannot parse 32-bit section from 64-bit \
                                     ELF file.")
              , Class::Elf64 => unsafe {
                    Ok(&*(raw as *const [u8] as *const u8 as *const Section))
                }
            }
        }
    }

    #[inline] fn get_type(&self) -> Type { self.elftype.as_type() }

    impl_getters! {
        #[doc = "Index for the start of [section header]s. \
                 [section header]: ../section/struct.Header.html"]
        fn sh_offset(&self) -> usize;
        #[doc = "Index for the start of program headers"]
        fn ph_offset(&self) -> usize;
        #[doc = "Index for the program entry point"]
        fn entry_point(&self) -> usize;
        #[doc = "Index of the section header [string table] \
                 [string table]: ../section/struct.StrTable.html"]
        fn sh_str_idx(&self) -> usize;
        fn flags(&self) -> u32;
        fn ident(&self) -> Ident;
        fn machine(&self) -> Machine;
    }

}

impl Header for HeaderRepr<u32> {

    type Word = u32;
    /// Attempt to extract an ELF file header from a slice of bytes.
    fn from_slice<'a>(input: &'a [u8]) -> ElfResult<&'a Self> {
        if input.len() < mem::size_of::<Self>() {
            Err("Input too short to extract ELF header")
        } else {
            unsafe { Ok(&super::extract_from_slice::<Self>(input, 0, 1)[0]) }
        }
    }

    /// Attempt to extract a section header from a slice of bytes.
    fn parse_section<'a>(&'a self, input: &'a [u8], idx: u16)
                            -> ElfResult<&'a Section>
    {
        if idx < section::SHN_LORESERVE {
            Err("Cannot parse reserved section.")
        } else {
            let start: u32// start offset for section
                = self.sh_offset + idx as u32 * self.sh_entry_size as u32;
            let end: u32 // end offset for section
                = start + self.sh_entry_size as u32;
            let raw
                = &input[start as usize .. end as usize];

            match self.ident.class {
                Class::None => Err("Invalid ELF class (ELFCLASSNONE).")
              , Class::Elf32 => unsafe {
                    Ok(&*(raw as *const [u8] as *const u8 as *const Section))
                }
              , Class::Elf64 => Err("Cannot parse 64-bit section from 32-bit \
                                     ELF file.")
            }
        }
    }

    #[inline] fn get_type(&self) -> Type { self.elftype.as_type() }

    impl_getters! {
        #[doc = "Index for the start of [section header]s. \
                 [section header]: ../section/struct.Header.html"]
        fn sh_offset(&self) -> usize;
        #[doc = "Index for the start of program headers"]
        fn ph_offset(&self) -> usize;
        #[doc = "Index for the program entry point"]
        fn entry_point(&self) -> usize;
        #[doc = "Index of the section header [string table] \
                 [string table]: ../section/struct.StrTable.html"]
        fn sh_str_idx(&self) -> usize;
        fn flags(&self) -> u32;
        fn ident(&self) -> Ident;
        fn machine(&self) -> Machine;
    }

}

/// ELF header magic
pub const MAGIC: Magic = [0x7f, b'E', b'L', b'F'];

/// Type of header magic
pub type Magic = [u8; 4];

/// ELF identifier (`e_ident` in the ELF standard)
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)]
pub struct Ident {
    /// ELF magic numbers. Must be equal to the [ELF magic], `[0x7, E, L, F]`.
    ///
    /// [ELF magic]: constant.MAGIC.html
    pub magic: Magic
  , /// ELF [file class](enum.Class.html) (32- or 64-bit)
    pub class: Class
  , /// ELF [data encoding](enum.DataEncoding.html) (big- or little-endian)
    pub encoding: DataEncoding
  , /// ELF file [version](enum.Version.html)
    pub version: Version
  , /// What [operating system ABI] this file was compiled for.
    ///
    /// [operating system ABI]: enum.OsAbi.html
    pub abi: OsAbi
  , /// ABI version (often this is just padding)
    pub abi_version: u8
  , _padding: [u8; 7]
}

impl Ident {
    #[inline] pub fn check_magic(&self) -> bool { self.magic == MAGIC }

    /// Returns true if the identifier section identifies a valid ELF file.
    #[inline] pub fn is_valid(&self) -> bool {
        // the ELF magic number is correct
        self.check_magic() &&
        // the file class is either 32- or 64-bits
        self.class.is_valid() &&
        // the data encoding is either big- or little-endian
        self.encoding.is_valid()
    }
}

/// Identifies the class of the ELF file
#[derive(Copy, Clone, PartialEq, Debug)]
#[repr(u8)]
pub enum Class {
    /// Invalid ELF class file (`ELFCLASSNONE` in the standard)
    None  = 0
  , /// 32-bit ELF file (`ELFCLASS32` in the standard)
    Elf32 = 1
  , /// 64-bit ELF file (`ELFCLASS64` in the standard)
    Elf64 = 2
}

impl Class {
    /// Returns true if the class field for this file is valid.
    #[inline]
    pub fn is_valid(&self) -> bool {
        match *self { Class::None => false
                    , _ => true
                    }
    }
}

/// Identifies the data encoding of the ELF file
#[derive(Copy, Clone, PartialEq, Debug)]
#[repr(u8)]
pub enum DataEncoding {
    /// Invalid data encoding (`ELFDATANONE` in the standard)
    None  = 0
  , /// Twos-complement little-endian data encoding
    /// (`ELFDATA2LSB` in the standard)
    LittleEndian = 1
  , /// Twos-complement big-endian data encoding
    /// (`ELFDATA2MSB` in the standard)
    BigEndian = 2
}

impl DataEncoding {
    /// Returns true if the data encoding field for this file is valid.
    #[inline]
    pub fn is_valid(&self) -> bool {
        match *self { DataEncoding::None => false
                    , _ => true
                    }
    }
}

/// Operating system ABI
#[derive(Copy, Clone, Debug)]
#[repr(u8)]
pub enum OsAbi { /// Ox00 also represents "none"
                 SystemV = 0x00
               , HpUx    = 0x01
               , NetBsd  = 0x02
               , Linux   = 0x03
               , Solaris = 0x06
               , Aix     = 0x07
               , Irix    = 0x08
               , FreeBsd = 0x09
               , OpenBsd = 0x0C
               , OpenVms = 0x0D
               }

/// Identifies the version of the ELF file
#[derive(Copy, Clone, PartialEq, Debug)]
#[repr(u8)]
pub enum Version { None = 0, Current = 1 }

#[derive(Clone, Copy, PartialEq)]
struct TypeRepr(u16);

impl TypeRepr {
    pub fn as_type(&self) -> Type {
        match self.0 {
            0 => Type::None
          , 1 => Type::Relocatable
          , 2 => Type::Executable
          , 3 => Type::SharedObject
          , 4 => Type::Core
          , anything => Type::Other(anything)
        }
    }
}

impl fmt::Debug for TypeRepr {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.as_type().fmt(f)
    }
}

#[derive(Clone, Copy, PartialEq, Debug)]
pub enum Type { None
              , Relocatable
              , Executable
              , SharedObject
              , Core
              , Other(u16)
              }

#[allow(non_camel_case_types)]
#[derive(Clone, Copy, PartialEq, Debug)]
#[repr(u16)]
pub enum Machine { None    = 0
                 , Sparc   = 0x02
                 , X86     = 0x03
                 , Mips    = 0x08
                 , PowerPc = 0x14
                 , Arm     = 0x28
                 , SuperH  = 0x2A
                 , Ia64    = 0x32
                 , X86_64  = 0x3E
                 , AArch64 = 0xB7
                 }
<commit_msg>feat(elf): add getters for more file header fields<commit_after>//
//  SOS: the Stupid Operating System
//  by Eliza Weisman (hi@hawkweisman.me)
//
//  Copyright (c) 2015-2017 Eliza Weisman
//  Released under the terms of the MIT license. See `LICENSE` in the root
//  directory of this repository for more information.
//
use super::{ElfResult, ElfWord, Section, section};

use core::{fmt, mem};

pub trait Header {
    type Word: ElfWord;

    /// Attempt to extract an ELF file header from a slice of bytes.
    /// TODO: should this be a From impl maybe?
    //          - eliza, 03/08/2017
    fn from_slice<'a>(input: &'a [u8]) -> ElfResult<&'a Self>;

    /// Attempt to extract a section header from a slice of bytes.
    /// TODO: can/should the index be `usize`?
    //          - eliza, 03/08/2017
    fn parse_section<'a>(&'a self, input: &'a [u8], idx: u16)
                            -> ElfResult<&'a Section>;

    // Field accessors -------------------------------------------
    fn ident(&self) -> Ident;
    fn get_type(&self) -> Type;
    fn machine(&self) -> Machine;
    /// Offset of the program entry point
    fn entry_point(&self) -> usize;
    /// Offset of the start of program headers
    fn ph_offset(&self) -> usize;
    /// Number of program headers.
    fn ph_count(&self) -> usize;
    /// Size of a program header.
    /// TODO: add function for using this and `ph_count` to make a memrange
    ///       representing the program header entries?
    //          - eliza, 03/08/2017
    fn ph_entry_size(&self) -> usize;
    /// Offset of the start of [section header]s.
    ///
    /// [section header]: ../section/struct.Header.html
    fn sh_offset(&self) -> usize;
    /// Number of [section header]s.
    ///
    /// [section header]: ../section/struct.Header.html
    fn sh_count(&self) -> usize;
    /// Size of a [section header].
    ///
    /// [section header]: ../section/struct.Header.html
    /// TODO: add function for using this and `sh_count` to make a memrange
    ///       representing the section header entries?
    //          - eliza, 03/08/2017
    fn sh_entry_size(&self) -> usize;
    /// TODO: can this return the flags type?
    //          - eliza, 03/08/2017
    fn flags(&self) -> u32;
    /// Index of the section header [string table].
    ///
    /// [string table]: ../section/struct.StrTable.html"]
    fn sh_str_idx(&self) -> usize;

}

/// An ELF file header
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)]
pub struct HeaderRepr<W: ElfWord> {
    /// the ELF [file identifier](struct.Ident.html)
    pub ident: Ident
  , elftype: TypeRepr
  , pub machine: Machine
  , /// Program entry point
    /// TODO: getters for turning these into `usize`?
    //          - eliza, 03/08/2017
    entry_point: W
  , /// Offset for start of program headers
    ph_offset: W
  , /// Offset for start of [section header]s.
    /// [section header]: ../section/struct.Header.html
    sh_offset: W
  , pub flags: u32
  , pub header_size: u16
  , pub ph_entry_size: u16
  , pub ph_count: u16
  , pub sh_entry_size: u16
  , pub sh_count: u16
  , /// Index of the section header string table
    sh_str_idx: u16
}

/// FIXME(style): generate more stuff with macros/use `macro_attr` to derive
///               these...
//                  - eliza, 03/08/2017
macro_rules! impl_getters {
    ($(#[$attr:meta])* pub fn $name:ident(&self) -> $ty:ident; $($rest:tt)*) => {
        $(#[$attr])* #[inline] pub fn $name(&self) -> $ty { self.$name as $ty }
        impl_getters!{ $( $rest )* }
    };
    ($(#[$attr:meta])* fn $name:ident(&self) -> $ty:ident; $($rest:tt)*) => {
        $(#[$attr])* #[inline] fn $name(&self) -> $ty { self.$name as $ty }
        impl_getters!{ $( $rest )* }
    };
    ( $(#[$attr:meta])* pub fn $name: ident (&self)-> $ty: ident; ) => {
        $(#[$attr])* #[inline] pub fn $name(&self) -> $ty { self.$name as $ty }
    };
    ( $(#[$attr:meta])* fn $name: ident (&self)-> $ty: ident; ) => {
        $(#[$attr])* #[inline] fn $name(&self) -> $ty { self.$name as $ty }
    };
    () => {};
}

impl Header for HeaderRepr<u64> {
    type Word = u64;

    /// Attempt to extract an ELF file header from a slice of bytes.
    /// TODO: can this also be macro-generated?
    ///         - eliza, 03/08/2017
    fn from_slice<'a>(input: &'a [u8]) -> ElfResult<&'a Self> {
        if input.len() < mem::size_of::<Self>() {
            Err("Input too short to extract ELF header")
        } else {
            unsafe { Ok(&super::extract_from_slice::<Self>(input, 0, 1)[0]) }
        }
    }

    /// Attempt to extract a section header from a slice of bytes.
    /// TODO: can this also be macro-generated?
    ///         - eliza, 03/08/2017
    fn parse_section<'a>(&'a self, input: &'a [u8], idx: u16)
                            -> ElfResult<&'a Section>
    {
        if idx < section::SHN_LORESERVE {
            Err("Cannot parse reserved section.")
        } else {
            let start: u64// start offset for section
                = self.sh_offset + idx as u64 * self.sh_entry_size as u64;
            let end: u64 // end offset for section
                = start + self.sh_entry_size as u64;
            let raw
                = &input[start as usize .. end as usize];

            match self.ident.class {
                Class::None => Err("Invalid ELF class (ELFCLASSNONE).")
              , Class::Elf32 => Err("Cannot parse 32-bit section from 64-bit \
                                     ELF file.")
              , Class::Elf64 => unsafe {
                    Ok(&*(raw as *const [u8] as *const u8 as *const Section))
                }
            }
        }
    }

    #[inline] fn get_type(&self) -> Type { self.elftype.as_type() }

    impl_getters! {
        #[doc = "Index for the start of [section header]s. \
                 [section header]: ../section/struct.Header.html"]
        fn sh_offset(&self) -> usize;
        fn sh_entry_size(&self) -> usize;
        fn sh_count(&self) -> usize;

        #[doc = "Index for the start of program headers"]
        fn ph_offset(&self) -> usize;
        fn ph_entry_size(&self) -> usize;
        fn ph_count(&self) -> usize;

        #[doc = "Index for the program entry point"]
        fn entry_point(&self) -> usize;
        #[doc = "Index of the section header [string table] \
                 [string table]: ../section/struct.StrTable.html"]
        fn sh_str_idx(&self) -> usize;
        fn flags(&self) -> u32;
        fn ident(&self) -> Ident;
        fn machine(&self) -> Machine;
    }

}

impl Header for HeaderRepr<u32> {

    type Word = u32;
    /// Attempt to extract an ELF file header from a slice of bytes.
    fn from_slice<'a>(input: &'a [u8]) -> ElfResult<&'a Self> {
        if input.len() < mem::size_of::<Self>() {
            Err("Input too short to extract ELF header")
        } else {
            unsafe { Ok(&super::extract_from_slice::<Self>(input, 0, 1)[0]) }
        }
    }

    /// Attempt to extract a section header from a slice of bytes.
    fn parse_section<'a>(&'a self, input: &'a [u8], idx: u16)
                            -> ElfResult<&'a Section>
    {
        if idx < section::SHN_LORESERVE {
            Err("Cannot parse reserved section.")
        } else {
            let start: u32// start offset for section
                = self.sh_offset + idx as u32 * self.sh_entry_size as u32;
            let end: u32 // end offset for section
                = start + self.sh_entry_size as u32;
            let raw
                = &input[start as usize .. end as usize];

            match self.ident.class {
                Class::None => Err("Invalid ELF class (ELFCLASSNONE).")
              , Class::Elf32 => unsafe {
                    Ok(&*(raw as *const [u8] as *const u8 as *const Section))
                }
              , Class::Elf64 => Err("Cannot parse 64-bit section from 32-bit \
                                     ELF file.")
            }
        }
    }

    #[inline] fn get_type(&self) -> Type { self.elftype.as_type() }

    impl_getters! {
        #[doc = "Index for the start of [section header]s. \
                 [section header]: ../section/struct.Header.html"]
        fn sh_offset(&self) -> usize;
        fn sh_entry_size(&self) -> usize;
        fn sh_count(&self) -> usize;

        #[doc = "Index for the start of program headers"]
        fn ph_offset(&self) -> usize;
        fn ph_entry_size(&self) -> usize;
        fn ph_count(&self) -> usize;

        #[doc = "Index for the program entry point"]
        fn entry_point(&self) -> usize;
        #[doc = "Index of the section header [string table] \
                 [string table]: ../section/struct.StrTable.html"]
        fn sh_str_idx(&self) -> usize;
        fn flags(&self) -> u32;
        fn ident(&self) -> Ident;
        fn machine(&self) -> Machine;
    }

}

/// ELF header magic
pub const MAGIC: Magic = [0x7f, b'E', b'L', b'F'];

/// Type of header magic
pub type Magic = [u8; 4];

/// ELF identifier (`e_ident` in the ELF standard)
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)]
pub struct Ident {
    /// ELF magic numbers. Must be equal to the [ELF magic], `[0x7, E, L, F]`.
    ///
    /// [ELF magic]: constant.MAGIC.html
    pub magic: Magic
  , /// ELF [file class](enum.Class.html) (32- or 64-bit)
    pub class: Class
  , /// ELF [data encoding](enum.DataEncoding.html) (big- or little-endian)
    pub encoding: DataEncoding
  , /// ELF file [version](enum.Version.html)
    pub version: Version
  , /// What [operating system ABI] this file was compiled for.
    ///
    /// [operating system ABI]: enum.OsAbi.html
    pub abi: OsAbi
  , /// ABI version (often this is just padding)
    pub abi_version: u8
  , _padding: [u8; 7]
}

impl Ident {
    #[inline] pub fn check_magic(&self) -> bool { self.magic == MAGIC }

    /// Returns true if the identifier section identifies a valid ELF file.
    #[inline] pub fn is_valid(&self) -> bool {
        // the ELF magic number is correct
        self.check_magic() &&
        // the file class is either 32- or 64-bits
        self.class.is_valid() &&
        // the data encoding is either big- or little-endian
        self.encoding.is_valid()
    }
}

/// Identifies the class of the ELF file
#[derive(Copy, Clone, PartialEq, Debug)]
#[repr(u8)]
pub enum Class {
    /// Invalid ELF class file (`ELFCLASSNONE` in the standard)
    None  = 0
  , /// 32-bit ELF file (`ELFCLASS32` in the standard)
    Elf32 = 1
  , /// 64-bit ELF file (`ELFCLASS64` in the standard)
    Elf64 = 2
}

impl Class {
    /// Returns true if the class field for this file is valid.
    #[inline]
    pub fn is_valid(&self) -> bool {
        match *self { Class::None => false
                    , _ => true
                    }
    }
}

/// Identifies the data encoding of the ELF file
#[derive(Copy, Clone, PartialEq, Debug)]
#[repr(u8)]
pub enum DataEncoding {
    /// Invalid data encoding (`ELFDATANONE` in the standard)
    None  = 0
  , /// Twos-complement little-endian data encoding
    /// (`ELFDATA2LSB` in the standard)
    LittleEndian = 1
  , /// Twos-complement big-endian data encoding
    /// (`ELFDATA2MSB` in the standard)
    BigEndian = 2
}

impl DataEncoding {
    /// Returns true if the data encoding field for this file is valid.
    #[inline]
    pub fn is_valid(&self) -> bool {
        match *self { DataEncoding::None => false
                    , _ => true
                    }
    }
}

/// Operating system ABI
#[derive(Copy, Clone, Debug)]
#[repr(u8)]
pub enum OsAbi { /// Ox00 also represents "none"
                 SystemV = 0x00
               , HpUx    = 0x01
               , NetBsd  = 0x02
               , Linux   = 0x03
               , Solaris = 0x06
               , Aix     = 0x07
               , Irix    = 0x08
               , FreeBsd = 0x09
               , OpenBsd = 0x0C
               , OpenVms = 0x0D
               }

/// Identifies the version of the ELF file
#[derive(Copy, Clone, PartialEq, Debug)]
#[repr(u8)]
pub enum Version { None = 0, Current = 1 }

#[derive(Clone, Copy, PartialEq)]
struct TypeRepr(u16);

impl TypeRepr {
    pub fn as_type(&self) -> Type {
        match self.0 {
            0 => Type::None
          , 1 => Type::Relocatable
          , 2 => Type::Executable
          , 3 => Type::SharedObject
          , 4 => Type::Core
          , anything => Type::Other(anything)
        }
    }
}

impl fmt::Debug for TypeRepr {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.as_type().fmt(f)
    }
}

#[derive(Clone, Copy, PartialEq, Debug)]
pub enum Type { None
              , Relocatable
              , Executable
              , SharedObject
              , Core
              , Other(u16)
              }

#[allow(non_camel_case_types)]
#[derive(Clone, Copy, PartialEq, Debug)]
#[repr(u16)]
pub enum Machine { None    = 0
                 , Sparc   = 0x02
                 , X86     = 0x03
                 , Mips    = 0x08
                 , PowerPc = 0x14
                 , Arm     = 0x28
                 , SuperH  = 0x2A
                 , Ia64    = 0x32
                 , X86_64  = 0x3E
                 , AArch64 = 0xB7
                 }
<|endoftext|>


<commit_before>use std::path::PathBuf;

use semver::Version;
use std::fmt::{Display, Debug, Formatter};
use std::fmt::Error as FmtError;
use std::result::Result as RResult;

use libimagerror::into::IntoError;

use error::StoreErrorKind as SEK;
use store::Result;
use store::Store;

/// The Index into the Store
#[derive(Debug, Clone, PartialEq, Hash, Eq, PartialOrd, Ord)]
pub struct StoreId {
    base: Option<PathBuf>,
    id:   PathBuf,
}

impl StoreId {

    pub fn new(base: Option<PathBuf>, id: PathBuf) -> Result<StoreId> {
        StoreId::new_baseless(id).map(|mut sid| { sid.base = base; sid })
    }

    pub fn new_baseless(id: PathBuf) -> Result<StoreId> {
        if id.is_absolute() {
            Err(SEK::StoreIdLocalPartAbsoluteError.into_error())
        } else {
            Ok(StoreId {
                base: None,
                id: id
            })
        }
    }

    pub fn without_base(mut self) -> StoreId {
        self.base = None;
        self
    }

    pub fn storified(self, store: &Store) -> StoreId {
        StoreId {
            base: Some(store.path().clone()),
            id: self.id
        }
    }

    pub fn exists(&self) -> bool {
        let pb : PathBuf = self.clone().into();
        pb.exists()
    }

    pub fn is_file(&self) -> bool {
        true
    }

    pub fn is_dir(&self) -> bool {
        false
    }

    pub fn to_str(&self) -> Result<String> {
        if self.base.is_some() {
            let mut base = self.base.as_ref().cloned().unwrap();
            base.push(self.id.clone());
            base
        } else {
            self.id.clone()
        }
        .to_str()
        .map(String::from)
        .ok_or(SEK::StoreIdHandlingError.into_error())
    }

}

impl Into<PathBuf> for StoreId {

    fn into(self) -> PathBuf {
        let mut base = self.base.unwrap_or(PathBuf::from("/"));
        base.push(self.id);
        base
    }

}

impl Display for StoreId {

    fn fmt(&self, fmt: &mut Formatter) -> RResult<(), FmtError> {
        match self.id.to_str() {
            Some(s) => write!(fmt, "{}", s),
            None    => write!(fmt, "{}", self.id.to_string_lossy()),
        }
    }

}

/// This Trait allows you to convert various representations to a single one
/// suitable for usage in the Store
pub trait IntoStoreId {
    fn into_storeid(self) -> StoreId;
}

impl IntoStoreId for StoreId {
    fn into_storeid(self) -> StoreId {
        self
    }
}

pub fn build_entry_path(store: &Store, path_elem: &str) -> Result<PathBuf> {
    debug!("Checking path element for version");
    if path_elem.split('~').last().map_or(false, |v| Version::parse(v).is_err()) {
        debug!("Version cannot be parsed from {:?}", path_elem);
        debug!("Path does not contain version!");
        return Err(SEK::StorePathLacksVersion.into());
    }
    debug!("Version checking succeeded");

    debug!("Building path from {:?}", path_elem);
    let mut path = store.path().clone();

    if path_elem.starts_with('/') {
        path.push(&path_elem[1..]);
    } else {
        path.push(path_elem);
    }

    Ok(path)
}

#[macro_export]
macro_rules! module_entry_path_mod {
    ($name:expr, $version:expr) => (
        #[deny(missing_docs,
                missing_copy_implementations,
                trivial_casts, trivial_numeric_casts,
                unsafe_code,
                unstable_features,
                unused_import_braces, unused_qualifications,
                unused_imports)]
        /// A helper module to create valid module entry paths
        pub mod module_path {
            use semver::Version;
            use std::convert::AsRef;
            use std::path::Path;
            use std::path::PathBuf;

            use $crate::storeid::StoreId;

            /// A Struct giving you the ability to choose store entries assigned
            /// to it.
            ///
            /// It is created through a call to `new`.
            pub struct ModuleEntryPath(PathBuf);

            impl ModuleEntryPath {
                /// Path has to be a valid UTF-8 string or this will panic!
                pub fn new<P: AsRef<Path>>(pa: P) -> ModuleEntryPath {
                    let mut path = PathBuf::new();
                    path.push(format!("{}", $name));
                    path.push(pa.as_ref().clone());
                    let version = Version::parse($version).unwrap();
                    let name = pa.as_ref().file_name().unwrap()
                        .to_str().unwrap();
                    path.set_file_name(format!("{}~{}",
                                               name,
                                               version));
                    ModuleEntryPath(path)
                }
            }

            impl $crate::storeid::IntoStoreId for ModuleEntryPath {
                fn into_storeid(self) -> $crate::storeid::StoreId {
                    StoreId::new(None, self.0)
                }
            }
        }
    )
}

pub struct StoreIdIterator {
    iter: Box<Iterator<Item = StoreId>>,
}

impl Debug for StoreIdIterator {

    fn fmt(&self, fmt: &mut Formatter) -> RResult<(), FmtError> {
        write!(fmt, "StoreIdIterator")
    }

}

impl StoreIdIterator {

    pub fn new(iter: Box<Iterator<Item = StoreId>>) -> StoreIdIterator {
        StoreIdIterator {
            iter: iter,
        }
    }

}

impl Iterator for StoreIdIterator {
    type Item = StoreId;

    fn next(&mut self) -> Option<StoreId> {
        self.iter.next()
    }

}

#[cfg(test)]
mod test {

    use storeid::IntoStoreId;

    module_entry_path_mod!("test", "0.2.0-alpha+leet1337");

    #[test]
    fn correct_path() {
        let p = module_path::ModuleEntryPath::new("test");

        assert_eq!(p.into_storeid().to_str().unwrap(), "test/test~0.2.0-alpha+leet1337");
    }

}
<commit_msg>Fix interface of IntoStoreId trait to return Result<StoreId> and impls<commit_after>use std::path::PathBuf;

use semver::Version;
use std::fmt::{Display, Debug, Formatter};
use std::fmt::Error as FmtError;
use std::result::Result as RResult;

use libimagerror::into::IntoError;

use error::StoreErrorKind as SEK;
use store::Result;
use store::Store;

/// The Index into the Store
#[derive(Debug, Clone, PartialEq, Hash, Eq, PartialOrd, Ord)]
pub struct StoreId {
    base: Option<PathBuf>,
    id:   PathBuf,
}

impl StoreId {

    pub fn new(base: Option<PathBuf>, id: PathBuf) -> Result<StoreId> {
        StoreId::new_baseless(id).map(|mut sid| { sid.base = base; sid })
    }

    pub fn new_baseless(id: PathBuf) -> Result<StoreId> {
        if id.is_absolute() {
            Err(SEK::StoreIdLocalPartAbsoluteError.into_error())
        } else {
            Ok(StoreId {
                base: None,
                id: id
            })
        }
    }

    pub fn without_base(mut self) -> StoreId {
        self.base = None;
        self
    }

    pub fn storified(self, store: &Store) -> StoreId {
        StoreId {
            base: Some(store.path().clone()),
            id: self.id
        }
    }

    pub fn exists(&self) -> bool {
        let pb : PathBuf = self.clone().into();
        pb.exists()
    }

    pub fn is_file(&self) -> bool {
        true
    }

    pub fn is_dir(&self) -> bool {
        false
    }

    pub fn to_str(&self) -> Result<String> {
        if self.base.is_some() {
            let mut base = self.base.as_ref().cloned().unwrap();
            base.push(self.id.clone());
            base
        } else {
            self.id.clone()
        }
        .to_str()
        .map(String::from)
        .ok_or(SEK::StoreIdHandlingError.into_error())
    }

}

impl Into<PathBuf> for StoreId {

    fn into(self) -> PathBuf {
        let mut base = self.base.unwrap_or(PathBuf::from("/"));
        base.push(self.id);
        base
    }

}

impl Display for StoreId {

    fn fmt(&self, fmt: &mut Formatter) -> RResult<(), FmtError> {
        match self.id.to_str() {
            Some(s) => write!(fmt, "{}", s),
            None    => write!(fmt, "{}", self.id.to_string_lossy()),
        }
    }

}

/// This Trait allows you to convert various representations to a single one
/// suitable for usage in the Store
pub trait IntoStoreId {
    fn into_storeid(self) -> Result<StoreId>;
}

impl IntoStoreId for StoreId {
    fn into_storeid(self) -> Result<StoreId> {
        Ok(self)
    }
}

pub fn build_entry_path(store: &Store, path_elem: &str) -> Result<PathBuf> {
    debug!("Checking path element for version");
    if path_elem.split('~').last().map_or(false, |v| Version::parse(v).is_err()) {
        debug!("Version cannot be parsed from {:?}", path_elem);
        debug!("Path does not contain version!");
        return Err(SEK::StorePathLacksVersion.into());
    }
    debug!("Version checking succeeded");

    debug!("Building path from {:?}", path_elem);
    let mut path = store.path().clone();

    if path_elem.starts_with('/') {
        path.push(&path_elem[1..]);
    } else {
        path.push(path_elem);
    }

    Ok(path)
}

#[macro_export]
macro_rules! module_entry_path_mod {
    ($name:expr, $version:expr) => (
        #[deny(missing_docs,
                missing_copy_implementations,
                trivial_casts, trivial_numeric_casts,
                unsafe_code,
                unstable_features,
                unused_import_braces, unused_qualifications,
                unused_imports)]
        /// A helper module to create valid module entry paths
        pub mod module_path {
            use semver::Version;
            use std::convert::AsRef;
            use std::path::Path;
            use std::path::PathBuf;

            use $crate::storeid::StoreId;
            use $crate::store::Result;

            /// A Struct giving you the ability to choose store entries assigned
            /// to it.
            ///
            /// It is created through a call to `new`.
            pub struct ModuleEntryPath(PathBuf);

            impl ModuleEntryPath {
                /// Path has to be a valid UTF-8 string or this will panic!
                pub fn new<P: AsRef<Path>>(pa: P) -> ModuleEntryPath {
                    let mut path = PathBuf::new();
                    path.push(format!("{}", $name));
                    path.push(pa.as_ref().clone());
                    let version = Version::parse($version).unwrap();
                    let name = pa.as_ref().file_name().unwrap()
                        .to_str().unwrap();
                    path.set_file_name(format!("{}~{}",
                                               name,
                                               version));
                    ModuleEntryPath(path)
                }
            }

            impl $crate::storeid::IntoStoreId for ModuleEntryPath {
                fn into_storeid(self) -> Result<$crate::storeid::StoreId> {
                    StoreId::new(None, self.0)
                }
            }
        }
    )
}

pub struct StoreIdIterator {
    iter: Box<Iterator<Item = StoreId>>,
}

impl Debug for StoreIdIterator {

    fn fmt(&self, fmt: &mut Formatter) -> RResult<(), FmtError> {
        write!(fmt, "StoreIdIterator")
    }

}

impl StoreIdIterator {

    pub fn new(iter: Box<Iterator<Item = StoreId>>) -> StoreIdIterator {
        StoreIdIterator {
            iter: iter,
        }
    }

}

impl Iterator for StoreIdIterator {
    type Item = StoreId;

    fn next(&mut self) -> Option<StoreId> {
        self.iter.next()
    }

}

#[cfg(test)]
mod test {

    use storeid::IntoStoreId;

    module_entry_path_mod!("test", "0.2.0-alpha+leet1337");

    #[test]
    fn correct_path() {
        let p = module_path::ModuleEntryPath::new("test");

        assert_eq!(p.into_storeid().to_str().unwrap(), "test/test~0.2.0-alpha+leet1337");
    }

}
<|endoftext|>


<commit_before>use clap::{App, Arg, AppSettings, Format, ArgMatches};
use std::io::{self, Write};
use std::process::exit;
use std::fs;
use crowbook::Book;

/// Prints an error on stderr and exit the program
pub fn print_error(s: &str) -> ! {
    writeln!(&mut io::stderr(), "{} {}", Format::Error("error:"), s).unwrap();
    exit(0);
}

/// sets the book options according to command line arguments
/// Also print these options to a string, so it can be used at
/// the creation of a book to check that parameters are OK and t
/// then print them to file
pub fn set_book_options(book: &mut Book, matches: &ArgMatches) -> String {
    let mut output = String::new();
    if let Some(iter) = matches.values_of("set") {
        let v:Vec<_> = iter.collect();
        if v.len() %2 != 0 {
            print_error("An odd number of arguments was passed to --set, but it takes a list of key value pairs.");
        }

        for i in 0..v.len()/2 {
            let key = v[i * 2];
            let value = v[i * 2 + 1];
            let res = book.set_option(key, value);
            if let Err(err) = res {
                print_error(&format!("Error in setting key {}: {}", key, err));
            }
            output.push_str(&format!("{}: {}\n", key, value));
        }
    }
    output
}

/// create a book file with the command line arguments
/// and exit the process at the end
pub fn create_book(matches: &ArgMatches) -> ! {
    if let Some(values) = matches.values_of("FILES") {
        let s = matches.value_of("BOOK").unwrap();
        if fs::metadata(s).is_ok() {
            print_error(&format!("Could not create file {}: it already exists!", s));
        }

        let mut f = fs::File::create(s).unwrap();

        if matches.is_present("set") {
            let mut book = Book::new();
            let s = set_book_options(&mut book, matches);
            f.write_all(&s.as_bytes()).unwrap();
        } else {
            f.write_all(b"author: Your name
title: Your title
lang: en

# Uncomment and fill to generate files
# output.html: some_file.html
# output.epub: some_file.epub
# output.pdf: some_file.pdf

# Uncomment and fill to set cover image (for Epub)
# cover: some_cover.png\n").unwrap();
        }
        f.write_all(b"\n# List of chapters\n").unwrap();
        for file in values {
            f.write_all(&format!("+ {}\n", file).as_bytes()).unwrap();
        }
        println!("Created {}, now you'll have to complete it!", s);
        exit(0);
    } else {
        print_error("--create must be used with a list of additonal files");
    }
}

pub fn create_matches<'a>() -> ArgMatches<'a> {
    let app = App::new("crowbook")
        .setting(AppSettings::ArgRequiredElseHelp)
        .setting(AppSettings::UnifiedHelpMessage)
        .version(env!("CARGO_PKG_VERSION"))
        .about("Render a markdown book in Epub, PDF or HTML.")
        .arg_from_usage("-v, --verbose 'Activate verbose mode'")
        .arg_from_usage("--create 'Creates a new book with existing markdown files.'")
        .arg(Arg::from_usage("-o, --output [FILE] 'Specifies output file.'")
             .requires("to"))
        .arg(Arg::from_usage("[FILES]... 'Files to list in book when using --create'")
             .index(2))
        .arg(Arg::from_usage("-t, --to [FORMAT] 'Generate specific format'")
             .possible_values(&["epub", "pdf", "html", "tex", "odt"]))
        .arg(Arg::from_usage("-s, --set [KEY_VALUES] 'Sets a list of book options'")
             .min_values(2))
        .arg(Arg::from_usage("-l --list-options 'Lists all possible options"))
        .arg_from_usage("--list-options-md 'List all options, formatted in Markdown")
        .arg(Arg::with_name("BOOK")
             .index(1)
//             .required(true)
             .help("File containing the book configuration."));

    let matches = app.get_matches();

    pre_check(&matches);

    matches
}

/// Pre-check the matches to see if there isn't illegal options not detected by clap
fn pre_check(matches: &ArgMatches) {
    if matches.is_present("files") && !matches.is_present("create") {
        print_error("A list of additional files is only valid with the --create option.");
    }
}
<commit_msg>tries to oreride clap tendency to exit with 1<commit_after>use clap::{App, Arg, AppSettings, Format, ArgMatches};
use std::io::{self, Write};
use std::process::exit;
use std::fs;
use crowbook::Book;

/// Prints an error on stderr and exit the program
pub fn print_error(s: &str) -> ! {
    writeln!(&mut io::stderr(), "{} {}", Format::Error("error:"), s).unwrap();
    exit(0);
}

/// sets the book options according to command line arguments
/// Also print these options to a string, so it can be used at
/// the creation of a book to check that parameters are OK and t
/// then print them to file
pub fn set_book_options(book: &mut Book, matches: &ArgMatches) -> String {
    let mut output = String::new();
    if let Some(iter) = matches.values_of("set") {
        let v:Vec<_> = iter.collect();
        if v.len() %2 != 0 {
            print_error("An odd number of arguments was passed to --set, but it takes a list of key value pairs.");
        }

        for i in 0..v.len()/2 {
            let key = v[i * 2];
            let value = v[i * 2 + 1];
            let res = book.set_option(key, value);
            if let Err(err) = res {
                print_error(&format!("Error in setting key {}: {}", key, err));
            }
            output.push_str(&format!("{}: {}\n", key, value));
        }
    }
    output
}

/// create a book file with the command line arguments
/// and exit the process at the end
pub fn create_book(matches: &ArgMatches) -> ! {
    if let Some(values) = matches.values_of("FILES") {
        let s = matches.value_of("BOOK").unwrap();
        if fs::metadata(s).is_ok() {
            print_error(&format!("Could not create file {}: it already exists!", s));
        }

        let mut f = fs::File::create(s).unwrap();

        if matches.is_present("set") {
            let mut book = Book::new();
            let s = set_book_options(&mut book, matches);
            f.write_all(&s.as_bytes()).unwrap();
        } else {
            f.write_all(b"author: Your name
title: Your title
lang: en

# Uncomment and fill to generate files
# output.html: some_file.html
# output.epub: some_file.epub
# output.pdf: some_file.pdf

# Uncomment and fill to set cover image (for Epub)
# cover: some_cover.png\n").unwrap();
        }
        f.write_all(b"\n# List of chapters\n").unwrap();
        for file in values {
            f.write_all(&format!("+ {}\n", file).as_bytes()).unwrap();
        }
        println!("Created {}, now you'll have to complete it!", s);
        exit(0);
    } else {
        print_error("--create must be used with a list of additonal files");
    }
}

pub fn create_matches<'a>() -> ArgMatches<'a> {
    let app = App::new("crowbook")
//        .setting(AppSettings::ArgRequiredElseHelp)
        .setting(AppSettings::UnifiedHelpMessage)
        .version(env!("CARGO_PKG_VERSION"))
        .about("Render a markdown book in Epub, PDF or HTML.")
        .arg_from_usage("-v, --verbose 'Activate verbose mode'")
        .arg_from_usage("--create 'Creates a new book with existing markdown files.'")
        .arg(Arg::from_usage("-o, --output [FILE] 'Specifies output file.'")
             .requires("to"))
        .arg(Arg::from_usage("[FILES]... 'Files to list in book when using --create'")
             .index(2))
        .arg(Arg::from_usage("-t, --to [FORMAT] 'Generate specific format'")
             .possible_values(&["epub", "pdf", "html", "tex", "odt"]))
        .arg(Arg::from_usage("-s, --set [KEY_VALUES] 'Sets a list of book options'")
             .min_values(2))
        .arg(Arg::from_usage("-l --list-options 'Lists all possible options"))
        .arg_from_usage("--list-options-md 'List all options, formatted in Markdown")
        .arg(Arg::with_name("BOOK")
             .index(1)
//             .required(true)
             .help("File containing the book configuration."));

    let matches = app.get_matches();

    pre_check(&matches);
    matches
}

/// Pre-check the matches to see if there isn't illegal options not detected by clap
fn pre_check(matches: &ArgMatches) {
    if matches.is_present("files") && !matches.is_present("create") {
        print_error("A list of additional files is only valid with the --create option.");
    }
}
<|endoftext|>


<commit_before>//
// Copyright 2019 The Project Oak Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

//! Test utilities to help with unit testing of Oak SDK code.

use log::{debug, info};
use oak_abi::proto::oak::application::{
    node_configuration::ConfigType, ApplicationConfiguration, NodeConfiguration,
    WebAssemblyConfiguration,
};
use prost::Message;
use std::{collections::HashMap, path::PathBuf, process::Command};
use tonic::transport::Identity;

// TODO(#544): re-enable unit tests of SDK functionality

/// Uses cargo to compile a Rust manifest to Wasm bytes.
pub fn compile_rust_wasm(cargo_path: &str, module_name: &str) -> std::io::Result<Vec<u8>> {
    // Use a separate target dir for Wasm build artifacts. The precise name is not relevant, but it
    // should end with `target` so that it gets automatically ignored by our `.gitignore`.
    let target_dir = PathBuf::from("oak_tests/target");

    Command::new("cargo")
        .args(&[
            "build",
            &format!(
                "--target-dir={}",
                target_dir.to_str().expect("invalid target dir")
            ),
            "--target=wasm32-unknown-unknown",
            &format!("--manifest-path={}", cargo_path),
        ])
        .env_remove("RUSTFLAGS")
        .spawn()?
        .wait()?;

    let mut module_path = target_dir;
    module_path.push("wasm32-unknown-unknown/debug");
    module_path.push(module_name);

    info!("compiled Wasm module path: {:?}", module_path);

    std::fs::read(module_path)
}

/// Default entrypoint name for the module under test.
const DEFAULT_ENTRYPOINT_NAME: &str = "oak_main";

const DEFAULT_MODULE_MANIFEST: &str = "Cargo.toml";
const MODULE_WASM_SUFFIX: &str = ".wasm";

/// Convenience helper to build and run a single-Node Application with the given module name, using
/// the default name "oak_main" for its entrypoint.
pub fn run_single_module_default(
    module_config_name: &str,
) -> Result<(oak_runtime::RuntimeProxy, oak_abi::Handle), oak::OakStatus> {
    run_single_module(module_config_name, DEFAULT_ENTRYPOINT_NAME)
}

/// Convenience helper to build and run a single-Node application with the given module name, using
/// the provided entrypoint name.
pub fn run_single_module(
    module_config_name: &str,
    entrypoint_name: &str,
) -> Result<(oak_runtime::RuntimeProxy, oak_abi::Handle), oak::OakStatus> {
    let combined_config = runtime_config(module_config_name, entrypoint_name);
    oak_runtime::configure_and_run(combined_config)
}

/// Build the configuration needed to launch a test Runtime instance that
/// runs a single-Node application with the given module name and entrypoint.
pub fn runtime_config(
    module_config_name: &str,
    entrypoint_name: &str,
) -> oak_runtime::RuntimeConfiguration {
    let wasm: HashMap<String, Vec<u8>> = [(
        module_config_name.to_owned(),
        compile_rust_wasm(
            DEFAULT_MODULE_MANIFEST,
            &(module_config_name.to_owned() + MODULE_WASM_SUFFIX),
        )
        .expect("failed to build wasm module"),
    )]
    .iter()
    .cloned()
    .collect();

    runtime_config_wasm(wasm, module_config_name, entrypoint_name)
}

/// Build the configuration needed to launch a test Runtime instance that runs the given
/// collection of Wasm modules, starting with the given module name and entrypoint.
pub fn runtime_config_wasm(
    wasm_modules: HashMap<String, Vec<u8>>,
    module_config_name: &str,
    entrypoint_name: &str,
) -> oak_runtime::RuntimeConfiguration {
    oak_runtime::RuntimeConfiguration {
        metrics_port: Some(9090),
        introspect_port: Some(1909),
        grpc_config: oak_runtime::GrpcConfiguration {
            grpc_server_tls_identity: Some(Identity::from_pem(
                include_str!("../certs/local.pem"),
                include_str!("../certs/local.key"),
            )),
            grpc_client_root_tls_certificate: Some(
                oak_runtime::config::load_certificate(&include_str!("../certs/ca.pem")).unwrap(),
            ),
            oidc_client_info: None,
        },
        app_config: ApplicationConfiguration {
            wasm_modules,
            initial_node_configuration: Some(NodeConfiguration {
                name: "test".to_string(),
                config_type: Some(ConfigType::WasmConfig(WebAssemblyConfiguration {
                    wasm_module_name: module_config_name.to_string(),
                    wasm_entrypoint_name: entrypoint_name.to_string(),
                })),
            }),
        },
    }
}

/// Inject a gRPC request into an Oak Application under test and return the corresponding
/// response (or error).
pub fn grpc_request<R, Q>(
    proxy: &oak_runtime::RuntimeProxy,
    handle: oak_abi::Handle,
    method_name: &str,
    req: &R,
) -> oak::grpc::Result<Q>
where
    R: prost::Message,
    Q: prost::Message + Default,
{
    // Put the request in a GrpcRequest wrapper and serialize into a message.
    let grpc_req =
        oak_abi::grpc::encap_request(req, method_name).expect("failed to build GrpcRequest");
    let mut req_msg = oak_runtime::NodeMessage {
        data: vec![],
        handles: vec![],
    };

    grpc_req
        .encode(&mut req_msg.data)
        .expect("failed to serialize GrpcRequest message");

    // Create a new channel to hold the request message.
    //
    // In most cases we do not care about labels, so we use the least privileged label for this
    // channel.
    let (req_write_handle, req_read_handle) = proxy
        .channel_create(&oak_abi::label::Label::public_untrusted())
        .expect("could not create channel");
    proxy
        .channel_write(req_write_handle, req_msg)
        .expect("could not write message");

    // Create a new channel for responses to arrive on and also attach that to the message.
    //
    // In most cases we do not care about labels, so we use the least privileged label for this
    // channel.
    let (rsp_write_handle, rsp_read_handle) = proxy
        .channel_create(&oak_abi::label::Label::public_untrusted())
        .expect("could not create channel");

    // Create a notification message and attach the method-invocation specific channels to it.
    let notify_msg = oak_runtime::NodeMessage {
        data: vec![],
        handles: vec![req_read_handle, rsp_write_handle],
    };

    // Send the notification message (with attached handles) into the Node under test.
    proxy
        .channel_write(handle, notify_msg)
        .expect("could not write message");
    proxy
        .channel_close(req_write_handle)
        .expect("failed to close channel");
    proxy
        .channel_close(req_read_handle)
        .expect("failed to close channel");
    proxy
        .channel_close(rsp_write_handle)
        .expect("failed to close channel");

    // Read the serialized, encapsulated response.
    loop {
        let rsp = match proxy.channel_read(rsp_read_handle) {
            Ok(Some(r)) => r,
            Ok(None) => {
                debug!("no pending gRPC response message; poll again soon");
                std::thread::sleep(std::time::Duration::from_millis(100));
                continue;
            }
            Err(e) => {
                panic!("failed to read from response channel: {:?}", e);
            }
        };
        proxy
            .channel_close(rsp_read_handle)
            .expect("failed to close channel");
        if rsp.data.is_empty() {
            info!("no pending message; poll again soon");
            std::thread::sleep(std::time::Duration::from_millis(100));
            continue;
        }
        let rsp = oak_abi::proto::oak::encap::GrpcResponse::decode(rsp.data.as_slice())
            .expect("failed to parse GrpcResponse message");
        if !rsp.last {
            panic!("Expected single final response");
        }

        let status = rsp.status.unwrap_or_default();
        if status.code != oak::grpc::Code::Ok as i32 {
            return Err(status);
        }
        let rsp =
            Q::decode(rsp.rsp_msg.as_slice()).expect("Failed to parse response protobuf message");
        return Ok(rsp);
    }
}
<commit_msg>sdk/oak_tests: add gRPC client helper<commit_after>//
// Copyright 2019 The Project Oak Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

//! Test utilities to help with unit testing of Oak SDK code.

use log::{debug, info};
use oak_abi::{
    label::Label,
    proto::oak::application::{
        node_configuration::ConfigType, ApplicationConfiguration, NodeConfiguration,
        WebAssemblyConfiguration,
    },
};
use prost::Message;
use std::{collections::HashMap, path::PathBuf, process::Command};
use tonic::{
    metadata::MetadataValue,
    transport::{Certificate, Channel, ClientTlsConfig, Identity},
    Request,
};

// TODO(#544): re-enable unit tests of SDK functionality

/// Uses cargo to compile a Rust manifest to Wasm bytes.
pub fn compile_rust_wasm(cargo_path: &str, module_name: &str) -> std::io::Result<Vec<u8>> {
    // Use a separate target dir for Wasm build artifacts. The precise name is not relevant, but it
    // should end with `target` so that it gets automatically ignored by our `.gitignore`.
    let target_dir = PathBuf::from("oak_tests/target");

    Command::new("cargo")
        .args(&[
            "build",
            &format!(
                "--target-dir={}",
                target_dir.to_str().expect("invalid target dir")
            ),
            "--target=wasm32-unknown-unknown",
            &format!("--manifest-path={}", cargo_path),
        ])
        .env_remove("RUSTFLAGS")
        .spawn()?
        .wait()?;

    let mut module_path = target_dir;
    module_path.push("wasm32-unknown-unknown/debug");
    module_path.push(module_name);

    info!("compiled Wasm module path: {:?}", module_path);

    std::fs::read(module_path)
}

/// Default entrypoint name for the module under test.
const DEFAULT_ENTRYPOINT_NAME: &str = "oak_main";
/// Default URI that the tests expect to find a live Runtime at.
const RUNTIME_URI: &str = "https://localhost:8080";

const DEFAULT_MODULE_MANIFEST: &str = "Cargo.toml";
const MODULE_WASM_SUFFIX: &str = ".wasm";

// Retry parameters when connecting to a gRPC server.
const RETRY_COUNT: u32 = 360;
const RETRY_INTERVAL: std::time::Duration = std::time::Duration::from_millis(500);

/// Convenience helper to build and run a single-Node Application with the given module name, using
/// the default name "oak_main" for its entrypoint.
pub fn run_single_module_default(
    module_config_name: &str,
) -> Result<(oak_runtime::RuntimeProxy, oak_abi::Handle), oak::OakStatus> {
    run_single_module(module_config_name, DEFAULT_ENTRYPOINT_NAME)
}

/// Convenience helper to build and run a single-Node application with the given module name, using
/// the provided entrypoint name.
pub fn run_single_module(
    module_config_name: &str,
    entrypoint_name: &str,
) -> Result<(oak_runtime::RuntimeProxy, oak_abi::Handle), oak::OakStatus> {
    let combined_config = runtime_config(module_config_name, entrypoint_name);
    oak_runtime::configure_and_run(combined_config)
}

/// Build the configuration needed to launch a test Runtime instance that
/// runs a single-Node application with the given module name and entrypoint.
pub fn runtime_config(
    module_config_name: &str,
    entrypoint_name: &str,
) -> oak_runtime::RuntimeConfiguration {
    let wasm: HashMap<String, Vec<u8>> = [(
        module_config_name.to_owned(),
        compile_rust_wasm(
            DEFAULT_MODULE_MANIFEST,
            &(module_config_name.to_owned() + MODULE_WASM_SUFFIX),
        )
        .expect("failed to build wasm module"),
    )]
    .iter()
    .cloned()
    .collect();

    runtime_config_wasm(wasm, module_config_name, entrypoint_name)
}

/// Build the configuration needed to launch a test Runtime instance that runs the given
/// collection of Wasm modules, starting with the given module name and entrypoint.
pub fn runtime_config_wasm(
    wasm_modules: HashMap<String, Vec<u8>>,
    module_config_name: &str,
    entrypoint_name: &str,
) -> oak_runtime::RuntimeConfiguration {
    oak_runtime::RuntimeConfiguration {
        metrics_port: Some(9090),
        introspect_port: Some(1909),
        grpc_config: oak_runtime::GrpcConfiguration {
            grpc_server_tls_identity: Some(Identity::from_pem(
                include_str!("../certs/local.pem"),
                include_str!("../certs/local.key"),
            )),
            grpc_client_root_tls_certificate: Some(
                oak_runtime::config::load_certificate(&include_str!("../certs/ca.pem")).unwrap(),
            ),
            oidc_client_info: None,
        },
        app_config: ApplicationConfiguration {
            wasm_modules,
            initial_node_configuration: Some(NodeConfiguration {
                name: "test".to_string(),
                config_type: Some(ConfigType::WasmConfig(WebAssemblyConfiguration {
                    wasm_module_name: module_config_name.to_string(),
                    wasm_entrypoint_name: entrypoint_name.to_string(),
                })),
            }),
        },
    }
}

/// Inject a gRPC request into an Oak Application under test and return the corresponding
/// response (or error).
pub fn grpc_request<R, Q>(
    proxy: &oak_runtime::RuntimeProxy,
    handle: oak_abi::Handle,
    method_name: &str,
    req: &R,
) -> oak::grpc::Result<Q>
where
    R: prost::Message,
    Q: prost::Message + Default,
{
    // Put the request in a GrpcRequest wrapper and serialize into a message.
    let grpc_req =
        oak_abi::grpc::encap_request(req, method_name).expect("failed to build GrpcRequest");
    let mut req_msg = oak_runtime::NodeMessage {
        data: vec![],
        handles: vec![],
    };

    grpc_req
        .encode(&mut req_msg.data)
        .expect("failed to serialize GrpcRequest message");

    // Create a new channel to hold the request message.
    //
    // In most cases we do not care about labels, so we use the least privileged label for this
    // channel.
    let (req_write_handle, req_read_handle) = proxy
        .channel_create(&oak_abi::label::Label::public_untrusted())
        .expect("could not create channel");
    proxy
        .channel_write(req_write_handle, req_msg)
        .expect("could not write message");

    // Create a new channel for responses to arrive on and also attach that to the message.
    //
    // In most cases we do not care about labels, so we use the least privileged label for this
    // channel.
    let (rsp_write_handle, rsp_read_handle) = proxy
        .channel_create(&oak_abi::label::Label::public_untrusted())
        .expect("could not create channel");

    // Create a notification message and attach the method-invocation specific channels to it.
    let notify_msg = oak_runtime::NodeMessage {
        data: vec![],
        handles: vec![req_read_handle, rsp_write_handle],
    };

    // Send the notification message (with attached handles) into the Node under test.
    proxy
        .channel_write(handle, notify_msg)
        .expect("could not write message");
    proxy
        .channel_close(req_write_handle)
        .expect("failed to close channel");
    proxy
        .channel_close(req_read_handle)
        .expect("failed to close channel");
    proxy
        .channel_close(rsp_write_handle)
        .expect("failed to close channel");

    // Read the serialized, encapsulated response.
    loop {
        let rsp = match proxy.channel_read(rsp_read_handle) {
            Ok(Some(r)) => r,
            Ok(None) => {
                debug!("no pending gRPC response message; poll again soon");
                std::thread::sleep(std::time::Duration::from_millis(100));
                continue;
            }
            Err(e) => {
                panic!("failed to read from response channel: {:?}", e);
            }
        };
        proxy
            .channel_close(rsp_read_handle)
            .expect("failed to close channel");
        if rsp.data.is_empty() {
            info!("no pending message; poll again soon");
            std::thread::sleep(std::time::Duration::from_millis(100));
            continue;
        }
        let rsp = oak_abi::proto::oak::encap::GrpcResponse::decode(rsp.data.as_slice())
            .expect("failed to parse GrpcResponse message");
        if !rsp.last {
            panic!("Expected single final response");
        }

        let status = rsp.status.unwrap_or_default();
        if status.code != oak::grpc::Code::Ok as i32 {
            return Err(status);
        }
        let rsp =
            Q::decode(rsp.rsp_msg.as_slice()).expect("Failed to parse response protobuf message");
        return Ok(rsp);
    }
}

/// Build a channel and interceptor suitable for building a client that connects
/// to a Runtime under test.
pub async fn channel_and_interceptor() -> (Channel, impl Into<tonic::Interceptor>) {
    // Build a channel that connects to the Runtime under test.
    let uri = RUNTIME_URI.parse().expect("Error parsing URI");
    let tls_config = ClientTlsConfig::new()
        .ca_certificate(Certificate::from_pem(include_str!("../certs/ca.pem")));
    let builder = Channel::builder(uri).tls_config(tls_config);

    // The Runtime may have just been started for a test, and make take some time
    // to come fully up, start a gRPC server and accept connections. Allow for
    // this by retrying at intervals until the server responds or we hit a retry
    // limit.
    let mut retries = 0;
    let channel;
    loop {
        match builder.connect().await {
            Ok(c) => {
                debug!("Connected to gRPC server");
                channel = c;
                break;
            }
            Err(err) => {
                if retries < RETRY_COUNT {
                    debug!("Failed to connect, try again momentarily: {:?}", err);
                    retries += 1;
                    std::thread::sleep(RETRY_INTERVAL);
                } else {
                    panic!("Failed to connect, last err: {:?}", err);
                }
            }
        }
    }

    // Build an interceptor that will attach a public-untrusted Oak label to
    // every gRPC request.
    let mut label = Vec::new();
    Label::public_untrusted()
        .encode(&mut label)
        .expect("Error encoding label");
    let interceptor = move |mut request: Request<()>| {
        request.metadata_mut().insert_bin(
            oak_abi::OAK_LABEL_GRPC_METADATA_KEY,
            MetadataValue::from_bytes(label.as_ref()),
        );
        Ok(request)
    };

    (channel, interceptor)
}
<|endoftext|>