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

use crate::{
    account_address::AccountAddress,
    transaction::Version,
    validator_set::ValidatorSet,
    validator_verifier::{ValidatorVerifier, VerifyError},
};
use canonical_serialization::{CanonicalSerialize, CanonicalSerializer, SimpleSerializer};
use crypto::{
    hash::{CryptoHash, CryptoHasher, LedgerInfoHasher},
    HashValue, *,
};
use failure::prelude::*;
#[cfg(any(test, feature = "testing"))]
use proptest_derive::Arbitrary;
use proto_conv::{FromProto, IntoProto};
use serde::{Deserialize, Serialize};
use std::{
    collections::HashMap,
    fmt::{Display, Formatter},
};

/// This structure serves a dual purpose.
///
/// First, if this structure is signed by 2f+1 validators it signifies the state of the ledger at
/// version `version` -- it contains the transaction accumulator at that version which commits to
/// all historical transactions. This structure may be expanded to include other information that
/// is derived from that accumulator (e.g. the current time according to the time contract) to
/// reduce the number of proofs a client must get.
///
/// Second, the structure contains a `consensus_data_hash` value. This is the hash of an internal
/// data structure that represents a block that is voted on in HotStuff. If 2f+1 signatures are
/// gathered on the same ledger info that represents a Quorum Certificate (QC) on the consensus
/// data.
///
/// Combining these two concepts, when a validator votes on a block, B it votes for a
/// LedgerInfo with the `version` being the latest version that will be committed if B gets 2f+1
/// votes. It sets `consensus_data_hash` to represent B so that if those 2f+1 votes are gathered a
/// QC is formed on B.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "testing"), derive(Arbitrary))]
pub struct LedgerInfo {
    /// The version of the latest transaction in the ledger.
    version: Version,

    /// The root hash of transaction accumulator that includes the latest transaction.
    transaction_accumulator_hash: HashValue,

    /// Hash of consensus specific data that is opaque to all parts of the system other than
    /// consensus.
    consensus_data_hash: HashValue,

    /// Block id of the last committed block corresponding to this LedgerInfo
    /// as reported by consensus.
    consensus_block_id: HashValue,

    /// Epoch number corresponds to the set of validators that are active for this ledger info.
    epoch_num: u64,

    /// Timestamp that represents the microseconds since the epoch (unix time) that is
    /// generated by the proposer of the block.  This is strictly increasing with every block.
    /// If a client reads a timestamp > the one they specified for transaction expiration time,
    /// they can be certain that their transaction will never be included in a block in the future
    /// (assuming that their transaction has not yet been included)
    timestamp_usecs: u64,

    /// An optional field keeping the set of new validators to start the next epoch.
    /// The very last ledger info of an epoch contains the validator set for the next one,
    /// other ledger info instances are None.
    next_validator_set: Option<ValidatorSet>,
}

impl Display for LedgerInfo {
    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
        write!(
            f,
            "LedgerInfo: [committed_block_id: {}, version: {}, epoch_num: {}, timestamp (us): {}, next_validator_set: {}]",
            self.consensus_block_id(),
            self.version(),
            self.epoch_num(),
            self.timestamp_usecs(),
            self.next_validator_set.as_ref().map_or("None".to_string(), |validator_set| format!("{}", validator_set)),
        )
    }
}

impl LedgerInfo {
    /// Constructs a `LedgerInfo` object at a specific version using a given
    /// transaction accumulator root and consensus data hash.
    pub fn new(
        version: Version,
        transaction_accumulator_hash: HashValue,
        consensus_data_hash: HashValue,
        consensus_block_id: HashValue,
        epoch_num: u64,
        timestamp_usecs: u64,
        next_validator_set: Option<ValidatorSet>,
    ) -> Self {
        LedgerInfo {
            version,
            transaction_accumulator_hash,
            consensus_data_hash,
            consensus_block_id,
            epoch_num,
            timestamp_usecs,
            next_validator_set,
        }
    }

    /// Returns the version of this `LedgerInfo`.
    pub fn version(&self) -> Version {
        self.version
    }

    /// Returns the transaction accumulator root of this `LedgerInfo`.
    pub fn transaction_accumulator_hash(&self) -> HashValue {
        self.transaction_accumulator_hash
    }

    /// Returns hash of consensus data in this `LedgerInfo`.
    pub fn consensus_data_hash(&self) -> HashValue {
        self.consensus_data_hash
    }

    pub fn consensus_block_id(&self) -> HashValue {
        self.consensus_block_id
    }

    pub fn set_consensus_data_hash(&mut self, consensus_data_hash: HashValue) {
        self.consensus_data_hash = consensus_data_hash;
    }

    pub fn epoch_num(&self) -> u64 {
        self.epoch_num
    }

    pub fn timestamp_usecs(&self) -> u64 {
        self.timestamp_usecs
    }

    /// A ledger info is nominal if it's not certifying any real version.
    pub fn is_zero(&self) -> bool {
        self.version == 0
    }

    pub fn next_validator_set(&self) -> Option<&ValidatorSet> {
        self.next_validator_set.as_ref()
    }
}

impl IntoProto for LedgerInfo {
    type ProtoType = crate::proto::ledger_info::LedgerInfo;

    fn into_proto(self) -> Self::ProtoType {
        let mut proto = Self::ProtoType::new();
        proto.set_version(self.version);
        proto.set_transaction_accumulator_hash(self.transaction_accumulator_hash.into_proto());
        proto.set_consensus_data_hash(self.consensus_data_hash.into_proto());
        proto.set_consensus_block_id(self.consensus_block_id.into_proto());
        proto.set_epoch_num(self.epoch_num);
        proto.set_timestamp_usecs(self.timestamp_usecs);
        if let Some(next_validator_set) = self.next_validator_set {
            proto.set_next_validator_set(next_validator_set.into_proto());
        }
        proto
    }
}

impl FromProto for LedgerInfo {
    type ProtoType = crate::proto::ledger_info::LedgerInfo;

    fn from_proto(proto: Self::ProtoType) -> Result<Self> {
        let version = proto.get_version();
        let transaction_accumulator_hash =
            HashValue::from_slice(proto.get_transaction_accumulator_hash())?;
        let consensus_data_hash = HashValue::from_slice(proto.get_consensus_data_hash())?;
        let consensus_block_id = HashValue::from_slice(proto.get_consensus_block_id())?;
        let epoch_num = proto.get_epoch_num();
        let timestamp_usecs = proto.get_timestamp_usecs();

        let next_validator_set =
            if let Some(validator_set_proto) = proto.next_validator_set.into_option() {
                Some(ValidatorSet::from_proto(validator_set_proto)?)
            } else {
                None
            };
        Ok(LedgerInfo::new(
            version,
            transaction_accumulator_hash,
            consensus_data_hash,
            consensus_block_id,
            epoch_num,
            timestamp_usecs,
            next_validator_set,
        ))
    }
}

impl CanonicalSerialize for LedgerInfo {
    fn serialize(&self, serializer: &mut impl CanonicalSerializer) -> Result<()> {
        serializer
            .encode_u64(self.version)?
            .encode_bytes(self.transaction_accumulator_hash.as_ref())?
            .encode_bytes(self.consensus_data_hash.as_ref())?
            .encode_bytes(self.consensus_block_id.as_ref())?
            .encode_u64(self.epoch_num)?
            .encode_u64(self.timestamp_usecs)?
            .encode_optional(&self.next_validator_set)?;
        Ok(())
    }
}

impl CryptoHash for LedgerInfo {
    type Hasher = LedgerInfoHasher;

    fn hash(&self) -> HashValue {
        let mut state = Self::Hasher::default();
        state.write(
            &SimpleSerializer::<Vec<u8>>::serialize(self).expect("Serialization should work."),
        );
        state.finish()
    }
}

/// The validator node returns this structure which includes signatures
/// from validators that confirm the state.  The client needs to only pass back
/// the LedgerInfo element since the validator node doesn't need to know the signatures
/// again when the client performs a query, those are only there for the client
/// to be able to verify the state
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct LedgerInfoWithSignatures<Sig> {
    ledger_info: LedgerInfo,
    /// The validator is identified by its account address: in order to verify a signature
    /// one needs to retrieve the public key of the validator for the given epoch.
    signatures: HashMap<AccountAddress, Sig>,
}

impl<Sig> Display for LedgerInfoWithSignatures<Sig> {
    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
        write!(f, "{}", self.ledger_info)
    }
}

impl<Sig: Signature> LedgerInfoWithSignatures<Sig> {
    pub fn new(ledger_info: LedgerInfo, signatures: HashMap<AccountAddress, Sig>) -> Self {
        LedgerInfoWithSignatures {
            ledger_info,
            signatures,
        }
    }

    pub fn ledger_info(&self) -> &LedgerInfo {
        &self.ledger_info
    }

    pub fn add_signature(&mut self, validator: AccountAddress, signature: Sig) {
        self.signatures.entry(validator).or_insert(signature);
    }

    pub fn remove_signature(&mut self, validator: AccountAddress) {
        self.signatures.remove(&validator);
    }

    pub fn signatures(&self) -> &HashMap<AccountAddress, Sig> {
        &self.signatures
    }

    pub fn verify(
        &self,
        validator: &ValidatorVerifier<Sig::VerifyingKeyMaterial>,
    ) -> ::std::result::Result<(), VerifyError> {
        if self.ledger_info.is_zero() {
            // We're not trying to verify nominal ledger info that does not carry any information.
            return Ok(());
        }
        let ledger_hash = self.ledger_info().hash();
        validator.batch_verify_aggregated_signature(ledger_hash, self.signatures())
    }
}

impl<Sig: Signature> FromProto for LedgerInfoWithSignatures<Sig> {
    type ProtoType = crate::proto::ledger_info::LedgerInfoWithSignatures;

