<commit_before>use std::{fmt, net::SocketAddr, path::PathBuf, str::FromStr};

use anyhow::bail;
use clap::{ArgEnum, ArgGroup, Parser, Subcommand};
use sccache::{
    config::{self, scheduler::ServerAuth as SchedulerServerAuth},
    dist::ServerId,
};
use syslog::Facility;

use crate::cmdline::{AuthSubcommand as ExternalAuthSubcommand, Command as ExternalCommand};

#[derive(Parser)]
#[clap(version)]
#[clap(propagate_version = true)]
struct Opts {
    #[clap(subcommand)]
    command: Command,
}

#[derive(Subcommand)]
enum Command {
    Auth(AuthOpts),
    Scheduler(SchedulerOpts),
    Server(ServerOpts),
}

#[derive(Parser)]
struct AuthOpts {
    #[clap(subcommand)]
    subcommand: AuthCommand,
}

#[derive(Subcommand)]
enum AuthCommand {
    GenerateJwtHs256Key,
    GenerateJwtHs256Token(ServerTokenOpts),
    GenerateSharedToken(SharedTokenOpts),
}

#[derive(Parser)]
#[clap(group(
    ArgGroup::new("key_source_mutual_exclusion")
        .args(&["secret-key", "config"])
        .required(true)
))]
struct ServerTokenOpts {
    /// Generate a key for the specified server
    #[clap(long, value_name = "SERVER_ADDR")]
    server: SocketAddr,
    /// Use specified key to create the token
    #[clap(long, value_name = "KEY")]
    secret_key: Option<String>,
    /// Use the key from the scheduler config file at PATH
    #[clap(long, value_name = "PATH")]
    config: Option<PathBuf>,
}

#[derive(Parser)]
struct SharedTokenOpts {
    /// Use the specified number of bits of randomness
    #[clap(long, default_value_t = TokenBits::default())]
    bits: TokenBits,
}

#[derive(Debug)]
struct TokenBits(usize);

impl TokenBits {
    fn as_bytes(&self) -> usize {
        self.0 / 8
    }
}

impl Default for TokenBits {
    fn default() -> Self {
        // Size based on https://briansmith.org/rustdoc/ring/hmac/fn.recommended_key_len.html
        Self(256)
    }
}

impl FromStr for TokenBits {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let bits: usize = s.parse()?;
        if bits % 8 != 0 || bits < 64 || bits > 4_096 {
            bail!("Number of bits must be divisible by 8, greater than 64 and less than 4096")
        }

        Ok(Self(bits))
    }
}

impl fmt::Display for TokenBits {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

#[derive(Parser)]
struct SchedulerOpts {
    /// Use the scheduler config file at PATH
    #[clap(long, value_name = "PATH")]
    config: PathBuf,
    /// Log to the syslog with LEVEL
    #[clap(long, value_name = "LEVEL", arg_enum)]
    syslog: LogLevel,
}

#[derive(ArgEnum, Clone, Copy)]
enum LogLevel {
    Error,
    Warn,
    Info,
    Debug,
    Trace,
}

impl From<LogLevel> for log::LevelFilter {
    fn from(log_level: LogLevel) -> Self {
        match log_level {
            LogLevel::Error => Self::Error,
            LogLevel::Warn => Self::Warn,
            LogLevel::Info => Self::Info,
            LogLevel::Debug => Self::Debug,
            LogLevel::Trace => Self::Trace,
        }
    }
}

#[derive(Parser)]
struct ServerOpts {
    /// Use the server config file at PATH
    #[clap(long, value_name = "PATH")]
    config: PathBuf,
    /// Log to the syslog with LEVEL
    #[clap(long, value_name = "LEVEL", arg_enum)]
    syslog: LogLevel,
}

/// Parse the commandline into a `Command` to execute.
pub fn parse() -> ExternalCommand {
    match try_parse() {
        Ok(cmd) => cmd,
        Err(e) => {
            println!("sccache-dist: {e}");
            for e in e.chain().skip(1) {
                println!("sccache-dist: caused by: {e}");
            }
            std::process::exit(1);
        }
    }
}

fn check_init_syslog(name: &str, log_level: LogLevel) {
    let level = log::LevelFilter::from(log_level);
    drop(syslog::init(Facility::LOG_DAEMON, level, Some(name)));
}

fn try_parse() -> anyhow::Result<ExternalCommand> {
    let Opts { command } = Opts::parse();
    Ok(match command {
        Command::Auth(AuthOpts { subcommand }) => ExternalCommand::Auth(match subcommand {
            AuthCommand::GenerateJwtHs256Key => ExternalAuthSubcommand::Base64 {
                num_bytes: TokenBits::default().as_bytes(),
            },
            AuthCommand::GenerateJwtHs256Token(ServerTokenOpts {
                server,
                config,
                secret_key,
            }) => {
                let secret_key = match (config, secret_key) {
                    (Some(config), None) => {
                        if let Some(config) = config::scheduler::from_path(&config)? {
                            match config.server_auth {
                                SchedulerServerAuth::JwtHS256 { secret_key } => secret_key,
                                SchedulerServerAuth::Insecure
                                | SchedulerServerAuth::Token { .. } => {
                                    bail!("Scheduler not configured with JWT HS256")
                                }
                            }
                        } else {
                            bail!("Could not load config")
                        }
                    }
                    (None, Some(secret_key)) => secret_key,
                    _ => unreachable!("Prevented by `key_source_mutual_exclusion` `ArgGroup`"),
                };

                ExternalAuthSubcommand::JwtHS256ServerToken {
                    secret_key,
                    server_id: ServerId::new(server),
                }
            }
            AuthCommand::GenerateSharedToken(SharedTokenOpts { bits }) => {
                ExternalAuthSubcommand::Base64 {
                    num_bytes: bits.as_bytes(),
                }
            }
        }),
        Command::Scheduler(SchedulerOpts { config, syslog }) => {
            check_init_syslog("sccache-scheduler", syslog);

            if let Some(config) = config::scheduler::from_path(&config)? {
                ExternalCommand::Scheduler(config)
            } else {
                bail!("Could not load config")
            }
        }
        Command::Server(ServerOpts { config, syslog }) => {
            check_init_syslog("sccache-buildserver", syslog);

            if let Some(config) = config::server::from_path(&config)? {
                ExternalCommand::Server(config)
            } else {
                bail!("Could not load config")
            }
        }
    })
}
<commit_msg>Fix subcommand name for `sccache-dist`<commit_after>use std::{fmt, net::SocketAddr, path::PathBuf, str::FromStr};

use anyhow::bail;
use clap::{ArgEnum, ArgGroup, Parser, Subcommand};
use sccache::{
    config::{self, scheduler::ServerAuth as SchedulerServerAuth},
    dist::ServerId,
};
use syslog::Facility;

use crate::cmdline::{AuthSubcommand as ExternalAuthSubcommand, Command as ExternalCommand};

#[derive(Parser)]
#[clap(version)]
#[clap(propagate_version = true)]
struct Opts {
    #[clap(subcommand)]
    command: Command,
}

#[derive(Subcommand)]
enum Command {
    Auth(AuthOpts),
    Scheduler(SchedulerOpts),
    Server(ServerOpts),
}

#[derive(Parser)]
struct AuthOpts {
    #[clap(subcommand)]
    subcommand: AuthCommand,
}

#[derive(Subcommand)]
enum AuthCommand {
    GenerateJwtHs256Key,
    GenerateJwtHs256ServerToken(ServerTokenOpts),
    GenerateSharedToken(SharedTokenOpts),
}

#[derive(Parser)]
#[clap(group(
    ArgGroup::new("key_source_mutual_exclusion")
        .args(&["secret-key", "config"])
        .required(true)
))]
struct ServerTokenOpts {
    /// Generate a key for the specified server
    #[clap(long, value_name = "SERVER_ADDR")]
    server: SocketAddr,
    /// Use specified key to create the token
    #[clap(long, value_name = "KEY")]
    secret_key: Option<String>,
    /// Use the key from the scheduler config file at PATH
    #[clap(long, value_name = "PATH")]
    config: Option<PathBuf>,
}

#[derive(Parser)]
struct SharedTokenOpts {
    /// Use the specified number of bits of randomness
    #[clap(long, default_value_t = TokenBits::default())]
    bits: TokenBits,
}

#[derive(Debug)]
struct TokenBits(usize);

impl TokenBits {
    fn as_bytes(&self) -> usize {
        self.0 / 8
    }
}

impl Default for TokenBits {
    fn default() -> Self {
        // Size based on https://briansmith.org/rustdoc/ring/hmac/fn.recommended_key_len.html
        Self(256)
    }
}

impl FromStr for TokenBits {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let bits: usize = s.parse()?;
        if bits % 8 != 0 || bits < 64 || bits > 4_096 {
            bail!("Number of bits must be divisible by 8, greater than 64 and less than 4096")
        }

        Ok(Self(bits))
    }
}

