<commit_before>// Copyright (C) 2018 O.S. Systems Sofware LTDA
//
// SPDX-License-Identifier: Apache-2.0

use crate::serde_helpers::{de, ser};

use chrono::Duration;
use failure::Fail;
use serde::{Deserialize, Serialize};
use serde_ini;
use slog_scope::{debug, error};
use std::{io, path::PathBuf};

const SYSTEM_SETTINGS_PATH: &str = "/etc/updatehub.conf";

// When running inside a test environment we default to the mock
// server
#[cfg(test)]
use mockito;

#[derive(Debug, Default, PartialEq, Deserialize, Serialize, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct Settings {
    pub(crate) firmware: Firmware,
    pub(crate) network: Network,
    pub(crate) polling: Polling,
    pub(crate) storage: Storage,
    pub(crate) update: Update,
}

impl Settings {
    /// Loads the settings from the filesystem. If
    /// `/etc/updatehub.conf` does not exists, it uses the default
    /// settings.
    pub fn load() -> Result<Self, failure::Error> {
        use std::{fs::File, io::Read, path::Path};

        let path = Path::new(SYSTEM_SETTINGS_PATH);

        if path.exists() {
            debug!(
                "Loading system settings from '{}'...",
                path.to_string_lossy()
            );

            let mut content = String::new();
            File::open(path)?.read_to_string(&mut content)?;

            Ok(Self::parse(&content)?)
        } else {
            debug!(
                "System settings file {} does not exists. Using default system settings...",
                path.to_string_lossy()
            );
            Ok(Self::default())
        }
    }

    // This parses the configuration file, taking into account the
    // needed validations for all fields, and returns either `Self` or
    // `Err`.
    fn parse(content: &str) -> Result<Self, failure::Error> {
        let settings = serde_ini::from_str::<Self>(content)?;

        if settings.polling.interval < Duration::seconds(60) {
            error!(
                "Invalid setting for polling interval. The interval cannot be less than 60 seconds"
            );
            return Err(Error::InvalidInterval.into());
        }

        if !&settings.network.server_address.starts_with("http://")
            && !&settings.network.server_address.starts_with("https://")
        {
            error!("Invalid setting for server address. The server address must use the protocol prefix");
            return Err(Error::InvalidServerAddress.into());
        }

        Ok(settings)
    }
}

#[derive(Debug, Fail)]
pub enum Error {
    #[cause]
    #[fail(display = "IO error")]
    Io(io::Error),
    #[cause]
    #[fail(display = "Invalid INI fail")]
    Ini(serde_ini::de::Error),
    #[fail(display = "Invalid interval")]
    InvalidInterval,
    #[fail(display = "Invalid server address")]
    InvalidServerAddress,
}

#[derive(Debug, Deserialize, PartialEq, Serialize, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct Polling {
    #[serde(
        deserialize_with = "de::duration_from_str",
        serialize_with = "ser::duration_to_int"
    )]
    /// Inverval to automatically poll the server for update. By
    /// default, it uses 1 day of interval.
    pub interval: Duration,
    #[serde(deserialize_with = "de::bool_from_str")]
    /// Defines if automatic polling is enabled or not. By default it
    /// is enabled.
    pub enabled: bool,
}

impl Default for Polling {
    fn default() -> Self {
        Self {
            interval: Duration::days(1),
            enabled: true,
        }
    }
}

#[derive(Debug, Deserialize, PartialEq, Serialize, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct Storage {
    #[serde(deserialize_with = "de::bool_from_str")]
    /// Determine if it should run on read-only mode or not. By
    /// default, read-only mode is disabled.
    pub read_only: bool,
    /// Define where the runtime settings are stored. By default,
    /// those are stored in
    /// `/var/lib/updatehub/runtime_settings.conf`.
    pub runtime_settings: String,
}

impl Default for Storage {
    fn default() -> Self {
        Self {
            read_only: false,
            runtime_settings: "/var/lib/updatehub/runtime_settings.conf".into(),
        }
    }
}

#[derive(Debug, Deserialize, PartialEq, Serialize, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct Update {
    pub download_dir: PathBuf,
    #[serde(rename = "SupportedInstallModes")]
    #[serde(deserialize_with = "de::vec_from_str")]
    pub install_modes: Vec<String>,
}

impl Default for Update {
    fn default() -> Self {
        Self {
            download_dir: "/tmp/updatehub".into(),
            install_modes: [
                "dry-run", "copy", "flash", "imxkobs", "raw", "tarball", "ubifs",
            ]
            .iter()
            .map(|i| i.to_string())
            .collect(),
        }
    }
}

#[derive(Debug, Deserialize, PartialEq, Serialize, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct Network {
    pub server_address: String,
}

impl Default for Network {
    fn default() -> Self {
        #[cfg(test)]
        let server_address = mockito::server_url().to_string();
        #[cfg(not(test))]
        let server_address = "https://api.updatehub.io".to_string();

        Self { server_address }
    }
}

#[derive(Debug, Deserialize, PartialEq, Serialize, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct Firmware {
    pub metadata_path: PathBuf,
}

impl Default for Firmware {
    fn default() -> Self {
        Self {
            metadata_path: "/usr/share/updatehub".into(),
        }
    }
}

#[test]
fn ok() {
    use pretty_assertions::assert_eq;
    let ini = r"
[Polling]
Interval=60s
Enabled=false

[Storage]
ReadOnly=true
RuntimeSettings=/run/updatehub/state

[Update]
DownloadDir=/tmp/download
SupportedInstallModes=mode1,mode2

[Network]
ServerAddress=http://localhost

[Firmware]
MetadataPath=/tmp/metadata
";

    let expected = Settings {
        polling: Polling {
            interval: Duration::seconds(60),
            enabled: false,
        },
        storage: Storage {
            read_only: true,
            runtime_settings: "/run/updatehub/state".into(),
        },
        update: Update {
            download_dir: "/tmp/download".into(),
            install_modes: ["mode1", "mode2"].iter().map(|i| i.to_string()).collect(),
        },
        network: Network {
            server_address: "http://localhost".into(),
        },
        firmware: Firmware {
            metadata_path: "/tmp/metadata".into(),
        },
    };

    assert_eq!(
        serde_ini::from_str::<Settings>(ini)
            .map_err(|e| println!("{}", e))
            .ok(),
        Some(expected)
    );
}

#[test]
fn invalid_polling_interval() {
    let ini = r"
[Polling]
Interval=59s
Enabled=false

[Storage]
ReadOnly=true
RuntimeSettings=/run/updatehub/state

[Update]
DownloadDir=/tmp/download
SupportedInstallModes=mode1,mode2

[Network]
ServerAddress=http://localhost

[Firmware]
MetadataPath=/tmp/metadata
";
    assert!(Settings::parse(ini).is_err());
}

#[test]
fn invalid_network_server_address() {
    let ini = r"
[Polling]
Interval=60s
Enabled=false

[Storage]
ReadOnly=true
RuntimeSettings=/run/updatehub/state

[Update]
DownloadDir=/tmp/download
SupportedInstallModes=mode1,mode2

[Network]
ServerAddress=localhost

[Firmware]
MetadataPath=/tmp/metadata
";

    assert!(Settings::parse(ini).is_err());
}

#[test]
fn default() {
    use pretty_assertions::assert_eq;
    let mut settings = Settings::default();
    settings.network.server_address = "https://api.updatehub.io".to_string();

    let expected = Settings {
        polling: Polling {
            interval: Duration::days(1),
            enabled: true,
        },
        storage: Storage {
            read_only: false,
            runtime_settings: "/var/lib/updatehub/runtime_settings.conf".into(),
        },
        update: Update {
            download_dir: "/tmp/updatehub".into(),
            install_modes: [
                "dry-run", "copy", "flash", "imxkobs", "raw", "tarball", "ubifs",
            ]
            .iter()
            .map(|i| i.to_string())
            .collect(),
        },
        network: Network {
            server_address: "https://api.updatehub.io".to_string(),
        },
        firmware: Firmware {
            metadata_path: "/usr/share/updatehub".into(),
        },
    };

    assert_eq!(Some(settings), Some(expected));
}
<commit_msg>settings: Fix parsing of existing file<commit_after>// Copyright (C) 2018 O.S. Systems Sofware LTDA
//
// SPDX-License-Identifier: Apache-2.0

use crate::serde_helpers::{de, ser};

use chrono::Duration;
use failure::Fail;
use serde::{Deserialize, Serialize};
use serde_ini;
use slog_scope::{debug, error};
use std::{io, path::PathBuf};

const SYSTEM_SETTINGS_PATH: &str = "/etc/updatehub.conf";

// When running inside a test environment we default to the mock
// server
#[cfg(test)]
use mockito;

#[derive(Debug, Default, PartialEq, Deserialize, Serialize, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct Settings {
    #[serde(default)]
    pub(crate) firmware: Firmware,
    pub(crate) network: Network,
    pub(crate) polling: Polling,
    pub(crate) storage: Storage,
    pub(crate) update: Update,
}

impl Settings {
    /// Loads the settings from the filesystem. If
    /// `/etc/updatehub.conf` does not exists, it uses the default
    /// settings.
    pub fn load() -> Result<Self, failure::Error> {
        use std::{fs::File, io::Read, path::Path};

        let path = Path::new(SYSTEM_SETTINGS_PATH);

        if path.exists() {
            debug!(
                "Loading system settings from '{}'...",
                path.to_string_lossy()
            );

            let mut content = String::new();
            File::open(path)?.read_to_string(&mut content)?;

            Ok(Self::parse(&content)?)
        } else {
            debug!(
                "System settings file {} does not exists. Using default system settings...",
                path.to_string_lossy()
            );
            Ok(Self::default())
        }
    }

    // This parses the configuration file, taking into account the
    // needed validations for all fields, and returns either `Self` or
    // `Err`.
    fn parse(content: &str) -> Result<Self, failure::Error> {
        let settings = serde_ini::from_str::<Self>(content)?;

        if settings.polling.interval < Duration::seconds(60) {
            error!(
                "Invalid setting for polling interval. The interval cannot be less than 60 seconds"
            );
            return Err(Error::InvalidInterval.into());
        }

        if !&settings.network.server_address.starts_with("http://")
            && !&settings.network.server_address.starts_with("https://")
        {
            error!("Invalid setting for server address. The server address must use the protocol prefix");
            return Err(Error::InvalidServerAddress.into());
        }

        Ok(settings)
    }
}

#[derive(Debug, Fail)]
pub enum Error {
    #[cause]
    #[fail(display = "IO error")]
    Io(io::Error),
    #[cause]
    #[fail(display = "Invalid INI fail")]
    Ini(serde_ini::de::Error),
    #[fail(display = "Invalid interval")]
    InvalidInterval,
    #[fail(display = "Invalid server address")]
    InvalidServerAddress,
}

#[derive(Debug, Deserialize, PartialEq, Serialize, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct Polling {
    #[serde(
        deserialize_with = "de::duration_from_str",
        serialize_with = "ser::duration_to_int"
    )]
    /// Inverval to automatically poll the server for update. By
    /// default, it uses 1 day of interval.
    pub interval: Duration,
    #[serde(deserialize_with = "de::bool_from_str")]
    /// Defines if automatic polling is enabled or not. By default it
    /// is enabled.
    pub enabled: bool,
}

impl Default for Polling {
    fn default() -> Self {
        Self {
            interval: Duration::days(1),
            enabled: true,
        }
    }
}