    fn from_proto(mut proto: Self::ProtoType) -> Result<Self> {
        let ledger_info = LedgerInfo::from_proto(proto.take_ledger_info())?;

        let signatures_proto = proto.take_signatures();
        let num_signatures = signatures_proto.len();
        let signatures = signatures_proto
            .into_iter()
            .map(|proto| {
                let validator_id = AccountAddress::from_proto(proto.get_validator_id().to_vec())?;
                let signature_bytes: &[u8] = proto.get_signature();
                let signature = Sig::try_from(signature_bytes)?;
                Ok((validator_id, signature))
            })
            .collect::<Result<HashMap<_, _>>>()?;
        ensure!(
            signatures.len() == num_signatures,
            "Signatures should be from different validators."
        );

        Ok(LedgerInfoWithSignatures {
            ledger_info,
            signatures,
        })
    }
}

impl<Sig: Signature> IntoProto for LedgerInfoWithSignatures<Sig> {
    type ProtoType = crate::proto::ledger_info::LedgerInfoWithSignatures;

    fn into_proto(self) -> Self::ProtoType {
        let mut proto = Self::ProtoType::new();
        proto.set_ledger_info(self.ledger_info.into_proto());
        self.signatures
            .into_iter()
            .for_each(|(validator_id, signature)| {
                let mut validator_signature = crate::proto::ledger_info::ValidatorSignature::new();
                validator_signature.set_validator_id(validator_id.into_proto());
                validator_signature.set_signature(signature.to_bytes().to_vec());
                proto.mut_signatures().push(validator_signature)
            });
        proto
    }
}
<commit_msg>[types] impl conversions from/to prost proto and LedgerInfo<commit_after>// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

use crate::{
    account_address::AccountAddress,
    transaction::Version,
    validator_set::ValidatorSet,
    validator_verifier::{ValidatorVerifier, VerifyError},
};
use canonical_serialization::{CanonicalSerialize, CanonicalSerializer, SimpleSerializer};
use crypto::{
    hash::{CryptoHash, CryptoHasher, LedgerInfoHasher},
    HashValue, *,
};
use failure::prelude::*;
#[cfg(any(test, feature = "testing"))]
use proptest_derive::Arbitrary;
use proto_conv::{FromProto, IntoProto};
use serde::{Deserialize, Serialize};
use std::{
    collections::HashMap,
    convert::TryFrom,
    fmt::{Display, Formatter},
};

/// This structure serves a dual purpose.
///
/// First, if this structure is signed by 2f+1 validators it signifies the state of the ledger at
/// version `version` -- it contains the transaction accumulator at that version which commits to
/// all historical transactions. This structure may be expanded to include other information that
/// is derived from that accumulator (e.g. the current time according to the time contract) to
/// reduce the number of proofs a client must get.
///
/// Second, the structure contains a `consensus_data_hash` value. This is the hash of an internal
/// data structure that represents a block that is voted on in HotStuff. If 2f+1 signatures are
/// gathered on the same ledger info that represents a Quorum Certificate (QC) on the consensus
/// data.
///
/// Combining these two concepts, when a validator votes on a block, B it votes for a
/// LedgerInfo with the `version` being the latest version that will be committed if B gets 2f+1
/// votes. It sets `consensus_data_hash` to represent B so that if those 2f+1 votes are gathered a
/// QC is formed on B.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "testing"), derive(Arbitrary))]
pub struct LedgerInfo {
    /// The version of the latest transaction in the ledger.
    version: Version,

    /// The root hash of transaction accumulator that includes the latest transaction.
    transaction_accumulator_hash: HashValue,

    /// Hash of consensus specific data that is opaque to all parts of the system other than
    /// consensus.
    consensus_data_hash: HashValue,

    /// Block id of the last committed block corresponding to this LedgerInfo
    /// as reported by consensus.
    consensus_block_id: HashValue,

    /// Epoch number corresponds to the set of validators that are active for this ledger info.
    epoch_num: u64,

    /// Timestamp that represents the microseconds since the epoch (unix time) that is
    /// generated by the proposer of the block.  This is strictly increasing with every block.
    /// If a client reads a timestamp > the one they specified for transaction expiration time,
    /// they can be certain that their transaction will never be included in a block in the future
    /// (assuming that their transaction has not yet been included)
    timestamp_usecs: u64,

    /// An optional field keeping the set of new validators to start the next epoch.
    /// The very last ledger info of an epoch contains the validator set for the next one,
    /// other ledger info instances are None.
    next_validator_set: Option<ValidatorSet>,
}

impl Display for LedgerInfo {
    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
        write!(
            f,
            "LedgerInfo: [committed_block_id: {}, version: {}, epoch_num: {}, timestamp (us): {}, next_validator_set: {}]",
            self.consensus_block_id(),
            self.version(),
            self.epoch_num(),
            self.timestamp_usecs(),
            self.next_validator_set.as_ref().map_or("None".to_string(), |validator_set| format!("{}", validator_set)),
        )
    }
}

impl LedgerInfo {
    /// Constructs a `LedgerInfo` object at a specific version using a given
    /// transaction accumulator root and consensus data hash.
    pub fn new(
        version: Version,
        transaction_accumulator_hash: HashValue,
        consensus_data_hash: HashValue,
        consensus_block_id: HashValue,
        epoch_num: u64,
        timestamp_usecs: u64,
        next_validator_set: Option<ValidatorSet>,
    ) -> Self {
        LedgerInfo {
            version,
            transaction_accumulator_hash,
            consensus_data_hash,
            consensus_block_id,
            epoch_num,
            timestamp_usecs,
            next_validator_set,
        }
    }

    /// Returns the version of this `LedgerInfo`.
    pub fn version(&self) -> Version {
        self.version
    }

    /// Returns the transaction accumulator root of this `LedgerInfo`.
    pub fn transaction_accumulator_hash(&self) -> HashValue {
        self.transaction_accumulator_hash
    }

    /// Returns hash of consensus data in this `LedgerInfo`.
    pub fn consensus_data_hash(&self) -> HashValue {
        self.consensus_data_hash
    }

    pub fn consensus_block_id(&self) -> HashValue {
        self.consensus_block_id
    }

    pub fn set_consensus_data_hash(&mut self, consensus_data_hash: HashValue) {
        self.consensus_data_hash = consensus_data_hash;
    }

    pub fn epoch_num(&self) -> u64 {
        self.epoch_num
    }

    pub fn timestamp_usecs(&self) -> u64 {
        self.timestamp_usecs
    }

    /// A ledger info is nominal if it's not certifying any real version.
    pub fn is_zero(&self) -> bool {
        self.version == 0
    }

    pub fn next_validator_set(&self) -> Option<&ValidatorSet> {
        self.next_validator_set.as_ref()
    }
}

impl IntoProto for LedgerInfo {
    type ProtoType = crate::proto::ledger_info::LedgerInfo;

    fn into_proto(self) -> Self::ProtoType {
        let mut proto = Self::ProtoType::new();
        proto.set_version(self.version);
        proto.set_transaction_accumulator_hash(self.transaction_accumulator_hash.into_proto());
        proto.set_consensus_data_hash(self.consensus_data_hash.into_proto());
        proto.set_consensus_block_id(self.consensus_block_id.into_proto());
        proto.set_epoch_num(self.epoch_num);
        proto.set_timestamp_usecs(self.timestamp_usecs);
        if let Some(next_validator_set) = self.next_validator_set {
            proto.set_next_validator_set(next_validator_set.into_proto());
        }
        proto
    }
}

impl FromProto for LedgerInfo {
    type ProtoType = crate::proto::ledger_info::LedgerInfo;

    fn from_proto(proto: Self::ProtoType) -> Result<Self> {
        let version = proto.get_version();
        let transaction_accumulator_hash =
            HashValue::from_slice(proto.get_transaction_accumulator_hash())?;
        let consensus_data_hash = HashValue::from_slice(proto.get_consensus_data_hash())?;
        let consensus_block_id = HashValue::from_slice(proto.get_consensus_block_id())?;
        let epoch_num = proto.get_epoch_num();
        let timestamp_usecs = proto.get_timestamp_usecs();

        let next_validator_set =
            if let Some(validator_set_proto) = proto.next_validator_set.into_option() {
                Some(ValidatorSet::from_proto(validator_set_proto)?)
            } else {
                None
            };
        Ok(LedgerInfo::new(
            version,
            transaction_accumulator_hash,
            consensus_data_hash,
            consensus_block_id,
            epoch_num,
            timestamp_usecs,
            next_validator_set,
        ))
    }
}

impl TryFrom<crate::proto::types::LedgerInfo> for LedgerInfo {
    type Error = Error;

    fn try_from(proto: crate::proto::types::LedgerInfo) -> Result<Self> {
        let version = proto.version;
        let transaction_accumulator_hash =
            HashValue::from_slice(&proto.transaction_accumulator_hash)?;
        let consensus_data_hash = HashValue::from_slice(&proto.consensus_data_hash)?;
        let consensus_block_id = HashValue::from_slice(&proto.consensus_block_id)?;
        let epoch_num = proto.epoch_num;
        let timestamp_usecs = proto.timestamp_usecs;

        let next_validator_set = if let Some(validator_set_proto) = proto.next_validator_set {
            Some(ValidatorSet::try_from(validator_set_proto)?)
        } else {
            None
        };
        Ok(LedgerInfo::new(
            version,
            transaction_accumulator_hash,
            consensus_data_hash,
            consensus_block_id,
            epoch_num,
            timestamp_usecs,
            next_validator_set,
        ))
    }
}

impl From<LedgerInfo> for crate::proto::types::LedgerInfo {
    fn from(ledger_info: LedgerInfo) -> Self {
        Self {
            version: ledger_info.version,
            transaction_accumulator_hash: ledger_info.transaction_accumulator_hash.to_vec(),
            consensus_data_hash: ledger_info.consensus_data_hash.to_vec(),
            consensus_block_id: ledger_info.consensus_block_id.to_vec(),
            epoch_num: ledger_info.epoch_num,
            timestamp_usecs: ledger_info.timestamp_usecs,
            next_validator_set: ledger_info.next_validator_set.map(Into::into),
        }
    }
}