impl fmt::Display for TokenBits {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

#[derive(Parser)]
struct SchedulerOpts {
    /// Use the scheduler config file at PATH
    #[clap(long, value_name = "PATH")]
    config: PathBuf,
    /// Log to the syslog with LEVEL
    #[clap(long, value_name = "LEVEL", arg_enum)]
    syslog: LogLevel,
}

#[derive(ArgEnum, Clone, Copy)]
enum LogLevel {
    Error,
    Warn,
    Info,
    Debug,
    Trace,
}

impl From<LogLevel> for log::LevelFilter {
    fn from(log_level: LogLevel) -> Self {
        match log_level {
            LogLevel::Error => Self::Error,
            LogLevel::Warn => Self::Warn,
            LogLevel::Info => Self::Info,
            LogLevel::Debug => Self::Debug,
            LogLevel::Trace => Self::Trace,
        }
    }
}

#[derive(Parser)]
struct ServerOpts {
    /// Use the server config file at PATH
    #[clap(long, value_name = "PATH")]
    config: PathBuf,
    /// Log to the syslog with LEVEL
    #[clap(long, value_name = "LEVEL", arg_enum)]
    syslog: LogLevel,
}

/// Parse the commandline into a `Command` to execute.
pub fn parse() -> ExternalCommand {
    match try_parse() {
        Ok(cmd) => cmd,
        Err(e) => {
            println!("sccache-dist: {e}");
            for e in e.chain().skip(1) {
                println!("sccache-dist: caused by: {e}");
            }
            std::process::exit(1);
        }
    }
}

fn check_init_syslog(name: &str, log_level: LogLevel) {
    let level = log::LevelFilter::from(log_level);
    drop(syslog::init(Facility::LOG_DAEMON, level, Some(name)));
}

fn try_parse() -> anyhow::Result<ExternalCommand> {
    let Opts { command } = Opts::parse();
    Ok(match command {
        Command::Auth(AuthOpts { subcommand }) => ExternalCommand::Auth(match subcommand {
            AuthCommand::GenerateJwtHs256Key => ExternalAuthSubcommand::Base64 {
                num_bytes: TokenBits::default().as_bytes(),
            },
            AuthCommand::GenerateJwtHs256ServerToken(ServerTokenOpts {
                server,
                config,
                secret_key,
            }) => {
                let secret_key = match (config, secret_key) {
                    (Some(config), None) => {
                        if let Some(config) = config::scheduler::from_path(&config)? {
                            match config.server_auth {
                                SchedulerServerAuth::JwtHS256 { secret_key } => secret_key,
                                SchedulerServerAuth::Insecure
                                | SchedulerServerAuth::Token { .. } => {
                                    bail!("Scheduler not configured with JWT HS256")
                                }
                            }
                        } else {
                            bail!("Could not load config")
                        }
                    }
                    (None, Some(secret_key)) => secret_key,
                    _ => unreachable!("Prevented by `key_source_mutual_exclusion` `ArgGroup`"),
                };

                ExternalAuthSubcommand::JwtHS256ServerToken {
                    secret_key,
                    server_id: ServerId::new(server),
                }
            }
            AuthCommand::GenerateSharedToken(SharedTokenOpts { bits }) => {
                ExternalAuthSubcommand::Base64 {
                    num_bytes: bits.as_bytes(),
                }
            }
        }),
        Command::Scheduler(SchedulerOpts { config, syslog }) => {
            check_init_syslog("sccache-scheduler", syslog);

            if let Some(config) = config::scheduler::from_path(&config)? {
                ExternalCommand::Scheduler(config)
            } else {
                bail!("Could not load config")
            }
        }
        Command::Server(ServerOpts { config, syslog }) => {
            check_init_syslog("sccache-buildserver", syslog);

            if let Some(config) = config::server::from_path(&config)? {
                ExternalCommand::Server(config)
            } else {
                bail!("Could not load config")
            }
        }
    })
}
<|endoftext|>


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

use aes::Aes128;
use aes::BLOCK_SIZE;
use aes::MODE;

use xor::XOR;

use serialize::from_base64_file;
use serialize::from_hex;

use mac::hmac_sha1;
use mac::mac_sha1;

use hmac_client;
use hmac_server;

use rand;
use rand::Rng;

use set2::prefix_length;
use set2::random_block;

use errors::*;

use prefix_suffix_oracles::{Oracle, Oracle26};

// The following imports are only required for challenge 29
// use byteorder::{ByteOrder, BigEndian, WriteBytesExt};
// use sha1::Sha1;

struct Encrypter25 {
    cleartext: Vec<u8>,
    key: Vec<u8>,
    ciphertext: Vec<u8>,
}

impl Encrypter25 {
    pub fn new() -> Result<Self, Error> {
        let cleartext = from_base64_file(Path::new("data/25.txt"))?;
        let key = random_block();
        let ciphertext = cleartext.encrypt(&key, None, MODE::CTR)?;
        Ok(Encrypter25 {
            cleartext,
            key,
            ciphertext,
        })
    }

    pub fn get_ciphertext(&self) -> &[u8] {
        &self.ciphertext
    }

    pub fn edit(&self, offset: usize, newtext: Vec<u8>) -> Result<Vec<u8>, Error> {
        let mut cleartext = self.cleartext.clone();
        let end = offset + newtext.len();
        if end > cleartext.len() {
            bail!("input out of bounds")
        }
        cleartext[offset..end].copy_from_slice(&newtext);
        cleartext.encrypt(&self.key, None, MODE::CTR)
    }

    pub fn verify_solution(&self, candidate_cleartext: &[u8]) -> Result<(), Error> {
        compare_eq(&self.cleartext[..], candidate_cleartext)
    }
}

fn challenge_25() -> Result<(), Error> {
    // This exercise is trivial: In CTR mode, if we know the underlying plaintext at some location,
    // we can trivially recover the used keystream by xor'ing the ciphertext with the
    // known plaintext. We simply use the edit function to set the entire cleartext to 0 so that
    // the ciphertext is even equal to the keystream.

    let encrypter = Encrypter25::new()?;
    let ciphertext = encrypter.get_ciphertext();
    let keystream = encrypter.edit(0, vec![0; ciphertext.len()])?;
    encrypter.verify_solution(&ciphertext.xor(&keystream))
}

fn challenge_26() -> Result<(), Error> {
    // This exercise is trivial: In CTR mode, if we know the underlying plaintext at some location,
    // we can inject any plaintext at the same location by xor'ing the ciphertext with
    // known_plaintext ^ target_plaintext.

    let oracle = Oracle26::new()?;
    let prefix_len = prefix_length(&oracle)?;
    let target_cleartext = b";admin=true";
    let mut ciphertext = oracle.encrypt(&vec![0; target_cleartext.len()])?;
    ciphertext.truncate(prefix_len + target_cleartext.len());
    ciphertext[prefix_len..].xor_inplace(target_cleartext);
    oracle.verify_solution(&ciphertext)
}

struct Sender27 {
    key: Vec<u8>,
}

struct Receiver27 {
    key: Vec<u8>,
}

impl Sender27 {
    pub fn encrypt(&self, input: &[u8]) -> Result<Vec<u8>, Error> {
        // Exclude ';' and '='
        if input
            .iter()
            .any(|&c| !c.is_ascii() || c == b';' || c == b'=')
        {
            bail!("invalid character in input");
        }

        let prefix = b"comment1=cooking%20MCs;userdata=";
        let suffix = b";comment2=%20like%20a%20pound%20of%20bacon";
        let mut cleartext = Vec::with_capacity(prefix.len() + input.len() + suffix.len());
        cleartext.extend_from_slice(prefix);
        cleartext.extend_from_slice(input);
        cleartext.extend_from_slice(suffix);
        cleartext.encrypt(&self.key, Some(&self.key), MODE::CBC)
    }
}

impl Receiver27 {
    pub fn try_decrypt(&self, ciphertext: &[u8]) -> Result<(), Error> {
        let cleartext = ciphertext.decrypt(&self.key, Some(&self.key), MODE::CBC)?;
        if !cleartext.is_ascii() {
            Err(NonAscii(cleartext).into())
        } else {
            Ok(())
        }
    }

