// SPDX-FileCopyrightText: 2021-2023 Heiko Schaefer <heiko@schaefer.name>
// SPDX-License-Identifier: MIT OR Apache-2.0

//! This crate implements the traits [CardBackend] and [CardTransaction].
//! It uses the PCSC middleware to access smart cards.
//!
//! This crate is mainly intended for use by the `openpgp-card` crate.

use std::convert::TryInto;
use std::{
    collections::HashMap,
    ffi::{CStr, CString},
};

use card_backend::{CardBackend, CardCaps, CardTransaction, PinType, SmartcardError};
use iso7816_tlv::simple::Tlv;
use pcsc::Disposition;

const FEATURE_VERIFY_PIN_DIRECT: u8 = 0x06;
const FEATURE_MODIFY_PIN_DIRECT: u8 = 0x07;
const FEATURE_GET_TLV_PROPERTIES: u8 = 0x12;

/// An opened PCSC Card (without open transaction).
/// Note: No application is `select`-ed on the card while setting up a PcscCard object.
///
/// This struct can be used to hold on to a Card, even while no operations
/// are performed on the Card. To perform operations on the card, a
/// [PcscTransaction] object needs to be obtained (via [PcscBackend::transaction]).
pub struct PcscBackend {
    card: pcsc::Card,
    mode: pcsc::ShareMode,
    reader_caps: HashMap<u8, Tlv>,

    // The reader name could be used as a hint about capabilities
    // (e.g. readers that don't support extended length)
    #[allow(dead_code)]
    reader_name: String,
    // FIXME: add a "adjust_card_caps" fn to card-backend? (could replace `max_cmd_len`)
}

/// Boxing helper (for easier consumption of PcscBackend)
impl From<PcscBackend> for Box<dyn CardBackend + Sync + Send> {
    fn from(backend: PcscBackend) -> Box<dyn CardBackend + Sync + Send> {
        Box::new(backend)
    }
}

/// An implementation of the CardTransaction trait that uses the PCSC lite
/// middleware to access the OpenPGP card application on smart cards, via a
/// PCSC "transaction".
///
/// This struct is created from a PcscCard by opening a transaction, using
/// PcscCard::transaction().
///
/// Transactions on a card cannot be opened and left idle
/// (e.g. Microsoft documents that on Windows, they will be closed after
/// 5s without a command:
/// <https://docs.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardbegintransaction?redirectedfrom=MSDN#remarks>)
pub struct PcscTransaction<'b> {
    tx: pcsc::Transaction<'b>,
    reader_caps: HashMap<u8, Tlv>, // FIXME: gets manually cloned
    was_reset: bool,
}

impl<'b> PcscTransaction<'b> {
    /// Start a transaction on `card`.
    ///
    /// If `reselect_application` is set, the application is SELECTed,
    /// if the card reports having been reset.
    fn new(
        card: &'b mut PcscBackend,
        reselect_application: Option<&[u8]>,
    ) -> Result<Self, SmartcardError> {
        log::trace!("Start a transaction");

        let mut was_reset = false;

        let mode = card.mode;
        let reader_caps = card.reader_caps.clone();

        let mut c = &mut card.card;

        loop {
            match c.transaction2() {
                Ok(tx) => {
                    // A pcsc transaction has been successfully started

                    let mut pt = Self {
                        tx,
                        reader_caps,
                        was_reset: false,
                    };

                    if was_reset {
                        log::trace!("Card was reset");

                        pt.was_reset = true;

                        // If the caller expects that an application on the
                        // card has been selected, re-select the application
                        // here.
                        //
                        // When initially opening a card, we don't do this
                        // (freshly opened cards don't have an application
                        // "SELECT"ed).
                        if let Some(app) = reselect_application {
                            log::trace!("Will re-select an application after card reset");

                            let mut res = CardTransaction::select(&mut pt, app)?;
                            log::trace!("select res: {:0x?}", res);

                            // Drop any bytes before the status code.
                            //
                            // e.g. SELECT on Basic Card 3.4 returns:
                            // [6f, 1d,
                            //  62, 15, 84, 10, d2, 76, 0, 1, 24, 1, 3, 4, 0, 5, 0, 0, a8, 35, 0, 0, 8a, 1, 5, 64, 4, 53, 2, c4, 41,
                            //  90, 0]
                            if res.len() > 2 {
                                res.drain(0..res.len() - 2);
                            }

                            if res != [0x90, 0x00] {
                                break Err(SmartcardError::Error(format!(
                                        "Error while attempting to (re-)select {:x?}, status code {:x?}",
                                        app, res
                                    )));
                            }

                            log::trace!("re-select ok");
                        }
                    }

                    break Ok(pt);
                }
                Err((c_, pcsc::Error::ResetCard)) => {
                    // Card was reset, need to reconnect
                    was_reset = true;

                    c = c_;

                    log::trace!("start_tx: do reconnect");

                    c.reconnect(mode, pcsc::Protocols::ANY, Disposition::ResetCard)
                        .map_err(|e| SmartcardError::Error(format!("Reconnect failed: {e:?}")))?;

                    log::trace!("start_tx: reconnected.");

                    // -> try opening a transaction again, in the next loop run
                }
                Err((_, e)) => {
                    log::trace!("start_tx: error {:?}", e);
                    break Err(SmartcardError::Error(format!("Error: {e:?}")));
                }
            };
        }
    }

