<commit_before>use crate::error::Error::{self, IOError, ModuleSignatureError};
use crate::module::{SerializedModule, LUCET_MODULE_SYM};
use crate::module_data::MODULE_DATA_SYM;
use crate::ModuleData;
use byteorder::{ByteOrder, LittleEndian};
use memoffset::offset_of;
pub use minisign::{PublicKey, SecretKey};
use minisign::{SignatureBones, SignatureBox};
use object::{read::ObjectSection, NativeFile as ObjectFile, Object, SymbolKind};
use std::fs::{File, OpenOptions};
use std::io::{self, Cursor, Read, Seek, SeekFrom, Write};
use std::path::Path;

pub struct ModuleSignature;

impl ModuleSignature {
    pub fn verify<P: AsRef<Path>>(
        so_path: P,
        pk: &PublicKey,
        module_data: &ModuleData<'_>,
    ) -> Result<(), Error> {
        let signature_box: SignatureBox =
            SignatureBones::from_bytes(&module_data.get_module_signature())
                .map_err(ModuleSignatureError)?
                .into();

        let mut raw_module_and_data = RawModuleAndData::from_file(&so_path).map_err(IOError)?;
        let cleared_module_data_bin =
            ModuleData::clear_module_signature(raw_module_and_data.module_data_bin())?;
        raw_module_and_data.patch_module_data(&cleared_module_data_bin);

        minisign::verify(
            &pk,
            &signature_box,
            Cursor::new(&raw_module_and_data.obj_bin),
            true,
            false,
        )
        .map_err(ModuleSignatureError)
    }

    pub fn sign<P: AsRef<Path>>(path: P, sk: &SecretKey) -> Result<(), Error> {
        let raw_module_and_data = RawModuleAndData::from_file(&path).map_err(IOError)?;
        let signature_box = minisign::sign(
            None,
            sk,
            Cursor::new(&raw_module_and_data.obj_bin),
            true,
            None,
            None,
        )
        .map_err(ModuleSignatureError)?;
        let signature_bones: SignatureBones = signature_box.into();
        let patched_module_data_bin = ModuleData::patch_module_signature(
            raw_module_and_data.module_data_bin(),
            &signature_bones.to_bytes(),
        )?;
        raw_module_and_data
            .write_patched_module_data(&path, &patched_module_data_bin)
            .map_err(IOError)?;
        Ok(())
    }
}

#[allow(dead_code)]
struct SymbolData {
    offset: usize,
    len: usize,
}

struct RawModuleAndData {
    pub obj_bin: Vec<u8>,
    pub module_data_offset: usize,
    pub module_data_len: usize,
}

impl RawModuleAndData {
    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, io::Error> {
        let mut obj_bin: Vec<u8> = Vec::new();
        File::open(&path)?.read_to_end(&mut obj_bin)?;

        let native_data_symbol_data =
            Self::symbol_data(&obj_bin, LUCET_MODULE_SYM, true)?.ok_or(io::Error::new(
                io::ErrorKind::InvalidInput,
                format!("`{}` symbol not present", LUCET_MODULE_SYM),
            ))?;

        // While `module_data` is the first field of the `SerializedModule` that `lucet_module` points
        // to, it is a virtual address, not a file offset. The translation is somewhat tricky at
        // the moment, so just look at the corresponding `lucet_module_data` symbol for now.
        let module_data_symbol_data =
            Self::symbol_data(&obj_bin, MODULE_DATA_SYM, true)?.ok_or(io::Error::new(
                io::ErrorKind::InvalidInput,
                format!("`{}` symbol not present", MODULE_DATA_SYM),
            ))?;

        let module_data_len = LittleEndian::read_u64(
            &obj_bin[(native_data_symbol_data.offset
                + offset_of!(SerializedModule, module_data_len))..],
        ) as usize;

        Ok(RawModuleAndData {
            obj_bin,
            module_data_offset: module_data_symbol_data.offset,
            module_data_len,
        })
    }

    pub fn module_data_bin(&self) -> &[u8] {
        &self.obj_bin[self.module_data_offset as usize
            ..self.module_data_offset as usize + self.module_data_len]
    }

    pub fn module_data_bin_mut(&mut self) -> &mut [u8] {
        &mut self.obj_bin[self.module_data_offset as usize
            ..self.module_data_offset as usize + self.module_data_len]
    }

    pub fn patch_module_data(&mut self, module_data_bin: &[u8]) {
        self.module_data_bin_mut().copy_from_slice(&module_data_bin);
    }

    pub fn write_patched_module_data<P: AsRef<Path>>(
        &self,
        path: P,
        patched_module_data_bin: &[u8],
    ) -> Result<(), io::Error> {
        let mut fp = OpenOptions::new()
            .write(true)
            .create_new(false)
            .open(&path)?;
        fp.seek(SeekFrom::Start(self.module_data_offset as u64))?;
        fp.write_all(&patched_module_data_bin)?;
        Ok(())
    }

    // Retrieving the offset of a symbol is not supported by the object crate.
    // In Mach-O, actual file offsets are encoded, whereas Elf encodes virtual
    // addresses, requiring extra steps to retrieve the section, its base
    // address as well as the section offset.

    // Elf
    #[cfg(all(target_family = "unix", not(target_os = "macos")))]
    fn symbol_data(
        obj_bin: &[u8],
        symbol_name: &str,
        _mangle: bool,
    ) -> Result<Option<SymbolData>, io::Error> {
        let obj = ObjectFile::parse(obj_bin)
            .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
        let symbol_map = obj.symbol_map();
        for symbol in symbol_map
            .symbols()
            .iter()
            .filter(|sym| sym.kind() == SymbolKind::Data)
            .filter(|sym| sym.name() == Some(symbol_name))
            .filter(|sym| sym.section_index().is_some())
        {
            let section_index = symbol.section_index().unwrap();
            let section = &obj.section_by_index(section_index).unwrap();
            let section_address = section.address();
            let section_offset = match section.file_range() {
                Some((offset, _)) => offset,
                None => Err(io::Error::new(
                    io::ErrorKind::InvalidInput,
                    "symbol has no associated data",
                ))?,
            };

            let offset = (symbol.address() - section_address + section_offset) as usize;
            let len = symbol.size() as usize;
            return Ok(Some(SymbolData { offset, len }));
        }
        Ok(None)
    }

    // Mach-O
    #[cfg(target_os = "macos")]
    fn symbol_data(
        obj_bin: &[u8],
        symbol_name: &str,
        mangle: bool,
    ) -> Result<Option<SymbolData>, io::Error> {
        let obj = ObjectFile::parse(obj_bin)
            .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
        let symbol_map = obj.symbol_map();
        let mangled_symbol_name = format!("_{}", symbol_name);
        let symbol_name = if mangle {
            &mangled_symbol_name
        } else {
            symbol_name
        };
        if let Some(symbol) = symbol_map
            .symbols()
            .iter()
            .filter(|sym| sym.kind() == SymbolKind::Data || sym.kind() == SymbolKind::Unknown)
            .find(|sym| sym.name() == Some(symbol_name))
        {
            let offset = symbol.address() as usize;
            let len = symbol.size() as usize;
            return Ok(Some(SymbolData { offset, len }));
        }
        Ok(None)
    }
}
<commit_msg>fix unused import<commit_after>use crate::error::Error::{self, IOError, ModuleSignatureError};
use crate::module::{SerializedModule, LUCET_MODULE_SYM};
use crate::module_data::MODULE_DATA_SYM;
use crate::ModuleData;
use byteorder::{ByteOrder, LittleEndian};
use memoffset::offset_of;
pub use minisign::{PublicKey, SecretKey};
use minisign::{SignatureBones, SignatureBox};
use object::{NativeFile as ObjectFile, Object, SymbolKind};
use std::fs::{File, OpenOptions};
use std::io::{self, Cursor, Read, Seek, SeekFrom, Write};
use std::path::Path;

pub struct ModuleSignature;

impl ModuleSignature {
    pub fn verify<P: AsRef<Path>>(
        so_path: P,
        pk: &PublicKey,
        module_data: &ModuleData<'_>,
    ) -> Result<(), Error> {
        let signature_box: SignatureBox =
            SignatureBones::from_bytes(&module_data.get_module_signature())
                .map_err(ModuleSignatureError)?
                .into();

        let mut raw_module_and_data = RawModuleAndData::from_file(&so_path).map_err(IOError)?;
        let cleared_module_data_bin =
            ModuleData::clear_module_signature(raw_module_and_data.module_data_bin())?;
        raw_module_and_data.patch_module_data(&cleared_module_data_bin);

        minisign::verify(
            &pk,
            &signature_box,
            Cursor::new(&raw_module_and_data.obj_bin),
            true,
            false,
        )
        .map_err(ModuleSignatureError)
    }

    pub fn sign<P: AsRef<Path>>(path: P, sk: &SecretKey) -> Result<(), Error> {
        let raw_module_and_data = RawModuleAndData::from_file(&path).map_err(IOError)?;
        let signature_box = minisign::sign(
            None,
            sk,
            Cursor::new(&raw_module_and_data.obj_bin),
            true,
            None,
            None,
        )
        .map_err(ModuleSignatureError)?;
        let signature_bones: SignatureBones = signature_box.into();
        let patched_module_data_bin = ModuleData::patch_module_signature(
            raw_module_and_data.module_data_bin(),
            &signature_bones.to_bytes(),
        )?;
        raw_module_and_data
            .write_patched_module_data(&path, &patched_module_data_bin)
            .map_err(IOError)?;
        Ok(())
    }
}

#[allow(dead_code)]
struct SymbolData {
    offset: usize,
    len: usize,
}

struct RawModuleAndData {
    pub obj_bin: Vec<u8>,
    pub module_data_offset: usize,
    pub module_data_len: usize,
}