    pub fn verify_solution(&self, candidate_key: &[u8]) -> Result<(), Error> {
        compare_eq(&self.key[..], candidate_key)
    }
}

fn get_sender_and_receiver_with_shared_key() -> (Sender27, Receiver27) {
    let secret_key = random_block();
    let sender = Sender27 {
        key: secret_key.clone(),
    };
    let receiver = Receiver27 {
        key: secret_key.clone(),
    };
    (sender, receiver)
}

#[derive(Debug, Fail)]
#[fail(display = "invalid input: {:?}", _0)]
struct NonAscii(Vec<u8>);

fn challenge_27() -> Result<(), Error> {
    let (sender, receiver) = get_sender_and_receiver_with_shared_key();

    let ciphertext = sender.encrypt(&[])?;

    let mut attack_ciphertext = Vec::with_capacity(3 * BLOCK_SIZE);
    attack_ciphertext.extend_from_slice(&ciphertext[0..BLOCK_SIZE]);
    attack_ciphertext.extend_from_slice(&[0; BLOCK_SIZE]);
    attack_ciphertext.extend_from_slice(&ciphertext[0..BLOCK_SIZE]);
    //Push last two blocks to preserve valid padding at the end
    attack_ciphertext.extend_from_slice(&ciphertext[ciphertext.len() - 2 * BLOCK_SIZE..]);

    if let Err(err) = receiver.try_decrypt(&attack_ciphertext) {
        if let Ok(NonAscii(attack_cleartext)) = err.downcast::<NonAscii>() {
            return receiver.verify_solution(
                &attack_cleartext[0..BLOCK_SIZE]
                    .xor(&attack_cleartext[2 * BLOCK_SIZE..3 * BLOCK_SIZE]),
            );
        }
    }

    bail!("attack ciphertext did not deceive the receiver");
}

fn challenge_28() -> Result<(), Error> {
    compare_eq(
        from_hex("2fd4e1c67a2d28fced849ee1bb76e7391b93eb12")?,
        mac_sha1(b"The quick brown fox ", b"jumps over the lazy dog"),
    )
}

/* The solution to challenge 29 requires a patched version of the sha1 crate.
 * To be precise, you have to clone the rust-sha1 repository and the apply the
 * following patch (tested for version 0.2.0 of rust-sha1):

------------------------------------
diff --git a/src/lib.rs b/src/lib.rs
index 352888b..325b94f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -74,6 +74,18 @@ impl Sha1 {
         }
     }

+    /// Creates a sha1 hash object with a predefined state.
+    pub fn new_with_state_and_length(state: [u32; 5], len: u64) -> Sha1 {
+        Sha1 {
+            state: Sha1State { state: state },
+            len: len,
+            blocks: Blocks {
+                len: 0,
+                block: [0; 64],
+            },
+        }
+    }
+
     /// Resets the hash object to it's initial state.
     pub fn reset(&mut self) {
         self.state = DEFAULT_STATE;
------------------------------------

Finally point Cargo.toml to your patched version of rust-sha1. */

/*

struct Server29 {
    key: Vec<u8>
}

impl Server29 {
    pub fn new() -> Self {
        let mut rng = rand::thread_rng();
        let key_len = rng.gen_range(1, 200);
        let key: Vec<u8> = rng.gen_iter().take(key_len).collect();
        Server29 { key }
    }

    pub fn get_message_with_mac(&self) -> (Vec<u8>, Vec<u8>) {
        let message = b"comment1=cooking%20MCs;userdata=foo;comment2=%20like%20a%20pound%20of%20bacon".to_vec();
        let mac = mac_sha1(&self.key, &message);
        (message, mac)
    }

    pub fn verify_mac(&self, message: &[u8], mac: &[u8]) -> bool {
        mac_sha1(&self.key, message) == mac
    }
}

fn padding(length: usize) -> Vec<u8> {
    let mut w = Vec::new();
    w.push(0x80u8);
    let zero_padding_count = 64 - ((length + 9) % 64);
    for _ in 0..zero_padding_count {
        w.push(0u8);
    }
    w.write_u64::<BigEndian>((length as u64) * 8).unwrap();
    w
}

fn challenge_29() -> Result<(), Error> {
    let server = Server29::new();
    let (input, hash) = server.get_message_with_mac();
    let suffix = b";admin=true";

    // Split the 20 bytes of `hash` into chunks of four
    // bytes and transmute each chunk into an u32.
    let mut state: [u32; 5] = [0; 5];
    for (b, s) in hash.chunks(4).zip(state.iter_mut()) {
        *s = BigEndian::read_u32(b)
    }

    for key_len in 0..200 {
        let secret_len = input.len() + key_len;
        let padding = padding(secret_len);
        let mut m2 = Sha1::new_with_state_and_length(state, (secret_len + padding.len()) as u64);
        m2.update(suffix);
        let mac = m2.digest().bytes();

        let mut message = Vec::new();
        message.extend_from_slice(&input);
        message.extend_from_slice(&padding);
        message.extend_from_slice(suffix);
        if server.verify_mac(&message, &mac) {
            return Ok(());
        }
    }
    bail!("No matching message found.");
}
*/

fn challenge_29() -> Result<(), Error> {
    Err(ChallengeError::Skipped(
        "Requires a patched version of the sha1 crate. \
         See the source code for detailed instructions.",
    ).into())
}

fn challenge_30() -> Result<(), Error> {
    /* Skipping/postponing Challenge 30 because
     * 1) SHA1 was much more interesting anyway,
     * 2) no MD4 implementation seems to be available.  */

    Err(ChallengeError::NotImplemented.into())
}

fn challenge_31() -> Result<(), Error> {
    compare_eq(
        from_hex("de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9")?,
        hmac_sha1(b"key", b"The quick brown fox jumps over the lazy dog"),
    )
}

fn challenge_32() -> Result<(), Error> {
    let skip_exercise = true;

    if skip_exercise {
        return Err(ChallengeError::Skipped("Runs very long.").into());
    }

    let mut rng = rand::thread_rng();
    let key: Vec<u8> = rng.gen_iter().take(20).collect();

    let mut server = hmac_server::start(key);

    hmac_client::run();

    server.close().context("failed to close connection")?;

    Ok(()) //TODO Add proper error handling
}

pub fn add_challenges(challenges: &mut Vec<fn() -> Result<(), Error>>) {
    challenges.push(challenge_25);
    challenges.push(challenge_26);
    challenges.push(challenge_27);
    challenges.push(challenge_28);
    challenges.push(challenge_29);
    challenges.push(challenge_30);
    challenges.push(challenge_31);
    challenges.push(challenge_32);
}
<commit_msg>Remove need to patch sha1 crate<commit_after>use std::path::Path;

use aes::Aes128;
use aes::BLOCK_SIZE;
use aes::MODE;

use xor::XOR;

use serialize::from_base64_file;
use serialize::from_hex;

use mac::hmac_sha1;
use mac::mac_sha1;

use hmac_client;
use hmac_server;

use rand;
use rand::Rng;

use set2::prefix_length;
use set2::random_block;

use errors::*;

use prefix_suffix_oracles::{Oracle, Oracle26};

use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
use sha1::Sha1;
use std::mem;

struct Encrypter25 {
    cleartext: Vec<u8>,
    key: Vec<u8>,
    ciphertext: Vec<u8>,
}

impl Encrypter25 {
    pub fn new() -> Result<Self, Error> {
        let cleartext = from_base64_file(Path::new("data/25.txt"))?;
        let key = random_block();
        let ciphertext = cleartext.encrypt(&key, None, MODE::CTR)?;
        Ok(Encrypter25 {
            cleartext,
            key,
            ciphertext,
        })
    }

    pub fn get_ciphertext(&self) -> &[u8] {
        &self.ciphertext
    }

    pub fn edit(&self, offset: usize, newtext: Vec<u8>) -> Result<Vec<u8>, Error> {
        let mut cleartext = self.cleartext.clone();
        let end = offset + newtext.len();
        if end > cleartext.len() {
            bail!("input out of bounds")
        }
        cleartext[offset..end].copy_from_slice(&newtext);
        cleartext.encrypt(&self.key, None, MODE::CTR)
    }

    pub fn verify_solution(&self, candidate_cleartext: &[u8]) -> Result<(), Error> {
        compare_eq(&self.cleartext[..], candidate_cleartext)
    }
}

fn challenge_25() -> Result<(), Error> {
    // This exercise is trivial: In CTR mode, if we know the underlying plaintext at some location,
    // we can trivially recover the used keystream by xor'ing the ciphertext with the
    // known plaintext. We simply use the edit function to set the entire cleartext to 0 so that
    // the ciphertext is even equal to the keystream.

    let encrypter = Encrypter25::new()?;
    let ciphertext = encrypter.get_ciphertext();
    let keystream = encrypter.edit(0, vec![0; ciphertext.len()])?;
    encrypter.verify_solution(&ciphertext.xor(&keystream))
}

fn challenge_26() -> Result<(), Error> {
    // This exercise is trivial: In CTR mode, if we know the underlying plaintext at some location,
    // we can inject any plaintext at the same location by xor'ing the ciphertext with
    // known_plaintext ^ target_plaintext.

    let oracle = Oracle26::new()?;
    let prefix_len = prefix_length(&oracle)?;
    let target_cleartext = b";admin=true";
    let mut ciphertext = oracle.encrypt(&vec![0; target_cleartext.len()])?;
    ciphertext.truncate(prefix_len + target_cleartext.len());
    ciphertext[prefix_len..].xor_inplace(target_cleartext);
    oracle.verify_solution(&ciphertext)
}

struct Sender27 {
    key: Vec<u8>,
}

struct Receiver27 {
    key: Vec<u8>,
}

impl Sender27 {
    pub fn encrypt(&self, input: &[u8]) -> Result<Vec<u8>, Error> {
        // Exclude ';' and '='
        if input
            .iter()
            .any(|&c| !c.is_ascii() || c == b';' || c == b'=')
        {
            bail!("invalid character in input");
        }

        let prefix = b"comment1=cooking%20MCs;userdata=";
        let suffix = b";comment2=%20like%20a%20pound%20of%20bacon";
        let mut cleartext = Vec::with_capacity(prefix.len() + input.len() + suffix.len());
        cleartext.extend_from_slice(prefix);
        cleartext.extend_from_slice(input);
        cleartext.extend_from_slice(suffix);
        cleartext.encrypt(&self.key, Some(&self.key), MODE::CBC)
    }
}

impl Receiver27 {
    pub fn try_decrypt(&self, ciphertext: &[u8]) -> Result<(), Error> {
        let cleartext = ciphertext.decrypt(&self.key, Some(&self.key), MODE::CBC)?;
        if !cleartext.is_ascii() {
            Err(NonAscii(cleartext).into())
        } else {
            Ok(())
        }
    }