impl CanonicalSerialize for LedgerInfo {
    fn serialize(&self, serializer: &mut impl CanonicalSerializer) -> Result<()> {
        serializer
            .encode_u64(self.version)?
            .encode_bytes(self.transaction_accumulator_hash.as_ref())?
            .encode_bytes(self.consensus_data_hash.as_ref())?
            .encode_bytes(self.consensus_block_id.as_ref())?
            .encode_u64(self.epoch_num)?
            .encode_u64(self.timestamp_usecs)?
            .encode_optional(&self.next_validator_set)?;
        Ok(())
    }
}

impl CryptoHash for LedgerInfo {
    type Hasher = LedgerInfoHasher;

    fn hash(&self) -> HashValue {
        let mut state = Self::Hasher::default();
        state.write(
            &SimpleSerializer::<Vec<u8>>::serialize(self).expect("Serialization should work."),
        );
        state.finish()
    }
}

/// The validator node returns this structure which includes signatures
/// from validators that confirm the state.  The client needs to only pass back
/// the LedgerInfo element since the validator node doesn't need to know the signatures
/// again when the client performs a query, those are only there for the client
/// to be able to verify the state
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct LedgerInfoWithSignatures<Sig> {
    ledger_info: LedgerInfo,
    /// The validator is identified by its account address: in order to verify a signature
    /// one needs to retrieve the public key of the validator for the given epoch.
    signatures: HashMap<AccountAddress, Sig>,
}

impl<Sig> Display for LedgerInfoWithSignatures<Sig> {
    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
        write!(f, "{}", self.ledger_info)
    }
}

impl<Sig: Signature> LedgerInfoWithSignatures<Sig> {
    pub fn new(ledger_info: LedgerInfo, signatures: HashMap<AccountAddress, Sig>) -> Self {
        LedgerInfoWithSignatures {
            ledger_info,
            signatures,
        }
    }

    pub fn ledger_info(&self) -> &LedgerInfo {
        &self.ledger_info
    }

    pub fn add_signature(&mut self, validator: AccountAddress, signature: Sig) {
        self.signatures.entry(validator).or_insert(signature);
    }

    pub fn remove_signature(&mut self, validator: AccountAddress) {
        self.signatures.remove(&validator);
    }

    pub fn signatures(&self) -> &HashMap<AccountAddress, Sig> {
        &self.signatures
    }

    pub fn verify(
        &self,
        validator: &ValidatorVerifier<Sig::VerifyingKeyMaterial>,
    ) -> ::std::result::Result<(), VerifyError> {
        if self.ledger_info.is_zero() {
            // We're not trying to verify nominal ledger info that does not carry any information.
            return Ok(());
        }
        let ledger_hash = self.ledger_info().hash();
        validator.batch_verify_aggregated_signature(ledger_hash, self.signatures())
    }
}

impl<Sig: Signature> FromProto for LedgerInfoWithSignatures<Sig> {
    type ProtoType = crate::proto::ledger_info::LedgerInfoWithSignatures;

    fn from_proto(mut proto: Self::ProtoType) -> Result<Self> {
        let ledger_info = LedgerInfo::from_proto(proto.take_ledger_info())?;

        let signatures_proto = proto.take_signatures();
        let num_signatures = signatures_proto.len();
        let signatures = signatures_proto
            .into_iter()
            .map(|proto| {
                let validator_id = AccountAddress::from_proto(proto.get_validator_id().to_vec())?;
                let signature_bytes: &[u8] = proto.get_signature();
                let signature = Sig::try_from(signature_bytes)?;
                Ok((validator_id, signature))
            })
            .collect::<Result<HashMap<_, _>>>()?;
        ensure!(
            signatures.len() == num_signatures,
            "Signatures should be from different validators."
        );

        Ok(LedgerInfoWithSignatures {
            ledger_info,
            signatures,
        })
    }
}

impl<Sig: Signature> IntoProto for LedgerInfoWithSignatures<Sig> {
    type ProtoType = crate::proto::ledger_info::LedgerInfoWithSignatures;

    fn into_proto(self) -> Self::ProtoType {
        let mut proto = Self::ProtoType::new();
        proto.set_ledger_info(self.ledger_info.into_proto());
        self.signatures
            .into_iter()
            .for_each(|(validator_id, signature)| {
                let mut validator_signature = crate::proto::ledger_info::ValidatorSignature::new();
                validator_signature.set_validator_id(validator_id.into_proto());
                validator_signature.set_signature(signature.to_bytes().to_vec());
                proto.mut_signatures().push(validator_signature)
            });
        proto
    }
}
<|endoftext|>


<commit_before>extern crate rand;
extern crate statrs;
use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion};
use rand::prelude::*;
use statrs::statistics::*;

fn bench_order_statistic(c: &mut Criterion) {
    let mut rng = thread_rng();
    let to_random_owned = |data: &[f64]| -> Vec<f64> {
        let mut rng = thread_rng();
        let mut owned = data.to_vec();
        owned.shuffle(&mut rng);
        owned
    };
    let k = black_box(rng.gen());
    let tau = black_box(rng.gen_range(0.0, 1.0));
    let mut group = c.benchmark_group("order statistic");
    let data: Vec<_> = (0..100).map(|x| x as f64).collect();
    group.bench_function("order_statistic", |b| {
        b.iter_batched(
            || to_random_owned(&data),
            |mut data| data.order_statistic(k),
            BatchSize::SmallInput,
        )
    });
    group.bench_function("median", |b| {
        b.iter_batched(
            || to_random_owned(&data),
            |data| data.median(),
            BatchSize::SmallInput,
        )
    });
    group.bench_function("quantile", |b| {
        b.iter_batched(
            || to_random_owned(&data),
            |mut data| data.quantile(tau),
            BatchSize::SmallInput,
        )
    });
    group.bench_function("percentile", |b| {
        b.iter_batched(
            || to_random_owned(&data),
            |mut data| data.percentile(k),
            BatchSize::SmallInput,
        )
    });
    group.bench_function("lower_quartile", |b| {
        b.iter_batched(
            || to_random_owned(&data),
            |mut data| data.lower_quartile(),
            BatchSize::SmallInput,
        )
    });
    group.bench_function("upper_quartile", |b| {
        b.iter_batched(
            || to_random_owned(&data),
            |mut data| data.upper_quartile(),
            BatchSize::SmallInput,
        )
    });
    group.bench_function("interquartile_range", |b| {
        b.iter_batched(
            || to_random_owned(&data),
            |mut data| data.interquartile_range(),
            BatchSize::SmallInput,
        )
    });
    group.bench_function("ranks: RankTieBreaker::First", |b| {
        b.iter_batched(
            || to_random_owned(&data),
            |mut data| data.ranks(RankTieBreaker::First),
            BatchSize::SmallInput,
        )
    });
    group.bench_function("ranks: RankTieBreaker::Average", |b| {
        b.iter_batched(
            || to_random_owned(&data),
            |mut data| data.ranks(RankTieBreaker::Average),
            BatchSize::SmallInput,
        )
    });
    group.bench_function("ranks: RankTieBreaker::Min", |b| {
        b.iter_batched(
            || to_random_owned(&data),
            |mut data| data.ranks(RankTieBreaker::Min),
            BatchSize::SmallInput,
        )
    });
    group.finish();
}

criterion_group!(benches, bench_order_statistic);
criterion_main!(benches);
<commit_msg>Fix benchmarks<commit_after>extern crate rand;
extern crate statrs;
use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion};
use rand::prelude::*;
use statrs::statistics::*;

fn bench_order_statistic(c: &mut Criterion) {
    let mut rng = thread_rng();
    let to_random_owned = |data: &[f64]| -> Data<Vec<f64>> {
        let mut rng = thread_rng();
        let mut owned = data.to_vec();
        owned.shuffle(&mut rng);
        Data::new(owned)
    };
    let k = black_box(rng.gen());
    let tau = black_box(rng.gen_range(0.0, 1.0));
    let mut group = c.benchmark_group("order statistic");
    let data: Vec<_> = (0..100).map(|x| x as f64).collect();
    group.bench_function("order_statistic", |b| {
        b.iter_batched(
            || to_random_owned(&data),
            |mut data| data.order_statistic(k),
            BatchSize::SmallInput,
        )
    });
    group.bench_function("median", |b| {
        b.iter_batched(
            || to_random_owned(&data),
            |data| data.median(),
            BatchSize::SmallInput,
        )
    });
    group.bench_function("quantile", |b| {
        b.iter_batched(
            || to_random_owned(&data),
            |mut data| data.quantile(tau),
            BatchSize::SmallInput,
        )
    });
    group.bench_function("percentile", |b| {
        b.iter_batched(
            || to_random_owned(&data),
            |mut data| data.percentile(k),
            BatchSize::SmallInput,
        )
    });
    group.bench_function("lower_quartile", |b| {
        b.iter_batched(
            || to_random_owned(&data),
            |mut data| data.lower_quartile(),
            BatchSize::SmallInput,
        )
    });
    group.bench_function("upper_quartile", |b| {
        b.iter_batched(
            || to_random_owned(&data),
            |mut data| data.upper_quartile(),
            BatchSize::SmallInput,
        )
    });
    group.bench_function("interquartile_range", |b| {
        b.iter_batched(
            || to_random_owned(&data),
            |mut data| data.interquartile_range(),
            BatchSize::SmallInput,
        )
    });
    group.bench_function("ranks: RankTieBreaker::First", |b| {
        b.iter_batched(
            || to_random_owned(&data),
            |mut data| data.ranks(RankTieBreaker::First),
            BatchSize::SmallInput,
        )
    });
    group.bench_function("ranks: RankTieBreaker::Average", |b| {
        b.iter_batched(
            || to_random_owned(&data),
            |mut data| data.ranks(RankTieBreaker::Average),
            BatchSize::SmallInput,
        )
    });
    group.bench_function("ranks: RankTieBreaker::Min", |b| {
        b.iter_batched(
            || to_random_owned(&data),
            |mut data| data.ranks(RankTieBreaker::Min),
            BatchSize::SmallInput,
        )
    });
    group.finish();
}

