// SPDX-License-Identifier: MIT
//
// Copyright IBM Corp. 2024

use std::fmt::Display;
use std::io::{Cursor, Seek, SeekFrom, Write};
use std::path::PathBuf;
use std::rc::Rc;

use anyhow::{anyhow, Context, Result};
use deku::DekuContainerRead;
use log::debug;
use openssl::pkey::{PKey, Public};
use pv::misc::read_file;
use pv::request::Confidential;
use pvimg::error::Error;
use pvimg::misc::{round_up, serialize_to_bytes, ShortPsw, PSW, PSW_MASK_BA, PSW_MASK_EA};
use pvimg::secured_comp::{
    ComponentTrait, Interval, Layout, SecuredComponent, SecuredComponentBuilder,
};
use pvimg::uvdata::{
    BuilderTrait, PlaintextControlFlagsV1, SeHdrBuilder, SeHdrVersion, SecretControlFlagsV1,
};

use crate::se_img_comps::ipib::Ipib;
use crate::se_img_comps::kernel::S390Kernel;
use crate::se_img_comps::metadata::ImgMetaData;
use crate::se_img_comps::sehdr::SeHdrComp;
use crate::se_img_comps::shortpsw::ShortPSWComp;
use crate::se_img_comps::{
    create_ipib, render_stage3a, render_stage3b, stage3a_path, stage3b_path, CompTweakV1,
    Component, ComponentKind, STAGE3A_ENTRY, STAGE3A_INIT_ENTRY, STAGE3A_LOAD_ADDRESS,
};

pub struct SeHdrArgs<'a> {
    pub keys: &'a [PKey<Public>],
    pub pcf: &'a PlaintextControlFlagsV1,
    pub scf: &'a SecretControlFlagsV1,
    pub cck: &'a Option<(PathBuf, Confidential<Vec<u8>>)>,
    pub hdr_aead_key: &'a Option<(PathBuf, Confidential<Vec<u8>>)>,
    pub psw_addr: &'a Option<u64>,
}

#[derive(Debug, PartialEq, Eq)]
pub struct ImgComponent {
    kind: ComponentKind,
    pub(crate) src: Rc<Interval>,
    pub(crate) secure_mode: Option<SecuredComponent>,
}

impl ImgComponent {
    pub fn kind(&self) -> ComponentKind {
        self.kind.clone()
    }
}

impl Ord for ImgComponent {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.src.cmp(&other.src)
    }
}

impl PartialOrd for ImgComponent {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Display for ImgComponent {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "| {:23} | ", self.kind.to_string())?;
        self.src.to_string().fmt(f)?;
        write!(f, " |")
    }
}

pub struct SeImgBuilder<W> {
    /// Expert mode (components encryption key and Secure Execution header
    /// protection key can be set). By default disabled.
    expert_mode: bool,
    writer: W,
    layout: Layout,
    comps: Vec<Rc<ImgComponent>>,
    builder: SecuredComponentBuilder,
    stage3a: Vec<u8>,
    stage3b: Vec<u8>,
    /// The legacy Secure Execution header address (directly after stage3a)
    legacy_se_hdr_addr: Option<u64>,
    finalized: bool,
}

impl<W: Write + Seek> SeImgBuilder<W> {
    const COMPONENT_ALIGNMENT_V1: u64 = SecuredComponentBuilder::COMPONENT_ALIGNMENT_V1;
    const DEFAULT_INITIAL_PSW_MASK: u64 = PSW_MASK_BA | PSW_MASK_EA;