    pub fn verify_solution(&self, candidate_key: &[u8]) -> Result<(), Error> {
        compare_eq(&self.key[..], candidate_key)
    }
}

fn get_sender_and_receiver_with_shared_key() -> (Sender27, Receiver27) {
    let secret_key = random_block();
    let sender = Sender27 {
        key: secret_key.clone(),
    };
    let receiver = Receiver27 {
        key: secret_key.clone(),
    };
    (sender, receiver)
}

#[derive(Debug, Fail)]
#[fail(display = "invalid input: {:?}", _0)]
struct NonAscii(Vec<u8>);

fn challenge_27() -> Result<(), Error> {
    let (sender, receiver) = get_sender_and_receiver_with_shared_key();

    let ciphertext = sender.encrypt(&[])?;

    let mut attack_ciphertext = Vec::with_capacity(3 * BLOCK_SIZE);
    attack_ciphertext.extend_from_slice(&ciphertext[0..BLOCK_SIZE]);
    attack_ciphertext.extend_from_slice(&[0; BLOCK_SIZE]);
    attack_ciphertext.extend_from_slice(&ciphertext[0..BLOCK_SIZE]);
    //Push last two blocks to preserve valid padding at the end
    attack_ciphertext.extend_from_slice(&ciphertext[ciphertext.len() - 2 * BLOCK_SIZE..]);

    if let Err(err) = receiver.try_decrypt(&attack_ciphertext) {
        if let Ok(NonAscii(attack_cleartext)) = err.downcast::<NonAscii>() {
            return receiver.verify_solution(
                &attack_cleartext[0..BLOCK_SIZE]
                    .xor(&attack_cleartext[2 * BLOCK_SIZE..3 * BLOCK_SIZE]),
            );
        }
    }

    bail!("attack ciphertext did not deceive the receiver");
}

fn challenge_28() -> Result<(), Error> {
    compare_eq(
        from_hex("2fd4e1c67a2d28fced849ee1bb76e7391b93eb12")?,
        mac_sha1(b"The quick brown fox ", b"jumps over the lazy dog"),
    )
}

// Below we want to create an instance of the Sha1 type from the
// sha1 crate with a specific state. Unfortunately the sha1 crate
// does not offer any way to do this.
// We therfore include a copy of the Sha1 type here. We can then create
// an instance of our type with the desired state and transmute it to Sha1.

struct Sha1_0_20 {
    _state: Sha1State,
    _blocks: Blocks,
    _len: u64,
}

struct Blocks {
    _len: u32,
    _block: [u8; 64],
}

struct Sha1State {
    _state: [u32; 5],
}

impl Sha1_0_20 {
    fn new(state: [u32; 5], len: u64) -> Self {
        Sha1_0_20 {
            _state: Sha1State { _state: state },
            _len: len,
            _blocks: Blocks {
                _len: 0,
                _block: [0; 64],
            },
        }
    }
}

struct Server29 {
    key: Vec<u8>,
}

impl Server29 {
    pub fn new() -> Self {
        let mut rng = rand::thread_rng();
        let key_len = rng.gen_range(1, 200);
        let key: Vec<u8> = rng.gen_iter().take(key_len).collect();
        Server29 { key }
    }

    pub fn get_message_with_mac(&self) -> (Vec<u8>, Vec<u8>) {
        let message =
            b"comment1=cooking%20MCs;userdata=foo;comment2=%20like%20a%20pound%20of%20bacon"
                .to_vec();
        let mac = mac_sha1(&self.key, &message);
        (message, mac)
    }

    pub fn verify_mac(&self, message: &[u8], mac: &[u8]) -> bool {
        mac_sha1(&self.key, message) == mac
    }
}

fn padding(length: usize) -> Vec<u8> {
    let mut w = Vec::new();
    w.push(0x80u8);
    let zero_padding_count = 64 - ((length + 9) % 64);
    for _ in 0..zero_padding_count {
        w.push(0u8);
    }
    w.write_u64::<BigEndian>((length as u64) * 8).unwrap();
    w
}

fn challenge_29() -> Result<(), Error> {
    let server = Server29::new();
    let (input, hash) = server.get_message_with_mac();
    let suffix = b";admin=true";

    // Split the 20 bytes of `hash` into chunks of four
    // bytes and interpret each chunk as a u32.
    let mut state: [u32; 5] = [0; 5];
    for (b, s) in hash.chunks(4).zip(state.iter_mut()) {
        *s = BigEndian::read_u32(b)
    }

    for key_len in 0..200 {
        let secret_len = input.len() + key_len;
        let padding = padding(secret_len);
        let mut m2: Sha1;
        unsafe {
            m2 = mem::transmute(Sha1_0_20::new(state, (secret_len + padding.len()) as u64));
        }
        m2.update(suffix);
        let mac = m2.digest().bytes();

        let mut message = Vec::new();
        message.extend_from_slice(&input);
        message.extend_from_slice(&padding);
        message.extend_from_slice(suffix);
        if server.verify_mac(&message, &mac) {
            return Ok(());
        }
    }
    bail!("No matching message found.");
}

fn challenge_30() -> Result<(), Error> {
    /* Skipping/postponing Challenge 30 because
     * 1) SHA1 was much more interesting anyway,
     * 2) no MD4 implementation seems to be available.  */

    Err(ChallengeError::NotImplemented.into())
}

fn challenge_31() -> Result<(), Error> {
    compare_eq(
        from_hex("de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9")?,
        hmac_sha1(b"key", b"The quick brown fox jumps over the lazy dog"),
    )
}

fn challenge_32() -> Result<(), Error> {
    let skip_exercise = true;

    if skip_exercise {
        return Err(ChallengeError::Skipped("Runs very long.").into());
    }

    let mut rng = rand::thread_rng();
    let key: Vec<u8> = rng.gen_iter().take(20).collect();

    let mut server = hmac_server::start(key);

    hmac_client::run();

    server.close().context("failed to close connection")?;

    Ok(()) //TODO Add proper error handling
}

pub fn add_challenges(challenges: &mut Vec<fn() -> Result<(), Error>>) {
    challenges.push(challenge_25);
    challenges.push(challenge_26);
    challenges.push(challenge_27);
    challenges.push(challenge_28);
    challenges.push(challenge_29);
    challenges.push(challenge_30);
    challenges.push(challenge_31);
    challenges.push(challenge_32);
}
<|endoftext|>


<commit_before>// check-pass

#![feature(type_alias_impl_trait)]
#![allow(dead_code)]

pub trait MyTrait {}

impl MyTrait for bool {}

struct Blah {
    my_foo: Foo,
    my_u8: u8
}

impl Blah {
    fn new() -> Blah {
        Blah {
            my_foo: make_foo(),
            my_u8: 12
        }
    }
    fn into_inner(self) -> (Foo, u8) {
        (self.my_foo, self.my_u8)
    }
}

fn make_foo() -> Foo {
    true
}

type Foo = impl MyTrait;

fn main() {}
<commit_msg>Rollup merge of #88183 - spastorino:add-tait-in-different-tuple-position, r=oli-obk<commit_after>// check-pass

#![feature(type_alias_impl_trait)]
#![allow(dead_code)]

pub trait MyTrait {}

impl MyTrait for bool {}

type Foo = impl MyTrait;

struct Blah {
    my_foo: Foo,
    my_u8: u8,
}

impl Blah {
    fn new() -> Blah {
        Blah { my_foo: make_foo(), my_u8: 12 }
    }
    fn into_inner(self) -> (Foo, u8, Foo) {
        (self.my_foo, self.my_u8, make_foo())
    }
}

fn make_foo() -> Foo {
    true
}

fn main() {}
<|endoftext|>


<commit_before>// check-pass

#![feature(type_alias_impl_trait)]
#![allow(dead_code)]

pub trait MyTrait {}

impl MyTrait for bool {}

struct Blah {
    my_foo: Foo,
    my_u8: u8
}

impl Blah {
    fn new() -> Blah {
        Blah {
            my_foo: make_foo(),
            my_u8: 12
        }
    }
    fn into_inner(self) -> (Foo, u8) {
        (self.my_foo, self.my_u8)
    }
}

fn make_foo() -> Foo {
    true
}

type Foo = impl MyTrait;

fn main() {}
<commit_msg>test TAIT in different positions<commit_after>// check-pass

#![feature(type_alias_impl_trait)]
#![allow(dead_code)]

pub trait MyTrait {}

impl MyTrait for bool {}

type Foo = impl MyTrait;

struct Blah {
    my_foo: Foo,
    my_u8: u8,
}

impl Blah {
    fn new() -> Blah {
        Blah { my_foo: make_foo(), my_u8: 12 }
    }
    fn into_inner(self) -> (Foo, u8, Foo) {
        (self.my_foo, self.my_u8, make_foo())
    }
}

fn make_foo() -> Foo {
    true
}

fn main() {}
<|endoftext|>


<commit_before>// Copyright 2018 Developers of the Rand project.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Implementation for WASM via wasm-bindgen

use rand_core::{Error, ErrorKind};
use super::OsRngImpl;

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
extern "C" {
    pub type Function;
    #[wasm_bindgen(constructor)]
    pub fn new(s: &str) -> Function;
    #[wasm_bindgen(method)]
    pub fn call(this: &Function, self_: &JsValue) -> JsValue;

    pub type This;
    #[wasm_bindgen(method, getter, structural, js_name = self)]
    pub fn self_(me: &This) -> JsValue;
    #[wasm_bindgen(method, getter, structural)]
    pub fn crypto(me: &This) -> JsValue;

    #[derive(Clone, Debug)]
    pub type BrowserCrypto;

    // TODO: these `structural` annotations here ideally wouldn't be here to
    // avoid a JS shim, but for now with feature detection they're
    // unavoidable.
    #[wasm_bindgen(method, js_name = getRandomValues, structural, getter)]
    pub fn get_random_values_fn(me: &BrowserCrypto) -> JsValue;
    #[wasm_bindgen(method, js_name = getRandomValues, structural)]
    pub fn get_random_values(me: &BrowserCrypto, buf: &mut [u8]);

    #[wasm_bindgen(js_name = require)]
    pub fn node_require(s: &str) -> NodeCrypto;

    #[derive(Clone, Debug)]
    pub type NodeCrypto;

    #[wasm_bindgen(method, js_name = randomFillSync, structural)]
    pub fn random_fill_sync(me: &NodeCrypto, buf: &mut [u8]);
}

#[derive(Clone, Debug)]
pub enum OsRng {
    Node(NodeCrypto),
    Browser(BrowserCrypto),
}

impl OsRngImpl for OsRng {
    fn new() -> Result<OsRng, Error> {
        // First up we need to detect if we're running in node.js or a
        // browser. To do this we get ahold of the `this` object (in a bit
        // of a roundabout fashion).
        //
        // Once we have `this` we look at its `self` property, which is
        // only defined on the web (either a main window or web worker).
        let this = Function::new("return this").call(&JsValue::undefined());
        assert!(this != JsValue::undefined());
        let this = This::from(this);
        let is_browser = this.self_() != JsValue::undefined();

        if !is_browser {
            return Ok(OsRng::Node(node_require("crypto")))
        }

        // If `self` is defined then we're in a browser somehow (main window
        // or web worker). Here we want to try to use
        // `crypto.getRandomValues`, but if `crypto` isn't defined we assume
        // we're in an older web browser and the OS RNG isn't available.
        let crypto = this.crypto();
        if crypto.is_undefined() {
            let msg = "self.crypto is undefined";
            return Err(Error::new(ErrorKind::Unavailable, msg))
        }

        // Test if `crypto.getRandomValues` is undefined as well
        let crypto: BrowserCrypto = crypto.into();
        if crypto.get_random_values_fn().is_undefined() {
            let msg = "crypto.getRandomValues is undefined";
            return Err(Error::new(ErrorKind::Unavailable, msg))
        }

        // Ok! `self.crypto.getRandomValues` is a defined value, so let's
        // assume we can do browser crypto.
        Ok(OsRng::Browser(crypto))
    }