criterion_group!(benches, bench_order_statistic);
criterion_main!(benches);
<|endoftext|>


<commit_before>extern crate ansi_term;
#[macro_use]
extern crate clap;
#[macro_use]
extern crate error_chain;
extern crate flexi_logger;
extern crate futures;
#[macro_use]
extern crate log;
extern crate mhost;
extern crate tokio_core;
extern crate trust_dns;

use mhost::{Query, Response, multiple_lookup};
use mhost::lookup::{self, Result as LookupResult};
use mhost::get;
use mhost::output::{self, OutputConfig, OutputModule};

use ansi_term::Colour;
use clap::{App, Arg, ArgMatches, Shell};
use flexi_logger::{Logger, LogRecord};
use futures::Future;
use log::LogLevel;
use std::net::IpAddr;
use std::str::FromStr;
use std::time::Duration;
use tokio_core::reactor::Core;
use trust_dns::rr::RecordType;

fn run() -> Result<()> {
    let args = build_cli().get_matches();

    init_looger(args.occurrences_of("debug level"))
        .start()
        .unwrap_or_else(|e| { panic!("Logger initialization failed with {}", e) });

    if args.is_present("completions") {
        return generate_completion(&args);
    }

    let (query, server_limit, output_cfg) = parse_args(&args)?;
    let responses = run_lookup(&args, query, server_limit);

    let output_modules = args.values_of_lossy("output modules").unwrap();
    output(output_modules, &output_cfg, &responses)?;

    Ok(())
}

fn build_cli() -> App<'static, 'static> {
    let name = env!("CARGO_PKG_NAME");
    let version = env!("CARGO_PKG_VERSION");
    let about = env!("CARGO_PKG_DESCRIPTION");

    App::new(name)
        .version(version)
        .about(about)
        .arg(
            Arg::with_name("domain name")
                .index(1)
                .required(true)
                .conflicts_with("completions")
                .help("domain name or IP address to look up")
        )
        .arg(
            Arg::with_name("timeout")
                .long("timeout")
                .default_value("5")
                .help("Sets timeout for server responses in sec")
                .takes_value(true)
        )
        .arg(
            Arg::with_name("record types")
                .long("type")
                .short("t")
                .takes_value(true)
                .multiple(true)
                .require_delimiter(true)
                .possible_values(
                    &[
                        "a",
                        "aaaa",
                        "any",
                        "cname",
                        "dnskey",
                        "mx",
                        "ns",
                        "opt",
                        "ptr",
                        "soa",
                        "srv",
                        "txt",
                    ],
                )
                .help("Selects resource record type [default: a, aaaa, mx]")
        )
        .arg(
            Arg::with_name("dont use local dns servers")
                .short("L")
                .help("Ignore local DNS servers from /etc/resolv.conf")
        )
        .arg(
            Arg::with_name("dont use local search domain")
                .short("S")
                .help("Ignore local search domains from /etc/resolv.conf")
        )
        .arg(Arg::with_name("predefined server").short("p").help(
            "Uses predefined DNS servers set"
        ))
        .arg(
            Arg::with_name("DNS servers")
                .long("server")
                .short("s")
                .takes_value(true)
                .multiple(true)
                .require_delimiter(true)
                .number_of_values(1)
                .help(
                    "Sets DNS servers to use"
                )
        )
        .arg(
            Arg::with_name("ungefiltert ids")
                .short("u")
                .takes_value(true)
                .multiple(true)
                .require_delimiter(true)
                .help("Retrieves DNS servers from https://www.ungefiltert-surfen.de/ for country ids, e.g., 'de'")
        )
        .arg(
            Arg::with_name("limit")
                .long("limit")
                .short("l")
                .default_value("100")
                .help("Limits the amount of servers to query")
                .takes_value(true)
        )
        .arg(
            Arg::with_name("output modules")
                .long("module")
                .short("m")
                .takes_value(true)
                .multiple(true)
                .require_delimiter(true)
                .default_value("summary")
                .possible_values(
                    &[
                        "summary",
                        "details",
                        "json",
                    ],
                )
                .help("Selects output module")
        )
        .arg(
            Arg::with_name("human-readable output")
                .short("h")
                .help("Sets human-readable output")
        )
        .arg(
            Arg::with_name("show unsupported")
                .long("show-unsupported")
                .help("Shows unsupported resource records")
        )
        .arg(
            Arg::with_name("show nxdomain")
                .long("show-nxdomain")
                .help("Shows NXDOMAIN responses")
        )
        .arg(
            Arg::with_name("hide headers")
                .long("hide-headers")
                .help("Hides output headers")
        )
        .arg(
            Arg::with_name("debug level")
                .short("d")
                .multiple(true)
                .help("Sets debug level")
        )
        .arg(
            Arg::with_name("completions")
                .long("completions")
                .takes_value(true)
                .hidden(true)
                .possible_values(&["bash", "fish", "zsh"])
                .help("The shell to generate the script for")
        )
}

fn init_looger(verbosity_level: u64) -> Logger {
    fn colour(l: LogLevel) -> Colour {
        match l {
            LogLevel::Error => Colour::Red,
            LogLevel::Warn => Colour::Yellow,
            LogLevel::Info => Colour::Blue,
            _ => Colour::White,
        }
    }

    fn log_format_info(r: &LogRecord) -> String {
        format!("{}", colour(r.level()).paint(
            format!("{}: {}",
                    r.location().module_path(),
                    &r.args()
            )
        ))
    }

    fn log_format_debug(r: &LogRecord) -> String {
        format!("{}", colour(r.level()).paint(
            format!("{}:{}:{}: {}",
                    r.location().module_path(),
                    r.location().file(),
                    r.location().line(),
                    &r.args()
            )
        ))
    }

    let (log_spec, log_format): (String, fn(&LogRecord) -> String) = match std::env::var("RUST_LOG") {
        Ok(spec) => (spec, log_format_debug),
        Err(_) if verbosity_level == 1 => ("mhost=info".to_string(), log_format_info),
        Err(_) if verbosity_level == 2 => ("mhost=debug".to_string(), log_format_debug),
        Err(_) if verbosity_level > 2 => ("mhost=trace".to_string(), log_format_debug),
        Err(_) => ("mhost=warn".to_string(), log_format_info),
    };

    Logger::with_str(&log_spec).format(log_format)
}

fn generate_completion(args: &ArgMatches) -> Result<()> {
    let bin_name = env!("CARGO_PKG_NAME");
    let shell = args.value_of("completions").ok_or(
        ErrorKind::CliArgsParsingError,
    )?;
    build_cli().gen_completions_to(
        bin_name,
        shell.parse::<Shell>().unwrap(),
        &mut std::io::stdout(),
    );
    Ok(())
}

fn parse_args(args: &ArgMatches) -> Result<(Query, usize, OutputConfig)> {
    let timeout = value_t!(args, "timeout", u64)
        .map(Duration::from_secs)
        .unwrap();

    let query = match IpAddr::from_str(args.value_of("domain name").unwrap()) {
        Ok(ip) => Query::from(ip, vec![RecordType::PTR]),
        Err(_) => {
            let domain_name = if args.is_present("dont use local search domain") {
                args.value_of("domain name").unwrap().to_string()
            } else {
                let cfg = get::resolv_conf()?;
                let domain_name_from_args = args.value_of("domain name").unwrap();
                if domain_name_from_args.ends_with('.') {
                    domain_name_from_args.to_string()
                } else if !cfg.search.is_empty() && domain_name_from_args.matches('.').count() >= cfg.ndots as usize {
                    format!("{}.", domain_name_from_args)
                } else {
                    format!("{}.{}.", domain_name_from_args, cfg.search.first().unwrap())
                }
            };
            debug!("domain_name: '{}'", domain_name);

            let record_types = get::record_types(args.values_of_lossy("record types"))
                .chain_err(|| ErrorKind::CliArgsParsingError)?;
            Query::new(&domain_name, record_types)
        }
    }.set_timeout(timeout);
    trace!("{:?}", query);

    let server_limit = value_t!(args, "limit", usize).unwrap();

    let output_cfg = OutputConfig {
        human_readable: args.is_present("human-readable output"),
        show_headers: !args.is_present("hide headers"),
        show_nx_domain: args.is_present("show nxdomain"),
        show_unsupported_rr: args.is_present("show unsupported"),
    };

    Ok((query, server_limit, output_cfg))
}

// TODO: make this a Result
fn run_lookup(args: &ArgMatches, query: Query, server_limit: usize) -> Vec<LookupResult<Response>> {
    let mut io_loop = Core::new().unwrap();
    let handle = io_loop.handle();
    let lookup = get::dns_servers(
        &handle,
        args.values_of_lossy("DNS servers"),
        args.is_present("predefined server"),
        args.is_present("dont use local dns servers"),
        args.values_of_lossy("ungefiltert ids"),
    )
        .map_err(|e| e.into())
        .and_then(|servers| {
            debug!("DNS Server set: {:?}", servers);
            let dns_endpoints = servers
                .into_iter()
                .map(|s| (s, 53))
                .take(server_limit)
                .collect();
            multiple_lookup(&handle, query, dns_endpoints).map_err(
                |_| {
                    Error::from_kind(ErrorKind::LookupFailed)
                },
            )
        });
    let result = io_loop.run(lookup);

    result.unwrap()
}

fn output<'a, 'b>(outputs: Vec<String>, output_cfg: &OutputConfig, responses: &'b [LookupResult<Response>]) -> Result<()> {
    let mut writer = std::io::stdout();
    for output in outputs {
        match output.as_ref() {
            "json" => output::Json::new(responses).output(&mut writer)?,
            "details" => output::DetailsOutput::new(output_cfg, responses).output(&mut writer)?,
            _ => output::SummaryOutput::new(output_cfg, responses).output(&mut writer)?,
        }
    }

    Ok(())
}

