/*
 * 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 gotham::state::State;
use gotham_ext::middleware::MetadataState;
use gotham_ext::middleware::Middleware;
use gotham_ext::middleware::PostResponseCallbacks;
use hyper::Body;
use hyper::Response;
use hyper::StatusCode;
use permission_checker::MononokeIdentitySetExt;
use stats::prelude::*;

use crate::handlers::EdenApiMethod;
use crate::handlers::HandlerInfo;

define_stats! {
    prefix = "mononoke.edenapi.request";
    requests: dynamic_timeseries("{}.requests", (method: String); Rate, Sum),
    success: dynamic_timeseries("{}.success", (method: String); Rate, Sum),
    failure_4xx: dynamic_timeseries("{}.failure_4xx", (method: String); Rate, Sum),
    failure_5xx: dynamic_timeseries("{}.failure_5xx", (method: String); Rate, Sum),
    response_bytes_sent: dynamic_histogram("{}.response_bytes_sent", (method: String); 1_500_000, 0, 150_000_000, Average, Sum, Count; P 50; P 75; P 95; P 99),
    blame_duration_ms: histogram(100, 0, 5000, Average, Sum, Count; P 50; P 75; P 95; P 99),
    capabilities_duration_ms: histogram(100, 0, 5000, Average, Sum, Count; P 50; P 75; P 95; P 99),
    files_duration_ms: histogram(100, 0, 5000, Average, Sum, Count; P 50; P 75; P 95; P 99),
    files2_duration_ms: histogram(100, 0, 5000, Average, Sum, Count; P 50; P 75; P 95; P 99),
    trees_duration_ms: histogram(100, 0, 5000, Average, Sum, Count; P 50; P 75; P 95; P 99),
    history_duration_ms: histogram(100, 0, 5000, Average, Sum, Count; P 50; P 75; P 95; P 99),
    commit_location_to_hash_duration_ms: histogram(10, 0, 500, Average, Sum, Count; P 50; P 75; P 95; P 99),
    commit_hash_to_location_duration_ms: histogram(10, 0, 500, Average, Sum, Count; P 50; P 75; P 95; P 99),
    commit_revlog_data_duration_ms: histogram(10, 0, 500, Average, Sum, Count; P 50; P 75; P 95; P 99),
    commit_hash_lookup_duration_ms: histogram(10, 0, 500, Average, Sum, Count; P 50; P 75; P 95; P 99),
    clone_duration: dynamic_histogram("{}.clone_data_ms", (repo: String); 10, 0, 500, Average, Sum, Count; P 50; P 75; P 95; P 99),
    bookmarks_duration_ms: histogram(10, 0, 500, Average, Sum, Count; P 50; P 75; P 95; P 99),
    set_bookmark_duration_ms: histogram(10, 0, 500, Average, Sum, Count; P 50; P 75; P 95; P 99),
    land_stack_duration_ms: histogram(10, 0, 500, Average, Sum, Count; P 50; P 75; P 95; P 99),
    lookup_duration_ms: histogram(100, 0, 5000, Average, Sum, Count; P 50; P 75; P 95; P 99),
    upload_file_duration_ms: histogram(100, 0, 5000, Average, Sum, Count; P 50; P 75; P 95; P 99),
    pull_fast_forward_duration_ms: histogram(100, 0, 5000, Average, Sum, Count; P 50; P 75; P 95; P 99),
    pull_lazy_duration_ms: histogram(100, 0, 5000, Average, Sum, Count; P 50; P 75; P 95; P 99),
    upload_hg_filenodes_duration_ms: histogram(100, 0, 5000, Average, Sum, Count; P 50; P 75; P 95; P 99),
    upload_trees_duration_ms: histogram(100, 0, 5000, Average, Sum, Count; P 50; P 75; P 95; P 99),
    upload_hg_changesets_duration_ms: histogram(100, 0, 5000, Average, Sum, Count; P 50; P 75; P 95; P 99),
    upload_bonsai_changeset_duration_ms: histogram(100, 0, 5000, Average, Sum, Count; P 50; P 75; P 95; P 99),
    ephemeral_prepare_duration_ms: histogram(100, 0, 5000, Average, Sum, Count; P 50; P 75; P 95; P 99),
    fetch_snapshot_duration_ms: histogram(100, 0, 5000, Average, Sum, Count; P 50; P 75; P 95; P 99),
    alter_snapshot_duration_ms: histogram(100, 0, 5000, Average, Sum, Count; P 50; P 75; P 95; P 99),
    commit_graph_duration_ms: histogram(100, 0, 5000, Average, Sum, Count; P 50; P 75; P 95; P 99),
    commit_graph_v2_duration_ms: histogram(100, 0, 5000, Average, Sum, Count; P 50; P 75; P 95; P 99),
    commit_graph_segments_duration_ms: histogram(100, 0, 5000, Average, Sum, Count; P 50; P 75; P 95; P 99),
    download_file_duration_ms: histogram(100, 0, 5000, Average, Sum, Count; P 50; P 75; P 95; P 99),
    commit_mutations_duration_ms: histogram(100, 0, 5000, Average, Sum, Count; P 50; P 75; P 95; P 99),
    commit_translate_id_duration_ms: histogram(100, 0, 5000, Average, Sum, Count; P 50; P 75; P 95; P 99),
}

fn log_stats(state: &mut State, status: StatusCode) -> Option<()> {
    // Proxygen can be configured to periodically send a preconfigured set of
    // requests to check server health. These requests will look like ordinary
    // user requests, but should be filtered out of the server's metrics.
    match state.try_borrow::<MetadataState>() {
        Some(state) if state.metadata().identities().is_proxygen_test_identity() => {
            return None;
        }
        _ => {}
    }

    let hander_info = state.try_borrow::<HandlerInfo>()?;
    let method = hander_info.method?;
    let repo = hander_info.repo.clone()?;

    let callbacks = state.try_borrow_mut::<PostResponseCallbacks>()?;

    callbacks.add(move |info| {
        if let Some(duration) = info.duration {
            let dur_ms = duration.as_millis() as i64;

            use EdenApiMethod::*;
            match method {
                Blame => STATS::blame_duration_ms.add_value(dur_ms),
                Capabilities => STATS::capabilities_duration_ms.add_value(dur_ms),
                Files => STATS::files_duration_ms.add_value(dur_ms),
                Files2 => STATS::files2_duration_ms.add_value(dur_ms),
                Trees => STATS::trees_duration_ms.add_value(dur_ms),
                History => STATS::history_duration_ms.add_value(dur_ms),
                CommitLocationToHash => {
                    STATS::commit_location_to_hash_duration_ms.add_value(dur_ms)
                }
                CommitHashToLocation => {
                    STATS::commit_hash_to_location_duration_ms.add_value(dur_ms)
                }
                CommitRevlogData => STATS::commit_revlog_data_duration_ms.add_value(dur_ms),
                CommitHashLookup => STATS::commit_hash_lookup_duration_ms.add_value(dur_ms),
                Clone => STATS::clone_duration.add_value(dur_ms, (repo,)),
                Bookmarks => STATS::bookmarks_duration_ms.add_value(dur_ms),
                SetBookmark => STATS::set_bookmark_duration_ms.add_value(dur_ms),
                LandStack => STATS::land_stack_duration_ms.add_value(dur_ms),
                Lookup => STATS::lookup_duration_ms.add_value(dur_ms),
                UploadFile => STATS::upload_file_duration_ms.add_value(dur_ms),
                PullFastForwardMaster => STATS::pull_fast_forward_duration_ms.add_value(dur_ms),
                PullLazy => STATS::pull_lazy_duration_ms.add_value(dur_ms),
                UploadHgFilenodes => STATS::upload_hg_filenodes_duration_ms.add_value(dur_ms),
                UploadTrees => STATS::upload_trees_duration_ms.add_value(dur_ms),
                UploadHgChangesets => STATS::upload_hg_changesets_duration_ms.add_value(dur_ms),
                UploadBonsaiChangeset => {
                    STATS::upload_bonsai_changeset_duration_ms.add_value(dur_ms)
                }
                EphemeralPrepare => STATS::ephemeral_prepare_duration_ms.add_value(dur_ms),
                FetchSnapshot => STATS::fetch_snapshot_duration_ms.add_value(dur_ms),
                AlterSnapshot => STATS::alter_snapshot_duration_ms.add_value(dur_ms),
                CommitGraphV2 => STATS::commit_graph_v2_duration_ms.add_value(dur_ms),
                CommitGraphSegments => STATS::commit_graph_segments_duration_ms.add_value(dur_ms),
                DownloadFile => STATS::download_file_duration_ms.add_value(dur_ms),
                CommitMutations => STATS::commit_mutations_duration_ms.add_value(dur_ms),
                CommitTranslateId => STATS::commit_translate_id_duration_ms.add_value(dur_ms),
            }
        }

        let method = method.to_string();
        STATS::requests.add_value(1, (method.clone(),));

        if status.is_success() {
            STATS::success.add_value(1, (method.clone(),));
        } else if status.is_client_error() {
            STATS::failure_4xx.add_value(1, (method.clone(),));
        } else if status.is_server_error() {
            STATS::failure_5xx.add_value(1, (method.clone(),));
        }

        if let Some(response_bytes_sent) = info.meta.as_ref().map(|m| m.body().bytes_sent) {
            STATS::response_bytes_sent.add_value(response_bytes_sent as i64, (method,))
        }
    });

    Some(())
}

pub struct OdsMiddleware;

impl OdsMiddleware {
    pub fn new() -> Self {
        OdsMiddleware
    }
}

#[async_trait::async_trait]
impl Middleware for OdsMiddleware {
    async fn outbound(&self, state: &mut State, response: &mut Response<Body>) {
        log_stats(state, response.status());
    }
}


/*
 * 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::sync::Arc;

use context::CoreContext;
use context::SessionContainer;
use fbinit::FacebookInit;
use gotham::state::FromState;
use gotham::state::State;
use gotham_derive::StateData;
use gotham_ext::middleware::MetadataState;
use gotham_ext::middleware::Middleware;
use gotham_ext::state_ext::StateExt;
use hyper::Body;
use hyper::Response;
use metadata::Metadata;
use rate_limiting::RateLimitEnvironment;
use scuba_ext::MononokeScubaSampleBuilder;
use slog::o;
use slog::Logger;

#[derive(StateData, Clone)]
pub struct RequestContext {
    pub ctx: CoreContext,
    pub logger: Logger,
}

impl RequestContext {
    async fn new(ctx: CoreContext, logger: Logger) -> Self {
        Self { ctx, logger }
    }
}

#[derive(Clone)]
pub struct RequestContextMiddleware {
    fb: FacebookInit,
    logger: Logger,
    scuba: Arc<MononokeScubaSampleBuilder>,
    rate_limiter: Option<RateLimitEnvironment>,
    readonly: bool,
}

impl RequestContextMiddleware {
    pub fn new(
        fb: FacebookInit,
        logger: Logger,
        scuba: MononokeScubaSampleBuilder,
        rate_limiter: Option<RateLimitEnvironment>,
        readonly: bool,
    ) -> Self {
        Self {
            fb,
            logger,
            scuba: Arc::new(scuba),
            rate_limiter,
            readonly,
        }
    }
}

#[async_trait::async_trait]
impl Middleware for RequestContextMiddleware {
    async fn inbound(&self, state: &mut State) -> Option<Response<Body>> {
        let metadata = if let Some(metadata_state) = MetadataState::try_borrow_from(state) {
            metadata_state.metadata().clone()
        } else {
            Metadata::default()
        };

        let session = SessionContainer::builder(self.fb)
            .metadata(Arc::new(metadata))
            .readonly(self.readonly)
            .rate_limiter(self.rate_limiter.as_ref().map(|r| r.get_rate_limiter()))
            .build();

        let request_id = state.short_request_id();
        let logger = self.logger.new(o!("request_id" => request_id.to_string()));
        let scuba = (*self.scuba).clone().with_seq("seq");
        let ctx = session.new_context(logger.clone(), scuba);

        state.put(RequestContext::new(ctx, logger).await);

        None
    }
}


/*
 * 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::HashSet;

use anyhow::bail;
use anyhow::Context;
use anyhow::Result;
use base64::encode as b64encode;
use bytes::Bytes;
use fbinit::FacebookInit;
use gotham::state::FromState;
use gotham::state::State;
use gotham_derive::StateData;
use gotham_ext::middleware::Middleware;
use gotham_ext::middleware::PostResponseCallbacks;
use gotham_ext::state_ext::StateExt;
use http::HeaderMap;
use hyper::Body;
use hyper::Response;
use lazy_static::lazy_static;
use scuba_ext::MononokeScubaSampleBuilder;
use slog::trace;
use slog::warn;
use tunables::tunables;

use crate::middleware::RequestContext;

static MAX_BODY_LEN: usize = 16 * 1024; // 16 KB
static MAX_BODY_LEN_DEBUG: usize = 4 * 1024; // 4 KB
const UPLOAD_PATH: &str = "/upload/";

lazy_static! {
    static ref FILTERED_HEADERS: HashSet<&'static str> = {
        let mut m = HashSet::new();
        m.insert("x-auth-cats");
        m
    };
}

#[derive(Debug, StateData, Clone, PartialEq)]
enum LogAction {
    Log,
    BodyTooBig,
    Upload,
}

#[derive(Debug, StateData, Clone)]
pub struct RequestDumper {
    logger: MononokeScubaSampleBuilder,
    log_action: LogAction,
    log_deserialized: bool,
}

fn get_content_len(headers: &HeaderMap) -> Option<usize> {
    let content_len = headers.get(http::header::CONTENT_LENGTH)?;
    let cl: Result<usize> = try { content_len.to_str()?.parse()? };
    cl.ok()
}

impl RequestDumper {
    pub fn add_http_req_prefix(&mut self, state: &State, headers: &HeaderMap) -> Result<()> {
        // Log request_id to match between scuba tables.
        self.logger
            .add("request_id", state.short_request_id().to_string());

        let method = http::method::Method::try_borrow_from(state)
            .context("Method not present in State")?
            .as_str();
        self.logger.add("method", method);

        let uri = http::uri::Uri::try_borrow_from(state).context("Uri not present in State")?;
        let uristr = uri
            .path_and_query()
            .context("path_and_query is None")?
            .as_str();

        if uristr.contains(UPLOAD_PATH) {
            self.log_action = LogAction::Upload;
            return Ok(());
        }

        self.logger.add("path", uristr);

        let mut headers_hs = HashSet::new();
        for (k, v) in headers
            .iter()
            .filter(|(k, _v)| !FILTERED_HEADERS.contains(k.as_str()))
        {
            headers_hs.insert(format!("{}: {}", k.as_str(), v.to_str()?));
        }
        self.logger.add("headers", headers_hs);
        Ok(())
    }

    fn should_log(&self) -> bool {
        match self.log_action {
            LogAction::Log => true,
            LogAction::BodyTooBig => false,
            LogAction::Upload => false,
        }
    }

    fn should_log_deserialized(&self) -> bool {
        self.should_log() && self.log_deserialized
    }

    pub fn set_log_deserialized(&mut self, log_deserialized: bool) {
        self.log_deserialized = log_deserialized;
    }

    pub fn log(&mut self) -> Result<()> {
        if !self.should_log() {
            bail!(
                "Shouldn't log this request. Either sampled or {:?}",
                self.log_action
            )
        }
        if !self.logger.log() {
            bail!("failed to dump request")
        }
        Ok(())
    }

    // If the request is not too big, log encoded, so it can be replayed.
    pub fn add_body(&mut self, body: &Bytes) {
        if !self.should_log() {
            return;
        }
        if body.len() > MAX_BODY_LEN {
            self.log_action = LogAction::BodyTooBig;
            return;
        }
        self.logger.add("body", b64encode(&body[..]));
    }

    // If the request is very small, log the request in human readable format.
    pub fn add_request<R>(&mut self, request: &R)
    where
        R: std::fmt::Debug,
    {
        if self.should_log_deserialized() {
            self.logger.add("request", format!("{:?}", request));
        }
    }

    // Add duration in ms for the origin request
    pub fn add_duration(&mut self, duration: i64) {
        self.logger.add("duration_ms_origin", duration);
    }

    pub fn new(fb: FacebookInit) -> Self {
        let scuba = MononokeScubaSampleBuilder::new(fb, "mononoke_replay_logged_edenapi_requests")
            .expect("Couldn't create scuba sample builder");
        Self {
            logger: scuba,
            log_action: LogAction::Log,
            log_deserialized: false,
        }
    }
}

#[derive(Clone)]
pub struct RequestDumperMiddleware {
    fb: FacebookInit,
}

impl RequestDumperMiddleware {
    pub fn new(fb: FacebookInit) -> Self {
        Self { fb }
    }
}

#[async_trait::async_trait]
impl Middleware for RequestDumperMiddleware {
    async fn inbound(&self, state: &mut State) -> Option<Response<Body>> {
        let logger = &RequestContext::borrow_from(state).logger;
        let headers = match HeaderMap::try_borrow_from(state).context("No headers in State") {
            Ok(headers) => headers,
            Err(e) => {
                warn!(logger, "Error when borrowing headers from State: {}", e);
                return None;
            }
        };
        let mut log_deserialized = false;
        if let Some(len) = get_content_len(headers) {
            if len > MAX_BODY_LEN {
                trace!(logger, "Body too big ({}), not recording", len);
                return None;
            }
            if len <= MAX_BODY_LEN_DEBUG {
                log_deserialized = true;
            }
        }
        let mut rd = RequestDumper::new(self.fb);
        if let Err(e) = rd.add_http_req_prefix(state, headers) {
            warn!(
                logger,
                "Err while attempting to record http req prefix: {}", e
            );
            return None;
        }
        rd.set_log_deserialized(log_deserialized);
        state.put(rd);
        None
    }

    async fn outbound(&self, state: &mut State, _response: &mut Response<Body>) {
        if let Some(mut request_dumper) = state.try_take::<RequestDumper>() {
            let rctx = RequestContext::borrow_from(state).clone();
            let logger = rctx.logger;
            if let Some(callbacks) = state.try_borrow_mut::<PostResponseCallbacks>() {
                callbacks.add(move |info| {
                    let dur_ms = if let Some(duration) = info.duration {
                        duration.as_millis() as i64
                    } else {
                        0
                    };
                    let threshold = tunables::tunables()
                        .edenapi_req_dumper_unsampled_duration_threshold_ms()
                        .unwrap_or_default();
                    let slow_request: bool = (threshold > 0) && (dur_ms > threshold);
                    let sample_ratio: u64 = tunables()
                        .edenapi_req_dumper_sample_ratio()
                        .unwrap_or_default()
                        .try_into()
                        .unwrap_or_default();
                    // Do not sample slow requests if tunables say
                    if !slow_request
                        && (sample_ratio == 0 || (rand::random::<u64>() % sample_ratio) != 0)
                    {
                        trace!(logger, "Won't record this request");
                        return;
                    }
                    request_dumper.add_duration(dur_ms);
                    if let Err(e) = request_dumper.log() {
                        warn!(logger, "Couldn't dump request: {}", e);
                    }
                });
            }
        }
    }
}


/*
 * 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.
 */

