<commit_before>// Copyright (c) Aptos
// SPDX-License-Identifier: Apache-2.0

// This is required because a diesel macro makes clippy sad
#![allow(clippy::extra_unused_lifetimes)]
#![allow(clippy::unused_unit)]

use crate::{
    database::PgPoolConnection,
    models::{events::EventModel, write_set_changes::WriteSetChangeModel},
    schema::{block_metadata_transactions, transactions, user_transactions},
};
use aptos_rest_client::aptos_api_types::{
    Address, BlockMetadataTransaction as APIBlockMetadataTransaction,
    Transaction as APITransaction, TransactionInfo, UserTransaction as APIUserTransaction, U64,
};
use diesel::{
    BelongingToDsl, ExpressionMethods, GroupedBy, OptionalExtension, QueryDsl, RunQueryDsl,
};
use futures::future::Either;
use serde::Serialize;

#[derive(AsChangeset, Debug, Identifiable, Insertable, Queryable, Serialize)]
#[primary_key(hash)]
#[diesel(table_name = "transactions")]
pub struct Transaction {
    #[diesel(column_name = type)]
    pub type_: String,
    pub payload: serde_json::Value,
    pub version: i64,
    pub hash: String,
    pub state_root_hash: String,
    pub event_root_hash: String,
    pub gas_used: i64,
    pub success: bool,
    pub vm_status: String,
    pub accumulator_root_hash: String,
    // Default time columns
    pub inserted_at: chrono::NaiveDateTime,
}

impl Transaction {
    pub fn get_many_by_version(
        start_version: i64,
        number_to_get: i64,
        connection: &PgPoolConnection,
    ) -> diesel::QueryResult<
        Vec<(
            Transaction,
            Option<UserTransaction>,
            Option<BlockMetadataTransaction>,
            Vec<EventModel>,
            Vec<WriteSetChangeModel>,
        )>,
    > {
        let mut txs = transactions::table
            .filter(transactions::version.ge(start_version))
            .order(transactions::version.asc())
            .limit(number_to_get)
            .load::<Transaction>(connection)?;

        let mut user_transactions: Vec<Vec<UserTransaction>> = UserTransaction::belonging_to(&txs)
            .load::<UserTransaction>(connection)?
            .grouped_by(&txs);

        let mut block_metadata_transactions: Vec<Vec<BlockMetadataTransaction>> =
            BlockMetadataTransaction::belonging_to(&txs)
                .load::<BlockMetadataTransaction>(connection)?
                .grouped_by(&txs);

        let mut events: Vec<Vec<EventModel>> = EventModel::belonging_to(&txs)
            .load::<EventModel>(connection)?
            .grouped_by(&txs);

        let mut write_set_changes: Vec<Vec<WriteSetChangeModel>> =
            WriteSetChangeModel::belonging_to(&txs)
                .load::<WriteSetChangeModel>(connection)?
                .grouped_by(&txs);

        // Convert to the nice result tuple
        let mut result = vec![];
        while !txs.is_empty() {
            result.push((
                txs.pop().unwrap(),
                user_transactions.pop().unwrap().pop(),
                block_metadata_transactions.pop().unwrap().pop(),
                events.pop().unwrap(),
                write_set_changes.pop().unwrap(),
            ))
        }

        Ok(result)
    }

    pub fn get_by_version(
        version: u64,
        connection: &PgPoolConnection,
    ) -> diesel::QueryResult<(
        Transaction,
        Option<UserTransaction>,
        Option<BlockMetadataTransaction>,
        Vec<EventModel>,
        Vec<WriteSetChangeModel>,
    )> {
        let transaction = transactions::table
            .filter(transactions::version.eq(version as i64))
            .first::<Transaction>(connection)?;

        let (user_transaction, block_metadata_transaction, events, write_set_changes) =
            transaction.get_details_for_transaction(connection)?;

        Ok((
            transaction,
            user_transaction,
            block_metadata_transaction,
            events,
            write_set_changes,
        ))
    }

    pub fn get_by_hash(
        transaction_hash: &str,
        connection: &PgPoolConnection,
    ) -> diesel::QueryResult<(
        Transaction,
        Option<UserTransaction>,
        Option<BlockMetadataTransaction>,
        Vec<EventModel>,
        Vec<WriteSetChangeModel>,
    )> {
        let transaction = transactions::table
            .filter(transactions::hash.eq(&transaction_hash))
            .first::<Transaction>(connection)?;

        let (user_transaction, block_metadata_transaction, events, write_set_changes) =
            transaction.get_details_for_transaction(connection)?;

        Ok((
            transaction,
            user_transaction,
            block_metadata_transaction,
            events,
            write_set_changes,
        ))
    }

    fn get_details_for_transaction(
        &self,
        connection: &PgPoolConnection,
    ) -> diesel::QueryResult<(
        Option<UserTransaction>,
        Option<BlockMetadataTransaction>,
        Vec<EventModel>,
        Vec<WriteSetChangeModel>,
    )> {
        let mut user_transaction: Option<UserTransaction> = None;
        let mut block_metadata_transaction: Option<BlockMetadataTransaction> = None;

        let events = crate::schema::events::table
            .filter(crate::schema::events::transaction_hash.eq(&self.hash))
            .load::<EventModel>(connection)?;

        let write_set_changes = crate::schema::write_set_changes::table
            .filter(crate::schema::write_set_changes::transaction_hash.eq(&self.hash))
            .load::<WriteSetChangeModel>(connection)?;

        match self.type_.as_str() {
            "user_transaction" => {
                user_transaction = user_transactions::table
                    .filter(user_transactions::hash.eq(&self.hash))
                    .first::<UserTransaction>(connection)
                    .optional()?;
            }
            "block_metadata_transaction" => {
                block_metadata_transaction = block_metadata_transactions::table
                    .filter(block_metadata_transactions::hash.eq(&self.hash))
                    .first::<BlockMetadataTransaction>(connection)
                    .optional()?;
            }
            "genesis_transaction" => {}
            "state_checkpoint_transaction" => {}
            _ => unreachable!("Unknown transaction type: {}", &self.type_),
        };
        Ok((
            user_transaction,
            block_metadata_transaction,
            events,
            write_set_changes,
        ))
    }

    pub fn from_transaction(
        transaction: &APITransaction,
    ) -> (
        Transaction,
        Option<Either<UserTransaction, BlockMetadataTransaction>>,
        Option<Vec<EventModel>>,
        Option<Vec<WriteSetChangeModel>>,
    ) {
        match transaction {
            APITransaction::UserTransaction(tx) => (
                Self::from_transaction_info(
                    &tx.info,
                    serde_json::to_value(&tx.request.payload).unwrap(),
                    transaction.type_str().to_string(),
                ),
                Some(Either::Left(UserTransaction::from_transaction(tx))),
                EventModel::from_events(tx.info.hash.to_string(), &tx.events),
                WriteSetChangeModel::from_write_set_changes(
                    tx.info.hash.to_string(),
                    &tx.info.changes,
                ),
            ),
            APITransaction::GenesisTransaction(tx) => (
                Self::from_transaction_info(
                    &tx.info,
                    serde_json::to_value(&tx.payload).unwrap(),
                    transaction.type_str().to_string(),
                ),
                None,
                EventModel::from_events(tx.info.hash.to_string(), &tx.events),
                WriteSetChangeModel::from_write_set_changes(
                    tx.info.hash.to_string(),
                    &tx.info.changes,
                ),
            ),
            APITransaction::BlockMetadataTransaction(tx) => (
                Self::from_transaction_info(
                    &tx.info,
                    serde_json::Value::Null,
                    transaction.type_str().to_string(),
                ),
                Some(Either::Right(BlockMetadataTransaction::from_transaction(
                    tx,
                ))),
                EventModel::from_events(tx.info.hash.to_string(), &tx.events),
                WriteSetChangeModel::from_write_set_changes(
                    tx.info.hash.to_string(),
                    &tx.info.changes,
                ),
            ),
            APITransaction::StateCheckpointTransaction(tx) => (
                Self::from_transaction_info(
                    &tx.info,
                    serde_json::Value::Null,
                    transaction.type_str().to_string(),
                ),
                None,
                None,
                None,
            ),
            APITransaction::PendingTransaction(..) => {
                unreachable!()
            }
        }
    }

    fn from_transaction_info(
        info: &TransactionInfo,
        payload: serde_json::Value,
        type_: String,
    ) -> Self {
        Self {
            type_,
            payload,
            version: *info.version.inner() as i64,
            hash: info.hash.to_string(),
            state_root_hash: info.state_root_hash.to_string(),
            event_root_hash: info.event_root_hash.to_string(),
            gas_used: *info.gas_used.inner() as i64,
            success: info.success,
            vm_status: info.vm_status.clone(),
            accumulator_root_hash: info.accumulator_root_hash.to_string(),
            inserted_at: chrono::Utc::now().naive_utc(),
        }
    }
}

#[derive(AsChangeset, Associations, Debug, Identifiable, Insertable, Queryable, Serialize)]
#[belongs_to(Transaction, foreign_key = "hash")]
#[primary_key(hash)]
#[diesel(table_name = "user_transactions")]
pub struct UserTransaction {
    pub hash: String,
    pub signature: serde_json::Value,
    pub sender: String,
    pub sequence_number: i64,
    pub max_gas_amount: i64,

    // from UserTransactionRequest
    pub expiration_timestamp_secs: chrono::NaiveDateTime,
    pub gas_unit_price: i64,

    // from UserTransaction
    pub timestamp: chrono::NaiveDateTime,

    // Default time columns
    pub inserted_at: chrono::NaiveDateTime,
}

impl UserTransaction {
    pub fn from_transaction(tx: &APIUserTransaction) -> Self {
        Self {
            hash: tx.info.hash.to_string(),
            signature: serde_json::to_value(&tx.request.signature).unwrap(),
            sender: tx.request.sender.inner().to_hex_literal(),
            sequence_number: *tx.request.sequence_number.inner() as i64,
            max_gas_amount: *tx.request.max_gas_amount.inner() as i64,
            expiration_timestamp_secs: chrono::NaiveDateTime::from_timestamp(
                *tx.request.expiration_timestamp_secs.inner() as i64,
                0,
            ),
            gas_unit_price: *tx.request.gas_unit_price.inner() as i64,
            timestamp: parse_timestamp(tx.timestamp, tx.info.version),
            inserted_at: chrono::Utc::now().naive_utc(),
        }
    }
}