    fn fill_chunk(&mut self, dest: &mut [u8]) -> Result<(), Error> {
        match *self {
            OsRng::Node(ref n) => n.random_fill_sync(dest),
            OsRng::Browser(ref n) => n.get_random_values(dest),
        }
        Ok(())
    }

    fn max_chunk_size(&self) -> usize {
        match *self {
            OsRng::Node(_) => usize::max_value(),
            OsRng::Browser(_) => {
                // see https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues
                //
                // where it says:
                //
                // > A QuotaExceededError DOMException is thrown if the
                // > requested length is greater than 65536 bytes.
                65536
            }
        }
    }

    fn method_str(&self) -> &'static str {
        match *self {
            OsRng::Node(_) => "crypto.randomFillSync",
            OsRng::Browser(_) => "crypto.getRandomValues",
        }
    }
}
<commit_msg>Fix `wasm32_bindgen` implementation<commit_after>// Copyright 2018 Developers of the Rand project.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Implementation for WASM via wasm-bindgen

use rand_core::{Error, ErrorKind};
use super::OsRngImpl;
use super::__wbg_shims::*;

use wasm_bindgen::prelude::*;

#[derive(Clone, Debug)]
pub enum OsRng {
    Node(NodeCrypto),
    Browser(BrowserCrypto),
}

impl OsRngImpl for OsRng {
    fn new() -> Result<OsRng, Error> {
        // First up we need to detect if we're running in node.js or a
        // browser. To do this we get ahold of the `this` object (in a bit
        // of a roundabout fashion).
        //
        // Once we have `this` we look at its `self` property, which is
        // only defined on the web (either a main window or web worker).
        let this = Function::new("return this").call(&JsValue::undefined());
        assert!(this != JsValue::undefined());
        let this = This::from(this);
        let is_browser = this.self_() != JsValue::undefined();

        if !is_browser {
            return Ok(OsRng::Node(node_require("crypto")))
        }

        // If `self` is defined then we're in a browser somehow (main window
        // or web worker). Here we want to try to use
        // `crypto.getRandomValues`, but if `crypto` isn't defined we assume
        // we're in an older web browser and the OS RNG isn't available.
        let crypto = this.crypto();
        if crypto.is_undefined() {
            let msg = "self.crypto is undefined";
            return Err(Error::new(ErrorKind::Unavailable, msg))
        }

        // Test if `crypto.getRandomValues` is undefined as well
        let crypto: BrowserCrypto = crypto.into();
        if crypto.get_random_values_fn().is_undefined() {
            let msg = "crypto.getRandomValues is undefined";
            return Err(Error::new(ErrorKind::Unavailable, msg))
        }

        // Ok! `self.crypto.getRandomValues` is a defined value, so let's
        // assume we can do browser crypto.
        Ok(OsRng::Browser(crypto))
    }

    fn fill_chunk(&mut self, dest: &mut [u8]) -> Result<(), Error> {
        match *self {
            OsRng::Node(ref n) => n.random_fill_sync(dest),
            OsRng::Browser(ref n) => n.get_random_values(dest),
        }
        Ok(())
    }

    fn max_chunk_size(&self) -> usize {
        match *self {
            OsRng::Node(_) => usize::max_value(),
            OsRng::Browser(_) => {
                // see https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues
                //
                // where it says:
                //
                // > A QuotaExceededError DOMException is thrown if the
                // > requested length is greater than 65536 bytes.
                65536
            }
        }
    }

    fn method_str(&self) -> &'static str {
        match *self {
            OsRng::Node(_) => "crypto.randomFillSync",
            OsRng::Browser(_) => "crypto.getRandomValues",
        }
    }
}
<|endoftext|>


<commit_before>//! Utilities to assist in testing tasks in this repository.

use std::fmt::Debug;