//! cbor.rs - Utilities for working with CBOR data in HTTP requests and responses.
use std::pin::Pin;

use anyhow::format_err;
use anyhow::Context;
use anyhow::Error;
use bytes::Bytes;
use edenapi_types::ToApi;
use edenapi_types::ToWire;
use futures::prelude::*;
use futures::ready;
use futures::task::Poll;
use gotham::state::State;
use gotham_ext::content_encoding::ContentEncoding;
use gotham_ext::error::HttpError;
use gotham_ext::response::ContentMetaProvider;
use gotham_ext::response::ErrorMeta;
use gotham_ext::response::ErrorMetaProvider;
use gotham_ext::response::ResponseStream;
use gotham_ext::response::ResponseTryStreamExt;
use gotham_ext::response::StreamBody;
use gotham_ext::response::TryIntoResponse;
use mime::Mime;
use once_cell::sync::Lazy;
use pin_project::pin_project;
use serde::de::DeserializeOwned;
use serde::Serialize;

use super::get_request_body;
use crate::errors::ErrorKind;

static CBOR_MIME: Lazy<Mime> = Lazy::new(|| "application/cbor".parse().unwrap());

pub fn cbor_mime() -> Mime {
    CBOR_MIME.clone()
}

pub fn to_cbor_bytes<S: Serialize>(s: &S) -> Result<Bytes, Error> {
    serde_cbor::to_vec(s)
        .map(Bytes::from)
        .context(ErrorKind::SerializationFailed)
}