    /// Create a Secure Execution boot image builder
    #[allow(clippy::similar_names)]
    pub(crate) fn new_v1(
        mut writer: W,
        encryption: bool,
        legacy_expected_se_hdr_size: Option<usize>,
        bootloader_dir: Option<&PathBuf>,
    ) -> Result<Self> {
        let stage3a = read_file(stage3a_path(bootloader_dir), "stage3a")?;
        let stage3b = read_file(stage3b_path(bootloader_dir), "stage3b")?;
        let mut legacy_se_hdr_addr = None;

        // Reserve memory space for the stage3a loader that will be written
        // later.
        let mut next_comp_addr: u64 = round_up(
            STAGE3A_LOAD_ADDRESS
                .checked_add(stage3a.len().try_into()?)
                .ok_or(Error::UnexpectedOverflow)?,
            Self::COMPONENT_ALIGNMENT_V1,
        )?;

        // Reserve memory space for the Secure Execution header in case of
        // legacy mode.
        if let Some(expected_se_hdr_size) = legacy_expected_se_hdr_size {
            // Place the Secure Execution header next to the stage3a and use as
            // the minimum address 0x14000. 0x14000 is used as the starting
            // point for searching the Secure Execution header in the
            // `pvextract-hdr` utility and we can therefore not use e.g. 0x13000
            // even if it would be possible in regard to the memory layout.
            const PV_EXTRACT_SE_HDR_SEARCH_ADDR: u64 = 0x14000;
            let se_hdr_addr = std::cmp::max(next_comp_addr, PV_EXTRACT_SE_HDR_SEARCH_ADDR);
            next_comp_addr = round_up(
                se_hdr_addr
                    .checked_add(expected_se_hdr_size.try_into()?)
                    .ok_or(Error::UnexpectedOverflow)?,
                Self::COMPONENT_ALIGNMENT_V1,
            )?;
            legacy_se_hdr_addr = Some(se_hdr_addr);
        }

        if next_comp_addr % Self::COMPONENT_ALIGNMENT_V1 != 0 {
            return Err(Error::UnalignedAddress {
                addr: next_comp_addr,
                alignment: Self::COMPONENT_ALIGNMENT_V1,
            }
            .into());
        }

        // Secure Execution expects, that the component addresses are aligned to
        // 4096.
        let layout = Layout::new(next_comp_addr, Self::COMPONENT_ALIGNMENT_V1)?;
        let builder = SecuredComponentBuilder::new_v1(encryption)?;

        // The layout of the boot image matches with the memory layout as it
        // it's loaded at location 0x0. Therefore let's seek to the
        // `next_comp_addr`.
        writer.seek(SeekFrom::Start(next_comp_addr))?;

        Ok(Self {
            layout,
            expert_mode: false,
            comps: vec![],
            writer,
            builder,
            legacy_se_hdr_addr,
            stage3a,
            stage3b,
            finalized: false,
        })
    }

    /// Enable expert mode - this is required for specifying component tweaks by
    /// hand etc...
    pub(crate) fn i_know_what_i_am_doing(&mut self) {
        self.builder.i_know_what_i_am_doing();
        self.expert_mode = true;
    }

    /// Prepare the given component as secured component, append it to the layout
    /// and write it to the output.
    ///
    /// # Errors
    ///
    /// This function will return an error if:
    ///  + stage3b has already been added
    ///  + problem with the preparation of the secured component
    ///  + serialization problem of the component tweak (very unlikely)
    ///  + a tweak was given, but the expert mode not enabled
    pub(crate) fn prepare_and_append_as_secure_component<T>(
        &mut self,
        component: &mut T,
        tweak: Option<Vec<u8>>,
    ) -> Result<Rc<ImgComponent>>
    where
        T: ComponentTrait<ComponentKind>,
    {
        if self.finalized {
            return Err(Error::ImgAlreadyFinalized.into());
        }

        if !component.secure_mode() {
            unreachable!("Bug")
        }

        if tweak.is_some() && !self.expert_mode {
            return Err(Error::NonExpertModeTweakGiven.into());
        }

        debug!("Preparing {} as secured component", component.kind());
        let tweak = tweak.unwrap_or(serialize_to_bytes(&CompTweakV1::new(component.kind())?)?);
        // No reason to seek as there are no holes between components (addr
        // alignment == alignment of the component size). If that changes we have to seek beforehand
        // to `self.layout.next_addr` self.writer.seek(SeekFrom::Start(self.layout.
        // next_addr))?;

        let secured_comp = self.builder.prepare_and_append_as_secure_component(
            &mut self.writer,
            &mut self.layout,
            component,
            tweak,
        )?;

        let img_comp = Rc::new(ImgComponent {
            kind: component.kind(),
            src: secured_comp.src.clone(),
            secure_mode: Some(secured_comp),
        });
        self.comps.push(img_comp.clone());
        Ok(img_comp)
    }

    /// Insert and write the given non-secured component at the given address.
    fn insert_nonsecure_component<T: ComponentTrait<ComponentKind>>(
        &mut self,
        component: &mut T,
        addr: u64,
    ) -> Result<Rc<ImgComponent>> {
        // FIXME Guarantee this during compile time using a "SecureMode" trait.
        if component.secure_mode() {
            unreachable!("Programming bug!")
        };

        let max_component_size = self.layout.max_size_of_chunk_at_addr(addr)?;
        let mut buf = vec![0_u8; self.builder.chunk_size()];
        let mut total_written_count: usize = 0;

        assert_ne!(buf.len(), 0);

        self.writer.seek(SeekFrom::Start(addr))?;
        loop {
            let read_count = component.read(&mut buf)?;
            // The end of file has reached as it's guaranteed that the buffer
            // [`buf`] has a length != 0. See
            // https://doc.rust-lang.org/std/io/trait.Read.html#tymethod.read
            if read_count == 0 {
                break;
            }

            if let Some(max_component_size) = max_component_size {
                if total_written_count
                    .checked_add(read_count)
                    .ok_or(Error::UnexpectedOverflow)?
                    > max_component_size
                {
                    return Err(anyhow!(
                        "BUG: Component is too large for this location in the image: {} > {}",
                        total_written_count + read_count,
                        max_component_size
                    ));
                }
            }

            self.writer.write_all(&buf[0..read_count])?;
            total_written_count = total_written_count
                .checked_add(read_count)
                .ok_or(Error::UnexpectedOverflow)?;
        }

        let src = self
            .layout
            .insert_interval(addr, total_written_count.try_into()?)?;
        let img_comp = Rc::new(ImgComponent {
            src,
            kind: component.kind(),
            secure_mode: None,
        });

        match self.comps.binary_search(&img_comp) {
            Ok(_pos) => {
                return Err(anyhow!(
                    "BUG: There is already another component at this location"
                ))
            }
            Err(pos) => self.comps.insert(pos, img_comp.clone()),
        }
        Ok(img_comp)
    }

