Rename greet_proto to greetd_ipc, Cargo.toml updates
This commit is contained in:
23
greetd_ipc/Cargo.toml
Normal file
23
greetd_ipc/Cargo.toml
Normal file
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "greetd_ipc"
|
||||
version = "0.5.0"
|
||||
authors = ["Kenny Levinsen"]
|
||||
edition = "2018"
|
||||
license = "GPL-3.0"
|
||||
homepage = "https://kl.wtf/projects/greetd"
|
||||
repository = "https://git.sr.ht/~kennylevinsen/greetd/"
|
||||
description = "An implementation of the greetd IPC protocol"
|
||||
keywords = ["greetd"]
|
||||
|
||||
[features]
|
||||
codec = ["thiserror"]
|
||||
sync-codec = ["codec"]
|
||||
tokio-codec = ["tokio", "async-trait"]
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1.3"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
tokio = { version = "0.2", features = ["io-util"], optional = true }
|
||||
async-trait = { version = "0.1", optional = true }
|
||||
thiserror = { version = "1.0", optional = true }
|
341
greetd_ipc/src/lib.rs
Normal file
341
greetd_ipc/src/lib.rs
Normal file
@@ -0,0 +1,341 @@
|
||||
//! # `greetd` IPC protocol library
|
||||
//!
|
||||
//! This library implements the [greetd](https://git.sr.ht/~kennylevinsen/greetd) IPC protocol.
|
||||
//!
|
||||
//! The library exposes a [Request](enum.Request.html) and a
|
||||
//! [Response](enum.Response.html) enum, representing the valid protocol
|
||||
//! messages, without the length marker.length
|
||||
//!
|
||||
//! Additional types are part of the different request and response values.
|
||||
//!
|
||||
//! See `agreety` for a simple example use of this library.
|
||||
//!
|
||||
//! # Format
|
||||
//!
|
||||
//! The message format is as follows:
|
||||
//!
|
||||
//! ```text
|
||||
//! +----------+-------------------+
|
||||
//! | len: u32 | JSON payload: str |
|
||||
//! +----------+-------------------+
|
||||
//! ```
|
||||
//!
|
||||
//! Length is in native byte-order.
|
||||
//!
|
||||
//! # Request and response types
|
||||
//!
|
||||
//! See [Request](enum.Request.html) and [Response](enum.Response.html) for
|
||||
//! information about the request and response types, as well as their
|
||||
//! serialization.
|
||||
//!
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A request from a greeter to greetd. The request type is internally tagged
|
||||
/// with the"type" field, with the type written in snake_case.
|
||||
///
|
||||
/// Example serialization:
|
||||
///
|
||||
/// ```json
|
||||
/// {
|
||||
/// "type": "create_session",
|
||||
/// "username": "bob"
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[serde(tag = "type")]
|
||||
pub enum Request {
|
||||
/// CreateSession initiates a login attempt for the given user.
|
||||
/// CreateSession returns either a Response::AuthMessage,
|
||||
/// Response::Success or Response::Failure.
|
||||
///
|
||||
/// If an auth message is returned, it should be answered with a
|
||||
/// Request::PostAuthMessageResponse. If a success is returned, the session
|
||||
/// can then be started with Request::StartSession.
|
||||
///
|
||||
/// If a login flow needs to be aborted at any point, send
|
||||
/// Request::CancelSession. Note that the session is cancelled
|
||||
/// automatically on error.
|
||||
CreateSession { username: String },
|
||||
|
||||
/// PostAuthMessageResponse responds to the last auth message, and returns
|
||||
/// either a Response::AuthMessage, Response::Success or Response::Failure.
|
||||
///
|
||||
/// If an auth message is returned, it should be answered with a
|
||||
/// Request::PostAuthMessageResponse. If a success is returned, the session
|
||||
/// can then be started with Request::StartSession.
|
||||
PostAuthMessageResponse { response: Option<String> },
|
||||
|
||||
/// Start a successfully logged in session. This will fail if the session
|
||||
/// has pending messages or has encountered an error.
|
||||
StartSession { cmd: Vec<String> },
|
||||
|
||||
/// Cancel a session. This can only be done if the session has not been
|
||||
/// started. Cancel does not have to be called if an error has been
|
||||
/// encountered in its setup or login flow.
|
||||
CancelSession,
|
||||
}
|
||||
|
||||
/// An error type for Response::Error. Serialized as snake_case.
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ErrorType {
|
||||
/// A generic error. See the error description for more details.
|
||||
Error,
|
||||
|
||||
/// An error caused by failed authentication.
|
||||
AuthError,
|
||||
}
|
||||
|
||||
/// A message type for a Response::AuthMessage. Serialized as snake_case.
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum AuthMessageType {
|
||||
/// A question whose answer should be visible during input.
|
||||
Visible,
|
||||
|
||||
/// A question whose answer should be kept secret during input.
|
||||
Secret,
|
||||
|
||||
/// An information message.
|
||||
Info,
|
||||
|
||||
/// An error message.
|
||||
Error,
|
||||
}
|
||||
|
||||
/// A response from greetd to a greeter. The request type is internally tagged
|
||||
/// with the"type" field, with the type written in snake_case.
|
||||
///
|
||||
/// Example serialization:
|
||||
///
|
||||
/// ```json
|
||||
/// {
|
||||
/// "type": "auth_message",
|
||||
/// "message": "Password:",
|
||||
/// "message_type": "secret"
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(tag = "type")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Response {
|
||||
/// The request was successful.
|
||||
Success,
|
||||
|
||||
/// The request failed. See the type and/or description for more
|
||||
/// information about this failure.
|
||||
Error {
|
||||
error_type: ErrorType,
|
||||
description: String,
|
||||
},
|
||||
|
||||
/// An authentication message needs to be answered to continue through the
|
||||
/// authentication flow.
|
||||
///
|
||||
/// An authentication message can consist of anything. While it will
|
||||
/// commonly just be a request for the users' password, it could also ask
|
||||
/// for TOTP codes, or whether or not you felt sad when Littlefoot's mother
|
||||
/// died in the original "Land Before Time". It is therefore important that
|
||||
/// no assumptions are made about the questions that will be asked, and
|
||||
/// attempts to automatically answer these questions should not be made.
|
||||
AuthMessage {
|
||||
auth_message_type: AuthMessageType,
|
||||
auth_message: String,
|
||||
},
|
||||
}
|
||||
|
||||
/// Reader/writer codecs for Request/Response.
|
||||
///
|
||||
/// This is implemented in the form of two traits, SyncCodec and TokioCodec,
|
||||
/// which operate on the `std` and `tokio` implementations of reader/writer
|
||||
/// traits. The former is intended as the name suggests for synchronous
|
||||
/// operation, while the latter is for asynchronous operation when using tokio.
|
||||
///
|
||||
/// These codecs are hidden behind the `sync-codec` and `tokio-codec` features,
|
||||
/// respectively. These features also implicitly enable the `codec` feature,
|
||||
/// which controls the entire `codec` module.
|
||||
///
|
||||
#[cfg(feature = "codec")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "codec")))]
|
||||
pub mod codec {
|
||||
use thiserror::Error as ThisError;
|
||||
|
||||
#[derive(Debug, ThisError)]
|
||||
pub enum Error {
|
||||
#[error("serialization error: {0}")]
|
||||
Serialization(String),
|
||||
#[error("i/o error: {0}")]
|
||||
Io(String),
|
||||
#[error("EOF")]
|
||||
Eof,
|
||||
}
|
||||
|
||||
impl From<serde_json::error::Error> for Error {
|
||||
fn from(error: serde_json::error::Error) -> Self {
|
||||
Error::Serialization(error.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for Error {
|
||||
fn from(error: std::io::Error) -> Self {
|
||||
Error::Io(format!("{}", error))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sync-codec")]
|
||||
mod sync_codec {
|
||||
use crate::{codec::Error, Request, Response};
|
||||
use std::io::{Read, Write};
|
||||
|
||||
/// Reader/writer implementation over std::io::{Read,Write}.
|
||||
pub trait SyncCodec {
|
||||
fn read_from<T: Read>(stream: &mut T) -> Result<Self, Error>
|
||||
where
|
||||
Self: std::marker::Sized;
|
||||
fn write_to<T: Write>(&self, stream: &mut T) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
impl SyncCodec for Request {
|
||||
fn read_from<T: Read>(stream: &mut T) -> Result<Self, Error> {
|
||||
let mut len_bytes = [0; 4];
|
||||
stream
|
||||
.read_exact(&mut len_bytes)
|
||||
.map_err(|e| match e.kind() {
|
||||
std::io::ErrorKind::UnexpectedEof => Error::Eof,
|
||||
_ => e.into(),
|
||||
})?;
|
||||
let len = u32::from_ne_bytes(len_bytes);
|
||||
|
||||
let mut resp_buf = vec![0; len as usize];
|
||||
stream.read_exact(&mut resp_buf)?;
|
||||
serde_json::from_slice(&resp_buf).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn write_to<T: Write>(&self, stream: &mut T) -> Result<(), Error> {
|
||||
let body_bytes = serde_json::to_vec(self)?;
|
||||
let len_bytes = (body_bytes.len() as u32).to_ne_bytes();
|
||||
stream.write_all(&len_bytes)?;
|
||||
stream.write_all(&body_bytes)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl SyncCodec for Response {
|
||||
fn read_from<T: Read>(stream: &mut T) -> Result<Self, Error> {
|
||||
let mut len_bytes = [0; 4];
|
||||
stream
|
||||
.read_exact(&mut len_bytes)
|
||||
.map_err(|e| match e.kind() {
|
||||
std::io::ErrorKind::UnexpectedEof => Error::Eof,
|
||||
_ => e.into(),
|
||||
})?;
|
||||
let len = u32::from_ne_bytes(len_bytes);
|
||||
|
||||
let mut resp_buf = vec![0; len as usize];
|
||||
stream.read_exact(&mut resp_buf)?;
|
||||
serde_json::from_slice(&resp_buf).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn write_to<T: Write>(&self, stream: &mut T) -> Result<(), Error> {
|
||||
let body_bytes = serde_json::to_vec(self)?;
|
||||
let len_bytes = (body_bytes.len() as u32).to_ne_bytes();
|
||||
stream.write_all(&len_bytes)?;
|
||||
stream.write_all(&body_bytes)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "sync-codec")]
|
||||
pub use sync_codec::SyncCodec;
|
||||
|
||||
#[cfg(feature = "tokio-codec")]
|
||||
mod tokio_codec {
|
||||
use crate::{codec::Error, Request, Response};
|
||||
use async_trait::async_trait;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
|
||||
/// Reader/writer implementation over tokio::io::{AsyncReadExt, AsyncWriteExt}.
|
||||
#[async_trait]
|
||||
pub trait TokioCodec {
|
||||
async fn read_from<T: AsyncReadExt + std::marker::Unpin + Send>(
|
||||
stream: &mut T,
|
||||
) -> Result<Self, Error>
|
||||
where
|
||||
Self: std::marker::Sized;
|
||||
async fn write_to<T: AsyncWriteExt + std::marker::Unpin + Send>(
|
||||
&self,
|
||||
stream: &mut T,
|
||||
) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl TokioCodec for Request {
|
||||
async fn read_from<T: AsyncReadExt + std::marker::Unpin + Send>(
|
||||
stream: &mut T,
|
||||
) -> Result<Self, Error> {
|
||||
let mut len_bytes = [0; 4];
|
||||
stream
|
||||
.read_exact(&mut len_bytes)
|
||||
.await
|
||||
.map_err(|e| match e.kind() {
|
||||
std::io::ErrorKind::UnexpectedEof => Error::Eof,
|
||||
_ => e.into(),
|
||||
})?;
|
||||
let len = u32::from_ne_bytes(len_bytes);
|
||||
|
||||
let mut body_bytes = vec![0; len as usize];
|
||||
stream.read_exact(&mut body_bytes).await?;
|
||||
let body = serde_json::from_slice(&body_bytes)?;
|
||||
Ok(body)
|
||||
}
|
||||
|
||||
async fn write_to<T: AsyncWriteExt + std::marker::Unpin + Send>(
|
||||
&self,
|
||||
stream: &mut T,
|
||||
) -> Result<(), Error> {
|
||||
let body_bytes = serde_json::to_vec(self)?;
|
||||
let len_bytes = (body_bytes.len() as u32).to_ne_bytes();
|
||||
stream.write_all(&len_bytes).await?;
|
||||
stream.write_all(&body_bytes).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl TokioCodec for Response {
|
||||
async fn read_from<T: AsyncReadExt + std::marker::Unpin + Send>(
|
||||
stream: &mut T,
|
||||
) -> Result<Self, Error> {
|
||||
let mut len_bytes = [0; 4];
|
||||
stream
|
||||
.read_exact(&mut len_bytes)
|
||||
.await
|
||||
.map_err(|e| match e.kind() {
|
||||
std::io::ErrorKind::UnexpectedEof => Error::Eof,
|
||||
_ => e.into(),
|
||||
})?;
|
||||
let len = u32::from_ne_bytes(len_bytes);
|
||||
|
||||
let mut body_bytes = vec![0; len as usize];
|
||||
stream.read_exact(&mut body_bytes).await?;
|
||||
let body = serde_json::from_slice(&body_bytes)?;
|
||||
Ok(body)
|
||||
}
|
||||
|
||||
async fn write_to<T: AsyncWriteExt + std::marker::Unpin + Send>(
|
||||
&self,
|
||||
stream: &mut T,
|
||||
) -> Result<(), Error> {
|
||||
let body_bytes = serde_json::to_vec(self)?;
|
||||
let len_bytes = (body_bytes.len() as u32).to_ne_bytes();
|
||||
stream.write_all(&len_bytes).await?;
|
||||
stream.write_all(&body_bytes).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "tokio-codec")]
|
||||
pub use tokio_codec::TokioCodec;
|
||||
}
|
Reference in New Issue
Block a user