/// Serialize each item of the input stream as CBOR and return a streaming
/// response. Any errors yielded by the stream will be filtered out.
pub fn cbor_stream_filtered_errors<S, T>(stream: S) -> impl TryIntoResponse
where
    S: Stream<Item = Result<T, Error>> + Send + 'static,
    T: Serialize + Send + 'static,
{
    let byte_stream = stream.and_then(|item| async move { to_cbor_bytes(&item) });
    let content_stream = ResponseStream::new(byte_stream).capture_first_err();

    StreamBody::new(content_stream, cbor_mime())
}

pub fn custom_cbor_stream<S, T, C, E>(stream: S, error_classifier: C) -> impl TryIntoResponse
where
    S: Stream<Item = T> + Send + 'static,
    T: ToWire + Send + 'static,
    C: (Fn(&T) -> Option<&E>) + Send + 'static,
    E: std::error::Error,
{
    let content_stream = CustomCborStream::new(stream, error_classifier);
    StreamBody::new(content_stream, cbor_mime())
}

pub async fn parse_cbor_request<R: DeserializeOwned>(state: &mut State) -> Result<R, HttpError> {
    let body = get_request_body(state).await?;
    serde_cbor::from_slice(&body)
        .context(ErrorKind::DeserializationFailed)
        .map_err(HttpError::e400)
}