    /// GET_FEATURE_REQUEST
    /// (see http://pcscworkgroup.com/Download/Specifications/pcsc10_v2.02.09.pdf)
    fn features(&mut self) -> Result<Vec<Tlv>, SmartcardError> {
        let mut recv = vec![0; 1024];

        let cm_ioctl_get_feature_request = pcsc::ctl_code(3400);
        let res = self
            .tx
            .control(cm_ioctl_get_feature_request, &[], &mut recv)
            .map_err(|e| {
                SmartcardError::Error(format!("GET_FEATURE_REQUEST control call failed: {e:?}"))
            })?;

        Ok(Tlv::parse_all(res))
    }

    /// Get the minimum pin length for pin_id.
    fn min_pin_len(&self, pin: PinType) -> u8 {
        match pin {
            PinType::User | PinType::Sign => 6,
            PinType::Admin => 8,
        }
    }

    /// Get the maximum pin length for pin_id.
    fn max_pin_len(
        &self,
        pin: PinType,
        card_caps: &Option<CardCaps>,
    ) -> Result<u8, SmartcardError> {
        if let Some(card_caps) = card_caps {
            match pin {
                PinType::User | PinType::Sign => Ok(card_caps.pw1_max_len()),
                PinType::Admin => Ok(card_caps.pw3_max_len()),
            }
        } else {
            Err(SmartcardError::Error("card_caps is None".into()))
        }
    }

    fn get_control_code(&self, feature_id: u8) -> Result<[u8; 4], SmartcardError> {
        self.reader_caps
            .get(&feature_id)
            .ok_or_else(|| SmartcardError::Error("no reader_capability".into()))?
            .value()
            .try_into()
            .map_err(|e| SmartcardError::Error(format!("unexpected feature data: {e:?}")))
    }

    fn get_reader_tlv_properties(&self) -> Result<Vec<Tlv>, SmartcardError> {
        let ioctl_tlv_props = self.get_control_code(FEATURE_GET_TLV_PROPERTIES)?;

        let mut recv = vec![0xAA; 256];

        let res = self
            .tx
            .control(u32::from_be_bytes(ioctl_tlv_props).into(), &[], &mut recv)
            .map_err(|e: pcsc::Error| SmartcardError::Error(format!("pcsc Error: {e:?}")))?;

        Ok(Tlv::parse_all(res))
    }

    /// Ask the reader what PIN entry lengths it supports
    fn get_reader_pin_max_min_len(&self) -> Result<(Option<u8>, Option<u8>), SmartcardError> {
        const MIN_PINSIZE: u8 = 0x6;
        const MAX_PINSIZE: u8 = 0x7;

        let tlv = self.get_reader_tlv_properties()?;

        let min = tlv
            .iter()
            .find(|&tlv| Into::<u8>::into(tlv.tag()) == MIN_PINSIZE)
            .map(|tlv| tlv.value());
        let max = tlv
            .iter()
            .find(|&tlv| Into::<u8>::into(tlv.tag()) == MAX_PINSIZE)
            .map(|tlv| tlv.value());

        match (min, max) {
            (Some(&[min]), Some(&[max])) => Ok((Some(min), Some(max))),
            _ => Ok((None, None)),
        }
    }

