<commit_before>// Copyright 2020 Google LLC
//
// 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.

//! # ICU number formatting support for rust
//!
//! Since 0.3.1

use {
    paste, rust_icu_common as common, rust_icu_sys as sys,
    rust_icu_sys::versioned_function,
    rust_icu_sys::*,
    rust_icu_uloc as uloc, rust_icu_ustring as ustring,
    rust_icu_ustring::buffered_uchar_method_with_retry,
    std::{convert::TryFrom, convert::TryInto, ptr},
};

#[derive(Debug)]
pub struct UNumberFormat {
    rep: ptr::NonNull<sys::UNumberFormat>,
}

impl Drop for UNumberFormat {
    /// Implements `unum_close`
    fn drop(&mut self) {
        unsafe { versioned_function!(unum_close)(self.rep.as_ptr()) };
    }
}

/// There is a slew of near-identical method calls which differ in the type of
/// the input argument and the name of the function to invoke.
macro_rules! format_ustring_for_type{
    ($method_name:ident, $function_name:ident, $type_decl:ty) => (
        /// Implements `$function_name`.
        pub fn $method_name(&self, number: $type_decl) -> Result<String, common::Error> {
            let result = paste::item! {
                self. [< $method_name _ustring>] (number)?
            };
            String::try_from(&result)
        }

        // Should be able to use https://github.com/google/rust_icu/pull/144 to
        // make this even shorter.
        paste::item! {
            /// Implements `$function_name`.
            pub fn [<$method_name _ustring>] (&self, param: $type_decl) -> Result<ustring::UChar, common::Error> {
                const CAPACITY: usize = 200;
                buffered_uchar_method_with_retry!(
                    [< $method_name _ustring_impl >],
                    CAPACITY,
                    [ rep: *const sys::UNumberFormat, param: $type_decl, ],
                    [ field: *mut sys::UFieldPosition, ]
                    );

                [<$method_name _ustring_impl>](
                    versioned_function!($function_name),
                    self.rep.as_ptr(),
                    param,
                    // The field position is unused for now.
                    0 as *mut sys::UFieldPosition,
                    )
            }
        }
    )
}

impl UNumberFormat {
    /// Implements `unum_open`, with a pattern.
    pub fn try_new_decimal_pattern_ustring(
        pattern: &ustring::UChar,
        locale: &uloc::ULoc,
    ) -> Result<UNumberFormat, common::Error> {
        UNumberFormat::try_new_style_pattern_ustring(
            sys::UNumberFormatStyle::UNUM_PATTERN_DECIMAL,
            pattern,
            locale,
        )
    }

    /// Implements `unum_open`, with rule-based formatting,
    pub fn try_new_decimal_rule_based_ustring(
        rule: &ustring::UChar,
        locale: &uloc::ULoc,
    ) -> Result<UNumberFormat, common::Error> {
        UNumberFormat::try_new_style_pattern_ustring(
            sys::UNumberFormatStyle::UNUM_PATTERN_RULEBASED,
            rule,
            locale,
        )
    }

    /// Implements `unum_open`, with style-based formatting.
    pub fn try_new_with_style(
        style: sys::UNumberFormatStyle,
        locale: &uloc::ULoc,
    ) -> Result<UNumberFormat, common::Error> {
        let rule = ustring::UChar::try_from("")?;
        assert_ne!(style, sys::UNumberFormatStyle::UNUM_PATTERN_RULEBASED);
        assert_ne!(style, sys::UNumberFormatStyle::UNUM_PATTERN_DECIMAL);
        UNumberFormat::try_new_style_pattern_ustring(style, &rule, locale)
    }

    /// Implements `unum_open`
    fn try_new_style_pattern_ustring(
        style: sys::UNumberFormatStyle,
        pattern: &ustring::UChar,
        locale: &uloc::ULoc,
    ) -> Result<UNumberFormat, common::Error> {
        let mut status = common::Error::OK_CODE;
        let mut parse = common::NO_PARSE_ERROR.clone();
        let loc = locale.as_c_str();

        // Unsafety note: all variables should be valid.
        let rep = unsafe {
            assert!(common::Error::is_ok(status));
            assert!(common::parse_ok(parse).is_ok());
            versioned_function!(unum_open)(
                style,
                pattern.as_c_ptr(),
                // Mostly OK...
                pattern.len() as i32,
                loc.as_ptr(),
                &mut parse,
                &mut status,
            )
        };
        common::Error::ok_or_warning(status)?;
        common::parse_ok(parse)?;
        assert_ne!(rep, 0 as *mut sys::UNumberFormat);
        Ok(UNumberFormat {
            rep: ptr::NonNull::new(rep).unwrap(),
        })
    }

    /// Implements `unum_clone`
    pub fn try_clone(&self) -> Result<UNumberFormat, common::Error> {
        let mut status = common::Error::OK_CODE;
        let rep = unsafe {
            assert!(common::Error::is_ok(status));
            versioned_function!(unum_clone)(self.rep.as_ptr(), &mut status)
        };
        common::Error::ok_or_warning(status)?;
        Ok(UNumberFormat {
            rep: ptr::NonNull::new(rep).unwrap(),
        })
    }

    // Can we make this into a generic method somehow?

    // Implements `unum_format`
    format_ustring_for_type!(format, unum_format, i32);

    // Implements `unum_formatInt64`
    format_ustring_for_type!(format_i64, unum_formatInt64, i64);

    // Implements `unum_formatDouble`
    format_ustring_for_type!(format_f64, unum_formatDouble, f64);
}

/// Used to iterate over the field positions.
pub struct UFieldPositionIterator<'a, T> {
    rep: ptr::NonNull<sys::UFieldPositionIterator>,
    // Owner does not own the representation above, but does own the underlying
    // data.
    owner: Option<&'a T>,
}

impl<'a, T> Drop for UFieldPositionIterator<'a, T> {
    fn drop(&mut self) {
        unsafe { versioned_function!(ufieldpositer_close)(self.rep.as_ptr()) };
    }
}

impl<'a, T> UFieldPositionIterator<'a, T> {
    pub fn try_new_owned(owner: &'a T) -> Result<UFieldPositionIterator<'a, T>, common::Error> {
        let mut status = common::Error::OK_CODE;
        let raw = unsafe {
            assert!(common::Error::is_ok(status));
            versioned_function!(ufieldpositer_open)(&mut status)
        };
        common::Error::ok_or_warning(status)?;
        Ok(UFieldPositionIterator {
            rep: ptr::NonNull::new(raw).expect("raw pointer is not null"),
            owner: None,
        })
    }

    pub fn try_new_unowned<'b>() -> Result<UFieldPositionIterator<'b, T>, common::Error> {
        let mut status = common::Error::OK_CODE;
        let raw = unsafe {
            assert!(common::Error::is_ok(status));
            versioned_function!(ufieldpositer_open)(&mut status)
        };
        common::Error::ok_or_warning(status)?;
        assert_ne!(raw, 0 as *mut sys::UFieldPositionIterator);
        Ok(UFieldPositionIterator {
            rep: ptr::NonNull::new(raw).expect("raw pointer is not null"),
            owner: None,
        })
    }
}

impl<'a, T> Iterator for UFieldPositionIterator<'a, T> {
    // TODO: Consider turning this into a range once the range properties
    // are known.
    /// The begin of the range and the end of the range index, in that order.
    type Item = (i32, i32);

    /// Gets the next position iterator pair.
    fn next(&mut self) -> Option<Self::Item> {
        let mut begin = 0i32;
        let mut end = 0i32;

        unsafe { versioned_function!(fieldpositer_next)(self.rep.as_ptr(), &mut begin, &mut end) };
        if begin < 0 || end < 0 {
            return None;
        }
        Some((begin, end))
    }
}

#[cfg(test)]
mod tests {

    use super::*;

    #[test]
    fn format_decimal_pattern_ustring() {
        struct TestCase {
            locale: &'static str,
            number: i32,
            pattern: &'static str,
            expected: &'static str,
        };

        let tests = vec![TestCase {
            locale: "sr-RS",
            number: 42,
            pattern: "",
            expected: "42",
        }];
        for test in tests {
            let locale = uloc::ULoc::try_from(test.locale).expect("locale exists");
            let pattern = ustring::UChar::try_from(test.pattern).expect("pattern is set");
            let fmt = crate::UNumberFormat::try_new_decimal_pattern_ustring(&pattern, &locale)
                .expect("formatter");

            let result = fmt
                .try_clone()
                .expect("clone")
                .format(test.number)
                .expect("format success");
            assert_eq!(test.expected, result);
        }
    }

    // TODO: find example rules.
    #[test]
    #[should_panic(expected = "U_MEMORY_ALLOCATION_ERROR")]
    fn format_decimal_rulebased_ustring() {
        struct TestCase {
            locale: &'static str,
            number: i32,
            rule: &'static str,
            expected: &'static str,
        };

        let tests = vec![TestCase {
            locale: "sr-RS",
            number: 42,
            rule: "",
            expected: "42",
        }];
        for test in tests {
            let locale = uloc::ULoc::try_from(test.locale).expect("locale exists");
            let pattern = ustring::UChar::try_from(test.rule).expect("pattern is set");
            let fmt = crate::UNumberFormat::try_new_decimal_rule_based_ustring(&pattern, &locale)
                .expect("formatter");

            let result = fmt
                .try_clone()
                .expect("clone")
                .format(test.number)
                .expect("format success");
            assert_eq!(test.expected, result);
        }
    }