pub async fn parse_wire_request<R: DeserializeOwned + ToApi>(
    state: &mut State,
) -> Result<<R as ToApi>::Api, HttpError>
where
    <R as ToApi>::Error: Send + Sync + 'static + std::error::Error,
{
    let cbor = parse_cbor_request::<R>(state).await?;
    cbor.to_api().map_err(HttpError::e400)
}

/// Supports error reporting for endpoint that serialize errors to clients.
#[pin_project]
pub struct CustomCborStream<S, F> {
    #[pin]
    stream: S,
    /// Evaluates whether an item is an error.
    classifier: F,
    /// First error this stream encountered.
    first_error: Option<Error>,
    /// Count of errors that were observed but not captured.
    extra_error_count: u64,
}

impl<S, F> CustomCborStream<S, F> {
    pub fn new(stream: S, classifier: F) -> Self {
        Self {
            stream,
            classifier,
            first_error: None,
            extra_error_count: 0,
        }
    }
}

impl<S, F, E> Stream for CustomCborStream<S, F>
where
    S: Stream,
    S::Item: ToWire,
    F: Fn(&S::Item) -> Option<&E>,
    E: std::fmt::Debug,
{
    type Item = Bytes;

    fn poll_next(
        self: Pin<&mut Self>,
        ctx: &mut futures::task::Context<'_>,
    ) -> Poll<Option<Self::Item>> {
        let mut this = self.project();

        match ready!(this.stream.as_mut().poll_next(ctx)) {
            Some(item) => {
                let classifier = &mut this.classifier;
                if let Some(e) = classifier(&item) {
                    if this.first_error.is_some() {
                        *this.extra_error_count += 1;
                    } else {
                        this.first_error.replace(format_err!("{:?}", e));
                    }
                }
                match to_cbor_bytes(&item.to_wire()) {
                    Ok(serialized) => Poll::Ready(Some(serialized)),
                    Err(e) => {
                        if this.first_error.is_some() {
                            *this.extra_error_count += 1;
                        } else {
                            this.first_error.replace(e);
                        }
                        // We end the stream early to signal that we have an unexpected error.
                        // Avoiding the error would make it too similar to "application" errors
                        // which are commonly omitted in the EdenApi response stream.
                        Poll::Ready(None)
                    }
                }
            }
            None => Poll::Ready(None),
        }
    }
}