error_chain! {
    errors {
        CliArgsParsingError {
            description("failed to parse CLI arguments")
            display("failed to parse CLI arguments")
        }

        LookupFailed {
            description("failed to run lookup")
            display("failed to run lookup")
        }
    }

    links {
        Get(get::Error, get::ErrorKind);
        Lookup(lookup::Error, lookup::ErrorKind);
        Output(output::Error, output::ErrorKind);
    }
}

quick_main!(run);
<commit_msg>Ref: -v -> -d, help msg uses singular<commit_after>extern crate ansi_term;
#[macro_use]
extern crate clap;
#[macro_use]
extern crate error_chain;
extern crate flexi_logger;
extern crate futures;
#[macro_use]
extern crate log;
extern crate mhost;
extern crate tokio_core;
extern crate trust_dns;

use mhost::{Query, Response, multiple_lookup};
use mhost::lookup::{self, Result as LookupResult};
use mhost::get;
use mhost::output::{self, OutputConfig, OutputModule};

use ansi_term::Colour;
use clap::{App, Arg, ArgMatches, Shell};
use flexi_logger::{Logger, LogRecord};
use futures::Future;
use log::LogLevel;
use std::net::IpAddr;
use std::str::FromStr;
use std::time::Duration;
use tokio_core::reactor::Core;
use trust_dns::rr::RecordType;

fn run() -> Result<()> {
    let args = build_cli().get_matches();

    init_looger(args.occurrences_of("debug level"))
        .start()
        .unwrap_or_else(|e| { panic!("Logger initialization failed with {}", e) });

    if args.is_present("completions") {
        return generate_completion(&args);
    }

    let (query, server_limit, output_cfg) = parse_args(&args)?;
    let responses = run_lookup(&args, query, server_limit);

    let output_modules = args.values_of_lossy("output modules").unwrap();
    output(output_modules, &output_cfg, &responses)?;

    Ok(())
}

fn build_cli() -> App<'static, 'static> {
    let name = env!("CARGO_PKG_NAME");
    let version = env!("CARGO_PKG_VERSION");
    let about = env!("CARGO_PKG_DESCRIPTION");

    App::new(name)
        .version(version)
        .about(about)
        .arg(
            Arg::with_name("domain name")
                .index(1)
                .required(true)
                .conflicts_with("completions")
                .help("domain name or IP address to look up")
        )
        .arg(
            Arg::with_name("timeout")
                .long("timeout")
                .default_value("5")
                .help("Sets timeout for server responses in sec")
                .takes_value(true)
        )
        .arg(
            Arg::with_name("record types")
                .long("type")
                .short("t")
                .takes_value(true)
                .value_name("record type")
                .multiple(true)
                .require_delimiter(true)
                .possible_values(
                    &[
                        "a",
                        "aaaa",
                        "any",
                        "cname",
                        "dnskey",
                        "mx",
                        "ns",
                        "opt",
                        "ptr",
                        "soa",
                        "srv",
                        "txt",
                    ],
                )
                .help("Selects resource record type [default: a, aaaa, mx]")
        )
        .arg(
            Arg::with_name("dont use local dns servers")
                .short("L")
                .help("Ignore local DNS servers from /etc/resolv.conf")
        )
        .arg(
            Arg::with_name("dont use local search domain")
                .short("S")
                .help("Ignore local search domains from /etc/resolv.conf")
        )
        .arg(Arg::with_name("predefined server").short("p").help(
            "Uses predefined DNS servers set"
        ))
        .arg(
            Arg::with_name("DNS servers")
                .long("server")
                .short("s")
                .takes_value(true)
                .value_name("DNS server")
                .multiple(true)
                .require_delimiter(true)
                .number_of_values(1)
                .help(
                    "Sets DNS servers to use"
                )
        )
        .arg(
            Arg::with_name("ungefiltert ids")
                .short("u")
                .takes_value(true)
                .value_name("country id")
                .multiple(true)
                .require_delimiter(true)
                .help("Retrieves DNS servers from https://public-dns.info for country id, e.g., 'de'")
        )
        .arg(
            Arg::with_name("limit")
                .long("limit")
                .short("l")
                .default_value("100")
                .help("Limits the amount of servers to query")
                .takes_value(true)
        )
        .arg(
            Arg::with_name("output modules")
                .long("module")
                .short("m")
                .takes_value(true)
                .value_name("output module")
                .multiple(true)
                .require_delimiter(true)
                .default_value("summary")
                .possible_values(
                    &[
                        "summary",
                        "details",
                        "json",
                    ],
                )
                .help("Selects output module")
        )
        .arg(
            Arg::with_name("human-readable output")
                .short("h")
                .help("Sets human-readable output")
        )
        .arg(
            Arg::with_name("show unsupported")
                .long("show-unsupported")
                .help("Shows unsupported resource records")
        )
        .arg(
            Arg::with_name("show nxdomain")
                .long("show-nxdomain")
                .help("Shows NXDOMAIN responses")
        )
        .arg(
            Arg::with_name("hide headers")
                .long("hide-headers")
                .help("Hides output headers")
        )
        .arg(
            Arg::with_name("debug level")
                .short("d")
                .multiple(true)
                .help("Sets debug level")
        )
        .arg(
            Arg::with_name("completions")
                .long("completions")
                .takes_value(true)
                .hidden(true)
                .possible_values(&["bash", "fish", "zsh"])
                .help("The shell to generate the script for")
        )
}

fn init_looger(verbosity_level: u64) -> Logger {
    fn colour(l: LogLevel) -> Colour {
        match l {
            LogLevel::Error => Colour::Red,
            LogLevel::Warn => Colour::Yellow,
            LogLevel::Info => Colour::Blue,
            _ => Colour::White,
        }
    }

    fn log_format_info(r: &LogRecord) -> String {
        format!("{}", colour(r.level()).paint(
            format!("{}: {}",
                    r.location().module_path(),
                    &r.args()
            )
        ))
    }

    fn log_format_debug(r: &LogRecord) -> String {
        format!("{}", colour(r.level()).paint(
            format!("{}:{}:{}: {}",
                    r.location().module_path(),
                    r.location().file(),
                    r.location().line(),
                    &r.args()
            )
        ))
    }

    let (log_spec, log_format): (String, fn(&LogRecord) -> String) = match std::env::var("RUST_LOG") {
        Ok(spec) => (spec, log_format_debug),
        Err(_) if verbosity_level == 1 => ("mhost=info".to_string(), log_format_info),
        Err(_) if verbosity_level == 2 => ("mhost=debug".to_string(), log_format_debug),
        Err(_) if verbosity_level > 2 => ("mhost=trace".to_string(), log_format_debug),
        Err(_) => ("mhost=warn".to_string(), log_format_info),
    };

    Logger::with_str(&log_spec).format(log_format)
}

fn generate_completion(args: &ArgMatches) -> Result<()> {
    let bin_name = env!("CARGO_PKG_NAME");
    let shell = args.value_of("completions").ok_or(
        ErrorKind::CliArgsParsingError,
    )?;
    build_cli().gen_completions_to(
        bin_name,
        shell.parse::<Shell>().unwrap(),
        &mut std::io::stdout(),
    );
    Ok(())
}

fn parse_args(args: &ArgMatches) -> Result<(Query, usize, OutputConfig)> {
    let timeout = value_t!(args, "timeout", u64)
        .map(Duration::from_secs)
        .unwrap();

    let query = match IpAddr::from_str(args.value_of("domain name").unwrap()) {
        Ok(ip) => Query::from(ip, vec![RecordType::PTR]),
        Err(_) => {
            let domain_name = if args.is_present("dont use local search domain") {
                args.value_of("domain name").unwrap().to_string()
            } else {
                let cfg = get::resolv_conf()?;
                let domain_name_from_args = args.value_of("domain name").unwrap();
                if domain_name_from_args.ends_with('.') {
                    domain_name_from_args.to_string()
                } else if !cfg.search.is_empty() && domain_name_from_args.matches('.').count() >= cfg.ndots as usize {
                    format!("{}.", domain_name_from_args)
                } else {
                    format!("{}.{}.", domain_name_from_args, cfg.search.first().unwrap())
                }
            };
            debug!("domain_name: '{}'", domain_name);

            let record_types = get::record_types(args.values_of_lossy("record types"))
                .chain_err(|| ErrorKind::CliArgsParsingError)?;
            Query::new(&domain_name, record_types)
        }
    }.set_timeout(timeout);
    trace!("{:?}", query);

    let server_limit = value_t!(args, "limit", usize).unwrap();

    let output_cfg = OutputConfig {
        human_readable: args.is_present("human-readable output"),
        show_headers: !args.is_present("hide headers"),
        show_nx_domain: args.is_present("show nxdomain"),
        show_unsupported_rr: args.is_present("show unsupported"),
    };

    Ok((query, server_limit, output_cfg))
}

// TODO: make this a Result
fn run_lookup(args: &ArgMatches, query: Query, server_limit: usize) -> Vec<LookupResult<Response>> {
    let mut io_loop = Core::new().unwrap();
    let handle = io_loop.handle();
    let lookup = get::dns_servers(
        &handle,
        args.values_of_lossy("DNS servers"),
        args.is_present("predefined server"),
        args.is_present("dont use local dns servers"),
        args.values_of_lossy("ungefiltert ids"),
    )
        .map_err(|e| e.into())
        .and_then(|servers| {
            debug!("DNS Server set: {:?}", servers);
            let dns_endpoints = servers
                .into_iter()
                .map(|s| (s, 53))
                .take(server_limit)
                .collect();
            multiple_lookup(&handle, query, dns_endpoints).map_err(
                |_| {
                    Error::from_kind(ErrorKind::LookupFailed)
                },
            )
        });
    let result = io_loop.run(lookup);

    result.unwrap()
}