    // TODO: add more, and relevant test cases.
    #[test]
    fn format_style_ustring() {
        struct TestCase {
            locale: &'static str,
            number: i32,
            style: sys::UNumberFormatStyle,
            expected: &'static str,
        };

        let tests = vec![
            TestCase {
                locale: "sr-RS",
                number: 42,
                style: sys::UNumberFormatStyle::UNUM_CURRENCY,
                expected: "42\u{a0}RSD",
            },
            TestCase {
                locale: "sr-RS",
                number: 42,
                style: sys::UNumberFormatStyle::UNUM_SPELLOUT,
                expected: "четрдесет и два",
            },
        ];
        for test in tests {
            let locale = uloc::ULoc::try_from(test.locale).expect("locale exists");
            let fmt =
                crate::UNumberFormat::try_new_with_style(test.style, &locale).expect("formatter");

            let result = fmt
                .try_clone()
                .expect("clone")
                .format(test.number)
                .expect("format success");
            assert_eq!(test.expected, result);
        }
    }
}
<commit_msg>Implement formatting double with field iteration.<commit_after>// Copyright 2020 Google LLC
//
// 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.

//! # ICU number formatting support for rust
//!
//! Since 0.3.1

use {
    paste, rust_icu_common as common, rust_icu_sys as sys,
    rust_icu_sys::versioned_function,
    rust_icu_sys::*,
    rust_icu_uloc as uloc, rust_icu_ustring as ustring,
    rust_icu_ustring::buffered_uchar_method_with_retry,
    std::{convert::TryFrom, convert::TryInto, ptr},
};

#[derive(Debug)]
pub struct UNumberFormat {
    rep: ptr::NonNull<sys::UNumberFormat>,
}

impl Drop for UNumberFormat {
    /// Implements `unum_close`
    fn drop(&mut self) {
        unsafe { versioned_function!(unum_close)(self.rep.as_ptr()) };
    }
}

/// There is a slew of near-identical method calls which differ in the type of
/// the input argument and the name of the function to invoke.
macro_rules! format_ustring_for_type{
    ($method_name:ident, $function_name:ident, $type_decl:ty) => (
        /// Implements `$function_name`.
        pub fn $method_name(&self, number: $type_decl) -> Result<String, common::Error> {
            let result = paste::item! {
                self. [< $method_name _ustring>] (number)?
            };
            String::try_from(&result)
        }

        // Should be able to use https://github.com/google/rust_icu/pull/144 to
        // make this even shorter.
        paste::item! {
            /// Implements `$function_name`.
            pub fn [<$method_name _ustring>] (&self, param: $type_decl) -> Result<ustring::UChar, common::Error> {
                const CAPACITY: usize = 200;
                buffered_uchar_method_with_retry!(
                    [< $method_name _ustring_impl >],
                    CAPACITY,
                    [ rep: *const sys::UNumberFormat, param: $type_decl, ],
                    [ field: *mut sys::UFieldPosition, ]
                    );

                [<$method_name _ustring_impl>](
                    versioned_function!($function_name),
                    self.rep.as_ptr(),
                    param,
                    // The field position is unused for now.
                    0 as *mut sys::UFieldPosition,
                    )
            }
        }
    )
}

impl UNumberFormat {
    /// Implements `unum_open`, with a pattern.
    pub fn try_new_decimal_pattern_ustring(
        pattern: &ustring::UChar,
        locale: &uloc::ULoc,
    ) -> Result<UNumberFormat, common::Error> {
        UNumberFormat::try_new_style_pattern_ustring(
            sys::UNumberFormatStyle::UNUM_PATTERN_DECIMAL,
            pattern,
            locale,
        )
    }

    /// Implements `unum_open`, with rule-based formatting,
    pub fn try_new_decimal_rule_based_ustring(
        rule: &ustring::UChar,
        locale: &uloc::ULoc,
    ) -> Result<UNumberFormat, common::Error> {
        UNumberFormat::try_new_style_pattern_ustring(
            sys::UNumberFormatStyle::UNUM_PATTERN_RULEBASED,
            rule,
            locale,
        )
    }

    /// Implements `unum_open`, with style-based formatting.
    pub fn try_new_with_style(
        style: sys::UNumberFormatStyle,
        locale: &uloc::ULoc,
    ) -> Result<UNumberFormat, common::Error> {
        let rule = ustring::UChar::try_from("")?;
        assert_ne!(style, sys::UNumberFormatStyle::UNUM_PATTERN_RULEBASED);
        assert_ne!(style, sys::UNumberFormatStyle::UNUM_PATTERN_DECIMAL);
        UNumberFormat::try_new_style_pattern_ustring(style, &rule, locale)
    }

    /// Implements `unum_open`
    fn try_new_style_pattern_ustring(
        style: sys::UNumberFormatStyle,
        pattern: &ustring::UChar,
        locale: &uloc::ULoc,
    ) -> Result<UNumberFormat, common::Error> {
        let mut status = common::Error::OK_CODE;
        let mut parse = common::NO_PARSE_ERROR.clone();
        let loc = locale.as_c_str();

        // Unsafety note: all variables should be valid.
        let rep = unsafe {
            assert!(common::Error::is_ok(status));
            assert!(common::parse_ok(parse).is_ok());
            versioned_function!(unum_open)(
                style,
                pattern.as_c_ptr(),
                // Mostly OK...
                pattern.len() as i32,
                loc.as_ptr(),
                &mut parse,
                &mut status,
            )
        };
        common::Error::ok_or_warning(status)?;
        common::parse_ok(parse)?;
        assert_ne!(rep, 0 as *mut sys::UNumberFormat);
        Ok(UNumberFormat {
            rep: ptr::NonNull::new(rep).unwrap(),
        })
    }

    /// Implements `unum_clone`
    pub fn try_clone(&self) -> Result<UNumberFormat, common::Error> {
        let mut status = common::Error::OK_CODE;
        let rep = unsafe {
            assert!(common::Error::is_ok(status));
            versioned_function!(unum_clone)(self.rep.as_ptr(), &mut status)
        };
        common::Error::ok_or_warning(status)?;
        Ok(UNumberFormat {
            rep: ptr::NonNull::new(rep).unwrap(),
        })
    }

    // Can we make this into a generic method somehow?

    // Implements `unum_format`
    format_ustring_for_type!(format, unum_format, i32);

    // Implements `unum_formatInt64`
    format_ustring_for_type!(format_i64, unum_formatInt64, i64);

    // Implements `unum_formatDouble`
    format_ustring_for_type!(format_f64, unum_formatDouble, f64);

    /// Implements `unum_formatDoubleForFields`
    ///
    /// Returns a formatted Unicode string, with a field position iterator yielding the ranges of
    /// each individual formatted field as indexes into the returned string.  An UTF8 version of
    /// this is not provided because the field position iterator does not give UTF8 compatible
    /// character indices.
    pub fn format_double_for_fields_ustring<'a>(
        &'a self,
        number: f64,
    ) -> Result<
        (
            ustring::UChar,
            UFieldPositionIterator<'a, *const sys::UNumberFormat>,
        ),
        common::Error,
    > {
        let mut iterator = UFieldPositionIterator::try_new_unowned()?;
        const CAPACITY: usize = 200;

        buffered_uchar_method_with_retry!(
            format_for_fields_impl,
            CAPACITY,
            [format: *const sys::UNumberFormat, number: f64,],
            [iter: *mut sys::UFieldPositionIterator,]
        );

        let result = format_for_fields_impl(
            versioned_function!(unum_formatDoubleForFields),
            self.rep.as_ptr(),
            number,
            iterator.as_mut_ptr(),
        )?;
        Ok((result, iterator))
    }
}

/// Used to iterate over the field positions.
pub struct UFieldPositionIterator<'a, T> {
    rep: ptr::NonNull<sys::UFieldPositionIterator>,
    // Owner does not own the representation above, but does own the underlying
    // data.  That's why we let the owner squat here to ensure proper lifetime
    // containment.
    #[allow(dead_code)]
    owner: Option<&'a T>,
}

impl<'a, T> Drop for UFieldPositionIterator<'a, T> {
    fn drop(&mut self) {
        unsafe { versioned_function!(ufieldpositer_close)(self.rep.as_ptr()) };
    }
}

impl<'a, T: 'a> UFieldPositionIterator<'a, T> {
    /// Try creatign a new iterator, based on data supplied by `owner`.
    pub fn try_new_owned(owner: &'a T) -> Result<UFieldPositionIterator<'a, T>, common::Error> {
        let raw = Self::new_raw()?;
        Ok(UFieldPositionIterator {
            rep: ptr::NonNull::new(raw).expect("raw pointer is not null"),
            owner: Some(owner),
        })
    }

    /// Try creating a new iterator, based on data with independent lifetime.
    pub fn try_new_unowned<'b>() -> Result<UFieldPositionIterator<'b, T>, common::Error> {
        let raw = Self::new_raw()?;
        Ok(UFieldPositionIterator {
            rep: ptr::NonNull::new(raw).expect("raw pointer is not null"),
            owner: None,
        })
    }

    fn new_raw() -> Result<*mut sys::UFieldPositionIterator, common::Error> {
        let mut status = common::Error::OK_CODE;
        let raw = unsafe {
            assert!(common::Error::is_ok(status));
            versioned_function!(ufieldpositer_open)(&mut status)
        };
        common::Error::ok_or_warning(status)?;
        assert_ne!(raw, 0 as *mut sys::UFieldPositionIterator);
        Ok(raw)
    }

    /// Returns the interal representation pointer.
    fn as_mut_ptr(&mut self) -> *mut sys::UFieldPositionIterator {
        self.rep.as_ptr()
    }
}

/// Returned by [UFieldPositionIterator], represents the spans of each type of
/// the formatting string.
#[derive(Debug, PartialEq)]
pub struct UFieldPositionType {
    /// The field type for the formatting.
    pub field_type: i32,

    /// The index in the buffer at which the range of interest begins.  For
    /// example, in a string "42 RSD", the beginning of "42" would be at index 0.
    pub begin_index: i32,

    /// The index one past the end of the buffer at which the range of interest ends.
    /// For example, in a string "42 RSD", the end of "42" would be at index 2.
    pub past_end_index: i32,
}

impl<'a, T> Iterator for UFieldPositionIterator<'a, T> {
    // TODO: Consider turning this into a range once the range properties
    // are known.
    /// The begin of the range and the end of the range index, in that order.
    type Item = UFieldPositionType;

    /// Gets the next position iterator pair.
    fn next(&mut self) -> Option<Self::Item> {
        let mut begin = 0i32;
        let mut end = 0i32;

        let field_type = unsafe {
            versioned_function!(ufieldpositer_next)(self.rep.as_ptr(), &mut begin, &mut end)
        };
        if field_type < 0 {
            return None;
        }
        Some(UFieldPositionType {
            field_type,
            begin_index: begin,
            past_end_index: end,
        })
    }
}

#[cfg(test)]
mod tests {

    use super::*;

    #[test]
    fn format_decimal_pattern_ustring() {
        struct TestCase {
            locale: &'static str,
            number: i32,
            pattern: &'static str,
            expected: &'static str,
        };

        let tests = vec![TestCase {
            locale: "sr-RS",
            number: 42,
            pattern: "",
            expected: "42",
        }];
        for test in tests {
            let locale = uloc::ULoc::try_from(test.locale).expect("locale exists");
            let pattern = ustring::UChar::try_from(test.pattern).expect("pattern is set");
            let fmt = crate::UNumberFormat::try_new_decimal_pattern_ustring(&pattern, &locale)
                .expect("formatter");

            let result = fmt
                .try_clone()
                .expect("clone")
                .format(test.number)
                .expect("format success");
            assert_eq!(test.expected, result);
        }
    }

    // TODO: find example rules.
    #[test]
    #[should_panic(expected = "U_MEMORY_ALLOCATION_ERROR")]
    fn format_decimal_rulebased_ustring() {
        struct TestCase {
            locale: &'static str,
            number: i32,
            rule: &'static str,
            expected: &'static str,
        };

        let tests = vec![TestCase {
            locale: "sr-RS",
            number: 42,
            rule: "",
            expected: "42",
        }];
        for test in tests {
            let locale = uloc::ULoc::try_from(test.locale).expect("locale exists");
            let pattern = ustring::UChar::try_from(test.rule).expect("pattern is set");
            let fmt = crate::UNumberFormat::try_new_decimal_rule_based_ustring(&pattern, &locale)
                .expect("formatter");

            let result = fmt
                .try_clone()
                .expect("clone")
                .format(test.number)
                .expect("format success");
            assert_eq!(test.expected, result);
        }
    }