    /// Determine a max PIN entry length for a PIN entry call, based on:
    /// - the reader's reported PIN length (if any)
    /// - the card's max PIN length
    /// - a defensive default
    fn reader_max_pin_len(&self, card_pin_max_size: u8) -> u8 {
        // The card reader doesn't tell us its limit.
        // We fall back to a defensive pin length limit that many readers can handle.
        const DEFAULT_MAX_PIN_LIMIT: u8 = 15;

        let (_reader_min_len, reader_max_len) =
            self.get_reader_pin_max_min_len().unwrap_or((None, None));

        // Decide what maximum PIN length to ask the reader to input.
        if let Some(reader_max_len) = reader_max_len {
            // If the card reader reports a max pin length, rely on that
            u8::min(reader_max_len, card_pin_max_size)
        } else {
            // Otherwise rely on a defensive default max PIN length
            u8::min(DEFAULT_MAX_PIN_LIMIT, card_pin_max_size)
        }
    }
}

impl CardTransaction for PcscTransaction<'_> {
    fn transmit(&mut self, cmd: &[u8], buf_size: usize) -> Result<Vec<u8>, SmartcardError> {
        let mut resp_buffer = vec![0; buf_size];

        let resp = self
            .tx
            .transmit(cmd, &mut resp_buffer)
            .map_err(|e| match e {
                pcsc::Error::NotTransacted => SmartcardError::NotTransacted,
                _ => SmartcardError::Error(format!("Transmit failed: {e:?}")),
            })?;

        Ok(resp.to_vec())
    }

    fn feature_pinpad_verify(&self) -> bool {
        self.reader_caps.contains_key(&FEATURE_VERIFY_PIN_DIRECT)
    }

    fn feature_pinpad_modify(&self) -> bool {
        self.reader_caps.contains_key(&FEATURE_MODIFY_PIN_DIRECT)
    }

    fn pinpad_verify(
        &mut self,
        pin: PinType,
        card_caps: &Option<CardCaps>,
    ) -> Result<Vec<u8>, SmartcardError> {
        log::trace!("pcsc pinpad_verify");

        let card_pin_min_size = self.min_pin_len(pin);
        let card_pin_max_size = self.max_pin_len(pin, card_caps)?;

        log::trace!(
            "pcsc pinpad_verify, card_pin_min_size {card_pin_min_size}, card_pin_max_size {card_pin_max_size} "
        );

        // Default to varlen, for now.
        // (NOTE: Some readers don't support varlen, and need explicit length
        // information. Also see https://wiki.gnupg.org/CardReader/PinpadInput)
        let fixedlen: u8 = 0;

        // APDU: 00 20 00 pin_id <len> (ff)*
        let mut ab_data = vec![
            0x00,     /* CLA */
            0x20,     /* INS: VERIFY */
            0x00,     /* P1 */
            pin.id(), /* P2 */
            fixedlen, /* Lc: 'fixedlen' data bytes */
        ];
        ab_data.extend([0xff].repeat(fixedlen as usize));

        let pin_max_size = self.reader_max_pin_len(card_pin_max_size);

        // PC/SC v2.02.05 Part 10 PIN verification data structure
        let mut send: Vec<u8> = vec![
            // 0 bTimeOut BYTE timeout in seconds (00 means use default
            // timeout)
            0x00,
            // 1 bTimeOut2 BYTE timeout in seconds after first key stroke
            0x00,
            // 2 bmFormatString BYTE formatting options USB_CCID_PIN_FORMAT_xxx
            0x82,
            // 3 bmPINBlockString BYTE
            // bits 7-4 bit size of PIN length in APDU
            // bits 3-0 PIN block size in bytes after justification and formatting
            fixedlen,
            // 4 bmPINLengthFormat BYTE
            // bits 7-5 RFU, bit 4 set if system units are bytes clear if
            // system units are bits,
            // bits 3-0 PIN length position in system units
            0x00,
            // 5 wPINMaxExtraDigit USHORT XXYY, where XX is minimum PIN size
            // in digits, YY is maximum
            pin_max_size,
            card_pin_min_size,
            // 7 bEntryValidationCondition BYTE Conditions under which PIN
            // entry should be considered complete.
            //
            // table for bEntryValidationCondition:
            // 0x01: Max size reached
            // 0x02: Validation key pressed
            // 0x04: Timeout occurred
            0x07,
            // 8 bNumberMessage BYTE Number of messages to display for PIN
            // verification
            0x01,
            // 9 wLangIdU SHORT Language for messages
            0x04,
            0x09, // US english
            // 11 bMsgIndex BYTE Message index (should be 00)
            0x00,
            // 12 bTeoPrologue BYTE[3] T=1 I-block prologue field to use (fill with 00)
            0x00,
            0x00,
            0x00,
        ];

        // 15 ulDataLength ULONG length of Data to be sent to the ICC
        send.extend((ab_data.len() as u32).to_le_bytes());

        // 19 abData BYTE[] Data to send to the ICC
        send.extend(ab_data);

        let mut recv = vec![0xAA; 256];

        let verify_ioctl = self.get_control_code(FEATURE_VERIFY_PIN_DIRECT)?;

        log::trace!(
            "pcsc pinpad_verify verify_ioctl: {:02x?} send {:02x?}",
            verify_ioctl,
            send
        );

        let res = self
            .tx
            .control(u32::from_be_bytes(verify_ioctl).into(), &send, &mut recv)
            .map_err(|e: pcsc::Error| SmartcardError::Error(format!("pcsc Error: {e:?}")))?;

        log::trace!(" <- pcsc pinpad_verify result: {:x?}", res);

        Ok(res.to_vec())
    }

    fn pinpad_modify(
        &mut self,
        pin: PinType,
        card_caps: &Option<CardCaps>,
    ) -> Result<Vec<u8>, SmartcardError> {
        let card_pin_min_size = self.min_pin_len(pin);
        let card_pin_max_size = self.max_pin_len(pin, card_caps)?;

        // Default to varlen, for now.
        // (NOTE: Some readers don't support varlen, and need explicit length
        // information. Also see https://wiki.gnupg.org/CardReader/PinpadInput)
        let fixedlen: u8 = 0;

        // APDU: 00 24 00 pin_id <len> [(ff)* x2]
        let mut ab_data = vec![
            0x00,         /* CLA */
            0x24,         /* INS: CHANGE_REFERENCE_DATA */
            0x00,         /* P1 */
            pin.id(),     /* P2 */
            fixedlen * 2, /* Lc: 'fixedlen' data bytes */
        ];
        ab_data.extend([0xff].repeat(fixedlen as usize * 2));

        let pin_max_size = self.reader_max_pin_len(card_pin_max_size);

        // PC/SC v2.02.05 Part 10 PIN modification data structure
        let mut send: Vec<u8> = vec![
            // 0 bTimeOut BYTE timeout in seconds (00 means use default
            // timeout)
            0x00,
            // 1 bTimeOut2 BYTE timeout in seconds after first key stroke
            0x00,
            // 2 bmFormatString BYTE formatting options USB_CCID_PIN_FORMAT_xxx
            0x82,
            // 3 bmPINBlockString BYTE
            // bits 7-4 bit size of PIN length in APDU
            // bits 3-0 PIN block size in bytes after justification and formatting
            fixedlen,
            // 4 bmPINLengthFormat BYTE
            // bits 7-5 RFU, bit 4 set if system units are bytes clear if
            // system units are bits,
            // bits 3-0 PIN length position in system units
            0x00,
            // 5 bInsertionOffsetOld BYTE Insertion position offset in bytes for
            // the current PIN
            0x00,
            // 6 bInsertionOffsetNew BYTE Insertion position offset in bytes for
            // the new PIN
            fixedlen,
            // 7 wPINMaxExtraDigit USHORT XXYY, where XX is minimum PIN size
            // in digits, YY is maximum
            pin_max_size,
            card_pin_min_size,
            // 9 bConfirmPIN
            0x03, // TODO check?
            // 10 bEntryValidationCondition BYTE Conditions under which PIN
            // entry should be considered complete.
            //
            // table for bEntryValidationCondition:
            // 0x01: Max size reached
            // 0x02: Validation key pressed
            // 0x04: Timeout occurred
            0x07,
            // 11 bNumberMessage BYTE Number of messages to display for PIN
            // verification
            0x03, // TODO check? (match with bConfirmPIN?)
            // 12 wLangId USHORT Language for messages
            0x04,
            0x09, // US english
            // 14 bMsgIndex1-3
            0x00,
            0x01,
            0x02,
            // 17 bTeoPrologue BYTE[3] T=1 I-block prologue field to use (fill with 00)
            0x00,
            0x00,
            0x00,
        ];

        // 15 ulDataLength ULONG length of Data to be sent to the ICC
        send.extend((ab_data.len() as u32).to_le_bytes());

        // 19 abData BYTE[] Data to send to the ICC
        send.extend(ab_data);

        log::trace!("pcsc pinpad_modify send: {:x?}", send);

        let mut recv = vec![0xAA; 256];

        let modify_ioctl = self.get_control_code(FEATURE_MODIFY_PIN_DIRECT)?;

        let res = self
            .tx
            .control(u32::from_be_bytes(modify_ioctl).into(), &send, &mut recv)
            .map_err(|e: pcsc::Error| SmartcardError::Error(format!("pcsc Error: {e:?}")))?;

        log::trace!(" <- pcsc pinpad_modify result: {:x?}", res);

        Ok(res.to_vec())
    }

    fn was_reset(&self) -> bool {
        self.was_reset
    }
}

