/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::io::Error;

///! Async job scheduling utilities for a blocking application
///!
///! We have a blocking application. We have async libraries. This crate provides common utilities
///! for communicating between the blocking world and the async world. It is intended to be a guide
///! so that not all developers have to get in depth understanding of Tokio in order to use async
///! functions.
///!
///! The crate sets up a common Runtime that all async tasks run on. We use a threaded scheduler
///! which enables parallelism. The async code is expected to be ran in parallel, not just
///! concurrently. As a reminder, Python has concurrency with multiple threads but no parallelism
///! because of the global interpreter lock.
///! The Runtime will get forcefully shut down when the main thread exits. Any running background
///! work will be lost at that time. This is not a hard requirement though, we can be tweak it to
///! wait for tasks to finish but requires some ceremony around the Runtime. Since we have no need
///! for that right now so that feature is skipped for now.
///!
///! TODO(T74221415): monitoring, signal handling
use futures::future::Future;
use futures::stream::BoxStream;
use futures::stream::Stream;
use futures::stream::StreamExt;
use once_cell::sync::Lazy;
use tokio::runtime::Builder as RuntimeBuilder;
use tokio::runtime::Runtime;
use tokio::task::JoinHandle;

static RUNTIME: Lazy<Runtime> = Lazy::new(|| {
    let nproc = num_cpus::get();
    RuntimeBuilder::new_multi_thread()
        .worker_threads(nproc.min(8))
        .enable_all()
        .build()
        .expect("failed to initialize the async runtime")
});
pub static STREAM_BUFFER_SIZE: usize = 128;

/// Spawn a task using the runtime.
pub fn spawn<T>(task: T) -> JoinHandle<T::Output>
where
    T: Future + Send + 'static,
    T::Output: Send + 'static,
{
    RUNTIME.spawn(task)
}

/// Run the provided function on an executor dedicated to blocking operations.
pub fn spawn_blocking<F, R>(func: F) -> JoinHandle<R>
where
    F: FnOnce() -> R + Send + 'static,
    R: Send + 'static,
{
    RUNTIME.spawn_blocking(func)
}

/// Blocks the current thread while waiting for the computation defined by the Future `f`.
pub fn block_on<F>(f: F) -> F::Output
where
    F: Future,
{
    RUNTIME.block_on(f)
}

/// Takes an async stream and provide its contents in the form of a regular iterator.
///
/// If processing one of the items in the stream panics then the stream stops without further
/// feedback. It wouldn't be a bad idea to propagate the issue somehow.
///
/// This implementation will poll as long as there is space in the buffer. The sync iterator will
/// be returned practically right after the function is called. Calls to `next()` on the
/// iterator will block as long as there are no items to read from the buffer and stream items are
/// in flight. Calls to `next()` return as soon as items are available to pop from the buffer.
/// `STREAM_BUFFER_SIZE` determines the default size of the buffer. If this value is inconvenient
/// then check out `RunStreamOptions` which allows tweaking the buffer size. The buffering is just
/// to streamline the async/parallel computation and manage some of the synchronization overhead.
/// Unless the stream `s` is buffered, the items in the stream will be processed one after the
/// other.
/// When you want to process items strictly one after the other without any sort of buffering, you
/// should use `block_on(my_stream.next())`.
/// Example.
///  1. unbuffered stream, stream_to_iter buffer size = 2.
///     - the stream has the first item polled and the result is added to the buffer
///     - the stream continues with polling the second item; while this is happening the iterator
///     that is returned may start being consumed releasing capacity in the buffer; for our example
///     let's say that the blocking thread hasn't reached that point yet and the stream fills the
///     buffer using the second item
///     - the stream will now poll the third item; assuming that the buffer is still full when the
///     computation is done, it will yield the thread that is is running on until the blocking
///     thread reads one of the items in the buffer.
///  2. buffered stream over 2 items (ordered), stream_to_iter buffer = 2
///     - the stream will take 2 futures from the underlying iterator and poll on them; when the
///     first one returns it enquees the result in our buffer and polls the third future in the
///     underlying stream. Assuming that f(x) produces r(x) we could write:
///      stream: #f1, *f2, *f3, f4, f5
///      buffer: r1
///     - let's assume that the blocking thread will not consume the buffer and the next future
///     finishes; the result then fills the buffer and f4 will get polled:
///      stream: #f1, #f2, *f3, *f4, f5
///      buffer: r1, r2
///     - adding the result of the third future to the buffer will have to wait until the blocking
///     thread consumes the returned iterator; only after that will the stream proceed with
///     polling the fifth future in the stream
pub fn stream_to_iter<S>(s: S) -> RunStream<S::Item>
where
    S: Stream + Unpin + Send + 'static,
    S::Item: Send,
{
    RunStreamOptions::new().run(s)
}

/// See `stream_to_iter`. Allows tweaking run parameters. See individual methods for parameter
/// details.
pub struct RunStreamOptions {
    buffer_size: usize,
}

impl RunStreamOptions {
    pub fn new() -> Self {
        Self {
            buffer_size: STREAM_BUFFER_SIZE,
        }
    }

    /// When dealing with heavy computation or objects a smaller buffer size may be appropriate.
    /// The current implementation does not provide a means to completely wait on polling the
    /// second item until the blocking thread reads the first value.
    pub fn buffer_size(&mut self, buffer_size: usize) -> &mut Self {
        self.buffer_size = buffer_size;
        self
    }

    /// Takes an async stream and provide it's contents in the form of a regular iterator.
    /// See `stream_to_iter`.
    pub fn run<S>(&self, mut s: S) -> RunStream<S::Item>
    where
        S: Stream + Unpin + Send + 'static,
        S::Item: Send,
    {
        // Why use a channel vs using `std::iter::from_fn`
        // It is probably a bit easier to reason about what happens when using the channel. The
        // implementation details of the executor and the buffered stream don't come in discussion
        // as when directly scheduling the next future. It's a bit of insurance against changes and
        // it separates the two worlds more clearly.  The channel approach can be optimized to
        // reduce entering the async runtime context when the stream is completed faster that it is
        // processed on the main thread. We could also add multiple consumers.
        let (tx, rx) = tokio::sync::mpsc::channel(self.buffer_size);
        let _guard = RUNTIME.enter();
        tokio::spawn(async move {
            while let Some(v) = s.next().await {
                if tx.send(v).await.is_err() {
                    // receiver dropped; TODO(T74252041): add logging
                    return;
                }
            }
        });
        RunStream { rx: Some(rx) }
    }
}

/// Blocking thread handler for receiving the results following processing a `Stream`.
pub struct RunStream<T> {
    // Option is used to workaround lifetime in Iterator::next.
    rx: Option<tokio::sync::mpsc::Receiver<T>>,
}

impl<T: Send + 'static> Iterator for RunStream<T> {
    type Item = T;

    /// Returns the items extracted from processing the stream. Will return `None` when the stream's
    /// end is reached or when processing an item panics.
    /// See `stream_to_iter`.
    fn next(&mut self) -> Option<Self::Item> {
        let mut rx = self.rx.take().unwrap();
        let (next, rx) = block_on(async {
            let next = rx.recv().await;
            (next, rx)
        });
        self.rx = Some(rx);
        next
    }
}

/// Convert a blocking iterator to an async stream.
///
/// Unlike `futures::stream::iter`, the iterator's `next()` function could be
/// blocking.
pub fn iter_to_stream<I: Send + 'static>(
    iter: impl Iterator<Item = I> + Send + 'static,
) -> BoxStream<'static, I> {
    let stream = futures::stream::unfold(iter, |mut iter| async {
        let (item, iter) = tokio::task::spawn_blocking(move || {
            let item = iter.next();
            (item, iter)
        })
        .await
        .unwrap();
        item.map(|item| (item, iter))
    });
    Box::pin(stream.fuse())
}
/// Blocks on the future from python code, interrupting future execution on Ctrl+C
/// Wraps future's output with Result that returns error when interrupted
/// If future already returns Result, use try_block_unless_interrupted
///
/// Send on this future only needed to prevent including `py` into this future
pub fn block_unless_interrupted<F: Future>(f: F) -> Result<F::Output, Error> {
    block_on(unless_interrupted(f))
}

/// Same as block_unless_interrupted but for futures that returns Result
pub fn try_block_unless_interrupted<O, E, F: Future<Output = Result<O, E>>>(f: F) -> Result<O, E>
where
    E: Send,
    E: From<Error>,
{
    block_on(async move { unless_interrupted(f).await? })
}

async fn unless_interrupted<F: Future>(f: F) -> Result<F::Output, Error> {
    // This function used to use tokio::signal::ctrl_c().
    // However tokio::signal::ctrl_c() cannot revert its side effect.
    // Since we have a global ctrl_c handler, we can drop the use of
    // tokio ctrl_c handling here.
    Ok(f.await)
}

#[cfg(test)]
mod tests {
    use std::thread;

    use futures::future;
    use futures::stream;

    use super::*;

    #[test]
    fn test_block_on() {
        assert_eq!(block_on(async { 2 + 2 }), 4);
    }

    #[test]
    #[should_panic]
    fn test_block_on_panic() {
        block_on(async {
            panic!("hello future");
        });
    }