    // TODO: add more, and relevant test cases.
    #[test]
    fn format_style_ustring() {
        struct TestCase {
            locale: &'static str,
            number: i32,
            style: sys::UNumberFormatStyle,
            expected: &'static str,
        };

        let tests = vec![
            TestCase {
                locale: "sr-RS",
                number: 42,
                style: sys::UNumberFormatStyle::UNUM_CURRENCY,
                expected: "42\u{a0}RSD",
            },
            TestCase {
                locale: "sr-RS",
                number: 42,
                style: sys::UNumberFormatStyle::UNUM_SPELLOUT,
                expected: "четрдесет и два",
            },
        ];
        for test in tests {
            let locale = uloc::ULoc::try_from(test.locale).expect("locale exists");
            let fmt =
                crate::UNumberFormat::try_new_with_style(test.style, &locale).expect("formatter");

            let result = fmt
                .try_clone()
                .expect("clone")
                .format(test.number)
                .expect("format success");
            assert_eq!(test.expected, result);
        }
    }

    #[test]
    fn format_double_with_fields() {
        struct TestCase {
            locale: &'static str,
            number: f64,
            style: sys::UNumberFormatStyle,
            expected: &'static str,
            expected_iter: Vec<UFieldPositionType>,
        };

        let tests = vec![TestCase {
            locale: "sr-RS",
            number: 42.1,
            style: sys::UNumberFormatStyle::UNUM_CURRENCY,
            expected: "42\u{a0}RSD",
            expected_iter: vec![
                // "42"
                UFieldPositionType {
                    field_type: 0,
                    begin_index: 0,
                    past_end_index: 2,
                },
                // "RSD"
                UFieldPositionType {
                    field_type: 7,
                    begin_index: 3,
                    past_end_index: 6,
                },
            ],
        }];
        for test in tests {
            let locale = uloc::ULoc::try_from(test.locale).expect("locale exists");
            let fmt =
                crate::UNumberFormat::try_new_with_style(test.style, &locale).expect("formatter");

            let (ustring, iter) = fmt
                .format_double_for_fields_ustring(test.number)
                .expect("format success");

            let s =
                String::try_from(ustring).expect(format!("string is convertible to utf8: {:?}", &ustring));
            assert_eq!(test.expected, s);
            let iter_values = iter.collect::<Vec<UFieldPositionType>>();
            assert_eq!(test.expected_iter, iter_values);
        }
    }
}
<|endoftext|>


<commit_before>// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use std::collections::BTreeMap;
use std::sync::Arc;
#[cfg(windows)]
use std::thread;
#[cfg(windows)]
use std::time::Duration;

#[cfg(windows)]
use std::io::Read;

use base::Event;
#[cfg(windows)]
use base::Result;
use devices::serial_device::{SerialHardware, SerialParameters, SerialType};
#[cfg(windows)]
use devices::Minijail;
#[cfg(unix)]
use devices::ProxyDevice;
use devices::{Bus, Serial};
use hypervisor::ProtectionType;
#[cfg(unix)]
use minijail::Minijail;
use remain::sorted;
use sync::Mutex;
use thiserror::Error as ThisError;

use crate::DeviceRegistrationError;

/// Add the default serial parameters for serial ports that have not already been specified.
///
/// This ensures that `serial_parameters` will contain parameters for each of the four PC-style
/// serial ports (COM1-COM4).
///
/// It also sets the first `SerialHardware::Serial` to be the default console device if no other
/// serial parameters exist with console=true and the first serial device has not already been
/// configured explicitly.
pub fn set_default_serial_parameters(
    serial_parameters: &mut BTreeMap<(SerialHardware, u8), SerialParameters>,
    is_vhost_user_console_enabled: bool,
) {
    // If no console device exists and the first serial port has not been specified,
    // set the first serial port as a stdout+stdin console.
    let default_console = (SerialHardware::Serial, 1);
    if !serial_parameters.iter().any(|(_, p)| p.console) && !is_vhost_user_console_enabled {
        serial_parameters
            .entry(default_console)
            .or_insert(SerialParameters {
                type_: SerialType::Stdout,
                hardware: SerialHardware::Serial,
                path: None,
                input: None,
                num: 1,
                console: true,
                earlycon: false,
                stdin: true,
                out_timestamp: false,
                ..Default::default()
            });
    }

    // Ensure all four of the COM ports exist.
    // If one of these four SerialHardware::Serial port was not configured by the user,
    // set it up as a sink.
    for num in 1..=4 {
        let key = (SerialHardware::Serial, num);
        serial_parameters.entry(key).or_insert(SerialParameters {
            type_: SerialType::Sink,
            hardware: SerialHardware::Serial,
            path: None,
            input: None,
            num,
            console: false,
            earlycon: false,
            stdin: false,
            out_timestamp: false,
            ..Default::default()
        });
    }
}

/// Address for Serial ports in x86
pub const SERIAL_ADDR: [u64; 4] = [0x3f8, 0x2f8, 0x3e8, 0x2e8];

/// A type for queueing input bytes to a serial device that abstracts if the device is local or part
/// of a sandboxed device process.
/// TODO(b/160806152) rizhang: Move to different file for readability.
#[cfg(windows)]
pub enum SerialInput {
    #[doc(hidden)]
    Local(Arc<Mutex<Serial>>),
}

#[cfg(windows)]
impl SerialInput {
    /// Creates a `SerialInput` that trivially forwards queued bytes to the device in the local
    /// process.
    pub fn new_local(serial: Arc<Mutex<Serial>>) -> SerialInput {
        SerialInput::Local(serial)
    }

    /// Just like `Serial::queue_input_bytes`, but abstracted over local and sandboxed serial
    /// devices.
    pub fn queue_input_bytes(&self, bytes: &[u8]) -> Result<()> {
        match self {
            SerialInput::Local(device) => device.lock().queue_input_bytes(bytes),
        }
    }
}

/// Adds serial devices to the provided bus based on the serial parameters given.
///
/// Only devices with hardware type `SerialHardware::Serial` are added by this function.
///
/// # Arguments
///
/// * `io_bus` - Bus to add the devices to
/// * `com_evt_1_3` - event for com1 and com3
/// * `com_evt_1_4` - event for com2 and com4
/// * `io_bus` - Bus to add the devices to
/// * `serial_parameters` - definitions of serial parameter configurations.
///   All four of the traditional PC-style serial ports (COM1-COM4) must be specified.
pub fn add_serial_devices(
    protected_vm: ProtectionType,
    io_bus: &Bus,
    com_evt_1_3: &Event,
    com_evt_2_4: &Event,
    serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>,
    serial_jail: Option<Minijail>,
) -> std::result::Result<(), DeviceRegistrationError> {
    for x in 0..=3 {
        let com_evt = match x {
            0 => &com_evt_1_3,
            1 => &com_evt_2_4,
            2 => &com_evt_1_3,
            3 => &com_evt_2_4,
            _ => &com_evt_1_3,
        };

        let param = serial_parameters
            .get(&(SerialHardware::Serial, x + 1))
            .ok_or(DeviceRegistrationError::MissingRequiredSerialDevice(x + 1))?;

        let mut preserved_descriptors = Vec::new();
        let com = param
            .create_serial_device::<Serial>(protected_vm, com_evt, &mut preserved_descriptors)
            .map_err(DeviceRegistrationError::CreateSerialDevice)?;

        match serial_jail.as_ref() {
            #[cfg(unix)]
            Some(jail) => {
                let com = Arc::new(Mutex::new(
                    ProxyDevice::new(
                        com,
                        &jail
                            .try_clone()
                            .map_err(DeviceRegistrationError::CloneJail)?,
                        preserved_descriptors,
                    )
                    .map_err(DeviceRegistrationError::ProxyDeviceCreation)?,
                ));
                io_bus
                    .insert(com.clone(), SERIAL_ADDR[x as usize], 0x8)
                    .unwrap();
            }
            #[cfg(windows)]
            Some(_) => (),
            None => {
                let com = Arc::new(Mutex::new(com));
                io_bus
                    .insert(com.clone(), SERIAL_ADDR[x as usize], 0x8)
                    .unwrap();

                #[cfg(windows)]
                {
                    if !param.stdin {
                        if let SerialType::SystemSerialType = param.type_ {
                            let mut in_pipe_result =
                                com.lock().in_stream.as_ref().unwrap().try_clone();
                            thread::spawn(move || {
                                let serial_input = SerialInput::new_local(com);
                                let in_pipe = in_pipe_result.as_mut().unwrap();

                                let mut buffer: [u8; 255] = [0; 255];
                                loop {
                                    let bytes = in_pipe.read(&mut buffer).unwrap_or(0);
                                    if bytes > 0 {
                                        serial_input.queue_input_bytes(&buffer[0..bytes]).unwrap();
                                    }
                                    // We can't issue blocking reads here and overlapped I/O is
                                    // incompatible with the call site where writes to this pipe are being
                                    // made, so instead we issue a small wait to prevent us from hogging
                                    // the CPU. This 20ms delay while typing doesn't seem to be noticeable.
                                    thread::sleep(Duration::from_millis(20));
                                }
                            });
                        }
                    }
                }
            }
        }
    }

    Ok(())
}

#[sorted]
#[derive(ThisError, Debug)]
pub enum GetSerialCmdlineError {
    #[error("Error appending to cmdline: {0}")]
    KernelCmdline(kernel_cmdline::Error),
    #[error("Hardware {0} not supported as earlycon")]
    UnsupportedEarlyconHardware(SerialHardware),
}

pub type GetSerialCmdlineResult<T> = std::result::Result<T, GetSerialCmdlineError>;