fn output<'a, 'b>(outputs: Vec<String>, output_cfg: &OutputConfig, responses: &'b [LookupResult<Response>]) -> Result<()> {
    let mut writer = std::io::stdout();
    for output in outputs {
        match output.as_ref() {
            "json" => output::Json::new(responses).output(&mut writer)?,
            "details" => output::DetailsOutput::new(output_cfg, responses).output(&mut writer)?,
            _ => output::SummaryOutput::new(output_cfg, responses).output(&mut writer)?,
        }
    }

    Ok(())
}

error_chain! {
    errors {
        CliArgsParsingError {
            description("failed to parse CLI arguments")
            display("failed to parse CLI arguments")
        }

        LookupFailed {
            description("failed to run lookup")
            display("failed to run lookup")
        }
    }

    links {
        Get(get::Error, get::ErrorKind);
        Lookup(lookup::Error, lookup::ErrorKind);
        Output(output::Error, output::ErrorKind);
    }
}

quick_main!(run);
<|endoftext|>


<commit_before>use std::borrow::Cow;

use assign::assign;
use js_int::uint;
use matches::assert_matches;
use ruma_common::MilliSecondsSinceUnixEpoch;
#[cfg(feature = "unstable-pre-spec")]
use ruma_events::{
    key::verification::VerificationMethod, room::message::KeyVerificationRequestEventContent,
};
use ruma_events::{
    room::message::{
        AudioMessageEventContent, InReplyTo, MessageEvent, MessageEventContent, MessageType,
        Relation, TextMessageEventContent,
    },
    Unsigned,
};
#[cfg(feature = "unstable-pre-spec")]
use ruma_identifiers::DeviceIdBox;
use ruma_identifiers::{event_id, mxc_uri, room_id, user_id};
use ruma_serde::Raw;
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};

macro_rules! json_object {
    ( $($key:expr => $value:expr),* $(,)? ) => {
        {
            let mut _map = serde_json::Map::<String, serde_json::Value>::new();
            $(
                let _ = _map.insert($key, $value);
            )*
            _map
        }
    };
}

#[test]
fn serialization() {
    let ev = MessageEvent {
        content: MessageEventContent::new(MessageType::Audio(AudioMessageEventContent::plain(
            "test".into(),
            mxc_uri!("mxc://example.org/ffed755USFFxlgbQYZGtryd"),
            None,
        ))),
        event_id: event_id!("$143273582443PhrSn:example.org"),
        origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(10_000)),
        room_id: room_id!("!testroomid:example.org"),
        sender: user_id!("@user:example.org"),
        unsigned: Unsigned::default(),
    };

    assert_eq!(
        to_json_value(ev).unwrap(),
        json!({
            "type": "m.room.message",
            "event_id": "$143273582443PhrSn:example.org",
            "origin_server_ts": 10_000,
            "room_id": "!testroomid:example.org",
            "sender": "@user:example.org",
            "content": {
                "body": "test",
                "msgtype": "m.audio",
                "url": "mxc://example.org/ffed755USFFxlgbQYZGtryd",
            }
        })
    );
}

#[test]
fn content_serialization() {
    let message_event_content =
        MessageEventContent::new(MessageType::Audio(AudioMessageEventContent::plain(
            "test".into(),
            mxc_uri!("mxc://example.org/ffed755USFFxlgbQYZGtryd"),
            None,
        )));

    assert_eq!(
        to_json_value(&message_event_content).unwrap(),
        json!({
            "body": "test",
            "msgtype": "m.audio",
            "url": "mxc://example.org/ffed755USFFxlgbQYZGtryd"
        })
    );
}

#[test]
fn custom_msgtype_serialization() {
    let json_data = json_object! {
        "custom_field".into() => json!("baba"),
        "another_one".into() => json!("abab"),
    };
    let custom_msgtype = MessageType::new("my_custom_msgtype", json_data).unwrap();

    assert_eq!(
        to_json_value(&custom_msgtype).unwrap(),
        json!({
            "msgtype": "my_custom_msgtype",
            "custom_field": "baba",
            "another_one": "abab",
        })
    );
}

#[test]
fn custom_content_deserialization() {
    let json_data = json!({
        "msgtype": "my_custom_msgtype",
        "custom_field": "baba",
        "another_one": "abab",
    });

    let expected_json_data = json_object! {
        "custom_field".into() => json!("baba"),
        "another_one".into() => json!("abab"),
    };

    let custom_event =
        from_json_value::<Raw<MessageType>>(json_data).unwrap().deserialize().unwrap();

    assert_eq!(custom_event.msgtype(), "my_custom_msgtype");
    assert_eq!(custom_event.data(), Cow::Owned(expected_json_data));
}

#[test]
fn formatted_body_serialization() {
    let message_event_content =
        MessageEventContent::text_html("Hello, World!", "Hello, World!");

    assert_eq!(
        to_json_value(&message_event_content).unwrap(),
        json!({
            "body": "Hello, World!",
            "msgtype": "m.text",
            "format": "org.matrix.custom.html",
            "formatted_body": "Hello, World!",
        })
    );
}

#[test]
fn plain_text_content_serialization() {
    let message_event_content =
        MessageEventContent::text_plain("> <@test:example.com> test\n\ntest reply");

    assert_eq!(
        to_json_value(&message_event_content).unwrap(),
        json!({
            "body": "> <@test:example.com> test\n\ntest reply",
            "msgtype": "m.text"
        })
    );
}

#[test]
#[cfg(feature = "markdown")]
fn markdown_content_serialization() {
    let formatted_message = MessageEventContent::new(MessageType::Text(
        TextMessageEventContent::markdown("Testing **bold** and _italic_!"),
    ));

    assert_eq!(
        to_json_value(&formatted_message).unwrap(),
        json!({
            "body": "Testing **bold** and _italic_!",
            "formatted_body": "
Testing bold and italic!
\n",
            "format": "org.matrix.custom.html",
            "msgtype": "m.text"
        })
    );

    let plain_message_simple = MessageEventContent::new(MessageType::Text(
        TextMessageEventContent::markdown("Testing a simple phrase…"),
    ));

    assert_eq!(
        to_json_value(&plain_message_simple).unwrap(),
        json!({
            "body": "Testing a simple phrase…",
            "msgtype": "m.text"
        })
    );

    let plain_message_paragraphs = MessageEventContent::new(MessageType::Text(
        TextMessageEventContent::markdown("Testing\n\nSeveral\n\nParagraphs."),
    ));

    assert_eq!(
        to_json_value(&plain_message_paragraphs).unwrap(),
        json!({
            "body": "Testing\n\nSeveral\n\nParagraphs.",
            "formatted_body": "
Testing
\n
Several
\n
Paragraphs.
\n",
            "format": "org.matrix.custom.html",
            "msgtype": "m.text"
        })
    );
}

#[test]
fn relates_to_content_serialization() {
    let message_event_content =
        assign!(MessageEventContent::text_plain("> <@test:example.com> test\n\ntest reply"), {
            relates_to: Some(Relation::Reply {
                in_reply_to: InReplyTo::new(event_id!("$15827405538098VGFWH:example.com")),
            }),
        });

    let json_data = json!({
        "body": "> <@test:example.com> test\n\ntest reply",
        "msgtype": "m.text",
        "m.relates_to": {
            "m.in_reply_to": {
                "event_id": "$15827405538098VGFWH:example.com"
            }
        }
    });

    assert_eq!(to_json_value(&message_event_content).unwrap(), json_data);
}

#[test]
#[cfg(not(feature = "unstable-pre-spec"))]
fn edit_deserialization_061() {
    let json_data = json!({
        "body": "s/foo/bar",
        "msgtype": "m.text",
        "m.relates_to": {
            "rel_type": "m.replace",
            "event_id": event_id!("$1598361704261elfgc:localhost"),
        },
        "m.new_content": {
            "body": "bar",
        },
    });

    assert_matches!(
        from_json_value::<MessageEventContent>(json_data).unwrap(),
        MessageEventContent {
            msgtype: MessageType::Text(TextMessageEventContent {
                body,
                formatted: None,
                ..
            }),
            relates_to: None,
            ..
        } if body == "s/foo/bar"
    );
}

#[test]
#[cfg(feature = "unstable-pre-spec")]
fn edit_deserialization_future() {
    use ruma_events::room::message::Replacement;

    let ev_id = event_id!("$1598361704261elfgc:localhost");
    let json_data = json!({
        "body": "s/foo/bar",
        "msgtype": "m.text",
        "m.relates_to": {
            "rel_type": "m.replace",
            "event_id": ev_id,
        },
        "m.new_content": {
            "body": "bar",
            "msgtype": "m.text",
        },
    });

    assert_matches!(
        from_json_value::<MessageEventContent>(json_data).unwrap(),
        MessageEventContent {
            msgtype: MessageType::Text(TextMessageEventContent {
                body,
                formatted: None,
                ..
            }),
            relates_to: Some(Relation::Replacement(Replacement { event_id, new_content, .. })),
            ..
        } if body == "s/foo/bar"
            && event_id == ev_id
            && matches!(
                &*new_content,
                MessageEventContent {
                    msgtype: MessageType::Text(TextMessageEventContent {
                        body,
                        formatted: None,
                        ..
                    }),
                    ..
                } if body == "bar"
            )
    );
}

#[test]
#[cfg(feature = "unstable-pre-spec")]
fn verification_request_deserialization() {
    let user_id = user_id!("@example2:localhost");
    let device_id: DeviceIdBox = "XOWLHHFSWM".into();

    let json_data = json!({
        "body": "@example:localhost is requesting to verify your key, ...",
        "msgtype": "m.key.verification.request",
        "to": user_id,
        "from_device": device_id,
        "methods": [
            "m.sas.v1",
            "m.qr_code.show.v1",
            "m.reciprocate.v1"
        ]
    });

    assert_matches!(
        from_json_value::<MessageEventContent>(json_data).unwrap(),
        MessageEventContent {
            msgtype: MessageType::VerificationRequest(KeyVerificationRequestEventContent {
                body,
                to,
                from_device,
                methods,
                ..
            }),
            ..
        } if body == "@example:localhost is requesting to verify your key, ..."
            && to == user_id
            && from_device == device_id
            && methods.contains(&VerificationMethod::SasV1)
    );
}

