<commit_before>use geo::Point;
use std::cmp;

use codearea::CodeArea;

use consts::{
    CODE_ALPHABET, ENCODING_BASE, GRID_CODE_LENGTH, GRID_COLUMNS, GRID_ROWS, LATITUDE_MAX,
    LAT_INTEGER_MULTIPLIER, LNG_INTEGER_MULTIPLIER, LONGITUDE_MAX, MAX_CODE_LENGTH,
    MIN_TRIMMABLE_CODE_LEN, PADDING_CHAR, PADDING_CHAR_STR, PAIR_CODE_LENGTH, PAIR_RESOLUTIONS,
    SEPARATOR, SEPARATOR_POSITION,
};

use private::{
    clip_latitude, code_value, compute_latitude_precision, normalize_longitude, prefix_by_reference,
};

/// Determines if a code is a valid Open Location Code.
pub fn is_valid(_code: &str) -> bool {
    let mut code: String = _code.to_string();
    if code.len() < 3 {
        // A code must have at-least a separator character + 1 lat/lng pair
        return false;
    }

    // Validate separator character
    if code.find(SEPARATOR).is_none() {
        // The code MUST contain a separator character
        return false;
    }
    if code.find(SEPARATOR) != code.rfind(SEPARATOR) {
        // .. And only one separator character
        return false;
    }
    let spos = code.find(SEPARATOR).unwrap();
    if spos % 2 == 1 || spos > SEPARATOR_POSITION {
        // The separator must be in a valid location
        return false;
    }
    if code.len() - spos - 1 == 1 {
        // There must be > 1 character after the separator
        return false;
    }

    // Validate padding
    let padstart = code.find(PADDING_CHAR);
    if padstart.is_some() {
        if spos < SEPARATOR_POSITION {
            // Short codes cannot have padding
            return false;
        }
        let ppos = padstart.unwrap();
        if ppos == 0 || ppos % 2 == 1 {
            // Padding must be "within" the string, starting at an even position
            return false;
        }
        if code.len() > spos + 1 {
            // If there is padding, the code must end with the separator char
            return false;
        }
        let eppos = code.rfind(PADDING_CHAR).unwrap();
        if eppos - ppos % 2 == 1 {
            // Must have even number of padding chars
            return false;
        }
        // Extract the padding from the code (mutates code)
        let padding: String = code.drain(ppos..eppos + 1).collect();
        if padding.chars().any(|c| c != PADDING_CHAR) {
            // Padding must be one, contiguous block of padding chars
            return false;
        }
    }

    // Validate all characters are permissible
    code.chars()
        .map(|c| c.to_ascii_uppercase())
        .all(|c| c == SEPARATOR || CODE_ALPHABET.contains(&c))
}

/// Determines if a code is a valid short code.
///
/// A short Open Location Code is a sequence created by removing four or more
/// digits from an Open Location Code. It must include a separator character.
pub fn is_short(_code: &str) -> bool {
    is_valid(_code) && _code.find(SEPARATOR).unwrap() < SEPARATOR_POSITION
}

/// Determines if a code is a valid full Open Location Code.
///
/// Not all possible combinations of Open Location Code characters decode to
/// valid latitude and longitude values. This checks that a code is valid
/// and also that the latitude and longitude values are legal. If the prefix
/// character is present, it must be the first character. If the separator
/// character is present, it must be after four characters.
pub fn is_full(_code: &str) -> bool {
    is_valid(_code) && !is_short(_code)
}

/// Encode a location into an Open Location Code.
///
/// Produces a code of the specified length, or the default length if no
/// length is provided.
/// The length determines the accuracy of the code. The default length is
/// 10 characters, returning a code of approximately 13.5x13.5 meters. Longer
/// codes represent smaller areas, but lengths > 14 are sub-centimetre and so
/// 11 or 12 are probably the limit of useful codes.
pub fn encode(pt: Point<f64>, code_length: usize) -> String {
    let mut lat = clip_latitude(pt.lat());
    let lng = normalize_longitude(pt.lng());

    let trimmed_code_length = cmp::min(code_length, MAX_CODE_LENGTH);

    // Latitude 90 needs to be adjusted to be just less, so the returned code
    // can also be decoded.
    if lat > LATITUDE_MAX || (LATITUDE_MAX - lat) < 1e-10f64 {
        lat -= compute_latitude_precision(trimmed_code_length);
    }

    // Convert to integers.
    let mut lat_val =
        (((lat + LATITUDE_MAX) * LAT_INTEGER_MULTIPLIER as f64 * 1e6).round() / 1e6f64) as i64;
    let mut lng_val =
        (((lng + LONGITUDE_MAX) * LNG_INTEGER_MULTIPLIER as f64 * 1e6).round() / 1e6f64) as i64;

    // Compute the code digits. This largely ignores the requested length - it
    // generates either a 10 digit code, or a 15 digit code, and then truncates
    // it to the requested length.

    // Build up the code digits in reverse order.
    let mut rev_code = String::with_capacity(trimmed_code_length + 1);

    // First do the grid digits.
    if code_length > PAIR_CODE_LENGTH {
        for _i in 0..GRID_CODE_LENGTH {
            let lat_digit = lat_val % GRID_ROWS as i64;
            let lng_digit = lng_val % GRID_COLUMNS as i64;
            let ndx = (lat_digit * GRID_COLUMNS as i64 + lng_digit) as usize;
            rev_code.push(CODE_ALPHABET[ndx]);
            lat_val /= GRID_ROWS as i64;
            lng_val /= GRID_COLUMNS as i64;
        }
    } else {
        // Adjust latitude and longitude values to skip the grid digits.
        lat_val /= GRID_ROWS.pow(GRID_CODE_LENGTH as u32) as i64;
        lng_val /= GRID_COLUMNS.pow(GRID_CODE_LENGTH as u32) as i64;
    }
    // Compute the pair section of the code.
    for i in 0..PAIR_CODE_LENGTH / 2 {
        rev_code.push(CODE_ALPHABET[(lng_val % ENCODING_BASE as i64) as usize]);
        lng_val /= ENCODING_BASE as i64;
        rev_code.push(CODE_ALPHABET[(lat_val % ENCODING_BASE as i64) as usize]);
        lat_val /= ENCODING_BASE as i64;
        // If we are at the separator position, add the separator.
        if i == 0 {
            rev_code.push(SEPARATOR);
        }
    }
    let mut code: String;
    // If we need to pad the code, replace some of the digits.
    if code_length < SEPARATOR_POSITION {
        code = rev_code.chars().rev().take(code_length).collect();
        code.push_str(
            PADDING_CHAR_STR
                .repeat(SEPARATOR_POSITION - code_length)
                .as_str(),
        );
        code.push(SEPARATOR);
    } else {
        code = rev_code.chars().rev().take(code_length + 1).collect();
    }

    return code;
}

/// Decodes an Open Location Code into the location coordinates.
///
/// Returns a CodeArea object that includes the coordinates of the bounding
/// box - the lower left, center and upper right.
pub fn decode(_code: &str) -> Result<CodeArea, String> {
    if !is_full(_code) {
        return Err(format!("Code must be a valid full code: {}", _code));
    }
    let mut code = _code
        .to_string()
        .replace(SEPARATOR, "")
        .replace(PADDING_CHAR_STR, "")
        .to_uppercase();
    if code.len() > MAX_CODE_LENGTH {
        code = code.chars().take(MAX_CODE_LENGTH).collect();
    }

    // Work out the values as integers and convert to floating point at the end.
    let mut lat: i64 = -90 * LAT_INTEGER_MULTIPLIER;
    let mut lng: i64 = -180 * LNG_INTEGER_MULTIPLIER;
    let mut lat_place_val: i64 = LAT_INTEGER_MULTIPLIER * ENCODING_BASE.pow(2) as i64;
    let mut lng_place_val: i64 = LNG_INTEGER_MULTIPLIER * ENCODING_BASE.pow(2) as i64;

    for (idx, chr) in code.chars().enumerate() {
        if idx < PAIR_CODE_LENGTH {
            if idx % 2 == 0 {
                lat_place_val /= ENCODING_BASE as i64;
                lat += lat_place_val * code_value(chr) as i64;
            } else {
                lng_place_val /= ENCODING_BASE as i64;
                lng += lng_place_val * code_value(chr) as i64;
            }
        } else {
            lat_place_val /= GRID_ROWS as i64;
            lng_place_val /= GRID_COLUMNS as i64;
            lat += lat_place_val * (code_value(chr) / GRID_COLUMNS) as i64;
            lng += lng_place_val * (code_value(chr) % GRID_COLUMNS) as i64;
        }
    }
    // Convert to floating point values.
    let lat_lo: f64 = lat as f64 / LAT_INTEGER_MULTIPLIER as f64;
    let lng_lo: f64 = lng as f64 / LNG_INTEGER_MULTIPLIER as f64;
    let lat_hi: f64 =
        (lat + lat_place_val) as f64 / (ENCODING_BASE.pow(3) * GRID_ROWS.pow(5)) as f64;
    let lng_hi: f64 =
        (lng + lng_place_val) as f64 / (ENCODING_BASE.pow(3) * GRID_COLUMNS.pow(5)) as f64;
    Ok(CodeArea::new(lat_lo, lng_lo, lat_hi, lng_hi, code.len()))
}

/// Remove characters from the start of an OLC code.
///
/// This uses a reference location to determine how many initial characters
/// can be removed from the OLC code. The number of characters that can be
/// removed depends on the distance between the code center and the reference
/// location.
/// The minimum number of characters that will be removed is four. If more
/// than four characters can be removed, the additional characters will be
/// replaced with the padding character. At most eight characters will be
/// removed.
/// The reference location must be within 50% of the maximum range. This
/// ensures that the shortened code will be able to be recovered using
/// slightly different locations.
///
/// It returns either the original code, if the reference location was not
/// close enough, or the .
pub fn shorten(_code: &str, ref_pt: Point<f64>) -> Result<String, String> {
    if !is_full(_code) {
        return Ok(_code.to_string());
    }
    if _code.find(PADDING_CHAR).is_some() {
        return Err("Cannot shorten padded codes".to_owned());
    }

    let codearea: CodeArea = decode(_code).unwrap();
    if codearea.code_length < MIN_TRIMMABLE_CODE_LEN {
        return Err(format!(
            "Code length must be at least {}",
            MIN_TRIMMABLE_CODE_LEN
        ));
    }

    // How close are the latitude and longitude to the code center.
    let range = (codearea.center.lat() - clip_latitude(ref_pt.lat()))
        .abs()
        .max((codearea.center.lng() - normalize_longitude(ref_pt.lng())).abs());

    for i in 0..PAIR_RESOLUTIONS.len() - 2 {
        // Check if we're close enough to shorten. The range must be less than 1/2
        // the resolution to shorten at all, and we want to allow some safety, so
        // use 0.3 instead of 0.5 as a multiplier.
        let idx = PAIR_RESOLUTIONS.len() - 2 - i;
        if range < (PAIR_RESOLUTIONS[idx] * 0.3f64) {
            let mut code = _code.to_string();
            code.drain(..((idx + 1) * 2));
            return Ok(code);
        }
    }
    Ok(_code.to_string())
}