/// Add serial options to the provided `cmdline` based on `serial_parameters`.
/// `serial_io_type` should be "io" if the platform uses x86-style I/O ports for serial devices
/// or "mmio" if the serial ports are memory mapped.
// TODO(b/227407433): Support cases where vhost-user console is specified.
pub fn get_serial_cmdline(
    cmdline: &mut kernel_cmdline::Cmdline,
    serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>,
    serial_io_type: &str,
) -> GetSerialCmdlineResult<()> {
    match serial_parameters
        .iter()
        .filter(|(_, p)| p.console)
        .map(|(k, _)| k)
        .next()
    {
        Some((SerialHardware::Serial, num)) => {
            cmdline
                .insert("console", &format!("ttyS{}", num - 1))
                .map_err(GetSerialCmdlineError::KernelCmdline)?;
        }
        Some((SerialHardware::VirtioConsole, num)) => {
            cmdline
                .insert("console", &format!("hvc{}", num - 1))
                .map_err(GetSerialCmdlineError::KernelCmdline)?;
        }
        Some((SerialHardware::Debugcon, _)) => {}
        None => {}
    }

    match serial_parameters
        .iter()
        .filter(|(_, p)| p.earlycon)
        .map(|(k, _)| k)
        .next()
    {
        Some((SerialHardware::Serial, num)) => {
            if let Some(addr) = SERIAL_ADDR.get(*num as usize - 1) {
                cmdline
                    .insert(
                        "earlycon",
                        &format!("uart8250,{},0x{:x}", serial_io_type, addr),
                    )
                    .map_err(GetSerialCmdlineError::KernelCmdline)?;
            }
        }
        Some((hw, _num)) => {
            return Err(GetSerialCmdlineError::UnsupportedEarlyconHardware(*hw));
        }
        None => {}
    }

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use kernel_cmdline::Cmdline;

    #[test]
    fn get_serial_cmdline_default() {
        let mut cmdline = Cmdline::new(4096);
        let mut serial_parameters = BTreeMap::new();

        set_default_serial_parameters(&mut serial_parameters, false);
        get_serial_cmdline(&mut cmdline, &serial_parameters, "io")
            .expect("get_serial_cmdline failed");

        let cmdline_str = cmdline.as_str();
        assert!(cmdline_str.contains("console=ttyS0"));
    }

    #[test]
    fn get_serial_cmdline_virtio_console() {
        let mut cmdline = Cmdline::new(4096);
        let mut serial_parameters = BTreeMap::new();

        // Add a virtio-console device with console=true.
        serial_parameters.insert(
            (SerialHardware::VirtioConsole, 1),
            SerialParameters {
                type_: SerialType::Stdout,
                hardware: SerialHardware::VirtioConsole,
                path: None,
                input: None,
                num: 1,
                console: true,
                earlycon: false,
                stdin: true,
                out_timestamp: false,
                debugcon_port: 0,
            },
        );

        set_default_serial_parameters(&mut serial_parameters, false);
        get_serial_cmdline(&mut cmdline, &serial_parameters, "io")
            .expect("get_serial_cmdline failed");

        let cmdline_str = cmdline.as_str();
        assert!(cmdline_str.contains("console=hvc0"));
    }

    #[test]
    fn get_serial_cmdline_virtio_console_serial_earlycon() {
        let mut cmdline = Cmdline::new(4096);
        let mut serial_parameters = BTreeMap::new();

        // Add a virtio-console device with console=true.
        serial_parameters.insert(
            (SerialHardware::VirtioConsole, 1),
            SerialParameters {
                type_: SerialType::Stdout,
                hardware: SerialHardware::VirtioConsole,
                path: None,
                input: None,
                num: 1,
                console: true,
                earlycon: false,
                stdin: true,
                out_timestamp: false,
                debugcon_port: 0,
            },
        );

        // Override the default COM1 with an earlycon device.
        serial_parameters.insert(
            (SerialHardware::Serial, 1),
            SerialParameters {
                type_: SerialType::Stdout,
                hardware: SerialHardware::Serial,
                path: None,
                input: None,
                num: 1,
                console: false,
                earlycon: true,
                stdin: false,
                out_timestamp: false,
                debugcon_port: 0,
            },
        );

        set_default_serial_parameters(&mut serial_parameters, false);
        get_serial_cmdline(&mut cmdline, &serial_parameters, "io")
            .expect("get_serial_cmdline failed");

        let cmdline_str = cmdline.as_str();
        assert!(cmdline_str.contains("console=hvc0"));
        assert!(cmdline_str.contains("earlycon=uart8250,io,0x3f8"));
    }

    #[test]
    fn get_serial_cmdline_virtio_console_invalid_earlycon() {
        let mut cmdline = Cmdline::new(4096);
        let mut serial_parameters = BTreeMap::new();

        // Try to add a virtio-console device with earlycon=true (unsupported).
        serial_parameters.insert(
            (SerialHardware::VirtioConsole, 1),
            SerialParameters {
                type_: SerialType::Stdout,
                hardware: SerialHardware::VirtioConsole,
                path: None,
                input: None,
                num: 1,
                console: false,
                earlycon: true,
                stdin: true,
                out_timestamp: false,
                debugcon_port: 0,
            },
        );

        set_default_serial_parameters(&mut serial_parameters, false);
        get_serial_cmdline(&mut cmdline, &serial_parameters, "io")
            .expect_err("get_serial_cmdline succeeded");
    }
}
<commit_msg>arch: update add_serial_devices comment.<commit_after>// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use std::collections::BTreeMap;
use std::sync::Arc;
#[cfg(windows)]
use std::thread;
#[cfg(windows)]
use std::time::Duration;

#[cfg(windows)]
use std::io::Read;

use base::Event;
#[cfg(windows)]
use base::Result;
use devices::serial_device::{SerialHardware, SerialParameters, SerialType};
#[cfg(windows)]
use devices::Minijail;
#[cfg(unix)]
use devices::ProxyDevice;
use devices::{Bus, Serial};
use hypervisor::ProtectionType;
#[cfg(unix)]
use minijail::Minijail;
use remain::sorted;
use sync::Mutex;
use thiserror::Error as ThisError;

use crate::DeviceRegistrationError;

/// Add the default serial parameters for serial ports that have not already been specified.
///
/// This ensures that `serial_parameters` will contain parameters for each of the four PC-style
/// serial ports (COM1-COM4).
///
/// It also sets the first `SerialHardware::Serial` to be the default console device if no other
/// serial parameters exist with console=true and the first serial device has not already been
/// configured explicitly.
pub fn set_default_serial_parameters(
    serial_parameters: &mut BTreeMap<(SerialHardware, u8), SerialParameters>,
    is_vhost_user_console_enabled: bool,
) {
    // If no console device exists and the first serial port has not been specified,
    // set the first serial port as a stdout+stdin console.
    let default_console = (SerialHardware::Serial, 1);
    if !serial_parameters.iter().any(|(_, p)| p.console) && !is_vhost_user_console_enabled {
        serial_parameters
            .entry(default_console)
            .or_insert(SerialParameters {
                type_: SerialType::Stdout,
                hardware: SerialHardware::Serial,
                path: None,
                input: None,
                num: 1,
                console: true,
                earlycon: false,
                stdin: true,
                out_timestamp: false,
                ..Default::default()
            });
    }

    // Ensure all four of the COM ports exist.
    // If one of these four SerialHardware::Serial port was not configured by the user,
    // set it up as a sink.
    for num in 1..=4 {
        let key = (SerialHardware::Serial, num);
        serial_parameters.entry(key).or_insert(SerialParameters {
            type_: SerialType::Sink,
            hardware: SerialHardware::Serial,
            path: None,
            input: None,
            num,
            console: false,
            earlycon: false,
            stdin: false,
            out_timestamp: false,
            ..Default::default()
        });
    }
}

/// Address for Serial ports in x86
pub const SERIAL_ADDR: [u64; 4] = [0x3f8, 0x2f8, 0x3e8, 0x2e8];

/// A type for queueing input bytes to a serial device that abstracts if the device is local or part
/// of a sandboxed device process.
/// TODO(b/160806152) rizhang: Move to different file for readability.
#[cfg(windows)]
pub enum SerialInput {
    #[doc(hidden)]
    Local(Arc<Mutex<Serial>>),
}

#[cfg(windows)]
impl SerialInput {
    /// Creates a `SerialInput` that trivially forwards queued bytes to the device in the local
    /// process.
    pub fn new_local(serial: Arc<Mutex<Serial>>) -> SerialInput {
        SerialInput::Local(serial)
    }

    /// Just like `Serial::queue_input_bytes`, but abstracted over local and sandboxed serial
    /// devices.
    pub fn queue_input_bytes(&self, bytes: &[u8]) -> Result<()> {
        match self {
            SerialInput::Local(device) => device.lock().queue_input_bytes(bytes),
        }
    }
}

/// Adds serial devices to the provided bus based on the serial parameters given.
///
/// Only devices with hardware type `SerialHardware::Serial` are added by this function.
///
/// # Arguments
///
/// * `protected_vm` - VM protection mode.
/// * `io_bus` - Bus to add the devices to
/// * `com_evt_1_3` - event for com1 and com3
/// * `com_evt_1_4` - event for com2 and com4
/// * `serial_parameters` - definitions of serial parameter configurations.
/// * `serial_jail` - minijail object cloned for use with each serial device.
///   All four of the traditional PC-style serial ports (COM1-COM4) must be specified.
pub fn add_serial_devices(
    protected_vm: ProtectionType,
    io_bus: &Bus,
    com_evt_1_3: &Event,
    com_evt_2_4: &Event,
    serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>,
    serial_jail: Option<Minijail>,
) -> std::result::Result<(), DeviceRegistrationError> {
    for x in 0..=3 {
        let com_evt = match x {
            0 => &com_evt_1_3,
            1 => &com_evt_2_4,
            2 => &com_evt_1_3,
            3 => &com_evt_2_4,
            _ => &com_evt_1_3,
        };

        let param = serial_parameters
            .get(&(SerialHardware::Serial, x + 1))
            .ok_or(DeviceRegistrationError::MissingRequiredSerialDevice(x + 1))?;

        let mut preserved_descriptors = Vec::new();
        let com = param
            .create_serial_device::<Serial>(protected_vm, com_evt, &mut preserved_descriptors)
            .map_err(DeviceRegistrationError::CreateSerialDevice)?;

        match serial_jail.as_ref() {
            #[cfg(unix)]
            Some(jail) => {
                let com = Arc::new(Mutex::new(
                    ProxyDevice::new(
                        com,
                        &jail
                            .try_clone()
                            .map_err(DeviceRegistrationError::CloneJail)?,
                        preserved_descriptors,
                    )
                    .map_err(DeviceRegistrationError::ProxyDeviceCreation)?,
                ));
                io_bus
                    .insert(com.clone(), SERIAL_ADDR[x as usize], 0x8)
                    .unwrap();
            }
            #[cfg(windows)]
            Some(_) => (),
            None => {
                let com = Arc::new(Mutex::new(com));
                io_bus
                    .insert(com.clone(), SERIAL_ADDR[x as usize], 0x8)
                    .unwrap();

                #[cfg(windows)]
                {
                    if !param.stdin {
                        if let SerialType::SystemSerialType = param.type_ {
                            let mut in_pipe_result =
                                com.lock().in_stream.as_ref().unwrap().try_clone();
                            thread::spawn(move || {
                                let serial_input = SerialInput::new_local(com);
                                let in_pipe = in_pipe_result.as_mut().unwrap();

                                let mut buffer: [u8; 255] = [0; 255];
                                loop {
                                    let bytes = in_pipe.read(&mut buffer).unwrap_or(0);
                                    if bytes > 0 {
                                        serial_input.queue_input_bytes(&buffer[0..bytes]).unwrap();
                                    }
                                    // We can't issue blocking reads here and overlapped I/O is
                                    // incompatible with the call site where writes to this pipe are being
                                    // made, so instead we issue a small wait to prevent us from hogging
                                    // the CPU. This 20ms delay while typing doesn't seem to be noticeable.
                                    thread::sleep(Duration::from_millis(20));
                                }
                            });
                        }
                    }
                }
            }
        }
    }

    Ok(())
}

