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

use crate::HeapSize;

/// Append-only deque which ensures the elements pushed into it never move.
/// Allocates chunks in doubling capacities.
#[derive(Debug)]
pub struct Deque<T> {
    storage: Vec<Vec<T>>,

    /// Capacity at which to allocate the next chunk.
    /// Doubles every chunk until reaching [`MAX_CHUNK_CAPACITY`].
    next_chunk_capacity: usize,
}

/// Minimum chunk capacity in the deque.
/// May be made configurable in the future.
const MIN_CHUNK_CAPACITY: usize = 1 << 10;

/// Maximum chunk capacity in the deque.
/// May be made configurable in the future.
const MAX_CHUNK_CAPACITY: usize = MIN_CHUNK_CAPACITY * (1 << 10);

impl<T> Default for Deque<T> {
    fn default() -> Self {
        Self::new()
    }
}

impl<T> Deque<T> {
    pub fn new() -> Self {
        let mut result = Self {
            storage: Default::default(),
            next_chunk_capacity: MIN_CHUNK_CAPACITY,
        };
        result.new_chunk();
        result
    }

    /// Append an element to the deque and return a reference to it.
    /// The element will not move after it is allocated.
    pub fn push(&mut self, val: T) -> &T {
        let chunk = self.storage.last().unwrap();
        if chunk.len() >= chunk.capacity() {
            self.new_chunk();
        }
        let chunk = self.storage.last_mut().unwrap();
        debug_assert!(
            chunk.len() < chunk.capacity(),
            "Invalid attempt to expand a chunk"
        );
        chunk.push(val);
        chunk.last().unwrap()
    }

    /// Return the number of elements that have been appended to the deque.
    pub fn len(&self) -> usize {
        let mut result = 0;
        for chunk in &self.storage {
            result += chunk.len();
        }
        result
    }

    pub fn is_empty(&self) -> bool {
        self.storage.is_empty()
    }

    /// Iterator over every element of the deque.
    pub fn iter(&self) -> impl Iterator<Item = &T> {
        self.storage.iter().flatten()
    }

    /// Iterator over every element of the deque.
    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
        self.storage.iter_mut().flatten()
    }

    /// Allocate a new chunk in the node storage.
    fn new_chunk(&mut self) {
        let capacity = self.next_chunk_capacity;
        self.storage.push(Vec::with_capacity(capacity));

        // Double the capacity if there's room.
        if capacity < MAX_CHUNK_CAPACITY {
            self.next_chunk_capacity = capacity * 2;
        }
    }
}

impl<T> HeapSize for Deque<T> {
    fn heap_size(&self) -> usize {
        let mut result = 0;
        for chunk in &self.storage {
            result += chunk.heap_size();
        }
        result
    }
}

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

    #[test]
    fn append() {
        let mut d = Deque::new();
        d.push(1);
        d.push(2);
        assert_eq!(d.iter().count(), 2);
    }

    #[test]
    fn multi_chunks() {
        let mut d = Deque::<usize>::new();
        let count = MIN_CHUNK_CAPACITY * 2;
        let mut ptr = std::ptr::null();
        for i in 0..count {
            let elem = d.push(i);
            if i == 1000 {
                ptr = elem as *const usize;
            }
        }
        assert_eq!(d.iter().count(), count);
        // Make sure nothing in the first chunk moved around.
        assert_eq!(unsafe { *ptr }, 1000);
    }
}


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

//! URL fetcher.
//!
//! Provides an abstraction to fetch data from an arbitrary URL. For now
//! only file: and data: schemes are supported, but more can be added easily
//! (for example http:).
//!
//! This all could be made flexible and configurable with providers and so on,
//! but I see no point to complicate it at this point.

use std::fs::File;
use std::io::Read;

use anyhow;
use base64;
use thiserror;
use url::Url;

#[derive(thiserror::Error, Debug)]
pub enum FetchError {
    #[error("URL parse error")]
    URLParseError(#[source] anyhow::Error),
    #[error("unsupported URL scheme")]
    UnsupportedScheme,
    #[error("URL scheme prohibited by policy")]
    ProhibitedScheme,
    #[error("invalid URL: {0}")]
    InvalidURL(&'static str),
    #[error("I/O error")]
    IOError(#[source] std::io::Error),
    #[error("error decoding data")]
    DecodeError(#[source] anyhow::Error),
}

impl From<std::io::Error> for FetchError {
    fn from(err: std::io::Error) -> Self {
        FetchError::IOError(err)
    }
}

/// Data data returned by [fetch_url()]. It could potentially be a file memory
/// mapping or who knows what else.
#[derive(Debug)]
pub struct Data {
    buf: Vec<u8>,
}

impl AsRef<[u8]> for Data {
    fn as_ref(&self) -> &[u8] {
        self.buf.as_slice()
    }
}

/// Flags to customize behavior of `fetch_url()` and more specifically to restrict it from accessing
/// file system or network.
pub struct FetchFlags {
    /// Allow fetching from a file.
    pub allow_file: bool,
    /// Allow fetching from a supported network protocol.
    #[allow(dead_code)]
    pub allow_network: bool,
}

impl Default for FetchFlags {
    fn default() -> Self {
        FetchFlags {
            allow_file: true,
            allow_network: true,
        }
    }
}

/// Fetch data from a URL with optional restrictions from accessing file system or network (if
/// everything is restricted, the only allowed option are data URLs where everything is encoded
/// in the URL itself).
pub fn fetch_url(url: &Url, flags: FetchFlags) -> Result<Data, FetchError> {
    match url.scheme() {
        "file" => {
            if flags.allow_file {
                fetch_file(url)
            } else {
                Err(FetchError::ProhibitedScheme)
            }
        }
        "data" => fetch_data(url),
        _ => Err(FetchError::UnsupportedScheme),
    }
}

/// A convenience wrapper around [fetch_url()] with a string URL and default flags.
pub fn fetch_from_str_url(str: &str) -> Result<Data, FetchError> {
    fetch_url(
        &Url::parse(str).map_err(|e| FetchError::URLParseError(anyhow::anyhow!(e)))?,
        Default::default(),
    )
}

/// Fetch from a data URL.
/// Data URL format is: `data:[<mediatype>][;base64],<data>`
fn fetch_data(url: &Url) -> Result<Data, FetchError> {
    if !url.cannot_be_a_base() || url.has_host() {
        return Err(FetchError::InvalidURL(
            "data URL cannot be a base or contain a host",
        ));
    }

    let path = url.path();
    // Split using ',' into mtype[;encoding] and base
    let (mtype_enc, data) = path
        .split_once(',')
        .ok_or(FetchError::InvalidURL("data URL missing ',' delimiter"))?;
    // Split using ';' into mtype and encoding. We require the encoding.
    let (_, enc) = mtype_enc
        .split_once(';')
        .ok_or(FetchError::InvalidURL("data URL missing ';encoding'"))?;

    // Only support base64 for now.
    if enc != "base64" {
        return Err(FetchError::InvalidURL("data URL unsupported encoding"));
    }

    let buf = base64::decode_config(data, base64::URL_SAFE).map_err(|e| {
        FetchError::DecodeError(anyhow::anyhow!(e).context("error decoding data URL"))
    })?;

    Ok(Data { buf })
}

/// Fetch from a file URL.
fn fetch_file(url: &Url) -> Result<Data, FetchError> {
    let path = url
        .to_file_path()
        .map_err(|_| FetchError::InvalidURL("invalid file URL"))?;
    let mut file = File::open(path)?;

    let mut buf = Vec::new();
    file.read_to_end(&mut buf)?;
    buf.shrink_to_fit();

    Ok(Data { buf })
}

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

    #[test]
    fn test_data_url() {
        assert_eq!(
            fetch_from_str_url("data:application/json;base64,YQ==")
                .unwrap()
                .as_ref(),
            "a".as_bytes()
        );

        // Invalid base64 encoding.
        assert!(matches!(
            fetch_from_str_url("data:application/json;base64,Y").unwrap_err(),
            FetchError::DecodeError(_)
        ));

        // Missing ,data
        assert!(matches!(
            fetch_from_str_url("data:text/plain;base64;YQ==").unwrap_err(),
            FetchError::InvalidURL("data URL missing ',' delimiter")
        ));

        // Missing ;base64
        assert!(matches!(
            fetch_from_str_url("data:,YQ==").unwrap_err(),
            FetchError::InvalidURL("data URL missing ';encoding'")
        ));
        assert!(matches!(
            fetch_from_str_url("data:text/plain,YQ==").unwrap_err(),
            FetchError::InvalidURL("data URL missing ';encoding'")
        ));

        // Check invalid encoding.
        assert!(matches!(
            fetch_from_str_url("data:text/plain;base63,YQ==").unwrap_err(),
            FetchError::InvalidURL("data URL unsupported encoding")
        ));
    }
}


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

use std::collections::HashMap;

/// Trait for allowing users to query how much memory a type uses in the heap.
pub trait HeapSize {
    /// Return the size of the heap allocated memory for the type, in bytes.
    fn heap_size(&self) -> usize;
}

impl HeapSize for String {
    fn heap_size(&self) -> usize {
        self.capacity()
    }
}

impl<T> HeapSize for Box<T> {
    fn heap_size(&self) -> usize {
        std::mem::size_of::<T>()
    }
}

impl<T> HeapSize for Vec<T> {
    fn heap_size(&self) -> usize {
        self.capacity() * std::mem::size_of::<T>()
    }
}

impl<K, V> HeapSize for HashMap<K, V> {
    fn heap_size(&self) -> usize {
        let entry_size: usize =
            std::mem::size_of::<u64>() + std::mem::size_of::<K>() + std::mem::size_of::<V>();
        self.capacity() * entry_size
    }
}


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

use std::fmt;
use std::io;
use std::io::BufWriter;
use std::io::Write;

/// Whether to pretty-print the JSON.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Pretty {
    No,
    Yes,
}

/// The state indicates the next expected emit call in the current collection.
#[derive(Debug)]
enum State {
    /// Expecting a dict key.
    DictKey {
        /// The dict is currently empty.
        is_empty: bool,
    },

    /// Expecting a dict value.
    DictValue,

    /// Expecting a value in an array.
    ArrayValue {
        /// The array is currently empty.
        is_empty: bool,
    },
}

/// Allows the caller to output JSON to any Write type.
/// The caller is responsible for ensuring calls to `open` and `close`
/// functions are valid, and that keys and values are emitted in the proper order.
/// If any emitter functions are called out of order, panics.
pub struct JSONEmitter<W: Write> {
    /// Where to write the generated JSON.
    out: BufWriter<W>,

    /// Whether to pretty print the output JSON.
    pretty: Pretty,

    /// Stack used to maintain state.
    states: Vec<State>,

    /// Current indentation level, used in pretty mode.
    indent: usize,

    /// Size of the indentation step.
    /// May be configurable in the future.
    indent_step: usize,

    /// Some(err) if an error has occurred when writing, else None.
    error: Option<io::Error>,
}

/// Print to the output stream if no errors have been seen so far.
/// `$gen_js` is a mutable reference to the GenJS struct.
/// `$arg` arguments follow the format pattern used by `format!`.
macro_rules! out {
    ($emitter:expr, $($arg:tt)*) => {{
        $emitter.write_if_no_errors(format_args!($($arg)*));
    }}
}

impl<W: Write> JSONEmitter<W> {
    /// Create a new `JSONEmitter` to write to `out`.
    pub fn new(out: W, pretty: Pretty) -> JSONEmitter<W> {
        JSONEmitter {
            out: BufWriter::new(out),
            pretty,
            indent: 0,
            indent_step: 2,
            states: Vec::new(),
            error: None,
        }
    }

    /// Write to the `out` writer if we haven't seen any errors.
    /// If we have seen any errors, do nothing.
    /// Used via the `out!` macro.
    fn write_if_no_errors(&mut self, args: fmt::Arguments<'_>) {
        if self.error.is_none() {
            if let Err(e) = self.out.write_fmt(args) {
                self.error = Some(e);
            }
        }
    }

    /// Emit a finite f64.
    /// Panics if `val` is infinite or NaN.
    pub fn emit_number(&mut self, val: f64) {
        assert!(val.is_finite(), "{} is not finite", val);
        self.will_emit_value();
        out!(self, "{}", crate::convert::number_to_string(val));
    }

    /// Emit a bool.
    pub fn emit_bool(&mut self, val: bool) {
        self.will_emit_value();
        out!(self, "{}", if val { "true" } else { "false" });
    }

    /// Emit the null value.
    pub fn emit_null(&mut self) {
        self.will_emit_value();
        out!(self, "null");
    }

    /// Emit a key in a dict.
    /// Panics if not currently in key position in a dict.
    pub fn emit_key(&mut self, key: &str) {
        let state: &mut State = match self.states.last_mut() {
            None => {
                return;
            }
            Some(state) => state,
        };
        let need_comma = match state {
            State::DictKey { is_empty } => !*is_empty,
            _ => panic!("Not expecting dict key"),
        };
        *state = State::DictValue;
        if need_comma {
            out!(self, ",");
        }
        self.newline();
        self.primitive_emit_string(key);
        out!(self, ":");
        if self.pretty == Pretty::Yes {
            out!(self, " ");
        }
    }

    /// Open a new dict.
    /// Panics if a new dict cannot be opened in this position.
    pub fn open_dict(&mut self) {
        self.will_emit_value();
        out!(self, "{{");
        self.inc_indent();
        self.states.push(State::DictKey { is_empty: true });
    }

    /// Close a dict.
    /// Panics if there is no dict to close or if a value is expected.
    pub fn close_dict(&mut self) {
        match self.states.pop() {
            Some(State::DictKey { is_empty }) => {
                self.dec_indent();
                if !is_empty {
                    self.newline();
                }
                out!(self, "}}");
            }
            _ => {
                panic!("Not in dict");
            }
        }
    }

    /// Open a new array.
    /// Panics if a new array cannot be opened in this position.
    pub fn open_array(&mut self) {
        self.will_emit_value();
        out!(self, "[");
        self.inc_indent();
        self.states.push(State::ArrayValue { is_empty: true });
    }

    /// Close an array.
    /// Panics if there is no array to close.
    pub fn close_array(&mut self) {
        match self.states.pop() {
            Some(State::ArrayValue { is_empty }) => {
                self.dec_indent();
                if !is_empty {
                    self.newline();
                }
                out!(self, "]");
            }
            _ => {
                panic!("Not in array");
            }
        }
    }

    /// Emit a valid UTF-8 string.
    pub fn emit_string(&mut self, string: &str) {
        self.will_emit_value();
        self.primitive_emit_string(string);
    }

    /// Emit a valid UTF-8 string.
    pub fn primitive_emit_string(&mut self, string: &str) {
        out!(self, "\"{}\"", string);
    }

    /// Consume the emitter and finish emitting.
    /// Panics if there are outstanding dicts/arrays to close.
    pub fn end(mut self) -> io::Result<()> {
        assert!(self.states.is_empty(), "Unterminated object");
        out!(self, "\n");
        match self.error {
            None => self.out.flush(),
            Some(err) => Err(err),
        }
    }

    /// Emit an escaped JS string literal.
    pub fn emit_string_literal(&mut self, value: &[u16]) {
        self.will_emit_value();
        out!(self, "\"");
        let esc = '\\';
        for &c in value {
            if c <= 0xff {
                match c as u8 as char {
                    '\\' => {
                        out!(self, "\\\\");
                        continue;
                    }
                    '\x08' => {
                        out!(self, "\\b");
                        continue;
                    }
                    '\x0c' => {
                        out!(self, "\\f");
                        continue;
                    }
                    '\n' => {
                        out!(self, "\\n");
                        continue;
                    }
                    '\r' => {
                        out!(self, "\\r");
                        continue;
                    }
                    '\t' => {
                        out!(self, "\\t");
                        continue;
                    }
                    '\x0b' => {
                        out!(self, "\\v");
                        continue;
                    }
                    _ => {}
                };
            }
            if c == esc as u16 {
                out!(self, "\\");
            }
            if (0x20..=0x7f).contains(&c) {
                // Printable.
                out!(self, "{}", c as u8 as char);
            } else {
                out!(self, "\\u{:04x}", c);
            }
        }
        out!(self, "\"");
    }

    /// Signal that we're about to emit a dict or array value and handle
    /// any necessary `,` and newlines.
    fn will_emit_value(&mut self) {
        // Need to use a local var here because we can't use `@` directly
        // in the `match` (unstable language feature).
        let state: &mut State = match self.states.last_mut() {
            None => {
                return;
            }
            Some(state) => state,
        };
        match state {
            State::DictValue => {
                *state = State::DictKey { is_empty: false };
            }
            State::ArrayValue { is_empty } => {
                let need_comma = !*is_empty;
                *state = State::ArrayValue { is_empty: false };
                if need_comma {
                    out!(self, ",");
                }
                self.newline();
            }
            _ => panic!("Expected a key"),
        };
    }

    fn newline(&mut self) {
        if self.pretty == Pretty::Yes {
            let indent = self.indent;
            out!(self, "\n{:indent$}", "", indent = indent);
        }
    }

    /// Increase the indent level.
    fn inc_indent(&mut self) {
        self.indent += self.indent_step;
    }

    /// Decrease the indent level.
    fn dec_indent(&mut self) {
        self.indent -= self.indent_step;
    }
}

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

    #[test]
    fn test_values() {
        let mut out = vec![];
        let mut emitter = JSONEmitter::new(&mut out, Pretty::No);
        emitter.emit_null();
        emitter.end().unwrap();
        assert_eq!(String::from_utf8(out).unwrap(), "null\n");
    }

    #[test]
    fn test_string() {
        let mut out = vec![];
        let mut emitter = JSONEmitter::new(&mut out, Pretty::No);
        emitter.emit_string_literal(&[0xd00a, 0x0a]);
        emitter.end().unwrap();
        assert_eq!(String::from_utf8(out).unwrap(), "\"\\ud00a\\n\"\n");
    }

    #[test]
    fn test_dict() {
        let mut out = vec![];
        let mut emitter = JSONEmitter::new(&mut out, Pretty::No);
        emitter.open_dict();
        emitter.emit_key("foo");
        emitter.emit_string("bar");
        emitter.emit_key("baz");
        emitter.emit_string("quz");
        emitter.close_dict();
        emitter.end().unwrap();
        assert_eq!(
            String::from_utf8(out).unwrap(),
            "{\"foo\":\"bar\",\"baz\":\"quz\"}\n"
        );
    }
}


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

extern crate libcplusplus;

mod nullbuf;
pub use nullbuf::NullTerminatedBuf;
mod timer;
pub use timer::Timer;
mod scoped_hashmap;
pub use scoped_hashmap::ScopedHashMap;
mod deque;
pub use deque::Deque;
mod heap_size;
pub use heap_size::HeapSize;

#[macro_use]
#[allow(unused_macros)]
pub mod str_enum;

#[macro_use]
#[allow(unused_macros)]
pub mod opaque_id;

pub mod atom_table;
pub mod case;
pub mod convert;
pub mod fetchurl;
pub mod json;
pub mod source_manager;


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

use std::io::Read;
use std::os::raw::c_char;

/// A null terminated memory buffer.
#[derive(Debug)]
pub struct NullTerminatedBuf(Vec<u8>);

impl NullTerminatedBuf {
    /// Create from a reader and null terminate.
    pub fn from_reader(reader: &mut dyn Read) -> Result<NullTerminatedBuf, std::io::Error> {
        let mut v = Vec::<u8>::new();
        reader.read_to_end(&mut v)?;
        v.push(0);

        Ok(NullTerminatedBuf(v))
    }

