...
 
Commits (55)
eval "$(lorri direnv)"
This diff is collapsed.
......@@ -18,7 +18,9 @@ The project is currently being re-written for a more modular and portable
approach. The new Release will be qaul.net 2.0. Please check
our milestones & issues to get an idea of the development plan and
status.
If you want to get involved, please [get in touch]()!
If you want to get involved, please [get in touch][contributors-guide]!
[contributors-guide]: /contributors/social/_intro.html
For the latest stable release, check the [`release-1.0.0`][release] branch.
......@@ -39,9 +41,12 @@ qaul.net 2.0 will have the following features:
## Build Instructions
The project is being re-written in Rust, thus using [cargo][cargo] as a build system.
The project is being re-written in Rust, thus using [cargo][] as a build system.
If you don't have Rust installed, you can get it [here](https://rustup.sh) or via your OS.
[cargo]: https://crates.io/
## Documentation
Documentation is avaliable [here](https://docs.qaul.net).
......@@ -49,8 +54,8 @@ Documentation is avaliable [here](https://docs.qaul.net).
## License
qaul.net is free open source software licensed under the
[GNU General Public License version 3](licenses/gpl-3.0.md).
qaul.net is free and open source software licensed under the
[GNU Affero General Public License version 3 or later](licenses/gpl-3.0.md).
To see all external code's licenses used in this project please
visit the [`licenses` directory](licenses).
......@@ -7,6 +7,8 @@ license = "GPL-3.0"
[dependencies]
libqaul = { path = "../../libqaul" }
messaging = { path = "../../libqaul/service/messaging", package = "qaul-messaging" }
ratman = { path = "../../ratman" }
ratman-netmod = { path = "../../ratman/netmod" }
netmod-mem = { path = "../../netmod-mem" }
\ No newline at end of file
use std::sync::Arc;
use {
libqaul::Qaul,
libqaul::{messages::Recipient, Qaul},
messaging::{Messaging, TextPayload},
netmod_mem::MemMod,
ratman::{netmod::Endpoint, Router, Identity},
ratman::{netmod::Endpoint, Router},
};
// This function implements a very simple message send and
// bootstrapping procedure. It is heavily documented to be useful as
// an onboarding device.
fn main() {
// Create virtual network with two devices
let mut mm1 = MemMod::new();
let mut mm2 = MemMod::new();
let mut mm3 = MemMod::new();
let mut mm4 = MemMod::new();
// Link the two devices together
mm1.link(&mut mm2);
mm3.link(&mut mm4);
// Print the sizehint for good measure to see how large a Message
// we can send through this endpoint
println!("mm1.sizehint() = {} bytes", mm1.size_hint());
// Create two routers. These hold routing state and journals and
// Create three routers. These hold routing state and journals and
// are responsible for routing packets through the network
let r1 = Router::new();
let r2 = Router::new();
let r3 = Router::new();
// Add the endpoints to their respective routers
r1.modify().add_ep(mm1);
r2.modify().add_ep(mm2);
r2.modify().add_ep(mm3);
r3.modify().add_ep(mm4);
// Generate two network IDs we will use instead of real user profiles
let id1 = Identity::with_digest(&vec![1]);
let id2 = Identity::with_digest(&vec![2]);
// While `libqaul` can't add users to the routing scope yet, we
// need to now create Qaul structures so we can create users
let q1 = Arc::new(Qaul::new(r1));
let q2 = Qaul::new(r2);
let q3 = Qaul::new(r3);
// This step is a hack because we don't have actual discovery
// Messages yet. It will be replaced soon though!
r1.modify().discover(id2.clone());
r1.modify().local(id1.clone());
// Generate two user profiles on node 1 and 3
let u1 = q1.users().create("abc").unwrap();
let u2 = q3.users().create("abc").unwrap();
// Teach Router 2 about ID1 and it's own local ID
r2.modify().discover(id1.clone());
r2.modify().local(id2.clone());
// Manually make Routers discover each other
#[allow(deprecated)]
{
q1.router().discover(u2.0, 0);
q2.router().discover(u1.0, 0);
q2.router().discover(u2.0, 1);
q3.router().discover(u1.0, 0);
}
// Initialise two Qaul instances with their respective Routers.
// At this point it's important that the routers were previously
// initialised. Changes _can_ be made, but only from inside libqaul,
// i.e. via some configuration service NOT
let q1 = Qaul::new(r1);
let q2 = Qaul::new(r2);
// We setup a messaging endpoint listener on node u2
let recv = Messaging::new(Arc::clone(&q3));
recv.listen(u2.clone(), |msg| {
dbg!(msg);
Ok(())
}).unwrap();
// Send a test message from id1 to id2 that says "hello world"
q1.send_test_message(id1, id2);
// Then we setup a messaging endponti on note u1 and send a message to u2
let msg = Messaging::new(Arc::clone(&q1));
msg.send(
u1,
Recipient::User(u2.0),
TextPayload {
text: "Hello, world!".into(),
},
)
.unwrap();
// This delay is required to make the main thread wait enough time
// for the exchange to complete. In a real app this is not a
......
......@@ -7,7 +7,7 @@
## Guides
* `contributers`: the contributers guide introduces and explains the structure of qaul.net and should guide you through the steps of how to the project.
* `contributors`: the contributors' guide introduces and explains the structure of qaul.net and should guide you through the steps of how to the project.
* `http-api`: contains the documentation of the qaul.net http-api.
* `index`: this folder contains the overview page.
......
# qaul.net Contributers Guide
# qaul.net Contributors' Guide
This guide should always be readable in it's latest version online:
https://docs.qaul.net
......@@ -46,4 +46,4 @@ To build the HTML version of this guide, run the following script:
./build.sh
```
You can find the finished book in the directory `book`.
\ No newline at end of file
You can find the finished book in the directory `book`.
......@@ -4,4 +4,4 @@
./build.sh
# upload book to web server
rsync -azhe "ssh -p 2222" ./book/ admin@docs.qaul.net:/home/admin/contributers
rsync -azhe "ssh -p 2222" ./book/ admin@docs.qaul.net:/home/admin/contributors
# Introduction
Welcome to the `qaul.net` contributers guide!
Welcome to the `qaul.net` contributors' guide!
This guide is open and publicly editable. You can find it's sources
in our [gitlab repository].
If you find any section of this guide lacking, don't hesitate to update this guide, [open a PR] or [send us a patch]!
[gitlab repository]: https://git.open-communication.net/qaul/qaul.net/docs/contributers/
[gitlab repository]: https://git.open-communication.net/qaul/qaul.net/docs/contributors/
[open a PR]: /social/contributions.html#submitting-a-pr
[send us a patch]: /social/contributions.html#submitting-an-e-mail-patch
......@@ -4,7 +4,7 @@ qaul.net runs many different web services:
* [qaul.net](https://qaul.net) the qaul.net web site
* [docs.qaul.net](https://docs.qaul.net) the qaul.net documentations
* [contributers guide](https://docs.qaul.net/contributers) (this document)
* [contributors' guide](https://docs.qaul.net/contributors) (this document)
* [http-api](https://docs.qaul.net/http-api) qaul.net REST API guide
* [Rust documentaiion](https://docs.qaul.net/api) the qaul.net rust software API documentation (automatically created from the qaul.net code sources)
* [get.qaul.net](https://get.qaul.net) the qaul.net download directory for the qaul.net binaries and big content files (e.g. videos, etc.).
......
......@@ -5,8 +5,8 @@ cd index
./deploy.sh
cd ..
# build and upload the contributers guide
cd contributers
# build and upload the contributors' guide
cd contributors
./deploy.sh
cd ..
......
......@@ -7,15 +7,21 @@ license = "AGPL-3.0"
edition = "2018"
[dependencies]
platform = { path = "platform", package = "qaul-platform" }
identity = { path = "../ratman/identity", package = "ratman-identity", features = ["digest"] }
alexandria = { git = "https://git.open-communication.net/qaul/alexandria" }
platform = { path = "platform", package = "qaul-platform" }
ratman = { path = "../ratman" }
async-std = "0.99"
rand = "0.7"
async-std = "1.0"
alexandria = { git = "https://git.open-communication.net/qaul/alexandria" }
conjoiner = { version = "1.2", package = "conjoiner-engine" }
serde = { version = "1.0", features = [ "derive" ] }
crossbeam-channel = "0.4"
base64 = "0.10"
blake2 = "0.8.0"
mime = "0.3"
rand = "0.7"
[features]
default = ["generate-message"]
generate-message = []
......@@ -50,7 +50,7 @@ impl AuthError {
fn into_error(&self) -> (Error, Status) {
let status = match self {
AuthError::QaulError(QaulError::NotAuthorised) => Status::Unauthorized,
AuthError::QaulError(QaulError::UnknownUser) => Status::NotFound,
AuthError::QaulError(QaulError::NoUser) => Status::NotFound,
AuthError::QaulError(QaulError::CallbackTimeout) => Status::InternalServerError,
AuthError::NotLoggedIn => Status::Unauthorized,
_ => Status::BadRequest,
......@@ -63,7 +63,7 @@ impl AuthError {
AuthError::NoAttributes => Some("No Attributes".into()),
AuthError::InvalidIdentity(_) => Some("Invalid identity".into()),
AuthError::QaulError(QaulError::NotAuthorised) => Some("Not Authorized".into()),
AuthError::QaulError(QaulError::UnknownUser) => Some("Unknown User".into()),
AuthError::QaulError(QaulError::NoUser) => Some("Unknown User".into()),
AuthError::QaulError(QaulError::InvalidQuery) => Some("Invalid Query".into()),
AuthError::QaulError(QaulError::InvalidPayload) => Some("Invalid Payload".into()),
AuthError::QaulError(QaulError::CallbackTimeout) => None,
......@@ -85,7 +85,7 @@ impl AuthError {
Some(format!("Failed to decode identity, decoded identity is {} bytes long when it should be {}", l, ID_LEN)),
AuthError::QaulError(QaulError::NotAuthorised) =>
Some("Current user is not authorised to perform this action".into()),
AuthError::QaulError(QaulError::UnknownUser) =>
AuthError::QaulError(QaulError::NoUser) =>
Some("Target user is not known to Qaul".into()),
AuthError::QaulError(QaulError::InvalidQuery) => None,
AuthError::QaulError(QaulError::InvalidPayload) =>
......
......@@ -7,3 +7,4 @@
//! Depending on how important this is, this crate might either be
//! a shim crate, or be removed again in the future because it's redundant.
#![allow(unused)]
//! `qaul.net` filesharing service
// //! `qaul.net` filesharing service
// #![allow(unused)]
use qaul::{Qaul, QaulResult, UserAuth};
use identity::Identity;
pub use mime::Mime;
// use qaul::{Qaul, QaulResult, UserAuth};
// use identity::Identity;
// pub use mime::Mime;
/// A typed file that can be sent across the network
pub struct File {
pub name: String,
pub mime: Mime,
pub data: Vec<u8>,
}
// /// A typed file that can be sent across the network
// pub struct File {
// pub name: String,
// pub mime: Mime,
// pub data: Vec<u8>,
// }
// TODO: Partial files/ file progress
// TODO: Download links with tokens
// // TODO: Partial files/ file progress
// // TODO: Download links with tokens
/// Filesharing service state
pub struct Filesharing<'q> {
qaul: &'q Qaul,
}
// /// Filesharing service state
// pub struct Filesharing<'q> {
// qaul: &'q Qaul,
// }
impl<'q> Filesharing<'q> {
/// Send a single file to a group of people
pub fn send_file(
&self,
user: UserAuth,
recipients: Vec<Identity>,
file: File,
) -> QaulResult<()> {
unimplemented!()
}
// impl<'q> Filesharing<'q> {
// /// Send a single file to a group of people
// pub fn send_file(
// &self,
// user: UserAuth,
// recipients: Vec<Identity>,
// file: File,
// ) -> QaulResult<()> {
// unimplemented!()
// }
/// Get all files that were received since the last poll
pub fn poll_files(&self, user: UserAuth) -> QaulResult<Vec<File>> {
unimplemented!()
}
}
// /// Get all files that were received since the last poll
// pub fn poll_files(&self, user: UserAuth) -> QaulResult<Vec<File>> {
// unimplemented!()
// }
// }
......@@ -8,6 +8,7 @@ license = "AGPL-3.0"
[dependencies]
qaul = { path = "../../", package = "libqaul" }
files = { path = "../files", package = "qaul-files" }
identity = { path = "../../../ratman/identity", package = "ratman-identity" }
serde = { version = "1.0", features = ["derive"] }
\ No newline at end of file
serde = { version = "1.0", features = ["derive"] }
conjoiner = { version = "1.2", package = "conjoiner-engine" }
......@@ -6,22 +6,64 @@
//! It's basically decentralised e-mail.
//! It's basically e-mail.
use files::File;
use identity::Identity;
use qaul::{Qaul, QaulResult, Recipient, UserAuth};
use serde::{Serialize, Deserialize};
use std::sync::Arc;
use conjoiner;
use qaul::{
error::{Error, Result},
messages::{Message, MsgRef, MsgId, Recipient, SigTrust},
users::UserAuth,
Identity, Qaul,
};
use serde::{Deserialize, Serialize};
use std::{convert::TryFrom, sync::Arc};
const ASC_NAME: &'static str = "qaul-messaging";
/// A list of file-attachments
pub type Attachments = Vec<File>;
// A list of file-attachments
// pub type Attachments = Vec<File>;
/// A plain-text message with optional attachments
#[derive(Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TextPayload {
pub text: String,
}
/// An incoming text message, with optional file attachments
///
/// This abstraction is a simplification of the `libqaul` internal
/// `Message` abstraction, and is not stored internally in this
/// form. Generally, when querying from the service API, the return
/// value needs to be mapped into this type.
#[derive(Debug, Clone)]
pub struct TextMessage {
text: String,
// attachments: Option<Attachments>,
pub id: MsgId,
pub sender: Identity,
pub recipient: Recipient,
pub sign: SigTrust,
pub payload: TextPayload,
}
impl TryFrom<MsgRef> for TextMessage {
type Error = Error;
/// Map from a `Message::In` into a `TextMessage`
fn try_from(msg: MsgRef) -> Result<Self> {
let Message {
ref id,
ref sender,
ref recipient,
ref sign,
ref payload,
associator: _,
} = msg.as_ref();
Ok(Self {
id: id.clone(),
sender: sender.clone(),
recipient: recipient.clone(),
sign: sign.clone(),
payload: conjoiner::deserialise(&payload)?,
})
}
}
/// Messaging service state
......@@ -39,7 +81,9 @@ impl Messaging {
/// but also check for the existence of a `filesharing` service.
/// Depending on this check, the `async_files` capability
/// can be set.
pub fn init(qaul: Arc<Qaul>) -> Self {
pub fn new(qaul: Arc<Qaul>) -> Self {
qaul.services().register(ASC_NAME).unwrap();
Self {
async_files: false,
qaul,
......@@ -57,21 +101,30 @@ impl Messaging {
/// `Message`, signs it and optionally encrypts it, if it's
/// `recipient` isn't `Recipient::Flood`, then queues it in the
/// routing layer.
pub fn send(&self, user: UserAuth, recipient: Recipient, msg: TextMessage) -> QaulResult<()> {
// self.qaul.message_send(user, recipient, ASC_NAME,
unimplemented!()
pub fn send(&self, user: UserAuth, recipient: Recipient, payload: TextPayload) -> Result<()> {
self.qaul
.messages()
.send(user, recipient, ASC_NAME, conjoiner::serialise(&payload)?)
}
/// Non-blockingly poll for new `TextMessage`s for a session
pub fn poll(&self, user: UserAuth) -> QaulResult<Vec<TextMessage>> {
unimplemented!()
pub fn poll(&self, user: UserAuth) -> Result<TextMessage> {
self.qaul
.messages()
.poll(user, ASC_NAME)
.map(|msg| TextMessage::try_from(msg))?
}
/// Setup a `TextMessage` listener for a specific user session
pub fn listen<F>(&self, user: UserAuth, listener: F) -> QaulResult<()>
pub fn listen<F: 'static + Send + Sync>(&self, user: UserAuth, listener: F) -> Result<()>
where
F: Fn(TextMessage) -> QaulResult<()>,
F: Fn(TextMessage) -> Result<()>,
{
unimplemented!()
self.qaul.messages().listen(user, ASC_NAME, move |msg| {
match TextMessage::try_from(msg) {
Ok(text) => listener(text),
Err(e) => return Err(e),
}
})
}
}
//! Service API contact book
//!
//! A user can store information about other users and query this
//! information later. The contact book is zoned per-user. Ideally it
//! would be encrypted (not for now).
use super::models::{QaulResult, UserAuth};
use crate::{ContactData, ContactList, ContactQuery, ContactStore, Qaul, UserProfile};
impl Qaul {
/// Add a new contact to a user's known contacts
pub fn contacts_add(&self, user: UserAuth, contact: UserProfile) -> QaulResult<()> {
let (ref my_id, ref token) = user.trusted()?;
self.auth.verify_token(my_id, token)?;
self.contacts.modify(&my_id, &contact.id, |mut c| {});
use crate::{
error::Result,
users::{UserAuth, UserProfile},
Identity, Qaul,
};
/// A user-local set of contact metadata in their contact book
///
/// All fields in this structure are entirely optional and can not be
/// relied on. They are additional points of data, that a user can
/// specify about another user, that are not available or shared with
/// the network. This is meant to allow users to curate a list of
/// trusted contacts, or build friend circles.
#[derive(Default, Debug, Clone)]
pub struct ContactEntry {
/// The name by which the associated contact is known by the owning user.
pub nick: Option<String>,
/// Set a user trust level
pub trust: i8,
/// The user has met this person
pub met: bool,
/// A free text location
pub location: Option<String>,
/// A general plain text notes section
pub notes: Option<String>,
}
/// Query structure to find contacts by
///
/// A query is always applied to a field that is present in
/// `ContactEntry`, and will filter contacts by what set of
/// prerequisites they fulfill.
pub enum ContactQuery<'a> {
/// A fuzzy nickname search
Nick(&'a str),
/// A fuzzy trust level search
Trust { val: i8, fuz: i8 },
/// Filter by physical meeting
Met(bool),
/// A fuzzy location string search
Location(&'a str),
/// A fuzzy notes string search
Notes(&'a str),
}
/// API scope type to access contact book functions
///
/// The contact book is a user local store of metadata, that can be
/// assigned for each `Identity`, that a user is aware of on the
/// network. A contact entry is backed by an entry in the user store,
/// available via the `users()` endpoint scope.
///
/// A `ContactEntry` should be considered additional information a
/// user can keep on someone they interact with on the network,
/// independent of the `UserProfile`, which is fetched from the remote
/// user themselves.
///
/// When assembling a complete view of a user, it's important to
/// consider both their primary profile, as well as the contact
/// metadata stored via this API. Because of this, queries only return
/// the `ContactEntry` structures, not the profile itself.
///
/// Furthermore, it is possible to query users via metadata set in a
/// user's local contact book, such as their nick, trust, location and
/// more.
pub struct Contacts<'chain> {
pub(crate) q: &'chain Qaul,
}
impl<'qaul> Contacts<'qaul> {
/// Drop this scope and return back to global `Qaul` scope
pub fn drop(&'qaul self) -> &'qaul Qaul {
self.q
}
/// Modify a user's contact entry in a user-local contact book
///
/// The `modify` lambda allows a user to add personal metadata for
/// a contact, such as a nickname, or trust levels. Each contact
/// list is user local and it's not possible to access other
/// user's contact metadata.
///
/// If no contact entry existed before, a fresh one will be
/// created before calling the passed-in lambda.
pub fn modify<F>(&self, user: UserAuth, contact: &Identity, modify: F) -> Result<()>
where
F: Fn(&mut ContactEntry),
{
let (ref id, _) = self.q.auth.trusted(user)?;
self.q.contacts.modify(id, contact, modify);
Ok(())
}
/// Find a subset of contacts with some query
pub fn contacts_query(
&self,
user: UserAuth,
query: ContactQuery,
) -> QaulResult<Vec<UserProfile>> {
let (ref my_id, ref token) = user.trusted()?;
self.auth.verify_token(my_id, token)?;
self.contacts
.query(my_id, query)?
.into_iter()
.map(|ref id| self.users.get(id))
.collect()
/// Get a single `ContactEntry` from a user's contact book
///
/// Considering that a `ContactEntry` might contain large amounts
/// of data, this is the only way to return a reference to the
/// full object. When trying to query all data from all contact
/// entries, it's advised to get a list of Identities via
/// `Contacts::get_all` first, and then map this collection over
/// `Contacts::get` afterwards.
pub fn get(&self, user: UserAuth, contact: &Identity) -> Result<ContactEntry> {
let (ref id, _) = self.q.auth.trusted(user)?;
self.q.contacts.get(id, contact)
}
/// Enumerate all contacts known by a user
pub fn contacts_get_all(&self, user: UserAuth) -> QaulResult<Vec<UserProfile>> {
let (ref my_id, ref token) = user.trusted()?;
self.auth.verify_token(my_id, token)?;
/// Query for a subset of users that have a `ContactEntry`
///
/// To get a list of all `ContactEntry` objects, map the result of
/// this function over `Contacts::get`.
///
/// ```rust
/// # use libqaul::{Qaul, error::Result, contacts::ContactQuery};
/// # let qaul = Qaul::dummy();
/// # let user = qaul.users().create("abc").unwrap();
/// let contacts = qaul.contacts();
/// # (|| -> Result<()> {
/// contacts
/// .query(user.clone(), ContactQuery::Nick("buddy"))?
/// .into_iter()
/// .map(|i| contacts.get(user.clone(), &i));
/// # Ok(())
/// # })().unwrap();
/// ````
pub fn query(&self, user: UserAuth, query: ContactQuery) -> Result<Vec<Identity>> {
let (ref id, _) = self.q.auth.trusted(user)?;
self.q.contacts.query(id, query)
}
self.contacts
.get_all(my_id)?
.into_iter()
.map(|ref id| self.users.get(id))
.collect()
/// Get all users that have a `ContactEntry` for this user
pub fn all(&self, user: UserAuth) -> Result<Vec<Identity>> {
let (ref id, _) = self.q.auth.trusted(user)?;
Ok(vec![])
}
}
// impl Qaul {
// /// Find a subset of contacts with some query
// pub fn contacts_query(
// &self,
// user: UserAuth,
// query: ContactQuery,
// ) -> QaulResult<Vec<UserProfile>> {
// let (ref my_id, ref token) = user.trusted()?;
// self.auth.verify_token(my_id, token)?;
// self.contacts
// .query(my_id, query)?
// .into_iter()
// .map(|ref id| self.users.get(id))
// .collect()
// }
// /// Enumerate all contacts known by a user
// pub fn contacts_get_all(&self, user: UserAuth) -> QaulResult<Vec<UserProfile>> {
// let (ref my_id, ref token) = user.trusted()?;
// self.auth.verify_token(my_id, token)?;
// self.contacts
// .get_all(my_id)?
// .into_iter()
// .map(|ref id| self.users.get(id))
// .collect()
// }
// }
//! Service API keystore access
//!
//! Provide simple read-only access to a users own keystore. Keys are
//! gathered and managed automatically. This endpoint is primarily
//! meant for service UIs to indicate to users whether or not
//! encrypted communication with another user is possible.
use super::UserAuth;
use crate::{Qaul, QaulResult};
impl Qaul {
/// Check if another user's public key is available
pub fn keystore_key_exists(user: UserAuth, id: &str) -> QaulResult<bool> {
Ok(false)
}
}
//! Service API: message store
use super::UserAuth;
use crate::{api::Message, Qaul, QaulResult};
impl Qaul {
/// A temporary endpoint designed to get all messages from a user
///
/// After passing authentication, it always returns the same list
/// of messages
#[deprecated]
pub fn store_all(&self, user: UserAuth) -> QaulResult<Vec<Message>> {
let (ref id, ref token) = user.trusted()?;
self.auth.verify_token(id, token)?;
Ok(vec![])
}
}
//! Service API: peer-to-peer messages
use crate::error::{Error, Result};
use crate::messages::{Envelope, MsgUtils, RatMessageProto};
use crate::qaul::{Identity, Qaul};
use crate::users::UserAuth;
use crate::utils::VecUtils;
use super::models::{Message, QaulResult, Recipient, UserAuth};
use crate::Qaul;
use identity::Identity;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
/// A reference to an internally stored message object
pub type MsgRef = Arc<Message>;
/// A unique, randomly generated message ID
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct MsgId(pub(crate) [u8; 16]);
impl MsgId {
/// Generate a new **random** message ID
pub(crate) fn new() -> Self {
crate::utils::random(16)
.into_iter()
.zip(0..16)
.fold(Self([0; 16]), |mut acc, (x, i)| {
acc.0[i] = x;
acc
})
}
}
/// Signature trust level of an incoming `Message`
///
/// The three variants encode `trusted`, `unverified` and `invalid`,
/// according to signature verification of the internal keystore.
///
/// The `SigTrust::ok` convenience function can be used to reject
/// non-verifiable (unknown or bad) `Message` signatures.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum SigTrust {
/// A verified signature by a known contact
Trusted,
/// An unverified signature by a known contact
/// (pubkey not available!)
Unverified,
/// A fraudulent signature
Invalid,
}
impl SigTrust {
pub fn ok(&self) -> Result<()> {
match self {
Self::Trusted => Ok(()),
Self::Unverified => Err(Error::NoSign),
Self::Invalid => Err(Error::BadSign),
}
}
}
/// Service message recipient
///
/// A recipient is either a single user or the entire network. The
/// "flood" mechanic is passed through to `RATMAN`, which might
/// implement this in the networking module, or emulate
/// it. Performance may vary.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Recipient {
/// A single user, known to this node
User(Identity),
/// A collection of users, sometimes called a Group
Group(Vec<Identity>),
/// Addressed to nobody, flooded into the network
Flood,
}
/// A multi-purpose service Message
///
/// While this representation is quite "low level", i.e. forces a user
/// to deal with payload encoding themselves and provides no
/// functionality for async payloads (via filesharing, or similar), it
/// is quite a high level abstraction considering the data that needs
/// to be sent over the network in order for it to reach it's
/// recipient.
///
/// This type is both returned by `listen`, `poll`, as well as
/// specific message `queries`
///
#[derive(Clone)]
pub struct Message {
/// A unique message ID
pub id: MsgId,
/// The sender identity
pub sender: Identity,
/// Recipient information
pub recipient: Recipient,
/// The embedded service associator
pub associator: String,
/// Verified signature data
pub sign: SigTrust,
/// A raw byte `Message` payload
pub payload: Vec<u8>,
}
impl Message {
/// Construct a new `Recipient`, in reply to a Message
///
/// If the `Message` was addressed to a single user, the sender is
/// used. If it was addressed to a group, the sender is added, and
/// self is removed from the `Group` set. If it was a flood, then
/// the reply is a flood.
pub fn reply(&self, id: &Identity) -> Recipient {
let recipient = &self.recipient;
let sender = self.sender;
use Recipient::*;
match recipient {
Group(ref group) => Group(group.clone().strip(id).add(sender)),
User(_) => User(sender),
Flood => Flood,
}
}
}
/// A query interface for the local `Message` store
///
/// Important to consider that a `Query` can only be applied to the
/// set of messages that the user has access to. User access
/// information is not encoded in this enum, but rather passed to the
/// `Messages::query` function as a first parameter.
///
/// While `Query` objects can't be combined (yet), it is also possible
/// to pass an `Option<String>` as service filter, meaning that only
/// messages addressed to the appropriate service will be
/// returned. Without this parameter, all messages will be returned.
pub enum MessageQuery {
/// Query by who a `Message` was composed by
Sender(Identity),
/// Query a Message by who it is addressed to
Recipient(Recipient),
}
/// API scope type to access messaging functions
///
/// Used entirely to namespace API endpoints on `Qaul` instance,
/// without having long type identifiers.
///
/// ```norun
/// # use libqaul::{Qaul, Messages};
/// # let user = unimplemented!();
/// let q = Qaul::default();
/// q.messages().poll(user)?;
/// ```
///
/// It's also possible to `drop` the current scope, back into the
/// primary `Qaul` scope, although this is not often useful.
///
/// ```norun
/// # use libqaul::{Qaul, Messages};
/// # let q = Qaul::default();
/// q.messages().drop(); // Returns `&Qaul` again
/// ```
pub struct Messages<'chain> {
pub(crate) q: &'chain crate::Qaul,
}
impl<'qaul> Messages<'qaul> {
/// Drop this scope and return back to global `Qaul` scope
pub fn drop(&'qaul self) -> &'qaul Qaul {
self.q
}
impl Qaul {
/// Send a message into the network
///
/// Because the term `Message` is overloaded slightly in
......@@ -30,25 +192,93 @@ impl Qaul {
/// a payload or recipient is however, and payloads that are
/// unsecured in a Service API message will have been encrypted by
/// the time that `RATMAN` handles them.
pub fn message_send(
pub fn send<S>(
&self,
user: UserAuth,
recipient: Recipient,
associator: String,
service: S,
payload: Vec<u8>,
) -> QaulResult<()> {
unimplemented!()
) -> Result<()>
where
S: Into<String>,
{
let (sender, _) = self.q.auth.trusted(user)?;
let recipients = MsgUtils::readdress(&recipient);
let associator = service.into();
let env = Envelope {
id: MsgId::new(),
sender,
associator,
payload,
};
let signature = MsgUtils::sign(&env);
MsgUtils::send(
&self.q.router,
RatMessageProto {
env,
recipients,
signature,
},
)
}
/// Non-blockingly poll the API for the latest `Message` for a service
///
/// Two notes on the data returned from this endpoint. For a more
/// general `Message` query/ enumeration API, see
/// `Messages::query` instead.
///
/// 1. This will only receive new messages, since last checking
/// and can be used, while in active operation, to handle
/// incoming messages as they are received.
/// 2. The `Message` variant returned from this endpoint will
/// **always** be `Message::In`, never an outgoing type.
pub fn poll<S>(&self, user: UserAuth, service: S) -> Result<MsgRef>
where
S: Into<String>,
{
self.q.auth.trusted(user)?;
self.q.services.poll_for(service.into())
}
pub fn message_poll(&self, user: UserAuth) -> QaulResult<Vec<Message>> {
unimplemented!()
/// Register a listener on new-message events for a service
///
/// This function works very similarly to `Messages::poll`, except
/// that it uses a lambda to call when a new `Message` is
/// received. Both caveats mentioned in the doc comment for
/// `poll` apply here as well.
pub fn listen<S, F: 'static + Send + Sync>(
&self,
user: UserAuth,
service: S,
listener: F,
) -> Result<()>
where
S: Into<String>,
F: Fn(MsgRef) -> Result<()>,
{
self.q.auth.trusted(user)?;
self.q.services.add_listener(service.into(), listener)
}
pub fn message_listen<S, F>(&self, user: UserAuth, associator: S, listener: F) -> QaulResult<()>
/// Retrieve locally stored messages from the store
///
/// A query is made in relation to an associated service
/// handle. It isn't possible to query all messages for all
/// services in an efficient manner due to how messages are stored
/// in a node.
pub fn query<S>(&self, user: UserAuth, service: S, query: MessageQuery) -> Result<Vec<MsgRef>>
where
S: Into<String>,
F: Fn(Message) -> QaulResult<()>,
{
unimplemented!()
let (id, _) = self.q.auth.trusted(user)?;
self.q
.messages
.query(id)
.constraints(query)
.service(service)
.exec()
}
}
//! # `libqaul` service API
//! # Service API scope providers
//!
//! The idea behind this interface is further documented in the
//! `contribute` book. It goes into detail about using it to write
......@@ -9,23 +9,19 @@
//! "core" service. All of them are implemented via this API,
//! allowing external developers to write their own services using
//! qaul.net libraries and networks.
//!
//! ## Models
//!
//! Models defined in this submodule are different from any other
//! models defined in `libqaul`: they are the public representations,
//! i.e. only fields that are relevant for service developers to
//! interact with, not including shared service state or secrets.
// #[cfg(test)]
// mod tests;
// mod files;
pub use contacts::Contacts;
pub(crate) mod contacts;
pub use messages::Messages;
pub(crate) mod messages;
mod contacts;
mod files;
mod message_store;
mod messages;
mod models;
mod service;
mod users;
pub(crate) mod services;
pub use services::Services;
pub use models::*;
pub use users::Users;
pub(crate) mod users;
//! Service API exchange models
use std::fmt::{self, Debug, Formatter};
use identity::Identity;
use ratman::Identity;
use mime::Mime;
/// Convenience type for API functions
pub type QaulResult<T> = Result<T, QaulError>;
/// Service API error wrapper
#[derive(Debug, Clone, PartialEq)]
pub enum QaulError {
/// Not authorised to perform this action
NotAuthorised,
/// The desired user was not known
UnknownUser,
/// Invalid search query
InvalidQuery,
/// Invalid payload (probably too big)
InvalidPayload,
/// A function callback timed out
CallbackTimeout,
}
// /// A security token to authenticate sessions
// #[derive(Clone, PartialEq, Eq)]
// pub struct Token(String);
// impl Debug for Token {
// fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
// write!(f, "<TOKEN>")
// }
// }
// impl From<String> for Token {
// fn from(s: String) -> Self {
// assert!(s.len() == 64);
// Token(s)
// }
// }
pub type Token = String;
/// A wrapper around user authentication state
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum UserAuth {
/// A user ID which has not been verified
Untrusted(Identity),
/// The user ID of the currently logged-in user
Trusted(Identity, Token),
}
impl UserAuth {
/// Returns an error if the UserAuth isn't Trusted.
pub fn trusted(self) -> QaulResult<(Identity, Token)> {
match self {
UserAuth::Trusted(id, s) => Ok((id, s)),
UserAuth::Untrusted(_) => Err(QaulError::NotAuthorised),
}
}
/// Returns the interior identity, regardless of trust status.
pub fn identity(self) -> Identity {
match self {
UserAuth::Trusted(id, _) => id,
UserAuth::Untrusted(id) => id,
}
}
/// Returns the interior identity as an `Untrusted`, regardless of trust status.
pub fn as_untrusted(&self) -> Self {
UserAuth::Untrusted(self.clone().identity())
}
}
/// Signature trust information embedded into service messages
pub enum SigTrust {
/// A verified signature by a known contact
Trusted(Identity),
/// An unverified signature by a known contact
/// (pubkey not available!)
Unverified(Identity),
/// A fraudulent signature
Invalid,
}
/// A service message
///
/// Differs from the `RATMAN` abstraction for messages
/// because it's signature has already been verified.
/// Instead of delivering the raw signature to a service,
/// this message only embeds validity information.
///
/// This makes it easier for service authors to trust
/// data provided by `libqaul`, without having to do
/// calls into some crypto library themselves.
///
/// In comparison to the `RATMAN` message, the `associator`
/// has also been removed because at this stage, only the
/// relevant related service is being handed a message anyway.
pub struct Message {
pub sender: Identity,
pub recipient: Recipient,
pub payload: Vec<u8>,
pub signature: SigTrust,
}
/// Service message recipient
///
/// A recipient is either a single user or the entire network. The
/// "flood" mechanic is passed through to `RATMAN`, which might
/// implement this in the networking module, or emulate
/// it. Performance may vary.
pub enum Recipient {
/// A single user, known to this node
User(Identity),
/// A collection of users, sometimes called a Group
Group(Vec<Identity>),
/// Addressed to nobody, flooded into the network
Flood,
}
/// Local file abstraction
pub struct File {
pub name: String,
......
//! Service interconnect interface
use super::models::Message;
use std::error::Error;
/// The interface through which communication with a service occurs
pub trait ServiceConnect<E: Error> {
/// The ID of the service this ServiceConnect is connected to
fn service_id(&self) -> String;
/// Send a Message to the service. Calls to this method must not block.
fn send_msg(&mut self) -> Result<(), E>;
/// Check for Messages received from the service. Calls to this message may block,
/// but not if the last call to poll_messages() returned true (if ServiceConnectAsync
/// is implemented for this type).
fn messages(&mut self) -> Result<Vec<Message>, E>;
}
/// Add-on interface for async communication with a service
pub trait ServiceConnectAsync<E: Error>: ServiceConnect<E> {
/// Check whether or not the next call to messages() will block.
fn poll_messages(&mut self) -> Result<bool, E>;
}
//! Service interconnect interface
use crate::{error::Result, messages::Message, Qaul};
/// API scope type to access service management functions
///
/// Used entirely to namespace API endpoints on `Qaul` instance,
/// without having long type identifiers.
pub struct Services<'chain> {
pub(crate) q: &'chain Qaul,
}
impl<'qaul