#[sorted]
#[derive(ThisError, Debug)]
pub enum GetSerialCmdlineError {
    #[error("Error appending to cmdline: {0}")]
    KernelCmdline(kernel_cmdline::Error),
    #[error("Hardware {0} not supported as earlycon")]
    UnsupportedEarlyconHardware(SerialHardware),
}

pub type GetSerialCmdlineResult<T> = std::result::Result<T, GetSerialCmdlineError>;

/// Add serial options to the provided `cmdline` based on `serial_parameters`.
/// `serial_io_type` should be "io" if the platform uses x86-style I/O ports for serial devices
/// or "mmio" if the serial ports are memory mapped.
// TODO(b/227407433): Support cases where vhost-user console is specified.
pub fn get_serial_cmdline(
    cmdline: &mut kernel_cmdline::Cmdline,
    serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>,
    serial_io_type: &str,
) -> GetSerialCmdlineResult<()> {
    match serial_parameters
        .iter()
        .filter(|(_, p)| p.console)
        .map(|(k, _)| k)
        .next()
    {
        Some((SerialHardware::Serial, num)) => {
            cmdline
                .insert("console", &format!("ttyS{}", num - 1))
                .map_err(GetSerialCmdlineError::KernelCmdline)?;
        }
        Some((SerialHardware::VirtioConsole, num)) => {
            cmdline
                .insert("console", &format!("hvc{}", num - 1))
                .map_err(GetSerialCmdlineError::KernelCmdline)?;
        }
        Some((SerialHardware::Debugcon, _)) => {}
        None => {}
    }

    match serial_parameters
        .iter()
        .filter(|(_, p)| p.earlycon)
        .map(|(k, _)| k)
        .next()
    {
        Some((SerialHardware::Serial, num)) => {
            if let Some(addr) = SERIAL_ADDR.get(*num as usize - 1) {
                cmdline
                    .insert(
                        "earlycon",
                        &format!("uart8250,{},0x{:x}", serial_io_type, addr),
                    )
                    .map_err(GetSerialCmdlineError::KernelCmdline)?;
            }
        }
        Some((hw, _num)) => {
            return Err(GetSerialCmdlineError::UnsupportedEarlyconHardware(*hw));
        }
        None => {}
    }

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use kernel_cmdline::Cmdline;

    #[test]
    fn get_serial_cmdline_default() {
        let mut cmdline = Cmdline::new(4096);
        let mut serial_parameters = BTreeMap::new();

        set_default_serial_parameters(&mut serial_parameters, false);
        get_serial_cmdline(&mut cmdline, &serial_parameters, "io")
            .expect("get_serial_cmdline failed");

        let cmdline_str = cmdline.as_str();
        assert!(cmdline_str.contains("console=ttyS0"));
    }

    #[test]
    fn get_serial_cmdline_virtio_console() {
        let mut cmdline = Cmdline::new(4096);
        let mut serial_parameters = BTreeMap::new();

        // Add a virtio-console device with console=true.
        serial_parameters.insert(
            (SerialHardware::VirtioConsole, 1),
            SerialParameters {
                type_: SerialType::Stdout,
                hardware: SerialHardware::VirtioConsole,
                path: None,
                input: None,
                num: 1,
                console: true,
                earlycon: false,
                stdin: true,
                out_timestamp: false,
                debugcon_port: 0,
            },
        );

        set_default_serial_parameters(&mut serial_parameters, false);
        get_serial_cmdline(&mut cmdline, &serial_parameters, "io")
            .expect("get_serial_cmdline failed");

        let cmdline_str = cmdline.as_str();
        assert!(cmdline_str.contains("console=hvc0"));
    }

    #[test]
    fn get_serial_cmdline_virtio_console_serial_earlycon() {
        let mut cmdline = Cmdline::new(4096);
        let mut serial_parameters = BTreeMap::new();

        // Add a virtio-console device with console=true.
        serial_parameters.insert(
            (SerialHardware::VirtioConsole, 1),
            SerialParameters {
                type_: SerialType::Stdout,
                hardware: SerialHardware::VirtioConsole,
                path: None,
                input: None,
                num: 1,
                console: true,
                earlycon: false,
                stdin: true,
                out_timestamp: false,
                debugcon_port: 0,
            },
        );

        // Override the default COM1 with an earlycon device.
        serial_parameters.insert(
            (SerialHardware::Serial, 1),
            SerialParameters {
                type_: SerialType::Stdout,
                hardware: SerialHardware::Serial,
                path: None,
                input: None,
                num: 1,
                console: false,
                earlycon: true,
                stdin: false,
                out_timestamp: false,
                debugcon_port: 0,
            },
        );

        set_default_serial_parameters(&mut serial_parameters, false);
        get_serial_cmdline(&mut cmdline, &serial_parameters, "io")
            .expect("get_serial_cmdline failed");

        let cmdline_str = cmdline.as_str();
        assert!(cmdline_str.contains("console=hvc0"));
        assert!(cmdline_str.contains("earlycon=uart8250,io,0x3f8"));
    }

    #[test]
    fn get_serial_cmdline_virtio_console_invalid_earlycon() {
        let mut cmdline = Cmdline::new(4096);
        let mut serial_parameters = BTreeMap::new();

        // Try to add a virtio-console device with earlycon=true (unsupported).
        serial_parameters.insert(
            (SerialHardware::VirtioConsole, 1),
            SerialParameters {
                type_: SerialType::Stdout,
                hardware: SerialHardware::VirtioConsole,
                path: None,
                input: None,
                num: 1,
                console: false,
                earlycon: true,
                stdin: true,
                out_timestamp: false,
                debugcon_port: 0,
            },
        );

        set_default_serial_parameters(&mut serial_parameters, false);
        get_serial_cmdline(&mut cmdline, &serial_parameters, "io")
            .expect_err("get_serial_cmdline succeeded");
    }
}
<|endoftext|>


<commit_before>extern crate vulkano;
extern crate winit;

#[cfg(target_os = "macos")]
extern crate objc;
#[cfg(target_os = "macos")]
extern crate cocoa;
#[cfg(target_os = "macos")]
extern crate metal_rs as metal;

use std::error;
use std::fmt;
#[cfg(target_os = "windows")]
use std::ptr;
use std::sync::Arc;

use vulkano::instance::Instance;
use vulkano::instance::InstanceExtensions;
use vulkano::swapchain::Surface;
use vulkano::swapchain::SurfaceCreationError;
use winit::{EventsLoop, WindowBuilder};
use winit::CreationError as WindowCreationError;

#[cfg(target_os = "macos")]
use cocoa::appkit::{NSView, NSWindow};
#[cfg(target_os = "macos")]
use cocoa::base::id as cocoa_id;
#[cfg(target_os = "macos")]
use metal::*;
#[cfg(target_os = "macos")]
use objc::runtime::YES;

#[cfg(target_os = "macos")]
use std::mem;

pub fn required_extensions() -> InstanceExtensions {
    let ideal = InstanceExtensions {
        khr_surface: true,
        khr_xlib_surface: true,
        khr_xcb_surface: true,
        khr_wayland_surface: true,
        khr_mir_surface: true,
        khr_android_surface: true,
        khr_win32_surface: true,
        mvk_ios_surface: true,
        mvk_macos_surface: true,
        ..InstanceExtensions::none()
    };

    match InstanceExtensions::supported_by_core() {
        Ok(supported) => supported.intersection(&ideal),
        Err(_) => InstanceExtensions::none(),
    }
}

pub trait VkSurfaceBuild {
    fn build_vk_surface(self, events_loop: &EventsLoop, instance: Arc<Instance>)
                        -> Result<Window, CreationError>;
}

impl VkSurfaceBuild for WindowBuilder {
    fn build_vk_surface(self, events_loop: &EventsLoop, instance: Arc<Instance>)
                        -> Result<Window, CreationError> {
        let window = self.build(events_loop)?;
        let surface = unsafe { winit_to_surface(instance, &window) }?;

        Ok(Window {
               window: window,
               surface: surface,
           })
    }
}

pub struct Window {
    window: winit::Window,
    surface: Arc<Surface>,
}

impl Window {
    #[inline]
    pub fn window(&self) -> &winit::Window {
        &self.window
    }

    #[inline]
    pub fn surface(&self) -> &Arc<Surface> {
        &self.surface
    }
}

/// Error that can happen when creating a window.
#[derive(Debug)]
pub enum CreationError {
    /// Error when creating the surface.
    SurfaceCreationError(SurfaceCreationError),
    /// Error when creating the window.
    WindowCreationError(WindowCreationError),
}

impl error::Error for CreationError {
    #[inline]
    fn description(&self) -> &str {
        match *self {
            CreationError::SurfaceCreationError(_) => "error while creating the surface",
            CreationError::WindowCreationError(_) => "error while creating the window",
        }
    }

    #[inline]
    fn cause(&self) -> Option<&error::Error> {
        match *self {
            CreationError::SurfaceCreationError(ref err) => Some(err),
            CreationError::WindowCreationError(ref err) => Some(err),
        }
    }
}

impl fmt::Display for CreationError {
    #[inline]
    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        write!(fmt, "{}", error::Error::description(self))
    }
}

impl From<SurfaceCreationError> for CreationError {
    #[inline]
    fn from(err: SurfaceCreationError) -> CreationError {
        CreationError::SurfaceCreationError(err)
    }
}

impl From<WindowCreationError> for CreationError {
    #[inline]
    fn from(err: WindowCreationError) -> CreationError {
        CreationError::WindowCreationError(err)
    }
}

#[cfg(target_os = "android")]
unsafe fn winit_to_surface(instance: Arc<Instance>, win: &winit::Window)
                           -> Result<Arc<Surface>, SurfaceCreationError> {
    use winit::os::android::WindowExt;
    Surface::from_anativewindow(instance, win.get_native_window())
}