    fn append_component<T: ComponentTrait<ComponentKind>>(
        &mut self,
        component: &mut T,
    ) -> Result<Rc<ImgComponent>> {
        let next_addr = self.layout.next_addr;
        self.insert_nonsecure_component(component, next_addr)
    }

    /// Prepare IPIB and write it to file
    fn add_ipib(&mut self, sehdr_src: &Interval) -> Result<Rc<ImgComponent>> {
        let img_comps_tweak_and_src: Result<Vec<_>> = self
            .comps
            .iter()
            .filter(|comp| comp.secure_mode.is_some())
            .map(|comp| {
                // Safety: We checked in the filter for `comp.secure_mode.is_some()`.
                let secure_mode_data = comp.secure_mode.as_ref().unwrap();
                let src = &comp.src;
                let (_, tweak) = CompTweakV1::from_bytes((secure_mode_data.tweak(), 0))?;
                Ok((tweak.pref, src.clone()))
            })
            .collect();
        let ipib = create_ipib(sehdr_src, img_comps_tweak_and_src?)?;
        let mut ipib_comp = Ipib::new(Box::new(Cursor::new(serialize_to_bytes(&ipib)?)));
        self.append_component(&mut ipib_comp)
    }

    /// Prepare Secure Execution header and write it to the output
    fn add_sehdr(&mut self, stage3b_entry: u64, sehdr_args: SeHdrArgs) -> Result<Rc<ImgComponent>> {
        let meta = self.builder.finish()?;

        let mut se_hdr_builder = SeHdrBuilder::new(
            SeHdrVersion::V1,
            PSW {
                addr: sehdr_args.psw_addr.unwrap_or(stage3b_entry),
                mask: Self::DEFAULT_INITIAL_PSW_MASK,
            },
            meta,
        )?;
        se_hdr_builder
            .add_hostkeys(sehdr_args.keys)?
            .with_pcf(sehdr_args.pcf)?
            .with_scf(sehdr_args.scf)?;

        if self.expert_mode {
            se_hdr_builder.i_know_what_i_am_doing();
        }

        if let Some((path, cck)) = &sehdr_args.cck {
            se_hdr_builder
                .with_cck(cck.clone())
                .with_context(|| format!("Failed to use '{}' as the CCK", path.display()))?;
        }

        if let Some((path, prot_key)) = sehdr_args.hdr_aead_key {
            se_hdr_builder
                .with_aead_key(prot_key.clone())
                .with_context(|| {
                    format!(
                        "Failed to use '{}' as the Secure Execution header protection key",
                        path.display()
                    )
                })?;
        }

        let se_hdr_bin = se_hdr_builder.build()?;
        let mut comp: Component =
            SeHdrComp::new(Box::new(Cursor::new(se_hdr_bin.as_bytes()?))).into();

        if let Some(se_hdr_addr) = self.legacy_se_hdr_addr {
            self.insert_nonsecure_component(&mut comp, se_hdr_addr)
        } else {
            self.append_component(&mut comp)
        }
    }