#[derive(Debug, Deserialize, PartialEq, Serialize, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct Storage {
    /// Determine if it should run on read-only mode or not. By
    /// default, read-only mode is disabled.
    #[serde(deserialize_with = "de::bool_from_str")]
    #[serde(default)]
    pub read_only: bool,
    /// Define where the runtime settings are stored. By default,
    /// those are stored in
    /// `/var/lib/updatehub/runtime_settings.conf`.
    #[serde(rename = "RuntimeSettingsPath")]
    pub runtime_settings: String,
}

impl Default for Storage {
    fn default() -> Self {
        Self {
            read_only: false,
            runtime_settings: "/var/lib/updatehub/runtime_settings.conf".into(),
        }
    }
}

#[derive(Debug, Deserialize, PartialEq, Serialize, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct Update {
    pub download_dir: PathBuf,
    #[serde(rename = "SupportedInstallModes")]
    #[serde(deserialize_with = "de::vec_from_str")]
    pub install_modes: Vec<String>,
}

impl Default for Update {
    fn default() -> Self {
        Self {
            download_dir: "/tmp/updatehub".into(),
            install_modes: [
                "dry-run", "copy", "flash", "imxkobs", "raw", "tarball", "ubifs",
            ]
            .iter()
            .map(|i| i.to_string())
            .collect(),
        }
    }
}

#[derive(Debug, Deserialize, PartialEq, Serialize, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct Network {
    pub server_address: String,
}

impl Default for Network {
    fn default() -> Self {
        #[cfg(test)]
        let server_address = mockito::server_url().to_string();
        #[cfg(not(test))]
        let server_address = "https://api.updatehub.io".to_string();

        Self { server_address }
    }
}

#[derive(Debug, Deserialize, PartialEq, Serialize, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct Firmware {
    pub metadata_path: PathBuf,
}

impl Default for Firmware {
    fn default() -> Self {
        Self {
            metadata_path: "/usr/share/updatehub".into(),
        }
    }
}

#[test]
fn ok() {
    use pretty_assertions::assert_eq;
    let ini = r"
[Polling]
Interval=60s
Enabled=false

[Storage]
RuntimeSettingsPath=/run/updatehub/state

[Update]
DownloadDir=/tmp/download
SupportedInstallModes=mode1,mode2

[Network]
ServerAddress=http://localhost
";

    let expected = Settings {
        polling: Polling {
            interval: Duration::seconds(60),
            enabled: false,
        },
        storage: Storage {
            read_only: false,
            runtime_settings: "/run/updatehub/state".into(),
        },
        update: Update {
            download_dir: "/tmp/download".into(),
            install_modes: ["mode1", "mode2"].iter().map(|i| i.to_string()).collect(),
        },
        network: Network {
            server_address: "http://localhost".into(),
        },
        firmware: Firmware {
            metadata_path: "/usr/share/updatehub".into(),
        },
    };

    assert_eq!(
        serde_ini::from_str::<Settings>(ini)
            .map_err(|e| println!("{}", e))
            .ok(),
        Some(expected)
    );
}

#[test]
fn invalid_polling_interval() {
    let ini = r"
[Polling]
Interval=59s
Enabled=false

[Storage]
ReadOnly=true
RuntimeSettings=/run/updatehub/state

[Update]
DownloadDir=/tmp/download
SupportedInstallModes=mode1,mode2

[Network]
ServerAddress=http://localhost

[Firmware]
MetadataPath=/tmp/metadata
";
    assert!(Settings::parse(ini).is_err());
}

#[test]
fn invalid_network_server_address() {
    let ini = r"
[Polling]
Interval=60s
Enabled=false

[Storage]
ReadOnly=true
RuntimeSettings=/run/updatehub/state

[Update]
DownloadDir=/tmp/download
SupportedInstallModes=mode1,mode2

[Network]
ServerAddress=localhost

[Firmware]
MetadataPath=/tmp/metadata
";

    assert!(Settings::parse(ini).is_err());
}

#[test]
fn default() {
    use pretty_assertions::assert_eq;
    let mut settings = Settings::default();
    settings.network.server_address = "https://api.updatehub.io".to_string();

    let expected = Settings {
        polling: Polling {
            interval: Duration::days(1),
            enabled: true,
        },
        storage: Storage {
            read_only: false,
            runtime_settings: "/var/lib/updatehub/runtime_settings.conf".into(),
        },
        update: Update {
            download_dir: "/tmp/updatehub".into(),
            install_modes: [
                "dry-run", "copy", "flash", "imxkobs", "raw", "tarball", "ubifs",
            ]
            .iter()
            .map(|i| i.to_string())
            .collect(),
        },
        network: Network {
            server_address: "https://api.updatehub.io".to_string(),
        },
        firmware: Firmware {
            metadata_path: "/usr/share/updatehub".into(),
        },
    };

    assert_eq!(Some(settings), Some(expected));
}
<|endoftext|>


<commit_before>use serde::ser::{Serialize, Serializer, SerializeStruct};
use serde::de::{Deserialize, Deserializer, Visitor, Error as DeError, MapAccess};
use std::iter::Iterator;
use std::fmt;
use std::ops::Add;
use std::convert::From;
use std::str::FromStr;

const AMI_LAUNCH_INDEX: &'static str = "ami-launch-index";
const LOCAL_HOSTNAME: &'static str = "local-hostname";
const AVAILABILITY_ZONE: &'static str = "availability-zone";
const INSTANCE_ID: &'static str = "instance-id";
const PUBLIC_IPV4: &'static str = "public-ipv4";
const PUBLIC_HOSTNAME: &'static str = "public-hostname";
const AMI_MANIFEST_PATH: &'static str = "ami-manifest-path";
const LOCAL_IPV4: &'static str = "local-ipv4";
const HOSTNAME: &'static str = "hostname";
const AMI_ID: &'static str = "ami-id";
const INSTANCE_TYPE: &'static str = "instance-type";
const JSON_FIELDS: &'static [&'static str] = &[AMI_LAUNCH_INDEX, LOCAL_HOSTNAME, AVAILABILITY_ZONE, INSTANCE_ID,
    PUBLIC_IPV4, PUBLIC_HOSTNAME, AMI_MANIFEST_PATH, LOCAL_IPV4,
    HOSTNAME, AMI_ID, INSTANCE_TYPE];
const RUST_FIELDS: &'static [&'static str] = &["ami_launch_index", "local_hostname", "availability_zone", "instance_id",
    "public_ip4", "public_hostname", "ami_manifest_path", "local_ip4", "hostname", "ami_id", "instance_type"];
const AMAZON_META_DATA: &'static str = "AmazonMetaData";

#[derive(Debug, PartialEq)]
pub struct AmazonMetaData {
    pub ami_launch_index: String,
    pub local_hostname: String,
    pub availability_zone: String,
    pub instance_id: String,
    pub public_ip4: String,
    pub public_hostname: String,
    pub ami_manifest_path: String,
    pub local_ip4: String,
    pub hostname: String,
    pub ami_id: String,
    pub instance_type: String
}

impl Serialize for AmazonMetaData {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where
        S: Serializer {
        let mut s = serializer.serialize_struct(AMAZON_META_DATA, 11)?;
        s.serialize_field(AMI_LAUNCH_INDEX, &self.ami_launch_index)?;
        s.serialize_field(LOCAL_HOSTNAME, &self.local_hostname)?;
        s.serialize_field(AVAILABILITY_ZONE, &self.availability_zone)?;
        s.serialize_field(INSTANCE_ID, &self.instance_id)?;
        s.serialize_field(PUBLIC_IPV4, &self.public_ip4)?;
        s.serialize_field(PUBLIC_HOSTNAME, &self.public_hostname)?;
        s.serialize_field(AMI_MANIFEST_PATH, &self.ami_manifest_path)?;
        s.serialize_field(LOCAL_IPV4, &self.local_ip4)?;
        s.serialize_field(HOSTNAME, &self.hostname)?;
        s.serialize_field(AMI_ID, &self.ami_id)?;
        s.serialize_field(INSTANCE_TYPE, &self.instance_type)?;
        s.end()
    }
}

impl<'de> Deserialize<'de> for AmazonMetaData {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where
        D: Deserializer<'de> {
        enum Field { AmiLaunchIndex, LocalHostname, AvailabilityZone, InstanceId, PublicIp4, PublicHostname, AmiManifestPath, LocalIp4, Hostname, AmiId, InstanceType };

        impl<'de> Deserialize<'de> for Field {
            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where
                D: Deserializer<'de> {
                struct FieldVisitor;

                impl<'de> Visitor<'de> for FieldVisitor {
                    type Value = Field;

                    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                        formatter.write_str("A AmazonMetaData field (see schema)")
                    }

                    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> where
                        E: DeError {
                        match v {
                            AMI_LAUNCH_INDEX => Ok(Field::AmiLaunchIndex),
                            LOCAL_HOSTNAME => Ok(Field::LocalHostname),
                            AVAILABILITY_ZONE => Ok(Field::AvailabilityZone),
                            INSTANCE_ID => Ok(Field::InstanceId),
                            PUBLIC_IPV4 => Ok(Field::PublicIp4),
                            PUBLIC_HOSTNAME => Ok(Field::PublicHostname),
                            AMI_MANIFEST_PATH => Ok(Field::AmiManifestPath),
                            LOCAL_IPV4 => Ok(Field::LocalIp4),
                            HOSTNAME => Ok(Field::Hostname),
                            AMI_ID => Ok(Field::AmiId),
                            INSTANCE_TYPE => Ok(Field::InstanceType),
                            _ => Err(DeError::unknown_field(v, JSON_FIELDS))
                        }
                    }
                }