#[cfg(all(unix, not(target_os = "android"), not(target_os = "macos")))]
unsafe fn winit_to_surface(instance: Arc<Instance>, win: &winit::Window)
                           -> Result<Arc<Surface>, SurfaceCreationError> {
    use winit::os::unix::WindowExt;
    match (win.get_wayland_display(), win.get_wayland_surface()) {
        (Some(display), Some(surface)) => Surface::from_wayland(instance, display, surface),
        _ => {
            // No wayland display found, check if we can use xlib.
            // If not, we use xcb.
            if instance.loaded_extensions().khr_xlib_surface {
                Surface::from_xlib(instance,
                                   win.get_xlib_display().unwrap(),
                                   win.get_xlib_window().unwrap() as _)
            } else {
                Surface::from_xcb(instance,
                                  win.get_xcb_connection().unwrap(),
                                  win.get_xlib_window().unwrap() as _)
            }
        },
    }
}

#[cfg(target_os = "windows")]
unsafe fn winit_to_surface(instance: Arc<Instance>, win: &winit::Window)
                           -> Result<Arc<Surface>, SurfaceCreationError> {
    use winit::os::windows::WindowExt;
    Surface::from_hwnd(instance,
                       ptr::null() as *const (), // FIXME
                       win.get_hwnd())
}

#[cfg(target_os = "macos")]
unsafe fn winit_to_surface(instance: Arc<Instance>, win: &winit::Window)
                           -> Result<Arc<Surface>, SurfaceCreationError> {
    use winit::os::macos::WindowExt;

    unsafe {
        let wnd: cocoa_id = mem::transmute(win.get_nswindow());

        let layer = CAMetalLayer::new();

        layer.set_edge_antialiasing_mask(0);
        layer.set_presents_with_transaction(false);
        layer.remove_all_animations();

        let view = wnd.contentView();
        view.setWantsLayer(YES);
        view.setLayer(mem::transmute(layer.0)); // Bombs here with out of memory
    }

    Surface::from_macos_moltenvk(instance, win.get_nsview() as *const ())
}
<commit_msg>Remove redundant unsafe<commit_after>extern crate vulkano;
extern crate winit;

#[cfg(target_os = "macos")]
extern crate objc;
#[cfg(target_os = "macos")]
extern crate cocoa;
#[cfg(target_os = "macos")]
extern crate metal_rs as metal;

use std::error;
use std::fmt;
#[cfg(target_os = "windows")]
use std::ptr;
use std::sync::Arc;

use vulkano::instance::Instance;
use vulkano::instance::InstanceExtensions;
use vulkano::swapchain::Surface;
use vulkano::swapchain::SurfaceCreationError;
use winit::{EventsLoop, WindowBuilder};
use winit::CreationError as WindowCreationError;

#[cfg(target_os = "macos")]
use cocoa::appkit::{NSView, NSWindow};
#[cfg(target_os = "macos")]
use cocoa::base::id as cocoa_id;
#[cfg(target_os = "macos")]
use metal::*;
#[cfg(target_os = "macos")]
use objc::runtime::YES;

#[cfg(target_os = "macos")]
use std::mem;

pub fn required_extensions() -> InstanceExtensions {
    let ideal = InstanceExtensions {
        khr_surface: true,
        khr_xlib_surface: true,
        khr_xcb_surface: true,
        khr_wayland_surface: true,
        khr_mir_surface: true,
        khr_android_surface: true,
        khr_win32_surface: true,
        mvk_ios_surface: true,
        mvk_macos_surface: true,
        ..InstanceExtensions::none()
    };

    match InstanceExtensions::supported_by_core() {
        Ok(supported) => supported.intersection(&ideal),
        Err(_) => InstanceExtensions::none(),
    }
}

pub trait VkSurfaceBuild {
    fn build_vk_surface(self, events_loop: &EventsLoop, instance: Arc<Instance>)
                        -> Result<Window, CreationError>;
}

impl VkSurfaceBuild for WindowBuilder {
    fn build_vk_surface(self, events_loop: &EventsLoop, instance: Arc<Instance>)
                        -> Result<Window, CreationError> {
        let window = self.build(events_loop)?;
        let surface = unsafe { winit_to_surface(instance, &window) }?;

        Ok(Window {
               window: window,
               surface: surface,
           })
    }
}

pub struct Window {
    window: winit::Window,
    surface: Arc<Surface>,
}

impl Window {
    #[inline]
    pub fn window(&self) -> &winit::Window {
        &self.window
    }

    #[inline]
    pub fn surface(&self) -> &Arc<Surface> {
        &self.surface
    }
}

/// Error that can happen when creating a window.
#[derive(Debug)]
pub enum CreationError {
    /// Error when creating the surface.
    SurfaceCreationError(SurfaceCreationError),
    /// Error when creating the window.
    WindowCreationError(WindowCreationError),
}

impl error::Error for CreationError {
    #[inline]
    fn description(&self) -> &str {
        match *self {
            CreationError::SurfaceCreationError(_) => "error while creating the surface",
            CreationError::WindowCreationError(_) => "error while creating the window",
        }
    }

    #[inline]
    fn cause(&self) -> Option<&error::Error> {
        match *self {
            CreationError::SurfaceCreationError(ref err) => Some(err),
            CreationError::WindowCreationError(ref err) => Some(err),
        }
    }
}

impl fmt::Display for CreationError {
    #[inline]
    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        write!(fmt, "{}", error::Error::description(self))
    }
}

impl From<SurfaceCreationError> for CreationError {
    #[inline]
    fn from(err: SurfaceCreationError) -> CreationError {
        CreationError::SurfaceCreationError(err)
    }
}

impl From<WindowCreationError> for CreationError {
    #[inline]
    fn from(err: WindowCreationError) -> CreationError {
        CreationError::WindowCreationError(err)
    }
}

#[cfg(target_os = "android")]
unsafe fn winit_to_surface(instance: Arc<Instance>, win: &winit::Window)
                           -> Result<Arc<Surface>, SurfaceCreationError> {
    use winit::os::android::WindowExt;
    Surface::from_anativewindow(instance, win.get_native_window())
}

#[cfg(all(unix, not(target_os = "android"), not(target_os = "macos")))]
unsafe fn winit_to_surface(instance: Arc<Instance>, win: &winit::Window)
                           -> Result<Arc<Surface>, SurfaceCreationError> {
    use winit::os::unix::WindowExt;
    match (win.get_wayland_display(), win.get_wayland_surface()) {
        (Some(display), Some(surface)) => Surface::from_wayland(instance, display, surface),
        _ => {
            // No wayland display found, check if we can use xlib.
            // If not, we use xcb.
            if instance.loaded_extensions().khr_xlib_surface {
                Surface::from_xlib(instance,
                                   win.get_xlib_display().unwrap(),
                                   win.get_xlib_window().unwrap() as _)
            } else {
                Surface::from_xcb(instance,
                                  win.get_xcb_connection().unwrap(),
                                  win.get_xlib_window().unwrap() as _)
            }
        },
    }
}

#[cfg(target_os = "windows")]
unsafe fn winit_to_surface(instance: Arc<Instance>, win: &winit::Window)
                           -> Result<Arc<Surface>, SurfaceCreationError> {
    use winit::os::windows::WindowExt;
    Surface::from_hwnd(instance,
                       ptr::null() as *const (), // FIXME
                       win.get_hwnd())
}

#[cfg(target_os = "macos")]
unsafe fn winit_to_surface(instance: Arc<Instance>, win: &winit::Window)
                           -> Result<Arc<Surface>, SurfaceCreationError> {
    use winit::os::macos::WindowExt;

    let wnd: cocoa_id = mem::transmute(win.get_nswindow());

    let layer = CAMetalLayer::new();

    layer.set_edge_antialiasing_mask(0);
    layer.set_presents_with_transaction(false);
    layer.remove_all_animations();

    let view = wnd.contentView();
    view.setWantsLayer(YES);
    view.setLayer(mem::transmute(layer.0)); // Bombs here with out of memory

    Surface::from_macos_moltenvk(instance, win.get_nsview() as *const ())
}
<|endoftext|>


<commit_before>// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

use admission_control_service::admission_control_service::AdmissionControlService;
use consensus::consensus_provider::{make_consensus_provider, ConsensusProvider};
use debug_interface::{
    node_debug_service::NodeDebugService,
    proto::node_debug_interface_server::NodeDebugInterfaceServer,
};
use executor::Executor;
use futures::{channel::mpsc::channel, executor::block_on};
use libra_config::config::{NetworkConfig, NodeConfig, RoleType};
use libra_json_rpc::bootstrap_from_config as bootstrap_rpc;
use libra_logger::prelude::*;
use libra_metrics::metric_server;
use libra_vm::LibraVM;
use network::validator_network::network_builder::{NetworkBuilder, TransportType};
use state_synchronizer::StateSynchronizer;
use std::{collections::HashMap, net::ToSocketAddrs, sync::Arc, thread, time::Instant};
use storage_client::{StorageReadServiceClient, StorageWriteServiceClient};
use storage_service::{init_libra_db, start_storage_service_with_db};
use tokio::runtime::{Builder, Runtime};

const AC_SMP_CHANNEL_BUFFER_SIZE: usize = 1_024;
const INTRA_NODE_CHANNEL_BUFFER_SIZE: usize = 1;

pub struct LibraHandle {
    _ac: Runtime,
    _rpc: Runtime,
    _mempool: Runtime,
    _state_synchronizer: StateSynchronizer,
    _network_runtimes: Vec<Runtime>,
    consensus: Option<Box<dyn ConsensusProvider>>,
    _storage: Runtime,
    _debug: Runtime,
}

impl Drop for LibraHandle {
    fn drop(&mut self) {
        if let Some(consensus) = &mut self.consensus {
            consensus.stop();
        }
    }
}

fn setup_executor(config: &NodeConfig) -> Arc<Executor<LibraVM>> {
    let storage_read_client = Arc::new(StorageReadServiceClient::new(&config.storage.address));
    let storage_write_client = Arc::new(StorageWriteServiceClient::new(&config.storage.address));

    Arc::new(Executor::new(
        storage_read_client,
        storage_write_client,
        config,
    ))
}

fn setup_debug_interface(config: &NodeConfig) -> Runtime {
    let rt = tokio::runtime::Runtime::new().unwrap();
    let addr = format!(
        "{}:{}",
        config.debug_interface.address, config.debug_interface.admission_control_node_debug_port,
    )
    .to_socket_addrs()
    .unwrap()
    .next()
    .unwrap();
    rt.spawn(
        tonic::transport::Server::builder()
            .add_service(NodeDebugInterfaceServer::new(NodeDebugService::new()))
            .serve(addr),
    );
    rt
}