impl RawModuleAndData {
    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, io::Error> {
        let mut obj_bin: Vec<u8> = Vec::new();
        File::open(&path)?.read_to_end(&mut obj_bin)?;

        let native_data_symbol_data =
            Self::symbol_data(&obj_bin, LUCET_MODULE_SYM, true)?.ok_or(io::Error::new(
                io::ErrorKind::InvalidInput,
                format!("`{}` symbol not present", LUCET_MODULE_SYM),
            ))?;

        // While `module_data` is the first field of the `SerializedModule` that `lucet_module` points
        // to, it is a virtual address, not a file offset. The translation is somewhat tricky at
        // the moment, so just look at the corresponding `lucet_module_data` symbol for now.
        let module_data_symbol_data =
            Self::symbol_data(&obj_bin, MODULE_DATA_SYM, true)?.ok_or(io::Error::new(
                io::ErrorKind::InvalidInput,
                format!("`{}` symbol not present", MODULE_DATA_SYM),
            ))?;

        let module_data_len = LittleEndian::read_u64(
            &obj_bin[(native_data_symbol_data.offset
                + offset_of!(SerializedModule, module_data_len))..],
        ) as usize;

        Ok(RawModuleAndData {
            obj_bin,
            module_data_offset: module_data_symbol_data.offset,
            module_data_len,
        })
    }

    pub fn module_data_bin(&self) -> &[u8] {
        &self.obj_bin[self.module_data_offset as usize
            ..self.module_data_offset as usize + self.module_data_len]
    }

    pub fn module_data_bin_mut(&mut self) -> &mut [u8] {
        &mut self.obj_bin[self.module_data_offset as usize
            ..self.module_data_offset as usize + self.module_data_len]
    }

    pub fn patch_module_data(&mut self, module_data_bin: &[u8]) {
        self.module_data_bin_mut().copy_from_slice(&module_data_bin);
    }

    pub fn write_patched_module_data<P: AsRef<Path>>(
        &self,
        path: P,
        patched_module_data_bin: &[u8],
    ) -> Result<(), io::Error> {
        let mut fp = OpenOptions::new()
            .write(true)
            .create_new(false)
            .open(&path)?;
        fp.seek(SeekFrom::Start(self.module_data_offset as u64))?;
        fp.write_all(&patched_module_data_bin)?;
        Ok(())
    }

    // Retrieving the offset of a symbol is not supported by the object crate.
    // In Mach-O, actual file offsets are encoded, whereas Elf encodes virtual
    // addresses, requiring extra steps to retrieve the section, its base
    // address as well as the section offset.

    // Elf
    #[cfg(all(target_family = "unix", not(target_os = "macos")))]
    fn symbol_data(
        obj_bin: &[u8],
        symbol_name: &str,
        _mangle: bool,
    ) -> Result<Option<SymbolData>, io::Error> {
        use object::read::ObjectSection;

        let obj = ObjectFile::parse(obj_bin)
            .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
        let symbol_map = obj.symbol_map();
        for symbol in symbol_map
            .symbols()
            .iter()
            .filter(|sym| sym.kind() == SymbolKind::Data)
            .filter(|sym| sym.name() == Some(symbol_name))
            .filter(|sym| sym.section_index().is_some())
        {
            let section_index = symbol.section_index().unwrap();
            let section = &obj.section_by_index(section_index).unwrap();
            let section_address = section.address();
            let section_offset = match section.file_range() {
                Some((offset, _)) => offset,
                None => Err(io::Error::new(
                    io::ErrorKind::InvalidInput,
                    "symbol has no associated data",
                ))?,
            };

            let offset = (symbol.address() - section_address + section_offset) as usize;
            let len = symbol.size() as usize;
            return Ok(Some(SymbolData { offset, len }));
        }
        Ok(None)
    }

    // Mach-O
    #[cfg(target_os = "macos")]
    fn symbol_data(
        obj_bin: &[u8],
        symbol_name: &str,
        mangle: bool,
    ) -> Result<Option<SymbolData>, io::Error> {
        let obj = ObjectFile::parse(obj_bin)
            .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
        let symbol_map = obj.symbol_map();
        let mangled_symbol_name = format!("_{}", symbol_name);
        let symbol_name = if mangle {
            &mangled_symbol_name
        } else {
            symbol_name
        };
        if let Some(symbol) = symbol_map
            .symbols()
            .iter()
            .filter(|sym| sym.kind() == SymbolKind::Data || sym.kind() == SymbolKind::Unknown)
            .find(|sym| sym.name() == Some(symbol_name))
        {
            let offset = symbol.address() as usize;
            let len = symbol.size() as usize;
            return Ok(Some(SymbolData { offset, len }));
        }
        Ok(None)
    }
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

<%! from data import to_rust_ident %>
<%namespace name="helpers" file="/helpers.mako.rs" />
<% from data import ALL_SIZES, PHYSICAL_SIDES, LOGICAL_SIDES %>

<% data.new_style_struct("Position", inherited=False) %>

// "top" / "left" / "bottom" / "right"
% for side in PHYSICAL_SIDES:
    ${helpers.predefined_type(side, "LengthOrPercentageOrAuto",
                              "computed::LengthOrPercentageOrAuto::Auto",
                              spec="https://www.w3.org/TR/CSS2/visuren.html#propdef-%s" % side,
                              animation_value_type="ComputedValue",
                              allow_quirks=True, servo_restyle_damage = "reflow_out_of_flow")}
% endfor
// offset-* logical properties, map to "top" / "left" / "bottom" / "right"
% for side in LOGICAL_SIDES:
    ${helpers.predefined_type("offset-%s" % side, "LengthOrPercentageOrAuto",
                              "computed::LengthOrPercentageOrAuto::Auto",
                              spec="https://drafts.csswg.org/css-logical-props/#propdef-offset-%s" % side,
                              animation_value_type="ComputedValue", logical=True)}
% endfor

#[cfg(feature = "gecko")]
macro_rules! impl_align_conversions {
    ($name: path) => {
        impl From<u8> for $name {
            fn from(bits: u8) -> $name {
                $name(::values::specified::align::AlignFlags::from_bits(bits)
                      .expect("bits contain valid flag"))
            }
        }

        impl From<$name> for u8 {
            fn from(v: $name) -> u8 {
                v.0.bits()
            }
        }
    };
}

${helpers.predefined_type(
    "z-index",
    "ZIndex",
    "computed::ZIndex::auto()",
    spec="https://www.w3.org/TR/CSS2/visuren.html#z-index",
    flags="CREATES_STACKING_CONTEXT",
    animation_value_type="ComputedValue",
)}

// CSS Flexible Box Layout Module Level 1
// http://www.w3.org/TR/css3-flexbox/

// Flex container properties
${helpers.single_keyword("flex-direction", "row row-reverse column column-reverse",
                         spec="https://drafts.csswg.org/css-flexbox/#flex-direction-property",
                         extra_prefixes="webkit", animation_value_type="discrete",
                         servo_restyle_damage = "reflow")}

${helpers.single_keyword("flex-wrap", "nowrap wrap wrap-reverse",
                         spec="https://drafts.csswg.org/css-flexbox/#flex-wrap-property",
                         extra_prefixes="webkit", animation_value_type="discrete",
                         servo_restyle_damage = "reflow")}

% if product == "servo":
    // FIXME: Update Servo to support the same Syntax as Gecko.
    ${helpers.single_keyword("justify-content", "flex-start stretch flex-end center space-between space-around",
                             extra_prefixes="webkit",
                             spec="https://drafts.csswg.org/css-align/#propdef-justify-content",
                             animation_value_type="discrete",
                             servo_restyle_damage = "reflow")}
% else:
    ${helpers.predefined_type(name="justify-content",
                              type="JustifyContent",
                              initial_value="specified::JustifyContent(specified::ContentDistribution::normal())",
                              spec="https://drafts.csswg.org/css-align/#propdef-justify-content",
                              extra_prefixes="webkit",
                              animation_value_type="discrete",
                              servo_restyle_damage = "reflow")}
% endif

% if product == "servo":
    // FIXME: Update Servo to support the same Syntax as Gecko.
    ${helpers.single_keyword("align-content", "stretch flex-start flex-end center space-between space-around",
                             extra_prefixes="webkit",
                             spec="https://drafts.csswg.org/css-align/#propdef-align-content",
                             animation_value_type="discrete",
                             servo_restyle_damage = "reflow")}

    ${helpers.single_keyword("align-items",
                             "stretch flex-start flex-end center baseline",
                             extra_prefixes="webkit",
                             spec="https://drafts.csswg.org/css-flexbox/#align-items-property",
                             animation_value_type="discrete",
                             servo_restyle_damage = "reflow")}
% else:
    ${helpers.predefined_type(name="align-content",
                              type="AlignContent",
                              initial_value="specified::AlignContent(specified::ContentDistribution::normal())",
                              spec="https://drafts.csswg.org/css-align/#propdef-align-content",
                              extra_prefixes="webkit",
                              animation_value_type="discrete",
                              servo_restyle_damage = "reflow")}

    ${helpers.predefined_type(name="align-items",
                              type="AlignItems",
                              initial_value="specified::AlignItems::normal()",
                              spec="https://drafts.csswg.org/css-align/#propdef-align-items",
                              extra_prefixes="webkit",
                              animation_value_type="discrete",
                              servo_restyle_damage = "reflow")}

    #[cfg(feature = "gecko")]
    impl_align_conversions!(::values::specified::align::AlignItems);

    ${helpers.predefined_type(name="justify-items",
                              type="JustifyItems",
                              initial_value="computed::JustifyItems::auto()",
                              spec="https://drafts.csswg.org/css-align/#propdef-justify-items",
                              animation_value_type="discrete")}