                deserializer.deserialize_identifier(FieldVisitor)
            }
        }

        struct AmazonMetaDataVisitor;

        impl<'de> Visitor<'de> for AmazonMetaDataVisitor {
            type Value = AmazonMetaData;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("struct AmazonMetaDataVisitor")
            }

            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> where
                A: MapAccess<'de> {
                let mut maybe_ami_launch_index = None;
                let mut maybe_local_hostname = None;
                let mut maybe_availability_zone = None;
                let mut maybe_instance_id = None;
                let mut maybe_public_ip4 = None;
                let mut maybe_public_hostname = None;
                let mut maybe_ami_manifest_path = None;
                let mut maybe_local_ip4 = None;
                let mut maybe_hostname = None;
                let mut maybe_ami_id = None;
                let mut maybe_instance_type = None;

                while let Some(key) = map.next_key()? {
                    match key {
                        Field::AmiLaunchIndex => {
                            if maybe_ami_launch_index.is_some() {
                                return Err(DeError::duplicate_field(AMI_LAUNCH_INDEX));
                            }
                            maybe_ami_launch_index = Some(map.next_value()?)
                        },
                        Field::LocalHostname => {
                            if maybe_local_hostname.is_some() {
                                return Err(DeError::duplicate_field(LOCAL_HOSTNAME));
                            }
                            maybe_local_hostname = Some(map.next_value()?)
                        },
                        Field::AvailabilityZone => {
                            if maybe_availability_zone.is_some() {
                                return Err(DeError::duplicate_field(AVAILABILITY_ZONE));
                            }
                            maybe_availability_zone= Some(map.next_value()?)
                        },
                        Field::InstanceId => {
                            if maybe_instance_id.is_some() {
                                return Err(DeError::duplicate_field(INSTANCE_ID));
                            }
                            maybe_instance_id= Some(map.next_value()?)
                        },
                        Field::PublicIp4 => {
                            if maybe_public_ip4.is_some() {
                                return Err(DeError::duplicate_field(PUBLIC_IPV4));
                            }
                            maybe_public_ip4= Some(map.next_value()?)
                        },
                        Field::PublicHostname => {
                            if maybe_public_hostname.is_some() {
                                return Err(DeError::duplicate_field(PUBLIC_HOSTNAME));
                            }
                            maybe_public_hostname= Some(map.next_value()?)
                        },
                        Field::AmiManifestPath => {
                            if maybe_ami_manifest_path.is_some() {
                                return Err(DeError::duplicate_field(AMI_MANIFEST_PATH));
                            }
                            maybe_ami_manifest_path= Some(map.next_value()?)
                        },
                        Field::LocalIp4 => {
                            if maybe_local_ip4.is_some() {
                                return Err(DeError::duplicate_field(LOCAL_IPV4));
                            }
                            maybe_local_ip4= Some(map.next_value()?)
                        },
                        Field::Hostname => {
                            if maybe_hostname.is_some() {
                                return Err(DeError::duplicate_field(HOSTNAME));
                            }
                            maybe_hostname= Some(map.next_value()?)
                        },
                        Field::AmiId => {
                            if maybe_ami_id.is_some() {
                                return Err(DeError::duplicate_field(AMI_ID));
                            }
                            maybe_ami_id= Some(map.next_value()?)
                        },
                        Field::InstanceType => {
                            if maybe_instance_type.is_some() {
                                return Err(DeError::duplicate_field(INSTANCE_TYPE));
                            }
                            maybe_instance_type= Some(map.next_value()?)
                        }
                    }
                }

                let ami_launch_index = maybe_ami_launch_index.ok_or_else(|| DeError::missing_field(AMI_LAUNCH_INDEX));
                let local_hostname = maybe_local_hostname.ok_or_else(|| DeError::missing_field(LOCAL_HOSTNAME));
                let availability_zone = maybe_availability_zone.ok_or_else(|| DeError::missing_field(AVAILABILITY_ZONE));
                let instance_id = maybe_instance_id.ok_or_else(|| DeError::missing_field(INSTANCE_ID));
                let public_ip4 = maybe_public_ip4.ok_or_else(|| DeError::missing_field(PUBLIC_IPV4));
                let public_hostname = maybe_public_hostname.ok_or_else(|| DeError::missing_field(PUBLIC_HOSTNAME));
                let ami_manifest_path = maybe_ami_manifest_path.ok_or_else(|| DeError::missing_field(AMI_MANIFEST_PATH));
                let local_ip4 = maybe_local_ip4.ok_or_else(|| DeError::missing_field(LOCAL_IPV4));
                let hostname = maybe_hostname.ok_or_else(|| DeError::missing_field(HOSTNAME));
                let ami_id = maybe_ami_id.ok_or_else(|| DeError::missing_field(AMI_ID));
                let instance_type = maybe_instance_type.ok_or_else(|| DeError::missing_field(INSTANCE_TYPE));

                Ok(AmazonMetaData {
                    ami_launch_index: ami_launch_index?,
                    local_hostname: local_hostname?,
                    availability_zone: availability_zone?,
                    instance_id: instance_id?,
                    public_ip4: public_ip4?,
                    public_hostname: public_hostname?,
                    ami_manifest_path: ami_manifest_path?,
                    local_ip4: local_ip4?,
                    hostname: hostname?,
                    ami_id: ami_id?,
                    instance_type: instance_type?
                })
            }
        }
        deserializer.deserialize_struct(AMAZON_META_DATA, RUST_FIELDS, AmazonMetaDataVisitor)
    }
}

mod test {
    use super::*;
    use serde_json;

    #[test]
    fn test_serialize_amazon_meta_data() {
        let md = AmazonMetaData {
            ami_launch_index: "001a".to_string(),
            local_hostname: "localhost0".to_string(),
            availability_zone: "US_East1a".to_string(),
            instance_id: "instance1a".to_string(),
            public_ip4: "32.23.21.212".to_string(),
            public_hostname: "foo.coma".to_string(),
            ami_manifest_path: "/dev/nulla".to_string(),
            local_ip4: "127.0.0.12".to_string(),
            hostname: "privatefoo.coma".to_string(),
            ami_id: "ami0023".to_string(),
            instance_type: "c4xlarged".to_string()
        };
        let json = sample_meta_data();

        let result = serde_json::to_string(&md).unwrap();
        assert_eq!(json, result);
    }

    #[test]
    fn test_deserialize_amazon_meta_data() {
        let md = AmazonMetaData {
            ami_launch_index: "001a".to_string(),
            local_hostname: "localhost0".to_string(),
            availability_zone: "US_East1a".to_string(),
            instance_id: "instance1a".to_string(),
            public_ip4: "32.23.21.212".to_string(),
            public_hostname: "foo.coma".to_string(),
            ami_manifest_path: "/dev/nulla".to_string(),
            local_ip4: "127.0.0.12".to_string(),
            hostname: "privatefoo.coma".to_string(),
            ami_id: "ami0023".to_string(),
            instance_type: "c4xlarged".to_string()
        };
        let json = sample_meta_data();
        let result = serde_json::from_str(&json).unwrap();
        assert_eq!(md, result);
    }

    fn sample_meta_data() -> String {
        r#"{ "ami-launch-index": "001a",
            "local-hostname": "localhost0",
            "availability-zone": "US_East1a",
            "instance-id": "instance1a",
            "public-ipv4": "32.23.21.212",
            "public-hostname": "foo.coma",
            "ami-manifest-path": "/dev/nulla",
            "local-ipv4": "127.0.0.12",
            "hostname": "privatefoo.coma",
            "ami-id": "ami0023",
            "instance-type": "c4xlarged" }"#
            .to_string()
            .replace(" ", "")
            .replace("\n", "")
    }
}<commit_msg>Remove unused imports<commit_after>use serde::ser::{Serialize, Serializer, SerializeStruct};
use serde::de::{Deserialize, Deserializer, Visitor, Error as DeError, MapAccess};
use std::fmt;

const AMI_LAUNCH_INDEX: &'static str = "ami-launch-index";
const LOCAL_HOSTNAME: &'static str = "local-hostname";
const AVAILABILITY_ZONE: &'static str = "availability-zone";
const INSTANCE_ID: &'static str = "instance-id";
const PUBLIC_IPV4: &'static str = "public-ipv4";
const PUBLIC_HOSTNAME: &'static str = "public-hostname";
const AMI_MANIFEST_PATH: &'static str = "ami-manifest-path";
const LOCAL_IPV4: &'static str = "local-ipv4";
const HOSTNAME: &'static str = "hostname";
const AMI_ID: &'static str = "ami-id";
const INSTANCE_TYPE: &'static str = "instance-type";
const JSON_FIELDS: &'static [&'static str] = &[AMI_LAUNCH_INDEX, LOCAL_HOSTNAME, AVAILABILITY_ZONE, INSTANCE_ID,
    PUBLIC_IPV4, PUBLIC_HOSTNAME, AMI_MANIFEST_PATH, LOCAL_IPV4,
    HOSTNAME, AMI_ID, INSTANCE_TYPE];
const RUST_FIELDS: &'static [&'static str] = &["ami_launch_index", "local_hostname", "availability_zone", "instance_id",
    "public_ip4", "public_hostname", "ami_manifest_path", "local_ip4", "hostname", "ami_id", "instance_type"];
const AMAZON_META_DATA: &'static str = "AmazonMetaData";

#[derive(Debug, PartialEq)]
pub struct AmazonMetaData {
    pub ami_launch_index: String,
    pub local_hostname: String,
    pub availability_zone: String,
    pub instance_id: String,
    pub public_ip4: String,
    pub public_hostname: String,
    pub ami_manifest_path: String,
    pub local_ip4: String,
    pub hostname: String,
    pub ami_id: String,
    pub instance_type: String
}

impl Serialize for AmazonMetaData {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where
        S: Serializer {
        let mut s = serializer.serialize_struct(AMAZON_META_DATA, 11)?;
        s.serialize_field(AMI_LAUNCH_INDEX, &self.ami_launch_index)?;
        s.serialize_field(LOCAL_HOSTNAME, &self.local_hostname)?;
        s.serialize_field(AVAILABILITY_ZONE, &self.availability_zone)?;
        s.serialize_field(INSTANCE_ID, &self.instance_id)?;
        s.serialize_field(PUBLIC_IPV4, &self.public_ip4)?;
        s.serialize_field(PUBLIC_HOSTNAME, &self.public_hostname)?;
        s.serialize_field(AMI_MANIFEST_PATH, &self.ami_manifest_path)?;
        s.serialize_field(LOCAL_IPV4, &self.local_ip4)?;
        s.serialize_field(HOSTNAME, &self.hostname)?;
        s.serialize_field(AMI_ID, &self.ami_id)?;
        s.serialize_field(INSTANCE_TYPE, &self.instance_type)?;
        s.end()
    }
}

impl<'de> Deserialize<'de> for AmazonMetaData {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where
        D: Deserializer<'de> {
        enum Field { AmiLaunchIndex, LocalHostname, AvailabilityZone, InstanceId, PublicIp4, PublicHostname, AmiManifestPath, LocalIp4, Hostname, AmiId, InstanceType };

        impl<'de> Deserialize<'de> for Field {
            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where
                D: Deserializer<'de> {
                struct FieldVisitor;

                impl<'de> Visitor<'de> for FieldVisitor {
                    type Value = Field;

                    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                        formatter.write_str("A AmazonMetaData field (see schema)")
                    }

                    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> where
                        E: DeError {
                        match v {
                            AMI_LAUNCH_INDEX => Ok(Field::AmiLaunchIndex),
                            LOCAL_HOSTNAME => Ok(Field::LocalHostname),
                            AVAILABILITY_ZONE => Ok(Field::AvailabilityZone),
                            INSTANCE_ID => Ok(Field::InstanceId),
                            PUBLIC_IPV4 => Ok(Field::PublicIp4),
                            PUBLIC_HOSTNAME => Ok(Field::PublicHostname),
                            AMI_MANIFEST_PATH => Ok(Field::AmiManifestPath),
                            LOCAL_IPV4 => Ok(Field::LocalIp4),
                            HOSTNAME => Ok(Field::Hostname),
                            AMI_ID => Ok(Field::AmiId),
                            INSTANCE_TYPE => Ok(Field::InstanceType),
                            _ => Err(DeError::unknown_field(v, JSON_FIELDS))
                        }
                    }
                }