// TODO(abhayb): Move to network crate (similar to consensus).
pub fn setup_network(config: &mut NetworkConfig, role: RoleType) -> (Runtime, NetworkBuilder) {
    let runtime = Builder::new()
        .thread_name("network-")
        .threaded_scheduler()
        .enable_all()
        .build()
        .expect("Failed to start runtime. Won't be able to start networking.");
    let mut network_builder = NetworkBuilder::new(
        runtime.handle().clone(),
        config.peer_id,
        config.listen_address.clone(),
        role,
    );
    network_builder
        .enable_remote_authentication(config.enable_remote_authentication)
        .advertised_address(config.advertised_address.clone())
        .add_connection_monitoring();
    if config.enable_remote_authentication {
        // If the node wants to run in permissioned mode, it should also have authentication and
        // encryption.
        assert!(
            config.enable_noise,
            "Permissioned network end-points should use authentication"
        );
        let seed_peers = config.seed_peers.seed_peers.clone();
        let network_keypairs = config
            .network_keypairs
            .as_mut()
            .expect("Network keypairs are not defined");
        let signing_keys = &mut network_keypairs.signing_keys;
        let identity_keys = &mut network_keypairs.identity_keys;

        let signing_private = signing_keys
            .take_private()
            .expect("Failed to take Network signing private key, key absent or already read");
        let signing_public = signing_keys.public().clone();
        let identity_private = identity_keys
            .take_private()
            .expect("Failed to take Network identity private key, key absent or already read");
        let identity_public = identity_keys.public().clone();
        let trusted_peers = if role == RoleType::Validator {
            // for validators, trusted_peers is empty will be populated from consensus
            HashMap::new()
        } else {
            config.network_peers.peers.clone()
        };
        network_builder
            .transport(TransportType::TcpNoise(Some((
                identity_private,
                identity_public,
            ))))
            .connectivity_check_interval_ms(config.connectivity_check_interval_ms)
            .seed_peers(seed_peers)
            .trusted_peers(trusted_peers)
            .signing_keys((signing_private, signing_public))
            .discovery_interval_ms(config.discovery_interval_ms)
            .add_discovery();
    } else if config.enable_noise {
        let identity_keys = &mut config
            .network_keypairs
            .as_mut()
            .expect("Network keypairs are not defined")
            .identity_keys;
        let identity_private = identity_keys
            .take_private()
            .expect("Failed to take Network identity private key, key absent or already read");
        let identity_public = identity_keys.public().clone();
        // Even if a network end-point operates without remote authentication, it might want to prove
        // its identity to another peer it connects to. For this, we use TCP + Noise but without
        // enforcing a trusted peers set.
        network_builder.transport(TransportType::TcpNoise(Some((
            identity_private,
            identity_public,
        ))));
    } else {
        network_builder.transport(TransportType::Tcp);
    }
    (runtime, network_builder)
}

pub fn setup_environment(node_config: &mut NodeConfig) -> LibraHandle {
    crash_handler::setup_panic_handler();

    // Some of our code uses the rayon global thread pool. Name the rayon threads so it doesn't
    // cause confusion, otherwise the threads would have their parent's name.
    rayon::ThreadPoolBuilder::new()
        .thread_name(|index| format!("rayon-global-{}", index))
        .build_global()
        .expect("Building rayon global thread pool should work.");

    let mut instant = Instant::now();
    let libra_db = init_libra_db(&node_config);
    let storage = start_storage_service_with_db(&node_config, Arc::clone(&libra_db));

    debug!(
        "Storage service started in {} ms",
        instant.elapsed().as_millis()
    );

    instant = Instant::now();
    let executor = setup_executor(&node_config);
    debug!("Executor setup in {} ms", instant.elapsed().as_millis());
    let mut network_runtimes = vec![];
    let mut state_sync_network_handles = vec![];
    let mut mempool_network_handles = vec![];
    let mut validator_network_provider = None;
    let mut reconfig_event_subscriptions = vec![];
    let (mempool_reconfig_subscription, mempool_reconfig_events) =
        libra_mempool::generate_reconfig_subscription();
    reconfig_event_subscriptions.push(mempool_reconfig_subscription);

    if let Some(network) = node_config.validator_network.as_mut() {
        let (runtime, mut network_builder) = setup_network(network, RoleType::Validator);
        state_sync_network_handles.push(state_synchronizer::network::add_to_network(
            &mut network_builder,
        ));

        let (mempool_sender, mempool_events) =
            libra_mempool::network::add_to_network(&mut network_builder);
        mempool_network_handles.push((network.peer_id, mempool_sender, mempool_events));
        validator_network_provider = Some((network.peer_id, runtime, network_builder));
    }

    for mut full_node_network in node_config.full_node_networks.iter_mut() {
        let (runtime, mut network_builder) =
            setup_network(&mut full_node_network, RoleType::FullNode);

        network_runtimes.push(runtime);
        state_sync_network_handles.push(state_synchronizer::network::add_to_network(
            &mut network_builder,
        ));
        let (mempool_sender, mempool_events) =
            libra_mempool::network::add_to_network(&mut network_builder);
        mempool_network_handles.push((full_node_network.peer_id, mempool_sender, mempool_events));

        // Start the network provider.
        let _listen_addr = network_builder.build();
        debug!("Network started for peer_id: {}", full_node_network.peer_id);
    }

    let debug_if = setup_debug_interface(&node_config);

    let metrics_port = node_config.debug_interface.metrics_server_port;
    let metric_host = node_config.debug_interface.address.clone();
    thread::spawn(move || metric_server::start_server(metric_host, metrics_port, false));
    let public_metrics_port = node_config.debug_interface.public_metrics_server_port;
    let public_metric_host = node_config.debug_interface.address.clone();
    thread::spawn(move || {
        metric_server::start_server(public_metric_host, public_metrics_port, true)
    });

    // for state sync to send requests to mempool
    let (state_sync_to_mempool_sender, state_sync_requests) =
        channel(INTRA_NODE_CHANNEL_BUFFER_SIZE);
    let state_synchronizer = StateSynchronizer::bootstrap(
        state_sync_network_handles,
        state_sync_to_mempool_sender,
        Arc::clone(&executor),
        &node_config,
        reconfig_event_subscriptions,
    );
    let (mp_client_sender, mp_client_events) = channel(AC_SMP_CHANNEL_BUFFER_SIZE);

    let admission_control_runtime =
        AdmissionControlService::bootstrap(&node_config, mp_client_sender.clone());
    let rpc_runtime = bootstrap_rpc(&node_config, libra_db, mp_client_sender);

    let mut consensus = None;
    let (consensus_to_mempool_sender, consensus_requests) = channel(INTRA_NODE_CHANNEL_BUFFER_SIZE);

    instant = Instant::now();
    let mempool = libra_mempool::bootstrap(
        node_config,
        mempool_network_handles,
        mp_client_events,
        consensus_requests,
        state_sync_requests,
        mempool_reconfig_events,
    );
    debug!("Mempool started in {} ms", instant.elapsed().as_millis());

    if let Some((peer_id, runtime, mut network_builder)) = validator_network_provider {
        // Note: We need to start network provider before consensus, because the consensus
        // initialization is blocked on state synchronizer to sync to the initial root ledger
        // info, which in turn cannot make progress before network initialization
        // because the NewPeer events which state synchronizer uses to know its
        // peers are delivered by network provider. If we were to start network
        // provider after consensus, we create a cyclic dependency from
        // network provider -> consensus -> state synchronizer -> network provider. This deadlock
        // was observed in GitHub Issue #749. A long term fix might be make
        // consensus initialization async instead of blocking on state synchronizer.
        let (consensus_network_sender, consensus_network_events) =
            consensus::network_interface::add_to_network(&mut network_builder);
        let _listen_addr = network_builder.build();
        network_runtimes.push(runtime);
        debug!("Network started for peer_id: {}", peer_id);

        // Make sure that state synchronizer is caught up at least to its waypoint
        // (in case it's present). There is no sense to start consensus prior to that.
        // TODO: Note that we need the networking layer to be able to discover & connect to the
        // peers with potentially outdated network identity public keys.
        debug!("Wait until state synchronizer is initialized");
        block_on(state_synchronizer.wait_until_initialized())
            .expect("State synchronizer initialization failure");
        debug!("State synchronizer initialization complete.");

        // Initialize and start consensus.
        instant = Instant::now();
        let mut consensus_provider = make_consensus_provider(
            node_config,
            consensus_network_sender,
            consensus_network_events,
            executor,
            state_synchronizer.create_client(),
            consensus_to_mempool_sender,
        );
        consensus_provider
            .start()
            .expect("Failed to start consensus. Can't proceed.");
        consensus = Some(consensus_provider);
        debug!("Consensus started in {} ms", instant.elapsed().as_millis());
    }

    LibraHandle {
        _network_runtimes: network_runtimes,
        _ac: admission_control_runtime,
        _rpc: rpc_runtime,
        _mempool: mempool,
        _state_synchronizer: state_synchronizer,
        consensus,
        _storage: storage,
        _debug: debug_if,
    }
}
<commit_msg>[libra node] defer initialization of debug interface<commit_after>// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

use admission_control_service::admission_control_service::AdmissionControlService;
use consensus::consensus_provider::{make_consensus_provider, ConsensusProvider};
use debug_interface::{
    node_debug_service::NodeDebugService,
    proto::node_debug_interface_server::NodeDebugInterfaceServer,
};
use executor::Executor;
use futures::{channel::mpsc::channel, executor::block_on};
use libra_config::config::{NetworkConfig, NodeConfig, RoleType};
use libra_json_rpc::bootstrap_from_config as bootstrap_rpc;
use libra_logger::prelude::*;
use libra_metrics::metric_server;
use libra_vm::LibraVM;
use network::validator_network::network_builder::{NetworkBuilder, TransportType};
use state_synchronizer::StateSynchronizer;
use std::{collections::HashMap, net::ToSocketAddrs, sync::Arc, thread, time::Instant};
use storage_client::{StorageReadServiceClient, StorageWriteServiceClient};
use storage_service::{init_libra_db, start_storage_service_with_db};
use tokio::runtime::{Builder, Runtime};

const AC_SMP_CHANNEL_BUFFER_SIZE: usize = 1_024;
const INTRA_NODE_CHANNEL_BUFFER_SIZE: usize = 1;

pub struct LibraHandle {
    _ac: Runtime,
    _rpc: Runtime,
    _mempool: Runtime,
    _state_synchronizer: StateSynchronizer,
    _network_runtimes: Vec<Runtime>,
    consensus: Option<Box<dyn ConsensusProvider>>,
    _storage: Runtime,
    _debug: Runtime,
}

impl Drop for LibraHandle {
    fn drop(&mut self) {
        if let Some(consensus) = &mut self.consensus {
            consensus.stop();
        }
    }
}

fn setup_executor(config: &NodeConfig) -> Arc<Executor<LibraVM>> {
    let storage_read_client = Arc::new(StorageReadServiceClient::new(&config.storage.address));
    let storage_write_client = Arc::new(StorageWriteServiceClient::new(&config.storage.address));

    Arc::new(Executor::new(
        storage_read_client,
        storage_write_client,
        config,
    ))
}