    /// Create from a file and null terminate it.
    pub fn from_file(f: &'_ mut std::fs::File) -> Result<NullTerminatedBuf, std::io::Error> {
        // TODO: this is an extremely naive implementation, it can be optimized in multiple ways:
        //       - obtain the size of the file and perform a single allocation and few syscalls
        //       - memory map the file
        //       - just use LLVM's MemoryBuffer
        //       One problem is that there isn't an obvious way in Rust to check portably whether
        //       something has a fixed size and is memory mappable (i.e. is not a pipe).

        Self::from_reader(f)
    }

    /// Create by copying a slice and appending null-termination.
    pub fn from_slice_copy(s: &[u8]) -> NullTerminatedBuf {
        let mut v = Vec::with_capacity(s.len() + 1);
        v.extend_from_slice(s);
        v.push(0);
        NullTerminatedBuf(v)
    }

    /// Create from a slice that may already be null-terminated.
    pub fn from_slice_check(s: &[u8]) -> NullTerminatedBuf {
        Self::from_slice_copy(if let [head @ .., 0] = s { head } else { s })
    }

    /// Create by copying a string and appending null-termination.
    pub fn from_str_copy(s: &str) -> NullTerminatedBuf {
        Self::from_slice_copy(s.as_bytes())
    }

    /// Create from a string that may already be null-terminated.
    pub fn from_str_check(s: &str) -> NullTerminatedBuf {
        Self::from_slice_check(s.as_bytes())
    }

    /// Return the length of the data including the null terminator.
    pub fn len(&self) -> usize {
        self.0.len()
    }

    /// Just a placeholder always returning `true`, since the there is always
    /// at least a null terminator.
    pub fn is_empty(&self) -> bool {
        false
    }

    /// A pointer to the start of the slice.
    /// # Safety
    /// It is not really unsafe, but is intended to be used in an unsafe context.
    pub unsafe fn as_ptr(&self) -> *const u8 {
        self.as_bytes().as_ptr()
    }

    /// Convenience wrapper returning C `const char *`.
    /// # Safety
    /// It is not really unsafe, but is intended to be used in an unsafe context.
    pub unsafe fn as_c_char_ptr(&self) -> *const c_char {
        self.as_ptr() as *const c_char
    }

    pub fn as_bytes(&self) -> &[u8] {
        self.0.as_slice()
    }
}

impl AsRef<[u8]> for NullTerminatedBuf {
    fn as_ref(&self) -> &[u8] {
        self.as_bytes()
    }
}


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

#[macro_export]
macro_rules! declare_opaque_id {
    ($name:ident) => {
        #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
        pub struct $name(std::num::NonZeroU32);
        impl $name {
            #[inline]
            fn new(v: usize) -> Self {
                debug_assert!(v < u32::MAX as usize);
                unsafe { Self::new_unchecked(v) }
            }
            #[inline]
            const unsafe fn new_unchecked(v: usize) -> Self {
                Self(std::num::NonZeroU32::new_unchecked((v + 1) as u32))
            }
            fn as_usize(self) -> usize {
                (self.0.get() - 1) as usize
            }
        }
        impl std::fmt::Display for $name {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                write!(f, "{}", self.as_usize())?;
                Ok(())
            }
        }
    };
}


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

//! ScopedHashMap
//!
//! Conceptually ScopedHashMap provides a stack of HashMap-s, where we can:
//! - Push and pop maps (we call them scopes)
//! - Insert elements in the top HashMap
//! - Search elements from top to bottom.
//!
//! The performance of a naive implementation would be:
//! - Push/pop a scope: O(1)
//! - Search: O(number of scopes)
//! It optimizes for pushing and popping scopes instead of searching.
//!
//! Instead this implementation instead optimizes for search:
//! - Push a scope: O(1)
//! - Pop a scope: O(number of elements in the scope)
//! - Search: O(1)
//!
//! Scopes usually have only a few elements, can be deeply nested, and searching
//! is very frequent. To that end, instead of a "stack of HashMap-s" we use a
//! "HashMap of stacks". In other words, every element of the single HashMap is
//! a stack of entries from different scopes. When we perform a lookup, we can
//! get to the topmost entry in O(1). When we pop a scope, we need to pop each
//! individual element belonging to it.
//!
//! All map entries are separately heap allocated, connected through pointers.
//! Every has a pointer to a "shadowed" entry in a previous scope, and a
//! next entry in the same scope. The hash table itself points to the top-most
//! node. Additionally a stack of scopes points to the first node in every
//! scope.
//!
//! If we perform the following operations:
//! ```[rust]
//! m.push_scope();
//! m.insert("a", 1);
//! m.insert("c", 3);
//! m.push_scope();
//! m.insert("b", 20);
//! m.insert("c", 30);
//! m.push_scope();
//! m.insert("a", 100);
//! m.insert("d", 400);
//! ```
//! We will get the following state in memory:
//! ```[text]
//!
//! Scope 1: --> "a":1 ------------------> "c": 3
//!               ^                         ^
//!               |                         |
//! Scope 2: -----+----------> "b":20 ---> "c": 30
//!               |             ^           ^
//!               |             |           |
//! Scope 3: --> "a":100 -------+-----------+--------> "d":400
//!               ^             |           |           ^
//!               |             |           |           |
//! HashTab:    ["a"]         ["b"]       ["c"]       ["d"]
//! ```