/// Generates a comprehensive test suite for a sorting algorithm.
///
/// This macro can be used to test any sort function that sorts a mutable slice of objects that
/// implement the `Ord` or `PartialOrd` traits.
///
/// # Example
///
/// ```
/// # #[allow(dead_code)]
/// fn sort<E>(elements: &mut [E]) where E: Ord {
///     elements.sort();
/// }
///
/// #[cfg(test)]
/// mod tests {
///     test_sort!(super::sort);
/// }
/// ```
#[macro_export]
macro_rules! test_sort {
    ( $function:path $(, #[$attr:meta])* ) => {
        use $crate::rand::Rng;

        macro_rules! test_case {
           ($name:ident => $values:expr) => {
                $(#[$attr])*
                #[test]
                fn $name() {
                    let mut values = $values;
                    $function(&mut values);
                    $crate::test_utils::check_sorted(&values);
                }
            }
        }

        test_case!(already_sorted => [-1_i32, 0, 3, 6, 99]);
        test_case!(array_of_strings => ["beach", "hotel", "airplane", "car", "house", "art"]);
        test_case!(empty_vector => Vec::<i32>::new());
        test_case!(one_element_vector => vec![0_i32]);
        test_case!(random_numbers => {
            let mut rng = $crate::rand::thread_rng();
            let mut numbers = [0i32; 10];
            rng.fill(&mut numbers);
            numbers
        });
        test_case!(reverse_sorted_array => [20_i32, 10, 0, -1, -5]);
        test_case!(unsorted_array => [4_i32, 65, 2, -31, 0, 99, 2, 83, 782, 1]);
        test_case!(unsorted_array_positive => [12_i32, 54, 2, 93, 13, 43, 15, 299, 234]);
        test_case!(unsorted_vector_positive => vec![1_i32, 9, 4, 7, 6, 5, 3, 2, 8]);
        test_case!(vector_with_repeated_elements => vec![1_i32, 1, 1, 1, 1]);
    }
}

/// Check if a slice is sorted properly.
pub fn check_sorted<E>(candidate: &[E])
where
    E: Ord + Copy + Debug, // `Copy` is possible here because it's always called with a `Copy` type
{
    let mut sorted = candidate.to_vec();
    sorted.sort(); // order matters. `sort_unstable` might break some tests

    assert_eq!(sorted, candidate);
}

#[cfg(test)]
mod tests {
    #[test]
    fn check_sorted() {
        let sorted = [1, 2, 3, 4, 5];

        super::check_sorted(&sorted);
    }

    #[test]
    #[should_panic]
    fn check_unsorted() {
        let unsorted = [1, 3, 2];

        super::check_sorted(&unsorted);
    }
}
<commit_msg>import `test_sort` in doc test<commit_after>//! Utilities to assist in testing tasks in this repository.

use std::fmt::Debug;

/// Generates a comprehensive test suite for a sorting algorithm.
///
/// This macro can be used to test any sort function that sorts a mutable slice of objects that
/// implement the `Ord` or `PartialOrd` traits.
///
/// # Example
///
/// ```
/// fn sort<E>(elements: &mut [E]) where E: Ord {
///     elements.sort();
/// }
///
/// #[cfg(test)]
/// mod tests {
///     use meta::test_sort;
///
///     test_sort!(super::sort);
/// }
/// ```
#[macro_export]
macro_rules! test_sort {
    ( $function:path $(, #[$attr:meta])* ) => {
        use $crate::rand::Rng;

        macro_rules! test_case {
           ($name:ident => $values:expr) => {
                $(#[$attr])*
                #[test]
                fn $name() {
                    let mut values = $values;
                    $function(&mut values);
                    $crate::test_utils::check_sorted(&values);
                }
            }
        }

        test_case!(already_sorted => [-1_i32, 0, 3, 6, 99]);
        test_case!(array_of_strings => ["beach", "hotel", "airplane", "car", "house", "art"]);
        test_case!(empty_vector => Vec::<i32>::new());
        test_case!(one_element_vector => vec![0_i32]);
        test_case!(random_numbers => {
            let mut rng = $crate::rand::thread_rng();
            let mut numbers = [0i32; 10];
            rng.fill(&mut numbers);
            numbers
        });
        test_case!(reverse_sorted_array => [20_i32, 10, 0, -1, -5]);
        test_case!(unsorted_array => [4_i32, 65, 2, -31, 0, 99, 2, 83, 782, 1]);
        test_case!(unsorted_array_positive => [12_i32, 54, 2, 93, 13, 43, 15, 299, 234]);
        test_case!(unsorted_vector_positive => vec![1_i32, 9, 4, 7, 6, 5, 3, 2, 8]);
        test_case!(vector_with_repeated_elements => vec![1_i32, 1, 1, 1, 1]);
    }
}

/// Check if a slice is sorted properly.
pub fn check_sorted<E>(candidate: &[E])
where
    E: Ord + Copy + Debug, // `Copy` is possible here because it's always called with a `Copy` type
{
    let mut sorted = candidate.to_vec();
    sorted.sort(); // order matters. `sort_unstable` might break some tests

    assert_eq!(sorted, candidate);
}

#[cfg(test)]
mod tests {
    #[test]
    fn check_sorted() {
        let sorted = [1, 2, 3, 4, 5];

        super::check_sorted(&sorted);
    }

    #[test]
    #[should_panic]
    fn check_unsorted() {
        let unsorted = [1, 3, 2];

        super::check_sorted(&unsorted);
    }
}
<|endoftext|>


<commit_before>// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Validates all used crates and extern libraries and loads their metadata

use driver::session::Session;
use metadata::cstore;
use metadata::decoder;
use metadata::loader;

use std::hashmap::HashMap;
use syntax::ast;
use syntax::abi;
use syntax::attr;
use syntax::attr::AttrMetaMethods;
use syntax::codemap::{Span, dummy_sp};
use syntax::diagnostic::span_handler;
use syntax::parse::token;
use syntax::parse::token::ident_interner;
use syntax::pkgid::PkgId;
use syntax::visit;

// Traverses an AST, reading all the information about use'd crates and extern
// libraries necessary for later resolving, typechecking, linking, etc.
pub fn read_crates(sess: Session,
                   crate: &ast::Crate,
                   os: loader::Os,
                   intr: @ident_interner) {
    let e = @mut Env {
        sess: sess,
        os: os,
        crate_cache: @mut ~[],
        next_crate_num: 1,
        intr: intr
    };
    let mut v = ReadCrateVisitor{ e:e };
    visit_crate(e, crate);
    visit::walk_crate(&mut v, crate, ());
    dump_crates(*e.crate_cache);
    warn_if_multiple_versions(e, sess.diagnostic(), *e.crate_cache);
}

struct ReadCrateVisitor { e:@mut Env }
impl visit::Visitor<()> for ReadCrateVisitor {
    fn visit_view_item(&mut self, a:&ast::view_item, _:()) {
        visit_view_item(self.e, a);
        visit::walk_view_item(self, a, ());
    }
    fn visit_item(&mut self, a:@ast::item, _:()) {
        visit_item(self.e, a);
        visit::walk_item(self, a, ());
    }
}

#[deriving(Clone)]
struct cache_entry {
    cnum: ast::CrateNum,
    span: Span,
    hash: @str,
    pkgid: PkgId,
}

fn dump_crates(crate_cache: &[cache_entry]) {
    debug!("resolved crates:");
    for entry in crate_cache.iter() {
        debug!("cnum: {:?}", entry.cnum);
        debug!("span: {:?}", entry.span);
        debug!("hash: {:?}", entry.hash);
    }
}

fn warn_if_multiple_versions(e: @mut Env,
                             diag: @mut span_handler,
                             crate_cache: &[cache_entry]) {
    if crate_cache.len() != 0u {
        let name = crate_cache[crate_cache.len() - 1].pkgid.name.clone();

        let (matches, non_matches) = crate_cache.partitioned(|entry|
            name == entry.pkgid.name);

        assert!(!matches.is_empty());

        if matches.len() != 1u {
            diag.handler().warn(
                format!("using multiple versions of crate `{}`", name));
            for match_ in matches.iter() {
                diag.span_note(match_.span, "used here");
                loader::note_pkgid_attr(diag, &match_.pkgid);
            }
        }

        warn_if_multiple_versions(e, diag, non_matches);
    }
}

struct Env {
    sess: Session,
    os: loader::Os,
    crate_cache: @mut ~[cache_entry],
    next_crate_num: ast::CrateNum,
    intr: @ident_interner
}

fn visit_crate(e: &Env, c: &ast::Crate) {
    let cstore = e.sess.cstore;

    for a in c.attrs.iter().filter(|m| "link_args" == m.name()) {
        match a.value_str() {
          Some(ref linkarg) => {
            cstore.add_used_link_args(*linkarg);
          }
          None => {/* fallthrough */ }
        }
    }
}

fn visit_view_item(e: @mut Env, i: &ast::view_item) {
    match i.node {
      ast::view_item_extern_mod(ident, path_opt, _, id) => {
          let ident = token::ident_to_str(&ident);
          debug!("resolving extern mod stmt. ident: {:?} path_opt: {:?}",
                 ident, path_opt);
          let (name, version) = match path_opt {
              Some((path_str, _)) => {
                  let pkgid: Option<PkgId> = from_str(path_str);
                  match pkgid {
                      None => (@"", @""),
                      Some(pkgid) => {
                          let version = match pkgid.version {
                              None => @"",
                              Some(ref ver) => ver.to_managed(),
                          };
                          (pkgid.name.to_managed(), version)
                      }
                  }
              }
              None => (ident, @""),
          };
          let cnum = resolve_crate(e,
                                   ident,
                                   name,
                                   version,
                                   @"",
                                   i.span);
          e.sess.cstore.add_extern_mod_stmt_cnum(id, cnum);
      }
      _ => ()
  }
}

fn visit_item(e: &Env, i: @ast::item) {
    match i.node {
        ast::item_foreign_mod(ref fm) => {
            if fm.abis.is_rust() || fm.abis.is_intrinsic() {
                return;
            }

            // First, add all of the custom link_args attributes
            let cstore = e.sess.cstore;
            let link_args = i.attrs.iter()
                .filter_map(|at| if "link_args" == at.name() {Some(at)} else {None})
                .to_owned_vec();
            for m in link_args.iter() {
                match m.value_str() {
                    Some(linkarg) => {
                        cstore.add_used_link_args(linkarg);
                    }
                    None => { /* fallthrough */ }
                }
            }

            // Next, process all of the #[link(..)]-style arguments
            let cstore = e.sess.cstore;
            let link_args = i.attrs.iter()
                .filter_map(|at| if "link" == at.name() {Some(at)} else {None})
                .to_owned_vec();
            for m in link_args.iter() {
                match m.meta_item_list() {
                    Some(items) => {
                        let kind = items.iter().find(|k| {
                            "kind" == k.name()
                        }).and_then(|a| a.value_str());
                        let kind = match kind {
                            Some(k) => {
                                if "static" == k {
                                    cstore::NativeStatic
                                } else if e.sess.targ_cfg.os == abi::OsMacos &&
                                          "framework" == k {
                                    cstore::NativeFramework
                                } else if "framework" == k {
                                    e.sess.span_err(m.span,
                                        "native frameworks are only available \
                                         on OSX targets");
                                    cstore::NativeUnknown
                                } else {
                                    e.sess.span_err(m.span,
                                        format!("unknown kind: `{}`", k));
                                    cstore::NativeUnknown
                                }
                            }
                            None => cstore::NativeUnknown
                        };
                        let n = items.iter().find(|n| {
                            "name" == n.name()
                        }).and_then(|a| a.value_str());
                        let n = match n {
                            Some(n) => n,
                            None => {
                                e.sess.span_err(m.span,
                                    "#[link(...)] specified without \
                                     `name = \"foo\"`");
                                @"foo"
                            }
                        };
                        if n.is_empty() {
                            e.sess.span_err(m.span, "#[link(name = \"\")] given with empty name");
                        } else {
                            cstore.add_used_library(n.to_owned(), kind);
                        }
                    }
                    None => {}
                }
            }
        }
        _ => { }
    }
}

fn existing_match(e: &Env, name: @str, version: @str, hash: &str) -> Option<ast::CrateNum> {
    for c in e.crate_cache.iter() {
        let pkgid_version = match c.pkgid.version {
            None => @"0.0",
            Some(ref ver) => ver.to_managed(),
        };
        if (name.is_empty() || c.pkgid.name.to_managed() == name) &&
            (version.is_empty() || pkgid_version == version) &&
            (hash.is_empty() || c.hash.as_slice() == hash) {
            return Some(c.cnum);
        }
    }
    None
}

fn resolve_crate(e: @mut Env,
                 ident: @str,
                 name: @str,
                 version: @str,
                 hash: @str,
                 span: Span)
              -> ast::CrateNum {
    match existing_match(e, name, version, hash) {
      None => {
        let load_ctxt = loader::Context {
            sess: e.sess,
            span: span,
            ident: ident,
            name: name,
            version: version,
            hash: hash,
            os: e.os,
            intr: e.intr
        };
        let loader::Library {
            dylib, rlib, metadata
        } = load_ctxt.load_library_crate();

        let attrs = decoder::get_crate_attributes(metadata.as_slice());
        let pkgid = attr::find_pkgid(attrs).unwrap();
        let hash = decoder::get_crate_hash(metadata.as_slice());

        // Claim this crate number and cache it
        let cnum = e.next_crate_num;
        e.crate_cache.push(cache_entry {
            cnum: cnum,
            span: span,
            hash: hash,
            pkgid: pkgid,
        });
        e.next_crate_num += 1;

        // Now resolve the crates referenced by this crate
        let cnum_map = resolve_crate_deps(e, metadata.as_slice());

        let cmeta = @cstore::crate_metadata {
            name: name,
            data: metadata,
            cnum_map: cnum_map,
            cnum: cnum
        };

        let cstore = e.sess.cstore;
        cstore.set_crate_data(cnum, cmeta);
        cstore.add_used_crate_source(cstore::CrateSource {
            dylib: dylib,
            rlib: rlib,
            cnum: cnum,
        });
        return cnum;
      }
      Some(cnum) => {
        return cnum;
      }
    }
}

// Go through the crate metadata and load any crates that it references
fn resolve_crate_deps(e: @mut Env, cdata: &[u8]) -> cstore::cnum_map {
    debug!("resolving deps of external crate");
    // The map from crate numbers in the crate we're resolving to local crate
    // numbers
    let mut cnum_map = HashMap::new();
    let r = decoder::get_crate_deps(cdata);
    for dep in r.iter() {
        let extrn_cnum = dep.cnum;
        let cname_str = token::ident_to_str(&dep.name);
        debug!("resolving dep crate {} ver: {} hash: {}",
               cname_str, dep.vers, dep.hash);
        match existing_match(e, cname_str, dep.vers, dep.hash) {
          Some(local_cnum) => {
            debug!("already have it");
            // We've already seen this crate
            cnum_map.insert(extrn_cnum, local_cnum);
          }
          None => {
            debug!("need to load it");
            // This is a new one so we've got to load it
            // FIXME (#2404): Need better error reporting than just a bogus
            // span.
            let fake_span = dummy_sp();
            let local_cnum = resolve_crate(e, cname_str, cname_str, dep.vers,
                                           dep.hash, fake_span);
            cnum_map.insert(extrn_cnum, local_cnum);
          }
        }
    }
    return @mut cnum_map;
}
<commit_msg>librustc: De-`@mut` `creader::Env`<commit_after>// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Validates all used crates and extern libraries and loads their metadata

use driver::session::Session;
use metadata::cstore;
use metadata::decoder;
use metadata::loader;

use std::hashmap::HashMap;
use syntax::ast;
use syntax::abi;
use syntax::attr;
use syntax::attr::AttrMetaMethods;
use syntax::codemap::{Span, dummy_sp};
use syntax::diagnostic::span_handler;
use syntax::parse::token;
use syntax::parse::token::ident_interner;
use syntax::pkgid::PkgId;
use syntax::visit;

// Traverses an AST, reading all the information about use'd crates and extern
// libraries necessary for later resolving, typechecking, linking, etc.
pub fn read_crates(sess: Session,
                   crate: &ast::Crate,
                   os: loader::Os,
                   intr: @ident_interner) {
    let mut e = Env {
        sess: sess,
        os: os,
        crate_cache: @mut ~[],
        next_crate_num: 1,
        intr: intr
    };
    visit_crate(&e, crate);
    {
        let mut v = ReadCrateVisitor {
            e: &mut e
        };
        visit::walk_crate(&mut v, crate, ());
    }
    dump_crates(*e.crate_cache);
    warn_if_multiple_versions(&mut e, sess.diagnostic(), *e.crate_cache);
}

struct ReadCrateVisitor<'a> {
    e: &'a mut Env,
}

impl<'a> visit::Visitor<()> for ReadCrateVisitor<'a> {
    fn visit_view_item(&mut self, a:&ast::view_item, _:()) {
        visit_view_item(self.e, a);
        visit::walk_view_item(self, a, ());
    }
    fn visit_item(&mut self, a:@ast::item, _:()) {
        visit_item(self.e, a);
        visit::walk_item(self, a, ());
    }
}

#[deriving(Clone)]
struct cache_entry {
    cnum: ast::CrateNum,
    span: Span,
    hash: @str,
    pkgid: PkgId,
}

fn dump_crates(crate_cache: &[cache_entry]) {
    debug!("resolved crates:");
    for entry in crate_cache.iter() {
        debug!("cnum: {:?}", entry.cnum);
        debug!("span: {:?}", entry.span);
        debug!("hash: {:?}", entry.hash);
    }
}

fn warn_if_multiple_versions(e: &mut Env,
                             diag: @mut span_handler,
                             crate_cache: &[cache_entry]) {
    if crate_cache.len() != 0u {
        let name = crate_cache[crate_cache.len() - 1].pkgid.name.clone();

        let (matches, non_matches) = crate_cache.partitioned(|entry|
            name == entry.pkgid.name);

        assert!(!matches.is_empty());

        if matches.len() != 1u {
            diag.handler().warn(
                format!("using multiple versions of crate `{}`", name));
            for match_ in matches.iter() {
                diag.span_note(match_.span, "used here");
                loader::note_pkgid_attr(diag, &match_.pkgid);
            }
        }

        warn_if_multiple_versions(e, diag, non_matches);
    }
}

struct Env {
    sess: Session,
    os: loader::Os,
    crate_cache: @mut ~[cache_entry],
    next_crate_num: ast::CrateNum,
    intr: @ident_interner
}

fn visit_crate(e: &Env, c: &ast::Crate) {
    let cstore = e.sess.cstore;

    for a in c.attrs.iter().filter(|m| "link_args" == m.name()) {
        match a.value_str() {
          Some(ref linkarg) => {
            cstore.add_used_link_args(*linkarg);
          }
          None => {/* fallthrough */ }
        }
    }
}

fn visit_view_item(e: &mut Env, i: &ast::view_item) {
    match i.node {
      ast::view_item_extern_mod(ident, path_opt, _, id) => {
          let ident = token::ident_to_str(&ident);
          debug!("resolving extern mod stmt. ident: {:?} path_opt: {:?}",
                 ident, path_opt);
          let (name, version) = match path_opt {
              Some((path_str, _)) => {
                  let pkgid: Option<PkgId> = from_str(path_str);
                  match pkgid {
                      None => (@"", @""),
                      Some(pkgid) => {
                          let version = match pkgid.version {
                              None => @"",
                              Some(ref ver) => ver.to_managed(),
                          };
                          (pkgid.name.to_managed(), version)
                      }
                  }
              }
              None => (ident, @""),
          };
          let cnum = resolve_crate(e,
                                   ident,
                                   name,
                                   version,
                                   @"",
                                   i.span);
          e.sess.cstore.add_extern_mod_stmt_cnum(id, cnum);
      }
      _ => ()
  }
}

fn visit_item(e: &Env, i: @ast::item) {
    match i.node {
        ast::item_foreign_mod(ref fm) => {
            if fm.abis.is_rust() || fm.abis.is_intrinsic() {
                return;
            }

            // First, add all of the custom link_args attributes
            let cstore = e.sess.cstore;
            let link_args = i.attrs.iter()
                .filter_map(|at| if "link_args" == at.name() {Some(at)} else {None})
                .to_owned_vec();
            for m in link_args.iter() {
                match m.value_str() {
                    Some(linkarg) => {
                        cstore.add_used_link_args(linkarg);
                    }
                    None => { /* fallthrough */ }
                }
            }

            // Next, process all of the #[link(..)]-style arguments
            let cstore = e.sess.cstore;
            let link_args = i.attrs.iter()
                .filter_map(|at| if "link" == at.name() {Some(at)} else {None})
                .to_owned_vec();
            for m in link_args.iter() {
                match m.meta_item_list() {
                    Some(items) => {
                        let kind = items.iter().find(|k| {
                            "kind" == k.name()
                        }).and_then(|a| a.value_str());
                        let kind = match kind {
                            Some(k) => {
                                if "static" == k {
                                    cstore::NativeStatic
                                } else if e.sess.targ_cfg.os == abi::OsMacos &&
                                          "framework" == k {
                                    cstore::NativeFramework
                                } else if "framework" == k {
                                    e.sess.span_err(m.span,
                                        "native frameworks are only available \
                                         on OSX targets");
                                    cstore::NativeUnknown
                                } else {
                                    e.sess.span_err(m.span,
                                        format!("unknown kind: `{}`", k));
                                    cstore::NativeUnknown
                                }
                            }
                            None => cstore::NativeUnknown
                        };
                        let n = items.iter().find(|n| {
                            "name" == n.name()
                        }).and_then(|a| a.value_str());
                        let n = match n {
                            Some(n) => n,
                            None => {
                                e.sess.span_err(m.span,
                                    "#[link(...)] specified without \
                                     `name = \"foo\"`");
                                @"foo"
                            }
                        };
                        if n.is_empty() {
                            e.sess.span_err(m.span, "#[link(name = \"\")] given with empty name");
                        } else {
                            cstore.add_used_library(n.to_owned(), kind);
                        }
                    }
                    None => {}
                }
            }
        }
        _ => { }
    }
}

fn existing_match(e: &Env, name: @str, version: @str, hash: &str) -> Option<ast::CrateNum> {
    for c in e.crate_cache.iter() {
        let pkgid_version = match c.pkgid.version {
            None => @"0.0",
            Some(ref ver) => ver.to_managed(),
        };
        if (name.is_empty() || c.pkgid.name.to_managed() == name) &&
            (version.is_empty() || pkgid_version == version) &&
            (hash.is_empty() || c.hash.as_slice() == hash) {
            return Some(c.cnum);
        }
    }
    None
}

fn resolve_crate(e: &mut Env,
                 ident: @str,
                 name: @str,
                 version: @str,
                 hash: @str,
                 span: Span)
              -> ast::CrateNum {
    match existing_match(e, name, version, hash) {
      None => {
        let load_ctxt = loader::Context {
            sess: e.sess,
            span: span,
            ident: ident,
            name: name,
            version: version,
            hash: hash,
            os: e.os,
            intr: e.intr
        };
        let loader::Library {
            dylib, rlib, metadata
        } = load_ctxt.load_library_crate();

        let attrs = decoder::get_crate_attributes(metadata.as_slice());
        let pkgid = attr::find_pkgid(attrs).unwrap();
        let hash = decoder::get_crate_hash(metadata.as_slice());

        // Claim this crate number and cache it
        let cnum = e.next_crate_num;
        e.crate_cache.push(cache_entry {
            cnum: cnum,
            span: span,
            hash: hash,
            pkgid: pkgid,
        });
        e.next_crate_num += 1;

        // Now resolve the crates referenced by this crate
        let cnum_map = resolve_crate_deps(e, metadata.as_slice());

        let cmeta = @cstore::crate_metadata {
            name: name,
            data: metadata,
            cnum_map: cnum_map,
            cnum: cnum
        };

        let cstore = e.sess.cstore;
        cstore.set_crate_data(cnum, cmeta);
        cstore.add_used_crate_source(cstore::CrateSource {
            dylib: dylib,
            rlib: rlib,
            cnum: cnum,
        });
        return cnum;
      }
      Some(cnum) => {
        return cnum;
      }
    }
}

// Go through the crate metadata and load any crates that it references
fn resolve_crate_deps(e: &mut Env, cdata: &[u8]) -> cstore::cnum_map {
    debug!("resolving deps of external crate");
    // The map from crate numbers in the crate we're resolving to local crate
    // numbers
    let mut cnum_map = HashMap::new();
    let r = decoder::get_crate_deps(cdata);
    for dep in r.iter() {
        let extrn_cnum = dep.cnum;
        let cname_str = token::ident_to_str(&dep.name);
        debug!("resolving dep crate {} ver: {} hash: {}",
               cname_str, dep.vers, dep.hash);
        match existing_match(e, cname_str, dep.vers, dep.hash) {
          Some(local_cnum) => {
            debug!("already have it");
            // We've already seen this crate
            cnum_map.insert(extrn_cnum, local_cnum);
          }
          None => {
            debug!("need to load it");
            // This is a new one so we've got to load it
            // FIXME (#2404): Need better error reporting than just a bogus
            // span.
            let fake_span = dummy_sp();
            let local_cnum = resolve_crate(e, cname_str, cname_str, dep.vers,
                                           dep.hash, fake_span);
            cnum_map.insert(extrn_cnum, local_cnum);
          }
        }
    }
    return @mut cnum_map;
}
<|endoftext|>


<commit_before>extern crate proc_macro;
extern crate syn;
#[macro_use]
extern crate quote;

use proc_macro::TokenStream;

#[proc_macro_derive(PerlinDocument)]
pub fn perlin_document(input: TokenStream) -> TokenStream {
    // Standard procedure when it comes to custom derive
    // See https://doc.rust-lang.org/book/procedural-macros.html
    let s = input.to_string();
    let ast = syn::parse_macro_input(&s).expect("AST: WHAT!?");

    let gen = impl_perlin_document(&ast);
    gen.parse().expect("GEN: WHAT!?")
}

fn impl_perlin_document(ast: &syn::MacroInput) -> quote::Tokens {
    let name = &ast.ident;
    if let syn::Body::Struct(ref variant_data) = ast.body {
        let commit = commit(variant_data.fields());
        let index_field = index_field(variant_data.fields());

        let params = create_params(variant_data.fields());
        let page_caches = create_page_caches(variant_data.fields());
        let field_creations = create_field_creations(variant_data.fields());

        quote! {
            use std::path::Path;
            impl #name {
                pub fn create(path: &Path #(,#params)*) -> Self {
                    use perlin_core::page_manager::{RamPageCache, FsPageManager};
                    #(#page_caches)*

                    #name {
                        #(#field_creations,)*
                    }
                }
            }

            impl PerlinDocument for #name {
                fn commit(&mut self) {
                    #(#commit)*
                }

                fn index_field(&mut self, doc_id: DocId, field_name: &str, field_contents: &str) {
                    match field_name {
                        #(#index_field,)*
                        _ => {panic!("WHAT!?")}
                    };
                }
            }
        }
    } else {
        panic!("PerlinDocument is only implemented for structs not enums!");
    }
}