/// Recover the nearest matching code to a specified location.
///
/// Given a short Open Location Code of between four and seven characters,
/// this recovers the nearest matching full code to the specified location.
/// The number of characters that will be prepended to the short code, depends
/// on the length of the short code and whether it starts with the separator.
/// If it starts with the separator, four characters will be prepended. If it
/// does not, the characters that will be prepended to the short code, where S
/// is the supplied short code and R are the computed characters, are as
/// follows:
///
/// * SSSS    -> RRRR.RRSSSS
/// * SSSSS   -> RRRR.RRSSSSS
/// * SSSSSS  -> RRRR.SSSSSS
/// * SSSSSSS -> RRRR.SSSSSSS
///
/// Note that short codes with an odd number of characters will have their
/// last character decoded using the grid refinement algorithm.
///
/// It returns the nearest full Open Location Code to the reference location
/// that matches the [shortCode]. Note that the returned code may not have the
/// same computed characters as the reference location (provided by
/// [referenceLatitude] and [referenceLongitude]). This is because it returns
/// the nearest match, not necessarily the match within the same cell. If the
/// passed code was not a valid short code, but was a valid full code, it is
/// returned unchanged.
pub fn recover_nearest(_code: &str, ref_pt: Point<f64>) -> Result<String, String> {
    if !is_short(_code) {
        if is_full(_code) {
            return Ok(_code.to_string().to_uppercase());
        } else {
            return Err(format!("Passed short code is not valid: {}", _code));
        }
    }

    let prefix_len = SEPARATOR_POSITION - _code.find(SEPARATOR).unwrap();
    let mut code = prefix_by_reference(ref_pt, prefix_len);
    code.push_str(_code);

    let code_area = decode(code.as_str()).unwrap();

    let resolution = compute_latitude_precision(prefix_len);
    let half_res = resolution / 2f64;

    let mut latitude = code_area.center.lat();
    let mut longitude = code_area.center.lng();

    let ref_lat = clip_latitude(ref_pt.lat());
    let ref_lng = normalize_longitude(ref_pt.lng());
    if ref_lat + half_res < latitude && latitude - resolution >= -LATITUDE_MAX {
        latitude -= resolution;
    } else if ref_lat - half_res > latitude && latitude + resolution <= LATITUDE_MAX {
        latitude += resolution;
    }
    if ref_lng + half_res < longitude {
        longitude -= resolution;
    } else if ref_lng - half_res > longitude {
        longitude += resolution;
    }
    Ok(encode(
        Point::new(longitude, latitude),
        code_area.code_length,
    ))
}
<commit_msg>Make argument names more idiomatic<commit_after>use geo::Point;
use std::cmp;

use codearea::CodeArea;

use consts::{
    CODE_ALPHABET, ENCODING_BASE, GRID_CODE_LENGTH, GRID_COLUMNS, GRID_ROWS, LATITUDE_MAX,
    LAT_INTEGER_MULTIPLIER, LNG_INTEGER_MULTIPLIER, LONGITUDE_MAX, MAX_CODE_LENGTH,
    MIN_TRIMMABLE_CODE_LEN, PADDING_CHAR, PADDING_CHAR_STR, PAIR_CODE_LENGTH, PAIR_RESOLUTIONS,
    SEPARATOR, SEPARATOR_POSITION,
};

use private::{
    clip_latitude, code_value, compute_latitude_precision, normalize_longitude, prefix_by_reference,
};

/// Determines if a code is a valid Open Location Code.
pub fn is_valid(code: &str) -> bool {
    let mut code: String = code.to_string();
    if code.len() < 3 {
        // A code must have at-least a separator character + 1 lat/lng pair
        return false;
    }

    // Validate separator character
    if code.find(SEPARATOR).is_none() {
        // The code MUST contain a separator character
        return false;
    }
    if code.find(SEPARATOR) != code.rfind(SEPARATOR) {
        // .. And only one separator character
        return false;
    }
    let spos = code.find(SEPARATOR).unwrap();
    if spos % 2 == 1 || spos > SEPARATOR_POSITION {
        // The separator must be in a valid location
        return false;
    }
    if code.len() - spos - 1 == 1 {
        // There must be > 1 character after the separator
        return false;
    }

    // Validate padding
    let padstart = code.find(PADDING_CHAR);
    if padstart.is_some() {
        if spos < SEPARATOR_POSITION {
            // Short codes cannot have padding
            return false;
        }
        let ppos = padstart.unwrap();
        if ppos == 0 || ppos % 2 == 1 {
            // Padding must be "within" the string, starting at an even position
            return false;
        }
        if code.len() > spos + 1 {
            // If there is padding, the code must end with the separator char
            return false;
        }
        let eppos = code.rfind(PADDING_CHAR).unwrap();
        if eppos - ppos % 2 == 1 {
            // Must have even number of padding chars
            return false;
        }
        // Extract the padding from the code (mutates code)
        let padding: String = code.drain(ppos..eppos + 1).collect();
        if padding.chars().any(|c| c != PADDING_CHAR) {
            // Padding must be one, contiguous block of padding chars
            return false;
        }
    }

    // Validate all characters are permissible
    code.chars()
        .map(|c| c.to_ascii_uppercase())
        .all(|c| c == SEPARATOR || CODE_ALPHABET.contains(&c))
}

/// Determines if a code is a valid short code.
///
/// A short Open Location Code is a sequence created by removing four or more
/// digits from an Open Location Code. It must include a separator character.
pub fn is_short(code: &str) -> bool {
    is_valid(code) && code.find(SEPARATOR).unwrap() < SEPARATOR_POSITION
}

/// Determines if a code is a valid full Open Location Code.
///
/// Not all possible combinations of Open Location Code characters decode to
/// valid latitude and longitude values. This checks that a code is valid
/// and also that the latitude and longitude values are legal. If the prefix
/// character is present, it must be the first character. If the separator
/// character is present, it must be after four characters.
pub fn is_full(code: &str) -> bool {
    is_valid(code) && !is_short(code)
}

/// Encode a location into an Open Location Code.
///
/// Produces a code of the specified length, or the default length if no
/// length is provided.
/// The length determines the accuracy of the code. The default length is
/// 10 characters, returning a code of approximately 13.5x13.5 meters. Longer
/// codes represent smaller areas, but lengths > 14 are sub-centimetre and so
/// 11 or 12 are probably the limit of useful codes.
pub fn encode(pt: Point<f64>, code_length: usize) -> String {
    let mut lat = clip_latitude(pt.lat());
    let lng = normalize_longitude(pt.lng());

    let trimmed_code_length = cmp::min(code_length, MAX_CODE_LENGTH);

    // Latitude 90 needs to be adjusted to be just less, so the returned code
    // can also be decoded.
    if lat > LATITUDE_MAX || (LATITUDE_MAX - lat) < 1e-10f64 {
        lat -= compute_latitude_precision(trimmed_code_length);
    }

    // Convert to integers.
    let mut lat_val =
        (((lat + LATITUDE_MAX) * LAT_INTEGER_MULTIPLIER as f64 * 1e6).round() / 1e6f64) as i64;
    let mut lng_val =
        (((lng + LONGITUDE_MAX) * LNG_INTEGER_MULTIPLIER as f64 * 1e6).round() / 1e6f64) as i64;

    // Compute the code digits. This largely ignores the requested length - it
    // generates either a 10 digit code, or a 15 digit code, and then truncates
    // it to the requested length.

    // Build up the code digits in reverse order.
    let mut rev_code = String::with_capacity(trimmed_code_length + 1);

    // First do the grid digits.
    if code_length > PAIR_CODE_LENGTH {
        for _i in 0..GRID_CODE_LENGTH {
            let lat_digit = lat_val % GRID_ROWS as i64;
            let lng_digit = lng_val % GRID_COLUMNS as i64;
            let ndx = (lat_digit * GRID_COLUMNS as i64 + lng_digit) as usize;
            rev_code.push(CODE_ALPHABET[ndx]);
            lat_val /= GRID_ROWS as i64;
            lng_val /= GRID_COLUMNS as i64;
        }
    } else {
        // Adjust latitude and longitude values to skip the grid digits.
        lat_val /= GRID_ROWS.pow(GRID_CODE_LENGTH as u32) as i64;
        lng_val /= GRID_COLUMNS.pow(GRID_CODE_LENGTH as u32) as i64;
    }
    // Compute the pair section of the code.
    for i in 0..PAIR_CODE_LENGTH / 2 {
        rev_code.push(CODE_ALPHABET[(lng_val % ENCODING_BASE as i64) as usize]);
        lng_val /= ENCODING_BASE as i64;
        rev_code.push(CODE_ALPHABET[(lat_val % ENCODING_BASE as i64) as usize]);
        lat_val /= ENCODING_BASE as i64;
        // If we are at the separator position, add the separator.
        if i == 0 {
            rev_code.push(SEPARATOR);
        }
    }
    let mut code: String;
    // If we need to pad the code, replace some of the digits.
    if code_length < SEPARATOR_POSITION {
        code = rev_code.chars().rev().take(code_length).collect();
        code.push_str(
            PADDING_CHAR_STR
                .repeat(SEPARATOR_POSITION - code_length)
                .as_str(),
        );
        code.push(SEPARATOR);
    } else {
        code = rev_code.chars().rev().take(code_length + 1).collect();
    }

    return code;
}

/// Decodes an Open Location Code into the location coordinates.
///
/// Returns a CodeArea object that includes the coordinates of the bounding
/// box - the lower left, center and upper right.
pub fn decode(code: &str) -> Result<CodeArea, String> {
    if !is_full(code) {
        return Err(format!("Code must be a valid full code: {}", code));
    }
    let mut code = code
        .to_string()
        .replace(SEPARATOR, "")
        .replace(PADDING_CHAR_STR, "")
        .to_uppercase();
    if code.len() > MAX_CODE_LENGTH {
        code = code.chars().take(MAX_CODE_LENGTH).collect();
    }

    // Work out the values as integers and convert to floating point at the end.
    let mut lat: i64 = -90 * LAT_INTEGER_MULTIPLIER;
    let mut lng: i64 = -180 * LNG_INTEGER_MULTIPLIER;
    let mut lat_place_val: i64 = LAT_INTEGER_MULTIPLIER * ENCODING_BASE.pow(2) as i64;
    let mut lng_place_val: i64 = LNG_INTEGER_MULTIPLIER * ENCODING_BASE.pow(2) as i64;

    for (idx, chr) in code.chars().enumerate() {
        if idx < PAIR_CODE_LENGTH {
            if idx % 2 == 0 {
                lat_place_val /= ENCODING_BASE as i64;
                lat += lat_place_val * code_value(chr) as i64;
            } else {
                lng_place_val /= ENCODING_BASE as i64;
                lng += lng_place_val * code_value(chr) as i64;
            }
        } else {
            lat_place_val /= GRID_ROWS as i64;
            lng_place_val /= GRID_COLUMNS as i64;
            lat += lat_place_val * (code_value(chr) / GRID_COLUMNS) as i64;
            lng += lng_place_val * (code_value(chr) % GRID_COLUMNS) as i64;
        }
    }
    // Convert to floating point values.
    let lat_lo: f64 = lat as f64 / LAT_INTEGER_MULTIPLIER as f64;
    let lng_lo: f64 = lng as f64 / LNG_INTEGER_MULTIPLIER as f64;
    let lat_hi: f64 =
        (lat + lat_place_val) as f64 / (ENCODING_BASE.pow(3) * GRID_ROWS.pow(5)) as f64;
    let lng_hi: f64 =
        (lng + lng_place_val) as f64 / (ENCODING_BASE.pow(3) * GRID_COLUMNS.pow(5)) as f64;
    Ok(CodeArea::new(lat_lo, lng_lo, lat_hi, lng_hi, code.len()))
}

/// Remove characters from the start of an OLC code.
///
/// This uses a reference location to determine how many initial characters
/// can be removed from the OLC code. The number of characters that can be
/// removed depends on the distance between the code center and the reference
/// location.
/// The minimum number of characters that will be removed is four. If more
/// than four characters can be removed, the additional characters will be
/// replaced with the padding character. At most eight characters will be
/// removed.
/// The reference location must be within 50% of the maximum range. This
/// ensures that the shortened code will be able to be recovered using
/// slightly different locations.
///
/// It returns either the original code, if the reference location was not
/// close enough, or the .
pub fn shorten(code: &str, ref_pt: Point<f64>) -> Result<String, String> {
    if !is_full(code) {
        return Ok(code.to_string());
    }
    if code.find(PADDING_CHAR).is_some() {
        return Err("Cannot shorten padded codes".to_owned());
    }

    let codearea: CodeArea = decode(code).unwrap();
    if codearea.code_length < MIN_TRIMMABLE_CODE_LEN {
        return Err(format!(
            "Code length must be at least {}",
            MIN_TRIMMABLE_CODE_LEN
        ));
    }

    // How close are the latitude and longitude to the code center.
    let range = (codearea.center.lat() - clip_latitude(ref_pt.lat()))
        .abs()
        .max((codearea.center.lng() - normalize_longitude(ref_pt.lng())).abs());

    for i in 0..PAIR_RESOLUTIONS.len() - 2 {
        // Check if we're close enough to shorten. The range must be less than 1/2
        // the resolution to shorten at all, and we want to allow some safety, so
        // use 0.3 instead of 0.5 as a multiplier.
        let idx = PAIR_RESOLUTIONS.len() - 2 - i;
        if range < (PAIR_RESOLUTIONS[idx] * 0.3f64) {
            let mut code = code.to_string();
            code.drain(..((idx + 1) * 2));
            return Ok(code);
        }
    }
    Ok(code.to_string())
}