                deserializer.deserialize_identifier(FieldVisitor)
            }
        }

        struct AmazonMetaDataVisitor;

        impl<'de> Visitor<'de> for AmazonMetaDataVisitor {
            type Value = AmazonMetaData;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("struct AmazonMetaDataVisitor")
            }

            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> where
                A: MapAccess<'de> {
                let mut maybe_ami_launch_index = None;
                let mut maybe_local_hostname = None;
                let mut maybe_availability_zone = None;
                let mut maybe_instance_id = None;
                let mut maybe_public_ip4 = None;
                let mut maybe_public_hostname = None;
                let mut maybe_ami_manifest_path = None;
                let mut maybe_local_ip4 = None;
                let mut maybe_hostname = None;
                let mut maybe_ami_id = None;
                let mut maybe_instance_type = None;

                while let Some(key) = map.next_key()? {
                    match key {
                        Field::AmiLaunchIndex => {
                            if maybe_ami_launch_index.is_some() {
                                return Err(DeError::duplicate_field(AMI_LAUNCH_INDEX));
                            }
                            maybe_ami_launch_index = Some(map.next_value()?)
                        },
                        Field::LocalHostname => {
                            if maybe_local_hostname.is_some() {
                                return Err(DeError::duplicate_field(LOCAL_HOSTNAME));
                            }
                            maybe_local_hostname = Some(map.next_value()?)
                        },
                        Field::AvailabilityZone => {
                            if maybe_availability_zone.is_some() {
                                return Err(DeError::duplicate_field(AVAILABILITY_ZONE));
                            }
                            maybe_availability_zone= Some(map.next_value()?)
                        },
                        Field::InstanceId => {
                            if maybe_instance_id.is_some() {
                                return Err(DeError::duplicate_field(INSTANCE_ID));
                            }
                            maybe_instance_id= Some(map.next_value()?)
                        },
                        Field::PublicIp4 => {
                            if maybe_public_ip4.is_some() {
                                return Err(DeError::duplicate_field(PUBLIC_IPV4));
                            }
                            maybe_public_ip4= Some(map.next_value()?)
                        },
                        Field::PublicHostname => {
                            if maybe_public_hostname.is_some() {
                                return Err(DeError::duplicate_field(PUBLIC_HOSTNAME));
                            }
                            maybe_public_hostname= Some(map.next_value()?)
                        },
                        Field::AmiManifestPath => {
                            if maybe_ami_manifest_path.is_some() {
                                return Err(DeError::duplicate_field(AMI_MANIFEST_PATH));
                            }
                            maybe_ami_manifest_path= Some(map.next_value()?)
                        },
                        Field::LocalIp4 => {
                            if maybe_local_ip4.is_some() {
                                return Err(DeError::duplicate_field(LOCAL_IPV4));
                            }
                            maybe_local_ip4= Some(map.next_value()?)
                        },
                        Field::Hostname => {
                            if maybe_hostname.is_some() {
                                return Err(DeError::duplicate_field(HOSTNAME));
                            }
                            maybe_hostname= Some(map.next_value()?)
                        },
                        Field::AmiId => {
                            if maybe_ami_id.is_some() {
                                return Err(DeError::duplicate_field(AMI_ID));
                            }
                            maybe_ami_id= Some(map.next_value()?)
                        },
                        Field::InstanceType => {
                            if maybe_instance_type.is_some() {
                                return Err(DeError::duplicate_field(INSTANCE_TYPE));
                            }
                            maybe_instance_type= Some(map.next_value()?)
                        }
                    }
                }

                let ami_launch_index = maybe_ami_launch_index.ok_or_else(|| DeError::missing_field(AMI_LAUNCH_INDEX));
                let local_hostname = maybe_local_hostname.ok_or_else(|| DeError::missing_field(LOCAL_HOSTNAME));
                let availability_zone = maybe_availability_zone.ok_or_else(|| DeError::missing_field(AVAILABILITY_ZONE));
                let instance_id = maybe_instance_id.ok_or_else(|| DeError::missing_field(INSTANCE_ID));
                let public_ip4 = maybe_public_ip4.ok_or_else(|| DeError::missing_field(PUBLIC_IPV4));
                let public_hostname = maybe_public_hostname.ok_or_else(|| DeError::missing_field(PUBLIC_HOSTNAME));
                let ami_manifest_path = maybe_ami_manifest_path.ok_or_else(|| DeError::missing_field(AMI_MANIFEST_PATH));
                let local_ip4 = maybe_local_ip4.ok_or_else(|| DeError::missing_field(LOCAL_IPV4));
                let hostname = maybe_hostname.ok_or_else(|| DeError::missing_field(HOSTNAME));
                let ami_id = maybe_ami_id.ok_or_else(|| DeError::missing_field(AMI_ID));
                let instance_type = maybe_instance_type.ok_or_else(|| DeError::missing_field(INSTANCE_TYPE));

                Ok(AmazonMetaData {
                    ami_launch_index: ami_launch_index?,
                    local_hostname: local_hostname?,
                    availability_zone: availability_zone?,
                    instance_id: instance_id?,
                    public_ip4: public_ip4?,
                    public_hostname: public_hostname?,
                    ami_manifest_path: ami_manifest_path?,
                    local_ip4: local_ip4?,
                    hostname: hostname?,
                    ami_id: ami_id?,
                    instance_type: instance_type?
                })
            }
        }
        deserializer.deserialize_struct(AMAZON_META_DATA, RUST_FIELDS, AmazonMetaDataVisitor)
    }
}

mod test {
    use super::*;
    use serde_json;

    #[test]
    fn test_serialize_amazon_meta_data() {
        let md = AmazonMetaData {
            ami_launch_index: "001a".to_string(),
            local_hostname: "localhost0".to_string(),
            availability_zone: "US_East1a".to_string(),
            instance_id: "instance1a".to_string(),
            public_ip4: "32.23.21.212".to_string(),
            public_hostname: "foo.coma".to_string(),
            ami_manifest_path: "/dev/nulla".to_string(),
            local_ip4: "127.0.0.12".to_string(),
            hostname: "privatefoo.coma".to_string(),
            ami_id: "ami0023".to_string(),
            instance_type: "c4xlarged".to_string()
        };
        let json = sample_meta_data();

        let result = serde_json::to_string(&md).unwrap();
        assert_eq!(json, result);
    }

    #[test]
    fn test_deserialize_amazon_meta_data() {
        let md = AmazonMetaData {
            ami_launch_index: "001a".to_string(),
            local_hostname: "localhost0".to_string(),
            availability_zone: "US_East1a".to_string(),
            instance_id: "instance1a".to_string(),
            public_ip4: "32.23.21.212".to_string(),
            public_hostname: "foo.coma".to_string(),
            ami_manifest_path: "/dev/nulla".to_string(),
            local_ip4: "127.0.0.12".to_string(),
            hostname: "privatefoo.coma".to_string(),
            ami_id: "ami0023".to_string(),
            instance_type: "c4xlarged".to_string()
        };
        let json = sample_meta_data();
        let result = serde_json::from_str(&json).unwrap();
        assert_eq!(md, result);
    }

    fn sample_meta_data() -> String {
        r#"{ "ami-launch-index": "001a",
            "local-hostname": "localhost0",
            "availability-zone": "US_East1a",
            "instance-id": "instance1a",
            "public-ipv4": "32.23.21.212",
            "public-hostname": "foo.coma",
            "ami-manifest-path": "/dev/nulla",
            "local-ipv4": "127.0.0.12",
            "hostname": "privatefoo.coma",
            "ami-id": "ami0023",
            "instance-type": "c4xlarged" }"#
            .to_string()
            .replace(" ", "")
            .replace("\n", "")
    }
}<|endoftext|>


<commit_before>// spell-checker:ignore (URL) appspot

use crate::numeric::*;

pub(crate) trait Basis {
    const BASIS: &'static [u64];
}

impl Basis for Montgomery<u64> {
    // Small set of bases for the Miller-Rabin prime test, valid for all 64b integers;
    //  discovered by Jim Sinclair on 2011-04-20, see miller-rabin.appspot.com
    #[allow(clippy::unreadable_literal)]
    const BASIS: &'static [u64] = &[2, 325, 9375, 28178, 450775, 9780504, 1795265022];
}

impl Basis for Montgomery<u32> {
    // Small set of bases for the Miller-Rabin prime test, valid for all 32b integers;
    //  discovered by Steve Worley on 2013-05-27, see miller-rabin.appspot.com
    #[allow(clippy::unreadable_literal)]
    const BASIS: &'static [u64] = &[
        4230279247111683200,
        14694767155120705706,
        16641139526367750375,
    ];
}

#[derive(Eq, PartialEq)]
pub(crate) enum Result {
    Prime,
    Pseudoprime,
    Composite(u64),
}

impl Result {
    pub(crate) fn is_prime(&self) -> bool {
        *self == Result::Prime
    }
}

// Deterministic Miller-Rabin primality-checking algorithm, adapted to extract
// (some) dividers; it will fail to factor strong pseudoprimes.
#[allow(clippy::many_single_char_names)]
pub(crate) fn test<A: Arithmetic + Basis>(m: A) -> Result {
    use self::Result::*;

    let n = m.modulus();
    if n < 2 {
        return Pseudoprime;
    }
    if n % 2 == 0 {
        return if n == 2 { Prime } else { Composite(2) };
    }

    // n-1 = r 2ⁱ
    let i = (n - 1).trailing_zeros();
    let r = (n - 1) >> i;

    let one = m.one();
    let minus_one = m.minus_one();

    for _a in A::BASIS.iter() {
        let _a = _a % n;
        if _a == 0 {
            break;
        }

        let a = m.from_u64(_a);

        // x = a^r mod n
        let mut x = m.pow(a, r);

        {
            // y = ((x²)²...)² i times = x ^ (2ⁱ) = a ^ (r 2ⁱ) = x ^ (n - 1)
            let mut y = x;
            for _ in 0..i {
                y = m.mul(y, y)
            }
            if y != one {
                return Pseudoprime;
            };
        }

        if x == one || x == minus_one {
            continue;
        }

        loop {
            let y = m.mul(x, x);
            if y == one {
                return Composite(gcd(m.to_u64(x) - 1, m.modulus()));
            }
            if y == minus_one {
                // This basis element is not a witness of `n` being composite.
                // Keep looking.
                break;
            }
            x = y;
        }
    }

    Prime
}

// Used by build.rs' tests and debug assertions
#[allow(dead_code)]
pub(crate) fn is_prime(n: u64) -> bool {
    if n % 2 == 0 {
        n == 2
    } else {
        test::<Montgomery<u64>>(Montgomery::new(n)).is_prime()
    }
}

#[cfg(test)]
mod tests {
    use super::is_prime;
    use quickcheck::quickcheck;
    const LARGEST_U64_PRIME: u64 = 0xFFFFFFFFFFFFFFC5;

    fn primes() -> impl Iterator<Item = u64> {
        use crate::table::{NEXT_PRIME, P_INVS_U64};
        use std::iter::once;
        once(2)
            .chain(P_INVS_U64.iter().map(|(p, _, _)| *p))
            .chain(once(NEXT_PRIME))
    }

    #[test]
    fn largest_prime() {
        assert!(is_prime(LARGEST_U64_PRIME));
    }

    #[test]
    fn largest_composites() {
        for i in LARGEST_U64_PRIME + 1..=u64::MAX {
            assert!(!is_prime(i), "2⁶⁴ - {} reported prime", u64::MAX - i + 1);
        }
    }

    #[test]
    fn first_primes() {
        for p in primes() {
            assert!(is_prime(p), "{} reported composite", p);
        }
    }

    #[test]
    fn first_composites() {
        assert!(!is_prime(0));
        assert!(!is_prime(1));

        for (p, q) in primes().zip(primes().skip(1)) {
            for i in p + 1..q {
                assert!(!is_prime(i), "{} reported prime", i);
            }
        }
    }

    #[test]
    fn issue_1556() {
        // 10 425 511 = 2441 × 4271
        assert!(!is_prime(10_425_511));
    }

    #[test]
    fn small_semiprimes() {
        for p in primes() {
            for q in primes().take_while(|q| *q <= p) {
                let n = p * q;
                assert!(!is_prime(n), "{} = {} × {} reported prime", n, p, q);
            }
        }
    }

    quickcheck! {
        fn composites(i: u64, j: u64) -> bool {
            i < 2 || j < 2 || !is_prime(i*j)
        }
    }
}
<commit_msg>factor::miller_rabin: Hoist edge-cases (even, <2) out of test()<commit_after>// spell-checker:ignore (URL) appspot

use crate::numeric::*;

pub(crate) trait Basis {
    const BASIS: &'static [u64];
}

impl Basis for Montgomery<u64> {
    // Small set of bases for the Miller-Rabin prime test, valid for all 64b integers;
    //  discovered by Jim Sinclair on 2011-04-20, see miller-rabin.appspot.com
    #[allow(clippy::unreadable_literal)]
    const BASIS: &'static [u64] = &[2, 325, 9375, 28178, 450775, 9780504, 1795265022];
}