impl<S, F> ErrorMetaProvider<Error> for CustomCborStream<S, F>
where
    S: Stream,
{
    fn report_errors(self: Pin<&mut Self>, errors: &mut ErrorMeta<Error>) {
        let this = self.project();
        errors.errors.extend(this.first_error.take());
        errors.extra_error_count += *this.extra_error_count;
    }
}

impl<S, F> ContentMetaProvider for CustomCborStream<S, F> {
    fn content_encoding(&self) -> ContentEncoding {
        ContentEncoding::Identity
    }

    fn content_length(&self) -> Option<u64> {
        None
    }
}

#[cfg(test)]
mod tests {
    use serde::Deserialize;

    use super::*;

    #[derive(Serialize, Deserialize, Clone)]
    struct CustomWireResult {
        ok: Option<i32>,
        err: Option<i32>,
    }

    impl CustomWireResult {
        pub fn ok(ok: i32) -> Self {
            CustomWireResult {
                ok: Some(ok),
                err: None,
            }
        }

        pub fn err(err: i32) -> Self {
            CustomWireResult {
                ok: None,
                err: Some(err),
            }
        }
    }

    struct CustomResult(Result<i32, i32>);

    impl ToWire for CustomResult {
        type Wire = CustomWireResult;

        fn to_wire(self) -> Self::Wire {
            match self.0 {
                Ok(t) => CustomWireResult::ok(t),
                Err(e) => CustomWireResult::err(e),
            }
        }
    }

    impl ToApi for CustomWireResult {
        type Api = CustomResult;
        type Error = std::convert::Infallible;

        fn to_api(self) -> Result<Self::Api, Self::Error> {
            unimplemented!()
        }
    }

    #[tokio::test]
    async fn test_monitor_err() {
        let results = vec![
            CustomResult(Ok(10)),
            CustomResult(Ok(12)),
            CustomResult(Err(-1)),
            CustomResult(Ok(11)),
            CustomResult(Err(-2)),
        ];
        fn classify(x: &CustomResult) -> Option<&i32> {
            x.0.as_ref().err()
        }
        let s = CustomCborStream::new(stream::iter(results), classify);

        futures::pin_mut!(s);

        let tcb = |cr: &CustomWireResult| to_cbor_bytes(cr).unwrap();
        assert_eq!(s.next().await, Some(tcb(&CustomWireResult::ok(10))));
        assert_eq!(s.next().await, Some(tcb(&CustomWireResult::ok(12))));
        assert_eq!(s.next().await, Some(tcb(&CustomWireResult::err(-1))));
        assert_eq!(s.next().await, Some(tcb(&CustomWireResult::ok(11))));
        assert_eq!(s.next().await, Some(tcb(&CustomWireResult::err(-2))));
        assert_eq!(s.next().await, None);

        let mut errors = ErrorMeta::new();
        s.report_errors(&mut errors);
        assert_eq!(errors.errors.len(), 1);
        assert_eq!(errors.extra_error_count, 1);
        assert_eq!(format!("{:?}", errors.errors[0]).as_str(), "-1");
    }
}


/*
 * 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.
 */

//! convert.rs - Conversions between Mercurial and Mononoke types.
//!
//! Mercurial and Mononoke use different types to represent similar
//! concepts, such as paths, identifiers, etc. While these types
//! fundamentally represent the same things, they often differ in
//! implementation details, adding some friction when converting.
//!
//! In theory, the conversions should never fail since these types
//! are used to represent the same data on the client and server
//! respectively, so any conversion failure should be considered
//! a bug. Nonetheless, since these types often differ substantially
//! in implentation, it is possible that conversion failures may occur
//! in practice.

use std::collections::BTreeMap;

use anyhow::bail;
use anyhow::Context;
use anyhow::Result;
use edenapi_types::commit::BonsaiFileChange;
use edenapi_types::token::UploadToken;
use edenapi_types::token::UploadTokenData;
use edenapi_types::token::UploadTokenMetadata;
use edenapi_types::AnyFileContentId;
use edenapi_types::AnyId;
use edenapi_types::HgChangesetContent;
use ephemeral_blobstore::BubbleId;
use mercurial_types::blobs::Extra;
use mercurial_types::blobs::RevlogChangeset;
use mercurial_types::HgManifestId;
use mercurial_types::HgNodeHash;
use mononoke_api::path::MononokePath;
use mononoke_api::CreateChange;
use mononoke_api::CreateChangeFile;
use mononoke_types::DateTime;
use mononoke_types::MPath;
use types::RepoPath;
use types::RepoPathBuf;

use crate::errors::ErrorKind;

/// Convert a Mercurial `RepoPath` or `RepoPathBuf` into a `MononokePath`.
/// The input will be copied due to differences in data representation.
pub fn to_mononoke_path(path: impl AsRef<RepoPath>) -> Result<MononokePath> {
    Ok(MononokePath::new(to_mpath(path)?))
}

/// Convert a Mercurial `RepoPath` or `RepoPathBuf` into an `Option<MPath>`.
/// The input will be copied due to differences in data representation.
pub fn to_mpath(path: impl AsRef<RepoPath>) -> Result<Option<MPath>> {
    let path_bytes = path.as_ref().as_byte_slice();
    MPath::new_opt(path_bytes).with_context(|| ErrorKind::InvalidPath(path_bytes.to_vec()))
}

/// Convert an `MPath` into a Mercurial `RepoPathBuf`.
/// The input will be copied due to differences in data representation.
pub fn to_hg_path(path: &MPath) -> Result<RepoPathBuf> {
    RepoPathBuf::from_utf8(path.to_vec()).with_context(|| ErrorKind::InvalidPath(path.to_vec()))
}