/// Recover the nearest matching code to a specified location.
///
/// Given a short Open Location Code of between four and seven characters,
/// this recovers the nearest matching full code to the specified location.
/// The number of characters that will be prepended to the short code, depends
/// on the length of the short code and whether it starts with the separator.
/// If it starts with the separator, four characters will be prepended. If it
/// does not, the characters that will be prepended to the short code, where S
/// is the supplied short code and R are the computed characters, are as
/// follows:
///
/// * SSSS    -> RRRR.RRSSSS
/// * SSSSS   -> RRRR.RRSSSSS
/// * SSSSSS  -> RRRR.SSSSSS
/// * SSSSSSS -> RRRR.SSSSSSS
///
/// Note that short codes with an odd number of characters will have their
/// last character decoded using the grid refinement algorithm.
///
/// It returns the nearest full Open Location Code to the reference location
/// that matches the [shortCode]. Note that the returned code may not have the
/// same computed characters as the reference location (provided by
/// [referenceLatitude] and [referenceLongitude]). This is because it returns
/// the nearest match, not necessarily the match within the same cell. If the
/// passed code was not a valid short code, but was a valid full code, it is
/// returned unchanged.
pub fn recover_nearest(code: &str, ref_pt: Point<f64>) -> Result<String, String> {
    if !is_short(code) {
        if is_full(code) {
            return Ok(code.to_string().to_uppercase());
        } else {
            return Err(format!("Passed short code is not valid: {}", code));
        }
    }

    let prefix_len = SEPARATOR_POSITION - code.find(SEPARATOR).unwrap();
    let mut code = prefix_by_reference(ref_pt, prefix_len) + code;

    let code_area = decode(code.as_str()).unwrap();

    let resolution = compute_latitude_precision(prefix_len);
    let half_res = resolution / 2f64;

    let mut latitude = code_area.center.lat();
    let mut longitude = code_area.center.lng();

    let ref_lat = clip_latitude(ref_pt.lat());
    let ref_lng = normalize_longitude(ref_pt.lng());
    if ref_lat + half_res < latitude && latitude - resolution >= -LATITUDE_MAX {
        latitude -= resolution;
    } else if ref_lat - half_res > latitude && latitude + resolution <= LATITUDE_MAX {
        latitude += resolution;
    }
    if ref_lng + half_res < longitude {
        longitude -= resolution;
    } else if ref_lng - half_res > longitude {
        longitude += resolution;
    }
    Ok(encode(
        Point::new(longitude, latitude),
        code_area.code_length,
    ))
}
<|endoftext|>


<commit_before>use russell_lab::{Matrix, Vector};

/// Defines a hexahedron with 8 nodes (bilinear faces)
///
/// The reference coordinates range from -1 to +1 with the geometry centred @ 0.
///
/// # Local IDs of nodes
///
/// ```text
///           4________________7
///         ,'|              ,'|
///       ,'  |            ,'  |             r     s     t
///     ,'    |          ,'    |     p:0 [-1.0, -1.0, -1.0]
///   ,'      |        ,'      |     p:1 [ 1.0, -1.0, -1.0]
/// 5'===============6'        |     p:2 [ 1.0,  1.0, -1.0]
/// |         |      |         |     p:3 [-1.0,  1.0, -1.0]
/// |         |      |         |     p:4 [-1.0, -1.0,  1.0]
/// |         0_____ | ________3     p:5 [ 1.0, -1.0,  1.0]
/// |       ,'       |       ,'      p:6 [ 1.0,  1.0,  1.0]
/// |     ,'         |     ,'        p:7 [-1.0,  1.0,  1.0]
/// |   ,'           |   ,'
/// | ,'             | ,'
/// 1________________2'
/// ```
///
/// # Local IDs of edges
///
/// ```text
///                     7                   p0  p1
///             +----------------+     e:0  [0, 1]
///           ,'|              ,'|     e:1  [1, 2]
///       4 ,'  |8          6,'  |     e:2  [2, 3]
///       ,'    |          ,'    |     e:3  [3, 0]
///     ,'      |   5    ,'      |11   e:4  [4, 5]
///   +'===============+'        |     e:5  [5, 6]
///   |         |      |         |     e:6  [6, 7]
///   |         |      |  3      |     e:7  [7, 4]
///   |         .- - - | -  - - -+     e:8  [0, 4]
///  9|       ,'       |       ,'      e:9  [1, 5]
///   |    0,'         |10   ,'        e:10 [2, 6]
///   |   ,'           |   ,' 2        e:11 [3, 7]
///   | ,'             | ,'
///   +----------------+'
///           1
/// ```
///
/// # Local IDs of faces
///
/// ```text
///           4----------------7
///         ,'|              ,'|
///       ,'  |  ___       ,'  |
///     ,'    |,'5,'  [0],'    |        p0 p1 p2 p3
///   ,'      |~~~     ,'      |    f:0 [0, 4, 7, 3]
/// 5'===============6'  ,'|   |    f:1 [1, 2, 6, 5]
/// |   ,'|   |      |   |3|   |    f:2 [0, 1, 5, 4]
/// |   |2|   |      |   |,'   |    f:3 [2, 3, 7, 6]
/// |   |,'   0- - - | +- - - -3    f:4 [0, 3, 2, 1]
/// |       ,'       |       ,'     f:5 [4, 5, 6, 7]
/// |     ,' [1]  ___|     ,'
/// |   ,'      ,'4,'|   ,'
/// | ,'        ~~~  | ,'
/// 1----------------2'
/// ```
///
/// # Note about face nodes
///
/// * The order of face nodes is such that the normals are outward
/// * The order of face nodes corresponds to **Qua4** nodes
pub struct Hex8 {}

impl Hex8 {
    pub const GEO_NDIM: usize = 3;
    pub const NNODE: usize = 8;
    pub const NEDGE: usize = 12;
    pub const NFACE: usize = 6;
    pub const EDGE_NNODE: usize = 2;
    pub const FACE_NNODE: usize = 4;
    pub const FACE_NEDGE: usize = 4;

    #[rustfmt::skip]
    pub const EDGE_NODE_IDS: [[usize; Hex8::EDGE_NNODE]; Hex8::NEDGE] = [
        [0, 1],
        [1, 2],
        [2, 3],
        [3, 0],
        [4, 5],
        [5, 6],
        [6, 7],
        [7, 4],
        [0, 4],
        [1, 5],
        [2, 6],
        [3, 7],
    ];

    #[rustfmt::skip]
    pub const FACE_NODE_IDS: [[usize; Hex8::FACE_NNODE]; Hex8::NFACE] = [
        [0, 4, 7, 3],
        [1, 2, 6, 5],
        [0, 1, 5, 4],
        [2, 3, 7, 6],
        [0, 3, 2, 1],
        [4, 5, 6, 7],
    ];

    #[rustfmt::skip]
    pub const FACE_EDGE_NODE_IDS: [[[usize; Hex8::EDGE_NNODE]; Hex8::FACE_NEDGE]; Hex8::NFACE] = [
        [[0, 4], [4, 7], [7, 3], [3, 0]],
        [[1, 2], [2, 6], [6, 5], [5, 1]],
        [[0, 1], [1, 5], [5, 4], [4, 0]],
        [[2, 3], [3, 7], [7, 6], [6, 2]],
        [[0, 3], [3, 2], [2, 1], [1, 0]],
        [[4, 5], [5, 6], [6, 7], [7, 4]],
    ];

    #[rustfmt::skip]
    pub const NODE_REFERENCE_COORDS: [[f64; Hex8::GEO_NDIM]; Hex8::NNODE] = [
        [-1.0, -1.0, -1.0],
        [ 1.0, -1.0, -1.0],
        [ 1.0,  1.0, -1.0],
        [-1.0,  1.0, -1.0],
        [-1.0, -1.0,  1.0],
        [ 1.0, -1.0,  1.0],
        [ 1.0,  1.0,  1.0],
        [-1.0,  1.0,  1.0],
    ];

    /// Computes the interpolation functions
    pub fn calc_interp(interp: &mut Vector, ksi: &[f64]) {
        let (r, s, t) = (ksi[0], ksi[1], ksi[2]);

        interp[0] = (1.0 - r - s + r * s - t + s * t + r * t - r * s * t) / 8.0;
        interp[1] = (1.0 + r - s - r * s - t + s * t - r * t + r * s * t) / 8.0;
        interp[2] = (1.0 + r + s + r * s - t - s * t - r * t - r * s * t) / 8.0;
        interp[3] = (1.0 - r + s - r * s - t - s * t + r * t + r * s * t) / 8.0;
        interp[4] = (1.0 - r - s + r * s + t - s * t - r * t + r * s * t) / 8.0;
        interp[5] = (1.0 + r - s - r * s + t - s * t + r * t - r * s * t) / 8.0;
        interp[6] = (1.0 + r + s + r * s + t + s * t + r * t + r * s * t) / 8.0;
        interp[7] = (1.0 - r + s - r * s + t + s * t - r * t - r * s * t) / 8.0;
    }

    /// Computes the derivatives of interpolation functions
    pub fn calc_deriv(deriv: &mut Matrix, ksi: &[f64]) {
        let (r, s, t) = (ksi[0], ksi[1], ksi[2]);

        deriv[0][0] = (-1.0 + s + t - s * t) / 8.0;
        deriv[0][1] = (-1.0 + r + t - r * t) / 8.0;
        deriv[0][2] = (-1.0 + r + s - r * s) / 8.0;

        deriv[1][0] = (1.0 - s - t + s * t) / 8.0;
        deriv[1][1] = (-1.0 - r + t + r * t) / 8.0;
        deriv[1][2] = (-1.0 - r + s + r * s) / 8.0;

        deriv[2][0] = (1.0 + s - t - s * t) / 8.0;
        deriv[2][1] = (1.0 + r - t - r * t) / 8.0;
        deriv[2][2] = (-1.0 - r - s - r * s) / 8.0;

        deriv[3][0] = (-1.0 - s + t + s * t) / 8.0;
        deriv[3][1] = (1.0 - r - t + r * t) / 8.0;
        deriv[3][2] = (-1.0 + r - s + r * s) / 8.0;

        deriv[4][0] = (-1.0 + s - t + s * t) / 8.0;
        deriv[4][1] = (-1.0 + r - t + r * t) / 8.0;
        deriv[4][2] = (1.0 - r - s + r * s) / 8.0;

        deriv[5][0] = (1.0 - s + t - s * t) / 8.0;
        deriv[5][1] = (-1.0 - r - t - r * t) / 8.0;
        deriv[5][2] = (1.0 + r - s - r * s) / 8.0;

        deriv[6][0] = (1.0 + s + t + s * t) / 8.0;
        deriv[6][1] = (1.0 + r + t + r * t) / 8.0;
        deriv[6][2] = (1.0 + r + s + r * s) / 8.0;

        deriv[7][0] = (-1.0 - s - t - s * t) / 8.0;
        deriv[7][1] = (1.0 - r + t - r * t) / 8.0;
        deriv[7][2] = (1.0 - r + s - r * s) / 8.0;
    }
}
<commit_msg>Add tests (Mathematica code)<commit_after>use russell_lab::{Matrix, Vector};