use std::borrow::Borrow;
use std::cmp::Ordering;
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::fmt::Debug;
use std::fmt::Formatter;
use std::hash::Hash;
use std::ptr::null_mut;

/// A heap allocated node representing a value inserted in a scope.
struct Node<K, V> {
    key: K,
    value: V,
    /// A node with the same key in a previous scope, or null if no previous.
    prev_shadowed: *mut Node<K, V>,
    /// The previous node in the same scope, or null if this is the first one.
    prev_in_scope: *mut Node<K, V>,
    /// Level of the scope this node belongs to.
    depth: usize,
}

/// The scope is the head of a singly linked list of nodes belonging to the
/// scope, chained through the [Node::prev_in_scope] pointer.
struct Scope<K, V> {
    /// The last node inserted into the scope, null initially.
    last: *mut Node<K, V>,
}

impl<K, V> Debug for Scope<K, V>
where
    K: Debug,
    V: Debug,
{
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let mut dm = f.debug_map();
        let mut cur_node = self.last;
        while !cur_node.is_null() {
            let n = unsafe { &*cur_node };
            dm.entry(&n.key, &n.value);
            cur_node = n.prev_in_scope;
        }
        dm.finish()
    }
}

#[derive(Debug)]
pub struct ScopedHashMap<K, V>
where
    K: Eq + Hash + Clone,
{
    /// Maps from keys to most current definition. All Nodes are owned by this.
    /// They are allocated by insertIntoScope and deleted by clearCurrentScope
    /// The pointers will never be null, since we remove the map entry if we
    /// pop the last node.
    map: HashMap<K, *mut Node<K, V>>,

    /// Stack of scopes.
    scopes: Vec<Scope<K, V>>,
}

impl<K, V> Default for ScopedHashMap<K, V>
where
    K: Eq + Hash + Clone,
{
    fn default() -> Self {
        let mut res = ScopedHashMap {
            map: Default::default(),
            scopes: Default::default(),
        };
        res.push_scope();
        res
    }
}

impl<K, V> ScopedHashMap<K, V>
where
    K: Eq + Hash + Clone,
{
    pub fn new() -> Self {
        Default::default()
    }

    /// Insert a key/value pair into the scope at the specified depth if
    /// possible, return an error otherwise.
    /// If the depth is smaller than the current depth, it is not guaranteed to
    /// succeed, because values may already be present "shadowing" the value
    /// that is being inserted.
    /// If a value is already present at the specified depth, it is replaced.
    pub fn insert_into_scope(
        &mut self,
        scope_depth: usize,
        key: K,
        value: V,
    ) -> Result<(), &'static str> {
        let scope = unsafe {
            assert!(scope_depth < self.scopes.len(), "scope_index out of range");
            self.scopes.get_unchecked_mut(scope_depth)
        };
        let mut entry = self.map.entry(key.clone());
        let next_shadowed = if let Entry::Occupied(o) = &mut entry {
            let node = unsafe { &mut **o.get() };
            match node.depth.cmp(&scope_depth) {
                Ordering::Greater => return Err("Value to be inserted is already shadowed"),
                Ordering::Equal => {
                    node.value = value;
                    return Ok(());
                }
                Ordering::Less => node,
            }
        } else {
            null_mut()
        };

        // All Nodes allocated here.
        let node = Box::new(Node {
            key,
            value,
            prev_shadowed: next_shadowed,
            prev_in_scope: scope.last,
            depth: scope_depth,
        });

        let vref = entry.or_insert(null_mut());
        let ptr = Box::into_raw(node);
        *vref = ptr;
        scope.last = ptr;
        Ok(())
    }

    /// Insert a key/value pair into the current scope. If the key already exists,
    /// the value is overwritten.
    pub fn insert(&mut self, key: K, value: V) {
        self.insert_into_scope(self.scopes.len() - 1, key, value)
            .unwrap();
    }

    pub fn contains_key<Q: ?Sized>(&self, k: &Q) -> bool
    where
        K: Borrow<Q>,
        Q: Hash + Eq,
    {
        self.map.contains_key(k)
    }

    pub fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V>
    where
        K: Borrow<Q>,
        Q: Hash + Eq,
    {
        self.map.get(k).map(|n| unsafe { &(**n).value })
    }

    pub fn get_mut<Q: ?Sized>(&mut self, k: &Q) -> Option<&mut V>
    where
        K: Borrow<Q>,
        Q: Hash + Eq,
    {
        self.map.get_mut(k).map(|n| unsafe { &mut (**n).value })
    }

    pub fn value(&self, k: K) -> Option<&V> {
        self.get(&k)
    }

    pub fn value_mut(&mut self, k: K) -> Option<&mut V> {
        self.get_mut(&k)
    }

    /// Push a new scope, run the specified callback in it, then pop the scope.
    pub fn in_new_scope<R, F: FnOnce(&mut Self) -> R>(&mut self, f: F) -> R {
        self.push_scope();
        let res = f(self);
        self.pop_scope();
        res
    }

    /// Return the depth of the current scope (the initial scope is depth 0).
    /// It can be used in order to call ['ScopedHashMap::insert_into_scope()'].
    pub fn current_scope_depth(&self) -> usize {
        debug_assert!(!self.scopes.is_empty(), "We don't allow popping of scope 0");
        self.scopes.len() - 1
    }

    pub fn push_scope(&mut self) {
        self.scopes.push(Scope { last: null_mut() });
    }

    pub fn pop_scope(&mut self) {
        assert!(self.scopes.len() > 1, "Cannot pop the root scope");
        self.pop_scope_impl();
    }

    fn pop_scope_impl(&mut self) {
        assert!(!self.scopes.is_empty(), "No current scope to clear");
        let cur_depth = self.scopes.len() - 1;
        unsafe {
            let mut current = self.scopes.pop().unwrap().last;
            while !current.is_null() {
                debug_assert!((*current).depth == cur_depth, "Bad scope link");
                let popped = self.pop_node(&(*current).key);
                debug_assert!(current == popped, "Unexpected innermost value for key");
                current = (*current).prev_in_scope;
                // All nodes deallocated here.
                std::mem::drop(Box::from_raw(popped));
            }
        }
    }

    /// Unlinks the innermost Node for a key and returns it.
    fn pop_node(&mut self, key: &K) -> *mut Node<K, V> {
        unsafe {
            let entry = self.map.get_mut(key).unwrap();
            let ptr = *entry;
            let next_shadowed = (*ptr).prev_shadowed;
            if !next_shadowed.is_null() {
                *entry = next_shadowed;
            } else {
                self.map.remove(key);
            }
            ptr
        }
    }
}

impl<K, V> Drop for ScopedHashMap<K, V>
where
    K: Eq + Hash + Clone,
{
    fn drop(&mut self) {
        while !self.scopes.is_empty() {
            self.pop_scope_impl();
        }
    }
}

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

    #[test]
    fn test1() {
        let mut map = ScopedHashMap::<i32, i32>::new();

        map.insert(1, 10);
        map.insert(2, 20);

        assert_eq!(*map.value(1).unwrap(), 10);
        assert_eq!(*map.value(2).unwrap(), 20);
        assert!(map.value(3).is_none());
    }

    #[test]
    fn test2() {
        let mut map = ScopedHashMap::<i32, i32>::new();

        map.insert(1, 10);
        map.insert(2, 20);

        assert_eq!(*map.value(1).unwrap(), 10);
        assert_eq!(*map.value(2).unwrap(), 20);
        assert!(map.value(3).is_none());

        map.in_new_scope(|map| {
            map.insert(1, 11);
            map.insert(3, 31);
            assert_eq!(*map.value(1).unwrap(), 11);
            assert_eq!(*map.value(2).unwrap(), 20);
            assert_eq!(*map.value(3).unwrap(), 31);
            map.in_new_scope(|map| {
                map.insert(1, 12);
                map.insert(3, 32);
                assert_eq!(*map.value(1).unwrap(), 12);
                assert_eq!(*map.value(2).unwrap(), 20);
                assert_eq!(*map.value(3).unwrap(), 32);
            });
            assert_eq!(*map.value(1).unwrap(), 11);
            assert_eq!(*map.value(2).unwrap(), 20);
            assert_eq!(*map.value(3).unwrap(), 31);
        });

        assert_eq!(*map.value(1).unwrap(), 10);
        assert_eq!(*map.value(2).unwrap(), 20);
        assert!(map.value(3).is_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.
 */

use std::cell::UnsafeCell;
use std::collections::HashMap;
use std::rc::Rc;

use crate::HeapSize;
use crate::NullTerminatedBuf;

/// A source range within a single JS file.
///
/// Represented as a closed interval: [start, end].
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct SourceRange {
    /// Index of the file this range is in.
    pub file: SourceId,

    /// Start of the source range, inclusive.
    pub start: SourceLoc,

    /// End of the source range, inclusive.
    pub end: SourceLoc,
}

impl SourceRange {
    /// Create a SourceRange describing a single location.
    pub fn from_loc(file: SourceId, start: SourceLoc) -> SourceRange {
        SourceRange {
            file,
            start,
            end: start,
        }
    }
}

/// Line and column of a file.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct SourceLoc {
    /// 1-based line number.
    pub line: u32,

    /// 1-based column number.
    pub col: u32,
}

impl SourceLoc {
    /// Return an instance of SourceLoc initialized to an invalid value.
    pub fn invalid() -> SourceLoc {
        SourceLoc { line: 0, col: 0 }
    }
}

/// An opaque value identifying a source registered with SourceManager.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct SourceId(pub u32);

impl SourceId {
    pub const INVALID: SourceId = SourceId(u32::MAX);

    pub fn is_valid(self) -> bool {
        self.0 != Self::INVALID.0
    }

    fn as_usize(self) -> usize {
        self.0 as usize
    }
}

#[derive(Debug, Default)]
struct Inner {
    num_errors: usize,
    num_warnings: usize,
    num_notes: usize,
}

/// SourceManager owns a collection of source buffers and their names and handles
/// reporting errors.
#[derive(Debug, Default)]
pub struct SourceManager {
    sources: Vec<(String, Rc<NullTerminatedBuf>)>,
    filenames: HashMap<String, SourceId>,
    inner: UnsafeCell<Inner>,
}

impl SourceManager {
    pub fn new() -> SourceManager {
        Default::default()
    }

    /// Register a source buffer with its name.
    pub fn add_source<S: Into<String>>(&mut self, name: S, buf: NullTerminatedBuf) -> SourceId {
        assert!(
            self.sources.len() < SourceId::INVALID.0 as usize,
            "Too many sources",
        );
        let id = SourceId(self.sources.len() as u32);
        let name_str = name.into();
        self.filenames.insert(name_str.clone(), id);
        self.sources.push((name_str, Rc::new(buf)));
        id
    }

    /// Obtain the number of registered sources.
    pub fn num_sources(&self) -> usize {
        self.sources.len()
    }

    /// Obtain the name of a previously registered source buffer.
    pub fn source_name(&self, source_id: SourceId) -> &str {
        self.sources[source_id.as_usize()].0.as_str()
    }

    /// Obtain a reference to a previously registered source buffer.
    pub fn source_buffer(&self, source_id: SourceId) -> &NullTerminatedBuf {
        &self.sources[source_id.as_usize()].1
    }

    /// Obtain a Rc of a previously registered source buffer.
    pub fn source_buffer_rc(&self, source_id: SourceId) -> Rc<NullTerminatedBuf> {
        Rc::clone(&self.sources[source_id.as_usize()].1)
    }

    /// Get the `SourceId` for a given file `name`.
    pub fn lookup_name<S: AsRef<str>>(&self, name: S) -> Option<SourceId> {
        self.filenames.get(name.as_ref()).copied()
    }

    /// Gain mutable access to the mutable inner object.
    ///
    /// # Safety
    /// The result reference must never escape.
    #[allow(clippy::mut_from_ref)]
    unsafe fn inner_mut(&self) -> &mut Inner {
        &mut *self.inner.get()
    }

    /// Gain access to the inner object.
    ///
    /// # Safety
    /// The result reference must never escape.
    unsafe fn inner(&self) -> &Inner {
        &mut *self.inner.get()
    }

    /// Number of errors that have been reported.
    pub fn num_errors(&self) -> usize {
        unsafe { self.inner() }.num_errors
    }
    /// Number of warnings that have been reported.
    pub fn num_warnings(&self) -> usize {
        unsafe { self.inner() }.num_warnings
    }

    /// Report an error at the specified range in the specified source buffer.
    pub fn error<S: Into<String>>(&self, range: SourceRange, msg: S) {
        let inner = unsafe { self.inner_mut() };
        inner.num_errors += 1;

        eprintln!(
            "{}:{}:{}: error: {}",
            self.source_name(range.file),
            range.start.line,
            range.start.col,
            msg.into()
        );
    }
    pub fn note<S: Into<String>>(&self, range: SourceRange, msg: S) {
        let inner = unsafe { self.inner_mut() };
        inner.num_notes += 1;

        eprintln!(
            "{}:{}:{}: note: {}",
            self.source_name(range.file),
            range.start.line,
            range.start.col,
            msg.into()
        );
    }
    /// Report a warning at the specified range in the specified source buffer.
    pub fn warning<S: Into<String>>(&self, range: SourceRange, msg: S) {
        let inner = unsafe { self.inner_mut() };
        inner.num_warnings += 1;

        eprintln!(
            "{}:{}:{}: warning: {}",
            self.source_name(range.file),
            range.start.line,
            range.start.col,
            msg.into()
        );
    }
}

impl HeapSize for SourceManager {
    fn heap_size(&self) -> usize {
        self.sources.heap_size() + self.filenames.heap_size()
    }
}

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

    #[test]
    fn smoke_test() {
        let mut sm = SourceManager::new();

        let id1 = sm.add_source("buf1", NullTerminatedBuf::from_str_copy("a"));
        let id2 = sm.add_source("buf2", NullTerminatedBuf::from_str_copy("bb"));

        assert_eq!("buf1", sm.source_name(id1));
        assert_eq!("buf2", sm.source_name(id2));

        assert_eq!(2, sm.source_buffer(id1).len());
        assert_eq!(3, sm.source_buffer(id2).len());

        let buf1 = sm.source_buffer_rc(id1);
        assert_eq!(2, buf1.len());
        assert_eq!(b"a\0", buf1.as_bytes());
    }
}


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