impl PcscBackend {
    /// Get a card from a specific reader. Typically using `PcscBackend::cards()` or
    /// `PcscBackend::card_backends()` will be simpler if they fit your use case.
    ///
    /// This is slightly safer than `from_pcsc_card` because you don't need to carry
    /// around extra parameters or risk parameter mismatches.
    pub fn from_pcsc_reader(
        context: &pcsc::Context,
        reader: &CStr,
        mode: Option<pcsc::ShareMode>,
    ) -> Result<PcscBackend, SmartcardError> {
        let mode = mode.unwrap_or(pcsc::ShareMode::Shared);

        let card = match context.connect(reader, mode, pcsc::Protocols::ANY) {
            Ok(card) => card,
            Err(pcsc::Error::NoSmartcard) => {
                return Err(SmartcardError::CardNotFound(
                    String::from_utf8_lossy(reader.to_bytes()).to_string(),
                ))
            }
            Err(err) => return Err(SmartcardError::Error(err.to_string())),
        };

        Self::from_pcsc_card(reader, mode, card)
    }

    /// Turn a `pcsc` card into a `PcscBackend`. Typically using `PcscBackend::cards()` or
    /// `PcscBackend::card_backends()` will be simpler if they fit your use case.
    ///
    /// This requires the same `reader_name` and `mode` from when the card was opened.
    pub fn from_pcsc_card(
        reader_name: &CStr,
        mode: pcsc::ShareMode,
        card: pcsc::Card,
    ) -> Result<PcscBackend, SmartcardError> {
        let backend = PcscBackend {
            card,
            mode,
            reader_caps: Default::default(),
            reader_name: String::from_utf8_lossy(reader_name.to_bytes()).to_string(),
        };
        backend.initialize_card()
    }