#[test]
#[cfg(feature = "unstable-pre-spec")]
fn verification_request_serialization() {
    let user_id = user_id!("@example2:localhost");
    let device_id: DeviceIdBox = "XOWLHHFSWM".into();
    let body = "@example:localhost is requesting to verify your key, ...".to_owned();

    let methods = vec![
        VerificationMethod::SasV1,
        VerificationMethod::_Custom("m.qr_code.show.v1".to_owned()),
        VerificationMethod::_Custom("m.reciprocate.v1".to_owned()),
    ];

    let json_data = json!({
        "body": body,
        "msgtype": "m.key.verification.request",
        "to": user_id,
        "from_device": device_id,
        "methods": methods
    });

    let content = MessageType::VerificationRequest(KeyVerificationRequestEventContent::new(
        body, methods, device_id, user_id,
    ));

    assert_eq!(to_json_value(&content).unwrap(), json_data,);
}

#[test]
fn content_deserialization() {
    let json_data = json!({
        "body": "test",
        "msgtype": "m.audio",
        "url": "mxc://example.org/ffed755USFFxlgbQYZGtryd"
    });

    assert_matches!(
        from_json_value::<Raw<MessageEventContent>>(json_data)
            .unwrap()
            .deserialize()
            .unwrap(),
        MessageEventContent {
            msgtype: MessageType::Audio(AudioMessageEventContent {
                body,
                info: None,
                url: Some(url),
                file: None,
                ..
            }),
            ..
        } if body == "test" && url.to_string() == "mxc://example.org/ffed755USFFxlgbQYZGtryd"
    );
}

#[test]
fn content_deserialization_failure() {
    let json_data = json!({
        "body": "test","msgtype": "m.location",
        "url": "http://example.com/audio.mp3"
    });
    assert!(from_json_value::<Raw<MessageEventContent>>(json_data).unwrap().deserialize().is_err());
}
<commit_msg>events: Fix a test<commit_after>use std::borrow::Cow;

use assign::assign;
use js_int::uint;
use matches::assert_matches;
use ruma_common::MilliSecondsSinceUnixEpoch;
#[cfg(feature = "unstable-pre-spec")]
use ruma_events::{
    key::verification::VerificationMethod, room::message::KeyVerificationRequestEventContent,
};
use ruma_events::{
    room::message::{
        AudioMessageEventContent, InReplyTo, MessageEvent, MessageEventContent, MessageType,
        Relation, TextMessageEventContent,
    },
    Unsigned,
};
#[cfg(feature = "unstable-pre-spec")]
use ruma_identifiers::DeviceIdBox;
use ruma_identifiers::{event_id, mxc_uri, room_id, user_id};
use ruma_serde::Raw;
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};

macro_rules! json_object {
    ( $($key:expr => $value:expr),* $(,)? ) => {
        {
            let mut _map = serde_json::Map::<String, serde_json::Value>::new();
            $(
                let _ = _map.insert($key, $value);
            )*
            _map
        }
    };
}

#[test]
fn serialization() {
    let ev = MessageEvent {
        content: MessageEventContent::new(MessageType::Audio(AudioMessageEventContent::plain(
            "test".into(),
            mxc_uri!("mxc://example.org/ffed755USFFxlgbQYZGtryd"),
            None,
        ))),
        event_id: event_id!("$143273582443PhrSn:example.org"),
        origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(10_000)),
        room_id: room_id!("!testroomid:example.org"),
        sender: user_id!("@user:example.org"),
        unsigned: Unsigned::default(),
    };

    assert_eq!(
        to_json_value(ev).unwrap(),
        json!({
            "type": "m.room.message",
            "event_id": "$143273582443PhrSn:example.org",
            "origin_server_ts": 10_000,
            "room_id": "!testroomid:example.org",
            "sender": "@user:example.org",
            "content": {
                "body": "test",
                "msgtype": "m.audio",
                "url": "mxc://example.org/ffed755USFFxlgbQYZGtryd",
            }
        })
    );
}

#[test]
fn content_serialization() {
    let message_event_content =
        MessageEventContent::new(MessageType::Audio(AudioMessageEventContent::plain(
            "test".into(),
            mxc_uri!("mxc://example.org/ffed755USFFxlgbQYZGtryd"),
            None,
        )));

    assert_eq!(
        to_json_value(&message_event_content).unwrap(),
        json!({
            "body": "test",
            "msgtype": "m.audio",
            "url": "mxc://example.org/ffed755USFFxlgbQYZGtryd"
        })
    );
}

#[test]
fn custom_msgtype_serialization() {
    let json_data = json_object! {
        "custom_field".into() => json!("baba"),
        "another_one".into() => json!("abab"),
    };
    let custom_msgtype = MessageType::new("my_custom_msgtype", json_data).unwrap();

    assert_eq!(
        to_json_value(&custom_msgtype).unwrap(),
        json!({
            "msgtype": "my_custom_msgtype",
            "custom_field": "baba",
            "another_one": "abab",
        })
    );
}

#[test]
fn custom_content_deserialization() {
    let json_data = json!({
        "msgtype": "my_custom_msgtype",
        "custom_field": "baba",
        "another_one": "abab",
    });

    let expected_json_data = json_object! {
        "custom_field".into() => json!("baba"),
        "another_one".into() => json!("abab"),
    };

    let custom_event =
        from_json_value::<Raw<MessageType>>(json_data).unwrap().deserialize().unwrap();

    assert_eq!(custom_event.msgtype(), "my_custom_msgtype");
    assert_eq!(custom_event.data(), Cow::Owned(expected_json_data));
}

#[test]
fn formatted_body_serialization() {
    let message_event_content =
        MessageEventContent::text_html("Hello, World!", "Hello, World!");

    assert_eq!(
        to_json_value(&message_event_content).unwrap(),
        json!({
            "body": "Hello, World!",
            "msgtype": "m.text",
            "format": "org.matrix.custom.html",
            "formatted_body": "Hello, World!",
        })
    );
}

#[test]
fn plain_text_content_serialization() {
    let message_event_content =
        MessageEventContent::text_plain("> <@test:example.com> test\n\ntest reply");

    assert_eq!(
        to_json_value(&message_event_content).unwrap(),
        json!({
            "body": "> <@test:example.com> test\n\ntest reply",
            "msgtype": "m.text"
        })
    );
}

#[test]
#[cfg(feature = "markdown")]
fn markdown_content_serialization() {
    let formatted_message = MessageEventContent::new(MessageType::Text(
        TextMessageEventContent::markdown("Testing **bold** and _italic_!"),
    ));

    assert_eq!(
        to_json_value(&formatted_message).unwrap(),
        json!({
            "body": "Testing **bold** and _italic_!",
            "formatted_body": "
Testing bold and italic!
\n",
            "format": "org.matrix.custom.html",
            "msgtype": "m.text"
        })
    );

    let plain_message_simple = MessageEventContent::new(MessageType::Text(
        TextMessageEventContent::markdown("Testing a simple phrase…"),
    ));

    assert_eq!(
        to_json_value(&plain_message_simple).unwrap(),
        json!({
            "body": "Testing a simple phrase…",
            "msgtype": "m.text"
        })
    );

    let plain_message_paragraphs = MessageEventContent::new(MessageType::Text(
        TextMessageEventContent::markdown("Testing\n\nSeveral\n\nParagraphs."),
    ));

    assert_eq!(
        to_json_value(&plain_message_paragraphs).unwrap(),
        json!({
            "body": "Testing\n\nSeveral\n\nParagraphs.",
            "formatted_body": "
Testing
\n
Several
\n
Paragraphs.
\n",
            "format": "org.matrix.custom.html",
            "msgtype": "m.text"
        })
    );
}

#[test]
fn relates_to_content_serialization() {
    let message_event_content =
        assign!(MessageEventContent::text_plain("> <@test:example.com> test\n\ntest reply"), {
            relates_to: Some(Relation::Reply {
                in_reply_to: InReplyTo::new(event_id!("$15827405538098VGFWH:example.com")),
            }),
        });

    let json_data = json!({
        "body": "> <@test:example.com> test\n\ntest reply",
        "msgtype": "m.text",
        "m.relates_to": {
            "m.in_reply_to": {
                "event_id": "$15827405538098VGFWH:example.com"
            }
        }
    });

    assert_eq!(to_json_value(&message_event_content).unwrap(), json_data);
}

#[test]
#[cfg(not(feature = "unstable-pre-spec"))]
fn edit_deserialization_061() {
    let json_data = json!({
        "body": "s/foo/bar",
        "msgtype": "m.text",
        "m.relates_to": {
            "rel_type": "m.replace",
            "event_id": event_id!("$1598361704261elfgc:localhost"),
        },
        "m.new_content": {
            "body": "bar",
        },
    });

    assert_matches!(
        from_json_value::<MessageEventContent>(json_data).unwrap(),
        MessageEventContent {
            msgtype: MessageType::Text(TextMessageEventContent {
                body,
                formatted: None,
                ..
            }),
            relates_to: Some(_),
            ..
        } if body == "s/foo/bar"
    );
}

#[test]
#[cfg(feature = "unstable-pre-spec")]
fn edit_deserialization_future() {
    use ruma_events::room::message::Replacement;

    let ev_id = event_id!("$1598361704261elfgc:localhost");
    let json_data = json!({
        "body": "s/foo/bar",
        "msgtype": "m.text",
        "m.relates_to": {
            "rel_type": "m.replace",
            "event_id": ev_id,
        },
        "m.new_content": {
            "body": "bar",
            "msgtype": "m.text",
        },
    });

    assert_matches!(
        from_json_value::<MessageEventContent>(json_data).unwrap(),
        MessageEventContent {
            msgtype: MessageType::Text(TextMessageEventContent {
                body,
                formatted: None,
                ..
            }),
            relates_to: Some(Relation::Replacement(Replacement { event_id, new_content, .. })),
            ..
        } if body == "s/foo/bar"
            && event_id == ev_id
            && matches!(
                &*new_content,
                MessageEventContent {
                    msgtype: MessageType::Text(TextMessageEventContent {
                        body,
                        formatted: None,
                        ..
                    }),
                    ..
                } if body == "bar"
            )
    );
}