/// Create a public enum with simple variants which can be converted
/// back and forth from a `&str` simply.
/// ```ignore
/// define_str_enum!(Foo,
///   ErrorType,
///   (Variant1, "String1"),
///   (Variant2, "String2")
/// );
/// ```
/// will create a `Foo` enum with two variants and their string counterparts.
/// `ErrorType` is the type of the error when a parse fails.
#[macro_export]
macro_rules! define_str_enum {
    ($name:ident, $error:ident, $(($variant:ident, $string:expr)),+ $(,)?) => {
        #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
        pub enum $name {
            $($variant),+
        }

        /// Implementing FromStr allows us to use the `.parse()` function.
        impl std::str::FromStr for $name {
            type Err = $error;

            fn from_str(value: &str) -> Result<Self, Self::Err> {
                match value {
                    $($string => Ok(Self::$variant),)+
                    _ => Err($error),
                }
            }
        }

        impl std::convert::TryFrom<&str> for $name {
            type Error = $error;

            fn try_from(value: &str) -> Result<Self, Self::Error> {
                use std::str::FromStr;
                Self::from_str(value)
            }
        }

        impl $name {
            pub fn as_str(&self) -> &'static str {
                match self {
                    $(Self::$variant => $string,)+
                }
            }
        }

    }
}


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

use std::fmt::Display;
use std::fmt::Formatter;
use std::time::Duration;
use std::time::Instant;

type Mark = (&'static str, Duration);

/// A convenience utility to measure execution time of sections.
#[derive(Debug)]
pub struct Timer {
    start_time: Instant,
    last_update: Duration,
    marks: Vec<Mark>,
}

impl Default for Timer {
    fn default() -> Self {
        Timer {
            start_time: Instant::now(),
            last_update: Default::default(),
            marks: vec![],
        }
    }
}

impl Timer {
    pub fn new() -> Self {
        Default::default()
    }

    /// Record the duration of the just completed section.
    pub fn mark(&mut self, name: &'static str) {
        let new_upd = self.start_time.elapsed();
        let duration = new_upd - self.last_update;
        self.last_update = new_upd;
        self.marks.push((name, duration));
    }
}

impl Display for Timer {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        // In "alternate" mode, pretty print the result in aligned table form.
        // In "normal mode", just dump the marks using debug formatting (JSON-like).
        if f.alternate() {
            let max_name_width = self
                .marks
                .iter()
                .fold(0usize, |accum, m| accum.max(m.0.len()));

            // Determine the number of decimal places and scale.
            let mut dec = 1;
            let mut scale = 0;
            let mut dur = self.last_update.as_secs_f64();
            for i in 0..=3 {
                scale = i;
                if dur >= 1.0 {
                    dec = f64::log10(dur).trunc() as usize + 1;
                    break;
                } else if i < 3 {
                    dur *= 1e3;
                }
            }

            const SCALES: [&str; 4] = ["s", "ms", "us", "ns"];

            let mult = f64::powf(1e3, scale as f64);

            let mut fmt = |m: &Mark| {
                writeln!(
                    f,
                    "{0:1$}: {2:>3$.4$} {5}",
                    m.0,
                    max_name_width,
                    m.1.as_secs_f64() * mult,
                    dec + 4,
                    3,
                    SCALES[scale]
                )
            };

            for m in &self.marks {
                fmt(m)?;
            }
            fmt(&("Total", self.last_update))?;
        } else {
            let mut map = f.debug_map();
            for m in &self.marks {
                map.entry(&m.0, &m.1);
            }
            map.entry(&"Total", &self.last_update);
            map.finish()?;
        }

        Ok(())
    }
}


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

fn main() {
    if cfg!(target_os = "macos") {
        println!("cargo:rustc-link-lib=c++");
    } else if cfg!(target_os = "linux") {
        println!("cargo:rustc-link-lib=stdc++");
    }
}


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

//! This crate links the correct libc++ or libstdc++.
//! The library itself contains no code, all code is in build.rs.
//! To link the C++ std library, take a dependency on this crate.

#![no_std]


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

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

    let dst = cmake::Config::new("../../../../")
        .build_target("FileCheck")
        .build();
    println!(
        "cargo:rustc-env=HERMES_LIT={}/build/bin/hermes-lit",
        dst.display()
    );
    println!(
        "cargo:rustc-env=HERMES_FILECHECK={}/build/bin/FileCheck",
        dst.display()
    );
}


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

/// Path to the LIT binary.
pub fn lit_path() -> &'static str {
    env!("HERMES_LIT")
}

/// Path to the FileCheck binary.
pub fn filecheck_path() -> &'static str {
    env!("HERMES_FILECHECK")
}


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

use std::process::Command;

fn main() {
    Command::new("make")
        .arg("clean")
        .arg("rlib")
        .status()
        .expect("make build failed");

    println!("cargo:rustc-link-search=native=./");
    println!("cargo:rustc-link-lib=static=netconsd");
}


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

use anyhow::bail;
use anyhow::Error;
use clap::Parser;
use libc::in6_addr;
use libc::sigaction;
use libc::sigaddset;
use libc::sigemptyset;
use libc::sigprocmask;
use libc::sigset_t;
use libc::sigwait;
use libc::sockaddr_in6;
use libc::strsignal;
use libc::AF_INET6;
use libc::SA_NODEFER;
use libc::SIGHUP;
use libc::SIGINT;
use libc::SIGPIPE;
use libc::SIGTERM;
use libc::SIGUSR1;
use libc::SIG_BLOCK;
use libc::SIG_IGN;

extern "C" {
    fn register_output_module(path: *const c_char, nr_workers: c_int);
    fn create_threads(params: &netconsd_params) -> *const c_void;
    fn destroy_threads(ctl: *const c_void);
    fn destroy_output_modules();
}

#[repr(C)]
#[derive(Copy, Clone)]
pub struct netconsd_params {
    pub nr_workers: c_int,
    pub nr_listeners: c_int,
    pub mmsg_batch: c_int,
    pub gc_int_ms: c_uint,
    pub gc_age_ms: c_uint,
    pub listen_addr: sockaddr_in6,
}

#[derive(Debug)]
struct GcParams {
    age: u32,
    interval: u32,
}

impl FromStr for GcParams {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let split: Vec<&str> = s.split("/").collect();
        if split.len() != 2 {
            bail!("Wrong GC format, it should be <age>/<interval>");
        }
        let age = match split[0].parse::<u32>() {
            Ok(x) => x,
            Err(_) => bail!("Could not parse age, it should be an unsigned 32bits integer."),
        };

        let interval = match split[1].parse::<u32>() {
            Ok(x) => x,
            Err(_) => bail!("Could not parse interval, it should be an unsigned 32bits integer."),
        };

        if age < interval {
            bail!("GC age should >= GC interval");
        }
        Ok(GcParams { age, interval })
    }
}

#[derive(Parser, Debug)]
struct CliArgs {
    /// Number of worker threads
    #[clap(short, long)]
    workers: Option<u16>,

    /// Number of listener threads
    #[clap(short, long)]
    listeners: Option<u16>,

    /// Message batch size
    #[clap(short, long)]
    batch: Option<u16>,

    /// UDP listen IPV6 address
    #[clap(short, long)]
    address: Option<Ipv6Addr>,

    /// UDP listen port
    #[clap(short = 'u', long)]
    port: Option<u16>,

    /// Garbage collector interval/age in ms
    #[clap(short, long)]
    gc: Option<GcParams>,

    /// Dynamic modules to load
    #[clap()]
    modules: Vec<String>,
}

/*
 * This exists to kick the blocking recvmmsg() call in the listener threads, so
 * they get -EINTR, notice the stop flag, and terminate.
 *
 * See also: stop_and_wait_for_listeners() in threads.c
 */
fn interrupter_handler(_sig: c_int) {
    return;
}

unsafe fn init_sighandlers() {
    let ignorer: sigaction = sigaction {
        sa_sigaction: SIG_IGN,
        sa_mask: MaybeUninit::zeroed().assume_init(),
        sa_flags: 0,
        sa_restorer: None,
    };
    let interrupter = sigaction {
        sa_sigaction: interrupter_handler as usize,
        sa_mask: MaybeUninit::zeroed().assume_init(),
        sa_flags: SA_NODEFER,
        sa_restorer: None,
    };

    sigaction(SIGUSR1, &interrupter, std::ptr::null_mut::<sigaction>());
    sigaction(SIGPIPE, &ignorer, std::ptr::null_mut::<sigaction>());
}

/*
 * Initialize the set of signals for which we try to terminate gracefully.
 */
unsafe fn init_sigset(set: &mut sigset_t) {
    sigemptyset(set);
    sigaddset(set, SIGTERM);
    sigaddset(set, SIGINT);
    sigaddset(set, SIGHUP);
}