#[derive(AsChangeset, Associations, Debug, Identifiable, Insertable, Queryable, Serialize)]
#[belongs_to(Transaction, foreign_key = "hash")]
#[primary_key("hash")]
#[diesel(table_name = "block_metadata_transactions")]
pub struct BlockMetadataTransaction {
    pub hash: String,
    pub id: String,
    pub round: i64,
    pub previous_block_votes: serde_json::Value,
    pub proposer: String,
    pub timestamp: chrono::NaiveDateTime,

    // Default time columns
    pub inserted_at: chrono::NaiveDateTime,
    pub epoch: i64,
    pub previous_block_votes_bitmap: serde_json::Value,
    pub failed_proposer_indices: serde_json::Value,
}

impl BlockMetadataTransaction {
    pub fn from_transaction(tx: &APIBlockMetadataTransaction) -> Self {
        Self {
            hash: tx.info.hash.to_string(),
            id: tx.id.to_string(),
            round: *tx.round.inner() as i64,
            // TODO: Deprecated, use previous_block_votes_bitmap instead. Column kept to not break indexer users (e.g., explorer), writing an empty vector.
            previous_block_votes: serde_json::to_value(vec![] as Vec<Address>).unwrap(),
            proposer: tx.proposer.inner().to_hex_literal(),
            // time is in milliseconds, but chronos wants seconds
            timestamp: parse_timestamp(tx.timestamp, tx.info.version),
            inserted_at: chrono::Utc::now().naive_utc(),
            epoch: *tx.epoch.inner() as i64,
            previous_block_votes_bitmap: serde_json::to_value(&tx.previous_block_votes).unwrap(),
            failed_proposer_indices: serde_json::to_value(&tx.failed_proposer_indices).unwrap(),
        }
    }
}

fn parse_timestamp(ts: U64, version: U64) -> chrono::NaiveDateTime {
    chrono::NaiveDateTime::from_timestamp_opt(*ts.inner() as i64 / 1000000, 0)
        .unwrap_or_else(|| panic!("Could not parse timestamp {:?} for version {}", ts, version))
}

// Prevent conflicts with other things named `Transaction`
pub type BlockMetadataTransactionModel = BlockMetadataTransaction;
pub type TransactionModel = Transaction;
pub type UserTransactionModel = UserTransaction;

#[cfg(test)]
mod tests {
    use super::*;
    use chrono::Datelike;