fn create_params(fields: &[syn::Field]) -> Vec<quote::Tokens> {
    let mut result = Vec::new();

    for field in fields {
        let ident = &field.ident;
        let gen_params = get_generics_from_field(&field.ty);

        result.push(quote!(#ident: Option<Pipeline#gen_params>));
    }

    result
}

fn get_generics_from_field(field: &syn::Ty) -> quote::Tokens {
    if let &syn::Ty::Path(_, ref path) = field {
        for segment in &path.segments {
            if segment.ident == "Field" {
                let params = &segment.parameters;;
                return quote!(#params);
            }
        }
    }
    panic!("NO FIELD FOUND!");
}

fn create_page_caches(fields: &[syn::Field]) -> Vec<quote::Tokens> {
    let mut result = Vec::new();

    for field in fields {
        let cache_ident = syn::Ident::from(format!("{}_page_cache", &field.ident.clone().unwrap()).to_string());
        result.push(quote!(
            let #cache_ident = RamPageCache::new(FsPageManager::new(&path.join(stringify!(#cache_ident))));));
    }

    result
}

fn create_field_creations(fields: &[syn::Field]) -> Vec<quote::Tokens> {
    let mut result = Vec::new();
    for field in fields {
        let cache_ident = syn::Ident::from(format!("{}_page_cache", &field.ident.clone().unwrap()).to_string());
        let ident = &field.ident;
        result.push(quote!(
            #ident: Field::create(#cache_ident, #ident)
            ));
    }

    result
}

fn commit(fields: &[syn::Field]) -> Vec<quote::Tokens> {
    let mut result = Vec::new();

    for field in fields {
        let ident = &field.ident;
        result.push(quote! {
            self.#ident.index.commit();
        });
    }
    result
}

fn index_field(fields: &[syn::Field]) -> Vec<quote::Tokens> {
    let mut result = Vec::new();

    for field in fields {
        let ident = &field.ident;
        result.push(quote! {
            stringify!(#ident) =>
            {
                let pipe = if let Some(ref pipeline) = self.#ident.pipeline
                {
                    pipeline()
                }
                else {
                    panic!(concat!("No pipeline found for ", stringify!(#ident)))
                };
                pipe(doc_id, self, field_contents);
            }});
    }
    result
}
<commit_msg>add surrounding module<commit_after>extern crate proc_macro;
extern crate syn;
#[macro_use]
extern crate quote;

use proc_macro::TokenStream;

#[proc_macro_derive(PerlinDocument)]
pub fn perlin_document(input: TokenStream) -> TokenStream {
    // Standard procedure when it comes to custom derive
    // See https://doc.rust-lang.org/book/procedural-macros.html
    let s = input.to_string();
    let ast = syn::parse_macro_input(&s).expect("AST: WHAT!?");

    let gen = impl_perlin_document(&ast);
    gen.parse().expect("GEN: WHAT!?")
}

fn impl_perlin_document(ast: &syn::MacroInput) -> quote::Tokens {
    let name = &ast.ident;
    if let syn::Body::Struct(ref variant_data) = ast.body {
        let commit = commit(variant_data.fields());
        let index_field = index_field(variant_data.fields());

        let params = create_params(variant_data.fields());
        let page_caches = create_page_caches(variant_data.fields());
        let field_creations = create_field_creations(variant_data.fields());

        quote! {
            pub mod perlin_document_impl {
                use super::*;

                use std::path::Path;

                impl #name {
                    pub fn create(path: &Path #(,#params)*) -> Self {
                        use perlin_core::page_manager::{RamPageCache, FsPageManager};
                        #(#page_caches)*

                        #name {
                            #(#field_creations,)*
                        }
                    }
                }

                impl PerlinDocument for #name {
                    fn commit(&mut self) {
                        #(#commit)*
                    }

                    fn index_field(&mut self, doc_id: DocId, field_name: &str, field_contents: &str) {
                        match field_name {
                            #(#index_field,)*
                            _ => {panic!("WHAT!?")}
                        };
                    }
                }
            }
        }
    } else {
        panic!("PerlinDocument is only implemented for structs not enums!");
    }
}

fn create_params(fields: &[syn::Field]) -> Vec<quote::Tokens> {
    let mut result = Vec::new();

    for field in fields {
        let ident = &field.ident;
        let gen_params = get_generics_from_field(&field.ty);

        result.push(quote!(#ident: Option<Pipeline#gen_params>));
    }

    result
}

fn get_generics_from_field(field: &syn::Ty) -> quote::Tokens {
    if let &syn::Ty::Path(_, ref path) = field {
        for segment in &path.segments {
            if segment.ident == "Field" {
                let params = &segment.parameters;;
                return quote!(#params);
            }
        }
    }
    panic!("NO FIELD FOUND!");
}

fn create_page_caches(fields: &[syn::Field]) -> Vec<quote::Tokens> {
    let mut result = Vec::new();

    for field in fields {
        let cache_ident = syn::Ident::from(format!("{}_page_cache", &field.ident.clone().unwrap()).to_string());
        result.push(quote!(
            let #cache_ident = RamPageCache::new(FsPageManager::new(&path.join(stringify!(#cache_ident))));));
    }

    result
}

fn create_field_creations(fields: &[syn::Field]) -> Vec<quote::Tokens> {
    let mut result = Vec::new();
    for field in fields {
        let cache_ident = syn::Ident::from(format!("{}_page_cache", &field.ident.clone().unwrap()).to_string());
        let ident = &field.ident;
        result.push(quote!(
            #ident: Field::create(#cache_ident, #ident)
            ));
    }

    result
}

fn commit(fields: &[syn::Field]) -> Vec<quote::Tokens> {
    let mut result = Vec::new();

    for field in fields {
        let ident = &field.ident;
        result.push(quote! {
            self.#ident.index.commit();
        });
    }
    result
}

fn index_field(fields: &[syn::Field]) -> Vec<quote::Tokens> {
    let mut result = Vec::new();

    for field in fields {
        let ident = &field.ident;
        result.push(quote! {
            stringify!(#ident) =>
            {
                let pipe = if let Some(ref pipeline) = self.#ident.pipeline
                {
                    pipeline()
                }
                else {
                    panic!(concat!("No pipeline found for ", stringify!(#ident)))
                };
                pipe(doc_id, self, field_contents);
            }});
    }
    result
}
<|endoftext|>