impl Basis for Montgomery<u32> {
    // Small set of bases for the Miller-Rabin prime test, valid for all 32b integers;
    //  discovered by Steve Worley on 2013-05-27, see miller-rabin.appspot.com
    #[allow(clippy::unreadable_literal)]
    const BASIS: &'static [u64] = &[
        4230279247111683200,
        14694767155120705706,
        16641139526367750375,
    ];
}

#[derive(Eq, PartialEq)]
pub(crate) enum Result {
    Prime,
    Pseudoprime,
    Composite(u64),
}

impl Result {
    pub(crate) fn is_prime(&self) -> bool {
        *self == Result::Prime
    }
}

// Deterministic Miller-Rabin primality-checking algorithm, adapted to extract
// (some) dividers; it will fail to factor strong pseudoprimes.
#[allow(clippy::many_single_char_names)]
pub(crate) fn test<A: Arithmetic + Basis>(m: A) -> Result {
    use self::Result::*;

    let n = m.modulus();
    debug_assert!(n > 1);
    debug_assert!(n % 2 != 0);

    // n-1 = r 2ⁱ
    let i = (n - 1).trailing_zeros();
    let r = (n - 1) >> i;

    let one = m.one();
    let minus_one = m.minus_one();

    for _a in A::BASIS.iter() {
        let _a = _a % n;
        if _a == 0 {
            break;
        }

        let a = m.from_u64(_a);

        // x = a^r mod n
        let mut x = m.pow(a, r);

        {
            // y = ((x²)²...)² i times = x ^ (2ⁱ) = a ^ (r 2ⁱ) = x ^ (n - 1)
            let mut y = x;
            for _ in 0..i {
                y = m.mul(y, y)
            }
            if y != one {
                return Pseudoprime;
            };
        }

        if x == one || x == minus_one {
            continue;
        }

        loop {
            let y = m.mul(x, x);
            if y == one {
                return Composite(gcd(m.to_u64(x) - 1, m.modulus()));
            }
            if y == minus_one {
                // This basis element is not a witness of `n` being composite.
                // Keep looking.
                break;
            }
            x = y;
        }
    }

    Prime
}

// Used by build.rs' tests and debug assertions
#[allow(dead_code)]
pub(crate) fn is_prime(n: u64) -> bool {
    if n < 2 {
        false
    } else if n % 2 == 0 {
        n == 2
    } else {
        test::<Montgomery<u64>>(Montgomery::new(n)).is_prime()
    }
}

#[cfg(test)]
mod tests {
    use super::is_prime;
    use quickcheck::quickcheck;
    const LARGEST_U64_PRIME: u64 = 0xFFFFFFFFFFFFFFC5;

    fn primes() -> impl Iterator<Item = u64> {
        use crate::table::{NEXT_PRIME, P_INVS_U64};
        use std::iter::once;
        once(2)
            .chain(P_INVS_U64.iter().map(|(p, _, _)| *p))
            .chain(once(NEXT_PRIME))
    }

    #[test]
    fn largest_prime() {
        assert!(is_prime(LARGEST_U64_PRIME));
    }

    #[test]
    fn largest_composites() {
        for i in LARGEST_U64_PRIME + 1..=u64::MAX {
            assert!(!is_prime(i), "2⁶⁴ - {} reported prime", u64::MAX - i + 1);
        }
    }

    #[test]
    fn first_primes() {
        for p in primes() {
            assert!(is_prime(p), "{} reported composite", p);
        }
    }

    #[test]
    fn first_composites() {
        assert!(!is_prime(0));
        assert!(!is_prime(1));

        for (p, q) in primes().zip(primes().skip(1)) {
            for i in p + 1..q {
                assert!(!is_prime(i), "{} reported prime", i);
            }
        }
    }

    #[test]
    fn issue_1556() {
        // 10 425 511 = 2441 × 4271
        assert!(!is_prime(10_425_511));
    }

    #[test]
    fn small_semiprimes() {
        for p in primes() {
            for q in primes().take_while(|q| *q <= p) {
                let n = p * q;
                assert!(!is_prime(n), "{} = {} × {} reported prime", n, p, q);
            }
        }
    }

    quickcheck! {
        fn composites(i: u64, j: u64) -> bool {
            i < 2 || j < 2 || !is_prime(i*j)
        }
    }
}
<|endoftext|>


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

//! This module implements a matcher that checks if an evaluation log matches the
//! patterns specified by a list of directives.
//!
//! The directives first get divided into groups, where each group consists of
//! 0 or more negative directives, followed by an optional positive directive.
//!
//!     // Directives:
//!     //     not: 1
//!     //     not: 2
//!     //     check: 3
//!     //     not: 4
//!     //     check: bar
//!     //     not : 6
//!
//!     // Groups:
//!     //     group 1:
//!     //         not: 1
//!     //         not: 2
//!     //         check: 3
//!     //     group 2:
//!     //         not: 4
//!     //         check: 5
//!     //     group 3:
//!     //         not: 6
//!
//! Then in order, we take one group at a time and match it against the evaluation log.
//! Recall that the (stringified) evaluation log is essentially a list of string entries
//! that look like this:
//!
//!     // [1] abc de
//!     // [2] foo 3
//!     // [3] 5 bar 6
//!     // [4] 7
//!
//! For each group, we find the earliest place in the current entry where any of the
//! directives matches.
//!     - If the matched directive is negative, abort and report error.
//!     - If the matched direcrive is positive, move on to the next group and start a
//!       new match right after the last matched location.
//!     - If no match is found, retry the current group with the next entry in the log.
//!
//! Example matches:
//!
//!     // [1] abc de
//!     // [2] foo 3
//!     //         ^
//!     //         check: 3
//!     // [3] 5 bar 6
//!     //       ^^^ ^
//!     //       |   not 6
//!     //       check: bar
//!     // [5] 7
//!
//! Note: the group matching procedure above requires searching for multiple string patterns
//! simultatenously. Right now this is implemented using the Aho-Corasick algorithm, achieving
//! an overall time complexity of O(n), where n is the length of the log + the total length of
//! the string patterns in the directives.
//!
//! In order for the match to succeed, it is required that:
//!     1) All positive directives are matched.
//!     2) No negative directives are matched.
//!     3) All error entries in the log are matched.
//!
//! The example above would fail with a negative match.

use crate::{
    checker::directives::Directive,
    evaluator::{EvaluationLog, EvaluationOutput},
};
use aho_corasick::{AhoCorasick, AhoCorasickBuilder};

/// A group consisting of 0 or more negative directives followed by an optional positive directive.
/// An Aho-Corasick automaton is used for efficient matching.
struct MatcherGroup<D> {
    directives: Vec<(usize, D)>,
    automaton: AhoCorasick,
}

/// A group match consisting of the type of the match (p/n), the id of the matched directive and
/// the start and end locations of the text matched (in bytes).
struct GroupMatch {
    is_positive: bool,
    directive_id: usize,
    start: usize,
    end: usize,
}

impl<D: AsRef<Directive>> MatcherGroup<D> {
    /// Find the earliest place where any directive in the group is matched.
    fn match_earliest(&self, s: &str) -> Option<GroupMatch> {
        self.automaton.earliest_find(s).map(|mat| {
            let pat_id = mat.pattern();
            let directive_id = self.directives[pat_id].0;
            let is_positive = self.directives[pat_id].1.as_ref().is_positive();

            GroupMatch {
                is_positive,
                directive_id,
                start: mat.start(),
                end: mat.end(),
            }
        })
    }
}

/// Divides the directives into matcher groups and builds an Aho-Corasick automaton for each group.
fn build_matcher_groups<I, D>(directives: I) -> Vec<MatcherGroup<D>>
where
    D: AsRef<Directive>,
    I: IntoIterator<Item = D>,
{
    let mut groups = vec![];
    let mut buffer = vec![];

    for (id, d) in directives.into_iter().enumerate() {
        if d.as_ref().is_positive() {
            buffer.push((id, d));
            groups.push(buffer);
            buffer = vec![];
        } else {
            buffer.push((id, d));
        }
    }
    if !buffer.is_empty() {
        groups.push(buffer);
    }

    groups
        .into_iter()
        .map(|directives| {
            let automaton = AhoCorasickBuilder::new()
                .dfa(true)
                .build(directives.iter().map(|(_, d)| d.as_ref().pattern_str()));
            MatcherGroup {
                directives,
                automaton,
            }
        })
        .collect()
}

/// An iterator that steps through all matches produced by the given matcher groups
/// against the (stringified) log.
struct MatchIterator<'a, D, S> {
    text: &'a [S],
    matcher_groups: &'a [MatcherGroup<D>],
    cur_entry_id: usize,
    cur_entry_offset: usize,
    cur_group_id: usize,
}

impl<'a, D, S> MatchIterator<'a, D, S>
where
    D: AsRef<Directive>,
    S: AsRef<str>,
{
    fn new(matcher_groups: &'a [MatcherGroup<D>], text: &'a [S]) -> Self {
        Self {
            text,
            matcher_groups,
            cur_entry_id: 0,
            cur_entry_offset: 0,
            cur_group_id: 0,
        }
    }
}

impl<'a, D, S> Iterator for MatchIterator<'a, D, S>
where
    D: AsRef<Directive>,
    S: AsRef<str>,
{
    type Item = (bool, Match);

    fn next(&mut self) -> Option<Self::Item> {
        if self.cur_entry_id >= self.text.len() || self.cur_group_id >= self.matcher_groups.len() {
            return None;
        }

        let cur_group = &self.matcher_groups[self.cur_group_id];
        while self.cur_entry_id < self.text.len() {
            let cur_entry = &self.text[self.cur_entry_id].as_ref();
            let cur_text_fragment = &cur_entry[self.cur_entry_offset..];

            match cur_group.match_earliest(cur_text_fragment) {
                Some(gm) => {
                    let m = Match {
                        pat_id: gm.directive_id,
                        entry_id: self.cur_entry_id,
                        start: gm.start + self.cur_entry_offset,
                        end: gm.end + self.cur_entry_offset,
                    };
                    self.cur_group_id += 1;
                    self.cur_entry_offset = m.end;
                    if self.cur_entry_offset >= cur_entry.len() {
                        self.cur_entry_id += 1;
                        self.cur_entry_offset = 0;
                    }
                    return Some((gm.is_positive, m));
                }
                None => {
                    self.cur_entry_id += 1;
                    self.cur_entry_offset = 0;
                }
            }
        }
        None
    }
}

/// A single match consisting of the index of the log entry, the start location and the end location (in bytes).
#[derive(Debug)]
pub struct Match {
    pub pat_id: usize,
    pub entry_id: usize,
    pub start: usize,
    pub end: usize,
}

/// A match error.
#[derive(Debug)]
pub enum MatchError {
    NegativeMatch(Match),
    UnmatchedDirectives(Vec<usize>),
    UnmatchedErrors(Vec<usize>),
}

/// The status of a match.
/// Can be either success or failure with errors.
#[derive(Debug)]
pub enum MatchStatus {
    Success,
    Failure(Vec<MatchError>),
}

impl MatchStatus {
    pub fn is_success(&self) -> bool {
        matches!(self, Self::Success)
    }

    pub fn is_failure(&self) -> bool {
        matches!(self, Self::Failure(_))
    }
}

/// The result of matching the directives against the evaluation log.
#[derive(Debug)]
pub struct MatchResult {
    pub status: MatchStatus,
    pub text: Vec<String>,
    pub matches: Vec<Match>,
}

impl MatchResult {
    pub fn is_success(&self) -> bool {
        self.status.is_success()
    }