    #[cfg(feature = "gecko")]
    impl_align_conversions!(::values::specified::align::JustifyItems);
% endif

// Flex item properties
${helpers.predefined_type("flex-grow", "NonNegativeNumber",
                          "From::from(0.0)",
                          spec="https://drafts.csswg.org/css-flexbox/#flex-grow-property",
                          extra_prefixes="webkit",
                          animation_value_type="NonNegativeNumber",
                          servo_restyle_damage = "reflow")}

${helpers.predefined_type("flex-shrink", "NonNegativeNumber",
                          "From::from(1.0)",
                          spec="https://drafts.csswg.org/css-flexbox/#flex-shrink-property",
                          extra_prefixes="webkit",
                          animation_value_type="NonNegativeNumber",
                          servo_restyle_damage = "reflow")}

// https://drafts.csswg.org/css-align/#align-self-property
% if product == "servo":
    // FIXME: Update Servo to support the same syntax as Gecko.
    ${helpers.single_keyword("align-self", "auto stretch flex-start flex-end center baseline",
                             extra_prefixes="webkit",
                             spec="https://drafts.csswg.org/css-flexbox/#propdef-align-self",
                             animation_value_type="discrete",
                             servo_restyle_damage = "reflow")}
% else:
    ${helpers.predefined_type(name="align-self",
                              type="AlignSelf",
                              initial_value="specified::AlignSelf(specified::SelfAlignment::auto())",
                              spec="https://drafts.csswg.org/css-align/#align-self-property",
                              extra_prefixes="webkit",
                              animation_value_type="discrete")}

    ${helpers.predefined_type(name="justify-self",
                              type="JustifySelf",
                              initial_value="specified::JustifySelf(specified::SelfAlignment::auto())",
                              spec="https://drafts.csswg.org/css-align/#justify-self-property",
                              animation_value_type="discrete")}

    #[cfg(feature = "gecko")]
    impl_align_conversions!(::values::specified::align::SelfAlignment);
% endif

// https://drafts.csswg.org/css-flexbox/#propdef-order
${helpers.predefined_type("order", "Integer", "0",
                          extra_prefixes="webkit",
                          animation_value_type="ComputedValue",
                          spec="https://drafts.csswg.org/css-flexbox/#order-property",
                          servo_restyle_damage = "reflow")}

% if product == "gecko":
    // FIXME: Gecko doesn't support content value yet.
    //
    // FIXME(emilio): I suspect this property shouldn't allow quirks, and this
    // was just a mistake, it's kind of justificable to support it given the
    // spec grammar is just `content | <width>`, but other browsers don't...
    ${helpers.predefined_type(
        "flex-basis",
        "MozLength",
        "computed::MozLength::auto()",
        extra_prefixes="webkit",
        animation_value_type="MozLength",
        allow_quirks=True,
        spec="https://drafts.csswg.org/css-flexbox/#flex-basis-property",
        servo_restyle_damage = "reflow"
    )}
% else:
    // FIXME: This property should be animatable.
    ${helpers.predefined_type("flex-basis",
                              "FlexBasis",
                              "computed::FlexBasis::auto()",
                              spec="https://drafts.csswg.org/css-flexbox/#flex-basis-property",
                              extra_prefixes="webkit",
                              animation_value_type="none",
                              servo_restyle_damage = "reflow")}
% endif
% for (size, logical) in ALL_SIZES:
    <%
        spec = "https://drafts.csswg.org/css-box/#propdef-%s"
        if logical:
            spec = "https://drafts.csswg.org/css-logical-props/#propdef-%s"
    %>
    // NOTE: Block-size doesn't support -moz-*-content keywords, since they make
    // no sense on the block axis, but it simplifies things the have that it has
    // the same type as the other properties, since otherwise we'd need to
    // handle logical props where the types are different, which looks like a
    // pain.
    % if product == "gecko":
        <%
            parse_function = "parse" if size != "block-size" else "parse_disallow_keyword"
        %>
        // width, height, block-size, inline-size
        ${helpers.predefined_type(
            size,
            "MozLength",
            "computed::MozLength::auto()",
            parse_function,
            logical=logical,
            allow_quirks=not logical,
            spec=spec % size,
            animation_value_type="MozLength",
            servo_restyle_damage = "reflow"
        )}
        // min-width, min-height, min-block-size, min-inline-size,
        ${helpers.predefined_type(
            "min-%s" % size,
            "MozLength",
            "computed::MozLength::auto()",
            parse_function,
            logical=logical,
            allow_quirks=not logical,
            spec=spec % size,
            animation_value_type="MozLength",
            servo_restyle_damage = "reflow"
        )}
        ${helpers.predefined_type(
            "max-%s" % size,
            "MaxLength",
            "computed::MaxLength::none()",
            parse_function,
            logical=logical,
            allow_quirks=not logical,
            spec=spec % size,
            animation_value_type="MaxLength",
            servo_restyle_damage = "reflow"
        )}
    % else:
        // servo versions (no keyword support)
        ${helpers.predefined_type(size,
                                  "LengthOrPercentageOrAuto",
                                  "computed::LengthOrPercentageOrAuto::Auto",
                                  "parse_non_negative",
                                  spec=spec % size,
                                  allow_quirks=not logical,
                                  animation_value_type="ComputedValue", logical = logical,
                                  servo_restyle_damage = "reflow")}
        ${helpers.predefined_type("min-%s" % size,
                                  "LengthOrPercentage",
                                  "computed::LengthOrPercentage::Length(computed::Length::new(0.))",
                                  "parse_non_negative",
                                  spec=spec % ("min-%s" % size),
                                  animation_value_type="ComputedValue",
                                  logical=logical,
                                  allow_quirks=not logical,
                                  servo_restyle_damage = "reflow")}
        ${helpers.predefined_type("max-%s" % size,
                                  "LengthOrPercentageOrNone",
                                  "computed::LengthOrPercentageOrNone::None",
                                  "parse_non_negative",
                                  spec=spec % ("min-%s" % size),
                                  animation_value_type="ComputedValue",
                                  logical=logical,
                                  allow_quirks=not logical,
                                  servo_restyle_damage = "reflow")}
    % endif
% endfor

${helpers.single_keyword("box-sizing",
                         "content-box border-box",
                         extra_prefixes="moz webkit",
                         spec="https://drafts.csswg.org/css-ui/#propdef-box-sizing",
                         gecko_enum_prefix="StyleBoxSizing",
                         custom_consts={ "content-box": "Content", "border-box": "Border" },
                         animation_value_type="discrete",
                         servo_restyle_damage = "reflow")}

${helpers.single_keyword("object-fit", "fill contain cover none scale-down",
                         products="gecko", animation_value_type="discrete",
                         spec="https://drafts.csswg.org/css-images/#propdef-object-fit")}

${helpers.predefined_type("object-position",
                          "Position",
                          "computed::Position::zero()",
                          products="gecko",
                          boxed=True,
                          spec="https://drafts.csswg.org/css-images-3/#the-object-position",
                          animation_value_type="ComputedValue")}

% for kind in ["row", "column"]:
    ${helpers.predefined_type("grid-%s-gap" % kind,
                              "NonNegativeLengthOrPercentage",
                              "computed::NonNegativeLengthOrPercentage::zero()",
                              spec="https://drafts.csswg.org/css-grid/#propdef-grid-%s-gap" % kind,
                              animation_value_type="NonNegativeLengthOrPercentage",
                              products="gecko")}

    % for range in ["start", "end"]:
        ${helpers.predefined_type("grid-%s-%s" % (kind, range),
                                  "GridLine",
                                  "Default::default()",
                                  animation_value_type="discrete",
                                  spec="https://drafts.csswg.org/css-grid/#propdef-grid-%s-%s" % (kind, range),
                                  products="gecko",
                                  boxed=True)}
    % endfor

    // NOTE: According to the spec, this should handle multiple values of `<track-size>`,
    // but gecko supports only a single value
    ${helpers.predefined_type("grid-auto-%ss" % kind,
                              "TrackSize",
                              "Default::default()",
                              animation_value_type="discrete",
                              spec="https://drafts.csswg.org/css-grid/#propdef-grid-auto-%ss" % kind,
                              products="gecko",
                              boxed=True)}

    ${helpers.predefined_type("grid-template-%ss" % kind,
                              "GridTemplateComponent",
                              "specified::GenericGridTemplateComponent::None",
                              products="gecko",
                              spec="https://drafts.csswg.org/css-grid/#propdef-grid-template-%ss" % kind,
                              boxed=True,
                              animation_value_type="discrete")}

% endfor

${helpers.predefined_type("grid-auto-flow",
                          "GridAutoFlow",
                          initial_value="computed::GridAutoFlow::row()",
                          products="gecko",
                          animation_value_type="discrete",
                          spec="https://drafts.csswg.org/css-grid/#propdef-grid-auto-flow")}

${helpers.predefined_type("grid-template-areas",
                          "GridTemplateAreas",
                          initial_value="computed::GridTemplateAreas::none()",
                          products="gecko",
                          animation_value_type="discrete",
                          gecko_pref="layout.css.grid.enabled",
                          spec="https://drafts.csswg.org/css-grid/#propdef-grid-template-areas")}
<commit_msg>style: Remove a stray pref value.<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

<%! from data import to_rust_ident %>
<%namespace name="helpers" file="/helpers.mako.rs" />
<% from data import ALL_SIZES, PHYSICAL_SIDES, LOGICAL_SIDES %>

<% data.new_style_struct("Position", inherited=False) %>

// "top" / "left" / "bottom" / "right"
% for side in PHYSICAL_SIDES:
    ${helpers.predefined_type(side, "LengthOrPercentageOrAuto",
                              "computed::LengthOrPercentageOrAuto::Auto",
                              spec="https://www.w3.org/TR/CSS2/visuren.html#propdef-%s" % side,
                              animation_value_type="ComputedValue",
                              allow_quirks=True, servo_restyle_damage = "reflow_out_of_flow")}
% endfor
// offset-* logical properties, map to "top" / "left" / "bottom" / "right"
% for side in LOGICAL_SIDES:
    ${helpers.predefined_type("offset-%s" % side, "LengthOrPercentageOrAuto",
                              "computed::LengthOrPercentageOrAuto::Auto",
                              spec="https://drafts.csswg.org/css-logical-props/#propdef-offset-%s" % side,
                              animation_value_type="ComputedValue", logical=True)}
% endfor

#[cfg(feature = "gecko")]
macro_rules! impl_align_conversions {
    ($name: path) => {
        impl From<u8> for $name {
            fn from(bits: u8) -> $name {
                $name(::values::specified::align::AlignFlags::from_bits(bits)
                      .expect("bits contain valid flag"))
            }
        }

        impl From<$name> for u8 {
            fn from(v: $name) -> u8 {
                v.0.bits()
            }
        }
    };
}

${helpers.predefined_type(
    "z-index",
    "ZIndex",
    "computed::ZIndex::auto()",
    spec="https://www.w3.org/TR/CSS2/visuren.html#z-index",
    flags="CREATES_STACKING_CONTEXT",
    animation_value_type="ComputedValue",
)}

// CSS Flexible Box Layout Module Level 1
// http://www.w3.org/TR/css3-flexbox/

// Flex container properties
${helpers.single_keyword("flex-direction", "row row-reverse column column-reverse",
                         spec="https://drafts.csswg.org/css-flexbox/#flex-direction-property",
                         extra_prefixes="webkit", animation_value_type="discrete",
                         servo_restyle_damage = "reflow")}

${helpers.single_keyword("flex-wrap", "nowrap wrap wrap-reverse",
                         spec="https://drafts.csswg.org/css-flexbox/#flex-wrap-property",
                         extra_prefixes="webkit", animation_value_type="discrete",
                         servo_restyle_damage = "reflow")}

% if product == "servo":
    // FIXME: Update Servo to support the same Syntax as Gecko.
    ${helpers.single_keyword("justify-content", "flex-start stretch flex-end center space-between space-around",
                             extra_prefixes="webkit",
                             spec="https://drafts.csswg.org/css-align/#propdef-justify-content",
                             animation_value_type="discrete",
                             servo_restyle_damage = "reflow")}
% else:
    ${helpers.predefined_type(name="justify-content",
                              type="JustifyContent",
                              initial_value="specified::JustifyContent(specified::ContentDistribution::normal())",
                              spec="https://drafts.csswg.org/css-align/#propdef-justify-content",
                              extra_prefixes="webkit",
                              animation_value_type="discrete",
                              servo_restyle_damage = "reflow")}
% endif

% if product == "servo":
    // FIXME: Update Servo to support the same Syntax as Gecko.
    ${helpers.single_keyword("align-content", "stretch flex-start flex-end center space-between space-around",
                             extra_prefixes="webkit",
                             spec="https://drafts.csswg.org/css-align/#propdef-align-content",
                             animation_value_type="discrete",
                             servo_restyle_damage = "reflow")}