pub fn to_revlog_changeset(cs: HgChangesetContent) -> Result<RevlogChangeset> {
    Ok(RevlogChangeset {
        p1: cs.parents.p1().cloned().map(HgNodeHash::from),
        p2: cs.parents.p2().cloned().map(HgNodeHash::from),
        manifestid: HgManifestId::new(HgNodeHash::from(cs.manifestid)),
        extra: Extra::new(
            cs.extras
                .into_iter()
                .map(|extra| (extra.key, extra.value))
                .collect::<BTreeMap<_, _>>(),
        ),
        files: cs
            .files
            .into_iter()
            .map(|file| to_mpath(file)?.context(ErrorKind::UnexpectedEmptyPath))
            .collect::<Result<_, _>>()?,
        message: cs.message,
        time: DateTime::from_timestamp(cs.time, cs.tz)?,
        user: cs.user,
    })
}

pub fn to_create_change(fc: BonsaiFileChange, bubble_id: Option<BubbleId>) -> Result<CreateChange> {
    fn extract_size(metadata: Option<UploadTokenMetadata>) -> Option<u64> {
        metadata
            .map(|UploadTokenMetadata::FileContentTokenMetadata(metadata)| metadata.content_size)
    }
    let verify = move |token: &UploadToken| -> Result<()> {
        // TODO: Verify signature on upload token
        if token.data.bubble_id != bubble_id.map(Into::into) {
            bail!("Wrong bubble id on upload token")
        }
        Ok(())
    };
    match fc {
        BonsaiFileChange::Change {
            file_type,
            upload_token,
        } => {
            verify(&upload_token)?;
            if let UploadTokenData {
                id: AnyId::AnyFileContentId(AnyFileContentId::ContentId(content_id)),
                bubble_id: _,
                metadata,
            } = upload_token.data
            {
                Ok(CreateChange::Tracked(
                    CreateChangeFile::Existing {
                        file_id: content_id.into(),
                        file_type: file_type.into(),
                        maybe_size: extract_size(metadata),
                    },
                    // TODO(yancouto): Add copy info on tracked changes
                    None,
                ))
            } else {
                bail!("Invalid upload token format, missing content id")
            }
        }
        BonsaiFileChange::UntrackedChange {
            file_type,
            upload_token,
        } => {
            verify(&upload_token)?;
            // TODO: Verify signature on upload token
            if let UploadTokenData {
                id: AnyId::AnyFileContentId(AnyFileContentId::ContentId(content_id)),
                bubble_id: _,
                metadata,
            } = upload_token.data
            {
                Ok(CreateChange::Untracked(CreateChangeFile::Existing {
                    file_id: content_id.into(),
                    file_type: file_type.into(),
                    maybe_size: extract_size(metadata),
                }))
            } else {
                bail!("Invalid upload token format, missing content id")
            }
        }
        BonsaiFileChange::UntrackedDeletion => Ok(CreateChange::UntrackedDeletion),
        BonsaiFileChange::Deletion => Ok(CreateChange::Deletion),
    }
}