    pub fn is_failure(&self) -> bool {
        self.status.is_failure()
    }
}

/// Matches the directives against the evaluation log.
pub fn match_output<I, D>(log: &EvaluationLog, directives: I) -> MatchResult
where
    D: AsRef<Directive>,
    I: IntoIterator<Item = D>,
{
    // Convert each entry of the evaluation log into a string, which will be later matched against.
    let text: Vec<_> = log
        .outputs
        .iter()
        .map(|output| match output {
            EvaluationOutput::Error(e) => format!("{:?}", e.root_cause()),
            _ => format!("{:?}", output),
        })
        .collect();

    // Split directives into groups and build an Aho-Corasick automaton for each group.
    let groups = build_matcher_groups(directives);

    // Compute the matches.
    let mut matches = vec![];
    let mut it = MatchIterator::new(&groups, &text);
    while let Some((is_positive, m)) = it.next() {
        if !is_positive {
            return MatchResult {
                status: MatchStatus::Failure(vec![MatchError::NegativeMatch(m)]),
                text,
                matches,
            };
        }
        matches.push(m);
    }

    // Check if all positive directives are matched.
    let mut errors = vec![];
    let mut unmatched_directives = groups[it.cur_group_id..]
        .iter()
        .flat_map(|group| {
            group
                .directives
                .iter()
                .filter(|(_, d)| d.as_ref().is_positive())
                .map(|(id, _)| *id)
        })
        .peekable();
    if unmatched_directives.peek().is_some() {
        errors.push(MatchError::UnmatchedDirectives(
            unmatched_directives.collect(),
        ));
    }

    // Check if all error entries are matched.
    let mut is_log_entry_matched: Vec<_> = text.iter().map(|_| false).collect();
    for m in &matches {
        is_log_entry_matched[m.entry_id] = true;
    }
    let mut unmatched_errors = is_log_entry_matched
        .iter()
        .enumerate()
        .filter(|(id, b)| !*b && log.outputs[*id].is_error())
        .map(|(id, _)| id)
        .peekable();
    if unmatched_errors.peek().is_some() {
        errors.push(MatchError::UnmatchedErrors(unmatched_errors.collect()));
    }

    // Return the result.
    MatchResult {
        status: if errors.is_empty() {
            MatchStatus::Success
        } else {
            MatchStatus::Failure(errors)
        },
        text,
        matches,
    }
}
<commit_msg>[functional-tests] fix typo<commit_after>// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

//! This module implements a matcher that checks if an evaluation log matches the
//! patterns specified by a list of directives.
//!
//! The directives first get divided into groups, where each group consists of
//! 0 or more negative directives, followed by an optional positive directive.
//!
//!     // Directives:
//!     //     not: 1
//!     //     not: 2
//!     //     check: 3
//!     //     not: 4
//!     //     check: bar
//!     //     not : 6
//!
//!     // Groups:
//!     //     group 1:
//!     //         not: 1
//!     //         not: 2
//!     //         check: 3
//!     //     group 2:
//!     //         not: 4
//!     //         check: bar
//!     //     group 3:
//!     //         not: 6
//!
//! Then in order, we take one group at a time and match it against the evaluation log.
//! Recall that the (stringified) evaluation log is essentially a list of string entries
//! that look like this:
//!
//!     // [1] abc de
//!     // [2] foo 3
//!     // [3] bar 6
//!     // [4] 7
//!
//! For each group, we find the earliest place in the current entry where any of the
//! directives matches.
//!     - If the matched directive is negative, abort and report error.
//!     - If the matched direcrive is positive, move on to the next group and start a
//!       new match right after the last matched location.
//!     - If no match is found, retry the current group with the next entry in the log.
//!
//! Example matches:
//!
//!     // [1] abc de
//!     // [2] foo 3
//!     //         ^
//!     //         check: 3
//!     // [3] bar 6
//!     //     ^^^ ^
//!     //     |   not 6
//!     //     check: bar
//!     // [4] 7
//!
//! Note: the group matching procedure above requires searching for multiple string patterns
//! simultatenously. Right now this is implemented using the Aho-Corasick algorithm, achieving
//! an overall time complexity of O(n), where n is the length of the log + the total length of
//! the string patterns in the directives.
//!
//! In order for the match to succeed, it is required that:
//!     1) All positive directives are matched.
//!     2) No negative directives are matched.
//!     3) All error entries in the log are matched.
//!
//! The example above would fail with a negative match.

use crate::{
    checker::directives::Directive,
    evaluator::{EvaluationLog, EvaluationOutput},
};
use aho_corasick::{AhoCorasick, AhoCorasickBuilder};

/// A group consisting of 0 or more negative directives followed by an optional positive directive.
/// An Aho-Corasick automaton is used for efficient matching.
struct MatcherGroup<D> {
    directives: Vec<(usize, D)>,
    automaton: AhoCorasick,
}

/// A group match consisting of the type of the match (p/n), the id of the matched directive and
/// the start and end locations of the text matched (in bytes).
struct GroupMatch {
    is_positive: bool,
    directive_id: usize,
    start: usize,
    end: usize,
}

impl<D: AsRef<Directive>> MatcherGroup<D> {
    /// Find the earliest place where any directive in the group is matched.
    fn match_earliest(&self, s: &str) -> Option<GroupMatch> {
        self.automaton.earliest_find(s).map(|mat| {
            let pat_id = mat.pattern();
            let directive_id = self.directives[pat_id].0;
            let is_positive = self.directives[pat_id].1.as_ref().is_positive();

            GroupMatch {
                is_positive,
                directive_id,
                start: mat.start(),
                end: mat.end(),
            }
        })
    }
}

/// Divides the directives into matcher groups and builds an Aho-Corasick automaton for each group.
fn build_matcher_groups<I, D>(directives: I) -> Vec<MatcherGroup<D>>
where
    D: AsRef<Directive>,
    I: IntoIterator<Item = D>,
{
    let mut groups = vec![];
    let mut buffer = vec![];

    for (id, d) in directives.into_iter().enumerate() {
        if d.as_ref().is_positive() {
            buffer.push((id, d));
            groups.push(buffer);
            buffer = vec![];
        } else {
            buffer.push((id, d));
        }
    }
    if !buffer.is_empty() {
        groups.push(buffer);
    }

    groups
        .into_iter()
        .map(|directives| {
            let automaton = AhoCorasickBuilder::new()
                .dfa(true)
                .build(directives.iter().map(|(_, d)| d.as_ref().pattern_str()));
            MatcherGroup {
                directives,
                automaton,
            }
        })
        .collect()
}

/// An iterator that steps through all matches produced by the given matcher groups
/// against the (stringified) log.
struct MatchIterator<'a, D, S> {
    text: &'a [S],
    matcher_groups: &'a [MatcherGroup<D>],
    cur_entry_id: usize,
    cur_entry_offset: usize,
    cur_group_id: usize,
}

impl<'a, D, S> MatchIterator<'a, D, S>
where
    D: AsRef<Directive>,
    S: AsRef<str>,
{
    fn new(matcher_groups: &'a [MatcherGroup<D>], text: &'a [S]) -> Self {
        Self {
            text,
            matcher_groups,
            cur_entry_id: 0,
            cur_entry_offset: 0,
            cur_group_id: 0,
        }
    }
}

impl<'a, D, S> Iterator for MatchIterator<'a, D, S>
where
    D: AsRef<Directive>,
    S: AsRef<str>,
{
    type Item = (bool, Match);

    fn next(&mut self) -> Option<Self::Item> {
        if self.cur_entry_id >= self.text.len() || self.cur_group_id >= self.matcher_groups.len() {
            return None;
        }

        let cur_group = &self.matcher_groups[self.cur_group_id];
        while self.cur_entry_id < self.text.len() {
            let cur_entry = &self.text[self.cur_entry_id].as_ref();
            let cur_text_fragment = &cur_entry[self.cur_entry_offset..];

            match cur_group.match_earliest(cur_text_fragment) {
                Some(gm) => {
                    let m = Match {
                        pat_id: gm.directive_id,
                        entry_id: self.cur_entry_id,
                        start: gm.start + self.cur_entry_offset,
                        end: gm.end + self.cur_entry_offset,
                    };
                    self.cur_group_id += 1;
                    self.cur_entry_offset = m.end;
                    if self.cur_entry_offset >= cur_entry.len() {
                        self.cur_entry_id += 1;
                        self.cur_entry_offset = 0;
                    }
                    return Some((gm.is_positive, m));
                }
                None => {
                    self.cur_entry_id += 1;
                    self.cur_entry_offset = 0;
                }
            }
        }
        None
    }
}

/// A single match consisting of the index of the log entry, the start location and the end location (in bytes).
#[derive(Debug)]
pub struct Match {
    pub pat_id: usize,
    pub entry_id: usize,
    pub start: usize,
    pub end: usize,
}

/// A match error.
#[derive(Debug)]
pub enum MatchError {
    NegativeMatch(Match),
    UnmatchedDirectives(Vec<usize>),
    UnmatchedErrors(Vec<usize>),
}

/// The status of a match.
/// Can be either success or failure with errors.
#[derive(Debug)]
pub enum MatchStatus {
    Success,
    Failure(Vec<MatchError>),
}

impl MatchStatus {
    pub fn is_success(&self) -> bool {
        matches!(self, Self::Success)
    }

    pub fn is_failure(&self) -> bool {
        matches!(self, Self::Failure(_))
    }
}

/// The result of matching the directives against the evaluation log.
#[derive(Debug)]
pub struct MatchResult {
    pub status: MatchStatus,
    pub text: Vec<String>,
    pub matches: Vec<Match>,
}

impl MatchResult {
    pub fn is_success(&self) -> bool {
        self.status.is_success()
    }

    pub fn is_failure(&self) -> bool {
        self.status.is_failure()
    }
}

/// Matches the directives against the evaluation log.
pub fn match_output<I, D>(log: &EvaluationLog, directives: I) -> MatchResult
where
    D: AsRef<Directive>,
    I: IntoIterator<Item = D>,
{
    // Convert each entry of the evaluation log into a string, which will be later matched against.
    let text: Vec<_> = log
        .outputs
        .iter()
        .map(|output| match output {
            EvaluationOutput::Error(e) => format!("{:?}", e.root_cause()),
            _ => format!("{:?}", output),
        })
        .collect();

    // Split directives into groups and build an Aho-Corasick automaton for each group.
    let groups = build_matcher_groups(directives);

    // Compute the matches.
    let mut matches = vec![];
    let mut it = MatchIterator::new(&groups, &text);
    while let Some((is_positive, m)) = it.next() {
        if !is_positive {
            return MatchResult {
                status: MatchStatus::Failure(vec![MatchError::NegativeMatch(m)]),
                text,
                matches,
            };
        }
        matches.push(m);
    }

    // Check if all positive directives are matched.
    let mut errors = vec![];
    let mut unmatched_directives = groups[it.cur_group_id..]
        .iter()
        .flat_map(|group| {
            group
                .directives
                .iter()
                .filter(|(_, d)| d.as_ref().is_positive())
                .map(|(id, _)| *id)
        })
        .peekable();
    if unmatched_directives.peek().is_some() {
        errors.push(MatchError::UnmatchedDirectives(
            unmatched_directives.collect(),
        ));
    }

    // Check if all error entries are matched.
    let mut is_log_entry_matched: Vec<_> = text.iter().map(|_| false).collect();
    for m in &matches {
        is_log_entry_matched[m.entry_id] = true;
    }
    let mut unmatched_errors = is_log_entry_matched
        .iter()
        .enumerate()
        .filter(|(id, b)| !*b && log.outputs[*id].is_error())
        .map(|(id, _)| id)
        .peekable();
    if unmatched_errors.peek().is_some() {
        errors.push(MatchError::UnmatchedErrors(unmatched_errors.collect()));
    }

    // Return the result.
    MatchResult {
        status: if errors.is_empty() {
            MatchStatus::Success
        } else {
            MatchStatus::Failure(errors)
        },
        text,
        matches,
    }
}
<|endoftext|>