    #[test]
    fn test_parse_timestamp() {
        let ts = parse_timestamp(U64::from(1649560602763949), U64::from(1));
        assert_eq!(ts.timestamp(), 1649560602);
        assert_eq!(ts.year(), 2022);
    }
}
<commit_msg>Truncate expiration timestamp to 10 years in future if it is longer (#2419)<commit_after>// Copyright (c) Aptos
// SPDX-License-Identifier: Apache-2.0

// This is required because a diesel macro makes clippy sad
#![allow(clippy::extra_unused_lifetimes)]
#![allow(clippy::unused_unit)]

use crate::{
    database::PgPoolConnection,
    models::{events::EventModel, write_set_changes::WriteSetChangeModel},
    schema::{block_metadata_transactions, transactions, user_transactions},
};
use aptos_rest_client::aptos_api_types::{
    Address, BlockMetadataTransaction as APIBlockMetadataTransaction,
    Transaction as APITransaction, TransactionInfo, UserTransaction as APIUserTransaction, U64,
};
use diesel::{
    BelongingToDsl, ExpressionMethods, GroupedBy, OptionalExtension, QueryDsl, RunQueryDsl,
};
use futures::future::Either;
use serde::Serialize;

static SECONDS_IN_10_YEARS: i64 = 60 * 60 * 24 * 365 * 10;

#[derive(AsChangeset, Debug, Identifiable, Insertable, Queryable, Serialize)]
#[primary_key(hash)]
#[diesel(table_name = "transactions")]
pub struct Transaction {
    #[diesel(column_name = type)]
    pub type_: String,
    pub payload: serde_json::Value,
    pub version: i64,
    pub hash: String,
    pub state_root_hash: String,
    pub event_root_hash: String,
    pub gas_used: i64,
    pub success: bool,
    pub vm_status: String,
    pub accumulator_root_hash: String,
    // Default time columns
    pub inserted_at: chrono::NaiveDateTime,
}

impl Transaction {
    pub fn get_many_by_version(
        start_version: i64,
        number_to_get: i64,
        connection: &PgPoolConnection,
    ) -> diesel::QueryResult<
        Vec<(
            Transaction,
            Option<UserTransaction>,
            Option<BlockMetadataTransaction>,
            Vec<EventModel>,
            Vec<WriteSetChangeModel>,
        )>,
    > {
        let mut txs = transactions::table
            .filter(transactions::version.ge(start_version))
            .order(transactions::version.asc())
            .limit(number_to_get)
            .load::<Transaction>(connection)?;

        let mut user_transactions: Vec<Vec<UserTransaction>> = UserTransaction::belonging_to(&txs)
            .load::<UserTransaction>(connection)?
            .grouped_by(&txs);

        let mut block_metadata_transactions: Vec<Vec<BlockMetadataTransaction>> =
            BlockMetadataTransaction::belonging_to(&txs)
                .load::<BlockMetadataTransaction>(connection)?
                .grouped_by(&txs);

        let mut events: Vec<Vec<EventModel>> = EventModel::belonging_to(&txs)
            .load::<EventModel>(connection)?
            .grouped_by(&txs);

        let mut write_set_changes: Vec<Vec<WriteSetChangeModel>> =
            WriteSetChangeModel::belonging_to(&txs)
                .load::<WriteSetChangeModel>(connection)?
                .grouped_by(&txs);

        // Convert to the nice result tuple
        let mut result = vec![];
        while !txs.is_empty() {
            result.push((
                txs.pop().unwrap(),
                user_transactions.pop().unwrap().pop(),
                block_metadata_transactions.pop().unwrap().pop(),
                events.pop().unwrap(),
                write_set_changes.pop().unwrap(),
            ))
        }

        Ok(result)
    }

    pub fn get_by_version(
        version: u64,
        connection: &PgPoolConnection,
    ) -> diesel::QueryResult<(
        Transaction,
        Option<UserTransaction>,
        Option<BlockMetadataTransaction>,
        Vec<EventModel>,
        Vec<WriteSetChangeModel>,
    )> {
        let transaction = transactions::table
            .filter(transactions::version.eq(version as i64))
            .first::<Transaction>(connection)?;

        let (user_transaction, block_metadata_transaction, events, write_set_changes) =
            transaction.get_details_for_transaction(connection)?;

        Ok((
            transaction,
            user_transaction,
            block_metadata_transaction,
            events,
            write_set_changes,
        ))
    }

    pub fn get_by_hash(
        transaction_hash: &str,
        connection: &PgPoolConnection,
    ) -> diesel::QueryResult<(
        Transaction,
        Option<UserTransaction>,
        Option<BlockMetadataTransaction>,
        Vec<EventModel>,
        Vec<WriteSetChangeModel>,
    )> {
        let transaction = transactions::table
            .filter(transactions::hash.eq(&transaction_hash))
            .first::<Transaction>(connection)?;

        let (user_transaction, block_metadata_transaction, events, write_set_changes) =
            transaction.get_details_for_transaction(connection)?;

        Ok((
            transaction,
            user_transaction,
            block_metadata_transaction,
            events,
            write_set_changes,
        ))
    }

    fn get_details_for_transaction(
        &self,
        connection: &PgPoolConnection,
    ) -> diesel::QueryResult<(
        Option<UserTransaction>,
        Option<BlockMetadataTransaction>,
        Vec<EventModel>,
        Vec<WriteSetChangeModel>,
    )> {
        let mut user_transaction: Option<UserTransaction> = None;
        let mut block_metadata_transaction: Option<BlockMetadataTransaction> = None;

        let events = crate::schema::events::table
            .filter(crate::schema::events::transaction_hash.eq(&self.hash))
            .load::<EventModel>(connection)?;

        let write_set_changes = crate::schema::write_set_changes::table
            .filter(crate::schema::write_set_changes::transaction_hash.eq(&self.hash))
            .load::<WriteSetChangeModel>(connection)?;

        match self.type_.as_str() {
            "user_transaction" => {
                user_transaction = user_transactions::table
                    .filter(user_transactions::hash.eq(&self.hash))
                    .first::<UserTransaction>(connection)
                    .optional()?;
            }
            "block_metadata_transaction" => {
                block_metadata_transaction = block_metadata_transactions::table
                    .filter(block_metadata_transactions::hash.eq(&self.hash))
                    .first::<BlockMetadataTransaction>(connection)
                    .optional()?;
            }
            "genesis_transaction" => {}
            "state_checkpoint_transaction" => {}
            _ => unreachable!("Unknown transaction type: {}", &self.type_),
        };
        Ok((
            user_transaction,
            block_metadata_transaction,
            events,
            write_set_changes,
        ))
    }

    pub fn from_transaction(
        transaction: &APITransaction,
    ) -> (
        Transaction,
        Option<Either<UserTransaction, BlockMetadataTransaction>>,
        Option<Vec<EventModel>>,
        Option<Vec<WriteSetChangeModel>>,
    ) {
        match transaction {
            APITransaction::UserTransaction(tx) => (
                Self::from_transaction_info(
                    &tx.info,
                    serde_json::to_value(&tx.request.payload).unwrap(),
                    transaction.type_str().to_string(),
                ),
                Some(Either::Left(UserTransaction::from_transaction(tx))),
                EventModel::from_events(tx.info.hash.to_string(), &tx.events),
                WriteSetChangeModel::from_write_set_changes(
                    tx.info.hash.to_string(),
                    &tx.info.changes,
                ),
            ),
            APITransaction::GenesisTransaction(tx) => (
                Self::from_transaction_info(
                    &tx.info,
                    serde_json::to_value(&tx.payload).unwrap(),
                    transaction.type_str().to_string(),
                ),
                None,
                EventModel::from_events(tx.info.hash.to_string(), &tx.events),
                WriteSetChangeModel::from_write_set_changes(
                    tx.info.hash.to_string(),
                    &tx.info.changes,
                ),
            ),
            APITransaction::BlockMetadataTransaction(tx) => (
                Self::from_transaction_info(
                    &tx.info,
                    serde_json::Value::Null,
                    transaction.type_str().to_string(),
                ),
                Some(Either::Right(BlockMetadataTransaction::from_transaction(
                    tx,
                ))),
                EventModel::from_events(tx.info.hash.to_string(), &tx.events),
                WriteSetChangeModel::from_write_set_changes(
                    tx.info.hash.to_string(),
                    &tx.info.changes,
                ),
            ),
            APITransaction::StateCheckpointTransaction(tx) => (
                Self::from_transaction_info(
                    &tx.info,
                    serde_json::Value::Null,
                    transaction.type_str().to_string(),
                ),
                None,
                None,
                None,
            ),
            APITransaction::PendingTransaction(..) => {
                unreachable!()
            }
        }
    }

    fn from_transaction_info(
        info: &TransactionInfo,
        payload: serde_json::Value,
        type_: String,
    ) -> Self {
        Self {
            type_,
            payload,
            version: *info.version.inner() as i64,
            hash: info.hash.to_string(),
            state_root_hash: info.state_root_hash.to_string(),
            event_root_hash: info.event_root_hash.to_string(),
            gas_used: *info.gas_used.inner() as i64,
            success: info.success,
            vm_status: info.vm_status.clone(),
            accumulator_root_hash: info.accumulator_root_hash.to_string(),
            inserted_at: chrono::Utc::now().naive_utc(),
        }
    }
}

#[derive(AsChangeset, Associations, Debug, Identifiable, Insertable, Queryable, Serialize)]
#[belongs_to(Transaction, foreign_key = "hash")]
#[primary_key(hash)]
#[diesel(table_name = "user_transactions")]
pub struct UserTransaction {
    pub hash: String,
    pub signature: serde_json::Value,
    pub sender: String,
    pub sequence_number: i64,
    pub max_gas_amount: i64,

    // from UserTransactionRequest
    pub expiration_timestamp_secs: chrono::NaiveDateTime,
    pub gas_unit_price: i64,

    // from UserTransaction
    pub timestamp: chrono::NaiveDateTime,

    // Default time columns
    pub inserted_at: chrono::NaiveDateTime,
}

impl UserTransaction {
    pub fn from_transaction(tx: &APIUserTransaction) -> Self {
        Self {
            hash: tx.info.hash.to_string(),
            signature: serde_json::to_value(&tx.request.signature).unwrap(),
            sender: tx.request.sender.inner().to_hex_literal(),
            sequence_number: *tx.request.sequence_number.inner() as i64,
            max_gas_amount: *tx.request.max_gas_amount.inner() as i64,
            expiration_timestamp_secs: parse_timestamp_secs(
                tx.request.expiration_timestamp_secs,
                tx.info.version,
            ),
            gas_unit_price: *tx.request.gas_unit_price.inner() as i64,
            timestamp: parse_timestamp(tx.timestamp, tx.info.version),
            inserted_at: chrono::Utc::now().naive_utc(),
        }
    }
}

#[derive(AsChangeset, Associations, Debug, Identifiable, Insertable, Queryable, Serialize)]
#[belongs_to(Transaction, foreign_key = "hash")]
#[primary_key("hash")]
#[diesel(table_name = "block_metadata_transactions")]
pub struct BlockMetadataTransaction {
    pub hash: String,
    pub id: String,
    pub round: i64,
    pub previous_block_votes: serde_json::Value,
    pub proposer: String,
    pub timestamp: chrono::NaiveDateTime,

    // Default time columns
    pub inserted_at: chrono::NaiveDateTime,
    pub epoch: i64,
    pub previous_block_votes_bitmap: serde_json::Value,
    pub failed_proposer_indices: serde_json::Value,
}

impl BlockMetadataTransaction {
    pub fn from_transaction(tx: &APIBlockMetadataTransaction) -> Self {
        Self {
            hash: tx.info.hash.to_string(),
            id: tx.id.to_string(),
            round: *tx.round.inner() as i64,
            // TODO: Deprecated, use previous_block_votes_bitmap instead. Column kept to not break indexer users (e.g., explorer), writing an empty vector.
            previous_block_votes: serde_json::to_value(vec![] as Vec<Address>).unwrap(),
            proposer: tx.proposer.inner().to_hex_literal(),
            // time is in milliseconds, but chronos wants seconds
            timestamp: parse_timestamp(tx.timestamp, tx.info.version),
            inserted_at: chrono::Utc::now().naive_utc(),
            epoch: *tx.epoch.inner() as i64,
            previous_block_votes_bitmap: serde_json::to_value(&tx.previous_block_votes).unwrap(),
            failed_proposer_indices: serde_json::to_value(&tx.failed_proposer_indices).unwrap(),
        }
    }
}

fn parse_timestamp(ts: U64, version: U64) -> chrono::NaiveDateTime {
    chrono::NaiveDateTime::from_timestamp_opt(*ts.inner() as i64 / 1000000, 0)
        .unwrap_or_else(|| panic!("Could not parse timestamp {:?} for version {}", ts, version))
}

fn parse_timestamp_secs(ts: U64, version: U64) -> chrono::NaiveDateTime {
    let mut timestamp = ts.0 as i64;
    let timestamp_in_10_years = chrono::offset::Utc::now().timestamp() + SECONDS_IN_10_YEARS;
    if timestamp > timestamp_in_10_years {
        timestamp = timestamp_in_10_years;
    }
    chrono::NaiveDateTime::from_timestamp_opt(timestamp, 0)
        .unwrap_or_else(|| panic!("Could not parse timestamp {:?} for version {}", ts, version))
}

// Prevent conflicts with other things named `Transaction`
pub type BlockMetadataTransactionModel = BlockMetadataTransaction;
pub type TransactionModel = Transaction;
pub type UserTransactionModel = UserTransaction;

#[cfg(test)]
mod tests {
    use super::*;
    use chrono::Datelike;

    #[test]
    fn test_parse_timestamp() {
        let current_year = chrono::offset::Utc::now().year();

        let ts = parse_timestamp(U64::from(1649560602763949), U64::from(1));
        assert_eq!(ts.timestamp(), 1649560602);
        assert_eq!(ts.year(), current_year);

        let ts2 = parse_timestamp_secs(U64::from(600000000000000), U64::from(2));
        assert_eq!(ts2.year(), current_year + 10);

        let ts3 = parse_timestamp_secs(U64::from(1659386386), U64::from(2));
        assert_eq!(ts3.timestamp(), 1659386386);
    }
}
<|endoftext|>


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

//! CSS handling for the computed value of
//! [`basic-shape`][basic-shape]s
//!
//! [basic-shape]: https://drafts.csswg.org/css-shapes/#typedef-basic-shape

use std::fmt;
use style_traits::ToCss;
use values::computed::LengthOrPercentage;
use values::generics::basic_shape::{BasicShape as GenericBasicShape};
use values::generics::basic_shape::{Circle as GenericCircle, ClippingShape as GenericClippingShape};
use values::generics::basic_shape::{Ellipse as GenericEllipse, FloatAreaShape as GenericFloatAreaShape};
use values::generics::basic_shape::{InsetRect as GenericInsetRect, ShapeRadius as GenericShapeRadius};

/// A specified clipping shape.
pub type ClippingShape = GenericClippingShape<BasicShape>;

/// A specified float area shape.
pub type FloatAreaShape = GenericFloatAreaShape<BasicShape>;

/// A computed basic shape.
pub type BasicShape = GenericBasicShape<LengthOrPercentage, LengthOrPercentage, LengthOrPercentage>;

/// The computed value of `inset()`
pub type InsetRect = GenericInsetRect<LengthOrPercentage>;

/// A computed circle.
pub type Circle = GenericCircle<LengthOrPercentage, LengthOrPercentage, LengthOrPercentage>;

/// A computed ellipse.
pub type Ellipse = GenericEllipse<LengthOrPercentage, LengthOrPercentage, LengthOrPercentage>;

/// The computed value of `ShapeRadius`
pub type ShapeRadius = GenericShapeRadius<LengthOrPercentage>;

impl ToCss for Circle {
    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
        dest.write_str("circle(")?;
        self.radius.to_css(dest)?;
        dest.write_str(" at ")?;
        self.position.to_css(dest)
    }
}

impl ToCss for Ellipse {
    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
        dest.write_str("ellipse(")?;
        if (self.semiaxis_x, self.semiaxis_y) != Default::default() {
            self.semiaxis_x.to_css(dest)?;
            dest.write_str(" ")?;
            self.semiaxis_y.to_css(dest)?;
            dest.write_str(" ")?;
        }
        dest.write_str("at ")?;
        self.position.to_css(dest)?;
        dest.write_str(")")
    }
}
<commit_msg>Auto merge of #17216 - nox:fix-computed-circle-serialisation, r=canaltinova<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! CSS handling for the computed value of
//! [`basic-shape`][basic-shape]s
//!
//! [basic-shape]: https://drafts.csswg.org/css-shapes/#typedef-basic-shape

use std::fmt;
use style_traits::ToCss;
use values::computed::LengthOrPercentage;
use values::generics::basic_shape::{BasicShape as GenericBasicShape};
use values::generics::basic_shape::{Circle as GenericCircle, ClippingShape as GenericClippingShape};
use values::generics::basic_shape::{Ellipse as GenericEllipse, FloatAreaShape as GenericFloatAreaShape};
use values::generics::basic_shape::{InsetRect as GenericInsetRect, ShapeRadius as GenericShapeRadius};

/// A specified clipping shape.
pub type ClippingShape = GenericClippingShape<BasicShape>;

/// A specified float area shape.
pub type FloatAreaShape = GenericFloatAreaShape<BasicShape>;

/// A computed basic shape.
pub type BasicShape = GenericBasicShape<LengthOrPercentage, LengthOrPercentage, LengthOrPercentage>;

/// The computed value of `inset()`
pub type InsetRect = GenericInsetRect<LengthOrPercentage>;

/// A computed circle.
pub type Circle = GenericCircle<LengthOrPercentage, LengthOrPercentage, LengthOrPercentage>;