    #[test]
    fn test_panic_in_future_does_not_poisons_runtime() {
        let th = thread::spawn(|| {
            block_on(async {
                panic!("no poison");
            })
        });
        assert!(th.join().is_err());
        assert_eq!(block_on(async { 2 + 2 }), 4);
    }

    #[test]
    fn test_block_on_block_on_other_thread() {
        let (tx, rx) = std::sync::mpsc::sync_channel(1);
        thread::spawn(|| {
            block_on(async move {
                for i in 12.. {
                    tx.send(i).unwrap();
                }
            })
        });
        assert_eq!(
            rx.into_iter().take(5).collect::<Vec<i32>>(),
            vec![12, 13, 14, 15, 16]
        );
    }

    #[test]
    fn test_block_pseudo_parallelism() {
        // This test will deadlock if threads can't schedule tasks while another thread
        // is waiting for a result
        let (tx, mut rx) = tokio::sync::mpsc::channel(1);
        let th1 = thread::spawn(|| block_on(async move { rx.recv().await }));
        let _th2 = thread::spawn(|| block_on(async move { tx.send(5).await }));
        assert_eq!(th1.join().unwrap(), Some(5));
    }

    #[test]
    fn test_stream_to_iter() {
        let stream = stream::iter(vec![42, 33, 12]);
        let iter = stream_to_iter(stream);
        assert_eq!(iter.collect::<Vec<_>>(), vec![42, 33, 12]);
    }

    #[test]
    fn test_stream_to_iter_two_instances() {
        let mut options = RunStreamOptions::new();
        options.buffer_size(1);
        let mut iter1 = options.run(stream::iter(vec![42, 33, 12]));
        let mut iter2 = options.run(stream::iter(vec![11, 25, 67]));
        assert_eq!(iter2.next(), Some(11));
        assert_eq!(iter2.next(), Some(25));
        assert_eq!(iter1.next(), Some(42));
        assert_eq!(iter2.next(), Some(67));
        assert_eq!(iter2.next(), None);
        assert_eq!(iter1.next(), Some(33));
        assert_eq!(iter1.next(), Some(12));
        assert_eq!(iter1.next(), None);
    }

    #[test]
    fn test_stream_to_iter_some_items_panic() {
        let stream = stream::iter(vec![43, 33, 12, 11])
            .then(future::ready)
            .map(|v| {
                assert!(v & 1 == 1);
                v + 1
            });
        let iter = stream_to_iter(stream);
        assert_eq!(iter.collect::<Vec<_>>(), vec![44, 34]);
    }