<commit_before>//! The `zfile` module contains functionality to create a zcode file
//!

pub use super::zbytes::Bytes;
pub use super::ztext;

enum JumpType {
    JUMP,
    BRANCH,
    ROUTINE
}

pub struct Zfile {
    pub data: Bytes,
    program_addr: u16,
    jumps: Vec<Zjump>,
    labels: Vec<Zlabel>
}

struct Zjump {
    pub from_addr: u16,
    pub name: String,
    pub jump_type: JumpType
}

struct Zlabel {
    pub to_addr: u16,
    pub name: String
}

impl Zfile {

    /// creates a new zfile
    pub fn new() -> Zfile {
        Zfile {
            data: Bytes{bytes: Vec::new()},
            program_addr: 0x800,
            jumps: Vec::new(),
            labels: Vec::new()
        }
    }

    /// creates the header of a zfile
    pub fn create_header(&mut self) {

        assert!(self.data.len() == 0, "create_header should run at the beginning of the op-codes");

        let alpha_addr: u16 = 0x40;
        let extension_addr: u16 = alpha_addr + 78;
        let global_addr: u16 = extension_addr as u16 + 4;
        let object_addr: u16 = global_addr + 480;
        let high_memory_addr: u16 = 0x308;
        let static_addr: u16 = 0x308;
        let dictionary_addr: u16 = 0x308;
        //let program_addr: u16 = 0x800;

        // version
        self.data.write_byte(8, 0x00);

        // flags
        self.data.write_byte(0, 0x01);

        // release version (0x02 und 0x03)
        self.data.write_u16(0, 0x02);

        // base of high memory (byte address) (0x04 and 0x05)
        self.data.write_u16(high_memory_addr, 0x04);

        // initial value of programm counter (0x06 and 0x07)
        self.data.write_u16(self.program_addr, 0x06);

        // location of dictionary (byte address) (0x08 and 0x09)
        self.data.write_u16(dictionary_addr, 0x08);

        // location of object table (byte address) (0x0a and 0x0b)
        self.data.write_u16(object_addr, 0x0a);

        // location of global variables table (byte address) (0x0c and 0x0d)
        self.data.write_u16(global_addr, 0x0c);

        // base of static memory (byte address) (0x0e and 0x0f)
        self.data.write_u16(static_addr, 0x0e);

        // alphabet address (bytes) - its 0x34 and 0x35, why not only 0x34?
        self.data.write_u16(alpha_addr, 0x34);

        // header extension table address (bytes) - its 0x36 and 0x37, why not only 0x36?
        self.data.write_u16(extension_addr, 0x36);

        // alphabet
        self.write_alphabet(alpha_addr as usize);

        // header extension table
        self.data.write_u16(3, extension_addr as usize); // Number of further words in table
        self.data.write_u16(0, extension_addr as usize + 1); // x-coordinate of mouse after a click
        self.data.write_u16(0, extension_addr as usize + 2); // y-coordinate of mouse after a click
        self.data.write_u16(0, extension_addr as usize + 3); // if != 0: unicode translation table address (optional)

        // global variables
        // ...

        // object table name
        self.write_object_name("object", 770);

        // dictionary
        let tmp: [u8; 4] = [0x00, 0x06, 0x00, 0x00];
        self.data.write_bytes(&tmp, dictionary_addr as usize);
    }

    /// writes the alphabet to index
    fn write_alphabet(&mut self, index: usize) {
        // TODO: is it possible to do this with map?
        let mut alpha_tmp: [u8; 78] = [0; 78];
        for i in 0..ztext::ALPHABET.len() {
            alpha_tmp[i] = ztext::ALPHABET[i] as u8;
        }
        self.data.write_bytes(&alpha_tmp, index);
    }

    /// writes the object-name to an index
    /// # Examples
    /// '''ignore
    /// write_object_name(name: 3, index: 10)
    /// '''
    fn write_object_name(&mut self, name: &str, index: usize) {
        let mut text_bytes: Bytes = Bytes{bytes: Vec::new()};
        let length: u16 = ztext::encode(&mut text_bytes, name);

        // length ob object name
        self.data.write_byte(length as u8, index);

        self.data.write_bytes(&text_bytes.bytes, index + 1);
    }

    /// saves the addresses of the labels to the positions of the jump-ops
    /// goes through all jumps and labels, if they have the same name:
    ///  write the "where to jump"-adress of the label to the position of the jump
    /// TODO: Error handling for nonexistant labels
    fn write_jumps(&mut self) {
        for jump in self.jumps.iter_mut() {
            for label in self.labels.iter_mut() {
                if label.name == jump.name {
                    match jump.jump_type {
                        JumpType::ROUTINE => {
                            let new_addr: u16 = label.to_addr / 8;
                            self.data.write_u16(new_addr, jump.from_addr as usize);
                        },
                        JumpType::BRANCH => {
                            let mut new_addr: i32 = label.to_addr as i32 - jump.from_addr as i32;
                            new_addr &= 0x3fff;
                            new_addr |= 0x8000;
                            self.data.write_u16(new_addr as u16, jump.from_addr as usize);
                        },
                        JumpType::JUMP => {
                            let new_addr: i32 = label.to_addr as i32 - jump.from_addr as i32;
                            self.data.write_u16(new_addr as u16, jump.from_addr as usize);
                        }
                    }
                }
            }
        }
    }

    /// adds jump to write the jump-addresses after reading all commands
    fn add_jump(&mut self, name: String, jump_type: JumpType) {
        let from_addr: u16 = self.data.bytes.len() as u16;
        let jump: Zjump = Zjump{ from_addr: from_addr, name: name, jump_type: jump_type};
        self.jumps.push(jump);

        // spacer for the adress where the to-jump-label will be written
        self.data.write_u16(0x0000, from_addr as usize);
    }

    /// adds label to the labels-vector. we need them later
    fn add_label(&mut self, name: String, to_addr: u16) {
        let label: Zlabel = Zlabel{ name: name, to_addr: to_addr };
        for other_label in self.labels.iter() {
            if other_label.name == label.name {
                panic!("label has to be unique, but \"{}\" isn't.", other_label.name);
            }
        }
        self.labels.push(label);
    }

    // ================================
    // no op-commands

    /// start of an zcode programm
    /// fills everying < program_addr with zeros
    /// should called as the first commend
    pub fn start(&mut self) {
        self.create_header();
        self.data.write_zero_until(self.program_addr as usize);
    }

    /// writes all stuff that couldn't written directly
    /// should be called as the last commend
    pub fn end(&mut self) {
        self.write_jumps();
    }

    /// command to create a routine
    pub fn routine(&mut self, name: &str, count_variables: u8) {
        let index: u16 = routine_address(self.data.bytes.len() as u16);

        assert!(count_variables == 0, "variables are not implemented until now");
        assert!(index % 8 == 0, "adress of a routine must start at address % 8 == 0");

        self.add_label(name.to_string(), index);
        self.data.write_byte(count_variables, index as usize);
    }

    /// command to create a label
    pub fn label(&mut self, name: &str) {
        let index: usize = self.data.bytes.len();
        self.add_label(name.to_string(), index as u16);
    }

    // ================================
    // specific ops

    /// print strings
    /// print is 0OP
    pub fn op_print(&mut self, content: &str) {
        let index: usize = self.data.bytes.len();
        self.op_0_op(0x02);

        let mut text_bytes: Bytes = Bytes{bytes: Vec::new()};
        ztext::encode(&mut text_bytes, content);
        self.data.write_bytes(&text_bytes.bytes, index + 1);
    }

    /// exits the program
    /// quit is 0OP
    pub fn op_quit(&mut self) {
        self.op_0_op(0x0a);
    }

    pub fn op_newline(&mut self) {
        self.op_0_op(0x0b);
    }

    /// calls a routine
    /// call_1n is 1OP
    pub fn op_call_1n(&mut self, jump_to_label: &str) {
        self.op_1_op(0x0f);
        self.add_jump(jump_to_label.to_string(), JumpType::ROUTINE);
    }
    /// sets the colors of the foreground (font) and background
    pub fn op_set_color(&mut self, fg:u8, bg:u8){
        let op_coding = 0x00 << 6 | 0x00 << 5 | 0x1B;
        self.data.append_byte(op_coding);
        self.data.append_byte(fg);
        self.data.append_byte(bg);
    }

    /// set the style of the text
    pub fn op_set_text_style(&mut self, bold: bool, reverse: bool, fp: bool, underline: bool){
        self.op_var(0x11);
        let byte = 0x01 << 6 | 0x03 << 4 | 0x03 << 2 | 0x03 << 0;
        self.data.append_byte(byte);
        let mut style_byte : u8;
        style_byte = 0x00;
        if bold {
            style_byte |=0x02
        }
         if reverse {
            style_byte |=0x01
        }
         if fp {
            style_byte |=0x08
        }
         if underline {
            style_byte |=0x04
        }
        self.data.append_byte(style_byte);
    }

    /// reads keys from the keyboard and saves the asci-value in local_var_id
    /// read_char is VAROP
    pub fn op_read_char(&mut self, local_var_id: u8) {
        self.op_var(0x16);

        // type of following arguments
        // first argument has value 0 to detect value from keyboard
        // => 0x01, becouse of constant < 255
        // "zero the other 3 arguments"
        let byte = 0x01 << 6 | 0x03 << 4 | 0x03 << 2 | 0x03 << 0;
        self.data.append_byte(byte);

        // write argument value
        self.data.append_byte(0x00);

        // write varible id
        self.data.append_byte(local_var_id);
    }

    /// jumps to a label
    pub fn op_jump(&mut self, jump_to_label: &str) {
        self.op_1_op(0x0c);
        self.add_jump(jump_to_label.to_string(), JumpType::JUMP);
    }

    /// jumps to a label if the value of local_var_id is equal to const
    /// is an 2OP, but with small constant and variable
    pub fn op_je(&mut self, local_var_id: u8, equal_to_const: u8, jump_to_label: &str) {

        // 0x01: variable; 0x00: constant; 0x01: je-opcode
        let op_coding = 0x01 << 6 | 0x00 << 5 | 0x01;
        self.data.append_byte(op_coding);

        // variable id
        self.data.append_byte(local_var_id);

        // const
        self.data.append_byte(equal_to_const);

        // jump
        self.add_jump(jump_to_label.to_string(), JumpType::BRANCH);
    }
    /// prints an unicode char to the current stream
    pub fn op_print_unicode_char(&mut self, value: u8){
        self.op_1_op(0xbe);
        self.data.append_byte(0x0b);
        let byte = 0x01 << 6 | 0x03 << 4 | 0x03 << 2 | 0x03 << 0;
        self.data.append_byte(byte);
        self.data.append_byte(value);
    }

    // ================================
    // general ops

    /// op-codes with 0 operators
    fn op_0_op(&mut self, value: u8) {
        let byte = value | 0xb0;
        self.data.append_byte(byte);
    }

    /// op-codes with 1 operator
    fn op_1_op(&mut self, value: u8) {
        let byte = value | 0x80;
        self.data.append_byte(byte);
    }

    /// op-codes with variable operators
    /// only one variable is supported at the moment
    fn op_var(&mut self, value: u8) {
        let byte = value | 0xe0;
        self.data.append_byte(byte);
    }
}