    /// A list of "raw" opened PCSC Cards and reader names
    /// (No application is selected)
    fn raw_pcsc_cards(mode: pcsc::ShareMode) -> Result<Vec<(pcsc::Card, CString)>, SmartcardError> {
        log::trace!("raw_pcsc_cards start");

        let ctx = match pcsc::Context::establish(pcsc::Scope::User) {
            Ok(ctx) => ctx,
            Err(err) => {
                log::trace!("Context::establish failed: {:?}", err);
                return Err(SmartcardError::ContextError(err.to_string()));
            }
        };

        log::trace!("raw_pcsc_cards got context");

        // List available readers.
        let mut readers_buf = [0; 2048];
        let readers = match ctx.list_readers(&mut readers_buf) {
            Ok(readers) => readers,
            Err(err) => {
                log::trace!("list_readers failed: {:?}", err);
                return Err(SmartcardError::ReaderError(err.to_string()));
            }
        };

        log::trace!(" readers: {:?}", readers);

        let mut cards = vec![];

        // Find a reader with a SmartCard.
        for reader in readers {
            log::trace!("Checking reader: {:?}", reader);

            // Try connecting to card in this reader
            let card = match ctx.connect(reader, mode, pcsc::Protocols::ANY) {
                Ok(card) => card,
                Err(pcsc::Error::NoSmartcard) => {
                    log::trace!("No Smartcard");

                    continue; // try next reader
                }
                Err(err) => {
                    log::warn!("Error connecting to card in reader: {:x?}", err);

                    continue;
                }
            };

            log::trace!("Found card");

            cards.push((card, reader.to_owned()));
        }

        Ok(cards)
    }