/*
 * 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 anyhow::Context;
use bytes::Bytes;
use gotham::state::FromState;
use gotham::state::State;
use gotham_ext::body_ext::BodyExt;
use gotham_ext::error::HttpError;
use http::HeaderMap;
use hyper::Body;
use mononoke_api_hg::HgRepoContext;
use mononoke_api_hg::RepoContextHgExt;
use rate_limiting::Metric;

use crate::context::ServerContext;
use crate::errors::ErrorKind;
use crate::errors::MononokeErrorExt;
use crate::middleware::request_dumper::RequestDumper;
use crate::middleware::RequestContext;

pub mod cbor;
pub mod convert;
pub mod monitor;

pub use cbor::cbor_mime;
pub use cbor::cbor_stream_filtered_errors;
pub use cbor::custom_cbor_stream;
pub use cbor::parse_cbor_request;
pub use cbor::parse_wire_request;
pub use cbor::to_cbor_bytes;
pub use convert::to_create_change;
pub use convert::to_hg_path;
pub use convert::to_mononoke_path;
pub use convert::to_mpath;
pub use convert::to_revlog_changeset;

pub async fn get_repo(
    sctx: &ServerContext,
    rctx: &RequestContext,
    name: impl AsRef<str>,
    throttle_metric: impl Into<Option<Metric>>,
) -> Result<HgRepoContext, HttpError> {
    rctx.ctx.session().check_load_shed()?;

    if let Some(throttle_metric) = throttle_metric.into() {
        rctx.ctx.session().check_rate_limit(throttle_metric).await?;
    }

    let name = name.as_ref();
    sctx.mononoke_api()
        .repo(rctx.ctx.clone(), name)
        .await
        .map_err(|e| e.into_http_error(ErrorKind::RepoLoadFailed(name.to_string())))?
        .with_context(|| ErrorKind::RepoDoesNotExist(name.to_string()))
        .map_err(HttpError::e404)?
        .build()
        .await
        .map(|repo| repo.hg())
        .map_err(|e| e.into_http_error(ErrorKind::RepoLoadFailed(name.to_string())))
}

pub async fn get_request_body(state: &mut State) -> Result<Bytes, HttpError> {
    let body = Body::take_from(state);
    let headers = HeaderMap::try_borrow_from(state);
    let body = body
        .try_concat_body_opt(headers)
        .context(ErrorKind::InvalidContentLength)
        .map_err(HttpError::e400)?
        .await
        .context(ErrorKind::ClientCancelled)
        .map_err(HttpError::e400)?;

    if let Some(rd) = RequestDumper::try_borrow_mut_from(state) {
        rd.add_body(&body);
    };

    Ok(body)
}


/*
 * 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::future::Future;
use std::pin::Pin;
use std::task::Context;
use std::task::Poll;

use futures::Stream;
use pin_project::pin_project;

#[pin_project]
pub struct Monitor<T, P> {
    #[pin]
    inner: T,
    /// Payload alongside the inner stream or future.  This is dropped when
    /// the stream or future is dropped.
    #[allow(unused)]
    payload: P,
}

impl<T, P> Monitor<T, P> {
    pub fn new(inner: T, payload: P) -> Self {
        Self { inner, payload }
    }
}

impl<F: Future, P> Future for Monitor<F, P> {
    type Output = F::Output;

    #[inline]
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        let mut this = self.project();
        this.inner.as_mut().poll(cx)
    }
}

impl<S: Stream, P> Stream for Monitor<S, P> {
    type Item = S::Item;

    #[inline]
    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
        let mut this = self.project();
        this.inner.as_mut().poll_next(cx)
    }

    #[inline]
    fn size_hint(&self) -> (usize, Option<usize>) {
        self.inner.size_hint()
    }
}


/*
 * 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::HashSet;

use anyhow::anyhow;
use anyhow::Context;
use async_recursion::async_recursion;
use blame::fetch_blame_v2;
use blame::fetch_content_for_blame;
use blame::BlameError;
use bytes::Bytes;
use context::CoreContext;
use futures::stream;
use futures::stream::TryStreamExt;
use futures::try_join;
use manifest::ManifestOps;
use mononoke_types::blame_v2::BlameParent;
use mononoke_types::blame_v2::BlameV2;
use mononoke_types::ChangesetId;
use mononoke_types::FileUnodeId;
use mononoke_types::MPath;
use unodes::RootUnodeManifestId;

use crate::common::find_possible_mutable_ancestors;
use crate::Repo;

#[async_recursion]
async fn fetch_mutable_blame(
    ctx: &CoreContext,
    repo: &impl Repo,
    my_csid: ChangesetId,
    path: &MPath,
    seen: &mut HashSet<ChangesetId>,
) -> Result<(BlameV2, FileUnodeId), BlameError> {
    let mutable_renames = repo.mutable_renames();

    if !seen.insert(my_csid) {
        return Err(anyhow!("Infinite loop in mutable blame").into());
    }

    // First case. Fix up blame directly if I have a mutable rename attached
    let my_mutable_rename = mutable_renames
        .get_rename(ctx, my_csid, Some(path.clone()))
        .await?;
    if let Some(rename) = my_mutable_rename {
        // We have a mutable rename, which replaces our p1 and our path.
        // Recurse to fetch a fully mutated blame for the new p1 parent
        // and path.
        //
        // This covers the case where we are a in the immutable history:
        // a
        // |
        // b  e
        // |  |
        // c  d
        // and there is a mutable rename saying that a's parent should be e, not b.
        // After this, because we did the blame a->e, and we fetched a mutant blame
        // for e, we're guaranteed to be done, even if there are mutations in e's history.
        let src_path = rename
            .src_path()
            .ok_or_else(|| anyhow!("Mutable rename points file to root directory"))?
            .clone();
        let (src_blame, src_content) =
            blame_with_content(ctx, repo, rename.src_cs_id(), rename.src_path(), true).await?;

        let blobstore = repo.repo_blobstore_arc();
        let unode = repo
            .repo_derived_data()
            .derive::<RootUnodeManifestId>(ctx, my_csid)
            .await?
            .manifest_unode_id()
            .find_entry(ctx.clone(), blobstore, Some(path.clone()))
            .await?
            .context("Unode missing")?
            .into_leaf()
            .ok_or_else(|| BlameError::IsDirectory(path.clone()))?;
        let my_content = fetch_content_for_blame(ctx, repo.as_blob_repo(), unode)
            .await?
            .into_bytes()?;

        // And reblame directly against the parent mutable renames gave us.
        let blame_parent = BlameParent::new(0, src_path, src_content, src_blame);
        let blame = BlameV2::new(my_csid, path.clone(), my_content, vec![blame_parent])?;
        return Ok((blame, unode));
    }

    // Second case. We don't have a mutable rename attached, so we're going to look
    // at the set of mutable renames for this path, and if any of those renames are ancestors
    // of this commit, we'll apply a mutated blame via BlameV2::apply_mutable_blame to
    // get the final blame result.

    // Check for historic mutable renames - those attached to commits older than us.
    // Given our history graph:
    // a
    // |
    // b
    // |
    // c
    // |\
    // d e
    // where we are b, this looks to see any if c, d, e (etc) has a mutable rename attached to
    // it that affects our current path.
    //
    // We then filter down to remove mutable renames that are ancestors of the currently handled
    // mutable rename, since recursing to get blame will fix those. We can then apply mutation
    // for each blame in any order, because the mutated blame will only affect one ancestry path.
    //
    // For example, if c has a mutable rename for our path, then we do not want to consider mutable
    // renames attached to d or e; however, if c does not, but d and e do, then we want to consider
    // the mutable renames for both d and e.
    let mut possible_mutable_ancestors =
        find_possible_mutable_ancestors(ctx, repo, my_csid, Some(path)).await?;

    // Fetch the immutable blame, which we're going to mutate
    let (mut blame, unode) = fetch_immutable_blame(ctx, repo, my_csid, path).await?;

    // We now have a stack of possible mutable ancestors, sorted so that the highest generation
    // is last. We now pop the last entry from the stack (highest generation) and apply mutation
    // based on that entry. Once that's done, we remove all ancestors of the popped entry
    // from the stack, so that we don't attempt to double-apply a mutation.
    //
    // This will mutate our blame to have all appropriate mutations from ancestors applied
    // If we have mutable blame down two ancestors of a merge, we'd expect that the order
    // of applying those mutations will not affect the final result
    while let Some((_, mutated_csid)) = possible_mutable_ancestors.pop() {
        // Apply mutation for mutated_csid
        let ((mutated_blame, _), (original_blame, _)) = try_join!(
            fetch_mutable_blame(ctx, repo, mutated_csid, path, seen),
            fetch_immutable_blame(ctx, repo, mutated_csid, path)
        )?;
        blame.apply_mutable_change(&original_blame, &mutated_blame)?;

        // Rebuild possible_mutable_ancestors without anything that's an ancestor
        // of mutated_csid. This must preserve order, so that we deal with the most
        // recent mutation entries first (which may well remove older mutation entries
        // from the stack)
        possible_mutable_ancestors =
            stream::iter(possible_mutable_ancestors.into_iter().map(anyhow::Ok))
                .try_filter_map({
                    move |(gen, csid)| async move {
                        if repo
                            .commit_graph()
                            .is_ancestor(ctx, csid, mutated_csid)
                            .await?
                        {
                            anyhow::Ok(None)
                        } else {
                            Ok(Some((gen, csid)))
                        }
                    }
                })
                .try_collect()
                .await?;
    }

    Ok((blame, unode))
}

async fn fetch_immutable_blame(
    ctx: &CoreContext,
    repo: &impl Repo,
    csid: ChangesetId,
    path: &MPath,
) -> Result<(BlameV2, FileUnodeId), BlameError> {
    fetch_blame_v2(ctx, repo.as_blob_repo(), csid, path.clone()).await
}

pub async fn blame(
    ctx: &CoreContext,
    repo: &impl Repo,
    csid: ChangesetId,
    path: Option<&MPath>,
    follow_mutable_file_history: bool,
) -> Result<(BlameV2, FileUnodeId), BlameError> {
    let path = path.ok_or_else(|| anyhow!("Blame is not available for directory: `/`"))?;
    if follow_mutable_file_history {
        fetch_mutable_blame(ctx, repo, csid, path, &mut HashSet::new()).await
    } else {
        fetch_immutable_blame(ctx, repo, csid, path).await
    }
}

/// Blame metadata for this path, and the content that was blamed.  If the file
/// content is too large or binary data is detected then
//  the fetch may be rejected.
pub async fn blame_with_content(
    ctx: &CoreContext,
    repo: &impl Repo,
    csid: ChangesetId,
    path: Option<&MPath>,
    follow_mutable_file_history: bool,
) -> Result<(BlameV2, Bytes), BlameError> {
    let (blame, file_unode_id) = blame(ctx, repo, csid, path, follow_mutable_file_history).await?;
    let content = fetch_content_for_blame(ctx, repo.as_blob_repo(), file_unode_id)
        .await?
        .into_bytes()?;
    Ok((blame, content))
}


/*
 * 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 anyhow::Error;
use context::CoreContext;
use futures::stream;
use futures::stream::TryStreamExt;
use mononoke_types::ChangesetId;
use mononoke_types::Generation;
use mononoke_types::MPath;

use crate::Repo;

/// Given a changeset and path finds all ancestors where the history for path
/// has been mutated. Given history graph:
/// a
/// |
/// b
/// |
/// c
/// |\
/// d e
/// where we are b, this will tell you if the path history for this specific
/// path was overriden at c, d or e.
///
/// Returs vector of (generation_number, changeset_id) for changesets sorted by
/// generation number.
pub(crate) async fn find_possible_mutable_ancestors(
    ctx: &CoreContext,
    repo: &impl Repo,
    csid: ChangesetId,
    path: Option<&MPath>,
) -> Result<Vec<(Generation, ChangesetId)>, Error> {
    let mutable_renames = repo.mutable_renames();
    let mutable_csids = mutable_renames
        .get_cs_ids_with_rename(ctx, path.cloned())
        .await?;
    let mut possible_mutable_ancestors: Vec<(Generation, ChangesetId)> =
        stream::iter(mutable_csids.into_iter().map(anyhow::Ok))
            .try_filter_map({
                move |mutated_at| async move {
                    // First, we filter out csids that cannot be reached from here. These
                    // are attached to mutable renames that are either descendants of us, or
                    // in a completely unrelated tree of history.
                    if repo
                        .commit_graph()
                        .is_ancestor(ctx, mutated_at, csid)
                        .await?
                    {
                        // We also want to grab generation here, because we're going to sort
                        // by generation and consider "most recent" candidate first
                        let cs_gen = repo
                            .changeset_fetcher()
                            .get_generation_number(ctx, mutated_at)
                            .await?;
                        Ok(Some((cs_gen, mutated_at)))
                    } else {
                        anyhow::Ok(None)
                    }
                }
            })
            .try_collect()
            .await?;
    // And turn the list of possible mutable ancestors into a stack sorted by generation
    possible_mutable_ancestors.sort_unstable_by_key(|(gen, _)| *gen);

    Ok(possible_mutable_ancestors)
}


/*
 * 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.
 */