/// A computed ellipse.
pub type Ellipse = GenericEllipse<LengthOrPercentage, LengthOrPercentage, LengthOrPercentage>;

/// The computed value of `ShapeRadius`
pub type ShapeRadius = GenericShapeRadius<LengthOrPercentage>;

impl ToCss for Circle {
    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
        dest.write_str("circle(")?;
        self.radius.to_css(dest)?;
        dest.write_str(" at ")?;
        self.position.to_css(dest)?;
        dest.write_str(")")
    }
}

impl ToCss for Ellipse {
    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
        dest.write_str("ellipse(")?;
        if (self.semiaxis_x, self.semiaxis_y) != Default::default() {
            self.semiaxis_x.to_css(dest)?;
            dest.write_str(" ")?;
            self.semiaxis_y.to_css(dest)?;
            dest.write_str(" ")?;
        }
        dest.write_str("at ")?;
        self.position.to_css(dest)?;
        dest.write_str(")")
    }
}
<|endoftext|>


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

use crate::{proto::oak::introspection_events::Events, Runtime};
use hyper::{
    header::{ACCESS_CONTROL_ALLOW_ORIGIN, CONTENT_TYPE},
    service::{make_service_fn, service_fn},
    Body, Error, Method, Request, Response, Server, StatusCode,
};
use log::info;
use prost::Message;
use regex::Regex;
use std::{net::SocketAddr, sync::Arc};

#[cfg(not(feature = "oak_introspection_client"))]
mod introspection_client {
    use super::*;

    pub fn find_client_file(_path: &str) -> Option<Response> {
        None
    }
}

#[cfg(feature = "oak_introspection_client")]
mod introspection_client {
    use super::*;
    use hyper::header::CONTENT_ENCODING;

    fn static_file(
        file: &str,
        content_type: &str,
        content_encoding: Option<&str>,
    ) -> Response {
        let mut response = Response::new(Body::from(file.to_string()));
        let headers = response.headers_mut();
        headers.insert(CONTENT_TYPE, content_type.parse().unwrap());
        // If the served file is compressed, add a header instructing the
        // browser how to decode it by indicating the compression algorithm in
        // the `Content-Encoding` header.
        // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding
        // For example, our webpack configuration will gzip text-based assets
        // when they are generated.
        // See: (../introspection_browser_client/webpack.config.js).
        if let Some(content_encoding_value) = content_encoding {
            headers.insert(CONTENT_ENCODING, content_encoding_value.parse().unwrap());
        }

        response
    }

    // Looks for a matching file used by the browser client and returns it
    pub fn find_client_file(path: &str) -> Option<Response> {
        let subpath = Regex::new(r"^/dynamic(?P<filepath>[^\s]*)$")
            .unwrap()
            .captures(path)?
            .name("filepath")
            .unwrap()
            .as_str();

        match subpath {
            "/index.js" => Some(static_file(
                "../introspection_browser_client/dist/index.js.gz",
                "application/javascript",
                Some("gzip"),
            )),
            "/graphvizlib.wasm" => Some(static_file(
                "../introspection_browser_client/dist/graphvizlib.wasm.gz",
                "application/wasm",
                Some("gzip"),
            )),
            "/favicon.png" => Some(static_file(
                "../introspection_browser_client/dist/favicon.png",
                "image/png",
                None,
            )),
            // Serve index.html for all other paths under /dynamic, enabling
            // client-side routing
            _ => Some(static_file(
                "../introspection_browser_client/dist/index.html.gz",
                "text/html",
                Some("gzip"),
            )),
        }
    }
}

/// Wrap a string holding a Graphviz Dot graph description in an HTML template
/// suitable for live display.
// TODO(#672): Shift to a templating library.
fn dot_wrap(title: &str, graph: &str) -> String {
    format!(
        r###"
<!DOCTYPE html>
<meta charset="utf-8">
<title>{}</title>

"###,
        title, graph,
    )
}

/// Wrap a body of text in an HTML template suitable for display.
fn html_wrap(title: &str, body: &str) -> String {
    format!(
        r###"
<!DOCTYPE html>
<meta charset="utf-8">
<title>{}</title>

{}
"###,
        title, body
    )
}

// Look for a path like "/{kind}/{id}" and return the id value.
fn find_id(path: &str, kind: &str) -> Option<u64> {
    Regex::new(&format!(r"^/{}/(?P<id>\d+)/?$", kind))
        .unwrap()
        .captures(path)?
        .name("id")
        .unwrap()
        .as_str()
        .parse::<u64>()
        .ok()
}

// Look for a path like "/{kind}/{id}/{subid}" and return the id values.
fn find_ids(path: &str, kind: &str) -> Option<(u64, u64)> {
    let captures = Regex::new(&format!(r"^/{}/(?P<id>\d+)/(?P<subid>\d+)/?$", kind))
        .unwrap()
        .captures(path)?;
    let id = captures.name("id").unwrap().as_str().parse::<u64>().ok()?;
    let subid = captures
        .name("subid")
        .unwrap()
        .as_str()
        .parse::<u64>()
        .ok()?;
    Some((id, subid))
}

// Handler for a single HTTP request to the introspection server.
fn handle_request(
    req: Request,
    runtime: Arc<Runtime>,
) -> Result<Response, hyper::Error> {
    if req.method() != Method::GET {
        let mut method_not_allowed = Response::default();
        *method_not_allowed.status_mut() = StatusCode::METHOD_NOT_ALLOWED;
        return Ok(method_not_allowed);
    }
    // TODO(#672): Shift to a framework.
    let path = req.uri().path();
    if path == "/" {
        return Ok(Response::new(Body::from(html_wrap(
            "Data Structures",
            &runtime.html(),
        ))));
    } else if path == "/graph" {
        return Ok(Response::new(Body::from(dot_wrap(
            "Runtime Graph",
            &runtime.graph(),
        ))));
    } else if path == "/objcount" {
        return Ok(Response::new(Body::from(html_wrap(
            "Object Counts",
            &runtime.html_counts(),
        ))));
    // Endpoint to load the list introspection events, serialized via protobuf
    } else if path == "/introspection-events" {
        let events_message = Events {
            events: Vec::from(runtime.introspection_event_queue.lock().unwrap().clone()),
        };

        let mut buffer: Vec<u8> = Vec::new();
        events_message.encode(&mut buffer).unwrap();

        let mut response = Response::new(Body::from(buffer));

        response
            .headers_mut()
            // Allow browsers from any origin (such as the introspection client
            // dev server) to access this resource. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
            .insert(ACCESS_CONTROL_ALLOW_ORIGIN, "*".parse().unwrap());
        response.headers_mut().insert(
            CONTENT_TYPE,
            // There isn't a standardized content-type for protobuf messages
            // yet. We use the non-standard type used by Google's python API
            // client: https://github.com/googleapis/google-api-python-client/blob/master/googleapiclient/model.py
            "application/x-protobuf".parse().unwrap(),
        );

        return Ok(response);
    } else if let Some(response) = introspection_client::find_client_file(path) {
        return Ok(response);
    } else if let Some(node_id) = find_id(path, "node") {
        if let Some(body) = runtime.html_for_node(node_id) {
            return Ok(Response::new(Body::from(html_wrap(
                &format!("Node {}", node_id),
                &body,
            ))));
        }
    } else if let Some((node_id, handle)) = find_ids(path, "node") {
        if let Some(body) = runtime.html_for_handle(node_id, handle) {
            return Ok(Response::new(Body::from(html_wrap(
                &format!("Node {} Handle {}", node_id, handle),
                &body,
            ))));
        }
    }
    let mut not_found = Response::default();
    *not_found.status_mut() = StatusCode::NOT_FOUND;
    Ok(not_found)
}

async fn make_server(
    port: u16,
    runtime: Arc<Runtime>,
    termination_notification_receiver: tokio::sync::oneshot::Receiver<()>,
) {
    // Initialize SocketAddr to listen on.
    let addr = SocketAddr::from((std::net::Ipv6Addr::UNSPECIFIED, port));
    info!("starting introspection server on {:?}", addr);

    // Initialize MakeService to handle each connection.
    let make_service = make_service_fn(move |_| {
        // The `Arc<Runtime>` is moved into this closure, but it needs to be
        // cloned because this closure is called for every connection.
        let runtime = runtime.clone();

        async move {
            Ok::<_, Error>(service_fn(move |req| {
                let runtime = runtime.clone();
                async move { handle_request(req, runtime) }
            }))
        }
    });

    // Bind an address and serve incoming connections.
    let server = Server::bind(&addr).serve(make_service);
    let graceful = server.with_graceful_shutdown(async {
        // Treat notification failure the same as a notification.
        let _ = termination_notification_receiver.await;
    });

    // Run until asked to terminate.
    let result = graceful.await;
    info!("introspection server terminated with {:?}", result);
}