fn get_netconsd_params(cli_args: &CliArgs) -> netconsd_params {
    netconsd_params {
        nr_workers: cli_args.workers.unwrap_or(2).into(),
        nr_listeners: cli_args.listeners.unwrap_or(1).into(),
        mmsg_batch: cli_args.batch.unwrap_or(512).into(),
        gc_int_ms: cli_args.gc.as_ref().map(|gc| gc.interval).unwrap_or(0),
        gc_age_ms: cli_args.gc.as_ref().map(|gc| gc.age).unwrap_or(0),
        listen_addr: sockaddr_in6 {
            sin6_family: AF_INET6 as u16,
            sin6_port: cli_args.port.unwrap_or(1514).to_be(),
            sin6_flowinfo: 0,
            sin6_addr: in6_addr {
                s6_addr: cli_args
                    .address
                    .map(|x| x.octets().clone())
                    .unwrap_or_else(|| [0u8; 16]),
            },
            sin6_scope_id: 0,
        },
    }
}

fn main() {
    let cli_args = CliArgs::parse();
    let params = get_netconsd_params(&cli_args);

    unsafe {
        for module in cli_args.modules.iter() {
            let path = CString::new(module.as_str()).unwrap();
            register_output_module(path.as_ptr(), params.nr_workers);
        }

        init_sighandlers();
        let mut set: sigset_t = MaybeUninit::zeroed().assume_init();
        init_sigset(&mut set);
        sigprocmask(SIG_BLOCK, &set, std::ptr::null_mut::<sigset_t>());

        let mut num: c_int = 0;
        let ctl = create_threads(&params);
        sigwait(&set, &mut num);

        println!(
            "Signal: '{:?}', terminating",
            CStr::from_ptr(strsignal(num))
        );

        destroy_threads(ctl);
        destroy_output_modules();
    };
}


/*
 * Utility functions to send fake netconsole messages, can be used to test
 * netconsd and its modules.
 *
 * Copyright (C) 2022, Meta, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the LICENSE file in
 * the root directory of this source tree.
 */
use std::io::Cursor;
use std::mem::size_of;
use std::thread::sleep;
use std::time::Duration;

use byteorder::BigEndian;
use byteorder::ReadBytesExt;
use libc::c_int;
use libc::c_void;
use libc::in6_addr;
use libc::sendto;
use libc::sockaddr;
use libc::sockaddr_in6;
use libc::socket;
use libc::AF_INET6;
use libc::IPPROTO_RAW;
use libc::IPPROTO_UDP;
use libc::SOCK_RAW;

#[derive(Debug)]
pub struct WorkerConfig {
    pub id: u8,
    pub packets_count: u64,
    pub dst_port: u16,
    pub sleep_duration: Option<Duration>,
    pub extended_msg: bool,
    pub sender_addr_rnd_bytes: usize,
}

#[repr(C, packed)]
#[derive(Default)]
struct UdpHdr {
    src_port: u16,
    dst_port: u16,
    len: u16,
    check: u16,
}

#[repr(C, packed)]
#[derive(Default)]
struct Ip6Hdr {
    ctl: u32,
    plen: u16,
    next: u8,
    hlim: u8,
    src: [u8; 16],
    dst: [u8; 16],
}

#[repr(C, packed)]
struct NetconsPacket {
    l3: Ip6Hdr,
    l4: UdpHdr,
    payload: [u8; 64],
}

impl NetconsPacket {
    fn new(dst_ip: [u8; 16], dst_port: u16) -> NetconsPacket {
        let len: u16 = 64 /* payload fixed length */ + 8 /* size of UdpHdr */;
        NetconsPacket {
            l3: Ip6Hdr {
                ctl: 6 << 4,
                plen: len.to_be(),
                next: IPPROTO_UDP as u8,
                hlim: 64,
                src: [0u8; 16],
                dst: dst_ip,
            },
            l4: UdpHdr {
                src_port: 6666u16.to_be(),
                dst_port: dst_port.to_be(),
                len: len.to_be(),
                check: 0,
            },
            payload: [0u8; 64],
        }
    }

    fn update_checksum(&mut self) {
        self.l4.check = compute_checksum(self);
    }

    fn set_payload(&mut self, msg: &str) {
        self.payload[0..msg.len()].copy_from_slice(msg.as_bytes());
        for i in msg.len()..64 {
            self.payload[i] = 0;
        }
    }
}

fn get_raw_socket() -> c_int {
    unsafe { socket(AF_INET6, SOCK_RAW, IPPROTO_RAW) }
}

fn sum_bytes_for_checksum(sum: &mut u32, b: &[u8]) {
    let mut i = 0;
    while i < b.len() {
        let mut v: u32 = b[i].into();
        i += 1;
        *sum += v << 8;
        v = b[i].into();
        *sum += v;
        i += 1;
    }
    if *sum > 0xffff {
        *sum -= 0xffff;
    }
}

fn sum_u16_for_checksum(sum: &mut u32, b: u16) {
    *sum += b as u32;
    if *sum > 0xffff {
        *sum -= 0xffff;
    }
}

fn compute_checksum(packet: &NetconsPacket) -> u16 {
    let mut sum = 0u32;
    sum_bytes_for_checksum(&mut sum, &packet.l3.src);
    sum_bytes_for_checksum(&mut sum, &packet.l3.dst);

    sum_u16_for_checksum(&mut sum, (packet.payload.len() + 8) as u16);
    sum_u16_for_checksum(&mut sum, IPPROTO_UDP as u16);

    sum_u16_for_checksum(&mut sum, packet.l4.src_port.to_be());
    sum_u16_for_checksum(&mut sum, packet.l4.dst_port.to_be());
    sum_u16_for_checksum(&mut sum, packet.l4.len.to_be());

    let mut payload_buf = Cursor::new(packet.payload);
    while let Ok(value) = payload_buf.read_u16::<BigEndian>() {
        sum_u16_for_checksum(&mut sum, value);
    }

    if sum == 0 {
        sum = 65535;
    }

    !(sum as u16).to_be()
}

fn send_packet(fd: c_int, packet: &NetconsPacket, sockaddr: &sockaddr_in6) {
    unsafe {
        let pkt_ptr = (packet as *const NetconsPacket) as *const c_void;
        let pkt_size = size_of::<NetconsPacket>();

        // libc::sendto requires a sockaddr pointer, but here we must use a sockaddr_in6,
        // this might be a bad implementation of rust libc.
        let sockaddr_ptr = &*((sockaddr as *const sockaddr_in6) as *const sockaddr);
        let sockaddr_size: u32 = size_of::<sockaddr_in6>()
            .try_into()
            .expect("Could not convert size of sockaddr_in6 to u32.");

        let _ = sendto(fd, pkt_ptr, pkt_size, 0, sockaddr_ptr, sockaddr_size);
    };
}

fn make_sockaddr_in6(dst_ip: [u8; 16]) -> sockaddr_in6 {
    sockaddr_in6 {
        sin6_family: AF_INET6 as u16,
        sin6_port: 0,
        sin6_flowinfo: 0,
        sin6_addr: in6_addr { s6_addr: dst_ip },
        sin6_scope_id: 0,
    }
}

pub fn blast_worker(config: WorkerConfig) {
    let fd = get_raw_socket();

    let mut dst_ip = [0u8; 16];
    dst_ip[15] = 1;
    let addr = make_sockaddr_in6(dst_ip);

    let mut packet = NetconsPacket::new(dst_ip, config.dst_port);
    packet.l3.src[15] = config.id as u8;

    for i in 0u64..config.packets_count {
        let msg = if config.extended_msg {
            format!("{},{},{},-;hello packet {} {}\n", 4, i, i, config.id, i)
        } else {
            format!("[{}] hello packet {} {}\n", i, config.id, i)
        };
        packet.set_payload(&msg);
        for j in 0..config.sender_addr_rnd_bytes {
            packet.l3.src[j] = rand::random();
        }
        packet.update_checksum();

        send_packet(fd, &packet, &addr);
        if let Some(t) = config.sleep_duration {
            sleep(t);
        }
    }
}


/*
 * Simple utility that sends netconsole messages to localhost.
 *
 * Copyright (C) 2022, Meta, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the LICENSE file in
 * the root directory of this source tree.
 */
use std::thread;
use std::time::Duration;
use std::time::Instant;

use clap::Parser;
use libblaster::blast_worker;
use libblaster::WorkerConfig;

#[derive(Parser)]
struct CliArgs {
    #[clap(short, long, default_value_t = 1)]
    threads: u16,

    #[clap(short, long, default_value_t = std::u64::MAX)]
    packets: u64,

    #[clap(short = 'u', long, default_value_t = 6666u16)]
    port: u16,

    #[clap(short, long)]
    sleep_time_nano: Option<u64>,

    #[clap(short = 'r', long, default_value_t = 0)]
    sender_ip_rnd_bytes: usize,
}

fn format_duration(duration: &Duration) -> String {
    let mins = duration.as_secs() / 60;
    let secs = duration.as_secs() % 60;
    let ms = duration.as_millis() % 1000;
    format!("{:02}:{:02}.{:03}", mins, secs, ms)
}

fn main() {
    let args = CliArgs::parse();

    let mut workers: Vec<thread::JoinHandle<()>> = Vec::new();
    let sleep_duration = args.sleep_time_nano.map(Duration::from_nanos);
    let start_time = Instant::now();
    for i in 0..args.threads {
        let config = WorkerConfig {
            id: i as u8,
            packets_count: args.packets,
            dst_port: args.port,
            sleep_duration,
            extended_msg: true,
            sender_addr_rnd_bytes: args.sender_ip_rnd_bytes,
        };
        workers.push(thread::spawn(move || {
            blast_worker(config);
        }))
    }

    for w in workers {
        let _ = w.join();
    }
    let packets_sent = args.packets * args.threads as u64;
    println!(
        "Sent {} packets with {} threads in {}",
        packets_sent,
        args.threads,
        format_duration(&start_time.elapsed())
    );
}


/*
 * A minimal example of a Rust netconsd module.
 *
 * Copyright (C) 2022, Meta, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the LICENSE file in
 * the root directory of this source tree.
 */

use netconsd_module::c_int;
use netconsd_module::format_in6_addr_ptr;
use netconsd_module::in6_addr;
use netconsd_module::MsgBuf;
use netconsd_module::NcrxMsg;

fn fmt_ptr<T: std::fmt::Display>(ptr: *const T) -> String {
    match unsafe { ptr.as_ref() } {
        None => "NULL".to_owned(),
        Some(x) => format!("{}", x),
    }
}

#[no_mangle]
pub extern "C" fn netconsd_output_init(nr_workers: c_int) -> c_int {
    println!(
        "Rust example module init! netconsd will use {} workers",
        nr_workers
    );
    0
}

#[no_mangle]
pub extern "C" fn netconsd_output_handler(
    t: c_int,
    in6_addr: *const in6_addr,
    buf: *const MsgBuf,
    msg: *const NcrxMsg,
) -> i32 {
    println!(
        "Received message from {} on thread {}",
        format_in6_addr_ptr(in6_addr),
        t
    );

    println!("Buf: {}", fmt_ptr(buf));
    println!("Msg: {}", fmt_ptr(msg));
    0
}

#[no_mangle]
pub extern "C" fn netconsd_output_exit() {
    println!("Rust example module bye bye");
}


/*
 * These structs have been generated with bindgen (except for the bitfields getters),
 * and are passed to netconsd_output_handler function defined in a netconsd module.
 *
 * Copyright (C) 2022, Meta, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the LICENSE file in
 * the root directory of this source tree.
 */

use std::ffi::CStr;
use std::fmt;
use std::os::raw::c_char;
pub use std::os::raw::c_int;
use std::os::raw::c_void;

pub use libc::in6_addr;
use libc::iovec;
pub use libc::sockaddr_in6;

#[repr(C)]
#[derive(Debug)]
pub struct MsgBuf {
    pub next: *const MsgBuf,
    pub iovec: iovec,
    pub src: sockaddr_in6,
    pub hole: [u8; 4],
    pub rcv_time: u64,
    pub rcv_flags: c_int,
    pub rcv_bytes: c_int,
}

#[derive(Debug)]
#[repr(C)]
pub struct NcrxList {
    pub next: *mut NcrxList,
    pub prev: *mut NcrxList,
}