    ${helpers.single_keyword("align-items",
                             "stretch flex-start flex-end center baseline",
                             extra_prefixes="webkit",
                             spec="https://drafts.csswg.org/css-flexbox/#align-items-property",
                             animation_value_type="discrete",
                             servo_restyle_damage = "reflow")}
% else:
    ${helpers.predefined_type(name="align-content",
                              type="AlignContent",
                              initial_value="specified::AlignContent(specified::ContentDistribution::normal())",
                              spec="https://drafts.csswg.org/css-align/#propdef-align-content",
                              extra_prefixes="webkit",
                              animation_value_type="discrete",
                              servo_restyle_damage = "reflow")}

    ${helpers.predefined_type(name="align-items",
                              type="AlignItems",
                              initial_value="specified::AlignItems::normal()",
                              spec="https://drafts.csswg.org/css-align/#propdef-align-items",
                              extra_prefixes="webkit",
                              animation_value_type="discrete",
                              servo_restyle_damage = "reflow")}

    #[cfg(feature = "gecko")]
    impl_align_conversions!(::values::specified::align::AlignItems);

    ${helpers.predefined_type(name="justify-items",
                              type="JustifyItems",
                              initial_value="computed::JustifyItems::auto()",
                              spec="https://drafts.csswg.org/css-align/#propdef-justify-items",
                              animation_value_type="discrete")}

    #[cfg(feature = "gecko")]
    impl_align_conversions!(::values::specified::align::JustifyItems);
% endif

// Flex item properties
${helpers.predefined_type("flex-grow", "NonNegativeNumber",
                          "From::from(0.0)",
                          spec="https://drafts.csswg.org/css-flexbox/#flex-grow-property",
                          extra_prefixes="webkit",
                          animation_value_type="NonNegativeNumber",
                          servo_restyle_damage = "reflow")}

${helpers.predefined_type("flex-shrink", "NonNegativeNumber",
                          "From::from(1.0)",
                          spec="https://drafts.csswg.org/css-flexbox/#flex-shrink-property",
                          extra_prefixes="webkit",
                          animation_value_type="NonNegativeNumber",
                          servo_restyle_damage = "reflow")}

// https://drafts.csswg.org/css-align/#align-self-property
% if product == "servo":
    // FIXME: Update Servo to support the same syntax as Gecko.
    ${helpers.single_keyword("align-self", "auto stretch flex-start flex-end center baseline",
                             extra_prefixes="webkit",
                             spec="https://drafts.csswg.org/css-flexbox/#propdef-align-self",
                             animation_value_type="discrete",
                             servo_restyle_damage = "reflow")}
% else:
    ${helpers.predefined_type(name="align-self",
                              type="AlignSelf",
                              initial_value="specified::AlignSelf(specified::SelfAlignment::auto())",
                              spec="https://drafts.csswg.org/css-align/#align-self-property",
                              extra_prefixes="webkit",
                              animation_value_type="discrete")}

    ${helpers.predefined_type(name="justify-self",
                              type="JustifySelf",
                              initial_value="specified::JustifySelf(specified::SelfAlignment::auto())",
                              spec="https://drafts.csswg.org/css-align/#justify-self-property",
                              animation_value_type="discrete")}

    #[cfg(feature = "gecko")]
    impl_align_conversions!(::values::specified::align::SelfAlignment);
% endif

// https://drafts.csswg.org/css-flexbox/#propdef-order
${helpers.predefined_type("order", "Integer", "0",
                          extra_prefixes="webkit",
                          animation_value_type="ComputedValue",
                          spec="https://drafts.csswg.org/css-flexbox/#order-property",
                          servo_restyle_damage = "reflow")}

% if product == "gecko":
    // FIXME: Gecko doesn't support content value yet.
    //
    // FIXME(emilio): I suspect this property shouldn't allow quirks, and this
    // was just a mistake, it's kind of justificable to support it given the
    // spec grammar is just `content | <width>`, but other browsers don't...
    ${helpers.predefined_type(
        "flex-basis",
        "MozLength",
        "computed::MozLength::auto()",
        extra_prefixes="webkit",
        animation_value_type="MozLength",
        allow_quirks=True,
        spec="https://drafts.csswg.org/css-flexbox/#flex-basis-property",
        servo_restyle_damage = "reflow"
    )}
% else:
    // FIXME: This property should be animatable.
    ${helpers.predefined_type("flex-basis",
                              "FlexBasis",
                              "computed::FlexBasis::auto()",
                              spec="https://drafts.csswg.org/css-flexbox/#flex-basis-property",
                              extra_prefixes="webkit",
                              animation_value_type="none",
                              servo_restyle_damage = "reflow")}
% endif
% for (size, logical) in ALL_SIZES:
    <%
        spec = "https://drafts.csswg.org/css-box/#propdef-%s"
        if logical:
            spec = "https://drafts.csswg.org/css-logical-props/#propdef-%s"
    %>
    // NOTE: Block-size doesn't support -moz-*-content keywords, since they make
    // no sense on the block axis, but it simplifies things the have that it has
    // the same type as the other properties, since otherwise we'd need to
    // handle logical props where the types are different, which looks like a
    // pain.
    % if product == "gecko":
        <%
            parse_function = "parse" if size != "block-size" else "parse_disallow_keyword"
        %>
        // width, height, block-size, inline-size
        ${helpers.predefined_type(
            size,
            "MozLength",
            "computed::MozLength::auto()",
            parse_function,
            logical=logical,
            allow_quirks=not logical,
            spec=spec % size,
            animation_value_type="MozLength",
            servo_restyle_damage = "reflow"
        )}
        // min-width, min-height, min-block-size, min-inline-size,
        ${helpers.predefined_type(
            "min-%s" % size,
            "MozLength",
            "computed::MozLength::auto()",
            parse_function,
            logical=logical,
            allow_quirks=not logical,
            spec=spec % size,
            animation_value_type="MozLength",
            servo_restyle_damage = "reflow"
        )}
        ${helpers.predefined_type(
            "max-%s" % size,
            "MaxLength",
            "computed::MaxLength::none()",
            parse_function,
            logical=logical,
            allow_quirks=not logical,
            spec=spec % size,
            animation_value_type="MaxLength",
            servo_restyle_damage = "reflow"
        )}
    % else:
        // servo versions (no keyword support)
        ${helpers.predefined_type(size,
                                  "LengthOrPercentageOrAuto",
                                  "computed::LengthOrPercentageOrAuto::Auto",
                                  "parse_non_negative",
                                  spec=spec % size,
                                  allow_quirks=not logical,
                                  animation_value_type="ComputedValue", logical = logical,
                                  servo_restyle_damage = "reflow")}
        ${helpers.predefined_type("min-%s" % size,
                                  "LengthOrPercentage",
                                  "computed::LengthOrPercentage::Length(computed::Length::new(0.))",
                                  "parse_non_negative",
                                  spec=spec % ("min-%s" % size),
                                  animation_value_type="ComputedValue",
                                  logical=logical,
                                  allow_quirks=not logical,
                                  servo_restyle_damage = "reflow")}
        ${helpers.predefined_type("max-%s" % size,
                                  "LengthOrPercentageOrNone",
                                  "computed::LengthOrPercentageOrNone::None",
                                  "parse_non_negative",
                                  spec=spec % ("min-%s" % size),
                                  animation_value_type="ComputedValue",
                                  logical=logical,
                                  allow_quirks=not logical,
                                  servo_restyle_damage = "reflow")}
    % endif
% endfor

${helpers.single_keyword("box-sizing",
                         "content-box border-box",
                         extra_prefixes="moz webkit",
                         spec="https://drafts.csswg.org/css-ui/#propdef-box-sizing",
                         gecko_enum_prefix="StyleBoxSizing",
                         custom_consts={ "content-box": "Content", "border-box": "Border" },
                         animation_value_type="discrete",
                         servo_restyle_damage = "reflow")}

${helpers.single_keyword("object-fit", "fill contain cover none scale-down",
                         products="gecko", animation_value_type="discrete",
                         spec="https://drafts.csswg.org/css-images/#propdef-object-fit")}

${helpers.predefined_type("object-position",
                          "Position",
                          "computed::Position::zero()",
                          products="gecko",
                          boxed=True,
                          spec="https://drafts.csswg.org/css-images-3/#the-object-position",
                          animation_value_type="ComputedValue")}

% for kind in ["row", "column"]:
    ${helpers.predefined_type("grid-%s-gap" % kind,
                              "NonNegativeLengthOrPercentage",
                              "computed::NonNegativeLengthOrPercentage::zero()",
                              spec="https://drafts.csswg.org/css-grid/#propdef-grid-%s-gap" % kind,
                              animation_value_type="NonNegativeLengthOrPercentage",
                              products="gecko")}