/// Defines a hexahedron with 8 nodes (bilinear faces)
///
/// The reference coordinates range from -1 to +1 with the geometry centred @ 0.
///
/// # Local IDs of nodes
///
/// ```text
///           4________________7
///         ,'|              ,'|
///       ,'  |            ,'  |             r     s     t
///     ,'    |          ,'    |     p:0 [-1.0, -1.0, -1.0]
///   ,'      |        ,'      |     p:1 [ 1.0, -1.0, -1.0]
/// 5'===============6'        |     p:2 [ 1.0,  1.0, -1.0]
/// |         |      |         |     p:3 [-1.0,  1.0, -1.0]
/// |         |      |         |     p:4 [-1.0, -1.0,  1.0]
/// |         0_____ | ________3     p:5 [ 1.0, -1.0,  1.0]
/// |       ,'       |       ,'      p:6 [ 1.0,  1.0,  1.0]
/// |     ,'         |     ,'        p:7 [-1.0,  1.0,  1.0]
/// |   ,'           |   ,'
/// | ,'             | ,'
/// 1________________2'
/// ```
///
/// # Local IDs of edges
///
/// ```text
///                     7                   p0  p1
///             +----------------+     e:0  [0, 1]
///           ,'|              ,'|     e:1  [1, 2]
///       4 ,'  |8          6,'  |     e:2  [2, 3]
///       ,'    |          ,'    |     e:3  [3, 0]
///     ,'      |   5    ,'      |11   e:4  [4, 5]
///   +'===============+'        |     e:5  [5, 6]
///   |         |      |         |     e:6  [6, 7]
///   |         |      |  3      |     e:7  [7, 4]
///   |         .- - - | -  - - -+     e:8  [0, 4]
///  9|       ,'       |       ,'      e:9  [1, 5]
///   |    0,'         |10   ,'        e:10 [2, 6]
///   |   ,'           |   ,' 2        e:11 [3, 7]
///   | ,'             | ,'
///   +----------------+'
///           1
/// ```
///
/// # Local IDs of faces
///
/// ```text
///           4----------------7
///         ,'|              ,'|
///       ,'  |  ___       ,'  |
///     ,'    |,'5,'  [0],'    |        p0 p1 p2 p3
///   ,'      |~~~     ,'      |    f:0 [0, 4, 7, 3]
/// 5'===============6'  ,'|   |    f:1 [1, 2, 6, 5]
/// |   ,'|   |      |   |3|   |    f:2 [0, 1, 5, 4]
/// |   |2|   |      |   |,'   |    f:3 [2, 3, 7, 6]
/// |   |,'   0- - - | +- - - -3    f:4 [0, 3, 2, 1]
/// |       ,'       |       ,'     f:5 [4, 5, 6, 7]
/// |     ,' [1]  ___|     ,'
/// |   ,'      ,'4,'|   ,'
/// | ,'        ~~~  | ,'
/// 1----------------2'
/// ```
///
/// # Note about face nodes
///
/// * The order of face nodes is such that the normals are outward
/// * The order of face nodes corresponds to **Qua4** nodes
pub struct Hex8 {}

impl Hex8 {
    pub const GEO_NDIM: usize = 3;
    pub const NNODE: usize = 8;
    pub const NEDGE: usize = 12;
    pub const NFACE: usize = 6;
    pub const EDGE_NNODE: usize = 2;
    pub const FACE_NNODE: usize = 4;
    pub const FACE_NEDGE: usize = 4;

    #[rustfmt::skip]
    pub const EDGE_NODE_IDS: [[usize; Hex8::EDGE_NNODE]; Hex8::NEDGE] = [
        [0, 1],
        [1, 2],
        [2, 3],
        [3, 0],
        [4, 5],
        [5, 6],
        [6, 7],
        [7, 4],
        [0, 4],
        [1, 5],
        [2, 6],
        [3, 7],
    ];

    #[rustfmt::skip]
    pub const FACE_NODE_IDS: [[usize; Hex8::FACE_NNODE]; Hex8::NFACE] = [
        [0, 4, 7, 3],
        [1, 2, 6, 5],
        [0, 1, 5, 4],
        [2, 3, 7, 6],
        [0, 3, 2, 1],
        [4, 5, 6, 7],
    ];

    #[rustfmt::skip]
    pub const FACE_EDGE_NODE_IDS: [[[usize; Hex8::EDGE_NNODE]; Hex8::FACE_NEDGE]; Hex8::NFACE] = [
        [[0, 4], [4, 7], [7, 3], [3, 0]],
        [[1, 2], [2, 6], [6, 5], [5, 1]],
        [[0, 1], [1, 5], [5, 4], [4, 0]],
        [[2, 3], [3, 7], [7, 6], [6, 2]],
        [[0, 3], [3, 2], [2, 1], [1, 0]],
        [[4, 5], [5, 6], [6, 7], [7, 4]],
    ];

    #[rustfmt::skip]
    pub const NODE_REFERENCE_COORDS: [[f64; Hex8::GEO_NDIM]; Hex8::NNODE] = [
        [-1.0, -1.0, -1.0],
        [ 1.0, -1.0, -1.0],
        [ 1.0,  1.0, -1.0],
        [-1.0,  1.0, -1.0],
        [-1.0, -1.0,  1.0],
        [ 1.0, -1.0,  1.0],
        [ 1.0,  1.0,  1.0],
        [-1.0,  1.0,  1.0],
    ];

    /// Computes the interpolation functions
    pub fn calc_interp(interp: &mut Vector, ksi: &[f64]) {
        let (r, s, t) = (ksi[0], ksi[1], ksi[2]);

        interp[0] = (1.0 - r - s + r * s - t + s * t + r * t - r * s * t) / 8.0;
        interp[1] = (1.0 + r - s - r * s - t + s * t - r * t + r * s * t) / 8.0;
        interp[2] = (1.0 + r + s + r * s - t - s * t - r * t - r * s * t) / 8.0;
        interp[3] = (1.0 - r + s - r * s - t - s * t + r * t + r * s * t) / 8.0;
        interp[4] = (1.0 - r - s + r * s + t - s * t - r * t + r * s * t) / 8.0;
        interp[5] = (1.0 + r - s - r * s + t - s * t + r * t - r * s * t) / 8.0;
        interp[6] = (1.0 + r + s + r * s + t + s * t + r * t + r * s * t) / 8.0;
        interp[7] = (1.0 - r + s - r * s + t + s * t - r * t - r * s * t) / 8.0;
    }

    /// Computes the derivatives of interpolation functions
    pub fn calc_deriv(deriv: &mut Matrix, ksi: &[f64]) {
        let (r, s, t) = (ksi[0], ksi[1], ksi[2]);

        deriv[0][0] = (-1.0 + s + t - s * t) / 8.0;
        deriv[0][1] = (-1.0 + r + t - r * t) / 8.0;
        deriv[0][2] = (-1.0 + r + s - r * s) / 8.0;

        deriv[1][0] = (1.0 - s - t + s * t) / 8.0;
        deriv[1][1] = (-1.0 - r + t + r * t) / 8.0;
        deriv[1][2] = (-1.0 - r + s + r * s) / 8.0;

        deriv[2][0] = (1.0 + s - t - s * t) / 8.0;
        deriv[2][1] = (1.0 + r - t - r * t) / 8.0;
        deriv[2][2] = (-1.0 - r - s - r * s) / 8.0;

        deriv[3][0] = (-1.0 - s + t + s * t) / 8.0;
        deriv[3][1] = (1.0 - r - t + r * t) / 8.0;
        deriv[3][2] = (-1.0 + r - s + r * s) / 8.0;

        deriv[4][0] = (-1.0 + s - t + s * t) / 8.0;
        deriv[4][1] = (-1.0 + r - t + r * t) / 8.0;
        deriv[4][2] = (1.0 - r - s + r * s) / 8.0;

        deriv[5][0] = (1.0 - s + t - s * t) / 8.0;
        deriv[5][1] = (-1.0 - r - t - r * t) / 8.0;
        deriv[5][2] = (1.0 + r - s - r * s) / 8.0;

        deriv[6][0] = (1.0 + s + t + s * t) / 8.0;
        deriv[6][1] = (1.0 + r + t + r * t) / 8.0;
        deriv[6][2] = (1.0 + r + s + r * s) / 8.0;

        deriv[7][0] = (-1.0 - s - t - s * t) / 8.0;
        deriv[7][1] = (1.0 - r + t - r * t) / 8.0;
        deriv[7][2] = (1.0 - r + s - r * s) / 8.0;
    }

    pub(super) fn mathematica_calc_interp(interp: &mut Vector, ksi: &[f64]) {
        let (r, s, t) = (ksi[0], ksi[1], ksi[2]);

        interp[0] = ((1.0 - r) * (1.0 - s) * (1.0 - t)) / 8.0;
        interp[1] = ((1.0 + r) * (1.0 - s) * (1.0 - t)) / 8.0;
        interp[2] = ((1.0 + r) * (1.0 + s) * (1.0 - t)) / 8.0;
        interp[3] = ((1.0 - r) * (1.0 + s) * (1.0 - t)) / 8.0;
        interp[4] = ((1.0 - r) * (1.0 - s) * (1.0 + t)) / 8.0;
        interp[5] = ((1.0 + r) * (1.0 - s) * (1.0 + t)) / 8.0;
        interp[6] = ((1.0 + r) * (1.0 + s) * (1.0 + t)) / 8.0;
        interp[7] = ((1.0 - r) * (1.0 + s) * (1.0 + t)) / 8.0;
    }