#[derive(Debug)]
#[repr(C)]
pub struct NcrxMsg {
    pub seq: u64,
    pub ts_usec: u64,
    pub text: *const c_char,
    pub dict: *const c_char,
    pub text_len: c_int,
    pub dict_len: c_int,
    pub facility: u8,
    pub level: u8,
    pub flags: u8,
    pub node: NcrxList,
    pub rx_at_mono: u64,
    pub rx_at_real: u64,
    pub ncfrag_off: c_int,
    pub ncfrag_len: c_int,
    pub ncfrag_left: c_int,
    pub _bitfield_align_2: [u8; 0],
    pub _bitfield_2: [u8; 1usize],
}

impl NcrxMsg {
    pub fn get_cont_start(&self) -> bool {
        self.flags & 0b1 > 0
    }
    pub fn get_cont(&self) -> bool {
        self.flags & 0b10 > 0
    }

    pub fn get_oos(&self) -> bool {
        self.flags & 0b100 > 0
    }
    pub fn get_seq_reset(&self) -> bool {
        self.flags & 0b1000 > 0
    }
}

pub fn format_in6_addr_ptr(ptr: *const in6_addr) -> String {
    match unsafe { ptr.as_ref() } {
        None => "NULL".to_owned(),
        Some(x) => format!("{:x?}", x.s6_addr),
    }
}

impl fmt::Display for NcrxMsg {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        let cont_start = if self.get_cont_start() {
            "[CONT_START]"
        } else {
            ""
        };
        let cont = if self.get_cont() { "[CONT]" } else { "" };
        let oos = if self.get_oos() { "[OOS]" } else { "" };
        let seq_reset = if self.get_seq_reset() {
            "[SEQ_RESET]"
        } else {
            ""
        };
        let text = str_from_c_void(self.text as *const c_void);
        write!(
            formatter,
            "S{} T{} F{}/L{}{}{}{}{}: {}",
            self.seq,
            self.ts_usec,
            self.facility,
            self.level,
            cont_start,
            cont,
            oos,
            seq_reset,
            text
        )
    }
}

fn str_from_c_void(ptr: *const c_void) -> &'static str {
    unsafe { CStr::from_ptr(ptr as *const c_char).to_str().unwrap() }
}

impl fmt::Display for MsgBuf {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(formatter, "{}", str_from_c_void(self.iovec.iov_base))
    }
}


/*
 * A netconsd module that sends messages to loopback and verifies that it
 * receives all the messages.
 * E.g. ./netconsd -w 2 -l 2 -u 6666 self_test_module.so
 *
 * Copyright (C) 2022, Meta, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the LICENSE file in
 * the root directory of this source tree.
 */
use std::ffi::CStr;
use std::os::raw::c_char;
use std::os::raw::c_int;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use std::sync::Mutex;
use std::sync::MutexGuard;
use std::thread;
use std::time::Duration;

use libblaster::blast_worker;
use libblaster::WorkerConfig;
use libc::in6_addr;
use netconsd_module::MsgBuf;
use netconsd_module::NcrxMsg;
use nix::sys::signal;
use nix::unistd::Pid;
use once_cell::sync::Lazy;

const MESSAGES_TO_SEND_PER_THREAD: usize = 100;
const SENDER_THREADS_COUNT: usize = 10;
const TOT_MESSAGES_TO_SEND: usize = SENDER_THREADS_COUNT * MESSAGES_TO_SEND_PER_THREAD;
static TERMINATED: AtomicBool = AtomicBool::new(false);
static INVOCATIONS_COUNT: AtomicUsize = AtomicUsize::new(0);
const TIMEOUT_DURATION: Duration = Duration::from_secs(5);
const BLASTER_SLEEP_DURATION: Duration = Duration::from_nanos(10);
const SENDR_ADDR_RND_BYTES: usize = 0;

#[derive(Clone, Debug)]
struct ReceivedMessage {
    src: [u8; 16],
    msg: String,
}

static RECEIVED_MESSAGES: Lazy<Mutex<Vec<ReceivedMessage>>> = Lazy::new(|| Mutex::new(Vec::new()));

fn result_str(result: bool) -> &'static str {
    if result { "-> OK" } else { "-> FAILURE" }
}

fn get_received_messages() -> MutexGuard<'static, Vec<ReceivedMessage>> {
    RECEIVED_MESSAGES
        .lock()
        .expect("Could not lock RECEIVED_MESSAGES")
}

fn verify_invocations_count() -> bool {
    let invocations_count = INVOCATIONS_COUNT.load(Ordering::SeqCst);
    let passed = invocations_count == TOT_MESSAGES_TO_SEND;
    print!("{} invocations ", invocations_count);
    if !passed {
        print!("but should have been {}", TOT_MESSAGES_TO_SEND);
    }
    println!("{}", result_str(passed));
    passed
}

fn verify_received_messages_texts() -> bool {
    let mut passed = true;
    for message in get_received_messages().iter() {
        if !message.msg.starts_with("hello packet") {
            println!("unexpected message: {}", message.msg);
            passed = false;
        }
    }
    println!("messages texts {}", result_str(passed));
    passed
}

fn verify_received_messages_count() -> bool {
    let messages_count = get_received_messages().len();
    print!("received {} ncrx messages ", messages_count);

    let passed = messages_count == TOT_MESSAGES_TO_SEND;
    if !passed {
        print!("but should have been {} ", TOT_MESSAGES_TO_SEND);
    }
    println!("{}", result_str(passed));
    passed
}

fn check_src_address(addr: &[u8; 16]) -> Option<usize> {
    if addr[0..15].iter().any(|x| *x != 0) {
        return None;
    }
    Some(addr[15].into())
}

fn verify_received_messages_addresses() -> bool {
    let mut passed = true;
    let mut seen_ids = vec![0; SENDER_THREADS_COUNT];

    for msg in get_received_messages().iter() {
        match check_src_address(&msg.src) {
            Some(id) => seen_ids[id] += 1,
            None => {
                println!("Bad src address: {:x?}", msg.src);
                passed = false;
            }
        }
    }
    for (i, seen_id) in seen_ids.into_iter().enumerate() {
        if seen_id != MESSAGES_TO_SEND_PER_THREAD {
            println!(
                "got {} messages from thread {}, should have been {}",
                seen_id, i, MESSAGES_TO_SEND_PER_THREAD
            );
            passed = false;
        }
    }
    println!("src addresses {}", result_str(passed));
    passed
}

fn end_self_test() {
    if !TERMINATED.fetch_or(true, Ordering::SeqCst) {
        signal::kill(Pid::this(), signal::SIGTERM).expect("Could not send SIGTERM");
    }
}

fn process_received_message(addr_ptr: *const in6_addr, msg_ptr: *const NcrxMsg) {
    if let Some(addr) = unsafe { addr_ptr.as_ref() } {
        if let Some(msg) = unsafe { msg_ptr.as_ref() } {
            let text = match unsafe { CStr::from_ptr(msg.text as *const c_char) }.to_str() {
                Ok(x) => x,
                Err(_) => {
                    println!("Could not convert NcrxMsg msg to string");
                    ""
                }
            };
            let received_message = ReceivedMessage {
                src: addr.s6_addr.clone(),
                msg: text.to_owned(),
            };
            get_received_messages().push(received_message);
        }
    }
}

fn spawn_blast_workers() {
    for i in 0..SENDER_THREADS_COUNT {
        thread::spawn(move || {
            let config = WorkerConfig {
                id: i as u8,
                packets_count: MESSAGES_TO_SEND_PER_THREAD as u64,
                dst_port: 6666,
                sleep_duration: Some(BLASTER_SLEEP_DURATION),
                extended_msg: true,
                sender_addr_rnd_bytes: SENDR_ADDR_RND_BYTES,
            };
            thread::sleep(Duration::from_millis(100));
            blast_worker(config);
        });
    }
}

fn start_test_timeout() {
    thread::spawn(|| {
        thread::sleep(TIMEOUT_DURATION);
        end_self_test();
    });
}

#[no_mangle]
pub extern "C" fn netconsd_output_init(nr_workers: c_int) -> c_int {
    println!("Selftest module init! {} workers", nr_workers);
    spawn_blast_workers();
    start_test_timeout();
    0
}

#[no_mangle]
pub extern "C" fn netconsd_output_handler(
    _t: c_int,
    in6_addr: *const in6_addr,
    _buf: *const MsgBuf,
    msg: *const NcrxMsg,
) -> i32 {
    process_received_message(in6_addr, msg);
    if INVOCATIONS_COUNT.fetch_add(1, Ordering::SeqCst) >= TOT_MESSAGES_TO_SEND - 1 {
        end_self_test();
    }
    0
}

#[no_mangle]
pub extern "C" fn netconsd_output_exit() {
    println!("\nSELF TEST RESULT");
    let mut passed = verify_invocations_count();
    passed &= verify_received_messages_count();
    passed &= verify_received_messages_texts();
    passed &= verify_received_messages_addresses();
    println!();
    if !passed {
        std::process::exit(1);
    }
}


// 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.
#![feature(exit_status_error)]

use std::process::Command;
use std::process::ExitStatusError;

pub fn cmd(prog: &str, args: &[&str], dir: Option<&std::path::Path>) -> Command {
    let mut prog_cmd = Command::new(prog);
    if let Some(path) = dir {
        prog_cmd.current_dir(path);
    }
    prog_cmd.args(args);
    prog_cmd
}

pub fn workspace_dir(ds: &[&str]) -> std::path::PathBuf {
    let mut cargo_cmd = cmd(
        "cargo",
        &["locate-project", "--workspace", "--message-format=plain"],
        None,
    );
    let output = cargo_cmd.output().unwrap().stdout;
    let root_cargo_toml = std::path::Path::new(std::str::from_utf8(&output).unwrap().trim());
    let mut p = root_cargo_toml.parent().unwrap().to_path_buf();
    for d in ds {
        p.push(d);
    }
    p
}

pub fn run(mut cmd: Command) -> Result<(), ExitStatusError> {
    cmd.spawn().unwrap().wait().ok().unwrap().exit_ok()
}

pub fn fmt_exit_status_err(err: ExitStatusError) -> String {
    format!("error status: {err}")
}

pub fn build_flavor() -> &'static str {
    if cfg!(debug_assertions) {
        "debug"
    } else {
        "release"
    }
}


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

use std::fmt::Display;

use proc_macro2::TokenStream;
use proc_macro2::TokenTree;

fn mismatch(ta: Option<TokenTree>, tb: Option<TokenTree>, ax: &TokenStream, bx: &TokenStream) -> ! {
    panic!(
        "Mismatch!\nLeft:  {}\nRight: {}\nwhen comparing\nLeft:  {}\nRight: {}\n",
        ta.map_or("None".into(), |t| t.to_string()),
        tb.map_or("None".into(), |t| t.to_string()),
        ax,
        bx
    );
}

pub fn assert_pat_eq<E: Display>(a: Result<TokenStream, E>, b: TokenStream) {
    let a = match a {
        Err(err) => {
            panic!("Unexpected error '{}'", err);
        }
        Ok(ok) => ok,
    };

    fn inner_cmp(a: TokenStream, b: TokenStream, ax: &TokenStream, bx: &TokenStream) {
        let mut ia = a.into_iter();
        let mut ib = b.into_iter();

        loop {
            let t_a = ia.next();
            let t_b = ib.next();
            if t_a.is_none() && t_b.is_none() {
                break;
            }
            if t_a.is_none() || t_b.is_none() {
                mismatch(t_a, t_b, ax, bx);
            }
            let t_a = t_a.unwrap();
            let t_b = t_b.unwrap();

            match (&t_a, &t_b) {
                (TokenTree::Ident(a), TokenTree::Ident(b)) if a == b => {}
                (TokenTree::Literal(a), TokenTree::Literal(b))
                    if a.to_string() == b.to_string() => {}
                (TokenTree::Punct(a), TokenTree::Punct(b)) if a.to_string() == b.to_string() => {}
                (TokenTree::Group(ga), TokenTree::Group(gb))
                    if ga.delimiter() == gb.delimiter() =>
                {
                    inner_cmp(ga.stream(), gb.stream(), ax, bx);
                }
                (TokenTree::Group(_), _)
                | (TokenTree::Ident(_), _)
                | (TokenTree::Punct(_), _)
                | (TokenTree::Literal(_), _) => mismatch(Some(t_a), Some(t_b), ax, bx),
            }
        }
    }

    inner_cmp(a.clone(), b.clone(), &a, &b);
}