    % for range in ["start", "end"]:
        ${helpers.predefined_type("grid-%s-%s" % (kind, range),
                                  "GridLine",
                                  "Default::default()",
                                  animation_value_type="discrete",
                                  spec="https://drafts.csswg.org/css-grid/#propdef-grid-%s-%s" % (kind, range),
                                  products="gecko",
                                  boxed=True)}
    % endfor

    // NOTE: According to the spec, this should handle multiple values of `<track-size>`,
    // but gecko supports only a single value
    ${helpers.predefined_type("grid-auto-%ss" % kind,
                              "TrackSize",
                              "Default::default()",
                              animation_value_type="discrete",
                              spec="https://drafts.csswg.org/css-grid/#propdef-grid-auto-%ss" % kind,
                              products="gecko",
                              boxed=True)}

    ${helpers.predefined_type("grid-template-%ss" % kind,
                              "GridTemplateComponent",
                              "specified::GenericGridTemplateComponent::None",
                              products="gecko",
                              spec="https://drafts.csswg.org/css-grid/#propdef-grid-template-%ss" % kind,
                              boxed=True,
                              animation_value_type="discrete")}

% endfor

${helpers.predefined_type("grid-auto-flow",
                          "GridAutoFlow",
                          initial_value="computed::GridAutoFlow::row()",
                          products="gecko",
                          animation_value_type="discrete",
                          spec="https://drafts.csswg.org/css-grid/#propdef-grid-auto-flow")}

${helpers.predefined_type("grid-template-areas",
                          "GridTemplateAreas",
                          initial_value="computed::GridTemplateAreas::none()",
                          products="gecko",
                          animation_value_type="discrete",
                          spec="https://drafts.csswg.org/css-grid/#propdef-grid-template-areas")}
<|endoftext|>


<commit_before>use crate::series::SeriesParams;
use crate::tui::component::Component;
use crate::tui::widget_util::{block, style};
use crate::{key::Key, series::info::SeriesInfo};
use crossterm::event::KeyCode;
use tui::backend::Backend;
use tui::layout::Rect;
use tui::style::Color;
use tui::terminal::Frame;
use tui::widgets::{List, ListItem, ListState};
use tui_utils::list::WrappedSelection;

pub struct SelectSeriesPanel {
    list_state: ListState,
    state: SelectState,
}

impl SelectSeriesPanel {
    #[inline(always)]
    pub fn new(state: SelectState) -> Self {
        Self {
            list_state: ListState::default(),
            state,
        }
    }

    #[inline(always)]
    pub fn take_params(self) -> SeriesParams {
        self.state.params
    }

    pub fn draw<B: Backend>(&mut self, rect: Rect, frame: &mut Frame<B>) {
        let names = self
            .state
            .series_list
            .iter()
            .map(|info| ListItem::new(info.title_preferred.as_ref()))
            .collect::<Vec<_>>();

        let items = List::new(names)
            .block(block::with_borders("Select a series from the list"))
            .style(style::fg(Color::White))
            .highlight_style(style::italic().fg(Color::Green))
            .highlight_symbol(">");

        self.list_state.select(Some(self.state.series_list.index()));

        frame.render_stateful_widget(items, rect, &mut self.list_state);
    }
}

impl Component for SelectSeriesPanel {
    type State = ();
    type KeyResult = SelectSeriesResult;

    fn process_key(&mut self, key: Key, _: &mut Self::State) -> Self::KeyResult {
        match *key {
            KeyCode::Up => {
                self.state.series_list.dec_selected();
                SelectSeriesResult::Ok
            }
            KeyCode::Down => {
                self.state.series_list.inc_selected();
                SelectSeriesResult::Ok
            }
            KeyCode::Enter => {
                if !self.state.series_list.is_valid_index() {
                    return SelectSeriesResult::Reset;
                }

                let selected = self.state.series_list.index();
                let info = self.state.series_list.swap_remove(selected);

                SelectSeriesResult::AddSeries(info)
            }
            KeyCode::Esc => SelectSeriesResult::Reset,
            _ => SelectSeriesResult::Ok,
        }
    }
}

pub struct SelectState {
    pub series_list: WrappedSelection<Vec<SeriesInfo>, SeriesInfo>,
    pub params: SeriesParams,
}

impl SelectState {
    pub fn new(series_list: Vec<SeriesInfo>, params: SeriesParams) -> Self {
        Self {
            series_list: WrappedSelection::new(series_list),
            params,
        }
    }
}

pub enum SelectSeriesResult {
    Ok,
    AddSeries(SeriesInfo),
    Reset,
}
<commit_msg>anup: Convert select series panel to use widgets from tui-utils library<commit_after>use crate::tui::component::Component;
use crate::tui::widget_util::block;
use crate::{key::Key, series::info::SeriesInfo};
use crate::{series::SeriesParams, tui::widget_util::text};
use crossterm::event::KeyCode;
use tui::layout::Rect;
use tui::style::Color;
use tui::terminal::Frame;
use tui::{backend::Backend, text::Span};
use tui_utils::{list::WrappedSelection, widgets::SimpleList};

pub struct SelectSeriesPanel {
    state: SelectState,
}

impl SelectSeriesPanel {
    #[inline(always)]
    pub fn new(state: SelectState) -> Self {
        Self { state }
    }

    #[inline(always)]
    pub fn take_params(self) -> SeriesParams {
        self.state.params
    }

    pub fn draw<B: Backend>(&mut self, rect: Rect, frame: &mut Frame<B>) {
        let block = block::with_borders("Select a series from the list");
        let block_area = block.inner(rect);

        frame.render_widget(block, rect);

        let names = self
            .state
            .series_list
            .iter()
            .map(|info| Span::raw(info.title_preferred.as_str()));

        let items = SimpleList::new(names)
            .highlight_symbol(text::italic_with(">", |s| s.fg(Color::Green)))
            .select(Some(self.state.series_list.index() as u16));

        frame.render_widget(items, block_area);
    }
}

impl Component for SelectSeriesPanel {
    type State = ();
    type KeyResult = SelectSeriesResult;

    fn process_key(&mut self, key: Key, _: &mut Self::State) -> Self::KeyResult {
        match *key {
            KeyCode::Up => {
                self.state.series_list.dec_selected();
                SelectSeriesResult::Ok
            }
            KeyCode::Down => {
                self.state.series_list.inc_selected();
                SelectSeriesResult::Ok
            }
            KeyCode::Enter => {
                if !self.state.series_list.is_valid_index() {
                    return SelectSeriesResult::Reset;
                }

                let selected = self.state.series_list.index();
                let info = self.state.series_list.swap_remove(selected);

                SelectSeriesResult::AddSeries(info)
            }
            KeyCode::Esc => SelectSeriesResult::Reset,
            _ => SelectSeriesResult::Ok,
        }
    }
}

pub struct SelectState {
    pub series_list: WrappedSelection<Vec<SeriesInfo>, SeriesInfo>,
    pub params: SeriesParams,
}

impl SelectState {
    pub fn new(series_list: Vec<SeriesInfo>, params: SeriesParams) -> Self {
        Self {
            series_list: WrappedSelection::new(series_list),
            params,
        }
    }
}

pub enum SelectSeriesResult {
    Ok,
    AddSeries(SeriesInfo),
    Reset,
}
<|endoftext|>


<commit_before>//! Get the system's UTC offset on Unix.

use core::mem::MaybeUninit;

use crate::{OffsetDateTime, UtcOffset};

/// Convert the given Unix timestamp to a `libc::tm`. Returns `None` on any error.
///
/// # Safety
///
/// This method must only be called when the process is single-threaded.
///
/// This method will remain `unsafe` until `std::env::set_var` is deprecated or has its behavior
/// altered. This method is, on its own, safe. It is the presence of a safe, unsound way to set
/// environment variables that makes it unsafe.
unsafe fn timestamp_to_tm(timestamp: i64) -> Option<libc::tm> {
    extern "C" {
        #[cfg_attr(target_os = "netbsd", link_name = "__tzset50")]
        fn tzset();
    }

    // The exact type of `timestamp` beforehand can vary, so this conversion is necessary.
    #[allow(clippy::useless_conversion)]
    let timestamp = timestamp.try_into().ok()?;

    let mut tm = MaybeUninit::uninit();

    // Update timezone information from system. `localtime_r` does not do this for us.
    //
    // Safety: tzset is thread-safe.
    unsafe { tzset() };

    // Safety: We are calling a system API, which mutates the `tm` variable. If a null
    // pointer is returned, an error occurred.
    let tm_ptr = unsafe { libc::localtime_r(&timestamp, tm.as_mut_ptr()) };

    if tm_ptr.is_null() {
        None
    } else {
        // Safety: The value was initialized, as we no longer have a null pointer.
        Some(unsafe { tm.assume_init() })
    }
}

/// Convert a `libc::tm` to a `UtcOffset`. Returns `None` on any error.
// `tm_gmtoff` extension
#[cfg(not(any(target_os = "solaris", target_os = "illumos")))]
fn tm_to_offset(tm: libc::tm) -> Option<UtcOffset> {
    let seconds: i32 = tm.tm_gmtoff.try_into().ok()?;
    UtcOffset::from_hms(
        (seconds / 3_600) as _,
        ((seconds / 60) % 60) as _,
        (seconds % 60) as _,
    )
    .ok()
}

/// Convert a `libc::tm` to a `UtcOffset`. Returns `None` on any error.
// Solaris/Illumos is unsound and requires opting into.
#[cfg(all(
    not(unsound_local_offset),
    any(target_os = "solaris", target_os = "illumos")
))]
#[allow(unused_variables, clippy::missing_const_for_fn)]
fn tm_to_offset(tm: libc::tm) -> Option<UtcOffset> {
    None
}