#[test]
#[cfg(feature = "unstable-pre-spec")]
fn verification_request_deserialization() {
    let user_id = user_id!("@example2:localhost");
    let device_id: DeviceIdBox = "XOWLHHFSWM".into();

    let json_data = json!({
        "body": "@example:localhost is requesting to verify your key, ...",
        "msgtype": "m.key.verification.request",
        "to": user_id,
        "from_device": device_id,
        "methods": [
            "m.sas.v1",
            "m.qr_code.show.v1",
            "m.reciprocate.v1"
        ]
    });

    assert_matches!(
        from_json_value::<MessageEventContent>(json_data).unwrap(),
        MessageEventContent {
            msgtype: MessageType::VerificationRequest(KeyVerificationRequestEventContent {
                body,
                to,
                from_device,
                methods,
                ..
            }),
            ..
        } if body == "@example:localhost is requesting to verify your key, ..."
            && to == user_id
            && from_device == device_id
            && methods.contains(&VerificationMethod::SasV1)
    );
}

#[test]
#[cfg(feature = "unstable-pre-spec")]
fn verification_request_serialization() {
    let user_id = user_id!("@example2:localhost");
    let device_id: DeviceIdBox = "XOWLHHFSWM".into();
    let body = "@example:localhost is requesting to verify your key, ...".to_owned();

    let methods = vec![
        VerificationMethod::SasV1,
        VerificationMethod::_Custom("m.qr_code.show.v1".to_owned()),
        VerificationMethod::_Custom("m.reciprocate.v1".to_owned()),
    ];

    let json_data = json!({
        "body": body,
        "msgtype": "m.key.verification.request",
        "to": user_id,
        "from_device": device_id,
        "methods": methods
    });

    let content = MessageType::VerificationRequest(KeyVerificationRequestEventContent::new(
        body, methods, device_id, user_id,
    ));

    assert_eq!(to_json_value(&content).unwrap(), json_data,);
}

#[test]
fn content_deserialization() {
    let json_data = json!({
        "body": "test",
        "msgtype": "m.audio",
        "url": "mxc://example.org/ffed755USFFxlgbQYZGtryd"
    });

    assert_matches!(
        from_json_value::<Raw<MessageEventContent>>(json_data)
            .unwrap()
            .deserialize()
            .unwrap(),
        MessageEventContent {
            msgtype: MessageType::Audio(AudioMessageEventContent {
                body,
                info: None,
                url: Some(url),
                file: None,
                ..
            }),
            ..
        } if body == "test" && url.to_string() == "mxc://example.org/ffed755USFFxlgbQYZGtryd"
    );
}

#[test]
fn content_deserialization_failure() {
    let json_data = json!({
        "body": "test","msgtype": "m.location",
        "url": "http://example.com/audio.mp3"
    });
    assert!(from_json_value::<Raw<MessageEventContent>>(json_data).unwrap().deserialize().is_err());
}
<|endoftext|>


<commit_before>#[cfg(feature = "rand")]
use crate::generate_localpart;

opaque_identifier! {
    /// A Matrix key ID.
    ///
    /// Device identifiers in Matrix are completely opaque character sequences. This type is
    /// provided simply for its semantic value.
    pub type DeviceId;
}

impl DeviceId {
    /// Generates a random `DeviceId`, suitable for assignment to a new device.
    #[cfg(feature = "rand")]
    pub fn new() -> Box<Self> {
        Self::from_owned(generate_localpart(8))
    }
}

#[cfg(all(test, feature = "rand"))]
mod tests {
    use super::DeviceId;

    #[test]
    fn generate_device_id() {
        assert_eq!(DeviceId::new().as_str().len(), 8);
    }
}
<commit_msg>identifiers: Document how to create a DeviceId<commit_after>#[cfg(feature = "rand")]
use crate::generate_localpart;

opaque_identifier! {
    /// A Matrix key ID.
    ///
    /// Device identifiers in Matrix are completely opaque character sequences. This type is
    /// provided simply for its semantic value.
    ///
    /// # Example
    ///
    /// ```
    /// use ruma_identifiers::{DeviceId, device_id};
    ///
    /// let random_id = DeviceId::new();
    /// assert_eq!(random_id.as_str().len(), 8);
    ///
    /// let static_id = device_id!("01234567");
    /// assert_eq!(static_id.as_str(), "01234567");
    ///
    /// let ref_id: &DeviceId = "abcdefghi".into();
    /// assert_eq!(ref_id.as_str(), "abcdefghi");
    ///
    /// let owned_id: Box<DeviceId> = "ijklmnop".into();
    /// assert_eq!(owned_id.as_str(), "ijklmnop");
    /// ```
    pub type DeviceId;
}

impl DeviceId {
    /// Generates a random `DeviceId`, suitable for assignment to a new device.
    #[cfg(feature = "rand")]
    pub fn new() -> Box<Self> {
        Self::from_owned(generate_localpart(8))
    }
}

#[cfg(all(test, feature = "rand"))]
mod tests {
    use super::DeviceId;

    #[test]
    fn generate_device_id() {
        assert_eq!(DeviceId::new().as_str().len(), 8);
    }

    #[test]
    fn create_device_id_from_str() {
        let ref_id: &DeviceId = "abcdefgh".into();
        assert_eq!(ref_id.as_str(), "abcdefgh");
    }

    #[test]
    fn create_boxed_device_id_from_str() {
        let box_id: Box<DeviceId> = "12345678".into();
        assert_eq!(box_id.as_str(), "12345678");
    }

    #[test]
    fn create_device_id_from_box() {
        let box_str: Box<str> = "ijklmnop".into();
        let device_id: Box<DeviceId> = DeviceId::from_owned(box_str);
        assert_eq!(device_id.as_str(), "ijklmnop");
    }
}
<|endoftext|>


<commit_before>//! We maintain invariant that all internal strings use `\n` as line separator.
//! This module does line ending conversion and detection (so that we can
//! convert back to `\r\n` on the way out).

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) enum LineEndings {
    Unix,
    Dos,
}

impl LineEndings {
    /// Replaces `\r\n` with `\n` in-place in `src`.
    pub(crate) fn normalize(src: String) -> (String, LineEndings) {
        if !src.as_bytes().contains(&b'\r') {
            return (src, LineEndings::Unix);
        }

        // We replace `\r\n` with `\n` in-place, which doesn't break utf-8 encoding.
        // While we *can* call `as_mut_vec` and do surgery on the live string
        // directly, let's rather steal the contents of `src`. This makes the code
        // safe even if a panic occurs.

        let mut buf = src.into_bytes();
        let mut gap_len = 0;
        let mut tail = buf.as_mut_slice();
        loop {
            let idx = match find_crlf(&tail[gap_len..]) {
                None => tail.len(),
                Some(idx) => idx + gap_len,
            };
            tail.copy_within(gap_len..idx, 0);
            tail = &mut tail[idx - gap_len..];
            if tail.len() == gap_len {
                break;
            }
            gap_len += 1;
        }

        // Account for removed `\r`.
        // After `set_len`, `buf` is guaranteed to contain utf-8 again.
        let new_len = buf.len() - gap_len;
        let src = unsafe {
            buf.set_len(new_len);
            String::from_utf8_unchecked(buf)
        };
        return (src, LineEndings::Dos);

        fn find_crlf(src: &[u8]) -> Option<usize> {
            let mut search_idx = 0;
            while let Some(idx) = find_cr(&src[search_idx..]) {
                if src[search_idx..].get(idx + 1) != Some(&b'\n') {
                    search_idx += idx + 1;
                    continue;
                }
                return Some(search_idx + idx);
            }
            None
        }

        fn find_cr(src: &[u8]) -> Option<usize> {
            src.iter().enumerate().find_map(|(idx, &b)| if b == b'\r' { Some(idx) } else { None })
        }
    }
}
<commit_msg>Simplify<commit_after>//! We maintain invariant that all internal strings use `\n` as line separator.
//! This module does line ending conversion and detection (so that we can
//! convert back to `\r\n` on the way out).

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) enum LineEndings {
    Unix,
    Dos,
}

impl LineEndings {
    /// Replaces `\r\n` with `\n` in-place in `src`.
    pub(crate) fn normalize(src: String) -> (String, LineEndings) {
        if !src.as_bytes().contains(&b'\r') {
            return (src, LineEndings::Unix);
        }

        // We replace `\r\n` with `\n` in-place, which doesn't break utf-8 encoding.
        // While we *can* call `as_mut_vec` and do surgery on the live string
        // directly, let's rather steal the contents of `src`. This makes the code
        // safe even if a panic occurs.

        let mut buf = src.into_bytes();
        let mut gap_len = 0;
        let mut tail = buf.as_mut_slice();
        loop {
            let idx = match find_crlf(&tail[gap_len..]) {
                None => tail.len(),
                Some(idx) => idx + gap_len,
            };
            tail.copy_within(gap_len..idx, 0);
            tail = &mut tail[idx - gap_len..];
            if tail.len() == gap_len {
                break;
            }
            gap_len += 1;
        }

        // Account for removed `\r`.
        // After `set_len`, `buf` is guaranteed to contain utf-8 again.
        let new_len = buf.len() - gap_len;
        let src = unsafe {
            buf.set_len(new_len);
            String::from_utf8_unchecked(buf)
        };
        return (src, LineEndings::Dos);

        fn find_crlf(src: &[u8]) -> Option<usize> {
            src.iter().zip(src.iter().skip(1)).position(|it| it == (&b'\r', &b'\n'))
        }
    }
}
<|endoftext|>