    pub(super) fn mathematica_calc_deriv(deriv: &mut Matrix, ksi: &[f64]) {
        let (r, s, t) = (ksi[0], ksi[1], ksi[2]);

        // w.r.t r
        deriv[0][0] = -((1.0 - s) * (1.0 - t)) / 8.0;
        deriv[1][0] = ((1.0 - s) * (1.0 - t)) / 8.0;
        deriv[2][0] = ((1.0 + s) * (1.0 - t)) / 8.0;
        deriv[3][0] = -((1.0 + s) * (1.0 - t)) / 8.0;
        deriv[4][0] = -((1.0 - s) * (1.0 + t)) / 8.0;
        deriv[5][0] = ((1.0 - s) * (1.0 + t)) / 8.0;
        deriv[6][0] = ((1.0 + s) * (1.0 + t)) / 8.0;
        deriv[7][0] = -((1.0 + s) * (1.0 + t)) / 8.0;

        // w.r.t s
        deriv[0][1] = -((1.0 - r) * (1.0 - t)) / 8.0;
        deriv[1][1] = -((1.0 + r) * (1.0 - t)) / 8.0;
        deriv[2][1] = ((1.0 + r) * (1.0 - t)) / 8.0;
        deriv[3][1] = ((1.0 - r) * (1.0 - t)) / 8.0;
        deriv[4][1] = -((1.0 - r) * (1.0 + t)) / 8.0;
        deriv[5][1] = -((1.0 + r) * (1.0 + t)) / 8.0;
        deriv[6][1] = ((1.0 + r) * (1.0 + t)) / 8.0;
        deriv[7][1] = ((1.0 - r) * (1.0 + t)) / 8.0;

        // w.r.t t
        deriv[0][2] = -((1.0 - r) * (1.0 - s)) / 8.0;
        deriv[1][2] = -((1.0 + r) * (1.0 - s)) / 8.0;
        deriv[2][2] = -((1.0 + r) * (1.0 + s)) / 8.0;
        deriv[3][2] = -((1.0 - r) * (1.0 + s)) / 8.0;
        deriv[4][2] = ((1.0 - r) * (1.0 - s)) / 8.0;
        deriv[5][2] = ((1.0 + r) * (1.0 - s)) / 8.0;
        deriv[6][2] = ((1.0 + r) * (1.0 + s)) / 8.0;
        deriv[7][2] = ((1.0 - r) * (1.0 + s)) / 8.0;
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#[cfg(test)]
mod tests {
    use super::Hex8;
    use russell_lab::{Matrix, Vector};

    #[test]
    fn interp_matches_mathematica_code() {
        let mut interp = Vector::new(Hex8::NNODE);
        let mut interp_math = Vector::new(Hex8::NNODE);
        for m in 0..Hex8::NNODE {
            let ksi = &Hex8::NODE_REFERENCE_COORDS[m];
            Hex8::calc_interp(&mut interp, ksi);
            Hex8::mathematica_calc_interp(&mut interp_math, ksi);
            assert_eq!(interp[m], 1.0);
            assert_eq!(interp[m], interp_math[m]);
            for n in 0..Hex8::NNODE {
                if n != m {
                    assert_eq!(interp[n], 0.0);
                }
            }
        }
    }

    #[test]
    fn deriv_matches_mathematica_code() {
        let mut deriv = Matrix::new(Hex8::NNODE, 3);
        let mut deriv_math = Matrix::new(Hex8::NNODE, 3);
        for m in 0..Hex8::NNODE {
            let ksi = &Hex8::NODE_REFERENCE_COORDS[m];
            Hex8::calc_deriv(&mut deriv, ksi);
            Hex8::mathematica_calc_deriv(&mut deriv_math, ksi);
            assert_eq!(deriv[m][0], deriv_math[m][0]);
            assert_eq!(deriv[m][1], deriv_math[m][1]);
            assert_eq!(deriv[m][2], deriv_math[m][2]);
        }
    }
}
<|endoftext|>


<commit_before>// Copyright 2015, Paul Osborne <osbpau@gmail.com>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/license/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option.  This file may not be copied, modified, or distributed
// except according to those terms.

#![allow(dead_code)]

use ioctl;
use std::mem;
use std::io;
use std::os::unix::io::RawFd;

// Constants extracted from linux/spi/spidev.h
bitflags! {
    flags SpiModeFlags: u8 {
        const SPI_CPHA = 0x01,
        const SPI_CPOL = 0x02,
        const SPI_MODE_0 = 0x00,
        const SPI_MODE_1 = SPI_CPHA.bits,
        const SPI_MODE_2 = SPI_CPOL.bits,
        const SPI_MODE_3 = (SPI_CPOL.bits | SPI_CPHA.bits),
    }
}

bitflags! {
    flags SpidevOptionFlags: u8 {
        const SPI_CS_HIGH = 0x04,
        const SPI_LSB_FIRST = 0x08,
        const SPI_3WIRE = 0x10,
        const SPI_LOOP = 0x20,
        const SPI_NO_CS = 0x40,
        const SPI_READY = 0x80,
    }
}

const SPI_IOC_MAGIC: u8 = 'k' as u8;

const SPI_IOC_NR_TRANSFER: u8 = 0;
const SPI_IOC_NR_MODE: u8 = 1;
const SPI_IOC_NR_LSB_FIRST: u8 = 2;
const SPI_IOC_NR_BITS_PER_WORD: u8 = 3;
const SPI_IOC_NR_MAX_SPEED_HZ: u8 = 4;
const SPI_IOC_NR_MODE32: u8 = 5;

/// Structure that is used when performing communication
/// with the kernel.
struct spi_ioc_transfer {
    pub tx_buf: u64,
    pub rx_buf: u64,

    pub len: u32,

    // optional overrides
    pub speed_hz: u32,
    pub delay_usecs: u16,
    pub bits_per_word: u8,
    pub cs_change: u8,
    pub pad: u32,
}

/// Representation of a spidev transfer that is shared
/// with external users
#[derive(Default)]
pub struct SpidevTransfer {
    tx_buf: Option<Box<[u8]>>,
    rx_buf: Option<Box<[u8]>>,
    len: u32,
    speed_hz: u32,
    delay_usecs: u16,
    bits_per_word: u8,
    cs_change: u8,
    pad: u32,
}

impl SpidevTransfer {
    pub fn read(length: u32) -> SpidevTransfer {
        let rx_buf_vec: Vec<u8> = Vec::with_capacity(length as usize);
        SpidevTransfer {
            tx_buf: None,
            rx_buf: Some(rx_buf_vec.into_boxed_slice()),
            len: length as u32,
            ..Default::default()
        }
    }

    pub fn write(tx_buf: &[u8]) -> SpidevTransfer {
        let rx_buf_vec: Vec<u8> = Vec::with_capacity(tx_buf.len());
        let mut tx_buf_vec: Vec<u8> = Vec::with_capacity(tx_buf.len());
        tx_buf_vec.clone_from_slice(tx_buf);
        SpidevTransfer {
            tx_buf: Some(tx_buf_vec.into_boxed_slice()),
            rx_buf: Some(rx_buf_vec.into_boxed_slice()),
            len: tx_buf.len() as u32,
            ..Default::default()
        }
    }

}

fn spidev_ioc_read<T>(fd: RawFd, nr: u8) -> io::Result<T> {
    let size: u16 = mem::size_of::<T>() as u16;
    let op = ioctl::op_read(SPI_IOC_MAGIC, nr, size);
    unsafe { ioctl::read(fd, op) }
}

fn spidev_ioc_write<T>(fd: RawFd, nr: u8, data: &T) -> io::Result<()> {
    let size: u16 = mem::size_of::<T>() as u16;
    let op = ioctl::op_write(SPI_IOC_MAGIC, nr, size);
    unsafe { ioctl::write(fd, op, data) }
}

pub fn get_mode(fd: RawFd) -> io::Result<u8> {
    // #define SPI_IOC_RD_MODE _IOR(SPI_IOC_MAGIC, 1, __u8)
    spidev_ioc_read::<u8>(fd, SPI_IOC_NR_MODE)
}

pub fn set_mode(fd: RawFd, mode: SpiModeFlags) -> io::Result<()> {
    // #define SPI_IOC_WR_MODE _IOW(SPI_IOC_MAGIC, 1, __u8)
    spidev_ioc_write(fd, SPI_IOC_NR_MODE, &mode.bits)
}

pub fn get_lsb_first(fd: RawFd) -> io::Result<bool> {
    // #define SPI_IOC_RD_LSB_FIRST _IOR(SPI_IOC_MAGIC, 2, __u8)
    Ok(try!(spidev_ioc_read::<u8>(fd, SPI_IOC_NR_LSB_FIRST)) != 0)
}

pub fn set_lsb_first(fd: RawFd, lsb_first: bool) -> io::Result<()> {
    // #define SPI_IOC_WR_LSB_FIRST _IOW(SPI_IOC_MAGIC, 2, __u8)
    let lsb_first_value: u8 = if lsb_first { 1 } else { 0 };
    spidev_ioc_write(fd, SPI_IOC_NR_LSB_FIRST, &lsb_first_value)
}

pub fn get_bits_per_word(fd: RawFd) -> io::Result<u8> {
    // #define SPI_IOC_RD_BITS_PER_WORD _IOR(SPI_IOC_MAGIC, 3, __u8)
    spidev_ioc_read::<u8>(fd, SPI_IOC_NR_BITS_PER_WORD)
}

pub fn set_bits_per_word(fd: RawFd, bits_per_word: u8) -> io::Result<()> {
    // #define SPI_IOC_WR_BITS_PER_WORD _IOW(SPI_IOC_MAGIC, 3, __u8)
    spidev_ioc_write(fd, SPI_IOC_NR_BITS_PER_WORD, &bits_per_word)
}

pub fn get_max_speed_hz(fd: RawFd) -> io::Result<u32> {
    // #define SPI_IOC_RD_MAX_SPEED_HZ _IOR(SPI_IOC_MAGIC, 4, __u32)
    spidev_ioc_read::<u32>(fd, SPI_IOC_NR_MAX_SPEED_HZ)
}

pub fn set_max_speed_hz(fd: RawFd, max_speed_hz: u32) -> io::Result<()> {
    // #define SPI_IOC_WR_MAX_SPEED_HZ _IOW(SPI_IOC_MAGIC, 4, __u32)
    spidev_ioc_write(fd, SPI_IOC_NR_MAX_SPEED_HZ, &max_speed_hz)
}

pub fn xfer(fd: RawFd, tx_buf: &[u8]) {}
<commit_msg>spidev: spidevioctl is now compiling, although still untested<commit_after>// Copyright 2015, Paul Osborne <osbpau@gmail.com>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/license/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option.  This file may not be copied, modified, or distributed
// except according to those terms.

#![allow(dead_code)]

use ioctl;
use std::mem;
use std::io;
use std::os::unix::io::RawFd;

// Constants extracted from linux/spi/spidev.h
bitflags! {
    flags SpiModeFlags: u8 {
        const SPI_CPHA = 0x01,
        const SPI_CPOL = 0x02,
        const SPI_MODE_0 = 0x00,
        const SPI_MODE_1 = SPI_CPHA.bits,
        const SPI_MODE_2 = SPI_CPOL.bits,
        const SPI_MODE_3 = (SPI_CPOL.bits | SPI_CPHA.bits),
    }
}

bitflags! {
    flags SpidevOptionFlags: u8 {
        const SPI_CS_HIGH = 0x04,
        const SPI_LSB_FIRST = 0x08,
        const SPI_3WIRE = 0x10,
        const SPI_LOOP = 0x20,
        const SPI_NO_CS = 0x40,
        const SPI_READY = 0x80,
    }
}

const SPI_IOC_MAGIC: u8 = 'k' as u8;

const SPI_IOC_NR_TRANSFER: u8 = 0;
const SPI_IOC_NR_MODE: u8 = 1;
const SPI_IOC_NR_LSB_FIRST: u8 = 2;
const SPI_IOC_NR_BITS_PER_WORD: u8 = 3;
const SPI_IOC_NR_MAX_SPEED_HZ: u8 = 4;
const SPI_IOC_NR_MODE32: u8 = 5;

/// Structure that is used when performing communication
/// with the kernel.
///
/// From the kernel documentation:
///
/// ```
/// struct spi_ioc_transfer - describes a single SPI transfer
/// @tx_buf: Holds pointer to userspace buffer with transmit data, or null.
///   If no data is provided, zeroes are shifted out.
/// @rx_buf: Holds pointer to userspace buffer for receive data, or null.
/// @len: Length of tx and rx buffers, in bytes.
/// @speed_hz: Temporary override of the device's bitrate.
/// @bits_per_word: Temporary override of the device's wordsize.
/// @delay_usecs: If nonzero, how long to delay after the last bit transfer
///      before optionally deselecting the device before the next transfer.
/// @cs_change: True to deselect device before starting the next transfer.
///
/// This structure is mapped directly to the kernel spi_transfer structure;
/// the fields have the same meanings, except of course that the pointers
/// are in a different address space (and may be of different sizes in some
/// cases, such as 32-bit i386 userspace over a 64-bit x86_64 kernel).
/// Zero-initialize the structure, including currently unused fields, to
/// accomodate potential future updates.
///
/// SPI_IOC_MESSAGE gives userspace the equivalent of kernel spi_sync().
/// Pass it an array of related transfers, they'll execute together.
/// Each transfer may be half duplex (either direction) or full duplex.
///
///      struct spi_ioc_transfer mesg[4];
///      ...
///      status = ioctl(fd, SPI_IOC_MESSAGE(4), mesg);
///
/// So for example one transfer might send a nine bit command (right aligned
/// in a 16-bit word), the next could read a block of 8-bit data before
/// terminating that command by temporarily deselecting the chip; the next
/// could send a different nine bit command (re-selecting the chip), and the
/// last transfer might write some register values.
/// ```
struct spi_ioc_transfer {
    pub tx_buf: u64,
    pub rx_buf: u64,

    pub len: u32,

    // optional overrides
    pub speed_hz: u32,
    pub delay_usecs: u16,
    pub bits_per_word: u8,
    pub cs_change: u8,
    pub pad: u32,
}

/// Representation of a spidev transfer that is shared
/// with external users
#[derive(Default)]
pub struct SpidevTransfer {
    tx_buf: Option<Box<[u8]>>,
    rx_buf: Option<Box<[u8]>>,
    len: u32,
    speed_hz: u32,
    delay_usecs: u16,
    bits_per_word: u8,
    cs_change: u8,
    pad: u32,
}

impl SpidevTransfer {
    pub fn read(length: u32) -> SpidevTransfer {
        let rx_buf_vec: Vec<u8> = Vec::with_capacity(length as usize);
        SpidevTransfer {
            tx_buf: None,
            rx_buf: Some(rx_buf_vec.into_boxed_slice()),
            len: length as u32,
            ..Default::default()
        }
    }

    pub fn write(tx_buf: &[u8]) -> SpidevTransfer {
        let rx_buf_vec: Vec<u8> = Vec::with_capacity(tx_buf.len());
        let mut tx_buf_vec: Vec<u8> = Vec::with_capacity(tx_buf.len());
        tx_buf_vec.clone_from_slice(tx_buf);
        SpidevTransfer {
            tx_buf: Some(tx_buf_vec.into_boxed_slice()),
            rx_buf: Some(rx_buf_vec.into_boxed_slice()),
            len: tx_buf.len() as u32,
            ..Default::default()
        }
    }

    fn as_spi_ioc_transfer(&self) -> spi_ioc_transfer {
        spi_ioc_transfer {
            tx_buf: match self.tx_buf {
                Some(ref bufbox) => bufbox.as_ptr() as u64,
                None => 0,
            },
            rx_buf: match self.rx_buf {
                Some(ref bufbox) => bufbox.as_ptr() as u64,
                None => 0,
            },
            len: self.len,
            speed_hz: self.speed_hz,
            delay_usecs: self.delay_usecs,
            bits_per_word: self.bits_per_word,
            cs_change: self.cs_change,
            pad: self.pad,
        }
    }
}

fn spidev_ioc_read<T>(fd: RawFd, nr: u8) -> io::Result<T> {
    let size: u16 = mem::size_of::<T>() as u16;
    let op = ioctl::op_read(SPI_IOC_MAGIC, nr, size);
    unsafe { ioctl::read(fd, op) }
}

fn spidev_ioc_write<T>(fd: RawFd, nr: u8, data: &T) -> io::Result<()> {
    let size: u16 = mem::size_of::<T>() as u16;
    let op = ioctl::op_write(SPI_IOC_MAGIC, nr, size);
    unsafe { ioctl::write(fd, op, data) }
}

pub fn get_mode(fd: RawFd) -> io::Result<u8> {
    // #define SPI_IOC_RD_MODE _IOR(SPI_IOC_MAGIC, 1, __u8)
    spidev_ioc_read::<u8>(fd, SPI_IOC_NR_MODE)
}

pub fn set_mode(fd: RawFd, mode: SpiModeFlags) -> io::Result<()> {
    // #define SPI_IOC_WR_MODE _IOW(SPI_IOC_MAGIC, 1, __u8)
    spidev_ioc_write(fd, SPI_IOC_NR_MODE, &mode.bits)
}

pub fn get_lsb_first(fd: RawFd) -> io::Result<bool> {
    // #define SPI_IOC_RD_LSB_FIRST _IOR(SPI_IOC_MAGIC, 2, __u8)
    Ok(try!(spidev_ioc_read::<u8>(fd, SPI_IOC_NR_LSB_FIRST)) != 0)
}

pub fn set_lsb_first(fd: RawFd, lsb_first: bool) -> io::Result<()> {
    // #define SPI_IOC_WR_LSB_FIRST _IOW(SPI_IOC_MAGIC, 2, __u8)
    let lsb_first_value: u8 = if lsb_first { 1 } else { 0 };
    spidev_ioc_write(fd, SPI_IOC_NR_LSB_FIRST, &lsb_first_value)
}

pub fn get_bits_per_word(fd: RawFd) -> io::Result<u8> {
    // #define SPI_IOC_RD_BITS_PER_WORD _IOR(SPI_IOC_MAGIC, 3, __u8)
    spidev_ioc_read::<u8>(fd, SPI_IOC_NR_BITS_PER_WORD)
}

pub fn set_bits_per_word(fd: RawFd, bits_per_word: u8) -> io::Result<()> {
    // #define SPI_IOC_WR_BITS_PER_WORD _IOW(SPI_IOC_MAGIC, 3, __u8)
    spidev_ioc_write(fd, SPI_IOC_NR_BITS_PER_WORD, &bits_per_word)
}

pub fn get_max_speed_hz(fd: RawFd) -> io::Result<u32> {
    // #define SPI_IOC_RD_MAX_SPEED_HZ _IOR(SPI_IOC_MAGIC, 4, __u32)
    spidev_ioc_read::<u32>(fd, SPI_IOC_NR_MAX_SPEED_HZ)
}

pub fn set_max_speed_hz(fd: RawFd, max_speed_hz: u32) -> io::Result<()> {
    // #define SPI_IOC_WR_MAX_SPEED_HZ _IOW(SPI_IOC_MAGIC, 4, __u32)
    spidev_ioc_write(fd, SPI_IOC_NR_MAX_SPEED_HZ, &max_speed_hz)
}

pub fn transfer(fd: RawFd, transfer: &mut SpidevTransfer) -> io::Result<()> {
    let transfers = vec!(transfer);
    transfer_multiple(fd, transfers)
}

pub fn transfer_multiple(fd: RawFd, transfers: Vec<&mut SpidevTransfer>) -> io::Result<()> {
    let mut raw_transfers: Vec<spi_ioc_transfer> = transfers.iter().map(|ref xfer| {
        xfer.as_spi_ioc_transfer()
    }).collect();
    let size: u16 = mem::size_of::<spi_ioc_transfer>() as u16;
    let op = ioctl::op_read_write(
        SPI_IOC_MAGIC,
        SPI_IOC_NR_TRANSFER,
        size * raw_transfers.len() as u16);
    unsafe { ioctl::read_write(fd, op, &mut raw_transfers) }
}
<|endoftext|>


<commit_before>// Copyright 2018 The Grin Developers
//
// 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.

use core::core::{self, amount_to_hr_string};
use libwallet::types::{AcctPathMapping, OutputData, TxLogEntry, WalletInfo};
use libwallet::Error;
use prettytable;
use std::io::prelude::Write;
use term;
use util;
use util::secp::pedersen;

/// Display outputs in a pretty way
pub fn outputs(
	account: &str,
	cur_height: u64,
	validated: bool,
	outputs: Vec<(OutputData, pedersen::Commitment)>,
) -> Result<(), Error> {
	let title = format!(
		"Wallet Outputs - Account '{}' - Block Height: {}",
		account, cur_height
	);
	println!();
	let mut t = term::stdout().unwrap();
	t.fg(term::color::MAGENTA).unwrap();
	writeln!(t, "{}", title).unwrap();
	t.reset().unwrap();

	let mut table = table!();

	table.set_titles(row![
		bMG->"Output Commitment",
		bMG->"Block Height",
		bMG->"Locked Until",
		bMG->"Status",
		bMG->"Coinbase?",
		bMG->"# Confirms",
		bMG->"Value",
		bMG->"Tx"
	]);

	for (out, commit) in outputs {
		let commit = format!("{}", util::to_hex(commit.as_ref().to_vec()));
		let height = format!("{}", out.height);
		let lock_height = format!("{}", out.lock_height);
		let status = format!("{:?}", out.status);
		let is_coinbase = format!("{}", out.is_coinbase);
		let num_confirmations = format!("{}", out.num_confirmations(cur_height));
		let value = format!("{}", core::amount_to_hr_string(out.value, false));
		let tx = match out.tx_log_entry {
			None => "".to_owned(),
			Some(t) => t.to_string(),
		};
		table.add_row(row![
			bFC->commit,
			bFB->height,
			bFB->lock_height,
			bFR->status,
			bFY->is_coinbase,
			bFB->num_confirmations,
			bFG->value,
			bFC->tx,
		]);
	}

	table.set_format(*prettytable::format::consts::FORMAT_NO_COLSEP);
	table.printstd();
	println!();

	if !validated {
		println!(
			"\nWARNING: Wallet failed to verify data. \
			 The above is from local cache and possibly invalid! \
			 (is your `grin server` offline or broken?)"
		);
	}
	Ok(())
}

/// Display transaction log in a pretty way
pub fn txs(
	account: &str,
	cur_height: u64,
	validated: bool,
	txs: Vec<TxLogEntry>,
	include_status: bool,
) -> Result<(), Error> {
	let title = format!(
		"Transaction Log - Account '{}' - Block Height: {}",
		account, cur_height
	);
	println!();
	let mut t = term::stdout().unwrap();
	t.fg(term::color::MAGENTA).unwrap();
	writeln!(t, "{}", title).unwrap();
	t.reset().unwrap();

	let mut table = table!();

	table.set_titles(row![
		bMG->"Id",
		bMG->"Type",
		bMG->"Shared Transaction Id",
		bMG->"Creation Time",
		bMG->"Confirmed?",
		bMG->"Confirmation Time",
		bMG->"Num. Inputs",
		bMG->"Num. Outputs",
		bMG->"Amount Credited",
		bMG->"Amount Debited",
		bMG->"Fee",
		bMG->"Net Difference",
		bMG->"Tx Data",
	]);

	for t in txs {
		let id = format!("{}", t.id);
		let slate_id = match t.tx_slate_id {
			Some(m) => format!("{}", m),
			None => "None".to_owned(),
		};
		let entry_type = format!("{}", t.tx_type);
		let creation_ts = format!("{}", t.creation_ts.format("%Y-%m-%d %H:%M:%S"));
		let confirmation_ts = match t.confirmation_ts {
			Some(m) => format!("{}", m.format("%Y-%m-%d %H:%M:%S")),
			None => "None".to_owned(),
		};
		let confirmed = format!("{}", t.confirmed);
		let num_inputs = format!("{}", t.num_inputs);
		let num_outputs = format!("{}", t.num_outputs);
		let amount_debited_str = core::amount_to_hr_string(t.amount_debited, true);
		let amount_credited_str = core::amount_to_hr_string(t.amount_credited, true);
		let fee = match t.fee {
			Some(f) => format!("{}", core::amount_to_hr_string(f, true)),
			None => "None".to_owned(),
		};
		let net_diff = if t.amount_credited >= t.amount_debited {
			core::amount_to_hr_string(t.amount_credited - t.amount_debited, true)
		} else {
			format!(
				"-{}",
				core::amount_to_hr_string(t.amount_debited - t.amount_credited, true)
			)
		};
		let tx_data = match t.tx_hex {
			Some(_) => format!("Exists"),
			None => "None".to_owned(),
		};
		table.add_row(row![
			bFC->id,
			bFC->entry_type,
			bFC->slate_id,
			bFB->creation_ts,
			bFC->confirmed,
			bFB->confirmation_ts,
			bFC->num_inputs,
			bFC->num_outputs,
			bFG->amount_credited_str,
			bFR->amount_debited_str,
			bFR->fee,
			bFY->net_diff,
			bFb->tx_data,
		]);
	}

	table.set_format(*prettytable::format::consts::FORMAT_NO_COLSEP);
	table.printstd();
	println!();

	if !validated && include_status {
		println!(
			"\nWARNING: Wallet failed to verify data. \
			 The above is from local cache and possibly invalid! \
			 (is your `grin server` offline or broken?)"
		);
	}
	Ok(())
}
/// Display summary info in a pretty way
pub fn info(account: &str, wallet_info: &WalletInfo, validated: bool) {
	println!(
		"\n____ Wallet Summary Info - Account '{}' as of height {} ____\n",
		account, wallet_info.last_confirmed_height
	);
	let mut table = table!(
		[bFG->"Total", FG->amount_to_hr_string(wallet_info.total, false)],
		[bFY->"Awaiting Confirmation", FY->amount_to_hr_string(wallet_info.amount_awaiting_confirmation, false)],
		[bFY->"Immature Coinbase", FY->amount_to_hr_string(wallet_info.amount_immature, false)],
		[bFG->"Currently Spendable", FG->amount_to_hr_string(wallet_info.amount_currently_spendable, false)],
		[Fw->"---------", Fw->"---------"],
		[Fr->"(Locked by previous transaction)", Fr->amount_to_hr_string(wallet_info.amount_locked, false)]
	);
	table.set_format(*prettytable::format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
	table.printstd();
	println!();
	if !validated {
		println!(
			"\nWARNING: Wallet failed to verify data against a live chain. \
			 The above is from local cache and only valid up to the given height! \
			 (is your `grin server` offline or broken?)"
		);
	}
}
/// Display list of wallet accounts in a pretty way
pub fn accounts(acct_mappings: Vec<AcctPathMapping>) {
	println!("\n____ Wallet Accounts ____\n",);
	let mut table = table!();

	table.set_titles(row![
		mMG->"Name",
		bMG->"Parent BIP-32 Derivation Path",
	]);
	for m in acct_mappings {
		table.add_row(row![
			bFC->m.label,
			bGC->m.path.to_bip_32_string(),
		]);
	}
	table.set_format(*prettytable::format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
	table.printstd();
	println!();
}
<commit_msg>fix: mark potential coinbase outputs as mining instead of unconfirmed in wallet (#1787)<commit_after>// Copyright 2018 The Grin Developers
//
// 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.

use core::core::{self, amount_to_hr_string};
use libwallet::types::{AcctPathMapping, OutputData, OutputStatus, TxLogEntry, WalletInfo};
use libwallet::Error;
use prettytable;
use std::io::prelude::Write;
use term;
use util;
use util::secp::pedersen;

/// Display outputs in a pretty way
pub fn outputs(
	account: &str,
	cur_height: u64,
	validated: bool,
	outputs: Vec<(OutputData, pedersen::Commitment)>,
) -> Result<(), Error> {
	let title = format!(
		"Wallet Outputs - Account '{}' - Block Height: {}",
		account, cur_height
	);
	println!();
	let mut t = term::stdout().unwrap();
	t.fg(term::color::MAGENTA).unwrap();
	writeln!(t, "{}", title).unwrap();
	t.reset().unwrap();

	let mut table = table!();

	table.set_titles(row![
		bMG->"Output Commitment",
		bMG->"Block Height",
		bMG->"Locked Until",
		bMG->"Status",
		bMG->"Coinbase?",
		bMG->"# Confirms",
		bMG->"Value",
		bMG->"Tx"
	]);

	for (out, commit) in outputs {
		let commit = format!("{}", util::to_hex(commit.as_ref().to_vec()));
		let height = format!("{}", out.height);
		let lock_height = format!("{}", out.lock_height);
		let is_coinbase = format!("{}", out.is_coinbase);

		// Mark unconfirmed coinbase outputs as "Mining" instead of "Unconfirmed"
		let status = match out.status {
			OutputStatus::Unconfirmed if out.is_coinbase => "Mining".to_string(),
			_ => format!("{}", out.status),
		};

		let num_confirmations = format!("{}", out.num_confirmations(cur_height));
		let value = format!("{}", core::amount_to_hr_string(out.value, false));
		let tx = match out.tx_log_entry {
			None => "".to_owned(),
			Some(t) => t.to_string(),
		};
		table.add_row(row![
			bFC->commit,
			bFB->height,
			bFB->lock_height,
			bFR->status,
			bFY->is_coinbase,
			bFB->num_confirmations,
			bFG->value,
			bFC->tx,
		]);
	}

	table.set_format(*prettytable::format::consts::FORMAT_NO_COLSEP);
	table.printstd();
	println!();

	if !validated {
		println!(
			"\nWARNING: Wallet failed to verify data. \
			 The above is from local cache and possibly invalid! \
			 (is your `grin server` offline or broken?)"
		);
	}
	Ok(())
}

/// Display transaction log in a pretty way
pub fn txs(
	account: &str,
	cur_height: u64,
	validated: bool,
	txs: Vec<TxLogEntry>,
	include_status: bool,
) -> Result<(), Error> {
	let title = format!(
		"Transaction Log - Account '{}' - Block Height: {}",
		account, cur_height
	);
	println!();
	let mut t = term::stdout().unwrap();
	t.fg(term::color::MAGENTA).unwrap();
	writeln!(t, "{}", title).unwrap();
	t.reset().unwrap();

	let mut table = table!();

	table.set_titles(row![
		bMG->"Id",
		bMG->"Type",
		bMG->"Shared Transaction Id",
		bMG->"Creation Time",
		bMG->"Confirmed?",
		bMG->"Confirmation Time",
		bMG->"Num. Inputs",
		bMG->"Num. Outputs",
		bMG->"Amount Credited",
		bMG->"Amount Debited",
		bMG->"Fee",
		bMG->"Net Difference",
		bMG->"Tx Data",
	]);

	for t in txs {
		let id = format!("{}", t.id);
		let slate_id = match t.tx_slate_id {
			Some(m) => format!("{}", m),
			None => "None".to_owned(),
		};
		let entry_type = format!("{}", t.tx_type);
		let creation_ts = format!("{}", t.creation_ts.format("%Y-%m-%d %H:%M:%S"));
		let confirmation_ts = match t.confirmation_ts {
			Some(m) => format!("{}", m.format("%Y-%m-%d %H:%M:%S")),
			None => "None".to_owned(),
		};
		let confirmed = format!("{}", t.confirmed);
		let num_inputs = format!("{}", t.num_inputs);
		let num_outputs = format!("{}", t.num_outputs);
		let amount_debited_str = core::amount_to_hr_string(t.amount_debited, true);
		let amount_credited_str = core::amount_to_hr_string(t.amount_credited, true);
		let fee = match t.fee {
			Some(f) => format!("{}", core::amount_to_hr_string(f, true)),
			None => "None".to_owned(),
		};
		let net_diff = if t.amount_credited >= t.amount_debited {
			core::amount_to_hr_string(t.amount_credited - t.amount_debited, true)
		} else {
			format!(
				"-{}",
				core::amount_to_hr_string(t.amount_debited - t.amount_credited, true)
			)
		};
		let tx_data = match t.tx_hex {
			Some(_) => format!("Exists"),
			None => "None".to_owned(),
		};
		table.add_row(row![
			bFC->id,
			bFC->entry_type,
			bFC->slate_id,
			bFB->creation_ts,
			bFC->confirmed,
			bFB->confirmation_ts,
			bFC->num_inputs,
			bFC->num_outputs,
			bFG->amount_credited_str,
			bFR->amount_debited_str,
			bFR->fee,
			bFY->net_diff,
			bFb->tx_data,
		]);
	}

	table.set_format(*prettytable::format::consts::FORMAT_NO_COLSEP);
	table.printstd();
	println!();

	if !validated && include_status {
		println!(
			"\nWARNING: Wallet failed to verify data. \
			 The above is from local cache and possibly invalid! \
			 (is your `grin server` offline or broken?)"
		);
	}
	Ok(())
}
/// Display summary info in a pretty way
pub fn info(account: &str, wallet_info: &WalletInfo, validated: bool) {
	println!(
		"\n____ Wallet Summary Info - Account '{}' as of height {} ____\n",
		account, wallet_info.last_confirmed_height
	);
	let mut table = table!(
		[bFG->"Total", FG->amount_to_hr_string(wallet_info.total, false)],
		[bFY->"Awaiting Confirmation", FY->amount_to_hr_string(wallet_info.amount_awaiting_confirmation, false)],
		[bFY->"Immature Coinbase", FY->amount_to_hr_string(wallet_info.amount_immature, false)],
		[bFG->"Currently Spendable", FG->amount_to_hr_string(wallet_info.amount_currently_spendable, false)],
		[Fw->"---------", Fw->"---------"],
		[Fr->"(Locked by previous transaction)", Fr->amount_to_hr_string(wallet_info.amount_locked, false)]
	);
	table.set_format(*prettytable::format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
	table.printstd();
	println!();
	if !validated {
		println!(
			"\nWARNING: Wallet failed to verify data against a live chain. \
			 The above is from local cache and only valid up to the given height! \
			 (is your `grin server` offline or broken?)"
		);
	}
}
/// Display list of wallet accounts in a pretty way
pub fn accounts(acct_mappings: Vec<AcctPathMapping>) {
	println!("\n____ Wallet Accounts ____\n",);
	let mut table = table!();

	table.set_titles(row![
		mMG->"Name",
		bMG->"Parent BIP-32 Derivation Path",
	]);
	for m in acct_mappings {
		table.add_row(row![
			bFC->m.label,
			bGC->m.path.to_bip_32_string(),
		]);
	}
	table.set_format(*prettytable::format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
	table.printstd();
	println!();
}
<|endoftext|>


<commit_before>// Copyright 2015 Robert Helmer <rhelmer@rhelmer.org>. See the LICENSE
// file at the top-level directory of this distribution.

//! # SymbolAPI
//!
//! SymbolAPI is a microservice which accepts lists of symbol+addresses, and returns
//! a list of symbolicated functions.
//!

/// The goal is to take an HTTP JSON request such as:
/// {"stacks":[
///   [
///     [0,11723767],
///     [1, 65802]
///    ]
///  ],
///  "memoryMap":[
///    ["xul.pdb","44E4EC8C2F41492B9369D6B9A059577C2"],
///    ["wntdll.pdb","D74F79EB1F8D4A45ABCD2F476CCABACC2"]
///  ],
///  "version":4
/// }
///
/// The symbolapi service then downloads the corresponding symbol files (e.g. from S3), and returns
/// the function names for the corresponding addresses (in the "stacks" array) and returns JSON
/// such as:
/// {"symbolicatedStacks": [
///   [
///     "XREMain::XRE_mainRun() (in xul.pdb)",
///     "KiUserCallbackDispatcher (in wntdll.pdb)"]
///   ],
///   "knownModules": [true, true]
/// }

extern crate breakpad_symbols;
extern crate flate2;
extern crate hyper;
#[macro_use]
extern crate log;
extern crate log4rs;
extern crate rustc_serialize;
extern crate toml;

use std::collections::HashMap;
use std::env;
use std::fs::{File, create_dir_all};
use std::io::{Read, Write};
use std::path::{PathBuf};
use std::thread;

use breakpad_symbols::{Symbolizer, SimpleSymbolSupplier};
use flate2::read::GzDecoder;
use hyper::Client;
use hyper::header::{ContentEncoding, Encoding};
use hyper::server::{Server, Request, Response};
use hyper::status::StatusCode;
use rustc_serialize::json;

///
/// Incoming JSON request format.
///
// required JSON keys are non-snakecase
#[allow(non_snake_case)]
#[derive(RustcDecodable)]
pub struct SymbolRequest {
    memoryMap: Vec<(String,String)>,
    // index, offset
    stacks: Vec<Vec<(i8,u64)>>,
    version: u8,
}

///
/// Outgoing JSON response format.
///
// required JSON keys are non-snakecase
#[allow(non_snake_case)]
#[derive(RustcEncodable)]
pub struct SymbolResponse {
    symbolicatedStacks: Vec<Vec<String>>,
    knownModules: Vec<bool>,
}

fn main() {
    log4rs::init_file("config/log.toml", Default::default()).unwrap();

    let default_port = 5000;
    let port;
    match env::var("PORT") {
        Ok(val) => port = val,
        Err(_) => {
            println!("$PORT unset, using default {}", default_port);
            port = format!("{}", default_port);
        }
    }

    let address = &*format!("0.0.0.0:{}", port);

    info!("Listening on {}", address);
    Server::http(address).unwrap().handle(server).unwrap();
}

/// Stacks come in as a JSON array, but really is a hash map of `<index, address>``
/// where `index` is the position of the `memoryMap` JSON result.
fn stacks_to_stack_map(decoded_stacks: Vec<Vec<(i8,u64)>>) -> HashMap<i8, Vec<u64>> {
    debug!("decoded_stacks: {:?}", decoded_stacks);

    let mut stack_map: HashMap<i8, Vec<u64>> = HashMap::new();
    for stack in &decoded_stacks[0] {
        let (index, offset) = *stack;

        let mut offsets = vec!();
        if stack_map.contains_key(&index) {
            offsets = stack_map.get(&index).unwrap().clone();
        }
        offsets.push(offset);
        &stack_map.insert(index, offsets);
    }

    debug!("stack_map: {:?}", stack_map);
    stack_map
}

///
/// Receives single HTTP requests and demuxes to symbols file fetches from S3 bucket.
///
fn server(mut req: Request, mut res: Response) {
    info!("incoming connection from {}", req.remote_addr);

    match req.method {
        hyper::Post => {
            let mut res = res.start().unwrap();

            let mut buffer = String::new();
            req.read_to_string(&mut buffer).unwrap();
            debug!("raw POST: {:?}", &buffer);

            let decoded: SymbolRequest = json::decode(&buffer).unwrap();

            debug!("decoded memoryMap: {:?}", decoded.memoryMap);
            debug!("decoded stacks: {:?}", decoded.stacks);
            debug!("decoded version: {}", decoded.version);

            let symbol_url = get_config("symbol_urls.public");
            let stack_map = stacks_to_stack_map(decoded.stacks);

            // FIXME limit the number of possible threads
            // TODO maybe push these into a queue and have a thread pool service the queue?
            let symbol_response = client(symbol_url, decoded.memoryMap, stack_map);
            res.write_all(symbol_response.as_bytes()).unwrap();

            res.end().unwrap();
        },
        hyper::Get => {
            let mut res = res.start().unwrap();
            res.write_all("symbolapi, see github.com/rhelmer/symbolapi".as_bytes()).unwrap();
        },
        _ => { *res.status_mut() = StatusCode::MethodNotAllowed },
    }

    debug!("finished serving request");
}

///
/// Creates multiple client connections and aggregates result.
///
fn client(url: String, memory_map: Vec<(String,String)>, stack_map: HashMap<i8, Vec<u64>>) -> String {
    let mut handles = vec![];
    let mut counter: i8 = 0;
    for (debug_file, debug_id) in memory_map {
        let debug_file_name = debug_file.clone();
        let pdb = debug_file.find(".pdb").unwrap();
        let (symbol_name, _) = debug_file.split_at(pdb);
        let symbol_file = format!("{}.sym", symbol_name);
        let this_url = format!("{}/{}/{}/{}", url, debug_file, debug_id, symbol_file);

        let mut symbol_path = PathBuf::new();
        // TODO get from config
        symbol_path.push("testdata/symbols");

        let mut full_symbol_path = symbol_path.clone();
        full_symbol_path.push(&debug_file);
        full_symbol_path.push(&debug_id);
        full_symbol_path.push(&symbol_file);

        let stack_map_copy = stack_map.clone();

        create_dir_all(&full_symbol_path.parent().unwrap()).unwrap();

        let supplier = SimpleSymbolSupplier::new(vec!(symbol_path.clone()));

        // TODO most of the time is probably spent waiting on I/O, maybe async would be more appropriate?
        // TODO decide min/max possible threads, possibly based on number of cores?
        handles.push(thread::spawn(move || {

            let symbolizer = Symbolizer::new(supplier);
            // FIXME use Arc<Mutex<File>> to prevent concurrent writes
            // TODO only write contents if server version newer
            if !&full_symbol_path.exists() {

                let c = Client::new();
                let mut res = c.get(&this_url).send().unwrap();
                let is_gzipped = match res.headers.get::<ContentEncoding>() {
                    Some(x) => x.contains(&Encoding::Gzip),
                    None => false,
                };

                let mut body = String::new();
                if is_gzipped {
                    let mut raw_body = vec!();
                    res.read_to_end(&mut raw_body).unwrap();
                    let mut d = match GzDecoder::new(&raw_body[..]) {
                        Ok(x) => x,
                        Err(x) => panic!("cannot gunzip {:?}, {:?}", raw_body, x)
                    };
                    d.read_to_string(&mut body).unwrap();
                } else {
                    res.read_to_string(&mut body).unwrap();
                }

                let mut f = File::create(&full_symbol_path).unwrap();
                f.write_all(body.as_bytes()).unwrap();
            }

            debug!("symbol_path: {:?}", &symbol_path);

            let mut symbols = vec!();
            let mut known_module = true;
            for stacks in stack_map_copy.get(&counter) {
                for address in stacks {
                    debug!("attempt to symbolicate: {} for: {:?}", *address, &full_symbol_path);
                    match symbolizer.get_symbol_at_address(&debug_file_name, &debug_id, *address) {
                        Some(x) => symbols.push(x),
                        // return the address rather than function name if symbol not found
                        None => {
                            symbols.push(format!("0x{:x}", address));
                            known_module = false;
                        },
                    }
                }
            }
            debug!("debug_file_name: {:?}, symbols: {:?}", debug_file_name, symbols);

            (debug_file_name, symbols, known_module)
        }));

        counter += 1;
    }

    let mut result = SymbolResponse {
        symbolicatedStacks: vec!(),
        knownModules: vec!(),
    };

    let mut symbolicated_stacks = vec!();

    // pass anything with index -1 through
    for stacks in stack_map.get(&-1) {
        for address in stacks {
            symbolicated_stacks.push(
                format!("0x{:x}", address)
            );
        }
    }

    for handle in handles {
        let (debug_file_name, symbols, known_module) = handle.join().unwrap();

        for symbol in symbols {
            symbolicated_stacks.push(
                format!("{} (in {})", symbol, debug_file_name)
            );
        }
        result.knownModules.push(known_module);
    }

    // the required result format requires this to be a vec-of-vecs
    result.symbolicatedStacks.push(symbolicated_stacks);

    json::encode(&result).unwrap()
}

///
/// Returns individual values from the configuration file.
///
fn get_config(value_name: &str) -> String {
    // TODO move to actual file, static str for the moment
    let toml: &'static str = r#"
        [symbol_urls]
        public = "https://s3-us-west-2.amazonaws.com/org.mozilla.crash-stats.symbols-public/v1"
    "#;
    // TODO support multiple URLs
    let value: toml::Value = toml.parse().unwrap();

    value.lookup(value_name).unwrap().as_str().unwrap().to_string()
}
<commit_msg>clarify docs, return SymbolResponse from client() instead of String<commit_after>// Copyright 2015 Robert Helmer <rhelmer@rhelmer.org>. See the LICENSE
// file at the top-level directory of this distribution.

//! # SymbolAPI
//!
//! SymbolAPI is a microservice which accepts lists of debug symbol names + memory addresses, and
//! returns a list of symbolicated function names.
//!

/// The goal is to take an HTTP JSON request such as:
/// ```
/// {"stacks":[
///   [
///     [0,11723767],
///     [1, 65802]
///    ]
///  ],
///  "memoryMap":[
///    ["xul.pdb","44E4EC8C2F41492B9369D6B9A059577C2"],
///    ["wntdll.pdb","D74F79EB1F8D4A45ABCD2F476CCABACC2"]
///  ],
///  "version":4
/// }
/// ```
/// The symbolapi service then downloads the corresponding symbol files (e.g. from S3), and returns
/// the function names for the corresponding addresses (in the "stacks" array) and returns JSON
/// such as:
/// ```
/// {"symbolicatedStacks": [
///   [
///     "XREMain::XRE_mainRun() (in xul.pdb)",
///     "KiUserCallbackDispatcher (in wntdll.pdb)"]
///   ],
///   "knownModules": [true, true]
/// }
/// ```

extern crate breakpad_symbols;
extern crate flate2;
extern crate hyper;
#[macro_use]
extern crate log;
extern crate log4rs;
extern crate rustc_serialize;
extern crate toml;

use std::collections::HashMap;
use std::env;
use std::fs::{File, create_dir_all};
use std::io::{Read, Write};
use std::path::{PathBuf};
use std::thread;

use breakpad_symbols::{Symbolizer, SimpleSymbolSupplier};
use flate2::read::GzDecoder;
use hyper::Client;
use hyper::header::{ContentEncoding, Encoding};
use hyper::server::{Server, Request, Response};
use hyper::status::StatusCode;
use rustc_serialize::json;

/// Incoming JSON request format.
// required JSON keys are non-snakecase
#[allow(non_snake_case)]
#[derive(RustcDecodable)]
pub struct SymbolRequest {
    pub memoryMap: Vec<(String,String)>,
    // index, offset
    pub stacks: Vec<Vec<(i8,u64)>>,
    pub version: u8,
}

/// Outgoing JSON response format.
// required JSON keys are non-snakecase
#[allow(non_snake_case)]
#[derive(RustcEncodable)]
pub struct SymbolResponse {
    pub symbolicatedStacks: Vec<Vec<String>>,
    pub knownModules: Vec<bool>,
}

fn main() {
    log4rs::init_file("config/log.toml", Default::default()).unwrap();

    let default_port = 5000;
    let port;
    match env::var("PORT") {
        Ok(val) => port = val,
        Err(_) => {
            println!("$PORT unset, using default {}", default_port);
            port = format!("{}", default_port);
        }
    }

    let address = &*format!("0.0.0.0:{}", port);

    info!("Listening on {}", address);
    Server::http(address).unwrap().handle(server).unwrap();
}

/// Stacks come in as a JSON array, but really is a hash map of `<index, address>``
/// where `index` is the position of the `memoryMap` JSON result.
pub fn stacks_to_stack_map(decoded_stacks: Vec<Vec<(i8,u64)>>) -> HashMap<i8, Vec<u64>> {
    debug!("decoded_stacks: {:?}", decoded_stacks);

    let mut stack_map: HashMap<i8, Vec<u64>> = HashMap::new();
    for stack in &decoded_stacks[0] {
        let (index, offset) = *stack;

        let mut offsets = vec!();
        if stack_map.contains_key(&index) {
            offsets = stack_map.get(&index).unwrap().clone();
        }
        offsets.push(offset);
        &stack_map.insert(index, offsets);
    }

    debug!("stack_map: {:?}", stack_map);
    stack_map
}

/// Receives single HTTP requests and demuxes to symbols file fetches from S3 bucket.
pub fn server(mut req: Request, mut res: Response) {
    info!("incoming connection from {}", req.remote_addr);

    match req.method {
        hyper::Post => {
            let mut res = res.start().unwrap();

            let mut buffer = String::new();
            req.read_to_string(&mut buffer).unwrap();
            debug!("raw POST: {:?}", &buffer);

            let decoded: SymbolRequest = json::decode(&buffer).unwrap();

            debug!("decoded memoryMap: {:?}", decoded.memoryMap);
            debug!("decoded stacks: {:?}", decoded.stacks);
            debug!("decoded version: {}", decoded.version);

            let symbol_url = get_config("symbol_urls.public");
            let stack_map = stacks_to_stack_map(decoded.stacks);

            // FIXME limit the number of possible threads
            // TODO maybe push these into a queue and have a thread pool service the queue?
            let symbol_response = json::encode(&client(symbol_url, decoded.memoryMap, stack_map)).unwrap();
            res.write_all(symbol_response.as_bytes()).unwrap();

            res.end().unwrap();
        },
        hyper::Get => {
            let mut res = res.start().unwrap();
            res.write_all("symbolapi, see github.com/rhelmer/symbolapi".as_bytes()).unwrap();
        },
        _ => { *res.status_mut() = StatusCode::MethodNotAllowed },
    }

    debug!("finished serving request");
}

/// Creates multiple client connections to fetch debug symbols from S3 bucket and symbolicate using
/// memory and stack maps, aggregates results and returns SymbolResult.
pub fn client(url: String, memory_map: Vec<(String,String)>, stack_map: HashMap<i8, Vec<u64>>) -> SymbolResponse {
    let mut handles = vec![];
    let mut counter: i8 = 0;
    for (debug_file, debug_id) in memory_map {
        let debug_file_name = debug_file.clone();
        let pdb = debug_file.find(".pdb").unwrap();
        let (symbol_name, _) = debug_file.split_at(pdb);
        let symbol_file = format!("{}.sym", symbol_name);
        let this_url = format!("{}/{}/{}/{}", url, debug_file, debug_id, symbol_file);

        let mut symbol_path = PathBuf::new();
        // TODO get from config
        symbol_path.push("testdata/symbols");

        let mut full_symbol_path = symbol_path.clone();
        full_symbol_path.push(&debug_file);
        full_symbol_path.push(&debug_id);
        full_symbol_path.push(&symbol_file);

        let stack_map_copy = stack_map.clone();

        create_dir_all(&full_symbol_path.parent().unwrap()).unwrap();

        let supplier = SimpleSymbolSupplier::new(vec!(symbol_path.clone()));

        // TODO most of the time is probably spent waiting on I/O, maybe async would be more appropriate?
        // TODO decide min/max possible threads, possibly based on number of cores?
        handles.push(thread::spawn(move || {

            let symbolizer = Symbolizer::new(supplier);
            // FIXME use Arc<Mutex<File>> to prevent concurrent writes
            // TODO only write contents if server version newer
            if !&full_symbol_path.exists() {

                let c = Client::new();
                let mut res = c.get(&this_url).send().unwrap();
                let is_gzipped = match res.headers.get::<ContentEncoding>() {
                    Some(x) => x.contains(&Encoding::Gzip),
                    None => false,
                };

                let mut body = String::new();
                if is_gzipped {
                    let mut raw_body = vec!();
                    res.read_to_end(&mut raw_body).unwrap();
                    let mut d = match GzDecoder::new(&raw_body[..]) {
                        Ok(x) => x,
                        Err(x) => panic!("cannot gunzip {:?}, {:?}", raw_body, x)
                    };
                    d.read_to_string(&mut body).unwrap();
                } else {
                    res.read_to_string(&mut body).unwrap();
                }

                let mut f = File::create(&full_symbol_path).unwrap();
                f.write_all(body.as_bytes()).unwrap();
            }

            debug!("symbol_path: {:?}", &symbol_path);

            let mut symbols = vec!();
            let mut known_module = true;
            for stacks in stack_map_copy.get(&counter) {
                for address in stacks {
                    debug!("attempt to symbolicate: {} for: {:?}", *address, &full_symbol_path);
                    match symbolizer.get_symbol_at_address(&debug_file_name, &debug_id, *address) {
                        Some(x) => symbols.push(x),
                        // return the address rather than function name if symbol not found
                        None => {
                            symbols.push(format!("0x{:x}", address));
                            known_module = false;
                        },
                    }
                }
            }
            debug!("debug_file_name: {:?}, symbols: {:?}", debug_file_name, symbols);

            (debug_file_name, symbols, known_module)
        }));

        counter += 1;
    }

    let mut result = SymbolResponse {
        symbolicatedStacks: vec!(),
        knownModules: vec!(),
    };

    let mut symbolicated_stacks = vec!();

    // pass anything with index -1 through
    for stacks in stack_map.get(&-1) {
        for address in stacks {
            symbolicated_stacks.push(
                format!("0x{:x}", address)
            );
        }
    }

    for handle in handles {
        let (debug_file_name, symbols, known_module) = handle.join().unwrap();

        for symbol in symbols {
            symbolicated_stacks.push(
                format!("{} (in {})", symbol, debug_file_name)
            );
        }
        result.knownModules.push(known_module);
    }

    // the required result format requires this to be a vec-of-vecs
    result.symbolicatedStacks.push(symbolicated_stacks);

    result
}

/// Returns individual values from the configuration file.
fn get_config(value_name: &str) -> String {
    // TODO move to actual file, static str for the moment
    let toml: &'static str = r#"
        [symbol_urls]
        public = "https://s3-us-west-2.amazonaws.com/org.mozilla.crash-stats.symbols-public/v1"
    "#;
    // TODO support multiple URLs
    let value: toml::Value = toml.parse().unwrap();

    value.lookup(value_name).unwrap().as_str().unwrap().to_string()
}
<|endoftext|>