/// Convert a `libc::tm` to a `UtcOffset`. Returns `None` on any error.
#[cfg(all(
    unsound_local_offset,
    any(target_os = "solaris", target_os = "illumos")
))]
fn tm_to_offset(tm: libc::tm) -> Option<UtcOffset> {
    use crate::Date;

    let mut tm = tm;
    if tm.tm_sec == 60 {
        // Leap seconds are not currently supported.
        tm.tm_sec = 59;
    }

    let local_timestamp =
        Date::from_ordinal_date(1900 + tm.tm_year, u16::try_from(tm.tm_yday).ok()? + 1)
            .ok()?
            .with_hms(
                tm.tm_hour.try_into().ok()?,
                tm.tm_min.try_into().ok()?,
                tm.tm_sec.try_into().ok()?,
            )
            .ok()?
            .assume_utc()
            .unix_timestamp();

    let diff_secs: i32 = (local_timestamp - datetime.unix_timestamp())
        .try_into()
        .ok()?;

    UtcOffset::from_hms(
        (diff_secs / 3_600) as _,
        ((diff_secs / 60) % 60) as _,
        (diff_secs % 60) as _,
    )
    .ok()
}

/// Obtain the system's UTC offset.
pub(super) fn local_offset_at(datetime: OffsetDateTime) -> Option<UtcOffset> {
    // Ensure that the process is single-threaded unless the user has explicitly opted out of this
    // check. This is to prevent issues with the environment being mutated by a different thread in
    // the process while execution of this function is taking place, which can cause a segmentation
    // fault by dereferencing a dangling pointer.
    // If the `num_threads` crate is incapable of determining the number of running threads, then
    // we conservatively return `None` to avoid a soundness bug.
    if !cfg!(unsound_local_offset) && num_threads::is_single_threaded() != Some(true) {
        return None;
    }

    // Safety: We have just confirmed that the process is single-threaded or the user has explicitly
    // opted out of soundness.
    let tm = unsafe { timestamp_to_tm(datetime.unix_timestamp()) }?;
    tm_to_offset(tm)
}
<commit_msg>Better gating for `tm_gmtoff` extension<commit_after>//! Get the system's UTC offset on Unix.

use core::mem::MaybeUninit;

use crate::{OffsetDateTime, UtcOffset};

/// Convert the given Unix timestamp to a `libc::tm`. Returns `None` on any error.
///
/// # Safety
///
/// This method must only be called when the process is single-threaded.
///
/// This method will remain `unsafe` until `std::env::set_var` is deprecated or has its behavior
/// altered. This method is, on its own, safe. It is the presence of a safe, unsound way to set
/// environment variables that makes it unsafe.
unsafe fn timestamp_to_tm(timestamp: i64) -> Option<libc::tm> {
    extern "C" {
        #[cfg_attr(target_os = "netbsd", link_name = "__tzset50")]
        fn tzset();
    }

    // The exact type of `timestamp` beforehand can vary, so this conversion is necessary.
    #[allow(clippy::useless_conversion)]
    let timestamp = timestamp.try_into().ok()?;

    let mut tm = MaybeUninit::uninit();

    // Update timezone information from system. `localtime_r` does not do this for us.
    //
    // Safety: tzset is thread-safe.
    unsafe { tzset() };

    // Safety: We are calling a system API, which mutates the `tm` variable. If a null
    // pointer is returned, an error occurred.
    let tm_ptr = unsafe { libc::localtime_r(&timestamp, tm.as_mut_ptr()) };

    if tm_ptr.is_null() {
        None
    } else {
        // Safety: The value was initialized, as we no longer have a null pointer.
        Some(unsafe { tm.assume_init() })
    }
}

/// Convert a `libc::tm` to a `UtcOffset`. Returns `None` on any error.
// This is available to any target known to have the `tm_gmtoff` extension.
#[cfg(any(
    target_os = "redox",
    target_os = "linux",
    target_os = "l4re",
    target_os = "android",
    target_os = "emscripten",
    target_os = "macos",
    target_os = "ios",
    target_os = "watchos",
    target_os = "freebsd",
    target_os = "dragonfly",
    target_os = "openbsd",
    target_os = "netbsd",
    target_os = "haiku",
))]
fn tm_to_offset(tm: libc::tm) -> Option<UtcOffset> {
    let seconds: i32 = tm.tm_gmtoff.try_into().ok()?;
    UtcOffset::from_hms(
        (seconds / 3_600) as _,
        ((seconds / 60) % 60) as _,
        (seconds % 60) as _,
    )
    .ok()
}

/// Convert a `libc::tm` to a `UtcOffset`. Returns `None` on any error.
#[cfg(all(
    not(unsound_local_offset),
    not(any(
        target_os = "redox",
        target_os = "linux",
        target_os = "l4re",
        target_os = "android",
        target_os = "emscripten",
        target_os = "macos",
        target_os = "ios",
        target_os = "watchos",
        target_os = "freebsd",
        target_os = "dragonfly",
        target_os = "openbsd",
        target_os = "netbsd",
        target_os = "haiku",
    ))
))]
#[allow(unused_variables, clippy::missing_const_for_fn)]
fn tm_to_offset(tm: libc::tm) -> Option<UtcOffset> {
    None
}

/// Convert a `libc::tm` to a `UtcOffset`. Returns `None` on any error.
// This method can return an incorrect value, as it only approximates the `tm_gmtoff` field. As such
// it is gated behind `--cfg unsound_local_offset`. The reason it can return an incorrect value is
// that daylight saving time does not start on the same date every year, nor are the rules for
// daylight saving time the same for every year. This implementation assumes 1970 is equivalent to
// every other year, which is not always the case.
#[cfg(all(
    unsound_local_offset,
    not(any(
        target_os = "redox",
        target_os = "linux",
        target_os = "l4re",
        target_os = "android",
        target_os = "emscripten",
        target_os = "macos",
        target_os = "ios",
        target_os = "watchos",
        target_os = "freebsd",
        target_os = "dragonfly",
        target_os = "openbsd",
        target_os = "netbsd",
        target_os = "haiku",
    ))
))]
fn tm_to_offset(tm: libc::tm) -> Option<UtcOffset> {
    use crate::Date;

    let mut tm = tm;
    if tm.tm_sec == 60 {
        // Leap seconds are not currently supported.
        tm.tm_sec = 59;
    }

    let local_timestamp =
        Date::from_ordinal_date(1900 + tm.tm_year, u16::try_from(tm.tm_yday).ok()? + 1)
            .ok()?
            .with_hms(
                tm.tm_hour.try_into().ok()?,
                tm.tm_min.try_into().ok()?,
                tm.tm_sec.try_into().ok()?,
            )
            .ok()?
            .assume_utc()
            .unix_timestamp();

    let diff_secs: i32 = (local_timestamp - datetime.unix_timestamp())
        .try_into()
        .ok()?;

    UtcOffset::from_hms(
        (diff_secs / 3_600) as _,
        ((diff_secs / 60) % 60) as _,
        (diff_secs % 60) as _,
    )
    .ok()
}

/// Obtain the system's UTC offset.
pub(super) fn local_offset_at(datetime: OffsetDateTime) -> Option<UtcOffset> {
    // Ensure that the process is single-threaded unless the user has explicitly opted out of this
    // check. This is to prevent issues with the environment being mutated by a different thread in
    // the process while execution of this function is taking place, which can cause a segmentation
    // fault by dereferencing a dangling pointer.
    // If the `num_threads` crate is incapable of determining the number of running threads, then
    // we conservatively return `None` to avoid a soundness bug.
    if !cfg!(unsound_local_offset) && num_threads::is_single_threaded() != Some(true) {
        return None;
    }

    // Safety: We have just confirmed that the process is single-threaded or the user has explicitly
    // opted out of soundness.
    let tm = unsafe { timestamp_to_tm(datetime.unix_timestamp()) }?;
    tm_to_offset(tm)
}
<|endoftext|>


<commit_before>extern crate josephpd3;

use josephpd3::*;

#[test]
fn lonely_hearts_club() {
    let v = vec!['1', '1', '2', 'a', 'b', 'b', 'b', 'c', 'c'];
    assert_eq!(get_lonely_items(&v), vec!['2', 'a']);
}<commit_msg>Add test for vector with no lonely elements<commit_after>extern crate josephpd3;

use josephpd3::*;

#[test]
fn lonely_hearts_club() {
    let v = vec!['1', '1', '2', 'a', 'b', 'b', 'b', 'c', 'c'];
    assert_eq!(get_lonely_items(&v), vec!['2', 'a']);
}

#[test]
fn for_never_alone() {
    let v = vec!['1', '1', '2', '2', 'a', 'a', 'b', 'b', 'b', 'c', 'c'];
    assert_eq!(get_lonely_items(&v), vec![]);
}<|endoftext|>


<commit_before>//! Manages the sliding windows.  Each time a tick is pushed in, the max/min price of each
//! of the monitored time perdiods is updated.  When ticks become older than the period of
//! the greatest window-newest tick, they are dropped off the structure.
//!
//! NOTE: This algorithm could be more efficiently impelented by a joint linked-list/binary-heap.
//! Traversing a Vector is currently used instead due to the complexity of implementing the
//! alternative.  However, if the latency and inefficniecy caused ever becomes a problem,
//! there are options available.

use std::collections::{HashMap, VecDeque};
use test;