pub fn assert_error<E: Display>(a: Result<TokenStream, E>, b: &str) {
    match a {
        Ok(a) => panic!("Expected error but got:\n{}", a),
        Err(e) => {
            let a = format!("{}", e);
            assert_eq!(a, b, "Incorrect error")
        }
    }
}


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

use std::cell::RefCell;
use std::cmp::max;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;

use crate::block::Header;
use crate::Allocator;
use crate::BlockBuilder;
use crate::MemoizationCache;
use crate::ToOcamlRep;
use crate::Value;

struct Chunk {
    data: Box<[Value<'static>]>,
    index: usize,

    /// Pointer to the prev arena segment.
    prev: Option<Box<Chunk>>,
}

impl Chunk {
    fn with_capacity(capacity: usize) -> Self {
        Self {
            index: 0,
            data: vec![Value::int(0); capacity].into_boxed_slice(),
            prev: None,
        }
    }

    fn capacity(&self) -> usize {
        self.data.len()
    }

    fn can_fit(&self, requested_size: usize) -> bool {
        self.index + requested_size <= self.data.len()
    }

    #[inline]
    pub fn alloc(&mut self, requested_size: usize) -> &mut [Value<'static>] {
        let previous_index = self.index;
        self.index += requested_size;
        &mut self.data[previous_index..self.index]
    }
}

// The generation number is used solely to identify which arena a cached value
// belongs to in `RcOc`.
//
// We use usize::max_value() / 2 here to avoid colliding with ocamlpool and
// SlabAllocator generation numbers (ocamlpool starts at 0, and SlabAllocator
// starts at usize::max_value() / 4). This generation trick isn't sound with the
// use of multiple generation counters, but this mitigation should make it
// extremely difficult to mix up values allocated with ocamlpool, Arena, and
// SlabAllocator in practice (one would have to serialize the same value with
// multiple Allocators, and only after increasing the generation of one by an
// absurd amount).
//
// If we add more allocators, we might want to rethink this strategy.
static NEXT_GENERATION: AtomicUsize = AtomicUsize::new(usize::max_value() / 2);

/// An [`Allocator`](trait.Allocator.html) which builds values in Rust-managed
/// memory. The memory is freed when the Arena is dropped.
pub struct Arena {
    generation: usize,
    current_chunk: RefCell<Chunk>,
    cache: MemoizationCache,
}

impl Arena {
    /// Create a new Arena with 4KB of capacity preallocated.
    pub fn new() -> Self {
        Self::with_capacity(1024 * 4)
    }

    /// Create a new Arena with `capacity_in_bytes` preallocated.
    pub fn with_capacity(capacity_in_bytes: usize) -> Self {
        let generation = NEXT_GENERATION.fetch_add(1, Ordering::SeqCst);
        let capacity_in_words = max(2, capacity_in_bytes / std::mem::size_of::<Value<'_>>());
        Self {
            generation,
            current_chunk: RefCell::new(Chunk::with_capacity(capacity_in_words)),
            cache: MemoizationCache::new(),
        }
    }

    #[inline]
    #[allow(clippy::mut_from_ref)]
    fn alloc<'a>(&'a self, requested_size: usize) -> &'a mut [Value<'a>] {
        if !self.current_chunk.borrow().can_fit(requested_size) {
            let prev_chunk_capacity = self.current_chunk.borrow().capacity();
            let prev_chunk = self.current_chunk.replace(Chunk::with_capacity(max(
                requested_size * 2,
                prev_chunk_capacity,
            )));
            self.current_chunk.borrow_mut().prev = Some(Box::new(prev_chunk));
        }
        let mut chunk = self.current_chunk.borrow_mut();
        let slice = chunk.alloc(requested_size);
        // Transmute the 'static lifetime to 'a, to allow Values which point to
        // blocks allocated using this Arena to be stored in other such blocks.
        // The lifetime ensures that callers cannot allow such Values to outlive
        // the arena (and therefore outlive the block they point to). This
        // transmute violates the 'static lifetime in Chunk, so it is critical
        // for safety that we never expose a view of those Values to code
        // outside this module (using the type `Value<'static>`).
        // Also transmute the unnamed lifetime referring to the mutable borrow
        // of `chunk` to 'a. This allows callers to hold multiple mutable blocks
        // at once. This is safe because the blocks handed out by Chunk::alloc
        // are non-overlapping, so there is no aliasing.
        unsafe { std::mem::transmute::<&'_ mut [Value<'static>], &'a mut [Value<'a>]>(slice) }
    }

    #[inline(always)]
    pub fn add<'a, T: ToOcamlRep + ?Sized>(&'a self, value: &'a T) -> Value<'a> {
        value.to_ocamlrep(self)
    }

    #[inline(always)]
    pub fn add_root<'a, T: ToOcamlRep + ?Sized>(&'a self, value: &'a T) -> Value<'a> {
        Allocator::add_root(self, value)
    }
}

impl Allocator for Arena {
    #[inline(always)]
    fn generation(&self) -> usize {
        self.generation
    }

    fn block_with_size_and_tag(&self, size: usize, tag: u8) -> BlockBuilder<'_> {
        let block = self.alloc(size + 1);
        let header = Header::new(size, tag);
        // Safety: We need to make sure that the Header written to index 0 of
        // this slice is never observed as a Value. We guarantee that by not
        // exposing raw Chunk memory--only allocated Values.
        block[0] = unsafe { Value::from_bits(header.to_bits()) };
        let slice = &mut block[1..];
        BlockBuilder::new(slice)
    }

    #[inline(always)]
    fn set_field<'a>(&self, block: &mut BlockBuilder<'a>, index: usize, value: Value<'a>) {
        unsafe { *self.block_ptr_mut(block).add(index) = value }
    }

    unsafe fn block_ptr_mut<'a>(&self, block: &mut BlockBuilder<'a>) -> *mut Value<'a> {
        block.address() as *mut _
    }

    fn memoized<'a>(
        &'a self,
        ptr: usize,
        size: usize,
        f: impl FnOnce(&'a Self) -> Value<'a>,
    ) -> Value<'a> {
        let bits = self.cache.memoized(ptr, size, || f(self).to_bits());
        // SAFETY: The only memoized values in the cache are those computed in
        // the closure on the previous line. Since f returns Value<'a>, any
        // cached bits must represent a valid Value<'a>,
        unsafe { Value::from_bits(bits) }
    }

    fn add_root<'a, T: ToOcamlRep + ?Sized>(&'a self, value: &'a T) -> Value<'a> {
        self.cache.with_cache(|| value.to_ocamlrep(self))
    }
}

#[cfg(test)]
mod tests {
    use std::io::Write;
    use std::time::Instant;

    use super::*;

    #[test]
    fn test_alloc_byte_string_with_len() {
        let arena = Arena::with_capacity(1000);

        let msg: &[u8] = b"Hello world!";
        let mut w = arena.byte_string_with_len(msg.len());
        let bytes_written = w.write(msg).ok().unwrap();
        assert!(bytes_written == msg.len());

        let value: Value<'_> = w.build();
        assert_eq!(value.as_byte_string().unwrap(), msg);
    }

    #[test]
    fn test_alloc_block_of_three_fields() {
        let arena = Arena::with_capacity(1000);

        let mut block = arena.block_with_size(3);
        arena.set_field(&mut block, 0, Value::int(1));
        arena.set_field(&mut block, 1, Value::int(2));
        arena.set_field(&mut block, 2, Value::int(3));
        let block = block.build().as_block().unwrap();

        assert_eq!(block.size(), 3);
        assert_eq!(block[0].as_int().unwrap(), 1);
        assert_eq!(block[1].as_int().unwrap(), 2);
        assert_eq!(block[2].as_int().unwrap(), 3);
    }

    #[test]
    fn test_large_allocs() {
        let arena = Arena::with_capacity(1000);

        let alloc_block = |size| arena.block_with_size(size).build().as_block().unwrap();

        let max = alloc_block(1000);
        assert_eq!(max.size(), 1000);

        let two_thousand = alloc_block(2000);
        assert_eq!(two_thousand.size(), 2000);

        let four_thousand = alloc_block(4000);
        assert_eq!(four_thousand.size(), 4000);
    }

    #[test]
    fn perf_test() {
        let arena = Arena::with_capacity(10_000);

        let alloc_block = |size| arena.block_with_size(size).build().as_block().unwrap();

        println!("Benchmarks for allocating [1] 200,000 times");
        let now = Instant::now();
        for _ in 0..200_000 {
            vec![0; 1].into_boxed_slice();
        }
        println!("Alloc: {:?}", now.elapsed());

        let now = Instant::now();
        for _ in 0..200_000 {
            alloc_block(1);
        }
        println!("Arena: {:?}", now.elapsed());

        println!("Benchmarks for allocating [5] 200,000 times");
        let now = Instant::now();
        for _ in 0..200_000 {
            vec![0; 5].into_boxed_slice();
        }
        println!("Alloc: {:?}", now.elapsed());

        let now = Instant::now();
        for _ in 0..200_000 {
            alloc_block(5);
        }
        println!("Arena: {:?}", now.elapsed());

        println!("Benchmarks for allocating [10] 200,000 times");
        let now = Instant::now();
        for _ in 0..200_000 {
            vec![0; 10].into_boxed_slice();
        }
        println!("Alloc: {:?}", now.elapsed());

        let now = Instant::now();
        for _ in 0..200_000 {
            alloc_block(10);
        }
        println!("Arena: {:?}", now.elapsed());
    }
}


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

use std::collections::HashMap;
use std::fmt;
use std::fmt::Debug;
use std::ops::Index;

use crate::Allocator;
use crate::Value;

/// Blocks with tags greater than or equal to NO_SCAN_TAG contain binary data,
/// and are not scanned by the garbage collector. Likewise, we must avoid
/// interpreting the fields of blocks with such tags as Values.
pub const NO_SCAN_TAG: u8 = 251;
pub const FORWARD_TAG: u8 = 250;
pub const INFIX_TAG: u8 = 249;
pub const OBJECT_TAG: u8 = 248;
pub const CLOSURE_TAG: u8 = 247;
pub const LAZY_TAG: u8 = 246;
pub const CONT_TAG: u8 = 245;
pub const FORCING_TAG: u8 = 244;
pub const ABSTRACT_TAG: u8 = 251;
pub const STRING_TAG: u8 = 252;
pub const DOUBLE_TAG: u8 = 253;
pub const DOUBLE_ARRAY_TAG: u8 = 254;
pub const CUSTOM_TAG: u8 = 255;

/// A recently-allocated, not-yet-finalized Block.
#[repr(transparent)]
pub struct BlockBuilder<'a> {
    fields: &'a mut [Value<'a>],
}

impl<'a> BlockBuilder<'a> {
    /// `address` may be a pointer or an offset (the `Allocator` which invokes
    /// `BlockBuilder::new` determines the meaning of `BlockBuilder` addresses).
    /// `size` must be greater than 0 and denotes the number of fields in the
    /// block.
    ///
    /// # Panics
    ///
    /// Panics if `fields.is_empty()`.
    #[inline(always)]
    pub fn new(fields: &'a mut [Value<'a>]) -> Self {
        if fields.is_empty() {
            panic!()
        }
        Self { fields }
    }

    /// The address of the field slice passed to `BlockBuilder::new`.
    #[inline(always)]
    pub fn address(&self) -> usize {
        self.fields.as_ptr() as _
    }

    /// The number of fields in this block.
    #[inline(always)]
    pub fn size(&self) -> usize {
        self.fields.len()
    }

    // TODO(jakebailey): This needs to be marked unsafe. The caller must
    // initialize all of the fields.
    #[inline(always)]
    pub fn build(self) -> Value<'a> {
        unsafe { Value::from_ptr(self.fields.as_ptr()) }
    }
}