    /// Returns an Iterator over Smart Cards that are accessible via PCSC.
    ///
    /// No application is SELECTed on the cards.
    /// You can not assume that any particular application is available on the cards.
    pub fn cards(
        mode: Option<pcsc::ShareMode>,
    ) -> Result<impl Iterator<Item = Result<Self, SmartcardError>>, SmartcardError> {
        let mode = mode.unwrap_or(pcsc::ShareMode::Shared);

        let cards = Self::raw_pcsc_cards(mode)?;

        Ok(cards
            .into_iter()
            .map(move |(card, reader_name)| Self::from_pcsc_card(&reader_name, mode, card)))
    }

    /// Returns an Iterator over Smart Cards that are accessible via PCSC.
    /// Like [Self::cards], but returns the cards as [CardBackend].
    pub fn card_backends(
        mode: Option<pcsc::ShareMode>,
    ) -> Result<
        impl Iterator<Item = Result<Box<dyn CardBackend + Send + Sync>, SmartcardError>>,
        SmartcardError,
    > {
        let cards = PcscBackend::cards(mode)?;

        Ok(cards.map(|c| match c {
            Ok(c) => Ok(Box::new(c) as Box<dyn CardBackend + Send + Sync>),
            Err(e) => Err(e),
        }))
    }

    /// Initialize this PcscBackend (obtains and stores feature lists from reader,
    /// to determine if the reader offers PIN pad functionality).
    fn initialize_card(mut self) -> Result<Self, SmartcardError> {
        log::trace!("pcsc initialize_card");

        let mut h: HashMap<u8, Tlv> = HashMap::default();

        let mut txc = PcscTransaction::new(&mut self, None)?;

        // Get Features from reader (pinpad verify/modify)
        if let Ok(feat) = txc.features() {
            for tlv in feat {
                log::trace!(" Found reader feature {:?}", tlv);
                h.insert(tlv.tag().into(), tlv);
            }
        }

        drop(txc);

        for (a, b) in h {
            self.reader_caps.insert(a, b);
        }

        Ok(self)
    }
}

impl CardBackend for PcscBackend {
    fn limit_card_caps(&self, card_caps: CardCaps) -> CardCaps {
        let mut ext = card_caps.ext_support();

        // Disable "extended length" support when the card reader is known not to support it
        if self.reader_name.starts_with("ACS ACR122U") {
            log::debug!("Disabling ext_support for reader {}", self.reader_name);
            ext = false;
        }

        CardCaps::new(
            ext,
            card_caps.chaining_support(),
            card_caps.max_cmd_bytes(),
            card_caps.max_rsp_bytes(),
            card_caps.pw1_max_len(),
            card_caps.pw3_max_len(),
        )
    }

    /// Get a CardTransaction for this PcscBackend (this starts a transaction)
    fn transaction(
        &mut self,
        reselect_application: Option<&[u8]>,
    ) -> Result<Box<dyn CardTransaction + Send + Sync + '_>, SmartcardError> {
        Ok(Box::new(PcscTransaction::new(self, reselect_application)?))
    }
}