use algobot_util::tick::Tick;

/// Contains the computed information about the ticks in each time monitored time frame.
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Window {
    pub min_price_t: Tick,
    pub max_price_t: Tick,
    pub oldest_t:    Tick
}

impl Window {
    pub fn new_empty() -> Window {
        Window {
            min_price_t: Tick::null(),
            max_price_t: Tick::null(),
            oldest_t:    Tick::null()
        }
    }
}

pub struct WindowManager {
    pub data: VecDeque<Tick>,
    pub windows: HashMap<i64, Window>,
    pub max_period: i64
}

/// Determines whether a tick is out of range of a window or not
fn out_of_range(period: i64, old_tick: Tick, newest_tick: Tick) -> bool {
    newest_tick.timestamp - old_tick.timestamp >= period
}

/// Checks if a tick is a max/min tick of a Window
fn is_max_min(t: Tick, window: Window) -> bool {
    if window.min_price_t.timestamp == 0 {
        // doesn't matter since we already have a recalc queued
        return false
    }
    t == window.min_price_t || t == window.max_price_t
}

impl WindowManager {
    pub fn new() -> WindowManager {
        WindowManager {
            data: VecDeque::new(),
            windows: HashMap::new(),
            max_period: 0
        }
    }

    pub fn add_period(&mut self, period: i64) {
        self.windows.insert(period, Window::new_empty());
        // update max period
        if period > self.max_period {
            self.max_period = period;
        }
    }

    pub fn remove_period(&mut self, period: i64) {
        let mut o = 0;
        for (k, _) in self.windows.iter() {
            if *k == period {
                o = *k;
                break;
            }
        }
        if o != 0 {
            self.windows.remove(&o);
            // assume that max_period is already set since we're removing an element
            if self.max_period == period {
                // calculate new max period
                let mut max = 0;
                for (k, _) in self.windows.iter() {
                    if *k > max{
                        max = *k;
                    }
                }
                self.max_period = max;
            }
        }
    }

    // Processes a new tick and returns a Vec containing periods whose mins/maxes have changed.
    pub fn push(&mut self, t: Tick) -> HashMap<i64, Window> {
        assert!(self.windows.len() > 0);
        if self.data.len() > 0 {
            assert!(t.timestamp > self.data.back().unwrap().timestamp);
        }
        // data is pushed on the back and popped off the front
        self.data.push_back(t);

        let mut ret = HashMap::new();
        for (period, window) in self.trim_overflow(t) {
            ret.insert(period, window);
        }

        ret
    }

    /// Removes ticks from the front of the queue until all timestamps are within
    /// the maximum period of the WindowManager.  For each tick that is removed,
    /// check to see if it was the maximum/minimum of any windows.  If it was,
    /// update that window.
    fn trim_overflow(&mut self, newest_tick: Tick) -> Vec<(i64, Window)> {
        let mut altered_periods = Vec::new();

        // iterate over all windows and check if the tick is out of their range
        //
        // NOTE: Currently windows are iterated over in an arbitrary order making it impossible to
        // do optimizations like use the max of smaller windows to avoid having to iterate over
        // those parts of larger windows.
        for (&period, &window) in self.windows.iter() {
            let mut window = window;
            let mut altered = false;
            // if it's the first tick
            if window.oldest_t.timestamp == 0 {
                window.oldest_t = newest_tick;
                window.min_price_t = newest_tick;
                window.max_price_t = newest_tick;
                altered = true;
            } else {
                // if our current oldest tick is no longer in range of the window
                if out_of_range(period, window.oldest_t, newest_tick) {
                    // have to re-scan the entire array starting at the oldest
                    let mut i = 0;

                    // loop through ticks until we find one that's inside the window
                    while let Some(newer_tick) = self.data.get(i as usize) {
                        i += 1;
                        if !out_of_range(period, *newer_tick, newest_tick) {
                            // found the first in-range tick
                            window.oldest_t = *newer_tick;
                            if is_max_min(*newer_tick, window) {
                                window.min_price_t.timestamp = 0;
                                window.max_price_t.timestamp = 0;
                            }
                            break;
                        }

                        // If the tick that went out of range max/min needs updating
                        if is_max_min(*newer_tick, window) {
                            window.min_price_t.timestamp = 0;
                            window.max_price_t.timestamp = 0;
                        }
                    }
                    // at this point the oldest tick for the window is either 0 or accurate
                    // (not accounting for the possibility of the newest tick)
                }
            }

            if window.min_price_t.timestamp == 0 || window.max_price_t.timestamp == 0 {
                let extremes = self.get_extreme_ticks(period);
                window.min_price_t = extremes.0;
                window.max_price_t = extremes.1;
                altered = true;
            } else {
                // check if newest_tick is a new max/min tick for the period
                if newest_tick.mid() < window.min_price_t.mid() {
                    window.min_price_t = newest_tick;
                    altered = true;
                }
                if newest_tick.mid() > window.max_price_t.mid() {
                    window.max_price_t = newest_tick;
                    altered = true;
                }
            }

            if altered {
                // Add the mutated window to the alteration list
                altered_periods.push((period, window));
            }
        }

        // make the alterations queued in the loop
        for &(period, window) in altered_periods.iter() {
            self.windows.insert(period, window);
        }

        // drop ticks that are outside of the range of the largest window
        while out_of_range(self.max_period, *self.data.front().unwrap(), newest_tick) {
            self.data.pop_front();
        }

        altered_periods
    }

    /// Gets the max and min tick in a window by iterating over every tick it contains.
    ///
    /// Returns (min_tick, max_tick)
    fn get_extreme_ticks(&self, period: i64) -> (Tick, Tick) {
        let newest_timestamp = self.data.back().unwrap().timestamp;
        let mut max_tick = Tick::null();
        let mut min_tick = Tick::null();
        let mut max_price = 0f64;
        let mut min_price = 0f64;

        // loop over all ticks, newest to oldest
        for t in self.data.iter().rev() {
            if newest_timestamp - t.timestamp >= period {
                break;
            }

            let price = t.mid();
            if price > max_price {
                max_tick = *t;
                max_price = price;
            }

            if price < min_price || min_price == 0f64 {
                min_tick = *t;
                min_price = price;
            }
        }

        (min_tick, max_tick)
    }
}

#[test]
fn period_addition_removal() {
    let mut wm = WindowManager::new();
    wm.add_period(2);
    wm.add_period(4);
    assert_eq!(wm.windows.len(), 2);
    wm.remove_period(2);
    wm.remove_period(4);
}

#[test]
fn min_max_accuracy() {
    let mut wm = WindowManager::new();
    wm.add_period(2);
    wm.add_period(4);

    let t1 = Tick{timestamp: 1i64, bid: 0.5f64, ask: 0.5f64};
    let wl = wm.push(t1);
    assert_eq!(*wl.get(&2).unwrap(), Window{min_price_t: t1, max_price_t: t1, oldest_t: t1});
    assert_eq!(*wl.get(&4).unwrap(), Window{min_price_t: t1, max_price_t: t1, oldest_t: t1});

    let t2 = Tick{timestamp: 2i64, bid: 2f64, ask: 2f64};
    let w2 = wm.push(t2);
    assert_eq!(*w2.get(&2).unwrap(), Window{min_price_t: t1, max_price_t: t2, oldest_t: t1});
    assert_eq!(*w2.get(&4).unwrap(), Window{min_price_t: t1, max_price_t: t2, oldest_t: t1});

    let t3 = Tick{timestamp: 3i64, bid: 4f64, ask: 4f64};
    let w3 = wm.push(t3);
    assert_eq!(*w3.get(&2).unwrap(), Window{min_price_t: t2, max_price_t: t3, oldest_t: t2});
    assert_eq!(*w3.get(&4).unwrap(), Window{min_price_t: t1, max_price_t: t3, oldest_t: t1});

    let t4 = Tick{timestamp: 6i64, bid: 5f64, ask: 5f64};
    let w4 = wm.push(t4);
    assert_eq!(*w4.get(&2).unwrap(), Window{min_price_t: t4, max_price_t: t4, oldest_t: t4});
    assert_eq!(*w4.get(&4).unwrap(), Window{min_price_t: t3, max_price_t: t4, oldest_t: t3});
}

#[bench]
fn small_wm_processing_speed(b: &mut test::Bencher) {
    let mut wm = WindowManager::new();
    wm.add_period(500);
    // preload with 500,000 ticks
    for i in 0..500 {
        wm.push(Tick{timestamp: i as i64, bid: 1f64, ask: 1f64});
    }
    let mut i = 500;
    b.iter(|| {
        i = i + 1;
        wm.push(Tick{timestamp: i as i64, bid: 1f64, ask: 1f64});
    });
}

// Commented because it takes a while to run
// #[bench]
// fn large_wm_processing_speed(b: &mut test::Bencher) {
//     let mut wm = WindowManager::new();
//     wm.add_period(5000000);
//     // preload with 500,000 ticks
//     for i in 0..5000000 {
//         wm.push(Tick{timestamp: i as i64, bid: 1f64, ask: 1f64});
//     }
//     let mut i = 5000000;
//     b.iter(|| {
//         i = i + 1;
//         wm.push(Tick{timestamp: i as i64, bid: 1f64, ask: 1f64});
//     });
// }
<commit_msg>More intelligently traverse arrays  - Keep a reference to the location of the previous oldest element that can be used as the starting point when looking for the new oldest element  - Values that are equal to the previous max/min are now counted as the new max/min which makes traversal less expensive<commit_after>//! Manages the sliding windows.  Each time a tick is pushed in, the max/min price of each
//! of the monitored time perdiods is updated.  When ticks become older than the period of
//! the greatest window-newest tick, they are dropped off the structure.
//!
//! NOTE: This algorithm could be more efficiently impelented by a joint linked-list/binary-heap.
//! Traversing a Vector is currently used instead due to the complexity of implementing the
//! alternative.  However, if the latency and inefficniecy caused ever becomes a problem,
//! there are options available.

use std::collections::{HashMap, VecDeque};
use test;

use algobot_util::tick::Tick;