    #[tokio::test]
    async fn test_iter_to_stream() {
        let iter = vec![1u8, 10, 20].into_iter();
        let mut stream = iter_to_stream(iter);
        assert_eq!(stream.next().await, Some(1));
        assert_eq!(stream.next().await, Some(10));
        assert_eq!(stream.next().await, Some(20));
        assert_eq!(stream.next().await, None);
        assert_eq!(stream.next().await, None);
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

#[cfg(unix)]
use std::fs;
use std::fs::File;
#[cfg(unix)]
use std::fs::Permissions;
use std::io;
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use std::path::PathBuf;

use tempfile::NamedTempFile;

/// Create a temp file and then rename it into the specified path to
/// achieve atomicity. The temp file is created in the same directory
/// as path to ensure the rename is not cross filesystem. If fysnc is
/// true, the file will be fsynced before and after renaming, and the
/// directory will by fsynced after renaming.
///
/// mode_perms is required but does nothing on windows. mode_perms is
/// not automatically umasked.
///
/// The renamed file is returned. Any further data written to the file
/// will not be atomic since the file is already visibile to readers.
///
/// Note that the rename operation will fail on windows if the
/// destination file exists and is open.
pub fn atomic_write(
    path: &Path,
    #[allow(dead_code)] mode_perms: u32,
    fsync: bool,
    op: impl FnOnce(&mut File) -> io::Result<()>,
) -> io::Result<File> {
    let mut af = AtomicFile::open(path, mode_perms, fsync)?;
    op(af.as_file())?;
    af.save()
}

pub struct AtomicFile {
    file: NamedTempFile,
    path: PathBuf,
    dir: PathBuf,
    fsync: bool,
}

impl AtomicFile {
    pub fn open(
        path: &Path,
        #[allow(unused_variables)] mode_perms: u32,
        fsync: bool,
    ) -> io::Result<Self> {
        let dir = match path.parent() {
            Some(dir) => dir,
            None => return Err(io::Error::from(io::ErrorKind::InvalidInput)),
        };

        #[allow(unused_mut)]
        let mut temp = NamedTempFile::new_in(dir)?;

        #[cfg(unix)]
        {
            let f = temp.as_file_mut();
            f.set_permissions(Permissions::from_mode(mode_perms))?;
        }

        Ok(Self {
            file: temp,
            path: path.to_path_buf(),
            dir: dir.to_path_buf(),
            fsync,
        })
    }

    pub fn as_file(&mut self) -> &mut File {
        self.file.as_file_mut()
    }

    pub fn save(self) -> io::Result<File> {
        #[allow(unused_variables)]
        let (mut temp, path, dir, fsync) = (self.file, self.path, self.dir, self.fsync);
        let f = temp.as_file_mut();

        if fsync {
            f.sync_data()?;
        }

        let max_retries = if cfg!(windows) { 5u16 } else { 0 };
        let mut retry = 0;
        loop {
            match temp.persist(&path) {
                Ok(persisted) => {
                    if fsync {
                        persisted.sync_all()?;

                        // Also sync the directory on Unix.
                        // Windows does not support syncing a directory.
                        #[cfg(unix)]
                        {
                            if let Ok(opened) = fs::OpenOptions::new().read(true).open(dir) {
                                let _ = opened.sync_all();
                            }
                        }
                    }

                    break Ok(persisted);
                }
                Err(e) => {
                    if retry == max_retries || e.error.kind() != io::ErrorKind::PermissionDenied {
                        break Err(e.error);
                    }

                    // Windows fails with "Access Denied" if destination file is open.
                    // Retry a few times.
                    tracing::info!(
                        retry,
                        ?path,
                        "atomic_write rename failed with EPERM. Will retry.",
                    );
                    std::thread::sleep(std::time::Duration::from_millis(1 << retry));
                    temp = e.file;

                    retry += 1;
                }
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use std::io::Write;
    #[cfg(unix)]
    use std::os::unix::prelude::MetadataExt;

    use tempfile::tempdir;

    use super::*;

    #[test]
    fn test_atomic_write() -> io::Result<()> {
        let td = tempdir()?;

        let foo_path = td.path().join("foo");
        atomic_write(&foo_path, 0o640, false, |f| {
            f.write_all(b"sushi")?;
            Ok(())
        })?;

        // Sanity check that we wrote contents and the temp file is gone.
        assert_eq!("sushi", std::fs::read_to_string(&foo_path)?);
        assert_eq!(1, std::fs::read_dir(td.path())?.count());

        // Make sure we can set the mode perms on unix.
        #[cfg(unix)]
        assert_eq!(
            0o640,
            0o777 & std::fs::File::open(&foo_path)?.metadata()?.mode()
        );

        Ok(())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::collections::HashMap;
use std::collections::HashSet;
use std::path::PathBuf;
use std::str;

use anyhow::Error;
use anyhow::Result;
use configmodel::Config;
use configmodel::Text;
use indexmap::IndexMap;
use thiserror::Error;
use url::Url;
use util::path::expand_path;

pub mod x509;

pub use x509::check_certs;
pub use x509::X509Error;

#[derive(Debug, Error)]
#[error("Certificate(s) or private key(s) not found for {url}: {missing:?}\n{msg}")]
pub struct MissingCerts {
    url: String,
    missing: HashSet<PathBuf>,
    msg: String,
}

/// A group of client authentiation settings from the user's config.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AuthGroup {
    pub name: String,
    pub prefix: String,
    pub cert: Option<PathBuf>,
    pub key: Option<PathBuf>,
    pub cacerts: Option<PathBuf>,
    pub username: Option<String>,
    pub schemes: Vec<String>,
    pub priority: i32,
    pub extras: HashMap<String, String>,
}

impl AuthGroup {
    fn new(group: &str, mut settings: HashMap<&str, Text>) -> Result<Self> {
        let name = group.into();

        let mut prefix = settings
            .remove("prefix")
            .map(|s| s.to_string())
            .ok_or_else(|| Error::msg("auth prefix missing"))?;

        let cert = settings
            .remove("cert")
            .filter(|s| !s.is_empty())
            .map(expand_path);
        let key = settings
            .remove("key")
            .filter(|s| !s.is_empty())
            .map(expand_path);
        let cacerts = settings
            .remove("cacerts")
            .filter(|s| !s.is_empty())
            .map(expand_path);

        let username = settings.remove("username").map(|s| s.to_string());

        // If the URL prefix for this group has a scheme specified, use that
        // and ignore the contents of the "schemes" field for this group.
        let schemes = if let Some(i) = prefix.find("://") {
            let _ = settings.remove("schemes");
            let scheme = prefix[..i].into();
            prefix = prefix[i + 3..].into();
            vec![scheme]
        } else {
            // Default to HTTPS if no schemes are specified in either the
            // prefix or schemes field.
            settings.remove("schemes").map_or_else(
                || vec!["https".into()],
                |line| line.split(' ').map(String::from).collect(),
            )
        };

        let priority = settings
            .remove("priority")
            .map(|s| s.parse())
            .transpose()?
            .unwrap_or_default();

        let extras = settings
            .into_iter()
            .map(|(k, v)| (k.to_string(), v.to_string()))
            .collect::<HashMap<_, _>>();

        Ok(Self {
            name,
            prefix,
            cert,
            key,
            cacerts,
            username,
            schemes,
            priority,
            extras,
        })
    }
}

#[derive(Clone)]
pub struct AuthSection<'a> {
    groups: Vec<AuthGroup>,
    config: &'a dyn Config,
}

impl<'a> AuthSection<'a> {
    /// Parse the `[auth]` section of a Mercurial config into a map
    /// of grouped auth settings.
    ///
    /// The keys of the resulting HashMap are group names from the config;
    /// i.e., the first component of a key of the form `group.setting`.
    /// All keys in the auth section are expected to follow this format.
    ///
    /// Values are parsed `Auth` structs containing all of the values
    /// found for the given grouping.
    pub fn from_config(config: &'a dyn Config) -> Self {
        // Use an IndexMap to preserve ordering; needed to correctly handle precedence.
        let mut groups = IndexMap::new();

        let keys = config.keys("auth");
        for key in &keys {
            // Skip keys that aren't valid UTF-8 or that don't match
            // the expected auth key format of `group.setting`.
            let (group, setting) = match key.find('.') {
                Some(i) => (&key[..i], &key[i + 1..]),
                None => continue,
            };
            if let Some(value) = config.get("auth", key) {
                groups
                    .entry(group)
                    .or_insert_with(HashMap::new)
                    .insert(setting, value);
            }
        }

        let groups = groups
            .into_iter()
            .filter_map(|(group, settings)| AuthGroup::new(group, settings).ok())
            .collect();

        Self { groups, config }
    }

    /// Find the best matching auth group for the given URL.
    pub fn best_match_for(&self, url: &Url) -> Result<Option<AuthGroup>, MissingCerts> {
        let mut best: Option<&AuthGroup> = None;
        let mut missing = HashSet::new();

        let scheme = url.scheme().to_string();
        let username = url.username();
        let url_suffix = strip_scheme_and_user(&url);

        'groups: for group in &self.groups {
            if !group.schemes.contains(&scheme) {
                continue;
            }

            // If the URL contains a username, the entry's username must
            // match if the entry's username field is non-None.
            if !username.is_empty() {
                match group.username {
                    Some(ref u) if u != username => continue,
                    _ => {}
                }
            }

            if group.prefix != "*" && !url_suffix.starts_with(&group.prefix) {
                continue;
            }

            // If there is an existing candidate, check whether the current
            // auth entry is a more specific match.
            if let Some(ref best) = best {
                // Take the entry with the longer prefix.
                if group.prefix.len() < best.prefix.len() {
                    continue;
                } else if group.prefix.len() == best.prefix.len() {
                    // If prefixes are the same, break the tie using priority.
                    if group.priority < best.priority {
                        continue;
                    // If the priorities are the same, prefer entries with usernames.
                    } else if group.priority == best.priority
                        && best.username.is_some()
                        && group.username.is_none()
                    {
                        continue;
                    }
                }
            }

            // Skip this group is any of the files are missing.
            for (label, path) in &[
                ("client certificate", &group.cert),
                ("private key", &group.key),
                ("CA certificate bundle", &group.cacerts),
            ] {
                match path {
                    Some(path) if !path.is_file() => {
                        tracing::debug!(
                            "Ignoring [auth] group {:?} because of missing {}: {:?}",
                            &group.name,
                            &label,
                            &path
                        );
                        missing.insert(path.to_path_buf());
                        continue 'groups;
                    }
                    _ => {}
                }
            }

            best = Some(group);
        }

        if let Some(best) = best {
            Ok(Some(best.clone()))
        } else if !missing.is_empty() {
            let msg = self.config.get("help", "tlsauthhelp").unwrap_or_default();
            Err(MissingCerts {
                url: url.to_string(),
                missing,
                msg: msg.to_string(),
            })
        } else {
            Ok(None)
        }
    }
}

/// Given a URL, strip off the scheme and username if present.
fn strip_scheme_and_user(url: &Url) -> String {
    let url = url.as_str();
    // Find the position immediately after the '@' if a username is present.
    // or after the scheme otherwise.
    let pos = url
        .find('@')
        .map(|i| i + 1)
        .or_else(|| url.find("://").map(|i| i + 3));

    match pos {
        Some(i) => &url[i..],
        None => url,
    }
    .to_string()
}

#[cfg(test)]
mod test {
    use staticconfig::static_config;

    use super::*;

    #[test]
    fn test_auth_groups() {
        let config = static_config!(
            r#"[auth]
foo.prefix = foo.com
foo.cert = /foo/cert
foo.key = /foo/key
foo.cacerts = /foo/cacerts
bar.prefix = bar.com
bar.cert = /bar/cert
bar.key = /bar/key
baz.cert = /baz/cert
baz.key = /baz/key
foo.username = user
foo.schemes = http https
foo.priority = 1"#
        );
        let groups = AuthSection::from_config(&config).groups;

        // Only 2 groups because "baz" is missing the required "prefix" setting.
        assert_eq!(groups.len(), 2);
        assert_eq!(
            groups[0],
            AuthGroup {
                name: "foo".into(),
                prefix: "foo.com".into(),
                cert: Some("/foo/cert".into()),
                key: Some("/foo/key".into()),
                cacerts: Some("/foo/cacerts".into()),
                username: Some("user".into()),
                schemes: vec!["http".into(), "https".into()],
                priority: 1,
                extras: HashMap::new(),
            }
        );
        assert_eq!(
            groups[1],
            AuthGroup {
                name: "bar".into(),
                prefix: "bar.com".into(),
                cert: Some("/bar/cert".into()),
                key: Some("/bar/key".into()),
                cacerts: None,
                username: None,
                schemes: vec!["https".into()],
                priority: 0,
                extras: HashMap::new(),
            }
        );
    }

    #[test]
    fn test_strip_scheme_and_user() -> Result<()> {
        let url = "https://example.com/".parse()?;
        let stripped = strip_scheme_and_user(&url);
        assert_eq!(stripped, "example.com/");

        let url = "https://user@example.com:433/some/path?query=1".parse()?;
        let stripped = strip_scheme_and_user(&url);
        assert_eq!(stripped, "example.com:433/some/path?query=1");

        Ok(())
    }

    #[test]
    fn test_best_match_for() -> Result<()> {
        let config = static_config!(
            r#"[auth]
default.prefix = *
a.prefix = example.com
a.schemes = http https
b.prefix = foo.com/bar
c.prefix = foo.com/bar/baz
d.prefix = bar.com
d.priority = 1
e.prefix = bar.com
e.username = e_user
f.prefix = baz.com
f.username = f_user
g.prefix = baz.com
h.prefix = example.net
h.username = h_user
i.prefix = example.net
i.username = i_user
j.prefix = invalid.com
j.cert = /does/not/exist"#
        );
        let auth = AuthSection::from_config(&config);

        // Basic case: an exact match.
        let group = auth
            .best_match_for(&"http://example.com".parse()?)?
            .unwrap();
        assert_eq!(group.name, "a");

        // Scheme mismatch.
        let group = auth.best_match_for(&"ftp://example.com".parse()?)?;
        assert!(group.is_none());

        // Given URL's hosts matches a config prefix, but doesn't match
        // the entire prefix.
        let group = auth.best_match_for(&"https://foo.com.".parse()?)?.unwrap();
        assert_eq!(group.name, "default");

        // Matching the entire prefix works as expected.
        let group = auth
            .best_match_for(&"https://foo.com/bar".parse()?)?
            .unwrap();
        assert_eq!(group.name, "b");

        // A more specific prefix wins.
        let group = auth
            .best_match_for(&"https://foo.com/bar/baz".parse()?)?
            .unwrap();
        assert_eq!(group.name, "c");

        // Still matches even if the URL has other components in it.
        let group = auth
            .best_match_for(&"https://foo.com/bar/baz/dir?query=1#fragment".parse()?)?
            .unwrap();
        assert_eq!(group.name, "c");

        // There are two entries matching this prefix, but one has higher priority.
        let group = auth.best_match_for(&"https://bar.com".parse()?)?.unwrap();
        assert_eq!(group.name, "d");

        // Even if another entry has a username match, the higher priority should win.
        let group = auth
            .best_match_for(&"https://e_user@bar.com".parse()?)?
            .unwrap();
        assert_eq!(group.name, "d");

        // Even if no user is specified in the URL, the entry with a username should
        // take precedence all else being equal.
        let group = auth.best_match_for(&"https://baz.com".parse()?)?.unwrap();
        assert_eq!(group.name, "f");

        // If all else fails, later entries take precedence over earlier ones.
        // Even if no user is specified in the URL, the entry with a username should
        // take precedence all else being equal.
        let group = auth
            .best_match_for(&"https://example.net".parse()?)?
            .unwrap();
        assert_eq!(group.name, "i");

        // If the cert of key is missing, the entry shouldn't match.
        let group = auth
            .best_match_for(&"https://invalid.com".parse()?)?
            .unwrap();
        assert_eq!(group.name, "default");

        Ok(())
    }

    #[test]
    fn test_extras() -> Result<()> {
        let config = static_config!(
            r#"[auth]
foo.prefix = foo.com
foo.username = user
foo.hello = world
foo.bar = baz
             "#
        );
        let auth = AuthSection::from_config(&config);

        let group = auth.best_match_for(&"https://foo.com".parse()?)?.unwrap();

        assert_eq!(group.extras.get("hello").unwrap(), "world");
        assert_eq!(group.extras.get("bar").unwrap(), "baz");
        assert_eq!(group.extras.get("username"), None);

        Ok(())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::fs::File;
use std::io;
use std::io::prelude::*;
use std::path::Path;
use std::path::PathBuf;

use anyhow::anyhow;
use chrono::offset::Local;
use chrono::offset::Utc;
use chrono::DateTime;
use simple_asn1::ASN1Block;
use thiserror::Error;

#[derive(Error, Debug)]
#[error("Problem with certificate {path}: {kind}")]
pub struct X509Error {
    #[source]
    pub kind: X509ErrorKind,
    pub path: PathBuf,
}

impl X509Error {
    fn new(kind: impl Into<X509ErrorKind>, path: impl AsRef<Path>) -> Self {
        Self {
            kind: kind.into(),
            path: path.as_ref().to_path_buf(),
        }
    }
}

#[derive(Error, Debug)]
pub enum X509ErrorKind {
    #[error("Certificate not found")]
    Missing(#[source] io::Error),
    #[error("Could not read certificate")]
    Unreadable(#[from] io::Error),
    #[error("Certificate is malformed")]
    Malformed(#[source] anyhow::Error),
    #[error(
        "Certificate has expired (valid from {0} to {1})",
        .not_before.with_timezone(&Local),
        .not_after.with_timezone(&Local))
    ]
    Expired {
        not_before: DateTime<Utc>,
        not_after: DateTime<Utc>,
    },
}

impl X509ErrorKind {
    pub fn is_missing(&self) -> bool {
        match self {
            Self::Missing(..) => true,
            _ => false,
        }
    }

    pub fn is_unreadable(&self) -> bool {
        match self {
            Self::Unreadable(..) => true,
            _ => false,
        }
    }

    pub fn is_malformed(&self) -> bool {
        match self {
            Self::Malformed(..) => true,
            _ => false,
        }
    }

    pub fn is_expired(&self) -> bool {
        match self {
            Self::Expired { .. } => true,
            _ => false,
        }
    }
}

/// Validate the dates of all X.509 certificates in the specified PEM file.
pub fn check_certs(path: impl AsRef<Path>) -> Result<(), X509Error> {
    let path = path.as_ref();
    let mut pem_file = File::open(path).map_err(|e| {
        let kind = match e.kind() {
            io::ErrorKind::NotFound => X509ErrorKind::Missing(e),
            _ => X509ErrorKind::Unreadable(e),
        };
        X509Error::new(kind, path)
    })?;
    let mut pem_bytes = Vec::new();
    pem_file
        .read_to_end(&mut pem_bytes)
        .map_err(|e| X509Error::new(e, path))?;

    certs_valid_at_time(&pem_bytes, Utc::now()).map_err(|e| X509Error::new(e, path))
}

/// Check whether all X.509 certificates found in the given PEM file would be
/// valid at a given time.
fn certs_valid_at_time(pem_bytes: &[u8], time: DateTime<Utc>) -> Result<(), X509ErrorKind> {
    let certs = pem::parse_many(pem_bytes)
        .into_iter()
        .filter(|pem| pem.tag == "CERTIFICATE")
        .collect::<Vec<_>>();

    if certs.is_empty() {
        return Err(X509ErrorKind::Malformed(anyhow!(
            "PEM file does not contain an X.509 certificate"
        )));
    }

    for cert in certs {
        cert_is_valid_at(&cert.contents, time)?;
    }

    Ok(())
}

/// Check whether an X.509 certificate would be valid at a given time.
///
/// This function only checks that the given time falls within the certificate's
/// valid date range. It does not perform any cryptographic verification of the
/// certificate's signature.
///
/// The input is expected to be a DER-encoded binary certificate that conforms
/// to the ASN.1 schema for X.509 certificates specified in [RFC 5280](RFC5280).
///
/// [RFC5280]: https://tools.ietf.org/html/rfc5280#section-4.1
fn cert_is_valid_at(cert: &[u8], time: DateTime<Utc>) -> Result<(), X509ErrorKind> {
    let (not_before, not_after) = parse_valid_date_range(cert)?;

    if not_before < time && time < not_after {
        Ok(())
    } else {
        Err(X509ErrorKind::Expired {
            not_before,
            not_after,
        })
    }
}

/// Parse and extract the valid date range from a DER-encoded X.509 certificate.
fn parse_valid_date_range(cert: &[u8]) -> Result<(DateTime<Utc>, DateTime<Utc>), X509ErrorKind> {
    let asn1 = simple_asn1::from_der(cert).map_err(|e| X509ErrorKind::Malformed(e.into()))?;

    // XXX: Due to some technical issues with vendoring 3rd party depenencies,
    // we presently can't use the `x509-parser` crate that would make this much
    // nicer. Instead, we have to parse the DER-encoded ASN.1 object manually.
    // The field indexes that are hardcoded here correspond to the field order
    // specified in RFC 5280, which all X.509 certificates must conform to.
    if let Some(ASN1Block::Sequence(_, cert)) = asn1.get(0) {
        if let Some(ASN1Block::Sequence(_, fields)) = cert.get(0) {
            if let Some(ASN1Block::Sequence(_, validity)) = fields.get(4) {
                if let Some(ASN1Block::UTCTime(_, not_before)) = validity.get(0) {
                    if let Some(ASN1Block::UTCTime(_, not_after)) = validity.get(1) {
                        return Ok((*not_before, *not_after));
                    }
                }
            }
        }
    }

    Err(X509ErrorKind::Malformed(anyhow!(
        "Certificate does not contain expected fields"
    )))
}

#[cfg(test)]
mod tests {
    use anyhow::Result;
    use chrono::offset::TimeZone;
    use once_cell::sync::Lazy;

    use super::*;

    const CERT_1: &[u8] = include_bytes!("test_certs/cert1.pem");
    const CERT_2: &[u8] = include_bytes!("test_certs/cert2.pem");
    const NOT_A_CERT: &[u8] = include_bytes!("test_certs/not_a_cert.pem");
    const COMBINED: &[u8] = include_bytes!("test_certs/combined.pem");

    // CERT_1 is valid from 2020-12-09 22:39:13 UTC to 2020-12-10 22:39:13 UTC.
    static CERT_1_NOT_BEFORE: Lazy<DateTime<Utc>> =
        Lazy::new(|| Utc.with_ymd_and_hms(2020, 12, 9, 22, 39, 13).unwrap());
    static CERT_1_NOT_AFTER: Lazy<DateTime<Utc>> =
        Lazy::new(|| Utc.with_ymd_and_hms(2020, 12, 10, 22, 39, 13).unwrap());

    // CERT_2 is valid from  2020-12-09 22:40:23 UTC to 2020-12-11 22:40:23 UTC.
    static CERT_2_NOT_BEFORE: Lazy<DateTime<Utc>> =
        Lazy::new(|| Utc.with_ymd_and_hms(2020, 12, 9, 22, 40, 23).unwrap());
    static CERT_2_NOT_AFTER: Lazy<DateTime<Utc>> =
        Lazy::new(|| Utc.with_ymd_and_hms(2020, 12, 11, 22, 40, 23).unwrap());

    // Both CERT_1 and CERT_1 are valid on this date.
    static CERT_1_VALID_DATE: Lazy<DateTime<Utc>> =
        Lazy::new(|| Utc.with_ymd_and_hms(2020, 12, 10, 0, 0, 0).unwrap());

    // On this date, CERT_2 is valid but CERT_1 is not.
    static CERT_2_VALID_DATE: Lazy<DateTime<Utc>> =
        Lazy::new(|| Utc.with_ymd_and_hms(2020, 12, 11, 0, 0, 0).unwrap());

    #[test]
    fn test_date_parsing() -> Result<()> {
        let pem = pem::parse(CERT_1)?;
        let (not_before, not_after) = parse_valid_date_range(&pem.contents)?;
        assert_eq!(not_before, *CERT_1_NOT_BEFORE);
        assert_eq!(not_after, *CERT_1_NOT_AFTER);

        let pem = pem::parse(CERT_2)?;
        let (not_before, not_after) = parse_valid_date_range(&pem.contents)?;
        assert_eq!(not_before, *CERT_2_NOT_BEFORE);
        assert_eq!(not_after, *CERT_2_NOT_AFTER);

        Ok(())
    }

    #[test]
    fn test_single_cert() -> Result<()> {
        certs_valid_at_time(CERT_1, *CERT_1_VALID_DATE)?;
        let res = certs_valid_at_time(CERT_1, *CERT_2_VALID_DATE);
        assert!(res.unwrap_err().is_expired());

        certs_valid_at_time(CERT_2, *CERT_1_VALID_DATE)?;
        certs_valid_at_time(CERT_2, *CERT_2_VALID_DATE)?;

        Ok(())
    }

    #[test]
    fn test_combined_certs() -> Result<()> {
        // The input file contains both CERT_1 and CERT_2. Since CERT_1 is
        // invalid on CERT_2_VALID_DATE, the entire file is considered invalid
        // on that date because it contains an expired cert.
        certs_valid_at_time(COMBINED, *CERT_1_VALID_DATE)?;
        let res = certs_valid_at_time(COMBINED, *CERT_2_VALID_DATE);
        assert!(res.unwrap_err().is_expired());

        Ok(())
    }

    #[test]
    fn test_no_cert() -> Result<()> {
        // The input file is a valid PEM file, but does not contain a cert.
        let res = certs_valid_at_time(NOT_A_CERT, *CERT_1_VALID_DATE);
        assert!(res.unwrap_err().is_malformed());

        Ok(())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::path::Path;
use std::sync::Arc;

use anyhow::anyhow;
use anyhow::Result;
use log::warn;
use manifest::List;
use revisionstore::scmstore::file::FileAuxData;
use revisionstore::scmstore::FetchMode;
use revisionstore::scmstore::FileAttributes;
use revisionstore::scmstore::FileStore;
use revisionstore::scmstore::FileStoreBuilder;
use revisionstore::scmstore::KeyFetchError;
use revisionstore::scmstore::StoreFile;
use revisionstore::scmstore::TreeStore;
use revisionstore::scmstore::TreeStoreBuilder;
use revisionstore::HgIdDataStore;
use revisionstore::MemcacheStore;
use tracing::event;
use tracing::instrument;
use tracing::Level;
use types::HgId;
use types::Key;
use types::RepoPathBuf;

pub struct BackingStore {
    filestore: Arc<FileStore>,
    treestore: Arc<TreeStore>,
}

impl BackingStore {
    pub fn new<P: AsRef<Path>>(root: P, allow_retries: bool) -> Result<Self> {
        let root = root.as_ref();
        let mut config = configloader::hg::load(Some(root), &[], &[])?;

        if !allow_retries {
            let source = configloader::config::Options::new().source("backingstore");
            config.set("lfs", "backofftimes", Some(""), &source);
            config.set("lfs", "throttlebackofftimes", Some(""), &source);
            config.set("edenapi", "max-retry-per-request", Some("0"), &source);
        }

        let ident = identity::must_sniff_dir(root)?;
        let hg = root.join(ident.dot_dir());
        let store_path = hg.join("store");

        let mut filestore = FileStoreBuilder::new(&config)
            .local_path(&store_path)
            .store_aux_data();

        let treestore = TreeStoreBuilder::new(&config)
            .local_path(&store_path)
            .suffix(Path::new("manifests"));

        // Memcache takes 30s to initialize on debug builds slowing down tests significantly, let's
        // not even try to initialize it then.
        if !cfg!(debug_assertions) {
            match MemcacheStore::new(&config) {
                Ok(memcache) => {
                    // XXX: Add the memcachestore for the treestore.
                    filestore = filestore.memcache(Arc::new(memcache));
                }
                Err(e) => warn!("couldn't initialize Memcache: {}", e),
            }
        }

        let filestore = Arc::new(filestore.build()?);
        let treestore = treestore.filestore(filestore.clone());

        Ok(Self {
            treestore: Arc::new(treestore.build()?),
            filestore,
        })
    }

    pub fn get_blob(&self, node: &[u8], fetch_mode: FetchMode) -> Result<Option<Vec<u8>>> {
        let hgid = HgId::from_slice(node)?;
        let key = Key::new(RepoPathBuf::new(), hgid);
        self.get_blob_by_key(key, fetch_mode)
    }

    #[instrument(level = "debug", skip(self))]
    fn get_blob_by_key(&self, key: Key, fetch_mode: FetchMode) -> Result<Option<Vec<u8>>> {
        if let FetchMode::LocalOnly = fetch_mode {
            event!(Level::TRACE, "attempting to fetch blob locally");
        }
        let fetch_result = self
            .filestore
            .fetch(std::iter::once(key), FileAttributes::CONTENT, fetch_mode)
            .single();

        Ok(if let Some(mut file) = fetch_result? {
            Some(file.file_content()?.into_vec())
        } else {
            None
        })
    }

    fn get_file_attrs_batch<F>(
        &self,
        keys: Vec<Key>,
        fetch_mode: FetchMode,
        resolve: F,
        attrs: FileAttributes,
    ) where
        F: Fn(usize, Result<Option<StoreFile>>) -> (),
    {
        // Resolve key errors
        let requests = keys.into_iter().enumerate();

        // Crate key-index mapping and fail fast for duplicate keys
        let mut indexes: HashMap<Key, usize> = HashMap::new();
        for (index, key) in requests {
            if let Entry::Vacant(vacant) = indexes.entry(key) {
                vacant.insert(index);
            } else {
                resolve(
                    index,
                    Err(anyhow!(
                        "duplicated keys are not supported by get_file_attrs_batch when using scmstore",
                    )),
                );
            }
        }

        // Handle local-only fetching
        if let FetchMode::LocalOnly = fetch_mode {
            event!(Level::TRACE, "attempting to fetch file aux data locally");
        }

        let fetch_results = self
            .filestore
            .fetch(indexes.keys().cloned(), attrs, fetch_mode);

        for result in fetch_results {
            match result {
                Ok((key, value)) => {
                    if let Some(index) = indexes.remove(&key) {
                        resolve(index, Ok(Some(value)));
                    }
                }
                Err(err) => {
                    match err {
                        KeyFetchError::KeyedError { key, mut errors } => {
                            if let Some(index) = indexes.remove(&key) {
                                if let Some(err) = errors.pop() {
                                    resolve(index, Err(err));
                                } else {
                                    resolve(index, Ok(None));
                                }
                            } else {
                                tracing::error!(
                                    "no index found for {}, scmstore returned a key we have no record of requesting",
                                    key
                                );
                            }
                        }
                        KeyFetchError::Other(_) => {
                            // TODO: How should we handle normal non-keyed errors?
                        }
                    };
                }
            }
        }
    }

    /// Fetch file contents in batch. Whenever a blob is fetched, the supplied `resolve` function is
    /// called with the file content or an error message, and the index of the blob in the request
    /// array.
    #[instrument(level = "debug", skip(self, resolve))]
    pub fn get_blob_batch<F>(&self, keys: Vec<Key>, fetch_mode: FetchMode, resolve: F)
    where
        F: Fn(usize, Result<Option<Vec<u8>>>) -> (),
    {
        self.get_file_attrs_batch(
            keys,
            fetch_mode,
            move |idx, res| {
                resolve(
                    idx,
                    res.transpose()
                        .map(|res| {
                            res.and_then(|mut file| {
                                file.file_content().map(|content| content.into_vec())
                            })
                        })
                        .transpose(),
                )
            },
            FileAttributes::CONTENT,
        )
    }

    #[instrument(level = "debug", skip(self))]
    pub fn get_tree(&self, node: &[u8], fetch_mode: FetchMode) -> Result<Option<List>> {
        let hgid = HgId::from_slice(node)?;
        let key = Key::new(RepoPathBuf::new(), hgid);

        if let FetchMode::LocalOnly = fetch_mode {
            event!(Level::TRACE, "attempting to fetch trees locally");
        }
        let fetch_results = self.treestore.fetch_batch(std::iter::once(key), fetch_mode);

        if let Some(mut entry) = fetch_results.single()? {
            Ok(Some(entry.manifest_tree_entry()?.try_into()?))
        } else {
            Ok(None)
        }
    }

    /// Fetch tree contents in batch. Whenever a tree is fetched, the supplied `resolve` function is
    /// called with the tree content or an error message, and the index of the tree in the request
    /// array.
    #[instrument(level = "debug", skip(self, resolve))]
    pub fn get_tree_batch<F>(&self, keys: Vec<Key>, fetch_mode: FetchMode, resolve: F)
    where
        F: Fn(usize, Result<Option<(List, HashMap<HgId, FileAuxData>)>>),
    {
        // Handle key errors
        let requests = keys.into_iter().enumerate();

        // Crate key-index mapping and fail fast for duplicate keys
        let mut indexes: HashMap<Key, usize> = HashMap::new();
        for (index, key) in requests {
            if let Entry::Vacant(vacant) = indexes.entry(key) {
                vacant.insert(index);
            } else {
                resolve(
                    index,
                    Err(anyhow!(
                        "duplicated keys are not supported by get_tree_batch when using scmstore",
                    )),
                );
            }
        }

        // Handle local-only fetching
        if let FetchMode::LocalOnly = fetch_mode {
            event!(Level::TRACE, "attempting to fetch trees locally");
        }
        let fetch_results = self
            .treestore
            .fetch_batch(indexes.keys().cloned(), fetch_mode);

        // Handle pey-key fetch results
        for result in fetch_results {
            match result {
                Ok((key, mut value)) => {
                    if let Some(index) = indexes.remove(&key) {
                        resolve(
                            index,
                            Some(value.manifest_tree_entry().and_then(|tree| {
                                let aux_data = value.aux_data()?;
                                Ok((tree.try_into()?, aux_data))
                            }))
                            .transpose(),
                        );
                    }
                }
                Err(err) => {
                    match err {
                        KeyFetchError::KeyedError { key, mut errors } => {
                            if let Some(index) = indexes.remove(&key) {
                                if let Some(err) = errors.pop() {
                                    resolve(index, Err(err));
                                } else {
                                    resolve(index, Ok(None));
                                }
                            } else {
                                tracing::error!(
                                    "no index found for {}, scmstore returned a key we have no record of requesting",
                                    key
                                );
                            }
                        }
                        KeyFetchError::Other(_) => {
                            // TODO: How should we handle normal non-keyed errors?
                        }
                    };
                }
            }
        }
    }

    pub fn get_file_aux(&self, node: &[u8], fetch_mode: FetchMode) -> Result<Option<FileAuxData>> {
        let hgid = HgId::from_slice(node)?;
        let key = Key::new(RepoPathBuf::new(), hgid);

        if let FetchMode::LocalOnly = fetch_mode {
            event!(Level::TRACE, "attempting to fetch file aux data locally");
        }
        let fetch_results =
            self.filestore
                .fetch(std::iter::once(key), FileAttributes::AUX, fetch_mode);

        if let Some(entry) = fetch_results.single()? {
            Ok(Some(entry.aux_data()?.try_into()?))
        } else {
            Ok(None)
        }
    }

    pub fn get_file_aux_batch<F>(&self, keys: Vec<Key>, fetch_mode: FetchMode, resolve: F)
    where
        F: Fn(usize, Result<Option<FileAuxData>>) -> (),
    {
        self.get_file_attrs_batch(
            keys,
            fetch_mode,
            move |idx, res| {
                resolve(
                    idx,
                    res.transpose()
                        .map(|res| res.and_then(|file| file.aux_data()))
                        .transpose(),
                )
            },
            FileAttributes::AUX,
        )
    }

    /// Forces backing store to rescan pack files or local indexes
    #[instrument(level = "debug", skip(self))]
    pub fn flush(&self) {
        self.filestore.refresh().ok();
        self.treestore.refresh().ok();
    }
}

impl Drop for BackingStore {
    fn drop(&mut self) {
        // Make sure that all the data that was fetched is written to the hgcache.
        self.flush();
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

//! backingstore - The crate provides backing store interface for EdenFS.
//!
//! This crate aims to provide EdenFS's backing store interface so EdenFS could use types in this
//! crate to import SCM blobs and trees directly from Mercurial's data store.
//!
//! The C++ code in `c_api` directory encapsulate Rust functions exposed from this crate into
//! regular C++ classes.

mod backingstore;
mod raw;

pub use crate::backingstore::BackingStore;


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use revisionstore::scmstore::file::FileAuxData as ScmStoreFileAuxData;

use crate::raw::CBytes;

#[repr(C)]
pub struct FileAuxData {
    total_size: u64,
    content_id: CBytes,
    content_sha1: CBytes,
    content_sha256: CBytes,
    // Using pointer as `Option<CBytes>`
    content_blake3: *mut CBytes,
}

impl From<ScmStoreFileAuxData> for FileAuxData {
    fn from(v: ScmStoreFileAuxData) -> Self {
        // TODO(meyer): Yet more unnecessary allocation, need to convert backing to cxx
        FileAuxData {
            total_size: v.total_size,
            content_id: v.content_id.as_ref().to_vec().into(),
            content_sha1: v.content_sha1.as_ref().to_vec().into(),
            content_sha256: v.content_sha256.as_ref().to_vec().into(),
            content_blake3: v.content_seeded_blake3.map_or(
                std::ptr::null_mut(),
                |content_blake3| {
                    let boxed_blake3 = Box::new(content_blake3.as_ref().to_vec().into());
                    Box::into_raw(boxed_blake3)
                },
            ),
        }
    }
}

impl Drop for FileAuxData {
    fn drop(&mut self) {
        let content_blake3 = unsafe {
            if self.content_blake3.is_null() {
                None
            } else {
                Some(Box::from_raw(self.content_blake3))
            }
        };
        drop(content_blake3);
    }
}

#[no_mangle]
pub extern "C" fn sapling_file_aux_free(aux: *mut FileAuxData) {
    assert!(!aux.is_null());
    let aux = unsafe { Box::from_raw(aux) };
    drop(aux);
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

//! Provides the c-bindings for `crate::backingstore`.

use std::collections::HashMap;
use std::str;

use anyhow::Error;
use anyhow::Result;
use libc::c_void;
use manifest::List;
use revisionstore::scmstore::FetchMode;
use revisionstore::scmstore::FileAuxData as ScmStoreFileAuxData;
use types::HgId;
use types::Key;

use crate::backingstore::BackingStore;
use crate::raw::CBytes;
use crate::raw::CFallible;
use crate::raw::CFallibleBase;
use crate::raw::FileAuxData;
use crate::raw::Request;
use crate::raw::Slice;
use crate::raw::Tree;

fn fetch_mode_from_local(local: bool) -> FetchMode {
    if local {
        FetchMode::LocalOnly
    } else {
        FetchMode::AllowRemote
    }
}

#[repr(C)]
pub struct BackingStoreOptions {
    allow_retries: bool,
}

#[no_mangle]
pub extern "C" fn sapling_backingstore_new(
    repository: Slice<u8>,
    options: &BackingStoreOptions,
) -> CFallibleBase {
    CFallible::make_with(|| {
        super::init::backingstore_global_init();

        let repo = str::from_utf8(repository.slice())?;
        BackingStore::new(repo, options.allow_retries)
    })
    .into()
}

#[no_mangle]
pub extern "C" fn sapling_backingstore_free(store: *mut BackingStore) {
    assert!(!store.is_null());
    let store = unsafe { Box::from_raw(store) };
    drop(store);
}

#[no_mangle]
pub extern "C" fn sapling_backingstore_get_tree(
    store: &mut BackingStore,
    node: Slice<u8>,
    local: bool,
) -> CFallibleBase {
    CFallible::<Tree>::make_with(|| {
        store
            .get_tree(node.slice(), fetch_mode_from_local(local))
            .and_then(|opt| opt.ok_or_else(|| Error::msg("no tree found")))
            .and_then(|list| (list, HashMap::new()).try_into())
    })
    .into()
}

#[no_mangle]
pub extern "C" fn sapling_backingstore_get_tree_batch(
    store: &mut BackingStore,
    requests: Slice<Request>,
    local: bool,
    data: *mut c_void,
    resolve: unsafe extern "C" fn(*mut c_void, usize, CFallibleBase),
) {
    let keys: Vec<Key> = requests.slice().iter().map(|req| req.key()).collect();

    store.get_tree_batch(keys, fetch_mode_from_local(local), |idx, result| {
        let result: Result<(List, HashMap<HgId, ScmStoreFileAuxData>)> =
            result.and_then(|opt| opt.ok_or_else(|| Error::msg("no tree found")));
        let result: Result<Tree> = result.and_then(|list| list.try_into());
        let result: CFallible<Tree> = result.into();
        unsafe { resolve(data, idx, result.into()) };
    });
}

#[no_mangle]
pub extern "C" fn sapling_backingstore_get_blob(
    store: &mut BackingStore,
    node: Slice<u8>,
    local: bool,
) -> CFallibleBase {
    CFallible::make_with(|| {
        store
            .get_blob(node.slice(), fetch_mode_from_local(local))
            .and_then(|opt| opt.ok_or_else(|| Error::msg("no blob found")))
            .map(CBytes::from_vec)
    })
    .into()
}

#[no_mangle]
pub extern "C" fn sapling_backingstore_get_blob_batch(
    store: &mut BackingStore,
    requests: Slice<Request>,
    local: bool,
    data: *mut c_void,
    resolve: unsafe extern "C" fn(*mut c_void, usize, CFallibleBase),
) {
    let keys: Vec<Key> = requests.slice().iter().map(|req| req.key()).collect();
    store.get_blob_batch(keys, fetch_mode_from_local(local), |idx, result| {
        let result: CFallible<CBytes> = result
            .and_then(|opt| opt.ok_or_else(|| Error::msg("no blob found")))
            .map(CBytes::from_vec)
            .into();
        unsafe { resolve(data, idx, result.into()) };
    });
}

#[no_mangle]
pub extern "C" fn sapling_backingstore_get_file_aux(
    store: &mut BackingStore,
    node: Slice<u8>,
    local: bool,
) -> CFallibleBase {
    CFallible::<FileAuxData>::make_with(|| {
        store
            .get_file_aux(node.slice(), fetch_mode_from_local(local))
            .and_then(|opt| opt.ok_or_else(|| Error::msg("no file aux data found")))
            .map(|aux| aux.into())
    })
    .into()
}

#[no_mangle]
pub extern "C" fn sapling_backingstore_get_file_aux_batch(
    store: &mut BackingStore,
    requests: Slice<Request>,
    local: bool,
    data: *mut c_void,
    resolve: unsafe extern "C" fn(*mut c_void, usize, CFallibleBase),
) {
    let keys: Vec<Key> = requests.slice().iter().map(|req| req.key()).collect();

    store.get_file_aux_batch(keys, fetch_mode_from_local(local), |idx, result| {
        let result: Result<ScmStoreFileAuxData> =
            result.and_then(|opt| opt.ok_or_else(|| Error::msg("no file aux data found")));
        let result: CFallible<FileAuxData> = result.map(|aux| aux.into()).into();
        unsafe { resolve(data, idx, result.into()) };
    });
}

#[no_mangle]
pub extern "C" fn sapling_backingstore_flush(store: &mut BackingStore) {
    store.flush();
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

//! Provides a struct to pass a Rust `vec` to C++. However, the C++ code must hold a reference to
//! the underlying Rust `vec` since `Vec::as_ptr` requires the vector remain valid and alive over
//! the lifetime of the pointer it returns.

use libc::size_t;

#[repr(C)]
pub struct CBytes {
    ptr: *mut u8,
    len: size_t,
    vec: *mut Vec<u8>,
}

impl CBytes {
    pub fn from_vec(vec: Vec<u8>) -> Self {
        let mut vec = Box::new(vec);
        let ptr = vec.as_mut_ptr();

        Self {
            ptr,
            len: vec.len(),
            vec: Box::into_raw(vec),
        }
    }
}

impl From<Vec<u8>> for CBytes {
    fn from(vec: Vec<u8>) -> Self {
        CBytes::from_vec(vec)
    }
}

impl Drop for CBytes {
    fn drop(&mut self) {
        let vec = unsafe { Box::from_raw(self.vec) };
        drop(vec);
    }
}

#[no_mangle]
pub extern "C" fn sapling_cbytes_free(vec: *mut CBytes) {
    let ptr = unsafe { Box::from_raw(vec) };
    drop(ptr);
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

//! Provides a Result-like struct that can be consumed by C/C++ code.
//!
//! The size of this struct is certain since it only holds pointers.
//!
//! # Memory Management
//!
//! Consumer of this struct needs to ensure the returned error string freed with
//! `sapling_cfallible_free_error`.

use std::ffi::c_void;
use std::ffi::CString;

use anyhow::Result;
use libc::c_char;

/// The monomorphized version of `CFallible` used solely because MSVC
/// does not allow returning template functions from extern "C" functions.
#[repr(C)]
pub struct CFallibleBase {
    value: *mut c_void,
    error: *mut c_char,
}

/// A `repr(C)` struct that can be consumed by C++ code. User of this struct should check
/// `is_error` field to see if there is an error.
///
/// Note: user of this struct is responsible to manage the memory passed through via this struct.
///
/// Note: MSVC toolchain dislikes the usage of template in extern functions. Because of this, we
/// cannot rely on cbindgen to generate the interface for this struct. All changes to this function
/// requires manual editing of the corresponding C++ struct definition in `cbindgen.toml`.
///
/// Therefore, at the last minute, these are mapped to CFallibleBase.
#[repr(C)]
pub struct CFallible<T> {
    value: *mut T,
    error: *mut c_char,
}

impl<T> CFallible<T> {
    pub fn make_with<F: FnOnce() -> Result<T>>(f: F) -> CFallible<T> {
        f().into()
    }

    /// Creates a `CFallible` with a valid pointer and no error.
    pub fn ok(value: T) -> Self {
        CFallible {
            value: Box::into_raw(Box::new(value)),
            error: std::ptr::null_mut(),
        }
    }

    /// Creates a `CFallible` with an error message but no value.
    ///
    /// This function will remove any '\0' in the error message.
    pub fn err<P: std::fmt::Debug>(err: P) -> Self {
        let mut err = format!("{:?}", err).into_bytes();
        // `CString::new` will return error only when there is a '\0' in the string. So we manually
        // remove any \0 in the error string to ensure it is safe to call `.expect`.
        err.retain(|&x| x != 0u8);
        let error = CString::new(err).expect("Error message contains \\0");

        CFallible {
            value: std::ptr::null_mut(),
            error: error.into_raw(),
        }
    }
}

impl<T> From<CFallible<T>> for CFallibleBase {
    fn from(value: CFallible<T>) -> CFallibleBase {
        CFallibleBase {
            value: value.value as *mut c_void,
            error: value.error,
        }
    }
}

impl<T> From<Result<T>> for CFallible<T> {
    fn from(value: Result<T>) -> Self {
        match value {
            Ok(value) => CFallible::ok(value),
            Err(err) => CFallible::err(err),
        }
    }
}

#[no_mangle]
pub extern "C" fn sapling_cfallible_free_error(ptr: *mut c_char) {
    let error = unsafe { CString::from_raw(ptr) };
    drop(error);
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::io;
use std::sync::Arc;
use std::sync::Once;

use parking_lot::Mutex;
use tracing_collector::TracingData;
use tracing_subscriber::fmt::format::FmtSpan;
use tracing_subscriber::fmt::Layer as FmtLayer;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::EnvFilter;
use tracing_subscriber::Layer;
use tracing_subscriber::Registry;

static RUST_INIT: Once = Once::new();

/// We use this function to ensure everything we need to initialized as the Rust code may not be
/// called when EdenFS starts. Right now it only calls `env_logger::init` so we can see logs from
/// `edenapi` and other crates. In longer term we should bridge the logs to folly logging.
pub(crate) fn backingstore_global_init() {
    RUST_INIT.call_once(|| {
        if let Some((var_name, _)) = identity::debug_env_var("LOG") {
            let data = Arc::new(Mutex::new(TracingData::new()));
            let collector = tracing_collector::TracingCollector::new(data);
            let env_filter = EnvFilter::from_env(var_name);
            let env_logger = FmtLayer::new()
                .with_span_events(FmtSpan::ACTIVE)
                .with_ansi(false)
                .with_writer(io::stderr);
            let subscriber = Registry::default()
                .with(collector)
                .with(env_filter.and_then(env_logger));
            if let Err(e) = tracing::subscriber::set_global_default(subscriber) {
                eprintln!("Failed to set rust tracing subscriber: {:?}", e);
            }
        }
        env_logger::init();

        edenapi::Builder::register_customize_build_func(eagerepo::edenapi_from_config);
    });
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

//! All structs and functions that are intended to be used in C/C++ code should be placed in this
//! mode, and all extern functions should have the `rust_` prefix to indicate the implementation of
//! the function is written in Rust. Changes to this mod may need regenerations of the C/C++
//! binding header. To regenerate the binding header, run `./tools/cbindgen.sh`.

mod auxdata;
mod backingstore;
mod cbytes;
mod cfallible;
mod init;
mod request;
mod slice;
mod tests;
mod tree;

pub use auxdata::FileAuxData;
pub use cbytes::CBytes;
pub use cfallible::CFallible;
pub use cfallible::CFallibleBase;
pub use request::Request;
pub use slice::Slice;
pub use tree::Tree;


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::slice;

use types::Key;
use types::Node;
use types::RepoPathBuf;

// Number of bytes of a node.
const NODE_LENGTH: usize = 20;

#[repr(C)]
pub struct Request {
    node: *const u8,
}

impl Request {
    pub fn key(&self) -> Key {
        let node: &[u8] = unsafe { slice::from_raw_parts(self.node, NODE_LENGTH) };
        Key::new(RepoPathBuf::new(), Node::from_slice(node).unwrap())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::slice;

use libc::size_t;

unsafe fn view_to_slice<'a, T, U>(ptr: *const T, length: size_t) -> &'a [U] {
    if ptr.is_null() {
        assert!(length == 0, "null slices must have zero length");
        &[]
    } else {
        // TODO: validate sizeof(T) * len < isize::MAX
        slice::from_raw_parts(ptr as *const U, length)
    }
}

#[repr(C)]
pub struct Slice<T> {
    ptr: *const T,
    len: size_t,
}

impl<T> Slice<T> {
    pub fn slice<'a>(&'a self) -> &'a [T] {
        unsafe { view_to_slice(self.ptr, self.len) }
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

//! This mod provides utilities functions needed for running tests.

use anyhow::anyhow;

use crate::raw::CBytes;
use crate::raw::CFallible;
use crate::raw::CFallibleBase;

/// Returns a `CFallible` with success return value 1. This function is intended to be called from
/// C++ tests.
#[no_mangle]
pub extern "C" fn sapling_test_cfallible_ok() -> CFallibleBase {
    CFallible::ok(0xFB).into()
}

#[no_mangle]
pub extern "C" fn sapling_test_cfallible_ok_free(val: *mut u8) {
    let x = unsafe { Box::from_raw(val) };
    drop(x);
}

/// Returns a `CFallible` with error message "context: failure!". This function is intended to be called
/// from C++ tests.
#[no_mangle]
pub extern "C" fn sapling_test_cfallible_err() -> CFallibleBase {
    CFallible::<u8>::err(anyhow!("failure!").context("context")).into()
}

#[no_mangle]
pub extern "C" fn sapling_test_cbytes() -> CBytes {
    CBytes::from_vec("hello world".to_string().into_bytes())
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

//! Representation of tree in EdenFS.
//!
//! Structs in this file should be keep in sync with `eden/fs/model/{Tree, TreeEntry}.h`.

use std::collections::HashMap;

use anyhow::format_err;
use anyhow::Result;
use manifest::FileType;
use manifest::FsNodeMetadata;
use manifest::List;
use revisionstore::scmstore::file::FileAuxData;
use types::HgId;
use types::PathComponentBuf;

use crate::raw::CBytes;

#[repr(u8)]
pub enum TreeEntryType {
    Tree,
    RegularFile,
    ExecutableFile,
    Symlink,
}

impl TreeEntryType {
    /// Returns `None` for entries that need to be skipped.
    fn from_file_type(file_type: FileType) -> Option<Self> {
        let entry_type = match file_type {
            FileType::Regular => TreeEntryType::RegularFile,
            FileType::Executable => TreeEntryType::ExecutableFile,
            FileType::Symlink => TreeEntryType::Symlink,
            FileType::GitSubmodule => return None,
        };
        Some(entry_type)
    }
}

#[repr(C)]
pub struct TreeEntry {
    hash: CBytes,
    name: CBytes,
    ttype: TreeEntryType,
    // Using pointer as `Option<T>`
    size: *mut u64,
    content_sha1: *mut CBytes,
    content_blake3: *mut CBytes,
}

impl TreeEntry {
    fn try_from_path_node(
        path: PathComponentBuf,
        node: FsNodeMetadata,
        aux: &HashMap<HgId, FileAuxData>,
    ) -> Option<Result<Self>> {
        let (ttype, hash, size, content_sha1, content_blake3) = match node {
            FsNodeMetadata::Directory(Some(hgid)) => (
                TreeEntryType::Tree,
                hgid.as_ref().to_vec(),
                None,
                None,
                None,
            ),
            FsNodeMetadata::File(metadata) => {
                let entry_type = match TreeEntryType::from_file_type(metadata.file_type) {
                    None => return None,
                    Some(entry_type) => entry_type,
                };
                if let Some(aux_data) = aux.get(&metadata.hgid) {
                    (
                        entry_type,
                        metadata.hgid.as_ref().to_vec(),
                        Some(aux_data.total_size),
                        Some(aux_data.content_sha1),
                        aux_data.content_seeded_blake3,
                    )
                } else {
                    (
                        entry_type,
                        metadata.hgid.as_ref().to_vec(),
                        None,
                        None,
                        None,
                    )
                }
            }
            _ => return Some(Err(format_err!("received an ephemeral directory"))),
        };

        let entry = TreeEntry {
            hash: hash.into(),
            name: path.as_ref().as_byte_slice().to_vec().into(),
            ttype,
            size: size.map_or(std::ptr::null_mut(), |size| {
                let boxed_size = Box::new(size);
                Box::into_raw(boxed_size)
            }),
            content_sha1: content_sha1.map_or(std::ptr::null_mut(), |content_sha1| {
                let boxed_sha1 = Box::new(content_sha1.as_ref().to_vec().into());
                Box::into_raw(boxed_sha1)
            }),
            content_blake3: content_blake3.map_or(std::ptr::null_mut(), |content_blake3| {
                let boxed_blake3 = Box::new(content_blake3.as_ref().to_vec().into());
                Box::into_raw(boxed_blake3)
            }),
        };
        Some(Ok(entry))
    }
}

#[repr(C)]
pub struct Tree {
    entries: *const TreeEntry,
    /// This makes sure `entries` above is pointing to a valid memory.
    entries_ptr: *mut Vec<TreeEntry>,
    length: usize,
}

impl TryFrom<(List, HashMap<HgId, FileAuxData>)> for Tree {
    type Error = anyhow::Error;

    fn try_from(entries: (List, HashMap<HgId, FileAuxData>)) -> Result<Self, Self::Error> {
        match entries.0 {
            List::NotFound | List::File => Err(format_err!("not found")),
            List::Directory(list) => {
                let entries = list
                    .into_iter()
                    .filter_map(|(path, node)| {
                        TreeEntry::try_from_path_node(path, node, &entries.1)
                    })
                    .collect::<Result<Vec<_>>>()?;

                let entries = Box::new(entries);
                let length = entries.len();

                Ok(Tree {
                    entries: entries.as_ptr(),
                    entries_ptr: Box::into_raw(entries),
                    length,
                })
            }
        }
    }
}

impl Drop for Tree {
    fn drop(&mut self) {
        let entries = unsafe { Box::from_raw(self.entries_ptr) };
        for entry in entries.iter() {
            let size = unsafe {
                if entry.size.is_null() {
                    None
                } else {
                    Some(Box::from_raw(entry.size))
                }
            };
            drop(size);
            let content_sha1 = unsafe {
                if entry.content_sha1.is_null() {
                    None
                } else {
                    Some(Box::from_raw(entry.content_sha1))
                }
            };
            drop(content_sha1);
            let content_blake3 = unsafe {
                if entry.content_blake3.is_null() {
                    None
                } else {
                    Some(Box::from_raw(entry.content_blake3))
                }
            };
            drop(content_blake3);
        }
        drop(entries);
    }
}

#[no_mangle]
pub extern "C" fn sapling_tree_free(tree: *mut Tree) {
    assert!(!tree.is_null());
    let tree = unsafe { Box::from_raw(tree) };
    drop(tree);
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use blackbox::event::Event;
use blackbox::event::NetworkOp;
use blackbox::Blackbox;
use blackbox::BlackboxOptions;
use blackbox::IndexFilter;
use minibench::bench;
use minibench::measure::Both;
use minibench::measure::Bytes;
use minibench::measure::Measure;
use minibench::measure::WallClock;
use minibench::measure::IO;
use serde_json::json;
use tempfile::tempdir;

// Insert 4000 entries
fn insert(blackbox: &mut Blackbox) {
    for _ in 0..2000 {
        blackbox.log(&Event::Alias {
            from: "foo".to_string(),
            to: "bar".to_string(),
        });
        blackbox.log(&Event::Network {
            op: NetworkOp::SshGetFiles,
            read_bytes: 1000,
            write_bytes: 9000,
            duration_ms: 100,
            calls: 2,
            latency_ms: 10,
            result: None,
        });
    }
    blackbox.sync();
}

fn main() {
    bench("blackbox insertion (4000 entries)", || {
        let dir = tempdir().unwrap();
        let mut blackbox = BlackboxOptions::new().open(dir.path()).unwrap();
        Both::<Both<WallClock, IO>, Bytes>::measure(move || {
            insert(&mut blackbox);
            dir.path().join("0").join("log").metadata().unwrap().len()
        })
    });

    {
        let dir = tempdir().unwrap();
        let mut blackbox = BlackboxOptions::new().open(dir.path()).unwrap();
        insert(&mut blackbox);

        bench("blackbox filter by index (4000 entries)", || {
            Both::<WallClock, IO>::measure(|| {
                blackbox.filter::<Event>(IndexFilter::Time(0, u64::max_value()), None);
            })
        });

        bench("blackbox filter by pattern (4000 entries)", || {
            Both::<WallClock, IO>::measure(|| {
                blackbox.filter::<Event>(
                    IndexFilter::Nop,
                    Some(json!({"network": {"read_bytes": ["range", 10, 2000]}})),
                );
            })
        });
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

extern crate proc_macro;
use proc_macro::Group;
use proc_macro::Ident;
use proc_macro::TokenStream;
use proc_macro::TokenTree;

/// Define an alternative structure (or enum) ending with `Alt`, with serde
/// attribute "alias" and "rename" swapped.
#[proc_macro_attribute]
pub fn serde_alt(_attr: TokenStream, item: TokenStream) -> TokenStream {
    fn translate(stream: TokenStream) -> TokenStream {
        let mut result = TokenStream::new();
        for tt in stream {
            let new_tt: TokenTree = match tt {
                TokenTree::Group(group) => {
                    let new_stream = translate(group.stream());
                    let new_group = Group::new(group.delimiter(), new_stream);
                    new_group.into()
                }
                TokenTree::Ident(id) => {
                    let name = id.to_string();
                    let new_name = if name.chars().next().unwrap().is_uppercase()
                        && name != "BTreeMap"
                        && name != "Clone"
                        && name != "Copy"
                        && name != "Debug"
                        && name != "Default"
                        && name != "Deserialize"
                        && name != "Display"
                        && name != "Eq"
                        && name != "Formatter"
                        && name != "HashMap"
                        && name != "Option"
                        && name != "Ord"
                        && name != "PartialEq"
                        && name != "Result"
                        && name != "Serialize"
                        && name != "String"
                        && name != "Value"
                        && name != "Vec"
                    {
                        // Append "Alt" to the type name
                        format!("{}Alt", name)
                    } else if name == "alias" {
                        // Translate "alias" to "rename"
                        "rename".to_string()
                    } else if name == "rename" {
                        // Translate "rename" to "alias"
                        "alias".to_string()
                    } else {
                        // Unchanged (ex. "u32", "String", field names)
                        name
                    };
                    Ident::new(&new_name, id.span()).into()
                }
                _ => tt.into(),
            };
            let new_stream: TokenStream = new_tt.into();
            result.extend(new_stream);
        }
        result
    }

    let mut new_stream = translate(item.clone());
    new_stream.extend(item);
    new_stream
}