// Start running an introspection server on the given port, running until the
// `termination_notificiation_receiver` is triggered.
pub fn serve(
    port: u16,
    runtime: Arc<Runtime>,
    termination_notificiation_receiver: tokio::sync::oneshot::Receiver<()>,
) {
    let mut tokio_runtime = tokio::runtime::Runtime::new().expect("Couldn't create Tokio runtime");
    tokio_runtime.block_on(make_server(
        port,
        runtime,
        termination_notificiation_receiver,
    ));
}
<commit_msg>Introspection: (Re)add missing include_bytes! call (#1561)<commit_after>//
// Copyright 2020 The Project Oak Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

use crate::{proto::oak::introspection_events::Events, Runtime};
use hyper::{
    header::{ACCESS_CONTROL_ALLOW_ORIGIN, CONTENT_TYPE},
    service::{make_service_fn, service_fn},
    Body, Error, Method, Request, Response, Server, StatusCode,
};
use log::info;
use prost::Message;
use regex::Regex;
use std::{net::SocketAddr, sync::Arc};

#[cfg(not(feature = "oak_introspection_client"))]
mod introspection_client {
    use super::*;

    pub fn find_client_file(_path: &str) -> Option<Response> {
        None
    }
}

#[cfg(feature = "oak_introspection_client")]
mod introspection_client {
    use super::*;
    use hyper::header::CONTENT_ENCODING;

    fn static_file(
        file: &'static [u8],
        content_type: &str,
        content_encoding: Option<&str>,
    ) -> Response {
        let mut response = Response::new(Body::from(file));
        let headers = response.headers_mut();
        headers.insert(CONTENT_TYPE, content_type.parse().unwrap());
        // If the served file is compressed, add a header instructing the
        // browser how to decode it by indicating the compression algorithm in
        // the `Content-Encoding` header.
        // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding
        // For example, our webpack configuration will gzip text-based assets
        // when they are generated.
        // See: (../introspection_browser_client/webpack.config.js).
        if let Some(content_encoding_value) = content_encoding {
            headers.insert(CONTENT_ENCODING, content_encoding_value.parse().unwrap());
        }

        response
    }

    // Looks for a matching file used by the browser client and returns it
    pub fn find_client_file(path: &str) -> Option<Response> {
        let subpath = Regex::new(r"^/dynamic(?P<filepath>[^\s]*)$")
            .unwrap()
            .captures(path)?
            .name("filepath")
            .unwrap()
            .as_str();

        match subpath {
            "/index.js" => Some(static_file(
                include_bytes!("../introspection_browser_client/dist/index.js.gz"),
                "application/javascript",
                Some("gzip"),
            )),
            "/graphvizlib.wasm" => Some(static_file(
                include_bytes!("../introspection_browser_client/dist/graphvizlib.wasm.gz"),
                "application/wasm",
                Some("gzip"),
            )),
            "/favicon.png" => Some(static_file(
                include_bytes!("../introspection_browser_client/dist/favicon.png"),
                "image/png",
                None,
            )),
            // Serve index.html for all other paths under /dynamic, enabling
            // client-side routing
            _ => Some(static_file(
                include_bytes!("../introspection_browser_client/dist/index.html.gz"),
                "text/html",
                Some("gzip"),
            )),
        }
    }
}

/// Wrap a string holding a Graphviz Dot graph description in an HTML template
/// suitable for live display.
// TODO(#672): Shift to a templating library.
fn dot_wrap(title: &str, graph: &str) -> String {
    format!(
        r###"
<!DOCTYPE html>
<meta charset="utf-8">
<title>{}</title>

"###,
        title, graph,
    )
}

/// Wrap a body of text in an HTML template suitable for display.
fn html_wrap(title: &str, body: &str) -> String {
    format!(
        r###"
<!DOCTYPE html>
<meta charset="utf-8">
<title>{}</title>

{}
"###,
        title, body
    )
}

// Look for a path like "/{kind}/{id}" and return the id value.
fn find_id(path: &str, kind: &str) -> Option<u64> {
    Regex::new(&format!(r"^/{}/(?P<id>\d+)/?$", kind))
        .unwrap()
        .captures(path)?
        .name("id")
        .unwrap()
        .as_str()
        .parse::<u64>()
        .ok()
}

// Look for a path like "/{kind}/{id}/{subid}" and return the id values.
fn find_ids(path: &str, kind: &str) -> Option<(u64, u64)> {
    let captures = Regex::new(&format!(r"^/{}/(?P<id>\d+)/(?P<subid>\d+)/?$", kind))
        .unwrap()
        .captures(path)?;
    let id = captures.name("id").unwrap().as_str().parse::<u64>().ok()?;
    let subid = captures
        .name("subid")
        .unwrap()
        .as_str()
        .parse::<u64>()
        .ok()?;
    Some((id, subid))
}

// Handler for a single HTTP request to the introspection server.
fn handle_request(
    req: Request,
    runtime: Arc<Runtime>,
) -> Result<Response, hyper::Error> {
    if req.method() != Method::GET {
        let mut method_not_allowed = Response::default();
        *method_not_allowed.status_mut() = StatusCode::METHOD_NOT_ALLOWED;
        return Ok(method_not_allowed);
    }
    // TODO(#672): Shift to a framework.
    let path = req.uri().path();
    if path == "/" {
        return Ok(Response::new(Body::from(html_wrap(
            "Data Structures",
            &runtime.html(),
        ))));
    } else if path == "/graph" {
        return Ok(Response::new(Body::from(dot_wrap(
            "Runtime Graph",
            &runtime.graph(),
        ))));
    } else if path == "/objcount" {
        return Ok(Response::new(Body::from(html_wrap(
            "Object Counts",
            &runtime.html_counts(),
        ))));
    // Endpoint to load the list introspection events, serialized via protobuf
    } else if path == "/introspection-events" {
        let events_message = Events {
            events: Vec::from(runtime.introspection_event_queue.lock().unwrap().clone()),
        };

        let mut buffer: Vec<u8> = Vec::new();
        events_message.encode(&mut buffer).unwrap();

        let mut response = Response::new(Body::from(buffer));

        response
            .headers_mut()
            // Allow browsers from any origin (such as the introspection client
            // dev server) to access this resource. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
            .insert(ACCESS_CONTROL_ALLOW_ORIGIN, "*".parse().unwrap());
        response.headers_mut().insert(
            CONTENT_TYPE,
            // There isn't a standardized content-type for protobuf messages
            // yet. We use the non-standard type used by Google's python API
            // client: https://github.com/googleapis/google-api-python-client/blob/master/googleapiclient/model.py
            "application/x-protobuf".parse().unwrap(),
        );

        return Ok(response);
    } else if let Some(response) = introspection_client::find_client_file(path) {
        return Ok(response);
    } else if let Some(node_id) = find_id(path, "node") {
        if let Some(body) = runtime.html_for_node(node_id) {
            return Ok(Response::new(Body::from(html_wrap(
                &format!("Node {}", node_id),
                &body,
            ))));
        }
    } else if let Some((node_id, handle)) = find_ids(path, "node") {
        if let Some(body) = runtime.html_for_handle(node_id, handle) {
            return Ok(Response::new(Body::from(html_wrap(
                &format!("Node {} Handle {}", node_id, handle),
                &body,
            ))));
        }
    }
    let mut not_found = Response::default();
    *not_found.status_mut() = StatusCode::NOT_FOUND;
    Ok(not_found)
}

async fn make_server(
    port: u16,
    runtime: Arc<Runtime>,
    termination_notification_receiver: tokio::sync::oneshot::Receiver<()>,
) {
    // Initialize SocketAddr to listen on.
    let addr = SocketAddr::from((std::net::Ipv6Addr::UNSPECIFIED, port));
    info!("starting introspection server on {:?}", addr);

    // Initialize MakeService to handle each connection.
    let make_service = make_service_fn(move |_| {
        // The `Arc<Runtime>` is moved into this closure, but it needs to be
        // cloned because this closure is called for every connection.
        let runtime = runtime.clone();

        async move {
            Ok::<_, Error>(service_fn(move |req| {
                let runtime = runtime.clone();
                async move { handle_request(req, runtime) }
            }))
        }
    });

    // Bind an address and serve incoming connections.
    let server = Server::bind(&addr).serve(make_service);
    let graceful = server.with_graceful_shutdown(async {
        // Treat notification failure the same as a notification.
        let _ = termination_notification_receiver.await;
    });

    // Run until asked to terminate.
    let result = graceful.await;
    info!("introspection server terminated with {:?}", result);
}

// Start running an introspection server on the given port, running until the
// `termination_notificiation_receiver` is triggered.
pub fn serve(
    port: u16,
    runtime: Arc<Runtime>,
    termination_notificiation_receiver: tokio::sync::oneshot::Receiver<()>,
) {
    let mut tokio_runtime = tokio::runtime::Runtime::new().expect("Couldn't create Tokio runtime");
    tokio_runtime.block_on(make_server(
        port,
        runtime,
        termination_notificiation_receiver,
    ));
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate clap;
extern crate simple_error;

use clap::App;
use clap::Arg;
use clap::ArgMatches;

use simple_error::SimpleError;

use std::process::Command;
use std::process::Output;
use std::os::unix::process::CommandExt;
use std::error::Error;
use std::io::Write;
use std::process::exit;
use std::fmt::Display;
use std::fmt;
use std::string::String;
use std::path::Path;

fn main() {
    let args = App::new("Macro runner")
                   .version(crate_version!())
                   .author("Geoff Yoerger <geoffreyiy1@gmail.com> [https://github.com/DirkyJerky]")
                   .about("Runs defined macros from the current active window")
                   .arg(Arg::with_name("id")
                                .index(1)
                                .required(true)
                                .help("Macro identifier, suggest using integers"))
                   .get_matches();
    if let Err(e) = run(args) {
        // On success the current process will be replaced with the script, the only way for
        // execution to make it to this point is if an error happens before that
        writeln!(&mut std::io::stderr(), "{:?}", e);
        exit(1);
    }
    exit(0)
}

fn run(args: ArgMatches) ->  Result<(), Box<Error>> {
    // use `xdotool` to get PID of currently active window
    let Output {status, stdout, stderr, .. } =
                      Command::new("xdotool")
                              .arg("getactivewindow")
                              .arg("getwindowpid")
                              .env("DISPLAY", ":0")
                              .output()
                              ?;
    if !status.success() {
        println!("{}", String::from_utf8(stdout).unwrap());
        println!("{}", String::from_utf8(stderr).unwrap());
        return Err(Box::new(SimpleError::new(
                status.code().map(|n| format!("xdotool exited with error code {}", n)).unwrap_or("xdotool unsuccessfully completed without error code".to_string())
            )));
    }

    let pid_str = String::from_utf8(stdout)?;

    let exe_file = Path::new("/proc").join(pid_str.trim()).join("exe");

    let exe_path = std::fs::read_link(exe_file)?;

    let window_name = exe_path.file_name().ok_or(SimpleError::new("Executables arent supposed to be non-existant"))?;

    let mut working_dir = std::env::current_exe()?;
    working_dir.set_file_name("by-exe");
    working_dir.push(window_name);

    if !working_dir.is_dir() {
        println!("{}", working_dir.to_str().unwrap());
        return Err(Box::new(SimpleError::new(
                    format!("No macro directory for current active window: '{}'",
                            window_name.to_str().ok_or(SimpleError::new("Executable name isnt valid UTF-8"))?
                    ))));
    }

    working_dir.push(format!("{}.sh", args.value_of("id").expect("required arg should exist here")));

    if !working_dir.is_file() {
        return Err(Box::new(SimpleError::new(format!("No script: {:?}", working_dir.to_str()))));
    }

    return Err(Box::new(
        Command::new("/bin/bash")
                .arg(working_dir.as_os_str())
                .env("DISPLAY", ":0")
                .exec()
        ));
}
<commit_msg>Default scripts<commit_after>#[macro_use]
extern crate clap;
extern crate simple_error;

use clap::App;
use clap::Arg;
use clap::ArgMatches;

use simple_error::SimpleError;

use std::process::Command;
use std::process::Output;
use std::error::Error;
use std::io::Write;
use std::process::exit;
use std::string::String;
use std::path::Path;
use std::os::unix::process::CommandExt;

fn main() {
    let args = App::new("Macro runner")
                   .version(crate_version!())
                   .author("Geoff Yoerger <geoffreyiy1@gmail.com> [https://github.com/DirkyJerky]")
                   .about("Runs defined macros from the current active window")
                   .arg(Arg::with_name("id")
                                .index(1)
                                .required(true)
                                .help("Macro identifier"))
                   .arg(Arg::with_name("v")
                                .short("v")
                                .multiple(true)
                                .help("Set verbosity level"))
                   .get_matches();

    let (e1, e2) = run(args);

    // On success the current process will be replaced with the script, the only way for
    // execution to make it to this point is if an error happens before that
    writeln!(&mut std::io::stderr(), "{:?}", e1);
    writeln!(&mut std::io::stderr(), "{:?}", e2);
    exit(1);
}

fn get_current_window() -> Result<String, Box<dyn Error>> {
    // use `xdotool` to get PID of currently active window
    let Output {status, stdout, stderr, .. } =
                      Command::new("xdotool")
                              .arg("getactivewindow")
                              .arg("getwindowpid")
                              .env("DISPLAY", ":0")
                              .output()
                              ?;
    if !status.success() {
        println!("{}", String::from_utf8(stdout).unwrap());
        println!("{}", String::from_utf8(stderr).unwrap());
        return Err(Box::new(SimpleError::new(
                status.code().map(|n| format!("xdotool exited with error code {}", n)).unwrap_or("xdotool unsuccessfully completed without error code".to_string())
            )));
    }

    let pid_str = String::from_utf8(stdout)?;

    let exe_file = Path::new("/proc").join(pid_str.trim()).join("exe");

    let exe_path = std::fs::read_link(exe_file)?;

    return match exe_path.file_name() {
        Some(name_osstr) => match name_osstr.to_str() {
            Some(name_str) => Ok(name_str.to_string()),
            None => Err(Box::new(SimpleError::new("File not valid UTF-8"))),
        },
        None => Err(Box::new(SimpleError::new("Executable path does not exist"))),
    }
}

fn try_specific_exe(exe_name: &str, script_id: &str) -> Box<dyn Error> {
    let mut working_dir = match std::env::current_exe() {
        Ok(r) => r,
        Err(err) => return Box::new(err),
    };
    working_dir.set_file_name("by-exe");
    working_dir.push(exe_name.clone());

    if !working_dir.is_dir() {
        println!("{}", working_dir.to_str().unwrap());
        return Box::new(SimpleError::new(format!("No macro directory for current active window: '{}'", exe_name)));
    }

    working_dir.push(format!("{}.sh", script_id));

    if !working_dir.is_file() {
        return Box::new(SimpleError::new(format!("No script: {:?}", working_dir.to_str())));
    }

    return Box::new(
            Command::new("/bin/bash")
            .arg(working_dir.as_os_str())
            .env("DISPLAY", ":0")
            .exec()
                   );
}

fn try_default(script_id: &str) -> Box<dyn Error> {
    let mut working_dir = match std::env::current_exe() {
        Ok(r) => r,
        Err(err) => return Box::new(err),
    };
    working_dir.set_file_name("default");
    working_dir.push(format!("{}.sh", script_id));

    if !working_dir.is_file() {
        return Box::new(SimpleError::new(format!("No default script: {:?}", working_dir.to_str())));
    }

    return Box::new(
            Command::new("/bin/bash")
            .arg(working_dir.as_os_str())
            .env("DISPLAY", ":0")
            .exec()
                   );
}

fn run(args: ArgMatches) ->  (Box<dyn Error>, Box<dyn Error>) {
    let exe_name_res = get_current_window();
    let script_id = args.value_of("id").expect("required arg should exist here");

    let specific_exe_attempt = match exe_name_res {
        Ok(exe_name) => try_specific_exe(exe_name.as_str(), script_id),
        Err(err) => err,
    };

    let default_attempt = try_default(script_id);

    // Both attempts failed here
    return (specific_exe_attempt, default_attempt);
}

<|endoftext|>


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

use nom::{IResult, ErrorKind};
use nom::{is_hex_digit, is_space, is_digit};

use num_bigint::BigUint;
use core::str::FromStr;
use std::str;

use script::{Program, ParseError};

fn prefix_word(word: &[u8]) -> Vec<u8> {
    let mut vec = Vec::new();
    vec.push(word.len() as u8 | 128u8);
    vec.extend_from_slice(word);
    vec
}

#[inline]
fn hex_digit(v: u8) -> u8 {
    match v {
        0x61u8...0x66u8 => v - 32 - 0x41 + 10,
        0x41u8...0x46u8 => v - 0x41 + 10,
        _ => v - 48,
    }
}

macro_rules! write_size {
    ($vec : expr, $size : expr) => {
      match $size {
        0...120 => $vec.push($size as u8),
        121...255 => {
            $vec.push(121u8);
            $vec.push($size as u8);
        }
        256...65535 => {
            $vec.push(122u8);
            $vec.push(($size >> 8) as u8);
            $vec.push($size as u8);
        }
        65536...4294967296 => {
            $vec.push(123u8);
            $vec.push(($size >> 24) as u8);
            $vec.push(($size >> 16) as u8);
            $vec.push(($size >> 8) as u8);
            $vec.push($size as u8);
        }
        _ => unreachable!()
      }
    };
}

fn bin(bin: &[u8]) -> Vec<u8> {
    let mut bin_ = Vec::new();
    for i in 0..bin.len() - 1 {
        if i % 2 != 0 {
            continue;
        }
        bin_.push((hex_digit(bin[i]) << 4) | hex_digit(bin[i + 1]));
    }
    let mut vec = Vec::new();
    let size = bin_.len();
    write_size!(vec, size);
    vec.extend_from_slice(bin_.as_slice());
    vec
}

fn string_to_vec(s: &[u8]) -> Vec<u8> {
    let mut bin = Vec::new();
    let size = s.len();
    write_size!(bin, size);
    bin.extend_from_slice(s);
    bin
}

fn sized_vec(s: Vec<u8>) -> Vec<u8> {
    let mut vec = Vec::new();
    let size = s.len();
    write_size!(vec, size);
    vec.extend_from_slice(s.as_slice());
    vec

}

fn is_word_char(s: u8) -> bool {
    (s >= b'a' && s <= b'z') || (s >= b'A' && s <= b'Z') || (s >= b'0' && s <= b'9') ||
    s == b'_' || s == b':' || s == b'-' || s == b'=' ||
    s == b'!' || s == b'#' || s == b'$' || s == b'%' || s == b'@' || s == b'?' ||
    s == b'/'
}

fn is_crlf(s: u8) -> bool {
    s == 10 || s == 13
}

fn flatten_program(p: Vec<Vec<u8>>) -> Vec<u8> {
    let mut vec = Vec::new();
    for mut item in p {
        vec.append(&mut item);
    }
    vec
}

fn space_or_end(i: &[u8]) -> IResult<&[u8], ()> {
    if i.len() == 0 || (i.len() >= 1 && i[0] == b' ') {
        return IResult::Done(&i[0..], ())
    } else {
        IResult::Error(ErrorKind::Custom(0))
    }
}

named!(uint<Vec<u8>>, do_parse!(
                     biguint: take_while1!(is_digit)      >>
                              space_or_end                >>
                              (sized_vec(BigUint::from_str(str::from_utf8(biguint).unwrap())
                                         .unwrap().to_bytes_be()))));
named!(word<Vec<u8>>, do_parse!(
                        word: take_while1!(is_word_char)  >>
                              (prefix_word(word))));
named!(binary<Vec<u8>>, do_parse!(
                              tag!(b"0x")                 >>
                         hex: take_while1!(is_hex_digit)  >>
                              (bin(hex))
));
named!(string<Vec<u8>>, do_parse!(
                         str: delimited!(char!('"'), is_not!("\""), char!('"')) >>
                              (string_to_vec(str))));
named!(code<Vec<u8>>, do_parse!(
                         prog: delimited!(char!('['), ws!(program), char!(']')) >>
                               (sized_vec(prog))));
named!(item<Vec<u8>>, alt!(binary | string | uint | code | word));
named!(program<Vec<u8>>, do_parse!(
                               take_while!(is_space)                        >>
                         item: separated_list!(take_while!(is_space), item) >>
                               (flatten_program(item))));
named!(pub programs<Vec<Vec<u8>>>, do_parse!(
                         item: separated_list!(take_while!(is_crlf), program)   >>
                               (item)));

/// Parses human-readable PumpkinScript
///
/// The format is simple, it is a sequence of space-separated tokens,
/// with binaries represented as:
///
/// * `0x<hexadecimal>` (hexadecimal form)
/// * `"STRING"` (string form, no quoted characters support yet)
/// * `integer` (integer form, will convert to a big endian big integer)
///
/// The rest of the instructions considered to be words.
///
/// One additional piece of syntax is code included within square
/// brackets: `[DUP]`. This means that the parser will take the code inside,
/// compile it to the binary form and add as a data push. This is useful for
/// words like EVAL
///
/// # Example
///
/// ```
/// parse("0xABCD DUP DROP DROP")
/// ```
///
/// It's especially useful for testing but there is a chance that there will be
/// a "suboptimal" protocol that allows to converse with PumpkinDB over telnet
pub fn parse(script: &str) -> Result<Program, ParseError> {
    match program(script.as_bytes()) {
        IResult::Done(_, x) => Ok(x),
        IResult::Incomplete(_) => Err(ParseError::Incomplete),
        IResult::Error(ErrorKind::Custom(code)) => Err(ParseError::Err(code)),
        _ => Err(ParseError::UnknownErr),
    }
}

#[cfg(test)]
mod tests {
    use script::textparser::{parse, programs};
    use num_bigint::BigUint;
    use core::str::FromStr;

    #[test]
    fn test_one() {
        let script = parse("0xAABB").unwrap();
        assert_eq!(script, vec![2, 0xaa,0xbb]);
        let script = parse("HELLO").unwrap();
        assert_eq!(script, vec![0x85, b'H', b'E', b'L', b'L', b'O']);
    }

    #[test]
    fn test_uint() {
        let script = parse("1234567890").unwrap();
        let mut bytes = BigUint::from_str("1234567890").unwrap().to_bytes_be();
        let mut sized = Vec::new();
        sized.push(4);
        sized.append(&mut bytes);
        assert_eq!(script, sized);
    }

    #[test]
    fn test_many_uint() {
        let script = parse("1 2 3").unwrap();

        let mut vec = Vec::new();

        let mut bytes = BigUint::from_str("1").unwrap().to_bytes_be();
        vec.push(1);
        vec.append(&mut bytes);

        let mut bytes = BigUint::from_str("2").unwrap().to_bytes_be();
        vec.push(1);
        vec.append(&mut bytes);

        let mut bytes = BigUint::from_str("3").unwrap().to_bytes_be();
        vec.push(1);
        vec.append(&mut bytes);

        assert_eq!(script, vec);
    }

    #[test]
    fn test_number_prefixed_word() {
        let script = parse("2DUP").unwrap();
        assert_eq!(script, b"\x842DUP");
    }

    #[test]
    fn test_extra_spaces() {
        let script = parse(" 0xAABB  \"Hi\" ").unwrap();
        assert_eq!(script, vec![2, 0xaa,0xbb, 2, b'H', b'i']);
    }

    #[test]
    fn test() {
        let script = parse("0xAABB DUP 0xFF00CC \"Hello\"").unwrap();

        assert_eq!(script, vec![0x02, 0xAA, 0xBB, 0x83, b'D', b'U', b'P',
                                0x03, 0xFF, 0x00, 0xCC, 0x05, b'H', b'e', b'l', b'l', b'o']);
    }

    #[test]
    fn test_code() {
        let script = parse("[DUP]").unwrap();
        let script_spaced = parse("[ DUP ]").unwrap();
        assert_eq!(script, vec![4, 0x83, b'D', b'U', b'P']);
        assert_eq!(script_spaced, vec![4, 0x83, b'D', b'U', b'P']);
    }

    #[test]
    fn test_programs() {
        let str = "SOMETHING : BURP DURP\nBURP : DURP";
        let (_, mut progs) = programs(str.as_bytes()).unwrap();
        assert_eq!(Vec::from(progs.pop().unwrap()), parse("BURP : DURP").unwrap());
        assert_eq!(Vec::from(progs.pop().unwrap()), parse("SOMETHING : BURP DURP").unwrap());

        let str = "SOMETHING : BURP DURP\nBURP : DURP";
        let (_, mut progs) = programs(str.as_bytes()).unwrap();
        assert_eq!(Vec::from(progs.pop().unwrap()), parse("BURP : DURP").unwrap());
        assert_eq!(Vec::from(progs.pop().unwrap()), parse("SOMETHING : BURP DURP").unwrap());
    }

}
<commit_msg>Problem: can't parse empty script (text form)<commit_after>// Copyright (c) 2017, All Contributors (see CONTRIBUTORS file)
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

use nom::{IResult, ErrorKind};
use nom::{is_hex_digit, is_space, is_digit};

use num_bigint::BigUint;
use core::str::FromStr;
use std::str;

use script::{Program, ParseError};

fn prefix_word(word: &[u8]) -> Vec<u8> {
    let mut vec = Vec::new();
    vec.push(word.len() as u8 | 128u8);
    vec.extend_from_slice(word);
    vec
}

#[inline]
fn hex_digit(v: u8) -> u8 {
    match v {
        0x61u8...0x66u8 => v - 32 - 0x41 + 10,
        0x41u8...0x46u8 => v - 0x41 + 10,
        _ => v - 48,
    }
}

macro_rules! write_size {
    ($vec : expr, $size : expr) => {
      match $size {
        0...120 => $vec.push($size as u8),
        121...255 => {
            $vec.push(121u8);
            $vec.push($size as u8);
        }
        256...65535 => {
            $vec.push(122u8);
            $vec.push(($size >> 8) as u8);
            $vec.push($size as u8);
        }
        65536...4294967296 => {
            $vec.push(123u8);
            $vec.push(($size >> 24) as u8);
            $vec.push(($size >> 16) as u8);
            $vec.push(($size >> 8) as u8);
            $vec.push($size as u8);
        }
        _ => unreachable!()
      }
    };
}

fn bin(bin: &[u8]) -> Vec<u8> {
    let mut bin_ = Vec::new();
    for i in 0..bin.len() - 1 {
        if i % 2 != 0 {
            continue;
        }
        bin_.push((hex_digit(bin[i]) << 4) | hex_digit(bin[i + 1]));
    }
    let mut vec = Vec::new();
    let size = bin_.len();
    write_size!(vec, size);
    vec.extend_from_slice(bin_.as_slice());
    vec
}

fn string_to_vec(s: &[u8]) -> Vec<u8> {
    let mut bin = Vec::new();
    let size = s.len();
    write_size!(bin, size);
    bin.extend_from_slice(s);
    bin
}

fn sized_vec(s: Vec<u8>) -> Vec<u8> {
    let mut vec = Vec::new();
    let size = s.len();
    write_size!(vec, size);
    vec.extend_from_slice(s.as_slice());
    vec

}

fn is_word_char(s: u8) -> bool {
    (s >= b'a' && s <= b'z') || (s >= b'A' && s <= b'Z') || (s >= b'0' && s <= b'9') ||
    s == b'_' || s == b':' || s == b'-' || s == b'=' ||
    s == b'!' || s == b'#' || s == b'$' || s == b'%' || s == b'@' || s == b'?' ||
    s == b'/'
}

fn is_crlf(s: u8) -> bool {
    s == 10 || s == 13
}

fn flatten_program(p: Vec<Vec<u8>>) -> Vec<u8> {
    let mut vec = Vec::new();
    for mut item in p {
        vec.append(&mut item);
    }
    vec
}

fn space_or_end(i: &[u8]) -> IResult<&[u8], ()> {
    if i.len() == 0 || (i.len() >= 1 && i[0] == b' ') {
        return IResult::Done(&i[0..], ())
    } else {
        IResult::Error(ErrorKind::Custom(0))
    }
}

fn eof(i: &[u8]) -> IResult<&[u8], Vec<u8>> {
    if i.len() == 0 {
        return IResult::Done(&i[0..], Vec::new())
    } else {
        IResult::Error(ErrorKind::Custom(1))
    }
}

named!(uint<Vec<u8>>, do_parse!(
                     biguint: take_while1!(is_digit)      >>
                              space_or_end                >>
                              (sized_vec(BigUint::from_str(str::from_utf8(biguint).unwrap())
                                         .unwrap().to_bytes_be()))));
named!(word<Vec<u8>>, do_parse!(
                        word: take_while1!(is_word_char)  >>
                              (prefix_word(word))));
named!(binary<Vec<u8>>, do_parse!(
                              tag!(b"0x")                 >>
                         hex: take_while1!(is_hex_digit)  >>
                              (bin(hex))
));
named!(string<Vec<u8>>, do_parse!(
                         str: delimited!(char!('"'), is_not!("\""), char!('"')) >>
                              (string_to_vec(str))));
named!(code<Vec<u8>>, do_parse!(
                         prog: delimited!(char!('['), ws!(program), char!(']')) >>
                               (sized_vec(prog))));
named!(item<Vec<u8>>, alt!(binary | string | uint | code | word));
named!(program<Vec<u8>>, alt!(do_parse!(
                               take_while!(is_space)                        >>
                            v: eof                                          >>
                               (v))
                              | do_parse!(
                               take_while!(is_space)                        >>
                         item: separated_list!(take_while!(is_space), item) >>
                               (flatten_program(item)))));
named!(pub programs<Vec<Vec<u8>>>, do_parse!(
                         item: separated_list!(take_while!(is_crlf), program)   >>
                               (item)));

/// Parses human-readable PumpkinScript
///
/// The format is simple, it is a sequence of space-separated tokens,
/// with binaries represented as:
///
/// * `0x<hexadecimal>` (hexadecimal form)
/// * `"STRING"` (string form, no quoted characters support yet)
/// * `integer` (integer form, will convert to a big endian big integer)
///
/// The rest of the instructions considered to be words.
///
/// One additional piece of syntax is code included within square
/// brackets: `[DUP]`. This means that the parser will take the code inside,
/// compile it to the binary form and add as a data push. This is useful for
/// words like EVAL
///
/// # Example
///
/// ```
/// parse("0xABCD DUP DROP DROP")
/// ```
///
/// It's especially useful for testing but there is a chance that there will be
/// a "suboptimal" protocol that allows to converse with PumpkinDB over telnet
pub fn parse(script: &str) -> Result<Program, ParseError> {
    match program(script.as_bytes()) {
        IResult::Done(_, x) => Ok(x),
        IResult::Incomplete(_) => Err(ParseError::Incomplete),
        IResult::Error(ErrorKind::Custom(code)) => Err(ParseError::Err(code)),
        _ => Err(ParseError::UnknownErr),
    }
}

#[cfg(test)]
mod tests {
    use script::textparser::{parse, programs};
    use num_bigint::BigUint;
    use core::str::FromStr;

    #[test]
    fn test_empty() {
        let script = parse("").unwrap();
        assert_eq!(script, vec![]);
        let script = parse("  ").unwrap();
        assert_eq!(script, vec![]);
    }

    #[test]
    fn test_one() {
        let script = parse("0xAABB").unwrap();
        assert_eq!(script, vec![2, 0xaa,0xbb]);
        let script = parse("HELLO").unwrap();
        assert_eq!(script, vec![0x85, b'H', b'E', b'L', b'L', b'O']);
    }

    #[test]
    fn test_uint() {
        let script = parse("1234567890").unwrap();
        let mut bytes = BigUint::from_str("1234567890").unwrap().to_bytes_be();
        let mut sized = Vec::new();
        sized.push(4);
        sized.append(&mut bytes);
        assert_eq!(script, sized);
    }

    #[test]
    fn test_many_uint() {
        let script = parse("1 2 3").unwrap();

        let mut vec = Vec::new();

        let mut bytes = BigUint::from_str("1").unwrap().to_bytes_be();
        vec.push(1);
        vec.append(&mut bytes);

        let mut bytes = BigUint::from_str("2").unwrap().to_bytes_be();
        vec.push(1);
        vec.append(&mut bytes);

        let mut bytes = BigUint::from_str("3").unwrap().to_bytes_be();
        vec.push(1);
        vec.append(&mut bytes);

        assert_eq!(script, vec);
    }

    #[test]
    fn test_number_prefixed_word() {
        let script = parse("2DUP").unwrap();
        assert_eq!(script, b"\x842DUP");
    }

    #[test]
    fn test_extra_spaces() {
        let script = parse(" 0xAABB  \"Hi\" ").unwrap();
        assert_eq!(script, vec![2, 0xaa,0xbb, 2, b'H', b'i']);
    }

    #[test]
    fn test() {
        let script = parse("0xAABB DUP 0xFF00CC \"Hello\"").unwrap();

        assert_eq!(script, vec![0x02, 0xAA, 0xBB, 0x83, b'D', b'U', b'P',
                                0x03, 0xFF, 0x00, 0xCC, 0x05, b'H', b'e', b'l', b'l', b'o']);
    }

    #[test]
    fn test_code() {
        let script = parse("[DUP]").unwrap();
        let script_spaced = parse("[ DUP ]").unwrap();
        assert_eq!(script, vec![4, 0x83, b'D', b'U', b'P']);
        assert_eq!(script_spaced, vec![4, 0x83, b'D', b'U', b'P']);
    }

    #[test]
    fn test_programs() {
        let str = "SOMETHING : BURP DURP\nBURP : DURP";
        let (_, mut progs) = programs(str.as_bytes()).unwrap();
        assert_eq!(Vec::from(progs.pop().unwrap()), parse("BURP : DURP").unwrap());
        assert_eq!(Vec::from(progs.pop().unwrap()), parse("SOMETHING : BURP DURP").unwrap());

        let str = "SOMETHING : BURP DURP\nBURP : DURP";
        let (_, mut progs) = programs(str.as_bytes()).unwrap();
        assert_eq!(Vec::from(progs.pop().unwrap()), parse("BURP : DURP").unwrap());
        assert_eq!(Vec::from(progs.pop().unwrap()), parse("SOMETHING : BURP DURP").unwrap());
    }

}
<|endoftext|>


<commit_before>fn generate_table () {
    let pi: f64 = 3.1415926536897932384626;
    let k1: f64 = 0.6072529350088812561694; // 1/k
    let num_bits: uint = 32;
    //let num_elms: uint = num_bits;
    let mul: uint = 1<<(num_bits-2);

    println!("Cordic sin in rust");
    println!("num bits {}", num_bits);
    println!("mul {}", mul);
    println!("1<<(num_bits-2) {}", ((1i << (num_bits-2)) as f64));
    println!("pi is {}", pi);
    println!("k1 is {}", k1);

    println!("let cordic_tab = [");

    let shift: f64 = 2.0;
    for ii in range(0, num_bits) {
        let ipow: f64 = 1f64/shift.powi(ii as i32);
        let cur: f64 = ipow.atan() * (mul as f64);

        if ii % 4 == 0 && ii > 0 {
            println!("");
        }
        if ii == 0 {
            print!("{:0.8f}f64, ", cur);
        } else {
            print!("{:0.8f}, ", cur);
        }
    }

    println!("];");
}

fn sin(theta: f64) {

    let num_bits: uint = 32;

    let k1: f64 = 0.6072529350088812561694; // 1/k
    let mul: uint = 1<<(num_bits-2);
    let mut x: f64 = (mul as f64) * k1;
    let mut y: f64 = 0.0;
    let mut z: f64 = theta;

    let mut d: int;

    let z: int = theta as int;
    let cordic_tab = [
        843314856.53262615f64, 497837829.38176435, 263043836.58692065, 133525158.66814968,
        67021686.89696868, 33543515.72887244, 16775850.86663180, 8388437.33958306,
        4194282.66686198, 2097149.33333944, 1048575.66666686, 524287.95833334,
        262143.99479167, 131071.99934896, 65535.99991862, 32767.99998983,
        16383.99999873, 8191.99999984, 4095.99999998, 2048.00000000,
        1024.00000000, 512.00000000, 256.00000000, 128.00000000,
        64.00000000, 32.00000000, 16.00000000, 8.00000000,
        4.00000000, 2.00000000, 1.00000000, 0.50000000];

    for ii in range(0, num_bits) {
        if z >= 0 {
            d = 0;
        } else {
            d = -1;
        }

        /*
        tx = x - (((y>>k) ^ d) - d);
        ty = y + (((x>>k) ^ d) - d);
        tz = z - ((cordic_ctab[k] ^ d) - d);
        x = tx; y = ty; z = tz;
        */

        println!("{}", d);
    }

}
fn main() {
    generate_table();

    sin(0.5);
}
<commit_msg>transmute f64 <--> int<commit_after>fn generate_table () {
    let pi: f64 = 3.1415926536897932384626;
    let k1: f64 = 0.6072529350088812561694; // 1/k
    let num_bits: uint = 32;
    //let num_elms: uint = num_bits;
    let mul: uint = 1<<(num_bits-2);

    println!("Cordic sin in rust");
    println!("num bits {}", num_bits);
    println!("mul {}", mul);
    println!("1<<(num_bits-2) {}", ((1i << (num_bits-2)) as f64));
    println!("pi is {}", pi);
    println!("k1 is {}", k1);

    println!("let cordic_tab = [");

    let shift: f64 = 2.0;
    for ii in range(0, num_bits) {
        let ipow: f64 = 1f64/shift.powi(ii as i32);
        let cur: f64 = ipow.atan() * (mul as f64);

        if ii % 4 == 0 && ii > 0 {
            println!("");
        }
        if ii == 0 {
            print!("{:0.8f}f64, ", cur);
        } else {
            print!("{:0.8f}, ", cur);
        }
    }

    println!("];");
}

fn int_to_f64 (lhs: int) -> f64 {
    let rhs: f64 = unsafe {
        std::mem::transmute(lhs)
    };
    return rhs;
}

fn f64_to_int (lhs: f64) -> int {
    let rhs: int = unsafe {
        std::mem::transmute(lhs)
    };
    return rhs;
}

fn sin(theta: f64) {

    let num_bits: uint = 32;

    let k1: f64 = 0.6072529350088812561694; // 1/k
    let mul: uint = 1<<(num_bits-2);
    let mut x: int = ((mul as f64) * k1) as int;
    let mut y: int = 0;
    let mut z: int = theta as int;
    let mut tx: int;
    let mut ty: int ;
    let mut tz: int;

    let mut d: int;

    println!("x {}", x);

    let cordic_tab = [
        843314856.53262615f64, 497837829.38176435, 263043836.58692065, 133525158.66814968,
        67021686.89696868, 33543515.72887244, 16775850.86663180, 8388437.33958306,
        4194282.66686198, 2097149.33333944, 1048575.66666686, 524287.95833334,
        262143.99479167, 131071.99934896, 65535.99991862, 32767.99998983,
        16383.99999873, 8191.99999984, 4095.99999998, 2048.00000000,
        1024.00000000, 512.00000000, 256.00000000, 128.00000000,
        64.00000000, 32.00000000, 16.00000000, 8.00000000,
        4.00000000, 2.00000000, 1.00000000, 0.50000000];

    for k in range(0, num_bits) {
        if z >= 0 {
            d = 0;
        } else {
            d = -1;
        }
        tx = x - (((y>>k) ^ d) - d);
        ty = y + (((x>>k) ^ d) - d);
        tz = z - (( (cordic_tab[k] as int) ^ d) - d);
        x = tx;
        y = ty;
        z = tz;
    }
}

fn main() {
    let i: int = 42882;
    let f: f64 = 3498043.4824;
    assert!(int_to_f64(f64_to_int(f)) == f);
    assert!(f64_to_int(int_to_f64(i)) == i);

    generate_table();
    sin(0.5);
}
<|endoftext|>


<commit_before>use std::env;
use std::fs;
use std::path::Path;

fn main() {
    println!("cargo:rerun-if-changed=build/glslangValidator");
    println!("cargo:rerun-if-changed=build/glslangValidator.exe");

    let target = env::var("TARGET").unwrap();
    let out_file = Path::new(&env::var("OUT_DIR").unwrap()).join("glslang_validator");

    let path = if target.contains("windows") {
        Path::new("build/glslangValidator.exe")
    } else if target.contains("linux") {
        Path::new("build/glslangValidator")
    } else {
        panic!("The platform `{}` is not supported", target);
    };

    if let Err(_) = fs::hard_link(&path, &out_file) {
        fs::copy(&path, &out_file).unwrap();
    }

    //fs::set_permissions(&out_file, std::io::USER_EXEC).unwrap();
}
<commit_msg>Set the proper permissions of the executable for glsl-to-spirv<commit_after>use std::env;
use std::fs;
use std::fs::Permissions;
use std::path::Path;

fn main() {
    println!("cargo:rerun-if-changed=build/glslangValidator");
    println!("cargo:rerun-if-changed=build/glslangValidator.exe");

    let target = env::var("TARGET").unwrap();
    let out_file = Path::new(&env::var("OUT_DIR").unwrap()).join("glslang_validator");

    let path = if target.contains("windows") {
        Path::new("build/glslangValidator.exe")
    } else if target.contains("linux") {
        Path::new("build/glslangValidator")
    } else {
        panic!("The platform `{}` is not supported", target);
    };

    if let Err(_) = fs::hard_link(&path, &out_file) {
        fs::copy(&path, &out_file).unwrap();
    }

    // setting permissions of the executable
    {
        #[cfg(linux)] fn permissions() -> Option<Permissions> {
            use std::os::unix::fs::PermissionsExt;
            Some(Permissions::from_mode(755))
        }
        #[cfg(not(linux))] fn permissions() -> Option<Permissions> { None }
        if let Some(permissions) = permissions() {
            fs::set_permissions(&out_file, permissions).unwrap();
        }
    }
}
<|endoftext|>