/// Contains the computed information about the ticks in each time monitored time frame.
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Window {
    pub min_price_t: Tick,
    pub max_price_t: Tick,
    pub oldest_t:    Tick,
    // index is front to back; .front()'s index is 0
    pub oldest_index:usize
}

impl Window {
    pub fn new_empty() -> Window {
        Window {
            min_price_t: Tick::null(),
            max_price_t: Tick::null(),
            oldest_t:    Tick::null(),
            oldest_index: 0
        }
    }
}

pub struct WindowManager {
    pub data: VecDeque<Tick>,
    pub windows: HashMap<i64, Window>,
    pub max_period: i64
}

/// Determines whether a tick is out of range of a window or not
fn out_of_range(period: i64, old_tick: Tick, newest_tick: Tick) -> bool {
    newest_tick.timestamp - old_tick.timestamp >= period
}

/// Checks if a tick is a max/min tick of a Window
fn is_max_min(t: Tick, window: Window) -> bool {
    if window.min_price_t.timestamp == 0 {
        // doesn't matter since we already have a recalc queued
        return false
    }
    t == window.min_price_t || t == window.max_price_t
}

impl WindowManager {
    pub fn new() -> WindowManager {
        WindowManager {
            data: VecDeque::new(),
            windows: HashMap::new(),
            max_period: 0
        }
    }

    pub fn add_period(&mut self, period: i64) {
        self.windows.insert(period, Window::new_empty());
        // update max period
        if period > self.max_period {
            self.max_period = period;
        }
    }

    pub fn remove_period(&mut self, period: i64) {
        let mut o = 0;
        for (k, _) in self.windows.iter() {
            if *k == period {
                o = *k;
                break;
            }
        }
        if o != 0 {
            self.windows.remove(&o);
            // assume that max_period is already set since we're removing an element
            if self.max_period == period {
                // calculate new max period
                let mut max = 0;
                for (k, _) in self.windows.iter() {
                    if *k > max{
                        max = *k;
                    }
                }
                self.max_period = max;
            }
        }
    }

    // Processes a new tick and returns a Vec containing periods whose mins/maxes have changed.
    pub fn push(&mut self, t: Tick) -> HashMap<i64, Window> {
        assert!(self.windows.len() > 0);
        if self.data.len() > 0 {
            assert!(t.timestamp > self.data.back().unwrap().timestamp);
        }
        // data is pushed on the back and popped off the front
        self.data.push_back(t);

        self.trim_overflow(t)
    }

    /// Removes ticks from the front of the queue until all timestamps are within
    /// the maximum period of the WindowManager.  For each tick that is removed,
    /// check to see if it was the maximum/minimum of any windows.  If it was,
    /// update that window.
    fn trim_overflow(&mut self, newest_tick: Tick) -> HashMap<i64, Window> {
        let mut altered_periods = HashMap::new();

        // iterate over all windows and check if the tick is out of their range
        //
        // NOTE: Currently windows are iterated over in an arbitrary order making it impossible to
        // do optimizations like use the max of smaller windows to avoid having to iterate over
        // those parts of larger windows.
        for (&period, &window) in self.windows.iter() {
            let mut window = window;
            let mut altered = false;
            // if it's the first tick
            if window.oldest_t.timestamp == 0 {
                window.oldest_t = newest_tick;
                window.min_price_t = newest_tick;
                window.max_price_t = newest_tick;
                altered = true;
            } else {
                // if our current oldest tick is no longer in range of the window
                if out_of_range(period, window.oldest_t, newest_tick) {
                    // have to re-scan the entire array starting at the oldest
                    let mut i = window.oldest_index + 1;

                    // loop through ticks until we find one that's inside the window
                    while let Some(newer_tick) = self.data.get(i as usize) {
                        // If the tick that went out of range max/min needs updating
                        if is_max_min(*newer_tick, window) {
                            window.min_price_t.timestamp = 0;
                            window.max_price_t.timestamp = 0;
                        }

                        if !out_of_range(period, *newer_tick, newest_tick) {
                            // found the first in-range tick
                            window.oldest_t = *newer_tick;
                            window.oldest_index = i;
                            break;
                        }

                        i += 1;
                    }
                    // at this point the oldest tick for the window is either 0 or accurate
                    // (not accounting for the possibility of the newest tick)
                }
            }

            if window.min_price_t.timestamp == 0 || window.max_price_t.timestamp == 0 {
                let extremes = self.get_extreme_ticks(period);
                window.min_price_t = extremes.0;
                window.max_price_t = extremes.1;
                altered = true;
            } else {
                // check if newest_tick is a new max/min tick for the period
                // maxs/mins are updated if they simply match the previous max/min
                if newest_tick.mid() <= window.min_price_t.mid() {
                    window.min_price_t = newest_tick;
                    altered = true;
                }
                if newest_tick.mid() >= window.max_price_t.mid() {
                    window.max_price_t = newest_tick;
                    altered = true;
                }
            }

            if altered {
                // Add the mutated window to the alteration list
                altered_periods.insert(period, window);
            }
        }

        // drop ticks that are outside of the range of the largest window
        let mut popped = 0;
        while out_of_range(self.max_period, *self.data.front().unwrap(), newest_tick) {
            self.data.pop_front();
            popped += 1;
        }

        if popped > 0 {
            let mut alter_q = HashMap::new();
            for (period, window) in altered_periods.iter() {
                let mut window = window.clone();
                window.oldest_index = window.oldest_index - popped;
                alter_q.insert(*period, window);
            }

            for (period, window) in alter_q.iter() {
                altered_periods.insert(*period, *window);
            }
        }

        for (period, window) in altered_periods.iter() {
            self.windows.insert(*period, *window);
        }

        altered_periods
    }

    /// Gets the max and min tick in a window by iterating over every tick it contains.
    ///
    /// Returns (min_tick, max_tick)
    fn get_extreme_ticks(&self, period: i64) -> (Tick, Tick) {
        let newest_timestamp = self.data.back().unwrap().timestamp;
        let mut max_tick = Tick::null();
        let mut min_tick = Tick::null();
        let mut max_price = 0f64;
        let mut min_price = 0f64;

        // loop over all ticks, newest to oldest
        for t in self.data.iter().rev() {
            if newest_timestamp - t.timestamp >= period {
                break;
            }

            let price = t.mid();
            if price > max_price {
                max_tick = *t;
                max_price = price;
            }

            if price < min_price || min_price == 0f64 {
                min_tick = *t;
                min_price = price;
            }
        }

        (min_tick, max_tick)
    }
}

#[test]
fn period_addition_removal() {
    let mut wm = WindowManager::new();
    wm.add_period(2);
    wm.add_period(4);
    assert_eq!(wm.windows.len(), 2);
    wm.remove_period(2);
    wm.remove_period(4);
}

#[test]
fn min_max_accuracy() {
    let mut wm = WindowManager::new();
    wm.add_period(2);
    wm.add_period(4);

    let t0 = Tick{timestamp: 1i64, bid: 0.5f64, ask: 0.5f64};
    let wl = wm.push(t0);
    assert_eq!(*wl.get(&2).unwrap(), Window{min_price_t: t0, max_price_t: t0, oldest_t: t0, oldest_index: 0});
    assert_eq!(*wl.get(&4).unwrap(), Window{min_price_t: t0, max_price_t: t0, oldest_t: t0, oldest_index: 0});

    let t1 = Tick{timestamp: 2i64, bid: 2f64, ask: 2f64};
    let w2 = wm.push(t1);
    assert_eq!(*w2.get(&2).unwrap(), Window{min_price_t: t0, max_price_t: t1, oldest_t: t0, oldest_index: 0});
    assert_eq!(*w2.get(&4).unwrap(), Window{min_price_t: t0, max_price_t: t1, oldest_t: t0, oldest_index: 0});

    let t2 = Tick{timestamp: 3i64, bid: 4f64, ask: 4f64};
    let w3 = wm.push(t2);
    assert_eq!(*w3.get(&2).unwrap(), Window{min_price_t: t1, max_price_t: t2, oldest_t: t1, oldest_index: 1});
    assert_eq!(*w3.get(&4).unwrap(), Window{min_price_t: t0, max_price_t: t2, oldest_t: t0, oldest_index: 0});

    let t3 = Tick{timestamp: 6i64, bid: 5f64, ask: 5f64};
    let w4 = wm.push(t3);
    assert_eq!(*w4.get(&2).unwrap(), Window{min_price_t: t3, max_price_t: t3, oldest_t: t3, oldest_index: 1});
    assert_eq!(*w4.get(&4).unwrap(), Window{min_price_t: t2, max_price_t: t3, oldest_t: t2, oldest_index: 0});
}

#[test]
fn out_of_range_dropping() {
    let mut wm = WindowManager::new();
    wm.add_period(2);
    wm.push(Tick{timestamp: 1, bid: 1f64, ask: 1f64});
    wm.push(Tick{timestamp: 3, bid: 1f64, ask: 1f64});
    assert_eq!(wm.data.len(), 1);
}

#[bench]
fn small_wm_processing_speed(b: &mut test::Bencher) {
    let mut wm = WindowManager::new();
    wm.add_period(500);
    // preload with 500,000 ticks
    for i in 0..500 {
        wm.push(Tick{timestamp: i as i64, bid: 1f64, ask: 1f64});
    }
    let mut i = 500;
    b.iter(|| {
        i = i + 1;
        wm.push(Tick{timestamp: i as i64, bid: 1f64, ask: 1f64});
    });
}

// Tests best-case scenario where array traversal is basically nonexistant.
#[bench]
fn large_wm_processing_speed(b: &mut test::Bencher) {
    let mut wm = WindowManager::new();
    wm.add_period(5000000);
    // preload with 500,000 ticks
    for i in 0..5000000 {
        wm.push(Tick{timestamp: i as i64, bid: 1f64, ask: 1f64});
    }
    let mut i = 5000000;
    b.iter(|| {
        i = i + 1;
        wm.push(Tick{timestamp: i as i64, bid: 1f64, ask: 1f64});
    });
}
<|endoftext|>