//! Utility crate for traversing mutable history
//!
//! While our derived data types crates are enough to serve immutable history for serving
//! the mutable one we need to combine data from multiple sources in more complex algorithm.
//!
//! This crate procides all the primitives useful for serving log and blame data (mutable and immutable).

#![feature(trait_alias)]

mod blame;
mod common;
mod log;

use blobrepo::AsBlobRepo;
use changeset_fetcher::ChangesetFetcherArc;
use changeset_fetcher::ChangesetFetcherRef;
use changesets::ChangesetsRef;
use commit_graph::CommitGraphRef;
pub use log::list_file_history;
pub use log::CsAndPath;
pub use log::FastlogError;
pub use log::FollowMutableFileHistory;
pub use log::HistoryAcrossDeletions;
pub use log::NextChangeset;
pub use log::TraversalOrder;
pub use log::Visitor;
use mutable_renames::MutableRenamesRef;
use repo_blobstore::RepoBlobstoreArc;
use repo_blobstore::RepoBlobstoreRef;
use repo_derived_data::RepoDerivedDataRef;
use repo_identity::RepoIdentityRef;

pub use crate::blame::blame;
pub use crate::blame::blame_with_content;

/// Trait alias for history traversal ops.
///
/// These are the repo attributes that are necessary to do most of the (mutable)
/// history traversal operations.
pub trait Repo = AsBlobRepo
    + ChangesetFetcherArc
    + ChangesetFetcherRef
    + ChangesetsRef
    + MutableRenamesRef
    + RepoBlobstoreRef
    + RepoBlobstoreArc
    + RepoDerivedDataRef
    + RepoIdentityRef
    + CommitGraphRef
    + Send
    + Sync;