KeyBoxen 0.1.0

Standalone secret-service daemon for window managers
// Copyright (C) 2022 KeyBoxen Authors
// SPDX-License-Identifier: GPL-3.0-or-later

//! Pinentry to KeyBoxen communication (Assuan replies)
//!
//! Receiving replies from the assuan server (pinentry) involves a lot
//! of waiting and processing of received data. The reply handling is
//! therefore carried out as a separate async task.

use super::{ErrorKind, Response, Result};
use pest::iterators::Pairs;
use pest::Parser;
use tokio::{
    io::{AsyncBufReadExt, BufReader},
    process::ChildStdout,
    sync::mpsc::Sender,
};

/// Decoder for responses
///
/// This struct is primarily a buffer for incoming 'D' data line until
/// the data stream is completed by an OK or an ERR.
struct Decoder(Vec<u8>);

/// Parser for Assuan lines
///
/// The struct and `Rules` enum are automatically implemented by pest
/// using the PEG grammar defined in a pest file (`responses.pest`).
#[derive(pest_derive::Parser)]
#[grammar = "pinentry/responses.pest"]
struct LineParser;

/// Pinentry receive loop
///
/// This function is executed as a separate async task to listen for
/// responses from the pinentry program and forward the decoded
/// responses to the [controller][super::controller] task. This is the
/// only interface for the receiver loop.
///
/// # Arguments
/// * `stream`: The stdout of the pinentry process
/// * `channel`: The sender side of the channel that is used to send
/// the responses to the controller
pub(super) async fn listen(stream: ChildStdout, channel: Sender<Response>) -> Result<()> {
    let mut reader = BufReader::new(stream).lines();
    let mut decoder = Decoder::new();
    loop {
        // Return when channel tx is dropped or closed
        if channel.is_closed() {
            println!("🛈 Pinentry: Response channel closed");
            break;
        }

        if let Some(line) = reader.next_line().await? {
            let response = decoder.decode(line)?;
            channel.send(response).await?;
        }
    }
    Ok(())
}

/// Decoder implementation
///
/// The decoder implementation consists of a single
/// [`decode`][Decoder::decode] method and several additional
/// specialized decoder methods. The `decode` method delegates the job
/// of decoding each variant to a specific method below it.
impl Decoder {
    fn new() -> Self {
        Self(Vec::new())
    }

    /// Decoder function for every response line
    fn decode(&mut self, line: String) -> Result<Response> {
        let parsed_line = LineParser::parse(Rule::Response, &line)?
            .next()
            .unwrap()
            .into_inner()
            .next()
            .ok_or(ErrorKind::EmptyResponse)?;
        let fields = parsed_line.clone().into_inner();
        Ok(match parsed_line.as_rule() {
            Rule::Ok => self.ok(fields),
            Rule::Error => self.error(fields),
            Rule::Data => self.data(fields),
            Rule::Comment => Self::comment(fields),
            Rule::Status => Self::status(fields),
            Rule::Enquiry => Self::enquiry(fields),
            _ => Err(ErrorKind::UnexpectedResponse(line.clone()))?,
        })
    }

    fn ok(&mut self, fields: Pairs<Rule>) -> Response {
        let msg = fields.tail();
        if self.0.len() == 0 {
            Response::Success(msg)
        } else {
            let resp = Response::DataOK(self.0.clone(), msg);
            self.0 = Vec::new();
            resp
        }
    }

    fn error(&mut self, mut fields: Pairs<Rule>) -> Response {
        let erc = fields.next().unwrap().as_str().parse().unwrap();
        let msg = fields.tail();
        if self.0.len() == 0 {
            Response::Failure(erc, msg)
        } else {
            let resp = Response::DataFail(self.0.clone(), erc, msg);
            self.0 = Vec::new();
            resp
        }
    }

    fn data(&mut self, fields: Pairs<Rule>) -> Response {
        self.0.append(&mut fields.tail().into_bytes());
        Response::DataBuffered
    }

    fn comment(fields: Pairs<Rule>) -> Response {
        Response::Comment(fields.tail())
    }

    fn status(mut fields: Pairs<Rule>) -> Response {
        let keyword = fields.next().unwrap().as_str().to_string();
        let msg = fields.tail();
        Response::Status(keyword, msg)
    }

    fn enquiry(mut fields: Pairs<Rule>) -> Response {
        let keyword = fields.next().unwrap().as_str().to_string();
        let params = fields.tail();
        Response::Enquiry(keyword, params)
    }
}

/// Tail message conversion
///
/// This trait enables the extraction of the tail portion of a
/// message- which may optionally contain a string payload. It also
/// creates a convenient method syntax.
trait ConvertTail {
    fn tail(self) -> String;
}

impl<'i> ConvertTail for Pairs<'i, Rule> {
    fn tail(mut self) -> String {
        self.next().map_or(format!(""), |p| p.as_str().to_string())
    }
}