    /// Finish the Secure Execution image - e.g. create Stage3a, Stage3b, Secure
    /// Execution header and so on.
    #[allow(clippy::similar_names)]
    pub fn finish(mut self, sehdr_args: SeHdrArgs) -> Result<Vec<Rc<ImgComponent>>> {
        if (sehdr_args.hdr_aead_key.is_some() || sehdr_args.psw_addr.is_some()) && !self.expert_mode
        {
            return Err(Error::NonExpertMode.into());
        }

        // Create stage3b and write it to the output file
        let psw = PSW {
            addr: S390Kernel::KERNEL_ENTRY,
            mask: Self::DEFAULT_INITIAL_PSW_MASK,
        };
        let stage3b_img_comp = self
            .add_stage3b(psw)
            .context("Failed to prepare stage3b component")?;

        // Create Secure Execution header and write it to the output file
        let sehdr_img_comp = self
            .add_sehdr(stage3b_img_comp.src.start, sehdr_args)
            .context("Failed to prepare Secure Execution header")?;

        // Create and write IPIB to the output file
        let ipib_img_comp = self
            .add_ipib(&sehdr_img_comp.src)
            .context("Failed to prepare IPIB")?;

        // Create and write stage3a to the output file
        let stage3a_img_comp = self
            .add_stage3a(&sehdr_img_comp.src, &ipib_img_comp.src)
            .context("Failed to prepare Stage3a")?;
        assert_eq!(stage3a_img_comp.src.start, STAGE3A_INIT_ENTRY);
        assert_eq!(stage3a_img_comp.src.start + 0x1000, STAGE3A_ENTRY);

        // Create and write short PSW at the beginning of the file
        let _short_psw_img_comp = self.add_short_psw(
            stage3a_img_comp
                .src
                .start
                .checked_add(0x1000)
                .ok_or(Error::UnexpectedOverflow)?,
        )?;

        // Create and write Secure Execution boot image meta data right after the short PSW
        let _metadata_img_comp =
            self.add_metadata(ipib_img_comp.src.start, sehdr_img_comp.src.start)?;

        Ok(self.comps)
    }

    /// Prepare stage3a and write it to file
    fn add_stage3a(
        &mut self,
        se_hdr_src: &Interval,
        ipib_src: &Interval,
    ) -> Result<Rc<ImgComponent>> {
        let stage3a_load_addr = STAGE3A_LOAD_ADDRESS;
        let mut stage3a_comp = render_stage3a(
            self.stage3a.clone(),
            stage3a_load_addr,
            se_hdr_src,
            ipib_src,
        )?;
        self.insert_nonsecure_component(&mut stage3a_comp, stage3a_load_addr)
    }

    /// Prepare short PSW and write it to file
    fn add_short_psw(&mut self, stage3a_entry: u64) -> Result<Rc<ImgComponent>> {
        let short_psw: ShortPsw = PSW {
            addr: stage3a_entry,
            mask: Self::DEFAULT_INITIAL_PSW_MASK,
        }
        .try_into()?;

        let mut short_psw_comp =
            ShortPSWComp::new(Box::new(Cursor::new(serialize_to_bytes(&short_psw)?)));
        self.insert_nonsecure_component(&mut short_psw_comp, ShortPSWComp::OFFSET)
    }

    /// Prepare Secure Execution image metadata and write it to the file
    fn add_metadata(&mut self, ipib_off: u64, hdr_off: u64) -> Result<Rc<ImgComponent>> {
        let mut metadata_comp = ImgMetaData::new(ipib_off, hdr_off)?;

        let metadata_img_comp =
            self.insert_nonsecure_component(&mut metadata_comp, ImgMetaData::OFFSET)?;
        if metadata_img_comp.src.size() > ImgMetaData::MAX_SIZE {
            unreachable!("The metadata should never be larger than the BSS size of stage3a");
        }
        Ok(metadata_img_comp)
    }

    /// Prepare stage3b and write it to file
    fn add_stage3b(&mut self, psw: PSW) -> Result<Rc<ImgComponent>> {
        // Prepare stage3b - for this we must prepare the arguments for it. Since we
        // have the memory layout for the movable components (kernel, cmdline, and
        // initrd) we can do this now.
        let mut stage3b_comp = render_stage3b(self.stage3b.clone(), psw, &self.comps)?;

        let result = self.prepare_and_append_as_secure_component(&mut stage3b_comp, None);
        // No other "regular components can be added now
        self.finalized = true;
        result
    }

    pub(crate) fn set_components_key(
        &mut self,
        key_data: Confidential<Vec<u8>>,
    ) -> pvimg::error::Result<()> {
        self.builder.set_components_key(key_data)
    }
}

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

    use super::SeImgBuilder;
    use crate::se_img::stage3a_path;
    use crate::se_img_comps::stage3b_path;

    #[test]
    fn test_comp_ctx_new() {
        // If the bootloader does not exist, we cannot test.
        if !stage3a_path(None).exists() || !stage3b_path(None).exists() {
            return;
        }

        let encryption = true;
        let mut writer = Cursor::new(Vec::new());
        let ctx_res = SeImgBuilder::new_v1(&mut writer, encryption, None, None);
        assert!(ctx_res.is_ok());
        let ctx = ctx_res.unwrap();

        assert_eq!(ctx.layout.next_addr, 0x13000);
        assert!(ctx.builder.encryption_enabled());
        assert_eq!(ctx.comps, vec![]);
    }
}