/// The contents of an OCaml block, consisting of a header and one or more
/// fields of type [`Value`](struct.Value.html).
#[repr(transparent)]
#[derive(Clone, Copy)]
pub struct Block<'arena>(pub(crate) &'arena [Value<'arena>]);

impl<'a> Block<'a> {
    #[inline(always)]
    pub fn header(self) -> Header {
        Header(self.0[0].0)
    }

    #[inline(always)]
    pub fn size(self) -> usize {
        self.header().size()
    }

    #[inline(always)]
    pub fn tag(self) -> u8 {
        self.header().tag()
    }

    #[inline(always)]
    pub fn as_value(self) -> Value<'a> {
        unsafe { Value::from_ptr(&self.0[1]) }
    }

    #[inline(always)]
    pub fn as_values(self) -> Option<&'a [Value<'a>]> {
        if self.tag() >= NO_SCAN_TAG {
            return None;
        }
        Some(&self.0[1..])
    }

    #[inline(always)]
    pub fn as_int_slice(self) -> &'a [usize] {
        let slice = &self.0[1..];
        unsafe { std::slice::from_raw_parts(slice.as_ptr().cast(), slice.len()) }
    }

    /// Helper for `Value::clone_with_allocator`.
    pub(crate) fn clone_with<'b, A: Allocator>(
        self,
        alloc: &'b A,
        seen: &mut HashMap<usize, Value<'b>>,
    ) -> Value<'b> {
        let mut block = alloc.block_with_size_and_tag(self.size(), self.tag());
        match self.as_values() {
            Some(fields) => {
                for (i, field) in fields.iter().enumerate() {
                    let field = field.clone_with(alloc, seen);
                    alloc.set_field(&mut block, i, field)
                }
            }
            None => {
                // Safety: Both pointers must be valid, aligned, and
                // non-overlapping. Both pointers are the heads of blocks which
                // came from some Allocator. Allocators are required to allocate
                // blocks with usize-aligned pointers, and those blocks are
                // required to be valid for reads and writes for the number of
                // usize-sized fields reported in the size in their header.
                // Allocators are also required to allocate non-overlapping
                // blocks.
                unsafe {
                    std::ptr::copy_nonoverlapping(
                        self.0.as_ptr().offset(1) as *const usize,
                        alloc.block_ptr_mut(&mut block) as *mut usize,
                        self.size(),
                    )
                }
            }
        }
        block.build()
    }
}

impl<'a> Index<usize> for Block<'a> {
    type Output = Value<'a>;

    #[inline(always)]
    fn index(&self, index: usize) -> &Self::Output {
        &self.0[index + 1]
    }
}

impl Debug for Block<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if self.tag() == STRING_TAG {
            write!(f, "{:?}", self.as_value().as_str().unwrap())
        } else if self.tag() == DOUBLE_TAG {
            write!(f, "{:?}", self.as_value().as_float().unwrap())
        } else {
            write!(f, "{}{:?}", self.tag(), self.as_values().unwrap())
        }
    }
}

// values from ocaml 'gc.h'
#[repr(usize)]
#[derive(Clone, Copy)]
pub enum Color {
    White = crate::CAML_WHITE,
    Gray = crate::CAML_GRAY,
    Blue = crate::CAML_BLUE,
    Black = crate::CAML_BLACK,
}

#[repr(transparent)]
#[derive(Clone, Copy)]
pub struct Header(usize);

impl Header {
    #[inline(always)]
    pub const fn new(size: usize, tag: u8) -> Self {
        Self::with_color(size, tag, Color::White)
    }

    #[inline(always)]
    pub const fn with_color(size: usize, tag: u8, color: Color) -> Self {
        let bits = size << 10 | (color as usize) | (tag as usize);
        Header(bits)
    }

    #[inline(always)]
    pub const fn size(self) -> usize {
        self.0 >> 10
    }

    #[inline(always)]
    pub const fn tag(self) -> u8 {
        self.0 as u8
    }

    #[inline(always)]
    pub const fn color(self) -> Color {
        match self.0 & Color::Black as usize {
            crate::CAML_WHITE => Color::White,
            crate::CAML_GRAY => Color::Gray,
            crate::CAML_BLUE => Color::Blue,
            crate::CAML_BLACK => Color::Black,
            _ => unreachable!(),
        }
    }

    #[inline(always)]
    pub const fn from_bits(bits: usize) -> Self {
        Header(bits)
    }

    #[inline(always)]
    pub const fn to_bits(self) -> usize {
        self.0
    }
}

impl Debug for Header {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Header")
            .field("size", &self.size())
            .field("tag", &self.tag())
            .finish()
    }
}

pub struct BlockBytes<'a>(&'a mut [u8], *const u8);

impl<'a> BlockBytes<'a> {
    // SAFETY: `bytes` must be the data segment of a valid block with
    // `STRING_TAG`, excluding the trailing padding bytes; see
    // `Allocator::byte_string_with_len`
    pub(crate) unsafe fn new(bytes: &'a mut [u8]) -> Self {
        BlockBytes(bytes, bytes.as_ptr())
    }
}

impl std::io::Write for BlockBytes<'_> {
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
        // If the number of bytes to be written exceeds the buffer size, error,
        // don't do short writes!
        self.0.write_all(buf)?;
        Ok(buf.len())
    }
    fn flush(&mut self) -> std::io::Result<()> {
        self.0.flush()
    }
}

impl<'a> BlockBytes<'a> {
    pub fn build(self) -> Value<'a> {
        let BlockBytes(slice, block) = self;
        if !slice.is_empty() {
            panic!("ocamlrep: BlockBytes invariant violation: not all bytes written to.");
        }
        unsafe { Value::from_bits(block as usize) }
    }
}


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

//! Provides `MemoizationCache`, a simple cache designed to aid implementation
//! of the `Allocator` trait.

use std::cell::RefCell;

type HashMap<K, V> = rustc_hash::FxHashMap<K, V>;

/// A simple scoped cache for memoizing conversions from one pointer-sized value
/// to another. Useful for memoizing conversions between OCaml values and Rust
/// references.
pub struct MemoizationCache {
    /// Maps from input (address,size_in_bytes) -> output
    cache: RefCell<Option<HashMap<(usize, usize), usize>>>,
}

impl MemoizationCache {
    #[inline(always)]
    pub fn new() -> Self {
        Self {
            cache: RefCell::new(None),
        }
    }

    #[inline(always)]
    pub fn with_cache<T>(&self, f: impl FnOnce() -> T) -> T {
        // The `replace` below should not panic because the only borrows of
        // `self.cache` are in this function and in `memoized`. In both
        // functions, we do not hold a `Ref` or `RefMut` while calling into code
        // which might attempt to re-enter `memoized` or `with_cache`.
        let prev_value = self.cache.replace(Some(Default::default()));
        if prev_value.is_some() {
            panic!(
                "Attempted to re-enter MemoizationCache::with_cache \
                (probably via ocamlrep::Allocator::add_root, which is not re-entrant)"
            );
        }
        let result = f();
        // As above, this `replace` should not panic.
        self.cache.replace(None);
        result
    }

    #[inline(always)]
    pub fn memoized(&self, input: usize, size_in_bytes: usize, f: impl FnOnce() -> usize) -> usize {
        if size_in_bytes == 0 {
            return f();
        }
        let memoized_output = match (self.cache.borrow().as_ref())
            .map(|cache| cache.get(&(input, size_in_bytes)).copied())
        {
            None => return f(),
            Some(output) => output,
        };
        match memoized_output {
            Some(output) => output,
            None => {
                let output = f();
                // The `borrow_mut` below should not panic because the only
                // borrows of `self.cache` are in this function and in
                // `with_cache`. In this function, we do not hold a `Ref` or
                // `RefMut` while calling into `f` (or any other function which
                // might attempt to re-enter this function or `with_cache`).
                let mut cache = self.cache.borrow_mut();
                // The `unwrap` below should not panic. We know `self.cache` was
                // not None upon entering this function because we looked up
                // `memoized_output`. The only function which can replace the
                // cache with None is `with_cache`, which would have panicked if
                // `f` attempted to re-enter it.
                (cache.as_mut().unwrap()).insert((input, size_in_bytes), output);
                output
            }
        }
    }
}


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

use std::error::Error;
use std::fmt;
use std::num::TryFromIntError;
use std::str::Utf8Error;

/// Returned by
/// [`OcamlRep::from_ocamlrep`](trait.OcamlRep.html#tymethod.from_ocamlrep) when
/// the given [`Value`](struct.Value.html) cannot be converted to a Rust value
/// of the expected type.
#[derive(Debug, PartialEq)]
pub enum FromError {
    BadUtf8(Utf8Error),
    BlockTagOutOfRange { max: u8, actual: u8 },
    ErrorInField(usize, Box<FromError>),
    ExpectedBlock(isize),
    ExpectedBlockTag { expected: u8, actual: u8 },
    ExpectedBool(isize),
    ExpectedChar(isize),
    ExpectedInt(usize),
    ExpectedUnit(isize),
    ExpectedZeroTag(u8),
    IntOutOfRange(TryFromIntError),
    NullaryVariantTagOutOfRange { max: usize, actual: isize },
    WrongBlockSize { expected: usize, actual: usize },
    UnexpectedCustomOps { expected: usize, actual: usize },
}

impl std::convert::From<TryFromIntError> for FromError {
    fn from(error: TryFromIntError) -> Self {
        FromError::IntOutOfRange(error)
    }
}

impl std::convert::From<Utf8Error> for FromError {
    fn from(error: Utf8Error) -> Self {
        FromError::BadUtf8(error)
    }
}

impl fmt::Display for FromError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        use FromError::*;
        match self {
            BadUtf8(_) => write!(f, "Invalid UTF-8"),
            BlockTagOutOfRange { max, actual } => {
                write!(f, "Expected tag value <= {}, but got {}", max, actual)
            }
            ErrorInField(idx, _) => write!(f, "Failed to convert field {}", idx),
            ExpectedBlock(x) => write!(f, "Expected block, but got integer value {}", x),
            ExpectedBlockTag { expected, actual } => write!(
                f,
                "Expected block with tag {}, but got {}",
                expected, actual
            ),
            ExpectedBool(x) => write!(f, "Expected bool, but got {}", x),
            ExpectedChar(x) => write!(f, "Expected char, but got {}", x),
            ExpectedInt(x) => {
                write!(f, "Expected integer value, but got block pointer {:p}", x)
            }
            ExpectedUnit(x) => write!(f, "Expected (), but got {}", x),
            ExpectedZeroTag(x) => write!(
                f,
                "Expected block with tag 0 (tuple, record, cons cell, etc), but got tag value {}",
                x
            ),
            IntOutOfRange(_) => write!(f, "Integer value out of range"),
            NullaryVariantTagOutOfRange { max, actual } => write!(
                f,
                "Expected nullary variant tag, where 0 <= tag <= {}, but got {}",
                max, actual
            ),
            WrongBlockSize { expected, actual } => write!(
                f,
                "Expected block of size {}, but got size {}",
                expected, actual
            ),
            UnexpectedCustomOps { expected, actual } => write!(
                f,
                "Expected custom operations struct address 0x{:x}, but got address 0x{:x}",
                expected, actual
            ),
        }
    }
}

impl Error for FromError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        use FromError::*;
        match self {
            BadUtf8(err) => Some(err),
            ErrorInField(_, err) => Some(err),
            IntOutOfRange(err) => Some(err),
            BlockTagOutOfRange { .. }
            | ExpectedBlock(..)
            | ExpectedBlockTag { .. }
            | ExpectedBool(..)
            | ExpectedChar(..)
            | ExpectedInt(..)
            | ExpectedUnit(..)
            | ExpectedZeroTag(..)
            | NullaryVariantTagOutOfRange { .. }
            | WrongBlockSize { .. }
            | UnexpectedCustomOps { .. } => None,
        }
    }
}