fn setup_debug_interface(config: &NodeConfig) -> Runtime {
    let rt = tokio::runtime::Runtime::new().unwrap();
    let addr = format!(
        "{}:{}",
        config.debug_interface.address, config.debug_interface.admission_control_node_debug_port,
    )
    .to_socket_addrs()
    .unwrap()
    .next()
    .unwrap();
    rt.spawn(
        tonic::transport::Server::builder()
            .add_service(NodeDebugInterfaceServer::new(NodeDebugService::new()))
            .serve(addr),
    );
    rt
}

// TODO(abhayb): Move to network crate (similar to consensus).
pub fn setup_network(config: &mut NetworkConfig, role: RoleType) -> (Runtime, NetworkBuilder) {
    let runtime = Builder::new()
        .thread_name("network-")
        .threaded_scheduler()
        .enable_all()
        .build()
        .expect("Failed to start runtime. Won't be able to start networking.");
    let mut network_builder = NetworkBuilder::new(
        runtime.handle().clone(),
        config.peer_id,
        config.listen_address.clone(),
        role,
    );
    network_builder
        .enable_remote_authentication(config.enable_remote_authentication)
        .advertised_address(config.advertised_address.clone())
        .add_connection_monitoring();
    if config.enable_remote_authentication {
        // If the node wants to run in permissioned mode, it should also have authentication and
        // encryption.
        assert!(
            config.enable_noise,
            "Permissioned network end-points should use authentication"
        );
        let seed_peers = config.seed_peers.seed_peers.clone();
        let network_keypairs = config
            .network_keypairs
            .as_mut()
            .expect("Network keypairs are not defined");
        let signing_keys = &mut network_keypairs.signing_keys;
        let identity_keys = &mut network_keypairs.identity_keys;

        let signing_private = signing_keys
            .take_private()
            .expect("Failed to take Network signing private key, key absent or already read");
        let signing_public = signing_keys.public().clone();
        let identity_private = identity_keys
            .take_private()
            .expect("Failed to take Network identity private key, key absent or already read");
        let identity_public = identity_keys.public().clone();
        let trusted_peers = if role == RoleType::Validator {
            // for validators, trusted_peers is empty will be populated from consensus
            HashMap::new()
        } else {
            config.network_peers.peers.clone()
        };
        network_builder
            .transport(TransportType::TcpNoise(Some((
                identity_private,
                identity_public,
            ))))
            .connectivity_check_interval_ms(config.connectivity_check_interval_ms)
            .seed_peers(seed_peers)
            .trusted_peers(trusted_peers)
            .signing_keys((signing_private, signing_public))
            .discovery_interval_ms(config.discovery_interval_ms)
            .add_discovery();
    } else if config.enable_noise {
        let identity_keys = &mut config
            .network_keypairs
            .as_mut()
            .expect("Network keypairs are not defined")
            .identity_keys;
        let identity_private = identity_keys
            .take_private()
            .expect("Failed to take Network identity private key, key absent or already read");
        let identity_public = identity_keys.public().clone();
        // Even if a network end-point operates without remote authentication, it might want to prove
        // its identity to another peer it connects to. For this, we use TCP + Noise but without
        // enforcing a trusted peers set.
        network_builder.transport(TransportType::TcpNoise(Some((
            identity_private,
            identity_public,
        ))));
    } else {
        network_builder.transport(TransportType::Tcp);
    }
    (runtime, network_builder)
}

pub fn setup_environment(node_config: &mut NodeConfig) -> LibraHandle {
    crash_handler::setup_panic_handler();

    // Some of our code uses the rayon global thread pool. Name the rayon threads so it doesn't
    // cause confusion, otherwise the threads would have their parent's name.
    rayon::ThreadPoolBuilder::new()
        .thread_name(|index| format!("rayon-global-{}", index))
        .build_global()
        .expect("Building rayon global thread pool should work.");

    let mut instant = Instant::now();
    let libra_db = init_libra_db(&node_config);
    let storage = start_storage_service_with_db(&node_config, Arc::clone(&libra_db));

    debug!(
        "Storage service started in {} ms",
        instant.elapsed().as_millis()
    );

    instant = Instant::now();
    let executor = setup_executor(&node_config);
    debug!("Executor setup in {} ms", instant.elapsed().as_millis());
    let mut network_runtimes = vec![];
    let mut state_sync_network_handles = vec![];
    let mut mempool_network_handles = vec![];
    let mut validator_network_provider = None;
    let mut reconfig_event_subscriptions = vec![];
    let (mempool_reconfig_subscription, mempool_reconfig_events) =
        libra_mempool::generate_reconfig_subscription();
    reconfig_event_subscriptions.push(mempool_reconfig_subscription);

    if let Some(network) = node_config.validator_network.as_mut() {
        let (runtime, mut network_builder) = setup_network(network, RoleType::Validator);
        state_sync_network_handles.push(state_synchronizer::network::add_to_network(
            &mut network_builder,
        ));

        let (mempool_sender, mempool_events) =
            libra_mempool::network::add_to_network(&mut network_builder);
        mempool_network_handles.push((network.peer_id, mempool_sender, mempool_events));
        validator_network_provider = Some((network.peer_id, runtime, network_builder));
    }

    for mut full_node_network in node_config.full_node_networks.iter_mut() {
        let (runtime, mut network_builder) =
            setup_network(&mut full_node_network, RoleType::FullNode);

        network_runtimes.push(runtime);
        state_sync_network_handles.push(state_synchronizer::network::add_to_network(
            &mut network_builder,
        ));
        let (mempool_sender, mempool_events) =
            libra_mempool::network::add_to_network(&mut network_builder);
        mempool_network_handles.push((full_node_network.peer_id, mempool_sender, mempool_events));

        // Start the network provider.
        let _listen_addr = network_builder.build();
        debug!("Network started for peer_id: {}", full_node_network.peer_id);
    }

    // for state sync to send requests to mempool
    let (state_sync_to_mempool_sender, state_sync_requests) =
        channel(INTRA_NODE_CHANNEL_BUFFER_SIZE);
    let state_synchronizer = StateSynchronizer::bootstrap(
        state_sync_network_handles,
        state_sync_to_mempool_sender,
        Arc::clone(&executor),
        &node_config,
        reconfig_event_subscriptions,
    );
    let (mp_client_sender, mp_client_events) = channel(AC_SMP_CHANNEL_BUFFER_SIZE);

    let admission_control_runtime =
        AdmissionControlService::bootstrap(&node_config, mp_client_sender.clone());
    let rpc_runtime = bootstrap_rpc(&node_config, libra_db, mp_client_sender);

    let mut consensus = None;
    let (consensus_to_mempool_sender, consensus_requests) = channel(INTRA_NODE_CHANNEL_BUFFER_SIZE);

    instant = Instant::now();
    let mempool = libra_mempool::bootstrap(
        node_config,
        mempool_network_handles,
        mp_client_events,
        consensus_requests,
        state_sync_requests,
        mempool_reconfig_events,
    );
    debug!("Mempool started in {} ms", instant.elapsed().as_millis());

    if let Some((peer_id, runtime, mut network_builder)) = validator_network_provider {
        // Note: We need to start network provider before consensus, because the consensus
        // initialization is blocked on state synchronizer to sync to the initial root ledger
        // info, which in turn cannot make progress before network initialization
        // because the NewPeer events which state synchronizer uses to know its
        // peers are delivered by network provider. If we were to start network
        // provider after consensus, we create a cyclic dependency from
        // network provider -> consensus -> state synchronizer -> network provider. This deadlock
        // was observed in GitHub Issue #749. A long term fix might be make
        // consensus initialization async instead of blocking on state synchronizer.
        let (consensus_network_sender, consensus_network_events) =
            consensus::network_interface::add_to_network(&mut network_builder);
        let _listen_addr = network_builder.build();
        network_runtimes.push(runtime);
        debug!("Network started for peer_id: {}", peer_id);

        // Make sure that state synchronizer is caught up at least to its waypoint
        // (in case it's present). There is no sense to start consensus prior to that.
        // TODO: Note that we need the networking layer to be able to discover & connect to the
        // peers with potentially outdated network identity public keys.
        debug!("Wait until state synchronizer is initialized");
        block_on(state_synchronizer.wait_until_initialized())
            .expect("State synchronizer initialization failure");
        debug!("State synchronizer initialization complete.");

        // Initialize and start consensus.
        instant = Instant::now();
        let mut consensus_provider = make_consensus_provider(
            node_config,
            consensus_network_sender,
            consensus_network_events,
            executor,
            state_synchronizer.create_client(),
            consensus_to_mempool_sender,
        );
        consensus_provider
            .start()
            .expect("Failed to start consensus. Can't proceed.");
        consensus = Some(consensus_provider);
        debug!("Consensus started in {} ms", instant.elapsed().as_millis());
    }

    let debug_if = setup_debug_interface(&node_config);

    let metrics_port = node_config.debug_interface.metrics_server_port;
    let metric_host = node_config.debug_interface.address.clone();
    thread::spawn(move || metric_server::start_server(metric_host, metrics_port, false));
    let public_metrics_port = node_config.debug_interface.public_metrics_server_port;
    let public_metric_host = node_config.debug_interface.address.clone();
    thread::spawn(move || {
        metric_server::start_server(public_metric_host, public_metrics_port, true)
    });

    LibraHandle {
        _network_runtimes: network_runtimes,
        _ac: admission_control_runtime,
        _rpc: rpc_runtime,
        _mempool: mempool,
        _state_synchronizer: state_synchronizer,
        consensus,
        _storage: storage,
        _debug: debug_if,
    }
}
<|endoftext|>


<commit_before>extern crate libnotify_sys as libnotify;
extern crate glib_2_0_sys as glib;

fn main() {
    unsafe {
        use libnotify::*;
        use std::mem::transmute as tm;

        if notify_init(tm("hello\0".as_ptr())) == 0 {
            panic!("Failed to init libnotify");
        }

        let notif = notify_notification_new(tm("Hello\0".as_ptr()),
                                            tm("World\0".as_ptr()),
                                            0 as *const i8);
        let mut error = 0 as *mut glib::GError;
        notify_notification_show(notif, &mut error);
        if error != 0 as *mut glib::GError {
            glib::g_error_free(error);
        }

        notify_uninit();
    }
}
<commit_msg>Make example more idiomatic<commit_after>extern crate libnotify_sys as libnotify;
extern crate glib_2_0_sys as glib;

fn main() {
    unsafe {
        use libnotify::*;
        use std::os::raw::c_char;
        use std::ptr;

        if notify_init("hello\0".as_ptr() as *const c_char) == 0 {
            panic!("Failed to init libnotify");
        }

        let notif = notify_notification_new("Hello\0".as_ptr() as *const c_char,
                                            "World\0".as_ptr() as *const c_char,
                                            ptr::null());
        let mut error = ptr::null_mut();
        notify_notification_show(notif, &mut error);

        if error != ptr::null_mut() {
            glib::g_error_free(error);
        }

        notify_uninit();
    }
}
<|endoftext|>