/// returns the routine address, should be adress % 8 == 0 (becouse its an packed address)
fn routine_address(adress: u16) -> u16 {
    adress + adress % 8
}
<commit_msg>changed variable names<commit_after>//! The `zfile` module contains functionality to create a zcode file
//!

pub use super::zbytes::Bytes;
pub use super::ztext;

enum JumpType {
    JUMP,
    BRANCH,
    ROUTINE
}

pub struct Zfile {
    pub data: Bytes,
    program_addr: u16,
    jumps: Vec<Zjump>,
    labels: Vec<Zlabel>
}

struct Zjump {
    pub from_addr: u16,
    pub name: String,
    pub jump_type: JumpType
}

struct Zlabel {
    pub to_addr: u16,
    pub name: String
}

impl Zfile {

    /// creates a new zfile
    pub fn new() -> Zfile {
        Zfile {
            data: Bytes{bytes: Vec::new()},
            program_addr: 0x800,
            jumps: Vec::new(),
            labels: Vec::new()
        }
    }

    /// creates the header of a zfile
    pub fn create_header(&mut self) {

        assert!(self.data.len() == 0, "create_header should run at the beginning of the op-codes");

        let alpha_addr: u16 = 0x40;
        let extension_addr: u16 = alpha_addr + 78;
        let global_addr: u16 = extension_addr as u16 + 4;
        let object_addr: u16 = global_addr + 480;
        let high_memory_addr: u16 = 0x308;
        let static_addr: u16 = 0x308;
        let dictionary_addr: u16 = 0x308;
        //let program_addr: u16 = 0x800;

        // version
        self.data.write_byte(8, 0x00);

        // flags
        self.data.write_byte(0, 0x01);

        // release version (0x02 und 0x03)
        self.data.write_u16(0, 0x02);

        // base of high memory (byte address) (0x04 and 0x05)
        self.data.write_u16(high_memory_addr, 0x04);

        // initial value of programm counter (0x06 and 0x07)
        self.data.write_u16(self.program_addr, 0x06);

        // location of dictionary (byte address) (0x08 and 0x09)
        self.data.write_u16(dictionary_addr, 0x08);

        // location of object table (byte address) (0x0a and 0x0b)
        self.data.write_u16(object_addr, 0x0a);

        // location of global variables table (byte address) (0x0c and 0x0d)
        self.data.write_u16(global_addr, 0x0c);

        // base of static memory (byte address) (0x0e and 0x0f)
        self.data.write_u16(static_addr, 0x0e);

        // alphabet address (bytes) - its 0x34 and 0x35, why not only 0x34?
        self.data.write_u16(alpha_addr, 0x34);

        // header extension table address (bytes) - its 0x36 and 0x37, why not only 0x36?
        self.data.write_u16(extension_addr, 0x36);

        // alphabet
        self.write_alphabet(alpha_addr as usize);

        // header extension table
        self.data.write_u16(3, extension_addr as usize); // Number of further words in table
        self.data.write_u16(0, extension_addr as usize + 1); // x-coordinate of mouse after a click
        self.data.write_u16(0, extension_addr as usize + 2); // y-coordinate of mouse after a click
        self.data.write_u16(0, extension_addr as usize + 3); // if != 0: unicode translation table address (optional)

        // global variables
        // ...

        // object table name
        self.write_object_name("object", 770);

        // dictionary
        let tmp: [u8; 4] = [0x00, 0x06, 0x00, 0x00];
        self.data.write_bytes(&tmp, dictionary_addr as usize);
    }

    /// writes the alphabet to index
    fn write_alphabet(&mut self, index: usize) {
        // TODO: is it possible to do this with map?
        let mut alpha_tmp: [u8; 78] = [0; 78];
        for i in 0..ztext::ALPHABET.len() {
            alpha_tmp[i] = ztext::ALPHABET[i] as u8;
        }
        self.data.write_bytes(&alpha_tmp, index);
    }

    /// writes the object-name to an index
    /// # Examples
    /// '''ignore
    /// write_object_name(name: 3, index: 10)
    /// '''
    fn write_object_name(&mut self, name: &str, index: usize) {
        let mut text_bytes: Bytes = Bytes{bytes: Vec::new()};
        let length: u16 = ztext::encode(&mut text_bytes, name);

        // length ob object name
        self.data.write_byte(length as u8, index);

        self.data.write_bytes(&text_bytes.bytes, index + 1);
    }

    /// saves the addresses of the labels to the positions of the jump-ops
    /// goes through all jumps and labels, if they have the same name:
    ///  write the "where to jump"-adress of the label to the position of the jump
    /// TODO: Error handling for nonexistant labels
    fn write_jumps(&mut self) {
        for jump in self.jumps.iter_mut() {
            for label in self.labels.iter_mut() {
                if label.name == jump.name {
                    match jump.jump_type {
                        JumpType::ROUTINE => {
                            let new_addr: u16 = label.to_addr / 8;
                            self.data.write_u16(new_addr, jump.from_addr as usize);
                        },
                        JumpType::BRANCH => {
                            let mut new_addr: i32 = label.to_addr as i32 - jump.from_addr as i32;
                            new_addr &= 0x3fff;
                            new_addr |= 0x8000;
                            self.data.write_u16(new_addr as u16, jump.from_addr as usize);
                        },
                        JumpType::JUMP => {
                            let new_addr: i32 = label.to_addr as i32 - jump.from_addr as i32;
                            self.data.write_u16(new_addr as u16, jump.from_addr as usize);
                        }
                    }
                }
            }
        }
    }

    /// adds jump to write the jump-addresses after reading all commands
    fn add_jump(&mut self, name: String, jump_type: JumpType) {
        let from_addr: u16 = self.data.bytes.len() as u16;
        let jump: Zjump = Zjump{ from_addr: from_addr, name: name, jump_type: jump_type};
        self.jumps.push(jump);

        // spacer for the adress where the to-jump-label will be written
        self.data.write_u16(0x0000, from_addr as usize);
    }

    /// adds label to the labels-vector. we need them later
    fn add_label(&mut self, name: String, to_addr: u16) {
        let label: Zlabel = Zlabel{ name: name, to_addr: to_addr };
        for other_label in self.labels.iter() {
            if other_label.name == label.name {
                panic!("label has to be unique, but \"{}\" isn't.", other_label.name);
            }
        }
        self.labels.push(label);
    }

    // ================================
    // no op-commands

    /// start of an zcode programm
    /// fills everying < program_addr with zeros
    /// should called as the first commend
    pub fn start(&mut self) {
        self.create_header();
        self.data.write_zero_until(self.program_addr as usize);
    }

    /// writes all stuff that couldn't written directly
    /// should be called as the last commend
    pub fn end(&mut self) {
        self.write_jumps();
    }

    /// command to create a routine
    pub fn routine(&mut self, name: &str, count_variables: u8) {
        let index: u16 = routine_address(self.data.bytes.len() as u16);

        assert!(count_variables == 0, "variables are not implemented until now");
        assert!(index % 8 == 0, "adress of a routine must start at address % 8 == 0");

        self.add_label(name.to_string(), index);
        self.data.write_byte(count_variables, index as usize);
    }

    /// command to create a label
    pub fn label(&mut self, name: &str) {
        let index: usize = self.data.bytes.len();
        self.add_label(name.to_string(), index as u16);
    }

    // ================================
    // specific ops

    /// print strings
    /// print is 0OP
    pub fn op_print(&mut self, content: &str) {
        let index: usize = self.data.bytes.len();
        self.op_0_op(0x02);

        let mut text_bytes: Bytes = Bytes{bytes: Vec::new()};
        ztext::encode(&mut text_bytes, content);
        self.data.write_bytes(&text_bytes.bytes, index + 1);
    }

    /// exits the program
    /// quit is 0OP
    pub fn op_quit(&mut self) {
        self.op_0_op(0x0a);
    }

    pub fn op_newline(&mut self) {
        self.op_0_op(0x0b);
    }

    /// calls a routine
    /// call_1n is 1OP
    pub fn op_call_1n(&mut self, jump_to_label: &str) {
        self.op_1_op(0x0f);
        self.add_jump(jump_to_label.to_string(), JumpType::ROUTINE);
    }
    /// sets the colors of the foreground (font) and background
    pub fn op_set_color(&mut self, fg:u8, bg:u8){
        let op_coding = 0x00 << 6 | 0x00 << 5 | 0x1B;
        self.data.append_byte(op_coding);
        self.data.append_byte(fg);
        self.data.append_byte(bg);
    }

    /// set the style of the text
    pub fn op_set_text_style(&mut self, bold: bool, reverse: bool, monospace: bool, italic: bool){
        self.op_var(0x11);
        let byte = 0x01 << 6 | 0x03 << 4 | 0x03 << 2 | 0x03 << 0;
        self.data.append_byte(byte);
        let mut style_byte : u8;
        style_byte = 0x00;
        if bold {
            style_byte |=0x02
        }
         if reverse {
            style_byte |=0x01
        }
         if monospace {
            style_byte |=0x08
        }
         if italic {
            style_byte |=0x04
        }
        self.data.append_byte(style_byte);
    }

    /// reads keys from the keyboard and saves the asci-value in local_var_id
    /// read_char is VAROP
    pub fn op_read_char(&mut self, local_var_id: u8) {
        self.op_var(0x16);

        // type of following arguments
        // first argument has value 0 to detect value from keyboard
        // => 0x01, becouse of constant < 255
        // "zero the other 3 arguments"
        let byte = 0x01 << 6 | 0x03 << 4 | 0x03 << 2 | 0x03 << 0;
        self.data.append_byte(byte);

        // write argument value
        self.data.append_byte(0x00);

        // write varible id
        self.data.append_byte(local_var_id);
    }

    /// jumps to a label
    pub fn op_jump(&mut self, jump_to_label: &str) {
        self.op_1_op(0x0c);
        self.add_jump(jump_to_label.to_string(), JumpType::JUMP);
    }

    /// jumps to a label if the value of local_var_id is equal to const
    /// is an 2OP, but with small constant and variable
    pub fn op_je(&mut self, local_var_id: u8, equal_to_const: u8, jump_to_label: &str) {

        // 0x01: variable; 0x00: constant; 0x01: je-opcode
        let op_coding = 0x01 << 6 | 0x00 << 5 | 0x01;
        self.data.append_byte(op_coding);

        // variable id
        self.data.append_byte(local_var_id);

        // const
        self.data.append_byte(equal_to_const);

        // jump
        self.add_jump(jump_to_label.to_string(), JumpType::BRANCH);
    }
    /// prints an unicode char to the current stream
    pub fn op_print_unicode_char(&mut self, value: u8){
        self.op_1_op(0xbe);
        self.data.append_byte(0x0b);
        let byte = 0x01 << 6 | 0x03 << 4 | 0x03 << 2 | 0x03 << 0;
        self.data.append_byte(byte);
        self.data.append_byte(value);
    }

    // ================================
    // general ops

    /// op-codes with 0 operators
    fn op_0_op(&mut self, value: u8) {
        let byte = value | 0xb0;
        self.data.append_byte(byte);
    }

    /// op-codes with 1 operator
    fn op_1_op(&mut self, value: u8) {
        let byte = value | 0x80;
        self.data.append_byte(byte);
    }

    /// op-codes with variable operators
    /// only one variable is supported at the moment
    fn op_var(&mut self, value: u8) {
        let byte = value | 0xe0;
        self.data.append_byte(byte);
    }
}

/// returns the routine address, should be adress % 8 == 0 (becouse its an packed address)
fn routine_address(adress: u16) -> u16 {
    adress + adress % 8
}
<|endoftext|>